#!/usr/bin/env python # -*- coding: utf-8 -*- from big_ol_pile_of_manim_imports 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.set_color(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(FRAME_X_RADIUS*LEFT, LEFT) euler.next_to(FRAME_X_RADIUS*RIGHT, RIGHT) pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") pi_answer.set_color(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(FRAME_Y_RADIUS) 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, FRAME_Y_RADIUS*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.set_color_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.set_color(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.set_color(BLUE) semi_circle.set_color(YELLOW) VGroup(radius, semi_circle).move_to( FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*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(FRAME_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.set_color(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].set_color, RED) for dot, edge in zip(path_dots[1:], path_edges): self.play( ShowCreation(edge), dot.set_color, 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*FRAME_X_RADIUS*LEFT).to_edge(DOWN) randy.move_to(0.5*FRAME_X_RADIUS*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.set_color, 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 + FRAME_X_RADIUS, "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 = FRAME_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.set_color(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.set_color, 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(FRAME_X_RADIUS) 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(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*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.set_color(BLUE) line_b = Line(C, B) line_b.set_color(RED) line_c = Line(A, B) line_h = Line(H, C) line_h.set_color(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.set_color(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.set_color_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(FRAME_WIDTH*RIGHT) screen = ScreenRectangle(height = 5.5) screen.next_to(title, DOWN) self.play( logo.shift, FRAME_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.set_color_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(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*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.set_color(BLUE) line_b = Line(C, B) line_b.set_color(RED) line_c = Line(A, B) line_h = Line(H, C) line_h.set_color(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.set_color_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.set_color(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").set_color(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").set_color(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 = FRAME_X_RADIUS * scale, height = 2 * FRAME_Y_RADIUS * 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(FRAME_HEIGHT - 1) group.to_edge(UP) self.add(group) class DiameterWordOverlay(Scene): def construct(self): word = TextMobject("Diameter") word.scale_to_fit_width(FRAME_X_RADIUS) 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.set_color_by_gradient(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.set_color(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.set_color(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].set_color(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.set_color(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()