Beginning EoC Chapter 2

This commit is contained in:
Grant Sanderson 2016-12-26 07:10:38 -08:00
parent 66dbe8aaae
commit 982ddb4c14
12 changed files with 599 additions and 89 deletions

1
.gitignore vendored
View file

@ -3,5 +3,6 @@
homeless.py
ka_playgrounds/
playground.py
special_animations.py
prettiness_hall_of_fame.py
files/

View file

@ -11,24 +11,30 @@ from animation import Animation
class Rotating(Animation):
CONFIG = {
"axes" : [RIGHT, UP],
"axis" : None,
"axes" : [],
"axis" : OUT,
"radians" : 2*np.pi,
"run_time" : 20.0,
"run_time" : 5,
"rate_func" : None,
"in_place" : True,
"about_point" : None,
}
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.points = np.array(starting_submobject.points)
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
axes = [self.axis] if self.axis is not None else self.axes
if self.in_place:
method = self.mobject.rotate_in_place
else:
method = self.mobject.rotate
method(alpha*self.radians, axes = axes)
axes = self.axes if self.axes else [self.axis]
about_point = None
if self.about_point is not None:
about_point = self.about_point
elif self.in_place: #This is superseeded
self.about_point = self.mobject.get_center()
self.mobject.rotate(
alpha*self.radians,
axes = axes,
about_point = self.about_point
)
class ShowPartial(Animation):
@ -267,8 +273,14 @@ class Succession(Animation):
last_anim = self.anims[self.last_index]
last_anim.clean_up()
if last_anim.mobject is curr_anim.mobject:
self.anims[index].starting_mobject = curr_anim.mobject.copy()
self.anims[index].update_mobject(scaled_alpha - index)
#TODO, is there a way to do this that doesn't
#require leveraging implementation details of
#Animations, and knowing about the different
#struction of Transform?
if hasattr(curr_anim, "ending_mobject"):
curr_anim.mobject.align_data(curr_anim.ending_mobject)
curr_anim.starting_mobject = curr_anim.mobject.copy()
curr_anim.update(scaled_alpha - index)
self.last_index = index

View file

@ -253,20 +253,23 @@ class OpeningQuote(Scene):
self.play(Write(author, run_time = 3))
self.dither()
def get_quote(self):
def get_quote(self, max_width = 2*SPACE_WIDTH-1):
if isinstance(self.quote, str):
quote = TextMobject(
"``%s''"%self.quote.strip(),
alignment = "",
)
else:
words = list(self.quote)
words[0] = "``%s"%words[0]
words[-1] += "''"
words = ["``"] + list(self.quote) + ["''"]
quote = TextMobject(*words, alignment = "")
##TODO, make less hacky
quote[0].shift(0.2*RIGHT)
quote[-1].shift(0.2*LEFT)
for term, color in self.highlighted_quote_terms.items():
quote.highlight_by_tex(term, color)
quote.to_edge(UP)
if quote.get_width() > max_width:
quote.scale_to_fit_width(max_width)
return quote
def get_author(self, quote):

425
eoc/chapter2.py Normal file
View file

