From a236e33e50f1e799d4c904b31fd14298b49f9cca Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 17 Jan 2018 15:18:02 -0800 Subject: [PATCH] working on basel --- .gitignore | 5 +- active_projects/basel.py | 329 +++++++++++++++++++++++++++++++++ mobject/mobject.py | 22 +++ mobject/point_cloud_mobject.py | 13 ++ topics/numerals.py | 14 +- 5 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 active_projects/basel.py diff --git a/.gitignore b/.gitignore index 1a2b587b..ea54dace 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ ka_playgrounds/ grant_playground.py special_animations.py prettiness_hall_of_fame.py -files/ \ No newline at end of file +files/ +ben_playground.py + +ben_cairo_test.py diff --git a/active_projects/basel.py b/active_projects/basel.py new file mode 100644 index 00000000..e192473e --- /dev/null +++ b/active_projects/basel.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.numerals import * +from topics.combinatorics import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + + +from mobject.vectorized_mobject import * + +## To watch one of these scenes, run the following: +## python extract_scene.py -p file_name + + + +class LightCone(Circle): + pass + + +class IntroScene(PiCreatureScene): + + CONFIG = { + "rect_height" : 0.2, + "duration" : 1.0, + "eq_spacing" : 3 * MED_LARGE_BUFF + } + + def construct(self): + + morty = self.get_primary_pi_creature() + morty.scale(0.7).to_corner(DOWN+RIGHT) + + self.build_up_euler_sum() + self.force_skipping() + self.build_up_sum_on_number_line() + self.show_pi_answer() + self.other_pi_formulas() + self.refocus_on_euler_sum() + + self.revert_to_original_skipping_status() + + + + + + + + def build_up_euler_sum(self): + + self.euler_sum = TexMobject( + "1", "+", + "{1 \\over 4}", "+", + "{1 \\over 9}", "+", + "{1 \\over 16}", "+", + "{1 \\over 25}", "+", + "\\cdots", "=", + arg_separator = " \\, " + ) + + self.euler_sum.to_edge(UP) + self.euler_sum.shift(2*LEFT) + + terms = [1./n**2 for n in range(1,6)] + partial_results_values = np.cumsum(terms) + + self.play( + FadeIn(self.euler_sum[0], run_time = self.duration) + ) + + equals_sign = self.euler_sum.get_part_by_tex("=") + + self.partial_sum_decimal = DecimalNumber(partial_results_values[1], + num_decimal_points = 2) + self.partial_sum_decimal.next_to(equals_sign, RIGHT) + + + + for i in range(4): + + FadeIn(self.partial_sum_decimal, run_time = self.duration) + + if i == 0: + + + self.play( + FadeIn(self.euler_sum[1], run_time = self.duration), + FadeIn(self.euler_sum[2], run_time = self.duration), + FadeIn(equals_sign, run_time = self.duration), + FadeIn(self.partial_sum_decimal, run_time = self.duration) + ) + + else: + self.play( + FadeIn(self.euler_sum[2*i+1], run_time = self.duration), + FadeIn(self.euler_sum[2*i+2], run_time = self.duration), + ChangeDecimalToValue( + self.partial_sum_decimal, + partial_results_values[i+1], + run_time = self.duration, + num_decimal_points = 6, + show_ellipsis = True, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ) + + self.wait() + + self.q_marks = TextMobject("???").highlight(YELLOW) + self.q_marks.move_to(self.partial_sum_decimal) + + self.play( + FadeIn(self.euler_sum[-3], run_time = self.duration), # + + FadeIn(self.euler_sum[-2], run_time = self.duration), # ... + ReplacementTransform(self.partial_sum_decimal, self.q_marks) + ) + + + + def build_up_sum_on_number_line(self): + + self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1, + stroke_width = 1, + numbers_with_elongated_ticks = [0,1,2,3], + numbers_to_show = np.arange(0,5), + unit_size = 5, + tick_frequency = 0.2, + line_to_number_buff = MED_LARGE_BUFF + ) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + # create slabs for series terms + + max_n = 10 + + terms = [0] + [1./(n**2) for n in range(1, max_n + 1)] + series_terms = np.cumsum(terms) + lines = VGroup() + self.rects = VGroup() + slab_colors = [YELLOW, BLUE] * (max_n / 2) + + for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): + line = Line(*map(self.number_line.number_to_point, [t1, t2])) + rect = Rectangle() + rect.stroke_width = 0 + rect.fill_opacity = 1 + rect.highlight(color) + rect.stretch_to_fit_height( + self.rect_height, + ) + rect.stretch_to_fit_width(line.get_width()) + rect.move_to(line) + + self.rects.add(rect) + lines.add(line) + + #self.rects.radial_gradient_highlight(ORIGIN, 5, RED, BLUE) + + for i in range(5): + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), + run_time = self.duration) + ) + + for i in range(5, max_n): + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = self.duration) + ) + + + def show_pi_answer(self): + + self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) + self.pi_answer.move_to(self.partial_sum_decimal) + self.pi_answer.next_to(self.euler_sum[-1], RIGHT, + submobject_to_align = self.pi_answer[-2]) + self.play(ReplacementTransform(self.q_marks, self.pi_answer)) + + + def other_pi_formulas(self): + + self.play( + FadeOut(self.rects), + FadeOut(self.number_line_labels), + FadeOut(self.number_line) + ) + + self.leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + self.wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.leibniz_sum.get_part_by_tex("=") + ) + + self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.wallis_product.get_part_by_tex("=") + ) + + + self.play( + Write(self.leibniz_sum) + ) + self.play( + Write(self.wallis_product) + ) + + + + def refocus_on_euler_sum(self): + + self.euler_sum.add(self.pi_answer) + + self.play( + FadeOut(self.leibniz_sum), + FadeOut(self.wallis_product), + ApplyMethod(self.euler_sum.shift, + ORIGIN + 2*UP - self.euler_sum.get_center()) + ) + + # focus on pi squared + pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] + self.play( + ScaleInPlace(pi_squared,2,rate_func = wiggle) + ) + + morty = self.get_primary_pi_creature() + bubble = ThoughtBubble(height = 2, width = 5) + bubble.pin_to(morty) + + thought = Circle() + thought.add(TexMobject("?")) + thought.next_to(morty,LEFT, LARGE_BUFF, UP) + thought.generate_target() + + bubble.add_content(thought.target) + + self.play( + morty.change_mode, "confused", + FadeIn(bubble) + ) + + self.wait() + + + +class FirstLightHouseScene(Scene): + + def construct(self): + self.show_lighthouses_on_number_line() + + + + def show_lighthouses_on_number_line(self): + + self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1, + stroke_width = 1, + numbers_with_elongated_ticks = [0,1,2,3], + numbers_to_show = np.arange(0,5), + unit_size = 5, + tick_frequency = 0.2, + line_to_number_buff = MED_LARGE_BUFF + ) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + first_lighthouse_indicator = Circle( + stroke_width = 1, + stroke_color = WHITE, + fill_color = RED, + fill_opacity = 1, + radius = 0.3 + ) + + origin_point = self.number_line.number_to_point(0) + first_lighthouse_indicator.move_to(origin_point) + + self.add(first_lighthouse_indicator) + + lighthouse1 = SVGMobject(file_name = "lighthouse") + self.add(lighthouse1) + + self.wait() + + + + + + + + + + + diff --git a/mobject/mobject.py b/mobject/mobject.py index d433b7a2..797efbda 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -436,6 +436,10 @@ class Mobject(object): self.submobject_gradient_highlight(*colors) return self + def radial_gradient_highlight(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): + self.submobject_radial_gradient_highlight(center, radius, inner_color, outer_color) + return self + def submobject_gradient_highlight(self, *colors): if len(colors) == 0: raise Exception("Need at least one color") @@ -444,10 +448,28 @@ class Mobject(object): mobs = self.family_members_with_points() new_colors = color_gradient(colors, len(mobs)) + for mob, color in zip(mobs, new_colors): mob.highlight(color, family = False) return self + def submobject_radial_gradient_highlight(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): + + mobs = self.family_members_with_points() + if center == None: + center = self.get_center() + + for mob in self.family_members_with_points(): + t = np.linalg.norm(mob.get_center() - center)/radius + t = min(t,1) + print t + mob_color = interpolate_color(inner_color, outer_color, t) + print mob_color + mob.highlight(mob_color, family = False) + + return self + + def set_color(self, color): self.highlight(color) self.color = Color(color) diff --git a/mobject/point_cloud_mobject.py b/mobject/point_cloud_mobject.py index 893fb47d..7db62125 100644 --- a/mobject/point_cloud_mobject.py +++ b/mobject/point_cloud_mobject.py @@ -54,6 +54,19 @@ class PMobject(Mobject): ]) return self + def radial_gradient_highlight(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): + start_rgba, end_rgba = map(color_to_rgba, [start_color, end_color]) + if center == None: + center = self.get_center() + for mob in self.family_members_with_points(): + num_points = mob.get_num_points() + t = min(1,np.abs(mob.get_center() - center)/radius) + + mob.rgbas = np.array( + [ interpolate(start_rgba, end_rgba, t) ] * num_points + ) + return self + def match_colors(self, mobject): Mobject.align_data(self, mobject) self.rgbas = np.array(mobject.rgbas) diff --git a/topics/numerals.py b/topics/numerals.py index 27631c4b..a7714ba8 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -9,7 +9,8 @@ from helpers import * class DecimalNumber(VMobject): CONFIG = { "num_decimal_points" : 2, - "digit_to_digit_buff" : 0.05 + "digit_to_digit_buff" : 0.05, + "show_ellipsis" : False } def __init__(self, number, **kwargs): digest_config(self, kwargs, locals()) @@ -18,6 +19,10 @@ class DecimalNumber(VMobject): TexMobject(char) for char in num_string ], **kwargs) + + if self.show_ellipsis: + self.add(TexMobject("\\dots")) + self.arrange_submobjects( buff = self.digit_to_digit_buff, aligned_edge = DOWN @@ -46,6 +51,7 @@ class Integer(VGroup): class ChangingDecimal(Animation): CONFIG = { "num_decimal_points" : None, + "show_ellipsis" : None, "spare_parts" : 2, "position_update_func" : None, "tracked_mobject" : None @@ -54,6 +60,8 @@ class ChangingDecimal(Animation): digest_config(self, kwargs, locals()) if self.num_decimal_points is None: self.num_decimal_points = decimal_number_mobject.num_decimal_points + if self.show_ellipsis is None: + self.show_ellipsis = decimal_number_mobject.show_ellipsis decimal_number_mobject.add(*[ VectorizedPoint(decimal_number_mobject.get_corner(DOWN+LEFT)) for x in range(self.spare_parts)] @@ -71,7 +79,9 @@ class ChangingDecimal(Animation): decimal = self.decimal_number_mobject new_number = self.number_update_func(alpha) new_decimal = DecimalNumber( - new_number, num_decimal_points = self.num_decimal_points + new_number, + num_decimal_points = self.num_decimal_points, + show_ellipsis = self.show_ellipsis ) new_decimal.replace(decimal, dim_to_match = 1) new_decimal.highlight(decimal.get_color())