From 1ba56c3bba2a50c438dcf6f2d46980f39fa4f918 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 11:41:44 -0800 Subject: [PATCH 01/24] Satisfying finicky spacing desires --- active_projects/basel.py | 159 ++++++++------------------------------- 1 file changed, 30 insertions(+), 129 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 2e468e50..40b16628 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -95,10 +95,6 @@ class AngleUpdater(ContinualAnimation): self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) - - - - class LightIndicator(Mobject): CONFIG = { "radius": 0.5, @@ -148,9 +144,6 @@ class LightIndicator(Mobject): print "Indicator cannot update, reason: no light source found" self.set_intensity(self.measured_intensity()) - - - class UpdateLightIndicator(AnimationGroup): def __init__(self, indicator, intensity, **kwargs): @@ -165,13 +158,11 @@ class UpdateLightIndicator(AnimationGroup): AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator - class ContinualLightIndicatorUpdate(ContinualAnimation): def update_mobject(self,dt): self.mobject.continual_update() - def copy_func(f): """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, @@ -221,21 +212,7 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) - - - - - - - - - - - - - - - +### class IntroScene(PiCreatureScene): @@ -472,25 +449,6 @@ class IntroScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - - class FirstLighthouseScene(PiCreatureScene): def construct(self): @@ -647,28 +605,6 @@ class FirstLighthouseScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - - - - - class SingleLighthouseScene(PiCreatureScene): def construct(self): @@ -858,24 +794,6 @@ class SingleLighthouseScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - class EarthScene(Scene): def construct(self): @@ -973,30 +891,6 @@ class EarthScene(Scene): MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), ) - - - - - - - - - - - - - - - - - - - - - - - - class ScreenShapingScene(ThreeDScene): @@ -1442,9 +1336,6 @@ class ScreenShapingScene(ThreeDScene): FadeIn(reading39), ) - - - class IndicatorScalingScene(Scene): def construct(self): @@ -1482,11 +1373,6 @@ class IndicatorScalingScene(Scene): self.play(FadeIn(reading3)) self.wait() - - - - - class BackToEulerSumScene(PiCreatureScene): @@ -1705,10 +1591,6 @@ class BackToEulerSumScene(PiCreatureScene): self.wait() - - - - class TwoLightSourcesScene(PiCreatureScene): def construct(self): @@ -1886,8 +1768,6 @@ class TwoLightSourcesScene(PiCreatureScene): Write(theorem_name), ) - - class IPTScene1(PiCreatureScene): def construct(self): @@ -2078,8 +1958,6 @@ class IPTScene1(PiCreatureScene): Transform(screen2, screen2pp), ) - - class IPTScene2(Scene): def construct(self): @@ -2131,7 +2009,6 @@ class IPTScene2(Scene): text.next_to(box,UP) self.play(ShowCreation(box),Write(text)) - class PondScene(Scene): def construct(self): @@ -2606,8 +2483,6 @@ class PondScene(Scene): ) self.play(FadeIn(self.number_line)) - - class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -2627,9 +2502,6 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) - - - class ArcHighlightOverlayScene(Scene): def construct(self): @@ -2686,3 +2558,32 @@ class ArcHighlightOverlayScene(Scene): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 99783d77a0bd28ffc192cefec80f4d934efa9034 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 11:58:56 -0800 Subject: [PATCH 02/24] Added edits to intro scene --- active_projects/basel.py | 133 ++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 57 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 40b16628..d4bcb914 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -214,6 +214,36 @@ class ScaleLightSources(Transform): ### +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 9, + height = 5, + ) + circles = bubble[:3] + angle = -15*DEGREES + circles.rotate(angle, about_point = bubble.get_bubble_center()) + circles.shift(LARGE_BUFF*LEFT) + for circle in circles: + circle.rotate(-angle) + bubble.pin_to(randy) + bubble.shift_onto_screen() + + self.play( + randy.change, "thinking", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(2) + self.play(randy.change, "hooray", bubble) + self.wait(2) + class IntroScene(PiCreatureScene): CONFIG = { @@ -223,23 +253,17 @@ class IntroScene(PiCreatureScene): } def construct(self): - randy = self.get_primary_pi_creature() randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.build_up_sum_on_number_line() self.show_pi_answer() self.other_pi_formulas() self.refocus_on_euler_sum() - - - def build_up_euler_sum(self): - - self.euler_sum = TexMobject( + euler_sum = self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", "{1 \\over 9}", "+", @@ -248,16 +272,15 @@ class IntroScene(PiCreatureScene): "\\cdots", "=", arg_separator = " \\, " ) + equals_sign = euler_sum.get_part_by_tex("=") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - terms = [1./n**2 for n in range(1,6)] + terms = [1./n**2 for n in range(1,100)] partial_results_values = np.cumsum(terms) - self.play( - FadeIn(self.euler_sum[0], run_time = self.duration) - ) + self.play(FadeIn(euler_sum[0])) equals_sign = self.euler_sum.get_part_by_tex("=") @@ -265,49 +288,7 @@ class IntroScene(PiCreatureScene): 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(LIGHT_COLOR) - 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): + ## Number line self.number_line = NumberLine( x_min = 0, @@ -365,6 +346,47 @@ class IntroScene(PiCreatureScene): ) + ## + + + 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(LIGHT_COLOR) + 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 show_pi_answer(self): self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) @@ -373,7 +395,6 @@ class IntroScene(PiCreatureScene): submobject_to_align = self.pi_answer[-2]) self.play(ReplacementTransform(self.q_marks, self.pi_answer)) - def other_pi_formulas(self): self.play( @@ -409,8 +430,6 @@ class IntroScene(PiCreatureScene): Write(self.wallis_product) ) - - def refocus_on_euler_sum(self): self.euler_sum.add(self.pi_answer) From 671b6e6fe88375088a32cada0aed0af183899ea9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 16:13:28 -0800 Subject: [PATCH 03/24] Changes to basel, and replication of the file --- active_projects/basel.py | 479 ++++-- active_projects/basel2.py | 2909 +++++++++++++++++++++++++++++++++++++ 2 files changed, 3299 insertions(+), 89 deletions(-) create mode 100644 active_projects/basel2.py diff --git a/active_projects/basel.py b/active_projects/basel.py index d4bcb914..c975060e 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -247,9 +247,10 @@ class ThinkAboutPondScene(PiCreatureScene): class IntroScene(PiCreatureScene): CONFIG = { - "rect_height" : 0.2, + "rect_height" : 0.1, "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF + "eq_spacing" : 3 * MED_LARGE_BUFF, + "n_rects_to_show" : 30, } def construct(self): @@ -257,12 +258,12 @@ class IntroScene(PiCreatureScene): randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.show_pi_answer() - self.other_pi_formulas() - self.refocus_on_euler_sum() - + self.show_history() + # self.other_pi_formulas() + # self.refocus_on_euler_sum() def build_up_euler_sum(self): + morty = self.pi_creature euler_sum = self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", @@ -273,24 +274,26 @@ class IntroScene(PiCreatureScene): arg_separator = " \\, " ) equals_sign = euler_sum.get_part_by_tex("=") + plusses = euler_sum.get_parts_by_tex("+") + term_mobjects = euler_sum.get_parts_by_tex("1") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - terms = [1./n**2 for n in range(1,100)] - partial_results_values = np.cumsum(terms) + max_n = self.n_rects_to_show + terms = [1./(n**2) for n in range(1, max_n + 1)] + series_terms = list(np.cumsum(terms)) + series_terms.append(np.pi**2/6) ##Just force this up there - self.play(FadeIn(euler_sum[0])) - - 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) + partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( + series_terms[1], + num_decimal_points = 2 + ) + partial_sum_decimal.next_to(equals_sign, RIGHT) ## Number line - self.number_line = NumberLine( + number_line = self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1, @@ -301,105 +304,214 @@ class IntroScene(PiCreatureScene): 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() + number_line.add_numbers() + number_line.to_edge(LEFT) + number_line.shift(MED_LARGE_BUFF*UP) # 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) + rects = self.rects = VGroup() + rect_labels = VGroup() + slab_colors = it.cycle([YELLOW, BLUE]) + rect_anims = [] + rect_label_anims = [] - 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, + for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): + color = slab_colors.next() + line = Line(*map(number_line.number_to_point, [t1, t2])) + rect = Rectangle( + stroke_width = 0, + fill_opacity = 1, + fill_color = color ) - rect.stretch_to_fit_width(line.get_width()) + rect.match_width(line) + rect.stretch_to_fit_height(self.rect_height) rect.move_to(line) - self.rects.add(rect) - lines.add(line) + if i <= 5: + if i == 1: + rect_label = TexMobject("1") + else: + rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) + rect_label.scale(0.75) + max_width = 0.7*rect.get_width() + if rect_label.get_width() > max_width: + rect_label.scale_to_fit_width(max_width) + rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) - #self.rects.radial_gradient_highlight(ORIGIN, 5, YELLOW, 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) - ) - - - ## - - - 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) + term_mobject = term_mobjects[i-1] + rect_anim = GrowFromPoint(rect, term_mobject.get_center()) + rect_label_anim = ReplacementTransform( + term_mobject.copy(), rect_label ) - 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), + rect_label = VectorizedPoint() + rect_anim = GrowFromPoint(rect, rect.get_left()) + rect_label_anim = FadeIn(rect_label) + + rects.add(rect) + rect_labels.add(rect_label) + rect_anims.append(rect_anim) + rect_label_anims.append(rect_label_anim) + lines.add(line) + dots = TexMobject("\\dots").scale(0.5) + last_rect = rect_anims[-1].target_mobject + dots.scale_to_fit_width(0.9*last_rect.get_width()) + dots.move_to(last_rect, UP+RIGHT) + rects.submobjects[-1] = dots + rect_anims[-1] = FadeIn(dots) + + self.add(number_line) + self.play(FadeIn(euler_sum[0])) + self.play( + rect_anims[0], + rect_label_anims[0] + ) + for i in range(4): + self.play( + FadeIn(term_mobjects[i+1]), + FadeIn(plusses[i]), + ) + anims = [ + rect_anims[i+1], + rect_label_anims[i+1], + ] + if i == 0: + anims += [ + FadeIn(equals_sign), + FadeIn(partial_sum_decimal) + ] + elif i <= 5: + anims += [ ChangeDecimalToValue( - self.partial_sum_decimal, - partial_results_values[i+1], - run_time = self.duration, + partial_sum_decimal, + series_terms[i+1], + run_time = 1, num_decimal_points = 6, - show_ellipsis = True, position_update_func = lambda m: m.next_to(equals_sign, RIGHT) ) - ) - - self.wait() + ] + self.play(*anims) - self.q_marks = TextMobject("???").highlight(LIGHT_COLOR) - self.q_marks.move_to(self.partial_sum_decimal) + for i in range(4, len(series_terms)-2): + anims = [ + rect_anims[i+1], + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + num_decimal_points = 6, + ), + ] + if i == 5: + anims += [ + FadeIn(euler_sum[-3]), # + + FadeIn(euler_sum[-2]), # ... + ] + self.play(*anims, run_time = 2./i) + + brace = self.brace = Brace(partial_sum_decimal, DOWN) + q_marks = self.q_marks = TextMobject("???") + q_marks.next_to(brace, DOWN) + q_marks.highlight(LIGHT_COLOR) 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) + GrowFromCenter(brace), + Write(q_marks), + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[-1], + num_decimal_points = 6, + ), + morty.change, "confused", + ) + self.wait() + + self.number_line_group = VGroup( + number_line, rects, rect_labels ) - def show_pi_answer(self): + def show_history(self): + # Pietro Mengoli in 1644 + morty = self.pi_creature + pietro = ImageMobject("Pietro_Mengoli") + euler = ImageMobject("Euler") - 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)) + pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") + pietro_words.scale(0.75) + pietro_words.next_to(pietro, DOWN) + pietro.add(pietro_words) + + euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") + euler_words.scale(0.75) + euler_words.next_to(euler, DOWN) + euler.add(euler_words) + + pietro.next_to(SPACE_WIDTH*LEFT, LEFT) + euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) + + pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") + pi_answer.highlight(YELLOW) + pi_answer.move_to(self.partial_sum_decimal, LEFT) + equals_sign = TexMobject("=") + equals_sign.next_to(pi_answer, RIGHT) + pi_answer.shift(SMALL_BUFF*UP) + self.partial_sum_decimal.generate_target() + self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) + + pi = pi_answer[0] + pi_rect = SurroundingRectangle(pi, color = RED) + pi_rect.save_state() + pi_rect.scale_to_fit_height(SPACE_HEIGHT) + pi_rect.center() + pi_rect.set_stroke(width = 0) + squared = pi_answer[1] + squared_rect = SurroundingRectangle(squared, color = BLUE) + + brace = Brace( + VGroup(self.euler_sum, self.partial_sum_decimal.target), + DOWN, buff = SMALL_BUFF + ) + basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) + + self.number_line_group.save_state() + self.play( + pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, + self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, + morty.change, "pondering", + ) + self.wait(2) + self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) + self.wait(2) + self.play( + ReplacementTransform(self.q_marks, pi_answer), + FadeIn(equals_sign), + FadeOut(self.brace), + MoveToTarget(self.partial_sum_decimal) + ) + self.wait() + self.play(morty.change, "surprised") + self.play(pi_rect.restore) + self.wait() + self.play(Transform(pi_rect, squared_rect)) + self.play(FadeOut(pi_rect)) + self.play(morty.change, "hesitant") + self.wait(2) + self.play( + GrowFromCenter(brace), + euler.to_edge, DOWN, + pietro.to_edge, DOWN, + self.number_line_group.restore, + self.number_line_group.shift, LARGE_BUFF*RIGHT, + ) + self.play(Write(basel_text)) + self.play(morty.change, "happy") + self.wait(4) def other_pi_formulas(self): self.play( FadeOut(self.rects), - FadeOut(self.number_line_labels), FadeOut(self.number_line) ) @@ -468,6 +580,195 @@ class IntroScene(PiCreatureScene): self.wait() +class PiHidingWrapper(Scene): + def construct(self): + title = TextMobject("Pi hiding in prime regularities") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen)) + self.wait(2) + +class MathematicalWebOfConnections(PiCreatureScene): + def construct(self): + self.complain_that_pi_is_not_about_circles() + self.show_other_pi_formulas() + self.question_fundamental() + self.draw_circle() + self.remove_all_but_basel_sum() + self.show_web_of_connections() + self.show_light() + + def complain_that_pi_is_not_about_circles(self): + jerk, randy = self.pi_creatures + + words = self.words = TextMobject( + "$\\pi$ is not", + "fundamentally \\\\", + "about circles" + ) + words.highlight_by_tex("fundamentally", YELLOW) + + self.play(PiCreatureSays( + jerk, words, + target_mode = "angry" + )) + self.play(randy.change, "guilty") + self.wait(2) + + def show_other_pi_formulas(self): + jerk, randy = self.pi_creatures + words = self.words + + basel_sum = TexMobject( + "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", + "=", "{\\pi^2 \\over 6}" + ) + leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + 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}") + + basel_sum.move_to(randy) + basel_sum.to_edge(UP) + basel_equals = basel_sum.get_part_by_tex("=") + + formulas = VGroup(basel_sum, leibniz_sum, wallis_product) + formulas.scale(0.75) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + for formula in formulas: + basel_equals_x = basel_equals.get_center()[0] + formula_equals_x = formula.get_part_by_tex("=").get_center()[0] + formula.shift((basel_equals_x - formula_equals_x)*RIGHT) + + formulas.move_to(randy) + formulas.to_edge(UP) + formulas.shift_onto_screen() + self.formulas = formulas + + self.play( + jerk.change, "sassy", + randy.change, "raise_right_hand", + FadeOut(jerk.bubble), + words.next_to, jerk, UP, + FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) + ) + for formula in formulas[1:]: + self.play( + FadeIn( + formula, + submobject_mode = "lagged_start", + run_time = 3 + ), + ) + self.wait() + + def question_fundamental(self): + jerk, randy = self.pi_creatures + words = self.words + fundamentally = words.get_part_by_tex("fundamentally") + words.remove(fundamentally) + + self.play( + fundamentally.move_to, self.pi_creatures, + fundamentally.shift, UP, + FadeOut(words), + jerk.change, "pondering", + randy.change, "pondering", + ) + self.wait() + + question = TextMobject("Does this mean \\\\ anything?") + question.scale(0.8) + question.set_stroke(WHITE, 0.5) + question.next_to(fundamentally, DOWN, LARGE_BUFF) + arrow = Arrow(question, fundamentally) + arrow.highlight(WHITE) + + self.play( + FadeIn(question), + GrowArrow(arrow) + ) + self.wait() + + fundamentally.add(question, arrow) + self.fundamentally = fundamentally + + def draw_circle(self): + semi_circle = Arc(angle = np.pi, radius = 2) + radius = Line(ORIGIN, semi_circle.points[0]) + radius.highlight(BLUE) + semi_circle.highlight(YELLOW) + + VGroup(radius, semi_circle).move_to( + SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, + ) + + decimal = DecimalNumber(0) + def decimal_position_update_func(decimal): + decimal.move_to(semi_circle.points[-1]) + decimal.shift(0.3*radius.get_vector()) + + one = TexMobject("1") + one.next_to(radius, UP) + + self.play(ShowCreation(radius), FadeIn(one)) + self.play( + Rotate(radius, np.pi, about_point = radius.get_start()), + ShowCreation(semi_circle), + ChangeDecimalToValue( + decimal, np.pi, + position_update_func = decimal_position_update_func + ), + MaintainPositionRelativeTo(one, radius), + run_time = 3, + ) + self.wait(2) + + self.circle_group = VGroup(semi_circle, radius, one, decimal) + + def remove_all_but_basel_sum(self): + to_shift_down = VGroup( + self.circle_group, self.pi_creatures, + self.fundamentally, self.formulas[1:], + ) + to_shift_down.generate_target() + for part in to_shift_down.target: + part.move_to(2*SPACE_HEIGHT*DOWN) + + basel_sum = self.formulas[0] + + self.play( + MoveToTarget(to_shift_down), + basel_sum.scale, 1.5, + basel_sum.move_to, 2*DOWN, + ) + + def show_web_of_connections(self): + pass + + def show_light(self): + pass + + ### + + def create_pi_creatures(self): + jerk = PiCreature(color = GREEN_D) + randy = Randolph().flip() + jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) + randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) + + return VGroup(jerk, randy) + + + + + class FirstLighthouseScene(PiCreatureScene): def construct(self): diff --git a/active_projects/basel2.py b/active_projects/basel2.py new file mode 100644 index 00000000..c975060e --- /dev/null +++ b/active_projects/basel2.py @@ -0,0 +1,2909 @@ +#!/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.continual_animation 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 topics.three_dimensions import * + +from topics.light import * + +import types +import functools + +LIGHT_COLOR = YELLOW +INDICATOR_RADIUS = 0.7 +INDICATOR_STROKE_WIDTH = 1 +INDICATOR_STROKE_COLOR = WHITE +INDICATOR_TEXT_COLOR = WHITE +INDICATOR_UPDATE_TIME = 0.2 +FAST_INDICATOR_UPDATE_TIME = 0.1 +OPACITY_FOR_UNIT_INTENSITY = 0.2 +SWITCH_ON_RUN_TIME = 1.5 +FAST_SWITCH_ON_RUN_TIME = 0.1 +NUM_LEVELS = 30 +NUM_CONES = 7 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 +AMBIENT_FULL = 0.5 +AMBIENT_DIMMED = 0.2 +SPOTLIGHT_FULL = 0.9 +SPOTLIGHT_DIMMED = 0.2 + +LIGHT_COLOR = YELLOW +DEGREES = TAU/360 + +inverse_power_law = lambda maxint,scale,cutoff,exponent: \ + (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) +inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) + + +A = np.array([5.,-3.,0.]) +B = np.array([-5.,3.,0.]) +C = np.array([-5.,-3.,0.]) +xA = A[0] +yA = A[1] +xB = B[0] +yB = B[1] +xC = C[0] +yC = C[1] + +# find the coords of the altitude point H +# as the solution of a certain LSE +prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic +prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) +H2 = np.linalg.solve(prelim_matrix,prelim_vector) +H = np.append(H2, 0.) + + + + +class AngleUpdater(ContinualAnimation): + def __init__(self, angle_arc, spotlight, **kwargs): + self.angle_arc = angle_arc + + self.spotlight = spotlight + ContinualAnimation.__init__(self, self.angle_arc, **kwargs) + + def update_mobject(self, dt): + new_arc = self.angle_arc.copy().set_bound_angles( + start = self.spotlight.start_angle(), + stop = self.spotlight.stop_angle() + ) + new_arc.generate_points() + new_arc.move_arc_center_to(self.spotlight.get_source_point()) + self.angle_arc.points = new_arc.points + self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, + at_start = True, at_end = True) + +class LightIndicator(Mobject): + CONFIG = { + "radius": 0.5, + "intensity": 0, + "opacity_for_unit_intensity": 1, + "precision": 3, + "show_reading": True, + "measurement_point": ORIGIN, + "light_source": None + } + + def generate_points(self): + self.background = Circle(color=BLACK, radius = self.radius) + self.background.set_fill(opacity=1.0) + self.foreground = Circle(color=self.color, radius = self.radius) + self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) + + self.add(self.background, self.foreground) + self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) + self.reading.set_fill(color=INDICATOR_TEXT_COLOR) + self.reading.move_to(self.get_center()) + if self.show_reading: + self.add(self.reading) + + def set_intensity(self, new_int): + self.intensity = new_int + new_opacity = min(1, new_int * self.opacity_for_unit_intensity) + self.foreground.set_fill(opacity=new_opacity) + ChangeDecimalToValue(self.reading, new_int).update(1) + return self + + def get_measurement_point(self): + if self.measurement_point != None: + return self.measurement_point + else: + return self.get_center() + + + def measured_intensity(self): + distance = np.linalg.norm(self.get_measurement_point() - + self.light_source.get_source_point()) + intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity + return intensity + + def continual_update(self): + if self.light_source == None: + print "Indicator cannot update, reason: no light source found" + self.set_intensity(self.measured_intensity()) + +class UpdateLightIndicator(AnimationGroup): + + def __init__(self, indicator, intensity, **kwargs): + if not isinstance(indicator,LightIndicator): + raise Exception("This transform applies only to LightIndicator") + + target_foreground = indicator.copy().set_intensity(intensity).foreground + change_opacity = Transform( + indicator.foreground, target_foreground + ) + changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) + AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) + self.mobject = indicator + +class ContinualLightIndicatorUpdate(ContinualAnimation): + + def update_mobject(self,dt): + self.mobject.continual_update() + +def copy_func(f): + """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" + g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, + argdefs=f.func_defaults, + closure=f.func_closure) + g = functools.update_wrapper(g, f) + return g + +class ScaleLightSources(Transform): + + def __init__(self, light_sources_mob, factor, about_point = None, **kwargs): + + if about_point == None: + about_point = light_sources_mob.get_center() + + ls_target = light_sources_mob.copy() + + for submob in ls_target: + + if type(submob) == LightSource: + + new_sp = submob.source_point.copy() # a mob + new_sp.scale(factor,about_point = about_point) + submob.move_source_to(new_sp.get_location()) + + #ambient_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: ambient_of(r/factor) + #submob.ambient_light.opacity_function = new_of + + #spotlight_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: spotlight_of(r/factor) + #submob.spotlight.change_opacity_function(new_of) + + new_r = factor * submob.radius + submob.set_radius(new_r) + + new_r = factor * submob.ambient_light.radius + submob.ambient_light.radius = new_r + + new_r = factor * submob.spotlight.radius + submob.spotlight.radius = new_r + + submob.ambient_light.scale_about_point(factor, new_sp.get_center()) + submob.spotlight.scale_about_point(factor, new_sp.get_center()) + + + Transform.__init__(self,light_sources_mob,ls_target,**kwargs) + + +### + +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 9, + height = 5, + ) + circles = bubble[:3] + angle = -15*DEGREES + circles.rotate(angle, about_point = bubble.get_bubble_center()) + circles.shift(LARGE_BUFF*LEFT) + for circle in circles: + circle.rotate(-angle) + bubble.pin_to(randy) + bubble.shift_onto_screen() + + self.play( + randy.change, "thinking", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(2) + self.play(randy.change, "hooray", bubble) + self.wait(2) + +class IntroScene(PiCreatureScene): + + CONFIG = { + "rect_height" : 0.1, + "duration" : 1.0, + "eq_spacing" : 3 * MED_LARGE_BUFF, + "n_rects_to_show" : 30, + } + + def construct(self): + randy = self.get_primary_pi_creature() + randy.scale(0.7).to_corner(DOWN+RIGHT) + + self.build_up_euler_sum() + self.show_history() + # self.other_pi_formulas() + # self.refocus_on_euler_sum() + + def build_up_euler_sum(self): + morty = self.pi_creature + euler_sum = self.euler_sum = TexMobject( + "1", "+", + "{1 \\over 4}", "+", + "{1 \\over 9}", "+", + "{1 \\over 16}", "+", + "{1 \\over 25}", "+", + "\\cdots", "=", + arg_separator = " \\, " + ) + equals_sign = euler_sum.get_part_by_tex("=") + plusses = euler_sum.get_parts_by_tex("+") + term_mobjects = euler_sum.get_parts_by_tex("1") + + self.euler_sum.to_edge(UP) + self.euler_sum.shift(2*LEFT) + + max_n = self.n_rects_to_show + terms = [1./(n**2) for n in range(1, max_n + 1)] + series_terms = list(np.cumsum(terms)) + series_terms.append(np.pi**2/6) ##Just force this up there + + partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( + series_terms[1], + num_decimal_points = 2 + ) + partial_sum_decimal.next_to(equals_sign, RIGHT) + + ## Number line + + number_line = 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 + ) + number_line.add_numbers() + number_line.to_edge(LEFT) + number_line.shift(MED_LARGE_BUFF*UP) + + # create slabs for series terms + + lines = VGroup() + rects = self.rects = VGroup() + rect_labels = VGroup() + slab_colors = it.cycle([YELLOW, BLUE]) + rect_anims = [] + rect_label_anims = [] + + for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): + color = slab_colors.next() + line = Line(*map(number_line.number_to_point, [t1, t2])) + rect = Rectangle( + stroke_width = 0, + fill_opacity = 1, + fill_color = color + ) + rect.match_width(line) + rect.stretch_to_fit_height(self.rect_height) + rect.move_to(line) + + if i <= 5: + if i == 1: + rect_label = TexMobject("1") + else: + rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) + rect_label.scale(0.75) + max_width = 0.7*rect.get_width() + if rect_label.get_width() > max_width: + rect_label.scale_to_fit_width(max_width) + rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + + term_mobject = term_mobjects[i-1] + rect_anim = GrowFromPoint(rect, term_mobject.get_center()) + rect_label_anim = ReplacementTransform( + term_mobject.copy(), rect_label + ) + else: + rect_label = VectorizedPoint() + rect_anim = GrowFromPoint(rect, rect.get_left()) + rect_label_anim = FadeIn(rect_label) + + rects.add(rect) + rect_labels.add(rect_label) + rect_anims.append(rect_anim) + rect_label_anims.append(rect_label_anim) + lines.add(line) + dots = TexMobject("\\dots").scale(0.5) + last_rect = rect_anims[-1].target_mobject + dots.scale_to_fit_width(0.9*last_rect.get_width()) + dots.move_to(last_rect, UP+RIGHT) + rects.submobjects[-1] = dots + rect_anims[-1] = FadeIn(dots) + + self.add(number_line) + self.play(FadeIn(euler_sum[0])) + self.play( + rect_anims[0], + rect_label_anims[0] + ) + for i in range(4): + self.play( + FadeIn(term_mobjects[i+1]), + FadeIn(plusses[i]), + ) + anims = [ + rect_anims[i+1], + rect_label_anims[i+1], + ] + if i == 0: + anims += [ + FadeIn(equals_sign), + FadeIn(partial_sum_decimal) + ] + elif i <= 5: + anims += [ + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + run_time = 1, + num_decimal_points = 6, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ] + self.play(*anims) + + for i in range(4, len(series_terms)-2): + anims = [ + rect_anims[i+1], + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + num_decimal_points = 6, + ), + ] + if i == 5: + anims += [ + FadeIn(euler_sum[-3]), # + + FadeIn(euler_sum[-2]), # ... + ] + self.play(*anims, run_time = 2./i) + + brace = self.brace = Brace(partial_sum_decimal, DOWN) + q_marks = self.q_marks = TextMobject("???") + q_marks.next_to(brace, DOWN) + q_marks.highlight(LIGHT_COLOR) + + self.play( + GrowFromCenter(brace), + Write(q_marks), + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[-1], + num_decimal_points = 6, + ), + morty.change, "confused", + ) + self.wait() + + self.number_line_group = VGroup( + number_line, rects, rect_labels + ) + + def show_history(self): + # Pietro Mengoli in 1644 + morty = self.pi_creature + pietro = ImageMobject("Pietro_Mengoli") + euler = ImageMobject("Euler") + + pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") + pietro_words.scale(0.75) + pietro_words.next_to(pietro, DOWN) + pietro.add(pietro_words) + + euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") + euler_words.scale(0.75) + euler_words.next_to(euler, DOWN) + euler.add(euler_words) + + pietro.next_to(SPACE_WIDTH*LEFT, LEFT) + euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) + + pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") + pi_answer.highlight(YELLOW) + pi_answer.move_to(self.partial_sum_decimal, LEFT) + equals_sign = TexMobject("=") + equals_sign.next_to(pi_answer, RIGHT) + pi_answer.shift(SMALL_BUFF*UP) + self.partial_sum_decimal.generate_target() + self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) + + pi = pi_answer[0] + pi_rect = SurroundingRectangle(pi, color = RED) + pi_rect.save_state() + pi_rect.scale_to_fit_height(SPACE_HEIGHT) + pi_rect.center() + pi_rect.set_stroke(width = 0) + squared = pi_answer[1] + squared_rect = SurroundingRectangle(squared, color = BLUE) + + brace = Brace( + VGroup(self.euler_sum, self.partial_sum_decimal.target), + DOWN, buff = SMALL_BUFF + ) + basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) + + self.number_line_group.save_state() + self.play( + pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, + self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, + morty.change, "pondering", + ) + self.wait(2) + self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) + self.wait(2) + self.play( + ReplacementTransform(self.q_marks, pi_answer), + FadeIn(equals_sign), + FadeOut(self.brace), + MoveToTarget(self.partial_sum_decimal) + ) + self.wait() + self.play(morty.change, "surprised") + self.play(pi_rect.restore) + self.wait() + self.play(Transform(pi_rect, squared_rect)) + self.play(FadeOut(pi_rect)) + self.play(morty.change, "hesitant") + self.wait(2) + self.play( + GrowFromCenter(brace), + euler.to_edge, DOWN, + pietro.to_edge, DOWN, + self.number_line_group.restore, + self.number_line_group.shift, LARGE_BUFF*RIGHT, + ) + self.play(Write(basel_text)) + self.play(morty.change, "happy") + self.wait(4) + + def other_pi_formulas(self): + + self.play( + FadeOut(self.rects), + 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 thinks of a circle + + q_circle = Circle( + stroke_color = YELLOW, + fill_color = YELLOW, + fill_opacity = 0.5, + radius = 0.4, + stroke_width = 10.0 + ) + q_mark = TexMobject("?") + q_mark.next_to(q_circle) + + thought = Group(q_circle, q_mark) + q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + self.pi_creature_thinks(thought,target_mode = "confused", + bubble_kwargs = { "height" : 2, "width" : 3 }) + + self.wait() + +class PiHidingWrapper(Scene): + def construct(self): + title = TextMobject("Pi hiding in prime regularities") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen)) + self.wait(2) + +class MathematicalWebOfConnections(PiCreatureScene): + def construct(self): + self.complain_that_pi_is_not_about_circles() + self.show_other_pi_formulas() + self.question_fundamental() + self.draw_circle() + self.remove_all_but_basel_sum() + self.show_web_of_connections() + self.show_light() + + def complain_that_pi_is_not_about_circles(self): + jerk, randy = self.pi_creatures + + words = self.words = TextMobject( + "$\\pi$ is not", + "fundamentally \\\\", + "about circles" + ) + words.highlight_by_tex("fundamentally", YELLOW) + + self.play(PiCreatureSays( + jerk, words, + target_mode = "angry" + )) + self.play(randy.change, "guilty") + self.wait(2) + + def show_other_pi_formulas(self): + jerk, randy = self.pi_creatures + words = self.words + + basel_sum = TexMobject( + "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", + "=", "{\\pi^2 \\over 6}" + ) + leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + 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}") + + basel_sum.move_to(randy) + basel_sum.to_edge(UP) + basel_equals = basel_sum.get_part_by_tex("=") + + formulas = VGroup(basel_sum, leibniz_sum, wallis_product) + formulas.scale(0.75) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + for formula in formulas: + basel_equals_x = basel_equals.get_center()[0] + formula_equals_x = formula.get_part_by_tex("=").get_center()[0] + formula.shift((basel_equals_x - formula_equals_x)*RIGHT) + + formulas.move_to(randy) + formulas.to_edge(UP) + formulas.shift_onto_screen() + self.formulas = formulas + + self.play( + jerk.change, "sassy", + randy.change, "raise_right_hand", + FadeOut(jerk.bubble), + words.next_to, jerk, UP, + FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) + ) + for formula in formulas[1:]: + self.play( + FadeIn( + formula, + submobject_mode = "lagged_start", + run_time = 3 + ), + ) + self.wait() + + def question_fundamental(self): + jerk, randy = self.pi_creatures + words = self.words + fundamentally = words.get_part_by_tex("fundamentally") + words.remove(fundamentally) + + self.play( + fundamentally.move_to, self.pi_creatures, + fundamentally.shift, UP, + FadeOut(words), + jerk.change, "pondering", + randy.change, "pondering", + ) + self.wait() + + question = TextMobject("Does this mean \\\\ anything?") + question.scale(0.8) + question.set_stroke(WHITE, 0.5) + question.next_to(fundamentally, DOWN, LARGE_BUFF) + arrow = Arrow(question, fundamentally) + arrow.highlight(WHITE) + + self.play( + FadeIn(question), + GrowArrow(arrow) + ) + self.wait() + + fundamentally.add(question, arrow) + self.fundamentally = fundamentally + + def draw_circle(self): + semi_circle = Arc(angle = np.pi, radius = 2) + radius = Line(ORIGIN, semi_circle.points[0]) + radius.highlight(BLUE) + semi_circle.highlight(YELLOW) + + VGroup(radius, semi_circle).move_to( + SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, + ) + + decimal = DecimalNumber(0) + def decimal_position_update_func(decimal): + decimal.move_to(semi_circle.points[-1]) + decimal.shift(0.3*radius.get_vector()) + + one = TexMobject("1") + one.next_to(radius, UP) + + self.play(ShowCreation(radius), FadeIn(one)) + self.play( + Rotate(radius, np.pi, about_point = radius.get_start()), + ShowCreation(semi_circle), + ChangeDecimalToValue( + decimal, np.pi, + position_update_func = decimal_position_update_func + ), + MaintainPositionRelativeTo(one, radius), + run_time = 3, + ) + self.wait(2) + + self.circle_group = VGroup(semi_circle, radius, one, decimal) + + def remove_all_but_basel_sum(self): + to_shift_down = VGroup( + self.circle_group, self.pi_creatures, + self.fundamentally, self.formulas[1:], + ) + to_shift_down.generate_target() + for part in to_shift_down.target: + part.move_to(2*SPACE_HEIGHT*DOWN) + + basel_sum = self.formulas[0] + + self.play( + MoveToTarget(to_shift_down), + basel_sum.scale, 1.5, + basel_sum.move_to, 2*DOWN, + ) + + def show_web_of_connections(self): + pass + + def show_light(self): + pass + + ### + + def create_pi_creatures(self): + jerk = PiCreature(color = GREEN_D) + randy = Randolph().flip() + jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) + randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) + + return VGroup(jerk, randy) + + + + + +class FirstLighthouseScene(PiCreatureScene): + + def construct(self): + self.remove(self.get_primary_pi_creature()) + 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.6, + stroke_width = 1, + numbers_with_elongated_ticks = range(1,5), + numbers_to_show = range(1,5), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + origin_point = self.number_line.number_to_point(0) + + self.default_pi_creature_class = Randolph + randy = self.get_primary_pi_creature() + + randy.scale(0.5) + randy.flip() + right_pupil = randy.pupils[1] + randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + + + + light_indicator = LightIndicator(radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR) + light_indicator.reading.scale(0.8) + + bubble = ThoughtBubble(direction = RIGHT, + width = 2.5, height = 3.5) + bubble.next_to(randy,LEFT+UP) + bubble.add_content(light_indicator) + + self.play( + randy.change, "wave_2", + ShowCreation(bubble), + FadeIn(light_indicator) + ) + + light_sources = [] + + + euler_sum_above = TexMobject("1", "+", "{1\over 4}", + "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") + + for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above): + #horizontal alignment with tick marks + term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2) + # vertical alignment with light indicator + old_y = term.get_center()[1] + new_y = light_indicator.get_center()[1] + term.shift([0,new_y - old_y,0]) + + + + for i in range(1,NUM_CONES+1): + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 12.0, + ) + point = self.number_line.number_to_point(i) + light_source.move_source_to(point) + light_sources.append(light_source) + + + for ls in light_sources: + self.add_foreground_mobject(ls.lighthouse) + + light_indicator.set_intensity(0) + + intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) + opacities = intensities * light_indicator.opacity_for_unit_intensity + + self.remove_foreground_mobjects(light_indicator) + + + # slowly switch on visible light cones and increment indicator + for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func( + smooth,indicator_start_time,indicator_stop_time) + self.play( + SwitchOn(light_source.ambient_light), + FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, + rate_func = indicator_rate_func), + FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, + rate_func = indicator_rate_func), + # this last line *technically* fades in the last term, but it is off-screen + ChangeDecimalToValue(light_indicator.reading,intensities[i], + rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) + ) + + if i == 0: + # move a copy out of the thought bubble for comparison + light_indicator_copy = light_indicator.copy() + old_y = light_indicator_copy.get_center()[1] + new_y = self.number_line.get_center()[1] + self.play( + light_indicator_copy.shift,[0, new_y - old_y,0] + ) + + # quickly switch on off-screen light cones and increment indicator + for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + smooth,indicator_start_time,indicator_stop_time) + self.play( + SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), + ChangeDecimalToValue(light_indicator.reading,intensities[i-1], + rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1]) + ) + + + # show limit value in light indicator and an equals sign + limit_reading = TexMobject("{\pi^2 \over 6}") + limit_reading.move_to(light_indicator.reading) + + equals_sign = TexMobject("=") + equals_sign.next_to(randy, UP) + old_y = equals_sign.get_center()[1] + new_y = euler_sum_above.get_center()[1] + equals_sign.shift([0,new_y - old_y,0]) + + self.play( + FadeOut(light_indicator.reading), + FadeIn(limit_reading), + FadeIn(equals_sign), + ) + + + + self.wait() + +class SingleLighthouseScene(PiCreatureScene): + + def construct(self): + + self.setup_elements() + self.setup_angle() # spotlight and angle msmt change when screen rotates + self.rotate_screen() + self.morph_lighthouse_into_sun() + + + def setup_elements(self): + + self.remove(self.get_primary_pi_creature()) + + SCREEN_SIZE = 3.0 + DISTANCE_FROM_LIGHTHOUSE = 10.0 + source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] + observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + + # Light source + + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL + ) + + self.light_source.move_source_to(source_point) + + + # Pi Creature + + morty = self.get_primary_pi_creature() + morty.scale(0.5) + morty.move_to(observer_point) + morty.shift(2*OUT) + self.add_foreground_mobject(morty) + + self.add(self.light_source.lighthouse) + + self.play( + SwitchOn(self.light_source.ambient_light) + ) + + # Screen + + self.screen = Rectangle( + width = 0.1, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) + + self.screen.rotate(-TAU/6) + self.screen.next_to(morty,LEFT) + + self.light_source.set_screen(self.screen) + + # Animations + + self.play(FadeIn(self.screen)) + + self.light_source.set_max_opacity_spotlight(0.001) + self.add(self.light_source.spotlight) + + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + # just calling .dim_ambient via ApplyMethod does not work, why? + dimmed_ambient_light = self.light_source.ambient_light.deepcopy() + dimmed_ambient_light.dimming(AMBIENT_DIMMED) + + self.play( + Transform(self.light_source.ambient_light,dimmed_ambient_light), + self.light_source.set_max_opacity_spotlight,1.0, + FadeIn(self.light_source.shadow) + ) + + self.add_foreground_mobject(morty) + + + + + def setup_angle(self): + + self.wait() + + + pointing_screen_at_source = Rotate(self.screen,TAU/6) + self.play(pointing_screen_at_source) + + # angle msmt (arc) + + arc_angle = self.light_source.spotlight.opening_angle() + # draw arc arrows to show the opening angle + self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), + angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) + #angle_arc.add_tip(at_start = True, at_end = True) + self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) + + + # angle msmt (decimal number) + + self.angle_indicator = DecimalNumber(arc_angle / DEGREES, + num_decimal_points = 0, + unit = "^\\circ") + self.angle_indicator.next_to(self.angle_arc,RIGHT) + + angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES + ca1 = ContinualChangingDecimal(self.angle_indicator,angle_update_func) + self.add(ca1) + + ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight) + self.add(ca2) + + self.play( + ShowCreation(self.angle_arc), + ShowCreation(self.angle_indicator) + ) + + self.wait() + + def rotate_screen(self): + + + + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) + self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) + + self.wait() + + self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + + self.wait() + + self.play(Rotate(self.light_source.spotlight.screen, TAU/4)) + +### The following is supposed to morph the scene into the Earth scene, +### but it doesn't work + + + def morph_lighthouse_into_sun(self): + + + + sun_position = [-100,0,0] + + + self.play( + FadeOut(self.angle_arc), + FadeOut(self.angle_indicator) + ) + + self.sun = self.light_source.deepcopy() + + #self.sun.num_levels = NUM_LEVELS, + #self.sun.set_radius(150) + #self.sun.set_max_opacity_ambient(AMBIENT_FULL) + + + + self.sun.spotlight.change_opacity_function(lambda r: 0.5) + self.sun.set_radius(150) + self.sun.move_source_to(sun_position) + + # self.sun.update() + + # self.add(self.sun) + # temporarily remove the screen tracker while we move the source + #self.remove(self.screen_tracker) + + #print self.sun.spotlight.get_source_point() + + self.play( + #self.light_source.spotlight.move_source_to,sun_position, + Transform(self.light_source,self.sun) + ) + + #self.add(ScreenTracker(self.sun)) + + self.wait() + +class EarthScene(Scene): + + def construct(self): + + SCREEN_THICKNESS = 10 + + self.screen_height = 2.0 + self.brightness_rect_height = 1.0 + + # screen + self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) + self.screen.set_points_as_corners([ + [3,-self.screen_height/2,0], + [3,self.screen_height/2,0] + ]) + + # Earth + + earth_center_x = 2 + earth_center = [earth_center_x,0,0] + earth_radius = 3 + earth = Circle(radius = earth_radius) + earth.move_to(earth_center) + #self.remove(self.screen_tracker) + + theta0 = 70 * DEGREES + dtheta = 10 * DEGREES + theta1 = theta0 + dtheta + theta = (theta0 + theta1)/2 + + earth.add(self.screen) + + # Morty + + morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) + self.add_foreground_mobject(morty) + + + # Light source (far-away Sun) + + sun_position = [-100,0,0] + + self.sun = LightSource( + opacity_function = lambda r : 0.5, + max_opacity_ambient = 0, + max_opacity_spotlight = 0.5, + num_levels = NUM_LEVELS, + radius = 150, + screen = self.screen + ) + + self.sun.move_source_to(sun_position) + + + # Add elements to scene + + self.add(self.sun,self.screen) + self.bring_to_back(self.sun.shadow) + screen_tracker = ScreenTracker(self.sun) + + self.add(screen_tracker) + + self.wait() + + self.play(FadeIn(earth)) + self.bring_to_back(earth) + + # move screen onto Earth + screen_on_earth = self.screen.deepcopy() + screen_on_earth.rotate(-theta) + screen_on_earth.scale(0.3) + screen_on_earth.move_to(np.array([ + earth_center_x - earth_radius * np.cos(theta), + earth_radius * np.sin(theta), + 0])) + + polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) + + self.play( + Transform(self.screen, screen_on_earth), + Transform(morty,polar_morty) + ) + + self.wait() + + + tropical_morty = polar_morty.copy() + tropical_morty.move_to(np.array([0,0,0])) + morty.target = tropical_morty + + # move screen to equator + + self.play( + Rotate(earth, theta0 + dtheta/2,run_time = 3), + MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), + ) + +class ScreenShapingScene(ThreeDScene): + + + # TODO: Morph from Earth Scene into this scene + + def construct(self): + + #self.force_skipping() + self.setup_elements() + self.deform_screen() + self.create_brightness_rect() + self.slant_screen() + self.unslant_screen() + self.left_shift_screen_while_showing_light_indicator() + self.add_distance_arrow() + self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() + self.left_shift_again() + #self.revert_to_original_skipping_status() + + self.morph_into_3d() + self.prove_inverse_square_law() + + + def setup_elements(self): + + SCREEN_THICKNESS = 10 + + self.screen_height = 1.0 + self.brightness_rect_height = 1.0 + + # screen + self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], + path_arc = 0, num_arc_anchors = 10) + + # light source + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,5,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity = 0.2 + #screen = self.screen + ) + self.light_source.set_max_opacity_spotlight(0.2) + + self.light_source.set_screen(self.screen) + self.light_source.move_source_to([-5,0,0]) + + # abbreviations + self.ambient_light = self.light_source.ambient_light + self.spotlight = self.light_source.spotlight + self.lighthouse = self.light_source.lighthouse + + + #self.add_foreground_mobject(self.light_source.shadow) + + # Morty + self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) + + # Add everything to the scene + self.add(self.lighthouse) + + self.wait() + self.play(FadeIn(self.screen)) + self.wait() + + self.add_foreground_mobject(self.screen) + self.add_foreground_mobject(self.morty) + + self.play(SwitchOn(self.ambient_light)) + + self.play( + SwitchOn(self.spotlight), + self.light_source.dim_ambient + ) + + screen_tracker = ScreenTracker(self.light_source) + self.add(screen_tracker) + + + self.wait() + + + + def deform_screen(self): + + self.wait() + + self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, 0)) + + + + + def create_brightness_rect(self): + + # in preparation for the slanting, create a rectangle that shows the brightness + + # a rect a zero width overlaying the screen + # so we can morph it into the brightness rect above + brightness_rect0 = Rectangle(width = 0, + height = self.screen_height).move_to(self.screen.get_center()) + self.add_foreground_mobject(brightness_rect0) + + self.brightness_rect = Rectangle(width = self.brightness_rect_height, + height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) + + self.brightness_rect.next_to(self.screen, UP, buff = 1) + + self.play( + ReplacementTransform(brightness_rect0,self.brightness_rect) + ) + + self.unslanted_screen = self.screen.deepcopy() + self.unslanted_brightness_rect = self.brightness_rect.copy() + # for unslanting the screen later + + + def slant_screen(self): + + SLANTING_AMOUNT = 0.1 + + lower_screen_point, upper_screen_point = self.screen.get_start_and_end() + + lower_slanted_screen_point = interpolate( + lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT + ) + upper_slanted_screen_point = interpolate( + upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT + ) + + self.slanted_brightness_rect = self.brightness_rect.copy() + self.slanted_brightness_rect.width *= 2 + self.slanted_brightness_rect.generate_points() + self.slanted_brightness_rect.set_fill(opacity = 0.25) + + self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, + path_arc = 0, num_arc_anchors = 10) + self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) + + self.play( + Transform(self.screen,self.slanted_screen), + Transform(self.brightness_rect,self.slanted_brightness_rect), + ) + + + + def unslant_screen(self): + + self.wait() + self.play( + Transform(self.screen,self.unslanted_screen), + Transform(self.brightness_rect,self.unslanted_brightness_rect), + ) + + + + + def left_shift_screen_while_showing_light_indicator(self): + + # Scene 5: constant screen size, changing opening angle + + OPACITY_FOR_UNIT_INTENSITY = 1 + + # let's use an actual light indicator instead of just rects + + self.indicator_intensity = 0.25 + indicator_height = 1.25 * self.screen_height + + self.indicator = LightIndicator(radius = indicator_height/2, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR, + precision = 2) + self.indicator.set_intensity(self.indicator_intensity) + + self.indicator.move_to(self.brightness_rect.get_center()) + + self.play( + FadeOut(self.brightness_rect), + FadeIn(self.indicator) + ) + + # Here some digits of the indicator disappear... + + self.add_foreground_mobject(self.indicator.reading) + + + self.unit_indicator_intensity = 1.0 # intensity at distance 1 + # (where we are about to move to) + + self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 + + self.play( + self.screen.shift,[-self.left_shift,0,0], + self.morty.shift,[-self.left_shift,0,0], + self.indicator.shift,[-self.left_shift,0,0], + self.indicator.set_intensity,self.unit_indicator_intensity, + ) + + + + def add_distance_arrow(self): + + # distance arrow (length 1) + left_x = self.spotlight.get_source_point()[0] + right_x = self.screen.get_center()[0] + arrow_y = -2 + arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) + arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) + arrow1.set_fill(color = WHITE) + arrow2.set_fill(color = WHITE) + distance_decimal = Integer(1).next_to(arrow1,DOWN) + self.arrow = VGroup(arrow1, arrow2,distance_decimal) + self.add(self.arrow) + + + # distance arrow (length 2) + # will be morphed into + self.distance_to_source = right_x - left_x + new_right_x = left_x + 2 * self.distance_to_source + new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) + new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) + new_arrow1.set_fill(color = WHITE) + new_arrow2.set_fill(color = WHITE) + new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) + self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) + # don't add it yet + + + def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): + + self.wait() + + self.play( + ReplacementTransform(self.arrow,self.new_arrow), + ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), + ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), + + ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), + # this should trigger ChangingDecimal, but it doesn't + # maybe bc it's an anim within an anim? + + ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), + ) + + + def left_shift_again(self): + + self.wait() + + self.play( + ReplacementTransform(self.new_arrow,self.arrow), + ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), + #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), + ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), + ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), + ) + + def morph_into_3d(self): + + + self.play(FadeOut(self.morty)) + + axes = ThreeDAxes() + self.add(axes) + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + phi1 = 60 * DEGREES # angle from zenith (0 to 180) + theta1 = -135 * DEGREES # azimuth (0 to 360) + distance1 = distance0 + target_point = self.camera.get_spherical_coords(phi1, theta1, distance1) + + dphi = phi1 - phi0 + dtheta = theta1 - theta0 + + camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) + projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) + + new_screen0 = Rectangle(height = self.screen_height, + width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen0.rotate(TAU/4,axis = DOWN) + new_screen0.move_to(self.screen.get_center()) + self.add(new_screen0) + self.remove(self.screen) + self.light_source.set_screen(new_screen0) + + self.light_source.set_camera(self.camera) + + + new_screen = Rectangle(height = self.screen_height, + width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen.rotate(TAU/4,axis = DOWN) + new_screen.move_to(self.screen.get_center()) + + self.add_foreground_mobject(self.ambient_light) + self.add_foreground_mobject(self.spotlight) + self.add_foreground_mobject(self.light_source.shadow) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + + ) + self.remove(self.spotlight) + + self.play(Transform(new_screen0,new_screen)) + + self.wait() + + self.unit_screen = new_screen0 # better name + + + + def prove_inverse_square_law(self): + + def orientate(mob): + mob.move_to(self.unit_screen) + mob.rotate(TAU/4, axis = LEFT) + mob.rotate(TAU/4, axis = OUT) + mob.rotate(TAU/2, axis = LEFT) + return mob + + unit_screen_copy = self.unit_screen.copy() + fourfold_screen = self.unit_screen.copy() + fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) + + self.remove(self.spotlight) + + + reading1 = TexMobject("1") + orientate(reading1) + + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + + + self.play( + Transform(self.unit_screen, fourfold_screen) + ) + + reading21 = TexMobject("{1\over 4}").scale(0.8) + orientate(reading21) + reading22 = reading21.deepcopy() + reading23 = reading21.deepcopy() + reading24 = reading21.deepcopy() + reading21.shift(0.5*OUT + 0.5*UP) + reading22.shift(0.5*OUT + 0.5*DOWN) + reading23.shift(0.5*IN + 0.5*UP) + reading24.shift(0.5*IN + 0.5*DOWN) + + + corners = fourfold_screen.get_anchors() + midpoint1 = (corners[0] + corners[1])/2 + midpoint2 = (corners[1] + corners[2])/2 + midpoint3 = (corners[2] + corners[3])/2 + midpoint4 = (corners[3] + corners[0])/2 + midline1 = Line(midpoint1, midpoint3) + midline2 = Line(midpoint2, midpoint4) + + self.play( + ShowCreation(midline1), + ShowCreation(midline2) + ) + + self.play( + FadeIn(reading21), + FadeIn(reading22), + FadeIn(reading23), + FadeIn(reading24), + ) + + self.wait() + + self.play( + FadeOut(reading21), + FadeOut(reading22), + FadeOut(reading23), + FadeOut(reading24), + FadeOut(midline1), + FadeOut(midline2) + ) + + ninefold_screen = unit_screen_copy.copy() + ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + + self.play( + Transform(self.unit_screen, ninefold_screen) + ) + + reading31 = TexMobject("{1\over 9}").scale(0.8) + orientate(reading31) + reading32 = reading31.deepcopy() + reading33 = reading31.deepcopy() + reading34 = reading31.deepcopy() + reading35 = reading31.deepcopy() + reading36 = reading31.deepcopy() + reading37 = reading31.deepcopy() + reading38 = reading31.deepcopy() + reading39 = reading31.deepcopy() + reading31.shift(IN + UP) + reading32.shift(IN) + reading33.shift(IN + DOWN) + reading34.shift(UP) + reading35.shift(ORIGIN) + reading36.shift(DOWN) + reading37.shift(OUT + UP) + reading38.shift(OUT) + reading39.shift(OUT + DOWN) + + corners = ninefold_screen.get_anchors() + midpoint11 = (2*corners[0] + corners[1])/3 + midpoint12 = (corners[0] + 2*corners[1])/3 + midpoint21 = (2*corners[1] + corners[2])/3 + midpoint22 = (corners[1] + 2*corners[2])/3 + midpoint31 = (2*corners[2] + corners[3])/3 + midpoint32 = (corners[2] + 2*corners[3])/3 + midpoint41 = (2*corners[3] + corners[0])/3 + midpoint42 = (corners[3] + 2*corners[0])/3 + midline11 = Line(midpoint11, midpoint32) + midline12 = Line(midpoint12, midpoint31) + midline21 = Line(midpoint21, midpoint42) + midline22 = Line(midpoint22, midpoint41) + + self.play( + ShowCreation(midline11), + ShowCreation(midline12), + ShowCreation(midline21), + ShowCreation(midline22), + ) + + self.play( + FadeIn(reading31), + FadeIn(reading32), + FadeIn(reading33), + FadeIn(reading34), + FadeIn(reading35), + FadeIn(reading36), + FadeIn(reading37), + FadeIn(reading38), + FadeIn(reading39), + ) + +class IndicatorScalingScene(Scene): + + def construct(self): + + unit_intensity = 0.6 + + indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator1.set_intensity(unit_intensity) + reading1 = TexMobject("1") + reading1.move_to(indicator1) + + + indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator2.shift(2*RIGHT) + indicator2.set_intensity(unit_intensity/4) + reading2 = TexMobject("{1\over 4}").scale(0.8) + reading2.move_to(indicator2) + + indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator3.shift(4*RIGHT) + indicator3.set_intensity(unit_intensity/9) + reading3 = TexMobject("{1\over 9}").scale(0.8) + reading3.move_to(indicator3) + + + self.play(FadeIn(indicator1)) + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + self.play(Transform(indicator1, indicator2)) + self.play(FadeIn(reading2)) + self.wait() + self.play(FadeOut(reading2)) + self.play(Transform(indicator1, indicator3)) + self.play(FadeIn(reading3)) + self.wait() + +class BackToEulerSumScene(PiCreatureScene): + + + def construct(self): + self.remove(self.get_primary_pi_creature()) + + NUM_CONES = 7 + NUM_VISIBLE_CONES = 6 + INDICATOR_RADIUS = 0.5 + OPACITY_FOR_UNIT_INTENSITY = 1.0 + + self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1.6, + stroke_width = 1, + numbers_with_elongated_ticks = range(1,5), + numbers_to_show = range(1,5), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ) + + self.number_line.label_direction = DOWN + #self.number_line.shift(3*UP) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + origin_point = self.number_line.number_to_point(0) + + self.default_pi_creature_class = Randolph + randy = self.get_primary_pi_creature() + + randy.scale(0.5) + randy.flip() + right_pupil = randy.pupils[1] + randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + + randy_copy = randy.copy() + randy_copy.target = randy.copy().shift(DOWN) + + + + bubble = ThoughtBubble(direction = RIGHT, + width = 4, height = 3, + file_name = "Bubbles_thought.svg") + bubble.next_to(randy,LEFT+UP) + bubble.set_fill(color = BLACK, opacity = 1) + + self.play( + randy.change, "wave_2", + ShowCreation(bubble), + ) + + + euler_sum = TexMobject("1", "+", "{1\over 4}", + "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ") + # the last entry is a dummy element which makes looping easier + # used just for putting the fractions into the light indicators + + intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)]) + opacities = intensities * OPACITY_FOR_UNIT_INTENSITY + + # repeat: + + # fade in lighthouse + # switch on / fade in ambient light + # show creation / write light indicator + # move indicator onto origin + # while morphing and dimming + # move indicator into thought bubble + # while indicators already inside shift to the back + # and while term appears in the series below + + point = self.number_line.number_to_point(1) + v = point - self.number_line.number_to_point(0) + light_source = LightSource() + light_source.move_source_to(point) + #light_source.ambient_light.move_source_to(point) + #light_source.lighthouse.move_to(point) + + self.play(FadeIn(light_source.lighthouse)) + self.play(SwitchOn(light_source.ambient_light)) + + + # create an indicator that will move along the number line + indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = False + ) + indicator_reading = euler_sum[0] + indicator_reading.scale_to_fit_height(0.5 * indicator.get_height()) + indicator_reading.move_to(indicator.get_center()) + indicator.add(indicator_reading) + indicator.tex_reading = indicator_reading + # the TeX reading is too bright at full intensity + indicator.tex_reading.set_fill(color = BLACK) + indicator.foreground.set_fill(None,opacities[0]) + + + indicator.move_to(point) + indicator.set_intensity(intensities[0]) + + self.play(FadeIn(indicator)) + self.add_foreground_mobject(indicator) + + collection_point = np.array([-6.,2.,0.]) + left_shift = 0.2*LEFT + collected_indicators = Mobject() + + + for i in range(2, NUM_VISIBLE_CONES + 1): + + previous_point = self.number_line.number_to_point(i - 1) + point = self.number_line.number_to_point(i) + + + v = point - previous_point + #print v + # Create and position the target indicator (next on number line). + indicator_target = indicator.deepcopy() + indicator_target.shift(v) + + + # Here we make a copy that will move into the thought bubble. + bubble_indicator = indicator.deepcopy() + # And its target + bubble_indicator_target = bubble_indicator.deepcopy() + bubble_indicator_target.set_intensity(intensities[i - 2]) + + # give the target the appropriate reading + euler_sum[2*i-4].move_to(bubble_indicator_target) + bubble_indicator_target.remove(bubble_indicator_target.tex_reading) + bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() + bubble_indicator_target.add(bubble_indicator_target.tex_reading) + # center it in the indicator + + if bubble_indicator_target.tex_reading.get_tex_string() != "1": + bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height()) + # the target is less bright, possibly switch to a white text color + if bubble_indicator_target.intensity < 0.7: + bubble_indicator.tex_reading.set_fill(color = WHITE) + + # position the target in the thought bubble + bubble_indicator_target.move_to(collection_point) + + + self.add_foreground_mobject(bubble_indicator) + + + self.wait() + + self.play( + Transform(bubble_indicator,bubble_indicator_target), + collected_indicators.shift,left_shift, + ) + + collected_indicators.add(bubble_indicator) + + new_light = light_source.deepcopy() + w = new_light.get_source_point() + new_light.move_source_to(w + (i-2)*v) + w2 = new_light.get_source_point() + + self.add(new_light.lighthouse) + self.play( + Transform(indicator,indicator_target), + new_light.lighthouse.shift,v, + ) + new_light.move_source_to(w + (i-1)*v) + new_light.lighthouse.move_to(w + (i-1)*v) + + self.play(SwitchOn(new_light.ambient_light), + ) + + + + + # quickly switch on off-screen light cones + for i in range(NUM_VISIBLE_CONES,NUM_CONES): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + smooth,indicator_start_time,indicator_stop_time) + ls = LightSource() + point = point = self.number_line.number_to_point(i) + ls.move_source_to(point) + self.play( + SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), + ) + + # and morph indicator stack into limit value + + sum_indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = False + ) + sum_indicator.set_intensity(intensities[0] * np.pi**2/6) + sum_indicator_reading = TexMobject("{\pi^2 \over 6}") + sum_indicator_reading.set_fill(color = BLACK) + sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) + sum_indicator.add(sum_indicator_reading) + sum_indicator.move_to(collection_point) + + self.play( + FadeOut(collected_indicators), + FadeIn(sum_indicator) + ) + + + + self.wait() + +class TwoLightSourcesScene(PiCreatureScene): + + def construct(self): + + MAX_OPACITY = 0.4 + INDICATOR_RADIUS = 0.6 + OPACITY_FOR_UNIT_INTENSITY = 0.5 + + morty = self.get_primary_pi_creature() + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + + horizontal = VMobject(stroke_width = 1) + horizontal.set_points_as_corners([C,A]) + vertical = VMobject(stroke_width = 1) + vertical.set_points_as_corners([C,B]) + + self.play( + ShowCreation(horizontal), + ShowCreation(vertical) + ) + + indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = True, + precision = 2 + ) + + indicator.next_to(morty,LEFT) + + self.play( + Write(indicator) + ) + + + ls1 = LightSource(radius = 20, num_levels = 50) + ls2 = ls1.deepcopy() + ls1.move_source_to(A) + ls2.move_source_to(B) + + self.play( + FadeIn(ls1.lighthouse), + FadeIn(ls2.lighthouse), + SwitchOn(ls1.ambient_light), + SwitchOn(ls2.ambient_light) + ) + + distance1 = np.linalg.norm(C - ls1.get_source_point()) + intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(C - ls2.get_source_point()) + intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + + self.play( + UpdateLightIndicator(indicator,intensity) + ) + + self.wait() + + ls3 = ls1.deepcopy() + ls3.move_to(np.array([6,3.5,0])) + + new_indicator = indicator.copy() + new_indicator.light_source = ls3 + new_indicator.measurement_point = C + self.add(new_indicator) + self.play( + indicator.shift, 2 * UP + ) + + + + #intensity = intensity_for_light_source(ls3) + + + self.play( + SwitchOff(ls1.ambient_light), + #FadeOut(ls1.lighthouse), + SwitchOff(ls2.ambient_light), + #FadeOut(ls2.lighthouse), + UpdateLightIndicator(new_indicator,0.0) + ) + + # create a *continual* animation for the replacement source + updater = ContinualLightIndicatorUpdate(new_indicator) + self.add(updater) + + self.play( + SwitchOn(ls3.ambient_light), + FadeIn(ls3.lighthouse), + + ) + + self.wait() + + # move the light source around + # TODO: moving along a path arc + + location = np.array([-3,-2.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([6.,1.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([5.,2.,0.]) + self.play(ls3.move_source_to,location) + closer_location = interpolate(location, C, 0.5) + self.play(ls3.move_source_to,closer_location) + self.play(ls3.move_source_to,location) + + # maybe move in a circle around C using a loop? + + + + self.play(ls3.move_source_to,H) + + + + # draw lines to complete the geometric picture + # and label the lengths + + line_a = VMobject() + line_a.set_points_as_corners([B,C]) + line_b = VMobject() + line_b.set_points_as_corners([A,C]) + line_c = VMobject() + line_c.set_points_as_corners([A,B]) + line_h = VMobject() + line_h.set_points_as_corners([H,C]) + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.play( + ShowCreation(line_a), + Write(label_a) + ) + + self.play( + ShowCreation(line_b), + Write(label_b) + ) + + self.play( + ShowCreation(line_c), + ) + + self.play( + ShowCreation(line_h), + Write(label_h) + ) + + + # state the IPT + theorem_location = np.array([3.,2.,0.]) + theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") + theorem_name = TextMobject("Inverse Pythagorean Theorem") + buffer = 1.2 + theorem_box = Rectangle(width = buffer*theorem.get_width(), + height = buffer*theorem.get_height()) + + theorem.move_to(theorem_location) + theorem_box.move_to(theorem_location) + theorem_name.next_to(theorem_box,UP) + + self.play( + Write(theorem), + ) + + self.play( + ShowCreation(theorem_box), + Write(theorem_name), + ) + +class IPTScene1(PiCreatureScene): + + def construct(self): + + show_detail = True + + SCREEN_SCALE = 0.1 + SCREEN_THICKNESS = 0.2 + + + # use the following for the zoomed inset + if show_detail: + self.camera.space_shape = (0.02 * SPACE_HEIGHT, 0.02 * SPACE_WIDTH) + self.camera.space_center = C + SCREEN_SCALE = 0.01 + SCREEN_THICKNESS = 0.02 + + + + + + morty = self.get_primary_pi_creature() + self.remove(morty) + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + + if not show_detail: + self.add_foreground_mobject(morty) + + stroke_width = 6 + line_a = Line(B,C,stroke_width = stroke_width) + line_b = Line(A,C,stroke_width = stroke_width) + line_c = Line(A,B,stroke_width = stroke_width) + line_h = Line(C,H,stroke_width = stroke_width) + + length_a = line_a.get_length() + length_b = line_b.get_length() + length_c = line_c.get_length() + length_h = line_h.get_length() + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.add_foreground_mobject(line_a) + self.add_foreground_mobject(line_b) + self.add_foreground_mobject(line_c) + self.add_foreground_mobject(line_h) + self.add_foreground_mobject(label_a) + self.add_foreground_mobject(label_b) + self.add_foreground_mobject(label_h) + + if not show_detail: + self.add_foreground_mobject(morty) + + ls1 = LightSource(radius = 10) + ls1.move_source_to(B) + + self.add(ls1.lighthouse) + + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) + + self.wait() + + # adding the first screen + + screen_width_a = SCREEN_SCALE * length_a + screen_width_b = SCREEN_SCALE * length_b + screen_width_ap = screen_width_a * length_a / length_c + screen_width_bp = screen_width_b * length_b / length_c + screen_width_c = SCREEN_SCALE * length_c + + screen_thickness_a = SCREEN_THICKNESS + screen_thickness_b = SCREEN_THICKNESS + + screen1 = Rectangle(width = screen_width_b, + height = screen_thickness_b, + stroke_width = 0, + fill_opacity = 1.0) + screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) + + if not show_detail: + self.add_foreground_mobject(morty) + + self.play( + FadeIn(screen1) + ) + self.add_foreground_mobject(screen1) + + ls1.set_screen(screen1) + screen_tracker = ScreenTracker(ls1) + self.add(screen_tracker) + #self.add(ls1.shadow) + + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) + + self.play( + SwitchOn(ls1.spotlight), + SwitchOff(ls1.ambient_light) + ) + + + + # now move the light source to the height point + # while shifting scaling the screen + screen1p = screen1.deepcopy() + screen1pp = screen1.deepcopy() + #self.add(screen1p) + angle = np.arccos(length_b / length_c) + + screen1p.stretch_to_fit_width(screen_width_bp) + screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) + screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) + + + self.play( + ls1.move_source_to,H, + Transform(screen1,screen1p) + ) + + # add and move the second light source and screen + ls2 = ls1.deepcopy() + ls2.move_source_to(A) + screen2 = Rectangle(width = screen_width_a, + height = screen_thickness_a, + stroke_width = 0, + fill_opacity = 1.0) + screen2.rotate(-TAU/4) + screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) + + self.play( + FadeIn(screen2) + ) + self.add_foreground_mobject(screen2) + + if not show_detail: + self.add_foreground_mobject(morty) + + # the same scene adding sequence as before + ls2.set_screen(screen2) + screen_tracker2 = ScreenTracker(ls2) + self.add(screen_tracker2) + + if not show_detail: + self.play( + SwitchOn(ls2.ambient_light) + ) + + self.wait() + + self.play( + SwitchOn(ls2.spotlight), + SwitchOff(ls2.ambient_light) + ) + + + + # now move the light source to the height point + # while shifting scaling the screen + screen2p = screen2.deepcopy() + screen2pp = screen2.deepcopy() + angle = np.arccos(length_a / length_c) + screen2p.stretch_to_fit_height(screen_width_ap) + screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) + screen2p.rotate(angle, about_point = C + screen_width_a * UP) + # we can reuse the translation vector + # screen2p.shift(vector) + + self.play( + ls2.move_source_to,H, + SwitchOff(ls1.ambient_light), + Transform(screen2,screen2p) + ) + + # now transform both screens back + self.play( + Transform(screen1, screen1pp), + Transform(screen2, screen2pp), + ) + +class IPTScene2(Scene): + + def construct(self): + + intensity1 = 0.3 + intensity2 = 0.2 + formula_scale = 01.2 + indy_radius = 1 + + indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy1.set_intensity(intensity1) + reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) + indy1.add(reading1) + + indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy2.set_intensity(intensity2) + reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) + indy2.add(reading2) + + indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy3.set_intensity(intensity1 + intensity2) + reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) + indy3.add(reading3) + + plus_sign = TexMobject("+").scale(formula_scale) + equals_sign = TexMobject("=").scale(formula_scale) + + plus_sign.next_to(indy1, RIGHT) + indy2.next_to(plus_sign, RIGHT) + equals_sign.next_to(indy2, RIGHT) + indy3.next_to(equals_sign, RIGHT) + + + formula = VGroup( + indy1, plus_sign, indy2, equals_sign, indy3 + ) + + formula.move_to(ORIGIN) + + self.play(FadeIn(indy1)) + self.play(FadeIn(plus_sign), FadeIn(indy2)) + self.play(FadeIn(equals_sign), FadeIn(indy3)) + + buffer = 1.5 + box = Rectangle(width = formula.get_width() * buffer, + height = formula.get_height() * buffer) + box.move_to(formula) + text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) + text.next_to(box,UP) + self.play(ShowCreation(box),Write(text)) + +class PondScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + baseline = VMobject() + baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + + obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + # Morty and indicator + morty = Mortimer().scale(0.3) + morty.next_to(OBSERVER_POINT,DOWN) + indicator = LightIndicator(precision = 2, + radius = INDICATOR_RADIUS, + show_reading = False, + color = LIGHT_COLOR + ) + indicator.next_to(morty,LEFT) + + # first lighthouse + ls0 = LightSource() + ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + + self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) + + self.wait() + + + # shore arcs + arc_left = Arc(-TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_left.next_to(arc_left,LEFT) + + + arc_right = Arc(TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_right.next_to(arc_right,RIGHT) + + self.play( + ShowCreation(arc_left), + Write(one_left), + ShowCreation(arc_right), + Write(one_right), + ) + + + self.play( + SwitchOn(ls0.ambient_light), + lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, + ) + + self.play(FadeIn(indicator)) + + self.play( + indicator.set_intensity,0.5 + ) + + # diameter + diameter = DoubleArrow(OBSERVER_POINT, + ls0.get_source_point(), + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + ShowCreation(diameter), + Write(diameter_text), + #FadeOut(obs_dot), + FadeOut(ls0_dot) + ) + + indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator.reading.move_to(indicator) + + self.play( + FadeIn(indicator.reading) + ) + + # replace d with its value + new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text.color = LAKE_COLOR + new_diameter_text.move_to(diameter_text) + self.play( + Transform(diameter_text,new_diameter_text) + ) + + # insert into indicator reading + new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) + new_reading.move_to(indicator) + + self.play( + Transform(indicator.reading,new_reading) + ) + + self.play( + FadeOut(one_left), + FadeOut(one_right), + FadeOut(diameter_text), + FadeOut(arc_left), + FadeOut(arc_right) + ) + + + + + def indicator_wiggle(): + INDICATOR_WIGGLE_FACTOR = 1.3 + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + + def angle_for_index(i,step): + return -TAU/4 + TAU/2**step * (i + 0.5) + + + def position_for_index(i, step, scaled_down = False): + + theta = angle_for_index(i,step) + radial_vector = np.array([np.cos(theta),np.sin(theta),0]) + position = self.lake_center + self.lake_radius * radial_vector + + if scaled_down: + return position.scale_about_point(OBSERVER_POINT,0.5) + else: + return position + + + def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) + + hyp = VMobject() + hyp1 = Line(self.lake_center,ls_new_loc1) + hyp2 = Line(self.lake_center,ls_new_loc2) + hyp.add(hyp2,hyp1) + self.new_hypotenuses.append(hyp) + + if show_steps == True: + self.play( + ShowCreation(hyp, run_time = run_time) + ) + + leg1 = Line(OBSERVER_POINT,ls_new_loc1) + leg2 = Line(OBSERVER_POINT,ls_new_loc2) + self.new_legs_1.append(leg1) + self.new_legs_2.append(leg2) + + if show_steps == True: + self.play( + ShowCreation(leg1, run_time = run_time), + ShowCreation(leg2, run_time = run_time), + ) + + ls1 = self.light_sources_array[i] + ls2 = ls1.copy() + self.add(ls2) + self.additional_light_sources.append(ls2) + + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.all(np.abs(ls_old_loc) < 10) + onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + show_animation = (onscreen_old or onscreen_1 or onscreen_2) + + if show_animation: + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + + + + def construction_step(n, scale_down = True, show_steps = True, run_time = 1, + simultaneous_splitting = False, ls_radius = 1): + + # we assume that the scene contains: + # an inner lake, self.inner_lake + # an outer lake, self.outer_lake + # light sources, self.light_sources + # legs from the observer point to each light source + # self.legs + # altitudes from the observer point to the + # locations of the light sources in the previous step + # self.altitudes + # hypotenuses connecting antipodal light sources + # self.hypotenuses + + # these are mobjects! + + + # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + else: + self.play( + FadeOut(self.inner_lake) + ) + + # create a new, outer lake + + new_outer_lake = Circle(radius = self.lake_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(self.lake_center) + + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) + + self.wait() + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + ls_radius = ls_radius + ) + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = VMobject() + for leg in self.new_legs_1: + self.legs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + + self.hypotenuses = VMobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + + # update scene + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) + + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, + ) + + + self.wait() + + if show_steps == True: + self.play(FadeOut(ls0_dot)) + + # scale down + if scale_down: + + indicator_wiggle() + + if show_steps == True: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + self.legs.scale_about_point,0.5,OBSERVER_POINT, + self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, + self.altitudes.scale_about_point,0.5,OBSERVER_POINT, + ) + else: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + ) + + # update the radii bc they haven't done so themselves + # bc reasons... + for ls in self.light_sources_array: + r = ls.radius + ls.set_radius(r*0.5) + + else: + # update the lake center and the radius + self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP + self.lake_radius *= 2 + + + + + + + + + self.lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = VMobject() + self.outer_lake = lake0 + self.legs = VMobject() + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) + self.altitudes = VMobject() + self.hypotenuses = VMobject() + self.light_sources_array = [ls0] + self.light_sources = VMobject() + self.light_sources.add(ls0) + + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + + self.add(self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + ls_radius = 25.0 + + for i in range(3): + construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) + + return + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + for i in range(3,5): + construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, + simultaneous_splitting = True, ls_radius = ls_radius/2**3) + + + + + # Now create a straight number line and transform into it + MAX_N = 17 + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1), + unit_size = LAKE0_RADIUS * TAU/4 / 4, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(2.5 * DOWN) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.wait() + + origin_point = self.number_line.number_to_point(0) + nl_sources = VMobject() + pond_sources = VMobject() + + for i in range(-MAX_N,MAX_N+1): + anchor = self.number_line.number_to_point(2*i + 1) + ls = self.light_sources_array[i].copy() + ls.move_source_to(anchor) + nl_sources.add(ls) + pond_sources.add(self.light_sources_array[i].copy()) + + self.add(pond_sources) + self.remove(self.light_sources) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + width = 20, + height = 10, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + ).flip().next_to(origin_point,UP,buff = 0) + + + + self.play( + Transform(pond_sources,nl_sources), + Transform(self.outer_lake,open_sea), + FadeOut(self.inner_lake) + ) + self.play(FadeIn(self.number_line)) + +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 1.3 + + Arc.__init__(self,angle,**kwargs) + + label = DecimalNumber(self.length, num_decimal_points = 0) + r = BUFFER * self.radius + theta = self.start_angle + self.angle/2 + label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) + + label.move_to(label_pos) + self.add(label) + +class ArcHighlightOverlayScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + FLASH_TIME = 0.25 + + def flash_arcs(n): + + angle = TAU/2**n + arcs = [] + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) + + for i in range(1,2**n): + arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) + + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) + + self.play( + FadeIn(arcs[0], run_time = FLASH_TIME) + ) + + for i in range(1,2**n + 1): + self.play( + FadeOut(arcs[i-1], run_time = FLASH_TIME), + FadeIn(arcs[i], run_time = FLASH_TIME) + ) + + self.play( + FadeOut(arcs[2**n], run_time = FLASH_TIME), + ) + + + flash_arcs(3) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 60a47d0e2e9024c8c4a950c01db633c091cbbac2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 16:15:03 -0800 Subject: [PATCH 04/24] Reverted basel.py to original pre-grant state --- active_projects/basel.py | 1531 +++++++++++++++++++++++--------------- 1 file changed, 950 insertions(+), 581 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index c975060e..5d72c415 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -18,7 +18,6 @@ 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 * @@ -40,14 +39,19 @@ FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -NUM_LEVELS = 30 NUM_CONES = 7 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -AMBIENT_FULL = 0.5 -AMBIENT_DIMMED = 0.2 -SPOTLIGHT_FULL = 0.9 + +NUM_LEVELS = 20 +AMBIENT_FULL = 0.8 +AMBIENT_DIMMED = 0.5 +AMBIENT_SCALE = 1.0 +AMBIENT_RADIUS = 20.0 +SPOTLIGHT_FULL = 0.8 SPOTLIGHT_DIMMED = 0.2 +SPOTLIGHT_SCALE = 1.0 +SPOTLIGHT_RADIUS = 20.0 LIGHT_COLOR = YELLOW DEGREES = TAU/360 @@ -95,6 +99,10 @@ class AngleUpdater(ContinualAnimation): self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) + + + + class LightIndicator(Mobject): CONFIG = { "radius": 0.5, @@ -144,6 +152,9 @@ class LightIndicator(Mobject): print "Indicator cannot update, reason: no light source found" self.set_intensity(self.measured_intensity()) + + + class UpdateLightIndicator(AnimationGroup): def __init__(self, indicator, intensity, **kwargs): @@ -158,11 +169,13 @@ class UpdateLightIndicator(AnimationGroup): AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator + class ContinualLightIndicatorUpdate(ContinualAnimation): def update_mobject(self,dt): self.mobject.continual_update() + def copy_func(f): """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, @@ -188,13 +201,13 @@ class ScaleLightSources(Transform): new_sp.scale(factor,about_point = about_point) submob.move_source_to(new_sp.get_location()) - #ambient_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: ambient_of(r/factor) - #submob.ambient_light.opacity_function = new_of + # ambient_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: ambient_of(r / factor) + # submob.ambient_light.change_opacity_function(new_of) - #spotlight_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: spotlight_of(r/factor) - #submob.spotlight.change_opacity_function(new_of) + # spotlight_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: spotlight_of(r / factor) + # submob.spotlight.change_opacity_function(new_of) new_r = factor * submob.radius submob.set_radius(new_r) @@ -212,59 +225,48 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) -### -class ThinkAboutPondScene(PiCreatureScene): - CONFIG = { - "default_pi_creature_class" : Randolph, - } - def construct(self): - randy = self.pi_creature - randy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble( - width = 9, - height = 5, - ) - circles = bubble[:3] - angle = -15*DEGREES - circles.rotate(angle, about_point = bubble.get_bubble_center()) - circles.shift(LARGE_BUFF*LEFT) - for circle in circles: - circle.rotate(-angle) - bubble.pin_to(randy) - bubble.shift_onto_screen() - self.play( - randy.change, "thinking", - ShowCreation(bubble) - ) - self.wait(2) - self.play(randy.change, "happy", bubble) - self.wait(2) - self.play(randy.change, "hooray", bubble) - self.wait(2) + + + + + + + + + + + + + class IntroScene(PiCreatureScene): CONFIG = { - "rect_height" : 0.1, - "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF, - "n_rects_to_show" : 30, + "rect_height" : 0.2, + "duration" : 0.5, + "eq_spacing" : 6 * MED_LARGE_BUFF } def construct(self): + randy = self.get_primary_pi_creature() randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.show_history() - # self.other_pi_formulas() - # self.refocus_on_euler_sum() + self.build_up_sum_on_number_line() + self.show_pi_answer() + self.other_pi_formulas() + self.refocus_on_euler_sum() + + + + def build_up_euler_sum(self): - morty = self.pi_creature - euler_sum = self.euler_sum = TexMobject( + + self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", "{1 \\over 9}", "+", @@ -273,27 +275,70 @@ class IntroScene(PiCreatureScene): "\\cdots", "=", arg_separator = " \\, " ) - equals_sign = euler_sum.get_part_by_tex("=") - plusses = euler_sum.get_parts_by_tex("+") - term_mobjects = euler_sum.get_parts_by_tex("1") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - max_n = self.n_rects_to_show - terms = [1./(n**2) for n in range(1, max_n + 1)] - series_terms = list(np.cumsum(terms)) - series_terms.append(np.pi**2/6) ##Just force this up there + terms = [1./n**2 for n in range(1,6)] + partial_results_values = np.cumsum(terms) - partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( - series_terms[1], - num_decimal_points = 2 + self.play( + FadeIn(self.euler_sum[0], run_time = self.duration) ) - partial_sum_decimal.next_to(equals_sign, RIGHT) - ## Number line + equals_sign = self.euler_sum.get_part_by_tex("=") - number_line = self.number_line = NumberLine( + 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(LIGHT_COLOR) + 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) + ) + + self.wait() + + + + def build_up_sum_on_number_line(self): + + self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1, @@ -303,234 +348,139 @@ class IntroScene(PiCreatureScene): unit_size = 5, tick_frequency = 0.2, line_to_number_buff = MED_LARGE_BUFF + ).shift(LEFT) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.play( + FadeIn(self.number_line), + FadeIn(self.number_line_labels) ) - number_line.add_numbers() - number_line.to_edge(LEFT) - number_line.shift(MED_LARGE_BUFF*UP) + self.wait() # create slabs for series terms - lines = VGroup() - rects = self.rects = VGroup() - rect_labels = VGroup() - slab_colors = it.cycle([YELLOW, BLUE]) - rect_anims = [] - rect_label_anims = [] + max_n1 = 10 + max_n2 = 100 - for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): - color = slab_colors.next() - line = Line(*map(number_line.number_to_point, [t1, t2])) - rect = Rectangle( - stroke_width = 0, - fill_opacity = 1, - fill_color = color + terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)] + series_terms = np.cumsum(terms) + lines = VGroup() + self.rects = VGroup() + slab_colors = [YELLOW, BLUE] * (max_n2 / 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.match_width(line) - rect.stretch_to_fit_height(self.rect_height) + rect.stretch_to_fit_width(0.5 * line.get_width()) rect.move_to(line) - if i <= 5: - if i == 1: - rect_label = TexMobject("1") - else: - rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) - rect_label.scale(0.75) - max_width = 0.7*rect.get_width() - if rect_label.get_width() > max_width: - rect_label.scale_to_fit_width(max_width) - rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + self.rects.add(rect) + lines.add(line) - term_mobject = term_mobjects[i-1] - rect_anim = GrowFromPoint(rect, term_mobject.get_center()) - rect_label_anim = ReplacementTransform( - term_mobject.copy(), rect_label + #self.rects.radial_gradient_highlight(ORIGIN, 5, YELLOW, BLUE) + + self.little_euler_terms = VGroup() + for i in range(1,7): + if i == 1: + term = TexMobject("1", fill_color = slab_colors[i-1]) + else: + term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1]) + term.scale(0.4) + self.little_euler_terms.add(term) + + + for i in range(5): + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), + run_time = 1) + ) + term = self.little_euler_terms.submobjects[i] + term.next_to(self.rects[i], UP) + self.play(FadeIn(term)) + + self.ellipsis = TexMobject("\cdots") + self.ellipsis.scale(0.4) + + for i in range(5, max_n1): + + if i == 5: + self.ellipsis.next_to(self.rects[i+3], UP) + self.play( + FadeIn(self.ellipsis), + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) ) else: - rect_label = VectorizedPoint() - rect_anim = GrowFromPoint(rect, rect.get_left()) - rect_label_anim = FadeIn(rect_label) + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) + ) - rects.add(rect) - rect_labels.add(rect_label) - rect_anims.append(rect_anim) - rect_label_anims.append(rect_label_anim) - lines.add(line) - dots = TexMobject("\\dots").scale(0.5) - last_rect = rect_anims[-1].target_mobject - dots.scale_to_fit_width(0.9*last_rect.get_width()) - dots.move_to(last_rect, UP+RIGHT) - rects.submobjects[-1] = dots - rect_anims[-1] = FadeIn(dots) - - self.add(number_line) - self.play(FadeIn(euler_sum[0])) - self.play( - rect_anims[0], - rect_label_anims[0] - ) - for i in range(4): + for i in range(max_n1, max_n2): self.play( - FadeIn(term_mobjects[i+1]), - FadeIn(plusses[i]), - ) - anims = [ - rect_anims[i+1], - rect_label_anims[i+1], - ] - if i == 0: - anims += [ - FadeIn(equals_sign), - FadeIn(partial_sum_decimal) - ] - elif i <= 5: - anims += [ - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - run_time = 1, - num_decimal_points = 6, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ] - self.play(*anims) + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.01) + ) - for i in range(4, len(series_terms)-2): - anims = [ - rect_anims[i+1], - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - num_decimal_points = 6, - ), - ] - if i == 5: - anims += [ - FadeIn(euler_sum[-3]), # + - FadeIn(euler_sum[-2]), # ... - ] - self.play(*anims, run_time = 2./i) - - brace = self.brace = Brace(partial_sum_decimal, DOWN) - q_marks = self.q_marks = TextMobject("???") - q_marks.next_to(brace, DOWN) - q_marks.highlight(LIGHT_COLOR) - - self.play( - GrowFromCenter(brace), - Write(q_marks), - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[-1], - num_decimal_points = 6, - ), - morty.change, "confused", - ) self.wait() - self.number_line_group = VGroup( - number_line, rects, rect_labels + PI = TAU/2 + P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT + Q = self.rects[-1].get_center() + 0.2 * UP + self.arrow = CurvedArrow(P, Q, + angle = TAU/12, + color = YELLOW ) - def show_history(self): - # Pietro Mengoli in 1644 - morty = self.pi_creature - pietro = ImageMobject("Pietro_Mengoli") - euler = ImageMobject("Euler") + self.play(FadeIn(self.arrow)) - pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") - pietro_words.scale(0.75) - pietro_words.next_to(pietro, DOWN) - pietro.add(pietro_words) - - euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") - euler_words.scale(0.75) - euler_words.next_to(euler, DOWN) - euler.add(euler_words) - - pietro.next_to(SPACE_WIDTH*LEFT, LEFT) - euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) - - pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") - pi_answer.highlight(YELLOW) - pi_answer.move_to(self.partial_sum_decimal, LEFT) - equals_sign = TexMobject("=") - equals_sign.next_to(pi_answer, RIGHT) - pi_answer.shift(SMALL_BUFF*UP) - self.partial_sum_decimal.generate_target() - self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) - - pi = pi_answer[0] - pi_rect = SurroundingRectangle(pi, color = RED) - pi_rect.save_state() - pi_rect.scale_to_fit_height(SPACE_HEIGHT) - pi_rect.center() - pi_rect.set_stroke(width = 0) - squared = pi_answer[1] - squared_rect = SurroundingRectangle(squared, color = BLUE) - - brace = Brace( - VGroup(self.euler_sum, self.partial_sum_decimal.target), - DOWN, buff = SMALL_BUFF - ) - basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) - - self.number_line_group.save_state() - self.play( - pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, - self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, - morty.change, "pondering", - ) - self.wait(2) - self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) - self.wait(2) - self.play( - ReplacementTransform(self.q_marks, pi_answer), - FadeIn(equals_sign), - FadeOut(self.brace), - MoveToTarget(self.partial_sum_decimal) - ) self.wait() - self.play(morty.change, "surprised") - self.play(pi_rect.restore) + + + 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, buff = 1, + submobject_to_align = self.pi_answer[-2]) + self.play(ReplacementTransform(self.q_marks, self.pi_answer)) + self.wait() - self.play(Transform(pi_rect, squared_rect)) - self.play(FadeOut(pi_rect)) - self.play(morty.change, "hesitant") - self.wait(2) - self.play( - GrowFromCenter(brace), - euler.to_edge, DOWN, - pietro.to_edge, DOWN, - self.number_line_group.restore, - self.number_line_group.shift, LARGE_BUFF*RIGHT, - ) - self.play(Write(basel_text)) - self.play(morty.change, "happy") - self.wait(4) + def other_pi_formulas(self): self.play( FadeOut(self.rects), - FadeOut(self.number_line) + FadeOut(self.number_line_labels), + FadeOut(self.number_line), + FadeOut(self.little_euler_terms), + FadeOut(self.ellipsis), + FadeOut(self.arrow) ) self.leibniz_sum = TexMobject( "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") + "=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ") 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}") + "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ") self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, + buff = 2, 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, + buff = 2, submobject_to_align = self.wallis_product.get_part_by_tex("=") ) @@ -542,6 +492,8 @@ class IntroScene(PiCreatureScene): Write(self.wallis_product) ) + + def refocus_on_euler_sum(self): self.euler_sum.add(self.pi_answer) @@ -556,7 +508,11 @@ class IntroScene(PiCreatureScene): # focus on pi squared pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] self.play( - ScaleInPlace(pi_squared,2,rate_func = wiggle) + WiggleOutThenIn(pi_squared, + scale_value = 4, + angle = 0.003 * TAU, + run_time = 2 + ) ) @@ -566,204 +522,37 @@ class IntroScene(PiCreatureScene): q_circle = Circle( stroke_color = YELLOW, fill_color = YELLOW, - fill_opacity = 0.5, - radius = 0.4, - stroke_width = 10.0 + fill_opacity = 0.25, + radius = 0.5, + stroke_width = 3.0 ) q_mark = TexMobject("?") q_mark.next_to(q_circle) thought = Group(q_circle, q_mark) - q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + q_mark.scale_to_fit_height(0.6 * q_circle.get_height()) + + self.look_at(pi_squared) self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 2, "width" : 3 }) + bubble_kwargs = { "height" : 2.5, "width" : 5 }) + self.look_at(pi_squared) self.wait() -class PiHidingWrapper(Scene): - def construct(self): - title = TextMobject("Pi hiding in prime regularities") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(screen)) - self.wait(2) -class MathematicalWebOfConnections(PiCreatureScene): - def construct(self): - self.complain_that_pi_is_not_about_circles() - self.show_other_pi_formulas() - self.question_fundamental() - self.draw_circle() - self.remove_all_but_basel_sum() - self.show_web_of_connections() - self.show_light() - def complain_that_pi_is_not_about_circles(self): - jerk, randy = self.pi_creatures - words = self.words = TextMobject( - "$\\pi$ is not", - "fundamentally \\\\", - "about circles" - ) - words.highlight_by_tex("fundamentally", YELLOW) - self.play(PiCreatureSays( - jerk, words, - target_mode = "angry" - )) - self.play(randy.change, "guilty") - self.wait(2) - def show_other_pi_formulas(self): - jerk, randy = self.pi_creatures - words = self.words - basel_sum = TexMobject( - "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", - "=", "{\\pi^2 \\over 6}" - ) - leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") - 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}") - basel_sum.move_to(randy) - basel_sum.to_edge(UP) - basel_equals = basel_sum.get_part_by_tex("=") - formulas = VGroup(basel_sum, leibniz_sum, wallis_product) - formulas.scale(0.75) - formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) - for formula in formulas: - basel_equals_x = basel_equals.get_center()[0] - formula_equals_x = formula.get_part_by_tex("=").get_center()[0] - formula.shift((basel_equals_x - formula_equals_x)*RIGHT) - formulas.move_to(randy) - formulas.to_edge(UP) - formulas.shift_onto_screen() - self.formulas = formulas - self.play( - jerk.change, "sassy", - randy.change, "raise_right_hand", - FadeOut(jerk.bubble), - words.next_to, jerk, UP, - FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) - ) - for formula in formulas[1:]: - self.play( - FadeIn( - formula, - submobject_mode = "lagged_start", - run_time = 3 - ), - ) - self.wait() - def question_fundamental(self): - jerk, randy = self.pi_creatures - words = self.words - fundamentally = words.get_part_by_tex("fundamentally") - words.remove(fundamentally) - self.play( - fundamentally.move_to, self.pi_creatures, - fundamentally.shift, UP, - FadeOut(words), - jerk.change, "pondering", - randy.change, "pondering", - ) - self.wait() - question = TextMobject("Does this mean \\\\ anything?") - question.scale(0.8) - question.set_stroke(WHITE, 0.5) - question.next_to(fundamentally, DOWN, LARGE_BUFF) - arrow = Arrow(question, fundamentally) - arrow.highlight(WHITE) - - self.play( - FadeIn(question), - GrowArrow(arrow) - ) - self.wait() - - fundamentally.add(question, arrow) - self.fundamentally = fundamentally - - def draw_circle(self): - semi_circle = Arc(angle = np.pi, radius = 2) - radius = Line(ORIGIN, semi_circle.points[0]) - radius.highlight(BLUE) - semi_circle.highlight(YELLOW) - - VGroup(radius, semi_circle).move_to( - SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, - ) - - decimal = DecimalNumber(0) - def decimal_position_update_func(decimal): - decimal.move_to(semi_circle.points[-1]) - decimal.shift(0.3*radius.get_vector()) - - one = TexMobject("1") - one.next_to(radius, UP) - - self.play(ShowCreation(radius), FadeIn(one)) - self.play( - Rotate(radius, np.pi, about_point = radius.get_start()), - ShowCreation(semi_circle), - ChangeDecimalToValue( - decimal, np.pi, - position_update_func = decimal_position_update_func - ), - MaintainPositionRelativeTo(one, radius), - run_time = 3, - ) - self.wait(2) - - self.circle_group = VGroup(semi_circle, radius, one, decimal) - - def remove_all_but_basel_sum(self): - to_shift_down = VGroup( - self.circle_group, self.pi_creatures, - self.fundamentally, self.formulas[1:], - ) - to_shift_down.generate_target() - for part in to_shift_down.target: - part.move_to(2*SPACE_HEIGHT*DOWN) - - basel_sum = self.formulas[0] - - self.play( - MoveToTarget(to_shift_down), - basel_sum.scale, 1.5, - basel_sum.move_to, 2*DOWN, - ) - - def show_web_of_connections(self): - pass - - def show_light(self): - pass - - ### - - def create_pi_creatures(self): - jerk = PiCreature(color = GREEN_D) - randy = Randolph().flip() - jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) - randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) - - return VGroup(jerk, randy) @@ -819,7 +608,7 @@ class FirstLighthouseScene(PiCreatureScene): width = 2.5, height = 3.5) bubble.next_to(randy,LEFT+UP) bubble.add_content(light_indicator) - + self.wait() self.play( randy.change, "wave_2", ShowCreation(bubble), @@ -844,15 +633,15 @@ class FirstLighthouseScene(PiCreatureScene): for i in range(1,NUM_CONES+1): light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1), num_levels = NUM_LEVELS, - radius = 12.0, + radius = AMBIENT_RADIUS, ) point = self.number_line.number_to_point(i) light_source.move_source_to(point) light_sources.append(light_source) - + self.wait() for ls in light_sources: self.add_foreground_mobject(ls.lighthouse) @@ -866,7 +655,7 @@ class FirstLighthouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) @@ -879,10 +668,12 @@ class FirstLighthouseScene(PiCreatureScene): # this last line *technically* fades in the last term, but it is off-screen ChangeDecimalToValue(light_indicator.reading,intensities[i], rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i], + rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME) ) if i == 0: + self.wait() # move a copy out of the thought bubble for comparison light_indicator_copy = light_indicator.copy() old_y = light_indicator_copy.get_center()[1] @@ -891,6 +682,10 @@ class FirstLighthouseScene(PiCreatureScene): light_indicator_copy.shift,[0, new_y - old_y,0] ) + self.wait() + + self.wait() + # quickly switch on off-screen light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size @@ -925,6 +720,28 @@ class FirstLighthouseScene(PiCreatureScene): self.wait() + + + + + + + + + + + + + + + + + + + + + + class SingleLighthouseScene(PiCreatureScene): def construct(self): @@ -932,7 +749,7 @@ class SingleLighthouseScene(PiCreatureScene): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates self.rotate_screen() - self.morph_lighthouse_into_sun() + #self.morph_lighthouse_into_sun() def setup_elements(self): @@ -947,10 +764,12 @@ class SingleLighthouseScene(PiCreatureScene): # Light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), num_levels = NUM_LEVELS, radius = 10, - max_opacity_ambient = AMBIENT_FULL + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, + ) self.light_source.move_source_to(source_point) @@ -973,7 +792,7 @@ class SingleLighthouseScene(PiCreatureScene): # Screen self.screen = Rectangle( - width = 0.1, + width = 0.06, height = 2, mark_paths_closed = True, fill_color = WHITE, @@ -990,26 +809,39 @@ class SingleLighthouseScene(PiCreatureScene): self.play(FadeIn(self.screen)) - self.light_source.set_max_opacity_spotlight(0.001) - self.add(self.light_source.spotlight) + #self.light_source.set_max_opacity_spotlight(0.001) + #self.play(SwitchOn(self.light_source.spotlight)) - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) self.wait() + + # just calling .dim_ambient via ApplyMethod does not work, why? dimmed_ambient_light = self.light_source.ambient_light.deepcopy() dimmed_ambient_light.dimming(AMBIENT_DIMMED) + self.light_source.update_shadow() self.play( - Transform(self.light_source.ambient_light,dimmed_ambient_light), - self.light_source.set_max_opacity_spotlight,1.0, - FadeIn(self.light_source.shadow) + FadeIn(self.light_source.shadow), + ) + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) + + self.play( + self.light_source.dim_ambient, + #Transform(self.light_source.ambient_light,dimmed_ambient_light), + #self.light_source.set_max_opacity_spotlight,1.0, + ) + self.play( + FadeIn(self.light_source.spotlight) ) - self.add_foreground_mobject(morty) + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() @@ -1036,7 +868,9 @@ class SingleLighthouseScene(PiCreatureScene): self.angle_indicator = DecimalNumber(arc_angle / DEGREES, num_decimal_points = 0, - unit = "^\\circ") + unit = "^\\circ", + fill_opacity = 1.0, + fill_color = WHITE) self.angle_indicator.next_to(self.angle_arc,RIGHT) angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES @@ -1059,6 +893,7 @@ class SingleLighthouseScene(PiCreatureScene): self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.wait() @@ -1073,47 +908,123 @@ class SingleLighthouseScene(PiCreatureScene): ### but it doesn't work - def morph_lighthouse_into_sun(self): +class MorphIntoSunScene(PiCreatureScene): + + def construct(self): + + self.setup_elements() + self.morph_lighthouse_into_sun() + def setup_elements(self): - sun_position = [-100,0,0] + self.remove(self.get_primary_pi_creature()) + SCREEN_SIZE = 3.0 + DISTANCE_FROM_LIGHTHOUSE = 10.0 + source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] + observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + + # Light source + + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) ) - self.sun = self.light_source.deepcopy() + self.light_source.move_source_to(source_point) - #self.sun.num_levels = NUM_LEVELS, - #self.sun.set_radius(150) - #self.sun.set_max_opacity_ambient(AMBIENT_FULL) + + # Pi Creature + + morty = self.get_primary_pi_creature() + morty.scale(0.5) + morty.move_to(observer_point) + morty.shift(2*OUT) + self.add_foreground_mobject(morty) + + self.add(self.light_source.lighthouse,self.light_source.ambient_light) + # Screen - self.sun.spotlight.change_opacity_function(lambda r: 0.5) - self.sun.set_radius(150) - self.sun.move_source_to(sun_position) + self.screen = Rectangle( + width = 0.06, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) - # self.sun.update() + self.screen.next_to(morty,LEFT) - # self.add(self.sun) - # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) + self.light_source.set_screen(self.screen) + self.add(self.screen,self.light_source.shadow) + + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) - #print self.sun.spotlight.get_source_point() + self.light_source.dim_ambient + self.add(self.light_source.spotlight) + + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + def morph_lighthouse_into_sun(self): + + sun_position = np.array([-100,0,0]) + + + + + # Why does none of this change the opacity function??? + + self.sun = self.light_source.copy() + + self.sun.change_spotlight_opacity_function(lambda r: 0.1) + # self.sun.spotlight.opacity_function = lambda r: 0.1 + # for submob in self.sun.spotlight.submobjects: + # submob.set_fill(opacity = 0.1) + + #self.sun.move_source_to(sun_position) + #self.sun.set_radius(120) + + self.sun.spotlight.generate_points() + + self.wait() self.play( - #self.light_source.spotlight.move_source_to,sun_position, Transform(self.light_source,self.sun) ) - #self.add(ScreenTracker(self.sun)) - self.wait() + + + + + + + + + + + + + + + + + + class EarthScene(Scene): def construct(self): @@ -1126,8 +1037,8 @@ class EarthScene(Scene): # screen self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) self.screen.set_points_as_corners([ - [3,-self.screen_height/2,0], - [3,self.screen_height/2,0] + [0,-self.screen_height/2,0], + [0,self.screen_height/2,0] ]) # Earth @@ -1136,6 +1047,7 @@ class EarthScene(Scene): earth_center = [earth_center_x,0,0] earth_radius = 3 earth = Circle(radius = earth_radius) + earth.add(self.screen) earth.move_to(earth_center) #self.remove(self.screen_tracker) @@ -1144,8 +1056,15 @@ class EarthScene(Scene): theta1 = theta0 + dtheta theta = (theta0 + theta1)/2 - earth.add(self.screen) + self.add_foreground_mobject(self.screen) + # background Earth + background_earth = SVGMobject( + file_name = "earth", + width = 2 * earth_radius, + fill_color = BLUE, + ) + background_earth.move_to(earth_center) # Morty morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) @@ -1178,8 +1097,13 @@ class EarthScene(Scene): self.wait() - self.play(FadeIn(earth)) - self.bring_to_back(earth) + self.play( + FadeIn(earth), + FadeIn(background_earth) + ) + self.add_foreground_mobject(earth) + self.add_foreground_mobject(self.screen) + # move screen onto Earth screen_on_earth = self.screen.deepcopy() @@ -1191,6 +1115,7 @@ class EarthScene(Scene): 0])) polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) + polar_morty.highlight(BLUE_C) self.play( Transform(self.screen, screen_on_earth), @@ -1202,6 +1127,8 @@ class EarthScene(Scene): tropical_morty = polar_morty.copy() tropical_morty.move_to(np.array([0,0,0])) + tropical_morty.highlight(RED) + morty.target = tropical_morty # move screen to equator @@ -1211,6 +1138,30 @@ class EarthScene(Scene): MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), ) + + + + + + + + + + + + + + + + + + + + + + + + class ScreenShapingScene(ThreeDScene): @@ -1656,6 +1607,9 @@ class ScreenShapingScene(ThreeDScene): FadeIn(reading39), ) + + + class IndicatorScalingScene(Scene): def construct(self): @@ -1693,6 +1647,11 @@ class IndicatorScalingScene(Scene): self.play(FadeIn(reading3)) self.wait() + + + + + class BackToEulerSumScene(PiCreatureScene): @@ -1911,6 +1870,10 @@ class BackToEulerSumScene(PiCreatureScene): self.wait() + + + + class TwoLightSourcesScene(PiCreatureScene): def construct(self): @@ -2088,6 +2051,8 @@ class TwoLightSourcesScene(PiCreatureScene): Write(theorem_name), ) + + class IPTScene1(PiCreatureScene): def construct(self): @@ -2278,6 +2243,8 @@ class IPTScene1(PiCreatureScene): Transform(screen2, screen2pp), ) + + class IPTScene2(Scene): def construct(self): @@ -2329,7 +2296,17 @@ class IPTScene2(Scene): text.next_to(box,UP) self.play(ShowCreation(box),Write(text)) -class PondScene(Scene): + + + + + + + +class PondScene(ThreeDScene): + + + def construct(self): @@ -2346,12 +2323,52 @@ class PondScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 5 + LIGHT_CUTOFF = 1 + + self.cumulated_zoom_factor = 1 + + #self.force_skipping() + + + def zoom_out_scene(factor): + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary - obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - + self.unzoomable_mobs.add(self.obs_dot, ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2360,6 +2377,7 @@ class PondScene(Scene): fill_opacity = LAKE_OPACITY ) lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) # Morty and indicator morty = Mortimer().scale(0.3) @@ -2370,12 +2388,15 @@ class PondScene(Scene): color = LIGHT_COLOR ) indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) # first lighthouse - ls0 = LightSource() + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func) ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,self.obs_dot,ls0_dot, ls0.lighthouse) self.wait() @@ -2435,15 +2456,16 @@ class PondScene(Scene): self.play( ShowCreation(diameter), Write(diameter_text), - #FadeOut(obs_dot), + #FadeOut(self.obs_dot), FadeOut(ls0_dot) ) - indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator.reading.move_to(indicator) + indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) self.play( - FadeIn(indicator.reading) + FadeIn(indicator_reading) ) # replace d with its value @@ -2459,7 +2481,7 @@ class PondScene(Scene): new_reading.move_to(indicator) self.play( - Transform(indicator.reading,new_reading) + Transform(indicator_reading,new_reading) ) self.play( @@ -2478,7 +2500,7 @@ class PondScene(Scene): self.play( ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) ) @@ -2493,12 +2515,12 @@ class PondScene(Scene): position = self.lake_center + self.lake_radius * radial_vector if scaled_down: - return position.scale_about_point(OBSERVER_POINT,0.5) + return position.scale_about_point(self.obs_dot.get_center(),0.5) else: return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + def split_light_source(i, step, show_steps = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2514,8 +2536,8 @@ class PondScene(Scene): ShowCreation(hyp, run_time = run_time) ) - leg1 = Line(OBSERVER_POINT,ls_new_loc1) - leg2 = Line(OBSERVER_POINT,ls_new_loc2) + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) @@ -2526,15 +2548,17 @@ class PondScene(Scene): ) ls1 = self.light_sources_array[i] + + ls2 = ls1.copy() self.add(ls2) self.additional_light_sources.append(ls2) # check if the light sources are on screen ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) if show_animation: @@ -2550,8 +2574,8 @@ class PondScene(Scene): - def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_radius = 1): + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2569,18 +2593,22 @@ class PondScene(Scene): # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) self.play( FadeOut(self.hypotenuses), FadeOut(self.altitudes), FadeOut(self.inner_lake) ) else: + self.zoomable_mobs.remove(self.inner_lake) self.play( FadeOut(self.inner_lake) ) # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP new_outer_lake = Circle(radius = self.lake_radius, stroke_width = LAKE_STROKE_WIDTH, @@ -2605,18 +2633,18 @@ class PondScene(Scene): self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] - self.new_hypotenuses = [] + self.new_hypotenuses = [] for i in range(2**n): split_light_source(i, step = n, show_steps = show_steps, - run_time = run_time, - ls_radius = ls_radius + run_time = run_time ) @@ -2627,16 +2655,23 @@ class PondScene(Scene): self.legs = VMobject() for leg in self.new_legs_1: self.legs.add(leg) + self.zoomable_mobs.add(leg) for leg in self.new_legs_2: self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) self.hypotenuses = VMobject() for hyp in self.new_hypotenuses: self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) for ls in self.additional_light_sources: self.light_sources.add(ls) self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) # update scene self.add( @@ -2644,6 +2679,7 @@ class PondScene(Scene): self.inner_lake, self.outer_lake, ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) if show_steps == True: self.add( @@ -2651,6 +2687,7 @@ class PondScene(Scene): self.hypotenuses, self.altitudes, ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) self.wait() @@ -2658,37 +2695,8 @@ class PondScene(Scene): if show_steps == True: self.play(FadeOut(ls0_dot)) - # scale down - if scale_down: - - indicator_wiggle() - - if show_steps == True: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - ) - else: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - ) - - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - - else: - # update the lake center and the radius - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP - self.lake_radius *= 2 + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 @@ -2711,6 +2719,8 @@ class PondScene(Scene): self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + self.add(self.inner_lake, self.outer_lake, self.legs, @@ -2725,29 +2735,44 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 - for i in range(3): - construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) + construction_step(0) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(1) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + + - return self.play( FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) - ) - - for i in range(3,5): - construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True, ls_radius = ls_radius/2**3) + ) + max_it = 6 + scale = 2**(max_it - 4) + TEX_SCALE *= scale + for i in range(3,max_it + 1): + construction_step(i, show_steps = False, run_time = 4.0/2**i) + #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it MAX_N = 17 + origin_point = self.obs_dot.get_center() + self.number_line = NumberLine( x_min = -MAX_N, x_max = MAX_N + 1, @@ -2755,13 +2780,13 @@ class PondScene(Scene): number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = range(-MAX_N,MAX_N + 1), - unit_size = LAKE0_RADIUS * TAU/4 / 4, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(2.5 * DOWN) + ).shift(scale * 2.5 * DOWN) self.number_line.label_direction = DOWN @@ -2786,8 +2811,8 @@ class PondScene(Scene): # open sea open_sea = Rectangle( - width = 20, - height = 10, + width = 20 * scale, + height = 10 * scale, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, @@ -2797,12 +2822,389 @@ class PondScene(Scene): self.play( - Transform(pond_sources,nl_sources), - Transform(self.outer_lake,open_sea), + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), FadeOut(self.inner_lake) ) self.play(FadeIn(self.number_line)) + + self.wait() + + v = 5 * scale * UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + #self.remove(self.obs_dot) + self.play( + indicator.move_to, origin_point + scale * UP, + indicator_reading.move_to, origin_point + scale * UP, + FadeOut(open_sea), + FadeOut(morty), + FadeIn(self.number_line_labels) + ) + + two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ + "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ + "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ + "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ + "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") + + nb_symbols = len(two_sided_sum.submobjects) + + two_sided_sum.scale(TEX_SCALE) + + for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.2 * scale * DOWN) + + self.play(Write(two_sided_sum)) + + covering_rectangle = Rectangle( + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1, + ) + covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + for i in range(10): + self.add_foreground_mobject(nl_sources.submobjects[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator_reading) + + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(indicator) + + central_plus_sign = two_sided_sum[13] + + self.play( + FadeIn(covering_rectangle), + ReplacementTransform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) + ) + + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + + self.play( + indicator.move_to,p, + half_indicator_reading.move_to,p, + FadeIn(equals_sign) + ) + + # show Randy admiring the result + randy = Randolph().scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + + + +class WaitScene(TeacherStudentsScene): + + def construct(self): + + self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!")) + + student_q = TextMobject("What about") + full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?") + full_sum.next_to(student_q,RIGHT) + student_q.add(full_sum) + + + self.student_says(student_q, target_mode = "angry") + + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + + unit_length = 1.5 + vertical_spacing = 2.5 * DOWN + switch_on_time = 0.2 + + sum_vertical_spacing = 1.5 + + randy = self.get_primary_pi_creature() + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + + ls_template = LightSource( + radius = 2, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.5,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_to_show = odd_range, + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_LARGE_BUFF, + include_tip = True + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0) + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1), + ) + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2), + ) + + + + for ls in full_lights.submobjects: + self.play( + FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights) + ) + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + Transform(even_lights,even_lights_copy) + ) + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + + + full_terms = VMobject() + for i in full_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + buffer = 2 + result_box = Rectangle(width = 15, + height = buffer*equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + equation.add(result_box) + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + ShowCreation(result_box) + ) + + self.play(equation.shift, -equation.get_center()[1] * UP + UP) + + + + + + + + + + + + + + + + + + class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -2822,6 +3224,12 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) + + + + + + class ArcHighlightOverlayScene(Scene): def construct(self): @@ -2867,43 +3275,4 @@ class ArcHighlightOverlayScene(Scene): ) - flash_arcs(3) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + flash_arcs(3) \ No newline at end of file From a302a6bf04a69dd45bb00f1f363772d2952a081c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 17:12:47 -0800 Subject: [PATCH 05/24] Finished MathematicalWebOfConnections scene --- active_projects/basel2.py | 117 ++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index c975060e..804b2896 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -245,9 +245,8 @@ class ThinkAboutPondScene(PiCreatureScene): self.wait(2) class IntroScene(PiCreatureScene): - CONFIG = { - "rect_height" : 0.1, + "rect_height" : 0.075, "duration" : 1.0, "eq_spacing" : 3 * MED_LARGE_BUFF, "n_rects_to_show" : 30, @@ -334,11 +333,11 @@ class IntroScene(PiCreatureScene): rect_label = TexMobject("1") else: rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) - rect_label.scale(0.75) + rect_label.scale(0.75) max_width = 0.7*rect.get_width() if rect_label.get_width() > max_width: rect_label.scale_to_fit_width(max_width) - rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + rect_label.next_to(rect, UP, buff = MED_LARGE_BUFF/(i+1)) term_mobject = term_mobjects[i-1] rect_anim = GrowFromPoint(rect, term_mobject.get_center()) @@ -746,14 +745,115 @@ class MathematicalWebOfConnections(PiCreatureScene): self.play( MoveToTarget(to_shift_down), basel_sum.scale, 1.5, - basel_sum.move_to, 2*DOWN, + basel_sum.move_to, 1.5*DOWN, ) + self.basel_sum = basel_sum + def show_web_of_connections(self): - pass + self.remove(self.pi_creatures) + title = TextMobject("Interconnected web of mathematics") + title.to_edge(UP) + basel_sum = self.basel_sum + + dots = VGroup(*[ + Dot(radius = 0.1).move_to( + (j - 0.5*(i%2))*RIGHT + \ + (np.sqrt(3)/2.0)* i*DOWN + \ + 0.5*(random.random()*RIGHT + random.random()*UP), + ) + for i in range(4) + for j in range(7+(i%2)) + ]) + dots.scale_to_fit_height(3) + dots.next_to(title, DOWN, MED_LARGE_BUFF) + edges = VGroup() + for x in range(100): + d1, d2 = random.sample(dots, 2) + edge = Line(d1.get_center(), d2.get_center()) + edge.set_stroke(YELLOW, 0.5) + edges.add(edge) + + ## Choose special path + path_dots = VGroup( + dots[-7], + dots[-14], + dots[9], + dots[19], + dots[14], + ) + path_edges = VGroup(*[ + Line( + d1.get_center(), d2.get_center(), + color = RED + ) + for d1, d2 in zip(path_dots, path_dots[1:]) + ]) + + circle = Circle(color = YELLOW, radius = 1) + radius = Line(circle.get_center(), circle.get_right()) + radius.highlight(BLUE) + VGroup(circle, radius).next_to(path_dots[-1], RIGHT) + + self.play( + Write(title), + LaggedStart(ShowCreation, edges, run_time = 3), + LaggedStart(GrowFromCenter, dots, run_time = 3) + ) + self.play(path_dots[0].highlight, RED) + for dot, edge in zip(path_dots[1:], path_edges): + self.play( + ShowCreation(edge), + dot.highlight, RED + ) + self.play(ShowCreation(radius)) + radius.set_points_as_corners(radius.get_anchors()) + self.play( + ShowCreation(circle), + Rotate(radius, angle = 0.999*TAU, about_point = radius.get_start()), + run_time = 2 + ) + self.wait() + + graph = VGroup(dots, edges, path_edges, title) + circle.add(radius) + basel_sum.generate_target() + basel_sum.target.to_edge(UP) + + arrow = Arrow( + UP, DOWN, + rectangular_stem_width = 0.1, + tip_length = 0.45, + color = RED, + ) + arrow.next_to(basel_sum.target, DOWN, buff = MED_LARGE_BUFF) + + self.play( + MoveToTarget(basel_sum), + graph.next_to, basel_sum.target, UP, LARGE_BUFF, + circle.next_to, arrow, DOWN, MED_LARGE_BUFF, + ) + self.play(GrowArrow(arrow)) + self.wait() + + self.arrow = arrow + self.circle = circle def show_light(self): - pass + light = AmbientLight( + num_levels = 500, radius = 13, + opacity_function = lambda r : 1.0/(r+1), + ) + pi = self.basel_sum[-1][0] + pi.set_stroke(BLACK, 0.5) + light.move_to(pi) + self.play( + SwitchOn(light, run_time = 3), + Animation(self.arrow), + Animation(self.circle), + Animation(self.basel_sum), + ) + self.wait() ### @@ -766,9 +866,6 @@ class MathematicalWebOfConnections(PiCreatureScene): return VGroup(jerk, randy) - - - class FirstLighthouseScene(PiCreatureScene): def construct(self): From 226f0153bd41a8c1aa5e3f19ae53ed5ead92a3ab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:01:44 -0800 Subject: [PATCH 06/24] Enabled coloring of the point in a GrowFromPoint animation --- animation/transform.py | 6 ++++++ topics/numerals.py | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/animation/transform.py b/animation/transform.py index 433650df..aec862b0 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -101,9 +101,15 @@ class Swap(CyclicReplace): pass #Renaming, more understandable for two entries class GrowFromPoint(Transform): + CONFIG = { + "point_color" : None, + } def __init__(self, mobject, point, **kwargs): + digest_config(self, kwargs) target = mobject.copy() point_mob = Point(point) + if self.point_color: + point_mob.highlight(self.point_color) mobject.replace(point_mob) mobject.highlight(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs) diff --git a/topics/numerals.py b/topics/numerals.py index 1bcc61fe..1390081c 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -144,7 +144,6 @@ class ContinualChangingDecimal(ContinualAnimation): def update_mobject(self, dt): self.anim.update(self.internal_time) - From 14f531086ccabff34ac5e0abdc69447235740892 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:02:12 -0800 Subject: [PATCH 07/24] Finished FirstLighthouseScene --- active_projects/basel2.py | 314 +++++++++++++++++++++++++------------- topics/light.py | 12 +- 2 files changed, 207 insertions(+), 119 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 804b2896..adb4065f 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -865,161 +865,259 @@ class MathematicalWebOfConnections(PiCreatureScene): return VGroup(jerk, randy) - class FirstLighthouseScene(PiCreatureScene): - + CONFIG = { + "num_levels" : 100, + "opacity_function" : inverse_quadratic(1,2,1), + } def construct(self): - self.remove(self.get_primary_pi_creature()) + self.remove(self.pi_creature) self.show_lighthouses_on_number_line() - - + self.describe_brightness_of_each() + self.ask_about_rearrangements() def show_lighthouses_on_number_line(self): - - self.number_line = NumberLine( + number_line = self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1.6, stroke_width = 1, - numbers_with_elongated_ticks = range(1,5), - numbers_to_show = range(1,5), + numbers_with_elongated_ticks = range(1,6), + numbers_to_show = range(1,6), unit_size = 2, tick_frequency = 0.2, line_to_number_buff = LARGE_BUFF, - label_direction = UP, + label_direction = DOWN, ) - self.number_line.label_direction = DOWN + number_line.add_numbers() + self.add(number_line) - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() + origin_point = number_line.number_to_point(0) - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + morty = self.pi_creature + morty.scale(0.75) + morty.flip() + right_pupil = morty.eyes[1] + morty.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + light_sources = VGroup() + for i in range(1,NUM_CONES+1): + light_source = LightSource( + opacity_function = self.opacity_function, + num_levels = self.num_levels, + radius = 12.0, + ) + point = number_line.number_to_point(i) + light_source.move_source_to(point) + light_sources.add(light_source) - light_indicator = LightIndicator(radius = INDICATOR_RADIUS, + lighthouses = self.lighthouses = VGroup(*[ + ls.lighthouse + for ls in light_sources[:NUM_VISIBLE_CONES+1] + ]) + + morty.save_state() + morty.scale(3) + morty.fade(1) + morty.center() + self.play(morty.restore) + self.play( + morty.change, "pondering", + LaggedStart( + FadeIn, lighthouses, + run_time = 1 + ) + ) + self.play(LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources + ]), + run_time = 5, + lag_ratio = 0.1, + rate_func = rush_into, + ), Animation(lighthouses)) + + self.light_sources = light_sources + + def describe_brightness_of_each(self): + number_line = self.number_line + morty = self.pi_creature + light_sources = self.light_sources + lighthouses = self.lighthouses + + light_indicator = LightIndicator( + radius = INDICATOR_RADIUS, opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR) + color = LIGHT_COLOR + ) light_indicator.reading.scale(0.8) + light_indicator.set_intensity(0) + intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) + opacities = intensities * light_indicator.opacity_for_unit_intensity - bubble = ThoughtBubble(direction = RIGHT, - width = 2.5, height = 3.5) - bubble.next_to(randy,LEFT+UP) + bubble = ThoughtBubble( + direction = RIGHT, + width = 2.5, height = 3.5 + ) + bubble.pin_to(morty) bubble.add_content(light_indicator) - self.play( - randy.change, "wave_2", - ShowCreation(bubble), - FadeIn(light_indicator) + euler_sum_above = TexMobject( + "1", "+", + "{1\over 4}", "+", + "{1\over 9}", "+", + "{1\over 16}", "+", + "{1\over 25}", "+", + "{1\over 36}" ) + euler_sum_terms = euler_sum_above[::2] + plusses = euler_sum_above[1::2] - light_sources = [] - - - euler_sum_above = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") - - for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above): + for i, term in enumerate(euler_sum_above): #horizontal alignment with tick marks - term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2) + term.next_to(number_line.number_to_point(0.5*i+1), UP , buff = 2) # vertical alignment with light indicator old_y = term.get_center()[1] new_y = light_indicator.get_center()[1] term.shift([0,new_y - old_y,0]) - - - - for i in range(1,NUM_CONES+1): - light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = NUM_LEVELS, - radius = 12.0, - ) - point = self.number_line.number_to_point(i) - light_source.move_source_to(point) - light_sources.append(light_source) - - - for ls in light_sources: - self.add_foreground_mobject(ls.lighthouse) - - light_indicator.set_intensity(0) - - intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) - opacities = intensities * light_indicator.opacity_for_unit_intensity - - self.remove_foreground_mobjects(light_indicator) - - - # slowly switch on visible light cones and increment indicator - for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func( - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light), - FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - # this last line *technically* fades in the last term, but it is off-screen - ChangeDecimalToValue(light_indicator.reading,intensities[i], - rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) - ) - - if i == 0: - # move a copy out of the thought bubble for comparison - light_indicator_copy = light_indicator.copy() - old_y = light_indicator_copy.get_center()[1] - new_y = self.number_line.get_center()[1] - self.play( - light_indicator_copy.shift,[0, new_y - old_y,0] - ) - - # quickly switch on off-screen light cones and increment indicator - for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ChangeDecimalToValue(light_indicator.reading,intensities[i-1], - rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1]) - ) - # show limit value in light indicator and an equals sign limit_reading = TexMobject("{\pi^2 \over 6}") limit_reading.move_to(light_indicator.reading) equals_sign = TexMobject("=") - equals_sign.next_to(randy, UP) + equals_sign.next_to(morty, UP) old_y = equals_sign.get_center()[1] new_y = euler_sum_above.get_center()[1] equals_sign.shift([0,new_y - old_y,0]) + #Triangle of light to morty's eye + ls0 = light_sources[0] + ls0.save_state() + eye = morty.eyes[1] + triangle = Polygon( + number_line.number_to_point(1), + eye.get_top(), eye.get_bottom(), + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 1, + ) + triangle_anim = GrowFromPoint( + triangle, triangle.get_right(), + point_color = YELLOW + ) + + # First lighthouse has apparent reading + self.play(LaggedStart(FadeOut, light_sources[1:])) + self.wait() + self.play( + triangle_anim, + # Animation(eye) + ) + for x in range(4): + triangle_copy = triangle.copy() + self.play( + FadeOut(triangle.copy()), + triangle_anim, + ) + self.play( + FadeOut(triangle), + ShowCreation(bubble), + FadeIn(light_indicator), + ) + self.play( + UpdateLightIndicator(light_indicator, 1), + FadeIn(euler_sum_terms[0]) + ) + self.wait(2) + + # Second lighthouse is 1/4, third is 1/9, etc. + for i in range(1, 5): + self.play( + ApplyMethod( + ls0.move_to, light_sources[i], + run_time = 3 + ), + UpdateLightIndicator(light_indicator, 1./(i+1)**2, run_time = 3), + FadeIn( + euler_sum_terms[i], + run_time = 3, + rate_func = squish_rate_func(smooth, 0.5, 1) + ), + ) + self.wait() + self.play( + ApplyMethod(ls0.restore), + UpdateLightIndicator(light_indicator, 1) + ) + + #Switch them all on + self.play( + LaggedStart(FadeIn, lighthouses[1:]), + morty.change, "hooray", + ) + self.play( + LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources[1:] + ]), + run_time = 5, + rate_func = rush_into, + ), + Animation(lighthouses), + Animation(euler_sum_above), + Write(plusses), + UpdateLightIndicator(light_indicator, np.pi**2/6, run_time = 5), + morty.change, "happy", + ) + self.wait() self.play( FadeOut(light_indicator.reading), FadeIn(limit_reading), - FadeIn(equals_sign), + morty.change, "confused", + ) + self.play(Write(equals_sign)) + self.wait() + + def ask_about_rearrangements(self): + light_sources = self.light_sources + origin = self.number_line.number_to_point(0) + morty = self.pi_creature + + self.play( + LaggedStart( + Rotate, light_sources, + lambda m : (m, (2*random.random()-1)*90*DEGREES), + about_point = origin, + rate_func = lambda t : wiggle(t, 4), + run_time = 10, + lag_ratio = 0.9, + ), + morty.change, "pondering", ) - +class RearrangeWords(Scene): + def construct(self): + words = TextMobject("Rearrange without changing \\\\ the apparent brightness") + self.play(Write(words)) + self.wait(5) +class ThatJustSeemsUseless(TeacherStudentsScene): + def construct(self): + self.student_says( + "How would \\\\ that help?", + target_mode = "sassy", + student_index = 2, + bubble_kwargs = {"direction" : LEFT}, + ) + self.play( + self.teacher.change, "guilty", + self.get_student_changes(*3*['sassy']) + ) self.wait() class SingleLighthouseScene(PiCreatureScene): diff --git a/topics/light.py b/topics/light.py index 3bbcd9e5..4de886b2 100644 --- a/topics/light.py +++ b/topics/light.py @@ -128,7 +128,6 @@ class LightSource(VMobject): self.camera_mob = new_cam_mob self.spotlight.camera_mob = new_cam_mob - def set_screen(self, new_screen): if self.has_screen(): self.spotlight.screen = new_screen @@ -158,9 +157,6 @@ class LightSource(VMobject): # in any case self.screen = new_screen - - - def move_source_to(self,point): apoint = np.array(point) v = apoint - self.get_source_point() @@ -190,7 +186,6 @@ class LightSource(VMobject): self.spotlight.update_sectors() self.update_shadow() - def update_lighthouse(self): new_lh = Lighthouse() new_lh.move_to(ORIGIN) @@ -198,7 +193,6 @@ class LightSource(VMobject): new_lh.shift(self.get_source_point()) self.lighthouse.submobjects = new_lh.submobjects - def update_ambient(self): new_ambient_light = AmbientLight( source_point = VectorizedPoint(location = ORIGIN), @@ -212,12 +206,9 @@ class LightSource(VMobject): new_ambient_light.move_source_to(self.get_source_point()) self.ambient_light.submobjects = new_ambient_light.submobjects - - def get_source_point(self): return self.source_point.get_location() - def rotation_matrix(self): if self.camera_mob == None: @@ -242,7 +233,6 @@ class LightSource(VMobject): R = np.dot(R2, R1) return R - def update_shadow(self): point = self.get_source_point() @@ -370,7 +360,7 @@ class AmbientLight(VMobject): "opacity_function" : lambda r : 1.0/(r+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, - "num_levels" : 10, + "num_levels" : NUM_LEVELS, "radius" : 5.0 } From ec610a9152c27ce242bab666dbb74700ee9faac4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:07:57 -0800 Subject: [PATCH 08/24] Really dumb copy-pasting of extract_scene.py and scene.py from master to get what I want. Not sure what's going on here. --- extract_scene.py | 6 +++--- scene/scene.py | 11 ++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/extract_scene.py b/extract_scene.py index 0d21e8a7..bbc17b91 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -18,7 +18,6 @@ from camera import Camera HELP_MESSAGE = """ Usage: python extract_scene.py [] - -p preview in low quality -s show and save picture of last frame -w write result to file [this is default if nothing else is stated] @@ -35,7 +34,6 @@ SCENE_NOT_FOUND_MESSAGE = """ CHOOSE_NUMBER_MESSAGE = """ Choose number corresponding to desired scene/arguments. (Use comma separated list for multiple entries) - Choice(s): """ INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit" @@ -95,6 +93,7 @@ def get_configuration(): "save_pngs" : args.save_pngs, #If -t is passed in (for transparent), this will be RGBA "saved_image_mode": "RGBA" if args.transparent else "RGB", + "movie_file_extension" : ".mov" if args.transparent else ".mp4", "quiet" : args.quiet or args.write_all, "ignore_waits" : args.preview, "write_all" : args.write_all, @@ -237,6 +236,7 @@ def main(): "write_to_movie", "output_directory", "save_pngs", + "movie_file_extension", "start_at_animation_number", "end_at_animation_number", ] @@ -260,4 +260,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/scene/scene.py b/scene/scene.py index 6dc31d5f..5168db25 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -82,7 +82,7 @@ class Scene(Container): def setup(self): """ This is meant to be implement by any scenes which - are commonly subclassed, and have some common setup + are comonly subclassed, and have some common setup involved before the construct method is called. """ pass @@ -366,7 +366,6 @@ class Scene(Container): Each arg can either be an animation, or a mobject method followed by that methods arguments (and potentially follow by a dict of kwargs for that method). - This animation list is built by going through the args list, and each animation is simply added, but when a mobject method s hit, a MoveToTarget animation is built using the args that @@ -387,7 +386,7 @@ class Scene(Container): animations.pop() #method should already have target then. else: - mobject.target = mobject.deepcopy() + mobject.generate_target() # if len(state["method_args"]) > 0 and isinstance(state["method_args"][-1], dict): method_kwargs = state["method_args"].pop() @@ -578,17 +577,12 @@ class Scene(Container): FFMPEG_BIN, '-y', # overwrite output file if it exists '-f', 'rawvideo', - '-vcodec','rawvideo', '-s', '%dx%d'%(width, height), # size of one frame '-pix_fmt', 'rgba', '-r', str(fps), # frames per second '-i', '-', # The imput comes from a pipe '-an', # Tells FFMPEG not to expect any audio - '-vcodec', 'mpeg', - '-c:v', 'libx264', - '-pix_fmt', 'yuv420p', '-loglevel', 'error', - temp_file_path, ] if self.movie_file_extension == ".mov": # This is if the background of the exported video @@ -624,4 +618,3 @@ class EndSceneEarlyException(Exception): - From c2b3e3f3e03eccd12d4c9a4a0ef1aa23f5a3b22e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:34:42 -0800 Subject: [PATCH 09/24] Changed mobject of ScreenTracker --- topics/light.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/topics/light.py b/topics/light.py index 4de886b2..23721ef1 100644 --- a/topics/light.py +++ b/topics/light.py @@ -314,8 +314,9 @@ class SwitchOn(LaggedStart): def __init__(self, light, **kwargs): if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): raise Exception("Only AmbientLights and Spotlights can be switched on") - LaggedStart.__init__(self, - FadeIn, light, **kwargs) + LaggedStart.__init__( + self, FadeIn, light, **kwargs + ) class SwitchOff(LaggedStart): @@ -333,8 +334,6 @@ class SwitchOff(LaggedStart): light.submobjects = light.submobjects[::-1] - - class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -344,7 +343,6 @@ class Lighthouse(SVGMobject): def move_to(self,point): self.next_to(point, DOWN, buff = 0) - class AmbientLight(VMobject): # Parameters are: @@ -421,19 +419,6 @@ class AmbientLight(VMobject): submob.set_fill(opacity = new_submob_alpha) - - - - - - - - - - - - - class Spotlight(VMobject): CONFIG = { @@ -590,7 +575,6 @@ class Spotlight(VMobject): - def dimming(self,new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha @@ -623,7 +607,27 @@ class Spotlight(VMobject): class ScreenTracker(ContinualAnimation): + def __init__(self, light_source, **kwargs): + self.light_source = light_source + dummy_mob = Mobject() + ContinualAnimation.__init__(self, dummy_mob, **kwargs) def update_mobject(self, dt): - self.mobject.update() + self.light_source.update() + + + + + + + + + + + + + + + + From 3afdce521defa6db1cc16b4009aa99e5f28ae772 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:35:13 -0800 Subject: [PATCH 10/24] Up to EarthScene of basel2 --- active_projects/basel2.py | 450 +++++++++++++++++++++----------------- 1 file changed, 250 insertions(+), 200 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index adb4065f..225e4c42 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -92,8 +92,10 @@ class AngleUpdater(ContinualAnimation): new_arc.generate_points() new_arc.move_arc_center_to(self.spotlight.get_source_point()) self.angle_arc.points = new_arc.points - self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, - at_start = True, at_end = True) + self.angle_arc.add_tip( + tip_length = ARC_TIP_LENGTH, + at_start = True, at_end = True + ) class LightIndicator(Mobject): CONFIG = { @@ -939,6 +941,7 @@ class FirstLighthouseScene(PiCreatureScene): lag_ratio = 0.1, rate_func = rush_into, ), Animation(lighthouses)) + self.wait() self.light_sources = light_sources @@ -1120,55 +1123,47 @@ class ThatJustSeemsUseless(TeacherStudentsScene): ) self.wait() -class SingleLighthouseScene(PiCreatureScene): - +class AskAboutBrightness(TeacherStudentsScene): def construct(self): + self.student_says( + "What do you mean \\\\ by ``brightness''?" + ) + self.play(self.teacher.change, "happy") + self.wait(3) +class IntroduceScreen(Scene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + "num_rays" : 250, + "min_ray_angle" : 0, + "max_ray_angle" : TAU, + } + def construct(self): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates self.rotate_screen() - self.morph_lighthouse_into_sun() - + # self.morph_lighthouse_into_sun() def setup_elements(self): - - self.remove(self.get_primary_pi_creature()) - SCREEN_SIZE = 3.0 - DISTANCE_FROM_LIGHTHOUSE = 10.0 - source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] - observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] - + source_point = self.source_point = 2.5*LEFT + observer_point = 3.5*RIGHT # Light source - self.light_source = LightSource( + light_source = self.light_source = LightSource( opacity_function = inverse_quadratic(1,2,1), - num_levels = NUM_LEVELS, - radius = 10, - max_opacity_ambient = AMBIENT_FULL + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, ) - self.light_source.move_source_to(source_point) - - - # Pi Creature - - morty = self.get_primary_pi_creature() - morty.scale(0.5) - morty.move_to(observer_point) - morty.shift(2*OUT) - self.add_foreground_mobject(morty) - - self.add(self.light_source.lighthouse) - - self.play( - SwitchOn(self.light_source.ambient_light) - ) + light_source.move_source_to(source_point) # Screen - self.screen = Rectangle( - width = 0.1, + screen = self.screen = Rectangle( + width = 0.05, height = 2, mark_paths_closed = True, fill_color = WHITE, @@ -1176,70 +1171,105 @@ class SingleLighthouseScene(PiCreatureScene): stroke_width = 0.0 ) - self.screen.rotate(-TAU/6) - self.screen.next_to(morty,LEFT) + screen.next_to(observer_point, LEFT) - self.light_source.set_screen(self.screen) - - # Animations - - self.play(FadeIn(self.screen)) - - self.light_source.set_max_opacity_spotlight(0.001) - self.add(self.light_source.spotlight) - - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) - - self.wait() - - - # just calling .dim_ambient via ApplyMethod does not work, why? - dimmed_ambient_light = self.light_source.ambient_light.deepcopy() - dimmed_ambient_light.dimming(AMBIENT_DIMMED) - - self.play( - Transform(self.light_source.ambient_light,dimmed_ambient_light), - self.light_source.set_max_opacity_spotlight,1.0, - FadeIn(self.light_source.shadow) + screen_label = TextMobject("Screen") + screen_label.next_to(screen, UP+LEFT) + screen_arrow = Arrow( + screen_label.get_bottom(), + screen.get_center(), ) - self.add_foreground_mobject(morty) + # Pi creature + morty = Mortimer() + morty.shift(screen.get_center() - morty.eyes.get_left()) + morty.look_at(source_point) + # Camera + camera = SVGMobject(file_name = "camera") + camera.rotate(TAU/4) + camera.scale_to_fit_height(1.5) + camera.move_to(morty.eyes, LEFT) + # Animations + light_source.set_max_opacity_spotlight(0.001) + screen_tracker = self.screen_tracker = ScreenTracker(light_source) + self.add(light_source.lighthouse) + self.play(SwitchOn(light_source.ambient_light)) + self.play( + Write(screen_label), + GrowArrow(screen_arrow), + FadeIn(screen) + ) + self.wait() + self.play(*map(FadeOut, [screen_label, screen_arrow])) + screen.save_state() + self.play( + FadeIn(morty), + screen.match_height, morty.eyes, + screen.next_to, morty.eyes, LEFT, SMALL_BUFF + ) + self.play(Blink(morty)) + self.play( + FadeOut(morty), + FadeIn(camera), + screen.scale, 2, {"about_edge" : UP}, + ) + self.wait() + self.play( + FadeOut(camera), + screen.restore, + ) + + light_source.set_screen(screen) + light_source.spotlight.opacity_function = lambda r : 0.2/(r+1) + screen_tracker.update(0) + + ## Ask about proportion + self.add_foreground_mobjects(light_source.shadow, screen) + self.shoot_rays() + + ## + self.play(SwitchOn(light_source.spotlight)) def setup_angle(self): self.wait() - - pointing_screen_at_source = Rotate(self.screen,TAU/6) - self.play(pointing_screen_at_source) - # angle msmt (arc) - arc_angle = self.light_source.spotlight.opening_angle() # draw arc arrows to show the opening angle - self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), - angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) + self.angle_arc = Arc( + radius = 3, + start_angle = self.light_source.spotlight.start_angle(), + angle = self.light_source.spotlight.opening_angle(), + tip_length = ARC_TIP_LENGTH + ) #angle_arc.add_tip(at_start = True, at_end = True) self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) # angle msmt (decimal number) - self.angle_indicator = DecimalNumber(arc_angle / DEGREES, + self.angle_indicator = DecimalNumber( + arc_angle / DEGREES, num_decimal_points = 0, - unit = "^\\circ") - self.angle_indicator.next_to(self.angle_arc,RIGHT) + unit = "^\\circ" + ) + self.angle_indicator.next_to(self.angle_arc, RIGHT) angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES - ca1 = ContinualChangingDecimal(self.angle_indicator,angle_update_func) - self.add(ca1) + angle_tracker = ContinualChangingDecimal( + self.angle_indicator, angle_update_func + ) + self.add(angle_tracker) - ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight) - self.add(ca2) + arc_tracker = AngleUpdater( + self.angle_arc, + self.light_source.spotlight + ) + self.add(arc_tracker) self.play( ShowCreation(self.angle_arc), @@ -1249,162 +1279,182 @@ class SingleLighthouseScene(PiCreatureScene): self.wait() def rotate_screen(self): - - - - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, TAU/4)) - -### The following is supposed to morph the scene into the Earth scene, -### but it doesn't work - - - def morph_lighthouse_into_sun(self): - - - - sun_position = [-100,0,0] - - - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) + self.add( + ContinualUpdateFromFunc( + self.light_source, + lambda m : m.update() + ), ) + def rotate_screen(angle): + self.play( + Rotate(self.light_source.spotlight.screen, angle), + Animation(self.angle_indicator), + Animation(self.angle_arc), + run_time = 2, + ) + for angle in TAU/8, -TAU/4, TAU/8, TAU/6: + rotate_screen(angle) + self.wait() + self.shoot_rays() + rotate_screen(-TAU/6) - self.sun = self.light_source.deepcopy() + ## - #self.sun.num_levels = NUM_LEVELS, - #self.sun.set_radius(150) - #self.sun.set_max_opacity_ambient(AMBIENT_FULL) - + def shoot_rays(self, show_creation_kwargs = None): + if show_creation_kwargs is None: + show_creation_kwargs = {} + source_point = self.source_point + screen = self.screen + # Rays + step_size = (self.max_ray_angle - self.min_ray_angle)/self.num_rays + rays = VGroup(*[ + Line(ORIGIN, self.radius*rotate_vector(RIGHT, angle)) + for angle in np.arange( + self.min_ray_angle, + self.max_ray_angle, + step_size + ) + ]) + rays.shift(source_point) + rays.set_stroke(YELLOW, 1) + max_angle = np.max([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + min_angle = np.min([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + for ray in rays: + if min_angle <= ray.get_angle() <= max_angle: + ray.target_color = GREEN + else: + ray.target_color = RED - self.sun.spotlight.change_opacity_function(lambda r: 0.5) - self.sun.set_radius(150) - self.sun.move_source_to(sun_position) - - # self.sun.update() - - # self.add(self.sun) - # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) - - #print self.sun.spotlight.get_source_point() - - self.play( - #self.light_source.spotlight.move_source_to,sun_position, - Transform(self.light_source,self.sun) - ) - - #self.add(ScreenTracker(self.sun)) - + self.play(*[ + ShowCreation(ray, run_time = 3, **show_creation_kwargs) + for ray in rays + ]) + self.play(*[ + ApplyMethod(ray.highlight, ray.target_color) + for ray in rays + ]) self.wait() + self.play(FadeOut(rays)) -class EarthScene(Scene): - +class EarthScene(IntroduceScreen): + CONFIG = { + "screen_height" : 0.5, + "screen_thickness" : 0, + "radius" : 100 + SPACE_WIDTH, + "source_point" : 100*LEFT, + "min_ray_angle" : -1.65*DEGREES, + "max_ray_angle" : 1.65*DEGREES, + "num_rays" : 100, + } def construct(self): + # Earth + earth_radius = 3 + earth = ImageMobject("earth") + earth_circle = Circle(radius = earth_radius) + earth_circle.to_edge(RIGHT) + earth.replace(earth_circle) - SCREEN_THICKNESS = 10 + black_rect = Rectangle( + height = 2*SPACE_HEIGHT, + width = earth_radius + LARGE_BUFF, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1 + ) + black_rect.move_to(earth.get_center(), LEFT) - self.screen_height = 2.0 - self.brightness_rect_height = 1.0 + self.add_foreground_mobjects(black_rect, earth) # screen - self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) - self.screen.set_points_as_corners([ - [3,-self.screen_height/2,0], - [3,self.screen_height/2,0] - ]) + screen = self.screen = Line( + self.screen_height*UP, ORIGIN, + stroke_color = WHITE, + stroke_width = self.screen_thickness, + ) + screen.move_to(earth.get_left()) + screen.generate_target() + screen.target.rotate( + -60*DEGREES, about_point = earth_circle.get_center() + ) - # Earth - - earth_center_x = 2 - earth_center = [earth_center_x,0,0] - earth_radius = 3 - earth = Circle(radius = earth_radius) - earth.move_to(earth_center) - #self.remove(self.screen_tracker) - - theta0 = 70 * DEGREES - dtheta = 10 * DEGREES - theta1 = theta0 + dtheta - theta = (theta0 + theta1)/2 - - earth.add(self.screen) - - # Morty - - morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) - self.add_foreground_mobject(morty) + equator_arrow = Vector( + DOWN+2*RIGHT, color = WHITE, + use_rectangular_stem = False, + ) + equator_arrow.next_to(screen.get_center(), UP+LEFT, SMALL_BUFF) + pole_arrow = Vector( + UP+3*RIGHT, + color = WHITE, + use_rectangular_stem = False, + path_arc = -60*DEGREES, + ) + pole_arrow.shift( + screen.target.get_center()+SMALL_BUFF*LEFT - \ + pole_arrow.get_end() + ) + for arrow in equator_arrow, pole_arrow: + arrow.pointwise_become_partial(arrow, 0, 0.95) + equator_words = TextMobject("Some", "unit of area") + pole_words = TextMobject("The same\\\\", "unit of area") + pole_words.next_to(pole_arrow.get_start(), DOWN) + equator_words.next_to(equator_arrow.get_start(), UP) # Light source (far-away Sun) - sun_position = [-100,0,0] - - self.sun = LightSource( + sun = sun = LightSource( opacity_function = lambda r : 0.5, max_opacity_ambient = 0, max_opacity_spotlight = 0.5, - num_levels = NUM_LEVELS, - radius = 150, - screen = self.screen + num_levels = 5, + radius = self.radius, + screen = screen ) + sun.move_source_to(self.source_point) + sunlight = sun.spotlight + sunlight.opacity_function = lambda r : 5./(r+1) - self.sun.move_source_to(sun_position) - + screen_tracker = ScreenTracker(sun) # Add elements to scene - self.add(self.sun,self.screen) - self.bring_to_back(self.sun.shadow) - screen_tracker = ScreenTracker(self.sun) - + self.add(screen) + self.play(SwitchOn( + sunlight, + rate_func = squish_rate_func(smooth, 0.7, 0.8), + )) self.add(screen_tracker) - - self.wait() - - self.play(FadeIn(earth)) - self.bring_to_back(earth) - - # move screen onto Earth - screen_on_earth = self.screen.deepcopy() - screen_on_earth.rotate(-theta) - screen_on_earth.scale(0.3) - screen_on_earth.move_to(np.array([ - earth_center_x - earth_radius * np.cos(theta), - earth_radius * np.sin(theta), - 0])) - - polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) - self.play( - Transform(self.screen, screen_on_earth), - Transform(morty,polar_morty) + Write(equator_words), + GrowArrow(equator_arrow) ) - + self.add_foreground_mobjects(equator_words, equator_arrow) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + }) self.wait() - - - tropical_morty = polar_morty.copy() - tropical_morty.move_to(np.array([0,0,0])) - morty.target = tropical_morty - - # move screen to equator - + # Point to patch self.play( - Rotate(earth, theta0 + dtheta/2,run_time = 3), - MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), + MoveToTarget(screen), + Transform(equator_arrow, pole_arrow), + Transform( + equator_words, pole_words, + rate_func = squish_rate_func(smooth, 0.6, 1), + ), + Animation(sunlight), + run_time = 3, ) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + }) + self.wait() class ScreenShapingScene(ThreeDScene): From c521a021fe768b888a4cffff3e93fc1e9e609a85 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:35:40 -0800 Subject: [PATCH 11/24] Changed order of args in paths searched for svg files --- mobject/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 8d0fbbc8..8a3fa899 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -37,9 +37,9 @@ class SVGMobject(VMobject): if self.file_name is None: raise Exception("Must specify file for SVGMobject") possible_paths = [ - self.file_name, os.path.join(SVG_IMAGE_DIR, self.file_name), os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"), + self.file_name, ] for path in possible_paths: if os.path.exists(path): From ad13082b73f0b29b1e615440a94011f4a81d2461 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:55:38 -0800 Subject: [PATCH 12/24] Added Mobject.stretch_to_fit_depth --- mobject/mobject.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mobject/mobject.py b/mobject/mobject.py index 6618d1aa..8bb2f268 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -389,6 +389,9 @@ class Mobject(Container): def stretch_to_fit_height(self, height, **kwargs): return self.rescale_to_fit(height, 1, stretch = True, **kwargs) + def stretch_to_fit_depth(self, depth, **kwargs): + return self.rescale_to_fit(depth, 1, stretch = True, **kwargs) + def scale_to_fit_width(self, width, **kwargs): return self.rescale_to_fit(width, 0, stretch = False, **kwargs) From ec4bf7b504b15d6f3edbe1518edcc80dde6f931a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:56:22 -0800 Subject: [PATCH 13/24] Fixed(?) Spotlight.update_sectors --- topics/light.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/topics/light.py b/topics/light.py index 23721ef1..5fc0b268 100644 --- a/topics/light.py +++ b/topics/light.py @@ -234,7 +234,6 @@ class LightSource(VMobject): return R def update_shadow(self): - point = self.get_source_point() projected_screen_points = [] if not self.has_screen(): @@ -304,7 +303,6 @@ class LightSource(VMobject): self.shadow.mark_paths_closed = True - class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -318,7 +316,6 @@ class SwitchOn(LaggedStart): self, FadeIn, light, **kwargs ) - class SwitchOff(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -333,7 +330,6 @@ class SwitchOff(LaggedStart): FadeOut, light, **kwargs) light.submobjects = light.submobjects[::-1] - class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -473,11 +469,6 @@ class Spotlight(VMobject): def new_sector(self,r,dr,lower_angle,upper_angle): - # Note: I'm not looking _too_ closely at the implementation - # of these updates based on viewing angles and such. It seems to - # behave as intended, but let me know if you'd like more thorough - # scrutiny - alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( inner_radius = r, @@ -564,15 +555,17 @@ class Spotlight(VMobject): def update_sectors(self): if self.screen == None: return - for submob in self.submobject_family(): + for submob in self.submobjects: if type(submob) == AnnularSector: lower_angle, upper_angle = self.viewing_angles(self.screen) #dr = submob.outer_radius - submob.inner_radius dr = self.radius / self.num_levels - new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle) - submob.points = new_submob.points - submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) - + new_submob = self.new_sector( + submob.inner_radius, dr, lower_angle, upper_angle + ) + # submob.points = new_submob.points + # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) + Transform(submob, new_submob).update(1) def dimming(self,new_alpha): From 0bf9f0b64431a4932518b25598106d6c87d194ce Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:56:55 -0800 Subject: [PATCH 14/24] Finished InverseSquareLaw scene --- active_projects/basel2.py | 677 +++++++++++++++----------------------- 1 file changed, 264 insertions(+), 413 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 225e4c42..8a14b248 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -100,8 +100,10 @@ class AngleUpdater(ContinualAnimation): class LightIndicator(Mobject): CONFIG = { "radius": 0.5, + "reading_height" : 0.25, "intensity": 0, "opacity_for_unit_intensity": 1, + "fill_color" : YELLOW, "precision": 3, "show_reading": True, "measurement_point": ORIGIN, @@ -110,13 +112,18 @@ class LightIndicator(Mobject): def generate_points(self): self.background = Circle(color=BLACK, radius = self.radius) - self.background.set_fill(opacity=1.0) + self.background.set_fill(opacity = 1.0) self.foreground = Circle(color=self.color, radius = self.radius) - self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) + self.foreground.set_stroke( + color=INDICATOR_STROKE_COLOR, + width=INDICATOR_STROKE_WIDTH + ) + self.foreground.set_fill(color = self.fill_color) self.add(self.background, self.foreground) self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) self.reading.set_fill(color=INDICATOR_TEXT_COLOR) + self.reading.scale_to_fit_height(self.reading_height) self.reading.move_to(self.get_center()) if self.show_reading: self.add(self.reading) @@ -126,6 +133,10 @@ class LightIndicator(Mobject): new_opacity = min(1, new_int * self.opacity_for_unit_intensity) self.foreground.set_fill(opacity=new_opacity) ChangeDecimalToValue(self.reading, new_int).update(1) + if new_int > 1.1: + self.reading.set_fill(color = BLACK) + else: + self.reading.set_fill(color = WHITE) return self def get_measurement_point(self): @@ -134,10 +145,11 @@ class LightIndicator(Mobject): else: return self.get_center() - def measured_intensity(self): - distance = np.linalg.norm(self.get_measurement_point() - - self.light_source.get_source_point()) + distance = np.linalg.norm( + self.get_measurement_point() - + self.light_source.get_source_point() + ) intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity return intensity @@ -157,11 +169,11 @@ class UpdateLightIndicator(AnimationGroup): indicator.foreground, target_foreground ) changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) + AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator class ContinualLightIndicatorUpdate(ContinualAnimation): - def update_mobject(self,dt): self.mobject.continual_update() @@ -1266,7 +1278,7 @@ class IntroduceScreen(Scene): self.add(angle_tracker) arc_tracker = AngleUpdater( - self.angle_arc, + self.angle_arc, self.light_source.spotlight ) self.add(arc_tracker) @@ -1285,18 +1297,24 @@ class IntroduceScreen(Scene): lambda m : m.update() ), ) + self.add( + ContinualUpdateFromFunc( + self.angle_indicator, + lambda m : m.set_stroke(width = 0).set_fill(opacity = 1) + ) + ) + self.remove(self.light_source.ambient_light) def rotate_screen(angle): self.play( Rotate(self.light_source.spotlight.screen, angle), - Animation(self.angle_indicator), Animation(self.angle_arc), run_time = 2, ) - for angle in TAU/8, -TAU/4, TAU/8, TAU/6: + for angle in TAU/8, -TAU/4, TAU/8, -TAU/6: rotate_screen(angle) self.wait() self.shoot_rays() - rotate_screen(-TAU/6) + rotate_screen(TAU/6) ## @@ -1437,7 +1455,7 @@ class EarthScene(IntroduceScreen): ) self.add_foreground_mobjects(equator_words, equator_arrow) self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) }) self.wait() # Point to patch @@ -1452,454 +1470,287 @@ class EarthScene(IntroduceScreen): run_time = 3, ) self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) }) self.wait() -class ScreenShapingScene(ThreeDScene): - - - # TODO: Morph from Earth Scene into this scene - +class InverseSquareLaw(ThreeDScene): + CONFIG = { + "screen_height" : 1.0, + "source_point" : 5*LEFT, + "unit_distance" : 4, + "num_levels" : 100, + } def construct(self): - - #self.force_skipping() - self.setup_elements() - self.deform_screen() - self.create_brightness_rect() - self.slant_screen() - self.unslant_screen() - self.left_shift_screen_while_showing_light_indicator() - self.add_distance_arrow() - self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() - self.left_shift_again() - #self.revert_to_original_skipping_status() - + self.move_screen_farther_away() self.morph_into_3d() - self.prove_inverse_square_law() - - def setup_elements(self): - - SCREEN_THICKNESS = 10 - - self.screen_height = 1.0 - self.brightness_rect_height = 1.0 + def move_screen_farther_away(self): + source_point = self.source_point + unit_distance = self.unit_distance # screen - self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], - path_arc = 0, num_arc_anchors = 10) + screen = self.screen = Line(self.screen_height*UP, ORIGIN) + screen.get_reference_point = screen.get_center + screen.shift( + source_point + unit_distance*RIGHT -\ + screen.get_reference_point() + ) # light source - self.light_source = LightSource( - opacity_function = inverse_quadratic(1,5,1), - num_levels = NUM_LEVELS, + light_source = self.light_source = LightSource( + # opacity_function = inverse_quadratic(1,5,1), + opacity_function = lambda r : 1./(r+1), + num_levels = self.num_levels, radius = 10, max_opacity = 0.2 - #screen = self.screen ) - self.light_source.set_max_opacity_spotlight(0.2) + light_source.set_max_opacity_spotlight(0.2) - self.light_source.set_screen(self.screen) - self.light_source.move_source_to([-5,0,0]) + light_source.set_screen(screen) + light_source.move_source_to(source_point) # abbreviations - self.ambient_light = self.light_source.ambient_light - self.spotlight = self.light_source.spotlight - self.lighthouse = self.light_source.lighthouse - - - #self.add_foreground_mobject(self.light_source.shadow) + ambient_light = light_source.ambient_light + spotlight = light_source.spotlight + lighthouse = light_source.lighthouse + shadow = light_source.shadow # Morty - self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) + morty = self.morty = Mortimer().scale(0.3) + morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF) - # Add everything to the scene - self.add(self.lighthouse) - + #Screen tracker + def update_spotlight(spotlight): + spotlight.update_sectors() + + spotlight_update = ContinualUpdateFromFunc(spotlight, update_spotlight) + shadow_update = ContinualUpdateFromFunc( + shadow, lambda m : light_source.update_shadow() + ) + + # Light indicator + light_indicator = self.light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + ) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(screen.get_reference_point() - source_point) + light_indicator.set_intensity(1.0/(distance/unit_distance)**2) + light_indicator.next_to(morty, UP, MED_LARGE_BUFF) + light_indicator_update = ContinualUpdateFromFunc( + light_indicator, update_light_indicator + ) + light_indicator_update.update(0) + + continual_updates = self.continual_updates = [ + spotlight_update, light_indicator_update, shadow_update + ] + + # Distance indicators + + one_arrow = DoubleArrow(ORIGIN, unit_distance*RIGHT, buff = 0) + two_arrow = DoubleArrow(ORIGIN, 2*unit_distance*RIGHT, buff = 0) + arrows = VGroup(one_arrow, two_arrow) + arrows.highlight(WHITE) + one_arrow.move_to(source_point + DOWN, LEFT) + two_arrow.move_to(source_point + 1.75*DOWN, LEFT) + one = Integer(1).next_to(one_arrow, UP, SMALL_BUFF) + two = Integer(2).next_to(two_arrow, DOWN, SMALL_BUFF) + arrow_group = VGroup(one_arrow, one, two_arrow, two) + + # Animations + + self.add_foreground_mobjects(lighthouse, screen, morty) + self.add(shadow_update) + + self.play( + SwitchOn(ambient_light), + morty.change, "pondering" + ) + self.play( + SwitchOn(spotlight), + FadeIn(light_indicator) + ) + # self.remove(spotlight) + self.add(*continual_updates) self.wait() - self.play(FadeIn(self.screen)) + for distance in -0.5, 0.5: + self.shift_by_distance(distance) + self.wait() + self.add_foreground_mobjects(one_arrow, one) + self.play(GrowFromCenter(one_arrow), Write(one)) + self.wait() + self.add_foreground_mobjects(two_arrow, two) + self.shift_by_distance(1, + GrowFromPoint(two_arrow, two_arrow.get_left()), + Write(two, rate_func = squish_rate_func(smooth, 0.5, 1)) + ) self.wait() - self.add_foreground_mobject(self.screen) - self.add_foreground_mobject(self.morty) - - self.play(SwitchOn(self.ambient_light)) - + q_marks = TextMobject("???") + q_marks.next_to(light_indicator, UP) self.play( - SwitchOn(self.spotlight), - self.light_source.dim_ambient + Write(q_marks), + morty.change, "confused", q_marks ) - - screen_tracker = ScreenTracker(self.light_source) - self.add(screen_tracker) - - + self.play(Blink(morty)) + self.play(FadeOut(q_marks), morty.change, "pondering") self.wait() + self.shift_by_distance(-1, arrow_group.shift, DOWN) - - - def deform_screen(self): - - self.wait() - - self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, 0)) - - - - - def create_brightness_rect(self): - - # in preparation for the slanting, create a rectangle that shows the brightness - - # a rect a zero width overlaying the screen - # so we can morph it into the brightness rect above - brightness_rect0 = Rectangle(width = 0, - height = self.screen_height).move_to(self.screen.get_center()) - self.add_foreground_mobject(brightness_rect0) - - self.brightness_rect = Rectangle(width = self.brightness_rect_height, - height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) - - self.brightness_rect.next_to(self.screen, UP, buff = 1) - - self.play( - ReplacementTransform(brightness_rect0,self.brightness_rect) - ) - - self.unslanted_screen = self.screen.deepcopy() - self.unslanted_brightness_rect = self.brightness_rect.copy() - # for unslanting the screen later - - - def slant_screen(self): - - SLANTING_AMOUNT = 0.1 - - lower_screen_point, upper_screen_point = self.screen.get_start_and_end() - - lower_slanted_screen_point = interpolate( - lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT - ) - upper_slanted_screen_point = interpolate( - upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT - ) - - self.slanted_brightness_rect = self.brightness_rect.copy() - self.slanted_brightness_rect.width *= 2 - self.slanted_brightness_rect.generate_points() - self.slanted_brightness_rect.set_fill(opacity = 0.25) - - self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, - path_arc = 0, num_arc_anchors = 10) - self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) - - self.play( - Transform(self.screen,self.slanted_screen), - Transform(self.brightness_rect,self.slanted_brightness_rect), - ) - - - - def unslant_screen(self): - - self.wait() - self.play( - Transform(self.screen,self.unslanted_screen), - Transform(self.brightness_rect,self.unslanted_brightness_rect), - ) - - - - - def left_shift_screen_while_showing_light_indicator(self): - - # Scene 5: constant screen size, changing opening angle - - OPACITY_FOR_UNIT_INTENSITY = 1 - - # let's use an actual light indicator instead of just rects - - self.indicator_intensity = 0.25 - indicator_height = 1.25 * self.screen_height - - self.indicator = LightIndicator(radius = indicator_height/2, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR, - precision = 2) - self.indicator.set_intensity(self.indicator_intensity) - - self.indicator.move_to(self.brightness_rect.get_center()) - - self.play( - FadeOut(self.brightness_rect), - FadeIn(self.indicator) - ) - - # Here some digits of the indicator disappear... - - self.add_foreground_mobject(self.indicator.reading) - - - self.unit_indicator_intensity = 1.0 # intensity at distance 1 - # (where we are about to move to) - - self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 - - self.play( - self.screen.shift,[-self.left_shift,0,0], - self.morty.shift,[-self.left_shift,0,0], - self.indicator.shift,[-self.left_shift,0,0], - self.indicator.set_intensity,self.unit_indicator_intensity, - ) - - - - def add_distance_arrow(self): - - # distance arrow (length 1) - left_x = self.spotlight.get_source_point()[0] - right_x = self.screen.get_center()[0] - arrow_y = -2 - arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) - arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) - arrow1.set_fill(color = WHITE) - arrow2.set_fill(color = WHITE) - distance_decimal = Integer(1).next_to(arrow1,DOWN) - self.arrow = VGroup(arrow1, arrow2,distance_decimal) - self.add(self.arrow) - - - # distance arrow (length 2) - # will be morphed into - self.distance_to_source = right_x - left_x - new_right_x = left_x + 2 * self.distance_to_source - new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) - new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) - new_arrow1.set_fill(color = WHITE) - new_arrow2.set_fill(color = WHITE) - new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) - self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) - # don't add it yet - - - def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): - - self.wait() - - self.play( - ReplacementTransform(self.arrow,self.new_arrow), - ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), - ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), - - ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), - # this should trigger ChangingDecimal, but it doesn't - # maybe bc it's an anim within an anim? - - ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), - ) - - - def left_shift_again(self): - - self.wait() - - self.play( - ReplacementTransform(self.new_arrow,self.arrow), - ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), - #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), - ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), - ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), + self.set_variables_as_attrs( + ambient_light, spotlight, shadow, lighthouse, + morty, arrow_group, + *continual_updates ) def morph_into_3d(self): + # axes = ThreeDAxes() + old_screen = self.screen + spotlight = self.spotlight + source_point = self.source_point + ambient_light = self.ambient_light + unit_distance = self.unit_distance + light_indicator = self.light_indicator + morty = self.morty + dr = ambient_light.radius/ambient_light.num_levels - - self.play(FadeOut(self.morty)) - - axes = ThreeDAxes() - self.add(axes) - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - phi1 = 60 * DEGREES # angle from zenith (0 to 180) - theta1 = -135 * DEGREES # azimuth (0 to 360) - distance1 = distance0 - target_point = self.camera.get_spherical_coords(phi1, theta1, distance1) - - dphi = phi1 - phi0 - dtheta = theta1 - theta0 - - camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) - projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) - - new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen0.rotate(TAU/4,axis = DOWN) - new_screen0.move_to(self.screen.get_center()) - self.add(new_screen0) - self.remove(self.screen) - self.light_source.set_screen(new_screen0) - - self.light_source.set_camera(self.camera) - - - new_screen = Rectangle(height = self.screen_height, - width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen.rotate(TAU/4,axis = DOWN) - new_screen.move_to(self.screen.get_center()) - - self.add_foreground_mobject(self.ambient_light) - self.add_foreground_mobject(self.spotlight) - self.add_foreground_mobject(self.light_source.shadow) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - + new_screen = Square( + side_length = self.screen_height, + stroke_color = WHITE, + stroke_width = 1, + fill_color = WHITE, + fill_opacity = 0.5 ) - self.remove(self.spotlight) + new_screen.rotate(TAU/4, UP) + new_screen.move_to(old_screen, IN) + old_screen.fade(1) + screen_group = VGroup(old_screen, new_screen) - self.play(Transform(new_screen0,new_screen)) + cone = VGroup(*[VGroup() for x in range(4)]) + cone.set_stroke(width = 0) + cone.set_fill(YELLOW, opacity = 0.5) + corner_directions = [OUT+UP, OUT+DOWN, IN+DOWN, IN+UP] + def update_cone(cone): + corners = map(new_screen.get_corner, corner_directions) + distance = np.linalg.norm(old_screen.get_reference_point() - self.source_point) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for face, (c1, c2) in zip(cone, adjacent_pairs(corners)): + face.submobjects = [] + for a1, a2 in zip(alphas, alphas[1:]): + face.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = YELLOW, + fill_opacity = ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + cone_update_anim = ContinualUpdateFromFunc(cone, update_cone) + cone_update_anim.update(0) + self.remove(self.spotlight_update, self.light_indicator_update) + self.add( + ContinualAnimation(new_screen), + cone_update_anim + ) + self.remove(spotlight) + self.move_camera( + phi = 60*DEGREES, + theta = -145*DEGREES, + added_anims = [ + # ApplyMethod( + # old_screen.scale, 1.8, {"about_edge" : DOWN}, + # run_time = 2, + # ), + ApplyFunction( + lambda m : m.fade(1).shift(1.5*DOWN), + light_indicator, + remover = True + ), + FadeOut(morty) + ], + run_time = 2, + ) + self.wait() + self.screen = screen_group + self.shift_by_distance(1) + self.shift_by_distance(-1) self.wait() - self.unit_screen = new_screen0 # better name + ## Create screen copies + screen_copy = new_screen.copy() + four_copies = VGroup(*[new_screen.copy() for x in range(4)]) + nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) + def update_four_copies(four_copies): + for mob, corner_direction in zip(four_copies, corner_directions): + mob.move_to(new_screen, corner_direction) + four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) + edge_directions = [ + UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN + ] + def update_nine_copies(nine_copies): + for mob, corner_direction in zip(nine_copies, edge_directions): + mob.move_to(new_screen, corner_direction) + nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) + three_arrow = DoubleArrow( + source_point + 4*DOWN, + source_point + 4*DOWN + 3*unit_distance*RIGHT, + buff = 0, + color = WHITE + ) + three = Integer(3) + three.next_to(three_arrow, DOWN) - - def prove_inverse_square_law(self): - - def orientate(mob): - mob.move_to(self.unit_screen) - mob.rotate(TAU/4, axis = LEFT) - mob.rotate(TAU/4, axis = OUT) - mob.rotate(TAU/2, axis = LEFT) - return mob - - unit_screen_copy = self.unit_screen.copy() - fourfold_screen = self.unit_screen.copy() - fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) - - self.remove(self.spotlight) - - - reading1 = TexMobject("1") - orientate(reading1) - - self.play(FadeIn(reading1)) + new_screen.fade(1) + self.add( + ContinualAnimation(screen_copy), + ContinualAnimation(four_copies), + ) + self.play( + screen_group.scale, 2, {"about_edge" : IN + DOWN}, + screen_group.shift, unit_distance*RIGHT, + four_copies_update_anim, + screen_copy.shift, 0.25*OUT, #WHY? + run_time = 2, + ) self.wait() - self.play(FadeOut(reading1)) - - - self.play( - Transform(self.unit_screen, fourfold_screen) + self.move_camera( + phi = 75*DEGREES, + theta = -155*DEGREES, + distance = 7, ) - - reading21 = TexMobject("{1\over 4}").scale(0.8) - orientate(reading21) - reading22 = reading21.deepcopy() - reading23 = reading21.deepcopy() - reading24 = reading21.deepcopy() - reading21.shift(0.5*OUT + 0.5*UP) - reading22.shift(0.5*OUT + 0.5*DOWN) - reading23.shift(0.5*IN + 0.5*UP) - reading24.shift(0.5*IN + 0.5*DOWN) - - - corners = fourfold_screen.get_anchors() - midpoint1 = (corners[0] + corners[1])/2 - midpoint2 = (corners[1] + corners[2])/2 - midpoint3 = (corners[2] + corners[3])/2 - midpoint4 = (corners[3] + corners[0])/2 - midline1 = Line(midpoint1, midpoint3) - midline2 = Line(midpoint2, midpoint4) - + self.begin_ambient_camera_rotation(rate = -0.01) + self.add(ContinualAnimation(nine_copies)) self.play( - ShowCreation(midline1), - ShowCreation(midline2) + screen_group.scale, 3./2, {"about_edge" : IN + DOWN}, + screen_group.shift, unit_distance*RIGHT, + nine_copies_update_anim, + UpdateFromAlphaFunc( + nine_copies, + lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) + ), + GrowFromPoint(three_arrow, three_arrow.get_left()), + Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), + run_time = 2, ) - - self.play( - FadeIn(reading21), - FadeIn(reading22), - FadeIn(reading23), - FadeIn(reading24), - ) - self.wait() - self.play( - FadeOut(reading21), - FadeOut(reading22), - FadeOut(reading23), - FadeOut(reading24), - FadeOut(midline1), - FadeOut(midline2) - ) - ninefold_screen = unit_screen_copy.copy() - ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + ### - self.play( - Transform(self.unit_screen, ninefold_screen) - ) + def shift_by_distance(self, distance, *added_anims): + anims = [ + self.screen.shift, self.unit_distance*distance*RIGHT, + ] + if self.morty in self.mobjects: + anims.append(MaintainPositionRelativeTo(self.morty, self.screen)) + anims += added_anims + self.play(*anims, run_time = 2) - reading31 = TexMobject("{1\over 9}").scale(0.8) - orientate(reading31) - reading32 = reading31.deepcopy() - reading33 = reading31.deepcopy() - reading34 = reading31.deepcopy() - reading35 = reading31.deepcopy() - reading36 = reading31.deepcopy() - reading37 = reading31.deepcopy() - reading38 = reading31.deepcopy() - reading39 = reading31.deepcopy() - reading31.shift(IN + UP) - reading32.shift(IN) - reading33.shift(IN + DOWN) - reading34.shift(UP) - reading35.shift(ORIGIN) - reading36.shift(DOWN) - reading37.shift(OUT + UP) - reading38.shift(OUT) - reading39.shift(OUT + DOWN) - - corners = ninefold_screen.get_anchors() - midpoint11 = (2*corners[0] + corners[1])/3 - midpoint12 = (corners[0] + 2*corners[1])/3 - midpoint21 = (2*corners[1] + corners[2])/3 - midpoint22 = (corners[1] + 2*corners[2])/3 - midpoint31 = (2*corners[2] + corners[3])/3 - midpoint32 = (corners[2] + 2*corners[3])/3 - midpoint41 = (2*corners[3] + corners[0])/3 - midpoint42 = (corners[3] + 2*corners[0])/3 - midline11 = Line(midpoint11, midpoint32) - midline12 = Line(midpoint12, midpoint31) - midline21 = Line(midpoint21, midpoint42) - midline22 = Line(midpoint22, midpoint41) - - self.play( - ShowCreation(midline11), - ShowCreation(midline12), - ShowCreation(midline21), - ShowCreation(midline22), - ) - - self.play( - FadeIn(reading31), - FadeIn(reading32), - FadeIn(reading33), - FadeIn(reading34), - FadeIn(reading35), - FadeIn(reading36), - FadeIn(reading37), - FadeIn(reading38), - FadeIn(reading39), - ) class IndicatorScalingScene(Scene): From 3a597953304802cd9343a198285680c5796e9e84 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 15:12:04 -0800 Subject: [PATCH 15/24] Added ManipulateLightsourceSetups --- active_projects/basel2.py | 357 ++++++++++++-------------------------- 1 file changed, 108 insertions(+), 249 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 8a14b248..06246add 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -56,7 +56,6 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) - A = np.array([5.,-3.,0.]) B = np.array([-5.,3.,0.]) C = np.array([-5.,-3.,0.]) @@ -74,9 +73,6 @@ prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) H2 = np.linalg.solve(prelim_matrix,prelim_vector) H = np.append(H2, 0.) - - - class AngleUpdater(ContinualAnimation): def __init__(self, angle_arc, spotlight, **kwargs): self.angle_arc = angle_arc @@ -1713,6 +1709,10 @@ class InverseSquareLaw(ThreeDScene): self.play( screen_group.scale, 2, {"about_edge" : IN + DOWN}, screen_group.shift, unit_distance*RIGHT, + UpdateFromAlphaFunc( + four_copies, + lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) + ), four_copies_update_anim, screen_copy.shift, 0.25*OUT, #WHY? run_time = 2, @@ -1737,8 +1737,7 @@ class InverseSquareLaw(ThreeDScene): Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), run_time = 2, ) - self.wait() - + self.wait(10) ### @@ -1751,261 +1750,121 @@ class InverseSquareLaw(ThreeDScene): anims += added_anims self.play(*anims, run_time = 2) - -class IndicatorScalingScene(Scene): - +class ScreensIntroWrapper(TeacherStudentsScene): def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.play(self.teacher.change, "raise_right_hand") + self.change_student_modes( + "pondering", "erm", "confused", + look_at_arg = point, + ) + self.play(self.teacher.look_at, point) + self.wait(5) - unit_intensity = 0.6 - - indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator1.set_intensity(unit_intensity) - reading1 = TexMobject("1") - reading1.move_to(indicator1) - - - indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator2.shift(2*RIGHT) - indicator2.set_intensity(unit_intensity/4) - reading2 = TexMobject("{1\over 4}").scale(0.8) - reading2.move_to(indicator2) - - indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator3.shift(4*RIGHT) - indicator3.set_intensity(unit_intensity/9) - reading3 = TexMobject("{1\over 9}").scale(0.8) - reading3.move_to(indicator3) - - - self.play(FadeIn(indicator1)) - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - self.play(Transform(indicator1, indicator2)) - self.play(FadeIn(reading2)) - self.wait() - self.play(FadeOut(reading2)) - self.play(Transform(indicator1, indicator3)) - self.play(FadeIn(reading3)) - self.wait() - -class BackToEulerSumScene(PiCreatureScene): - - +class ManipulateLightsourceSetups(PiCreatureScene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + } def construct(self): - self.remove(self.get_primary_pi_creature()) + unit_distance = 3 - NUM_CONES = 7 - NUM_VISIBLE_CONES = 6 - INDICATOR_RADIUS = 0.5 - OPACITY_FOR_UNIT_INTENSITY = 1.0 + # Morty + morty = self.pi_creature + morty.flip() + morty.scale(0.5) + morty.move_to(2*LEFT + SPACE_HEIGHT*DOWN/2) + observer_point = morty.eyes[1].get_center() - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1.6, - stroke_width = 1, - numbers_with_elongated_ticks = range(1,5), - numbers_to_show = range(1,5), - unit_size = 2, - tick_frequency = 0.2, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, + bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) + bubble.set_fill(BLACK, 1) + bubble.pin_to(morty) + + # Indicator + light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + fill_color = YELLOW, + radius = 0.4, + reading_height = 0.2, ) + light_indicator.move_to(bubble.get_bubble_center()) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(light_source.get_source_point()-observer_point) + light_indicator.set_intensity((unit_distance/distance)**2) - self.number_line.label_direction = DOWN - #self.number_line.shift(3*UP) + #Light source + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_to(observer_point + unit_distance*RIGHT) - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) + #Light source copies + light_source_copies = VGroup(*[light_source.copy() for x in range(2)]) + for lsc, vect in zip(light_source_copies, [RIGHT, UP]): + lsc.move_to(observer_point + np.sqrt(2)*unit_distance*vect) + + self.add(light_source) + self.add_foreground_mobjects(morty, bubble, light_indicator) + self.add(ContinualUpdateFromFunc(light_indicator, update_light_indicator)) + self.play( + ApplyMethod( + light_source.shift, 0.66*unit_distance*LEFT, + rate_func = wiggle, + run_time = 5, + ), + morty.change, "erm", + ) + self.play( + UpdateFromAlphaFunc( + light_source, + lambda ls, a : ls.move_to( + observer_point + rotate_vector( + unit_distance*RIGHT, (1+1./8)*a*TAU + ) + ), + run_time = 6, + rate_func = bezier([0, 0, 1, 1]) + ), + morty.change, "pondering", + UpdateFromFunc(morty, lambda m : m.look_at(light_source)) + ) self.wait() - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) - - randy_copy = randy.copy() - randy_copy.target = randy.copy().shift(DOWN) - - - - bubble = ThoughtBubble(direction = RIGHT, - width = 4, height = 3, - file_name = "Bubbles_thought.svg") - bubble.next_to(randy,LEFT+UP) - bubble.set_fill(color = BLACK, opacity = 1) - + plus = TexMobject("+") + point = light_indicator.get_center() + plus.move_to(point) + light_indicator_copy = light_indicator.copy() + self.add_foreground_mobjects(plus, light_indicator_copy) self.play( - randy.change, "wave_2", - ShowCreation(bubble), + ReplacementTransform( + light_source, light_source_copies[0] + ), + ReplacementTransform( + light_source.copy().fade(1), + light_source_copies[1] + ), + FadeIn(plus), + UpdateFromFunc( + light_indicator_copy, + lambda li : update_light_indicator(li), + ), + UpdateFromAlphaFunc( + light_indicator, lambda m, a : m.move_to( + point + a*0.75*RIGHT, + ) + ), + UpdateFromAlphaFunc( + light_indicator_copy, lambda m, a : m.move_to( + point + a*0.75*LEFT, + ) + ), + run_time = 2 ) - - - euler_sum = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ") - # the last entry is a dummy element which makes looping easier - # used just for putting the fractions into the light indicators - - intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)]) - opacities = intensities * OPACITY_FOR_UNIT_INTENSITY - - # repeat: - - # fade in lighthouse - # switch on / fade in ambient light - # show creation / write light indicator - # move indicator onto origin - # while morphing and dimming - # move indicator into thought bubble - # while indicators already inside shift to the back - # and while term appears in the series below - - point = self.number_line.number_to_point(1) - v = point - self.number_line.number_to_point(0) - light_source = LightSource() - light_source.move_source_to(point) - #light_source.ambient_light.move_source_to(point) - #light_source.lighthouse.move_to(point) - - self.play(FadeIn(light_source.lighthouse)) - self.play(SwitchOn(light_source.ambient_light)) - - - # create an indicator that will move along the number line - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - indicator_reading = euler_sum[0] - indicator_reading.scale_to_fit_height(0.5 * indicator.get_height()) - indicator_reading.move_to(indicator.get_center()) - indicator.add(indicator_reading) - indicator.tex_reading = indicator_reading - # the TeX reading is too bright at full intensity - indicator.tex_reading.set_fill(color = BLACK) - indicator.foreground.set_fill(None,opacities[0]) - - - indicator.move_to(point) - indicator.set_intensity(intensities[0]) - - self.play(FadeIn(indicator)) - self.add_foreground_mobject(indicator) - - collection_point = np.array([-6.,2.,0.]) - left_shift = 0.2*LEFT - collected_indicators = Mobject() - - - for i in range(2, NUM_VISIBLE_CONES + 1): - - previous_point = self.number_line.number_to_point(i - 1) - point = self.number_line.number_to_point(i) - - - v = point - previous_point - #print v - # Create and position the target indicator (next on number line). - indicator_target = indicator.deepcopy() - indicator_target.shift(v) - - - # Here we make a copy that will move into the thought bubble. - bubble_indicator = indicator.deepcopy() - # And its target - bubble_indicator_target = bubble_indicator.deepcopy() - bubble_indicator_target.set_intensity(intensities[i - 2]) - - # give the target the appropriate reading - euler_sum[2*i-4].move_to(bubble_indicator_target) - bubble_indicator_target.remove(bubble_indicator_target.tex_reading) - bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() - bubble_indicator_target.add(bubble_indicator_target.tex_reading) - # center it in the indicator - - if bubble_indicator_target.tex_reading.get_tex_string() != "1": - bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height()) - # the target is less bright, possibly switch to a white text color - if bubble_indicator_target.intensity < 0.7: - bubble_indicator.tex_reading.set_fill(color = WHITE) - - # position the target in the thought bubble - bubble_indicator_target.move_to(collection_point) - - - self.add_foreground_mobject(bubble_indicator) - - - self.wait() - - self.play( - Transform(bubble_indicator,bubble_indicator_target), - collected_indicators.shift,left_shift, - ) - - collected_indicators.add(bubble_indicator) - - new_light = light_source.deepcopy() - w = new_light.get_source_point() - new_light.move_source_to(w + (i-2)*v) - w2 = new_light.get_source_point() - - self.add(new_light.lighthouse) - self.play( - Transform(indicator,indicator_target), - new_light.lighthouse.shift,v, - ) - new_light.move_source_to(w + (i-1)*v) - new_light.lighthouse.move_to(w + (i-1)*v) - - self.play(SwitchOn(new_light.ambient_light), - ) - - - - - # quickly switch on off-screen light cones - for i in range(NUM_VISIBLE_CONES,NUM_CONES): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - ls = LightSource() - point = point = self.number_line.number_to_point(i) - ls.move_source_to(point) - self.play( - SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ) - - # and morph indicator stack into limit value - - sum_indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - sum_indicator.set_intensity(intensities[0] * np.pi**2/6) - sum_indicator_reading = TexMobject("{\pi^2 \over 6}") - sum_indicator_reading.set_fill(color = BLACK) - sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) - sum_indicator.add(sum_indicator_reading) - sum_indicator.move_to(collection_point) - - self.play( - FadeOut(collected_indicators), - FadeIn(sum_indicator) - ) - - - - self.wait() + self.play(morty.change, "hooray") + self.wait(2) class TwoLightSourcesScene(PiCreatureScene): From 59d81feb269d64139b61865e23167ff1138d3b58 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 17:27:39 -0800 Subject: [PATCH 16/24] Finished TwoLightSourcesScene --- active_projects/basel2.py | 415 ++++++++++++++++++++++++-------------- 1 file changed, 259 insertions(+), 156 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 06246add..3dbcf12b 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -56,22 +56,22 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) -A = np.array([5.,-3.,0.]) -B = np.array([-5.,3.,0.]) -C = np.array([-5.,-3.,0.]) -xA = A[0] -yA = A[1] -xB = B[0] -yB = B[1] -xC = C[0] -yC = C[1] +# A = np.array([5.,-3.,0.]) +# B = np.array([-5.,3.,0.]) +# C = np.array([-5.,-3.,0.]) +# xA = A[0] +# yA = A[1] +# xB = B[0] +# yB = B[1] +# xC = C[0] +# yC = C[1] # find the coords of the altitude point H # as the solution of a certain LSE -prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic -prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) -H2 = np.linalg.solve(prelim_matrix,prelim_vector) -H = np.append(H2, 0.) +# prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic +# prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) +# H2 = np.linalg.solve(prelim_matrix,prelim_vector) +# H = np.append(H2, 0.) class AngleUpdater(ContinualAnimation): def __init__(self, angle_arc, spotlight, **kwargs): @@ -136,7 +136,7 @@ class LightIndicator(Mobject): return self def get_measurement_point(self): - if self.measurement_point != None: + if self.measurement_point is not None: return self.measurement_point else: return self.get_center() @@ -1765,15 +1765,13 @@ class ManipulateLightsourceSetups(PiCreatureScene): CONFIG = { "num_levels" : 100, "radius" : 10, + "pi_creature_point" : 2*LEFT + 2*DOWN, } def construct(self): unit_distance = 3 # Morty morty = self.pi_creature - morty.flip() - morty.scale(0.5) - morty.move_to(2*LEFT + SPACE_HEIGHT*DOWN/2) observer_point = morty.eyes[1].get_center() bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) @@ -1866,182 +1864,287 @@ class ManipulateLightsourceSetups(PiCreatureScene): self.play(morty.change, "hooray") self.wait(2) -class TwoLightSourcesScene(PiCreatureScene): + ## + def create_pi_creature(self): + morty = Mortimer() + morty.flip() + morty.scale(0.5) + morty.move_to(self.pi_creature_point) + return morty + +class TwoLightSourcesScene(ManipulateLightsourceSetups): + CONFIG = { + "num_levels" : 200, + "radius" : 15, + "a" : 9, + "b" : 5, + } def construct(self): - MAX_OPACITY = 0.4 INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 + origin_point = 5*LEFT + 2.5*DOWN - morty = self.get_primary_pi_creature() - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - horizontal = VMobject(stroke_width = 1) - horizontal.set_points_as_corners([C,A]) - vertical = VMobject(stroke_width = 1) - vertical.set_points_as_corners([C,B]) - - self.play( - ShowCreation(horizontal), - ShowCreation(vertical) + #Morty + morty = self.pi_creature + morty.change("hooray") # From last scen + morty.generate_target() + morty.target.change("plain") + morty.target.scale(0.6) + morty.target.next_to( + origin_point, LEFT, buff = 0, + submobject_to_align = morty.target.eyes[1] ) - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = True, - precision = 2 + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, ) + axes.shift(origin_point) - indicator.next_to(morty,LEFT) - - self.play( - Write(indicator) + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) - - ls1 = LightSource(radius = 20, num_levels = 50) - ls2 = ls1.deepcopy() - ls1.move_source_to(A) - ls2.move_source_to(B) - - self.play( - FadeIn(ls1.lighthouse), - FadeIn(ls2.lighthouse), - SwitchOn(ls1.ambient_light), - SwitchOn(ls2.ambient_light) + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), ) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) - distance1 = np.linalg.norm(C - ls1.get_source_point()) - intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity - distance2 = np.linalg.norm(C - ls2.get_source_point()) - intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) - self.play( - UpdateLightIndicator(indicator,intensity) - ) + #Identical lighthouse labels + identical_lighthouses_words = TextMobject("All identical \\\\ lighthouses") + identical_lighthouses_words.to_corner(UP+RIGHT) + identical_lighthouses_words.shift(LEFT) + identical_lighthouses_arrows = VGroup(*[ + Arrow( + identical_lighthouses_words.get_bottom(), + ls.get_source_point(), + buff = SMALL_BUFF, + color = WHITE, + ) + for ls in lsA, lsB, lsC + ]) - self.wait() - - ls3 = ls1.deepcopy() - ls3.move_to(np.array([6,3.5,0])) - - new_indicator = indicator.copy() - new_indicator.light_source = ls3 - new_indicator.measurement_point = C - self.add(new_indicator) - self.play( - indicator.shift, 2 * UP - ) - - - - #intensity = intensity_for_light_source(ls3) - - - self.play( - SwitchOff(ls1.ambient_light), - #FadeOut(ls1.lighthouse), - SwitchOff(ls2.ambient_light), - #FadeOut(ls2.lighthouse), - UpdateLightIndicator(new_indicator,0.0) - ) - - # create a *continual* animation for the replacement source - updater = ContinualLightIndicatorUpdate(new_indicator) - self.add(updater) - - self.play( - SwitchOn(ls3.ambient_light), - FadeIn(ls3.lighthouse), - - ) - - self.wait() - - # move the light source around - # TODO: moving along a path arc - - location = np.array([-3,-2.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([6.,1.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([5.,2.,0.]) - self.play(ls3.move_source_to,location) - closer_location = interpolate(location, C, 0.5) - self.play(ls3.move_source_to,closer_location) - self.play(ls3.move_source_to,location) - - # maybe move in a circle around C using a loop? - - - - self.play(ls3.move_source_to,H) - - - - # draw lines to complete the geometric picture - # and label the lengths - - line_a = VMobject() - line_a.set_points_as_corners([B,C]) - line_b = VMobject() - line_b.set_points_as_corners([A,C]) - line_c = VMobject() - line_c.set_points_as_corners([A,B]) - line_h = VMobject() - line_h.set_points_as_corners([H,C]) + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + # perp_mark.highlight(BLACK) + + #Indicators + indicator = LightIndicator( + color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = True, + precision = 2, + ) + indicator.next_to(origin_point, UP+LEFT) + def update_indicator(indicator): + intensity = 0 + for ls in lsA, lsB, lsC: + if ls in self.mobjects: + distance = np.linalg.norm(ls.get_source_point() - origin_point) + d_indensity = fdiv( + 3./(distance**2), + indicator.opacity_for_unit_intensity + ) + d_indensity *= ls.ambient_light.submobjects[1].get_fill_opacity() + intensity += d_indensity + indicator.set_intensity(intensity) + indicator_update_anim = ContinualUpdateFromFunc(indicator, update_indicator) + + new_indicator = indicator.copy() + new_indicator.light_source = lsC + new_indicator.measurement_point = C + + #Note sure what this is... + distance1 = np.linalg.norm(origin_point - lsA.get_source_point()) + intensity = lsA.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(origin_point - lsB.get_source_point()) + intensity += lsB.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + + #Transition from last_scene self.play( - ShowCreation(line_a), - Write(label_a) + ShowCreation(axes, run_time = 2), + MoveToTarget(morty), + FadeIn(indicator), ) + #Move lsC around + self.add(lsC) + indicator_update_anim.update(0) + intensity = indicator.reading.number self.play( - ShowCreation(line_b), - Write(label_b) - ) - - self.play( - ShowCreation(line_c), + SwitchOn(lsC.ambient_light), + FadeIn(lsC.lighthouse), + UpdateFromAlphaFunc( + indicator, lambda i, a : i.set_intensity(a*intensity) + ) ) + self.add(indicator_update_anim) + for point in axes.coords_to_point(5, 2), H: + self.play( + lsC.move_source_to, point, + path_arc = TAU/4, + run_time = 1.5, + ) + self.wait() + # Draw line self.play( ShowCreation(line_h), - Write(label_h) + morty.change, "pondering" ) - - - # state the IPT - theorem_location = np.array([3.,2.,0.]) - theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") - theorem_name = TextMobject("Inverse Pythagorean Theorem") - buffer = 1.2 - theorem_box = Rectangle(width = buffer*theorem.get_width(), - height = buffer*theorem.get_height()) - - theorem.move_to(theorem_location) - theorem_box.move_to(theorem_location) - theorem_name.next_to(theorem_box,UP) - + self.wait() self.play( - Write(theorem), + ShowCreation(line_c), + ShowCreation(perp_mark) ) + self.wait() + self.add_foreground_mobjects(line_c, line_h) + #Add alternate light_sources + for ls in lsA, lsB: + ls.save_state() + ls.move_to(lsC) + ls.fade(1) + self.add(ls) + self.play( + ls.restore, + run_time = 2 + ) + self.wait() + A_label.save_state() + A_label.center().fade(1) + self.play(A_label.restore) + self.wait() + self.play(ReplacementTransform( + A_label.copy().fade(1), B_label + )) + self.wait(2) + + #Compare combined of laA + lsB with lsC + rect = SurroundingRectangle(indicator, color = RED) + self.play( + FadeOut(lsA), + FadeOut(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.play(FadeOut(lsC)) + self.add(lsA, lsB) + self.play( + FadeIn(lsA), + FadeIn(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.wait(2) + + # All standard lighthouses + self.add(lsC) + self.play(FadeIn(lsC)) + self.play( + Write(identical_lighthouses_words), + LaggedStart(GrowArrow, identical_lighthouses_arrows) + ) + self.wait() + self.play(*map(FadeOut, [ + identical_lighthouses_words, + identical_lighthouses_arrows, + ])) + + #Show labels of lengths + self.play(ShowCreation(line_a), Write(label_a)) + self.wait() + self.play(ShowCreation(line_b), Write(label_b)) + self.wait() + self.play(Write(label_h)) + self.wait() + + #Write IPT + self.play(Write(theorem)) + self.wait() self.play( - ShowCreation(theorem_box), Write(theorem_name), + ShowCreation(theorem_box) ) + self.play(morty.change, "confused") + self.wait(2) + + + class IPTScene1(PiCreatureScene): From d7b84b05079c39dd21b6de4e27705ee619855d4c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 22:39:50 -0800 Subject: [PATCH 17/24] Simpler move_lighthouse_to --- topics/light.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/topics/light.py b/topics/light.py index 5fc0b268..4d19ba27 100644 --- a/topics/light.py +++ b/topics/light.py @@ -187,11 +187,12 @@ class LightSource(VMobject): self.update_shadow() def update_lighthouse(self): - new_lh = Lighthouse() - new_lh.move_to(ORIGIN) - new_lh.apply_matrix(self.rotation_matrix()) - new_lh.shift(self.get_source_point()) - self.lighthouse.submobjects = new_lh.submobjects + self.lighthouse.move_to(self.get_source_point()) + # new_lh = Lighthouse() + # new_lh.move_to(ORIGIN) + # new_lh.apply_matrix(self.rotation_matrix()) + # new_lh.shift(self.get_source_point()) + # self.lighthouse.submobjects = new_lh.submobjects def update_ambient(self): new_ambient_light = AmbientLight( @@ -416,7 +417,6 @@ class AmbientLight(VMobject): class Spotlight(VMobject): - CONFIG = { "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, @@ -445,13 +445,10 @@ class Spotlight(VMobject): w = project_along_vector(point,v) return w - def get_source_point(self): return self.source_point.get_location() - def generate_points(self): - self.submobjects = [] self.add(self.source_point) @@ -467,7 +464,6 @@ class Spotlight(VMobject): new_sector = self.new_sector(r,dr,lower_angle,upper_angle) self.add(new_sector) - def new_sector(self,r,dr,lower_angle,upper_angle): alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( @@ -504,7 +500,6 @@ class Spotlight(VMobject): else: return -absolute_angle - def viewing_angles(self,screen): screen_points = screen.get_anchors() @@ -531,7 +526,6 @@ class Spotlight(VMobject): return lower_ray, upper_ray - def opening_angle(self): l,u = self.viewing_angles(self.screen) return u - l @@ -551,7 +545,6 @@ class Spotlight(VMobject): self.update_sectors() return self - def update_sectors(self): if self.screen == None: return @@ -567,7 +560,6 @@ class Spotlight(VMobject): # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) Transform(submob, new_submob).update(1) - def dimming(self,new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha From 004d3603f25d836e8001b4c491aa7e7bce61be29 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 22:40:37 -0800 Subject: [PATCH 18/24] IPTScene in basel2 --- active_projects/basel2.py | 392 +++++++++++++++++++++++++++++++++++++- 1 file changed, 388 insertions(+), 4 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 3dbcf12b..1bfde062 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -20,6 +20,7 @@ from topics.number_line import * from topics.numerals import * #from topics.combinatorics import * from scene import Scene +from scene.zoomed_scene import * from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * @@ -1879,13 +1880,13 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): "radius" : 15, "a" : 9, "b" : 5, + "origin_point" : 5*LEFT + 2.5*DOWN } def construct(self): MAX_OPACITY = 0.4 INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 - origin_point = 5*LEFT + 2.5*DOWN - + origin_point = self.origin_point #Morty morty = self.pi_creature @@ -1951,7 +1952,7 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): identical_lighthouses_words.shift(LEFT) identical_lighthouses_arrows = VGroup(*[ Arrow( - identical_lighthouses_words.get_bottom(), + identical_lighthouses_words.get_corner(DOWN+LEFT), ls.get_source_point(), buff = SMALL_BUFF, color = WHITE, @@ -2053,6 +2054,7 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): ) ) self.add(indicator_update_anim) + self.play(Animation(lsC), run_time = 0) #Why is this needed? for point in axes.coords_to_point(5, 2), H: self.play( lsC.move_source_to, point, @@ -2143,11 +2145,393 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): self.play(morty.change, "confused") self.wait(2) +class MathologerVideoWrapper(Scene): + def construct(self): + title = TextMobject(""" + Mathologer's excellent video on \\\\ + the many Pythagorean theorem cousins + """) + # title.scale(0.7) + title.to_edge(UP) + logo = ImageMobject("mathologer_logo") + logo.scale_to_fit_height(1) + logo.to_corner(UP+LEFT) + logo.shift(2*SPACE_WIDTH*RIGHT) + screen = ScreenRectangle(height = 5.5) + screen.next_to(title, DOWN) + + self.play( + logo.shift, 2*SPACE_WIDTH*LEFT, + LaggedStart(FadeIn, title), + run_time = 2 + ) + self.play(ShowCreation(screen)) + self.wait(5) + +class SimpleIPTProof(Scene): + def construct(self): + A = 5*RIGHT + B = 3*UP + C = ORIGIN + #Dumb and inefficient + alphas = np.linspace(0, 1, 500) + i = np.argmin(map( + lambda a : np.linalg.norm(interpolate(A, B, a)), + alphas + )) + H = interpolate(A, B, alphas[i]) + triangle = VGroup( + Line(C, A, color = BLUE), + Line(C, B, color = RED), + Line(A, B, color = WHITE), + Line(C, H, color = GREEN) + ) + for line, char in zip(triangle, ["a", "b", "c", "h"]): + label = TexMobject(char) + label.match_color(line) + vect = line.get_center() - triangle.get_center() + vect /= np.linalg.norm(vect) + label.next_to(line.get_center(), vect) + triangle.add(label) + if char == "h": + label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) + + triangle.to_corner(UP+LEFT) + self.add(triangle) + + argument_lines = VGroup( + TexMobject( + "\\text{Area} = ", + "{1 \\over 2}", "a", "b", "=", + "{1 \\over 2}", "c", "h" + ), + TexMobject("\\Downarrow"), + TexMobject("a^2", "b^2", "=", "c^2", "h^2"), + TexMobject("\\Downarrow"), + TexMobject( + "a^2", "b^2", "=", + "(", "a^2", "+", "b^2", ")", "h^2" + ), + TexMobject("\\Downarrow"), + TexMobject( + "{1 \\over ", "h^2}", "=", + "{1 \\over ", "b^2}", "+", + "{1 \\over ", "a^2}", + ), + ) + argument_lines.arrange_submobjects(DOWN) + for line in argument_lines: + line.highlight_by_tex_to_color_map({ + "a" : BLUE, + "b" : RED, + "h" : GREEN, + "Area" : WHITE, + "Downarrow" : WHITE, + }) + all_equals = line.get_parts_by_tex("=") + if all_equals: + line.alignment_mob = all_equals[-1] + else: + line.alignment_mob = line[0] + line.shift(-line.alignment_mob.get_center()[0]*RIGHT) + argument_lines.next_to(triangle, RIGHT) + argument_lines.to_edge(UP) + + prev_line = argument_lines[0] + self.play(FadeIn(prev_line)) + for arrow, line in zip(argument_lines[1::2], argument_lines[2::2]): + line.save_state() + line.shift( + prev_line.alignment_mob.get_center() - \ + line.alignment_mob.get_center() + ) + line.fade(1) + self.play( + line.restore, + GrowFromPoint(arrow, arrow.get_top()) + ) + self.wait() + prev_line = line + +class WeCanHaveMoreFunThanThat(TeacherStudentsScene): + def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.teacher_says( + "We can have \\\\ more fun than that!", + target_mode = "hooray" + ) + self.change_student_modes(*3*["erm"], look_at_arg = point) + self.wait() + self.play( + RemovePiCreatureBubble( + self.teacher, + target_mode = "raise_right_hand", + look_at_arg = point, + ), + self.get_student_changes(*3*["pondering"], look_at_arg = point) + ) + self.wait(3) + +class IPTScene(TwoLightSourcesScene, ZoomedScene): + CONFIG = { + "max_opacity_ambient" : 0.2, + "num_levels" : 200, + } + def construct(self): + #Copy pasting from TwoLightSourcesScene....Very bad... + origin_point = self.origin_point + self.remove(self.pi_creature) + + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, + ) + axes.shift(origin_point) + + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] + ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) + + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), + max_opacity_ambient = self.max_opacity_ambient, + ) + lsA.lighthouse.scale(0.5, about_edge = UP) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) + + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) + + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) + + label_a = TexMobject("a") + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) + label_b = TexMobject("b") + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) + label_h = TexMobject("h") + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + + # Mini triangle + m_hyp_a = Line(H, A) + m_a = line_a.copy() + m_hyp_b = Line(H, B) + m_b = line_b.copy() + mini_triangle = VGroup(m_a, m_hyp_a, m_b, m_hyp_b) + mini_triangle.set_stroke(width = 5) + + mini_triangle.generate_target() + mini_triangle.target.scale(0.1, about_point = origin_point) + for part, part_target in zip(mini_triangle, mini_triangle.target): + part.target = part_target + + # Screen label + screen_word = TextMobject("Screen") + screen_word.next_to(mini_triangle.target, UP+RIGHT, LARGE_BUFF) + screen_arrow = Arrow( + screen_word.get_bottom(), + mini_triangle.target.get_center(), + color = WHITE, + ) + + # Setup spotlights + spotlight_a = VGroup() + spotlight_a.screen = m_hyp_a + spotlight_b = VGroup() + spotlight_b.screen = m_hyp_b + for spotlight in spotlight_a, spotlight_b: + spotlight.get_source_point = lsC.get_source_point + dr = lsC.ambient_light.radius/lsC.ambient_light.num_levels + def update_spotlight(spotlight): + spotlight.submobjects = [] + source_point = spotlight.get_source_point() + c1, c2 = spotlight.screen.get_start(), spotlight.screen.get_end() + distance = max( + np.linalg.norm(c1 - source_point), + np.linalg.norm(c2 - source_point), + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for a1, a2 in zip(alphas, alphas[1:]): + spotlight.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = YELLOW, + fill_opacity = 2*lsC.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + + def update_spotlights(spotlights): + for spotlight in spotlights: + update_spotlight(spotlight) + + def get_spotlight_triangle(spotlight): + sp = spotlight.get_source_point() + c1 = spotlight.screen.get_start() + c2 = spotlight.screen.get_end() + return Polygon( + sp, c1, c2, + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 0.5, + ) + + spotlights = VGroup(spotlight_a, spotlight_b) + spotlights_update_anim = ContinualUpdateFromFunc( + spotlights, update_spotlights + ) + + # Add components + self.add( + axes, + lsA.ambient_light, + lsB.ambient_light, + lsC.ambient_light, + line_c, + ) + self.add_foreground_mobjects( + lsA.lighthouse, A_label, + lsB.lighthouse, B_label, + lsC.lighthouse, line_h, + ) + + # Show miniature triangle + self.play(ShowCreation(mini_triangle, submobject_mode = "all_at_once")) + self.play( + MoveToTarget(mini_triangle), + run_time = 2, + ) + self.add_foreground_mobject(mini_triangle) + + # Show beams of light + self.play( + Write(screen_word), + GrowArrow(screen_arrow), + ) + self.wait() + spotlights_update_anim.update(0) + self.play( + LaggedStart(FadeIn, spotlight_a), + LaggedStart(FadeIn, spotlight_b), + Animation(screen_arrow), + ) + self.add(spotlights_update_anim) + self.play(*map(FadeOut, [screen_word, screen_arrow])) + self.wait() + + # Reshape screen + m_hyps = [m_hyp_a, m_hyp_b] + for hyp, line in (m_hyp_a, m_a), (m_hyp_b, m_b): + hyp.save_state() + hyp.alt_version = line.copy() + hyp.alt_version.highlight(WHITE) + + for x in range(2): + self.play(*[ + Transform(m, m.alt_version) + for m in m_hyps + ]) + self.wait() + self.play(*[m.restore for m in m_hyps]) + self.wait() + + # Show spotlight a key point + def show_key_point(spotlight, new_point): + screen = spotlight.screen + update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) + self.play( + Transform(screen, screen.alt_version), + update_spotlight_anim, + ) + triangle = get_spotlight_triangle(spotlight) + for x in range(3): + anims = [] + if x > 0: + anims.append(FadeOut(triangle.copy())) + anims.append(GrowFromPoint(triangle, H)) + self.play(*anims) + self.play(FadeOut(triangle)) + self.play(screen.restore, update_spotlight_anim) + self.wait() + self.play( + lsC.move_source_to, new_point, + Transform(screen, screen.alt_version), + update_spotlight_anim, + run_time = 2 + ) + self.wait() + self.play( + lsC.move_source_to, H, + screen.restore, + update_spotlight_anim, + run_time = 2 + ) + self.wait() + + self.remove(spotlights_update_anim) + self.add(spotlight_b) + self.play(*map(FadeOut, [ + spotlight_a, lsA.ambient_light, lsB.ambient_light + ])) + show_key_point(spotlight_b, A) + self.play( + FadeOut(spotlight_b), + FadeIn(spotlight_a), + ) + show_key_point(spotlight_a, B) + self.wait() -class IPTScene1(PiCreatureScene): +class IPTScene1(Scene): def construct(self): show_detail = True From 7e4bd54da784f2361202f1c82c15fb5993de9f8a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 09:34:38 -0800 Subject: [PATCH 19/24] Tweaks to IPTScene --- active_projects/basel2.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 1bfde062..bb920165 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -2483,6 +2483,17 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): self.wait() # Show spotlight a key point + def show_beaming_light(spotlight): + triangle = get_spotlight_triangle(spotlight) + for x in range(3): + anims = [] + if x > 0: + anims.append(FadeOut(triangle.copy())) + anims.append(GrowFromPoint(triangle, triangle.points[0])) + self.play(*anims) + self.play(FadeOut(triangle)) + pass + def show_key_point(spotlight, new_point): screen = spotlight.screen update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) @@ -2490,14 +2501,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): Transform(screen, screen.alt_version), update_spotlight_anim, ) - triangle = get_spotlight_triangle(spotlight) - for x in range(3): - anims = [] - if x > 0: - anims.append(FadeOut(triangle.copy())) - anims.append(GrowFromPoint(triangle, H)) - self.play(*anims) - self.play(FadeOut(triangle)) + show_beaming_light(spotlight) self.play(screen.restore, update_spotlight_anim) self.wait() self.play( @@ -2506,6 +2510,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): update_spotlight_anim, run_time = 2 ) + show_beaming_light(spotlight) self.wait() self.play( lsC.move_source_to, H, From 74f3beca7c5f033fd29b7a71d9747060458cc082 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 21:39:25 -0800 Subject: [PATCH 20/24] Makes Patron names show up lower to start --- topics/common_scenes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 3e7369c1..4d1354f6 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -176,7 +176,7 @@ class PatreonEndScreen(PatreonThanks): aligned_edge = UP, ) columns.scale_to_fit_width(total_width - 1) - columns.next_to(black_rect, DOWN, LARGE_BUFF) + columns.next_to(black_rect, DOWN, 3*LARGE_BUFF) columns.to_edge(RIGHT) self.play( From ebc418d2bdb3c7af7f5522d87f527cd8c786919d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 21:39:38 -0800 Subject: [PATCH 21/24] Many additions while editing video together --- active_projects/basel2.py | 1762 +++++++++++++++++++++++++++++-------- 1 file changed, 1374 insertions(+), 388 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index bb920165..fb7239d9 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- from helpers import * @@ -25,8 +26,9 @@ from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.three_dimensions import * - from topics.light import * +from topics.objects import * +from topics.common_scenes import * import types import functools @@ -233,8 +235,8 @@ class ThinkAboutPondScene(PiCreatureScene): randy = self.pi_creature randy.to_corner(DOWN+LEFT) bubble = ThoughtBubble( - width = 9, - height = 5, + width = 11, + height = 7, ) circles = bubble[:3] angle = -15*DEGREES @@ -243,7 +245,6 @@ class ThinkAboutPondScene(PiCreatureScene): for circle in circles: circle.rotate(-angle) bubble.pin_to(randy) - bubble.shift_onto_screen() self.play( randy.change, "thinking", @@ -1133,12 +1134,29 @@ class ThatJustSeemsUseless(TeacherStudentsScene): self.wait() class AskAboutBrightness(TeacherStudentsScene): + CONFIG = { + "num_levels" : 200, + "radius" : 10, + } def construct(self): + light_source = LightSource( + num_levels = self.num_levels, + radius = self.radius, + opacity_function = inverse_quadratic(1,2,1), + ) + light_source.lighthouse.scale(0.5, about_edge = UP) + light_source.move_source_to(5*LEFT + 2*UP) + + self.add_foreground_mobjects(self.pi_creatures) self.student_says( - "What do you mean \\\\ by ``brightness''?" + "What do you mean \\\\ by ``brightness''?", + added_anims = [ + SwitchOn(light_source.ambient_light), + Animation(light_source.lighthouse) + ] ) self.play(self.teacher.change, "happy") - self.wait(3) + self.wait(4) class IntroduceScreen(Scene): CONFIG = { @@ -1723,6 +1741,7 @@ class InverseSquareLaw(ThreeDScene): phi = 75*DEGREES, theta = -155*DEGREES, distance = 7, + run_time = 10, ) self.begin_ambient_camera_rotation(rate = -0.01) self.add(ContinualAnimation(nine_copies)) @@ -1751,6 +1770,40 @@ class InverseSquareLaw(ThreeDScene): anims += added_anims self.play(*anims, run_time = 2) +class OtherInstanceOfInverseSquareLaw(Scene): + def construct(self): + title = TextMobject("Where the inverse square law shows up") + title.to_edge(UP) + h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH) + h_line.next_to(title, DOWN) + self.add(title, h_line) + + items = VGroup(*[ + TextMobject("- %s"%s).scale(1) + for s in [ + "Heat", "Sound", "Radio waves", "Electric fields", + ] + ]) + items.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) + items.next_to(h_line, DOWN, LARGE_BUFF) + items.to_edge(LEFT) + + dot = Dot() + dot.move_to(4*RIGHT) + self.add(dot) + def get_broadcast(): + return Broadcast(dot, big_radius = 5, run_time = 5) + + self.play( + LaggedStart(FadeIn, items, run_time = 4, lag_ratio = 0.7), + Succession(*[ + get_broadcast() + for x in range(2) + ]) + ) + self.play(get_broadcast()) + self.wait() + class ScreensIntroWrapper(TeacherStudentsScene): def construct(self): point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) @@ -2379,6 +2432,21 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): color = WHITE, ) + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + # Setup spotlights spotlight_a = VGroup() spotlight_a.screen = m_hyp_a @@ -2440,6 +2508,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): lsA.lighthouse, A_label, lsB.lighthouse, B_label, lsC.lighthouse, line_h, + theorem, theorem_name, theorem_box, ) # Show miniature triangle @@ -2533,259 +2602,177 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): show_key_point(spotlight_a, B) self.wait() - - - -class IPTScene1(Scene): +class HomeworkWrapper(Scene): def construct(self): + title = TextMobject("Homework") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.center() + self.add(title) + self.play(ShowCreation(screen)) + self.wait(5) - show_detail = True - - SCREEN_SCALE = 0.1 - SCREEN_THICKNESS = 0.2 - - - # use the following for the zoomed inset - if show_detail: - self.camera.space_shape = (0.02 * SPACE_HEIGHT, 0.02 * SPACE_WIDTH) - self.camera.space_center = C - SCREEN_SCALE = 0.01 - SCREEN_THICKNESS = 0.02 - - - - - - morty = self.get_primary_pi_creature() - self.remove(morty) - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - - if not show_detail: - self.add_foreground_mobject(morty) - - stroke_width = 6 - line_a = Line(B,C,stroke_width = stroke_width) - line_b = Line(A,C,stroke_width = stroke_width) - line_c = Line(A,B,stroke_width = stroke_width) - line_h = Line(C,H,stroke_width = stroke_width) - - length_a = line_a.get_length() - length_b = line_b.get_length() - length_c = line_c.get_length() - length_h = line_h.get_length() - - label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) - label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) - label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) - - self.add_foreground_mobject(line_a) - self.add_foreground_mobject(line_b) - self.add_foreground_mobject(line_c) - self.add_foreground_mobject(line_h) - self.add_foreground_mobject(label_a) - self.add_foreground_mobject(label_b) - self.add_foreground_mobject(label_h) - - if not show_detail: - self.add_foreground_mobject(morty) - - ls1 = LightSource(radius = 10) - ls1.move_source_to(B) - - self.add(ls1.lighthouse) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - +class HeresWhereThingsGetGood(TeacherStudentsScene): + def construct(self): + self.teacher_says("Now for the \\\\ good part!") + self.change_student_modes(*["hooray"]*3) + self.change_student_modes(*["happy"]*3) self.wait() - # adding the first screen - - screen_width_a = SCREEN_SCALE * length_a - screen_width_b = SCREEN_SCALE * length_b - screen_width_ap = screen_width_a * length_a / length_c - screen_width_bp = screen_width_b * length_b / length_c - screen_width_c = SCREEN_SCALE * length_c - - screen_thickness_a = SCREEN_THICKNESS - screen_thickness_b = SCREEN_THICKNESS - - screen1 = Rectangle(width = screen_width_b, - height = screen_thickness_b, - stroke_width = 0, - fill_opacity = 1.0) - screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) - - if not show_detail: - self.add_foreground_mobject(morty) - - self.play( - FadeIn(screen1) - ) - self.add_foreground_mobject(screen1) - - ls1.set_screen(screen1) - screen_tracker = ScreenTracker(ls1) - self.add(screen_tracker) - #self.add(ls1.shadow) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - - self.play( - SwitchOn(ls1.spotlight), - SwitchOff(ls1.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen1p = screen1.deepcopy() - screen1pp = screen1.deepcopy() - #self.add(screen1p) - angle = np.arccos(length_b / length_c) - - screen1p.stretch_to_fit_width(screen_width_bp) - screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) - screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) - - - self.play( - ls1.move_source_to,H, - Transform(screen1,screen1p) - ) - - # add and move the second light source and screen - ls2 = ls1.deepcopy() - ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, - height = screen_thickness_a, - stroke_width = 0, - fill_opacity = 1.0) - screen2.rotate(-TAU/4) - screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) - - self.play( - FadeIn(screen2) - ) - self.add_foreground_mobject(screen2) - - if not show_detail: - self.add_foreground_mobject(morty) - - # the same scene adding sequence as before - ls2.set_screen(screen2) - screen_tracker2 = ScreenTracker(ls2) - self.add(screen_tracker2) - - if not show_detail: - self.play( - SwitchOn(ls2.ambient_light) - ) - - self.wait() - - self.play( - SwitchOn(ls2.spotlight), - SwitchOff(ls2.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen2p = screen2.deepcopy() - screen2pp = screen2.deepcopy() - angle = np.arccos(length_a / length_c) - screen2p.stretch_to_fit_height(screen_width_ap) - screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) - screen2p.rotate(angle, about_point = C + screen_width_a * UP) - # we can reuse the translation vector - # screen2p.shift(vector) - - self.play( - ls2.move_source_to,H, - SwitchOff(ls1.ambient_light), - Transform(screen2,screen2p) - ) - - # now transform both screens back - self.play( - Transform(screen1, screen1pp), - Transform(screen2, screen2pp), - ) - -class IPTScene2(Scene): - +class DiameterTheorem(TeacherStudentsScene): def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) - intensity1 = 0.3 - intensity2 = 0.2 - formula_scale = 01.2 - indy_radius = 1 + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) - indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy1.set_intensity(intensity1) - reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) - indy1.add(reading1) + diameter_word = TextMobject("Diameter") + diameter_word.next_to(center, DOWN, SMALL_BUFF) - indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy2.set_intensity(intensity2) - reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) - indy2.add(reading2) - - indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy3.set_intensity(intensity1 + intensity2) - reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) - indy3.add(reading3) - - plus_sign = TexMobject("+").scale(formula_scale) - equals_sign = TexMobject("=").scale(formula_scale) - - plus_sign.next_to(indy1, RIGHT) - indy2.next_to(plus_sign, RIGHT) - equals_sign.next_to(indy2, RIGHT) - indy3.next_to(equals_sign, RIGHT) - - - formula = VGroup( - indy1, plus_sign, indy2, equals_sign, indy3 + point = VectorizedPoint(circle.get_top()) + triangle = Polygon(LEFT, RIGHT, UP) + triangle.set_stroke(BLUE) + triangle.set_fill(WHITE, 0.5) + def update_triangle(triangle): + triangle.set_points_as_corners([ + circle.get_left(), circle.get_right(), + point.get_center(), circle.get_left(), + ]) + triangle_update_anim = ContinualUpdateFromFunc( + triangle, update_triangle ) + triangle_update_anim.update(0) - formula.move_to(ORIGIN) + perp_mark = VMobject() + perp_mark.set_points_as_corners([LEFT, DOWN, RIGHT]) + perp_mark.shift(DOWN) + perp_mark.scale(0.15, about_point = ORIGIN) + perp_mark.shift(point.get_center()) + perp_mark.add(point.copy()) - self.play(FadeIn(indy1)) - self.play(FadeIn(plus_sign), FadeIn(indy2)) - self.play(FadeIn(equals_sign), FadeIn(indy3)) + self.play( + self.teacher.change, "raise_right_hand", + DrawBorderThenFill(triangle), + Write(diameter_word), + ) + self.play( + ShowCreation(perp_mark), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(perp_mark) + self.add(triangle_update_anim) + for angle in 0.2*TAU, -0.4*TAU, 0.3*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) - buffer = 1.5 - box = Rectangle(width = formula.get_width() * buffer, - height = formula.get_height() * buffer) - box.move_to(formula) - text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) - text.next_to(box,UP) - self.play(ShowCreation(box),Write(text)) + perp_mark.generate_target() + perp_mark.target.rotate(angle/2) + perp_mark.target.shift( + point.target.get_center() - \ + perp_mark.target[1].get_center() + ) -class PondScene(Scene): + self.play( + MoveToTarget(point), + MoveToTarget(perp_mark), + path_arc = angle, + run_time = 3, + ) +class InscribedeAngleThreorem(TeacherStudentsScene): + def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) + + title = TextMobject("Inscribed angle \\\\ theorem") + title.to_corner(UP+LEFT) + self.add(title) + + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) + + point = VectorizedPoint(circle.get_left()) + shape = Polygon(UP+LEFT, ORIGIN, DOWN+LEFT, RIGHT) + shape.set_stroke(BLUE) + def update_shape(shape): + shape.set_points_as_corners([ + point.get_center(), + circle.point_from_proportion(7./8), + circle.get_center(), + circle.point_from_proportion(1./8), + point.get_center(), + ]) + shape_update_anim = ContinualUpdateFromFunc( + shape, update_shape + ) + shape_update_anim.update(0) + + angle_mark = Arc(start_angle = -TAU/8, angle = TAU/4) + angle_mark.scale(0.3, about_point = ORIGIN) + angle_mark.shift(circle.get_center()) + theta = TexMobject("\\theta").highlight(RED) + theta.next_to(angle_mark, RIGHT, MED_SMALL_BUFF) + angle_mark.match_color(theta) + + half_angle_mark = Arc(start_angle = -TAU/16, angle = TAU/8) + half_angle_mark.scale(0.3, about_point = ORIGIN) + half_angle_mark.shift(point.get_center()) + half_angle_mark.add(point.copy()) + theta_halves = TexMobject("\\theta/2").highlight(GREEN) + theta_halves.scale(0.7) + half_angle_mark.match_color(theta_halves) + theta_halves_update = UpdateFromFunc( + theta_halves, lambda m : m.move_to(interpolate( + point.get_center(), + half_angle_mark.point_from_proportion(0.5), + 2.5, + )) + ) + theta_halves_update.update(0) + + self.play( + self.teacher.change, "raise_right_hand", + ShowCreation(shape, rate_func = None), + ) + self.play(*map(FadeIn, [angle_mark, theta])) + self.play( + ShowCreation(half_angle_mark), + Write(theta_halves), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(half_angle_mark, theta_halves) + self.add(shape_update_anim) + for angle in 0.25*TAU, -0.4*TAU, 0.3*TAU, -0.35*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) + + half_angle_mark.generate_target() + half_angle_mark.target.rotate(angle/2) + half_angle_mark.target.shift( + point.target.get_center() - \ + half_angle_mark.target[1].get_center() + ) + + self.play( + MoveToTarget(point), + MoveToTarget(half_angle_mark), + theta_halves_update, + path_arc = angle, + run_time = 3, + ) + +class PondScene(ThreeDScene): def construct(self): BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] + OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) LAKE0_RADIUS = 1.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 + LIGHTHOUSE_HEIGHT = 0.5 LAKE_COLOR = BLUE LAKE_OPACITY = 0.15 LAKE_STROKE_WIDTH = 5.0 @@ -2793,12 +2780,89 @@ class PondScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 2.5 + LIGHT_CUTOFF = 1 + + RIGHT_ANGLE_SIZE = 0.3 + + self.cumulated_zoom_factor = 1 + + def right_angle(pointA, pointB, pointC, size = 1): + + v1 = pointA - pointB + v1 = size * v1/np.linalg.norm(v1) + v2 = pointC - pointB + v2 = size * v2/np.linalg.norm(v2) + + P = pointB + Q = pointB + v1 + R = Q + v2 + S = R - v1 + angle_sign = VMobject() + angle_sign.set_points_as_corners([P,Q,R,S,P]) + angle_sign.mark_paths_closed = True + angle_sign.set_fill(color = WHITE, opacity = 1) + angle_sign.set_stroke(width = 0) + return angle_sign + + def triangle(pointA, pointB, pointC): + + mob = VMobject() + mob.set_points_as_corners([pointA, pointB, pointC, pointA]) + mob.mark_paths_closed = True + mob.set_fill(color = WHITE, opacity = 0.5) + mob.set_stroke(width = 0) + return mob + + def zoom_out_scene(factor): + + self.remove_foreground_mobject(self.ls0_dot) + self.remove(self.ls0_dot) + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + # place ls0_dot by hand + #old_radius = self.ls0_dot.radius + #self.ls0_dot.radius = 2 * old_radius + + #v = self.ls0_dot.get_center() - self.obs_dot.get_center() + #self.ls0_dot.shift(v) + #self.ls0_dot.move_to(self.outer_lake.get_center()) + self.ls0_dot.scale(2, about_point = ORIGIN) + + #self.add_foreground_mobject(self.ls0_dot) + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary - obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - + obs_dot = self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2807,9 +2871,10 @@ class PondScene(Scene): fill_opacity = LAKE_OPACITY ) lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Mortimer().scale(0.3) + morty = Randolph().scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -2817,15 +2882,17 @@ class PondScene(Scene): color = LIGHT_COLOR ) indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) # first lighthouse - ls0 = LightSource() + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 150) + ls0.lighthouse.scale_to_fit_height(LIGHTHOUSE_HEIGHT) + ls0.lighthouse.height = LIGHTHOUSE_HEIGHT ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) - - self.wait() - + # self.add(lake0, morty, obs_dot, ls0_dot, ls0.lighthouse) # shore arcs arc_left = Arc(-TAU/2, @@ -2838,8 +2905,7 @@ class PondScene(Scene): one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_left.next_to(arc_left,LEFT) - - + arc_right = Arc(TAU/2, radius = LAKE0_RADIUS, start_angle = -TAU/4, @@ -2851,28 +2917,76 @@ class PondScene(Scene): one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_right.next_to(arc_right,RIGHT) + # New introduction + lake0.save_state() + morty.save_state() + lake0.scale_to_fit_height(6) + morty.to_corner(UP+LEFT) + morty.fade(1) + lake0.center() + + lake_word = TextMobject("Lake") + lake_word.scale(2) + lake_word.move_to(lake0) + + self.play( + DrawBorderThenFill(lake0, stroke_width = 1), + Write(lake_word) + ) + self.play( + lake0.restore, + lake_word.scale, 0.5, {"about_point" : lake0.get_bottom()}, + lake_word.fade, 1 + ) + self.remove(lake_word) + self.play(morty.restore) + self.play( + GrowFromCenter(obs_dot), + GrowFromCenter(ls0_dot), + FadeIn(ls0.lighthouse) + ) + self.add_foreground_mobjects(ls0.lighthouse, obs_dot, ls0_dot) + self.play( + SwitchOn(ls0.ambient_light), + Animation(ls0.lighthouse), + ) + self.wait() + self.play( + morty.move_to, ls0.lighthouse, + run_time = 3, + path_arc = TAU/2, + rate_func = there_and_back + ) + self.play( - ShowCreation(arc_left), - Write(one_left), ShowCreation(arc_right), Write(one_right), ) - - self.play( - SwitchOn(ls0.ambient_light), - lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, + ShowCreation(arc_left), + Write(one_left), ) + self.play( + lake0.set_stroke, { + "color": LAKE_STROKE_COLOR, + "width" : LAKE_STROKE_WIDTH + }, + ) + self.wait() + self.add_foreground_mobjects(morty) + + # Show indicator self.play(FadeIn(indicator)) - self.play( - indicator.set_intensity,0.5 - ) + self.play(indicator.set_intensity, 0.5) + + diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02) + diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) # diameter - diameter = DoubleArrow(OBSERVER_POINT, - ls0.get_source_point(), + diameter = DoubleArrow(diameter_start, + diameter_stop, buff = 0, color = WHITE, ) @@ -2880,55 +2994,70 @@ class PondScene(Scene): diameter_text.next_to(diameter,RIGHT) self.play( - ShowCreation(diameter), + GrowFromCenter(diameter), Write(diameter_text), - #FadeOut(obs_dot), + #FadeOut(self.obs_dot), FadeOut(ls0_dot) ) + self.wait() - indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator.reading.move_to(indicator) + indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) self.play( - FadeIn(indicator.reading) + ReplacementTransform( + diameter_text[0].copy(), + indicator_reading[2], + ), + FadeIn(indicator_reading) ) + self.wait() # replace d with its value - new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text = TexMobject("{2 \over \pi}").scale(TEX_SCALE) new_diameter_text.color = LAKE_COLOR new_diameter_text.move_to(diameter_text) - self.play( - Transform(diameter_text,new_diameter_text) - ) + self.play(FadeOut(diameter_text)) + self.play(FadeIn(new_diameter_text)) + self.wait(2) # insert into indicator reading new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) new_reading.move_to(indicator) + new_diameter_text_copy = new_diameter_text.copy() + new_diameter_text_copy.submobjects.reverse() self.play( - Transform(indicator.reading,new_reading) + FadeOut(indicator_reading), + ReplacementTransform( + new_diameter_text_copy, + new_reading, + parth_arc = 30*DEGREES + ) ) + indicator_reading = new_reading + + self.wait(2) self.play( FadeOut(one_left), FadeOut(one_right), - FadeOut(diameter_text), + FadeOut(new_diameter_text), FadeOut(arc_left), FadeOut(arc_right) ) - - - + self.add_foreground_mobjects(indicator, indicator_reading) + self.unzoomable_mobs.add(indicator_reading) def indicator_wiggle(): INDICATOR_WIGGLE_FACTOR = 1.3 self.play( ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) ) - def angle_for_index(i,step): return -TAU/4 + TAU/2**step * (i + 0.5) @@ -2940,12 +3069,12 @@ class PondScene(Scene): position = self.lake_center + self.lake_radius * radial_vector if scaled_down: - return position.scale_about_point(OBSERVER_POINT,0.5) + return position.scale_about_point(self.obs_dot.get_center(),0.5) else: return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2961,8 +3090,8 @@ class PondScene(Scene): ShowCreation(hyp, run_time = run_time) ) - leg1 = Line(OBSERVER_POINT,ls_new_loc1) - leg2 = Line(OBSERVER_POINT,ls_new_loc2) + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) @@ -2973,32 +3102,38 @@ class PondScene(Scene): ) ls1 = self.light_sources_array[i] + + ls2 = ls1.copy() - self.add(ls2) + if animate == True: + self.add(ls2) + self.additional_light_sources.append(ls2) # check if the light sources are on screen ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) - if show_animation: + if show_animation or animate: + ls1.generate_target() + ls2.generate_target() + ls1.target.move_source_to(ls_new_loc1) + ls2.target.move_source_to(ls_new_loc2) + ls1.fade(1) self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + MoveToTarget(ls1), MoveToTarget(ls2), + run_time = run_time ) else: ls1.move_source_to(ls_new_loc1) ls2.move_source_to(ls_new_loc1) - - - - def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_radius = 1): + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -3016,18 +3151,22 @@ class PondScene(Scene): # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) self.play( FadeOut(self.hypotenuses), FadeOut(self.altitudes), FadeOut(self.inner_lake) ) else: + self.zoomable_mobs.remove(self.inner_lake) self.play( FadeOut(self.inner_lake) ) # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP new_outer_lake = Circle(radius = self.lake_radius, stroke_width = LAKE_STROKE_WIDTH, @@ -3040,7 +3179,7 @@ class PondScene(Scene): if show_steps == True: self.play( FadeIn(new_outer_lake, run_time = run_time), - FadeIn(ls0_dot) + FadeIn(self.ls0_dot) ) else: self.play( @@ -3052,38 +3191,124 @@ class PondScene(Scene): self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] self.new_hypotenuses = [] - for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time, - ls_radius = ls_radius + if simultaneous_splitting == False: + + for i in range(2**n): + + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time + ) + + if n == 1 and i == 0: + # show again where the right angles are + A = self.light_sources[0].get_center() + B = self.additional_light_sources[0].get_center() + C = self.obs_dot.get_center() + + triangle1 = triangle( + A, C, B + ) + right_angle1 = right_angle( + A, C, B, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle1), + FadeIn(right_angle1) + ) + + self.wait() + + self.play( + FadeOut(triangle1), + FadeOut(right_angle1) + ) + + self.wait() + + H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT + L = self.outer_lake.get_center() + triangle2 = triangle( + L, H, C + ) + + right_angle2 = right_angle( + L, H, C, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle2), + FadeIn(right_angle2) + ) + + self.wait() + + self.play( + FadeOut(triangle2), + FadeOut(right_angle2) + ) + + self.wait() + + else: # simultaneous splitting + + old_lake = self.outer_lake.copy() + old_ls = self.light_sources.copy() + old_ls2 = old_ls.copy() + for submob in old_ls2.submobjects: + old_ls.add(submob) + + self.remove(self.outer_lake, self.light_sources) + self.add(old_lake, old_ls) + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + animate = False + ) + + self.play( + ReplacementTransform(old_ls, self.light_sources, run_time = run_time), + ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), ) + # collect the newly created mobs (in arrays) # into the appropriate Mobject containers self.legs = VMobject() for leg in self.new_legs_1: self.legs.add(leg) + self.zoomable_mobs.add(leg) for leg in self.new_legs_2: self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) self.hypotenuses = VMobject() for hyp in self.new_hypotenuses: self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) for ls in self.additional_light_sources: self.light_sources.add(ls) self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) # update scene self.add( @@ -3091,6 +3316,7 @@ class PondScene(Scene): self.inner_lake, self.outer_lake, ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) if show_steps == True: self.add( @@ -3098,51 +3324,16 @@ class PondScene(Scene): self.hypotenuses, self.altitudes, ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) self.wait() if show_steps == True: - self.play(FadeOut(ls0_dot)) - - # scale down - if scale_down: - - indicator_wiggle() - - if show_steps == True: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - ) - else: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - ) - - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - - else: - # update the lake center and the radius - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP - self.lake_radius *= 2 - - - - - - + self.play(FadeOut(self.ls0_dot)) + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 self.lake_center = ls0_loc = ls0.get_source_point() @@ -3158,7 +3349,10 @@ class PondScene(Scene): self.lake_radius = 2 * LAKE0_RADIUS # don't ask... - self.add(self.inner_lake, + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + + self.add( + self.inner_lake, self.outer_lake, self.legs, self.altitudes, @@ -3172,29 +3366,116 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 + construction_step(0) + + my_triangle = triangle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point() + ) + + angle_sign1 = right_angle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point(), + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign1), + FadeIn(my_triangle) + ) + + angle_sign2 = right_angle( + self.light_sources[1].get_source_point(), + self.lake_center, + OBSERVER_POINT, + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign2) + ) + + self.wait() + + self.play( + FadeOut(angle_sign1), + FadeOut(angle_sign2), + FadeOut(my_triangle) + ) + + indicator_wiggle() + self.remove(self.ls0_dot) + zoom_out_scene(2) + + + construction_step(1) + indicator_wiggle() + #self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + - for i in range(3): - construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) - return self.play( FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) - ) + ) - for i in range(3,5): - construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True, ls_radius = ls_radius/2**3) + max_it = 6 + scale = 2**(max_it - 4) + TEX_SCALE *= scale + # for i in range(3,max_it + 1): + # construction_step(i, show_steps = False, run_time = 4.0/2**i, + # simultaneous_splitting = True) + + + + # simultaneous expansion of light sources from now on + self.play(FadeOut(self.inner_lake)) + + for n in range(3,max_it + 1): + + new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) + for ls in self.light_sources_array: + lsp = ls.copy() + self.light_sources.add(lsp) + self.add(lsp) + self.light_sources_array.append(lsp) + + new_lake_center = new_lake.get_center() + new_lake_radius = 0.5 * new_lake.get_width() + + shift_list = (Transform(self.outer_lake,new_lake),) + + + for i in range(2**n): + theta = -TAU/4 + (i + 0.5) * TAU / 2**n + v = np.array([np.cos(theta), np.sin(theta),0]) + pos1 = new_lake_center + new_lake_radius * v + pos2 = new_lake_center - new_lake_radius * v + shift_list += (self.light_sources.submobjects[i].move_source_to,pos1) + shift_list += (self.light_sources.submobjects[i+2**n].move_source_to,pos2) + + self.play(*shift_list) + + #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it MAX_N = 17 + origin_point = self.obs_dot.get_center() + self.number_line = NumberLine( x_min = -MAX_N, x_max = MAX_N + 1, @@ -3202,13 +3483,13 @@ class PondScene(Scene): number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = range(-MAX_N,MAX_N + 1), - unit_size = LAKE0_RADIUS * TAU/4 / 4, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(2.5 * DOWN) + ).shift(scale * 2.5 * DOWN) self.number_line.label_direction = DOWN @@ -3233,23 +3514,465 @@ class PondScene(Scene): # open sea open_sea = Rectangle( - width = 20, - height = 10, + width = 20 * scale, + height = 10 * scale, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, fill_opacity = LAKE_OPACITY, ).flip().next_to(origin_point,UP,buff = 0) - - self.play( - Transform(pond_sources,nl_sources), - Transform(self.outer_lake,open_sea), + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), FadeOut(self.inner_lake) ) self.play(FadeIn(self.number_line)) + self.wait() + + v = 4 * scale * UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + self.obs_dot.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + #self.remove(self.obs_dot) + self.play( + indicator.move_to, origin_point + scale * UP, + indicator_reading.move_to, origin_point + scale * UP, + FadeOut(open_sea), + FadeOut(morty), + FadeIn(self.number_line_labels) + ) + + two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ + "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ + "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ + "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ + "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") + + nb_symbols = len(two_sided_sum.submobjects) + + two_sided_sum.scale(TEX_SCALE) + + for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.3 * scale * DOWN) + + self.play(Write(two_sided_sum)) + + for i in range(MAX_N - 5, MAX_N): + self.remove(nl_sources.submobjects[i].ambient_light) + + for i in range(MAX_N, MAX_N + 5): + self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light) + + self.wait() + + covering_rectangle = Rectangle( + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1, + ) + covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + for i in range(10): + self.add_foreground_mobject(nl_sources.submobjects[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator_reading) + + + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(indicator) + + central_plus_sign = two_sided_sum[13] + + self.play( + FadeIn(covering_rectangle), + Transform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) + ) + + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP + + self.play( + indicator.move_to,p, + indicator_reading.move_to,p, + FadeIn(equals_sign), + ) + + self.revert_to_original_skipping_status() + + # show Randy admiring the result + randy = Randolph(color = MAROON_E).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + self.play(randy.change,"hooray") + +class WalkThroughOneMoreStep(TeacherStudentsScene): + def construct(self): + self.student_says(""" + Wait...can you walk \\\\ + through one more step? + """) + self.play(self.teacher.change, "happy") + self.wait(4) + +class ButWait(TeacherStudentsScene): + def construct(self): + self.student_says( + "But wait!", + target_mode = "angry", + run_time = 1, + ) + self.change_student_modes( + "sassy", "angry", "sassy", + added_anims = [self.teacher.change, "guilty"], + run_time = 1 + ) + self.student_says( + """ + You promised us \\\\ + $1+{1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots$ + """, + target_mode = "sassy", + ) + self.wait(3) + self.teacher_says("Yes, but that's \\\\ very close.") + self.change_student_modes(*["plain"]*3) + self.wait(2) + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + + unit_length = 1.5 + vertical_spacing = 2.5 * DOWN + switch_on_time = 0.2 + + sum_vertical_spacing = 1.5 + + randy = self.get_primary_pi_creature() + randy.highlight(MAROON_D) + randy.color = MAROON_D + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + self.wait() + + ls_template = LightSource( + radius = 1, + num_levels = 10, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.75,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_to_show = full_range, + number_scale_val = 0.5, + numbers_with_elongated_ticks = [], + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_SMALL_BUFF, + include_tip = True, + label_direction = UP, + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) + self.number_line1.add_numbers() + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1, run_time = 5), + ) + self.wait() + + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, + stroke_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + result1.shift(0.87 * vertical_spacing) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + self.number_line2.add_numbers() + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2, run_time = 5), + ) + self.wait() + + + full_lighthouses = VMobject() + full_ambient_lights = VMobject() + for ls in full_lights: + full_lighthouses.add(ls.lighthouse) + full_ambient_lights.add(ls.ambient_light) + + self.play( + LaggedStart(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), + ) + + self.play( + LaggedStart(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) + ) + + # for ls in full_lights.submobjects: + # self.play( + # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + # ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + self.wait() + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights, run_time = 2) + ) + + + self.wait() + + for i in range(6): + self.play( + Transform(even_lights[i], even_lights_copy[i]) + ) + self.wait() + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + ) + self.wait() + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + self.wait() + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + self.wait() + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + self.wait() + + full_terms = VMobject() + for i in range(1,8): #full_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + elif i == 7: + term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + #return + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + self.wait() + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + result1.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + self.wait() + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + self.wait() + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + full_terms.shift,LEFT, + ) + self.wait() + + self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) + + result_box = Rectangle(width = 1.1 * equation.get_width(), + height = 2 * equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + + + self.play( + ShowCreation(result_box) + ) + self.wait() + class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -3257,7 +3980,7 @@ class LabeledArc(Arc): def __init__(self, angle, **kwargs): - BUFFER = 1.3 + BUFFER = 0.8 Arc.__init__(self,angle,**kwargs) @@ -3269,13 +3992,14 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) -class ArcHighlightOverlayScene(Scene): - +class ArcHighlightOverlaySceneCircumferenceEight(Scene): + CONFIG = { + "n" : 2, + } def construct(self): - BASELINE_YPOS = -2.5 OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 + LAKE0_RADIUS = 2.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 LIGHTHOUSE_HEIGHT = 0.2 @@ -3286,7 +4010,7 @@ class ArcHighlightOverlayScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE - FLASH_TIME = 0.25 + FLASH_TIME = 1 def flash_arcs(n): @@ -3313,31 +4037,293 @@ class ArcHighlightOverlayScene(Scene): FadeOut(arcs[2**n], run_time = FLASH_TIME), ) + flash_arcs(self.n) - flash_arcs(3) - - - - - - - - - - - - - - - - - - - - - - - +class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight): + CONFIG = { + "n" : 3, + } + +class ThumbnailScene(Scene): + + def construct(self): + + equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") + equation.scale(1.5) + equation.move_to(1.5 * UP) + q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) + q_mark.next_to(equation, DOWN, buff = 1.5) + #equation.move_to(2 * UP) + #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #q_mark.next_to(equation, DOWN, buff = 1) + + lake_radius = 6 + lake_center = ORIGIN + op_scale = 0.4 + + lake = Circle( + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + radius = lake_radius, + stroke_color = LAKE_STROKE_COLOR, + stroke_width = LAKE_STROKE_WIDTH, + ) + lake.move_to(lake_center) + + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource( + radius = 15.0, + num_levels = 150, + max_opacity_ambient = 1.0, + opacity_function = inverse_quadratic(1,op_scale,1) + ) + ls.move_source_to(pos) + lake.add(ls.ambient_light, ls.lighthouse) + + self.add(lake) + + self.add(equation, q_mark) + + self.wait() + +class InfiniteCircleScene(PiCreatureScene): + + def construct(self): + + morty = self.get_primary_pi_creature() + morty.highlight(MAROON_D).flip() + morty.color = MAROON_D + morty.scale(0.5).move_to(ORIGIN) + + arrow = Arrow(ORIGIN, 2.4 * RIGHT) + dot = Dot(color = BLUE).next_to(arrow) + ellipsis = TexMobject("\dots") + + infsum = VGroup() + infsum.add(ellipsis.copy()) + + for i in range(3): + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(dot.copy().next_to(infsum.submobjects[-1])) + + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) + + infsum.next_to(morty,DOWN, buff = 1) + + self.wait() + self.play( + LaggedStart(FadeIn,infsum,lag_ratio = 0.2) + ) + self.wait() + + A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT + B = A + RIGHT + 1.3 * UP + 0.025 * LEFT + right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, + stroke_width = 8).apply_complex_function(np.exp) + right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) + right_tip_line = Arrow(B - UP, B, color = WHITE) + right_tip_line.add_tip() + right_tip = right_tip_line.get_tip() + right_tip.set_fill(color = YELLOW) + right_arc.add(right_tip) + + + C = B + 3.2 * UP + right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, + stroke_width = 8) + + ru_arc = right_arc.copy().rotate(angle = TAU/4) + ru_arc.remove(ru_arc.submobjects[-1]) + ru_arc.to_edge(UP+RIGHT, buff = 0.15) + + D = np.array([5.85, 3.85,0]) + E = np.array([-D[0],D[1],0]) + up_line = DashedLine(D, E, stroke_color = YELLOW, + stroke_width = 8) + + lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) + left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) + + left_arc = right_arc.copy().rotate(-TAU/4) + left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) + + right_arc.shift(0.2 * RIGHT) + right_line.shift(0.2 * RIGHT) + + self.play(FadeIn(right_arc)) + self.play(ShowCreation(right_line)) + self.play(FadeIn(ru_arc)) + self.play(ShowCreation(up_line)) + self.play(FadeIn(lu_arc)) + self.play(ShowCreation(left_line)) + self.play(FadeIn(left_arc)) + + + + self.wait() + +class BaselPatreonThanks(PatreonEndScreen): + CONFIG = { + "specific_patrons" : [ + "CrypticSwarm ", + "Ali Yahya", + "Juan Benet", + "Markus Persson", + "Damion Kistler", + "Burt Humburg", + "Yu Jun", + "Dave Nicponski", + "Kaustuv DeBiswas", + "Joseph John Cox", + "Luc Ritchie", + "Achille Brighton", + "Rish Kundalia", + "Yana Chernobilsky", + "Shìmín kuāng", + "Mathew Bramson", + "Jerry Ling", + "Mustafa Mahdi", + "Meshal Alshammari", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Robert Teed", + "Samantha D. Suplee", + "Mark Govea", + "John Haley", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Desmos ", + "Boris Veselinovich", + "Ryan Dahl", + "Ripta Pasay", + "Eric Lavault", + "Randall Hunt", + "Andrew Busey", + "Mads Elvheim", + "Tianyu Ge", + "Awoo", + "Dr. David G. Stork", + "Linh Tran", + "Jason Hise", + "Bernd Sing", + "James H. Park", + "Ankalagon ", + "Devin Scott", + "Mathias Jansson", + "David Clark", + "Ted Suzman", + "Eric Chow", + "Michael Gardner", + "David Kedmey", + "Jonathan Eppele", + "Clark Gaebel", + "Jordan Scales", + "Ryan Atallah", + "supershabam ", + "1stViewMaths ", + "Jacob Magnuson", + "Chloe Zhou", + "Ross Garber", + "Thomas Tarler", + "Isak Hietala", + "Egor Gumenuk", + "Waleed Hamied", + "Oliver Steele", + "Yaw Etse", + "David B", + "Delton Ding", + "James Thornton", + "Felix Tripier", + "Arthur Zey", + "George Chiesa", + "Norton Wang", + "Kevin Le", + "Alexander Feldman", + "David MacCumber", + "Jacob Kohl", + "Sergei ", + "Frank Secilia", + "Patrick Mézard", + "George John", + "Akash Kumar", + "Britt Selvitelle", + "Jonathan Wilson", + "Ignacio Freiberg", + "Zhilong Yang", + "Karl Niu", + "Dan Esposito", + "Michael Kunze", + "Giovanni Filippi", + "Eric Younge", + "Prasant Jagannath", + "Andrejs Olins", + "Cody Brocious", + ], + } + def construct(self): + next_video = TextMobject("$\\uparrow$ Next video $\\uparrow$") + next_video.to_edge(RIGHT, buff = 1.5) + next_video.shift(MED_SMALL_BUFF*UP) + next_video.highlight(YELLOW) + self.add_foreground_mobject(next_video) + PatreonEndScreen.construct(self) + + + +class Thumbnail(Scene): + CONFIG = { + "light_source_config" : { + "num_levels" : 250, + "radius" : 10.0, + "max_opacity_ambient" : 1.0, + "opacity_function" : inverse_quadratic(1,0.5,1) + } + } + def construct(self): + equation = TexMobject( + "1", "+", "{1\over 4}", "+", + "{1\over 9}","+", "{1\over 16}","+", + "{1\over 25}", "+", "\cdots" + ) + equation.scale(1.8) + equation.move_to(2*UP) + equation.set_stroke(BLACK, 1) + answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) + answer.scale(3) + answer.set_stroke(RED, 1) + # answer.next_to(equation, DOWN, buff = 1) + answer.move_to(1.25*DOWN) + #equation.move_to(2 * UP) + #answer = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #answer.next_to(equation, DOWN, buff = 1) + + lake_radius = 6 + lake_center = ORIGIN + + lake = Circle( + fill_color = BLUE, + fill_opacity = 0.15, + radius = lake_radius, + stroke_color = BLUE_D, + stroke_width = 3, + ) + lake.move_to(lake_center) + + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource(**self.light_source_config) + ls.move_source_to(pos) + lake.add(ls.ambient_light) + lake.add(ls.lighthouse) + + self.add(lake) + self.add(equation, answer) + self.wait() From 8b1643f55d3d82f1466aa8e18711e3bd305285d4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 4 Mar 2018 10:43:23 -0800 Subject: [PATCH 22/24] Finished Basel project --- active_projects/basel2.py | 581 +++++++++++++++++++++++++++++--------- 1 file changed, 445 insertions(+), 136 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index fb7239d9..8d8c804b 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -224,6 +224,45 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) +class ThreeDSpotlight(VGroup): + CONFIG = { + "fill_color" : YELLOW, + } + def __init__(self, screen, ambient_light, source_point_func, **kwargs): + self.screen = screen + self.ambient_light = ambient_light + self.source_point_func = source_point_func + self.dr = ambient_light.radius/ambient_light.num_levels + VGroup.__init__(self, **kwargs) + + def update(self): + screen = self.screen + source_point = self.source_point_func() + dr = self.dr + corners = screen.get_anchors() + self.submobjects = [VGroup() for a in screen.get_anchors()] + + distance = np.linalg.norm( + screen.get_center() - source_point + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for face, (c1, c2) in zip(self, adjacent_pairs(corners)): + face.submobjects = [] + for a1, a2 in zip(alphas, alphas[1:]): + face.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = self.fill_color, + fill_opacity = self.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + +class ContinualThreeDLightConeUpdate(ContinualAnimation): + def update(self, dt): + self.mobject.update() ### @@ -236,7 +275,7 @@ class ThinkAboutPondScene(PiCreatureScene): randy.to_corner(DOWN+LEFT) bubble = ThoughtBubble( width = 11, - height = 7, + height = 8, ) circles = bubble[:3] angle = -15*DEGREES @@ -245,6 +284,12 @@ class ThinkAboutPondScene(PiCreatureScene): for circle in circles: circle.rotate(-angle) bubble.pin_to(randy) + bubble.shift(DOWN) + bubble[:3].rotate(np.pi, axis = UP+2*RIGHT, about_edge = UP+LEFT) + bubble[:3].scale(0.7, about_edge = DOWN+RIGHT) + bubble[:3].shift(1.5*DOWN) + for oval in bubble[:3]: + oval.rotate(TAU/3) self.play( randy.change, "thinking", @@ -252,7 +297,7 @@ class ThinkAboutPondScene(PiCreatureScene): ) self.wait(2) self.play(randy.change, "happy", bubble) - self.wait(2) + self.wait(4) self.play(randy.change, "hooray", bubble) self.wait(2) @@ -615,7 +660,7 @@ class MathematicalWebOfConnections(PiCreatureScene): jerk, randy = self.pi_creatures words = self.words = TextMobject( - "$\\pi$ is not", + "I am not", "fundamentally \\\\", "about circles" ) @@ -657,9 +702,8 @@ class MathematicalWebOfConnections(PiCreatureScene): formula_equals_x = formula.get_part_by_tex("=").get_center()[0] formula.shift((basel_equals_x - formula_equals_x)*RIGHT) - formulas.move_to(randy) - formulas.to_edge(UP) - formulas.shift_onto_screen() + formulas.to_corner(UP+RIGHT) + formulas.shift(2*LEFT) self.formulas = formulas self.play( @@ -1165,6 +1209,9 @@ class IntroduceScreen(Scene): "num_rays" : 250, "min_ray_angle" : 0, "max_ray_angle" : TAU, + "source_point" : 2.5*LEFT, + "observer_point" : 3.5*RIGHT, + "screen_height" : 2, } def construct(self): self.setup_elements() @@ -1174,24 +1221,17 @@ class IntroduceScreen(Scene): def setup_elements(self): SCREEN_SIZE = 3.0 - source_point = self.source_point = 2.5*LEFT - observer_point = 3.5*RIGHT + source_point = self.source_point + observer_point = self.observer_point, + # Light source - - light_source = self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = self.num_levels, - radius = self.radius, - max_opacity_ambient = AMBIENT_FULL, - ) - - light_source.move_source_to(source_point) + light_source = self.light_source = self.get_light_source() # Screen screen = self.screen = Rectangle( width = 0.05, - height = 2, + height = self.screen_height, mark_paths_closed = True, fill_color = WHITE, fill_opacity = 1.0, @@ -1333,6 +1373,16 @@ class IntroduceScreen(Scene): ## + def get_light_source(self): + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_source_to(self.source_point) + return light_source + def shoot_rays(self, show_creation_kwargs = None): if show_creation_kwargs is None: show_creation_kwargs = {} @@ -1489,6 +1539,73 @@ class EarthScene(IntroduceScreen): }) self.wait() +class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene): + CONFIG = { + "num_levels" : 200, + } + def construct(self): + light_source = self.get_light_source() + screens = VGroup( + Square(), + RegularPolygon(8), + Circle().insert_n_anchor_points(25), + ) + for screen in screens: + screen.scale_to_fit_height(self.screen_height) + screens.rotate(TAU/4, UP) + screens.next_to(self.observer_point, LEFT) + screens.set_stroke(WHITE, 2) + screens.set_fill(WHITE, 0.5) + screen = screens[0] + + cone = ThreeDSpotlight( + screen, light_source.ambient_light, + light_source.get_source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + + self.add(light_source, screen, cone) + self.add(cone_update_anim) + self.move_camera( + phi = 60*DEGREES, + theta = -155*DEGREES, + run_time = 3, + ) + self.begin_ambient_camera_rotation() + kwargs = {"run_time" : 2} + self.play(screen.stretch, 0.5, 1, **kwargs) + self.play(screen.stretch, 2, 2, **kwargs) + self.play(Rotate( + screen, TAU/4, + axis = UP+OUT, + rate_func = there_and_back, + run_time = 3, + )) + self.play(Transform(screen, screens[1], **kwargs)) + self.play(screen.stretch, 0.5, 2, **kwargs) + self.play(Transform(screen, screens[2], **kwargs)) + self.wait(2) + self.play( + screen.stretch, 0.5, 1, + screen.stretch, 2, 2, + **kwargs + ) + self.play( + screen.stretch, 3, 1, + screen.stretch, 0.7, 2, + **kwargs + ) + self.wait(2) + +class LightInThreeDimensionsOverlay(Scene): + def construct(self): + words = TextMobject(""" + ``Solid angle'' \\\\ + (measured in ``steradians'') + """) + self.play(Write(words)) + self.wait() + class InverseSquareLaw(ThreeDScene): CONFIG = { "screen_height" : 1.0, @@ -1628,7 +1745,6 @@ class InverseSquareLaw(ThreeDScene): unit_distance = self.unit_distance light_indicator = self.light_indicator morty = self.morty - dr = ambient_light.radius/ambient_light.num_levels new_screen = Square( side_length = self.screen_height, @@ -1640,31 +1756,13 @@ class InverseSquareLaw(ThreeDScene): new_screen.rotate(TAU/4, UP) new_screen.move_to(old_screen, IN) old_screen.fade(1) - screen_group = VGroup(old_screen, new_screen) - cone = VGroup(*[VGroup() for x in range(4)]) - cone.set_stroke(width = 0) - cone.set_fill(YELLOW, opacity = 0.5) - corner_directions = [OUT+UP, OUT+DOWN, IN+DOWN, IN+UP] - def update_cone(cone): - corners = map(new_screen.get_corner, corner_directions) - distance = np.linalg.norm(old_screen.get_reference_point() - self.source_point) - n_parts = np.ceil(distance/dr) - alphas = np.linspace(0, 1, n_parts+1) - for face, (c1, c2) in zip(cone, adjacent_pairs(corners)): - face.submobjects = [] - for a1, a2 in zip(alphas, alphas[1:]): - face.add(Polygon( - interpolate(source_point, c1, a1), - interpolate(source_point, c1, a2), - interpolate(source_point, c2, a2), - interpolate(source_point, c2, a1), - fill_color = YELLOW, - fill_opacity = ambient_light.opacity_function(a1*distance), - stroke_width = 0 - )) - cone_update_anim = ContinualUpdateFromFunc(cone, update_cone) - cone_update_anim.update(0) + cone = ThreeDSpotlight( + new_screen, ambient_light, + source_point_func = lambda : source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + self.remove(self.spotlight_update, self.light_indicator_update) self.add( @@ -1690,26 +1788,41 @@ class InverseSquareLaw(ThreeDScene): run_time = 2, ) self.wait() - self.screen = screen_group - self.shift_by_distance(1) - self.shift_by_distance(-1) - self.wait() ## Create screen copies - screen_copy = new_screen.copy() - four_copies = VGroup(*[new_screen.copy() for x in range(4)]) - nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) - def update_four_copies(four_copies): - for mob, corner_direction in zip(four_copies, corner_directions): - mob.move_to(new_screen, corner_direction) - four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) - edge_directions = [ - UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN - ] - def update_nine_copies(nine_copies): - for mob, corner_direction in zip(nine_copies, edge_directions): - mob.move_to(new_screen, corner_direction) - nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) + def get_screen_copy_group(distance): + n = int(distance)**2 + copies = VGroup(*[new_screen.copy() for x in range(n)]) + copies.rotate(-TAU/4, axis = UP) + copies.arrange_submobjects_in_grid(buff = 0) + copies.rotate(TAU/4, axis = UP) + copies.move_to(source_point, IN) + copies.shift(distance*RIGHT*unit_distance) + return copies + screen_copy_groups = map(get_screen_copy_group, range(1, 8)) + def get_screen_copy_group_anim(n): + group = screen_copy_groups[n] + prev_group = screen_copy_groups[n-1] + group.save_state() + group.fade(1) + group.replace(prev_group, dim_to_match = 1) + return ApplyMethod(group.restore) + + # corner_directions = [UP+OUT, DOWN+OUT, DOWN+IN, UP+IN] + # edge_directions = [ + # UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN + # ] + + # four_copies = VGroup(*[new_screen.copy() for x in range(4)]) + # nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) + # def update_four_copies(four_copies): + # for mob, corner_direction in zip(four_copies, corner_directions): + # mob.move_to(new_screen, corner_direction) + # four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) + # def update_nine_copies(nine_copies): + # for mob, corner_direction in zip(nine_copies, edge_directions): + # mob.move_to(new_screen, corner_direction) + # nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) three_arrow = DoubleArrow( source_point + 4*DOWN, @@ -1721,19 +1834,17 @@ class InverseSquareLaw(ThreeDScene): three.next_to(three_arrow, DOWN) new_screen.fade(1) - self.add( - ContinualAnimation(screen_copy), - ContinualAnimation(four_copies), - ) + # self.add( + # ContinualAnimation(screen_copy), + # ContinualAnimation(four_copies), + # ) + + self.add(ContinualAnimation(screen_copy_groups[0])) + self.add(ContinualAnimation(screen_copy_groups[1])) self.play( - screen_group.scale, 2, {"about_edge" : IN + DOWN}, - screen_group.shift, unit_distance*RIGHT, - UpdateFromAlphaFunc( - four_copies, - lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) - ), - four_copies_update_anim, - screen_copy.shift, 0.25*OUT, #WHY? + new_screen.scale, 2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(1), run_time = 2, ) self.wait() @@ -1744,20 +1855,40 @@ class InverseSquareLaw(ThreeDScene): run_time = 10, ) self.begin_ambient_camera_rotation(rate = -0.01) - self.add(ContinualAnimation(nine_copies)) + self.add(ContinualAnimation(screen_copy_groups[2])) self.play( - screen_group.scale, 3./2, {"about_edge" : IN + DOWN}, - screen_group.shift, unit_distance*RIGHT, - nine_copies_update_anim, - UpdateFromAlphaFunc( - nine_copies, - lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) - ), + new_screen.scale, 3./2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(2), GrowFromPoint(three_arrow, three_arrow.get_left()), Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), run_time = 2, ) - self.wait(10) + self.begin_ambient_camera_rotation(rate = -0.01) + self.play(LaggedStart( + ApplyMethod, screen_copy_groups[2], + lambda m : (m.highlight, RED), + run_time = 5, + rate_func = there_and_back, + )) + self.wait(2) + self.move_camera(distance = 18) + self.play(*[ + ApplyMethod(mob.fade, 1) + for mob in screen_copy_groups[:2] + ]) + last_group = screen_copy_groups[2] + for n in range(4, len(screen_copy_groups)+1): + group = screen_copy_groups[n-1] + self.add(ContinualAnimation(group)) + self.play( + new_screen.scale, float(n)/(n-1), {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(n-1), + last_group.fade, 1, + ) + last_group = group + self.wait() ### @@ -2189,7 +2320,25 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): self.wait() #Write IPT - self.play(Write(theorem)) + a_part = theorem[:2] + b_part = theorem[2:5] + h_part = theorem[5:] + for part in a_part, b_part, h_part: + part.save_state() + part.scale(3) + part.fade(1) + a_part.move_to(lsA) + b_part.move_to(lsB) + h_part.move_to(lsC) + + self.play(*map(FadeOut, [lsA, lsB, lsC, indicator])) + for ls, part in (lsA, a_part), (lsB, b_part), (lsC, h_part): + self.add(ls) + self.play( + SwitchOn(ls.ambient_light, run_time = 2), + FadeIn(ls.lighthouse), + part.restore + ) self.wait() self.play( Write(theorem_name), @@ -2874,7 +3023,7 @@ class PondScene(ThreeDScene): self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Randolph().scale(0.3) + morty = Mortimer().flip().scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -3622,6 +3771,42 @@ class PondScene(ThreeDScene): self.play(randy.change,"happy") self.play(randy.change,"hooray") +class CircumferenceText(Scene): + CONFIG = {"n" : 16} + def construct(self): + words = TextMobject("Circumference %d"%self.n) + words.scale(1.25) + words.to_corner(UP+LEFT) + self.add(words) + +class CenterOfLargerCircleOverlayText(Scene): + def construct(self): + words = TextMobject("Center of \\\\ larger circle") + arrow = Vector(DOWN+LEFT, color = WHITE) + arrow.shift(words.get_bottom() + SMALL_BUFF*DOWN - arrow.get_start()) + group = VGroup(words, arrow) + group.scale_to_fit_height(2*SPACE_HEIGHT - 1) + group.to_edge(UP) + self.add(group) + +class DiameterWordOverlay(Scene): + def construct(self): + word = TextMobject("Diameter") + word.scale_to_fit_width(SPACE_WIDTH) + word.rotate(-45*DEGREES) + self.play(Write(word)) + self.wait() + +class YayIPTApplies(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Heyo! The Inverse \\\\ Pythagorean Theorem \\\\ applies!", + bubble_kwargs = {"width" : 5}, + target_mode = "surprised" + ) + self.change_student_modes(*3*["hooray"]) + self.wait(2) + class WalkThroughOneMoreStep(TeacherStudentsScene): def construct(self): self.student_says(""" @@ -3631,6 +3816,113 @@ class WalkThroughOneMoreStep(TeacherStudentsScene): self.play(self.teacher.change, "happy") self.wait(4) +class ThinkBackToHowAmazingThisIs(ThreeDScene): + CONFIG = { + "x_radius" : 100, + "max_shown_n" : 20, + } + def construct(self): + self.show_sum() + self.show_giant_circle() + + def show_sum(self): + number_line = NumberLine( + x_min = -self.x_radius, + x_max = self.x_radius, + numbers_to_show = range(-self.max_shown_n, self.max_shown_n), + ) + number_line.add_numbers() + number_line.shift(2*DOWN) + + positive_dots, negative_dots = [ + VGroup(*[ + Dot(number_line.number_to_point(u*x)) + for x in range(1, int(self.x_radius), 2) + ]) + for u in 1, -1 + ] + dot_pairs = it.starmap(VGroup, zip(positive_dots, negative_dots)) + + # Decimal + decimal = DecimalNumber(0, num_decimal_points = 6) + decimal.to_edge(UP) + terms = [2./(n**2) for n in range(1, 100, 2)] + partial_sums = np.cumsum(terms) + + # pi^2/4 label + brace = Brace(decimal, DOWN) + pi_term = TexMobject("\pi^2 \over 4") + pi_term.next_to(brace, DOWN) + + term_mobjects = VGroup() + for n in range(1, self.max_shown_n, 2): + p_term = TexMobject("\\left(\\frac{1}{%d}\\right)^2"%n) + n_term = TexMobject("\\left(\\frac{-1}{%d}\\right)^2"%n) + group = VGroup(p_term, n_term) + group.scale(0.7) + p_term.next_to(number_line.number_to_point(n), UP, LARGE_BUFF) + n_term.next_to(number_line.number_to_point(-n), UP, LARGE_BUFF) + term_mobjects.add(group) + term_mobjects.gradient_highlight(BLUE, YELLOW) + plusses = VGroup(*[ + VGroup(*[ + TexMobject("+").next_to( + number_line.number_to_point(u*n), UP, buff = 1.25, + ) + for u in -1, 1 + ]) + for n in range(0, self.max_shown_n, 2) + ]) + + zoom_out = AmbientMovement( + self.camera.rotation_mobject, + direction = OUT, rate = 0.4 + ) + def update_decimal(decimal): + z = self.camera.rotation_mobject.get_center()[2] + decimal.scale_to_fit_height(0.07*z) + decimal.move_to(0.7*z*UP) + scale_decimal = ContinualUpdateFromFunc(decimal, update_decimal) + + + self.add(number_line, *dot_pairs) + self.add(zoom_out, scale_decimal) + + tuples = zip(term_mobjects, plusses, partial_sums) + run_time = 1 + for term_mobs, plus_pair, partial_sum in tuples: + self.play( + FadeIn(term_mobs), + Write(plus_pair, run_time = 1), + ChangeDecimalToValue(decimal, partial_sum), + run_time = run_time + ) + self.wait(run_time) + run_time *= 0.9 + self.play(ChangeDecimalToValue(decimal, np.pi**2/4, run_time = 5)) + zoom_out.begin_wind_down() + self.wait() + self.remove(zoom_out, scale_decimal) + self.play(*map(FadeOut, it.chain( + term_mobjects, plusses, + number_line.numbers, [decimal] + ))) + + self.number_line = number_line + + def show_giant_circle(self): + self.number_line.main_line.insert_n_anchor_points(10000) + everything = VGroup(*self.mobjects) + circle = everything.copy() + circle.move_to(ORIGIN) + circle.apply_function( + lambda (x, y, z) : complex_to_R3(7*np.exp(complex(0, 0.0315*x))) + ) + circle.rotate(-TAU/4, about_point = ORIGIN) + circle.center() + + self.play(Transform(everything, circle, run_time = 6)) + class ButWait(TeacherStudentsScene): def construct(self): self.student_says( @@ -4044,50 +4336,6 @@ class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircu "n" : 3, } -class ThumbnailScene(Scene): - - def construct(self): - - equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") - equation.scale(1.5) - equation.move_to(1.5 * UP) - q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) - q_mark.next_to(equation, DOWN, buff = 1.5) - #equation.move_to(2 * UP) - #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) - #q_mark.next_to(equation, DOWN, buff = 1) - - lake_radius = 6 - lake_center = ORIGIN - op_scale = 0.4 - - lake = Circle( - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - radius = lake_radius, - stroke_color = LAKE_STROKE_COLOR, - stroke_width = LAKE_STROKE_WIDTH, - ) - lake.move_to(lake_center) - - for i in range(16): - theta = -TAU/4 + (i + 0.5) * TAU/16 - pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) - ls = LightSource( - radius = 15.0, - num_levels = 150, - max_opacity_ambient = 1.0, - opacity_function = inverse_quadratic(1,op_scale,1) - ) - ls.move_source_to(pos) - lake.add(ls.ambient_light, ls.lighthouse) - - self.add(lake) - - self.add(equation, q_mark) - - self.wait() - class InfiniteCircleScene(PiCreatureScene): def construct(self): @@ -4165,6 +4413,73 @@ class InfiniteCircleScene(PiCreatureScene): self.wait() +class Credits(Scene): + def construct(self): + credits = VGroup(*[ + VGroup(*map(TextMobject, pair)) + for pair in [ + ("Primary writer and animator:", "Ben Hambrecht"), + ("Editing, advising, narrating:", "Grant Sanderson"), + ("Based on a paper originally by:", "Johan Wästlund"), + ] + ]) + for credit, color in zip(credits, [MAROON_D, BLUE_D, WHITE]): + credit[1].highlight(color) + credit.arrange_submobjects(DOWN, buff = SMALL_BUFF) + + credits.arrange_submobjects(DOWN, buff = LARGE_BUFF) + + credits.center() + patreon_logo = PatreonLogo() + patreon_logo.to_edge(UP) + + for credit in credits: + self.play(LaggedStart(FadeIn, credit[0])) + self.play(FadeIn(credit[1])) + self.wait() + self.play( + credits.next_to, patreon_logo.get_bottom(), DOWN, MED_LARGE_BUFF, + DrawBorderThenFill(patreon_logo) + ) + self.wait() + +class Promotion(PiCreatureScene): + CONFIG = { + "seconds_to_blink" : 5, + } + def construct(self): + url = TextMobject("https://brilliant.org/3b1b/") + url.to_corner(UP+LEFT) + + rect = Rectangle(height = 9, width = 16) + rect.scale_to_fit_height(5.5) + rect.next_to(url, DOWN) + rect.to_edge(LEFT) + + self.play( + Write(url), + self.pi_creature.change, "raise_right_hand" + ) + self.play(ShowCreation(rect)) + self.wait(2) + self.change_mode("thinking") + self.wait() + self.look_at(url) + self.wait(10) + self.change_mode("happy") + self.wait(10) + self.change_mode("raise_right_hand") + self.wait(10) + + self.remove(rect) + self.play( + url.next_to, self.pi_creature, UP+LEFT + ) + url_rect = SurroundingRectangle(url) + self.play(ShowCreation(url_rect)) + self.play(FadeOut(url_rect)) + self.wait(3) + class BaselPatreonThanks(PatreonEndScreen): CONFIG = { "specific_patrons" : [ @@ -4182,7 +4497,7 @@ class BaselPatreonThanks(PatreonEndScreen): "Achille Brighton", "Rish Kundalia", "Yana Chernobilsky", - "Shìmín kuāng", + "Shìmín Ku$\\overline{\\text{a}}$ng", "Mathew Bramson", "Jerry Ling", "Mustafa Mahdi", @@ -4272,8 +4587,6 @@ class BaselPatreonThanks(PatreonEndScreen): self.add_foreground_mobject(next_video) PatreonEndScreen.construct(self) - - class Thumbnail(Scene): CONFIG = { "light_source_config" : { @@ -4291,7 +4604,7 @@ class Thumbnail(Scene): ) equation.scale(1.8) equation.move_to(2*UP) - equation.set_stroke(BLACK, 1) + equation.set_stroke(RED, 1) answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) answer.scale(3) answer.set_stroke(RED, 1) @@ -4336,7 +4649,3 @@ class Thumbnail(Scene): - - - - From d0b32a9eb9289d612521bc5feb5716278d7390ab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 4 Mar 2018 10:43:35 -0800 Subject: [PATCH 23/24] Randomize patron names --- topics/common_scenes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 4d1354f6..265e210c 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -123,8 +123,11 @@ class PatreonEndScreen(PatreonThanks): "n_patron_columns" : 3, "max_patron_width" : 3, "run_time" : 20, + "randomize_order" : True, } def construct(self): + if self.randomize_order: + random.shuffle(self.specific_patrons) self.add_title() self.scroll_through_patrons() @@ -141,7 +144,6 @@ class PatreonEndScreen(PatreonThanks): pi.next_to(title, vect, buff = MED_LARGE_BUFF) self.add_foreground_mobjects(title, randy, morty) - def scroll_through_patrons(self): logo_box = Square(side_length = 2.5) logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) From d4d6c1ee20b2c0ee09ee16e01572936f46d9be23 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 5 Mar 2018 19:37:03 -0800 Subject: [PATCH 24/24] Changed thumbnail lighthouse brightness --- active_projects/basel2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 8d8c804b..dd3049a9 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -4593,7 +4593,7 @@ class Thumbnail(Scene): "num_levels" : 250, "radius" : 10.0, "max_opacity_ambient" : 1.0, - "opacity_function" : inverse_quadratic(1,0.5,1) + "opacity_function" : inverse_quadratic(1,0.25,1) } } def construct(self):