@ -0,0 +1,425 @@
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from eoc.chapter1 import OpeningQuote
from eoc.graph_scene import *
class Car(SVGMobject):
CONFIG = {
"file_name" : "Car",
"height" : 1,
"color" : GREY,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
self.set_stroke(color = WHITE, width = 0)
self.set_fill(self.color, opacity = 1)
randy = Randolph(mode = "happy")
randy.scale_to_fit_height(0.6*self.get_height())
randy.stretch(0.8, 0)
randy.look(RIGHT)
randy.move_to(self)
randy.shift(0.07*self.height*(RIGHT+UP))
self.add_to_back(randy)
orientation_line = Line(self.get_left(), self.get_right())
orientation_line.set_stroke(width = 0)
self.add(orientation_line)
self.orientation_line = orientation_line
self.add_treds_to_tires()
def move_to(self, point_or_mobject):
vect = rotate_vector(
UP+LEFT, self.orientation_line.get_angle()
)
self.next_to(point_or_mobject, vect, buff = 0)
return self
def get_front_line(self):
return DashedLine(
self.get_corner(UP+RIGHT),
self.get_corner(DOWN+RIGHT),
color = YELLOW,
dashed_segment_length = 0.05,
)
def add_treds_to_tires(self):
for tire in self.get_tires():
radius = tire.get_width()/2
center = tire.get_center()
tred = Line(
0.9*radius*RIGHT, 1.4*radius*RIGHT,
stroke_width = 2,
color = BLACK
)
tred.rotate_in_place(np.pi/4)
for theta in np.arange(0, 2*np.pi, np.pi/4):
new_tred = tred.copy()
new_tred.rotate(theta)
new_tred.shift(center)
tire.add(new_tred)
return self
def get_tires(self):
return self[1][1], self[1][3]
class MoveCar(ApplyMethod):
def __init__(self, car, target_point, **kwargs):
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
displacement = self.ending_mobject.get_right()-self.starting_mobject.get_right()
distance = np.linalg.norm(displacement)
tire_radius = car.get_tires()[0].get_width()/2
self.total_tire_radians = -distance/tire_radius
def update_mobject(self, alpha):
ApplyMethod.update_mobject(self, alpha)
if alpha == 0:
return
radians = alpha*self.total_tire_radians
for tire in self.mobject.get_tires():
tire.rotate_in_place(radians)
class IncrementNumber(Succession):
CONFIG = {
"start_num" : 0,
"changes_per_second" : 1,
"run_time" : 11,
}
def __init__(self, num_mob, **kwargs):
digest_config(self, kwargs)
n_iterations = int(self.run_time * self.changes_per_second)
new_num_mobs = [
TexMobject(str(num)).move_to(num_mob, LEFT)
for num in range(self.start_num, self.start_num+n_iterations)
]
transforms = [
Transform(
num_mob, new_num_mob,
run_time = 1.0/self.changes_per_second,
rate_func = squish_rate_func(smooth, 0, 0.5)
)
for new_num_mob in new_num_mobs
]
Succession.__init__(
self, *transforms, **{
"rate_func" : None,
"run_time" : self.run_time,
}
)
class IncrementTest(Scene):
def construct(self):
num = TexMobject("0")
num.shift(UP)
self.play(IncrementNumber(num))
self.dither()
############################
class Chapter2OpeningQuote(OpeningQuote):
CONFIG = {
"quote" : [
"So far as the theories of mathematics are about",
"reality,",
"they are not",
"certain;",
"so far as they are",
"certain,",
"they are not about",
"reality.",
],
"highlighted_quote_terms" : {
"reality," : BLUE,
"certain;" : GREEN,
"certain," : GREEN,
"reality." : BLUE,
},
"author" : "Albert Einstein"
}
class Introduction(TeacherStudentsScene):
def construct(self):
self.student_says(
"What is a derivative?"
)
self.play(self.get_teacher().change_mode, "happy")
self.dither()
self.teacher_says(
"It's actually a \\\\",
"very subtle idea",
target_mode = "well"
)
self.change_student_modes(None, "pondering", "thinking")
self.dither()
self.change_student_modes("erm")
self.student_says(
"Doesn't the derivative measure\\\\",
"instantaneous rate of change", "?",
student_index = 0,
)
self.dither()
bubble = self.get_students()[0].bubble
phrase = bubble.content[1]
bubble.content.remove(phrase)
self.play(
phrase.center,
phrase.scale, 1.5,
phrase.to_edge, UP,
FadeOut(bubble),
FadeOut(bubble.content),
*it.chain(*[
[
pi.change_mode, mode,
pi.look_at, SPACE_HEIGHT*UP
]
for pi, mode in zip(self.get_everyone(), [
"speaking", "pondering", "confused", "confused",
])
])
)
self.dither()
change = VGroup(*phrase[-len("change"):])
instantaneous = VGroup(*phrase[:len("instantaneous")])
change_brace = Brace(change)
change_description = change_brace.get_text(
"Requires multiple \\\\ points in time"
)
instantaneous_brace = Brace(instantaneous)
instantaneous_description = instantaneous_brace.get_text(
"One point \\\\ in time"
)
clock = Clock()
clock.next_to(change_description, DOWN)
def get_clock_anim(run_time = 3):
return ClockPassesTime(
clock,
hours_passed = 0.4*run_time,
run_time = run_time,
)
self.play(FadeIn(clock))
self.play(
change.gradient_highlight, BLUE, YELLOW,
GrowFromCenter(change_brace),
Write(change_description),
get_clock_anim()
)
self.play(get_clock_anim(1))
stopped_clock = clock.copy()
stopped_clock.next_to(instantaneous_description, DOWN)
self.play(
instantaneous.highlight, BLUE,
GrowFromCenter(instantaneous_brace),
Transform(change_description.copy(), instantaneous_description),
clock.copy().next_to, instantaneous_description, DOWN,
get_clock_anim(3)
)
self.play(get_clock_anim(6))
class FathersOfCalculus(Scene):
CONFIG = {
"names" : [
"Barrow",
"Newton",
"Leibniz",
"Cauchy",
"Weierstrass",
],
"picture_height" : 2.5,
}
def construct(self):
title = TextMobject("(A few) Fathers of Calculus")
title.to_edge(UP)
self.add(title)
men = Mobject()
for name in self.names:
image = ImageMobject(name, invert = False)
image.scale_to_fit_height(self.picture_height)
title = TextMobject(name)
title.scale(0.8)
title.next_to(image, DOWN)
image.add(title)
men.add(image)
men.arrange_submobjects(RIGHT, aligned_edge = UP)
men.shift(DOWN)
discover_brace = Brace(Mobject(*men[:3]), UP)
discover = discover_brace.get_text("Discovered it")
VGroup(discover_brace, discover).highlight(BLUE)
rigor_brace = Brace(Mobject(*men[3:]), UP)
rigor = rigor_brace.get_text("Made it rigorous")
rigor.shift(0.1*DOWN)
VGroup(rigor_brace, rigor).highlight(YELLOW)
for man in men:
self.play(FadeIn(man))
self.play(
GrowFromCenter(discover_brace),
Write(discover, run_time = 1)
)
self.play(
GrowFromCenter(rigor_brace),
Write(rigor, run_time = 1)
)
self.dither()
class IntroduceCar(Scene):
def construct(self):
point_A = DOWN+4*LEFT
point_B = DOWN+5*RIGHT
A = Dot(point_A)
B = Dot(point_B)
line = Line(point_A, point_B)
VGroup(A, B, line).highlight(WHITE)
for dot, tex in (A, "A"), (B, "B"):
label = TexMobject(tex).next_to(dot, DOWN)
dot.add(label)
car = Car()
car.move_to(point_A)
front_line = car.get_front_line()
time_label = TextMobject("Time (in seconds):", "0")
time_label.shift(2*UP)
distance_brace = Brace(line, UP)
# distance_brace.set_fill(opacity = 0.5)
distance = distance_brace.get_text("100m")
self.add(A, B, line, car, time_label)
self.play(ShowCreation(front_line))
self.play(FadeOut(front_line))
self.play(
MoveCar(car, point_B, run_time = 10),
IncrementNumber(time_label[1], run_time = 11)
)
front_line = car.get_front_line()
self.play(ShowCreation(front_line))
self.play(FadeOut(front_line))
self.play(
GrowFromCenter(distance_brace),
Write(distance)
)
self.dither()
self.play(
car.move_to, point_A,
FadeOut(time_label),
FadeOut(distance_brace),
FadeOut(distance)
)
graph_scene = GraphCarTrajectory(skip_animations = True)
origin = graph_scene.graph_origin
top = graph_scene.coords_to_point(0, 100)
new_length = np.linalg.norm(top-origin)
new_point_B = point_A + new_length*RIGHT
group = VGroup(car, A, B, line)
for mob in group:
mob.generate_target()
group.target = VGroup(*[m.target for m in group])
B.target.shift(new_point_B - point_B)
line.target.put_start_and_end_on(
point_A, new_point_B
)
group.target.rotate(np.pi/2, about_point = point_A)
group.target.shift(graph_scene.graph_origin - point_A)
self.play(MoveToTarget(group))
self.dither()
class GraphCarTrajectory(GraphScene):
CONFIG = {
"x_min" : 0,
"x_axis_label" : "Time (seconds)",
"y_min" : 0,
"y_max" : 110,
"y_tick_frequency" : 10,
"y_labeled_nums" : range(10, 110, 10),
"y_axis_label" : "Distance traveled \\\\ (meters)",
"graph_origin" : 2.5*DOWN + 5*LEFT,
}
def construct(self):
self.setup_axes(animate = False)
graph = self.graph_function(
lambda t : 100*smooth(t/10.),
)
self.remove(graph)
car = Car()
car.rotate(np.pi/2)
car.move_to(self.coords_to_point(0, 0))
self.add(car)

View file

@ -2,7 +2,7 @@ from helpers import *
from scene import Scene
# from topics.geometry import
from mobject.tex_mobject import TexMobject
from mobject.tex_mobject import TexMobject, TextMobject
from mobject.vectorized_mobject import VGroup, VectorizedPoint
from animation.simple_animations import Write, ShowCreation
from topics.number_line import NumberLine
@ -15,14 +15,14 @@ class GraphScene(Scene):
"x_max" : 10,
"x_axis_width" : 9,
"x_tick_frequency" : 1,
"x_leftmost_tick" : -1,
"x_leftmost_tick" : None, #Change if different from x_min
"x_labeled_nums" : range(1, 10),
"x_axis_label" : "x",
"y_min" : -1,
"y_max" : 10,
"y_axis_height" : 6,
"y_tick_frequency" : 1,
"y_bottom_tick" : -1,
"y_bottom_tick" : None, #Change if different from y_min
"y_labeled_nums" : range(1, 10),
"y_axis_label" : "y",
"axes_color" : GREY,
@ -36,15 +36,16 @@ class GraphScene(Scene):
x_max = self.x_max,
space_unit_to_num = self.x_axis_width/x_num_range,
tick_frequency = self.x_tick_frequency,
leftmost_tick = self.x_leftmost_tick,
leftmost_tick = self.x_leftmost_tick or self.x_min,
numbers_with_elongated_ticks = self.x_labeled_nums,
color = self.axes_color
)
x_axis.shift(self.graph_origin - x_axis.number_to_point(0))
if self.x_labeled_nums:
x_axis.add_numbers(*self.x_labeled_nums)
x_label = TexMobject(self.x_axis_label)
x_label = TextMobject(self.x_axis_label)
x_label.next_to(x_axis, RIGHT+UP, buff = SMALL_BUFF)
x_label.shift_onto_screen()
x_axis.add(x_label)
self.x_axis_label_mob = x_label
@ -54,7 +55,7 @@ class GraphScene(Scene):
x_max = self.y_max,
space_unit_to_num = self.y_axis_height/y_num_range,
tick_frequency = self.y_tick_frequency,
leftmost_tick = self.y_bottom_tick,
leftmost_tick = self.y_bottom_tick or self.y_min,
numbers_with_elongated_ticks = self.y_labeled_nums,
color = self.axes_color
)
@ -63,8 +64,9 @@ class GraphScene(Scene):
if self.y_labeled_nums:
y_axis.add_numbers(*self.y_labeled_nums)
y_axis.numbers.shift(self.y_axis_numbers_nudge)
y_label = TexMobject(self.y_axis_label)
y_label = TextMobject(self.y_axis_label)
y_label.next_to(y_axis.get_top(), RIGHT, buff = 2*MED_BUFF)
y_label.shift_onto_screen()
y_axis.add(y_label)
self.y_axis_label_mob = y_label
@ -82,7 +84,7 @@ class GraphScene(Scene):
def graph_function(self, func,
color = BLUE,
animate = True,
animate = False,
is_main_graph = True,
):

