Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber

This commit is contained in:
Sridhar Ramesh 2018-03-06 12:30:36 -08:00
commit 10d6739058
17 changed files with 9568 additions and 1956 deletions

View file

@ -24,7 +24,7 @@ pip install -r requirements.txt
```
Note: pip will install the python module `aggdraw` from
https://github.com/scottopell/aggdraw-64bits/ and it might requires additional
https://github.com/scottopell/aggdraw-64bits/ and it might have additional
dependencies.
This doesn't install freetype, but I don't think it's required for this project

File diff suppressed because it is too large Load diff

View file

@ -358,7 +358,6 @@ class Succession(Animation):
Each arg will either be an animation, or an animation class
followed by its arguments (and potentially a dict for
configuration).
For example,
Succession(
ShowCreation(circle),
@ -539,4 +538,3 @@ class EmptyAnimation(Animation):
def __init__(self, *args, **kwargs):
return Animation.__init__(self, Group(), *args, **kwargs)

View file

@ -101,9 +101,15 @@ class Swap(CyclicReplace):
pass #Renaming, more understandable for two entries
class GrowFromPoint(Transform):
CONFIG = {
"point_color" : None,
}
def __init__(self, mobject, point, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
point_mob = Point(point)
if self.point_color:
point_mob.highlight(self.point_color)
mobject.replace(point_mob)
mobject.highlight(point_mob.get_color())
Transform.__init__(self, mobject, target, **kwargs)

View file

@ -18,7 +18,6 @@ from camera import Camera
HELP_MESSAGE = """
Usage:
python extract_scene.py <module> [<scene name>]
-p preview in low quality
-s show and save picture of last frame
-w write result to file [this is default if nothing else is stated]
@ -35,7 +34,6 @@ SCENE_NOT_FOUND_MESSAGE = """
CHOOSE_NUMBER_MESSAGE = """
Choose number corresponding to desired scene/arguments.
(Use comma separated list for multiple entries)
Choice(s): """
INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit"
@ -262,4 +260,4 @@ def main():
if __name__ == "__main__":
main()
main()

View file

@ -389,6 +389,9 @@ class Mobject(Container):
def stretch_to_fit_height(self, height, **kwargs):
return self.rescale_to_fit(height, 1, stretch = True, **kwargs)
def stretch_to_fit_depth(self, depth, **kwargs):
return self.rescale_to_fit(depth, 1, stretch = True, **kwargs)
def scale_to_fit_width(self, width, **kwargs):
return self.rescale_to_fit(width, 0, stretch = False, **kwargs)

View file

@ -37,9 +37,9 @@ class SVGMobject(VMobject):
if self.file_name is None:
raise Exception("Must specify file for SVGMobject")
possible_paths = [
self.file_name,
os.path.join(SVG_IMAGE_DIR, self.file_name),
os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"),
self.file_name,
]
for path in possible_paths:
if os.path.exists(path):

View file

@ -247,7 +247,6 @@ class VMobject(Mobject):
a single "path", in the svg sense of the word.
However, one such path may really consist of separate
continuous components if there is a move_to command.
These other portions of the path will be treated as submobjects,
but will be tracked in a separate special list for when
it comes time to display.
@ -290,7 +289,6 @@ class VMobject(Mobject):
If the distance between a given handle point H and its associated
anchor point A is d, then it changes H to be a distances factor*d
away from A, but so that the line from A to H doesn't change.
This is mostly useful in the context of applying a (differentiable)
function, to preserve tangency properties. One would pull all the
handles closer to their anchors, apply the function then push them out
@ -484,4 +482,3 @@ class VectorizedPoint(VMobject):
def set_location(self,new_loc):
self.set_points(np.array([new_loc]))

4536
old_projects/basel/basel.py Normal file

File diff suppressed because it is too large Load diff

4651
old_projects/basel/basel2.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1842,7 +1842,7 @@ class IntroduceDopplerRadar(Scene):
words = ["Original signal", "Echo"]
for graph, word in zip([pulse_graph, echo_graph], words):
arrow = Vector(DOWN)
arrow.next_to(graph.peak_point, UP, MED_SMALL_BUFF)
arrow.next_to(graph.peak_point, UP, SMALL_BUFF)
arrow.match_color(graph)
graph.arrow = arrow
label = TextMobject(word)
@ -2400,7 +2400,6 @@ class RadarOperatorUncertainty(Scene):
vector_gdw.move_to(plane_gdw)
vector_gdw.shift(2*RIGHT)
self.add(randy, dish, bubble, plane_cloud, pulse)
self.play(randy.change, "confused")
self.wait(3)

View file

@ -366,7 +366,6 @@ class Scene(Container):
Each arg can either be an animation, or a mobject method
followed by that methods arguments (and potentially follow
by a dict of kwargs for that method).
This animation list is built by going through the args list,
and each animation is simply added, but when a mobject method
s hit, a MoveToTarget animation is built using the args that
@ -619,4 +618,3 @@ class EndSceneEarlyException(Exception):

View file

@ -123,8 +123,11 @@ class PatreonEndScreen(PatreonThanks):
"n_patron_columns" : 3,
"max_patron_width" : 3,
"run_time" : 20,
"randomize_order" : True,
}
def construct(self):
if self.randomize_order:
random.shuffle(self.specific_patrons)
self.add_title()
self.scroll_through_patrons()
@ -141,7 +144,6 @@ class PatreonEndScreen(PatreonThanks):
pi.next_to(title, vect, buff = MED_LARGE_BUFF)
self.add_foreground_mobjects(title, randy, morty)
def scroll_through_patrons(self):
logo_box = Square(side_length = 2.5)
logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF)
@ -176,7 +178,7 @@ class PatreonEndScreen(PatreonThanks):
aligned_edge = UP,
)
columns.scale_to_fit_width(total_width - 1)
columns.next_to(black_rect, DOWN, LARGE_BUFF)
columns.next_to(black_rect, DOWN, 3*LARGE_BUFF)
columns.to_edge(RIGHT)
self.play(

View file

@ -96,6 +96,57 @@ class Arc(VMobject):
return self
class ArcBetweenPoints(Arc):
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
if angle == 0:
raise Exception("Arc with zero curve angle: use Line instead.")
midpoint = 0.5 * (start_point + end_point)
distance_vector = end_point - start_point
normal_vector = np.array([-distance_vector[1], distance_vector[0],0])
distance = np.linalg.norm(normal_vector)
normal_vector /= distance
if angle < 0:
normal_vector *= -1
radius = distance/2 / np.sin(0.5 * np.abs(angle))
l = distance/2 / np.tan(0.5 * np.abs(angle))
arc_center = midpoint + l * normal_vector
w = start_point - arc_center
if w[0] != 0:
start_angle = np.arctan2(w[1],w[0])
else:
start_angle = np.pi/2
Arc.__init__(self, angle,
radius = radius,
start_angle = start_angle,
**kwargs)
self.move_arc_center_to(arc_center)
class CurvedArrow(ArcBetweenPoints):
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
# I know this is in reverse, but it works
if angle >= 0:
ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs)
self.add_tip(at_start = True, at_end = False)
else:
ArcBetweenPoints.__init__(self, end_point, start_point, angle = -angle, **kwargs)
self.add_tip(at_start = False, at_end = True)
class CurvedDoubleArrow(ArcBetweenPoints):
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs)
self.add_tip(at_start = True, at_end = True)
class Circle(Arc):
CONFIG = {
"color" : RED,

View file

@ -18,6 +18,7 @@ from mobject.svg_mobject import *
from topics.three_dimensions import *
from scipy.spatial import ConvexHull
from traceback import *
LIGHT_COLOR = YELLOW
@ -28,13 +29,12 @@ NUM_LEVELS = 30
NUM_CONES = 7 # in first lighthouse scene
NUM_VISIBLE_CONES = 5 # ibidem
ARC_TIP_LENGTH = 0.2
AMBIENT_FULL = 0.5
AMBIENT_DIMMED = 0.2
SPOTLIGHT_FULL = 0.9
SPOTLIGHT_DIMMED = 0.2
AMBIENT_FULL = 0.8
AMBIENT_DIMMED = 0.5
SPOTLIGHT_FULL = 0.8
SPOTLIGHT_DIMMED = 0.5
LIGHTHOUSE_HEIGHT = 0.8
LIGHT_COLOR = YELLOW
DEGREES = TAU/360
inverse_power_law = lambda maxint,scale,cutoff,exponent: \
@ -42,222 +42,6 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)
# Note: Overall, this class seems perfectly reasonable to me, the main
# thing to be wary of is that calling self.add(submob) puts that submob
# at the end of the submobjects list, and hence on top of everything else
# which is why the shadow might sometimes end up behind the spotlight
class LightSource(VMobject):
# combines:
# a lighthouse
# an ambient light
# a spotlight
# and a shadow
CONFIG = {
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
"color": LIGHT_COLOR,
"num_levels": 10,
"radius": 5,
"screen": None,
"opacity_function": inverse_quadratic(1,2,1),
"max_opacity_ambient": AMBIENT_FULL,
"max_opacity_spotlight": SPOTLIGHT_FULL,
"camera": None
}
def generate_points(self):
self.add(self.source_point)
self.lighthouse = Lighthouse()
self.ambient_light = AmbientLight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_ambient
)
if self.has_screen():
self.spotlight = Spotlight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
screen = self.screen,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_spotlight,
camera = self.camera
)
else:
self.spotlight = Spotlight()
self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK)
self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0)
self.ambient_light.move_source_to(self.get_source_point())
if self.has_screen():
self.spotlight.move_source_to(self.get_source_point())
self.update_shadow()
self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow)
def has_screen(self):
return (self.screen != None)
def dim_ambient(self):
self.set_max_opacity_ambient(AMBIENT_DIMMED)
def set_max_opacity_ambient(self,new_opacity):
self.max_opacity_ambient = new_opacity
self.ambient_light.dimming(new_opacity)
def dim_spotlight(self):
self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED)
def set_max_opacity_spotlight(self,new_opacity):
self.max_opacity_spotlight = new_opacity
self.spotlight.dimming(new_opacity)
def set_camera(self,new_cam):
self.camera = new_cam
self.spotlight.camera = new_cam
def set_screen(self, new_screen):
if self.has_screen():
self.spotlight.screen = new_screen
else:
# Note: See below
index = self.submobjects.index(self.spotlight)
camera = self.spotlight.camera
self.remove(self.spotlight)
self.spotlight = Spotlight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
screen = new_screen,
camera = self.camera
)
self.spotlight.move_source_to(self.get_source_point())
# Note: This line will make spotlight show up at the end
# of the submojects list, which can make it show up on
# top of the shadow. To make it show up in the
# same spot, you could try the following line,
# where "index" is what I defined above:
self.submobjects.insert(index, self.spotlight)
#self.add(self.spotlight)
# in any case
self.screen = new_screen
def move_source_to(self,point):
apoint = np.array(point)
v = apoint - self.get_source_point()
# Note: As discussed, things stand to behave better if source
# point is a submobject, so that it automatically interpolates
# during an animation, and other updates can be defined wrt
# that source point's location
self.source_point.set_location(apoint)
#self.lighthouse.next_to(apoint,DOWN,buff = 0)
#self.ambient_light.move_source_to(apoint)
self.lighthouse.shift(v)
#self.ambient_light.shift(v)
self.ambient_light.move_source_to(apoint)
if self.has_screen():
self.spotlight.move_source_to(apoint)
self.update()
return self
def set_radius(self,new_radius):
self.radius = new_radius
self.ambient_light.radius = new_radius
self.spotlight.radius = new_radius
def update(self):
self.spotlight.update_sectors()
self.update_shadow()
def get_source_point(self):
return self.source_point.get_location()
def update_shadow(self):
point = self.get_source_point()
projected_screen_points = []
if not self.has_screen():
return
for point in self.screen.get_anchors():
projected_screen_points.append(self.spotlight.project(point))
projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction())
projected_point_cloud_3d = np.append(
projected_screen_points,
np.reshape(projected_source,(1,3)),
axis = 0
)
rotation_matrix = z_to_vector(self.spotlight.projection_direction())
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T)
# these points now should all have z = 0
point_cloud_2d = rotated_point_cloud_3d[:,:2]
# now we can compute the convex hull
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
hull = []
# we also need the projected source point
source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2]
index = 0
for point in point_cloud_2d[hull_2d.vertices]:
if np.all(point - source_point_2d < 1.0e-6):
source_index = index
continue
point_3d = np.array([point[0], point[1], 0])
hull.append(point_3d)
index += 1
index = source_index
hull_mobject = VMobject()
hull_mobject.set_points_as_corners(hull)
hull_mobject.apply_matrix(rotation_matrix)
anchors = hull_mobject.get_anchors()
# add two control points for the outer cone
ray1 = anchors[index - 1] - projected_source
ray1 = ray1/np.linalg.norm(ray1) * 100
ray2 = anchors[index] - projected_source
ray2 = ray2/np.linalg.norm(ray2) * 100
outpoint1 = anchors[index - 1] + ray1
outpoint2 = anchors[index] + ray2
new_anchors = anchors[:index]
new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0)
new_anchors = np.append(new_anchors,anchors[index:],axis = 0)
self.shadow.set_points_as_corners(new_anchors)
# Note: Theoretically this should not be necessary as long as we make
# sure the shadow shows up after the spotlight in the submobjects list.
#
# shift it closer to the camera so it is in front of the spotlight
self.shadow.shift(1e-5*self.spotlight.projection_direction())
self.shadow.mark_paths_closed = True
class SwitchOn(LaggedStart):
CONFIG = {
"lag_ratio": 0.2,
@ -267,9 +51,9 @@ class SwitchOn(LaggedStart):
def __init__(self, light, **kwargs):
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
raise Exception("Only AmbientLights and Spotlights can be switched on")
LaggedStart.__init__(self,
FadeIn, light, **kwargs)
LaggedStart.__init__(
self, FadeIn, light, **kwargs
)
class SwitchOff(LaggedStart):
CONFIG = {
@ -285,19 +69,17 @@ class SwitchOff(LaggedStart):
FadeOut, light, **kwargs)
light.submobjects = light.submobjects[::-1]
class Lighthouse(SVGMobject):
CONFIG = {
"file_name" : "lighthouse",
"height" : LIGHTHOUSE_HEIGHT
"height" : LIGHTHOUSE_HEIGHT,
"fill_color" : WHITE,
"fill_opacity" : 1.0,
}
def move_to(self,point):
self.next_to(point, DOWN, buff = 0)
class AmbientLight(VMobject):
# Parameters are:
@ -313,7 +95,7 @@ class AmbientLight(VMobject):
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
"color" : LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"num_levels" : NUM_LEVELS,
"radius" : 5.0
}
@ -373,31 +155,16 @@ class AmbientLight(VMobject):
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
submob.set_fill(opacity = new_submob_alpha)
class Spotlight(VMobject):
CONFIG = {
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
"opacity_function" : lambda r : 1.0/(r/2+1.0)**2,
"color" : LIGHT_COLOR,
"color" : GREEN, # LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"radius" : 5.0,
"radius" : 10.0,
"screen" : None,
"camera": None
"camera_mob": None
}
def projection_direction(self):
@ -405,24 +172,22 @@ class Spotlight(VMobject):
# need to be sure that any 3d scene including a spotlight
# somewhere assigns that spotlights "camera" attribute
# to be the camera associated with that scene.
if self.camera == None:
if self.camera_mob == None:
return OUT
else:
v = self.camera.get_cartesian_coords()
return v/np.linalg.norm(v)
[phi, theta, r] = self.camera_mob.get_center()
v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)])
return v #/np.linalg.norm(v)
def project(self,point):
v = self.projection_direction()
w = project_along_vector(point,v)
return w
def get_source_point(self):
return self.source_point.get_location()
def generate_points(self):
self.submobjects = []
self.add(self.source_point)
@ -438,13 +203,7 @@ class Spotlight(VMobject):
new_sector = self.new_sector(r,dr,lower_angle,upper_angle)
self.add(new_sector)
def new_sector(self,r,dr,lower_angle,upper_angle):
# Note: I'm not looking _too_ closely at the implementation
# of these updates based on viewing angles and such. It seems to
# behave as intended, but let me know if you'd like more thorough
# scrutiny
alpha = self.max_opacity * self.opacity_function(r)
annular_sector = AnnularSector(
inner_radius = r,
@ -480,7 +239,6 @@ class Spotlight(VMobject):
else:
return -absolute_angle
def viewing_angles(self,screen):
screen_points = screen.get_anchors()
@ -494,6 +252,8 @@ class Spotlight(VMobject):
lower_angle = np.min(viewing_angles)
upper_angle = np.max(viewing_angles)
if upper_angle - lower_angle > TAU/2:
lower_angle, upper_angle = upper_angle, lower_angle + TAU
return lower_angle, upper_angle
def viewing_rays(self,screen):
@ -505,7 +265,6 @@ class Spotlight(VMobject):
return lower_ray, upper_ray
def opening_angle(self):
l,u = self.viewing_angles(self.screen)
return u - l
@ -525,21 +284,20 @@ class Spotlight(VMobject):
self.update_sectors()
return self
def update_sectors(self):
if self.screen == None:
return
for submob in self.submobject_family():
for submob in self.submobjects:
if type(submob) == AnnularSector:
lower_angle, upper_angle = self.viewing_angles(self.screen)
#dr = submob.outer_radius - submob.inner_radius
dr = self.radius / self.num_levels
new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle)
submob.points = new_submob.points
submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
# print "new opacity:", self.opacity_function(submob.outer_radius)
new_submob = self.new_sector(
submob.inner_radius, dr, lower_angle, upper_angle
)
# submob.points = new_submob.points
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
Transform(submob, new_submob).update(1)
def dimming(self,new_alpha):
old_alpha = self.max_opacity
@ -570,18 +328,290 @@ class Spotlight(VMobject):
alpha = self.opacity_function(r)
submob.set_fill(opacity = alpha)
# Warning: This class is likely quite buggy.
class LightSource(VMobject):
# combines:
# a lighthouse
# an ambient light
# a spotlight
# and a shadow
CONFIG = {
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
"color": LIGHT_COLOR,
"num_levels": 10,
"radius": 10.0,
"screen": None,
"opacity_function": inverse_quadratic(1,2,1),
"max_opacity_ambient": AMBIENT_FULL,
"max_opacity_spotlight": SPOTLIGHT_FULL,
"camera_mob": None
}
# Note: Stylistically, I typically keep all of the implementation for an
# update inside the relevant Animation or ContinualAnimation, rather than
# in the mobject. Things are fine the way you've done it, but sometimes
# I personally like a nice conceptual divide between all the things that
# determine how the mobject is displayed in a single moment (implement in
# the mobject's class itself) and all the things determining how that changes.
#
# Up to you though.
def generate_points(self):
self.add(self.source_point)
self.lighthouse = Lighthouse()
self.ambient_light = AmbientLight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_ambient
)
if self.has_screen():
self.spotlight = Spotlight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
screen = self.screen,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_spotlight,
camera_mob = self.camera_mob
)
else:
self.spotlight = Spotlight()
self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK)
self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0)
self.ambient_light.move_source_to(self.get_source_point())
if self.has_screen():
self.spotlight.move_source_to(self.get_source_point())
self.update_shadow()
self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow)
def has_screen(self):
if self.screen == None:
return False
elif np.size(self.screen.points) == 0:
return False
else:
return True
def dim_ambient(self):
self.set_max_opacity_ambient(AMBIENT_DIMMED)
def set_max_opacity_ambient(self,new_opacity):
self.max_opacity_ambient = new_opacity
self.ambient_light.dimming(new_opacity)
def dim_spotlight(self):
self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED)
def set_max_opacity_spotlight(self,new_opacity):
self.max_opacity_spotlight = new_opacity
self.spotlight.dimming(new_opacity)
def set_camera_mob(self,new_cam_mob):
self.camera_mob = new_cam_mob
self.spotlight.camera_mob = new_cam_mob
def set_screen(self, new_screen):
if self.has_screen():
self.spotlight.screen = new_screen
else:
# Note: See below
index = self.submobjects.index(self.spotlight)
camera_mob = self.spotlight.camera_mob
self.remove(self.spotlight)
self.spotlight = Spotlight(
source_point = VectorizedPoint(location = self.get_source_point()),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
screen = new_screen,
camera_mob = self.camera_mob,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_spotlight,
)
self.spotlight.move_source_to(self.get_source_point())
# Note: This line will make spotlight show up at the end
# of the submojects list, which can make it show up on
# top of the shadow. To make it show up in the
# same spot, you could try the following line,
# where "index" is what I defined above:
self.submobjects.insert(index, self.spotlight)
#self.add(self.spotlight)
# in any case
self.screen = new_screen
def move_source_to(self,point):
apoint = np.array(point)
v = apoint - self.get_source_point()
# Note: As discussed, things stand to behave better if source
# point is a submobject, so that it automatically interpolates
# during an animation, and other updates can be defined wrt
# that source point's location
self.source_point.set_location(apoint)
#self.lighthouse.next_to(apoint,DOWN,buff = 0)
#self.ambient_light.move_source_to(apoint)
self.lighthouse.shift(v)
#self.ambient_light.shift(v)
self.ambient_light.move_source_to(apoint)
if self.has_screen():
self.spotlight.move_source_to(apoint)
self.update()
return self
def change_spotlight_opacity_function(self, new_of):
self.spotlight.change_opacity_function(new_of)
def set_radius(self,new_radius):
self.radius = new_radius
self.ambient_light.radius = new_radius
self.spotlight.radius = new_radius
def update(self):
self.update_lighthouse()
self.update_ambient()
self.spotlight.update_sectors()
self.update_shadow()
def update_lighthouse(self):
self.lighthouse.move_to(self.get_source_point())
# new_lh = Lighthouse()
# new_lh.move_to(ORIGIN)
# new_lh.apply_matrix(self.rotation_matrix())
# new_lh.shift(self.get_source_point())
# self.lighthouse.submobjects = new_lh.submobjects
def update_ambient(self):
new_ambient_light = AmbientLight(
source_point = VectorizedPoint(location = ORIGIN),
color = self.color,
num_levels = self.num_levels,
radius = self.radius,
opacity_function = self.opacity_function,
max_opacity = self.max_opacity_ambient
)
new_ambient_light.apply_matrix(self.rotation_matrix())
new_ambient_light.move_source_to(self.get_source_point())
self.ambient_light.submobjects = new_ambient_light.submobjects
def get_source_point(self):
return self.source_point.get_location()
def rotation_matrix(self):
if self.camera_mob == None:
return np.eye(3)
phi = self.camera_mob.get_center()[0]
theta = self.camera_mob.get_center()[1]
R1 = np.array([
[1, 0, 0],
[0, np.cos(phi), -np.sin(phi)],
[0, np.sin(phi), np.cos(phi)]
])
R2 = np.array([
[np.cos(theta + TAU/4), -np.sin(theta + TAU/4), 0],
[np.sin(theta + TAU/4), np.cos(theta + TAU/4), 0],
[0, 0, 1]
])
R = np.dot(R2, R1)
return R
def update_shadow(self):
point = self.get_source_point()
projected_screen_points = []
if not self.has_screen():
return
for point in self.screen.get_anchors():
projected_screen_points.append(self.spotlight.project(point))
projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction())
projected_point_cloud_3d = np.append(
projected_screen_points,
np.reshape(projected_source,(1,3)),
axis = 0
)
rotation_matrix = self.rotation_matrix() # z_to_vector(self.spotlight.projection_direction())
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T)
# these points now should all have z = 0
point_cloud_2d = rotated_point_cloud_3d[:,:2]
# now we can compute the convex hull
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
hull = []
# we also need the projected source point
source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2]
index = 0
for point in point_cloud_2d[hull_2d.vertices]:
if np.all(np.abs(point - source_point_2d) < 1.0e-6):
source_index = index
index += 1
continue
point_3d = np.array([point[0], point[1], 0])
hull.append(point_3d)
index += 1
hull_mobject = VMobject()
hull_mobject.set_points_as_corners(hull)
hull_mobject.apply_matrix(rotation_matrix)
anchors = hull_mobject.get_anchors()
# add two control points for the outer cone
if np.size(anchors) == 0:
self.shadow.points = []
return
ray1 = anchors[source_index - 1] - projected_source
ray1 = ray1/np.linalg.norm(ray1) * 100
ray2 = anchors[source_index] - projected_source
ray2 = ray2/np.linalg.norm(ray2) * 100
outpoint1 = anchors[source_index - 1] + ray1
outpoint2 = anchors[source_index] + ray2
new_anchors = anchors[:source_index]
new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0)
new_anchors = np.append(new_anchors,anchors[source_index:],axis = 0)
self.shadow.set_points_as_corners(new_anchors)
# shift it closer to the camera so it is in front of the spotlight
self.shadow.mark_paths_closed = True
class ScreenTracker(ContinualAnimation):
def __init__(self, light_source, **kwargs):
self.light_source = light_source
dummy_mob = Mobject()
ContinualAnimation.__init__(self, dummy_mob, **kwargs)
def update_mobject(self, dt):
self.mobject.update()
self.light_source.update()

View file

@ -120,6 +120,9 @@ class NumberLine(VMobject):
result.add(mob)
return result
def get_labels(self):
return self.get_number_mobjects()
def add_numbers(self, *numbers, **kwargs):
self.numbers = self.get_number_mobjects(
*numbers, **kwargs
@ -241,7 +244,7 @@ class Axes(VGroup):
elif lx > x and rx < x:
lh, rh = rh, lh
return points[1]
return self.coords_to_point(x, graph.underlying_function(x))
class ThreeDAxes(Axes):
CONFIG = {

View file

@ -144,7 +144,6 @@ class ContinualChangingDecimal(ContinualAnimation):
def update_mobject(self, dt):
self.anim.update(self.internal_time)