diff --git a/constants.py b/constants.py index 3257bfd3..66b7df09 100644 --- a/constants.py +++ b/constants.py @@ -33,7 +33,7 @@ MED_BUFF = 0.25 LARGE_BUFF = 1 DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_BUFF -DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = SMALL_BUFF +DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_BUFF #All in seconds diff --git a/helpers.py b/helpers.py index 1bef59b1..dd7fa057 100644 --- a/helpers.py +++ b/helpers.py @@ -42,6 +42,7 @@ def play_finish_sound(): play_chord(12, 9, 5, 2) def get_smooth_handle_points(points): + points = np.array(points) num_handles = len(points) - 1 dim = points.shape[1] if num_handles < 1: diff --git a/scene/scene.py b/scene/scene.py index f222363a..74008662 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -76,12 +76,12 @@ class Scene(object): ### def extract_mobject_family_members(self, *mobjects): - return remove_list_redundancies(list( + return list( it.chain(*[ m.submobject_family() for m in mobjects ]) - )) + ) def add(self, *mobjects_to_add): """ @@ -237,7 +237,6 @@ class Scene(object): animations = self.align_run_times(*animations, **kwargs) moving_mobjects, static_mobjects = \ self.separate_moving_and_static_mobjects(*animations) - self.update_frame(static_mobjects) static_image = self.get_frame() for t in self.get_time_progression(animations): diff --git a/topics/characters.py b/topics/characters.py index 3777ca3c..af8abc9a 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -51,11 +51,11 @@ class PiCreature(SVGMobject): def name_parts(self): self.mouth = self.submobjects[MOUTH_INDEX] self.body = self.submobjects[BODY_INDEX] - self.pupils = VMobject(*[ + self.pupils = VGroup(*[ self.submobjects[LEFT_PUPIL_INDEX], self.submobjects[RIGHT_PUPIL_INDEX] ]) - self.eyes = VMobject(*[ + self.eyes = VGroup(*[ self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX] ]) @@ -176,6 +176,28 @@ class Mathematician(PiCreature): "color" : GREY, } +class BabyPiCreature(PiCreature): + CONFIG = { + "scale_factor" : 0.5, + "eye_scale_factor" : 1.2, + "pupil_scale_factor" : 1.3 + } + def __init__(self, *args, **kwargs): + PiCreature.__init__(self, *args, **kwargs) + self.scale(self.scale_factor) + self.shift(LEFT) + self.to_edge(DOWN, buff = LARGE_BUFF) + eyes = VGroup(self.eyes, self.pupils) + eyes_bottom = eyes.get_bottom() + eyes.scale(self.eye_scale_factor) + eyes.move_to(eyes_bottom, aligned_edge = DOWN) + looking_direction = self.get_looking_direction() + for pupil in self.pupils: + pupil.scale_in_place(self.pupil_scale_factor) + self.look(looking_direction) + + + class Blink(ApplyMethod): CONFIG = { "rate_func" : squish_rate_func(there_and_back) diff --git a/topics/geometry.py b/topics/geometry.py index ab4589d4..255d6332 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -50,7 +50,7 @@ class Circle(Arc): class Dot(Circle): #Use 1D density, even though 2D CONFIG = { - "radius" : 0.05, + "radius" : 0.08, "stroke_width" : 0, "fill_opacity" : 1.0, "color" : WHITE diff --git a/wcat.py b/wcat.py new file mode 100644 index 00000000..4ec52515 --- /dev/null +++ b/wcat.py @@ -0,0 +1,537 @@ +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 scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + + +class ClosedLoopScene(Scene): + CONFIG = { + "loop_anchor_points" : [ + 3*RIGHT, + 2*RIGHT+UP, + 3*RIGHT + 3*UP, + UP, + 2*UP+LEFT, + 2*LEFT + 2*UP, + 3*LEFT, + 2*LEFT+DOWN, + 3*LEFT+2*DOWN, + 2*DOWN+RIGHT, + LEFT+DOWN, + ], + "square_vertices" : [ + 2*RIGHT+UP, + 2*UP+LEFT, + 2*LEFT+DOWN, + 2*DOWN+RIGHT + ], + "rect_vertices" : [ + 0*RIGHT + 1*UP, + -1*RIGHT + 2*UP, + -3*RIGHT + 0*UP, + -2*RIGHT + -1*UP, + ], + "dot_color" : YELLOW, + "connecting_lines_color" : BLUE, + } + def setup(self): + self.dots = VGroup() + self.connecting_lines = VGroup() + self.add_loop() + + def add_loop(self): + self.loop = self.get_default_loop() + self.add(self.loop) + + def get_default_loop(self): + loop = VMobject() + loop.set_points_smoothly( + self.loop_anchor_points + [self.loop_anchor_points[0]] + ) + return loop + + def get_square(self): + return Polygon(*self.square_vertices) + + def get_rect_vertex_dots(self, square = False): + if square: + vertices = self.square_vertices + else: + vertices = self.rect_vertices + dots = VGroup(*[Dot(v) for v in vertices]) + dots.highlight(self.dot_color) + return dots + + def add_dot(self, dot): + self.add_dots(dot) + + def add_dots(self, *dots): + self.dots.add(*dots) + self.add(self.dots) + + def add_rect_dots(self, square = False): + self.add_dots(*self.get_rect_vertex_dots()) + + def add_dots_at_alphas(self, *alphas): + self.add_dots(*[ + Dot( + self.loop.point_from_proportion(alpha), + color = self.dot_color + ) + for alpha in alphas + ]) + + def add_connecting_lines(self, cyclic = False): + if cyclic: + pairs = adjascent_pairs(self.dots) + else: + n_pairs = len(list(self.dots))/2 + pairs = zip(self.dots[:n_pairs], self.dots[n_pairs:]) + for d1, d2 in pairs: + line = Line( + d1.get_center(), d2.get_center(), + stroke_width = 6 + ) + line.start_dot = d1 + line.end_dot = d2 + line.update_anim = UpdateFromFunc( + line, + lambda l : l.put_start_and_end_on( + l.start_dot.get_center(), + l.end_dot.get_center() + ) + ) + self.connecting_lines.add(line) + self.connecting_lines.highlight(self.connecting_lines_color) + self.add(self.connecting_lines, self.dots) + + def get_line_anims(self): + return [ + line.update_anim + for line in self.connecting_lines + ] + [Animation(self.dots)] + + def get_dot_alphas(self, dots = None, precision = 0.005): + if dots == None: + dots = self.dots + alphas = [] + alpha_range = np.arange(0, 1, precision) + loop_points = np.array(map(self.loop.point_from_proportion, alpha_range)) + for dot in dots: + vects = loop_points - dot.get_center() + norms = np.apply_along_axis(np.linalg.norm, 1, vects) + index = np.argmin(norms) + alphas.append(alpha_range[index]) + return alphas + + def let_dots_wonder(self, run_time = 5, random_seed = None, added_anims = []): + if random_seed is not None: + np.random.seed(random_seed) + start_alphas = self.get_dot_alphas() + alpha_rates = 0.1 + 0.1*np.random.random(len(list(self.dots))) + def generate_rate_func(start, rate): + return lambda t : (start + t*rate*run_time)%1 + anims = [ + MoveAlongPath( + dot, + self.loop, + rate_func = generate_rate_func(start, rate) + ) + for dot, start, rate in zip(self.dots, start_alphas, alpha_rates) + ] + anims += self.get_line_anims() + anims += added_anims + self.play(*anims, run_time = run_time) + + def move_dots_to_alphas(self, alphas, run_time = 3): + assert(len(alphas) == len(list(self.dots))) + start_alphas = self.get_dot_alphas() + def generate_rate_func(start_alpha, alpha): + return lambda t : interpolate(start_alpha, alpha, smooth(t)) + anims = [ + MoveAlongPath( + dot, self.loop, + rate_func = generate_rate_func(sa, a), + run_time = run_time, + ) + for dot, sa, a in zip(self.dots, start_alphas, alphas) + ] + anims += self.get_line_anims() + self.play(*anims) + + def transform_loop(self, target_loop, **kwargs): + alphas = self.get_dot_alphas() + dot_anims = [] + for dot, alpha in zip(self.dots, alphas): + dot.generate_target() + dot.target.move_to(target_loop.point_from_proportion(alpha)) + dot_anims.append(MoveToTarget(dot)) + self.play( + Transform(self.loop, target_loop), + *dot_anims + self.get_line_anims(), + **kwargs + ) + self.remove(self.loop) + self.loop = target_loop + self.add(self.loop) + + def find_square(self): + alpha_quads = list(it.combinations( + np.arange(0, 1, 0.02) , 4 + )) + quads = np.array([ + [ + self.loop.point_from_proportion(alpha) + for alpha in quad + ] + for quad in alpha_quads + ]) + scores = self.square_scores(quads) + index = np.argmin(scores) + return quads[index] + + def square_scores(self, all_quads): + midpoint_diffs = np.apply_along_axis( + np.linalg.norm, 1, + 0.5*(all_quads[:,0] + all_quads[:,2]) - 0.5*(all_quads[:,1] + all_quads[:,3]) + ) + vects1 = all_quads[:,0] - all_quads[:,2] + vects2 = all_quads[:,1] - all_quads[:,3] + distances1 = np.apply_along_axis(np.linalg.norm, 1, vects1) + distances2 = np.apply_along_axis(np.linalg.norm, 1, vects2) + distance_diffs = np.abs(distances1 - distances2) + midpoint_diffs /= distances1 + distance_diffs /= distances2 + + buffed_d1s = np.repeat(distances1, 3).reshape(vects1.shape) + buffed_d2s = np.repeat(distances2, 3).reshape(vects2.shape) + unit_v1s = vects1/buffed_d1s + unit_v2s = vects2/buffed_d2s + dots = np.abs(unit_v1s[:,0]*unit_v2s[:,0] + unit_v1s[:,1]*unit_v2s[:,1] + unit_v1s[:,2]*unit_v2s[:,2]) + + return midpoint_diffs + distance_diffs + dots + + + +############################# + +class Introduction(TeacherStudentsScene): + def construct(self): + self.play(self.get_teacher().change_mode, "hooray") + self.random_blink() + self.teacher_says("") + for pi in self.get_students(): + pi.generate_target() + pi.target.change_mode("happy") + pi.target.look_at(self.get_teacher().bubble) + self.play(*map(MoveToTarget, self.get_students())) + self.random_blink(3) + self.teacher_says( + "Here's why \\\\ I'm excited...", + pi_creature_target_mode = "hooray" + ) + for pi in self.get_students(): + pi.target.look_at(self.get_teacher().eyes) + self.play(*map(MoveToTarget, self.get_students())) + self.dither() + +class WhenIWasAKid(TeacherStudentsScene): + def construct(self): + children = self.get_children() + speaker = self.get_speaker() + + self.prepare_everyone(children, speaker) + self.transition_from_previous_scene(children, speaker) + self.students = children + self.teacher = speaker + self.run_class() + self.grow_up() + + def transition_from_previous_scene(self, children, speaker): + self.play(self.get_teacher().change_mode, "hooray", run_time = 0) + self.change_student_modes(*["happy"]*3) + + speaker.look_at(children) + me = children[-1] + self.play( + FadeOut(self.get_students()), + Transform(self.get_teacher(), me) + ) + self.remove(self.get_teacher()) + self.add(me) + self.play(*map(FadeIn, children[:-1] + [speaker])) + self.random_blink() + + def run_class(self): + children = self.students + speaker = self.teacher + title = TextMobject("Topology") + title.to_edge(UP) + + self.random_blink() + self.play(self.teacher.change_mode, "speaking") + self.play(Write(title)) + self.random_blink() + pi1, pi2, pi3, me = children + self.play(pi1.change_mode, "raise_right_hand") + self.random_blink() + self.play( + pi2.change_mode, "confused", + pi3.change_mode, "happy", + pi2.look_at, pi3.eyes, + pi3.look_at, pi2.eyes, + ) + self.random_blink() + self.play(me.change_mode, "pondering") + self.dither() + self.random_blink(2) + self.play(pi1.change_mode, "raise_left_hand") + self.dither() + self.play(pi2.change_mode, "erm") + self.random_blink() + self.student_says( + "How is this math?", + student_index = -1, + pi_creature_target_mode = "pleading", + width = 5, + height = 3, + direction = RIGHT + ) + self.play( + pi1.change_mode, "pondering", + pi2.change_mode, "pondering", + pi3.change_mode, "pondering", + ) + self.play(speaker.change_mode, "pondering") + self.random_blink() + + def grow_up(self): + me = self.students[-1] + self.students.remove(me) + morty = Mortimer(mode = "pondering") + morty.flip() + morty.move_to(me, aligned_edge = DOWN) + morty.to_edge(LEFT) + morty.look(RIGHT) + + self.play( + Transform(me, morty), + *map(FadeOut, [ + self.students, self.teacher, + me.bubble, me.bubble.content + ]) + ) + self.remove(me) + self.add(morty) + self.play(Blink(morty)) + self.dither() + self.play(morty.change_mode, "hooray") + self.dither() + + + def prepare_everyone(self, children, speaker): + self.everyone = list(children) + [speaker] + for pi in self.everyone: + pi.bubble = None + + def get_children(self): + colors = [MAROON_E, YELLOW_D, PINK, GREY_BROWN] + children = VGroup(*[ + BabyPiCreature(color = color) + for color in colors + ]) + children.arrange_submobjects(RIGHT) + children.to_edge(DOWN, buff = LARGE_BUFF) + children.to_edge(LEFT) + return children + + def get_speaker(self): + speaker = Mathematician(mode = "happy") + speaker.flip() + speaker.to_edge(DOWN, buff = LARGE_BUFF) + speaker.to_edge(RIGHT) + return speaker + + def get_everyone(self): + if hasattr(self, "everyone"): + return self.everyone + else: + return TeacherStudentsScene.get_everyone(self) + +class FormingTheMobiusStrip(Scene): + def construct(self): + pass + +class DrawLineOnMobiusStrip(Scene): + def construct(self): + pass + +class MugIntoTorus(Scene): + def construct(self): + pass + +class DefineInscribedSquareProblem(ClosedLoopScene): + def construct(self): + self.draw_loop() + self.cycle_through_shapes() + self.ask_about_rectangles() + + def draw_loop(self): + self.title = TextMobject("Inscribed", "square", "problem") + self.title.to_edge(UP) + + #Draw loop + self.remove(self.loop) + self.play(Write(self.title)) + self.dither() + self.play(ShowCreation( + self.loop, + run_time = 3, + rate_func = None + )) + self.dither() + self.add_rect_dots(square = True) + self.play(ShowCreation(self.dots, run_time = 2)) + self.dither() + self.add_connecting_lines(cyclic = True) + self.play( + ShowCreation( + self.connecting_lines, + submobject_mode = "all_at_once", + run_time = 2 + ), + Animation(self.dots) + ) + self.dither(2) + + def cycle_through_shapes(self): + circle = Circle(radius = 2.5, color = WHITE) + ellipse = circle.copy() + ellipse.stretch(1.5, 0) + ellipse.stretch(0.7, 1) + ellipse.rotate(-np.pi/2) + ellipse.scale_to_fit_height(4) + pi_loop = TexMobject("\\pi")[0] + pi_loop.set_fill(opacity = 0) + pi_loop.set_stroke( + color = WHITE, + width = DEFAULT_POINT_THICKNESS + ) + pi_loop.scale_to_fit_height(4) + randy = Randolph() + randy.look(DOWN) + randy.scale_to_fit_width(pi_loop.get_width()) + randy.move_to(pi_loop, aligned_edge = DOWN) + randy.body.set_fill(opacity = 0) + randy.mouth.set_stroke(width = 0) + + self.transform_loop(circle) + self.dither() + odd_eigths = np.linspace(1./8, 7./8, 4) + self.move_dots_to_alphas(odd_eigths) + self.dither() + for nudge in 0.1, -0.1, 0: + self.move_dots_to_alphas(odd_eigths+nudge) + self.dither() + self.transform_loop(ellipse) + self.dither() + nudge = 0.055 + self.move_dots_to_alphas( + odd_eigths + [nudge, -nudge, nudge, -nudge] + ) + self.dither(2) + self.transform_loop(pi_loop) + self.let_dots_wonder() + randy_anims = [ + FadeIn(randy), + Animation(randy), + Blink(randy), + Animation(randy), + Blink(randy, rate_func = smooth) + ] + for anim in randy_anims: + self.let_dots_wonder( + run_time = 1, + random_seed = 0, + added_anims = [anim] + ) + self.remove(randy) + self.transform_loop(self.get_default_loop()) + + def ask_about_rectangles(self): + morty = Mortimer() + morty.next_to(ORIGIN, DOWN) + morty.to_edge(RIGHT) + + new_title = TextMobject("Inscribed", "rectangle", "problem") + new_title.highlight_by_tex("rectangle", YELLOW) + new_title.to_edge(UP) + rect_dots = self.get_rect_vertex_dots() + rect_alphas = self.get_dot_alphas(rect_dots) + + self.play(FadeIn(morty)) + self.play(morty.change_mode, "speaking") + self.play(Transform(self.title, new_title)) + self.move_dots_to_alphas(rect_alphas) + self.dither() + self.play(morty.change_mode, "hooray") + self.play(Blink(morty)) + self.dither() + self.play(FadeOut(self.connecting_lines)) + self.connecting_lines = VGroup() + self.play(morty.change_mode, "plain") + + dot_pairs = [ + VGroup(self.dots[i], self.dots[j]) + for i, j in (0, 2), (1, 3) + ] + pair_colors = MAROON_B, PURPLE_C + diag_lines = [ + Line(d1.get_center(), d2.get_center(), color = c) + for (d1, d2), c in zip(dot_pairs, pair_colors) + ] + + for pair, line in zip(dot_pairs, diag_lines): + self.play( + FadeIn(line), + pair.highlight, line.get_color(), + ) + + + + + + + + + + + + + + + + + + + +