From e5dc0db8efba0fe59aa5f2bfe7d6a5d8c1d4bc43 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 30 Apr 2016 15:08:53 -0700 Subject: [PATCH] Additive/multiplicative rules --- animation/simple_animations.py | 31 ++-- camera.py | 2 + mobject/region.py | 1 - mobject/vectorized_mobject.py | 14 +- topics/characters.py | 8 +- topics/number_line.py | 8 +- triangle_of_power/triangle.py | 289 +++++++++++++++++++++++++++++++-- 7 files changed, 320 insertions(+), 33 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 6a09d62d..48275a45 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -130,19 +130,18 @@ class Homotopy(Animation): class PhaseFlow(Animation): CONFIG = { - "virtual_time" : 1 + "virtual_time" : 1, + "rate_func" : None, } def __init__(self, function, mobject, **kwargs): digest_config(self, kwargs, locals()) - self.get_nudge_func = lambda alpha_diff : \ - lambda point : point + alpha_diff*function(point) Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): if hasattr(self, "last_alpha"): - nudge = self.virtual_time*(alpha-self.last_alpha) + dt = self.virtual_time*(alpha-self.last_alpha) self.mobject.apply_function( - self.get_nudge_func(nudge) + lambda p : p + dt*self.function(p) ) self.last_alpha = alpha @@ -160,19 +159,25 @@ class MoveAlongPath(Animation): class ApplyToCenters(Animation): def __init__(self, AnimationClass, mobjects, **kwargs): - centers = [mob.get_center() for mob in mobjects] - kwargs["mobject"] = Mobject().add_points(centers) - self.centers_container = AnimationClass(**kwargs) - kwargs.pop("mobject") - Animation.__init__(self, Mobject(*mobjects), **kwargs) + full_kwargs = AnimationClass.CONFIG + full_kwargs.update(kwargs) + full_kwargs["mobject"] = Mobject(*[ + mob.get_point_mobject() + for mob in mobjects + ]) + self.centers_container = AnimationClass(**full_kwargs) + full_kwargs.pop("mobject") + Animation.__init__(self, Mobject(*mobjects), **full_kwargs) self.name = str(self) + AnimationClass.__name__ def update_mobject(self, alpha): self.centers_container.update_mobject(alpha) - points = self.centers_container.mobject.points + center_mobs = self.centers_container.mobject.split() mobjects = self.mobject.split() - for point, mobject in zip(points, mobjects): - mobject.center().shift(point) + for center_mob, mobject in zip(center_mobs, mobjects): + mobject.shift( + center_mob.get_center()-mobject.get_center() + ) diff --git a/camera.py b/camera.py index 736303ca..2a3d4ca0 100644 --- a/camera.py +++ b/camera.py @@ -129,6 +129,8 @@ class Camera(object): if len(points) == 0: continue coords = self.points_to_pixel_coords(points) + if np.all(~self.on_screen_pixels(coords)): + return result start = "M%d %d"%tuple(coords[0]) #(handle1, handle2, anchor) tripletes triplets = zip(*[ diff --git a/mobject/region.py b/mobject/region.py index 7c233180..019e2c6f 100644 --- a/mobject/region.py +++ b/mobject/region.py @@ -1,7 +1,6 @@ import numpy as np import itertools as it from PIL import Image -import cv2 from copy import deepcopy from mobject import Mobject diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index c2a508e2..b702c38f 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -87,9 +87,11 @@ class VMobject(Mobject): def get_stroke_color(self): try: + self.stroke_rgb[self.stroke_rgb<0] = 0 + self.stroke_rgb[self.stroke_rgb>1] = 1 return Color(rgb = self.stroke_rgb) except: - return Color(rgb = 0.99*self.stroke_rgb) + return Color(WHITE) #TODO, get color? Specify if stroke or fill #is the predominant color attribute? @@ -192,6 +194,16 @@ class VMobject(Mobject): self.submobjects ) + def arrange_submobjects(self, + direction = RIGHT, + buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, + center = True): + for m1, m2 in zip(self.submobjects, self.submobjects[1:]): + m2.next_to(m1, direction, buff = buff) + if center: + self.center() + return self + ## Information about line def component_curves(self): diff --git a/topics/characters.py b/topics/characters.py index ba7a8dc5..5382a850 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -56,10 +56,10 @@ class PiCreature(SVGMobject): self.set_stroke(color = BLACK, width = self.stroke_width) if not self.parts_named: self.name_parts() - self.mouth.set_fill(BLACK) - self.body.set_fill(self.color) - self.pupils.set_fill(BLACK) - self.eyes.set_fill(WHITE) + self.mouth.set_fill(BLACK, opacity = 1) + self.body.set_fill(self.color, opacity = 1) + self.pupils.set_fill(BLACK, opacity = 1) + self.eyes.set_fill(WHITE, opacity = 1) return self diff --git a/topics/number_line.py b/topics/number_line.py index 5c0527a8..b3bf0b93 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -98,6 +98,9 @@ class UnitInterval(NumberLine): class Axes(VMobject): + CONFIG = { + "propogate_style_to_family" : True + } def generate_points(self, **kwargs): self.x_axis = NumberLine(**kwargs) self.y_axis = NumberLine(**kwargs).rotate(np.pi/2) @@ -116,9 +119,10 @@ class NumberPlane(VMobject): "x_line_frequency" : 1, "y_line_frequency" : 1, "secondary_line_ratio" : 1, - "written_coordinate_height" : 0.5, + "written_coordinate_height" : 0.2, "written_coordinate_nudge" : 0.1*(DOWN+RIGHT), "num_pair_at_center" : (0, 0), + "propogate_style_to_family" : False, } def generate_points(self): @@ -160,7 +164,7 @@ class NumberPlane(VMobject): def init_colors(self): VMobject.init_colors(self) self.axes.set_stroke(self.axes_color) - # self.main_lines.set_stroke(self.color) + self.main_lines.set_stroke(self.color) self.secondary_lines.set_stroke(self.secondary_color, 1) return self diff --git a/triangle_of_power/triangle.py b/triangle_of_power/triangle.py index 8f62a666..ddff6e9c 100644 --- a/triangle_of_power/triangle.py +++ b/triangle_of_power/triangle.py @@ -82,7 +82,7 @@ def get_top_inverse(i, j): class TOP(VMobject): CONFIG = { "triangle_height_to_number_height" : 3, - "offset_multiple" : 1.2, + "offset_multiple" : 1.5, "radius" : 1.5, "propogate_style_to_family" : False, } @@ -121,7 +121,7 @@ class TOP(VMobject): value = TexMobject(value) if isinstance(value, TOP): return self.put_top_on_vertix(index, value) - value.scale_to_fit_height(self.get_value_height()) + self.rescale_corner_mobject(value) value.center() if index == 0: offset = -value.get_corner(UP+RIGHT) @@ -134,6 +134,26 @@ class TOP(VMobject): value.shift(anchors[index]) return value + def put_top_on_vertix(self, index, top): + top.scale_to_fit_height(2*self.get_value_height()) + vertices = top.get_vertices() + vertices[index] = 0 + start = reduce(op.add, vertices)/2 + end = self.triangle.get_anchors_and_handles()[0][index] + top.shift(end-start) + return top + + def put_in_vertex(self, index, mobject): + self.rescale_corner_mobject(mobject) + mobject.center() + mobject.shift(interpolate( + self.get_center(), + self.get_vertices()[index], + 0.7 + )) + return mobject + + def get_surrounding_circle(self, color = YELLOW): return Circle( radius = 1.7*self.radius, @@ -143,20 +163,20 @@ class TOP(VMobject): (self.triangle.get_height()/6)*DOWN ) - def put_top_on_vertix(self, index, top): - top.scale_to_fit_height(2*self.get_value_height()) - top_anchors = list( - top.triangle.get_anchors_and_handles()[0][:3] - ) - top_anchors[index] = 0 - start = reduce(op.add, top_anchors)/2 - end = self.triangle.get_anchors_and_handles()[0][index] - top.shift(end-start) - return top + def rescale_corner_mobject(self, mobject): + if mobject.get_height() > self.get_value_height(): + mobject.scale_to_fit_height(self.get_value_height()) + return self def get_value_height(self): return self.triangle.get_height()/self.triangle_height_to_number_height + def get_center(self): + return center_of_mass(self.get_vertices()) + + def get_vertices(self): + return self.triangle.get_anchors_and_handles()[0][:3] + def reset_submobjects(self): self.submobjects = [self.triangle] + self.values return self @@ -501,6 +521,251 @@ class AdditiveProperty(Scene): self.remove(copies) +class DrawInsideTriangle(Scene): + def construct(self): + top = TOP() + top.scale(2) + dot = top.put_in_vertex(0, Dot()) + plus = top.put_in_vertex(1, TexMobject("+")) + times = top.put_in_vertex(2, TexMobject("\\times")) + plus.highlight(GREEN) + times.highlight(YELLOW) + + self.add(top) + self.dither() + for mob in dot, plus, times: + self.play(Write(mob, run_time = 1)) + self.dither() + +class ConstantOnTop(Scene): + def construct(self): + top = TOP() + dot = top.put_in_vertex(1, Dot()) + times1 = top.put_in_vertex(0, TexMobject("\\times")) + times2 = top.put_in_vertex(2, TexMobject("\\times")) + times1.highlight(YELLOW) + times2.highlight(YELLOW) + three = top.put_on_vertex(1, "3") + lower_left_x = top.put_on_vertex(0, "x") + lower_right_x = top.put_on_vertex(2, "x") + x_cubed = TexMobject("x^3").to_edge(UP) + x_cubed.submobjects.reverse() #To align better + cube_root_x = TexMobject("\\sqrt[3]{x}").to_edge(UP) + + self.add(top) + self.play(ShowCreation(three)) + self.play( + FadeIn(lower_left_x), + Write(x_cubed), + run_time = 1 + ) + self.dither() + self.play(*[ + Transform(*pair, path_arc = np.pi) + for pair in [ + (lower_left_x, lower_right_x), + (x_cubed, cube_root_x), + ] + ]) + self.dither(2) + for mob in dot, times1, times2: + self.play(ShowCreation(mob)) + self.dither() + +def get_const_top_TOP(*args): + top = TOP(*args) + dot = top.put_in_vertex(1, Dot()) + times1 = top.put_in_vertex(0, TexMobject("\\times")) + times2 = top.put_in_vertex(2, TexMobject("\\times")) + times1.highlight(YELLOW) + times2.highlight(YELLOW) + top.add(dot, times1, times2) + return top + + +class MultiplyWithConstantTop(Scene): + def construct(self): + top1 = get_const_top_TOP("x", "3") + top2 = get_const_top_TOP("y", "3") + top3 = get_const_top_TOP("xy", "3") + times = TexMobject("\\times") + equals = TexMobject("=") + top_exp_equation = VMobject( + top1, times, top2, equals, top3 + ) + top_exp_equation.arrange_submobjects() + old_style_exp = TexMobject("(x^3)(y^3) = (xy)^3") + old_style_exp.to_edge(UP) + old_style_exp.highlight(GREEN) + old_style_rad = TexMobject("\\sqrt[3]{x} \\sqrt[3]{y} = \\sqrt[3]{xy}") + old_style_rad.to_edge(UP) + old_style_rad.highlight(RED) + + self.add(top_exp_equation, old_style_exp) + self.dither(3) + + old_tops = [top1, top2, top3] + new_tops = [] + for top in old_tops: + new_top = top.copy() + new_top.put_on_vertex(2, new_top.values[0]) + new_top.shift(0.5*LEFT) + new_tops.append(new_top) + self.play( + Transform(old_style_exp, old_style_rad), + Transform( + VMobject(*old_tops), + VMobject(*new_tops), + path_arc = np.pi/2 + ) + ) + self.dither(3) + +class RightStaysConstantQ(Scene): + def construct(self): + top1, top2, top3 = old_tops = [ + TOP(None, s, "8") + for s in "x", "y", TexMobject("x?y") + ] + q_mark = TexMobject("?").scale(2) + equation = VMobject( + top1, q_mark, top2, TexMobject("="), top3 + ) + equation.arrange_submobjects(buff = 0.7) + symbols_at_top = VMobject(*[ + top.values[1] + for top in top1, top2, top3 + ]) + symbols_at_lower_right = VMobject(*[ + top.put_on_vertex(0, top.values[1].copy()) + for top in top1, top2, top3 + ]) + old_style_eq1 = TexMobject("\\sqrt[x]{8} ? \\sqrt[y]{8} = \\sqrt[x?y]{8}") + old_style_eq1.highlight(BLUE) + old_style_eq2 = TexMobject("\\log_x(8) ? \\log_y(8) = \\log_{x?y}(8)") + old_style_eq2.highlight(YELLOW) + for eq in old_style_eq1, old_style_eq2: + eq.to_edge(UP) + + randy = Randolph() + randy.to_corner() + bubble = ThoughtBubble().pin_to(randy) + bubble.add_content(TOP(None, None, "8")) + + self.add(randy, bubble) + self.play(ApplyMethod(randy.change_mode, "pondering")) + self.dither(3) + triangle = bubble.content.triangle + eight = bubble.content.values[2] + bubble.clear() + self.play( + Transform(triangle, equation), + FadeOut(eight), + ApplyPointwiseFunction( + lambda p : (p+2*DOWN)*15/np.linalg.norm(p+2*DOWN), + bubble + ), + FadeIn(old_style_eq1), + ApplyMethod(randy.shift, 3*DOWN + 3*LEFT), + run_time = 2 + ) + self.remove(triangle) + self.add(equation) + self.dither(4) + self.play( + Transform( + symbols_at_top, symbols_at_lower_right, + path_arc = np.pi/2 + ), + Transform(old_style_eq1, old_style_eq2) + ) + self.dither(2) + + +class AOplusB(Scene): + def construct(self): + self.add(TexMobject( + "a \\oplus b = \\dfrac{1}{\\frac{1}{a} + \\frac{1}{b}}" + ).scale(2)) + self.dither() + + +class ConstantLowerRight(Scene): + def construct(self): + top = TOP() + times = top.put_in_vertex(0, TexMobject("\\times")) + times.highlight(YELLOW) + oplus = top.put_in_vertex(1, TexMobject("\\oplus")) + oplus.highlight(BLUE) + dot = top.put_in_vertex(2, Dot()) + eight = top.put_on_vertex(2, TexMobject("8")) + + self.add(top) + self.play(ShowCreation(eight)) + for mob in dot, oplus, times: + self.play(ShowCreation(mob)) + self.dither() + + top.add(eight) + top.add(times, oplus, dot) + top1, top2, top3 = tops = [ + top.copy() for i in range(3) + ] + big_oplus = TexMobject("\\oplus").scale(2).highlight(BLUE) + equals = TexMobject("=") + equation = VMobject( + top1, big_oplus, top2, equals, top3 + ) + equation.arrange_submobjects() + top3.shift(0.5*RIGHT) + x, y, xy = [ + t.put_on_vertex(0, s) + for t, s in zip(tops, ["x", "y", "xy"]) + ] + old_style_eq = TexMobject( + "\\dfrac{1}{\\frac{1}{\\log_x(8)} + \\frac{1}{\\log_y(8)}} = \\log_{xy}(8)" + ) + old_style_eq.to_edge(UP).highlight(RED) + + triple_top_copy = VMobject(*[ + top.copy() for i in range(3) + ]) + self.clear() + self.play( + Transform(triple_top_copy, VMobject(*tops)), + FadeIn(VMobject(x, y, xy, big_oplus, equals)) + ) + self.remove(triple_top_copy) + self.add(*tops) + self.play(Write(old_style_eq)) + self.dither(3) + + syms = VMobject(x, y, xy) + new_syms = VMobject(*[ + t.put_on_vertex(1, s) + for t, s in zip(tops, ["x", "y", "x \\oplus y"]) + ]) + new_old_style_eq = TexMobject( + "\\sqrt[x]{8} \\sqrt[y]{8} = \\sqrt[X]{8}" + ) + X = new_old_style_eq.split()[-4] + frac = TexMobject("\\frac{1}{\\frac{1}{x} + \\frac{1}{y}}") + frac.replace(X) + frac_lower_right = frac.get_corner(DOWN+RIGHT) + frac.scale(2) + frac.shift(frac_lower_right - frac.get_corner(DOWN+RIGHT)) + new_old_style_eq.submobjects[-4] = frac + new_old_style_eq.to_edge(UP) + new_old_style_eq.highlight(RED) + big_times = TexMobject("\\times").highlight(YELLOW) + big_times.shift(big_oplus.get_center()) + self.play( + Transform(old_style_eq, new_old_style_eq), + Transform(syms, new_syms, path_arc = np.pi/2), + Transform(big_oplus, big_times) + ) + self.dither(4) + class Test(Scene):