diff --git a/active_projects/basel.py b/active_projects/basel.py index 8a069ddf..20814be5 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -426,7 +426,6 @@ class IntroScene(PiCreatureScene): self.ellipsis = TexMobject("\cdots") self.ellipsis.scale(0.4) - for i in range(5, max_n1): if i == 5: @@ -441,7 +440,6 @@ class IntroScene(PiCreatureScene): GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), run_time = 0.5) ) - for i in range(max_n1, max_n2): self.play( GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), @@ -929,15 +927,11 @@ class SingleLighthouseScene(PiCreatureScene): class MorphIntoSunScene(PiCreatureScene): - def construct(self): - self.setup_elements() self.morph_lighthouse_into_sun() - def setup_elements(self): - self.remove(self.get_primary_pi_creature()) SCREEN_SIZE = 3.0 @@ -988,10 +982,8 @@ class MorphIntoSunScene(PiCreatureScene): self.add_foreground_mobject(self.light_source.shadow) self.add_foreground_mobject(morty) - self.light_source.dim_ambient self.add(self.light_source.spotlight) - self.screen_tracker = ScreenTracker(self.light_source) self.add(self.screen_tracker) @@ -4351,10 +4343,6 @@ class ArcHighlightOverlayScene(Scene): ) - flash_arcs(3) - - - class ThumbnailScene(Scene): @@ -4545,14 +4533,4 @@ class RightAnglesOverlay(Scene): self.play(FadeOut(lines)) - - - - - - - - - - - + flash_arcs(3) diff --git a/active_projects/basel2.py b/active_projects/basel2.py new file mode 100644 index 00000000..dd3049a9 --- /dev/null +++ b/active_projects/basel2.py @@ -0,0 +1,4651 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +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 scene.zoomed_scene import * +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 + +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, + "reading_height" : 0.25, + "intensity": 0, + "opacity_for_unit_intensity": 1, + "fill_color" : YELLOW, + "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.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) + + 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) + 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): + if self.measurement_point is not 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 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() + +### + +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 11, + height = 8, + ) + 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(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", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(4) + self.play(randy.change, "hooray", bubble) + self.wait(2) + +class IntroScene(PiCreatureScene): + CONFIG = { + "rect_height" : 0.075, + "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, buff = MED_LARGE_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( + "I am 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.to_corner(UP+RIGHT) + formulas.shift(2*LEFT) + 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, 1.5*DOWN, + ) + + self.basel_sum = basel_sum + + def show_web_of_connections(self): + 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): + 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() + + ### + + 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): + CONFIG = { + "num_levels" : 100, + "opacity_function" : inverse_quadratic(1,2,1), + } + def construct(self): + 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): + 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,6), + numbers_to_show = range(1,6), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = DOWN, + ) + + number_line.add_numbers() + self.add(number_line) + + origin_point = number_line.number_to_point(0) + + 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) + + 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.wait() + + 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 + ) + 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.pin_to(morty) + bubble.add_content(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] + + for i, term in enumerate(euler_sum_above): + #horizontal alignment with tick marks + 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]) + + # 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(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), + 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 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''?", + added_anims = [ + SwitchOn(light_source.ambient_light), + Animation(light_source.lighthouse) + ] + ) + self.play(self.teacher.change, "happy") + self.wait(4) + +class IntroduceScreen(Scene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + "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() + self.setup_angle() # spotlight and angle msmt change when screen rotates + self.rotate_screen() + # self.morph_lighthouse_into_sun() + + def setup_elements(self): + SCREEN_SIZE = 3.0 + source_point = self.source_point + observer_point = self.observer_point, + + # Light source + light_source = self.light_source = self.get_light_source() + + # Screen + + screen = self.screen = Rectangle( + width = 0.05, + height = self.screen_height, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) + + screen.next_to(observer_point, LEFT) + + screen_label = TextMobject("Screen") + screen_label.next_to(screen, UP+LEFT) + screen_arrow = Arrow( + screen_label.get_bottom(), + screen.get_center(), + ) + + # 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() + + # angle msmt (arc) + arc_angle = self.light_source.spotlight.opening_angle() + # draw arc arrows to show the opening angle + 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, + 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 + angle_tracker = ContinualChangingDecimal( + self.angle_indicator, angle_update_func + ) + self.add(angle_tracker) + + arc_tracker = AngleUpdater( + self.angle_arc, + self.light_source.spotlight + ) + self.add(arc_tracker) + + self.play( + ShowCreation(self.angle_arc), + ShowCreation(self.angle_indicator) + ) + + self.wait() + + def rotate_screen(self): + self.add( + ContinualUpdateFromFunc( + self.light_source, + 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_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) + + ## + + 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 = {} + 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.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(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) + + 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.add_foreground_mobjects(black_rect, earth) + + # screen + 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() + ) + + 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 = sun = LightSource( + opacity_function = lambda r : 0.5, + max_opacity_ambient = 0, + max_opacity_spotlight = 0.5, + 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) + + screen_tracker = ScreenTracker(sun) + + # Add elements to scene + + self.add(screen) + self.play(SwitchOn( + sunlight, + rate_func = squish_rate_func(smooth, 0.7, 0.8), + )) + self.add(screen_tracker) + self.play( + Write(equator_words), + GrowArrow(equator_arrow) + ) + self.add_foreground_mobjects(equator_words, equator_arrow) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) + }) + self.wait() + # Point to patch + self.play( + 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 t : interpolate(0.98, 1, smooth(t)) + }) + 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, + "source_point" : 5*LEFT, + "unit_distance" : 4, + "num_levels" : 100, + } + def construct(self): + self.move_screen_farther_away() + self.morph_into_3d() + + def move_screen_farther_away(self): + source_point = self.source_point + unit_distance = self.unit_distance + + # screen + 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 + 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 + ) + light_source.set_max_opacity_spotlight(0.2) + + light_source.set_screen(screen) + light_source.move_source_to(source_point) + + # abbreviations + ambient_light = light_source.ambient_light + spotlight = light_source.spotlight + lighthouse = light_source.lighthouse + shadow = light_source.shadow + + # Morty + morty = self.morty = Mortimer().scale(0.3) + morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF) + + #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() + 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() + + q_marks = TextMobject("???") + q_marks.next_to(light_indicator, UP) + self.play( + Write(q_marks), + morty.change, "confused", q_marks + ) + self.play(Blink(morty)) + self.play(FadeOut(q_marks), morty.change, "pondering") + self.wait() + self.shift_by_distance(-1, arrow_group.shift, DOWN) + + 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 + + new_screen = Square( + side_length = self.screen_height, + stroke_color = WHITE, + stroke_width = 1, + fill_color = WHITE, + fill_opacity = 0.5 + ) + new_screen.rotate(TAU/4, UP) + new_screen.move_to(old_screen, IN) + old_screen.fade(1) + + 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( + 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() + + ## Create screen 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, + source_point + 4*DOWN + 3*unit_distance*RIGHT, + buff = 0, + color = WHITE + ) + three = Integer(3) + three.next_to(three_arrow, DOWN) + + new_screen.fade(1) + # self.add( + # ContinualAnimation(screen_copy), + # ContinualAnimation(four_copies), + # ) + + self.add(ContinualAnimation(screen_copy_groups[0])) + self.add(ContinualAnimation(screen_copy_groups[1])) + self.play( + new_screen.scale, 2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(1), + run_time = 2, + ) + self.wait() + self.move_camera( + phi = 75*DEGREES, + theta = -155*DEGREES, + distance = 7, + run_time = 10, + ) + self.begin_ambient_camera_rotation(rate = -0.01) + self.add(ContinualAnimation(screen_copy_groups[2])) + self.play( + 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.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() + + ### + + 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) + +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) + 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) + +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 + observer_point = morty.eyes[1].get_center() + + 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) + + #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) + + #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() + + 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( + 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 + ) + self.play(morty.change, "hooray") + self.wait(2) + + ## + + 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, + "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 = self.origin_point + + #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] + ) + + #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), + ) + 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) + + #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_corner(DOWN+LEFT), + ls.get_source_point(), + buff = SMALL_BUFF, + color = WHITE, + ) + for ls in lsA, lsB, lsC + ]) + + #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) + # 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(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( + SwitchOn(lsC.ambient_light), + FadeIn(lsC.lighthouse), + UpdateFromAlphaFunc( + indicator, lambda i, a : i.set_intensity(a*intensity) + ) + ) + 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, + path_arc = TAU/4, + run_time = 1.5, + ) + self.wait() + + # Draw line + self.play( + ShowCreation(line_h), + morty.change, "pondering" + ) + self.wait() + self.play( + 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 + 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), + ShowCreation(theorem_box) + ) + 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, + ) + + # 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 + 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, + theorem, theorem_name, theorem_box, + ) + + # 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_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) + self.play( + Transform(screen, screen.alt_version), + update_spotlight_anim, + ) + show_beaming_light(spotlight) + 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 + ) + show_beaming_light(spotlight) + 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 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) + +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() + +class DiameterTheorem(TeacherStudentsScene): + def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) + + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) + + diameter_word = TextMobject("Diameter") + diameter_word.next_to(center, DOWN, SMALL_BUFF) + + 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) + + 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( + 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()) + + perp_mark.generate_target() + perp_mark.target.rotate(angle/2) + perp_mark.target.shift( + point.target.get_center() - \ + perp_mark.target[1].get_center() + ) + + 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 = np.array([0,BASELINE_YPOS,0]) + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.5 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + 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 = 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, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) + + # Morty and indicator + morty = Mortimer().flip().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) + self.unzoomable_mobs.add(morty, indicator) + + # first lighthouse + 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) + + # 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) + + # 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_right), + Write(one_right), + ) + self.play( + 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) + + 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(diameter_start, + diameter_stop, + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + GrowFromCenter(diameter), + Write(diameter_text), + #FadeOut(self.obs_dot), + FadeOut(ls0_dot) + ) + self.wait() + + indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) + + self.play( + 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.color = LAKE_COLOR + new_diameter_text.move_to(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( + 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(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) + ) + + 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(self.obs_dot.get_center(),0.5) + else: + return position + + + 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) + + 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(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) + + 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() + 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.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 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( + 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, show_steps = True, run_time = 1, + simultaneous_splitting = False): + + # 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.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, + 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(self.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.lake_center = self.outer_lake.get_center() + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + 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( + self.light_sources, + 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( + self.legs, + self.hypotenuses, + self.altitudes, + ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) + + + self.wait() + + if show_steps == True: + 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() + + 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.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, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + 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)) + + + + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + 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, + 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,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(scale * 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 * 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( + 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 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(""" + Wait...can you walk \\\\ + through one more step? + """) + 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( + "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 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 0.8 + + 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 ArcHighlightOverlaySceneCircumferenceEight(Scene): + CONFIG = { + "n" : 2, + } + def construct(self): + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 2.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 = 1 + + 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(self.n) + +class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight): + CONFIG = { + "n" : 3, + } + +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 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" : [ + "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$\\overline{\\text{a}}$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.25,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(RED, 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() + + + + + + + + + + + + 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/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/helpers.py b/helpers.py index aff9a365..9eddc42e 100644 --- a/helpers.py +++ b/helpers.py @@ -693,6 +693,7 @@ class DictAsObject(object): def __init__(self, dict): self.__dict__ = dict +# Just to have a less heavyweight name for this extremely common operation # # We may wish to have more fine-grained control over division by zero behavior # in the future (separate specifiable values for 0/0 and x/0 with x != 0), @@ -707,7 +708,6 @@ def fdiv(a, b, zero_over_zero_value = None): return np.true_divide(a, b, out = out, where = where) - def add_extension_if_not_present(file_name, extension): # This could conceivably be smarter about handling existing differing extensions if(file_name[-len(extension):] != extension): 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) 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): 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): - diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 3e7369c1..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) @@ -176,7 +178,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( diff --git a/topics/light.py b/topics/light.py index 144f998a..2e239654 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 @@ -160,9 +159,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() @@ -195,14 +191,13 @@ class LightSource(VMobject): self.spotlight.update_sectors() 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( @@ -217,12 +212,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: @@ -247,9 +239,7 @@ class LightSource(VMobject): R = np.dot(R2, R1) return R - def update_shadow(self): - point = self.get_source_point() projected_screen_points = [] if not self.has_screen(): @@ -319,7 +309,6 @@ class LightSource(VMobject): self.shadow.mark_paths_closed = True - class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -329,9 +318,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): CONFIG = { @@ -347,9 +336,6 @@ class SwitchOff(LaggedStart): FadeOut, light, **kwargs) light.submobjects = light.submobjects[::-1] - - - class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -361,7 +347,6 @@ class Lighthouse(SVGMobject): def move_to(self,point): self.next_to(point, DOWN, buff = 0) - class AmbientLight(VMobject): # Parameters are: @@ -377,8 +362,8 @@ class AmbientLight(VMobject): "opacity_function" : lambda r : 1.0/(r+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 10.0 + "num_levels" : NUM_LEVELS, + "radius" : 5.0 } def generate_points(self): @@ -438,21 +423,7 @@ class AmbientLight(VMobject): submob.set_fill(opacity = new_submob_alpha) - - - - - - - - - - - - - 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, @@ -481,13 +452,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) @@ -503,13 +471,7 @@ 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): - # 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, @@ -545,7 +507,6 @@ class Spotlight(VMobject): else: return -absolute_angle - def viewing_angles(self,screen): screen_points = screen.get_anchors() @@ -572,7 +533,6 @@ class Spotlight(VMobject): return lower_ray, upper_ray - def opening_angle(self): l,u = self.viewing_angles(self.screen) return u - l @@ -592,21 +552,20 @@ class Spotlight(VMobject): self.update_sectors() return self - 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): old_alpha = self.max_opacity @@ -640,7 +599,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() + + + + + + + + + + + + + + + + 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) -