diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 68130e11..fac20e9d 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -130,14 +130,23 @@ class Homotopy(Animation): """ Homotopy a function from (x, y, z, t) to (x', y', z') """ - digest_config(self, kwargs, locals()) + def function_at_time_t(t): + return lambda p : homotopy(p[0], p[1], p[2], t) + self.function_at_time_t = function_at_time_t + digest_config(self, kwargs) Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): - self.mobject.points = np.array([ - self.homotopy((x, y, z, alpha)) - for x, y, z in self.starting_mobject.points - ]) + pairs = zip( + self.mobject.submobject_family(), + self.starting_mobject.submobject_family() + ) + for mob, start_mob in pairs: + mob.become_partial(start_mob, 0, 1) + self.mobject.apply_function( + self.function_at_time_t(alpha) + ) + class PhaseFlow(Animation): CONFIG = { diff --git a/animation/transform.py b/animation/transform.py index 0cac5435..1731c837 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -8,7 +8,7 @@ from helpers import * from animation import Animation from simple_animations import DelayByOrder -from mobject import Mobject, Point +from mobject import Mobject, Point, VMobject class Transform(Animation): CONFIG = { @@ -100,6 +100,8 @@ class FadeIn(Transform): def __init__(self, mobject, **kwargs): target = mobject.copy() mobject.fade(1) + if isinstance(mobject, VMobject): + mobject.set_stroke(width = 0) Transform.__init__(self, mobject, target, **kwargs) # self.mobject.rgbs = self.starting_mobject.rgbs * alpha # if self.mobject.points.shape != self.starting_mobject.points.shape: diff --git a/eola/chapter0.py b/eola/chapter0.py index ccff52f3..d0f70df2 100644 --- a/eola/chapter0.py +++ b/eola/chapter0.py @@ -18,7 +18,8 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from mobject.vectorized_mobject import * -from eola.utils import * +from eola.matrix import * +from eola.two_d_space import * EXAMPLE_TRANFORM = [[0, 1], [-1, 1]] TRANFORMED_VECTOR = [[1], [2]] diff --git a/eola/chapter1.py b/eola/chapter1.py index 809f7a1d..094b886f 100644 --- a/eola/chapter1.py +++ b/eola/chapter1.py @@ -18,7 +18,9 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from mobject.vectorized_mobject import * -from eola.utils import * +from eola.matrix import * +from eola.two_d_space import * +from eola.chapter0 import UpcomingSeriesOfVidoes import random @@ -326,6 +328,14 @@ class DifferentConceptions(Scene): syms.arrange_submobjects(RIGHT) syms.center().shift(2*UP) + statement = TextMobject("We'll ignore him \\\\ for now") + statement.highlight(PINK) + statement.scale_to_fit_width(arrays.get_width()) + statement.next_to(arrays, DOWN, buff = 2) + arrow_to_mathy = Arrow(statement, mathy, color = PINK, buff = 0) + circle = Circle() + circle.shift(syms.get_bottom()) + VMobject(v_arrow, v_array, v_sym).highlight(v_color) VMobject(w_arrow, w_array, w_sym).highlight(w_color) VMobject(sum_arrow, sum_array).highlight(sum_color) @@ -338,6 +348,16 @@ class DifferentConceptions(Scene): ) self.play(Blink(mathy)) self.add_scaling(arrows, syms, arrays) + self.play(Write(statement)) + self.play(ShowCreation(arrow_to_mathy, submobject_mode = "one_at_a_time")) + self.play(ApplyMethod(mathy.change_mode, "sad")) + self.dither() + self.play( + ShowCreation(circle), + ApplyMethod(mathy.change_mode, "plain") + ) + self.dither() + def add_scaling(self, arrows, syms, arrays): s_arrows = VMobject( @@ -658,8 +678,10 @@ class Write3DVector(Scene): class VectorAddition(VectorScene): def construct(self): self.add_plane() - self.define_addition() - self.answer_why() + vects = self.define_addition() + # vects = map(Vector, [[1, 2], [3, -1], [4, 1]]) + self.ask_why(*vects) + self.answer_why(*vects) def define_addition(self): v1 = self.add_vector([1, 2]) @@ -673,9 +695,415 @@ class VectorAddition(VectorScene): sum_tex = "\\vec{\\textbf{v}} + \\vec{\\textbf{w}}" self.label_vector(v_sum, sum_tex, rotate = True) self.dither(3) + return v1, v2, v_sum + + def ask_why(self, v1, v2, v_sum): + why = TextMobject("Why?") + why_not_this = TextMobject("Why not \\\\ this?") + new_v2 = v2.copy().shift(-v2.get_start()) + new_v_sum = v_sum.copy() + alt_vect_sum = new_v2.get_end() - v1.get_end() + new_v_sum.shift(-new_v_sum.get_start()) + new_v_sum.rotate( + angle_of_vector(alt_vect_sum) - new_v_sum.get_angle() + ) + new_v_sum.scale(np.linalg.norm(alt_vect_sum)/new_v_sum.get_length()) + new_v_sum.shift(v1.get_end()) + new_v_sum.submobjects.reverse()#No idea why I have to do this + original_v_sum = v_sum.copy() + + why.next_to(v2, RIGHT) + why_not_this.next_to(new_v_sum, RIGHT) + why_not_this.shift(0.5*UP) + + self.play(Write(why, run_time = 1)) + self.dither(2) + self.play( + Transform(v2, new_v2), + Transform(v_sum, new_v_sum), + Transform(why, why_not_this) + ) + self.dither(2) + self.play( + FadeOut(why), + Transform(v_sum, original_v_sum) + ) + self.remove(why) + self.dither() + + def answer_why(self, v1, v2, v_sum): + randy = Randolph(color = PINK) + randy.shift(-randy.get_bottom()) + self.remove(v1, v2, v_sum) + for v in v1, v2, v_sum: + self.add(v) + self.show_ghost_movement(v) + self.remove(v) + self.add(v1, v2 ) + self.dither() + self.play(ApplyMethod(randy.scale, 0.3)) + self.play(ApplyMethod(randy.shift, v1.get_end())) + self.dither() + self.play(ApplyMethod(v2.shift, v1.get_end())) + self.play(ApplyMethod(randy.move_to, v2.get_end())) + self.dither() + self.remove(randy) + randy.move_to(ORIGIN) + self.play(FadeIn(v_sum)) + self.play(ApplyMethod(randy.shift, v_sum.get_end())) + self.dither() + + +class AddingNumbersOnNumberLine(Scene): + def construct(self): + number_line = NumberLine() + number_line.add_numbers() + two_vect = Vector([2, 0]) + five_vect = Vector([5, 0], color = MAROON_B) + seven_vect = Vector([7, 0], color = PINK) + five_vect.shift(two_vect.get_end()) + seven_vect.shift(0.5*DOWN) + vects = [two_vect, five_vect, seven_vect] + + two, five, seven = map(TexMobject, ["2", "5", "7"]) + two.next_to(two_vect, UP) + five.next_to(five_vect, UP) + seven.next_to(seven_vect, DOWN) + nums = [two, five, seven] + + sum_mob = TexMobject("2 + 5").shift(3*UP) + + self.play(ShowCreation(number_line, submobject_mode = "one_at_a_time")) + self.dither() + self.play(Write(sum_mob, run_time = 2)) + self.dither() + for vect, num in zip(vects, nums): + self.play( + ShowCreation(vect, submobject_mode = "one_at_a_time"), + Write(num, run_time = 1) + ) + self.dither() + + +class VectorAdditionNumerically(VectorScene): + def construct(self): + plus = TexMobject("+") + equals = TexMobject("=") + randy = Randolph() + randy.scale_to_fit_height(1) + randy.shift(-randy.get_bottom()) + + axes = self.add_axes() + x_axis, y_axis = axes.split() + + v1 = self.add_vector([1, 2]) + coords1, x_line1, y_line1 = self.vector_to_coords(v1, cleanup = False) + self.play(ApplyFunction( + lambda m : m.next_to(y_axis, RIGHT).to_edge(UP), + coords1 + )) + plus.next_to(coords1, RIGHT) + + v2 = self.add_vector([3, -1], color = MAROON_B) + coords2, x_line2, y_line2 = self.vector_to_coords(v2, cleanup = False) + self.dither() + self.play( + ApplyMethod(coords2.next_to, plus, RIGHT), + Write(plus, run_time = 1), + *[ + ApplyMethod(mob.shift, v1.get_end()) + for mob in v2, x_line2, y_line2 + ] + ) + equals.next_to(coords2, RIGHT) + self.dither() + + self.play(FadeIn(randy)) + for step in [RIGHT, 2*UP, 3*RIGHT, DOWN]: + self.play(ApplyMethod(randy.shift, step, run_time = 1.5)) + self.dither() + self.play(ApplyMethod(randy.shift, -randy.get_bottom())) + + self.play(ApplyMethod(x_line2.shift, 2*DOWN)) + self.play(ApplyMethod(y_line1.shift, 3*RIGHT)) + for step in [4*RIGHT, 2*UP, DOWN]: + self.play(ApplyMethod(randy.shift, step)) + self.play(FadeOut(randy)) + self.remove(randy) + one_brace = Brace(x_line1) + three_brace = Brace(x_line2) + one = TexMobject("1").next_to(one_brace, DOWN) + three = TexMobject("3").next_to(three_brace, DOWN) + self.play( + GrowFromCenter(one_brace), + GrowFromCenter(three_brace), + Write(one), + Write(three), + run_time = 1 + ) + self.dither() + + two_brace = Brace(y_line1, RIGHT) + two = TexMobject("2").next_to(two_brace, RIGHT) + new_y_line = Line(4*RIGHT, 4*RIGHT+UP, color = Y_COLOR) + two_minus_one_brace = Brace(new_y_line, RIGHT) + two_minus_one = TexMobject("2+(-1)").next_to(two_minus_one_brace, RIGHT) + self.play( + GrowFromCenter(two_brace), + Write(two, run_time = 1) + ) + self.dither() + self.play( + Transform(two_brace, two_minus_one_brace), + Transform(two, two_minus_one), + Transform(y_line1, new_y_line), + Transform(y_line2, new_y_line) + ) + self.dither() + self.add_vector(v2.get_end(), color = PINK ) + + sum_coords = Matrix(["1+3", "2+(-1)"]) + sum_coords.scale_to_fit_height(coords1.get_height()) + sum_coords.next_to(equals, RIGHT) + brackets = sum_coords.get_brackets() + x1, y1 = coords1.get_mob_matrix().flatten() + x2, y2 = coords2.get_mob_matrix().flatten() + sum_x, sum_y = sum_coords.get_mob_matrix().flatten() + sum_x_start = VMobject(x1, x2).copy() + sum_y_start = VMobject(y1, y2).copy() + self.play( + Write(brackets), + Write(equals), + Transform(sum_x_start, sum_x), + run_time = 1 + ) + self.play(Transform(sum_y_start, sum_y)) + self.dither(2) + + starters = [x1, y1, x2, y2, sum_x_start, sum_y_start] + variables = map(TexMobject, [ + "x_1", "y_1", "x_2", "y_2", "x_1+y_1", "x_2+y_2" + ]) + for i, (var, starter) in enumerate(zip(variables, starters)): + if i%2 == 0: + var.highlight(X_COLOR) + else: + var.highlight(Y_COLOR) + var.scale(VECTOR_LABEL_SCALE_VAL) + var.move_to(starter) + self.play( + Transform( + VMobject(*starters[:4]), + VMobject(*variables[:4]) + ), + FadeOut(sum_x_start), + FadeOut(sum_y_start) + ) + sum_x_end, sum_y_end = variables[-2:] + self.dither(2) + self.play( + Transform(VMobject(x1, x2).copy(), sum_x_end) + ) + self.play( + Transform(VMobject(y1, y2).copy(), sum_y_end) + ) + self.dither(3) + +class MultiplicationByANumberIntro(Scene): + def construct(self): + v = TexMobject("\\vec{\\textbf{v}}") + v.highlight(YELLOW) + nums = map(TexMobject, ["2", "\\dfrac{1}{3}", "-1.8"]) + for mob in [v] + nums: + mob.scale(1.5) + + self.play(Write(v, run_time = 1)) + last = None + for num in nums: + num.next_to(v, LEFT) + if last: + self.play(Transform(last, num)) + else: + self.play(FadeIn(num)) + last = num + self.dither() + +class ShowScalarMultiplication(VectorScene): + def construct(self): + plane = self.add_plane() + v = self.add_vector([3, 1]) + label = self.label_vector(v, "v", add_to_vector = False) + + self.scale_vector(v, 2, label) + self.scale_vector(v, 1./3, label, factor_tex = "\\dfrac{1}{3}") + self.scale_vector(v, -1.8, label) + self.remove(label) + self.describe_scalars(v, plane) + + + def scale_vector(self, v, factor, v_label, + v_name = "v", factor_tex = None): + starting_mobjects = list(self.mobjects) + + if factor_tex is None: + factor_tex = str(factor) + scaled_vector = self.add_vector( + factor*v.get_end(), animate = False + ) + self.remove(scaled_vector) + label_tex = "%s\\vec{\\textbf{%s}}"%(factor_tex, v_name) + label = self.label_vector( + scaled_vector, label_tex, animate = False, + add_to_vector = False + ) + self.remove(label) + factor_mob = TexMobject(factor_tex) + if factor_mob.get_height() > 1: + factor_mob.scale_to_fit_height(0.9) + if factor_mob.get_width() > 1: + factor_mob.scale_to_fit_width(0.9) + factor_mob.shift(1.5*RIGHT+2.5*UP) + + num_factor_parts = len(factor_mob.split()) + factor_mob_parts_in_label = label.split()[:num_factor_parts] + label_remainder_parts = label.split()[num_factor_parts:] + factor_in_label = VMobject(*factor_mob_parts_in_label) + label_remainder = VMobject(*label_remainder_parts) + + + self.play(Write(factor_mob, run_time = 1)) + self.dither() + self.play( + ApplyMethod(v.copy().highlight, DARK_GREY), + ApplyMethod(v_label.copy().highlight, DARK_GREY), + Transform(factor_mob, factor_in_label), + Transform(v.copy(), scaled_vector), + Transform(v_label.copy(), label_remainder), + ) + self.dither(2) + + self.clear() + self.add(*starting_mobjects) + + def describe_scalars(self, v, plane): + axes = plane.get_axes() + long_v = Vector(2*v.get_end()) + long_minus_v = Vector(-2*v.get_end()) + original_v = v.copy() + scaling_word = TextMobject("``Scaling''").to_corner(UP+LEFT) + scaling_word.shift(2*RIGHT) + scalars = VMobject(*map(TexMobject, [ + "2,", "\\dfrac{1}{3},", "-1.8,", "\\dots" + ])) + scalars.arrange_submobjects(RIGHT, buff = 0.4) + scalars.next_to(scaling_word, DOWN, aligned_edge = LEFT) + scalars_word = TextMobject("``Scalars''") + scalars_word.next_to(scalars, DOWN, aligned_edge = LEFT) + + self.remove(plane) + self.add(axes) + self.play( + Write(scaling_word), + Transform(v, long_v), + run_time = 1.5 + ) + self.play(Transform(v, long_minus_v, run_time = 3)) + self.play(Write(scalars)) + self.dither() + self.play(Write(scalars_word)) + self.play(Transform(v, original_v), run_time = 3) + self.dither(2) + +class ScalingNumerically(VectorScene): + def construct(self): + two_dot = TexMobject("2\\cdot") + equals = TexMobject("=") + self.add_axes() + v = self.add_vector([3, 1]) + v_coords, vx_line, vy_line = self.vector_to_coords(v, cleanup = False) + self.play(ApplyMethod(v_coords.to_edge, UP)) + two_dot.next_to(v_coords, LEFT) + equals.next_to(v_coords, RIGHT) + two_v = self.add_vector([6, 2], animate = False) + self.remove(two_v) + self.play( + Transform(v.copy(), two_v), + Write(two_dot, run_time = 1) + ) + two_v_coords, two_v_x_line, two_v_y_line = self.vector_to_coords( + two_v, cleanup = False + ) + self.play( + ApplyMethod(two_v_coords.next_to, equals, RIGHT), + Write(equals, run_time = 1) + ) + self.dither(2) + + x, y = v_coords.get_mob_matrix().flatten() + two_v_elems = two_v_coords.get_mob_matrix().flatten() + x_sym, y_sym = map(TexMobject, ["x", "y"]) + two_x_sym, two_y_sym = map(TexMobject, ["2x", "2y"]) + VMobject(x_sym, two_x_sym).highlight(X_COLOR) + VMobject(y_sym, two_y_sym).highlight(Y_COLOR) + syms = [x_sym, y_sym, two_x_sym, two_y_sym] + VMobject(*syms).scale(VECTOR_LABEL_SCALE_VAL) + for sym, num in zip(syms, [x, y] + list(two_v_elems)): + sym.move_to(num) + self.play( + Transform(x, x_sym), + Transform(y, y_sym), + FadeOut(VMobject(*two_v_elems)) + ) + self.dither() + self.play( + Transform( + VMobject(two_dot.copy(), x.copy()), + two_x_sym + ), + Transform( + VMobject(two_dot.copy(), y.copy() ), + two_y_sym + ) + ) + self.dither(2) + + + +class FollowingVideos(UpcomingSeriesOfVidoes): + def construct(self): + v_sum = VMobject( + Vector([1, 1], color = YELLOW), + Vector([3, 1], color = BLUE).shift(RIGHT+UP), + Vector([4, 2], color = GREEN), + ) + scalar_multiplication = VMobject( + TexMobject("2 \\cdot "), + Vector([1, 1]), + TexMobject("="), + Vector([2, 2], color = WHITE) + ) + scalar_multiplication.arrange_submobjects(RIGHT) + both = VMobject(v_sum, scalar_multiplication) + both.arrange_submobjects(RIGHT, buff = 1) + both.shift(2*DOWN) + self.add(both) + + UpcomingSeriesOfVidoes.construct(self) + last_video = self.mobjects[-1] + self.play(ApplyMethod(last_video.highlight, YELLOW)) + self.dither() + everything = VMobject(*self.mobjects) + everything.remove(last_video) + big_last_video = last_video.copy() + big_last_video.center() + big_last_video.scale_to_fit_height(2.5*SPACE_HEIGHT) + big_last_video.set_fill(opacity = 0) + self.play( + ApplyMethod(everything.shift, 2*SPACE_WIDTH*LEFT), + Transform(last_video, big_last_video), + run_time = 2 + ) - def answer_why(self): - pass class ItDoesntMatterWhich(Scene): @@ -736,6 +1164,137 @@ class ItDoesntMatterWhich(Scene): Transform(physy_statement, back_and_forth) ) self.dither() + + +class DataAnalyst(Scene): + def construct(self): + plane = NumberPlane() + ellipse = ParametricFunction( + lambda x : 2*np.cos(x)*(UP+RIGHT) + np.sin(x)*(UP+LEFT), + color = PINK, + t_max = 2*np.pi + ) + ellipse_points = [ + ellipse.point_from_proportion(x) + for x in np.arange(0, 1, 1./20) + ] + string_vects = [ + matrix_to_mobject(("%.02f %.02f"%tuple(ep[:2])).split()) + for ep in ellipse_points + ] + string_vects_matrix = Matrix( + np.array(string_vects).reshape((4, 5)) + ) + string_vects = string_vects_matrix.get_mob_matrix().flatten() + string_vects = VMobject(*string_vects) + + vects = VMobject(*map(Vector, ellipse_points)) + + self.play(Write(string_vects)) + self.dither(2) + self.play( + FadeIn(plane), + Transform(string_vects, vects) + ) + self.remove(string_vects) + self.add(vects) + self.dither() + self.play( + ApplyMethod(plane.fade, 0.7), + ApplyMethod(vects.highlight, DARK_GREY), + ShowCreation(ellipse) + ) + self.dither(3) + + +class ManipulateSpace(LinearTransformationScene): + CONFIG = { + "include_background_plane" : False, + "show_basis_vectors" : False, + } + + def construct(self): + matrix_rule = TexMobject(""" + \\left[ + \\begin{array}{c} + x \\\\ y + \\end{array} + \\right] + \\rightarrow + \\left[ + \\begin{array}{c} + 2x + y \\\\ y + 2x + \\end{array} + \\right] + """) + + self.setup() + pi_creature = PiCreature(color = PINK).scale(0.5) + pi_creature.shift(-pi_creature.get_corner(DOWN+LEFT)) + self.plane.prepare_for_nonlinear_transform() + + def homotopy(x, y, z, t): + norm = np.linalg.norm([x, y]) + tau = interpolate(5, -5, t) + norm/SPACE_WIDTH + alpha = sigmoid(tau) + return [x, y + 0.5*np.sin(2*np.pi*alpha), z] + + self.play(ShowCreation( + self.plane, + submobject_mode = "one_at_a_time", + run_time = 2 + )) + self.play(FadeIn(pi_creature)) + self.play(Blink(pi_creature)) + self.plane.add(pi_creature) + self.play(Homotopy(homotopy, self.plane, run_time = 3)) + self.dither(2) + self.apply_matrix([[2, 1], [1, 2]]) + self.dither() + self.play( + FadeOut(self.plane), + Write(matrix_rule), + run_time = 2 + ) + self.dither() + +class CodingMathyAnimation(Scene): + pass + +class NextVideo(Scene): + def construct(self): + title = TextMobject("Next video: Linear combinations, span, and bases") + title.to_edge(UP) + rect = Rectangle(width = 16, height = 9, color = BLUE) + rect.scale_to_fit_height(6) + rect.next_to(title, DOWN) + + self.add(title) + self.play(ShowCreation(rect)) + self.dither() + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eola/chapter2.py b/eola/chapter2.py new file mode 100644 index 00000000..333b4b03 --- /dev/null +++ b/eola/chapter2.py @@ -0,0 +1,99 @@ +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 animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.combinatorics 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 * + +class OpeningQuote(Scene): + def construct(self): + words = TextMobject(""" + Mathematics requires a small dose, not of genius, \\\\ + but of an imaginative freedom which, in a larger \\\\ + dose, would be insanity. + """) + words.to_edge(UP) + for mob in words.submobjects[49:49+18]: + mob.highlight(GREEN) + words.show() + author = TextMobject("-Angus K. Rodgers") + author.highlight(YELLOW) + author.next_to(words, DOWN, buff = 0.5) + + self.play(FadeIn(words)) + self.dither(3) + self.play(Write(author, run_time = 3)) + self.dither() + + +class CoordinatesWereFamiliar(TeacherStudentsScene): + def construct(self): + self.setup() + self.student_says("I know this already") + self.random_blink() + self.teacher_says("Ah, but there is a subtlety") + self.random_blink() + self.dither() + + +class CoordinatesAsScalars(VectorScene): + def construct(self): + self.add_axes() + vector = self.add_vector([3, -2]) + array, x_line, y_line = self.vector_to_coords(vector) + self.add(array) + self.dither() + self.general_idea_of_scalars(array) + + def general_idea_of_scalars(self, array): + starting_mobjects = self.get_mobjects() + starting_mobjects.remove(array) + + title = TextMobject("Think of each coordinate as a scalar") + title.to_edge(UP) + + x, y = array.get_mob_matrix().flatten() + new_x = x.copy().scale(2).highlight(X_COLOR) + new_x.move_to(3*LEFT+2*UP) + new_y = y.copy().scale(2).highlight(Y_COLOR) + new_y.move_to(3*RIGHT+2*UP) + + self.play( + FadeOut(*starting_mobjects) + Transform(x, new_x), + Transform(y, new_y), + Write(title), + ) + + + + + + + + + + + + + + + + + diff --git a/eola/utils.py b/eola/matrix.py similarity index 50% rename from eola/utils.py rename to eola/matrix.py index 41b77d9f..b4a5c258 100644 --- a/eola/utils.py +++ b/eola/matrix.py @@ -7,17 +7,13 @@ from mobject.tex_mobject import TexMobject, TextMobject from animation.transform import ApplyPointwiseFunction, Transform, \ ApplyMethod, FadeOut, ApplyFunction from animation.simple_animations import ShowCreation, Write -from topics.number_line import NumberPlane -from topics.geometry import Vector, Line, Circle, Arrow +from topics.number_line import NumberPlane, Axes +from topics.geometry import Vector, Line, Circle, Arrow, Dot from helpers import * VECTOR_LABEL_SCALE_VAL = 0.7 -X_COLOR = GREEN_C -Y_COLOR = RED_C -Z_COLOR = BLUE_D - def matrix_to_tex_string(matrix): matrix = np.array(matrix).astype("string") if matrix.ndim == 1: @@ -52,94 +48,6 @@ def vector_coordinate_label(vector_mob, integer_labels = True, n_dim = 2): label.shift(shift_dir) return label - -class LinearTransformationScene(Scene): - CONFIG = { - "include_background_plane" : True, - "include_foreground_plane" : True, - "foreground_plane_kwargs" : { - "x_radius" : 2*SPACE_WIDTH, - "y_radius" : 2*SPACE_HEIGHT, - "secondary_line_ratio" : 0 - }, - "background_plane_kwargs" : { - "color" : GREY, - "secondary_color" : DARK_GREY, - "axes_color" : GREY, - }, - "show_coordinates" : False, - "show_basis_vectors" : True, - "i_hat_color" : GREEN_B, - "j_hat_color" : RED, - } - def setup(self): - self.background_mobjects = [] - self.transformable_mobject = [] - self.moving_vectors = [] - - self.background_plane = NumberPlane( - **self.background_plane_kwargs - ) - - if self.show_coordinates: - self.background_plane.add_coordinates() - if self.include_background_plane: - self.add_background_mobject(self.background_plane) - if self.include_foreground_plane: - self.plane = NumberPlane(**self.foreground_plane_kwargs) - self.add_transformable_mobject(self.plane) - if self.show_basis_vectors: - self.add_vector((1, 0), self.i_hat_color) - self.add_vector((0, 1), self.j_hat_color) - - def add_background_mobject(self, *mobjects): - for mobject in mobjects: - if mobject not in self.background_mobjects: - self.background_mobjects.append(mobject) - self.add(mobject) - - def add_transformable_mobject(self, *mobjects): - for mobject in mobjects: - if mobject not in self.transformable_mobject: - self.transformable_mobject.append(mobject) - self.add(mobject) - - def add_vector(self, coords, color = YELLOW): - vector = Vector(self.background_plane.num_pair_to_point(coords)) - vector.highlight(color) - self.moving_vectors.append(vector) - return vector - - def apply_matrix(self, matrix, **kwargs): - matrix = np.array(matrix) - if matrix.shape == (2, 2): - new_matrix = np.identity(3) - new_matrix[:2, :2] = matrix - matrix = new_matrix - elif matrix.shape != (3, 3): - raise "Matrix has bad dimensions" - transpose = np.transpose(matrix) - - def func(point): - return np.dot(point, transpose) - - new_vectors = [ - Vector(func(v.get_end()), color = v.get_stroke_color()) - for v in self.moving_vectors - ] - self.play( - ApplyPointwiseFunction( - func, - VMobject(*self.transformable_mobject), - **kwargs - ), - Transform( - VMobject(*self.moving_vectors), - VMobject(*new_vectors), - **kwargs - ) - ) - class Matrix(VMobject): CONFIG = { "v_buff" : 0.5, @@ -327,145 +235,6 @@ class NumericalMatrixMultiplication(Scene): -class VectorScene(Scene): - def add_plane(self, animate = False, **kwargs): - plane = NumberPlane(**kwargs) - if animate: - self.play(ShowCreation(plane, submobject_mode = "lagged_start")) - self.add(plane) - return plane - - def add_vector(self, vector, animate = True, color = YELLOW): - arrow = Vector(vector, color = color) - if animate: - self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) - self.add(arrow) - return arrow - - def label_vector(self, vector, label, animate = True, - direction = "left", rotate = False, - color = WHITE, add_to_vector = True, - buff_factor = 1.5): - if len(label) == 1: - label = "\\vec{\\textbf{%s}}"%label - label = TexMobject(label) - label.highlight(color) - label.scale(VECTOR_LABEL_SCALE_VAL) - if rotate: - label.rotate(vector.get_angle()) - - vector_vect = vector.get_end() - vector.get_start() - if direction is "left": - rot_angle = -np.pi/2 - else: - rot_angle = np.pi/2 - label.shift(-buff_factor*label.get_boundary_point( - rotate_vector(vector_vect, rot_angle) - )) - label.shift(vector.get_center()) - - if add_to_vector: - vector.add(label) - if animate: - self.play(Write(label, run_time = 1)) - self.add(label) - return label - - def position_x_coordinate(self, x_coord, x_line, vector): - x_coord.next_to(x_line, -vector[1]*UP) - x_coord.highlight(X_COLOR) - return x_coord - - def position_y_coordinate(self, y_coord, y_line, vector): - y_coord.next_to(y_line, vector[0]*RIGHT) - y_coord.highlight(Y_COLOR) - return y_coord - - def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, cleanup = True): - starting_mobjects = list(self.mobjects) - array = Matrix(vector) - array.shift(coords_start) - arrow = Vector(vector) - x_line = Line(ORIGIN, vector[0]*RIGHT) - y_line = Line(x_line.get_end(), arrow.get_end()) - x_line.highlight(X_COLOR) - y_line.highlight(Y_COLOR) - x_coord, y_coord = array.get_mob_matrix().flatten() - - self.play(Write(array, run_time = 1)) - self.dither() - self.play(ApplyFunction( - lambda x : self.position_x_coordinate(x, x_line, vector), - x_coord - )) - self.play(ShowCreation(x_line)) - self.play( - ApplyFunction( - lambda y : self.position_y_coordinate(y, y_line, vector), - y_coord - ), - FadeOut(array.get_brackets()) - ) - self.play(ShowCreation(y_line)) - self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) - self.dither() - if cleanup: - self.clear() - self.add(*starting_mobjects) - - def vector_to_coords(self, vector, integer_labels = True, cleanup = True): - starting_mobjects = list(self.mobjects) - show_creation = False - if isinstance(vector, Arrow): - arrow = vector - vector = arrow.get_end()[:2] - else: - arrow = Vector(vector) - show_creation = True - array = vector_coordinate_label(arrow, integer_labels = integer_labels) - x_line = Line(ORIGIN, vector[0]*RIGHT) - y_line = Line(x_line.get_end(), arrow.get_end()) - x_line.highlight(X_COLOR) - y_line.highlight(Y_COLOR) - x_coord, y_coord = array.get_mob_matrix().flatten() - x_coord_start = self.position_x_coordinate( - x_coord.copy(), x_line, vector - ) - y_coord_start = self.position_y_coordinate( - y_coord.copy(), y_line, vector - ) - brackets = array.get_brackets() - - if show_creation: - self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) - self.play( - ShowCreation(x_line), - Write(x_coord_start), - run_time = 1 - ) - self.play( - ShowCreation(y_line), - Write(y_coord_start), - run_time = 1 - ) - self.dither() - self.play( - Transform(x_coord_start, x_coord), - Transform(y_coord_start, y_coord), - Write(brackets), - run_time = 1 - ) - self.dither() - - self.remove(x_coord_start, y_coord_start) - self.add(x_coord, y_coord) - if cleanup: - self.clear() - self.add(*starting_mobjects) - - - - diff --git a/eola/two_d_space.py b/eola/two_d_space.py new file mode 100644 index 00000000..c0e097f8 --- /dev/null +++ b/eola/two_d_space.py @@ -0,0 +1,294 @@ +import numpy as np + +from scene import Scene +from mobject import Mobject +from mobject.vectorized_mobject import VMobject +from mobject.tex_mobject import TexMobject, TextMobject +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 + +from helpers import * +from eola.matrix import Matrix, VECTOR_LABEL_SCALE_VAL, vector_coordinate_label + + +X_COLOR = GREEN_C +Y_COLOR = RED_C +Z_COLOR = BLUE_D + + +class LinearTransformationScene(Scene): + CONFIG = { + "include_background_plane" : True, + "include_foreground_plane" : True, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + "secondary_line_ratio" : 0 + }, + "background_plane_kwargs" : { + "color" : GREY, + "secondary_color" : DARK_GREY, + "axes_color" : GREY, + }, + "show_coordinates" : False, + "show_basis_vectors" : True, + "i_hat_color" : X_COLOR, + "j_hat_color" : Y_COLOR, + } + def setup(self): + self.background_mobjects = [] + self.transformable_mobject = [] + self.moving_vectors = [] + + self.background_plane = NumberPlane( + **self.background_plane_kwargs + ) + + if self.show_coordinates: + self.background_plane.add_coordinates() + if self.include_background_plane: + self.add_background_mobject(self.background_plane) + if self.include_foreground_plane: + self.plane = NumberPlane(**self.foreground_plane_kwargs) + self.add_transformable_mobject(self.plane) + if self.show_basis_vectors: + self.add_vector((1, 0), self.i_hat_color) + self.add_vector((0, 1), self.j_hat_color) + + def add_background_mobject(self, *mobjects): + for mobject in mobjects: + if mobject not in self.background_mobjects: + self.background_mobjects.append(mobject) + self.add(mobject) + + def add_transformable_mobject(self, *mobjects): + for mobject in mobjects: + if mobject not in self.transformable_mobject: + self.transformable_mobject.append(mobject) + self.add(mobject) + + def add_vector(self, coords, color = YELLOW): + vector = Vector(self.background_plane.num_pair_to_point(coords)) + vector.highlight(color) + self.moving_vectors.append(vector) + return vector + + def apply_matrix(self, matrix, **kwargs): + matrix = np.array(matrix) + if matrix.shape == (2, 2): + new_matrix = np.identity(3) + new_matrix[:2, :2] = matrix + matrix = new_matrix + elif matrix.shape != (3, 3): + raise "Matrix has bad dimensions" + transpose = np.transpose(matrix) + + def func(point): + return np.dot(point, transpose) + + new_vectors = [ + Vector(func(v.get_end()), color = v.get_stroke_color()) + for v in self.moving_vectors + ] + self.play( + ApplyPointwiseFunction( + func, + VMobject(*self.transformable_mobject), + **kwargs + ), + Transform( + VMobject(*self.moving_vectors), + VMobject(*new_vectors), + **kwargs + ) + ) + + + + +class VectorScene(Scene): + def add_plane(self, animate = False, **kwargs): + plane = NumberPlane(**kwargs) + if animate: + self.play(ShowCreation(plane, submobject_mode = "lagged_start")) + self.add(plane) + return plane + + def add_axes(self, animate = False, color = WHITE, **kwargs): + axes = Axes(color = color) + if animate: + self.play(ShowCreation(axes, submobject_mode = "one_at_a_time")) + self.add(axes) + return axes + + def add_vector(self, vector, animate = True, color = YELLOW): + arrow = Vector(vector, color = color) + if animate: + self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) + self.add(arrow) + return arrow + + def get_basis_vectors(self): + i_hat = Vector([1, 0], color = X_COLOR) + j_hat = Vector([0, 1], color = Y_COLOR) + return i_hat, j_hat + + def label_vector(self, vector, label, animate = True, + direction = "left", rotate = False, + color = WHITE, add_to_vector = True, + buff_factor = 1.5): + if len(label) == 1: + label = "\\vec{\\textbf{%s}}"%label + label = TexMobject(label) + label.highlight(color) + label.scale(VECTOR_LABEL_SCALE_VAL) + if rotate: + label.rotate(vector.get_angle()) + + vector_vect = vector.get_end() - vector.get_start() + if direction is "left": + rot_angle = -np.pi/2 + else: + rot_angle = np.pi/2 + label.shift(-buff_factor*label.get_boundary_point( + rotate_vector(vector_vect, rot_angle) + )) + label.shift(vector.get_center()) + + if add_to_vector: + vector.add(label) + if animate: + self.play(Write(label, run_time = 1)) + self.add(label) + return label + + def position_x_coordinate(self, x_coord, x_line, vector): + x_coord.next_to(x_line, -np.sign(vector[1])*UP) + x_coord.highlight(X_COLOR) + return x_coord + + def position_y_coordinate(self, y_coord, y_line, vector): + y_coord.next_to(y_line, np.sign(vector[0])*RIGHT) + y_coord.highlight(Y_COLOR) + return y_coord + + def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, cleanup = True): + starting_mobjects = list(self.mobjects) + array = Matrix(vector) + array.shift(coords_start) + arrow = Vector(vector) + x_line = Line(ORIGIN, vector[0]*RIGHT) + y_line = Line(x_line.get_end(), arrow.get_end()) + x_line.highlight(X_COLOR) + y_line.highlight(Y_COLOR) + x_coord, y_coord = array.get_mob_matrix().flatten() + + self.play(Write(array, run_time = 1)) + self.dither() + self.play(ApplyFunction( + lambda x : self.position_x_coordinate(x, x_line, vector), + x_coord + )) + self.play(ShowCreation(x_line)) + self.play( + ApplyFunction( + lambda y : self.position_y_coordinate(y, y_line, vector), + y_coord + ), + FadeOut(array.get_brackets()) + ) + self.play(ShowCreation(y_line)) + self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) + self.dither() + if cleanup: + self.clear() + self.add(*starting_mobjects) + + def vector_to_coords(self, vector, integer_labels = True, cleanup = True): + starting_mobjects = list(self.mobjects) + show_creation = False + if isinstance(vector, Arrow): + arrow = vector + vector = arrow.get_end()[:2] + else: + arrow = Vector(vector) + show_creation = True + array = vector_coordinate_label(arrow, integer_labels = integer_labels) + x_line = Line(ORIGIN, vector[0]*RIGHT) + y_line = Line(x_line.get_end(), arrow.get_end()) + x_line.highlight(X_COLOR) + y_line.highlight(Y_COLOR) + x_coord, y_coord = array.get_mob_matrix().flatten() + x_coord_start = self.position_x_coordinate( + x_coord.copy(), x_line, vector + ) + y_coord_start = self.position_y_coordinate( + y_coord.copy(), y_line, vector + ) + brackets = array.get_brackets() + + if show_creation: + self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) + self.play( + ShowCreation(x_line), + Write(x_coord_start), + run_time = 1 + ) + self.play( + ShowCreation(y_line), + Write(y_coord_start), + run_time = 1 + ) + self.dither() + self.play( + Transform(x_coord_start, x_coord), + Transform(y_coord_start, y_coord), + Write(brackets), + run_time = 1 + ) + self.dither() + + self.remove(x_coord_start, y_coord_start) + self.add(x_coord, y_coord) + if cleanup: + self.clear() + self.add(*starting_mobjects) + return array, x_line, y_line + + def show_ghost_movement(self, vector): + if isinstance(vector, Arrow): + vector = vector.get_end() - vector.get_start() + elif len(vector) == 2: + vector = np.append(np.array(vector), 0.0) + x_max = int(SPACE_WIDTH + abs(vector[0])) + y_max = int(SPACE_HEIGHT + abs(vector[1])) + dots = VMobject(*[ + Dot(x*RIGHT + y*UP) + for x in range(-x_max, x_max) + for y in range(-y_max, y_max) + ]) + dots.set_fill(BLACK, opacity = 0) + dots_halfway = dots.copy().shift(vector/2).set_fill(WHITE, 1) + dots_end = dots.copy().shift(vector) + + self.play(Transform( + dots, dots_halfway, rate_func = rush_into + )) + self.play(Transform( + dots, dots_end, rate_func = rush_from + )) + self.remove(dots) + + + + + + + + + + + diff --git a/scene/scene.py b/scene/scene.py index 144bd66a..41c297ca 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -90,6 +90,7 @@ class Scene(object): def remove(self, *mobjects): if not all_elements_are_instances(mobjects, Mobject): raise Exception("Removing something which is not a mobject") + mobjects = it.chain(*[m.submobject_family() for m in mobjects]) mobjects = filter(lambda m : m in self.mobjects, mobjects) if len(mobjects) == 0: return @@ -109,6 +110,12 @@ class Scene(object): self.mobjects = [] return self + def get_mobjects(self): + return list(self.mobjects) + + def get_mobject_copies(self): + return [m.copy() for m in self.mobjects] + def align_run_times(self, *animations, **kwargs): if "run_time" in kwargs: run_time = kwargs["run_time"] diff --git a/topics/characters.py b/topics/characters.py index 91ec91fa..a82f00a5 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -5,7 +5,9 @@ from mobject.svg_mobject import SVGMobject from mobject.vectorized_mobject import VMobject from mobject.tex_mobject import TextMobject -from animation.transform import ApplyMethod +from animation.transform import ApplyMethod, FadeOut, FadeIn +from animation.simple_animations import Write +from scene import Scene PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature") @@ -92,6 +94,7 @@ class PiCreature(SVGMobject): x, y = direction[:2] for pupil, eye in zip(self.pupils.split(), self.eyes.split()): pupil.move_to(eye, side_to_align = direction) + #Some hacky nudging is required here if y > 0 and x != 0: # Look up and to a side nudge_size = pupil.get_height()/4. if x > 0: @@ -99,6 +102,9 @@ class PiCreature(SVGMobject): else: nudge = nudge_size*(DOWN+RIGHT) pupil.shift(nudge) + elif y < 0: + nudge_size = pupil.get_height()/8. + pupil.shift(nudge_size*UP) return self def get_looking_direction(self): @@ -186,7 +192,8 @@ class Bubble(SVGMobject): self.content = Mobject() def get_tip(self): - return self.get_corner(DOWN+self.direction) + #TODO, find a better way + return self.get_corner(DOWN+self.direction)-0.6*self.direction def get_bubble_center(self): return self.get_center() + self.get_height()*UP/8.0 @@ -204,8 +211,8 @@ class Bubble(SVGMobject): mob_center = mobject.get_center() if (mob_center[0] > 0) != (self.direction[0] > 0): self.flip() - boundary_point = mobject.get_boundary_point(UP-self.direction) - vector_from_center = 1.2*(boundary_point-mob_center) + boundary_point = mobject.get_critical_point(UP-self.direction) + vector_from_center = 1.0*(boundary_point-mob_center) self.move_tip_to(mob_center+vector_from_center) return self @@ -256,6 +263,107 @@ class ThoughtBubble(Bubble): return self +class TeacherStudentsScene(Scene): + def setup(self): + self.teacher = Mortimer() + self.teacher.to_corner(DOWN + RIGHT) + self.teacher.look(DOWN+LEFT) + self.students = VMobject(*[ + Randolph(color = c) + for c in BLUE_D, BLUE_C, BLUE_E + ]) + self.students.arrange_submobjects(RIGHT) + self.students.scale(0.8) + self.students.to_corner(DOWN+LEFT) + + for pi_creature in self.get_everyone(): + pi_creature.bubble = None + self.add(*self.get_everyone()) + + def get_teacher(self): + return self.teacher + + def get_students(self): + return self.students.split() + + def get_everyone(self): + return [self.get_teacher()] + self.get_students() + + def introduce_bubble(self, content, bubble_type, pi_creature, + content_intro_anim = None, + pi_creature_target_mode = None, + added_anims = []): + bubble = pi_creature.get_bubble(bubble_type) + if isinstance(content, str): + content = TextMobject(content) + bubble.position_mobject_inside(content) + pi_creature.bubble = bubble + + content_intro_anim = content_intro_anim or Write(content) + run_time = content_intro_anim.run_time + one_sec_rate_func = squish_rate_func( + smooth, 0, 1./run_time + ) + if not pi_creature_target_mode: + if bubble_type is "speech": + pi_creature_target_mode = "speaking" + else: + pi_creature_target_mode = "pondering" + + faders = [] + to_plains = [] + for p in self.get_everyone(): + if p.bubble and p is not pi_creature: + faders.append(FadeOut( + p.bubble, rate_func = one_sec_rate_func + )) + p.bubble = None + to_plains.append(ApplyMethod( + p.change_mode, "plain", + rate_func = one_sec_rate_func + )) + + anims = faders + to_plains + added_anims + [ + content_intro_anim, + FadeIn(bubble, rate_func = one_sec_rate_func), + ApplyMethod( + pi_creature.change_mode, + pi_creature_target_mode, + rate_func = one_sec_rate_func + ), + ] + self.play(*anims, run_time = run_time) + bubble.add_content(content) + + def teacher_says(self, content, **kwargs): + self.introduce_bubble( + content, "speech", self.get_teacher(), **kwargs + ) + + def student_says(self, content, student_index = 1, **kwargs): + student = self.get_students()[student_index] + self.introduce_bubble(content, "speech", student, **kwargs) + + def teacher_thinks(self, content): + self.introduce_bubble( + content, "thought", self.get_teacher(), **kwargs + ) + + def student_thinks(self, content, student_index = 1, **kwargs): + student = self.get_students()[student_index] + self.introduce_bubble(content, "thought", student, **kwargs) + + def random_blink(self): + pi_creature = random.choice(self.get_everyone()) + self.play(Blink(pi_creature)) + + + + + + + +