View file

@ -403,7 +403,8 @@ class Mobject(object):
def reduce_across_dimension(self, points_func, reduce_func, dim):
try:
values = [points_func(self.points[:, dim])]
points = self.get_points_defining_boundary()
values = [points_func(points[:, dim])]
except:
values = []
values += [
@ -426,6 +427,9 @@ class Mobject(object):
### Getters ###
def get_points_defining_boundary(self):
return self.points
def get_num_points(self):
return len(self.points)
@ -517,7 +521,7 @@ class Mobject(object):
def submobject_family(self):
sub_families = map(Mobject.submobject_family, self.submobjects)
all_mobjects = [self] + reduce(op.add, sub_families, [])
all_mobjects = [self] + list(it.chain(*sub_families))
return remove_list_redundancies(all_mobjects)
def family_members_with_points(self):

View file

@ -204,7 +204,8 @@ class VMobject(Mobject):
it comes time to display.
"""
subpath_mobject = self.copy()#TODO, better way?
# subpath_mobject = VMobject()
subpath_mobject.submobjects = []
# subpath_mobject = self.__class__()
subpath_mobject.is_subpath = True
subpath_mobject.set_points(points)
self.add(subpath_mobject)
@ -253,6 +254,13 @@ class VMobject(Mobject):
for i in range(3)
]
def get_anchors(self):
return self.points[::3]
def get_points_defining_boundary(self):
return self.get_anchors()
## Alignment
def align_points(self, mobject):

View file

@ -17,67 +17,13 @@ from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from scene import Scene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
class Clock(VMobject):
CONFIG = {
"propogate_style_to_family" : True,
}
def __init__(self, **kwargs):
circle = Circle()
ticks = []
for x in range(12):
alpha = x/12.
point = complex_to_R3(
np.exp(2*np.pi*alpha*complex(0, 1))
)
length = 0.2 if x%3 == 0 else 0.1
ticks.append(
Line(point, (1-length)*point)
)
self.hour_hand = Line(ORIGIN, 0.3*UP)
self.minute_hand = Line(ORIGIN, 0.6*UP)
for hand in self.hour_hand, self.minute_hand:
#Balance out where the center is
hand.add(VectorizedPoint(-hand.get_end()))
VMobject.__init__(
self, circle,
self.hour_hand, self.minute_hand,
*ticks
)
class ClockPassesTime(Animation):
CONFIG = {
"run_time" : 5,
"rate_func" : None,
}
def __init__(self, clock, **kwargs):
assert(isinstance(clock, Clock))
rot_kwargs = {
"axis" : OUT,
"in_place" : True
}
self.hour_rotation = Rotating(
clock.hour_hand,
radians = -2*np.pi,
**rot_kwargs
)
self.minute_rotation = Rotating(
clock.minute_hand,
radians = -12*2*np.pi,
**rot_kwargs
)
Animation.__init__(self, clock, **kwargs)
def update_mobject(self, alpha):
for rotation in self.hour_rotation, self.minute_rotation:
rotation.update_mobject(alpha)
class SideGigToFullTime(Scene):
def construct(self):
morty = Mortimer()

View file

@ -1357,7 +1357,8 @@ class FromRealToComplex(ComplexTransformationScene):
VGroup(*lines[1::2]).highlight(RED)
final_dot = Dot(
self.z_to_point(power_sums[-1]),
# self.z_to_point(power_sums[-1]),
self.z_to_point(zeta(exponent)),
color = self.output_color
)
@ -3448,7 +3449,7 @@ class Thumbnail(ZetaTransformationScene):
self.apply_zeta_function()
self.plane.set_stroke(width = 4)
div_sum = TexMobject("1+2+3+4+\\cdots = -", "\\frac{1}{12}")
div_sum = TexMobject("-\\frac{1}{12} = ", "1+2+3+4+\\cdots")
div_sum.scale_to_fit_width(2*SPACE_WIDTH-1)
div_sum.to_edge(DOWN)
div_sum.highlight(YELLOW)
@ -3460,7 +3461,7 @@ class Thumbnail(ZetaTransformationScene):
zeta.to_corner(UP+LEFT)
million = TexMobject("\\$1{,}000{,}000")
million.scale_to_fit_width(SPACE_WIDTH)
million.scale_to_fit_width(SPACE_WIDTH+1)
million.to_edge(UP+RIGHT)
million.highlight(GREEN_B)
million.add_background_rectangle()
@ -3468,9 +3469,51 @@ class Thumbnail(ZetaTransformationScene):
self.add(div_sum, million, zeta)
class ZetaPartialSums(ZetaTransformationScene):
CONFIG = {
"anchor_density" : 35,
"num_partial_sums" : 12,
}
def construct(self):
self.add_transformable_plane()
self.add_extra_plane_lines_for_zeta()
self.prepare_for_transformation(self.plane)
N_list = [2**k for k in range(self.num_partial_sums)]
sigma = TexMobject(
"\\sum_{n = 1}^N \\frac{1}{n^s}"
)
sigmas = []
for N in N_list + ["\\infty"]:
tex = TexMobject(str(N))
tex.highlight(YELLOW)
new_sigma = sigma.copy()
top = new_sigma[0]
tex.move_to(top, DOWN)
new_sigma.remove(top)
new_sigma.add(tex)
new_sigma.to_corner(UP+LEFT)
sigmas.append(new_sigma)
def get_partial_sum_func(n_terms):
return lambda s : sum([1./(n**s) for n in range(1, n_terms+1)])
interim_planes = [
self.plane.copy().apply_complex_function(
get_partial_sum_func(N)
)
for N in N_list
]
interim_planes.append(self.plane.copy().apply_complex_function(zeta))
symbol = VGroup(TexMobject("s"))
symbol.scale(2)
symbol.highlight(YELLOW)
symbol.to_corner(UP+LEFT)
for plane, sigma in zip(interim_planes, sigmas):
self.play(
Transform(self.plane, plane),
Transform(symbol, sigma)
)
self.dither()

View file

@ -155,7 +155,7 @@ class Scene(object):
if animation.run_time != max_run_time:
new_rate_func = squish_rate_func(
animation.get_rate_func(),
0, 1./max_run_time
0, float(animation.run_time)/max_run_time
)
animation.set_rate_func(new_rate_func)
animation.set_run_time(max_run_time)
@ -337,6 +337,7 @@ class Scene(object):
full_path = os.path.join(path, file_name)
if not os.path.exists(path):
os.makedirs(path)
self.update_frame()
Image.fromarray(self.get_frame()).save(full_path)
def get_movie_file_path(self, name, extension):

View file

@ -416,11 +416,15 @@ class TeacherStudentsScene(Scene):
for x in range(num_times):
pi_creature = random.choice(self.get_everyone())
self.play(Blink(pi_creature))
self.dither()
Scene.dither(self)
def dither(self, time = 1):
self.random_blink(num_times = max(time/2, 1))
def change_student_modes(self, *modes, **kwargs):
added_anims = kwargs.get("added_anims", [])
pairs = zip(self.get_students(), modes)
pairs = [(s, m) for s, m in pairs if m is not None]
start = VGroup(*[s for s, m in pairs])
target = VGroup(*[s.copy().change_mode(m) for s, m in pairs])
self.play(

View file

@ -5,6 +5,10 @@ from mobject.vectorized_mobject import VGroup
from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject
from animation import Animation
from animation.simple_animations import Rotating
from topics.geometry import Circle, Line
class VideoIcon(SVGMobject):
@ -50,6 +54,63 @@ class Headphones(SVGMobject):
self.set_stroke(width = 0)
self.set_fill(color = self.color)
class Clock(VGroup):
CONFIG = {
"propogate_style_to_family" : True,
}
def __init__(self, **kwargs):
circle = Circle()
ticks = []
for x in range(12):
alpha = x/12.
point = complex_to_R3(
np.exp(2*np.pi*alpha*complex(0, 1))
)
length = 0.2 if x%3 == 0 else 0.1
ticks.append(
Line(point, (1-length)*point)
)
self.hour_hand = Line(ORIGIN, 0.3*UP)
self.minute_hand = Line(ORIGIN, 0.6*UP)
# for hand in self.hour_hand, self.minute_hand:
# #Balance out where the center is
# hand.add(VectorizedPoint(-hand.get_end()))
VGroup.__init__(
self, circle,
self.hour_hand, self.minute_hand,
*ticks
)
class ClockPassesTime(Animation):
CONFIG = {
"run_time" : 5,
"hours_passed" : 12,
"rate_func" : None,
}
def __init__(self, clock, **kwargs):
digest_config(self, kwargs)
assert(isinstance(clock, Clock))
rot_kwargs = {
"axis" : OUT,
"about_point" : clock.get_center()
}
hour_radians = -self.hours_passed*2*np.pi/12
self.hour_rotation = Rotating(
clock.hour_hand,
radians = hour_radians,
**rot_kwargs
)
self.minute_rotation = Rotating(
clock.minute_hand,
radians = 12*hour_radians,
**rot_kwargs
)
Animation.__init__(self, clock, **kwargs)
def update_mobject(self, alpha):
for rotation in self.hour_rotation, self.minute_rotation:
rotation.update_mobject(alpha)
class Bubble(SVGMobject):
CONFIG = {