├── 2020_11_15a_logo_to_banner.py ├── 2020_11_15b_5hamiltonian_cycle.py ├── 2020_11_18_insertion_sort.py ├── 2020_11_22_complex_exponential.py ├── 2020_11_27_probability.py ├── 2020_12_02_LabeledDot_and_Cutout_v0_1_1.py ├── 2020_12_08_lissajous_curve.py ├── 2020_12_16_binary_search_tree.py ├── 2020_12_21_orbit.py ├── 2021_01_01_new_year_post_parametric_firework.py ├── 2021_01_03_v0.2.0_release_tour.py ├── 2021_02_06_inverse_pythagorean_theorem.py ├── 2021_02_14_valentines.py ├── 2021_02_26_pendulum.py ├── 2021_03_04_v0.4.0_release.py ├── 2021_03_11_simultaneous_lissajous.py ├── 2021_03_24_lenses.py ├── 2021_04_10_modular_multiplier.py ├── 2021_05_5_release0_6_0.ipynb ├── 2021_06_03_v0.7.0_release_tour.ipynb ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── 2021_01_03 │ ├── config_new.py │ ├── config_old.py │ ├── markup_example.py │ ├── methodanim_new.py │ ├── methodanim_old.py │ └── rubikscube.png └── 2021_03_04 │ ├── Blivet2.svg │ ├── present.svg │ ├── rocket-2.svg │ ├── tree.svg │ └── world.svg └── jupyter_notebooks └── pendulum ├── pendulum.py ├── pendulum_example.ipynb ├── tick.mp3 └── tock.mp3 /2020_11_15a_logo_to_banner.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | def normalize(v): 5 | norm = np.linalg.norm(v) 6 | if norm == 0: 7 | return v 8 | return v / norm 9 | 10 | 11 | # Render with -c "#ece6e2" 12 | class DraftScene(Scene): 13 | def construct(self): 14 | logo_green = "#81b29a" 15 | logo_blue = "#454866" 16 | logo_red = "#e07a5f" 17 | logo_black = "#343434" 18 | 19 | ds_m = MathTex(r"\mathbb{M}", z_index=20).scale(7).set_color(logo_black) 20 | ds_m.shift(2.25 * LEFT + 1.5 * UP) 21 | 22 | circle = Circle(color=logo_green, fill_opacity=1, z_index=7).shift(LEFT) 23 | square = Square(color=logo_blue, fill_opacity=1, z_index=5).shift(UP) 24 | triangle = Triangle(color=logo_red, fill_opacity=1, z_index=3).shift(RIGHT) 25 | 26 | vgroup = VGroup(triangle, square, circle, ds_m).scale(0.7) 27 | vgroup.move_to(ORIGIN) 28 | 29 | self.add(circle, square, triangle) 30 | 31 | shape_center = VGroup(circle, square, triangle).get_center() 32 | 33 | spiral_run_time = 2.1 34 | expansion_factor = 8 35 | m_height_over_anim_height = 0.75748 36 | m_shape_offset = 4.37 37 | m_anim_buff = 0.06 38 | 39 | tracker = ValueTracker(0) 40 | 41 | for mob in [circle, square, triangle]: 42 | mob.final_position = mob.get_center() 43 | mob.initial_position = ( 44 | mob.final_position 45 | + (mob.final_position - shape_center) * expansion_factor 46 | ) 47 | mob.initial_to_final_distance = np.linalg.norm( 48 | mob.final_position - mob.initial_position 49 | ) 50 | mob.move_to(mob.initial_position) 51 | mob.current_time = 0 52 | mob.starting_mobject = mob.copy() 53 | 54 | def updater(mob, dt): 55 | mob.become(mob.starting_mobject) 56 | mob.shift( 57 | normalize((shape_center - mob.get_center())) 58 | * mob.initial_to_final_distance 59 | * tracker.get_value() 60 | ) 61 | mob.rotate(TAU * tracker.get_value(), about_point=shape_center) 62 | mob.rotate(-TAU * tracker.get_value()) 63 | 64 | mob.add_updater(updater) 65 | 66 | self.play(tracker.set_value, 1, run_time=spiral_run_time) 67 | 68 | circle.clear_updaters() 69 | square.clear_updaters() 70 | triangle.clear_updaters() 71 | 72 | self.wait(0.3) 73 | 74 | self.play(FadeIn(ds_m), rate_func=rate_functions.ease_in_sine) 75 | self.wait(0.7) 76 | 77 | ds_m_target = ds_m.generate_target() 78 | circle_target = circle.generate_target().shift(RIGHT * m_shape_offset) 79 | square_target = square.generate_target().shift(RIGHT * m_shape_offset) 80 | triangle_target = triangle.generate_target().shift(RIGHT * m_shape_offset) 81 | 82 | anim = VGroup() 83 | for i, ch in enumerate("anim"): 84 | tex = Tex( 85 | "\\textbf{" + ch + "}", 86 | z_index=10, 87 | tex_template=TexFontTemplates.gnu_freeserif_freesans, 88 | ) 89 | if i != 0: 90 | tex.next_to(anim, buff=0.01) 91 | tex.align_to(ds_m, DOWN) 92 | anim.add(tex) 93 | 94 | anim.set_color(logo_black).set_height( 95 | m_height_over_anim_height * ds_m.get_height() 96 | ).next_to(ds_m_target, buff=m_anim_buff).align_to(ds_m, DOWN) 97 | 98 | banner = VGroup( 99 | ds_m_target, anim, circle_target, square_target, triangle_target 100 | ) 101 | banner.move_to(ORIGIN) 102 | 103 | ds_m_offset_vec = ds_m_target.get_center() - ds_m.get_center() 104 | 105 | self.play( 106 | circle.shift, 107 | ds_m_offset_vec, 108 | square.shift, 109 | ds_m_offset_vec, 110 | triangle.shift, 111 | ds_m_offset_vec, 112 | ds_m.shift, 113 | ds_m_offset_vec, 114 | ) 115 | 116 | tracker.set_value(0) 117 | shape_center = VGroup(circle, square, triangle).get_center() 118 | for mob in [circle, square, triangle]: 119 | mob.starting_mobject = mob.copy() 120 | mob.shape_center_offset = mob.get_center() - shape_center 121 | 122 | def updater(mob, dt): 123 | center = shape_center + RIGHT * tracker.get_value() * m_shape_offset 124 | mob.become(mob.starting_mobject) 125 | mob.move_to(center + mob.shape_center_offset) 126 | 127 | mob.add_updater(updater) 128 | 129 | self.play( 130 | tracker.set_value, 131 | 1, 132 | FadeIn(anim, lag_ratio=1), 133 | ) 134 | anim.z_index = 20 135 | self.wait(1) 136 | -------------------------------------------------------------------------------- /2020_11_15b_5hamiltonian_cycle.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class HamiltonianCycle(Scene): 5 | def construct(self): 6 | dots = [Dot(z_index=30) for _ in range(20)] 7 | for ind, dot in enumerate(dots[:5]): 8 | dot.move_to( 9 | 3.75 10 | * ( 11 | np.cos(ind / 5 * TAU + TAU / 4) * RIGHT 12 | + np.sin(ind / 5 * TAU + TAU / 4) * UP 13 | ) 14 | ) 15 | for ind, dot in enumerate(dots[5:10]): 16 | dot.move_to( 17 | 2.75 18 | * ( 19 | np.cos(ind / 5 * TAU + TAU / 4) * RIGHT 20 | + np.sin(ind / 5 * TAU + TAU / 4) * UP 21 | ) 22 | ) 23 | for ind, dot in enumerate(dots[10:15]): 24 | dot.move_to( 25 | 1.5 26 | * ( 27 | np.cos(ind / 5 * TAU - TAU / 4) * RIGHT 28 | + np.sin(ind / 5 * TAU - TAU / 4) * UP 29 | ) 30 | ) 31 | for ind, dot in enumerate(dots[15:]): 32 | dot.move_to( 33 | 0.75 34 | * ( 35 | np.cos(ind / 5 * TAU - TAU / 4) * RIGHT 36 | + np.sin(ind / 5 * TAU - TAU / 4) * UP 37 | ) 38 | ) 39 | lines = ( 40 | [ 41 | Line(dots[k].get_center(), dots[(k + 1) % 5].get_center()) 42 | for k in range(5) 43 | ] 44 | + [Line(dots[k].get_center(), dots[5 + k].get_center()) for k in range(5)] 45 | + [ 46 | Line(dots[5 + k].get_center(), dots[10 + (k + 2) % 5].get_center()) 47 | for k in range(5) 48 | ] 49 | + [ 50 | Line(dots[5 + k].get_center(), dots[10 + (k + 3) % 5].get_center()) 51 | for k in range(5) 52 | ] 53 | + [ 54 | Line(dots[10 + k].get_center(), dots[15 + k].get_center()) 55 | for k in range(5) 56 | ] 57 | + [ 58 | Line(dots[15 + k].get_center(), dots[15 + (k + 1) % 5].get_center()) 59 | for k in range(5) 60 | ] 61 | ) 62 | vgroup = VGroup(*lines, *dots) 63 | vgroup.move_to(ORIGIN) 64 | self.play(*[ShowCreation(dot) for dot in dots]) 65 | self.play(*[ShowCreation(line) for line in lines]) 66 | self.wait(1) 67 | cycle_ind = [ 68 | 0, 69 | 1, 70 | 2, 71 | 7, 72 | 14, 73 | 6, 74 | 13, 75 | 5, 76 | 12, 77 | 9, 78 | 11, 79 | 16, 80 | 17, 81 | 18, 82 | 19, 83 | 15, 84 | 10, 85 | 8, 86 | 3, 87 | 4, 88 | ] 89 | cycle_lines = [] 90 | for k in range(len(cycle_ind)): 91 | self.play( 92 | dots[cycle_ind[k]].set_color, RED, run_time=0.3, rate_function=linear 93 | ) 94 | new_line = Line( 95 | dots[cycle_ind[k]].get_center(), 96 | dots[cycle_ind[(k + 1) % len(cycle_ind)]].get_center(), 97 | color=RED, 98 | stroke_width=5, 99 | ) 100 | cycle_lines.append(new_line) 101 | self.play(ShowCreation(new_line), run_time=0.65) 102 | self.wait(1) 103 | self.play(VGroup(vgroup, *cycle_lines).shift, 3 * LEFT) 104 | t1 = Tex("The graph") 105 | t1.next_to(vgroup, RIGHT) 106 | self.play(Write(t1)) 107 | self.play( 108 | ApplyFunction( 109 | lambda obj: obj.scale(0.2).next_to(t1, RIGHT).shift(0.4 * UP), 110 | VGroup(*lines, *dots).copy(), 111 | ) 112 | ) 113 | t2 = Tex("has a Hamiltonian cycle.") 114 | t2.next_to(t1, DOWN) 115 | t2.align_to(t1, LEFT) 116 | self.play(Write(t2)) 117 | self.wait(1) 118 | self.play(*[FadeOut(obj) for obj in self.mobjects]) 119 | -------------------------------------------------------------------------------- /2020_11_18_insertion_sort.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | import random 3 | from enum import Enum 4 | 5 | 6 | class SwapMode(Enum): 7 | OVER = 1 8 | ACROSS = 2 9 | 10 | 11 | class Array(VGroup): 12 | def __init__(self, array, run_time=0.3): 13 | super().__init__() 14 | self.run_time = run_time 15 | self.build_array(array) 16 | 17 | def build_array(self, array): 18 | for i, x in enumerate(array): 19 | cell = VDict({"cell": Square(), "number": Integer(x)}) 20 | if i != 0: 21 | cell.next_to(self, RIGHT, buff=0) 22 | self.add(cell) 23 | self.move_to(ORIGIN) 24 | 25 | def value_at_index(self, index): 26 | return self[index]["number"].get_value() 27 | 28 | def swap(self, scn, i, j, swap_mode=SwapMode.ACROSS): 29 | # Swap in submobjects list 30 | temp = self.submobjects[i] 31 | self.submobjects[i] = self.submobjects[j] 32 | self.submobjects[j] = temp 33 | 34 | # Swap on screen 35 | if swap_mode == SwapMode.ACROSS: 36 | scn.play( 37 | self.submobjects[j].shift, 38 | LEFT * self.submobjects[i].get_width(), 39 | self.submobjects[i].shift, 40 | RIGHT * self.submobjects[j].get_width(), 41 | run_time=self.run_time, 42 | ) 43 | elif swap_mode == SwapMode.OVER: 44 | scn.play( 45 | self.submobjects[j].shift, 46 | self.submobjects[j].get_height() * UP, 47 | run_time=self.run_time / 3, 48 | ) 49 | scn.play( 50 | self.submobjects[j].shift, 51 | self.submobjects[j].get_width() * LEFT, 52 | self.submobjects[i].shift, 53 | self.submobjects[j].get_width() * RIGHT, 54 | run_time=self.run_time / 3, 55 | ) 56 | scn.play( 57 | self.submobjects[j].shift, 58 | self.submobjects[j].get_height() * DOWN, 59 | run_time=self.run_time / 3, 60 | ) 61 | else: 62 | raise ValueError(f"Unknown SwapMode {swap_mode}") 63 | 64 | 65 | class HeightArray(Array): 66 | def __init__(self, array, unit_width=1.5, unit_height=1, run_time=0.3): 67 | self.unit_height = unit_height 68 | self.unit_width = unit_width 69 | super().__init__(array, run_time=run_time) 70 | 71 | def value_at_index(self, index): 72 | return self[index].get_height() / self.unit_height 73 | 74 | def build_array(self, array): 75 | for i, x in enumerate(array): 76 | cell = Rectangle(width=self.unit_width, height=x * self.unit_height) 77 | if i != 0: 78 | cell.next_to(self, RIGHT, buff=0) 79 | cell.align_to(self, DOWN) 80 | self.add(cell) 81 | self.move_to(ORIGIN) 82 | 83 | 84 | class DraftScene(Scene): 85 | def construct(self): 86 | self.sort_array() 87 | 88 | def sort_array(self): 89 | arr = list(range(1, 51)) 90 | random.shuffle(arr) 91 | arr_mob = HeightArray(arr, run_time=0.03) 92 | if type(arr_mob) == Array: 93 | arr_mob.set_width(13) 94 | elif isinstance(arr_mob, HeightArray): 95 | arr_mob.set_height(7) 96 | arr_mob.to_edge(DOWN) 97 | self.play(ShowCreation(arr_mob)) 98 | self.wait() 99 | 100 | i = 1 101 | arr_mob[0].set_color(GREEN) 102 | while i < len(arr_mob.submobjects): 103 | arr_mob[i].set_color(YELLOW) 104 | j = i 105 | while j > 0 and arr_mob.value_at_index(j - 1) > arr_mob.value_at_index(j): 106 | arr_mob.swap(self, j, j - 1) 107 | j = j - 1 108 | arr_mob[j].set_color(GREEN) 109 | i = i + 1 110 | self.wait() 111 | -------------------------------------------------------------------------------- /2020_11_22_complex_exponential.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from manim import * 3 | 4 | 5 | class ComplexPlaneWithFunctionDots(ComplexPlane): 6 | class InputDot(Dot): 7 | def __init__(self, plane, **kwargs): 8 | super().__init__(**kwargs) 9 | 10 | self.plane = plane 11 | 12 | def get_value(self): 13 | return self.plane.p2n(self.get_center()) 14 | 15 | def set_value(self, value): 16 | self.move_to(self.plane.n2p(value)) 17 | 18 | return self 19 | 20 | class OutputDot(Dot): 21 | def __init__(self, plane, input_dot, func, **kwargs): 22 | super().__init__(**kwargs) 23 | 24 | self.plane = plane 25 | self.input_dot = input_dot 26 | self.func = func 27 | 28 | self.update_position() 29 | always(self.update_position) 30 | 31 | def get_value(self): 32 | return self.plane.p2n(self.get_center()) 33 | 34 | def update_position(self): 35 | self.move_to(self.plane.n2p(self.func(self.input_dot.get_value()))) 36 | 37 | def get_function_dots(self, func, *, input_config={}, output_config={}): 38 | input_dot = self.InputDot(self, **input_config) 39 | output_dot = self.OutputDot(self, input_dot, func, **output_config) 40 | 41 | return input_dot, output_dot 42 | 43 | 44 | class Euler(Scene): 45 | def construct(self): 46 | plane = ComplexPlaneWithFunctionDots() 47 | plane.add_coordinates(*plane.get_default_coordinate_values()) 48 | self.add(plane) 49 | 50 | theta_dot, z_dot = plane.get_function_dots( 51 | lambda z: np.exp(1j * z), 52 | input_config={"color": RED}, 53 | output_config={"color": YELLOW, "z_index": 1}, 54 | ) 55 | 56 | path = TracedPath(z_dot.get_center, min_distance_to_new_point=0) 57 | 58 | formula = MathTex("z", "=e^{i", r"\theta}").move_to(5.5 * LEFT + 2.5 * UP) 59 | formula[0].set_color(z_dot.get_color()) 60 | formula[2].set_color(theta_dot.get_color()) 61 | 62 | formula_box = Rectangle( 63 | width=formula.get_width() + MED_SMALL_BUFF, 64 | height=formula.get_height() + MED_SMALL_BUFF, 65 | ) 66 | formula_box.move_to(formula).set_fill(BLACK, opacity=1).set_stroke( 67 | BLUE, opacity=1, width=DEFAULT_STROKE_WIDTH / 2 68 | ) 69 | 70 | formula_group = VGroup(formula_box, formula) 71 | 72 | self.add(path, formula_group, theta_dot, z_dot) 73 | self.play(theta_dot.set_value, TAU, run_time=3) 74 | 75 | indicate_path = Circle(color=PINK) 76 | self.play(ShowCreationThenDestruction(indicate_path), run_time=3) 77 | 78 | self.play(theta_dot.set_value, 1j, run_time=3) 79 | self.play(Rotating(theta_dot, about_point=ORIGIN), run_time=3) 80 | self.play(theta_dot.move_to, plane.n2p(0)) 81 | -------------------------------------------------------------------------------- /2020_11_27_probability.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | import colour 3 | 4 | 5 | NUM_DOTS = 250 6 | FALL_TIME = 1 7 | RUNTIME = 2.5 8 | RED = "#FF4040" 9 | BLUE = "#4040FF" 10 | 11 | 12 | class EventMobject(VGroup): 13 | def __init__(self, probability, label, color=WHITE, y_coordinate=0): 14 | super().__init__() 15 | line = Line( 16 | start=LEFT * probability * config["frame_width"] * 0.5, 17 | end=RIGHT * probability * config["frame_width"] * 0.5, 18 | stroke_width=12, 19 | color=color, 20 | ) 21 | label = Tex(label, color=color).next_to(line, direction=DOWN) 22 | 23 | self.line = line 24 | self.label = label 25 | self.event_color = color 26 | 27 | self.add(self.line, self.label) 28 | 29 | def put_start_and_end_on(self, start, end): 30 | self.line.put_start_and_end_on(start, end) 31 | self.label.next_to(self.line, direction=DOWN) 32 | 33 | def set_label(self, label): 34 | self.label = Tex(label, color=self.event_color) 35 | self.submobjects[1] = self.label 36 | 37 | def set_color(self, color): 38 | super().set_color(color) 39 | self.event_color = color 40 | 41 | 42 | class Label(VDict): 43 | def __init__(self, label, color=BLUE): 44 | super().__init__() 45 | tex = Tex(label, color=color) 46 | label_background = Rectangle( 47 | height=tex.get_height() + MED_SMALL_BUFF, 48 | width=tex.get_width() + MED_SMALL_BUFF, 49 | stroke_opacity=0, 50 | fill_color=BLACK, 51 | fill_opacity=1, 52 | ).move_to(tex.get_center()) 53 | self.add({"background": label_background, "tex": tex}) 54 | 55 | 56 | class DraftScene(Scene): 57 | def construct(self): 58 | event_a = EventMobject(0.7, "$A$", color=BLUE).shift( 59 | UP * 0.1 * config["frame_height"] 60 | ) 61 | events = [event_a] 62 | 63 | p_a = Label("P($A$)").to_corner(UP + RIGHT) 64 | 65 | self.add(event_a, p_a) 66 | self.wait(0.5) 67 | 68 | self.raindrop_scene(events, p_a) 69 | 70 | p_not_a = Label("P($\\neg A$)").to_corner(UP + RIGHT) 71 | 72 | self.play( 73 | event_a.set_color, 74 | WHITE, 75 | FadeOutAndShift(p_a), 76 | FadeInFrom(p_not_a, UP), 77 | ) 78 | 79 | event_a.event_color = WHITE 80 | self.raindrop_scene(events, p_not_a, default_dot_color=BLUE) 81 | 82 | event_a_target = event_a.generate_target() 83 | event_a_target.put_start_and_end_on( 84 | UP * 0.2 * config["frame_height"] 85 | + LEFT * (0.5 * config["frame_width"] - 1), 86 | UP * 0.2 * config["frame_height"] + RIGHT * 2.5, 87 | ) 88 | event_a_target.set_color(BLUE) 89 | 90 | event_b = event_a_target.copy() 91 | event_b.set_label("$B$") 92 | event_b.put_start_and_end_on( 93 | UP * 0.0 * config["frame_height"] 94 | + RIGHT * (0.5 * config["frame_width"] - 1), 95 | UP * 0.0 * config["frame_height"] + LEFT * 2.5, 96 | ) 97 | events.append(event_b) 98 | p_a_or_b = Label("P($A \\cup B$)").to_corner(UP + RIGHT) 99 | 100 | self.play( 101 | MoveToTarget(event_a), 102 | FadeIn(event_b), 103 | FadeOutAndShift(p_not_a), 104 | FadeInFrom(p_a_or_b, UP), 105 | ) 106 | event_a.event_color = BLUE 107 | self.raindrop_scene(events, p_a_or_b) 108 | 109 | p_a_and_b = Label( 110 | "P($A \\cap B$)", color=interpolate_color(BLUE, RED, 0.5) 111 | ).to_corner(UP + RIGHT) 112 | self.play( 113 | event_b.set_color, 114 | RED, 115 | FadeOutAndShift(p_a_or_b), 116 | FadeInFrom(p_a_and_b, UP), 117 | ) 118 | event_b.event_color = RED 119 | 120 | self.raindrop_scene(events, p_a_and_b) 121 | 122 | p_a_given_b = Label( 123 | "P($A \\mid B$)", color=interpolate_color(BLUE, RED, 0.5) 124 | ).to_corner(UP + RIGHT) 125 | 126 | event_b_target = event_b.generate_target() 127 | event_b_target.put_start_and_end_on( 128 | UP * event_b.line.get_center()[1] + RIGHT * 0.5 * config["frame_width"], 129 | UP * event_b.line.get_center()[1] + LEFT * 0.5 * config["frame_width"], 130 | ) 131 | 132 | # Scale both events by the same amount. 133 | event_b_growth_ratio = ( 134 | event_b_target.line.get_length() / event_b.line.get_length() 135 | ) 136 | event_a_offset = event_a.line.get_right()[0] - event_b.line.get_right()[0] 137 | 138 | event_a_target = event_a.generate_target() 139 | event_a_right = UP * event_a.line.get_center()[1] + RIGHT * ( 140 | event_b_target.line.get_right()[0] + event_a_offset * event_b_growth_ratio 141 | ) 142 | event_a_target.put_start_and_end_on( 143 | event_a_right + LEFT * event_b_target.line.get_length(), 144 | event_a_right, 145 | ) 146 | event_a_target.label.move_to( 147 | UP * event_a.label.get_center()[1] 148 | + RIGHT 149 | * (event_a_target.get_right()[0] - config["frame_width"] * 0.5) 150 | * 0.5 151 | ) 152 | 153 | self.play( 154 | FadeOutAndShift(p_a_and_b), 155 | FadeInFrom(p_a_given_b, UP), 156 | MoveToTarget(event_b), 157 | MoveToTarget(event_a), 158 | ) 159 | 160 | self.raindrop_scene(events, p_a_given_b) 161 | 162 | self.wait() 163 | 164 | def raindrop_scene(self, events, label, default_dot_color=WHITE): 165 | upper_left_corner = ( 166 | UP * 0.5 * config["frame_height"] + LEFT * 0.5 * config["frame_width"] 167 | ) 168 | 169 | tracker = ValueTracker(0) 170 | tracker.add_updater(lambda t, dt: t.increment_value(dt)) 171 | self.add(tracker) 172 | 173 | # Reach the bottom of the screen in 1 second 174 | falling_rate = config["frame_height"] / FALL_TIME 175 | 176 | def fall(dot, dt): 177 | if tracker.get_value() < dot.falling_delay: 178 | return 179 | old_center = dot.get_center() 180 | dot.shift(DOWN * falling_rate * dt) 181 | new_center = dot.get_center() 182 | 183 | for event in events: 184 | if ( 185 | event.get_left()[0] < old_center[0] 186 | and old_center[0] < event.get_right()[0] 187 | and new_center[1] < event.line.get_center()[1] 188 | and event.line.get_center()[1] < old_center[1] 189 | ): 190 | dot_color = np.array(color.Color(dot.get_color()).get_rgb()) 191 | event_color = np.array(color.Color(event.event_color).get_rgb()) 192 | dot.color_array.append(event_color) 193 | new_color = interpolate( 194 | dot_color, event_color, 1 / len(dot.color_array) 195 | ) 196 | dot.set_color(colour.Color(rgb=new_color)) 197 | 198 | for _ in range(NUM_DOTS): 199 | dot = Dot(color=default_dot_color).move_to( 200 | upper_left_corner + random.random() * RIGHT * config["frame_width"] 201 | ) 202 | dot.shift(UP * dot.radius) 203 | dot.falling_delay = random.random() * RUNTIME 204 | dot.color_array = [] 205 | dot.add_updater(fall) 206 | self.add(dot) 207 | 208 | self.add(*events) 209 | self.add(label) 210 | self.wait(RUNTIME + FALL_TIME) 211 | -------------------------------------------------------------------------------- /2020_12_02_LabeledDot_and_Cutout_v0_1_1.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | import pkg_resources 3 | 4 | version_num = "0.1.1" 5 | 6 | 7 | class Ball(LabeledDot): 8 | CONFIG = {"radius": 0.4, "fill_opacity": 1, "color": BLUE} 9 | 10 | def __init__(self, lable=r"\alpha", **kwargs): 11 | LabeledDot.__init__(self, lable, **kwargs) 12 | self.velocity = np.array((2, 1.5, 0)) 13 | 14 | def get_top(self): 15 | return self.get_center()[1] + self.radius 16 | 17 | def get_bottom(self): 18 | return self.get_center()[1] - self.radius 19 | 20 | def get_right_edge(self): 21 | return self.get_center()[0] + self.radius 22 | 23 | def get_left_edge(self): 24 | return self.get_center()[0] - self.radius 25 | 26 | 27 | class Box(Rectangle): 28 | def __init__(self, **kwargs): 29 | Rectangle.__init__(self, height=6, width=8, color=GREEN_C, **kwargs) # Edges 30 | self.top = 0.5 * self.height 31 | self.bottom = -0.5 * self.height 32 | self.right_edge = 0.5 * self.width 33 | self.left_edge = -0.5 * self.width 34 | 35 | 36 | class TwitterScene(Scene): 37 | def construct(self): 38 | self.camera.background_color = "#ece6e2" 39 | version = Tex(f"v{version_num}").to_corner(UR).set_color(BLACK) 40 | self.add(version) 41 | 42 | box = Box() 43 | ball = Ball(lable=Text("v0.1.1").scale(0.3)) 44 | self.add(box) 45 | self.play(Write(ball)) 46 | 47 | def update_ball(ball, dt): 48 | ball.acceleration = np.array((0, -5, 0)) 49 | ball.velocity = ball.velocity + ball.acceleration * dt 50 | ball.shift(ball.velocity * dt) # Bounce off ground and roof 51 | if ball.get_bottom() <= box.bottom or ball.get_top() >= box.top: 52 | ball.velocity[1] = -ball.velocity[1] 53 | # Bounce off walls 54 | if ( 55 | ball.get_left_edge() <= box.left_edge 56 | or ball.get_right_edge() >= box.right_edge 57 | ): 58 | ball.velocity[0] = -ball.velocity[0] 59 | 60 | ball.add_updater(update_ball) 61 | self.add(ball) 62 | 63 | ball2 = Ball(lable=r"\Psi") 64 | self.play(Write(ball2)) 65 | ball2.add_updater(update_ball) 66 | 67 | ball3 = Ball(lable=r"\alpha") 68 | self.play(Write(ball3)) 69 | ball3.add_updater(update_ball) 70 | 71 | ball4 = Ball(lable=r"\lambda") 72 | self.play(Write(ball4)) 73 | ball4.add_updater(update_ball) 74 | 75 | self.wait(3) 76 | cu1 = Cutout( 77 | box, 78 | ball, 79 | ball2, 80 | ball3, 81 | ball4, 82 | fill_opacity=0.2, 83 | color=GREY, 84 | stroke_color=RED, 85 | ) 86 | self.add(cu1) 87 | cu1_small = cu1.copy().scale(0.3).to_edge(RIGHT).shift(2 * UP) 88 | self.play(Transform(cu1, cu1_small)) 89 | 90 | cu2 = Cutout( 91 | box, 92 | ball, 93 | ball2, 94 | ball3, 95 | ball4, 96 | fill_opacity=0.2, 97 | color=GREY, 98 | stroke_color=RED, 99 | ) 100 | self.add(cu2) 101 | cu2_small = cu2.copy().scale(0.3).to_edge(RIGHT) 102 | self.play(Transform(cu2, cu2_small)) 103 | 104 | self.wait() 105 | # 106 | banner = ManimBanner(dark_theme=False).scale(0.3).to_corner(DR) 107 | self.play(*[FadeOut(x) for x in self.mobjects], FadeIn(banner), run_time=2.5) 108 | self.play(banner.expand()) 109 | self.wait() 110 | -------------------------------------------------------------------------------- /2020_12_08_lissajous_curve.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | def lissajous_curve_func(t): 5 | return np.array((np.sin(3 * t), np.sin(4 * t) + 2 / 3 * PI, 0)) 6 | 7 | 8 | class TwitterScene(Scene): 9 | def construct(self): 10 | self.camera.background_color = "#ece6e2" 11 | dot = Dot() 12 | dummy_func = ParametricFunction(lissajous_curve_func, t_max=TAU, fill_opacity=0) 13 | dummy_func.scale(2).move_to(ORIGIN) 14 | func1 = dummy_func.copy().set_stroke(width=18) 15 | func1 = CurvesAsSubmobjects(func1) 16 | func1.set_color_by_gradient(YELLOW_A, YELLOW_D) 17 | func2 = dummy_func.copy().set_color(BLACK).set_stroke(width=20) 18 | dot.add_updater(lambda m: m.move_to(dummy_func.get_end())) 19 | dummy_func.set_opacity(0) 20 | # or dummy_func.fade(1) ) 21 | self.add(dot) 22 | self.play( 23 | ShowCreation(dummy_func), 24 | ShowCreation(func2), 25 | ShowCreation(func1), 26 | rate_func=linear, 27 | run_time=9, 28 | ) 29 | self.add(func1) 30 | self.wait() 31 | banner = ManimBanner(dark_theme=False).scale(0.3).to_corner(DR) 32 | self.play(FadeIn(banner)) 33 | self.play(banner.expand()) 34 | self.wait(3) 35 | -------------------------------------------------------------------------------- /2020_12_16_binary_search_tree.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | import random 3 | import math 4 | 5 | 6 | class BinarySearchTree(VGroup): 7 | def __init__( 8 | self, 9 | scene, 10 | levels=3, 11 | base_offset=0.5, 12 | node_radius=0.5, 13 | child_offset_factor=1.2, 14 | label_scale_factor=1, 15 | color_nodes=False, 16 | max_value=16, 17 | animation_runtime=0.2, 18 | insertion_initial_offset=1, 19 | ): 20 | super().__init__() 21 | self.scene = scene 22 | self.empty = True 23 | self.child_down_offset = DOWN * child_offset_factor 24 | self.child_left_offset = LEFT * base_offset * 2 * math.log2(levels) 25 | self.node_radius = node_radius 26 | self.label_scale_factor = label_scale_factor 27 | self.color_nodes = color_nodes 28 | self.max_value = max_value 29 | self.animation_runtime = animation_runtime 30 | self.insertion_initial_offset = insertion_initial_offset 31 | 32 | self.root = self.get_node(None) 33 | self.add(self.root) 34 | 35 | def get_node(self, value): 36 | node = VDict( 37 | { 38 | "node": Circle(radius=self.node_radius, color=WHITE), 39 | "label": MathTex("\\varnothing" if value is None else str(value)).scale( 40 | self.label_scale_factor 41 | ), 42 | } 43 | ) 44 | if self.label_scale_factor != 0: 45 | node["label"] = MathTex( 46 | "\\varnothing" if value is None else str(value) 47 | ).scale(self.label_scale_factor) 48 | if value is not None: 49 | node_color = interpolate_color(BLUE, RED, value / self.max_value) 50 | node.set_stroke(node_color) 51 | if self.color_nodes: 52 | node.set_fill(node_color, opacity=1) 53 | node.color = node_color 54 | node.value = value 55 | node.left_child = None 56 | node.right_child = None 57 | return node 58 | 59 | def insert(self, value): 60 | node = self.get_node(value) 61 | if self.root.value is None: 62 | node.move_to(self.root.get_center()) 63 | self.scene.play( 64 | FadeInFrom(node, UP * self.insertion_initial_offset), 65 | FadeOut(self.root), 66 | run_time=self.animation_runtime, 67 | ) 68 | self.remove(self.root) 69 | self.root = node 70 | self.add(node) 71 | self.empty = False 72 | return 73 | 74 | node.move_to(self.root.get_center() + UP * self.insertion_initial_offset) 75 | cur_node = self.root 76 | child_left_offset = self.child_left_offset.copy() 77 | while cur_node is not None: 78 | if node.value <= cur_node.value: 79 | self.scene.play( 80 | node.move_to, 81 | cur_node.get_center() + 2 * cur_node["node"].radius * LEFT, 82 | run_time=self.animation_runtime, 83 | ) 84 | if cur_node.left_child is not None: 85 | cur_node = cur_node.left_child 86 | else: 87 | child_location = ( 88 | cur_node.get_center() 89 | + self.child_down_offset 90 | + child_left_offset 91 | ) 92 | parent_child_vector = normalize( 93 | child_location - cur_node.get_center() 94 | ) 95 | 96 | edge_start = ( 97 | cur_node.get_center() + parent_child_vector * self.node_radius 98 | ) 99 | edge_end = child_location - parent_child_vector * self.node_radius 100 | edge = Line(edge_start, edge_end, stroke_color=node.color) 101 | 102 | self.scene.play( 103 | node.move_to, 104 | child_location, 105 | FadeIn(edge), 106 | run_time=self.animation_runtime, 107 | ) 108 | cur_node.left_child = node 109 | self.add(node, edge) 110 | break 111 | else: 112 | self.scene.play( 113 | node.move_to, 114 | cur_node.get_center() + 2 * cur_node["node"].radius * RIGHT, 115 | run_time=self.animation_runtime, 116 | ) 117 | if cur_node.right_child is not None: 118 | cur_node = cur_node.right_child 119 | else: 120 | child_location = ( 121 | cur_node.get_center() 122 | + self.child_down_offset 123 | - child_left_offset 124 | ) 125 | parent_child_vector = normalize( 126 | child_location - cur_node.get_center() 127 | ) 128 | 129 | edge_start = ( 130 | cur_node.get_center() + parent_child_vector * self.node_radius 131 | ) 132 | edge_end = child_location - parent_child_vector * self.node_radius 133 | edge = Line(edge_start, edge_end, stroke_color=node.color) 134 | 135 | self.scene.play( 136 | node.move_to, 137 | child_location, 138 | FadeIn(edge), 139 | run_time=self.animation_runtime, 140 | ) 141 | cur_node.right_child = node 142 | self.add(node, edge) 143 | break 144 | child_left_offset /= 2 145 | 146 | 147 | class DraftScene(Scene): 148 | def construct(self): 149 | tree = BinarySearchTree(self, base_offset=0.75, max_value=16).shift(UP * 2) 150 | self.add(tree) 151 | label = ( 152 | Text("Great for storing structured data.").scale(0.8).to_edge(UP, buff=0.1) 153 | ) 154 | self.add(label) 155 | 156 | nums = [8, 4, 2, 1, 3, 6, 5, 7, 12, 10, 9, 11, 14, 13, 15] 157 | for i in nums: 158 | tree.insert(i) 159 | 160 | self.wait(0.5) 161 | self.play(FadeOut(tree)) 162 | self.remove(label) 163 | 164 | # tree = BinarySearchTree( 165 | # self, 166 | # base_offset=0.9, 167 | # node_radius=0.05, 168 | # child_offset_factor=0.8, 169 | # label_scale_factor=0, 170 | # color_nodes=True, 171 | # max_value=31, 172 | # animation_runtime=0.05, 173 | # insertion_initial_offset=0.6 174 | # ).shift(UP * 2.5 + LEFT * 0.5) 175 | # self.add(tree) 176 | # self.add( 177 | # Text("Though random data can get ugly.").scale(0.8).to_edge(UP, buff=0.1) 178 | # ) 179 | 180 | # # Though random data can get ugly. 181 | # nums = [i + 1 for i in range(31)] 182 | # random.seed(0) 183 | # random.shuffle(nums) 184 | # for i in nums: 185 | # tree.insert(i) 186 | 187 | # self.wait() 188 | -------------------------------------------------------------------------------- /2020_12_21_orbit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from manim import * 3 | 4 | ORBIT_WIDTH = 3.2 5 | ORBIT_HEIGHT = 0.4 6 | PATH_SCALE_FACTOR = 0.5 7 | ORBIT_RATE = 0.23 8 | WAIT_TIME = 20 9 | EARTH_START_PROPORTION = 0.738 10 | SUN_MOVEMENT_RATE = 1.1 11 | CAMERA_LAG_TIME = 3.4 12 | DIAGRAM_STROKE_WIDTH = 2 13 | # EARTH_SUN_X_DISPLACEMENT_MIN = -1.637 14 | EARTH_SUN_X_DISPLACEMENT_MIN = -1.6094 15 | # EARTH_SUN_X_DISPLACEMENT_MAX = 1.561 16 | EARTH_SUN_X_DISPLACEMENT_MAX = 1.5904 17 | LABEL_SCALE_FACTOR = 0.35 18 | ARROW_SCALE_FACTOR = 0.4 19 | 20 | 21 | def normalize(v): 22 | norm = np.linalg.norm(v) 23 | if norm == 0: 24 | return v 25 | return v / norm 26 | 27 | 28 | class DraftScene(MovingCameraScene): 29 | def construct(self): 30 | # Earth 31 | orbit_path = Ellipse( 32 | width=ORBIT_WIDTH, 33 | height=ORBIT_HEIGHT, 34 | stroke_opacity=0, 35 | ) 36 | self.add(orbit_path) # TODO: Remove this 37 | 38 | v = ValueTracker() 39 | earth = Dot(color=BLUE, stroke_opacity=0).move_to( 40 | orbit_path.point_from_proportion(EARTH_START_PROPORTION) 41 | ) 42 | 43 | def orbit(earth): 44 | alpha = (EARTH_START_PROPORTION + v.get_value() * ORBIT_RATE) % 1 45 | earth.move_to(orbit_path.point_from_proportion(alpha)) 46 | 47 | earth.add_updater(orbit) 48 | self.add(earth) 49 | 50 | # Earth trail 51 | last_earth_sun_distance = 0 52 | 53 | def trail_earth(path, dt): 54 | path.add_line_to(earth.get_center()) 55 | 56 | # Update the camera here, since updaters can't be added to the 57 | # camera. 58 | if v.get_value() >= CAMERA_LAG_TIME: 59 | self.camera_frame.shift( 60 | normalize(sun_shift_direction) * SUN_MOVEMENT_RATE * dt 61 | ) 62 | 63 | earth_sun_x_displacement = (sun.get_center() - earth.get_center())[0] 64 | 65 | # if ( 66 | # abs(earth_sun_x_displacement - EARTH_SUN_X_DISPLACEMENT_MIN) < 0.001 67 | # or abs(earth_sun_x_displacement - EARTH_SUN_X_DISPLACEMENT_MAX) < 0.0015 68 | # ): 69 | if ( 70 | earth_sun_x_displacement < EARTH_SUN_X_DISPLACEMENT_MIN 71 | or earth_sun_x_displacement > EARTH_SUN_X_DISPLACEMENT_MAX 72 | ): 73 | diagram = VGroup() 74 | ellipse = Ellipse( 75 | width=ORBIT_WIDTH, 76 | height=ORBIT_HEIGHT, 77 | stroke_opacity=1, 78 | stroke_color=WHITE, 79 | stroke_width=DIAGRAM_STROKE_WIDTH, 80 | ).move_to(orbit_path.get_center()) 81 | 82 | sun_dot = Dot( 83 | color=WHITE, fill_opacity=0, stroke_width=DIAGRAM_STROKE_WIDTH 84 | ).move_to(ellipse.get_center()) 85 | 86 | sun_shine = VGroup() 87 | for theta in np.linspace(0, 2 * PI, num=8, endpoint=False): 88 | shine = Line( 89 | start=sun_dot.get_center() + sun_dot.radius * RIGHT, 90 | end=sun_dot.get_center() + (sun_dot.radius + 0.07) * RIGHT, 91 | stroke_width=DIAGRAM_STROKE_WIDTH, 92 | ) 93 | shine.rotate(theta, about_point=sun_dot.get_center()) 94 | sun_shine.add(shine) 95 | 96 | earth_dot = Dot( 97 | color=WHITE, fill_opacity=0, stroke_width=DIAGRAM_STROKE_WIDTH 98 | ).move_to(earth.get_center()) 99 | 100 | earth_axis_tilt_direction = normalize(UP * 1.5 + LEFT) 101 | earth_axis = Line( 102 | start=earth.get_center() + earth_axis_tilt_direction * 0.15, 103 | end=earth.get_center() - earth_axis_tilt_direction * 0.15, 104 | stroke_width=DIAGRAM_STROKE_WIDTH, 105 | ) 106 | self.add(earth_axis) 107 | 108 | earth_label = VGroup() 109 | if earth_sun_x_displacement < 0: 110 | date_tex = Tex( 111 | "JUNE 21", tex_template=TexFontTemplates.american_typewriter 112 | ) 113 | earth_tex = Tex( 114 | "EARTH", tex_template=TexFontTemplates.american_typewriter 115 | ).next_to(date_tex, DOWN) 116 | earth_label.add(date_tex, earth_tex) 117 | earth_label.scale(LABEL_SCALE_FACTOR) 118 | earth_label.next_to(earth, RIGHT, buff=0.1) 119 | else: 120 | earth_tex = Tex( 121 | "EARTH", tex_template=TexFontTemplates.american_typewriter 122 | ) 123 | date_tex = Tex( 124 | "DEC 21", tex_template=TexFontTemplates.american_typewriter 125 | ).next_to(earth_tex, DOWN) 126 | earth_label.add(date_tex, earth_tex) 127 | earth_label.scale(LABEL_SCALE_FACTOR) 128 | earth_label.next_to(earth, LEFT, buff=0.1) 129 | earth_north = ( 130 | Tex("N", tex_template=TexFontTemplates.american_typewriter) 131 | .scale(LABEL_SCALE_FACTOR) 132 | .next_to(earth_dot, earth_axis_tilt_direction, buff=0.1) 133 | ) 134 | earth_north.shift(RIGHT * 0.15) 135 | 136 | sun_label = ( 137 | Tex("SUN", tex_template=TexFontTemplates.american_typewriter) 138 | .scale(LABEL_SCALE_FACTOR) 139 | .next_to(sun, LEFT, buff=0.04) 140 | ) 141 | sun_label.shift(UP * 0.07) 142 | 143 | arrows = VGroup() 144 | right_arrow = Arrow( 145 | start=LEFT, end=RIGHT * 0.3, stroke_width=DIAGRAM_STROKE_WIDTH 146 | ) 147 | VMobject.scale(right_arrow, ARROW_SCALE_FACTOR) 148 | right_arrow.next_to(ellipse, DOWN, buff=0.1) 149 | right_arrow.shift(RIGHT * 0.1) 150 | arrows.add(right_arrow) 151 | 152 | left_arrow = Arrow( 153 | start=RIGHT, end=LEFT * 0.3, stroke_width=DIAGRAM_STROKE_WIDTH 154 | ) 155 | VMobject.scale(left_arrow, ARROW_SCALE_FACTOR) 156 | left_arrow.next_to(ellipse, UP, buff=0.1) 157 | left_arrow.shift(LEFT * 0.1) 158 | arrows.add(left_arrow) 159 | 160 | diagram.add( 161 | ellipse, 162 | sun_dot, 163 | earth_dot, 164 | earth_label, 165 | sun_label, 166 | arrows, 167 | sun_shine, 168 | earth_north, 169 | ) 170 | self.add(diagram) 171 | 172 | earth_orbit_alpha = ( 173 | EARTH_START_PROPORTION + v.get_value() * ORBIT_RATE 174 | ) % 1 175 | 176 | if any( 177 | # abs(earth_orbit_alpha - x) < 0.0075 # low quality 178 | abs(earth_orbit_alpha - x) < 0.0019 179 | for x in [0.15 + 0.25 * x for x in [0, 1, 2, 3]] 180 | ): 181 | line1 = Line( 182 | start=sun.get_center(), 183 | end=sun.get_center() 184 | + 0.6 * rotate_vector(-sun_shift_direction, -PI / 8), 185 | stroke_width=DIAGRAM_STROKE_WIDTH, 186 | ) 187 | line2 = Line( 188 | start=sun.get_center(), 189 | end=sun.get_center() 190 | + 0.6 * rotate_vector(-sun_shift_direction, PI / 8), 191 | stroke_width=DIAGRAM_STROKE_WIDTH, 192 | ) 193 | arrow = VGroup(line1, line2) 194 | self.add(arrow) 195 | 196 | # Don't label March when the animation first starts. 197 | if v.get_value() < 0.3: 198 | return 199 | 200 | # if abs(earth_orbit_alpha - 0.3) < 0.007: # low quality 201 | if abs(earth_orbit_alpha - 0.3) < 0.0019: # low quality 202 | self.add( 203 | Tex( 204 | "SETPEMBER 23", 205 | tex_template=TexFontTemplates.american_typewriter, 206 | ) 207 | .scale(LABEL_SCALE_FACTOR) 208 | .next_to(earth, RIGHT, buff=0.1) 209 | .shift(RIGHT * 0.5 + DOWN * 0.2) 210 | ) 211 | # elif abs(earth_orbit_alpha - 0.8) < 0.008: # low quality 212 | elif abs(earth_orbit_alpha - 0.8) < 0.002: # low quality 213 | self.add( 214 | Tex( 215 | "MARCH 21", 216 | tex_template=TexFontTemplates.american_typewriter, 217 | ) 218 | .scale(LABEL_SCALE_FACTOR) 219 | .next_to(earth, LEFT, buff=0.1) 220 | .shift(LEFT * 0.6 + DOWN * 0.15) 221 | ) 222 | 223 | earth_trail = VMobject(stroke_width=DIAGRAM_STROKE_WIDTH) 224 | earth_trail.points = np.array([earth.get_center()]) 225 | earth_trail.add_updater(trail_earth) 226 | self.add(earth_trail) 227 | 228 | # Sun 229 | sun_shift_direction = ORIGIN - earth.get_center() 230 | sun = Dot(color=YELLOW) 231 | always_shift(sun, normalize(sun_shift_direction), rate=SUN_MOVEMENT_RATE) 232 | always_shift(orbit_path, normalize(sun_shift_direction), rate=SUN_MOVEMENT_RATE) 233 | self.add(sun) 234 | 235 | # Sun trail 236 | original_earth_center = earth.get_center() 237 | sun_trail = Line( 238 | original_earth_center, sun.get_center(), stroke_width=DIAGRAM_STROKE_WIDTH 239 | ) 240 | 241 | def trail_sun(trail): 242 | trail.put_start_and_end_on(original_earth_center, sun.get_center()) 243 | 244 | sun_trail.add_updater(trail_sun) 245 | self.add(sun_trail) 246 | 247 | self.play(v.set_value, WAIT_TIME, run_time=WAIT_TIME, rate_func=linear) 248 | -------------------------------------------------------------------------------- /2021_01_01_new_year_post_parametric_firework.py: -------------------------------------------------------------------------------- 1 | class NewYearPost(MovingCameraScene): 2 | def construct(self): 3 | self.camera_frame.move_to(3 * UP) 4 | text = MathTex( 5 | r" s(t) &=\left( \begin{array}{c} " 6 | r"x(t)" 7 | r"\\ y(t)" 8 | r"\end{array} \right)" 9 | r"\\ &=\left( \begin{array}{c} " 10 | r"v_0 t \cos(\theta)" 11 | r"\\ v_0 t \sin(\theta) - \frac{1}{2}gt^2" 12 | r"\end{array} \right)" 13 | ) 14 | 15 | text.to_corner(DL).shift(3 * UP) 16 | 17 | def func(t): 18 | v0 = 10 19 | theta = 0.85 * PI / 2 20 | g = 9.81 21 | return np.array( 22 | (v0 * t * np.cos(theta), v0 * t * np.sin(theta) - 0.5 * g * t ** 2, 0) 23 | ) 24 | 25 | rocket = ParametricFunction(func, t_max=1, fill_opacity=0).set_color(WHITE) 26 | dot = Dot().set_color(WHITE) 27 | dot2 = Dot().set_color(WHITE).move_to(rocket.get_end()) 28 | self.add(dot) 29 | self.play(Write(rocket), rate_func=linear) 30 | self.add(dot2) 31 | all_sparcs = VGroup() 32 | for theta in np.random.uniform(0, TAU, 90): 33 | 34 | def func2(t): 35 | v0 = 10 36 | g = 9.81 37 | return np.array( 38 | ( 39 | v0 * t * np.cos(theta) + dot2.get_x(), 40 | v0 * t * np.sin(theta) - 0.5 * g * t ** 2 + dot2.get_y(), 41 | 0, 42 | ) 43 | ) 44 | 45 | sparcle = ParametricFunction( 46 | func2, t_min=0.04, t_max=0.3, fill_opacity=0 47 | ).set_color(ORANGE) 48 | all_sparcs.add((sparcle)) 49 | self.play( 50 | *[Write(x) for x in all_sparcs.submobjects], run_time=0.8, rate_func=linear 51 | ) 52 | dots = [ 53 | Dot(point=[x, y, 0]) 54 | for x, y in zip(np.random.uniform(-4, 4, 10), np.random.uniform(0, 6, 10)) 55 | ] 56 | self.play(*[Flash(dot) for dot in dots], lag_ratio=0.2) 57 | dots = [ 58 | Dot(point=[x, y, 0]) 59 | for x, y in zip(np.random.uniform(-4, 4, 10), np.random.uniform(0, 6, 10)) 60 | ] 61 | self.play(FadeIn(text), *[Flash(dot) for dot in dots], lag_ratio=0.2) 62 | dots = [ 63 | Dot(point=[x, y, 0]) 64 | for x, y in zip(np.random.uniform(-4, 4, 30), np.random.uniform(0, 6, 30)) 65 | ] 66 | self.play(*[Flash(dot) for dot in dots], lag_ratio=0.2) 67 | 68 | banner = ManimBanner(dark_theme=True).scale(0.3).to_corner(DR) 69 | self.play(FadeIn(banner.shift(3 * UP))) 70 | self.play(banner.expand()) 71 | self.play(FadeOut(banner)) 72 | -------------------------------------------------------------------------------- /2021_01_03_v0.2.0_release_tour.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class ReleaseV020(Scene): 5 | def construct(self): 6 | banner = ManimBanner().scale(0.35).to_edge(UP, buff=0.25).shift(RIGHT * 1.09375) 7 | self.play(FadeIn(banner)) 8 | self.play(banner.expand()) 9 | release_tour = MarkupText("v0.2.0 Release Tour") 10 | release_tour.next_to(banner, DOWN) 11 | self.play(Write(release_tour)) 12 | self.wait(1) 13 | boxes = [Square(side_length=config.frame_height / 3) for _ in range(6)] 14 | boxes_left = VGroup(*boxes[:3]).arrange(DOWN, buff=0).to_edge(LEFT, buff=0) 15 | boxes_right = VGroup(*boxes[3:]).arrange(DOWN, buff=0).to_edge(RIGHT, buff=0) 16 | self.play(AnimationGroup(*[FadeIn(box) for box in boxes], lag_ratio=0.1)) 17 | 18 | content_0 = ( 19 | VGroup( 20 | MarkupText( 21 | 'CONFIG dicts have been removed!' 22 | ).scale(0.7), 23 | Code("assets/2021_01_03/config_old.py"), 24 | MarkupText("is now written as").scale(0.5), 25 | Code("assets/2021_01_03/config_new.py"), 26 | ) 27 | .arrange(DOWN) 28 | .to_edge(DOWN, buff=0.25) 29 | ) 30 | self.play(Write(content_0), run_time=4) 31 | self.wait(1.5) 32 | self.play(content_0.animate.scale(0.27).move_to(boxes[0].get_center())) 33 | self.wait(0.25) 34 | 35 | content_1 = ( 36 | VGroup( 37 | MarkupText("New .animate syntax for method animations!").scale( 38 | 0.7 39 | ), 40 | Code("assets/2021_01_03/methodanim_old.py"), 41 | MarkupText("is now written as").scale(0.5), 42 | Code("assets/2021_01_03/methodanim_new.py"), 43 | Square(fill_color="#525893", fill_opacity=1), 44 | ) 45 | .scale(0.8) 46 | .arrange(DOWN) 47 | .to_edge(DOWN, buff=0.25) 48 | ) 49 | self.play(Write(content_1), run_time=3) 50 | self.wait(0.5) 51 | self.play(content_1[-1].animate.scale(0.65).rotate(-PI / 4)) 52 | self.wait(1.5) 53 | self.play(content_1.animate.scale(0.27).move_to(boxes[1].get_center())) 54 | self.wait(0.25) 55 | 56 | content_2 = ( 57 | Group( 58 | VGroup( 59 | MarkupText("New feature: plugin system!").scale(0.7), 60 | MarkupText( 61 | "Discover @ https://plugins.manim.community" 62 | ).scale(0.5), 63 | MarkupText("Example: manim-rubikscube").scale(0.5), 64 | ).arrange(DOWN), 65 | ImageMobject("assets/2021_01_03/rubikscube.png"), 66 | ) 67 | .arrange(DOWN) 68 | .to_edge(DOWN, buff=0.25) 69 | ) 70 | self.play(Write(content_2[0]), run_time=2) 71 | self.play(FadeIn(content_2[1])) 72 | self.wait(1.5) 73 | self.play(content_2.animate.scale(0.27).move_to(boxes[2].get_center())) 74 | self.wait(0.25) 75 | 76 | content_3 = ( 77 | VGroup( 78 | MarkupText("New feature: MarkupText").scale(0.7), 79 | Code("assets/2021_01_03/markup_example.py").scale(0.8), 80 | MarkupText("renders as").scale(0.5), 81 | VGroup( 82 | MarkupText("foo bar foobar"), 83 | MarkupText( 84 | "foo bar" "big small" 85 | ), 86 | MarkupText('colors'), 87 | ) 88 | .arrange(DOWN) 89 | .scale(0.8), 90 | ) 91 | .arrange(DOWN) 92 | .scale(0.7) 93 | .to_edge(DOWN, buff=0.25) 94 | ) 95 | self.play(Write(content_3), run_time=4) 96 | self.wait(1.5) 97 | self.play(content_3.animate.scale(0.27).move_to(boxes[3].get_center())) 98 | self.wait(0.25) 99 | 100 | import networkx as nx 101 | import numpy as np 102 | 103 | g = nx.erdos_renyi_graph(15, 0.3) 104 | circle_layout_g = dict( 105 | [(v, np.append(pos, 0)) for v, pos in nx.layout.circular_layout(g).items()] 106 | ) 107 | G = Graph(g.nodes, g.edges, layout_scale=2.5) 108 | content_4 = ( 109 | VGroup(MarkupText("New feature: Graph").scale(0.7), G) 110 | .arrange(DOWN) 111 | .to_edge(DOWN, buff=0.25) 112 | ) 113 | ct = G.get_center() 114 | self.play(Write(content_4), run_time=3) 115 | self.wait(0.5) 116 | 117 | self.play( 118 | *[G[v].animate.move_to(ct + 1.5 * circle_layout_g[v]) for v in G.vertices] 119 | ) 120 | self.wait(1.5) 121 | self.play(content_4.animate.scale(0.4).move_to(boxes[4].get_center())) 122 | self.wait(0.25) 123 | 124 | content_5 = ( 125 | VGroup( 126 | MarkupText("... many more bugfixes").scale(0.7), 127 | MarkupText("and improvements!").scale(0.7), 128 | ManimBanner().scale(0.4), 129 | MarkupText("Changelog:").scale(0.7), 130 | MarkupText( 131 | "https://docs.manim.community/en/v0.2.0/changelog.html" 132 | ).scale(0.3), 133 | ) 134 | .arrange(DOWN) 135 | .to_edge(DOWN, buff=0.25) 136 | ) 137 | self.play(Write(content_5), run_time=2) 138 | self.wait(1.5) 139 | self.play(content_5.animate.scale(0.4).move_to(boxes[5].get_center())) 140 | 141 | self.wait(0.25) 142 | group = VGroup(banner, release_tour) 143 | self.play(group.animate.move_to(ORIGIN)) 144 | self.play(*[FadeOut(mobj) for mobj in self.mobjects]) 145 | self.wait(0.25) 146 | -------------------------------------------------------------------------------- /2021_02_06_inverse_pythagorean_theorem.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class DraftScene(Scene): 5 | def construct(self): 6 | self.camera.background_color = WHITE 7 | 8 | def line_to_normal(line, rotation_direction="CLOCKWISE"): 9 | if rotation_direction == "CLOCKWISE": 10 | return normalize( 11 | rotate_vector(line.get_end() - line.get_start(), PI / 2) 12 | ) 13 | elif rotation_direction == "COUNTERCLOCKWISE": 14 | return normalize( 15 | rotate_vector(line.get_end() - line.get_start(), -PI / 2) 16 | ) 17 | else: 18 | raise Exception(rotation_direction) 19 | 20 | def get_h(triangle): 21 | h = Line( 22 | ORIGIN, 23 | ORIGIN 24 | + normalize( 25 | rotate_vector( 26 | triangle["c"].get_end() - triangle["c"].get_start(), PI / 2 27 | ) 28 | ), 29 | ) 30 | h.shift(triangle_points["C"] - h.get_start()) 31 | 32 | triangle_points["D"] = line_intersection( 33 | triangle["c"].get_start_and_end(), h.get_start_and_end() 34 | ) 35 | h_length = get_norm(triangle_points["D"] - triangle_points["C"]) 36 | 37 | h.put_start_and_end_on( 38 | h.get_start(), 39 | h.get_start() 40 | + normalize( 41 | rotate_vector( 42 | triangle["c"].get_end() - triangle["c"].get_start(), PI / 2 43 | ) 44 | ) 45 | * h_length, 46 | ) 47 | h.shift(triangle_points["C"] - h.get_start()) 48 | return h 49 | 50 | triangle_points = { 51 | "A": LEFT * 2.7 + UP * 1.7, 52 | "B": RIGHT * 2.7 + DOWN * 1.3, 53 | "C": LEFT * 2.7 + DOWN * 1.3, 54 | } 55 | lines = [ 56 | Line(triangle_points["A"], triangle_points["B"]), 57 | Line(triangle_points["B"], triangle_points["C"]), 58 | Line(triangle_points["C"], triangle_points["A"]), 59 | ] 60 | triangle = VDict( 61 | { 62 | "c": lines[0], 63 | "a": lines[1], 64 | "b": lines[2], 65 | "c_label": MathTex("c").move_to( 66 | lines[0].get_center() + line_to_normal(lines[0]) * 0.3 67 | ), 68 | "a_label": MathTex("a").move_to( 69 | lines[1].get_center() + line_to_normal(lines[1]) * 0.3 70 | ), 71 | "b_label": MathTex("b").move_to( 72 | lines[2].get_center() + line_to_normal(lines[2]) * 0.3 73 | ), 74 | } 75 | ) 76 | # self.play(ShowCreation(triangle)) 77 | self.play( 78 | ShowCreation(VGroup(triangle["a"], triangle["b"], triangle["c"])), 79 | ) 80 | self.play( 81 | Write( 82 | VGroup(triangle["a_label"], triangle["b_label"], triangle["c_label"]) 83 | ), 84 | ) 85 | self.wait(0.5) 86 | 87 | triangle.add({"h": get_h(triangle)}) 88 | triangle.add( 89 | { 90 | "h_label": MathTex("h").move_to( 91 | triangle["h"].get_center() 92 | + line_to_normal( 93 | triangle["h"], rotation_direction="COUNTERCLOCKWISE" 94 | ) 95 | * 0.3 96 | ) 97 | } 98 | ) 99 | self.play(ShowCreation(triangle["h"])) 100 | self.play(Write(triangle["h_label"])) 101 | 102 | def get_triangle_area(points, color, opacity=0.7): 103 | return ( 104 | Polygon(*points) 105 | .set_fill(color=color, opacity=opacity) 106 | .set_stroke(color=color) 107 | ) 108 | 109 | triangle_area_positions = { 110 | "ABC": UP * 2.5 + LEFT * 2, 111 | "ADC": UP * 2.5 + LEFT * 0.1, 112 | "BDC": UP * 2.5 + RIGHT * 1.6, 113 | } 114 | 115 | # Animate full triangle area. 116 | ABC_area = get_triangle_area( 117 | [triangle_points["A"], triangle_points["B"], triangle_points["C"]], BLUE 118 | ) 119 | self.play(FadeIn(ABC_area)) 120 | ABC_area.generate_target().scale(0.3).move_to(triangle_area_positions["ABC"]) 121 | self.play(MoveToTarget(ABC_area)) 122 | self.wait(0.5) 123 | 124 | ADC_area = get_triangle_area( 125 | [triangle_points["A"], triangle_points["D"], triangle_points["C"]], ORANGE 126 | ) 127 | ADC_area.generate_target().scale(0.3).move_to(triangle_area_positions["ADC"]) 128 | triangle_area_equals = MathTex("=").next_to(ABC_area.target, RIGHT, buff=0.1) 129 | 130 | BDC_area = get_triangle_area( 131 | [triangle_points["B"], triangle_points["D"], triangle_points["C"]], GREEN 132 | ) 133 | triangle_area_plus = MathTex("+").next_to(triangle_area_equals, RIGHT, buff=1.2) 134 | BDC_area.generate_target().scale(0.3).move_to(triangle_area_positions["BDC"]) 135 | 136 | # Animate partial triangle areas. 137 | self.play(FadeIn(ADC_area), FadeIn(BDC_area)) 138 | self.play( 139 | MoveToTarget(ADC_area), 140 | MoveToTarget(BDC_area), 141 | FadeIn(triangle_area_equals), 142 | FadeIn(triangle_area_plus), 143 | ) 144 | self.wait(0.8) 145 | 146 | half_a_b = MathTex("\\frac{1}{2}ab").move_to( 147 | ABC_area.get_center() + RIGHT * 0.3 148 | ) 149 | self.play(ReplacementTransform(ABC_area, half_a_b)) 150 | self.wait(0.3) 151 | 152 | short_leg_length = Line( 153 | triangle_points["D"], triangle_points["A"], stroke_color=ORANGE 154 | ) 155 | short_leg_variable = MathTex("x", color=ORANGE).move_to( 156 | triangle_points["A"] + (UP + LEFT) * 0.2 157 | ) 158 | self.play(ShowCreation(short_leg_length), FadeIn(short_leg_variable)) 159 | self.wait(0.3) 160 | 161 | half_h_x = MathTex("\\frac{1}{2}hx").move_to(ADC_area.get_center()) 162 | self.play(ReplacementTransform(ADC_area, half_h_x)) 163 | self.wait(0.3) 164 | 165 | long_leg_length = Line( 166 | triangle_points["D"], triangle_points["B"], stroke_color=GREEN 167 | ) 168 | long_leg_variable = MathTex("y", color=GREEN).move_to( 169 | triangle_points["B"] + (DOWN + RIGHT) * 0.2 170 | ) 171 | self.play(ShowCreation(long_leg_length), FadeIn(long_leg_variable)) 172 | self.wait(0.3) 173 | 174 | half_h_y = MathTex("\\frac{1}{2}hy").move_to(BDC_area.get_center() + LEFT * 0.2) 175 | self.play(ReplacementTransform(BDC_area, half_h_y)) 176 | self.wait(0.8) 177 | 178 | self.play( 179 | FadeOut(VGroup(half_a_b[0][0:3])), 180 | FadeOut(VGroup(half_h_x[0][0:3])), 181 | FadeOut(VGroup(half_h_y[0][0:3])), 182 | ) 183 | 184 | ab_equals_h_x_plus_y = MathTex("ab=h(x+y)").move_to(UP * 2.46) 185 | self.play( 186 | ReplacementTransform( 187 | VGroup(half_a_b[0][3:]), VGroup(ab_equals_h_x_plus_y[0][0:2]) 188 | ), 189 | ReplacementTransform(triangle_area_equals, ab_equals_h_x_plus_y[0][2]), 190 | ReplacementTransform(triangle_area_plus, ab_equals_h_x_plus_y[0][6]), 191 | ReplacementTransform(half_h_x[0][3], ab_equals_h_x_plus_y[0][3]), 192 | FadeIn(ab_equals_h_x_plus_y[0][4]), 193 | ReplacementTransform(half_h_x[0][4], ab_equals_h_x_plus_y[0][5]), 194 | ReplacementTransform(half_h_y[0][4], ab_equals_h_x_plus_y[0][7]), 195 | ReplacementTransform(half_h_y[0][3], ab_equals_h_x_plus_y[0][3]), 196 | FadeIn(ab_equals_h_x_plus_y[0][8]), 197 | ) 198 | self.wait(0.8) 199 | 200 | ab_equals_hc = MathTex("ab=hc") 201 | ab_equals_hc.shift( 202 | ab_equals_h_x_plus_y[0][0].get_center() - ab_equals_hc[0][0].get_center() 203 | ) 204 | self.play( 205 | ReplacementTransform(ab_equals_h_x_plus_y[0][0], ab_equals_hc[0][0]), 206 | ReplacementTransform(ab_equals_h_x_plus_y[0][1], ab_equals_hc[0][1]), 207 | ReplacementTransform(ab_equals_h_x_plus_y[0][2], ab_equals_hc[0][2]), 208 | ReplacementTransform(ab_equals_h_x_plus_y[0][3], ab_equals_hc[0][3]), 209 | ReplacementTransform( 210 | VGroup(ab_equals_h_x_plus_y[0][4:]), ab_equals_hc[0][4] 211 | ), 212 | FadeOut( 213 | VGroup( 214 | long_leg_length, 215 | long_leg_variable, 216 | short_leg_length, 217 | short_leg_variable, 218 | ) 219 | ), 220 | ) 221 | self.wait(0.5) 222 | 223 | ab_over_h_equals_c = MathTex("\\frac{ab}{h}=c").move_to( 224 | ab_equals_hc.get_center() 225 | ) 226 | self.play( 227 | ReplacementTransform( 228 | VGroup(ab_equals_hc[0][0:2]), VGroup(ab_over_h_equals_c[0][0:2]) 229 | ), 230 | FadeIn(ab_over_h_equals_c[0][2]), 231 | ReplacementTransform(ab_equals_hc[0][3], ab_over_h_equals_c[0][3]), 232 | ReplacementTransform(ab_equals_hc[0][2], ab_over_h_equals_c[0][4]), 233 | ReplacementTransform(ab_equals_hc[0][4], ab_over_h_equals_c[0][5]), 234 | ) 235 | self.wait(0.8) 236 | 237 | self.play( 238 | ab_over_h_equals_c.animate.shift(LEFT * 2), 239 | triangle.animate.shift(LEFT * 3), 240 | ) 241 | 242 | pythagorean_theorem_text = MathTex( 243 | "\\underline{\\text{Pythagorean Theorem}}" 244 | ).shift(RIGHT * 3 + UP * 3) 245 | pythagorean_theorem = MathTex("a^2 + b^2 = c^2").next_to( 246 | pythagorean_theorem_text, DOWN 247 | ) 248 | self.play(Write(pythagorean_theorem_text)) 249 | self.wait(0.5) 250 | self.play(Write(pythagorean_theorem), run_time=1.5) 251 | self.wait(0.8) 252 | 253 | pythagorean_substitution = MathTex( 254 | "a^2 + b^2 = \\left(\\frac{ab}{h}\\right)^2" 255 | ).next_to(pythagorean_theorem, DOWN, buff=0.1) 256 | self.play( 257 | ReplacementTransform( 258 | VGroup(pythagorean_theorem[0][:6]).copy(), 259 | VGroup(pythagorean_substitution[0][:6]), 260 | ), 261 | FadeIn( 262 | VGroup(pythagorean_substitution[0][6], pythagorean_substitution[0][11]) 263 | ), 264 | ReplacementTransform( 265 | VGroup(ab_over_h_equals_c[0][0:4]).copy(), 266 | VGroup(pythagorean_substitution[0][7:11]), 267 | ), 268 | ReplacementTransform( 269 | pythagorean_theorem[0][7], 270 | pythagorean_substitution[0][12], 271 | ), 272 | run_time=1.5, 273 | ) 274 | self.wait(0.8) 275 | 276 | pythagorean_substitution_2 = MathTex( 277 | "a^2", "+", "b^2", "=", "\\frac{a^2b^2}{h^2}" 278 | ).next_to(pythagorean_substitution, DOWN) 279 | self.play( 280 | # Transform squares 281 | ReplacementTransform( 282 | pythagorean_substitution[0][-1].copy(), pythagorean_substitution_2[4][1] 283 | ), 284 | ReplacementTransform( 285 | pythagorean_substitution[0][-1].copy(), pythagorean_substitution_2[4][3] 286 | ), 287 | ReplacementTransform( 288 | pythagorean_substitution[0][-1].copy(), pythagorean_substitution_2[4][6] 289 | ), 290 | ReplacementTransform( 291 | pythagorean_substitution[0][0].copy(), 292 | pythagorean_substitution_2[0][0], 293 | ), 294 | ReplacementTransform( 295 | pythagorean_substitution[0][1].copy(), 296 | pythagorean_substitution_2[0][1], 297 | ), 298 | ReplacementTransform( 299 | pythagorean_substitution[0][2].copy(), 300 | pythagorean_substitution_2[1][0], 301 | ), 302 | ReplacementTransform( 303 | pythagorean_substitution[0][3].copy(), 304 | pythagorean_substitution_2[2][0], 305 | ), 306 | ReplacementTransform( 307 | pythagorean_substitution[0][4].copy(), 308 | pythagorean_substitution_2[2][1], 309 | ), 310 | ReplacementTransform( 311 | pythagorean_substitution[0][5].copy(), 312 | pythagorean_substitution_2[3][0], 313 | ), 314 | ReplacementTransform( 315 | pythagorean_substitution[0][7].copy(), 316 | pythagorean_substitution_2[4][0], 317 | ), 318 | ReplacementTransform( 319 | pythagorean_substitution[0][8].copy(), 320 | pythagorean_substitution_2[4][2], 321 | ), 322 | ReplacementTransform( 323 | pythagorean_substitution[0][9].copy(), 324 | pythagorean_substitution_2[4][4], 325 | ), 326 | ReplacementTransform( 327 | pythagorean_substitution[0][10].copy(), 328 | pythagorean_substitution_2[4][5], 329 | ), 330 | ) 331 | self.wait(0.8) 332 | 333 | pythagorean_substitution_3 = MathTex( 334 | "\\frac{a^2}{a^2b^2}", "+", "\\frac{b^2}{a^2b^2}", "=", "\\frac{1}{h^2}" 335 | ).next_to(pythagorean_substitution_2, DOWN) 336 | self.play( 337 | ReplacementTransform( 338 | VGroup(pythagorean_substitution_2[4][:4]).copy(), 339 | VGroup(pythagorean_substitution_3[0][3:]), 340 | ), 341 | ReplacementTransform( 342 | VGroup(pythagorean_substitution_2[4][:4]).copy(), 343 | VGroup(pythagorean_substitution_3[2][3:]), 344 | ), 345 | ReplacementTransform( 346 | pythagorean_substitution_2[0][0].copy(), 347 | pythagorean_substitution_3[0][0], 348 | ), 349 | ReplacementTransform( 350 | pythagorean_substitution_2[0][1].copy(), 351 | pythagorean_substitution_3[0][1], 352 | ), 353 | FadeIn(pythagorean_substitution_3[0][2]), 354 | ReplacementTransform( 355 | pythagorean_substitution_2[1].copy(), pythagorean_substitution_3[1] 356 | ), 357 | ReplacementTransform( 358 | pythagorean_substitution_2[2][0].copy(), 359 | pythagorean_substitution_3[2][0], 360 | ), 361 | ReplacementTransform( 362 | pythagorean_substitution_2[2][1].copy(), 363 | pythagorean_substitution_3[2][1], 364 | ), 365 | FadeIn(pythagorean_substitution_3[2][2]), 366 | ReplacementTransform( 367 | pythagorean_substitution_2[3].copy(), pythagorean_substitution_3[3] 368 | ), 369 | FadeIn(pythagorean_substitution_3[4][0]), 370 | ReplacementTransform( 371 | pythagorean_substitution_2[4][4].copy(), 372 | pythagorean_substitution_3[4][1], 373 | ), 374 | ReplacementTransform( 375 | pythagorean_substitution_2[4][5].copy(), 376 | pythagorean_substitution_3[4][2], 377 | ), 378 | ReplacementTransform( 379 | pythagorean_substitution_2[4][6].copy(), 380 | pythagorean_substitution_3[4][3], 381 | ), 382 | ) 383 | self.wait(0.8) 384 | 385 | crossed_tex = [ 386 | VGroup(pythagorean_substitution_3[0][:2]), 387 | VGroup(pythagorean_substitution_3[0][3:5]), 388 | VGroup(pythagorean_substitution_3[2][:2]), 389 | VGroup(pythagorean_substitution_3[2][5:7]), 390 | ] 391 | crosses = [] 392 | for tex in crossed_tex: 393 | crosses.append(Line(tex.get_critical_point(DL), tex.get_critical_point(UR))) 394 | self.play(*[ShowCreation(cross) for cross in crosses]) 395 | self.wait(0.8) 396 | 397 | inverse_pythagorean_theorem = MathTex( 398 | "\\frac{1}{a^2} + \\frac{1}{b^2} = \\frac{1}{h^2}" 399 | ).next_to(pythagorean_substitution_3, DOWN) 400 | self.play(Write(inverse_pythagorean_theorem), run_time=3) 401 | self.play( 402 | AnimationOnSurroundingRectangle( 403 | inverse_pythagorean_theorem, 404 | ShowCreation, 405 | surrounding_rectangle_config={"color": BLACK}, 406 | ) 407 | ) 408 | 409 | # Position labels for each side. 410 | self.wait() 411 | -------------------------------------------------------------------------------- /2021_02_14_valentines.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class ValentineScene(GraphScene, MovingCameraScene): 5 | def setup(self): 6 | MovingCameraScene.setup(self) 7 | 8 | def construct(self): 9 | self.camera.background_color = WHITE 10 | self.axes_color = BLACK 11 | self.x_min = -PI 12 | self.x_max = PI 13 | self.x_axis_config = {"tick_frequency": PI / 2} 14 | self.x_axis_width = 10 15 | 16 | self.y_min = -3 17 | self.y_max = 3 18 | self.y_axis_height = 10 19 | self.graph_origin = ORIGIN 20 | 21 | self.camera_frame.scale(1.3) 22 | self.setup_axes() 23 | self.remove( 24 | self.x_axis, self.y_axis, self.y_axis_label_mob, self.x_axis_label_mob 25 | ) 26 | self.y_axis_label_mob.shift(0.4 * DOWN + 0.2 * RIGHT) 27 | self.play( 28 | Write( 29 | VGroup( 30 | self.x_axis, 31 | self.y_axis, 32 | self.y_axis_label_mob, 33 | self.x_axis_label_mob, 34 | ) 35 | ) 36 | ) 37 | 38 | axis_labels = [ 39 | MathTex("\\frac{\\pi}{2}") 40 | .scale(0.8) 41 | .move_to(self.coords_to_point(PI / 2, -0.35)), 42 | MathTex("-\\frac{\\pi}{2}") 43 | .scale(0.8) 44 | .move_to(self.coords_to_point(-PI / 2, -0.35)), 45 | ] 46 | 47 | # Label axes. 48 | self.play(*[Write(label) for label in axis_labels]) 49 | 50 | # Draw positive sin. 51 | positive_sin = self.get_graph( 52 | lambda x: np.sin(x) + 1, color=GRAY, x_min=-PI, x_max=PI 53 | ) 54 | positive_sin_label = ( 55 | MathTex("f(x) = ", "\\sin(x) + 1") 56 | .scale(0.7) 57 | .move_to(self.coords_to_point(-PI, 1.2)) 58 | ) 59 | self.play( 60 | Write(positive_sin_label), 61 | ShowCreation(positive_sin), 62 | ) 63 | self.wait(0.7) 64 | 65 | # Trace heart section. 66 | heart_red = "#e0245e" 67 | heart_stroke_width = 6 68 | 69 | def draw_positive_section(section, dt): 70 | section.total_time += dt 71 | section.total_time = min(section.total_time, 1) 72 | new_section = self.get_graph( 73 | lambda x: np.sin(x) + 1, 74 | color=heart_red, 75 | x_min=-PI / 2, 76 | x_max=-PI / 2 + PI * section.total_time, 77 | stroke_width=heart_stroke_width, 78 | ) 79 | section.become(new_section) 80 | 81 | positive_sin_heart_section = self.get_graph( 82 | lambda x: np.sin(x) + 1, 83 | color=heart_red, 84 | x_min=-PI / 2, 85 | x_max=-PI / 2, 86 | stroke_width=heart_stroke_width, 87 | ) 88 | positive_sin_heart_section.total_time = 0 89 | positive_sin_heart_section.add_updater(draw_positive_section) 90 | self.add(positive_sin_heart_section) 91 | 92 | self.wait() 93 | self.wait(0.7) 94 | 95 | # Draw negative sin. 96 | negative_sin = self.get_graph( 97 | lambda x: -np.sin(x) - 1, color=GRAY, x_min=-PI, x_max=PI 98 | ) 99 | negative_sin_label = ( 100 | MathTex("f(x) = ", "-\\sin(x) - 1") 101 | .scale(0.7) 102 | .move_to(self.coords_to_point(-PI, -1.2)) 103 | ) 104 | self.play( 105 | Write(negative_sin_label), 106 | ShowCreation(negative_sin), 107 | ) 108 | self.wait(0.7) 109 | 110 | # Trace heart section. 111 | def draw_negative_section(section, dt): 112 | section.total_time += dt 113 | section.total_time = min(section.total_time, 1) 114 | new_section = self.get_graph( 115 | lambda x: -np.sin(x) - 1, 116 | color=heart_red, 117 | x_min=-PI / 2, 118 | x_max=-PI / 2 + PI * section.total_time, 119 | stroke_width=heart_stroke_width, 120 | ) 121 | section.become(new_section) 122 | 123 | negative_sin_heart_section = self.get_graph( 124 | lambda x: -np.sin(x) - 1, 125 | color=heart_red, 126 | x_min=-PI / 2, 127 | x_max=PI / 2, 128 | stroke_width=heart_stroke_width, 129 | ) 130 | negative_sin_heart_section.total_time = 0 131 | negative_sin_heart_section.add_updater(draw_negative_section) 132 | self.add(negative_sin_heart_section) 133 | self.wait() 134 | self.wait(0.7) 135 | 136 | # Draw the positive circle. 137 | positive_circle = Circle( 138 | radius=self.y_axis_config["unit_size"], 139 | stroke_color=GRAY, 140 | ).move_to(self.coords_to_point(PI / 2, 1)) 141 | positive_circle_label = ( 142 | MathTex("\\left(x-\\frac{\\pi}{2}\\right)^2 + (y-1)^2 = 1") 143 | .scale(0.7) 144 | .next_to(positive_circle, UR, buff=0.1) 145 | .shift(LEFT * 0.8 + DOWN * 0.2) 146 | ) 147 | self.bring_to_back(positive_circle) 148 | self.play(ShowCreation(positive_circle), Write(positive_circle_label)) 149 | 150 | # Trace the heart section. 151 | def draw_positive_circle_section(section, dt): 152 | section.total_time += dt 153 | section.total_time = min(section.total_time, 1) 154 | new_section = Arc( 155 | radius=self.y_axis_config["unit_size"], 156 | start_angle=PI / 2, 157 | angle=-PI * section.total_time, 158 | color=heart_red, 159 | stroke_width=heart_stroke_width, 160 | ).move_arc_center_to(self.coords_to_point(PI / 2, 1)) 161 | section.become(new_section) 162 | 163 | positive_circle_heart_section = Arc( 164 | radius=self.y_axis_config["unit_size"], 165 | start_angle=PI / 2, 166 | angle=-PI, 167 | color=heart_red, 168 | stroke_width=heart_stroke_width, 169 | ).move_arc_center_to(self.coords_to_point(PI / 2, 1)) 170 | positive_circle_heart_section.total_time = 0 171 | self.add(positive_circle_heart_section) 172 | positive_circle_heart_section.add_updater(draw_positive_circle_section) 173 | self.wait() 174 | self.wait(0.7) 175 | 176 | # Draw the negative circle. 177 | negative_circle = Circle( 178 | radius=self.y_axis_config["unit_size"], 179 | stroke_color=GRAY, 180 | ).move_to(self.coords_to_point(PI / 2, -1)) 181 | negative_circle_label = ( 182 | MathTex("\\left(x-\\frac{\\pi}{2}\\right)^2 + (y+1)^2 = 1") 183 | .scale(0.7) 184 | .next_to(negative_circle, DR, buff=0.1) 185 | .shift(LEFT * 0.8 + UP * 0.2) 186 | ) 187 | self.bring_to_back(negative_circle) 188 | self.play(ShowCreation(negative_circle), Write(negative_circle_label)) 189 | 190 | # Trace the heart section. 191 | def draw_negative_circle_section(section, dt): 192 | section.total_time += dt 193 | section.total_time = min(section.total_time, 1) 194 | new_section = Arc( 195 | radius=self.y_axis_config["unit_size"], 196 | start_angle=-PI / 2, 197 | angle=PI * section.total_time, 198 | color=heart_red, 199 | stroke_width=heart_stroke_width, 200 | ).move_arc_center_to(self.coords_to_point(PI / 2, -1)) 201 | section.become(new_section) 202 | 203 | negative_circle_heart_section = Arc( 204 | radius=self.y_axis_config["unit_size"], 205 | start_angle=-PI / 2, 206 | angle=PI, 207 | color=heart_red, 208 | stroke_width=heart_stroke_width, 209 | ).move_arc_center_to(self.coords_to_point(PI / 2, 1)) 210 | negative_circle_heart_section.total_time = 0 211 | self.add(negative_circle_heart_section) 212 | negative_circle_heart_section.add_updater(draw_negative_circle_section) 213 | 214 | self.wait() 215 | self.wait(0.7) 216 | 217 | # Flip over y = x 218 | def inverse_function(func): 219 | flipped_func = func.copy() 220 | for i, point in enumerate(func.points): 221 | x, y, _ = point 222 | flipped_func.points[i] = self.coords_to_point(y * 2 / PI, x * PI / 5.2) 223 | return flipped_func 224 | 225 | graph_sections = [ 226 | positive_sin, 227 | positive_sin_heart_section, 228 | negative_sin, 229 | negative_sin_heart_section, 230 | positive_circle, 231 | positive_circle_heart_section, 232 | negative_circle, 233 | negative_circle_heart_section, 234 | ] 235 | for func in graph_sections: 236 | func.clear_updaters() 237 | 238 | transforms = [] 239 | for func in graph_sections: 240 | transforms.append(Transform(func, inverse_function(func))) 241 | 242 | graph_label_data = [ 243 | # f(x) = sin(x) + 1 244 | { 245 | "label": positive_sin_label, 246 | "offset": UP * 4.5 + RIGHT * 0.5, 247 | "inverse": ["f(x) = ", "-\\arcsin(-x + 1)"], 248 | }, 249 | # f(x) = sin(x) - 1 250 | { 251 | "label": negative_sin_label, 252 | "offset": UP * 4.5 + LEFT * 0.5, 253 | "inverse": ["f(x) = ", "-\\arcsin(x + 1)"], 254 | }, 255 | # \\left(x-\\frac{\\pi}{2}\\right)^2 + (y-1)^2 = 1 256 | { 257 | "label": positive_circle_label, 258 | "offset": DOWN * 4.1 + RIGHT * 0, 259 | "inverse": "\\left(y-\\frac{\\pi}{2}\\right)^2 + (x-1)^2 = 1", 260 | }, 261 | # \\left(x-\\frac{\\pi}{2}\\right)^2 + (y+1)^2 = 1 262 | { 263 | "label": negative_circle_label, 264 | "offset": DOWN * 4.1 + LEFT * 0, 265 | "inverse": "\\left(y-\\frac{\\pi}{2}\\right)^2 + (x+1)^2 = 1", 266 | }, 267 | ] 268 | animations = [] 269 | for i, data in enumerate(graph_label_data): 270 | label = data["label"] 271 | offset = data["offset"] 272 | inverse = data["inverse"] 273 | x, y, _ = label.get_center() 274 | target = label.generate_target() 275 | # Match the corresponding terms for the sin transformations. 276 | if i < 2: 277 | target_tex = MathTex(inverse[0], inverse[1]) 278 | else: 279 | target_tex = MathTex(inverse) 280 | target.become(target_tex) 281 | target.move_to(self.coords_to_point(y, x)).shift(offset) 282 | animations.append(MoveToTarget(label)) 283 | 284 | self.play(self.camera_frame.animate.scale(1.2), *transforms, *animations) 285 | self.wait(0.5) 286 | 287 | graph_sections = [ 288 | positive_sin, 289 | negative_sin, 290 | positive_circle, 291 | negative_circle, 292 | ] 293 | graph_labels = [data["label"] for data in graph_label_data] 294 | animations = [ 295 | FadeOut(mob) 296 | for mob in graph_sections 297 | + graph_labels 298 | + axis_labels 299 | + [self.x_axis, self.y_axis, self.x_axis_label_mob, self.y_axis_label_mob] 300 | ] 301 | self.play(*animations) 302 | 303 | heart_mob = VMobject( 304 | stroke_color=heart_red, 305 | fill_color=heart_red, 306 | stroke_width=heart_stroke_width, 307 | ) 308 | for i, graph_section in enumerate( 309 | [ 310 | positive_sin_heart_section, 311 | positive_circle_heart_section, 312 | negative_sin_heart_section, 313 | negative_circle_heart_section, 314 | ] 315 | ): 316 | heart_mob.append_points(graph_section.points) 317 | 318 | self.remove( 319 | positive_sin_heart_section, 320 | positive_circle_heart_section, 321 | negative_circle_heart_section, 322 | negative_sin_heart_section, 323 | ) 324 | self.add(heart_mob) 325 | self.play(heart_mob.animate.set_fill(opacity=1)) 326 | self.wait(0.5) 327 | 328 | heart_mob.generate_target().scale(0.15).shift(UP * 1.7) 329 | self.play(MoveToTarget(heart_mob)) 330 | 331 | message_front = ( 332 | Text("With", fill_color=BLACK).scale(1.5).next_to(heart_mob, LEFT) 333 | ) 334 | message_back = ( 335 | Text("from", fill_color=BLACK).scale(1.5).next_to(heart_mob, RIGHT) 336 | ) 337 | 338 | self.play(FadeIn(message_front), FadeIn(message_back)) 339 | 340 | banner_location = ( 341 | VGroup(message_front, heart_mob, message_back).get_center() + 3 * DOWN 342 | ) 343 | 344 | banner = ManimBanner(dark_theme=False).scale(0.9).move_to(banner_location) 345 | banner.scale_factor = 2.5 346 | self.play(banner.create()) 347 | 348 | for mob in [banner.triangle, banner.circle, banner.square]: 349 | mob.clear_updaters() 350 | self.bring_to_front(banner.M) 351 | 352 | # Position the targets of the M. 353 | banner.M.generate_target().shift(LEFT * banner.anim.get_width() * 0.57), 354 | 355 | # Position the anim based on the location of the target. 356 | banner.anim.next_to(banner.M.target, RIGHT, 0.1) 357 | banner.anim.align_to(banner.M.target, DOWN) 358 | 359 | self.play( 360 | banner.M.animate.shift(LEFT * banner.anim.get_width() * 0.57), 361 | banner.triangle.animate.shift(RIGHT * banner.anim.get_width() * 0.57), 362 | banner.square.animate.shift(RIGHT * banner.anim.get_width() * 0.57), 363 | banner.circle.animate.shift(RIGHT * banner.anim.get_width() * 0.57), 364 | AnimationGroup( 365 | ApplyMethod(VGroup(banner.anim[1], banner.anim[2]).set_opacity, 1), 366 | ApplyMethod(VGroup(banner.anim[0], banner.anim[-1]).set_opacity, 1), 367 | lag_ratio=0.3, 368 | ), 369 | ) 370 | 371 | self.wait() 372 | -------------------------------------------------------------------------------- /2021_02_26_pendulum.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class Pendulum(VGroup): 5 | def phi_fun(self, amplitude, acceleration, length, time): 6 | return amplitude * np.sin(np.sqrt(acceleration / length) * time - np.pi / 2) 7 | 8 | def __init__(self, weight, amplitude, acceleration, length, time): 9 | VGroup.__init__(self) 10 | self.sound_stamps_there = [] 11 | self.sound_stamps_back = [] 12 | 13 | self.amplitude = amplitude 14 | self.acceleration = acceleration 15 | self.length = length 16 | self.time = time 17 | self.phi = self.phi_fun(amplitude, acceleration, length, time) 18 | self.anchor = Dot(ORIGIN, color=RED) 19 | self.line = Line( 20 | ORIGIN, length * 1.8 * DOWN, stroke_width=1.6, stroke_opacity=0.2 21 | ) 22 | self.line.rotate(self.phi * DEGREES, about_point=self.line.get_start()) 23 | self.mass = Dot().set_color(BLUE).scale(1.4) 24 | self.mass.move_to(self.line.get_end()) 25 | self.mobj = VGroup(self.line, self.anchor, self.mass) 26 | self.add(self.mobj) 27 | 28 | def start(self): 29 | self.mobj.current_time = 0.000001 30 | 31 | def updater(mob, dt): 32 | mob.current_time += dt 33 | old_phi = self.phi_fun( 34 | self.amplitude, 35 | self.acceleration, 36 | self.length, 37 | mob.current_time - 2 * dt, 38 | ) 39 | new_phi = self.phi_fun( 40 | self.amplitude, self.acceleration, self.length, mob.current_time 41 | ) 42 | mob[0].rotate( 43 | (new_phi - self.phi) * DEGREES, about_point=self.line.get_start() 44 | ) 45 | if (old_phi > self.phi) & ( 46 | self.phi < new_phi 47 | ): # only used when sound is added. 48 | self.sound_stamps_there.append(mob.current_time) 49 | if (old_phi < self.phi) & (self.phi > new_phi): 50 | self.sound_stamps_there.append(mob.current_time) 51 | 52 | self.phi = new_phi 53 | self.mass.move_to(self.line.get_end()) 54 | 55 | self.mobj.add_updater(updater) 56 | 57 | 58 | class PendulumScene(Scene): 59 | def construct(self): 60 | g = 10 61 | oszilations = np.array([8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) 62 | period_length = 30 63 | times = 1 / oszilations * period_length 64 | lengths = (times / (2 * PI)) ** 2 * g 65 | total = len(lengths) 66 | pendulums1 = [] 67 | for i, val in enumerate(lengths): 68 | pendulum = Pendulum( 69 | weight=1, amplitude=32, acceleration=g, length=val, time=0 70 | ) 71 | if i % 2 == 0: 72 | pendulum.mass.set_color(GREEN) 73 | anchor_pos = pendulum.anchor.get_center() 74 | dest_pos = (-total / 2 + i) * 0.6 * RIGHT + 3.5 * UP 75 | pendulum.shift(anchor_pos + dest_pos) 76 | pendulums1.append(pendulum) 77 | self.add(pendulum) 78 | pendulums2 = [] 79 | for i, val in enumerate(lengths): 80 | pendulum = Pendulum( 81 | weight=1, amplitude=32, acceleration=10, length=val, time=0 82 | ) 83 | if i % 2 == 0: 84 | pendulum.mass.set_color(GREEN) 85 | anchor_pos = pendulum.anchor.get_center() 86 | dest_pos = 3.5 * UP 87 | pendulum.shift(anchor_pos + dest_pos) 88 | pendulums2.append(pendulum) 89 | self.wait() 90 | self.play(Transform(VGroup(*pendulums1), VGroup(*pendulums2))) 91 | self.wait(1) 92 | for el in pendulums1: 93 | el.start() 94 | self.wait(35) 95 | banner = ManimBanner(dark_theme=True).scale(0.3).to_corner(DR) 96 | self.play(FadeIn(banner)) 97 | self.play(banner.expand()) 98 | self.wait(30) 99 | -------------------------------------------------------------------------------- /2021_03_04_v0.4.0_release.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class V040(Scene): 5 | def construct(self): 6 | self.camera.background_color = "#ece6e2" 7 | positions = VGroup(*[Dot() for _ in range(6)]) 8 | positions.arrange_in_grid(n_rows=2, n_cols=3, buff=4) 9 | 10 | assets_path = "assets/2021_03_04/" 11 | world = SVGMobject(assets_path + "world.svg").scale(3) 12 | self.play(DrawBorderThenFill(world)) 13 | self.wait(0.5) 14 | self.play(world.animate.scale(0.4).move_to(positions[0])) 15 | 16 | rocket = SVGMobject(assets_path + "rocket-2.svg").scale(2) 17 | self.play(DrawBorderThenFill(rocket)) 18 | self.wait(0.5) 19 | self.play(rocket.animate.scale(0.5).move_to(positions[1])) 20 | 21 | blivet = SVGMobject(assets_path + "Blivet2.svg").scale(2) 22 | self.play(DrawBorderThenFill(blivet)) 23 | self.wait(0.5) 24 | self.play(blivet.animate.scale(0.5).move_to(positions[2])) 25 | 26 | tree = SVGMobject(assets_path + "tree.svg").scale(2.5) 27 | self.play(DrawBorderThenFill(tree)) 28 | self.wait(0.5) 29 | self.play(tree.animate.scale(0.75).move_to(positions[3])) 30 | 31 | knot = SVGMobject(assets_path + "present.svg").scale(1.5) 32 | self.play(DrawBorderThenFill(knot)) 33 | self.wait(0.5) 34 | self.play(knot.animate.scale(0.75).move_to(positions[4])) 35 | 36 | banner = ManimBanner(dark_theme=False).scale(0.3).move_to(positions[5]) 37 | banner.shift(RIGHT) 38 | self.play(FadeIn(banner)) 39 | self.play(banner.expand()) 40 | version = Tex( 41 | "\\textbf{v0.4.0}", 42 | tex_template=TexFontTemplates.gnu_freeserif_freesans, 43 | ) 44 | version = ( 45 | version.next_to(banner, DOWN).align_to(banner, RIGHT).set_color("#343434") 46 | ) 47 | self.play(Write(version)) 48 | self.wait(1) 49 | 50 | self.play(*[FadeOut(mobj) for mobj in self.mobjects]) 51 | self.wait(0.5) 52 | -------------------------------------------------------------------------------- /2021_03_11_simultaneous_lissajous.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class Lissajous(Scene): 5 | def construct(self): 6 | # Simultaneous lissajous curves. 7 | lissajous_size = 2 8 | lissajous_a = 1 9 | lissajous_b = 1 10 | lissajous_delta = PI / 4 11 | lissajous_rate = 5 12 | lissajous_alpha = ValueTracker() 13 | offset = PI / 2 14 | 15 | def lissajous_location(t, delta): 16 | A = lissajous_size 17 | a = lissajous_a 18 | b = lissajous_b 19 | x = A * np.sin(a * t + offset) 20 | y = A * np.sin(b * t + delta + offset) 21 | return x * RIGHT + y * UP 22 | 23 | def get_line_length(mob): 24 | length = 0 25 | start_anchors = mob.get_start_anchors() 26 | for i in range(len(start_anchors) - 1): 27 | length += get_norm(start_anchors[i + 1] - start_anchors[i]) 28 | return length 29 | 30 | def grow_line(mob): 31 | new_position = lissajous_location( 32 | lissajous_alpha.get_value() * mob.rate, mob.delta 33 | ) 34 | 35 | # Update line length. 36 | mob.add_line_to(new_position) 37 | mob.line_length += get_norm(new_position - mob.points[-1]) 38 | 39 | while get_line_length(mob) > mob.maximum_length: 40 | mob.set_points(mob.points[4:]) 41 | 42 | def get_lissajous_line(delta, rate): 43 | line = VMobject() 44 | line.delta = delta 45 | line.line_length = 0 46 | line.maximum_length = 8 47 | line.rate = rate 48 | line.points = np.array([lissajous_location(0, line.delta)]) 49 | line.add_updater(grow_line) 50 | return line 51 | 52 | self.add(get_lissajous_line(1 * PI / 8, 1).set_color(RED)) 53 | self.add(get_lissajous_line(2 * PI / 8, 2).set_color(ORANGE)) 54 | self.add(get_lissajous_line(3 * PI / 8, 3).set_color(YELLOW)) 55 | self.add(get_lissajous_line(4 * PI / 8, 4).set_color(GREEN)) 56 | self.add(get_lissajous_line(5 * PI / 8, 5).set_color(BLUE)) 57 | self.add(get_lissajous_line(6 * PI / 8, 6).set_color(BLUE_B)) 58 | self.add(get_lissajous_line(7 * PI / 8, 7).set_color(PURPLE)) 59 | 60 | self.play(lissajous_alpha.animate.set_value(20), run_time=32, rate_func=linear) 61 | -------------------------------------------------------------------------------- /2021_03_24_lenses.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class Lens(Scene): 5 | def construct(self): 6 | lens_height = 3 7 | lens_width = 0.5 8 | focal_length = 4 9 | 10 | lens = VGroup() 11 | lens.add( 12 | Line( 13 | UP * lens_height / 2 + LEFT * lens_width / 2, 14 | UP * lens_height / 2 + RIGHT * lens_width / 2, 15 | ) 16 | ) 17 | lens.add( 18 | Line( 19 | DOWN * lens_height / 2 + LEFT * lens_width / 2, 20 | DOWN * lens_height / 2 + RIGHT * lens_width / 2, 21 | ) 22 | ) 23 | lens_angle = TAU / 3 - 1.3 24 | left_arc = ( 25 | Arc(start_angle=-lens_angle / 2, angle=lens_angle) 26 | .rotate(PI) 27 | .set_height(lens_height) 28 | .align_to(lens[0], LEFT) 29 | ) 30 | left_arc.shift(LEFT * left_arc.get_width()) 31 | lens.add(left_arc) 32 | right_arc = ( 33 | Arc(start_angle=-lens_angle / 2, angle=lens_angle) 34 | .set_height(lens_height) 35 | .align_to(lens[0], RIGHT) 36 | ) 37 | right_arc.shift(RIGHT * right_arc.get_width()) 38 | lens.add(right_arc) 39 | 40 | original_dist_color = YELLOW 41 | image_dist_color = BLUE 42 | 43 | # Update the original. 44 | original_arrow = Arrow(ORIGIN, UP, buff=0).shift(3 * LEFT) 45 | original_arrow_tracker = ValueTracker(original_arrow.get_x()) 46 | 47 | def update_original_arrow(mob): 48 | time = original_arrow_tracker.get_value() 49 | x_offset = RIGHT * (-4 + np.sin(time * 1.3)) 50 | mob.move_to(x_offset) 51 | 52 | original_arrow.add_updater(update_original_arrow) 53 | 54 | # Update the arrow indicating the distance to the original. 55 | original_dist_arrow = Arrow( 56 | ORIGIN, RIGHT * original_arrow_tracker.get_value() 57 | ).set_color(original_dist_color) 58 | 59 | def update_original_dist_arrow(mob): 60 | original_arrow_x_offset = original_arrow.get_center()[0] 61 | mob.put_start_and_end_on(ORIGIN, RIGHT * original_arrow_x_offset) 62 | mob.shift(diagram_shift) 63 | 64 | original_dist_arrow.add_updater(update_original_dist_arrow) 65 | 66 | # Update the image arrow. 67 | image_arrow = Arrow( 68 | ORIGIN, 69 | DOWN, 70 | buff=0, 71 | ).shift(3 * RIGHT) 72 | 73 | def update_image(mob): 74 | object_lens_dist = original_arrow.get_center()[0] 75 | image_lens_dist = 1 / (1 / focal_length - 1 / object_lens_dist) 76 | magnification = image_lens_dist / object_lens_dist 77 | 78 | arrow_tip_height = mob.submobjects[0].get_height() 79 | new_arrow = Line( 80 | RIGHT * image_lens_dist + diagram_shift, 81 | RIGHT * image_lens_dist 82 | + UP * (magnification + arrow_tip_height) 83 | + diagram_shift, 84 | ) 85 | mob.submobjects[0].next_to(new_arrow, DOWN, buff=0) 86 | new_arrow.add(mob.submobjects[0]) 87 | mob.become(new_arrow) 88 | 89 | image_arrow.add_updater(update_image) 90 | 91 | # Update the arrow indicating the distance to the image. 92 | object_lens_dist = original_arrow.get_center()[0] 93 | image_lens_dist = 1 / (1 / focal_length - 1 / object_lens_dist) 94 | image_dist_arrow = Arrow(ORIGIN, RIGHT * image_lens_dist).set_color( 95 | image_dist_color 96 | ) 97 | 98 | def update_image_dist_arrow(mob): 99 | object_lens_dist = original_arrow.get_center()[0] 100 | image_lens_dist = 1 / (1 / focal_length - 1 / object_lens_dist) 101 | start = ORIGIN + diagram_shift 102 | end = RIGHT * image_lens_dist + diagram_shift 103 | mob.put_start_and_end_on(start, end) 104 | 105 | image_dist_arrow.add_updater(update_image_dist_arrow) 106 | 107 | self.add(original_arrow, image_arrow) 108 | self.add(lens) 109 | 110 | """ 111 | v = 1 / (1 / f - 1 / u) 112 | 1 / u + 1 / v = (n - 1)(1 / R1 - 1 / R2 + (n-1)d / (n*R1*R2)) 113 | (1 / u + 1 / v)/ (n-1) = 1 / R1 - 1 / R2 + (n-1)d / (n*R1*R2) 114 | (1 / u + 1 / v)/ (n-1) - 1/R1 + 1/R2 = (n-1)d / (n*R1*R2) 115 | (1/u + 1/v) / (n-1)^2 - (1/R1 + 1/R2)/(n-1) = d/(n*R1*R2) 116 | (n*R1*R2) * (1/u + 1/v) / (n-1)^2 - (1/R1 + 1/R2)/(n-1) = d 117 | """ 118 | 119 | image_lens_dist = 10 # This is only to set the arrow size. 120 | diagram_shift = DOWN * 0.5 121 | 122 | original_dist_arrow.add_updater(update_original_dist_arrow) 123 | self.add(original_dist_arrow) 124 | 125 | # Indicate the distance to the image arrow. 126 | 127 | self.add(image_dist_arrow) 128 | 129 | VGroup( 130 | lens, original_arrow, image_arrow, original_dist_arrow, image_dist_arrow 131 | ).shift(diagram_shift) 132 | 133 | description = ( 134 | Tex( 135 | "The distance between an object and its projected \\\\" 136 | "image is given by the thin lens equation: " 137 | ) 138 | .scale(0.7) 139 | .to_edge(UP) 140 | ) 141 | thin_lens_equation = ( 142 | MathTex( 143 | "\\frac{1}{u}", 144 | "+", 145 | "\\frac{1}{v}", 146 | "=", 147 | "\\frac{1}{f}", 148 | ) 149 | .scale(0.7) 150 | .next_to(description, DOWN) 151 | ) 152 | # Color the distance variables. 153 | thin_lens_equation[0][2].set_color(original_dist_color) 154 | thin_lens_equation[2][2].set_color(image_dist_color) 155 | u_label = MathTex("u", fill_color=original_dist_color).shift(LEFT + DOWN) 156 | v_label = MathTex("v", fill_color=image_dist_color).shift(RIGHT + DOWN) 157 | 158 | self.play( 159 | original_arrow_tracker.animate.set_value(3.5), 160 | Write(description), 161 | rate_func=linear, 162 | run_time=3.5, 163 | ) 164 | self.play( 165 | original_arrow_tracker.animate.set_value(4), 166 | rate_func=linear, 167 | run_time=0.5, 168 | ) 169 | self.play( 170 | original_arrow_tracker.animate.set_value(5.5), 171 | Write(thin_lens_equation), 172 | Write(VGroup(u_label, v_label)), 173 | rate_func=linear, 174 | run_time=1.5, 175 | ) 176 | self.play( 177 | original_arrow_tracker.animate.set_value(6.5), 178 | FadeOut(description), 179 | rate_func=linear, 180 | run_time=1, 181 | ) 182 | 183 | image_dist_arrow.set_color(GRAY) 184 | v_label.set_color(GRAY) 185 | thin_lens_equation[2][2].set_color(GRAY) 186 | fixed_distance_explanation = ( 187 | Tex( 188 | "If the distance to the image is fixed, the distance to the original " 189 | "will still change according to the lensmaker equation:" 190 | ) 191 | .scale(0.7) 192 | .to_edge(UP) 193 | ) 194 | self.wait(0.5) 195 | self.play(Write(fixed_distance_explanation)) 196 | 197 | lens_width_color = RED 198 | lensmaker_equation = ( 199 | MathTex( 200 | "(n-1)", 201 | "\\left[", 202 | "\\frac{1}{R_1}", 203 | "-", 204 | "\\frac{1}{R_2}", 205 | "+", 206 | "\\frac{(n-1)d}{nR_1R_2}", 207 | "\\right]", 208 | ) 209 | .scale(0.7) 210 | .next_to(thin_lens_equation[3], RIGHT) 211 | ) 212 | lensmaker_equation[6][5].set_color(lens_width_color) 213 | target = thin_lens_equation.generate_target() 214 | target.submobjects[4] = lensmaker_equation 215 | target.shift(RIGHT * -target.get_center()[1]) 216 | 217 | distance_label = ( 218 | DoubleArrow( 219 | LEFT * lens.get_width() / 2, RIGHT * lens.get_width() / 2, buff=0 220 | ) 221 | .next_to(lens, UP, buff=SMALL_BUFF) 222 | .set_color(lens_width_color) 223 | ) 224 | self.play( 225 | MoveToTarget( 226 | thin_lens_equation, 227 | ), 228 | FadeIn(distance_label), 229 | ) 230 | 231 | self.play(FadeOut(fixed_distance_explanation)) 232 | distance_explanation = ( 233 | Tex( 234 | "If the ", 235 | "thickness of the lens", 236 | " is allowed to vary with the distance to the object " 237 | "the image can be preserved at a fixed distance.", 238 | ) 239 | .scale(0.7) 240 | .to_edge(UP) 241 | ) 242 | distance_explanation[1].set_color(lens_width_color) 243 | image_dist_arrow.clear_updaters() 244 | image_arrow.clear_updaters() 245 | 246 | def update_image_2(mob): 247 | object_lens_dist = original_arrow.get_center()[0] 248 | image_lens_dist = 1 / (1 / focal_length - 1 / object_lens_dist) 249 | magnification = image_lens_dist / object_lens_dist 250 | 251 | original_top = mob.get_top() 252 | 253 | arrow_tip_height = mob.submobjects[0].get_height() 254 | new_arrow = Line( 255 | original_top, 256 | original_top + UP * (magnification + arrow_tip_height), 257 | ) 258 | mob.submobjects[0].next_to(new_arrow, DOWN, buff=0) 259 | new_arrow.add(mob.submobjects[0]) 260 | mob.become(new_arrow) 261 | 262 | image_arrow.add_updater(update_image_2) 263 | 264 | initial_x_offset = original_arrow.get_center()[0] 265 | 266 | def update_lens_thickness(mob): 267 | original_arrow_x_offset = original_arrow.get_center()[0] 268 | new_width = lens_width / original_arrow_x_offset * initial_x_offset 269 | original_lens_center = mob.get_center() 270 | lens = VGroup() 271 | lens.add( 272 | Line( 273 | UP * lens_height / 2 + LEFT * new_width / 2, 274 | UP * lens_height / 2 + RIGHT * new_width / 2, 275 | ) 276 | ) 277 | lens.add( 278 | Line( 279 | DOWN * lens_height / 2 + LEFT * new_width / 2, 280 | DOWN * lens_height / 2 + RIGHT * new_width / 2, 281 | ) 282 | ) 283 | lens_angle = TAU / 3 - 1.3 284 | right_arc = ( 285 | Arc(start_angle=-lens_angle / 2, angle=lens_angle) 286 | .set_height(lens_height) 287 | .align_to(lens[0], RIGHT) 288 | ) 289 | right_arc.shift(RIGHT * right_arc.get_width()) 290 | lens.add(right_arc) 291 | left_arc = ( 292 | Arc(start_angle=-lens_angle / 2, angle=lens_angle) 293 | .rotate(PI) 294 | .set_height(lens_height) 295 | .align_to(lens[0], LEFT) 296 | ) 297 | left_arc.shift(LEFT * left_arc.get_width()) 298 | lens.add(left_arc) 299 | lens.move_to(original_lens_center) 300 | mob.become(lens) 301 | 302 | # Update width label. 303 | arrow_y_offset = distance_label.get_start()[1] 304 | distance_label.put_start_and_end_on( 305 | LEFT * lens.get_width() / 2 + UP * arrow_y_offset, 306 | RIGHT * lens.get_width() / 2 + UP * arrow_y_offset, 307 | ) 308 | 309 | lens.add_updater(update_lens_thickness) 310 | 311 | self.play( 312 | original_arrow_tracker.animate.set_value(8), 313 | Write(distance_explanation), 314 | run_time=1.5, 315 | rate_func=linear, 316 | ) 317 | self.play( 318 | original_arrow_tracker.animate.set_value(10.5), 319 | run_time=2.5, 320 | rate_func=linear, 321 | ) 322 | for mob in self.mobjects: 323 | mob.clear_updaters() 324 | self.play( 325 | FadeOutAndShift( 326 | VGroup(*[m for m in self.mobjects if isinstance(m, VMobject)]), UP 327 | ) 328 | ) 329 | # 330 | # 331 | # class Eye(Scene): 332 | # def construct(self): 333 | def get_eye(): 334 | lens_height = 2 335 | lens_width = 0.6 336 | 337 | lens = Ellipse( 338 | width=lens_width, 339 | height=lens_height, 340 | fill_opacity=1, 341 | stroke_color=BLUE, 342 | fill_color="#EEEEEE", 343 | ) 344 | 345 | cilliary_muscle_length = 0.2 346 | cilliary_muscle = VGroup( 347 | Line( 348 | lens.get_top(), 349 | lens.get_top() + cilliary_muscle_length * UP, 350 | stroke_width=8, 351 | ).set_color(RED_E), 352 | Line( 353 | lens.get_bottom(), 354 | lens.get_bottom() + cilliary_muscle_length * DOWN, 355 | stroke_width=8, 356 | ).set_color(RED_E), 357 | ) 358 | 359 | vitreous_chamber_angle = 0.8 * TAU 360 | vitreous_chamber_back = Arc( 361 | angle=vitreous_chamber_angle, 362 | fill_opacity=1, 363 | fill_color=BLUE_C, 364 | stroke_color=RED_B, 365 | ) 366 | angle_to_rotate = (vitreous_chamber_angle - TAU) / 2 367 | vitreous_chamber_back.rotate( 368 | PI - angle_to_rotate, about_point=ORIGIN 369 | ).scale(2).shift(RIGHT * 1.7) 370 | 371 | # retina = Arc( 372 | # 0.8 * TAU, 373 | # stroke_color=RED_B, 374 | # ) 375 | # retina.rotate(PI - angle_to_rotate, about_point=ORIGIN,).scale( 376 | # 2 377 | # ).shift(RIGHT * 1.7) 378 | 379 | aqueous_humor_angle = TAU - vitreous_chamber_angle 380 | aqueous_humor = ( 381 | Arc(angle=aqueous_humor_angle, fill_opacity=1, stroke_opacity=0) 382 | .set_color(BLUE_A) 383 | .rotate(PI - aqueous_humor_angle / 2, about_point=ORIGIN) 384 | .scale(2.05) 385 | .next_to(vitreous_chamber_back, LEFT, buff=0) 386 | ) 387 | 388 | cornea_angle = 0.4 * TAU 389 | cornea = Arc( 390 | angle=cornea_angle, 391 | stroke_color="#EEEEEE", 392 | stroke_opacity=0.5, 393 | fill_color=BLUE_A, 394 | fill_opacity=1, 395 | stroke_width=14, 396 | ) 397 | cornea.rotate(PI - cornea_angle / 2, about_point=ORIGIN) 398 | cornea.next_to(vitreous_chamber_back, LEFT, buff=0) 399 | cornea.scale(1.2) 400 | 401 | eye = VGroup( 402 | # retina, 403 | cornea, 404 | vitreous_chamber_back, 405 | aqueous_humor, 406 | cilliary_muscle, 407 | lens, 408 | ) 409 | eye.lens = lens 410 | eye.shift(-eye.get_center()) 411 | return eye 412 | 413 | eye = get_eye() 414 | eye_text = Tex("Eye").scale(1.5).next_to(eye, UP) 415 | self.play( 416 | FadeInFrom(eye_text, UP), 417 | FadeInFrom(eye, UP), 418 | ) 419 | self.wait(0.5) 420 | 421 | saved_eye = eye.generate_target() # Save the eye to restore later. 422 | target_eye = eye.generate_target() 423 | target_eye.submobjects[0].shift(LEFT * 2) # cornea 424 | target_eye.submobjects[2].shift(LEFT * 2) # aqueous humor 425 | target_eye.submobjects[4].shift(RIGHT * 0.7) # lens 426 | target_eye.submobjects[1].shift(RIGHT * 2) # vitreous_chamber_back 427 | target_eye.submobjects[3].shift(LEFT * 0.5) # cilliary_muscle 428 | self.play( 429 | MoveToTarget(eye), 430 | FadeOutAndShift(eye_text, UP), 431 | ) 432 | 433 | cornea_label = Tex("Cornea").scale(0.8).next_to(target_eye.submobjects[0], LEFT) 434 | aqueous_humor_label = ( 435 | Tex("Aqueous Humor").scale(0.8).next_to(target_eye.submobjects[2], DOWN) 436 | ) 437 | lens_label = ( 438 | Tex("Lens", fill_color=BLUE) 439 | .scale(0.8) 440 | .next_to(target_eye.submobjects[4], DOWN) 441 | ) 442 | vitreous_chamber_label = ( 443 | Tex("Vitreous\\\\Chamber") 444 | .scale(0.8) 445 | .move_to(target_eye.submobjects[1].get_center()) 446 | ) 447 | cilliary_muscle_label = ( 448 | Tex("Cilliary\\\\Muscle").scale(0.8).next_to(target_eye.submobjects[3], UP) 449 | ) 450 | retina_label = ( 451 | Tex("Retina", fill_color=YELLOW) 452 | .scale(0.8) 453 | .next_to(target_eye.submobjects[1], RIGHT) 454 | ) 455 | self.play( 456 | FadeIn( 457 | VGroup( 458 | cornea_label, 459 | aqueous_humor_label, 460 | lens_label, 461 | vitreous_chamber_label, 462 | cilliary_muscle_label, 463 | retina_label, 464 | ) 465 | ), 466 | ) 467 | 468 | eye_lens_explanation = Tex( 469 | "This is how the ", 470 | "lenses", 471 | " in our eyes focus light on our ", 472 | "retinas", 473 | ).scale(0.9) 474 | eye_lens_explanation[1].set_color(BLUE) 475 | eye_lens_explanation[3].set_color(YELLOW) 476 | eye_lens_explanation.shift( 477 | UP 478 | * ( 479 | target_eye.submobjects[1].get_bottom()[1] 480 | - eye_lens_explanation.get_top()[1] 481 | - 0.8 482 | ) 483 | ) 484 | eye_lens_explanation_2 = ( 485 | Tex("which are always a fixed distance away.") 486 | .scale(0.9) 487 | .next_to(eye_lens_explanation, DOWN, buff=SMALL_BUFF) 488 | ) 489 | self.play(Write(eye_lens_explanation)) 490 | self.wait(0.5) 491 | 492 | eye.target = saved_eye 493 | self.play( 494 | MoveToTarget(eye), 495 | FadeOut( 496 | VGroup( 497 | cornea_label, 498 | aqueous_humor_label, 499 | lens_label, 500 | vitreous_chamber_label, 501 | cilliary_muscle_label, 502 | retina_label, 503 | ) 504 | ), 505 | ) 506 | self.play(eye.animate.shift(3 * RIGHT), run_time=0.7) 507 | 508 | original_arrow = Arrow(ORIGIN, UP * 0.8, buff=0).shift(3 * LEFT) 509 | image_arrow = Arrow(ORIGIN, DOWN * 0.7, buff=0).shift(4.8 * RIGHT) 510 | focal_axis = Line( 511 | original_arrow.get_bottom(), 512 | image_arrow.get_top(), 513 | stroke_width=3, 514 | stroke_color=GREY_B, 515 | ) 516 | self.play(FadeIn(VGroup(original_arrow, image_arrow))) 517 | self.play(ShowCreation(focal_axis), run_time=0.7) 518 | 519 | original_arrow_tracker = ValueTracker() 520 | 521 | # Update the original arrow. 522 | original_arrow_starting_position = original_arrow.get_center() 523 | 524 | def update_original(mob): 525 | time = original_arrow_tracker.get_value() 526 | x_offset = 1.5 * RIGHT * np.sin(time * 1.5) 527 | mob.move_to(original_arrow_starting_position + RIGHT * x_offset) 528 | 529 | original_arrow.add_updater(update_original) 530 | 531 | lens = eye.submobjects[4] 532 | original_image_height = image_arrow.get_height() 533 | object_lens_dist = lens.get_center()[0] - original_arrow.get_center()[0] 534 | image_lens_dist = image_arrow.get_center()[0] - lens.get_center()[0] 535 | original_magnification = image_lens_dist / object_lens_dist 536 | magnification_offset_ratio = original_image_height / original_magnification 537 | 538 | def update_image(mob): 539 | lens = eye.submobjects[4] 540 | object_lens_dist = lens.get_center()[0] - original_arrow.get_center()[0] 541 | image_lens_dist = image_arrow.get_center()[0] - lens.get_center()[0] 542 | magnification = image_lens_dist / object_lens_dist 543 | magnification *= magnification_offset_ratio 544 | image_arrow_base = image_arrow.get_top() 545 | 546 | arrow_tip_height = mob.submobjects[0].get_height() 547 | new_arrow = Line( 548 | image_arrow_base, 549 | image_arrow_base + DOWN * (magnification - arrow_tip_height), 550 | ) 551 | mob.submobjects[0].next_to(new_arrow, DOWN, buff=0) 552 | new_arrow.add(mob.submobjects[0]) 553 | mob.become(new_arrow) 554 | 555 | image_arrow.add_updater(update_image) 556 | 557 | # Update the thickness of the lens. 558 | starting_lens_dist = eye.lens.get_center()[0] - original_arrow.get_center()[0] 559 | 560 | def update_lens(mob): 561 | original_lens_dist = ( 562 | eye.lens.get_center()[0] - original_arrow.get_center()[0] 563 | ) 564 | mob.stretch_to_fit_width(0.6 * starting_lens_dist / original_lens_dist) 565 | 566 | lens = eye.lens 567 | lens.add_updater(update_lens) 568 | 569 | def update_axis(mob): 570 | new_axis = Line( 571 | original_arrow.get_bottom(), 572 | image_arrow.get_top(), 573 | stroke_width=3, 574 | stroke_color=GREY_B, 575 | ) 576 | mob.become(new_axis) 577 | 578 | focal_axis.add_updater(update_axis) 579 | 580 | self.play( 581 | Write(eye_lens_explanation_2), 582 | original_arrow_tracker.animate.set_value(1), 583 | rate_func=linear, 584 | run_time=1, 585 | ) 586 | self.play( 587 | original_arrow_tracker.animate.set_value(6), 588 | rate_func=linear, 589 | run_time=5, 590 | ) 591 | -------------------------------------------------------------------------------- /2021_04_10_modular_multiplier.py: -------------------------------------------------------------------------------- 1 | import manim as mn 2 | import numpy as np 3 | 4 | def regular_vertices(n, *, radius=1, start_angle=None): 5 | if start_angle is None: 6 | if n % 2 == 0: 7 | start_angle = 0 8 | else: 9 | start_angle = mn.TAU / 4 10 | 11 | start_vector = mn.rotate_vector(mn.RIGHT * radius, start_angle) 12 | vertices = mn.compass_directions(n, start_vector) 13 | 14 | return vertices, start_angle 15 | 16 | class Star(mn.Polygon): 17 | def __init__(self, n=5, *, density=2, outer_radius=1, inner_radius=None, start_angle=None, **kwargs): 18 | if density <= 0 or density >= n / 2: 19 | raise ValueError(f"Incompatible density {density}") 20 | 21 | inner_angle = mn.TAU / (2 * n) 22 | 23 | if inner_radius is None: 24 | # Calculate the inner radius for n and density. 25 | # See https://math.stackexchange.com/a/2136292 26 | 27 | outer_angle = mn.TAU * density / n 28 | 29 | inverse_x = 1 - np.tan(inner_angle) * ((np.cos(outer_angle) - 1) / np.sin(outer_angle)) 30 | 31 | inner_radius = outer_radius / (np.cos(inner_angle) * inverse_x) 32 | 33 | outer_vertices, self.start_angle = regular_vertices(n, radius=outer_radius, start_angle=start_angle) 34 | inner_vertices, _ = regular_vertices(n, radius=inner_radius, start_angle=self.start_angle + inner_angle) 35 | 36 | vertices = [] 37 | for pair in zip(outer_vertices, inner_vertices): 38 | vertices.extend(pair) 39 | 40 | super().__init__(*vertices, **kwargs) 41 | 42 | class ModularMultiplier(mn.Group): 43 | def __init__(self, mobject, *, modulus, factor, rate_func=mn.linear, line_config=None, **kwargs): 44 | super().__init__(**kwargs) 45 | 46 | self.mobject = mobject.copy() 47 | self.modulus = modulus 48 | self.factor = mn.ValueTracker(factor) 49 | self.rate_func = rate_func 50 | 51 | self.add(self.mobject) 52 | 53 | if line_config is None: 54 | line_config = {} 55 | 56 | self.line_config = line_config 57 | self.init_lines() 58 | mn.always(self.update_lines) 59 | 60 | def number_to_point(self, n): 61 | n %= self.modulus 62 | n /= self.modulus 63 | 64 | return self.mobject.point_from_proportion(self.rate_func(n)) 65 | 66 | def n2p(self, n): 67 | return self.number_to_point(n) 68 | 69 | def init_lines(self): 70 | self.lines = mn.VGroup() 71 | self.add_to_back(self.lines) 72 | 73 | factor = self.factor.get_value() 74 | for n in range(self.modulus): 75 | n_point = self.n2p(n) 76 | mult_point = self.n2p(n * factor) 77 | 78 | line = mn.Line(n_point, mult_point, **self.line_config) 79 | self.lines.add(line) 80 | 81 | def update_lines(self): 82 | factor = self.factor.get_value() 83 | 84 | for n, line in enumerate(self.lines): 85 | n_point = self.n2p(n) 86 | mult_point = self.n2p(n * factor) 87 | 88 | line.set_start_and_end_attrs(n_point, mult_point) 89 | line.generate_points() 90 | 91 | class ModularScene(mn.Scene): 92 | def construct(self): 93 | mod = ModularMultiplier( 94 | Star(outer_radius=3, color=mn.BLUE), 95 | 96 | modulus = 300, 97 | factor = 1, 98 | 99 | line_config = { 100 | "stroke_width": 1, 101 | }, 102 | ) 103 | 104 | mod_var = mn.Tex("Modulus", "$=$", f"${mod.modulus}$") 105 | factor_var = mn.Variable(mod.factor.get_value(), mn.Tex("Factor")) 106 | 107 | # Arrange mod_var so it lines up well with factor_var's elements 108 | mod_var[2].align_to(factor_var, mn.UR) 109 | mod_var[1].next_to(factor_var.label[0], mn.RIGHT) 110 | mod_var[0].next_to(mod_var[1], mn.LEFT) 111 | mod_var.shift((factor_var.height + 0.25) * mn.UP) 112 | 113 | factor_var.add_updater(lambda v: v.tracker.set_value(mod.factor.get_value())) 114 | 115 | info = mn.VGroup(mod_var, factor_var) 116 | info.to_corner(mn.UR) 117 | 118 | self.add(info, mod) 119 | 120 | self.animate_factors(mod, [2, 1]) 121 | 122 | def animate_factors(self, mod, iterable, *, wait=0.3, run_time=2, **kwargs): 123 | for f in iterable: 124 | self.play(mod.factor.animate.set_value(f), run_time=run_time, **kwargs) 125 | self.wait(wait) 126 | -------------------------------------------------------------------------------- /2021_05_5_release0_6_0.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 13, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from manim import *" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 29, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stderr", 19 | "output_type": "stream", 20 | "text": [ 21 | " \r" 22 | ] 23 | }, 24 | { 25 | "data": { 26 | "text/html": [ 27 | "" 30 | ], 31 | "text/plain": [ 32 | "" 33 | ] 34 | }, 35 | "metadata": {}, 36 | "output_type": "display_data" 37 | } 38 | ], 39 | "source": [ 40 | "%%manim -v WARNING --disable_caching -qm Example1\n", 41 | "\n", 42 | "class Example1(Scene):\n", 43 | " def construct(self):\n", 44 | " self.add(Title(\"Manim Version 0.6.0\"))\n", 45 | " func = lambda pos: ((pos[0]*UR+pos[1]*LEFT) - pos)/3\n", 46 | " f1= StreamLines(func).scale(0.5)\n", 47 | " f1t=Text(\"Reworked StreamLines\").next_to(f1,DOWN)\n", 48 | " self.play(f1.create(), run_time=1)\n", 49 | " self.play(FadeIn(f1t))\n", 50 | " self.play(FadeOut(f1), FadeOut(f1t))" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 37, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stderr", 60 | "output_type": "stream", 61 | "text": [ 62 | " \r" 63 | ] 64 | }, 65 | { 66 | "data": { 67 | "text/html": [ 68 | "" 71 | ], 72 | "text/plain": [ 73 | "" 74 | ] 75 | }, 76 | "metadata": {}, 77 | "output_type": "display_data" 78 | } 79 | ], 80 | "source": [ 81 | "%%manim -v WARNING --disable_caching -qm LineGraphExample\n", 82 | "\n", 83 | "class LineGraphExample(Scene):\n", 84 | " def construct(self):\n", 85 | " self.add(Title(\"Manim Version 0.6.0\"))\n", 86 | "\n", 87 | " plane = NumberPlane(\n", 88 | " x_range = (0, 7),\n", 89 | " y_range = (0, 5),\n", 90 | " x_length = 7,\n", 91 | " axis_config={\"include_numbers\": True},\n", 92 | " )\n", 93 | " plane.center()\n", 94 | " line_graph = plane.get_line_graph(\n", 95 | " x_values = [0, 1.5, 2, 2.8, 4, 6.25],\n", 96 | " y_values = [1, 3, 2.25, 4, 2.5, 1.75],\n", 97 | " line_color=GOLD_E,\n", 98 | " vertex_dot_style=dict(stroke_width=3, fill_color=PURPLE),\n", 99 | " stroke_width = 4,\n", 100 | " )\n", 101 | " f2 = VGroup() + plane + line_graph\n", 102 | " f2t=Text(\"LineGraph\").next_to(f2,DOWN)\n", 103 | " self.play(FadeIn(f2),FadeIn(f2t))\n", 104 | " self.wait(1)\n", 105 | " self.play(FadeOut(f2), FadeOut(f2t))\n", 106 | " " 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 52, 112 | "metadata": {}, 113 | "outputs": [ 114 | { 115 | "name": "stderr", 116 | "output_type": "stream", 117 | "text": [ 118 | " \r" 119 | ] 120 | }, 121 | { 122 | "data": { 123 | "text/html": [ 124 | "" 127 | ], 128 | "text/plain": [ 129 | "" 130 | ] 131 | }, 132 | "metadata": {}, 133 | "output_type": "display_data" 134 | } 135 | ], 136 | "source": [ 137 | "%%manim -v WARNING --disable_caching -qm PolyhedronSubMobjects\n", 138 | "\n", 139 | "class PolyhedronSubMobjects(ThreeDScene):\n", 140 | " def construct(self):\n", 141 | " self.add_fixed_in_frame_mobjects(Title(\"Manim Version 0.6.0\"))\n", 142 | " self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)\n", 143 | " self.begin_ambient_camera_rotation(rate=0.2)\n", 144 | " self.wait()\n", 145 | "\n", 146 | " octahedron = Octahedron(edge_length = 3)\n", 147 | " octahedron.graph[0].set_color(RED)\n", 148 | " octahedron.faces[2].set_color(YELLOW)\n", 149 | " f3=octahedron\n", 150 | " f3t=Text(\"Octahedron\")\n", 151 | " self.add_fixed_in_frame_mobjects(f3t.to_edge(DOWN))\n", 152 | " self.play(FadeIn(f3))\n", 153 | " self.wait(1)\n", 154 | " self.remove(f3t)\n", 155 | " self.play(FadeOut(f3))\n", 156 | "\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 60, 162 | "metadata": {}, 163 | "outputs": [ 164 | { 165 | "name": "stderr", 166 | "output_type": "stream", 167 | "text": [ 168 | " \r" 169 | ] 170 | }, 171 | { 172 | "data": { 173 | "text/html": [ 174 | "" 177 | ], 178 | "text/plain": [ 179 | "" 180 | ] 181 | }, 182 | "metadata": {}, 183 | "output_type": "display_data" 184 | } 185 | ], 186 | "source": [ 187 | "%%manim -v WARNING --disable_caching -qm Indications\n", 188 | "\n", 189 | "class Indications(Scene):\n", 190 | " def construct(self):\n", 191 | " self.add(Title(\"Manim Version 0.6.0\"))\n", 192 | " f4t= Text(\"New Indications\").to_edge(DOWN)\n", 193 | " self.play(FadeIn(f4t))\n", 194 | " indications = [ApplyWave,Circumscribe,Flash,FocusOn,Indicate,ShowPassingFlash,Wiggle]\n", 195 | " names = [Tex(i.__name__).scale(3) for i in indications]\n", 196 | "\n", 197 | " self.add(names[0])\n", 198 | " for i in range(len(names)):\n", 199 | " if indications[i] is Flash:\n", 200 | " self.play(Flash(UP))\n", 201 | " elif indications[i] is ShowPassingFlash:\n", 202 | " self.play(ShowPassingFlash(Underline(names[i])))\n", 203 | " else:\n", 204 | " self.play(indications[i](names[i]))\n", 205 | " self.play(AnimationGroup(\n", 206 | " FadeOutAndShift(names[i], UP*1.5),\n", 207 | " FadeInFrom(names[(i+1)%len(names)], DOWN*1.5),\n", 208 | " ))\n", 209 | " self.play(FadeOut(f4t))" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 99, 215 | "metadata": {}, 216 | "outputs": [ 217 | { 218 | "name": "stderr", 219 | "output_type": "stream", 220 | "text": [ 221 | " \r" 222 | ] 223 | }, 224 | { 225 | "data": { 226 | "text/html": [ 227 | "" 230 | ], 231 | "text/plain": [ 232 | "" 233 | ] 234 | }, 235 | "metadata": {}, 236 | "output_type": "display_data" 237 | } 238 | ], 239 | "source": [ 240 | "%%manim -v WARNING --disable_caching -qm End\n", 241 | "\n", 242 | "class End(Scene):\n", 243 | " def construct(self):\n", 244 | " self.add(Title(\"Manim Version 0.6.0\"))\n", 245 | " f5t= Text(\"...and lots of other enhancements!\").scale(1.3).shift(UP)\n", 246 | " f5tb= Text(\"Visit us at docs.manim.community\",t2c={'[9:]':\"#e07a5f\"}).scale(1.3).shift(DOWN)\n", 247 | " self.play(FadeInFrom(f5t,DOWN))\n", 248 | " self.play(FadeInFrom(f5tb,DOWN))\n", 249 | " banner = ManimBanner(dark_theme=True).scale(0.3).to_corner(DR).shift(LEFT)\n", 250 | " self.play(FadeIn(banner))\n", 251 | " self.play(banner.expand())\n", 252 | " #self.play(FadeOut(banner))\n", 253 | " self.wait(2)" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "\n", 270 | " class Indications(Scene):\n", 271 | " def construct(self):\n", 272 | " indications = [ApplyWave,Circumscribe,Flash,FocusOn,Indicate,ShowPassingFlash,Wiggle]\n", 273 | " names = [Tex(i.__name__).scale(3) for i in indications]\n", 274 | "\n", 275 | " self.add(names[0])\n", 276 | " for i in range(len(names)):\n", 277 | " if indications[i] is Flash:\n", 278 | " self.play(Flash(UP))\n", 279 | " elif indications[i] is ShowPassingFlash:\n", 280 | " self.play(ShowPassingFlash(Underline(names[i])))\n", 281 | " else:\n", 282 | " self.play(indications[i](names[i]))\n", 283 | " self.play(AnimationGroup(\n", 284 | " FadeOutAndShift(names[i], UP*1.5),\n", 285 | " FadeInFrom(names[(i+1)%len(names)], DOWN*1.5),\n", 286 | " ))\n" 287 | ] 288 | } 289 | ], 290 | "metadata": { 291 | "kernelspec": { 292 | "display_name": "Python 3", 293 | "language": "python", 294 | "name": "python3" 295 | }, 296 | "language_info": { 297 | "codemirror_mode": { 298 | "name": "ipython", 299 | "version": 3 300 | }, 301 | "file_extension": ".py", 302 | "mimetype": "text/x-python", 303 | "name": "python", 304 | "nbconvert_exporter": "python", 305 | "pygments_lexer": "ipython3", 306 | "version": "3.9.2" 307 | } 308 | }, 309 | "nbformat": 4, 310 | "nbformat_minor": 4 311 | } 312 | -------------------------------------------------------------------------------- /2021_06_03_v0.7.0_release_tour.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "id": "1059ad74", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "data": { 11 | "text/html": [ 12 | "
Manim Community v0.7.0\n",
 13 |        "\n",
 14 |        "
