From 6fe057c0f9c7829dd22d5dab08ca6a72611eb1a1 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 2 Aug 2016 12:26:15 -0700 Subject: [PATCH] First page of chapter 5 --- eola/chapter5.py | 355 ++++++++++++++++++++++++++++++++++ eola/two_d_space.py | 52 ++++- extract_scene.py | 3 +- mobject/mobject.py | 4 +- mobject/vectorized_mobject.py | 4 +- scene/scene.py | 10 +- topics/characters.py | 7 +- topics/geometry.py | 4 + 8 files changed, 419 insertions(+), 20 deletions(-) create mode 100644 eola/chapter5.py diff --git a/eola/chapter5.py b/eola/chapter5.py new file mode 100644 index 00000000..803f92c2 --- /dev/null +++ b/eola/chapter5.py @@ -0,0 +1,355 @@ +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import VMobject + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.numerals import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from mobject.vectorized_mobject import * + +from eola.matrix import * +from eola.two_d_space import * +from eola.chapter3 import MatrixVectorMultiplicationAbstract + +class Blob(Circle): + CONFIG = { + "stroke_color" : TEAL, + "fill_color" : BLUE_E, + "fill_opacity" : 1, + "random_seed" : 1, + "random_nudge_size" : 0.5, + "height" : 2, + } + def __init__(self, **kwargs): + Circle.__init__(self, **kwargs) + random.seed(self.random_seed) + self.apply_complex_function( + lambda z : z*(1+self.random_nudge_size*(random.random()-0.5)) + ) + self.scale_to_fit_height(self.height).center() + + def probably_contains(self, point): + border_points = np.array(self.get_anchors_and_handles()[0]) + distances = map(lambda p : np.linalg.norm(p-point), border_points) + min3 = border_points[np.argsort(distances)[:3]] + center_direction = self.get_center() - point + in_center_direction = map( + lambda p : np.dot(p-point, center_direction) > 0, + min3 + ) + return sum(in_center_direction) <= 2 + + +class OpeningQuote(Scene): + def construct(self): + words = TextMobject([ + "``The purpose of computation is \\\\", + "insight", + ", not ", + "numbers.", + "''", + ], separate_list_arg_with_spaces = False) + # words.scale_to_fit_width(2*SPACE_WIDTH - 2) + words.to_edge(UP) + words.split()[1].highlight(BLUE) + words.split()[3].highlight(GREEN) + author = TextMobject("-Richard Hamming") + author.highlight(YELLOW) + author.next_to(words, DOWN, buff = 0.5) + + self.play(FadeIn(words)) + self.dither(2) + self.play(Write(author, run_time = 3)) + self.dither() + +class MovingForward(TeacherStudentsScene): + def construct(self): + self.setup() + student = self.get_students()[1] + bubble = student.get_bubble(direction = RIGHT, width = 5) + bubble.rotate(-np.pi/12) + bubble.next_to(student, UP, aligned_edge = RIGHT) + bubble.shift(0.5*LEFT) + bubble.make_green_screen() + + self.teacher_says(""" + Y'all know about linear + transformations, right? + """, width = 7) + self.play( + ShowCreation(bubble), + student.change_mode, "pondering" + ) + self.dither(2) + +class StretchingTransformation(LinearTransformationScene): + def construct(self): + self.setup() + self.add_title("Generally stretches space") + self.apply_transposed_matrix([[3, 1], [-1, 2]]) + self.dither() + +class SquishingTransformation(LinearTransformationScene): + CONFIG = { + "foreground_plane_kwargs" : { + "x_radius" : 3*SPACE_WIDTH, + "y_radius" : 3*SPACE_WIDTH, + "secondary_line_ratio" : 0 + }, + } + def construct(self): + self.setup() + self.add_title("Generally squishes space") + self.apply_transposed_matrix([[1./2, -0.5], [1, 1./3]]) + self.dither() + +class AskAboutStretching(LinearTransformationScene): + def construct(self): + self.setup() + words = TextMobject(""" + Exactly how much are + things being stretched? + """) + words.add_background_rectangle() + words.to_corner(UP+RIGHT) + words.highlight(YELLOW) + self.apply_transposed_matrix( + [[2, 1], [-1, 3]], + added_anims = [Write(words)] + ) + self.dither() + +class AskAboutStretchingSpecifically(LinearTransformationScene): + def construct(self): + self.setup() + self.add_title(["How much are", "areas", "scaled?"]) + hma, areas, scaled = self.title.split()[1].split() + areas.highlight(YELLOW) + blob = Blob().shift(UP+RIGHT) + + label = TextMobject("Area") + label.highlight(YELLOW) + label = VMobject(VectorizedPoint(label.get_left()), label) + label.move_to(blob) + target_label = TexMobject(["c \\cdot", "\\text{Area}"]) + target_label.split()[1].highlight(YELLOW) + + self.add_transformable_mobject(blob) + self.add_moving_mobject(label, target_label) + self.dither() + self.apply_transposed_matrix([[2, -1], [1, 1]]) + arrow = Arrow(scaled, label.target.split()[0]) + self.play(ShowCreation(arrow)) + self.dither() + +class BeautyNowUsesLater(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says("Beauty now, uses later") + self.dither() + +class DiagonalExample(LinearTransformationScene): + CONFIG = { + "show_square" : False, + "show_coordinates" : True, + "transposed_matrix" : [[3, 0], [0, 2]] + } + def construct(self): + self.setup() + matrix = Matrix(np.array(self.transposed_matrix).transpose()) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrix.next_to(ORIGIN, LEFT).to_edge(UP) + matrix_background = BackgroundRectangle(matrix) + self.play(ShowCreation(matrix_background), Write(matrix)) + if self.show_square: + self.add_unit_square(animate = True) + self.add_foreground_mobject(matrix_background, matrix) + self.dither() + self.apply_transposed_matrix([self.transposed_matrix[0], [0, 1]]) + self.apply_transposed_matrix([[1, 0], self.transposed_matrix[1]]) + self.dither() + if self.show_square: + + + bottom_brace = Brace(self.i_hat, DOWN) + right_brace = Brace(self.square, RIGHT) + width = TexMobject(str(self.transposed_matrix[0][0])) + height = TexMobject(str(self.transposed_matrix[1][1])) + width.next_to(bottom_brace, DOWN) + height.next_to(right_brace, RIGHT) + for mob in bottom_brace, width, right_brace, height: + mob.add_background_rectangle() + self.play(Write(mob, run_time = 0.5)) + self.dither() + + width_target, height_target = width.copy(), height.copy() + det = np.linalg.det(self.transposed_matrix) + times, eq_det = map(TexMobject, ["\\times", "=%d"%det]) + words = TextMobject("New area $=$") + equation = VMobject( + words, width_target, times, height_target, eq_det + ) + equation.arrange_submobjects(RIGHT, buff = 0.2) + equation.next_to(self.square, UP, aligned_edge = LEFT) + equation.shift(0.5*RIGHT) + background_rect = BackgroundRectangle(equation) + + self.play( + ShowCreation(background_rect), + Transform(width.copy(), width_target), + Transform(height.copy(), height_target), + *map(Write, [words, times, eq_det]) + ) + self.dither() + +class DiagonalExampleWithSquare(DiagonalExample): + CONFIG = { + "show_square" : True + } + +class ShearExample(DiagonalExample): + CONFIG = { + "show_square" : False, + "show_coordinates" : True, + "transposed_matrix" : [[1, 0], [1, 1]] + } + +class ShearExampleWithSquare(DiagonalExample): + CONFIG = { + "show_square" : True, + "show_coordinates" : True, + "show_coordinates" : False, + "transposed_matrix" : [[1, 0], [1, 1]] + } + +class ThisSquareTellsEverything(LinearTransformationScene): + def construct(self): + self.setup() + self.add_unit_square() + words = TextMobject(""" + This square gives you + everything you need. + """) + words.to_corner(UP+RIGHT) + words.highlight(YELLOW) + words.add_background_rectangle() + arrow = Arrow( + words.get_bottom(), self.square.get_right(), + color = WHITE + ) + + self.play(Write(words, run_time = 2)) + self.play(ShowCreation(arrow)) + self.add_foreground_mobject(words, arrow) + self.dither() + self.apply_transposed_matrix([[1.5, -0.5], [1, 1.5]]) + self.dither() + +class WhatHappensToOneSquareHappensToAll(LinearTransformationScene): + def construct(self): + self.setup() + self.add_unit_square() + pairs = [ + (2*RIGHT+UP, 1), + (3*LEFT, 2), + (2*LEFT+DOWN, 0.5), + (3.5*RIGHT+2.5*UP, 1.5), + (RIGHT+2*DOWN, 0.25), + (3*LEFT+3*DOWN, 1), + ] + squares = VMobject() + for position, side_length in pairs: + square = self.square.copy() + square.scale(side_length) + square.shift(position) + squares.add(square) + self.play(FadeIn( + squares, submobject_mode = "lagged_start", + run_time = 3 + )) + self.add_transformable_mobject(squares) + self.apply_transposed_matrix([[1, -1], [0.5, 1]]) + self.dither() + +class BreakBlobIntoGridSquares(LinearTransformationScene): + CONFIG = { + "square_size" : 0.5, + "blob_height" : 3, + } + def construct(self): + self.setup() + blob = Blob( + height = self.blob_height, + random_seed = 5, + random_nudge_size = 0.2, + ) + blob.next_to(ORIGIN, UP+RIGHT) + self.add_transformable_mobject(blob) + arange = np.arange( + 0, self.blob_height + self.square_size, + self.square_size + ) + square = Square(side_length = self.square_size) + square.set_stroke(YELLOW, width = 2) + square.set_fill(YELLOW, opacity = 0.3) + squares = VMobject() + for x, y in it.product(*[arange]*2): + point = x*RIGHT + y*UP + if blob.probably_contains(point): + squares.add(square.copy().shift(point)) + self.play(ShowCreation( + squares, submobject_mode = "lagged_start", + run_time = 2, + )) + self.add_transformable_mobject(squares) + self.dither() + self.apply_transposed_matrix([[1, -1], [0.5, 1]]) + self.dither() + +class BreakBlobIntoGridSquaresGranular(BreakBlobIntoGridSquares): + CONFIG = { + "square_size" : 0.25 + } + +class BreakBlobIntoGridSquaresVeryGranular(BreakBlobIntoGridSquares): + CONFIG = { + "square_size" : 0.1 + } + +class NameDeterminant(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[1, 0], [2, 1]] + } + def construct(self): + self.setup() + self.add_unit_square() + self.add_title(["The", "``determinant''", "of a transformation"]) + self.title.split()[1].split()[1].highlight(YELLOW) + + text = TextMobject("Area $=1$") + text.move_to(self.square) + det = np.linalg.det(self.t_matrix) + self.add_moving_mobject(text, TextMobject("Area $=%d$"%det)) + self.show_frame() + + + + + + + + + + + + diff --git a/eola/two_d_space.py b/eola/two_d_space.py index 03072c60..ce0aec38 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -9,7 +9,8 @@ from animation.transform import ApplyPointwiseFunction, Transform, \ ApplyMethod, FadeOut, ApplyFunction from animation.simple_animations import ShowCreation, Write from topics.number_line import NumberPlane, Axes -from topics.geometry import Vector, Line, Circle, Arrow, Dot, BackgroundRectangle +from topics.geometry import Vector, Line, Circle, Arrow, Dot, \ + BackgroundRectangle, Square from helpers import * from eola.matrix import Matrix, VECTOR_LABEL_SCALE_VAL, vector_coordinate_label @@ -260,8 +261,9 @@ class LinearTransformationScene(VectorScene): self.background_mobjects = [] self.foreground_mobjects = [] self.transformable_mobjects = [] - self.moving_vectors = [] + self.moving_vectors = [] self.transformable_labels = [] + self.moving_mobjects = [] self.background_plane = NumberPlane( @@ -301,6 +303,24 @@ class LinearTransformationScene(VectorScene): def add_transformable_mobject(self, *mobjects): self.add_special_mobjects(self.transformable_mobjects, *mobjects) + def add_moving_mobject(self, mobject, target_mobject = None): + mobject.target = target_mobject + self.add_special_mobjects(self.moving_mobjects, mobject) + + def add_unit_square(self, color = YELLOW, opacity = 0.3, animate = False): + square = Square(color = color, side_length = 1) + square.shift(-square.get_corner(DOWN+LEFT)) + if animate: + added_anims = map(Animation, self.moving_vectors) + self.play(ShowCreation(square), *added_anims) + self.play(square.set_fill, color, opacity, *added_anims) + else: + square.set_fill(color, opacity) + self.add_transformable_mobject(square) + self.bring_to_front(*self.moving_vectors) + self.square = square + return self + def add_vector(self, vector, color = YELLOW, **kwargs): vector = VectorScene.add_vector( self, vector, color = color, **kwargs @@ -334,6 +354,7 @@ class LinearTransformationScene(VectorScene): if animate: self.play(Write(title)) self.add_foreground_mobject(title) + self.title = title return self def get_matrix_transformation(self, transposed_matrix): @@ -353,6 +374,14 @@ class LinearTransformationScene(VectorScene): self.add(start.copy().fade(0.7)) return Transform(start, target, submobject_mode = "all_at_once") + def get_moving_mobject_movement(self, func): + for m in self.moving_mobjects: + if m.target is None: + m.target = m.copy() + target_point = func(m.get_center()) + m.target.move_to(target_point) + return self.get_piece_movement(self.moving_mobjects) + def get_vector_movement(self, func): for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color = v.get_color()) @@ -382,13 +411,18 @@ class LinearTransformationScene(VectorScene): def apply_function(self, function, added_anims = [], **kwargs): if "run_time" not in kwargs: kwargs["run_time"] = 3 - added_anims.append(self.get_vector_movement(function)) - added_anims.append(self.get_transformable_label_movement()) - added_anims += map(Animation, self.foreground_mobjects) - function_application = ApplyPointwiseFunction( - function, VMobject(*self.transformable_mobjects) - ) - self.play(function_application, *added_anims, **kwargs) + anims = [ + ApplyPointwiseFunction(function, t_mob) + for t_mob in self.transformable_mobjects + ] + [ + self.get_vector_movement(function), + self.get_transformable_label_movement(), + self.get_moving_mobject_movement(function), + ] + [ + Animation(f_mob) + for f_mob in self.foreground_mobjects + ] + added_anims + self.play(*anims, **kwargs) diff --git a/extract_scene.py b/extract_scene.py index f737fb80..36b6a993 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -122,7 +122,8 @@ def is_scene(obj): def prompt_user_for_choice(name_to_obj): num_to_name = {} - for count, name in zip(it.count(1), name_to_obj): + names = sorted(name_to_obj.keys()) + for count, name in zip(it.count(1), names): print "%d: %s"%(count, name) num_to_name[count] = name try: diff --git a/mobject/mobject.py b/mobject/mobject.py index 9dde2abd..26c550c4 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -224,12 +224,12 @@ class Mobject(object): self.shift(target_point - point_to_align + buff*direction) return self - def shift_onto_screen(self): + def shift_onto_screen(self, **kwargs): space_lengths = [SPACE_WIDTH, SPACE_HEIGHT] for vect in UP, DOWN, LEFT, RIGHT: dim = np.argmax(np.abs(vect)) if abs(self.get_edge_center(vect)[dim]) > space_lengths[dim]: - self.to_edge(vect) + self.to_edge(vect, **kwargs) def stretch_to_fit(self, length, dim, stretch = True): old_length = self.length_over_dim(dim) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index a48f6c82..615e2450 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -14,6 +14,7 @@ class VMobject(Mobject): "is_subpath" : False, "close_new_points" : False, "mark_paths_closed" : False, + "considered_smooth" : True, "propogate_style_to_family" : False, } def __init__(self, *args, **kwargs): @@ -181,6 +182,7 @@ class VMobject(Mobject): return self def make_smooth(self): + self.considered_smooth = True return self.change_anchor_mode("smooth") def make_jagged(self): @@ -211,7 +213,7 @@ class VMobject(Mobject): def apply_function(self, function, maintain_smoothness = True): Mobject.apply_function(self, function) - if maintain_smoothness: + if maintain_smoothness and self.considered_smooth: self.make_smooth() return self diff --git a/scene/scene.py b/scene/scene.py index ffe28601..bdbe1131 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -114,13 +114,13 @@ class Scene(object): ) return self - def bring_to_front(self, mobject): - self.add(mobject) + def bring_to_front(self, *mobjects): + self.add(*mobjects) return self - def bring_to_back(self, mobject): - self.remove(mobject) - self.mobjects = [mobject] + self.mobjects + def bring_to_back(self, *mobjects): + self.remove(*mobjects) + self.mobjects = mobjects + self.mobjects return self def clear(self): diff --git a/topics/characters.py b/topics/characters.py index 48f402bd..7b88952c 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -189,6 +189,7 @@ class Bubble(SVGMobject): self.stretch_to_fit_width(self.width) if self.direction[0] > 0: Mobject.flip(self) + self.direction_was_specified = ("direction" in kwargs) self.content = Mobject() def get_tip(self): @@ -207,9 +208,11 @@ class Bubble(SVGMobject): self.direction = -np.array(self.direction) return self - def pin_to(self, mobject, allow_flipping = True): + def pin_to(self, mobject): mob_center = mobject.get_center() - if np.sign(mob_center[0]) != np.sign(self.direction[0]) and allow_flipping: + want_to_filp = np.sign(mob_center[0]) != np.sign(self.direction[0]) + can_flip = not self.direction_was_specified + if want_to_filp and can_flip: self.flip() boundary_point = mobject.get_critical_point(UP-self.direction) vector_from_center = 1.0*(boundary_point-mob_center) diff --git a/topics/geometry.py b/topics/geometry.py index f3c9c24f..dff36478 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -56,6 +56,7 @@ class Dot(Circle): #Use 1D density, even though 2D class Line(VMobject): CONFIG = { "buff" : 0, + "considered_smooth" : False, } def __init__(self, start, end, **kwargs): digest_config(self, kwargs) @@ -214,6 +215,7 @@ class Polygon(VMobject): "color" : GREEN_D, "mark_paths_closed" : True, "close_new_points" : True, + "considered_smooth" : False, } def __init__(self, *vertices, **kwargs): assert len(vertices) > 1 @@ -244,6 +246,7 @@ class Rectangle(VMobject): "width" : 4.0, "mark_paths_closed" : True, "close_new_points" : True, + "considered_smooth" : False, } def generate_points(self): y, x = self.height/2., self.width/2. @@ -302,6 +305,7 @@ class Grid(VMobject): CONFIG = { "height" : 6.0, "width" : 6.0, + "considered_smooth" : False, } def __init__(self, rows, columns, **kwargs): digest_config(self, kwargs, locals())