diff --git a/__init__.py b/__init__.py index 8b137891..e69de29b 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +0,0 @@ - diff --git a/active_projects/name_animation.py b/active_projects/name_animation.py index 2ff96bff..112669d7 100644 --- a/active_projects/name_animation.py +++ b/active_projects/name_animation.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + from big_ol_pile_of_manim_imports import * NAME_WITH_SPACES = "Prime Meridian" @@ -5,71 +8,100 @@ DIAMETER = 3.0 RADIUS = DIAMETER / 2 LETTER_SCALE = 1 + class NameAnimationScene(Scene): + CONFIG = { + "animated_name": "Prime Meridian" + } def construct(self): - - name = ''.join(NAME_WITH_SPACES.split(' ')) - letters = list(name) - nb_letters = len(letters) + name = self.animated_name + letter_mobs = TextMobject(name) + nb_letters = len(letter_mobs) randy = PiCreature() randy.move_to(ORIGIN).scale_to_fit_height(0.5 * DIAMETER) randy.set_color(BLUE_E) randy.look_at(UP + RIGHT) self.add(randy) - dtheta = TAU/nb_letters - angles = np.arange(TAU/4,-3 * TAU / 4,-dtheta) + dtheta = TAU / nb_letters + angles = np.arange(TAU / 4, -3 * TAU / 4, -dtheta) name_mob = VGroup() - for (letter, angle) in zip(letters, angles): - letter_mob = TextMobject(letter).scale(LETTER_SCALE) + for (letter_mob, angle) in zip(letter_mobs, angles): + letter_mob.scale(LETTER_SCALE) pos = RADIUS * np.cos(angle) * RIGHT + RADIUS * np.sin(angle) * UP letter_mob.move_to(pos) name_mob.add(letter_mob) - pos2 = RADIUS * np.cos(angles[2]) * RIGHT + RADIUS * np.sin(angles[2]) * UP + pos2 = RADIUS * np.cos(angles[2]) * RIGHT + \ + RADIUS * np.sin(angles[2]) * UP + + times_n_label = VGroup( + TexMobject("\\times"), + Integer(1) + ) + times_n_label.arrange_submobjects(RIGHT) + times_n_label.shift(FRAME_WIDTH * RIGHT / 4) + times_n_label.to_edge(UP) self.play( - LaggedStart(Write, name_mob, run_time = 3), - ApplyMethod(randy.look_at, pos2, run_time = 3) + LaggedStart(FadeIn, name_mob, run_time=3), + ApplyMethod(randy.change, "pondering", pos2, run_time=1), + FadeIn(times_n_label) ) - for i in range(2,nb_letters + 2): + for n in range(2, nb_letters + 2): group = [] - for (j,letter_mob) in enumerate(name_mob.submobjects): + for (j, letter_mob) in enumerate(name_mob.submobjects): - new_angle = TAU / 4 - i * j * dtheta - new_pos = RADIUS * np.cos(new_angle) * RIGHT + RADIUS * np.sin(new_angle) * UP + new_angle = TAU / 4 - n * j * dtheta + new_pos = RADIUS * np.cos(new_angle) * \ + RIGHT + RADIUS * np.sin(new_angle) * UP letter_mob.target = letter_mob.copy().move_to(new_pos) - anim = MoveToTarget(letter_mob, path_arc = - j * dtheta) + anim = MoveToTarget(letter_mob, path_arc=- j * dtheta) group.append(anim) - + new_n = Integer(n) + new_n.move_to(times_n_label[1]) self.play( - AnimationGroup(*group, run_time = 3), - ApplyMethod(randy.look_at,name_mob.submobjects[2], run_time = 3) + AnimationGroup(*group, run_time=3), + UpdateFromFunc(randy, lambda r: r.look_at(name_mob.submobjects[2])), + FadeOut(times_n_label[1]), + FadeIn(new_n) ) + times_n_label.submobjects[1] = new_n self.wait(0.5) - thank_you = TextMobject("Thank You!").next_to(randy, DOWN) new_randy = randy.copy() new_randy.change("hooray") new_randy.set_color(BLUE_E) new_randy.look_at(ORIGIN) self.play( - Transform(name_mob, thank_you), + ReplacementTransform(name_mob, VGroup(*thank_you)), Transform(randy, new_randy) ) + self.play(Blink(randy)) + + def __str__(self): + return self.animated_name.replace(" ", "") + "Animation" +names = [] - - - - - - - - - +if __name__ == "__main__": + for name in names: + try: + NameAnimationScene( + frame_duration=PRODUCTION_QUALITY_FRAME_DURATION, + camera_config=PRODUCTION_QUALITY_CAMERA_CONFIG, + animated_name=name, + write_to_movie=True, + output_directory=os.path.join( + ANIMATIONS_DIR, + "active_projects", + "name_animations", + ), + ) + except Exception as e: + print "Could not animate %s: %s" % (name, e) diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index 64a67fbf..3bc3aa55 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -25,6 +25,26 @@ def get_chord_f_label(chord, arg="f", direction=DOWN): chord_f.angle = angle return chord_f + +def get_wallis_product(n_terms=6, show_result=True): + tex_mob_args = [] + for x in range(n_terms): + if x % 2 == 0: + numerator = x + 2 + denominator = x + 1 + else: + numerator = x + 1 + denominator = x + 2 + tex_mob_args += [ + "{%d" % numerator, "\\over", "%d}" % denominator, "\\cdot" + ] + tex_mob_args[-1] = "\\cdots" + if show_result: + tex_mob_args += ["=", "{\\pi", "\\over", "2}"] + + result = TexMobject(*tex_mob_args) + return result + # Scenes @@ -33,7 +53,7 @@ class DistanceProductScene(MovingCameraScene): "ambient_light_config": { "opacity_function": DEFAULT_OPACITY_FUNCTION, "num_levels": 100, - "light_radius": 5, + "radius": 5, "max_opacity": 0.8, "color": PRODUCT_COLOR, }, @@ -112,11 +132,15 @@ class DistanceProductScene(MovingCameraScene): self.lights.add(light) return self.lights - def get_distance_lines(self): - self.distance_lines = VGroup(*[ - Line(self.get_observer_point(), point) + def get_distance_lines(self, start_point=None, line_class=Line): + if start_point is None: + start_point = self.get_observer_point() + lines = VGroup(*[ + line_class(start_point, point) for point in self.get_lh_points() ]) + lines.set_stroke(width=2) + self.distance_lines = lines return self.distance_lines def get_symbolic_distance_labels(self): @@ -145,7 +169,7 @@ class DistanceProductScene(MovingCameraScene): include_background_rectangle=True, ) label.scale_to_fit_height(self.numeric_distance_label_height) - max_width = 0.5 * line.get_length() + max_width = 0.5 * max(line.get_length(), 0.1) if label.get_width() > max_width: label.scale_to_fit_width(max_width) angle = (line.get_angle() % TAU) - TAU / 2 @@ -185,7 +209,7 @@ class DistanceProductScene(MovingCameraScene): product_decimal.scale_to_fit_height(self.numeric_distance_label_height) product_decimal.next_to(h_line, DOWN) product_decimal.align_to(stacked_labels, RIGHT) - product_decimal.set_color(BLUE) + product_decimal[1].set_color(BLUE) return VGroup(stacked_labels, h_line, times, product_decimal) def get_circle_group(self): @@ -1468,14 +1492,14 @@ class PlugObserverIntoPolynomial(DistanceProductScene): i = polynomial[1].submobjects.index(eq) return polynomial[1][:i], polynomial[1][i], polynomial[1][i + 1:] - def get_lines(self): - dot = self.observer_dot - lines = VGroup(*[ - DashedLine(dot.get_center(), point) - for point in self.get_lh_points() - ]) - lines.set_stroke(width=2) - return lines + def get_lines(self, start_point=None): + return self.get_distance_lines( + start_point=start_point, + line_class=DashedLine + ) + + def get_observer_point(self, dummy_arg): + return self.observer_dot.get_center() class PlugObserverIntoPolynomial5Lighthouses(PlugObserverIntoPolynomial): @@ -1664,9 +1688,9 @@ class DistanceProductIsChordF(PlugObserverIntoPolynomial): self.add_plane() self.add_circle_group() self.add_polynomial("O") - self.add_observer_and_lines() + self.show_all_animations() - def add_observer_and_lines(self): + def show_all_animations(self): fraction = self.observer_fraction = 0.3 circle = self.circle @@ -1821,13 +1845,294 @@ class DistanceProductIsChordF(PlugObserverIntoPolynomial): self.wait() - - - - - - - +class ProveLemma2(PlugObserverIntoPolynomial): + CONFIG = { + "include_lighthouses": False, + "num_lighthouses": 8, + # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + # "add_lights_in_foreground": False, + } + + def construct(self): + self.add_plane() + self.add_circle_group() + self.add_polynomial("O") + + self.replace_first_lighthouse() + self.rearrange_polynomial() + self.plug_in_one() + + def replace_first_lighthouse(self): + light_to_remove = self.lights[0] + dot = self.observer_dot = Dot(color=self.observer_config["color"]) + dot.move_to(self.get_circle_point_at_proportion(0.5 / self.num_lighthouses)) + arrow = Vector(0.5 * DL, color=WHITE) + arrow.next_to(dot, UR, SMALL_BUFF) + O_label = self.O_dot_label = TexMobject("O") + O_label.match_color(dot) + O_label.add_background_rectangle() + O_label.next_to(arrow, UR, SMALL_BUFF) + + # First, move the lighthouse + self.add_foreground_mobject(dot) + self.play( + dot.move_to, light_to_remove, + MaintainPositionRelativeTo(arrow, dot), + MaintainPositionRelativeTo(O_label, dot), + path_arc=-TAU / 2 + ) + + black_rect = Rectangle( + height=6, width=3.5, + stroke_width=0, + fill_color=BLACK, + fill_opacity=1, + ) + black_rect.to_corner(DL, buff=0) + lines = self.get_lines(self.circle.get_right()) + labels = self.get_numeric_distance_labels() + column_group = self.get_distance_product_column( + black_rect.get_top() + MED_SMALL_BUFF * DOWN + ) + stacked_labels, h_line, times, product_decimal = column_group + q_marks = self.q_marks = TextMobject("???") + q_marks.move_to(product_decimal, LEFT) + q_marks.match_color(product_decimal) + + zero_rects = VGroup(*map(SurroundingRectangle, [dot, stacked_labels[0]])) + + self.play( + LaggedStart(ShowCreation, lines), + LaggedStart(FadeIn, labels), + ) + self.play( + FadeIn(black_rect), + ShowCreation(h_line), + Write(times), + ReplacementTransform(labels.copy(), stacked_labels) + ) + self.wait() + self.play(ReplacementTransform( + stacked_labels.copy(), + VGroup(product_decimal) + )) + self.wait() + self.add_foreground_mobject(zero_rects) + self.play(*map(ShowCreation, zero_rects)) + self.wait(2) + self.play( + VGroup(light_to_remove, zero_rects[0]).shift, FRAME_WIDTH * RIGHT / 2, + path_arc=-60 * DEGREES, + rate_func=running_start, + remover=True + ) + self.play( + VGroup(stacked_labels[0], zero_rects[1]).shift, 4 * LEFT, + rate_func=running_start, + remover=True, + ) + self.remove_foreground_mobjects(zero_rects) + self.play( + FadeOut(product_decimal), + FadeIn(q_marks) + ) + self.play(FadeOut(labels)) + self.wait() + + def rearrange_polynomial(self): + dot = self.observer_dot + lhs, equals, rhs = self.get_polynomial_split(self.polynomial) + polynomial_background = self.polynomial[0] + first_factor = rhs[:5] + remaining_factors = rhs[5:] + equals_remaining_factors = VGroup(equals, remaining_factors) + + # first_factor_rect = SurroundingRectangle(first_factor) + lhs_rect = SurroundingRectangle(lhs) + + frac_line = Line(LEFT, RIGHT, color=WHITE) + frac_line.match_width(lhs, stretch=True) + frac_line.next_to(lhs, DOWN, SMALL_BUFF) + O_minus_1 = TexMobject("\\left(", "O", "-", "1", "\\right)") + O_minus_1.next_to(frac_line, DOWN, SMALL_BUFF) + new_lhs_background = BackgroundRectangle(VGroup(lhs, O_minus_1), buff=SMALL_BUFF) + new_lhs_rect = SurroundingRectangle(VGroup(lhs, O_minus_1)) + + roots_of_unity_circle = VGroup(*[ + Circle(radius=0.2, color=YELLOW).move_to(point) + for point in self.get_lh_points() + ]) + for circle in roots_of_unity_circle: + circle.save_state() + circle.scale(4) + circle.fade(1) + + self.play(ShowCreation(lhs_rect)) + self.add_foreground_mobject(roots_of_unity_circle) + self.play(LaggedStart( + ApplyMethod, roots_of_unity_circle, + lambda m: (m.restore,) + )) + self.wait() + frac_line_copy = frac_line.copy() + self.play( + FadeIn(new_lhs_background), + polynomial_background.stretch, 0.8, 0, + polynomial_background.move_to, frac_line_copy, LEFT, + equals_remaining_factors.arrange_submobjects, RIGHT, SMALL_BUFF, + equals_remaining_factors.next_to, frac_line_copy, RIGHT, MED_SMALL_BUFF, + ReplacementTransform(first_factor, O_minus_1, path_arc=-90 * DEGREES), + ShowCreation(frac_line), + Animation(lhs), + ReplacementTransform(lhs_rect, new_lhs_rect), + ) + self.play( + roots_of_unity_circle[0].shift, FRAME_WIDTH * RIGHT / 2, + path_arc=(-60 * DEGREES), + rate_func=running_start, + remover=True + ) + + # Expand rhs + expanded_rhs = self.expanded_rhs = TexMobject( + "=", "1", "+", + "O", "+", + "O", "^2", "+", + "\\cdots", + "O", "^{N-1}" + ) + expanded_rhs.next_to(frac_line, RIGHT) + expanded_rhs.shift(LEFT) + expanded_rhs.scale(0.9) + expanded_rhs.set_color_by_tex("O", dot.get_color()) + + self.play( + polynomial_background.stretch, 1.8, 0, {"about_edge": LEFT}, + FadeIn(expanded_rhs), + equals_remaining_factors.scale, 0.9, + equals_remaining_factors.next_to, expanded_rhs, + VGroup( + new_lhs_background, lhs, frac_line, O_minus_1, + new_lhs_rect, + ).shift, LEFT, + ) + self.wait() + + def plug_in_one(self): + expanded_rhs = self.expanded_rhs + O_terms = expanded_rhs.get_parts_by_tex("O") + ones = VGroup(*[ + TexMobject("1").move_to(O_term, RIGHT) + for O_term in O_terms + ]) + ones.match_color(O_terms[0]) + + equals_1 = TexMobject("= 1") + equals_1.next_to(self.O_dot_label, RIGHT, SMALL_BUFF) + brace = Brace(expanded_rhs[1:], DOWN) + N_term = brace.get_text("N") + + product = DecimalNumber( + self.num_lighthouses, + num_decimal_points=3, + show_ellipsis=True + ) + product.move_to(self.q_marks, LEFT) + + self.play(Write(equals_1)) + self.play( + FocusOn(brace), + GrowFromCenter(brace) + ) + self.wait(2) + self.play(ReplacementTransform(O_terms, ones)) + self.wait() + self.play(Write(N_term)) + self.play(FocusOn(product)) + self.play( + FadeOut(self.q_marks), + FadeIn(product) + ) + self.wait() + + +class ArmedWithTwoKeyFacts(TeacherStudentsScene, DistanceProductScene): + CONFIG = { + "num_lighthouses": 6, + "ambient_light_config": { + "opacity_function": inverse_power_law(1, 1, 1, 6), + "radius": 1, + "num_levels": 100, + "max_opacity": 1, + }, + } + + def setup(self): + TeacherStudentsScene.setup(self) + DistanceProductScene.setup(self) + + def construct(self): + circle1 = self.circle + circle1.scale_to_fit_height(1.5) + circle1.to_corner(UL) + circle2 = circle1.copy() + circle2.next_to(circle1, DOWN, MED_LARGE_BUFF) + + wallis_product = get_wallis_product(n_terms=8) + + N = self.num_lighthouses + labels = VGroup() + for circle, f, dp in (circle1, 0.5, "2"), (circle2, 0, "N"): + self.circle = circle + lights = self.get_lights() + if f == 0: + lights.submobjects.pop(0) + observer = Dot(color=MAROON_B) + frac = f / N + point = self.get_circle_point_at_proportion(frac) + observer.move_to(point) + lines = self.get_distance_lines(point, line_class=DashedLine) + + label = TextMobject("Distance product = %s" % dp) + label.scale(0.7) + label.next_to(circle, RIGHT) + labels.add(label) + + group = VGroup(lines, observer, label) + self.play( + FadeIn(circle), + LaggedStart(FadeIn, VGroup(*it.chain(lights))), + LaggedStart( + FadeIn, VGroup(*it.chain(group.family_members_with_points())) + ), + self.teacher.change, "raise_right_hand", + self.get_student_changes(*["pondering"] * 3) + ) + wallis_product.move_to(labels).to_edge(RIGHT) + self.play( + LaggedStart(FadeIn, wallis_product), + self.teacher.change_mode, "hooray", + self.get_student_changes(*["thinking"] * 3, look_at_arg=wallis_product) + ) + self.wait(2) + + +class KeeperAndSailor(DistanceProductScene): + CONFIG = { + "num_lighthouses": 9, + "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + } + + def construct(self): + self.place_lighthouses() + self.introduce_observers() + + +class Test(Scene): + def construct(self): + product = get_wallis_product(8) + product.get_parts_by_tex("\\over").set_color(YELLOW) + self.add(product) diff --git a/for_3b1b_videos/pi_creature_scene.py b/for_3b1b_videos/pi_creature_scene.py index 3f9da5db..73d925f3 100644 --- a/for_3b1b_videos/pi_creature_scene.py +++ b/for_3b1b_videos/pi_creature_scene.py @@ -39,7 +39,7 @@ class PiCreatureScene(Scene): } def setup(self): - self.pi_creatures = self.create_pi_creatures() + self.pi_creatures = VGroup(*self.create_pi_creatures()) self.pi_creature = self.get_primary_pi_creature() if self.pi_creatures_start_on_screen: self.add(*self.pi_creatures) diff --git a/mobject/geometry.py b/mobject/geometry.py index f7c3e53c..f9683221 100644 --- a/mobject/geometry.py +++ b/mobject/geometry.py @@ -439,6 +439,9 @@ class DashedLine(Line): def generate_points(self): length = np.linalg.norm(self.end - self.start) + if length == 0: + self.add(Line(self.start, self.end)) + return self num_interp_points = int(length / self.dashed_segment_length) points = [ interpolate(self.start, self.end, alpha) diff --git a/mobject/svg/svg_mobject.py b/mobject/svg/svg_mobject.py index 3f405005..5c02191f 100644 --- a/mobject/svg/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -165,7 +165,7 @@ class SVGMobject(VMobject): # input preprocessing if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE): opacity = 0 - fill_color = BLACK # shdn't be necessary but avoids error msgs + fill_color = BLACK # shdn't be necessary but avoids error msgs if fill_color in ["#000", "#000000"]: fill_color = WHITE if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE): @@ -175,10 +175,6 @@ class SVGMobject(VMobject): stroke_color = WHITE if stroke_width in ["", "none", "0"]: stroke_width = 0 - - # is there sth to draw? - if opacity == 0 and stroke_width == 0: - return if corner_radius in ["", "0", "none"]: corner_radius = 0 @@ -187,22 +183,22 @@ class SVGMobject(VMobject): if corner_radius == 0: mob = Rectangle( - width = float(rect_element.getAttribute("width")), - height = float(rect_element.getAttribute("height")), - stroke_width = stroke_width, - stroke_color = stroke_color, - fill_color = fill_color, - fill_opacity = opacity + width=float(rect_element.getAttribute("width")), + height=float(rect_element.getAttribute("height")), + stroke_width=stroke_width, + stroke_color=stroke_color, + fill_color=fill_color, + fill_opacity=opacity ) else: mob = RoundedRectangle( - width = float(rect_element.getAttribute("width")), - height = float(rect_element.getAttribute("height")), - stroke_width = stroke_width, - stroke_color = stroke_color, - fill_color = fill_color, - fill_opacity = opacity, - corner_radius = corner_radius + width=float(rect_element.getAttribute("width")), + height=float(rect_element.getAttribute("height")), + stroke_width=stroke_width, + stroke_color=stroke_color, + fill_color=fill_color, + fill_opacity=opacity, + corner_radius=corner_radius ) mob.shift(mob.get_center() - mob.get_corner(UP + LEFT))