\n" 15 | ], 16 | "text/plain": [ 17 | "" 18 | ] 19 | }, 20 | "metadata": {}, 21 | "output_type": "display_data" 22 | } 23 | ], 24 | "source": [ 25 | "import manim\n", 26 | "from manim import *" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "769fffc1", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 4, 40 | "id": "973b9b8c", 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "name": "stderr", 45 | "output_type": "stream", 46 | "text": [ 47 | " \r" 48 | ] 49 | }, 50 | { 51 | "data": { 52 | "text/html": [ 53 | "" 56 | ], 57 | "text/plain": [ 58 | "" 59 | ] 60 | }, 61 | "metadata": {}, 62 | "output_type": "display_data" 63 | } 64 | ], 65 | "source": [ 66 | "%%manim -v WARNING --disable_caching -qh Intro\n", 67 | "\n", 68 | "class Intro(Scene):\n", 69 | " def construct(self):\n", 70 | " text=Text(\"Release Tour v0.7.0\").to_edge(UP).shift(DOWN).scale(2)\n", 71 | " self.add(text)\n", 72 | " banner = ManimBanner().scale(0.5).shift(DOWN)\n", 73 | " self.play(banner.create())\n", 74 | " self.wait()\n", 75 | " self.play(\n", 76 | " FadeOut(text),\n", 77 | " Transform(banner, ManimBanner().scale(0.3).to_corner(DR))\n", 78 | " )\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 6, 84 | "id": "f9c3c635", 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stderr", 89 | "output_type": "stream", 90 | "text": [ 91 | " \r" 92 | ] 93 | }, 94 | { 95 | "data": { 96 | "text/html": [ 97 | "" 100 | ], 101 | "text/plain": [ 102 | "" 103 | ] 104 | }, 105 | "metadata": {}, 106 | "output_type": "display_data" 107 | } 108 | ], 109 | "source": [ 110 | "%%manim -v WARNING --disable_caching -qh Polar\n", 111 | "\n", 112 | "class Polar(Scene):\n", 113 | " def construct(self):\n", 114 | " self.add(ManimBanner().scale(0.3).to_corner(DR))\n", 115 | "\n", 116 | " polarplane_pi = PolarPlane(\n", 117 | " azimuth_units=\"PI radians\",\n", 118 | " size=6,\n", 119 | " azimuth_label_scale=0.7,\n", 120 | " radius_config={\"number_scale_value\": 0.7},\n", 121 | " ).add_coordinates()\n", 122 | " l1= Line(color=YELLOW)\n", 123 | " t1= Text(\"New Mobjects:\").scale(0.7)\n", 124 | " t2= Text(\"PolarPlane\").scale(0.7)\n", 125 | " t3= Text(\"ArcBrace\").scale(0.7)\n", 126 | "\n", 127 | " t1.to_corner(UR)\n", 128 | " t2.next_to(t1,DOWN)\n", 129 | " t2.set_color(BLUE)\n", 130 | " t3.set_color(YELLOW)\n", 131 | " t3.next_to(t2,DOWN)\n", 132 | "\n", 133 | "\n", 134 | " arc_2 = Arc(radius=2,start_angle=0,angle=PI/2).set_color(ORANGE)\n", 135 | " brace_2 = ArcBrace(arc_2).set_color(YELLOW)\n", 136 | " group_2 = VGroup(arc_2,brace_2)\n", 137 | " self.play(FadeIn(polarplane_pi,t1,t2))\n", 138 | " self.wait()\n", 139 | " self.play(FadeIn(group_2, t3))\n", 140 | " self.wait(2)\n", 141 | " self.play(FadeOut(polarplane_pi), FadeOut(t1), FadeOut(t2), FadeOut(t3), FadeOut(group_2))" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 7, 147 | "id": "fb886ca0", 148 | "metadata": {}, 149 | "outputs": [ 150 | { 151 | "name": "stderr", 152 | "output_type": "stream", 153 | "text": [ 154 | " \r" 155 | ] 156 | }, 157 | { 158 | "data": { 159 | "text/html": [ 160 | "" 163 | ], 164 | "text/plain": [ 165 | "" 166 | ] 167 | }, 168 | "metadata": {}, 169 | "output_type": "display_data" 170 | } 171 | ], 172 | "source": [ 173 | "%%manim -v WARNING --disable_caching -qh StarPolygram\n", 174 | "\n", 175 | "class StarPolygram(Scene):\n", 176 | " def construct(self):\n", 177 | " self.add(ManimBanner().scale(0.3).to_corner(DR))\n", 178 | "\n", 179 | " e1=Dot()\n", 180 | " e1t=Text(\"Star\").to_edge(DOWN).shift(UP)\n", 181 | " e1 = Star(7, outer_radius=2, density=2, color=RED)\n", 182 | " e2 = Star(7, outer_radius=2, density=3, color=PURPLE)\n", 183 | " hexagram = Polygram(\n", 184 | " [[0, 2, 0], [-np.sqrt(3), -1, 0], [np.sqrt(3), -1, 0]],\n", 185 | " [[-np.sqrt(3), 1, 0], [0, -2, 0], [np.sqrt(3), 1, 0]],\n", 186 | " )\n", 187 | " self.play(FadeIn(e1t,e1))\n", 188 | " self.play(Transform(e1, e2))\n", 189 | " hexa_text=Text(\"Polygram\").to_edge(DOWN).shift(UP)\n", 190 | " self.play(FadeOut(e1, scale=2),FadeIn(hexagram, scale=0.1),FadeOut(e1t, scale=1.4), FadeIn(hexa_text,scale=0.4))\n", 191 | " self.wait()\n", 192 | " self.play(FadeOut(hexagram), FadeOut(hexa_text))" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 8, 198 | "id": "49af9fab", 199 | "metadata": {}, 200 | "outputs": [ 201 | { 202 | "name": "stderr", 203 | "output_type": "stream", 204 | "text": [ 205 | " \r" 206 | ] 207 | }, 208 | { 209 | "data": { 210 | "text/html": [ 211 | "" 214 | ], 215 | "text/plain": [ 216 | "" 217 | ] 218 | }, 219 | "metadata": {}, 220 | "output_type": "display_data" 221 | } 222 | ], 223 | "source": [ 224 | "%%manim -v WARNING --disable_caching -qh Diagrams\n", 225 | "\n", 226 | "class Diagrams(Scene):\n", 227 | " def construct(self):\n", 228 | " # self.camera.background_color = WHITE\n", 229 | " text=Text(\"Class diagrams in the docs\").to_edge(UP).scale(1.2)\n", 230 | " self.add(BackgroundRectangle(text, buff=0.15))\n", 231 | " self.play(Write(text))\n", 232 | " self.wait()\n", 233 | "\n", 234 | " " 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 10, 240 | "id": "22e5d38d", 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "name": "stderr", 245 | "output_type": "stream", 246 | "text": [ 247 | " \r" 248 | ] 249 | }, 250 | { 251 | "data": { 252 | "text/html": [ 253 | "" 256 | ], 257 | "text/plain": [ 258 | "" 259 | ] 260 | }, 261 | "metadata": {}, 262 | "output_type": "display_data" 263 | } 264 | ], 265 | "source": [ 266 | "%%manim -v WARNING --disable_caching -qh ColorsOverview\n", 267 | "\n", 268 | "\n", 269 | "from manim.utils.color import Colors\n", 270 | "\n", 271 | "\n", 272 | "\n", 273 | "class ColorsOverview(Scene):\n", 274 | " def construct(self):\n", 275 | " banner = ManimBanner().scale(0.3).to_corner(DR)\n", 276 | " self.add(banner)\n", 277 | "\n", 278 | " def color_group(color):\n", 279 | " group = VGroup(\n", 280 | " *[\n", 281 | " Line(ORIGIN, RIGHT * 1.5, stroke_width=35, color=Colors[name].value)\n", 282 | " for name in subnames(color)\n", 283 | " ]\n", 284 | " ).arrange_submobjects(buff=0.4, direction=DOWN)\n", 285 | "\n", 286 | " name = Text(color).scale(0.6).next_to(group, UP, buff=0.3)\n", 287 | " if any(decender in color for decender in \"gjpqy\"):\n", 288 | " name.shift(DOWN * 0.08)\n", 289 | " group.add(name)\n", 290 | " return group\n", 291 | "\n", 292 | " def subnames(name):\n", 293 | " return [name + \"_\" + char for char in \"abcde\"]\n", 294 | "\n", 295 | " color_groups = VGroup(\n", 296 | " *[\n", 297 | " color_group(color)\n", 298 | " for color in [\n", 299 | " \"blue\",\n", 300 | " \"teal\",\n", 301 | " \"green\",\n", 302 | " \"yellow\",\n", 303 | " \"gold\",\n", 304 | " \"red\",\n", 305 | " \"maroon\",\n", 306 | " \"purple\",\n", 307 | " ]\n", 308 | " ]\n", 309 | " ).arrange_submobjects(buff=0.2, aligned_edge=DOWN)\n", 310 | "\n", 311 | " for line, char in zip(color_groups[0], \"abcde\"):\n", 312 | " color_groups.add(Text(char).scale(0.6).next_to(line, LEFT, buff=0.2))\n", 313 | "\n", 314 | " def named_lines_group(length, colors, names, text_colors, align_to_block):\n", 315 | " lines = VGroup(\n", 316 | " *[\n", 317 | " Line(\n", 318 | " ORIGIN,\n", 319 | " RIGHT * length,\n", 320 | " stroke_width=55,\n", 321 | " color=Colors[color].value,\n", 322 | " )\n", 323 | " for color in colors\n", 324 | " ]\n", 325 | " ).arrange_submobjects(buff=0.6, direction=DOWN)\n", 326 | "\n", 327 | " for line, name, color in zip(lines, names, text_colors):\n", 328 | " line.add(Text(name, color=color).scale(0.6).move_to(line))\n", 329 | " lines.next_to(color_groups, DOWN, buff=0.5).align_to(\n", 330 | " color_groups[align_to_block], LEFT\n", 331 | " )\n", 332 | " return lines\n", 333 | "\n", 334 | " other_colors = (\n", 335 | " \"pink\",\n", 336 | " \"light_pink\",\n", 337 | " \"orange\",\n", 338 | " \"light_brown\",\n", 339 | " \"dark_brown\",\n", 340 | " \"gray_brown\",\n", 341 | " )\n", 342 | "\n", 343 | " other_lines = named_lines_group(\n", 344 | " 3.2,\n", 345 | " other_colors,\n", 346 | " other_colors,\n", 347 | " [BLACK] * 4 + [WHITE] * 2,\n", 348 | " 0,\n", 349 | " )\n", 350 | "\n", 351 | " gray_lines = named_lines_group(\n", 352 | " 6.6,\n", 353 | " [\"white\"] + subnames(\"gray\") + [\"black\"],\n", 354 | " [\n", 355 | " \"white\",\n", 356 | " \"lighter_gray / gray_a\",\n", 357 | " \"light_gray / gray_b\",\n", 358 | " \"gray / gray_c\",\n", 359 | " \"dark_gray / gray_d\",\n", 360 | " \"darker_gray / gray_e\",\n", 361 | " \"black\",\n", 362 | " ],\n", 363 | " [BLACK] * 3 + [WHITE] * 4,\n", 364 | " 2,\n", 365 | " )\n", 366 | "\n", 367 | " pure_colors = (\n", 368 | " \"pure_red\",\n", 369 | " \"pure_green\",\n", 370 | " \"pure_blue\",\n", 371 | " )\n", 372 | "\n", 373 | " pure_lines = named_lines_group(\n", 374 | " 3.2,\n", 375 | " pure_colors,\n", 376 | " pure_colors,\n", 377 | " [BLACK, BLACK, WHITE],\n", 378 | " 6,\n", 379 | " )\n", 380 | "\n", 381 | " self.add(color_groups, other_lines, gray_lines, pure_lines)\n", 382 | "\n", 383 | " VGroup(*self.mobjects).remove(banner).move_to(ORIGIN)\n", 384 | " self.remove(color_groups, other_lines, gray_lines, pure_lines)\n", 385 | " tt=Text(\"New color cheatsheet in the docs\")\n", 386 | " self.play(FadeIn(tt), run_time=0.2)\n", 387 | " self.play(FadeIn(color_groups,shift=0.1*UP, lag_ratio=0.05), run_time=3)\n", 388 | " self.play(FadeOut(tt),run_time=0.2)\n", 389 | "\n", 390 | " self.play(FadeIn(other_lines,gray_lines,pure_lines,shift=0.1*UP))\n", 391 | " self.wait()\n", 392 | " self.play(FadeOut(*self.mobjects))\n", 393 | "\n" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 16, 399 | "id": "a2b4c2d8", 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "name": "stderr", 404 | "output_type": "stream", 405 | "text": [ 406 | " \r" 407 | ] 408 | }, 409 | { 410 | "data": { 411 | "text/html": [ 412 | "" 415 | ], 416 | "text/plain": [ 417 | "" 418 | ] 419 | }, 420 | "metadata": {}, 421 | "output_type": "display_data" 422 | } 423 | ], 424 | "source": [ 425 | "%%manim -v WARNING --disable_caching -qh End\n", 426 | "\n", 427 | "class End(Scene):\n", 428 | " def construct(self):\n", 429 | " banner = ManimBanner().scale(0.3).to_corner(DR)\n", 430 | " self.add(banner)\n", 431 | "\n", 432 | " f5t= Text(\"...and lots of other enhancements!\").scale(1.3).shift(UP)\n", 433 | " f5tb= Text(\"Visit us at docs.manim.community\",t2c={'[9:]':\"#e07a5f\"}).scale(1.3).shift(DOWN)\n", 434 | " self.play(FadeIn(f5t,shift=UP))\n", 435 | " self.play(FadeIn(f5tb,shift=UP))\n", 436 | " self.play(banner.animate.shift(LEFT))\n", 437 | " self.play(banner.expand())\n", 438 | " self.wait(2)\n" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": null, 444 | "id": "becba3fe", 445 | "metadata": {}, 446 | "outputs": [], 447 | "source": [] 448 | } 449 | ], 450 | "metadata": { 451 | "kernelspec": { 452 | "display_name": "Python 3", 453 | "language": "python", 454 | "name": "python3" 455 | }, 456 | "language_info": { 457 | "codemirror_mode": { 458 | "name": "ipython", 459 | "version": 3 460 | }, 461 | "file_extension": ".py", 462 | "mimetype": "text/x-python", 463 | "name": "python", 464 | "nbconvert_exporter": "python", 465 | "pygments_lexer": "ipython3", 466 | "version": "3.9.2" 467 | } 468 | }, 469 | "nbformat": 4, 470 | "nbformat_minor": 5 471 | } 472 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | 4 | If you haven't read our code of conduct, please do so at the 5 | [manim repository](https://github.com/ManimCommunity/manim/blob/master/CODE_OF_CONDUCT.md#code-of-conduct). -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM manimcommunity/manim:v0.3.0 2 | 3 | COPY --chown=manimuser:manimuser . /manim 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Manim Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # manim-tweets 2 | 3 | Code for tweets by @manim_community on twitter. Both the code and video output are licensed under the MIT license. 4 | 5 | ## Try online without installation: 6 | 7 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/ManimCommunity/manim-tweets/HEAD?filepath=jupyter_notebooks%2Fpendulum%2Fpendulum_example.ipynb) : An animated pendulum 8 | 9 | **NOTE** 10 | 11 | The filenames are in the format `year_date_day_scenename.py` (e.g.`2020_12_08_lissajous_curve.py`). 12 | Be aware that the day might be off +-1 of the day of the tweet because of different time zones. 13 | 14 | ## Code of Conduct 15 | 16 | Our full code of conduct, and how we enforce it, can be read on [our website](https://docs.manim.community/en/latest/conduct.html). 17 | -------------------------------------------------------------------------------- /assets/2021_01_03/config_new.py: -------------------------------------------------------------------------------- 1 | obj = MyAwesomeMobject(coolness=9001) 2 | -------------------------------------------------------------------------------- /assets/2021_01_03/config_old.py: -------------------------------------------------------------------------------- 1 | class MyAwesomeMobject: 2 | CONFIG = { 3 | "coolness": 9001, 4 | } 5 | 6 | 7 | obj = MyAwesomeMobject() 8 | -------------------------------------------------------------------------------- /assets/2021_01_03/markup_example.py: -------------------------------------------------------------------------------- 1 | group = VGroup( 2 | MarkupText("foo bar foobar"), 3 | MarkupText("foo bar" "big small"), 4 | MarkupText('colors'), 5 | ).arrange(DOWN) 6 | self.add(group) 7 | -------------------------------------------------------------------------------- /assets/2021_01_03/methodanim_new.py: -------------------------------------------------------------------------------- 1 | sq = Square() 2 | self.play(sq.animate.scale(0.65).rotate(-PI / 4)) 3 | -------------------------------------------------------------------------------- /assets/2021_01_03/methodanim_old.py: -------------------------------------------------------------------------------- 1 | sq = Square() 2 | self.play(sq.scale, 0.65, sq.rotate, -PI / 4) 3 | -------------------------------------------------------------------------------- /assets/2021_01_03/rubikscube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManimCommunity/manim-tweets/fd6b76f51227426ff21677d55a56c9a3031bf1ac/assets/2021_01_03/rubikscube.png -------------------------------------------------------------------------------- /assets/2021_03_04/Blivet2.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | Blivet 2 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/2021_03_04/present.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/2021_03_04/rocket-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 14 | 17 | 20 | 21 | 23 | 25 | 26 | 29 | 30 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /assets/2021_03_04/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jupyter_notebooks/pendulum/pendulum.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | 4 | class Pendulum(VGroup): 5 | def phi_function(self, amplitude, acceleration, length, time): 6 | return amplitude * np.sin(np.sqrt(acceleration / length) * time - np.pi / 2) 7 | 8 | def __init__(self, weight, amplitude, acceleration, length, time): 9 | VGroup.__init__(self) 10 | self.sound_stamps_there = [] 11 | self.sound_stamps_back = [] 12 | 13 | self.amplitude = amplitude 14 | self.acceleration = acceleration 15 | self.length = length 16 | self.time = time 17 | self.phi = self.phi_function(amplitude, acceleration, length, time) 18 | self.anchor = Dot(ORIGIN) 19 | self.line = Line(ORIGIN, length * DOWN) 20 | self.line.rotate(self.phi * DEGREES, about_point=self.line.get_start()) 21 | self.mass = LabeledDot(label=f"{weight}" + r"\, \text{kg}").scale(0.3) 22 | self.mass.move_to(self.line.get_end()) 23 | self.mobj = VGroup(self.line, self.anchor, self.mass) 24 | self.add(self.mobj) 25 | 26 | def start(self): 27 | self.mobj.current_time = 0.000001 28 | 29 | def updater(mob, dt): 30 | mob.current_time += dt 31 | new_phi = self.phi_function( 32 | self.amplitude, self.acceleration, self.length, mob.current_time 33 | ) 34 | mob[0].rotate( 35 | (new_phi - self.phi) * DEGREES, about_point=self.line.get_start() 36 | ) 37 | if np.sign(self.phi) < np.sign(new_phi): 38 | self.sound_stamps_there.append(mob.current_time) 39 | if np.sign(self.phi) > np.sign(new_phi): 40 | self.sound_stamps_back.append(mob.current_time) 41 | 42 | self.phi = new_phi 43 | self.mass.move_to(self.line.get_end()) 44 | 45 | self.mobj.add_updater(updater) 46 | -------------------------------------------------------------------------------- /jupyter_notebooks/pendulum/pendulum_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import manim\n", 10 | "from pendulum import Pendulum\n", 11 | "from manim import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "%%manim Pendulum1 -v WARNING --disable_caching -qm -s \n", 21 | "\n", 22 | "class Pendulum1(Scene):\n", 23 | " def construct(self):\n", 24 | " pendulum = Pendulum(weight=1, amplitude=12, acceleration=10, length=3, time=0)\n", 25 | " self.add(pendulum)" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "%%manim Pendulum2 -v WARNING --disable_caching -qm -s\n", 35 | "# remove -s to see the video\n", 36 | "\n", 37 | "class Pendulum2(Scene):\n", 38 | " def construct(self):\n", 39 | " pendulum = Pendulum(weight=1, amplitude=12, acceleration=10, length=3, time=0)\n", 40 | " self.add(pendulum)\n", 41 | " pendulum.start()\n", 42 | " self.wait(2)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "%%manim Pendulum3 -v WARNING --disable_caching -qm \n", 52 | "\n", 53 | "class Pendulum3(Scene):\n", 54 | " def construct(self):\n", 55 | " amplitudes = [5,10,15,20,25,30,35]\n", 56 | " total= len(amplitudes)\n", 57 | " for i,val in enumerate(amplitudes):\n", 58 | " pendulum = Pendulum(weight= 1.8,amplitude=val,acceleration= 10, length=4,time= 0)\n", 59 | " anchor_pos= pendulum.anchor.get_center()\n", 60 | " pendulum.anchor.set_color(YELLOW)\n", 61 | " pendulum.mass.scale(1.8)\n", 62 | " dest_pos = (-total/2+i)*1.2*RIGHT+2*UP\n", 63 | " pendulum.shift(anchor_pos+dest_pos)\n", 64 | " self.add(pendulum)\n", 65 | " pendulum.start()\n", 66 | " self.wait(6)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "%%manim Pendulum4 -v WARNING --disable_caching -qm \n", 76 | "\n", 77 | "class Pendulum4(Scene):\n", 78 | " def construct(self):\n", 79 | " lengths = [2,2.2,2.3,2.4,2.5,2.6]\n", 80 | " total= len(lengths)\n", 81 | " for i,val in enumerate(lengths):\n", 82 | " pendulum = Pendulum(weight= 4,amplitude=30,acceleration= 10, length=val,time= 0)\n", 83 | " pendulum.anchor.set_color(RED)\n", 84 | " pendulum.mass.scale(1.4)\n", 85 | " anchor_pos= pendulum.anchor.get_center()\n", 86 | " dest_pos = (-total/2+i)*1.1*RIGHT+2*UP\n", 87 | " pendulum.shift(anchor_pos+dest_pos)\n", 88 | " self.add(pendulum)\n", 89 | " pendulum.start()\n", 90 | " waiting_time=6\n", 91 | " self.wait(waiting_time)\n", 92 | "\n", 93 | " for mobj in self.mobjects:\n", 94 | " for sound_stamp in mobj.sound_stamps_there:\n", 95 | " self.add_sound(\"tick.mp3\", time_offset=-waiting_time + sound_stamp)\n", 96 | " for sound_stamp in mobj.sound_stamps_back:\n", 97 | " self.add_sound(\"tock.mp3\", time_offset=-waiting_time + sound_stamp)\n", 98 | " self.wait(1)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Python 3", 112 | "language": "python", 113 | "name": "python3" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.9.1" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 4 130 | } 131 | -------------------------------------------------------------------------------- /jupyter_notebooks/pendulum/tick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManimCommunity/manim-tweets/fd6b76f51227426ff21677d55a56c9a3031bf1ac/jupyter_notebooks/pendulum/tick.mp3 -------------------------------------------------------------------------------- /jupyter_notebooks/pendulum/tock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManimCommunity/manim-tweets/fd6b76f51227426ff21677d55a56c9a3031bf1ac/jupyter_notebooks/pendulum/tock.mp3 --------------------------------------------------------------------------------