From e1a5667ec07a33ffe9802bbda3d7d591b4801087 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 9 Feb 2018 16:40:02 +0100 Subject: [PATCH 01/73] fixed issue with finding the angles of left-oriented spotlights --- topics/light.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topics/light.py b/topics/light.py index 67f1458c..c58c3d79 100644 --- a/topics/light.py +++ b/topics/light.py @@ -494,6 +494,8 @@ class Spotlight(VMobject): lower_angle = np.min(viewing_angles) upper_angle = np.max(viewing_angles) + if upper_angle - lower_angle > TAU/2: + lower_angle, upper_angle = upper_angle, lower_angle + TAU return lower_angle, upper_angle def viewing_rays(self,screen): @@ -537,7 +539,6 @@ class Spotlight(VMobject): new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle) submob.points = new_submob.points submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) - print "new opacity:", self.opacity_function(submob.outer_radius) From 512caa90c6c32449f8fd4ce8d77de4791affdff9 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 9 Feb 2018 16:41:15 +0100 Subject: [PATCH 02/73] Wrote main scene on IPT --- active_projects/basel.py | 200 +++++++++++++++++++++++++++++++++------ 1 file changed, 173 insertions(+), 27 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index d08b7be3..4bad27a7 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -55,6 +55,26 @@ inverse_power_law = lambda maxint,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 @@ -780,7 +800,7 @@ class SingleLighthouseScene(PiCreatureScene): # temporarily remove the screen tracker while we move the source #self.remove(self.screen_tracker) - #print self.sun.spotlight.source_point + #print self.sun.spotlight.get_source_point() self.play( #self.light_source.spotlight.move_source_to,sun_position, @@ -1050,10 +1070,10 @@ class ScreenShapingScene(ThreeDScene): lower_screen_point, upper_screen_point = self.screen.get_start_and_end() lower_slanted_screen_point = interpolate( - lower_screen_point, self.spotlight.source_point, SLANTING_AMOUNT + lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT ) upper_slanted_screen_point = interpolate( - upper_screen_point, self.spotlight.source_point, -SLANTING_AMOUNT + upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT ) self.slanted_brightness_rect = self.brightness_rect.copy() @@ -1115,7 +1135,7 @@ class ScreenShapingScene(ThreeDScene): self.unit_indicator_intensity = 1.0 # intensity at distance 1 # (where we are about to move to) - self.left_shift = (self.screen.get_center()[0] - self.spotlight.source_point[0])/2 + self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 self.play( self.screen.shift,[-self.left_shift,0,0], @@ -1129,7 +1149,7 @@ class ScreenShapingScene(ThreeDScene): def add_distance_arrow(self): # distance arrow (length 1) - left_x = self.spotlight.source_point[0] + left_x = self.spotlight.get_source_point()[0] right_x = self.screen.get_center()[0] arrow_y = -2 arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) @@ -1459,10 +1479,6 @@ class TwoLightSourcesScene(PiCreatureScene): INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 - A = np.array([5.,-3.,0.]) - B = np.array([-5.,3.,0.]) - C = np.array([-5.,-3.,0.]) - morty = self.get_primary_pi_creature() morty.scale(0.3).flip() right_pupil = morty.pupils[1] @@ -1494,14 +1510,8 @@ class TwoLightSourcesScene(PiCreatureScene): ls1 = LightSource(radius = 20, num_levels = 50) ls2 = ls1.deepcopy() - #print "===" - #print ls1.get_source_point() ls1.move_source_to(A) - #print ls1.get_source_point() - #print "===" - #print ls2.get_source_point() ls2.move_source_to(B) - #print ls2.get_source_point() self.play( FadeIn(ls1.lighthouse), @@ -1572,18 +1582,7 @@ class TwoLightSourcesScene(PiCreatureScene): # maybe move in a circle around C using a loop? - # find the coords of the altitude point H - # as the solution of a certain LSE - xA = A[0] - yA = A[1] - xB = B[0] - yB = B[1] - xC = C[0] - yC = C[1] - matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic - vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) - H2 = np.linalg.solve(matrix,vector) - H = np.append(H2, 0.) + self.play(ls3.move_source_to,H) @@ -1651,6 +1650,153 @@ class TwoLightSourcesScene(PiCreatureScene): +class IPTScene1(PiCreatureScene): + + def construct(self): + + SCREEN_SCALE = 0.1 + + morty = self.get_primary_pi_creature() + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + + line_a = VMobject() + line_a.set_points_as_corners([B,C]) + line_b = VMobject() + line_b.set_points_as_corners([A,C]) + line_c = VMobject() + line_c.set_points_as_corners([A,B]) + line_h = VMobject() + line_h.set_points_as_corners([C,H]) + + length_a = np.linalg.norm(B - C) + length_b = np.linalg.norm(A - C) + length_c = np.linalg.norm(A - B) + length_h = np.linalg.norm(C - H) + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.add(line_a, line_b, line_c, label_a, label_b, line_h) + + ls1 = LightSource(radius = 10) + ls1.move_source_to(B) + + self.add(ls1.lighthouse) + + self.play( + SwitchOn(ls1.ambient_light) + ) + + self.wait() + + # adding the first screen + + screen_width_a = SCREEN_SCALE * length_a + screen_width_b = SCREEN_SCALE * length_b + screen_width_ap = screen_width_a * length_a / length_c + screen_width_bp = screen_width_b * length_b / length_c + screen_width_c = SCREEN_SCALE * length_c + + screen1 = Rectangle(width = screen_width_b, height = 0.2, stroke_width = 0, fill_opacity = 1.0) + screen1.next_to(C,RIGHT,buff = 0) + print screen1.points + + ls1.set_screen(screen1) + screen_tracker = ScreenTracker(ls1) + self.add(screen_tracker) + + self.add_foreground_mobject(morty) + + self.play( + FadeIn(screen1) + ) + + self.play( + SwitchOn(ls1.spotlight) + ) + + self.play( + FadeIn(ls1.shadow) + ) + + self.add_foreground_mobject(screen1) + + + # now move the light source to the height point + # while shifting scaling the screen + screen1p = screen1#.deepcopy() + + self.add(screen1p) + angle = np.arccos(length_b / length_c) + vector = (H - C) * SCREEN_SCALE * 0.5 + + self.play( + ls1.move_source_to,H, + + screen1p.stretch_to_fit_width,screen_width_bp, + screen1p.rotate,-angle, + screen1p.shift,vector, + ) + + # add and move the second light source and screen + ls2 = ls1.deepcopy() + ls2.move_source_to(A) + screen2 = Rectangle(width = screen_width_a, height = 0.2, stroke_width = 0, fill_opacity = 1) + screen2.rotate(-TAU/4) + screen2.next_to(C,UP,buff = 0) + + + # the same scene adding sequence as before + ls2.set_screen(screen2) + screen_tracker2 = ScreenTracker(ls2) + self.add(screen_tracker2) + + self.play( + FadeIn(screen2) + ) + + self.play( + SwitchOn(ls2.spotlight) + ) + + self.play( + FadeIn(ls2.shadow) + ) + + self.add_foreground_mobject(screen2) + + + # now move the light source to the height point + # while shifting scaling the screen + screen2p = screen2#.deepcopy() + angle = np.arccos(length_a / length_c) + + self.play( + ls2.move_source_to,H, + + screen2p.stretch_to_fit_width,screen_width_ap, + screen2p.rotate,angle, + # we can reuse the translation vector + screen2p.shift,vector, + ) + + + + + + + + + + + + From cf9156a3cc5c824b1c58a877a07bc24598422ba9 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 10 Feb 2018 16:14:14 +0100 Subject: [PATCH 03/73] Fixed shadow issue --- topics/light.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/topics/light.py b/topics/light.py index c58c3d79..15c15c5f 100644 --- a/topics/light.py +++ b/topics/light.py @@ -103,7 +103,12 @@ class LightSource(VMobject): self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow) def has_screen(self): - return (self.screen != None) + if self.screen == None: + return False + elif np.size(self.screen.points) == 0: + return False + else: + return True def dim_ambient(self): self.set_max_opacity_ambient(AMBIENT_DIMMED) @@ -218,14 +223,14 @@ class LightSource(VMobject): index = 0 for point in point_cloud_2d[hull_2d.vertices]: - if np.all(point - source_point_2d < 1.0e-6): + if np.all(np.abs(point - source_point_2d) < 1.0e-6): source_index = index + index += 1 continue point_3d = np.array([point[0], point[1], 0]) hull.append(point_3d) index += 1 - index = source_index hull_mobject = VMobject() hull_mobject.set_points_as_corners(hull) @@ -235,25 +240,23 @@ class LightSource(VMobject): anchors = hull_mobject.get_anchors() # add two control points for the outer cone + if np.size(anchors) == 0: + self.shadow.points = [] + return - - ray1 = anchors[index - 1] - projected_source + ray1 = anchors[source_index - 1] - projected_source ray1 = ray1/np.linalg.norm(ray1) * 100 - ray2 = anchors[index] - projected_source + ray2 = anchors[source_index] - projected_source ray2 = ray2/np.linalg.norm(ray2) * 100 - outpoint1 = anchors[index - 1] + ray1 - outpoint2 = anchors[index] + ray2 + outpoint1 = anchors[source_index - 1] + ray1 + outpoint2 = anchors[source_index] + ray2 - new_anchors = anchors[:index] + new_anchors = anchors[:source_index] new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0) - new_anchors = np.append(new_anchors,anchors[index:],axis = 0) + new_anchors = np.append(new_anchors,anchors[source_index:],axis = 0) self.shadow.set_points_as_corners(new_anchors) - # Note: Theoretically this should not be necessary as long as we make - # sure the shadow shows up after the spotlight in the submobjects list. - # # shift it closer to the camera so it is in front of the spotlight - self.shadow.shift(1e-5*self.spotlight.projection_direction()) self.shadow.mark_paths_closed = True From 8c0387e0879d8f3b2ff0235045bb9f2c467842e6 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 10 Feb 2018 16:15:32 +0100 Subject: [PATCH 04/73] minor edit in IPTScene1 --- active_projects/basel.py | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 4bad27a7..1ee889c1 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -967,7 +967,7 @@ class ScreenShapingScene(ThreeDScene): self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() - #self.morph_into_3d() + self.morph_into_3d() def setup_elements(self): @@ -1705,27 +1705,27 @@ class IPTScene1(PiCreatureScene): screen1 = Rectangle(width = screen_width_b, height = 0.2, stroke_width = 0, fill_opacity = 1.0) screen1.next_to(C,RIGHT,buff = 0) - print screen1.points ls1.set_screen(screen1) screen_tracker = ScreenTracker(ls1) self.add(screen_tracker) + self.remove(ls1.spotlight) self.add_foreground_mobject(morty) self.play( FadeIn(screen1) ) - - self.play( - SwitchOn(ls1.spotlight) - ) - - self.play( - FadeIn(ls1.shadow) - ) - self.add_foreground_mobject(screen1) + self.add_foreground_mobject(line_a) + self.add_foreground_mobject(line_b) + + self.play( + SwitchOn(ls1.spotlight), + FadeIn(ls1.shadow), + ls1.dim_ambient, + ) + # now move the light source to the height point @@ -1747,7 +1747,7 @@ class IPTScene1(PiCreatureScene): # add and move the second light source and screen ls2 = ls1.deepcopy() ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, height = 0.2, stroke_width = 0, fill_opacity = 1) + screen2 = Rectangle(width = screen_width_a, height = 0., stroke_width = 10, fill_opacity = 1) screen2.rotate(-TAU/4) screen2.next_to(C,UP,buff = 0) @@ -1757,19 +1757,23 @@ class IPTScene1(PiCreatureScene): screen_tracker2 = ScreenTracker(ls2) self.add(screen_tracker2) + self.play( + SwitchOn(ls2.ambient_light) + ) + self.play( FadeIn(screen2) ) - - self.play( - SwitchOn(ls2.spotlight) - ) - - self.play( - FadeIn(ls2.shadow) - ) - self.add_foreground_mobject(screen2) + self.add_foreground_mobject(line_a) + self.add_foreground_mobject(line_b) + + self.play( + SwitchOn(ls2.spotlight), + FadeIn(ls2.shadow), + ls2.dim_ambient + ) + # now move the light source to the height point @@ -1784,6 +1788,7 @@ class IPTScene1(PiCreatureScene): screen2p.rotate,angle, # we can reuse the translation vector screen2p.shift,vector, + SwitchOff(ls1.ambient_light) ) From b4fe022fc27562287f81937c507afa6bc83fc114 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 10 Feb 2018 16:15:37 +0100 Subject: [PATCH 05/73] typo --- scene/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/scene.py b/scene/scene.py index 4c72897c..2112b3de 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -69,7 +69,7 @@ class Scene(Container): def setup(self): """ This is meant to be implement by any scenes which - are comonly subclassed, and have some common setup + are commonly subclassed, and have some common setup involved before the construct method is called. """ pass From ea18fe08f1ff24462cc169a5e3d56ecf78058b1e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 13 Feb 2018 17:49:35 +0100 Subject: [PATCH 06/73] Fidgeting with the little screens --- active_projects/basel.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 1ee889c1..a2dee416 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1730,18 +1730,20 @@ class IPTScene1(PiCreatureScene): # now move the light source to the height point # while shifting scaling the screen - screen1p = screen1#.deepcopy() - - self.add(screen1p) + screen1p = screen1.deepcopy() + screen1pp = screen1.deepcopy() + #self.add(screen1p) angle = np.arccos(length_b / length_c) vector = (H - C) * SCREEN_SCALE * 0.5 + screen1p.stretch_to_fit_width(screen_width_bp) + screen1p.rotate(-angle) + screen1p.shift(vector) + + self.play( ls1.move_source_to,H, - - screen1p.stretch_to_fit_width,screen_width_bp, - screen1p.rotate,-angle, - screen1p.shift,vector, + Transform(screen1,screen1p) ) # add and move the second light source and screen @@ -1778,17 +1780,25 @@ class IPTScene1(PiCreatureScene): # now move the light source to the height point # while shifting scaling the screen - screen2p = screen2#.deepcopy() + screen2p = screen2.deepcopy() + screen2pp = screen2.deepcopy() angle = np.arccos(length_a / length_c) + screen2p.stretch_to_fit_width(screen_width_ap) + screen2p.rotate(angle) + # we can reuse the translation vector + screen2p.shift(vector) self.play( ls2.move_source_to,H, + SwitchOff(ls1.ambient_light), + Transform(screen2,screen2p) + ) - screen2p.stretch_to_fit_width,screen_width_ap, - screen2p.rotate,angle, - # we can reuse the translation vector - screen2p.shift,vector, - SwitchOff(ls1.ambient_light) + + # now transform both screens back + self.play( + Transform(screen1, screen1pp), + Transform(screen2, screen2pp), ) @@ -1806,5 +1816,3 @@ class IPTScene1(PiCreatureScene): - - From 5a3e6723665327888ba14094a2775430d3417bdd Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 14 Feb 2018 00:14:39 +0100 Subject: [PATCH 07/73] Working on PondScene --- active_projects/basel.py | 202 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 192 insertions(+), 10 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index a2dee416..f643e59a 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -958,16 +958,16 @@ class ScreenShapingScene(ThreeDScene): def construct(self): self.setup_elements() - self.deform_screen() - self.create_brightness_rect() - self.slant_screen() - self.unslant_screen() - self.left_shift_screen_while_showing_light_indicator() - self.add_distance_arrow() - self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() - self.left_shift_again() + # self.deform_screen() + # self.create_brightness_rect() + # self.slant_screen() + # self.unslant_screen() + # self.left_shift_screen_while_showing_light_indicator() + # self.add_distance_arrow() + # self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() + # self.left_shift_again() - self.morph_into_3d() + # self.morph_into_3d() def setup_elements(self): @@ -1804,7 +1804,189 @@ class IPTScene1(PiCreatureScene): - +class PondScene(ThreeDScene): + + def construct(self): + + BASELINE_YPOS = -1 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + POND0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + SEA_COLOR = BLUE + SEA_OPACITY = 0.3 + TEX_SCALE = 0.8 + + baseline = VMobject() + baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + + obs_dot = Dot(OBSERVER_POINT) + ls0_dot = Dot(OBSERVER_POINT + 2 * POND0_RADIUS * UP) + + + # pond + pond0 = Circle(radius = POND0_RADIUS, + stroke_width = 0, + fill_color = SEA_COLOR, + fill_opacity = SEA_OPACITY + ) + pond0.move_to(OBSERVER_POINT + POND0_RADIUS * UP) + + # Morty and indicator + morty = Mortimer().scale(0.3) + morty.next_to(OBSERVER_POINT,DOWN) + indicator = LightIndicator(precision = 2, + radius = INDICATOR_RADIUS, + show_reading = False, + color = LIGHT_COLOR + ) + indicator.next_to(morty,LEFT) + + self.add(pond0,morty,indicator,obs_dot,ls0_dot) + + self.wait() + + + # shore arcs + arc_left = Arc(-TAU/2, + radius = POND0_RADIUS, + start_angle = -TAU/4 + ) + arc_left.move_arc_center_to(OBSERVER_POINT + POND0_RADIUS * UP) + + one_left = TexMobject("1") + one_left.next_to(arc_left,LEFT) + + + arc_right = Arc(TAU/2, + radius = POND0_RADIUS, + start_angle = -TAU/4 + ) + arc_right.move_arc_center_to(OBSERVER_POINT + POND0_RADIUS * UP) + + one_right = TexMobject("1").scale(TEX_SCALE) + one_right.next_to(arc_right,RIGHT) + + self.play( + ShowCreation(arc_left), + Write(one_left), + ShowCreation(arc_right), + Write(one_right), + ) + + # first lighthouse + ls0 = LightSource() + ls0.move_source_to(OBSERVER_POINT + POND0_RADIUS * 2 * UP) + + self.play( + SwitchOn(ls0.ambient_light), + FadeIn(ls0.lighthouse) + ) + self.play( + indicator.set_intensity,0.5 + ) + + # diameter + diameter = DoubleArrow(OBSERVER_POINT, + ls0.get_source_point(), + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + ShowCreation(diameter), + Write(diameter_text), + FadeOut(ls0_dot), + FadeOut(obs_dot) + ) + + indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator.reading.move_to(indicator) + + self.play( + FadeIn(indicator.reading) + ) + + # replace d with its value + new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text.move_to(diameter_text) + self.play( + Transform(diameter_text,new_diameter_text) + ) + + # insert into indicator reading + new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) + new_reading.move_to(indicator) + + self.play( + Transform(indicator.reading,new_reading) + ) + + + + + + + + # # # # # # # # # # # # # # + # first construction step # + # # # # # # # # # # # # # # + + def tangent_direction(point): + # gives a unit vector perpendicular to + # the line from point to OBSERVER_POINT + v = np.array(point) - np.array(OBSERVER_POINT) + return np.array([-v[1], v[0], 0])/np.abs(v) + + + pond1_radius = 2 * POND0_RADIUS + pond1 = Circle(radius = pond1_radius, + stroke_width = 0, + fill_color = SEA_COLOR, + fill_opacity = SEA_OPACITY + ) + pond1.move_to(ls0.get_center()) + + self.play( + FadeIn(pond1), + FadeOut(pond0), + FadeOut(one_left), + FadeOut(one_right) + ) + + ls0_loc = ls0.get_source_point() + ls11_loc = ls0_loc + pond1_radius * LEFT + ls12_loc = ls0_loc + pond1_radius * RIGHT + t11 = Line(ls0_loc, ls11_loc) + t12 = Line(ls0_loc, ls12_loc) + + self.play( + ShowCreation(t11), + ShowCreation(t12), + ) + + hypot11 = Line(ls11_loc,OBSERVER_POINT) + hypot12 = Line(ls12_loc,OBSERVER_POINT) + + self.play( + ShowCreation(hypot11), + ShowCreation(hypot12), + ) + + + # place lighthouses at intersection points + ls11 = ls0 + ls12 = ls0.copy() + + self.add(ls12) + + self.play( + ls11.move_source_to,ls11_loc, + ls12.move_source_to,ls12_loc, + ) From 77ae163c46cff08abfa16324a971c93bfb625304 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 14 Feb 2018 15:53:44 +0100 Subject: [PATCH 08/73] Three iterations animated --- active_projects/basel.py | 399 +++++++++++++++++++++++++++++++++++---- 1 file changed, 358 insertions(+), 41 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index f643e59a..5512f32b 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1804,34 +1804,37 @@ class IPTScene1(PiCreatureScene): -class PondScene(ThreeDScene): +class PondScene(Scene): def construct(self): - BASELINE_YPOS = -1 + BASELINE_YPOS = -2.5 OBSERVER_POINT = [0,BASELINE_YPOS,0] - POND0_RADIUS = 1.5 + LAKE0_RADIUS = 1.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 LIGHTHOUSE_HEIGHT = 0.2 - SEA_COLOR = BLUE - SEA_OPACITY = 0.3 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.3 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE TEX_SCALE = 0.8 + DOT_COLOR = BLUE baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) - obs_dot = Dot(OBSERVER_POINT) - ls0_dot = Dot(OBSERVER_POINT + 2 * POND0_RADIUS * UP) + obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = DOT_COLOR) - # pond - pond0 = Circle(radius = POND0_RADIUS, + # lake + lake0 = Circle(radius = LAKE0_RADIUS, stroke_width = 0, - fill_color = SEA_COLOR, - fill_opacity = SEA_OPACITY + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY ) - pond0.move_to(OBSERVER_POINT + POND0_RADIUS * UP) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) # Morty and indicator morty = Mortimer().scale(0.3) @@ -1843,27 +1846,35 @@ class PondScene(ThreeDScene): ) indicator.next_to(morty,LEFT) - self.add(pond0,morty,indicator,obs_dot,ls0_dot) + # first lighthouse + ls0 = LightSource() + ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + + self.add(lake0,morty,indicator,obs_dot,ls0_dot, ls0.lighthouse) self.wait() # shore arcs arc_left = Arc(-TAU/2, - radius = POND0_RADIUS, - start_angle = -TAU/4 + 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 + POND0_RADIUS * UP) + arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) one_left = TexMobject("1") one_left.next_to(arc_left,LEFT) arc_right = Arc(TAU/2, - radius = POND0_RADIUS, - start_angle = -TAU/4 + 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 + POND0_RADIUS * UP) + arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) one_right = TexMobject("1").scale(TEX_SCALE) one_right.next_to(arc_right,RIGHT) @@ -1875,13 +1886,9 @@ class PondScene(ThreeDScene): Write(one_right), ) - # first lighthouse - ls0 = LightSource() - ls0.move_source_to(OBSERVER_POINT + POND0_RADIUS * 2 * UP) self.play( SwitchOn(ls0.ambient_light), - FadeIn(ls0.lighthouse) ) self.play( indicator.set_intensity,0.5 @@ -1899,8 +1906,8 @@ class PondScene(ThreeDScene): self.play( ShowCreation(diameter), Write(diameter_text), - FadeOut(ls0_dot), - FadeOut(obs_dot) + FadeOut(obs_dot), + FadeOut(ls0_dot) ) indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) @@ -1942,24 +1949,25 @@ class PondScene(ThreeDScene): return np.array([-v[1], v[0], 0])/np.abs(v) - pond1_radius = 2 * POND0_RADIUS - pond1 = Circle(radius = pond1_radius, - stroke_width = 0, - fill_color = SEA_COLOR, - fill_opacity = SEA_OPACITY + lake1_radius = 2 * LAKE0_RADIUS + lake1 = Circle(radius = lake1_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR ) - pond1.move_to(ls0.get_center()) + lake1.move_to(ls0.get_center()) self.play( - FadeIn(pond1), - FadeOut(pond0), + FadeIn(lake1), + lake0.set_fill,{"opacity" : 0}, FadeOut(one_left), FadeOut(one_right) ) - ls0_loc = ls0.get_source_point() - ls11_loc = ls0_loc + pond1_radius * LEFT - ls12_loc = ls0_loc + pond1_radius * RIGHT + lake_center = ls0_loc = ls0.get_source_point() + ls11_loc = ls0_loc + lake1_radius * LEFT + ls12_loc = ls0_loc + lake1_radius * RIGHT t11 = Line(ls0_loc, ls11_loc) t12 = Line(ls0_loc, ls12_loc) @@ -1968,12 +1976,12 @@ class PondScene(ThreeDScene): ShowCreation(t12), ) - hypot11 = Line(ls11_loc,OBSERVER_POINT) - hypot12 = Line(ls12_loc,OBSERVER_POINT) + leg11 = Line(OBSERVER_POINT,ls11_loc) + leg12 = Line(OBSERVER_POINT,ls12_loc) self.play( - ShowCreation(hypot11), - ShowCreation(hypot12), + ShowCreation(leg11), + ShowCreation(leg12), ) @@ -1982,10 +1990,301 @@ class PondScene(ThreeDScene): ls12 = ls0.copy() self.add(ls12) - self.play( ls11.move_source_to,ls11_loc, ls12.move_source_to,ls12_loc, + FadeIn(ls0_dot) + ) + + INDICATOR_WIGGLE_FACTOR = 1.3 + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + self.play( + FadeOut(diameter), + FadeOut(diameter_text) + ) + + def scale_construction_down(): + scaling_args = [] + non_scaling_mobs = [indicator, indicator.reading, morty] + for mob in self.mobjects: + if type(mob) == AmbientLight or type(mob) == Lighthouse or mob in non_scaling_mobs: + continue + scaling_args.append(mob.scale_about_point) + scaling_args.append(0.5) + scaling_args.append(OBSERVER_POINT) + + self.play(*scaling_args) + + + + scale_construction_down() + + # # # # # # # # # # # # # # # + # second construction step # + # # # # # # # # # # # # # # # + + def ls_location_for_angle(angle): + return lake_center + lake1_radius * np.array([np.cos(angle),np.sin(angle),0]) + + self.play(FadeIn(ls0_dot)) + + lake2_radius = lake1_radius # not * 2 bc we have rescaled! + lake2 = Circle(radius = lake2_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + lake2.move_to(lake_center) + + self.play( + FadeIn(lake2), + FadeOut(lake0), + FadeOut(ls0_dot), + lake1.set_fill,{"opacity": 0}, + FadeOut(arc_left), + FadeOut(arc_right), + FadeOut(t11), + FadeOut(t12), + ) + + ls0_dot.move_to(lake_center) + self.play(FadeIn(ls0_dot)) + + ls21_loc = ls_location_for_angle(-3*TAU/8) + ls22_loc = ls_location_for_angle(TAU/8) + ls23_loc = ls_location_for_angle(3*TAU/8) + ls24_loc = ls_location_for_angle(-TAU/8) + t21 = Line(lake_center, ls21_loc) + t22 = Line(lake_center, ls22_loc) + t23 = Line(lake_center, ls23_loc) + t24 = Line(lake_center, ls24_loc) + leg21 = Line(OBSERVER_POINT,ls21_loc) + leg22 = Line(OBSERVER_POINT,ls22_loc) + leg23 = Line(OBSERVER_POINT,ls23_loc) + leg24 = Line(OBSERVER_POINT,ls24_loc) + + self.play( + ShowCreation(t21), + ShowCreation(t22), + ) + + + # place lighthouses at intersection points + ls21 = ls11 + ls22 = ls11.copy() + ls23 = ls12 + ls24 = ls12.copy() + + + self.play( + ShowCreation(leg21), + ShowCreation(leg22), + ) + + + self.add(ls21, ls22) + self.play( + ls21.move_source_to,ls21_loc, + ls22.move_source_to,ls22_loc, + ) + + + + self.play( + ShowCreation(t23), + ShowCreation(t24), + ) + + + + + self.play( + ShowCreation(leg23), + ShowCreation(leg24), + ) + + + self.add(ls23, ls24) + self.play( + ls23.move_source_to,ls23_loc, + ls24.move_source_to,ls24_loc, + ) + + + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + self.play( + FadeOut(lake1), + FadeOut(leg11), + FadeOut(leg12), + ) + + self.play( + FadeOut(ls0_dot) + ) + + ls0_dot.move_to(lake_center) + + self.play( + FadeIn(ls0_dot) + ) + + # again scale everything down + scale_construction_down() + + + + + + + + # # # # # # # # # # # # # # + # third construction step # + # # # # # # # # # # # # # # + + + lake3_radius = lake2_radius # not * 2 bc we have rescaled! + lake3 = Circle(radius = lake3_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + lake3.move_to(lake_center) + + self.play( + FadeIn(lake3), + FadeOut(lake1), + FadeOut(ls0_dot), + lake2.set_fill,{"opacity": 0}, + FadeOut(t21), + FadeOut(t22), + FadeOut(t23), + FadeOut(t24), + ) + + ls0_dot.move_to(lake_center) + self.play(FadeIn(ls0_dot)) + + ls31_loc = ls_location_for_angle(-3*TAU/16) + ls32_loc = ls_location_for_angle(5*TAU/16) + ls33_loc = ls_location_for_angle(-TAU/16) + ls34_loc = ls_location_for_angle(7*TAU/16) + ls35_loc = ls_location_for_angle(TAU/16) + ls36_loc = ls_location_for_angle(9*TAU/16) + ls37_loc = ls_location_for_angle(3*TAU/16) + ls38_loc = ls_location_for_angle(-5*TAU/16) + + t31 = Line(lake_center, ls31_loc) + t32 = Line(lake_center, ls32_loc) + t33 = Line(lake_center, ls33_loc) + t34 = Line(lake_center, ls34_loc) + t35 = Line(lake_center, ls35_loc) + t36 = Line(lake_center, ls36_loc) + t37 = Line(lake_center, ls37_loc) + t38 = Line(lake_center, ls38_loc) + + + leg31 = Line(OBSERVER_POINT,ls31_loc) + leg32 = Line(OBSERVER_POINT,ls32_loc) + leg33 = Line(OBSERVER_POINT,ls33_loc) + leg34 = Line(OBSERVER_POINT,ls34_loc) + leg35 = Line(OBSERVER_POINT,ls35_loc) + leg36 = Line(OBSERVER_POINT,ls36_loc) + leg37 = Line(OBSERVER_POINT,ls37_loc) + leg38 = Line(OBSERVER_POINT,ls38_loc) + + + # place lighthouses at intersection points + ls31 = ls24 + ls32 = ls24.copy() + ls33 = ls22 + ls34 = ls22.copy() + ls35 = ls23 + ls36 = ls23.copy() + ls37 = ls21 + ls38 = ls21.copy() + + # + + self.play( + ShowCreation(t31), + ShowCreation(t32), + ) + + self.play( + ShowCreation(leg31), + ShowCreation(leg32), + ) + + self.add(ls31, ls32) + self.play( + ls31.move_source_to,ls31_loc, + ls32.move_source_to,ls32_loc, + ) + + # + + self.play( + ShowCreation(t33), + ShowCreation(t34), + ) + + self.play( + ShowCreation(leg33), + ShowCreation(leg34), + ) + + self.add(ls33, ls34) + self.play( + ls33.move_source_to,ls33_loc, + ls34.move_source_to,ls34_loc, + ) + + # + + self.play( + ShowCreation(t35), + ShowCreation(t36), + ) + + self.play( + ShowCreation(leg35), + ShowCreation(leg36), + ) + + self.add(ls35, ls36) + self.play( + ls35.move_source_to,ls35_loc, + ls36.move_source_to,ls36_loc, + ) + + # + + self.play( + ShowCreation(t37), + ShowCreation(t38), + ) + + self.play( + ShowCreation(leg37), + ShowCreation(leg38), + ) + + self.add(ls37, ls38) + self.play( + ls37.move_source_to,ls37_loc, + ls38.move_source_to,ls38_loc, ) @@ -1998,3 +2297,21 @@ class PondScene(ThreeDScene): + self.play( + FadeOut(lake1), + ) + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + + + + + + + + + From a2c322331b7caf7a8501d1abd62025c7c62a0046 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 14 Feb 2018 18:26:23 +0100 Subject: [PATCH 09/73] Working on making the PondScene construction iterable --- active_projects/basel.py | 813 ++++++++++++++++++++++++--------------- 1 file changed, 498 insertions(+), 315 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 5512f32b..0d1f5c84 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1933,214 +1933,199 @@ class PondScene(Scene): ) - - - - - - # # # # # # # # # # # # # # - # first construction step # - # # # # # # # # # # # # # # - - def tangent_direction(point): - # gives a unit vector perpendicular to - # the line from point to OBSERVER_POINT - v = np.array(point) - np.array(OBSERVER_POINT) - return np.array([-v[1], v[0], 0])/np.abs(v) - - - lake1_radius = 2 * LAKE0_RADIUS - lake1 = Circle(radius = lake1_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - lake1.move_to(ls0.get_center()) - - self.play( - FadeIn(lake1), - lake0.set_fill,{"opacity" : 0}, - FadeOut(one_left), - FadeOut(one_right) - ) - - lake_center = ls0_loc = ls0.get_source_point() - ls11_loc = ls0_loc + lake1_radius * LEFT - ls12_loc = ls0_loc + lake1_radius * RIGHT - t11 = Line(ls0_loc, ls11_loc) - t12 = Line(ls0_loc, ls12_loc) - - self.play( - ShowCreation(t11), - ShowCreation(t12), - ) - - leg11 = Line(OBSERVER_POINT,ls11_loc) - leg12 = Line(OBSERVER_POINT,ls12_loc) - - self.play( - ShowCreation(leg11), - ShowCreation(leg12), - ) - - - # place lighthouses at intersection points - ls11 = ls0 - ls12 = ls0.copy() - - self.add(ls12) - self.play( - ls11.move_source_to,ls11_loc, - ls12.move_source_to,ls12_loc, - FadeIn(ls0_dot) - ) - - INDICATOR_WIGGLE_FACTOR = 1.3 - - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) - - self.play( - FadeOut(diameter), - FadeOut(diameter_text) - ) - - def scale_construction_down(): - scaling_args = [] - non_scaling_mobs = [indicator, indicator.reading, morty] - for mob in self.mobjects: - if type(mob) == AmbientLight or type(mob) == Lighthouse or mob in non_scaling_mobs: - continue - scaling_args.append(mob.scale_about_point) - scaling_args.append(0.5) - scaling_args.append(OBSERVER_POINT) - - self.play(*scaling_args) - - - - scale_construction_down() - - # # # # # # # # # # # # # # # - # second construction step # + # # # # # # # # # # # # # # # + # # first construction step # # # # # # # # # # # # # # # # - def ls_location_for_angle(angle): - return lake_center + lake1_radius * np.array([np.cos(angle),np.sin(angle),0]) - - self.play(FadeIn(ls0_dot)) - - lake2_radius = lake1_radius # not * 2 bc we have rescaled! - lake2 = Circle(radius = lake2_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - lake2.move_to(lake_center) - - self.play( - FadeIn(lake2), - FadeOut(lake0), - FadeOut(ls0_dot), - lake1.set_fill,{"opacity": 0}, - FadeOut(arc_left), - FadeOut(arc_right), - FadeOut(t11), - FadeOut(t12), - ) - - ls0_dot.move_to(lake_center) - self.play(FadeIn(ls0_dot)) - - ls21_loc = ls_location_for_angle(-3*TAU/8) - ls22_loc = ls_location_for_angle(TAU/8) - ls23_loc = ls_location_for_angle(3*TAU/8) - ls24_loc = ls_location_for_angle(-TAU/8) - t21 = Line(lake_center, ls21_loc) - t22 = Line(lake_center, ls22_loc) - t23 = Line(lake_center, ls23_loc) - t24 = Line(lake_center, ls24_loc) - leg21 = Line(OBSERVER_POINT,ls21_loc) - leg22 = Line(OBSERVER_POINT,ls22_loc) - leg23 = Line(OBSERVER_POINT,ls23_loc) - leg24 = Line(OBSERVER_POINT,ls24_loc) - - self.play( - ShowCreation(t21), - ShowCreation(t22), - ) + # def tangent_direction(point): + # # gives a unit vector perpendicular to + # # the line from point to OBSERVER_POINT + # v = np.array(point) - np.array(OBSERVER_POINT) + # return np.array([-v[1], v[0], 0])/np.abs(v) - # place lighthouses at intersection points - ls21 = ls11 - ls22 = ls11.copy() - ls23 = ls12 - ls24 = ls12.copy() + # lake1_radius = 2 * LAKE0_RADIUS + # lake1 = Circle(radius = lake1_radius, + # stroke_width = LAKE_STROKE_WIDTH, + # fill_color = LAKE_COLOR, + # fill_opacity = LAKE_OPACITY, + # stroke_color = LAKE_STROKE_COLOR + # ) + # lake1.move_to(ls0.get_center()) + + # self.play( + # FadeIn(lake1), + # lake0.set_fill,{"opacity" : 0}, + # FadeOut(one_left), + # FadeOut(one_right) + # ) + + # lake_center = ls0_loc = ls0.get_source_point() + # ls11_loc = ls0_loc + lake1_radius * LEFT + # ls12_loc = ls0_loc + lake1_radius * RIGHT + # t11 = Line(ls0_loc, ls11_loc) + # t12 = Line(ls0_loc, ls12_loc) + + # self.play( + # ShowCreation(t11), + # ShowCreation(t12), + # ) + + # leg11 = Line(OBSERVER_POINT,ls11_loc) + # leg12 = Line(OBSERVER_POINT,ls12_loc) + + # self.play( + # ShowCreation(leg11), + # ShowCreation(leg12), + # ) - self.play( - ShowCreation(leg21), - ShowCreation(leg22), - ) + # # place lighthouses at intersection points + # ls11 = ls0 + # ls12 = ls0.copy() + + # self.add(ls12) + # self.play( + # ls11.move_source_to,ls11_loc, + # ls12.move_source_to,ls12_loc, + # FadeIn(ls0_dot) + # ) - self.add(ls21, ls22) - self.play( - ls21.move_source_to,ls21_loc, - ls22.move_source_to,ls22_loc, - ) + # self.play( + # FadeOut(diameter), + # FadeOut(diameter_text) + # ) + + # def scale_construction_down(): + # scaling_args = [] + # non_scaling_mobs = [indicator, indicator.reading, morty] + # for mob in self.mobjects: + # if type(mob) == AmbientLight or type(mob) == Lighthouse or mob in non_scaling_mobs: + # continue + # scaling_args.append(mob.scale_about_point) + # scaling_args.append(0.5) + # scaling_args.append(OBSERVER_POINT) + + # self.play(*scaling_args) - self.play( - ShowCreation(t23), - ShowCreation(t24), - ) + # scale_construction_down() + + # # # # # # # # # # # # # # # # + # # second construction step # + # # # # # # # # # # # # # # # # + + # def ls_location_for_angle(angle): + # return lake_center + lake1_radius * np.array([np.cos(angle),np.sin(angle),0]) + + # self.play(FadeIn(ls0_dot)) + + # lake2_radius = lake1_radius # not * 2 bc we have rescaled! + # lake2 = Circle(radius = lake2_radius, + # stroke_width = LAKE_STROKE_WIDTH, + # fill_color = LAKE_COLOR, + # fill_opacity = LAKE_OPACITY, + # stroke_color = LAKE_STROKE_COLOR + # ) + # lake2.move_to(lake_center) + + # self.play( + # FadeIn(lake2), + # FadeOut(lake0), + # FadeOut(ls0_dot), + # lake1.set_fill,{"opacity": 0}, + # FadeOut(arc_left), + # FadeOut(arc_right), + # FadeOut(t11), + # FadeOut(t12), + # ) + + # ls0_dot.move_to(lake_center) + # self.play(FadeIn(ls0_dot)) + + # ls21_loc = ls_location_for_angle(-3*TAU/8) + # ls22_loc = ls_location_for_angle(TAU/8) + # ls23_loc = ls_location_for_angle(3*TAU/8) + # ls24_loc = ls_location_for_angle(-TAU/8) + # t21 = Line(lake_center, ls21_loc) + # t22 = Line(lake_center, ls22_loc) + # t23 = Line(lake_center, ls23_loc) + # t24 = Line(lake_center, ls24_loc) + # leg21 = Line(OBSERVER_POINT,ls21_loc) + # leg22 = Line(OBSERVER_POINT,ls22_loc) + # leg23 = Line(OBSERVER_POINT,ls23_loc) + # leg24 = Line(OBSERVER_POINT,ls24_loc) + + # self.play( + # ShowCreation(t21), + # ShowCreation(t22), + # ) + + + # # place lighthouses at intersection points + # ls21 = ls11 + # ls22 = ls11.copy() + # ls23 = ls12 + # ls24 = ls12.copy() + + + # self.play( + # ShowCreation(leg21), + # ShowCreation(leg22), + # ) + + + # self.add(ls21, ls22) + # self.play( + # ls21.move_source_to,ls21_loc, + # ls22.move_source_to,ls22_loc, + # ) + + + + # self.play( + # ShowCreation(t23), + # ShowCreation(t24), + # ) - self.play( - ShowCreation(leg23), - ShowCreation(leg24), - ) + # self.play( + # ShowCreation(leg23), + # ShowCreation(leg24), + # ) - self.add(ls23, ls24) - self.play( - ls23.move_source_to,ls23_loc, - ls24.move_source_to,ls24_loc, - ) + # self.add(ls23, ls24) + # self.play( + # ls23.move_source_to,ls23_loc, + # ls24.move_source_to,ls24_loc, + # ) - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) - self.play( - FadeOut(lake1), - FadeOut(leg11), - FadeOut(leg12), - ) + # self.play( + # FadeOut(lake1), + # FadeOut(leg11), + # FadeOut(leg12), + # ) - self.play( - FadeOut(ls0_dot) - ) + # self.play( + # FadeOut(ls0_dot) + # ) - ls0_dot.move_to(lake_center) + # ls0_dot.move_to(lake_center) - self.play( - FadeIn(ls0_dot) - ) + # self.play( + # FadeIn(ls0_dot) + # ) - # again scale everything down - scale_construction_down() + # # again scale everything down + # scale_construction_down() @@ -2148,144 +2133,350 @@ class PondScene(Scene): - # # # # # # # # # # # # # # - # third construction step # - # # # # # # # # # # # # # # + # # # # # # # # # # # # # # # + # # third construction step # + # # # # # # # # # # # # # # # - lake3_radius = lake2_radius # not * 2 bc we have rescaled! - lake3 = Circle(radius = lake3_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - lake3.move_to(lake_center) + # lake3_radius = lake2_radius # not * 2 bc we have rescaled! + # lake3 = Circle(radius = lake3_radius, + # stroke_width = LAKE_STROKE_WIDTH, + # fill_color = LAKE_COLOR, + # fill_opacity = LAKE_OPACITY, + # stroke_color = LAKE_STROKE_COLOR + # ) + # lake3.move_to(lake_center) - self.play( - FadeIn(lake3), - FadeOut(lake1), - FadeOut(ls0_dot), - lake2.set_fill,{"opacity": 0}, - FadeOut(t21), - FadeOut(t22), - FadeOut(t23), - FadeOut(t24), - ) + # self.play( + # FadeIn(lake3), + # FadeOut(lake1), + # FadeOut(ls0_dot), + # lake2.set_fill,{"opacity": 0}, + # FadeOut(t21), + # FadeOut(t22), + # FadeOut(t23), + # FadeOut(t24), + # ) - ls0_dot.move_to(lake_center) - self.play(FadeIn(ls0_dot)) + # ls0_dot.move_to(lake_center) + # self.play(FadeIn(ls0_dot)) - ls31_loc = ls_location_for_angle(-3*TAU/16) - ls32_loc = ls_location_for_angle(5*TAU/16) - ls33_loc = ls_location_for_angle(-TAU/16) - ls34_loc = ls_location_for_angle(7*TAU/16) - ls35_loc = ls_location_for_angle(TAU/16) - ls36_loc = ls_location_for_angle(9*TAU/16) - ls37_loc = ls_location_for_angle(3*TAU/16) - ls38_loc = ls_location_for_angle(-5*TAU/16) + # ls31_loc = ls_location_for_angle(-3*TAU/16) + # ls32_loc = ls_location_for_angle(5*TAU/16) + # ls33_loc = ls_location_for_angle(-TAU/16) + # ls34_loc = ls_location_for_angle(7*TAU/16) + # ls35_loc = ls_location_for_angle(TAU/16) + # ls36_loc = ls_location_for_angle(9*TAU/16) + # ls37_loc = ls_location_for_angle(3*TAU/16) + # ls38_loc = ls_location_for_angle(-5*TAU/16) - t31 = Line(lake_center, ls31_loc) - t32 = Line(lake_center, ls32_loc) - t33 = Line(lake_center, ls33_loc) - t34 = Line(lake_center, ls34_loc) - t35 = Line(lake_center, ls35_loc) - t36 = Line(lake_center, ls36_loc) - t37 = Line(lake_center, ls37_loc) - t38 = Line(lake_center, ls38_loc) + # t31 = Line(lake_center, ls31_loc) + # t32 = Line(lake_center, ls32_loc) + # t33 = Line(lake_center, ls33_loc) + # t34 = Line(lake_center, ls34_loc) + # t35 = Line(lake_center, ls35_loc) + # t36 = Line(lake_center, ls36_loc) + # t37 = Line(lake_center, ls37_loc) + # t38 = Line(lake_center, ls38_loc) - leg31 = Line(OBSERVER_POINT,ls31_loc) - leg32 = Line(OBSERVER_POINT,ls32_loc) - leg33 = Line(OBSERVER_POINT,ls33_loc) - leg34 = Line(OBSERVER_POINT,ls34_loc) - leg35 = Line(OBSERVER_POINT,ls35_loc) - leg36 = Line(OBSERVER_POINT,ls36_loc) - leg37 = Line(OBSERVER_POINT,ls37_loc) - leg38 = Line(OBSERVER_POINT,ls38_loc) + # leg31 = Line(OBSERVER_POINT,ls31_loc) + # leg32 = Line(OBSERVER_POINT,ls32_loc) + # leg33 = Line(OBSERVER_POINT,ls33_loc) + # leg34 = Line(OBSERVER_POINT,ls34_loc) + # leg35 = Line(OBSERVER_POINT,ls35_loc) + # leg36 = Line(OBSERVER_POINT,ls36_loc) + # leg37 = Line(OBSERVER_POINT,ls37_loc) + # leg38 = Line(OBSERVER_POINT,ls38_loc) - # place lighthouses at intersection points - ls31 = ls24 - ls32 = ls24.copy() - ls33 = ls22 - ls34 = ls22.copy() - ls35 = ls23 - ls36 = ls23.copy() - ls37 = ls21 - ls38 = ls21.copy() + # # place lighthouses at intersection points + # ls31 = ls24 + # ls32 = ls24.copy() + # ls33 = ls22 + # ls34 = ls22.copy() + # ls35 = ls23 + # ls36 = ls23.copy() + # ls37 = ls21 + # ls38 = ls21.copy() - # + # # - self.play( - ShowCreation(t31), - ShowCreation(t32), + # self.play( + # ShowCreation(t31), + # ShowCreation(t32), + # ) + + # self.play( + # ShowCreation(leg31), + # ShowCreation(leg32), + # ) + + # self.add(ls31, ls32) + # self.play( + # ls31.move_source_to,ls31_loc, + # ls32.move_source_to,ls32_loc, + # ) + + # # + + # self.play( + # ShowCreation(t33), + # ShowCreation(t34), + # ) + + # self.play( + # ShowCreation(leg33), + # ShowCreation(leg34), + # ) + + # self.add(ls33, ls34) + # self.play( + # ls33.move_source_to,ls33_loc, + # ls34.move_source_to,ls34_loc, + # ) + + # # + + # self.play( + # ShowCreation(t35), + # ShowCreation(t36), + # ) + + # self.play( + # ShowCreation(leg35), + # ShowCreation(leg36), + # ) + + # self.add(ls35, ls36) + # self.play( + # ls35.move_source_to,ls35_loc, + # ls36.move_source_to,ls36_loc, + # ) + + # # + + # self.play( + # ShowCreation(t37), + # ShowCreation(t38), + # ) + + # self.play( + # ShowCreation(leg37), + # ShowCreation(leg38), + # ) + + # self.add(ls37, ls38) + # self.play( + # ls37.move_source_to,ls37_loc, + # ls38.move_source_to,ls38_loc, + # ) + + + + + + + + + + + + # self.play( + # FadeOut(lake1), + # ) + + # self.play( + # ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + # ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + # ) + + + + + 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/2**step * (i - 0.25) + + + 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 = lake_center + 2 * LAKE0_RADIUS * radial_vector + + if scaled_down: + return position.scale_about_point(OBSERVER_POINT,0.5) + else: + return position + + + def split_light_source(i, step): + + ls_new_loc1 = position_for_index(i,step) + ls_new_loc2 = position_for_index(i + 2**step,step) + + hyp = Mobject() + hyp1 = Line(lake_center,ls_new_loc1) + hyp2 = Line(lake_center,ls_new_loc2) + hyp.add(hyp1,hyp2) + self.new_hypotenuses.append(hyp) + + self.play( + ShowCreation(hyp) + ) + + leg1 = Line(OBSERVER_POINT,ls_new_loc1) + leg2 = Line(OBSERVER_POINT,ls_new_loc2) + self.new_legs_1.append(leg1) + self.new_legs_2.append(leg2) + + self.play( + ShowCreation(leg1), + ShowCreation(leg2), + ) + + ls1 = self.light_sources_array[i] + ls2 = ls1.copy() + self.add(ls2) + + self.play( + ls1.move_source_to,ls_new_loc1, + ls2.move_source_to,ls_new_loc2, + ) + + + + + + def construction_step(n, scale_down = True): + + # 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! + + # create a new, outer lake + + new_outer_lake = Circle(radius = 2 * LAKE0_RADIUS, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(lake_center) + + self.play( + FadeIn(new_outer_lake), + FadeOut(ls0_dot), + ) + + # fade out all of the hypotenuses and altitudes + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + for i in range(2**n): + split_light_source(i, step = n) + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = Mobject() + for leg in self.new_legs_1: + self.legs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + + self.hypotenuses = Mobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + + # update scene + self.add(self.light_sources,self.legs,self.hypotenuses,self.altitudes) + + # scale down + if scale_down: + self.play( + self.light_sources.scale_about_point,0.5,OBSERVER_POINT, + self.legs.scale_about_point,0.5,OBSERVER_POINT, + self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, + self.altitudes.scale_about_point,0.5,OBSERVER_POINT, + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + ) + + + + + + + + + + lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = Mobject() + self.outer_lake = lake0 + self.legs = Mobject() + self.legs.add(Line(OBSERVER_POINT,lake_center)) + self.altitudes = Mobject() + self.hypotenuses = Mobject() + self.light_sources_array = [ls0] + self.light_sources = Mobject() + self.light_sources.add(ls0) + + + + self.add(self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses ) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + construction_step(0, scale_down = True) + indicator_wiggle() - self.play( - ShowCreation(leg31), - ShowCreation(leg32), - ) - - self.add(ls31, ls32) - self.play( - ls31.move_source_to,ls31_loc, - ls32.move_source_to,ls32_loc, - ) - - # - - self.play( - ShowCreation(t33), - ShowCreation(t34), - ) - - self.play( - ShowCreation(leg33), - ShowCreation(leg34), - ) - - self.add(ls33, ls34) - self.play( - ls33.move_source_to,ls33_loc, - ls34.move_source_to,ls34_loc, - ) - - # - - self.play( - ShowCreation(t35), - ShowCreation(t36), - ) - - self.play( - ShowCreation(leg35), - ShowCreation(leg36), - ) - - self.add(ls35, ls36) - self.play( - ls35.move_source_to,ls35_loc, - ls36.move_source_to,ls36_loc, - ) - - # - - self.play( - ShowCreation(t37), - ShowCreation(t38), - ) - - self.play( - ShowCreation(leg37), - ShowCreation(leg38), - ) - - self.add(ls37, ls38) - self.play( - ls37.move_source_to,ls37_loc, - ls38.move_source_to,ls38_loc, - ) @@ -2297,14 +2488,6 @@ class PondScene(Scene): - self.play( - FadeOut(lake1), - ) - - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) From 9f94580ce392764771c241482ee0583335a6c496 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 15 Feb 2018 18:09:19 +0100 Subject: [PATCH 10/73] Tweaking the PondScene --- active_projects/basel.py | 569 +++++++++++---------------------------- 1 file changed, 159 insertions(+), 410 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 0d1f5c84..4020dd0f 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -27,6 +27,8 @@ from topics.three_dimensions import * from topics.light import * +import types +import functools LIGHT_COLOR = YELLOW INDICATOR_RADIUS = 0.7 @@ -170,10 +172,50 @@ class ContinualLightIndicatorUpdate(ContinualAnimation): 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.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) @@ -1815,7 +1857,7 @@ class PondScene(Scene): TICK_SIZE = 0.5 LIGHTHOUSE_HEIGHT = 0.2 LAKE_COLOR = BLUE - LAKE_OPACITY = 0.3 + LAKE_OPACITY = 0.15 LAKE_STROKE_WIDTH = 5.0 LAKE_STROKE_COLOR = BLUE TEX_SCALE = 0.8 @@ -1825,7 +1867,7 @@ class PondScene(Scene): baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = DOT_COLOR) + ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) # lake @@ -1864,7 +1906,7 @@ class PondScene(Scene): ) arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - one_left = TexMobject("1") + one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_left.next_to(arc_left,LEFT) @@ -1876,7 +1918,7 @@ class PondScene(Scene): ) arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - one_right = TexMobject("1").scale(TEX_SCALE) + one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_right.next_to(arc_right,RIGHT) self.play( @@ -1889,6 +1931,7 @@ class PondScene(Scene): self.play( SwitchOn(ls0.ambient_light), + lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, ) self.play( indicator.set_intensity,0.5 @@ -1919,6 +1962,7 @@ class PondScene(Scene): # replace d with its value new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text.color = LAKE_COLOR new_diameter_text.move_to(diameter_text) self.play( Transform(diameter_text,new_diameter_text) @@ -1932,364 +1976,13 @@ class PondScene(Scene): Transform(indicator.reading,new_reading) ) - - # # # # # # # # # # # # # # # - # # first construction step # - # # # # # # # # # # # # # # # - - # def tangent_direction(point): - # # gives a unit vector perpendicular to - # # the line from point to OBSERVER_POINT - # v = np.array(point) - np.array(OBSERVER_POINT) - # return np.array([-v[1], v[0], 0])/np.abs(v) - - - # lake1_radius = 2 * LAKE0_RADIUS - # lake1 = Circle(radius = lake1_radius, - # stroke_width = LAKE_STROKE_WIDTH, - # fill_color = LAKE_COLOR, - # fill_opacity = LAKE_OPACITY, - # stroke_color = LAKE_STROKE_COLOR - # ) - # lake1.move_to(ls0.get_center()) - - # self.play( - # FadeIn(lake1), - # lake0.set_fill,{"opacity" : 0}, - # FadeOut(one_left), - # FadeOut(one_right) - # ) - - # lake_center = ls0_loc = ls0.get_source_point() - # ls11_loc = ls0_loc + lake1_radius * LEFT - # ls12_loc = ls0_loc + lake1_radius * RIGHT - # t11 = Line(ls0_loc, ls11_loc) - # t12 = Line(ls0_loc, ls12_loc) - - # self.play( - # ShowCreation(t11), - # ShowCreation(t12), - # ) - - # leg11 = Line(OBSERVER_POINT,ls11_loc) - # leg12 = Line(OBSERVER_POINT,ls12_loc) - - # self.play( - # ShowCreation(leg11), - # ShowCreation(leg12), - # ) - - - # # place lighthouses at intersection points - # ls11 = ls0 - # ls12 = ls0.copy() - - # self.add(ls12) - # self.play( - # ls11.move_source_to,ls11_loc, - # ls12.move_source_to,ls12_loc, - # FadeIn(ls0_dot) - # ) - - - # self.play( - # FadeOut(diameter), - # FadeOut(diameter_text) - # ) - - # def scale_construction_down(): - # scaling_args = [] - # non_scaling_mobs = [indicator, indicator.reading, morty] - # for mob in self.mobjects: - # if type(mob) == AmbientLight or type(mob) == Lighthouse or mob in non_scaling_mobs: - # continue - # scaling_args.append(mob.scale_about_point) - # scaling_args.append(0.5) - # scaling_args.append(OBSERVER_POINT) - - # self.play(*scaling_args) - - - - # scale_construction_down() - - # # # # # # # # # # # # # # # # - # # second construction step # - # # # # # # # # # # # # # # # # - - # def ls_location_for_angle(angle): - # return lake_center + lake1_radius * np.array([np.cos(angle),np.sin(angle),0]) - - # self.play(FadeIn(ls0_dot)) - - # lake2_radius = lake1_radius # not * 2 bc we have rescaled! - # lake2 = Circle(radius = lake2_radius, - # stroke_width = LAKE_STROKE_WIDTH, - # fill_color = LAKE_COLOR, - # fill_opacity = LAKE_OPACITY, - # stroke_color = LAKE_STROKE_COLOR - # ) - # lake2.move_to(lake_center) - - # self.play( - # FadeIn(lake2), - # FadeOut(lake0), - # FadeOut(ls0_dot), - # lake1.set_fill,{"opacity": 0}, - # FadeOut(arc_left), - # FadeOut(arc_right), - # FadeOut(t11), - # FadeOut(t12), - # ) - - # ls0_dot.move_to(lake_center) - # self.play(FadeIn(ls0_dot)) - - # ls21_loc = ls_location_for_angle(-3*TAU/8) - # ls22_loc = ls_location_for_angle(TAU/8) - # ls23_loc = ls_location_for_angle(3*TAU/8) - # ls24_loc = ls_location_for_angle(-TAU/8) - # t21 = Line(lake_center, ls21_loc) - # t22 = Line(lake_center, ls22_loc) - # t23 = Line(lake_center, ls23_loc) - # t24 = Line(lake_center, ls24_loc) - # leg21 = Line(OBSERVER_POINT,ls21_loc) - # leg22 = Line(OBSERVER_POINT,ls22_loc) - # leg23 = Line(OBSERVER_POINT,ls23_loc) - # leg24 = Line(OBSERVER_POINT,ls24_loc) - - # self.play( - # ShowCreation(t21), - # ShowCreation(t22), - # ) - - - # # place lighthouses at intersection points - # ls21 = ls11 - # ls22 = ls11.copy() - # ls23 = ls12 - # ls24 = ls12.copy() - - - # self.play( - # ShowCreation(leg21), - # ShowCreation(leg22), - # ) - - - # self.add(ls21, ls22) - # self.play( - # ls21.move_source_to,ls21_loc, - # ls22.move_source_to,ls22_loc, - # ) - - - - # self.play( - # ShowCreation(t23), - # ShowCreation(t24), - # ) - - - - - # self.play( - # ShowCreation(leg23), - # ShowCreation(leg24), - # ) - - - # self.add(ls23, ls24) - # self.play( - # ls23.move_source_to,ls23_loc, - # ls24.move_source_to,ls24_loc, - # ) - - - - - # self.play( - # FadeOut(lake1), - # FadeOut(leg11), - # FadeOut(leg12), - # ) - - # self.play( - # FadeOut(ls0_dot) - # ) - - # ls0_dot.move_to(lake_center) - - # self.play( - # FadeIn(ls0_dot) - # ) - - # # again scale everything down - # scale_construction_down() - - - - - - - - # # # # # # # # # # # # # # # - # # third construction step # - # # # # # # # # # # # # # # # - - - # lake3_radius = lake2_radius # not * 2 bc we have rescaled! - # lake3 = Circle(radius = lake3_radius, - # stroke_width = LAKE_STROKE_WIDTH, - # fill_color = LAKE_COLOR, - # fill_opacity = LAKE_OPACITY, - # stroke_color = LAKE_STROKE_COLOR - # ) - # lake3.move_to(lake_center) - - # self.play( - # FadeIn(lake3), - # FadeOut(lake1), - # FadeOut(ls0_dot), - # lake2.set_fill,{"opacity": 0}, - # FadeOut(t21), - # FadeOut(t22), - # FadeOut(t23), - # FadeOut(t24), - # ) - - # ls0_dot.move_to(lake_center) - # self.play(FadeIn(ls0_dot)) - - # ls31_loc = ls_location_for_angle(-3*TAU/16) - # ls32_loc = ls_location_for_angle(5*TAU/16) - # ls33_loc = ls_location_for_angle(-TAU/16) - # ls34_loc = ls_location_for_angle(7*TAU/16) - # ls35_loc = ls_location_for_angle(TAU/16) - # ls36_loc = ls_location_for_angle(9*TAU/16) - # ls37_loc = ls_location_for_angle(3*TAU/16) - # ls38_loc = ls_location_for_angle(-5*TAU/16) - - # t31 = Line(lake_center, ls31_loc) - # t32 = Line(lake_center, ls32_loc) - # t33 = Line(lake_center, ls33_loc) - # t34 = Line(lake_center, ls34_loc) - # t35 = Line(lake_center, ls35_loc) - # t36 = Line(lake_center, ls36_loc) - # t37 = Line(lake_center, ls37_loc) - # t38 = Line(lake_center, ls38_loc) - - - # leg31 = Line(OBSERVER_POINT,ls31_loc) - # leg32 = Line(OBSERVER_POINT,ls32_loc) - # leg33 = Line(OBSERVER_POINT,ls33_loc) - # leg34 = Line(OBSERVER_POINT,ls34_loc) - # leg35 = Line(OBSERVER_POINT,ls35_loc) - # leg36 = Line(OBSERVER_POINT,ls36_loc) - # leg37 = Line(OBSERVER_POINT,ls37_loc) - # leg38 = Line(OBSERVER_POINT,ls38_loc) - - - # # place lighthouses at intersection points - # ls31 = ls24 - # ls32 = ls24.copy() - # ls33 = ls22 - # ls34 = ls22.copy() - # ls35 = ls23 - # ls36 = ls23.copy() - # ls37 = ls21 - # ls38 = ls21.copy() - - # # - - # self.play( - # ShowCreation(t31), - # ShowCreation(t32), - # ) - - # self.play( - # ShowCreation(leg31), - # ShowCreation(leg32), - # ) - - # self.add(ls31, ls32) - # self.play( - # ls31.move_source_to,ls31_loc, - # ls32.move_source_to,ls32_loc, - # ) - - # # - - # self.play( - # ShowCreation(t33), - # ShowCreation(t34), - # ) - - # self.play( - # ShowCreation(leg33), - # ShowCreation(leg34), - # ) - - # self.add(ls33, ls34) - # self.play( - # ls33.move_source_to,ls33_loc, - # ls34.move_source_to,ls34_loc, - # ) - - # # - - # self.play( - # ShowCreation(t35), - # ShowCreation(t36), - # ) - - # self.play( - # ShowCreation(leg35), - # ShowCreation(leg36), - # ) - - # self.add(ls35, ls36) - # self.play( - # ls35.move_source_to,ls35_loc, - # ls36.move_source_to,ls36_loc, - # ) - - # # - - # self.play( - # ShowCreation(t37), - # ShowCreation(t38), - # ) - - # self.play( - # ShowCreation(leg37), - # ShowCreation(leg38), - # ) - - # self.add(ls37, ls38) - # self.play( - # ls37.move_source_to,ls37_loc, - # ls38.move_source_to,ls38_loc, - # ) - - - - - - - - - - - - # self.play( - # FadeOut(lake1), - # ) - - # self.play( - # ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - # ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - # ) + self.play( + FadeOut(one_left), + FadeOut(one_right), + FadeOut(diameter_text), + FadeOut(arc_left), + FadeOut(arc_right) + ) @@ -2304,14 +1997,14 @@ class PondScene(Scene): def angle_for_index(i,step): - return TAU/2**step * (i - 0.25) + 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 = lake_center + 2 * LAKE0_RADIUS * radial_vector + position = self.lake_center + self.lake_radius * radial_vector if scaled_down: return position.scale_about_point(OBSERVER_POINT,0.5) @@ -2319,45 +2012,49 @@ class PondScene(Scene): return position - def split_light_source(i, step): + def split_light_source(i, step, show_steps = True, run_time = 1): - ls_new_loc1 = position_for_index(i,step) - ls_new_loc2 = position_for_index(i + 2**step,step) + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) hyp = Mobject() - hyp1 = Line(lake_center,ls_new_loc1) - hyp2 = Line(lake_center,ls_new_loc2) - hyp.add(hyp1,hyp2) + 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) - self.play( - ShowCreation(hyp) - ) + if show_steps == True: + self.play( + ShowCreation(hyp, run_time = run_time) + ) leg1 = Line(OBSERVER_POINT,ls_new_loc1) leg2 = Line(OBSERVER_POINT,ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) - self.play( - ShowCreation(leg1), - ShowCreation(leg2), - ) + if show_steps == True: + self.play( + ShowCreation(leg1, run_time = run_time), + ShowCreation(leg2, run_time = run_time), + ) ls1 = self.light_sources_array[i] ls2 = ls1.copy() self.add(ls2) + self.additional_light_sources.append(ls2) self.play( - ls1.move_source_to,ls_new_loc1, - ls2.move_source_to,ls_new_loc2, + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), ) - def construction_step(n, scale_down = True): + def construction_step(n, scale_down = True, show_steps = True, run_time = 1, + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2373,27 +2070,36 @@ class PondScene(Scene): # these are mobjects! + + # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + # create a new, outer lake - new_outer_lake = Circle(radius = 2 * LAKE0_RADIUS, + 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(lake_center) + new_outer_lake.move_to(self.lake_center) - self.play( - FadeIn(new_outer_lake), - FadeOut(ls0_dot), - ) + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) - # fade out all of the hypotenuses and altitudes - self.play( - FadeOut(self.hypotenuses), - FadeOut(self.altitudes), - FadeOut(self.inner_lake) - ) + self.wait() self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake @@ -2405,7 +2111,7 @@ class PondScene(Scene): self.new_hypotenuses = [] for i in range(2**n): - split_light_source(i, step = n) + split_light_source(i, step = n, show_steps = show_steps, run_time = run_time) @@ -2427,20 +2133,50 @@ class PondScene(Scene): self.light_sources_array.append(ls) # update scene - self.add(self.light_sources,self.legs,self.hypotenuses,self.altitudes) + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) - # scale down - if scale_down: - self.play( - self.light_sources.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, ) + self.wait() + + if show_steps == True: + self.play(FadeOut(ls0_dot)) + + # scale down + if scale_down: + + indicator_wiggle() + + if show_steps == True: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + self.legs.scale_about_point,0.5,OBSERVER_POINT, + self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, + self.altitudes.scale_about_point,0.5,OBSERVER_POINT, + ) + else: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + ) + + else: + # update the lake center and the radius + self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP + self.lake_radius *= 2 @@ -2448,19 +2184,20 @@ class PondScene(Scene): - lake_center = ls0_loc = ls0.get_source_point() + + self.lake_center = ls0_loc = ls0.get_source_point() self.inner_lake = Mobject() self.outer_lake = lake0 self.legs = Mobject() - self.legs.add(Line(OBSERVER_POINT,lake_center)) + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) self.altitudes = Mobject() self.hypotenuses = Mobject() self.light_sources_array = [ls0] self.light_sources = Mobject() self.light_sources.add(ls0) - + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... self.add(self.inner_lake, self.outer_lake, @@ -2468,14 +2205,26 @@ class PondScene(Scene): 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, scale_down = True) - indicator_wiggle() + for i in range(3): + construction_step(i, scale_down = True) + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + for i in range(3,6): + construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, + simultaneous_splitting = True) From 904cded16da096486a93e728ab17107426fe0011 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 15 Feb 2018 21:16:18 +0100 Subject: [PATCH 11/73] more tweaks in PondScene --- active_projects/basel.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 4020dd0f..144088cf 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1892,7 +1892,7 @@ class PondScene(Scene): ls0 = LightSource() ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) - self.add(lake0,morty,indicator,obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) self.wait() @@ -1933,6 +1933,9 @@ class PondScene(Scene): SwitchOn(ls0.ambient_light), lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, ) + + self.play(FadeIn(indicator)) + self.play( indicator.set_intensity,0.5 ) @@ -1949,7 +1952,7 @@ class PondScene(Scene): self.play( ShowCreation(diameter), Write(diameter_text), - FadeOut(obs_dot), + #FadeOut(obs_dot), FadeOut(ls0_dot) ) @@ -2017,7 +2020,7 @@ class PondScene(Scene): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) - hyp = Mobject() + hyp = VMobject() hyp1 = Line(self.lake_center,ls_new_loc1) hyp2 = Line(self.lake_center,ls_new_loc2) hyp.add(hyp2,hyp1) @@ -2078,6 +2081,10 @@ class PondScene(Scene): FadeOut(self.altitudes), FadeOut(self.inner_lake) ) + else: + self.play( + FadeOut(self.inner_lake) + ) # create a new, outer lake @@ -2118,13 +2125,13 @@ class PondScene(Scene): # collect the newly created mobs (in arrays) # into the appropriate Mobject containers - self.legs = Mobject() + self.legs = VMobject() for leg in self.new_legs_1: self.legs.add(leg) for leg in self.new_legs_2: self.legs.add(leg) - self.hypotenuses = Mobject() + self.hypotenuses = VMobject() for hyp in self.new_hypotenuses: self.hypotenuses.add(hyp) @@ -2187,14 +2194,14 @@ class PondScene(Scene): self.lake_center = ls0_loc = ls0.get_source_point() - self.inner_lake = Mobject() + self.inner_lake = VMobject() self.outer_lake = lake0 - self.legs = Mobject() + self.legs = VMobject() self.legs.add(Line(OBSERVER_POINT,self.lake_center)) - self.altitudes = Mobject() - self.hypotenuses = Mobject() + self.altitudes = VMobject() + self.hypotenuses = VMobject() self.light_sources_array = [ls0] - self.light_sources = Mobject() + self.light_sources = VMobject() self.light_sources.add(ls0) self.lake_radius = 2 * LAKE0_RADIUS # don't ask... @@ -2222,7 +2229,7 @@ class PondScene(Scene): FadeOut(self.legs) ) - for i in range(3,6): + for i in range(3,10): construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, simultaneous_splitting = True) From 69d84e270a80f901ba9c89a1f47aeb9d6ff88f2b Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 16 Feb 2018 11:40:42 +0100 Subject: [PATCH 12/73] tweaking IPTScene1 --- active_projects/basel.py | 41 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 144088cf..44c4a342 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1744,24 +1744,32 @@ class IPTScene1(PiCreatureScene): screen_width_ap = screen_width_a * length_a / length_c screen_width_bp = screen_width_b * length_b / length_c screen_width_c = SCREEN_SCALE * length_c - - screen1 = Rectangle(width = screen_width_b, height = 0.2, stroke_width = 0, fill_opacity = 1.0) - screen1.next_to(C,RIGHT,buff = 0) - - ls1.set_screen(screen1) - screen_tracker = ScreenTracker(ls1) - self.add(screen_tracker) - self.remove(ls1.spotlight) + screen_thickness_after = 0.2 + screen_thickness_a = screen_thickness_after * length_c/length_a + screen_thickness_b = screen_thickness_after * length_c/length_b + + screen1 = Rectangle(width = screen_width_b, + height = screen_thickness_b, + stroke_width = 0, + fill_opacity = 1.0) + screen1.next_to(C,RIGHT+DOWN,buff = 0) + self.add_foreground_mobject(morty) self.play( FadeIn(screen1) ) + self.add_foreground_mobject(screen1) self.add_foreground_mobject(line_a) self.add_foreground_mobject(line_b) + ls1.set_screen(screen1) + screen_tracker = ScreenTracker(ls1) + self.remove(ls1.spotlight) + self.add(screen_tracker) + self.play( SwitchOn(ls1.spotlight), FadeIn(ls1.shadow), @@ -1790,8 +1798,8 @@ class IPTScene1(PiCreatureScene): # add and move the second light source and screen ls2 = ls1.deepcopy() - ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, height = 0., stroke_width = 10, fill_opacity = 1) +# ls2.move_source_to(A) + screen2 = Rectangle(width = screen_width_a, height = 0.2, stroke_width = 0, fill_opacity = 1) screen2.rotate(-TAU/4) screen2.next_to(C,UP,buff = 0) @@ -1825,7 +1833,7 @@ class IPTScene1(PiCreatureScene): screen2p = screen2.deepcopy() screen2pp = screen2.deepcopy() angle = np.arccos(length_a / length_c) - screen2p.stretch_to_fit_width(screen_width_ap) + screen2p.stretch_to_fit_height(screen_width_ap) screen2p.rotate(angle) # we can reuse the translation vector screen2p.shift(vector) @@ -1836,12 +1844,11 @@ class IPTScene1(PiCreatureScene): Transform(screen2,screen2p) ) - - # now transform both screens back - self.play( - Transform(screen1, screen1pp), - Transform(screen2, screen2pp), - ) + # # now transform both screens back + # self.play( + # Transform(screen1, screen1pp), + # Transform(screen2, screen2pp), + # ) From 29cdd3e8d7dafebdca9e33008900d9d1a39afe6d Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 16 Feb 2018 14:09:13 +0100 Subject: [PATCH 13/73] tweaked IPTScene1, wrote IPTScene2 (inset formula) --- active_projects/basel.py | 136 +++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 41 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 44c4a342..d37e68ed 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1502,7 +1502,8 @@ class BackToEulerSumScene(PiCreatureScene): sum_indicator.move_to(collection_point) self.play( - Transform(collected_indicators,sum_indicator) + FadeOut(collected_indicators), + FadeIn(sum_indicator) ) @@ -1697,25 +1698,24 @@ class IPTScene1(PiCreatureScene): def construct(self): SCREEN_SCALE = 0.1 + SCREEN_THICKNESS = 0.2 morty = self.get_primary_pi_creature() morty.scale(0.3).flip() right_pupil = morty.pupils[1] morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + self.add_foreground_mobject(morty) - line_a = VMobject() - line_a.set_points_as_corners([B,C]) - line_b = VMobject() - line_b.set_points_as_corners([A,C]) - line_c = VMobject() - line_c.set_points_as_corners([A,B]) - line_h = VMobject() - line_h.set_points_as_corners([C,H]) + stroke_width = 6 + line_a = Line(B,C,stroke_width = stroke_width) + line_b = Line(A,C,stroke_width = stroke_width) + line_c = Line(A,B,stroke_width = stroke_width) + line_h = Line(C,H,stroke_width = stroke_width) - length_a = np.linalg.norm(B - C) - length_b = np.linalg.norm(A - C) - length_c = np.linalg.norm(A - B) - length_h = np.linalg.norm(C - H) + length_a = line_a.get_length() + length_b = line_b.get_length() + length_c = line_c.get_length() + length_h = line_h.get_length() label_a = TexMobject("a") label_a.next_to(line_a, LEFT, buff = 0.5) @@ -1724,8 +1724,16 @@ class IPTScene1(PiCreatureScene): label_h = TexMobject("h") label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) - self.add(line_a, line_b, line_c, label_a, label_b, line_h) + self.add_foreground_mobject(line_a) + self.add_foreground_mobject(line_b) + self.add_foreground_mobject(line_c) + self.add_foreground_mobject(line_h) + self.add_foreground_mobject(label_a) + self.add_foreground_mobject(label_b) + self.add_foreground_mobject(label_h) + self.add_foreground_mobject(morty) + ls1 = LightSource(radius = 10) ls1.move_source_to(B) @@ -1745,35 +1753,30 @@ class IPTScene1(PiCreatureScene): screen_width_bp = screen_width_b * length_b / length_c screen_width_c = SCREEN_SCALE * length_c - screen_thickness_after = 0.2 - screen_thickness_a = screen_thickness_after * length_c/length_a - screen_thickness_b = screen_thickness_after * length_c/length_b + screen_thickness_a = SCREEN_THICKNESS + screen_thickness_b = SCREEN_THICKNESS screen1 = Rectangle(width = screen_width_b, height = screen_thickness_b, stroke_width = 0, fill_opacity = 1.0) - screen1.next_to(C,RIGHT+DOWN,buff = 0) + screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) self.add_foreground_mobject(morty) self.play( FadeIn(screen1) ) - self.add_foreground_mobject(screen1) - self.add_foreground_mobject(line_a) - self.add_foreground_mobject(line_b) ls1.set_screen(screen1) screen_tracker = ScreenTracker(ls1) - self.remove(ls1.spotlight) self.add(screen_tracker) + #self.add(ls1.shadow) self.play( SwitchOn(ls1.spotlight), - FadeIn(ls1.shadow), - ls1.dim_ambient, + SwitchOff(ls1.ambient_light) ) @@ -1798,11 +1801,19 @@ class IPTScene1(PiCreatureScene): # add and move the second light source and screen ls2 = ls1.deepcopy() -# ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, height = 0.2, stroke_width = 0, fill_opacity = 1) + ls2.move_source_to(A) + screen2 = Rectangle(width = screen_width_a, + height = screen_thickness_a, + stroke_width = 0, + fill_opacity = 1.0) screen2.rotate(-TAU/4) - screen2.next_to(C,UP,buff = 0) + screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) + self.play( + FadeIn(screen2) + ) + self.add_foreground_mobject(screen2) + self.add_foreground_mobject(morty) # the same scene adding sequence as before ls2.set_screen(screen2) @@ -1813,17 +1824,10 @@ class IPTScene1(PiCreatureScene): SwitchOn(ls2.ambient_light) ) - self.play( - FadeIn(screen2) - ) - self.add_foreground_mobject(screen2) - self.add_foreground_mobject(line_a) - self.add_foreground_mobject(line_b) self.play( SwitchOn(ls2.spotlight), - FadeIn(ls2.shadow), - ls2.dim_ambient + SwitchOff(ls2.ambient_light) ) @@ -1834,7 +1838,7 @@ class IPTScene1(PiCreatureScene): screen2pp = screen2.deepcopy() angle = np.arccos(length_a / length_c) screen2p.stretch_to_fit_height(screen_width_ap) - screen2p.rotate(angle) + screen2p.rotate(angle, about_point = C + screen_width_ap * UP) # we can reuse the translation vector screen2p.shift(vector) @@ -1844,13 +1848,63 @@ class IPTScene1(PiCreatureScene): Transform(screen2,screen2p) ) - # # now transform both screens back - # self.play( - # Transform(screen1, screen1pp), - # Transform(screen2, screen2pp), - # ) + # now transform both screens back + self.play( + Transform(screen1, screen1pp), + Transform(screen2, screen2pp), + ) +class IPTScene2(Scene): + + def construct(self): + + intensity1 = 0.3 + intensity2 = 0.2 + formula_scale = 01.2 + indy_radius = 1 + + indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy1.set_intensity(intensity1) + reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) + indy1.add(reading1) + + indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy2.set_intensity(intensity2) + reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) + indy2.add(reading2) + + indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy3.set_intensity(intensity1 + intensity2) + reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) + indy3.add(reading3) + + plus_sign = TexMobject("+").scale(formula_scale) + equals_sign = TexMobject("=").scale(formula_scale) + + plus_sign.next_to(indy1, RIGHT) + indy2.next_to(plus_sign, RIGHT) + equals_sign.next_to(indy2, RIGHT) + indy3.next_to(equals_sign, RIGHT) + + + formula = VGroup( + indy1, plus_sign, indy2, equals_sign, indy3 + ) + + formula.move_to(ORIGIN) + + self.play(FadeIn(indy1)) + self.play(FadeIn(plus_sign), FadeIn(indy2)) + self.play(FadeIn(equals_sign), FadeIn(indy3)) + + buffer = 1.5 + box = Rectangle(width = formula.get_width() * buffer, + height = formula.get_height() * buffer) + box.move_to(formula) + text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) + text.next_to(box,UP) + self.play(ShowCreation(box),Write(text)) class PondScene(Scene): From c2468865e2ebc6fca3882da467d2018188551b13 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 16 Feb 2018 16:49:38 +0100 Subject: [PATCH 14/73] tweaking the zoomed IPTScene1 --- active_projects/basel.py | 102 ++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index d37e68ed..ec4daf75 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -661,6 +661,8 @@ class FirstLighthouseScene(PiCreatureScene): + + @@ -999,17 +1001,19 @@ class ScreenShapingScene(ThreeDScene): def construct(self): + self.force_skipping() self.setup_elements() - # self.deform_screen() - # self.create_brightness_rect() - # self.slant_screen() - # self.unslant_screen() - # self.left_shift_screen_while_showing_light_indicator() - # self.add_distance_arrow() - # self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() - # self.left_shift_again() - - # self.morph_into_3d() + self.deform_screen() + self.create_brightness_rect() + self.slant_screen() + self.unslant_screen() + self.left_shift_screen_while_showing_light_indicator() + self.add_distance_arrow() + self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() + self.left_shift_again() + self.revert_to_original_skipping_status() + + self.morph_into_3d() def setup_elements(self): @@ -1247,8 +1251,8 @@ class ScreenShapingScene(ThreeDScene): def morph_into_3d(self): - #axes = ThreeDAxes() - #self.add(axes) + axes = ThreeDAxes() + self.add(axes) phi0 = self.camera.get_phi() # default is 0 degs theta0 = self.camera.get_theta() # default is -90 degs @@ -1271,18 +1275,20 @@ class ScreenShapingScene(ThreeDScene): projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) new_screen0 = Rectangle(height = self.screen_height, - width = 0.5, stroke_color = RED) + width = 3, stroke_color = RED) new_screen0.rotate(TAU/4,axis = DOWN) new_screen0.move_to(self.screen.get_center()) self.add(new_screen0) self.remove(self.screen) self.light_source.set_screen(new_screen0) - # # new_screen = new_screen0.deepcopy() - # # new_screen.width = new_screen.height + new_screen = new_screen0.deepcopy() + new_screen.width = new_screen.height + + self.play(Transform(new_screen0,new_screen)) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - # #Transform(new_screen0,new_screen) + ) self.wait() @@ -1697,14 +1703,31 @@ class IPTScene1(PiCreatureScene): def construct(self): + show_detail = True + SCREEN_SCALE = 0.1 SCREEN_THICKNESS = 0.2 + + # use the following for the zoomed inset + if show_detail: + self.camera.space_shape = (0.02 * SPACE_HEIGHT, 0.02 * SPACE_WIDTH) + self.camera.space_center = C + SCREEN_SCALE = 0.01 + SCREEN_THICKNESS = 0.02 + + + + + morty = self.get_primary_pi_creature() + self.remove(morty) morty.scale(0.3).flip() right_pupil = morty.pupils[1] morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - self.add_foreground_mobject(morty) + + if not show_detail: + self.add_foreground_mobject(morty) stroke_width = 6 line_a = Line(B,C,stroke_width = stroke_width) @@ -1732,16 +1755,18 @@ class IPTScene1(PiCreatureScene): self.add_foreground_mobject(label_b) self.add_foreground_mobject(label_h) - self.add_foreground_mobject(morty) + if not show_detail: + self.add_foreground_mobject(morty) ls1 = LightSource(radius = 10) ls1.move_source_to(B) self.add(ls1.lighthouse) - self.play( - SwitchOn(ls1.ambient_light) - ) + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) self.wait() @@ -1762,7 +1787,8 @@ class IPTScene1(PiCreatureScene): fill_opacity = 1.0) screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) - self.add_foreground_mobject(morty) + if not show_detail: + self.add_foreground_mobject(morty) self.play( FadeIn(screen1) @@ -1774,6 +1800,11 @@ class IPTScene1(PiCreatureScene): self.add(screen_tracker) #self.add(ls1.shadow) + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) + self.play( SwitchOn(ls1.spotlight), SwitchOff(ls1.ambient_light) @@ -1787,12 +1818,11 @@ class IPTScene1(PiCreatureScene): screen1pp = screen1.deepcopy() #self.add(screen1p) angle = np.arccos(length_b / length_c) - vector = (H - C) * SCREEN_SCALE * 0.5 - + screen1p.stretch_to_fit_width(screen_width_bp) - screen1p.rotate(-angle) - screen1p.shift(vector) - + screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) + screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) + self.play( ls1.move_source_to,H, @@ -1813,17 +1843,21 @@ class IPTScene1(PiCreatureScene): FadeIn(screen2) ) self.add_foreground_mobject(screen2) - self.add_foreground_mobject(morty) + + if not show_detail: + self.add_foreground_mobject(morty) # the same scene adding sequence as before ls2.set_screen(screen2) screen_tracker2 = ScreenTracker(ls2) self.add(screen_tracker2) - self.play( - SwitchOn(ls2.ambient_light) - ) + if not show_detail: + self.play( + SwitchOn(ls2.ambient_light) + ) + self.wait() self.play( SwitchOn(ls2.spotlight), @@ -1838,9 +1872,10 @@ class IPTScene1(PiCreatureScene): screen2pp = screen2.deepcopy() angle = np.arccos(length_a / length_c) screen2p.stretch_to_fit_height(screen_width_ap) - screen2p.rotate(angle, about_point = C + screen_width_ap * UP) + screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) + screen2p.rotate(angle, about_point = C + screen_width_a * UP) # we can reuse the translation vector - screen2p.shift(vector) + # screen2p.shift(vector) self.play( ls2.move_source_to,H, @@ -1855,6 +1890,7 @@ class IPTScene1(PiCreatureScene): ) + class IPTScene2(Scene): def construct(self): From 79fe27a16e515d075d278355626950fbd6022a64 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 16 Feb 2018 17:53:55 +0100 Subject: [PATCH 15/73] cleanup in ScreenShapingScene, in prep for 3D scene --- active_projects/basel.py | 44 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index ec4daf75..b46ed878 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1001,7 +1001,6 @@ class ScreenShapingScene(ThreeDScene): def construct(self): - self.force_skipping() self.setup_elements() self.deform_screen() self.create_brightness_rect() @@ -1011,8 +1010,7 @@ class ScreenShapingScene(ThreeDScene): self.add_distance_arrow() self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() - self.revert_to_original_skipping_status() - + self.morph_into_3d() @@ -1026,11 +1024,10 @@ class ScreenShapingScene(ThreeDScene): # screen self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], path_arc = 0, num_arc_anchors = 10) - - + # light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,5,1), + opacity_function = inverse_quadratic(1,2,1), num_levels = NUM_LEVELS, radius = 10, #screen = self.screen @@ -1043,32 +1040,32 @@ class ScreenShapingScene(ThreeDScene): self.spotlight = self.light_source.spotlight self.lighthouse = self.light_source.lighthouse - screen_tracker = ScreenTracker(self.light_source) - self.add(screen_tracker,self.light_source.shadow) - + #self.add_foreground_mobject(self.light_source.shadow) # Morty self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) # Add everything to the scene - self.add(self.ambient_light, self.lighthouse) - #self.add_foreground_mobject(self.morty) + self.add(self.lighthouse) self.wait() self.play(FadeIn(self.screen)) self.wait() self.add_foreground_mobject(self.screen) + self.add_foreground_mobject(self.morty) + + self.play(SwitchOn(self.ambient_light)) - dimmed_ambient_light = self.ambient_light.copy() - dimmed_ambient_light.dimming(AMBIENT_DIMMED) - #self.light_source.set_max_opacity_spotlight(0.001) self.play( - self.light_source.set_max_opacity_spotlight,1.0, # this hides Morty for a moment, why? - Transform(self.ambient_light,dimmed_ambient_light), - FadeIn(self.light_source.shadow), - ) + SwitchOn(self.spotlight), + self.light_source.dim_ambient + ) + + screen_tracker = ScreenTracker(self.light_source) + self.add(screen_tracker) + self.wait() @@ -1227,7 +1224,7 @@ class ScreenShapingScene(ThreeDScene): self.play( ReplacementTransform(self.arrow,self.new_arrow), ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), - #ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), + ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), # this should trigger ChangingDecimal, but it doesn't @@ -1275,22 +1272,27 @@ class ScreenShapingScene(ThreeDScene): projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) new_screen0 = Rectangle(height = self.screen_height, - width = 3, stroke_color = RED) + width = 0.1, stroke_color = RED) new_screen0.rotate(TAU/4,axis = DOWN) new_screen0.move_to(self.screen.get_center()) self.add(new_screen0) self.remove(self.screen) self.light_source.set_screen(new_screen0) + + self.light_source.set_camera(self.camera) + + new_screen = new_screen0.deepcopy() new_screen.width = new_screen.height - self.play(Transform(new_screen0,new_screen)) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), ) + #self.play(Transform(new_screen0,new_screen)) + self.wait() From 75fd78cefce434269f69c1e5db8ad6c36d97f951 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 17 Feb 2018 20:09:09 +0100 Subject: [PATCH 16/73] Lighthouse now also stable under camera rotations --- topics/light.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/topics/light.py b/topics/light.py index 15c15c5f..40c7ef10 100644 --- a/topics/light.py +++ b/topics/light.py @@ -185,8 +185,14 @@ class LightSource(VMobject): self.spotlight.radius = new_radius def update(self): + + M = z_to_vector(self.spotlight.projection_direction()) + R = np.array([[0,-1,0],[1,0,0],[0,0,1]]) + self.rotation_matrix = np.dot(M,R) + self.spotlight.update_sectors() self.update_shadow() + self.update_lighthouse() def get_source_point(self): return self.source_point.get_location() @@ -208,8 +214,10 @@ class LightSource(VMobject): np.reshape(projected_source,(1,3)), axis = 0 ) - rotation_matrix = z_to_vector(self.spotlight.projection_direction()) - back_rotation_matrix = rotation_matrix.T # i. e. its inverse + rotation_matrix_here = z_to_vector(self.spotlight.projection_direction()) + # self.rotation matrix contains an extra 90 degree rotation + # (this is apparently necessary to orient the lighthouse) + back_rotation_matrix = rotation_matrix_here.T # i. e. its inverse rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T) # these points now should all have z = 0 @@ -234,7 +242,7 @@ class LightSource(VMobject): hull_mobject = VMobject() hull_mobject.set_points_as_corners(hull) - hull_mobject.apply_matrix(rotation_matrix) + hull_mobject.apply_matrix(rotation_matrix_here) anchors = hull_mobject.get_anchors() @@ -260,6 +268,13 @@ class LightSource(VMobject): self.shadow.mark_paths_closed = True + def update_lighthouse(self): + + new_lh = Lighthouse().move_to(ORIGIN) + new_lh.apply_matrix(self.rotation_matrix) + new_lh.shift(self.get_source_point()) + self.lighthouse.submobjects = new_lh.submobjects + class SwitchOn(LaggedStart): CONFIG = { @@ -299,6 +314,7 @@ class Lighthouse(SVGMobject): def move_to(self,point): self.next_to(point, DOWN, buff = 0) + return self class AmbientLight(VMobject): @@ -393,14 +409,14 @@ class AmbientLight(VMobject): class Spotlight(VMobject): CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "source_point" : VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, "radius" : 5.0, "screen" : None, - "camera": None + "camera" : None } def projection_direction(self): From f178a6981cbc4f3f7db47d8aa24a83c1a3386333 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 17 Feb 2018 21:13:50 +0100 Subject: [PATCH 17/73] Ambient light now also stable under camera rotations --- topics/light.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/topics/light.py b/topics/light.py index 40c7ef10..2836c8d1 100644 --- a/topics/light.py +++ b/topics/light.py @@ -190,6 +190,7 @@ class LightSource(VMobject): R = np.array([[0,-1,0],[1,0,0],[0,0,1]]) self.rotation_matrix = np.dot(M,R) + self.update_ambient_light() self.spotlight.update_sectors() self.update_shadow() self.update_lighthouse() @@ -276,6 +277,22 @@ class LightSource(VMobject): self.lighthouse.submobjects = new_lh.submobjects + def update_ambient_light(self): + + new_ambient_light = AmbientLight( + source_point = VectorizedPoint(location = ORIGIN), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_ambient + ) + new_ambient_light.apply_matrix(self.rotation_matrix) + new_ambient_light.move_source_to(self.get_source_point()) + self.ambient_light.submobjects = new_ambient_light.submobjects + + + class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, From 7eca959e2f2c65a17db91ee1ecca66070db53148 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 19 Feb 2018 09:17:00 +0100 Subject: [PATCH 18/73] incremental progress --- active_projects/basel.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index b46ed878..5267be08 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1001,6 +1001,7 @@ class ScreenShapingScene(ThreeDScene): def construct(self): + #self.force_skipping() self.setup_elements() self.deform_screen() self.create_brightness_rect() @@ -1010,8 +1011,10 @@ class ScreenShapingScene(ThreeDScene): self.add_distance_arrow() self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() + #self.revert_to_original_skipping_status() self.morph_into_3d() + self.prove_inverse_square_law() def setup_elements(self): @@ -1248,6 +1251,8 @@ class ScreenShapingScene(ThreeDScene): def morph_into_3d(self): + self.play(FadeOut(self.morty)) + axes = ThreeDAxes() self.add(axes) @@ -1255,11 +1260,6 @@ class ScreenShapingScene(ThreeDScene): theta0 = self.camera.get_theta() # default is -90 degs distance0 = self.camera.get_distance() - # this is an ugly hack bc remove, FadeOut and SwitchOff don't work - self.play( - self.light_source.set_max_opacity_ambient,0.001 - ) - phi1 = 60 * DEGREES # angle from zenith (0 to 180) theta1 = -135 * DEGREES # azimuth (0 to 360) distance1 = distance0 @@ -1272,7 +1272,7 @@ class ScreenShapingScene(ThreeDScene): projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED) + width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) new_screen0.rotate(TAU/4,axis = DOWN) new_screen0.move_to(self.screen.get_center()) self.add(new_screen0) @@ -1282,19 +1282,36 @@ class ScreenShapingScene(ThreeDScene): self.light_source.set_camera(self.camera) - new_screen = new_screen0.deepcopy() - new_screen.width = new_screen.height + new_screen = Rectangle(height = self.screen_height, + width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen.rotate(TAU/4,axis = DOWN) + new_screen.move_to(self.screen.get_center()) + self.add_foreground_mobject(self.ambient_light) + self.add_foreground_mobject(self.spotlight) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), ) - #self.play(Transform(new_screen0,new_screen)) + self.play(Transform(new_screen0,new_screen)) self.wait() + self.unit_screen = new_screen0 # better name + + + + + def prove_inverse_square_law(self): + + unit_screen_copy = self.unit_screen.copy() + fourfold_screen = self.unit_screen.copy() + fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) + self.play( + Transform(self.unit_screen, fourfold_screen) + ) From ea6dc6957778d7a205cdcd3bbddc3311ef07c36e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 19 Feb 2018 15:30:21 +0100 Subject: [PATCH 19/73] edits that broke light.py --- topics/light.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/topics/light.py b/topics/light.py index 2836c8d1..f4e1fdcc 100644 --- a/topics/light.py +++ b/topics/light.py @@ -18,6 +18,7 @@ from mobject.svg_mobject import * from topics.three_dimensions import * from scipy.spatial import ConvexHull +from traceback import * LIGHT_COLOR = YELLOW @@ -79,16 +80,7 @@ class LightSource(VMobject): max_opacity = self.max_opacity_ambient ) if self.has_screen(): - self.spotlight = Spotlight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - screen = self.screen, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_spotlight, - camera = self.camera - ) + self.create_spotlight() else: self.spotlight = Spotlight() @@ -110,6 +102,18 @@ class LightSource(VMobject): else: return True + def create_spotlight(self): + self.spotlight = Spotlight( + source_point = VectorizedPoint(location = self.get_source_point()), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + screen = self.screen, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_spotlight, + camera = self.camera + ) + def dim_ambient(self): self.set_max_opacity_ambient(AMBIENT_DIMMED) @@ -130,21 +134,15 @@ class LightSource(VMobject): def set_screen(self, new_screen): + print "setting screen" + print_stack() if self.has_screen(): self.spotlight.screen = new_screen else: # Note: See below index = self.submobjects.index(self.spotlight) - camera = self.spotlight.camera self.remove(self.spotlight) - self.spotlight = Spotlight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - screen = new_screen, - camera = self.camera - ) + self.create_spotlight() self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end From 3b99207172d86f7212007fb5068ae8a424f6baff Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 19 Feb 2018 15:30:33 +0100 Subject: [PATCH 20/73] Revert "Ambient light now also stable under camera rotations" This reverts commit f178a6981cbc4f3f7db47d8aa24a83c1a3386333. --- topics/light.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/topics/light.py b/topics/light.py index f4e1fdcc..4934f588 100644 --- a/topics/light.py +++ b/topics/light.py @@ -188,7 +188,6 @@ class LightSource(VMobject): R = np.array([[0,-1,0],[1,0,0],[0,0,1]]) self.rotation_matrix = np.dot(M,R) - self.update_ambient_light() self.spotlight.update_sectors() self.update_shadow() self.update_lighthouse() @@ -275,22 +274,6 @@ class LightSource(VMobject): self.lighthouse.submobjects = new_lh.submobjects - def update_ambient_light(self): - - new_ambient_light = AmbientLight( - source_point = VectorizedPoint(location = ORIGIN), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_ambient - ) - new_ambient_light.apply_matrix(self.rotation_matrix) - new_ambient_light.move_source_to(self.get_source_point()) - self.ambient_light.submobjects = new_ambient_light.submobjects - - - class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, From b0d9a1eaac262a2c172466a1d59650cdc8260afd Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 19 Feb 2018 15:34:11 +0100 Subject: [PATCH 21/73] Revert "Lighthouse now also stable under camera rotations" This reverts commit 75fd78cefce434269f69c1e5db8ad6c36d97f951. --- topics/light.py | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/topics/light.py b/topics/light.py index 4934f588..46ff0c85 100644 --- a/topics/light.py +++ b/topics/light.py @@ -183,14 +183,8 @@ class LightSource(VMobject): self.spotlight.radius = new_radius def update(self): - - M = z_to_vector(self.spotlight.projection_direction()) - R = np.array([[0,-1,0],[1,0,0],[0,0,1]]) - self.rotation_matrix = np.dot(M,R) - self.spotlight.update_sectors() self.update_shadow() - self.update_lighthouse() def get_source_point(self): return self.source_point.get_location() @@ -212,10 +206,8 @@ class LightSource(VMobject): np.reshape(projected_source,(1,3)), axis = 0 ) - rotation_matrix_here = z_to_vector(self.spotlight.projection_direction()) - # self.rotation matrix contains an extra 90 degree rotation - # (this is apparently necessary to orient the lighthouse) - back_rotation_matrix = rotation_matrix_here.T # i. e. its inverse + rotation_matrix = z_to_vector(self.spotlight.projection_direction()) + back_rotation_matrix = rotation_matrix.T # i. e. its inverse rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T) # these points now should all have z = 0 @@ -240,7 +232,7 @@ class LightSource(VMobject): hull_mobject = VMobject() hull_mobject.set_points_as_corners(hull) - hull_mobject.apply_matrix(rotation_matrix_here) + hull_mobject.apply_matrix(rotation_matrix) anchors = hull_mobject.get_anchors() @@ -266,13 +258,6 @@ class LightSource(VMobject): self.shadow.mark_paths_closed = True - def update_lighthouse(self): - - new_lh = Lighthouse().move_to(ORIGIN) - new_lh.apply_matrix(self.rotation_matrix) - new_lh.shift(self.get_source_point()) - self.lighthouse.submobjects = new_lh.submobjects - class SwitchOn(LaggedStart): CONFIG = { @@ -312,7 +297,6 @@ class Lighthouse(SVGMobject): def move_to(self,point): self.next_to(point, DOWN, buff = 0) - return self class AmbientLight(VMobject): @@ -407,14 +391,14 @@ class AmbientLight(VMobject): class Spotlight(VMobject): CONFIG = { - "source_point" : VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, "radius" : 5.0, "screen" : None, - "camera" : None + "camera": None } def projection_direction(self): From 935ae923ca5716f8677c7aabd71a4c135cd8c6b4 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 19 Feb 2018 15:36:16 +0100 Subject: [PATCH 22/73] Revert "edits that broke light.py" This reverts commit ea6dc6957778d7a205cdcd3bbddc3311ef07c36e. --- topics/light.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/topics/light.py b/topics/light.py index 46ff0c85..15c15c5f 100644 --- a/topics/light.py +++ b/topics/light.py @@ -18,7 +18,6 @@ from mobject.svg_mobject import * from topics.three_dimensions import * from scipy.spatial import ConvexHull -from traceback import * LIGHT_COLOR = YELLOW @@ -80,7 +79,16 @@ class LightSource(VMobject): max_opacity = self.max_opacity_ambient ) if self.has_screen(): - self.create_spotlight() + self.spotlight = Spotlight( + source_point = VectorizedPoint(location = self.get_source_point()), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + screen = self.screen, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_spotlight, + camera = self.camera + ) else: self.spotlight = Spotlight() @@ -102,18 +110,6 @@ class LightSource(VMobject): else: return True - def create_spotlight(self): - self.spotlight = Spotlight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - screen = self.screen, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_spotlight, - camera = self.camera - ) - def dim_ambient(self): self.set_max_opacity_ambient(AMBIENT_DIMMED) @@ -134,15 +130,21 @@ class LightSource(VMobject): def set_screen(self, new_screen): - print "setting screen" - print_stack() if self.has_screen(): self.spotlight.screen = new_screen else: # Note: See below index = self.submobjects.index(self.spotlight) + camera = self.spotlight.camera self.remove(self.spotlight) - self.create_spotlight() + self.spotlight = Spotlight( + source_point = VectorizedPoint(location = self.get_source_point()), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + screen = new_screen, + camera = self.camera + ) self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end From 5e9ab3a6ce674ecd9c160d15f629a219dc4d89fe Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 20 Feb 2018 11:21:01 +0100 Subject: [PATCH 23/73] ScreenScalingScene --- active_projects/basel.py | 170 ++++++++++++++++++++++++++++++++++++++- topics/light.py | 9 ++- 2 files changed, 174 insertions(+), 5 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 5267be08..821a6bd5 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1030,11 +1030,14 @@ class ScreenShapingScene(ThreeDScene): # light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,5,1), num_levels = NUM_LEVELS, radius = 10, + max_opacity = 0.2 #screen = self.screen ) + self.light_source.set_max_opacity_spotlight(0.2) + self.light_source.set_screen(self.screen) self.light_source.move_source_to([-5,0,0]) @@ -1251,6 +1254,7 @@ class ScreenShapingScene(ThreeDScene): def morph_into_3d(self): + self.play(FadeOut(self.morty)) axes = ThreeDAxes() @@ -1289,11 +1293,13 @@ class ScreenShapingScene(ThreeDScene): self.add_foreground_mobject(self.ambient_light) self.add_foreground_mobject(self.spotlight) + self.add_foreground_mobject(self.light_source.shadow) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), ) + self.remove(self.spotlight) self.play(Transform(new_screen0,new_screen)) @@ -1303,16 +1309,176 @@ class ScreenShapingScene(ThreeDScene): - def prove_inverse_square_law(self): + def orientate(mob): + mob.move_to(self.unit_screen) + mob.rotate(TAU/4, axis = LEFT) + mob.rotate(TAU/4, axis = OUT) + mob.rotate(TAU/2, axis = LEFT) + return mob + unit_screen_copy = self.unit_screen.copy() fourfold_screen = self.unit_screen.copy() fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) + + self.remove(self.spotlight) + + + reading1 = TexMobject("1") + orientate(reading1) + + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + + self.play( Transform(self.unit_screen, fourfold_screen) ) + reading21 = TexMobject("{1\over 4}").scale(0.8) + orientate(reading21) + reading22 = reading21.deepcopy() + reading23 = reading21.deepcopy() + reading24 = reading21.deepcopy() + reading21.shift(0.5*OUT + 0.5*UP) + reading22.shift(0.5*OUT + 0.5*DOWN) + reading23.shift(0.5*IN + 0.5*UP) + reading24.shift(0.5*IN + 0.5*DOWN) + + + corners = fourfold_screen.get_anchors() + midpoint1 = (corners[0] + corners[1])/2 + midpoint2 = (corners[1] + corners[2])/2 + midpoint3 = (corners[2] + corners[3])/2 + midpoint4 = (corners[3] + corners[0])/2 + midline1 = Line(midpoint1, midpoint3) + midline2 = Line(midpoint2, midpoint4) + + self.play( + ShowCreation(midline1), + ShowCreation(midline2) + ) + + self.play( + FadeIn(reading21), + FadeIn(reading22), + FadeIn(reading23), + FadeIn(reading24), + ) + + self.wait() + + self.play( + FadeOut(reading21), + FadeOut(reading22), + FadeOut(reading23), + FadeOut(reading24), + FadeOut(midline1), + FadeOut(midline2) + ) + + ninefold_screen = unit_screen_copy.copy() + ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + + self.play( + Transform(self.unit_screen, ninefold_screen) + ) + + reading31 = TexMobject("{1\over 9}").scale(0.8) + orientate(reading31) + reading32 = reading31.deepcopy() + reading33 = reading31.deepcopy() + reading34 = reading31.deepcopy() + reading35 = reading31.deepcopy() + reading36 = reading31.deepcopy() + reading37 = reading31.deepcopy() + reading38 = reading31.deepcopy() + reading39 = reading31.deepcopy() + reading31.shift(IN + UP) + reading32.shift(IN) + reading33.shift(IN + DOWN) + reading34.shift(UP) + reading35.shift(ORIGIN) + reading36.shift(DOWN) + reading37.shift(OUT + UP) + reading38.shift(OUT) + reading39.shift(OUT + DOWN) + + corners = ninefold_screen.get_anchors() + midpoint11 = (2*corners[0] + corners[1])/3 + midpoint12 = (corners[0] + 2*corners[1])/3 + midpoint21 = (2*corners[1] + corners[2])/3 + midpoint22 = (corners[1] + 2*corners[2])/3 + midpoint31 = (2*corners[2] + corners[3])/3 + midpoint32 = (corners[2] + 2*corners[3])/3 + midpoint41 = (2*corners[3] + corners[0])/3 + midpoint42 = (corners[3] + 2*corners[0])/3 + midline11 = Line(midpoint11, midpoint32) + midline12 = Line(midpoint12, midpoint31) + midline21 = Line(midpoint21, midpoint42) + midline22 = Line(midpoint22, midpoint41) + + self.play( + ShowCreation(midline11), + ShowCreation(midline12), + ShowCreation(midline21), + ShowCreation(midline22), + ) + + self.play( + FadeIn(reading31), + FadeIn(reading32), + FadeIn(reading33), + FadeIn(reading34), + FadeIn(reading35), + FadeIn(reading36), + FadeIn(reading37), + FadeIn(reading38), + FadeIn(reading39), + ) + + + +class IndicatorScalingScene(Scene): + + def construct(self): + + unit_intensity = 0.6 + + indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator1.set_intensity(unit_intensity) + reading1 = TexMobject("1") + reading1.move_to(indicator1) + + + indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator2.shift(2*RIGHT) + indicator2.set_intensity(unit_intensity/4) + reading2 = TexMobject("{1\over 4}").scale(0.8) + reading2.move_to(indicator2) + + indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator3.shift(4*RIGHT) + indicator3.set_intensity(unit_intensity/9) + reading3 = TexMobject("{1\over 9}").scale(0.8) + reading3.move_to(indicator3) + + + self.play(FadeIn(indicator1)) + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + self.play(Transform(indicator1, indicator2)) + self.play(FadeIn(reading2)) + self.wait() + self.play(FadeOut(reading2)) + self.play(Transform(indicator1, indicator3)) + self.play(FadeIn(reading3)) + self.wait() + + diff --git a/topics/light.py b/topics/light.py index 15c15c5f..cb4f69c8 100644 --- a/topics/light.py +++ b/topics/light.py @@ -18,9 +18,10 @@ from mobject.svg_mobject import * from topics.three_dimensions import * from scipy.spatial import ConvexHull +from traceback import * -LIGHT_COLOR = YELLOW +LIGHT_COLOR = GREEN SHADOW_COLOR = BLACK SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 @@ -34,7 +35,6 @@ SPOTLIGHT_FULL = 0.9 SPOTLIGHT_DIMMED = 0.2 LIGHTHOUSE_HEIGHT = 0.8 -LIGHT_COLOR = YELLOW DEGREES = TAU/360 inverse_power_law = lambda maxint,scale,cutoff,exponent: \ @@ -162,6 +162,7 @@ class LightSource(VMobject): def move_source_to(self,point): + print_stack() apoint = np.array(point) v = apoint - self.get_source_point() # Note: As discussed, things stand to behave better if source @@ -369,11 +370,13 @@ class AmbientLight(VMobject): def dimming(self,new_alpha): + print "dimming" old_alpha = self.max_opacity self.max_opacity = new_alpha for submob in self.submobjects: old_submob_alpha = submob.fill_opacity new_submob_alpha = old_submob_alpha * new_alpha / old_alpha + print old_submob_alpha, new_submob_alpha submob.set_fill(opacity = new_submob_alpha) @@ -395,7 +398,7 @@ class Spotlight(VMobject): CONFIG = { "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, - "color" : LIGHT_COLOR, + "color" : GREEN, # LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, "radius" : 5.0, From 2d93c45624080b24a9b08d84359a400b1fcc01c6 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 20 Feb 2018 15:37:19 +0100 Subject: [PATCH 24/73] Pond now smoothly morphs into a number line --- active_projects/basel.py | 83 +++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 821a6bd5..0cd0dd54 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2329,10 +2329,21 @@ class PondScene(Scene): self.add(ls2) self.additional_light_sources.append(ls2) - self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), - ) + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.all(np.abs(ls_old_loc) < 10) + onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + show_animation = (onscreen_old or onscreen_1 or onscreen_2) + + if show_animation: + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) @@ -2511,18 +2522,70 @@ class PondScene(Scene): FadeOut(self.legs) ) - for i in range(3,10): + for i in range(3,5): construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, simultaneous_splitting = True) - - - - - + # Now create a straight number line and transform into it + MAX_N = 17 + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1), + unit_size = LAKE0_RADIUS * TAU/4 / 4, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(2.5 * DOWN) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.wait() + + origin_point = self.number_line.number_to_point(0) + nl_sources = VMobject() + pond_sources = VMobject() + + for i in range(-MAX_N,MAX_N+1): + anchor = self.number_line.number_to_point(2*i + 1) + ls = self.light_sources_array[i].copy() + ls.move_source_to(anchor) + nl_sources.add(ls) + pond_sources.add(self.light_sources_array[i].copy()) + + self.add(pond_sources) + self.remove(self.light_sources) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + width = 20, + height = 10, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + ).flip().next_to(origin_point,UP,buff = 0) + + + + self.play( + Transform(pond_sources,nl_sources), + Transform(self.outer_lake,open_sea), + FadeOut(self.inner_lake) + ) + self.play(FadeIn(self.number_line)) From fe1c3ca0f9cc6d372d12e9daaf511a2b3a65cd99 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 20 Feb 2018 17:44:33 +0100 Subject: [PATCH 25/73] 3D anims of LightSource fixed (everything faces the camera again) --- topics/light.py | 92 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/topics/light.py b/topics/light.py index cb4f69c8..55a7d136 100644 --- a/topics/light.py +++ b/topics/light.py @@ -21,7 +21,7 @@ from scipy.spatial import ConvexHull from traceback import * -LIGHT_COLOR = GREEN +LIGHT_COLOR = YELLOW SHADOW_COLOR = BLACK SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 @@ -62,7 +62,7 @@ class LightSource(VMobject): "opacity_function": inverse_quadratic(1,2,1), "max_opacity_ambient": AMBIENT_FULL, "max_opacity_spotlight": SPOTLIGHT_FULL, - "camera": None + "camera_mob": None } def generate_points(self): @@ -87,7 +87,7 @@ class LightSource(VMobject): screen = self.screen, opacity_function = self.opacity_function, max_opacity = self.max_opacity_spotlight, - camera = self.camera + camera_mob = self.camera_mob ) else: self.spotlight = Spotlight() @@ -124,9 +124,9 @@ class LightSource(VMobject): self.max_opacity_spotlight = new_opacity self.spotlight.dimming(new_opacity) - def set_camera(self,new_cam): - self.camera = new_cam - self.spotlight.camera = new_cam + def set_camera_mob(self,new_cam_mob): + self.camera_mob = new_cam_mob + self.spotlight.camera_mob = new_cam_mob def set_screen(self, new_screen): @@ -135,7 +135,7 @@ class LightSource(VMobject): else: # Note: See below index = self.submobjects.index(self.spotlight) - camera = self.spotlight.camera + camera_mob = self.spotlight.camera_mob self.remove(self.spotlight) self.spotlight = Spotlight( source_point = VectorizedPoint(location = self.get_source_point()), @@ -143,7 +143,7 @@ class LightSource(VMobject): num_levels = self.num_levels, radius = self.radius, screen = new_screen, - camera = self.camera + camera_mob = self.camera_mob ) self.spotlight.move_source_to(self.get_source_point()) @@ -162,7 +162,6 @@ class LightSource(VMobject): def move_source_to(self,point): - print_stack() apoint = np.array(point) v = apoint - self.get_source_point() # Note: As discussed, things stand to behave better if source @@ -186,12 +185,64 @@ class LightSource(VMobject): self.spotlight.radius = new_radius def update(self): + self.update_lighthouse() + self.update_ambient() self.spotlight.update_sectors() self.update_shadow() + + def update_lighthouse(self): + new_lh = Lighthouse() + new_lh.move_to(ORIGIN) + new_lh.apply_matrix(self.rotation_matrix()) + new_lh.shift(self.get_source_point()) + self.lighthouse.submobjects = new_lh.submobjects + + + def update_ambient(self): + new_ambient_light = AmbientLight( + source_point = VectorizedPoint(location = ORIGIN), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_ambient + ) + new_ambient_light.apply_matrix(self.rotation_matrix()) + new_ambient_light.move_source_to(self.get_source_point()) + self.ambient_light.submobjects = new_ambient_light.submobjects + + + def get_source_point(self): return self.source_point.get_location() + + def rotation_matrix(self): + + if self.camera_mob == None: + return np.eye(3) + + phi = self.camera_mob.get_center()[0] + theta = self.camera_mob.get_center()[1] + + + R1 = np.array([ + [1, 0, 0], + [0, np.cos(phi), -np.sin(phi)], + [0, np.sin(phi), np.cos(phi)] + ]) + + R2 = np.array([ + [np.cos(theta + TAU/4), -np.sin(theta + TAU/4), 0], + [np.sin(theta + TAU/4), np.cos(theta + TAU/4), 0], + [0, 0, 1] + ]) + + R = np.dot(R2, R1) + return R + + def update_shadow(self): point = self.get_source_point() @@ -209,11 +260,12 @@ class LightSource(VMobject): np.reshape(projected_source,(1,3)), axis = 0 ) - rotation_matrix = z_to_vector(self.spotlight.projection_direction()) + rotation_matrix = self.rotation_matrix() # z_to_vector(self.spotlight.projection_direction()) back_rotation_matrix = rotation_matrix.T # i. e. its inverse rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T) # these points now should all have z = 0 + point_cloud_2d = rotated_point_cloud_3d[:,:2] # now we can compute the convex hull hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw @@ -247,6 +299,7 @@ class LightSource(VMobject): ray1 = anchors[source_index - 1] - projected_source ray1 = ray1/np.linalg.norm(ray1) * 100 + ray2 = anchors[source_index] - projected_source ray2 = ray2/np.linalg.norm(ray2) * 100 outpoint1 = anchors[source_index - 1] + ray1 @@ -370,13 +423,11 @@ class AmbientLight(VMobject): def dimming(self,new_alpha): - print "dimming" old_alpha = self.max_opacity self.max_opacity = new_alpha for submob in self.submobjects: old_submob_alpha = submob.fill_opacity new_submob_alpha = old_submob_alpha * new_alpha / old_alpha - print old_submob_alpha, new_submob_alpha submob.set_fill(opacity = new_submob_alpha) @@ -403,7 +454,7 @@ class Spotlight(VMobject): "num_levels" : 10, "radius" : 5.0, "screen" : None, - "camera": None + "camera_mob": None } def projection_direction(self): @@ -411,11 +462,12 @@ class Spotlight(VMobject): # need to be sure that any 3d scene including a spotlight # somewhere assigns that spotlights "camera" attribute # to be the camera associated with that scene. - if self.camera == None: + if self.camera_mob == None: return OUT else: - v = self.camera.get_cartesian_coords() - return v/np.linalg.norm(v) + [phi, theta, r] = self.camera_mob.get_center() + v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)]) + return v #/np.linalg.norm(v) def project(self,point): v = self.projection_direction() @@ -578,14 +630,6 @@ class Spotlight(VMobject): submob.set_fill(opacity = alpha) -# Note: Stylistically, I typically keep all of the implementation for an -# update inside the relevant Animation or ContinualAnimation, rather than -# in the mobject. Things are fine the way you've done it, but sometimes -# I personally like a nice conceptual divide between all the things that -# determine how the mobject is displayed in a single moment (implement in -# the mobject's class itself) and all the things determining how that changes. -# -# Up to you though. class ScreenTracker(ContinualAnimation): From 59deed97aa5bc69dd0107a059f7a962bf44ff589 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 20 Feb 2018 21:20:55 +0100 Subject: [PATCH 26/73] added ArcHighLighterOverlayScene --- active_projects/basel.py | 80 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 0cd0dd54..8315a77a 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1441,6 +1441,7 @@ class ScreenShapingScene(ThreeDScene): + class IndicatorScalingScene(Scene): def construct(self): @@ -2297,7 +2298,7 @@ class PondScene(Scene): return position - def split_light_source(i, step, show_steps = True, run_time = 1): + def split_light_source(i, step, show_steps = True, run_time = 1, ls_size = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2350,7 +2351,7 @@ class PondScene(Scene): def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False): + simultaneous_splitting = False, ls_size = 1): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2411,7 +2412,12 @@ class PondScene(Scene): self.new_hypotenuses = [] for i in range(2**n): - split_light_source(i, step = n, show_steps = show_steps, run_time = run_time) + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + ls_size = ls_size + ) @@ -2589,6 +2595,74 @@ class PondScene(Scene): +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 1.3 + + Arc.__init__(self,angle,**kwargs) + + label = DecimalNumber(self.length, num_decimal_points = 0) + r = BUFFER * self.radius + theta = self.start_angle + self.angle/2 + label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) + + label.move_to(label_pos) + self.add(label) + + + + +class ArcHighlightOverlayScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + FLASH_TIME = 0.25 + + def flash_arcs(n): + + angle = TAU/2**n + arcs = [] + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) + + for i in range(1,2**n): + arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) + + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) + + self.play( + FadeIn(arcs[0], run_time = FLASH_TIME) + ) + + for i in range(1,2**n + 1): + self.play( + FadeOut(arcs[i-1], run_time = FLASH_TIME), + FadeIn(arcs[i], run_time = FLASH_TIME) + ) + + self.play( + FadeOut(arcs[2**n], run_time = FLASH_TIME), + ) + + + flash_arcs(3) From 18357e909bcf6700406ad8f168fb8de853bff809 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 20 Feb 2018 23:12:08 +0100 Subject: [PATCH 27/73] working on getting the pond's LS to scale correctly --- active_projects/basel.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 8315a77a..2e468e50 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -197,13 +197,16 @@ class ScaleLightSources(Transform): new_sp.scale(factor,about_point = about_point) submob.move_source_to(new_sp.get_location()) - ambient_of = copy_func(submob.ambient_light.opacity_function) - new_of = lambda r: ambient_of(r/factor) - submob.ambient_light.opacity_function = new_of + #ambient_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: ambient_of(r/factor) + #submob.ambient_light.opacity_function = new_of - spotlight_of = copy_func(submob.ambient_light.opacity_function) - new_of = lambda r: spotlight_of(r/factor) - submob.spotlight.change_opacity_function(new_of) + #spotlight_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: spotlight_of(r/factor) + #submob.spotlight.change_opacity_function(new_of) + + new_r = factor * submob.radius + submob.set_radius(new_r) new_r = factor * submob.ambient_light.radius submob.ambient_light.radius = new_r @@ -2298,7 +2301,7 @@ class PondScene(Scene): return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_size = 1): + def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2351,7 +2354,7 @@ class PondScene(Scene): def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_size = 1): + simultaneous_splitting = False, ls_radius = 1): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2416,7 +2419,7 @@ class PondScene(Scene): step = n, show_steps = show_steps, run_time = run_time, - ls_size = ls_size + ls_radius = ls_radius ) @@ -2479,6 +2482,12 @@ class PondScene(Scene): self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, ) + # update the radii bc they haven't done so themselves + # bc reasons... + for ls in self.light_sources_array: + r = ls.radius + ls.set_radius(r*0.5) + else: # update the lake center and the radius self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP @@ -2519,8 +2528,12 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] + ls_radius = 25.0 + for i in range(3): - construction_step(i, scale_down = True) + construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) + + return self.play( FadeOut(self.altitudes), @@ -2530,7 +2543,7 @@ class PondScene(Scene): for i in range(3,5): construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True) + simultaneous_splitting = True, ls_radius = ls_radius/2**3) From e8d9f6d6e5cccd04da0882e1c6d2053d9d4bf996 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 21 Feb 2018 18:27:50 +0100 Subject: [PATCH 28/73] incremental changes in basel --- active_projects/basel.py | 409 ++++----------------------------------- 1 file changed, 38 insertions(+), 371 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 2e468e50..b46ed878 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -197,16 +197,13 @@ class ScaleLightSources(Transform): new_sp.scale(factor,about_point = about_point) submob.move_source_to(new_sp.get_location()) - #ambient_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: ambient_of(r/factor) - #submob.ambient_light.opacity_function = new_of + ambient_of = copy_func(submob.ambient_light.opacity_function) + new_of = lambda r: ambient_of(r/factor) + submob.ambient_light.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) + 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.ambient_light.radius submob.ambient_light.radius = new_r @@ -1004,7 +1001,6 @@ class ScreenShapingScene(ThreeDScene): def construct(self): - #self.force_skipping() self.setup_elements() self.deform_screen() self.create_brightness_rect() @@ -1014,10 +1010,8 @@ class ScreenShapingScene(ThreeDScene): self.add_distance_arrow() self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() - #self.revert_to_original_skipping_status() self.morph_into_3d() - self.prove_inverse_square_law() def setup_elements(self): @@ -1033,14 +1027,11 @@ class ScreenShapingScene(ThreeDScene): # light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,5,1), + opacity_function = inverse_quadratic(1,2,1), num_levels = NUM_LEVELS, radius = 10, - max_opacity = 0.2 #screen = self.screen ) - self.light_source.set_max_opacity_spotlight(0.2) - self.light_source.set_screen(self.screen) self.light_source.move_source_to([-5,0,0]) @@ -1257,9 +1248,6 @@ class ScreenShapingScene(ThreeDScene): def morph_into_3d(self): - - self.play(FadeOut(self.morty)) - axes = ThreeDAxes() self.add(axes) @@ -1267,6 +1255,11 @@ class ScreenShapingScene(ThreeDScene): theta0 = self.camera.get_theta() # default is -90 degs distance0 = self.camera.get_distance() + # this is an ugly hack bc remove, FadeOut and SwitchOff don't work + self.play( + self.light_source.set_max_opacity_ambient,0.001 + ) + phi1 = 60 * DEGREES # angle from zenith (0 to 180) theta1 = -135 * DEGREES # azimuth (0 to 360) distance1 = distance0 @@ -1279,7 +1272,7 @@ class ScreenShapingScene(ThreeDScene): projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) + width = 0.1, stroke_color = RED) new_screen0.rotate(TAU/4,axis = DOWN) new_screen0.move_to(self.screen.get_center()) self.add(new_screen0) @@ -1289,199 +1282,19 @@ class ScreenShapingScene(ThreeDScene): self.light_source.set_camera(self.camera) - new_screen = Rectangle(height = self.screen_height, - width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen.rotate(TAU/4,axis = DOWN) - new_screen.move_to(self.screen.get_center()) + new_screen = new_screen0.deepcopy() + new_screen.width = new_screen.height - self.add_foreground_mobject(self.ambient_light) - self.add_foreground_mobject(self.spotlight) - self.add_foreground_mobject(self.light_source.shadow) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), ) - self.remove(self.spotlight) - self.play(Transform(new_screen0,new_screen)) + #self.play(Transform(new_screen0,new_screen)) self.wait() - self.unit_screen = new_screen0 # better name - - - - def prove_inverse_square_law(self): - - def orientate(mob): - mob.move_to(self.unit_screen) - mob.rotate(TAU/4, axis = LEFT) - mob.rotate(TAU/4, axis = OUT) - mob.rotate(TAU/2, axis = LEFT) - return mob - - unit_screen_copy = self.unit_screen.copy() - fourfold_screen = self.unit_screen.copy() - fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) - - self.remove(self.spotlight) - - - reading1 = TexMobject("1") - orientate(reading1) - - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - - - self.play( - Transform(self.unit_screen, fourfold_screen) - ) - - reading21 = TexMobject("{1\over 4}").scale(0.8) - orientate(reading21) - reading22 = reading21.deepcopy() - reading23 = reading21.deepcopy() - reading24 = reading21.deepcopy() - reading21.shift(0.5*OUT + 0.5*UP) - reading22.shift(0.5*OUT + 0.5*DOWN) - reading23.shift(0.5*IN + 0.5*UP) - reading24.shift(0.5*IN + 0.5*DOWN) - - - corners = fourfold_screen.get_anchors() - midpoint1 = (corners[0] + corners[1])/2 - midpoint2 = (corners[1] + corners[2])/2 - midpoint3 = (corners[2] + corners[3])/2 - midpoint4 = (corners[3] + corners[0])/2 - midline1 = Line(midpoint1, midpoint3) - midline2 = Line(midpoint2, midpoint4) - - self.play( - ShowCreation(midline1), - ShowCreation(midline2) - ) - - self.play( - FadeIn(reading21), - FadeIn(reading22), - FadeIn(reading23), - FadeIn(reading24), - ) - - self.wait() - - self.play( - FadeOut(reading21), - FadeOut(reading22), - FadeOut(reading23), - FadeOut(reading24), - FadeOut(midline1), - FadeOut(midline2) - ) - - ninefold_screen = unit_screen_copy.copy() - ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) - - self.play( - Transform(self.unit_screen, ninefold_screen) - ) - - reading31 = TexMobject("{1\over 9}").scale(0.8) - orientate(reading31) - reading32 = reading31.deepcopy() - reading33 = reading31.deepcopy() - reading34 = reading31.deepcopy() - reading35 = reading31.deepcopy() - reading36 = reading31.deepcopy() - reading37 = reading31.deepcopy() - reading38 = reading31.deepcopy() - reading39 = reading31.deepcopy() - reading31.shift(IN + UP) - reading32.shift(IN) - reading33.shift(IN + DOWN) - reading34.shift(UP) - reading35.shift(ORIGIN) - reading36.shift(DOWN) - reading37.shift(OUT + UP) - reading38.shift(OUT) - reading39.shift(OUT + DOWN) - - corners = ninefold_screen.get_anchors() - midpoint11 = (2*corners[0] + corners[1])/3 - midpoint12 = (corners[0] + 2*corners[1])/3 - midpoint21 = (2*corners[1] + corners[2])/3 - midpoint22 = (corners[1] + 2*corners[2])/3 - midpoint31 = (2*corners[2] + corners[3])/3 - midpoint32 = (corners[2] + 2*corners[3])/3 - midpoint41 = (2*corners[3] + corners[0])/3 - midpoint42 = (corners[3] + 2*corners[0])/3 - midline11 = Line(midpoint11, midpoint32) - midline12 = Line(midpoint12, midpoint31) - midline21 = Line(midpoint21, midpoint42) - midline22 = Line(midpoint22, midpoint41) - - self.play( - ShowCreation(midline11), - ShowCreation(midline12), - ShowCreation(midline21), - ShowCreation(midline22), - ) - - self.play( - FadeIn(reading31), - FadeIn(reading32), - FadeIn(reading33), - FadeIn(reading34), - FadeIn(reading35), - FadeIn(reading36), - FadeIn(reading37), - FadeIn(reading38), - FadeIn(reading39), - ) - - - - -class IndicatorScalingScene(Scene): - - def construct(self): - - unit_intensity = 0.6 - - indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator1.set_intensity(unit_intensity) - reading1 = TexMobject("1") - reading1.move_to(indicator1) - - - indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator2.shift(2*RIGHT) - indicator2.set_intensity(unit_intensity/4) - reading2 = TexMobject("{1\over 4}").scale(0.8) - reading2.move_to(indicator2) - - indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator3.shift(4*RIGHT) - indicator3.set_intensity(unit_intensity/9) - reading3 = TexMobject("{1\over 9}").scale(0.8) - reading3.move_to(indicator3) - - - self.play(FadeIn(indicator1)) - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - self.play(Transform(indicator1, indicator2)) - self.play(FadeIn(reading2)) - self.wait() - self.play(FadeOut(reading2)) - self.play(Transform(indicator1, indicator3)) - self.play(FadeIn(reading3)) - self.wait() - @@ -2301,7 +2114,7 @@ class PondScene(Scene): return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + def split_light_source(i, step, show_steps = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2333,28 +2146,17 @@ class PondScene(Scene): self.add(ls2) self.additional_light_sources.append(ls2) - # check if the light sources are on screen - ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) - show_animation = (onscreen_old or onscreen_1 or onscreen_2) - - if show_animation: - self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), - ) - else: - ls1.move_source_to(ls_new_loc1) - ls2.move_source_to(ls_new_loc1) + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_radius = 1): + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2415,12 +2217,7 @@ class PondScene(Scene): self.new_hypotenuses = [] for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time, - ls_radius = ls_radius - ) + split_light_source(i, step = n, show_steps = show_steps, run_time = run_time) @@ -2482,12 +2279,6 @@ class PondScene(Scene): self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, ) - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - else: # update the lake center and the radius self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP @@ -2528,12 +2319,8 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 - for i in range(3): - construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) - - return + construction_step(i, scale_down = True) self.play( FadeOut(self.altitudes), @@ -2541,141 +2328,21 @@ class PondScene(Scene): FadeOut(self.legs) ) - for i in range(3,5): + for i in range(3,10): construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True, ls_radius = ls_radius/2**3) - - - - - # Now create a straight number line and transform into it - MAX_N = 17 - - self.number_line = NumberLine( - x_min = -MAX_N, - x_max = MAX_N + 1, - color = WHITE, - number_at_center = 0, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = range(-MAX_N,MAX_N + 1), - unit_size = LAKE0_RADIUS * TAU/4 / 4, - tick_frequency = 1, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, - ).shift(2.5 * DOWN) - - self.number_line.label_direction = DOWN - - self.number_line_labels = self.number_line.get_number_mobjects() - self.wait() - - origin_point = self.number_line.number_to_point(0) - nl_sources = VMobject() - pond_sources = VMobject() - - for i in range(-MAX_N,MAX_N+1): - anchor = self.number_line.number_to_point(2*i + 1) - ls = self.light_sources_array[i].copy() - ls.move_source_to(anchor) - nl_sources.add(ls) - pond_sources.add(self.light_sources_array[i].copy()) - - self.add(pond_sources) - self.remove(self.light_sources) - - self.outer_lake.rotate(TAU/8) - - # open sea - open_sea = Rectangle( - width = 20, - height = 10, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - ).flip().next_to(origin_point,UP,buff = 0) - - - - self.play( - Transform(pond_sources,nl_sources), - Transform(self.outer_lake,open_sea), - FadeOut(self.inner_lake) - ) - self.play(FadeIn(self.number_line)) - - - -class LabeledArc(Arc): - CONFIG = { - "length" : 1 - } - - def __init__(self, angle, **kwargs): - - BUFFER = 1.3 - - Arc.__init__(self,angle,**kwargs) - - label = DecimalNumber(self.length, num_decimal_points = 0) - r = BUFFER * self.radius - theta = self.start_angle + self.angle/2 - label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) - - label.move_to(label_pos) - self.add(label) - - - - -class ArcHighlightOverlayScene(Scene): - - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - FLASH_TIME = 0.25 - - def flash_arcs(n): - - angle = TAU/2**n - arcs = [] - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) - - for i in range(1,2**n): - arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) - - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) - - self.play( - FadeIn(arcs[0], run_time = FLASH_TIME) - ) - - for i in range(1,2**n + 1): - self.play( - FadeOut(arcs[i-1], run_time = FLASH_TIME), - FadeIn(arcs[i], run_time = FLASH_TIME) - ) - - self.play( - FadeOut(arcs[2**n], run_time = FLASH_TIME), - ) - - - flash_arcs(3) + simultaneous_splitting = True) + + + + + + + + + + + + From 831257b68aeb2a94f5d031f9090909682751f975 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 21 Feb 2018 19:09:22 +0100 Subject: [PATCH 29/73] day's work --- active_projects/basel.py | 507 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 470 insertions(+), 37 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index b46ed878..7eb9285a 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -197,13 +197,16 @@ class ScaleLightSources(Transform): new_sp.scale(factor,about_point = about_point) submob.move_source_to(new_sp.get_location()) - ambient_of = copy_func(submob.ambient_light.opacity_function) - new_of = lambda r: ambient_of(r/factor) - submob.ambient_light.opacity_function = new_of + # ambient_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: ambient_of(r / factor) + # submob.ambient_light.change_opacity_function(new_of) - spotlight_of = copy_func(submob.ambient_light.opacity_function) - new_of = lambda r: spotlight_of(r/factor) - submob.spotlight.change_opacity_function(new_of) + # spotlight_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: spotlight_of(r / factor) + # submob.spotlight.change_opacity_function(new_of) + + new_r = factor * submob.radius + submob.set_radius(new_r) new_r = factor * submob.ambient_light.radius submob.ambient_light.radius = new_r @@ -1001,6 +1004,7 @@ class ScreenShapingScene(ThreeDScene): def construct(self): + #self.force_skipping() self.setup_elements() self.deform_screen() self.create_brightness_rect() @@ -1010,8 +1014,10 @@ class ScreenShapingScene(ThreeDScene): self.add_distance_arrow() self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() + #self.revert_to_original_skipping_status() self.morph_into_3d() + self.prove_inverse_square_law() def setup_elements(self): @@ -1027,11 +1033,14 @@ class ScreenShapingScene(ThreeDScene): # light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,5,1), num_levels = NUM_LEVELS, radius = 10, + max_opacity = 0.2 #screen = self.screen ) + self.light_source.set_max_opacity_spotlight(0.2) + self.light_source.set_screen(self.screen) self.light_source.move_source_to([-5,0,0]) @@ -1248,6 +1257,9 @@ class ScreenShapingScene(ThreeDScene): def morph_into_3d(self): + + self.play(FadeOut(self.morty)) + axes = ThreeDAxes() self.add(axes) @@ -1255,11 +1267,6 @@ class ScreenShapingScene(ThreeDScene): theta0 = self.camera.get_theta() # default is -90 degs distance0 = self.camera.get_distance() - # this is an ugly hack bc remove, FadeOut and SwitchOff don't work - self.play( - self.light_source.set_max_opacity_ambient,0.001 - ) - phi1 = 60 * DEGREES # angle from zenith (0 to 180) theta1 = -135 * DEGREES # azimuth (0 to 360) distance1 = distance0 @@ -1272,7 +1279,7 @@ class ScreenShapingScene(ThreeDScene): projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED) + width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) new_screen0.rotate(TAU/4,axis = DOWN) new_screen0.move_to(self.screen.get_center()) self.add(new_screen0) @@ -1282,19 +1289,199 @@ class ScreenShapingScene(ThreeDScene): self.light_source.set_camera(self.camera) - new_screen = new_screen0.deepcopy() - new_screen.width = new_screen.height + new_screen = Rectangle(height = self.screen_height, + width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen.rotate(TAU/4,axis = DOWN) + new_screen.move_to(self.screen.get_center()) + self.add_foreground_mobject(self.ambient_light) + self.add_foreground_mobject(self.spotlight) + self.add_foreground_mobject(self.light_source.shadow) self.play( ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), ) + self.remove(self.spotlight) - #self.play(Transform(new_screen0,new_screen)) + self.play(Transform(new_screen0,new_screen)) self.wait() + self.unit_screen = new_screen0 # better name + + + + def prove_inverse_square_law(self): + + def orientate(mob): + mob.move_to(self.unit_screen) + mob.rotate(TAU/4, axis = LEFT) + mob.rotate(TAU/4, axis = OUT) + mob.rotate(TAU/2, axis = LEFT) + return mob + + unit_screen_copy = self.unit_screen.copy() + fourfold_screen = self.unit_screen.copy() + fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) + + self.remove(self.spotlight) + + + reading1 = TexMobject("1") + orientate(reading1) + + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + + + self.play( + Transform(self.unit_screen, fourfold_screen) + ) + + reading21 = TexMobject("{1\over 4}").scale(0.8) + orientate(reading21) + reading22 = reading21.deepcopy() + reading23 = reading21.deepcopy() + reading24 = reading21.deepcopy() + reading21.shift(0.5*OUT + 0.5*UP) + reading22.shift(0.5*OUT + 0.5*DOWN) + reading23.shift(0.5*IN + 0.5*UP) + reading24.shift(0.5*IN + 0.5*DOWN) + + + corners = fourfold_screen.get_anchors() + midpoint1 = (corners[0] + corners[1])/2 + midpoint2 = (corners[1] + corners[2])/2 + midpoint3 = (corners[2] + corners[3])/2 + midpoint4 = (corners[3] + corners[0])/2 + midline1 = Line(midpoint1, midpoint3) + midline2 = Line(midpoint2, midpoint4) + + self.play( + ShowCreation(midline1), + ShowCreation(midline2) + ) + + self.play( + FadeIn(reading21), + FadeIn(reading22), + FadeIn(reading23), + FadeIn(reading24), + ) + + self.wait() + + self.play( + FadeOut(reading21), + FadeOut(reading22), + FadeOut(reading23), + FadeOut(reading24), + FadeOut(midline1), + FadeOut(midline2) + ) + + ninefold_screen = unit_screen_copy.copy() + ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + + self.play( + Transform(self.unit_screen, ninefold_screen) + ) + + reading31 = TexMobject("{1\over 9}").scale(0.8) + orientate(reading31) + reading32 = reading31.deepcopy() + reading33 = reading31.deepcopy() + reading34 = reading31.deepcopy() + reading35 = reading31.deepcopy() + reading36 = reading31.deepcopy() + reading37 = reading31.deepcopy() + reading38 = reading31.deepcopy() + reading39 = reading31.deepcopy() + reading31.shift(IN + UP) + reading32.shift(IN) + reading33.shift(IN + DOWN) + reading34.shift(UP) + reading35.shift(ORIGIN) + reading36.shift(DOWN) + reading37.shift(OUT + UP) + reading38.shift(OUT) + reading39.shift(OUT + DOWN) + + corners = ninefold_screen.get_anchors() + midpoint11 = (2*corners[0] + corners[1])/3 + midpoint12 = (corners[0] + 2*corners[1])/3 + midpoint21 = (2*corners[1] + corners[2])/3 + midpoint22 = (corners[1] + 2*corners[2])/3 + midpoint31 = (2*corners[2] + corners[3])/3 + midpoint32 = (corners[2] + 2*corners[3])/3 + midpoint41 = (2*corners[3] + corners[0])/3 + midpoint42 = (corners[3] + 2*corners[0])/3 + midline11 = Line(midpoint11, midpoint32) + midline12 = Line(midpoint12, midpoint31) + midline21 = Line(midpoint21, midpoint42) + midline22 = Line(midpoint22, midpoint41) + + self.play( + ShowCreation(midline11), + ShowCreation(midline12), + ShowCreation(midline21), + ShowCreation(midline22), + ) + + self.play( + FadeIn(reading31), + FadeIn(reading32), + FadeIn(reading33), + FadeIn(reading34), + FadeIn(reading35), + FadeIn(reading36), + FadeIn(reading37), + FadeIn(reading38), + FadeIn(reading39), + ) + + + + +class IndicatorScalingScene(Scene): + + def construct(self): + + unit_intensity = 0.6 + + indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator1.set_intensity(unit_intensity) + reading1 = TexMobject("1") + reading1.move_to(indicator1) + + + indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator2.shift(2*RIGHT) + indicator2.set_intensity(unit_intensity/4) + reading2 = TexMobject("{1\over 4}").scale(0.8) + reading2.move_to(indicator2) + + indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator3.shift(4*RIGHT) + indicator3.set_intensity(unit_intensity/9) + reading3 = TexMobject("{1\over 9}").scale(0.8) + reading3.move_to(indicator3) + + + self.play(FadeIn(indicator1)) + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + self.play(Transform(indicator1, indicator2)) + self.play(FadeIn(reading2)) + self.wait() + self.play(FadeOut(reading2)) + self.play(Transform(indicator1, indicator3)) + self.play(FadeIn(reading3)) + self.wait() + @@ -1962,6 +2149,10 @@ class PondScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 5 + LIGHT_CUTOFF = 1 + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) @@ -1988,7 +2179,8 @@ class PondScene(Scene): indicator.next_to(morty,LEFT) # first lighthouse - ls0 = LightSource() + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func) ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) @@ -2055,11 +2247,11 @@ class PondScene(Scene): FadeOut(ls0_dot) ) - indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator.reading.move_to(indicator) + indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) self.play( - FadeIn(indicator.reading) + FadeIn(indicator_reading) ) # replace d with its value @@ -2075,7 +2267,7 @@ class PondScene(Scene): new_reading.move_to(indicator) self.play( - Transform(indicator.reading,new_reading) + Transform(indicator_reading,new_reading) ) self.play( @@ -2094,7 +2286,7 @@ class PondScene(Scene): self.play( ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) ) @@ -2114,7 +2306,7 @@ class PondScene(Scene): return position - def split_light_source(i, step, show_steps = True, run_time = 1): + def split_light_source(i, step, show_steps = True, run_time = 1, scale_down = True): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2142,14 +2334,43 @@ class PondScene(Scene): ) ls1 = self.light_sources_array[i] + + if scale_down: + # print "scaling down" + # #new_of = lambda x: original_op_func(x/0.5**step) + # #new_of = inverse_quadratic(LIGHT_MAX_INT, LIGHT_SCALE * 10**(step+1), LIGHT_CUTOFF) + # print "==============" + # for t in np.arange(0.0,5.0,1.0): + # print ls1.ambient_light.opacity_function(t) + + # print "==============" + + new_of = lambda x: np.sin(10*x) + ls1.ambient_light.change_opacity_function(lambda x: np.sin(100*x)) + # for t in np.arange(0.0,5.0,1.0): + # print ls1.ambient_light.opacity_function(t) + + ls1.spotlight.change_opacity_function(new_of) + ls2 = ls1.copy() self.add(ls2) self.additional_light_sources.append(ls2) - self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), - ) + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.all(np.abs(ls_old_loc) < 10) + onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + show_animation = (onscreen_old or onscreen_1 or onscreen_2) + + if show_animation: + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) @@ -2217,7 +2438,12 @@ class PondScene(Scene): self.new_hypotenuses = [] for i in range(2**n): - split_light_source(i, step = n, show_steps = show_steps, run_time = run_time) + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + scale_down = scale_down + ) @@ -2279,6 +2505,12 @@ class PondScene(Scene): self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, ) + # update the radii bc they haven't done so themselves + # bc reasons... + for ls in self.light_sources_array: + r = ls.radius + ls.set_radius(r*0.5) + else: # update the lake center and the radius self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP @@ -2319,30 +2551,231 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] + ls_radius = 25.0 + for i in range(3): construction_step(i, scale_down = True) + + self.play( FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) ) - for i in range(3,10): + for i in range(3,5): construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, simultaneous_splitting = True) - - - - - - - - + # Now create a straight number line and transform into it + MAX_N = 17 + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 4, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(2.5 * DOWN) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.wait() + + origin_point = self.number_line.number_to_point(0) + nl_sources = VMobject() + pond_sources = VMobject() + + for i in range(-MAX_N,MAX_N+1): + anchor = self.number_line.number_to_point(2*i + 1) + ls = self.light_sources_array[i].copy() + ls.move_source_to(anchor) + nl_sources.add(ls) + pond_sources.add(self.light_sources_array[i].copy()) + + self.add(pond_sources) + self.remove(self.light_sources) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + width = 20, + height = 10, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + ).flip().next_to(origin_point,UP,buff = 0) + + + + self.play( + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), + FadeOut(self.inner_lake) + ) + self.play(FadeIn(self.number_line)) + + + self.wait() + + v = 5*UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + self.remove(obs_dot) + self.play( + indicator.move_to, origin_point + UP, + indicator_reading.move_to, origin_point + 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(0.8) + + 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) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.2 * DOWN) + + self.play(Write(two_sided_sum)) + + covering_rectangle = Rectangle( + width = SPACE_WIDTH, + height = 2 * SPACE_HEIGHT, + 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(self.light_sources_array[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator.reading) + + half_indicator = indicator.copy() + half_indicator.show_reading = False + half_indicator.set_intensity(indicator.intensity / 2) + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(half_indicator) + half_indicator.remove(indicator_reading) + half_indicator.add(half_indicator_reading) + + half_indicator_copy = half_indicator.deepcopy() + half_indicator_reading_copy = half_indicator_reading.deepcopy() + + self.play( + FadeIn(covering_rectangle), + FadeIn(half_indicator), + ) + + p = 2*LEFT + self.play( + half_indicator_copy.move_to,p, + half_indicator_reading_copy.move_to,p + ) + + + +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 1.3 + + Arc.__init__(self,angle,**kwargs) + + label = DecimalNumber(self.length, num_decimal_points = 0) + r = BUFFER * self.radius + theta = self.start_angle + self.angle/2 + label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) + + label.move_to(label_pos) + self.add(label) + + + + +class ArcHighlightOverlayScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + FLASH_TIME = 0.25 + + def flash_arcs(n): + + angle = TAU/2**n + arcs = [] + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) + + for i in range(1,2**n): + arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) + + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) + + self.play( + FadeIn(arcs[0], run_time = FLASH_TIME) + ) + + for i in range(1,2**n + 1): + self.play( + FadeOut(arcs[i-1], run_time = FLASH_TIME), + FadeIn(arcs[i], run_time = FLASH_TIME) + ) + + self.play( + FadeOut(arcs[2**n], run_time = FLASH_TIME), + ) + + + flash_arcs(3) From 25744bdb543e7d5d019c9881527913fbcc5892d5 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 22 Feb 2018 11:18:20 +0100 Subject: [PATCH 30/73] rewriting PondScene with ThreeDCamera --- active_projects/basel.py | 112 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 7eb9285a..282ab3ee 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2132,7 +2132,10 @@ class IPTScene2(Scene): self.play(ShowCreation(box),Write(text)) -class PondScene(Scene): +class PondScene(ThreeDScene): + + + def construct(self): @@ -2153,12 +2156,44 @@ class PondScene(Scene): LIGHT_SCALE = 5 LIGHT_CUTOFF = 1 + self.cumulated_zoom_factor = 1 + + + def zoom_out_scene(factor): + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_fill(opacity = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - + self.zoomable_mobs.add(obs_dot, ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2167,6 +2202,7 @@ class PondScene(Scene): fill_opacity = LAKE_OPACITY ) lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) # Morty and indicator morty = Mortimer().scale(0.3) @@ -2177,11 +2213,13 @@ class PondScene(Scene): 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) 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) @@ -2249,6 +2287,7 @@ class PondScene(Scene): indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) self.play( FadeIn(indicator_reading) @@ -2306,7 +2345,7 @@ class PondScene(Scene): return position - def split_light_source(i, step, show_steps = True, run_time = 1, scale_down = True): + def split_light_source(i, step, show_steps = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2335,22 +2374,6 @@ class PondScene(Scene): ls1 = self.light_sources_array[i] - if scale_down: - # print "scaling down" - # #new_of = lambda x: original_op_func(x/0.5**step) - # #new_of = inverse_quadratic(LIGHT_MAX_INT, LIGHT_SCALE * 10**(step+1), LIGHT_CUTOFF) - # print "==============" - # for t in np.arange(0.0,5.0,1.0): - # print ls1.ambient_light.opacity_function(t) - - # print "==============" - - new_of = lambda x: np.sin(10*x) - ls1.ambient_light.change_opacity_function(lambda x: np.sin(100*x)) - # for t in np.arange(0.0,5.0,1.0): - # print ls1.ambient_light.opacity_function(t) - - ls1.spotlight.change_opacity_function(new_of) ls2 = ls1.copy() self.add(ls2) @@ -2376,7 +2399,7 @@ class PondScene(Scene): - def construction_step(n, scale_down = True, show_steps = True, run_time = 1, + def construction_step(n, show_steps = True, run_time = 1, simultaneous_splitting = False): # we assume that the scene contains: @@ -2441,8 +2464,7 @@ class PondScene(Scene): split_light_source(i, step = n, show_steps = show_steps, - run_time = run_time, - scale_down = scale_down + run_time = run_time ) @@ -2484,37 +2506,8 @@ class PondScene(Scene): if show_steps == True: self.play(FadeOut(ls0_dot)) - # scale down - if scale_down: - - indicator_wiggle() - - if show_steps == True: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - ) - else: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - ) - - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - - else: - # update the lake center and the radius - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP - self.lake_radius *= 2 + self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP + self.lake_radius *= 2 @@ -2537,6 +2530,8 @@ class PondScene(Scene): self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + self.add(self.inner_lake, self.outer_lake, self.legs, @@ -2551,10 +2546,12 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 + for i in range(3): - construction_step(i, scale_down = True) + construction_step(i) + indicator_wiggle() + zoom_out_scene(2) @@ -2565,11 +2562,10 @@ class PondScene(Scene): ) for i in range(3,5): - construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True) - + construction_step(i, show_steps = False, run_time = 1.0/2**i) + return # Now create a straight number line and transform into it MAX_N = 17 From fd91761f892a2b68a78fedc9287b70b264f6b85e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 22 Feb 2018 18:20:25 +0100 Subject: [PATCH 31/73] Finalized PondScene (save overlays), working on final scenes --- active_projects/basel.py | 226 ++++++++++++++++++++++++++++----------- 1 file changed, 163 insertions(+), 63 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 282ab3ee..8bd8a8d8 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2132,6 +2132,27 @@ class IPTScene2(Scene): self.play(ShowCreation(box),Write(text)) + +BASELINE_YPOS = -2.5 +OBSERVER_POINT = [0,BASELINE_YPOS,0] +LAKE0_RADIUS = 1.5 +INDICATOR_RADIUS = 0.6 +TICK_SIZE = 0.5 +LIGHTHOUSE_HEIGHT = 0.2 +LAKE_COLOR = BLUE +LAKE_OPACITY = 0.15 +LAKE_STROKE_WIDTH = 5.0 +LAKE_STROKE_COLOR = BLUE +TEX_SCALE = 0.8 +DOT_COLOR = BLUE + +LIGHT_MAX_INT = 1 +LIGHT_SCALE = 5 +LIGHT_CUTOFF = 1 + + + + class PondScene(ThreeDScene): @@ -2139,27 +2160,14 @@ class PondScene(ThreeDScene): def construct(self): - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - LIGHT_MAX_INT = 1 - LIGHT_SCALE = 5 - LIGHT_CUTOFF = 1 self.cumulated_zoom_factor = 1 + self.force_skipping() + def zoom_out_scene(factor): + phi0 = self.camera.get_phi() # default is 0 degs theta0 = self.camera.get_theta() # default is -90 degs distance0 = self.camera.get_distance() @@ -2169,6 +2177,7 @@ class PondScene(ThreeDScene): 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}, ) @@ -2188,12 +2197,12 @@ class PondScene(ThreeDScene): baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) - baseline.set_fill(opacity = 0) # in case it gets accidentally added to the scene + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene self.zoomable_mobs.add(baseline) # prob not necessary - obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.zoomable_mobs.add(obs_dot, ls0_dot) + self.unzoomable_mobs.add(self.obs_dot, ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2221,7 +2230,7 @@ class PondScene(ThreeDScene): ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,self.obs_dot,ls0_dot, ls0.lighthouse) self.wait() @@ -2281,7 +2290,7 @@ class PondScene(ThreeDScene): self.play( ShowCreation(diameter), Write(diameter_text), - #FadeOut(obs_dot), + #FadeOut(self.obs_dot), FadeOut(ls0_dot) ) @@ -2340,7 +2349,7 @@ class PondScene(ThreeDScene): position = self.lake_center + self.lake_radius * radial_vector if scaled_down: - return position.scale_about_point(OBSERVER_POINT,0.5) + return position.scale_about_point(self.obs_dot.get_center(),0.5) else: return position @@ -2361,8 +2370,8 @@ class PondScene(ThreeDScene): ShowCreation(hyp, run_time = run_time) ) - leg1 = Line(OBSERVER_POINT,ls_new_loc1) - leg2 = Line(OBSERVER_POINT,ls_new_loc2) + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) @@ -2381,9 +2390,9 @@ class PondScene(ThreeDScene): # check if the light sources are on screen ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) if show_animation: @@ -2418,18 +2427,22 @@ class PondScene(ThreeDScene): # 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, @@ -2454,11 +2467,12 @@ class PondScene(ThreeDScene): self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] - self.new_hypotenuses = [] + self.new_hypotenuses = [] for i in range(2**n): split_light_source(i, @@ -2475,16 +2489,23 @@ class PondScene(ThreeDScene): 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( @@ -2492,6 +2513,7 @@ class PondScene(ThreeDScene): 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( @@ -2499,6 +2521,7 @@ class PondScene(ThreeDScene): self.hypotenuses, self.altitudes, ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) self.wait() @@ -2506,7 +2529,7 @@ class PondScene(ThreeDScene): if show_steps == True: self.play(FadeOut(ls0_dot)) - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP self.lake_radius *= 2 @@ -2547,11 +2570,20 @@ class PondScene(ThreeDScene): self.new_hypotenuses = [] + construction_step(0) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(1) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) - for i in range(3): - construction_step(i) - indicator_wiggle() - zoom_out_scene(2) @@ -2559,17 +2591,22 @@ class PondScene(ThreeDScene): FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) - ) + ) - for i in range(3,5): - construction_step(i, show_steps = False, run_time = 1.0/2**i) + 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) - return + 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, @@ -2579,11 +2616,11 @@ class PondScene(ThreeDScene): 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 / 4, + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(2.5 * DOWN) + ).shift(scale * 2.5 * DOWN) self.number_line.label_direction = DOWN @@ -2608,8 +2645,8 @@ class PondScene(ThreeDScene): # open sea open_sea = Rectangle( - width = 20, - height = 10, + width = 20 * scale, + height = 10 * scale, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, @@ -2628,7 +2665,7 @@ class PondScene(ThreeDScene): self.wait() - v = 5*UP + v = 5 * scale * UP self.play( nl_sources.shift,v, morty.shift,v, @@ -2640,10 +2677,10 @@ class PondScene(ThreeDScene): self.number_line_labels.shift(v) origin_point = self.number_line.number_to_point(0) - self.remove(obs_dot) + #self.remove(self.obs_dot) self.play( - indicator.move_to, origin_point + UP, - indicator_reading.move_to, origin_point + UP, + 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) @@ -2657,51 +2694,114 @@ class PondScene(ThreeDScene): nb_symbols = len(two_sided_sum.submobjects) - two_sided_sum.scale(0.8) + 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) + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions - submob.shift(0.2 * DOWN) + submob.shift(0.2 * scale * DOWN) self.play(Write(two_sided_sum)) covering_rectangle = Rectangle( - width = SPACE_WIDTH, - height = 2 * SPACE_HEIGHT, + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, stroke_width = 0, fill_color = BLACK, fill_opacity = 1, ) covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) for i in range(10): - self.add_foreground_mobject(self.light_sources_array[i]) + self.add_foreground_mobject(nl_sources.submobjects[i]) self.add_foreground_mobject(indicator) - self.add_foreground_mobject(indicator.reading) + self.add_foreground_mobject(indicator_reading) - half_indicator = indicator.copy() - half_indicator.show_reading = False - half_indicator.set_intensity(indicator.intensity / 2) half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) half_indicator_reading.move_to(half_indicator) - half_indicator.remove(indicator_reading) - half_indicator.add(half_indicator_reading) - half_indicator_copy = half_indicator.deepcopy() - half_indicator_reading_copy = half_indicator_reading.deepcopy() + central_plus_sign = two_sided_sum[13] self.play( FadeIn(covering_rectangle), - FadeIn(half_indicator), + ReplacementTransform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) ) - p = 2*LEFT + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + self.play( - half_indicator_copy.move_to,p, - half_indicator_reading_copy.move_to,p + indicator.move_to,p, + half_indicator_reading.move_to,p, + FadeIn(equals_sign) ) + # show Randy admiring the result + randy = Randolph().scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + + + +class WaitScene(TeacherStudentsScene): + + def construct(self): + + self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!")) + + student_q = TextMobject("What about") + full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?") + full_sum.next_to(student_q,RIGHT) + student_q.add(full_sum) + + self.student_says(student_q) + + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + odd_range = range(1,11,2) + even_range = range(2,12,2) + full_range = range(1,12,1) + + self.number_line = NumberLine( + x_min = 0, + x_max = 10, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_to_show = odd_range, + unit_size = 1, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ) + + odd_lights = VMobject() + + for i in odd_range: + + pos = self.number_line.number_to_point(i) + ls = LightSource() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play(ShowCreation(self.number_line)) + self.play(FadeIn(odd_lights)) + + + + + + + + + + class LabeledArc(Arc): From d416890fbcc643111c4577c21e4865aa55df03db Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 22 Feb 2018 23:02:54 +0100 Subject: [PATCH 32/73] added synonym for number line labels --- topics/number_line.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/topics/number_line.py b/topics/number_line.py index 9d02286c..3848feba 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -120,6 +120,9 @@ class NumberLine(VMobject): result.add(mob) return result + def get_labels(self): + return self.get_number_mobjects() + def add_numbers(self, *numbers, **kwargs): self.numbers = self.get_number_mobjects( *numbers, **kwargs @@ -219,29 +222,7 @@ class Axes(VGroup): return graph def input_to_graph_point(self, x, graph): - if hasattr(graph, "underlying_function"): - return self.coords_to_point(x, graph.underlying_function(x)) - else: - #binary search - lh, rh = 0, 1 - while abs(lh - rh) > 0.001: - mh = np.mean([lh, rh]) - hands = [lh, mh, rh] - points = map(graph.point_from_proportion, hands) - lx, mx, rx = map(self.x_axis.point_to_number, points) - if lx <= x and rx >= x: - if mx > x: - rh = mh - else: - lh = mh - elif lx <= x and rx <= x: - return points[2] - elif lx >= x and rx >= x: - return points[0] - elif lx > x and rx < x: - lh, rh = rh, lh - return points[1] - + return self.coords_to_point(x, graph.underlying_function(x)) class ThreeDAxes(Axes): CONFIG = { From 4f8bc1fee928d409f7b1ffbc39c0b03183577c18 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 22 Feb 2018 23:03:05 +0100 Subject: [PATCH 33/73] working on final scene --- active_projects/basel.py | 185 ++++++++++++++++++++++++++++++++------- 1 file changed, 153 insertions(+), 32 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 8bd8a8d8..757b4254 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2133,22 +2133,7 @@ class IPTScene2(Scene): -BASELINE_YPOS = -2.5 -OBSERVER_POINT = [0,BASELINE_YPOS,0] -LAKE0_RADIUS = 1.5 -INDICATOR_RADIUS = 0.6 -TICK_SIZE = 0.5 -LIGHTHOUSE_HEIGHT = 0.2 -LAKE_COLOR = BLUE -LAKE_OPACITY = 0.15 -LAKE_STROKE_WIDTH = 5.0 -LAKE_STROKE_COLOR = BLUE -TEX_SCALE = 0.8 -DOT_COLOR = BLUE -LIGHT_MAX_INT = 1 -LIGHT_SCALE = 5 -LIGHT_CUTOFF = 1 @@ -2160,10 +2145,26 @@ class PondScene(ThreeDScene): def construct(self): + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 5 + LIGHT_CUTOFF = 1 self.cumulated_zoom_factor = 1 - self.force_skipping() + #self.force_skipping() def zoom_out_scene(factor): @@ -2600,7 +2601,7 @@ class PondScene(ThreeDScene): for i in range(3,max_it + 1): construction_step(i, show_steps = False, run_time = 4.0/2**i) - self.revert_to_original_skipping_status() + #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it MAX_N = 17 @@ -2718,7 +2719,7 @@ class PondScene(ThreeDScene): self.add_foreground_mobject(indicator_reading) half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) - half_indicator_reading.move_to(half_indicator) + half_indicator_reading.move_to(indicator) central_plus_sign = two_sided_sum[13] @@ -2756,42 +2757,159 @@ class WaitScene(TeacherStudentsScene): full_sum.next_to(student_q,RIGHT) student_q.add(full_sum) - self.student_says(student_q) + + self.student_says(student_q, target_mode = "angry") class FinalSumManipulationScene(PiCreatureScene): def construct(self): - odd_range = range(1,11,2) - even_range = range(2,12,2) - full_range = range(1,12,1) + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 - self.number_line = NumberLine( + unit_length = 1.5 + vertical_spacing = 1.8 * DOWN + switch_on_time = 0.2 + + randy = self.get_primary_pi_creature() + randy.scale(0.7).to_edge(DOWN + RIGHT) + + ls_template = LightSource( + radius = 2, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.5,1) + ) + + + odd_range = np.arange(1,13,2) + even_range = np.arange(2,28,2) + full_range = np.arange(1,14,1) + + self.number_line1 = NumberLine( x_min = 0, - x_max = 10, + x_max = 11, color = LAKE_STROKE_COLOR, number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, numbers_to_show = odd_range, - unit_size = 1, + unit_size = unit_length, tick_frequency = 1, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, + line_to_number_buff = MED_LARGE_BUFF, + include_tip = True ) + self.number_line1.next_to(3 * UP + 3 * LEFT, RIGHT, buff = 0) + odd_lights = VMobject() for i in odd_range: - - pos = self.number_line.number_to_point(i) - ls = LightSource() + 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_line)) - self.play(FadeIn(odd_lights)) + labels1 = self.number_line1.get_labels() + self.play( + ShowCreation(self.number_line1), + ShowCreation(labels1) + ) + + for ls in odd_lights.submobjects: + self.play(SwitchOn(ls.ambient_light), run_time = switch_on_time) + + result1 = TexMobject("{\pi^2\over 8} =") + result1.next_to(self.number_line1, LEFT, buff = 2) + 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) + labels2 = self.number_line2.get_labels() + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2), + ShowCreation(labels2) + ) + + for ls in full_lights.submobjects: + self.play(SwitchOn(ls.ambient_light, run_time = 0.1 * switch_on_time)) + + + self.number_line3 = self.number_line1.copy() + self.number_line3.numbers_to_show = even_range + self.number_line3.shift(vertical_spacing) + labels3 = self.number_line3.get_labels() + + missing_text = TextMobject("missing:") + missing_text.next_to(self.number_line3, LEFT, buff = 2) + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line3.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + even_lights.add(ls) + + self.play(Write(missing_text)) + + self.play( + ShowCreation(self.number_line3), + ShowCreation(labels3), + ) + + for ls in even_lights.submobjects: + self.play(SwitchOn(ls.ambient_light), run_time = switch_on_time) + + + # now morph the even lights into the full lights + even_lights_line = VMobject() + even_lights_line.add(even_lights, self.number_line2) + even_lights_line_copy = even_lights_line.copy() + + full_lights_line = VMobject() + number_line2p = self.number_line2 + number_line2p.tick_frequency = 0.5 + full_lights_line.add(full_lights.copy(), number_line2p) + + self.play( + Transform(even_lights_line,full_lights_line) + ) + + self.play( + Transform(even_lights_line,even_lights_line_copy) + ) + + + + + + + + + + + + + + + + + + @@ -2826,6 +2944,9 @@ class LabeledArc(Arc): + + + class ArcHighlightOverlayScene(Scene): def construct(self): From e6dca3b44d1a79ba4c6be7d174cc1688caaac680 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 23 Feb 2018 14:44:29 +0100 Subject: [PATCH 34/73] finished sum manipulation scene --- active_projects/basel.py | 198 +++++++++++++++++++++++++++++++-------- 1 file changed, 158 insertions(+), 40 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 757b4254..1f3b49f6 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2771,12 +2771,17 @@ class FinalSumManipulationScene(PiCreatureScene): LAKE_STROKE_COLOR = BLUE TEX_SCALE = 0.8 + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + unit_length = 1.5 - vertical_spacing = 1.8 * DOWN + vertical_spacing = 2.5 * DOWN switch_on_time = 0.2 + sum_vertical_spacing = 1.5 + randy = self.get_primary_pi_creature() - randy.scale(0.7).to_edge(DOWN + RIGHT) + randy.scale(0.7).flip().to_edge(DOWN + LEFT) ls_template = LightSource( radius = 2, @@ -2785,9 +2790,9 @@ class FinalSumManipulationScene(PiCreatureScene): ) - odd_range = np.arange(1,13,2) - even_range = np.arange(2,28,2) - full_range = np.arange(1,14,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, @@ -2803,107 +2808,220 @@ class FinalSumManipulationScene(PiCreatureScene): include_tip = True ) - self.number_line1.next_to(3 * UP + 3 * LEFT, RIGHT, buff = 0) + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0) odd_lights = VMobject() - for i in odd_range: pos = self.number_line1.number_to_point(i) ls = ls_template.copy() ls.move_source_to(pos) odd_lights.add(ls) - labels1 = self.number_line1.get_labels() self.play( ShowCreation(self.number_line1), - ShowCreation(labels1) ) - for ls in odd_lights.submobjects: - self.play(SwitchOn(ls.ambient_light), run_time = switch_on_time) + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) - result1 = TexMobject("{\pi^2\over 8} =") - result1.next_to(self.number_line1, LEFT, buff = 2) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) self.play(Write(result1)) + + + self.number_line2 = self.number_line1.copy() self.number_line2.numbers_to_show = full_range self.number_line2.shift(2 * vertical_spacing) - labels2 = self.number_line2.get_labels() 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), - ShowCreation(labels2) ) + + for ls in full_lights.submobjects: - self.play(SwitchOn(ls.ambient_light, run_time = 0.1 * switch_on_time)) + 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), + ) - self.number_line3 = self.number_line1.copy() - self.number_line3.numbers_to_show = even_range - self.number_line3.shift(vertical_spacing) - labels3 = self.number_line3.get_labels() - missing_text = TextMobject("missing:") - missing_text.next_to(self.number_line3, LEFT, buff = 2) + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + even_lights = VMobject() for i in even_range: - pos = self.number_line3.number_to_point(i) + 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) - self.play(Write(missing_text)) + 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.play( - ShowCreation(self.number_line3), - ShowCreation(labels3), - ) - - for ls in even_lights.submobjects: - self.play(SwitchOn(ls.ambient_light), run_time = switch_on_time) # now morph the even lights into the full lights - even_lights_line = VMobject() - even_lights_line.add(even_lights, self.number_line2) - even_lights_line_copy = even_lights_line.copy() + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() - full_lights_line = VMobject() - number_line2p = self.number_line2 - number_line2p.tick_frequency = 0.5 - full_lights_line.add(full_lights.copy(), number_line2p) self.play( - Transform(even_lights_line,full_lights_line) + Transform(even_lights,full_lights) + ) + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + Transform(even_lights,even_lights_copy) + ) + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) ) self.play( - Transform(even_lights_line,even_lights_line_copy) + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + ) + full_terms = VMobject() + for i in full_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + buffer = 2 + result_box = Rectangle(width = 15, + height = buffer*equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + equation.add(result_box) + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + ShowCreation(result_box) + ) + + self.play(equation.shift, -equation.get_center()[1] * UP + UP) From 72efc2cad9590837a615bcba81515b1e5e067470 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 23 Feb 2018 18:36:54 +0100 Subject: [PATCH 35/73] classes ArcBetweenPoints, CurvedArrow, CurvedDoubleArrow This makes it easier to draw arcs --- topics/geometry.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/topics/geometry.py b/topics/geometry.py index ec1b3f73..92bb07bb 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -96,6 +96,47 @@ class Arc(VMobject): return self + + +class ArcBetweenPoints(Arc): + + def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): + if angle == 0: + raise Exception("Arc with zero curve angle. Use Line instead.") + + midpoint = 0.5 * (start_point + end_point) + distance_vector = end_point - start_point + normal_vector = np.array([-distance_vector[1], distance_vector[0],0]) + distance = np.linalg.norm(normal_vector) + normal_vector /= distance + radius = distance/2 / np.sin(0.5 * angle) + l = distance/2 / np.tan(0.5 * angle) + arc_center = midpoint + l * normal_vector + w = start_point - arc_center + if w[0] != 0: + start_angle = np.arctan(w[1]/w[0]) + else: + start_angle = np.pi/2 + + Arc.__init__(self, angle, + radius = radius, + start_angle = start_angle, + **kwargs) + self.move_arc_center_to(arc_center) + +class CurvedArrow(ArcBetweenPoints): + + def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): + ArcBetweenPoints.__init__(self, start_point, end_point, angle = TAU/4, **kwargs) + self.add_tip() + +class CurvedDoubleArrow(ArcBetweenPoints): + + def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): + ArcBetweenPoints.__init__(self, start_point, end_point, angle = TAU/4, **kwargs) + self.add_tip(at_start = True, at_end = True) + + class Circle(Arc): CONFIG = { "color" : RED, From 497961082e3e1d31f1d27c4ff2eb7d66e4f86a8e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 23 Feb 2018 19:27:59 +0100 Subject: [PATCH 36/73] curved arcs can now handle negative angles --- topics/geometry.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/topics/geometry.py b/topics/geometry.py index 92bb07bb..ff157215 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -102,19 +102,22 @@ class ArcBetweenPoints(Arc): def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): if angle == 0: - raise Exception("Arc with zero curve angle. Use Line instead.") + raise Exception("Arc with zero curve angle: use Line instead.") midpoint = 0.5 * (start_point + end_point) distance_vector = end_point - start_point normal_vector = np.array([-distance_vector[1], distance_vector[0],0]) distance = np.linalg.norm(normal_vector) normal_vector /= distance - radius = distance/2 / np.sin(0.5 * angle) - l = distance/2 / np.tan(0.5 * angle) + if angle < 0: + normal_vector *= -1 + + radius = distance/2 / np.sin(0.5 * np.abs(angle)) + l = distance/2 / np.tan(0.5 * np.abs(angle)) arc_center = midpoint + l * normal_vector w = start_point - arc_center if w[0] != 0: - start_angle = np.arctan(w[1]/w[0]) + start_angle = np.arctan2(w[1],w[0]) else: start_angle = np.pi/2 @@ -122,18 +125,25 @@ class ArcBetweenPoints(Arc): radius = radius, start_angle = start_angle, **kwargs) + self.move_arc_center_to(arc_center) class CurvedArrow(ArcBetweenPoints): def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): - ArcBetweenPoints.__init__(self, start_point, end_point, angle = TAU/4, **kwargs) - self.add_tip() + # I know this is in reverse, but it works + if angle >= 0: + ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs) + self.add_tip(at_start = True, at_end = False) + else: + ArcBetweenPoints.__init__(self, end_point, start_point, angle = -angle, **kwargs) + self.add_tip(at_start = False, at_end = True) + class CurvedDoubleArrow(ArcBetweenPoints): def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): - ArcBetweenPoints.__init__(self, start_point, end_point, angle = TAU/4, **kwargs) + ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs) self.add_tip(at_start = True, at_end = True) From f5e545e3dd1228785f3ecd22d6ad80774cc007fe Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 23 Feb 2018 19:38:27 +0100 Subject: [PATCH 37/73] finished IntroScene tweaks --- active_projects/basel.py | 111 ++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 1f3b49f6..ff8f4316 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -241,8 +241,8 @@ class IntroScene(PiCreatureScene): CONFIG = { "rect_height" : 0.2, - "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF + "duration" : 0.5, + "eq_spacing" : 6 * MED_LARGE_BUFF } def construct(self): @@ -328,6 +328,8 @@ class IntroScene(PiCreatureScene): ReplacementTransform(self.partial_sum_decimal, self.q_marks) ) + self.wait() + def build_up_sum_on_number_line(self): @@ -342,21 +344,25 @@ class IntroScene(PiCreatureScene): unit_size = 5, tick_frequency = 0.2, line_to_number_buff = MED_LARGE_BUFF - ) + ).shift(LEFT) self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) + self.play( + FadeIn(self.number_line), + FadeIn(self.number_line_labels) + ) self.wait() # create slabs for series terms - max_n = 10 + max_n1 = 10 + max_n2 = 100 - terms = [0] + [1./(n**2) for n in range(1, max_n + 1)] + terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)] series_terms = np.cumsum(terms) lines = VGroup() self.rects = VGroup() - slab_colors = [YELLOW, BLUE] * (max_n / 2) + slab_colors = [YELLOW, BLUE] * (max_n2 / 2) for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): line = Line(*map(self.number_line.number_to_point, [t1, t2])) @@ -375,52 +381,102 @@ class IntroScene(PiCreatureScene): #self.rects.radial_gradient_highlight(ORIGIN, 5, YELLOW, BLUE) + self.little_euler_terms = VGroup() + for i in range(1,7): + if i == 1: + term = TexMobject("1", fill_color = slab_colors[i-1]) + else: + term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1]) + term.scale(0.4) + self.little_euler_terms.add(term) + + for i in range(5): self.play( GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), - run_time = self.duration) + run_time = 1) ) + term = self.little_euler_terms.submobjects[i] + term.next_to(self.rects[i], UP) + self.play(FadeIn(term)) - for i in range(5, max_n): + self.ellipsis = TexMobject("\cdots") + self.ellipsis.scale(0.4) + + for i in range(5, max_n1): + + if i == 5: + self.ellipsis.next_to(self.rects[i+3], UP) + self.play( + FadeIn(self.ellipsis), + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) + ) + else: + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) + ) + + for i in range(max_n1, max_n2): self.play( - GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), - run_time = self.duration) - ) + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.01) + ) + + self.wait() + + PI = TAU/2 + P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT + Q = self.rects[-1].get_center() + 0.2 * UP + self.arrow = CurvedArrow(P, Q, + angle = TAU/12, + color = YELLOW + ) + + self.play(FadeIn(self.arrow)) + + self.wait() def show_pi_answer(self): self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) self.pi_answer.move_to(self.partial_sum_decimal) - self.pi_answer.next_to(self.euler_sum[-1], RIGHT, + self.pi_answer.next_to(self.euler_sum[-1], RIGHT, buff = 1, submobject_to_align = self.pi_answer[-2]) self.play(ReplacementTransform(self.q_marks, self.pi_answer)) + self.wait() + def other_pi_formulas(self): self.play( FadeOut(self.rects), FadeOut(self.number_line_labels), - FadeOut(self.number_line) + FadeOut(self.number_line), + FadeOut(self.little_euler_terms), + FadeOut(self.ellipsis), + FadeOut(self.arrow) ) self.leibniz_sum = TexMobject( "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") + "=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ") self.wallis_product = TexMobject( "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") + "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ") self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, + buff = 2, submobject_to_align = self.leibniz_sum.get_part_by_tex("=") ) self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, + buff = 2, submobject_to_align = self.wallis_product.get_part_by_tex("=") ) @@ -448,7 +504,11 @@ class IntroScene(PiCreatureScene): # focus on pi squared pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] self.play( - ScaleInPlace(pi_squared,2,rate_func = wiggle) + WiggleOutThenIn(pi_squared, + scale_value = 4, + angle = 0.003 * TAU, + run_time = 2 + ) ) @@ -458,17 +518,20 @@ class IntroScene(PiCreatureScene): q_circle = Circle( stroke_color = YELLOW, fill_color = YELLOW, - fill_opacity = 0.5, - radius = 0.4, - stroke_width = 10.0 + fill_opacity = 0.25, + radius = 0.5, + stroke_width = 3.0 ) q_mark = TexMobject("?") q_mark.next_to(q_circle) thought = Group(q_circle, q_mark) - q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + q_mark.scale_to_fit_height(0.6 * q_circle.get_height()) + + self.look_at(pi_squared) self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 2, "width" : 3 }) + bubble_kwargs = { "height" : 2.5, "width" : 5 }) + self.look_at(pi_squared) self.wait() From 123b45298afeccad5b04f374191566ec5bedda92 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 26 Feb 2018 18:39:33 +0100 Subject: [PATCH 38/73] microedits --- active_projects/basel.py | 108 ++++++++++++++++++++++++++++----------- topics/light.py | 18 ++++--- 2 files changed, 87 insertions(+), 39 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index ff8f4316..d85ee926 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -18,7 +18,6 @@ from topics.characters import * from topics.functions import * from topics.number_line import * from topics.numerals import * -#from topics.combinatorics import * from scene import Scene from camera import Camera from mobject.svg_mobject import * @@ -40,14 +39,19 @@ FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -NUM_LEVELS = 30 NUM_CONES = 7 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -AMBIENT_FULL = 0.5 -AMBIENT_DIMMED = 0.2 -SPOTLIGHT_FULL = 0.9 + +NUM_LEVELS = 20 +AMBIENT_FULL = 0.8 +AMBIENT_DIMMED = 0.5 +AMBIENT_SCALE = 1.0 +AMBIENT_RADIUS = 20.0 +SPOTLIGHT_FULL = 0.8 SPOTLIGHT_DIMMED = 0.2 +SPOTLIGHT_SCALE = 1.0 +SPOTLIGHT_RADIUS = 20.0 LIGHT_COLOR = YELLOW DEGREES = TAU/360 @@ -373,7 +377,7 @@ class IntroScene(PiCreatureScene): rect.stretch_to_fit_height( self.rect_height, ) - rect.stretch_to_fit_width(line.get_width()) + rect.stretch_to_fit_width(0.5 * line.get_width()) rect.move_to(line) self.rects.add(rect) @@ -604,7 +608,7 @@ class FirstLighthouseScene(PiCreatureScene): width = 2.5, height = 3.5) bubble.next_to(randy,LEFT+UP) bubble.add_content(light_indicator) - + self.wait() self.play( randy.change, "wave_2", ShowCreation(bubble), @@ -629,15 +633,15 @@ class FirstLighthouseScene(PiCreatureScene): for i in range(1,NUM_CONES+1): light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1), num_levels = NUM_LEVELS, - radius = 12.0, + radius = AMBIENT_RADIUS, ) point = self.number_line.number_to_point(i) light_source.move_source_to(point) light_sources.append(light_source) - + self.wait() for ls in light_sources: self.add_foreground_mobject(ls.lighthouse) @@ -651,7 +655,7 @@ class FirstLighthouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) @@ -664,10 +668,12 @@ class FirstLighthouseScene(PiCreatureScene): # this last line *technically* fades in the last term, but it is off-screen ChangeDecimalToValue(light_indicator.reading,intensities[i], rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i], + rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME) ) if i == 0: + self.wait() # move a copy out of the thought bubble for comparison light_indicator_copy = light_indicator.copy() old_y = light_indicator_copy.get_center()[1] @@ -676,6 +682,10 @@ class FirstLighthouseScene(PiCreatureScene): light_indicator_copy.shift,[0, new_y - old_y,0] ) + self.wait() + + self.wait() + # quickly switch on off-screen light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size @@ -754,10 +764,12 @@ class SingleLighthouseScene(PiCreatureScene): # Light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), num_levels = NUM_LEVELS, radius = 10, - max_opacity_ambient = AMBIENT_FULL + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, + ) self.light_source.move_source_to(source_point) @@ -780,7 +792,7 @@ class SingleLighthouseScene(PiCreatureScene): # Screen self.screen = Rectangle( - width = 0.1, + width = 0.06, height = 2, mark_paths_closed = True, fill_color = WHITE, @@ -797,26 +809,39 @@ class SingleLighthouseScene(PiCreatureScene): self.play(FadeIn(self.screen)) - self.light_source.set_max_opacity_spotlight(0.001) - self.add(self.light_source.spotlight) + #self.light_source.set_max_opacity_spotlight(0.001) + #self.play(SwitchOn(self.light_source.spotlight)) - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) self.wait() + + # just calling .dim_ambient via ApplyMethod does not work, why? dimmed_ambient_light = self.light_source.ambient_light.deepcopy() dimmed_ambient_light.dimming(AMBIENT_DIMMED) + self.light_source.update_shadow() self.play( - Transform(self.light_source.ambient_light,dimmed_ambient_light), - self.light_source.set_max_opacity_spotlight,1.0, - FadeIn(self.light_source.shadow) + FadeIn(self.light_source.shadow), + ) + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) + + self.play( + self.light_source.dim_ambient, + #Transform(self.light_source.ambient_light,dimmed_ambient_light), + #self.light_source.set_max_opacity_spotlight,1.0, + ) + self.play( + FadeIn(self.light_source.spotlight) ) - self.add_foreground_mobject(morty) + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() @@ -843,7 +868,9 @@ class SingleLighthouseScene(PiCreatureScene): self.angle_indicator = DecimalNumber(arc_angle / DEGREES, num_decimal_points = 0, - unit = "^\\circ") + unit = "^\\circ", + fill_opacity = 1.0, + fill_color = WHITE) self.angle_indicator.next_to(self.angle_arc,RIGHT) angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES @@ -866,6 +893,7 @@ class SingleLighthouseScene(PiCreatureScene): self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.wait() @@ -891,6 +919,7 @@ class SingleLighthouseScene(PiCreatureScene): FadeOut(self.angle_arc), FadeOut(self.angle_indicator) ) + self.wait() self.sun = self.light_source.deepcopy() @@ -904,9 +933,10 @@ class SingleLighthouseScene(PiCreatureScene): self.sun.set_radius(150) self.sun.move_source_to(sun_position) - # self.sun.update() + #self.sun.update() - # self.add(self.sun) + #self.add(self.sun) + self.wait() # temporarily remove the screen tracker while we move the source #self.remove(self.screen_tracker) @@ -951,8 +981,8 @@ class EarthScene(Scene): # screen self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) self.screen.set_points_as_corners([ - [3,-self.screen_height/2,0], - [3,self.screen_height/2,0] + [0,-self.screen_height/2,0], + [0,self.screen_height/2,0] ]) # Earth @@ -961,6 +991,7 @@ class EarthScene(Scene): earth_center = [earth_center_x,0,0] earth_radius = 3 earth = Circle(radius = earth_radius) + earth.add(self.screen) earth.move_to(earth_center) #self.remove(self.screen_tracker) @@ -969,8 +1000,15 @@ class EarthScene(Scene): theta1 = theta0 + dtheta theta = (theta0 + theta1)/2 - earth.add(self.screen) + self.add_foreground_mobject(self.screen) + # background Earth + background_earth = SVGMobject( + file_name = "earth", + width = 2 * earth_radius, + fill_color = BLUE, + ) + background_earth.move_to(earth_center) # Morty morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) @@ -1003,8 +1041,13 @@ class EarthScene(Scene): self.wait() - self.play(FadeIn(earth)) - self.bring_to_back(earth) + self.play( + FadeIn(earth), + FadeIn(background_earth) + ) + self.add_foreground_mobject(earth) + self.add_foreground_mobject(self.screen) + # move screen onto Earth screen_on_earth = self.screen.deepcopy() @@ -1016,6 +1059,7 @@ class EarthScene(Scene): 0])) polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) + polar_morty.highlight(BLUE_C) self.play( Transform(self.screen, screen_on_earth), @@ -1027,6 +1071,8 @@ class EarthScene(Scene): tropical_morty = polar_morty.copy() tropical_morty.move_to(np.array([0,0,0])) + tropical_morty.highlight(RED) + morty.target = tropical_morty # move screen to equator diff --git a/topics/light.py b/topics/light.py index 3bbcd9e5..c24662a5 100644 --- a/topics/light.py +++ b/topics/light.py @@ -29,10 +29,10 @@ 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 +AMBIENT_FULL = 0.8 +AMBIENT_DIMMED = 0.5 +SPOTLIGHT_FULL = 0.8 +SPOTLIGHT_DIMMED = 0.5 LIGHTHOUSE_HEIGHT = 0.8 DEGREES = TAU/360 @@ -57,7 +57,7 @@ class LightSource(VMobject): "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "color": LIGHT_COLOR, "num_levels": 10, - "radius": 5, + "radius": 10.0, "screen": None, "opacity_function": inverse_quadratic(1,2,1), "max_opacity_ambient": AMBIENT_FULL, @@ -143,7 +143,9 @@ class LightSource(VMobject): num_levels = self.num_levels, radius = self.radius, screen = new_screen, - camera_mob = self.camera_mob + camera_mob = self.camera_mob, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_spotlight, ) self.spotlight.move_source_to(self.get_source_point()) @@ -371,7 +373,7 @@ class AmbientLight(VMobject): "color" : LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, - "radius" : 5.0 + "radius" : 10.0 } def generate_points(self): @@ -452,7 +454,7 @@ class Spotlight(VMobject): "color" : GREEN, # LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, - "radius" : 5.0, + "radius" : 10.0, "screen" : None, "camera_mob": None } From 1ba56c3bba2a50c438dcf6f2d46980f39fa4f918 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 11:41:44 -0800 Subject: [PATCH 39/73] Satisfying finicky spacing desires --- active_projects/basel.py | 159 ++++++++------------------------------- 1 file changed, 30 insertions(+), 129 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 2e468e50..40b16628 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -95,10 +95,6 @@ class AngleUpdater(ContinualAnimation): self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) - - - - class LightIndicator(Mobject): CONFIG = { "radius": 0.5, @@ -148,9 +144,6 @@ class LightIndicator(Mobject): print "Indicator cannot update, reason: no light source found" self.set_intensity(self.measured_intensity()) - - - class UpdateLightIndicator(AnimationGroup): def __init__(self, indicator, intensity, **kwargs): @@ -165,13 +158,11 @@ class UpdateLightIndicator(AnimationGroup): AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator - class ContinualLightIndicatorUpdate(ContinualAnimation): def update_mobject(self,dt): self.mobject.continual_update() - def copy_func(f): """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, @@ -221,21 +212,7 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) - - - - - - - - - - - - - - - +### class IntroScene(PiCreatureScene): @@ -472,25 +449,6 @@ class IntroScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - - class FirstLighthouseScene(PiCreatureScene): def construct(self): @@ -647,28 +605,6 @@ class FirstLighthouseScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - - - - - class SingleLighthouseScene(PiCreatureScene): def construct(self): @@ -858,24 +794,6 @@ class SingleLighthouseScene(PiCreatureScene): self.wait() - - - - - - - - - - - - - - - - - - class EarthScene(Scene): def construct(self): @@ -973,30 +891,6 @@ class EarthScene(Scene): MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), ) - - - - - - - - - - - - - - - - - - - - - - - - class ScreenShapingScene(ThreeDScene): @@ -1442,9 +1336,6 @@ class ScreenShapingScene(ThreeDScene): FadeIn(reading39), ) - - - class IndicatorScalingScene(Scene): def construct(self): @@ -1482,11 +1373,6 @@ class IndicatorScalingScene(Scene): self.play(FadeIn(reading3)) self.wait() - - - - - class BackToEulerSumScene(PiCreatureScene): @@ -1705,10 +1591,6 @@ class BackToEulerSumScene(PiCreatureScene): self.wait() - - - - class TwoLightSourcesScene(PiCreatureScene): def construct(self): @@ -1886,8 +1768,6 @@ class TwoLightSourcesScene(PiCreatureScene): Write(theorem_name), ) - - class IPTScene1(PiCreatureScene): def construct(self): @@ -2078,8 +1958,6 @@ class IPTScene1(PiCreatureScene): Transform(screen2, screen2pp), ) - - class IPTScene2(Scene): def construct(self): @@ -2131,7 +2009,6 @@ class IPTScene2(Scene): text.next_to(box,UP) self.play(ShowCreation(box),Write(text)) - class PondScene(Scene): def construct(self): @@ -2606,8 +2483,6 @@ class PondScene(Scene): ) self.play(FadeIn(self.number_line)) - - class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -2627,9 +2502,6 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) - - - class ArcHighlightOverlayScene(Scene): def construct(self): @@ -2686,3 +2558,32 @@ class ArcHighlightOverlayScene(Scene): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 99783d77a0bd28ffc192cefec80f4d934efa9034 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 11:58:56 -0800 Subject: [PATCH 40/73] Added edits to intro scene --- active_projects/basel.py | 133 ++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 57 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 40b16628..d4bcb914 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -214,6 +214,36 @@ class ScaleLightSources(Transform): ### +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 9, + height = 5, + ) + circles = bubble[:3] + angle = -15*DEGREES + circles.rotate(angle, about_point = bubble.get_bubble_center()) + circles.shift(LARGE_BUFF*LEFT) + for circle in circles: + circle.rotate(-angle) + bubble.pin_to(randy) + bubble.shift_onto_screen() + + self.play( + randy.change, "thinking", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(2) + self.play(randy.change, "hooray", bubble) + self.wait(2) + class IntroScene(PiCreatureScene): CONFIG = { @@ -223,23 +253,17 @@ class IntroScene(PiCreatureScene): } def construct(self): - randy = self.get_primary_pi_creature() randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.build_up_sum_on_number_line() self.show_pi_answer() self.other_pi_formulas() self.refocus_on_euler_sum() - - - def build_up_euler_sum(self): - - self.euler_sum = TexMobject( + euler_sum = self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", "{1 \\over 9}", "+", @@ -248,16 +272,15 @@ class IntroScene(PiCreatureScene): "\\cdots", "=", arg_separator = " \\, " ) + equals_sign = euler_sum.get_part_by_tex("=") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - terms = [1./n**2 for n in range(1,6)] + terms = [1./n**2 for n in range(1,100)] partial_results_values = np.cumsum(terms) - self.play( - FadeIn(self.euler_sum[0], run_time = self.duration) - ) + self.play(FadeIn(euler_sum[0])) equals_sign = self.euler_sum.get_part_by_tex("=") @@ -265,49 +288,7 @@ class IntroScene(PiCreatureScene): num_decimal_points = 2) self.partial_sum_decimal.next_to(equals_sign, RIGHT) - - - for i in range(4): - - FadeIn(self.partial_sum_decimal, run_time = self.duration) - - if i == 0: - - self.play( - FadeIn(self.euler_sum[1], run_time = self.duration), - FadeIn(self.euler_sum[2], run_time = self.duration), - FadeIn(equals_sign, run_time = self.duration), - FadeIn(self.partial_sum_decimal, run_time = self.duration) - ) - - else: - self.play( - FadeIn(self.euler_sum[2*i+1], run_time = self.duration), - FadeIn(self.euler_sum[2*i+2], run_time = self.duration), - ChangeDecimalToValue( - self.partial_sum_decimal, - partial_results_values[i+1], - run_time = self.duration, - num_decimal_points = 6, - show_ellipsis = True, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ) - - self.wait() - - self.q_marks = TextMobject("???").highlight(LIGHT_COLOR) - self.q_marks.move_to(self.partial_sum_decimal) - - self.play( - FadeIn(self.euler_sum[-3], run_time = self.duration), # + - FadeIn(self.euler_sum[-2], run_time = self.duration), # ... - ReplacementTransform(self.partial_sum_decimal, self.q_marks) - ) - - - - def build_up_sum_on_number_line(self): + ## Number line self.number_line = NumberLine( x_min = 0, @@ -365,6 +346,47 @@ class IntroScene(PiCreatureScene): ) + ## + + + for i in range(4): + + FadeIn(self.partial_sum_decimal, run_time = self.duration) + + if i == 0: + + self.play( + FadeIn(self.euler_sum[1], run_time = self.duration), + FadeIn(self.euler_sum[2], run_time = self.duration), + FadeIn(equals_sign, run_time = self.duration), + FadeIn(self.partial_sum_decimal, run_time = self.duration) + ) + + else: + self.play( + FadeIn(self.euler_sum[2*i+1], run_time = self.duration), + FadeIn(self.euler_sum[2*i+2], run_time = self.duration), + ChangeDecimalToValue( + self.partial_sum_decimal, + partial_results_values[i+1], + run_time = self.duration, + num_decimal_points = 6, + show_ellipsis = True, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ) + + self.wait() + + self.q_marks = TextMobject("???").highlight(LIGHT_COLOR) + self.q_marks.move_to(self.partial_sum_decimal) + + self.play( + FadeIn(self.euler_sum[-3], run_time = self.duration), # + + FadeIn(self.euler_sum[-2], run_time = self.duration), # ... + ReplacementTransform(self.partial_sum_decimal, self.q_marks) + ) + def show_pi_answer(self): self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) @@ -373,7 +395,6 @@ class IntroScene(PiCreatureScene): submobject_to_align = self.pi_answer[-2]) self.play(ReplacementTransform(self.q_marks, self.pi_answer)) - def other_pi_formulas(self): self.play( @@ -409,8 +430,6 @@ class IntroScene(PiCreatureScene): Write(self.wallis_product) ) - - def refocus_on_euler_sum(self): self.euler_sum.add(self.pi_answer) From a06c9d32d9bbdd37678a14d019a35a20a07781dc Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 27 Feb 2018 00:22:16 +0100 Subject: [PATCH 41/73] microedits (not yet working) --- active_projects/basel.py | 112 +++++++++++++++++++++++++++++---------- topics/light.py | 3 ++ 2 files changed, 87 insertions(+), 28 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index d85ee926..1854360a 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -749,7 +749,7 @@ class SingleLighthouseScene(PiCreatureScene): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates self.rotate_screen() - self.morph_lighthouse_into_sun() + #self.morph_lighthouse_into_sun() def setup_elements(self): @@ -908,46 +908,102 @@ class SingleLighthouseScene(PiCreatureScene): ### but it doesn't work - def morph_lighthouse_into_sun(self): +class MorphIntoSunScene(PiCreatureScene): + + def construct(self): + + self.setup_elements() + self.morph_lighthouse_into_sun() + def setup_elements(self): - sun_position = [-100,0,0] + self.remove(self.get_primary_pi_creature()) + SCREEN_SIZE = 3.0 + DISTANCE_FROM_LIGHTHOUSE = 10.0 + source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] + observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + + # Light source + + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) ) - self.wait() - self.sun = self.light_source.deepcopy() + self.light_source.move_source_to(source_point) - #self.sun.num_levels = NUM_LEVELS, - #self.sun.set_radius(150) - #self.sun.set_max_opacity_ambient(AMBIENT_FULL) + + # Pi Creature + + morty = self.get_primary_pi_creature() + morty.scale(0.5) + morty.move_to(observer_point) + morty.shift(2*OUT) + self.add_foreground_mobject(morty) + + self.add(self.light_source.lighthouse,self.light_source.ambient_light) + # Screen - self.sun.spotlight.change_opacity_function(lambda r: 0.5) - self.sun.set_radius(150) - self.sun.move_source_to(sun_position) - - #self.sun.update() - - #self.add(self.sun) - self.wait() - # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) - - #print self.sun.spotlight.get_source_point() - - self.play( - #self.light_source.spotlight.move_source_to,sun_position, - Transform(self.light_source,self.sun) + self.screen = Rectangle( + width = 0.06, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 ) - #self.add(ScreenTracker(self.sun)) + self.screen.next_to(morty,LEFT) + + self.light_source.set_screen(self.screen) + self.add(self.screen,self.light_source.shadow) + + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) + + self.light_source.dim_ambient + self.add(self.light_source.spotlight) + + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + def morph_lighthouse_into_sun(self): + + sun_position = np.array([-100,0,0]) + + + + + # Why does none of this change the opacity function??? + + self.sun = self.light_source.copy() + + self.sun.change_spotlight_opacity_function(lambda r: 0.1) + # self.sun.spotlight.opacity_function = lambda r: 0.1 + # for submob in self.sun.spotlight.submobjects: + # submob.set_fill(opacity = 0.1) + + #self.sun.move_source_to(sun_position) + #self.sun.set_radius(120) + + self.sun.spotlight.generate_points() + + self.wait() + + self.play( + Transform(self.light_source,self.sun) + ) self.wait() diff --git a/topics/light.py b/topics/light.py index c24662a5..5b95f63e 100644 --- a/topics/light.py +++ b/topics/light.py @@ -180,6 +180,9 @@ class LightSource(VMobject): self.spotlight.move_source_to(apoint) self.update() return self + + def change_spotlight_opacity_function(self, new_of): + self.spotlight.change_opacity_function(new_of) def set_radius(self,new_radius): self.radius = new_radius From 671b6e6fe88375088a32cada0aed0af183899ea9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 16:13:28 -0800 Subject: [PATCH 42/73] Changes to basel, and replication of the file --- active_projects/basel.py | 479 ++++-- active_projects/basel2.py | 2909 +++++++++++++++++++++++++++++++++++++ 2 files changed, 3299 insertions(+), 89 deletions(-) create mode 100644 active_projects/basel2.py diff --git a/active_projects/basel.py b/active_projects/basel.py index d4bcb914..c975060e 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -247,9 +247,10 @@ class ThinkAboutPondScene(PiCreatureScene): class IntroScene(PiCreatureScene): CONFIG = { - "rect_height" : 0.2, + "rect_height" : 0.1, "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF + "eq_spacing" : 3 * MED_LARGE_BUFF, + "n_rects_to_show" : 30, } def construct(self): @@ -257,12 +258,12 @@ class IntroScene(PiCreatureScene): randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.show_pi_answer() - self.other_pi_formulas() - self.refocus_on_euler_sum() - + self.show_history() + # self.other_pi_formulas() + # self.refocus_on_euler_sum() def build_up_euler_sum(self): + morty = self.pi_creature euler_sum = self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", @@ -273,24 +274,26 @@ class IntroScene(PiCreatureScene): arg_separator = " \\, " ) equals_sign = euler_sum.get_part_by_tex("=") + plusses = euler_sum.get_parts_by_tex("+") + term_mobjects = euler_sum.get_parts_by_tex("1") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - terms = [1./n**2 for n in range(1,100)] - partial_results_values = np.cumsum(terms) + max_n = self.n_rects_to_show + terms = [1./(n**2) for n in range(1, max_n + 1)] + series_terms = list(np.cumsum(terms)) + series_terms.append(np.pi**2/6) ##Just force this up there - self.play(FadeIn(euler_sum[0])) - - equals_sign = self.euler_sum.get_part_by_tex("=") - - self.partial_sum_decimal = DecimalNumber(partial_results_values[1], - num_decimal_points = 2) - self.partial_sum_decimal.next_to(equals_sign, RIGHT) + partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( + series_terms[1], + num_decimal_points = 2 + ) + partial_sum_decimal.next_to(equals_sign, RIGHT) ## Number line - self.number_line = NumberLine( + number_line = self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1, @@ -301,105 +304,214 @@ class IntroScene(PiCreatureScene): tick_frequency = 0.2, line_to_number_buff = MED_LARGE_BUFF ) - - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() + number_line.add_numbers() + number_line.to_edge(LEFT) + number_line.shift(MED_LARGE_BUFF*UP) # create slabs for series terms - max_n = 10 - - terms = [0] + [1./(n**2) for n in range(1, max_n + 1)] - series_terms = np.cumsum(terms) lines = VGroup() - self.rects = VGroup() - slab_colors = [YELLOW, BLUE] * (max_n / 2) + rects = self.rects = VGroup() + rect_labels = VGroup() + slab_colors = it.cycle([YELLOW, BLUE]) + rect_anims = [] + rect_label_anims = [] - for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): - line = Line(*map(self.number_line.number_to_point, [t1, t2])) - rect = Rectangle() - rect.stroke_width = 0 - rect.fill_opacity = 1 - rect.highlight(color) - rect.stretch_to_fit_height( - self.rect_height, + for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): + color = slab_colors.next() + line = Line(*map(number_line.number_to_point, [t1, t2])) + rect = Rectangle( + stroke_width = 0, + fill_opacity = 1, + fill_color = color ) - rect.stretch_to_fit_width(line.get_width()) + rect.match_width(line) + rect.stretch_to_fit_height(self.rect_height) rect.move_to(line) - self.rects.add(rect) - lines.add(line) + if i <= 5: + if i == 1: + rect_label = TexMobject("1") + else: + rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) + rect_label.scale(0.75) + max_width = 0.7*rect.get_width() + if rect_label.get_width() > max_width: + rect_label.scale_to_fit_width(max_width) + rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) - #self.rects.radial_gradient_highlight(ORIGIN, 5, YELLOW, BLUE) - - for i in range(5): - self.play( - GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), - run_time = self.duration) - ) - - for i in range(5, max_n): - self.play( - GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), - run_time = self.duration) - ) - - - ## - - - for i in range(4): - - FadeIn(self.partial_sum_decimal, run_time = self.duration) - - if i == 0: - - self.play( - FadeIn(self.euler_sum[1], run_time = self.duration), - FadeIn(self.euler_sum[2], run_time = self.duration), - FadeIn(equals_sign, run_time = self.duration), - FadeIn(self.partial_sum_decimal, run_time = self.duration) + term_mobject = term_mobjects[i-1] + rect_anim = GrowFromPoint(rect, term_mobject.get_center()) + rect_label_anim = ReplacementTransform( + term_mobject.copy(), rect_label ) - else: - self.play( - FadeIn(self.euler_sum[2*i+1], run_time = self.duration), - FadeIn(self.euler_sum[2*i+2], run_time = self.duration), + rect_label = VectorizedPoint() + rect_anim = GrowFromPoint(rect, rect.get_left()) + rect_label_anim = FadeIn(rect_label) + + rects.add(rect) + rect_labels.add(rect_label) + rect_anims.append(rect_anim) + rect_label_anims.append(rect_label_anim) + lines.add(line) + dots = TexMobject("\\dots").scale(0.5) + last_rect = rect_anims[-1].target_mobject + dots.scale_to_fit_width(0.9*last_rect.get_width()) + dots.move_to(last_rect, UP+RIGHT) + rects.submobjects[-1] = dots + rect_anims[-1] = FadeIn(dots) + + self.add(number_line) + self.play(FadeIn(euler_sum[0])) + self.play( + rect_anims[0], + rect_label_anims[0] + ) + for i in range(4): + self.play( + FadeIn(term_mobjects[i+1]), + FadeIn(plusses[i]), + ) + anims = [ + rect_anims[i+1], + rect_label_anims[i+1], + ] + if i == 0: + anims += [ + FadeIn(equals_sign), + FadeIn(partial_sum_decimal) + ] + elif i <= 5: + anims += [ ChangeDecimalToValue( - self.partial_sum_decimal, - partial_results_values[i+1], - run_time = self.duration, + partial_sum_decimal, + series_terms[i+1], + run_time = 1, num_decimal_points = 6, - show_ellipsis = True, position_update_func = lambda m: m.next_to(equals_sign, RIGHT) ) - ) - - self.wait() + ] + self.play(*anims) - self.q_marks = TextMobject("???").highlight(LIGHT_COLOR) - self.q_marks.move_to(self.partial_sum_decimal) + for i in range(4, len(series_terms)-2): + anims = [ + rect_anims[i+1], + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + num_decimal_points = 6, + ), + ] + if i == 5: + anims += [ + FadeIn(euler_sum[-3]), # + + FadeIn(euler_sum[-2]), # ... + ] + self.play(*anims, run_time = 2./i) + + brace = self.brace = Brace(partial_sum_decimal, DOWN) + q_marks = self.q_marks = TextMobject("???") + q_marks.next_to(brace, DOWN) + q_marks.highlight(LIGHT_COLOR) self.play( - FadeIn(self.euler_sum[-3], run_time = self.duration), # + - FadeIn(self.euler_sum[-2], run_time = self.duration), # ... - ReplacementTransform(self.partial_sum_decimal, self.q_marks) + GrowFromCenter(brace), + Write(q_marks), + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[-1], + num_decimal_points = 6, + ), + morty.change, "confused", + ) + self.wait() + + self.number_line_group = VGroup( + number_line, rects, rect_labels ) - def show_pi_answer(self): + def show_history(self): + # Pietro Mengoli in 1644 + morty = self.pi_creature + pietro = ImageMobject("Pietro_Mengoli") + euler = ImageMobject("Euler") - self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) - self.pi_answer.move_to(self.partial_sum_decimal) - self.pi_answer.next_to(self.euler_sum[-1], RIGHT, - submobject_to_align = self.pi_answer[-2]) - self.play(ReplacementTransform(self.q_marks, self.pi_answer)) + pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") + pietro_words.scale(0.75) + pietro_words.next_to(pietro, DOWN) + pietro.add(pietro_words) + + euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") + euler_words.scale(0.75) + euler_words.next_to(euler, DOWN) + euler.add(euler_words) + + pietro.next_to(SPACE_WIDTH*LEFT, LEFT) + euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) + + pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") + pi_answer.highlight(YELLOW) + pi_answer.move_to(self.partial_sum_decimal, LEFT) + equals_sign = TexMobject("=") + equals_sign.next_to(pi_answer, RIGHT) + pi_answer.shift(SMALL_BUFF*UP) + self.partial_sum_decimal.generate_target() + self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) + + pi = pi_answer[0] + pi_rect = SurroundingRectangle(pi, color = RED) + pi_rect.save_state() + pi_rect.scale_to_fit_height(SPACE_HEIGHT) + pi_rect.center() + pi_rect.set_stroke(width = 0) + squared = pi_answer[1] + squared_rect = SurroundingRectangle(squared, color = BLUE) + + brace = Brace( + VGroup(self.euler_sum, self.partial_sum_decimal.target), + DOWN, buff = SMALL_BUFF + ) + basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) + + self.number_line_group.save_state() + self.play( + pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, + self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, + morty.change, "pondering", + ) + self.wait(2) + self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) + self.wait(2) + self.play( + ReplacementTransform(self.q_marks, pi_answer), + FadeIn(equals_sign), + FadeOut(self.brace), + MoveToTarget(self.partial_sum_decimal) + ) + self.wait() + self.play(morty.change, "surprised") + self.play(pi_rect.restore) + self.wait() + self.play(Transform(pi_rect, squared_rect)) + self.play(FadeOut(pi_rect)) + self.play(morty.change, "hesitant") + self.wait(2) + self.play( + GrowFromCenter(brace), + euler.to_edge, DOWN, + pietro.to_edge, DOWN, + self.number_line_group.restore, + self.number_line_group.shift, LARGE_BUFF*RIGHT, + ) + self.play(Write(basel_text)) + self.play(morty.change, "happy") + self.wait(4) def other_pi_formulas(self): self.play( FadeOut(self.rects), - FadeOut(self.number_line_labels), FadeOut(self.number_line) ) @@ -468,6 +580,195 @@ class IntroScene(PiCreatureScene): self.wait() +class PiHidingWrapper(Scene): + def construct(self): + title = TextMobject("Pi hiding in prime regularities") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen)) + self.wait(2) + +class MathematicalWebOfConnections(PiCreatureScene): + def construct(self): + self.complain_that_pi_is_not_about_circles() + self.show_other_pi_formulas() + self.question_fundamental() + self.draw_circle() + self.remove_all_but_basel_sum() + self.show_web_of_connections() + self.show_light() + + def complain_that_pi_is_not_about_circles(self): + jerk, randy = self.pi_creatures + + words = self.words = TextMobject( + "$\\pi$ is not", + "fundamentally \\\\", + "about circles" + ) + words.highlight_by_tex("fundamentally", YELLOW) + + self.play(PiCreatureSays( + jerk, words, + target_mode = "angry" + )) + self.play(randy.change, "guilty") + self.wait(2) + + def show_other_pi_formulas(self): + jerk, randy = self.pi_creatures + words = self.words + + basel_sum = TexMobject( + "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", + "=", "{\\pi^2 \\over 6}" + ) + leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + basel_sum.move_to(randy) + basel_sum.to_edge(UP) + basel_equals = basel_sum.get_part_by_tex("=") + + formulas = VGroup(basel_sum, leibniz_sum, wallis_product) + formulas.scale(0.75) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + for formula in formulas: + basel_equals_x = basel_equals.get_center()[0] + formula_equals_x = formula.get_part_by_tex("=").get_center()[0] + formula.shift((basel_equals_x - formula_equals_x)*RIGHT) + + formulas.move_to(randy) + formulas.to_edge(UP) + formulas.shift_onto_screen() + self.formulas = formulas + + self.play( + jerk.change, "sassy", + randy.change, "raise_right_hand", + FadeOut(jerk.bubble), + words.next_to, jerk, UP, + FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) + ) + for formula in formulas[1:]: + self.play( + FadeIn( + formula, + submobject_mode = "lagged_start", + run_time = 3 + ), + ) + self.wait() + + def question_fundamental(self): + jerk, randy = self.pi_creatures + words = self.words + fundamentally = words.get_part_by_tex("fundamentally") + words.remove(fundamentally) + + self.play( + fundamentally.move_to, self.pi_creatures, + fundamentally.shift, UP, + FadeOut(words), + jerk.change, "pondering", + randy.change, "pondering", + ) + self.wait() + + question = TextMobject("Does this mean \\\\ anything?") + question.scale(0.8) + question.set_stroke(WHITE, 0.5) + question.next_to(fundamentally, DOWN, LARGE_BUFF) + arrow = Arrow(question, fundamentally) + arrow.highlight(WHITE) + + self.play( + FadeIn(question), + GrowArrow(arrow) + ) + self.wait() + + fundamentally.add(question, arrow) + self.fundamentally = fundamentally + + def draw_circle(self): + semi_circle = Arc(angle = np.pi, radius = 2) + radius = Line(ORIGIN, semi_circle.points[0]) + radius.highlight(BLUE) + semi_circle.highlight(YELLOW) + + VGroup(radius, semi_circle).move_to( + SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, + ) + + decimal = DecimalNumber(0) + def decimal_position_update_func(decimal): + decimal.move_to(semi_circle.points[-1]) + decimal.shift(0.3*radius.get_vector()) + + one = TexMobject("1") + one.next_to(radius, UP) + + self.play(ShowCreation(radius), FadeIn(one)) + self.play( + Rotate(radius, np.pi, about_point = radius.get_start()), + ShowCreation(semi_circle), + ChangeDecimalToValue( + decimal, np.pi, + position_update_func = decimal_position_update_func + ), + MaintainPositionRelativeTo(one, radius), + run_time = 3, + ) + self.wait(2) + + self.circle_group = VGroup(semi_circle, radius, one, decimal) + + def remove_all_but_basel_sum(self): + to_shift_down = VGroup( + self.circle_group, self.pi_creatures, + self.fundamentally, self.formulas[1:], + ) + to_shift_down.generate_target() + for part in to_shift_down.target: + part.move_to(2*SPACE_HEIGHT*DOWN) + + basel_sum = self.formulas[0] + + self.play( + MoveToTarget(to_shift_down), + basel_sum.scale, 1.5, + basel_sum.move_to, 2*DOWN, + ) + + def show_web_of_connections(self): + pass + + def show_light(self): + pass + + ### + + def create_pi_creatures(self): + jerk = PiCreature(color = GREEN_D) + randy = Randolph().flip() + jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) + randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) + + return VGroup(jerk, randy) + + + + + class FirstLighthouseScene(PiCreatureScene): def construct(self): diff --git a/active_projects/basel2.py b/active_projects/basel2.py new file mode 100644 index 00000000..c975060e --- /dev/null +++ b/active_projects/basel2.py @@ -0,0 +1,2909 @@ +#!/usr/bin/env python + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.continual_animation import * + +from animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.numerals import * +#from topics.combinatorics import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from topics.three_dimensions import * + +from topics.light import * + +import types +import functools + +LIGHT_COLOR = YELLOW +INDICATOR_RADIUS = 0.7 +INDICATOR_STROKE_WIDTH = 1 +INDICATOR_STROKE_COLOR = WHITE +INDICATOR_TEXT_COLOR = WHITE +INDICATOR_UPDATE_TIME = 0.2 +FAST_INDICATOR_UPDATE_TIME = 0.1 +OPACITY_FOR_UNIT_INTENSITY = 0.2 +SWITCH_ON_RUN_TIME = 1.5 +FAST_SWITCH_ON_RUN_TIME = 0.1 +NUM_LEVELS = 30 +NUM_CONES = 7 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 +AMBIENT_FULL = 0.5 +AMBIENT_DIMMED = 0.2 +SPOTLIGHT_FULL = 0.9 +SPOTLIGHT_DIMMED = 0.2 + +LIGHT_COLOR = YELLOW +DEGREES = TAU/360 + +inverse_power_law = lambda maxint,scale,cutoff,exponent: \ + (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) +inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) + + +A = np.array([5.,-3.,0.]) +B = np.array([-5.,3.,0.]) +C = np.array([-5.,-3.,0.]) +xA = A[0] +yA = A[1] +xB = B[0] +yB = B[1] +xC = C[0] +yC = C[1] + +# find the coords of the altitude point H +# as the solution of a certain LSE +prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic +prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) +H2 = np.linalg.solve(prelim_matrix,prelim_vector) +H = np.append(H2, 0.) + + + + +class AngleUpdater(ContinualAnimation): + def __init__(self, angle_arc, spotlight, **kwargs): + self.angle_arc = angle_arc + + self.spotlight = spotlight + ContinualAnimation.__init__(self, self.angle_arc, **kwargs) + + def update_mobject(self, dt): + new_arc = self.angle_arc.copy().set_bound_angles( + start = self.spotlight.start_angle(), + stop = self.spotlight.stop_angle() + ) + new_arc.generate_points() + new_arc.move_arc_center_to(self.spotlight.get_source_point()) + self.angle_arc.points = new_arc.points + self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, + at_start = True, at_end = True) + +class LightIndicator(Mobject): + CONFIG = { + "radius": 0.5, + "intensity": 0, + "opacity_for_unit_intensity": 1, + "precision": 3, + "show_reading": True, + "measurement_point": ORIGIN, + "light_source": None + } + + def generate_points(self): + self.background = Circle(color=BLACK, radius = self.radius) + self.background.set_fill(opacity=1.0) + self.foreground = Circle(color=self.color, radius = self.radius) + self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) + + self.add(self.background, self.foreground) + self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) + self.reading.set_fill(color=INDICATOR_TEXT_COLOR) + self.reading.move_to(self.get_center()) + if self.show_reading: + self.add(self.reading) + + def set_intensity(self, new_int): + self.intensity = new_int + new_opacity = min(1, new_int * self.opacity_for_unit_intensity) + self.foreground.set_fill(opacity=new_opacity) + ChangeDecimalToValue(self.reading, new_int).update(1) + return self + + def get_measurement_point(self): + if self.measurement_point != None: + return self.measurement_point + else: + return self.get_center() + + + def measured_intensity(self): + distance = np.linalg.norm(self.get_measurement_point() - + self.light_source.get_source_point()) + intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity + return intensity + + def continual_update(self): + if self.light_source == None: + print "Indicator cannot update, reason: no light source found" + self.set_intensity(self.measured_intensity()) + +class UpdateLightIndicator(AnimationGroup): + + def __init__(self, indicator, intensity, **kwargs): + if not isinstance(indicator,LightIndicator): + raise Exception("This transform applies only to LightIndicator") + + target_foreground = indicator.copy().set_intensity(intensity).foreground + change_opacity = Transform( + indicator.foreground, target_foreground + ) + changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) + AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) + self.mobject = indicator + +class ContinualLightIndicatorUpdate(ContinualAnimation): + + def update_mobject(self,dt): + self.mobject.continual_update() + +def copy_func(f): + """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" + g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, + argdefs=f.func_defaults, + closure=f.func_closure) + g = functools.update_wrapper(g, f) + return g + +class ScaleLightSources(Transform): + + def __init__(self, light_sources_mob, factor, about_point = None, **kwargs): + + if about_point == None: + about_point = light_sources_mob.get_center() + + ls_target = light_sources_mob.copy() + + for submob in ls_target: + + if type(submob) == LightSource: + + new_sp = submob.source_point.copy() # a mob + new_sp.scale(factor,about_point = about_point) + submob.move_source_to(new_sp.get_location()) + + #ambient_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: ambient_of(r/factor) + #submob.ambient_light.opacity_function = new_of + + #spotlight_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: spotlight_of(r/factor) + #submob.spotlight.change_opacity_function(new_of) + + new_r = factor * submob.radius + submob.set_radius(new_r) + + new_r = factor * submob.ambient_light.radius + submob.ambient_light.radius = new_r + + new_r = factor * submob.spotlight.radius + submob.spotlight.radius = new_r + + submob.ambient_light.scale_about_point(factor, new_sp.get_center()) + submob.spotlight.scale_about_point(factor, new_sp.get_center()) + + + Transform.__init__(self,light_sources_mob,ls_target,**kwargs) + + +### + +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 9, + height = 5, + ) + circles = bubble[:3] + angle = -15*DEGREES + circles.rotate(angle, about_point = bubble.get_bubble_center()) + circles.shift(LARGE_BUFF*LEFT) + for circle in circles: + circle.rotate(-angle) + bubble.pin_to(randy) + bubble.shift_onto_screen() + + self.play( + randy.change, "thinking", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(2) + self.play(randy.change, "hooray", bubble) + self.wait(2) + +class IntroScene(PiCreatureScene): + + CONFIG = { + "rect_height" : 0.1, + "duration" : 1.0, + "eq_spacing" : 3 * MED_LARGE_BUFF, + "n_rects_to_show" : 30, + } + + def construct(self): + randy = self.get_primary_pi_creature() + randy.scale(0.7).to_corner(DOWN+RIGHT) + + self.build_up_euler_sum() + self.show_history() + # self.other_pi_formulas() + # self.refocus_on_euler_sum() + + def build_up_euler_sum(self): + morty = self.pi_creature + euler_sum = self.euler_sum = TexMobject( + "1", "+", + "{1 \\over 4}", "+", + "{1 \\over 9}", "+", + "{1 \\over 16}", "+", + "{1 \\over 25}", "+", + "\\cdots", "=", + arg_separator = " \\, " + ) + equals_sign = euler_sum.get_part_by_tex("=") + plusses = euler_sum.get_parts_by_tex("+") + term_mobjects = euler_sum.get_parts_by_tex("1") + + self.euler_sum.to_edge(UP) + self.euler_sum.shift(2*LEFT) + + max_n = self.n_rects_to_show + terms = [1./(n**2) for n in range(1, max_n + 1)] + series_terms = list(np.cumsum(terms)) + series_terms.append(np.pi**2/6) ##Just force this up there + + partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( + series_terms[1], + num_decimal_points = 2 + ) + partial_sum_decimal.next_to(equals_sign, RIGHT) + + ## Number line + + number_line = self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1, + stroke_width = 1, + numbers_with_elongated_ticks = [0,1,2,3], + numbers_to_show = np.arange(0,5), + unit_size = 5, + tick_frequency = 0.2, + line_to_number_buff = MED_LARGE_BUFF + ) + number_line.add_numbers() + number_line.to_edge(LEFT) + number_line.shift(MED_LARGE_BUFF*UP) + + # create slabs for series terms + + lines = VGroup() + rects = self.rects = VGroup() + rect_labels = VGroup() + slab_colors = it.cycle([YELLOW, BLUE]) + rect_anims = [] + rect_label_anims = [] + + for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): + color = slab_colors.next() + line = Line(*map(number_line.number_to_point, [t1, t2])) + rect = Rectangle( + stroke_width = 0, + fill_opacity = 1, + fill_color = color + ) + rect.match_width(line) + rect.stretch_to_fit_height(self.rect_height) + rect.move_to(line) + + if i <= 5: + if i == 1: + rect_label = TexMobject("1") + else: + rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) + rect_label.scale(0.75) + max_width = 0.7*rect.get_width() + if rect_label.get_width() > max_width: + rect_label.scale_to_fit_width(max_width) + rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + + term_mobject = term_mobjects[i-1] + rect_anim = GrowFromPoint(rect, term_mobject.get_center()) + rect_label_anim = ReplacementTransform( + term_mobject.copy(), rect_label + ) + else: + rect_label = VectorizedPoint() + rect_anim = GrowFromPoint(rect, rect.get_left()) + rect_label_anim = FadeIn(rect_label) + + rects.add(rect) + rect_labels.add(rect_label) + rect_anims.append(rect_anim) + rect_label_anims.append(rect_label_anim) + lines.add(line) + dots = TexMobject("\\dots").scale(0.5) + last_rect = rect_anims[-1].target_mobject + dots.scale_to_fit_width(0.9*last_rect.get_width()) + dots.move_to(last_rect, UP+RIGHT) + rects.submobjects[-1] = dots + rect_anims[-1] = FadeIn(dots) + + self.add(number_line) + self.play(FadeIn(euler_sum[0])) + self.play( + rect_anims[0], + rect_label_anims[0] + ) + for i in range(4): + self.play( + FadeIn(term_mobjects[i+1]), + FadeIn(plusses[i]), + ) + anims = [ + rect_anims[i+1], + rect_label_anims[i+1], + ] + if i == 0: + anims += [ + FadeIn(equals_sign), + FadeIn(partial_sum_decimal) + ] + elif i <= 5: + anims += [ + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + run_time = 1, + num_decimal_points = 6, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ] + self.play(*anims) + + for i in range(4, len(series_terms)-2): + anims = [ + rect_anims[i+1], + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + num_decimal_points = 6, + ), + ] + if i == 5: + anims += [ + FadeIn(euler_sum[-3]), # + + FadeIn(euler_sum[-2]), # ... + ] + self.play(*anims, run_time = 2./i) + + brace = self.brace = Brace(partial_sum_decimal, DOWN) + q_marks = self.q_marks = TextMobject("???") + q_marks.next_to(brace, DOWN) + q_marks.highlight(LIGHT_COLOR) + + self.play( + GrowFromCenter(brace), + Write(q_marks), + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[-1], + num_decimal_points = 6, + ), + morty.change, "confused", + ) + self.wait() + + self.number_line_group = VGroup( + number_line, rects, rect_labels + ) + + def show_history(self): + # Pietro Mengoli in 1644 + morty = self.pi_creature + pietro = ImageMobject("Pietro_Mengoli") + euler = ImageMobject("Euler") + + pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") + pietro_words.scale(0.75) + pietro_words.next_to(pietro, DOWN) + pietro.add(pietro_words) + + euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") + euler_words.scale(0.75) + euler_words.next_to(euler, DOWN) + euler.add(euler_words) + + pietro.next_to(SPACE_WIDTH*LEFT, LEFT) + euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) + + pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") + pi_answer.highlight(YELLOW) + pi_answer.move_to(self.partial_sum_decimal, LEFT) + equals_sign = TexMobject("=") + equals_sign.next_to(pi_answer, RIGHT) + pi_answer.shift(SMALL_BUFF*UP) + self.partial_sum_decimal.generate_target() + self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) + + pi = pi_answer[0] + pi_rect = SurroundingRectangle(pi, color = RED) + pi_rect.save_state() + pi_rect.scale_to_fit_height(SPACE_HEIGHT) + pi_rect.center() + pi_rect.set_stroke(width = 0) + squared = pi_answer[1] + squared_rect = SurroundingRectangle(squared, color = BLUE) + + brace = Brace( + VGroup(self.euler_sum, self.partial_sum_decimal.target), + DOWN, buff = SMALL_BUFF + ) + basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) + + self.number_line_group.save_state() + self.play( + pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, + self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, + morty.change, "pondering", + ) + self.wait(2) + self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) + self.wait(2) + self.play( + ReplacementTransform(self.q_marks, pi_answer), + FadeIn(equals_sign), + FadeOut(self.brace), + MoveToTarget(self.partial_sum_decimal) + ) + self.wait() + self.play(morty.change, "surprised") + self.play(pi_rect.restore) + self.wait() + self.play(Transform(pi_rect, squared_rect)) + self.play(FadeOut(pi_rect)) + self.play(morty.change, "hesitant") + self.wait(2) + self.play( + GrowFromCenter(brace), + euler.to_edge, DOWN, + pietro.to_edge, DOWN, + self.number_line_group.restore, + self.number_line_group.shift, LARGE_BUFF*RIGHT, + ) + self.play(Write(basel_text)) + self.play(morty.change, "happy") + self.wait(4) + + def other_pi_formulas(self): + + self.play( + FadeOut(self.rects), + FadeOut(self.number_line) + ) + + self.leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + self.wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.leibniz_sum.get_part_by_tex("=") + ) + + self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.wallis_product.get_part_by_tex("=") + ) + + + self.play( + Write(self.leibniz_sum) + ) + self.play( + Write(self.wallis_product) + ) + + def refocus_on_euler_sum(self): + + self.euler_sum.add(self.pi_answer) + + self.play( + FadeOut(self.leibniz_sum), + FadeOut(self.wallis_product), + ApplyMethod(self.euler_sum.shift, + ORIGIN + 2*UP - self.euler_sum.get_center()) + ) + + # focus on pi squared + pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] + self.play( + ScaleInPlace(pi_squared,2,rate_func = wiggle) + ) + + + + # Morty thinks of a circle + + q_circle = Circle( + stroke_color = YELLOW, + fill_color = YELLOW, + fill_opacity = 0.5, + radius = 0.4, + stroke_width = 10.0 + ) + q_mark = TexMobject("?") + q_mark.next_to(q_circle) + + thought = Group(q_circle, q_mark) + q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + self.pi_creature_thinks(thought,target_mode = "confused", + bubble_kwargs = { "height" : 2, "width" : 3 }) + + self.wait() + +class PiHidingWrapper(Scene): + def construct(self): + title = TextMobject("Pi hiding in prime regularities") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen)) + self.wait(2) + +class MathematicalWebOfConnections(PiCreatureScene): + def construct(self): + self.complain_that_pi_is_not_about_circles() + self.show_other_pi_formulas() + self.question_fundamental() + self.draw_circle() + self.remove_all_but_basel_sum() + self.show_web_of_connections() + self.show_light() + + def complain_that_pi_is_not_about_circles(self): + jerk, randy = self.pi_creatures + + words = self.words = TextMobject( + "$\\pi$ is not", + "fundamentally \\\\", + "about circles" + ) + words.highlight_by_tex("fundamentally", YELLOW) + + self.play(PiCreatureSays( + jerk, words, + target_mode = "angry" + )) + self.play(randy.change, "guilty") + self.wait(2) + + def show_other_pi_formulas(self): + jerk, randy = self.pi_creatures + words = self.words + + basel_sum = TexMobject( + "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", + "=", "{\\pi^2 \\over 6}" + ) + leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + basel_sum.move_to(randy) + basel_sum.to_edge(UP) + basel_equals = basel_sum.get_part_by_tex("=") + + formulas = VGroup(basel_sum, leibniz_sum, wallis_product) + formulas.scale(0.75) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + for formula in formulas: + basel_equals_x = basel_equals.get_center()[0] + formula_equals_x = formula.get_part_by_tex("=").get_center()[0] + formula.shift((basel_equals_x - formula_equals_x)*RIGHT) + + formulas.move_to(randy) + formulas.to_edge(UP) + formulas.shift_onto_screen() + self.formulas = formulas + + self.play( + jerk.change, "sassy", + randy.change, "raise_right_hand", + FadeOut(jerk.bubble), + words.next_to, jerk, UP, + FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) + ) + for formula in formulas[1:]: + self.play( + FadeIn( + formula, + submobject_mode = "lagged_start", + run_time = 3 + ), + ) + self.wait() + + def question_fundamental(self): + jerk, randy = self.pi_creatures + words = self.words + fundamentally = words.get_part_by_tex("fundamentally") + words.remove(fundamentally) + + self.play( + fundamentally.move_to, self.pi_creatures, + fundamentally.shift, UP, + FadeOut(words), + jerk.change, "pondering", + randy.change, "pondering", + ) + self.wait() + + question = TextMobject("Does this mean \\\\ anything?") + question.scale(0.8) + question.set_stroke(WHITE, 0.5) + question.next_to(fundamentally, DOWN, LARGE_BUFF) + arrow = Arrow(question, fundamentally) + arrow.highlight(WHITE) + + self.play( + FadeIn(question), + GrowArrow(arrow) + ) + self.wait() + + fundamentally.add(question, arrow) + self.fundamentally = fundamentally + + def draw_circle(self): + semi_circle = Arc(angle = np.pi, radius = 2) + radius = Line(ORIGIN, semi_circle.points[0]) + radius.highlight(BLUE) + semi_circle.highlight(YELLOW) + + VGroup(radius, semi_circle).move_to( + SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, + ) + + decimal = DecimalNumber(0) + def decimal_position_update_func(decimal): + decimal.move_to(semi_circle.points[-1]) + decimal.shift(0.3*radius.get_vector()) + + one = TexMobject("1") + one.next_to(radius, UP) + + self.play(ShowCreation(radius), FadeIn(one)) + self.play( + Rotate(radius, np.pi, about_point = radius.get_start()), + ShowCreation(semi_circle), + ChangeDecimalToValue( + decimal, np.pi, + position_update_func = decimal_position_update_func + ), + MaintainPositionRelativeTo(one, radius), + run_time = 3, + ) + self.wait(2) + + self.circle_group = VGroup(semi_circle, radius, one, decimal) + + def remove_all_but_basel_sum(self): + to_shift_down = VGroup( + self.circle_group, self.pi_creatures, + self.fundamentally, self.formulas[1:], + ) + to_shift_down.generate_target() + for part in to_shift_down.target: + part.move_to(2*SPACE_HEIGHT*DOWN) + + basel_sum = self.formulas[0] + + self.play( + MoveToTarget(to_shift_down), + basel_sum.scale, 1.5, + basel_sum.move_to, 2*DOWN, + ) + + def show_web_of_connections(self): + pass + + def show_light(self): + pass + + ### + + def create_pi_creatures(self): + jerk = PiCreature(color = GREEN_D) + randy = Randolph().flip() + jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) + randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) + + return VGroup(jerk, randy) + + + + + +class FirstLighthouseScene(PiCreatureScene): + + def construct(self): + self.remove(self.get_primary_pi_creature()) + self.show_lighthouses_on_number_line() + + + + def show_lighthouses_on_number_line(self): + + self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1.6, + stroke_width = 1, + numbers_with_elongated_ticks = range(1,5), + numbers_to_show = range(1,5), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + origin_point = self.number_line.number_to_point(0) + + self.default_pi_creature_class = Randolph + randy = self.get_primary_pi_creature() + + randy.scale(0.5) + randy.flip() + right_pupil = randy.pupils[1] + randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + + + + light_indicator = LightIndicator(radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR) + light_indicator.reading.scale(0.8) + + bubble = ThoughtBubble(direction = RIGHT, + width = 2.5, height = 3.5) + bubble.next_to(randy,LEFT+UP) + bubble.add_content(light_indicator) + + self.play( + randy.change, "wave_2", + ShowCreation(bubble), + FadeIn(light_indicator) + ) + + light_sources = [] + + + euler_sum_above = TexMobject("1", "+", "{1\over 4}", + "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") + + for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above): + #horizontal alignment with tick marks + term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2) + # vertical alignment with light indicator + old_y = term.get_center()[1] + new_y = light_indicator.get_center()[1] + term.shift([0,new_y - old_y,0]) + + + + for i in range(1,NUM_CONES+1): + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 12.0, + ) + point = self.number_line.number_to_point(i) + light_source.move_source_to(point) + light_sources.append(light_source) + + + for ls in light_sources: + self.add_foreground_mobject(ls.lighthouse) + + light_indicator.set_intensity(0) + + intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) + opacities = intensities * light_indicator.opacity_for_unit_intensity + + self.remove_foreground_mobjects(light_indicator) + + + # slowly switch on visible light cones and increment indicator + for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func( + smooth,indicator_start_time,indicator_stop_time) + self.play( + SwitchOn(light_source.ambient_light), + FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, + rate_func = indicator_rate_func), + FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, + rate_func = indicator_rate_func), + # this last line *technically* fades in the last term, but it is off-screen + ChangeDecimalToValue(light_indicator.reading,intensities[i], + rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) + ) + + if i == 0: + # move a copy out of the thought bubble for comparison + light_indicator_copy = light_indicator.copy() + old_y = light_indicator_copy.get_center()[1] + new_y = self.number_line.get_center()[1] + self.play( + light_indicator_copy.shift,[0, new_y - old_y,0] + ) + + # quickly switch on off-screen light cones and increment indicator + for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + smooth,indicator_start_time,indicator_stop_time) + self.play( + SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), + ChangeDecimalToValue(light_indicator.reading,intensities[i-1], + rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1]) + ) + + + # show limit value in light indicator and an equals sign + limit_reading = TexMobject("{\pi^2 \over 6}") + limit_reading.move_to(light_indicator.reading) + + equals_sign = TexMobject("=") + equals_sign.next_to(randy, UP) + old_y = equals_sign.get_center()[1] + new_y = euler_sum_above.get_center()[1] + equals_sign.shift([0,new_y - old_y,0]) + + self.play( + FadeOut(light_indicator.reading), + FadeIn(limit_reading), + FadeIn(equals_sign), + ) + + + + self.wait() + +class SingleLighthouseScene(PiCreatureScene): + + def construct(self): + + self.setup_elements() + self.setup_angle() # spotlight and angle msmt change when screen rotates + self.rotate_screen() + self.morph_lighthouse_into_sun() + + + def setup_elements(self): + + self.remove(self.get_primary_pi_creature()) + + SCREEN_SIZE = 3.0 + DISTANCE_FROM_LIGHTHOUSE = 10.0 + source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] + observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + + # Light source + + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL + ) + + self.light_source.move_source_to(source_point) + + + # Pi Creature + + morty = self.get_primary_pi_creature() + morty.scale(0.5) + morty.move_to(observer_point) + morty.shift(2*OUT) + self.add_foreground_mobject(morty) + + self.add(self.light_source.lighthouse) + + self.play( + SwitchOn(self.light_source.ambient_light) + ) + + # Screen + + self.screen = Rectangle( + width = 0.1, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) + + self.screen.rotate(-TAU/6) + self.screen.next_to(morty,LEFT) + + self.light_source.set_screen(self.screen) + + # Animations + + self.play(FadeIn(self.screen)) + + self.light_source.set_max_opacity_spotlight(0.001) + self.add(self.light_source.spotlight) + + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + # just calling .dim_ambient via ApplyMethod does not work, why? + dimmed_ambient_light = self.light_source.ambient_light.deepcopy() + dimmed_ambient_light.dimming(AMBIENT_DIMMED) + + self.play( + Transform(self.light_source.ambient_light,dimmed_ambient_light), + self.light_source.set_max_opacity_spotlight,1.0, + FadeIn(self.light_source.shadow) + ) + + self.add_foreground_mobject(morty) + + + + + def setup_angle(self): + + self.wait() + + + pointing_screen_at_source = Rotate(self.screen,TAU/6) + self.play(pointing_screen_at_source) + + # angle msmt (arc) + + arc_angle = self.light_source.spotlight.opening_angle() + # draw arc arrows to show the opening angle + self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), + angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) + #angle_arc.add_tip(at_start = True, at_end = True) + self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) + + + # angle msmt (decimal number) + + self.angle_indicator = DecimalNumber(arc_angle / DEGREES, + num_decimal_points = 0, + unit = "^\\circ") + self.angle_indicator.next_to(self.angle_arc,RIGHT) + + angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES + ca1 = ContinualChangingDecimal(self.angle_indicator,angle_update_func) + self.add(ca1) + + ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight) + self.add(ca2) + + self.play( + ShowCreation(self.angle_arc), + ShowCreation(self.angle_indicator) + ) + + self.wait() + + def rotate_screen(self): + + + + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) + self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) + + self.wait() + + self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + + self.wait() + + self.play(Rotate(self.light_source.spotlight.screen, TAU/4)) + +### The following is supposed to morph the scene into the Earth scene, +### but it doesn't work + + + def morph_lighthouse_into_sun(self): + + + + sun_position = [-100,0,0] + + + self.play( + FadeOut(self.angle_arc), + FadeOut(self.angle_indicator) + ) + + self.sun = self.light_source.deepcopy() + + #self.sun.num_levels = NUM_LEVELS, + #self.sun.set_radius(150) + #self.sun.set_max_opacity_ambient(AMBIENT_FULL) + + + + self.sun.spotlight.change_opacity_function(lambda r: 0.5) + self.sun.set_radius(150) + self.sun.move_source_to(sun_position) + + # self.sun.update() + + # self.add(self.sun) + # temporarily remove the screen tracker while we move the source + #self.remove(self.screen_tracker) + + #print self.sun.spotlight.get_source_point() + + self.play( + #self.light_source.spotlight.move_source_to,sun_position, + Transform(self.light_source,self.sun) + ) + + #self.add(ScreenTracker(self.sun)) + + self.wait() + +class EarthScene(Scene): + + def construct(self): + + SCREEN_THICKNESS = 10 + + self.screen_height = 2.0 + self.brightness_rect_height = 1.0 + + # screen + self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) + self.screen.set_points_as_corners([ + [3,-self.screen_height/2,0], + [3,self.screen_height/2,0] + ]) + + # Earth + + earth_center_x = 2 + earth_center = [earth_center_x,0,0] + earth_radius = 3 + earth = Circle(radius = earth_radius) + earth.move_to(earth_center) + #self.remove(self.screen_tracker) + + theta0 = 70 * DEGREES + dtheta = 10 * DEGREES + theta1 = theta0 + dtheta + theta = (theta0 + theta1)/2 + + earth.add(self.screen) + + # Morty + + morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) + self.add_foreground_mobject(morty) + + + # Light source (far-away Sun) + + sun_position = [-100,0,0] + + self.sun = LightSource( + opacity_function = lambda r : 0.5, + max_opacity_ambient = 0, + max_opacity_spotlight = 0.5, + num_levels = NUM_LEVELS, + radius = 150, + screen = self.screen + ) + + self.sun.move_source_to(sun_position) + + + # Add elements to scene + + self.add(self.sun,self.screen) + self.bring_to_back(self.sun.shadow) + screen_tracker = ScreenTracker(self.sun) + + self.add(screen_tracker) + + self.wait() + + self.play(FadeIn(earth)) + self.bring_to_back(earth) + + # move screen onto Earth + screen_on_earth = self.screen.deepcopy() + screen_on_earth.rotate(-theta) + screen_on_earth.scale(0.3) + screen_on_earth.move_to(np.array([ + earth_center_x - earth_radius * np.cos(theta), + earth_radius * np.sin(theta), + 0])) + + polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) + + self.play( + Transform(self.screen, screen_on_earth), + Transform(morty,polar_morty) + ) + + self.wait() + + + tropical_morty = polar_morty.copy() + tropical_morty.move_to(np.array([0,0,0])) + morty.target = tropical_morty + + # move screen to equator + + self.play( + Rotate(earth, theta0 + dtheta/2,run_time = 3), + MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), + ) + +class ScreenShapingScene(ThreeDScene): + + + # TODO: Morph from Earth Scene into this scene + + def construct(self): + + #self.force_skipping() + self.setup_elements() + self.deform_screen() + self.create_brightness_rect() + self.slant_screen() + self.unslant_screen() + self.left_shift_screen_while_showing_light_indicator() + self.add_distance_arrow() + self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() + self.left_shift_again() + #self.revert_to_original_skipping_status() + + self.morph_into_3d() + self.prove_inverse_square_law() + + + def setup_elements(self): + + SCREEN_THICKNESS = 10 + + self.screen_height = 1.0 + self.brightness_rect_height = 1.0 + + # screen + self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], + path_arc = 0, num_arc_anchors = 10) + + # light source + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,5,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity = 0.2 + #screen = self.screen + ) + self.light_source.set_max_opacity_spotlight(0.2) + + self.light_source.set_screen(self.screen) + self.light_source.move_source_to([-5,0,0]) + + # abbreviations + self.ambient_light = self.light_source.ambient_light + self.spotlight = self.light_source.spotlight + self.lighthouse = self.light_source.lighthouse + + + #self.add_foreground_mobject(self.light_source.shadow) + + # Morty + self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) + + # Add everything to the scene + self.add(self.lighthouse) + + self.wait() + self.play(FadeIn(self.screen)) + self.wait() + + self.add_foreground_mobject(self.screen) + self.add_foreground_mobject(self.morty) + + self.play(SwitchOn(self.ambient_light)) + + self.play( + SwitchOn(self.spotlight), + self.light_source.dim_ambient + ) + + screen_tracker = ScreenTracker(self.light_source) + self.add(screen_tracker) + + + self.wait() + + + + def deform_screen(self): + + self.wait() + + self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, 0)) + + + + + def create_brightness_rect(self): + + # in preparation for the slanting, create a rectangle that shows the brightness + + # a rect a zero width overlaying the screen + # so we can morph it into the brightness rect above + brightness_rect0 = Rectangle(width = 0, + height = self.screen_height).move_to(self.screen.get_center()) + self.add_foreground_mobject(brightness_rect0) + + self.brightness_rect = Rectangle(width = self.brightness_rect_height, + height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) + + self.brightness_rect.next_to(self.screen, UP, buff = 1) + + self.play( + ReplacementTransform(brightness_rect0,self.brightness_rect) + ) + + self.unslanted_screen = self.screen.deepcopy() + self.unslanted_brightness_rect = self.brightness_rect.copy() + # for unslanting the screen later + + + def slant_screen(self): + + SLANTING_AMOUNT = 0.1 + + lower_screen_point, upper_screen_point = self.screen.get_start_and_end() + + lower_slanted_screen_point = interpolate( + lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT + ) + upper_slanted_screen_point = interpolate( + upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT + ) + + self.slanted_brightness_rect = self.brightness_rect.copy() + self.slanted_brightness_rect.width *= 2 + self.slanted_brightness_rect.generate_points() + self.slanted_brightness_rect.set_fill(opacity = 0.25) + + self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, + path_arc = 0, num_arc_anchors = 10) + self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) + + self.play( + Transform(self.screen,self.slanted_screen), + Transform(self.brightness_rect,self.slanted_brightness_rect), + ) + + + + def unslant_screen(self): + + self.wait() + self.play( + Transform(self.screen,self.unslanted_screen), + Transform(self.brightness_rect,self.unslanted_brightness_rect), + ) + + + + + def left_shift_screen_while_showing_light_indicator(self): + + # Scene 5: constant screen size, changing opening angle + + OPACITY_FOR_UNIT_INTENSITY = 1 + + # let's use an actual light indicator instead of just rects + + self.indicator_intensity = 0.25 + indicator_height = 1.25 * self.screen_height + + self.indicator = LightIndicator(radius = indicator_height/2, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR, + precision = 2) + self.indicator.set_intensity(self.indicator_intensity) + + self.indicator.move_to(self.brightness_rect.get_center()) + + self.play( + FadeOut(self.brightness_rect), + FadeIn(self.indicator) + ) + + # Here some digits of the indicator disappear... + + self.add_foreground_mobject(self.indicator.reading) + + + self.unit_indicator_intensity = 1.0 # intensity at distance 1 + # (where we are about to move to) + + self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 + + self.play( + self.screen.shift,[-self.left_shift,0,0], + self.morty.shift,[-self.left_shift,0,0], + self.indicator.shift,[-self.left_shift,0,0], + self.indicator.set_intensity,self.unit_indicator_intensity, + ) + + + + def add_distance_arrow(self): + + # distance arrow (length 1) + left_x = self.spotlight.get_source_point()[0] + right_x = self.screen.get_center()[0] + arrow_y = -2 + arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) + arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) + arrow1.set_fill(color = WHITE) + arrow2.set_fill(color = WHITE) + distance_decimal = Integer(1).next_to(arrow1,DOWN) + self.arrow = VGroup(arrow1, arrow2,distance_decimal) + self.add(self.arrow) + + + # distance arrow (length 2) + # will be morphed into + self.distance_to_source = right_x - left_x + new_right_x = left_x + 2 * self.distance_to_source + new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) + new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) + new_arrow1.set_fill(color = WHITE) + new_arrow2.set_fill(color = WHITE) + new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) + self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) + # don't add it yet + + + def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): + + self.wait() + + self.play( + ReplacementTransform(self.arrow,self.new_arrow), + ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), + ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), + + ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), + # this should trigger ChangingDecimal, but it doesn't + # maybe bc it's an anim within an anim? + + ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), + ) + + + def left_shift_again(self): + + self.wait() + + self.play( + ReplacementTransform(self.new_arrow,self.arrow), + ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), + #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), + ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), + ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), + ) + + def morph_into_3d(self): + + + self.play(FadeOut(self.morty)) + + axes = ThreeDAxes() + self.add(axes) + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + phi1 = 60 * DEGREES # angle from zenith (0 to 180) + theta1 = -135 * DEGREES # azimuth (0 to 360) + distance1 = distance0 + target_point = self.camera.get_spherical_coords(phi1, theta1, distance1) + + dphi = phi1 - phi0 + dtheta = theta1 - theta0 + + camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) + projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) + + new_screen0 = Rectangle(height = self.screen_height, + width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen0.rotate(TAU/4,axis = DOWN) + new_screen0.move_to(self.screen.get_center()) + self.add(new_screen0) + self.remove(self.screen) + self.light_source.set_screen(new_screen0) + + self.light_source.set_camera(self.camera) + + + new_screen = Rectangle(height = self.screen_height, + width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) + new_screen.rotate(TAU/4,axis = DOWN) + new_screen.move_to(self.screen.get_center()) + + self.add_foreground_mobject(self.ambient_light) + self.add_foreground_mobject(self.spotlight) + self.add_foreground_mobject(self.light_source.shadow) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + + ) + self.remove(self.spotlight) + + self.play(Transform(new_screen0,new_screen)) + + self.wait() + + self.unit_screen = new_screen0 # better name + + + + def prove_inverse_square_law(self): + + def orientate(mob): + mob.move_to(self.unit_screen) + mob.rotate(TAU/4, axis = LEFT) + mob.rotate(TAU/4, axis = OUT) + mob.rotate(TAU/2, axis = LEFT) + return mob + + unit_screen_copy = self.unit_screen.copy() + fourfold_screen = self.unit_screen.copy() + fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) + + self.remove(self.spotlight) + + + reading1 = TexMobject("1") + orientate(reading1) + + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + + + self.play( + Transform(self.unit_screen, fourfold_screen) + ) + + reading21 = TexMobject("{1\over 4}").scale(0.8) + orientate(reading21) + reading22 = reading21.deepcopy() + reading23 = reading21.deepcopy() + reading24 = reading21.deepcopy() + reading21.shift(0.5*OUT + 0.5*UP) + reading22.shift(0.5*OUT + 0.5*DOWN) + reading23.shift(0.5*IN + 0.5*UP) + reading24.shift(0.5*IN + 0.5*DOWN) + + + corners = fourfold_screen.get_anchors() + midpoint1 = (corners[0] + corners[1])/2 + midpoint2 = (corners[1] + corners[2])/2 + midpoint3 = (corners[2] + corners[3])/2 + midpoint4 = (corners[3] + corners[0])/2 + midline1 = Line(midpoint1, midpoint3) + midline2 = Line(midpoint2, midpoint4) + + self.play( + ShowCreation(midline1), + ShowCreation(midline2) + ) + + self.play( + FadeIn(reading21), + FadeIn(reading22), + FadeIn(reading23), + FadeIn(reading24), + ) + + self.wait() + + self.play( + FadeOut(reading21), + FadeOut(reading22), + FadeOut(reading23), + FadeOut(reading24), + FadeOut(midline1), + FadeOut(midline2) + ) + + ninefold_screen = unit_screen_copy.copy() + ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + + self.play( + Transform(self.unit_screen, ninefold_screen) + ) + + reading31 = TexMobject("{1\over 9}").scale(0.8) + orientate(reading31) + reading32 = reading31.deepcopy() + reading33 = reading31.deepcopy() + reading34 = reading31.deepcopy() + reading35 = reading31.deepcopy() + reading36 = reading31.deepcopy() + reading37 = reading31.deepcopy() + reading38 = reading31.deepcopy() + reading39 = reading31.deepcopy() + reading31.shift(IN + UP) + reading32.shift(IN) + reading33.shift(IN + DOWN) + reading34.shift(UP) + reading35.shift(ORIGIN) + reading36.shift(DOWN) + reading37.shift(OUT + UP) + reading38.shift(OUT) + reading39.shift(OUT + DOWN) + + corners = ninefold_screen.get_anchors() + midpoint11 = (2*corners[0] + corners[1])/3 + midpoint12 = (corners[0] + 2*corners[1])/3 + midpoint21 = (2*corners[1] + corners[2])/3 + midpoint22 = (corners[1] + 2*corners[2])/3 + midpoint31 = (2*corners[2] + corners[3])/3 + midpoint32 = (corners[2] + 2*corners[3])/3 + midpoint41 = (2*corners[3] + corners[0])/3 + midpoint42 = (corners[3] + 2*corners[0])/3 + midline11 = Line(midpoint11, midpoint32) + midline12 = Line(midpoint12, midpoint31) + midline21 = Line(midpoint21, midpoint42) + midline22 = Line(midpoint22, midpoint41) + + self.play( + ShowCreation(midline11), + ShowCreation(midline12), + ShowCreation(midline21), + ShowCreation(midline22), + ) + + self.play( + FadeIn(reading31), + FadeIn(reading32), + FadeIn(reading33), + FadeIn(reading34), + FadeIn(reading35), + FadeIn(reading36), + FadeIn(reading37), + FadeIn(reading38), + FadeIn(reading39), + ) + +class IndicatorScalingScene(Scene): + + def construct(self): + + unit_intensity = 0.6 + + indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator1.set_intensity(unit_intensity) + reading1 = TexMobject("1") + reading1.move_to(indicator1) + + + indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator2.shift(2*RIGHT) + indicator2.set_intensity(unit_intensity/4) + reading2 = TexMobject("{1\over 4}").scale(0.8) + reading2.move_to(indicator2) + + indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) + indicator3.shift(4*RIGHT) + indicator3.set_intensity(unit_intensity/9) + reading3 = TexMobject("{1\over 9}").scale(0.8) + reading3.move_to(indicator3) + + + self.play(FadeIn(indicator1)) + self.play(FadeIn(reading1)) + self.wait() + self.play(FadeOut(reading1)) + self.play(Transform(indicator1, indicator2)) + self.play(FadeIn(reading2)) + self.wait() + self.play(FadeOut(reading2)) + self.play(Transform(indicator1, indicator3)) + self.play(FadeIn(reading3)) + self.wait() + +class BackToEulerSumScene(PiCreatureScene): + + + def construct(self): + self.remove(self.get_primary_pi_creature()) + + NUM_CONES = 7 + NUM_VISIBLE_CONES = 6 + INDICATOR_RADIUS = 0.5 + OPACITY_FOR_UNIT_INTENSITY = 1.0 + + self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1.6, + stroke_width = 1, + numbers_with_elongated_ticks = range(1,5), + numbers_to_show = range(1,5), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ) + + self.number_line.label_direction = DOWN + #self.number_line.shift(3*UP) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.add(self.number_line,self.number_line_labels) + self.wait() + + origin_point = self.number_line.number_to_point(0) + + self.default_pi_creature_class = Randolph + randy = self.get_primary_pi_creature() + + randy.scale(0.5) + randy.flip() + right_pupil = randy.pupils[1] + randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + + randy_copy = randy.copy() + randy_copy.target = randy.copy().shift(DOWN) + + + + bubble = ThoughtBubble(direction = RIGHT, + width = 4, height = 3, + file_name = "Bubbles_thought.svg") + bubble.next_to(randy,LEFT+UP) + bubble.set_fill(color = BLACK, opacity = 1) + + self.play( + randy.change, "wave_2", + ShowCreation(bubble), + ) + + + euler_sum = TexMobject("1", "+", "{1\over 4}", + "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ") + # the last entry is a dummy element which makes looping easier + # used just for putting the fractions into the light indicators + + intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)]) + opacities = intensities * OPACITY_FOR_UNIT_INTENSITY + + # repeat: + + # fade in lighthouse + # switch on / fade in ambient light + # show creation / write light indicator + # move indicator onto origin + # while morphing and dimming + # move indicator into thought bubble + # while indicators already inside shift to the back + # and while term appears in the series below + + point = self.number_line.number_to_point(1) + v = point - self.number_line.number_to_point(0) + light_source = LightSource() + light_source.move_source_to(point) + #light_source.ambient_light.move_source_to(point) + #light_source.lighthouse.move_to(point) + + self.play(FadeIn(light_source.lighthouse)) + self.play(SwitchOn(light_source.ambient_light)) + + + # create an indicator that will move along the number line + indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = False + ) + indicator_reading = euler_sum[0] + indicator_reading.scale_to_fit_height(0.5 * indicator.get_height()) + indicator_reading.move_to(indicator.get_center()) + indicator.add(indicator_reading) + indicator.tex_reading = indicator_reading + # the TeX reading is too bright at full intensity + indicator.tex_reading.set_fill(color = BLACK) + indicator.foreground.set_fill(None,opacities[0]) + + + indicator.move_to(point) + indicator.set_intensity(intensities[0]) + + self.play(FadeIn(indicator)) + self.add_foreground_mobject(indicator) + + collection_point = np.array([-6.,2.,0.]) + left_shift = 0.2*LEFT + collected_indicators = Mobject() + + + for i in range(2, NUM_VISIBLE_CONES + 1): + + previous_point = self.number_line.number_to_point(i - 1) + point = self.number_line.number_to_point(i) + + + v = point - previous_point + #print v + # Create and position the target indicator (next on number line). + indicator_target = indicator.deepcopy() + indicator_target.shift(v) + + + # Here we make a copy that will move into the thought bubble. + bubble_indicator = indicator.deepcopy() + # And its target + bubble_indicator_target = bubble_indicator.deepcopy() + bubble_indicator_target.set_intensity(intensities[i - 2]) + + # give the target the appropriate reading + euler_sum[2*i-4].move_to(bubble_indicator_target) + bubble_indicator_target.remove(bubble_indicator_target.tex_reading) + bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() + bubble_indicator_target.add(bubble_indicator_target.tex_reading) + # center it in the indicator + + if bubble_indicator_target.tex_reading.get_tex_string() != "1": + bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height()) + # the target is less bright, possibly switch to a white text color + if bubble_indicator_target.intensity < 0.7: + bubble_indicator.tex_reading.set_fill(color = WHITE) + + # position the target in the thought bubble + bubble_indicator_target.move_to(collection_point) + + + self.add_foreground_mobject(bubble_indicator) + + + self.wait() + + self.play( + Transform(bubble_indicator,bubble_indicator_target), + collected_indicators.shift,left_shift, + ) + + collected_indicators.add(bubble_indicator) + + new_light = light_source.deepcopy() + w = new_light.get_source_point() + new_light.move_source_to(w + (i-2)*v) + w2 = new_light.get_source_point() + + self.add(new_light.lighthouse) + self.play( + Transform(indicator,indicator_target), + new_light.lighthouse.shift,v, + ) + new_light.move_source_to(w + (i-1)*v) + new_light.lighthouse.move_to(w + (i-1)*v) + + self.play(SwitchOn(new_light.ambient_light), + ) + + + + + # quickly switch on off-screen light cones + for i in range(NUM_VISIBLE_CONES,NUM_CONES): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + smooth,indicator_start_time,indicator_stop_time) + ls = LightSource() + point = point = self.number_line.number_to_point(i) + ls.move_source_to(point) + self.play( + SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), + ) + + # and morph indicator stack into limit value + + sum_indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = False + ) + sum_indicator.set_intensity(intensities[0] * np.pi**2/6) + sum_indicator_reading = TexMobject("{\pi^2 \over 6}") + sum_indicator_reading.set_fill(color = BLACK) + sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) + sum_indicator.add(sum_indicator_reading) + sum_indicator.move_to(collection_point) + + self.play( + FadeOut(collected_indicators), + FadeIn(sum_indicator) + ) + + + + self.wait() + +class TwoLightSourcesScene(PiCreatureScene): + + def construct(self): + + MAX_OPACITY = 0.4 + INDICATOR_RADIUS = 0.6 + OPACITY_FOR_UNIT_INTENSITY = 0.5 + + morty = self.get_primary_pi_creature() + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + + horizontal = VMobject(stroke_width = 1) + horizontal.set_points_as_corners([C,A]) + vertical = VMobject(stroke_width = 1) + vertical.set_points_as_corners([C,B]) + + self.play( + ShowCreation(horizontal), + ShowCreation(vertical) + ) + + indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = True, + precision = 2 + ) + + indicator.next_to(morty,LEFT) + + self.play( + Write(indicator) + ) + + + ls1 = LightSource(radius = 20, num_levels = 50) + ls2 = ls1.deepcopy() + ls1.move_source_to(A) + ls2.move_source_to(B) + + self.play( + FadeIn(ls1.lighthouse), + FadeIn(ls2.lighthouse), + SwitchOn(ls1.ambient_light), + SwitchOn(ls2.ambient_light) + ) + + distance1 = np.linalg.norm(C - ls1.get_source_point()) + intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(C - ls2.get_source_point()) + intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + + self.play( + UpdateLightIndicator(indicator,intensity) + ) + + self.wait() + + ls3 = ls1.deepcopy() + ls3.move_to(np.array([6,3.5,0])) + + new_indicator = indicator.copy() + new_indicator.light_source = ls3 + new_indicator.measurement_point = C + self.add(new_indicator) + self.play( + indicator.shift, 2 * UP + ) + + + + #intensity = intensity_for_light_source(ls3) + + + self.play( + SwitchOff(ls1.ambient_light), + #FadeOut(ls1.lighthouse), + SwitchOff(ls2.ambient_light), + #FadeOut(ls2.lighthouse), + UpdateLightIndicator(new_indicator,0.0) + ) + + # create a *continual* animation for the replacement source + updater = ContinualLightIndicatorUpdate(new_indicator) + self.add(updater) + + self.play( + SwitchOn(ls3.ambient_light), + FadeIn(ls3.lighthouse), + + ) + + self.wait() + + # move the light source around + # TODO: moving along a path arc + + location = np.array([-3,-2.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([6.,1.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([5.,2.,0.]) + self.play(ls3.move_source_to,location) + closer_location = interpolate(location, C, 0.5) + self.play(ls3.move_source_to,closer_location) + self.play(ls3.move_source_to,location) + + # maybe move in a circle around C using a loop? + + + + self.play(ls3.move_source_to,H) + + + + # draw lines to complete the geometric picture + # and label the lengths + + line_a = VMobject() + line_a.set_points_as_corners([B,C]) + line_b = VMobject() + line_b.set_points_as_corners([A,C]) + line_c = VMobject() + line_c.set_points_as_corners([A,B]) + line_h = VMobject() + line_h.set_points_as_corners([H,C]) + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.play( + ShowCreation(line_a), + Write(label_a) + ) + + self.play( + ShowCreation(line_b), + Write(label_b) + ) + + self.play( + ShowCreation(line_c), + ) + + self.play( + ShowCreation(line_h), + Write(label_h) + ) + + + # state the IPT + theorem_location = np.array([3.,2.,0.]) + theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") + theorem_name = TextMobject("Inverse Pythagorean Theorem") + buffer = 1.2 + theorem_box = Rectangle(width = buffer*theorem.get_width(), + height = buffer*theorem.get_height()) + + theorem.move_to(theorem_location) + theorem_box.move_to(theorem_location) + theorem_name.next_to(theorem_box,UP) + + self.play( + Write(theorem), + ) + + self.play( + ShowCreation(theorem_box), + Write(theorem_name), + ) + +class IPTScene1(PiCreatureScene): + + def construct(self): + + show_detail = True + + SCREEN_SCALE = 0.1 + SCREEN_THICKNESS = 0.2 + + + # use the following for the zoomed inset + if show_detail: + self.camera.space_shape = (0.02 * SPACE_HEIGHT, 0.02 * SPACE_WIDTH) + self.camera.space_center = C + SCREEN_SCALE = 0.01 + SCREEN_THICKNESS = 0.02 + + + + + + morty = self.get_primary_pi_creature() + self.remove(morty) + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) + + if not show_detail: + self.add_foreground_mobject(morty) + + stroke_width = 6 + line_a = Line(B,C,stroke_width = stroke_width) + line_b = Line(A,C,stroke_width = stroke_width) + line_c = Line(A,B,stroke_width = stroke_width) + line_h = Line(C,H,stroke_width = stroke_width) + + length_a = line_a.get_length() + length_b = line_b.get_length() + length_c = line_c.get_length() + length_h = line_h.get_length() + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.add_foreground_mobject(line_a) + self.add_foreground_mobject(line_b) + self.add_foreground_mobject(line_c) + self.add_foreground_mobject(line_h) + self.add_foreground_mobject(label_a) + self.add_foreground_mobject(label_b) + self.add_foreground_mobject(label_h) + + if not show_detail: + self.add_foreground_mobject(morty) + + ls1 = LightSource(radius = 10) + ls1.move_source_to(B) + + self.add(ls1.lighthouse) + + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) + + self.wait() + + # adding the first screen + + screen_width_a = SCREEN_SCALE * length_a + screen_width_b = SCREEN_SCALE * length_b + screen_width_ap = screen_width_a * length_a / length_c + screen_width_bp = screen_width_b * length_b / length_c + screen_width_c = SCREEN_SCALE * length_c + + screen_thickness_a = SCREEN_THICKNESS + screen_thickness_b = SCREEN_THICKNESS + + screen1 = Rectangle(width = screen_width_b, + height = screen_thickness_b, + stroke_width = 0, + fill_opacity = 1.0) + screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) + + if not show_detail: + self.add_foreground_mobject(morty) + + self.play( + FadeIn(screen1) + ) + self.add_foreground_mobject(screen1) + + ls1.set_screen(screen1) + screen_tracker = ScreenTracker(ls1) + self.add(screen_tracker) + #self.add(ls1.shadow) + + if not show_detail: + self.play( + SwitchOn(ls1.ambient_light) + ) + + self.play( + SwitchOn(ls1.spotlight), + SwitchOff(ls1.ambient_light) + ) + + + + # now move the light source to the height point + # while shifting scaling the screen + screen1p = screen1.deepcopy() + screen1pp = screen1.deepcopy() + #self.add(screen1p) + angle = np.arccos(length_b / length_c) + + screen1p.stretch_to_fit_width(screen_width_bp) + screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) + screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) + + + self.play( + ls1.move_source_to,H, + Transform(screen1,screen1p) + ) + + # add and move the second light source and screen + ls2 = ls1.deepcopy() + ls2.move_source_to(A) + screen2 = Rectangle(width = screen_width_a, + height = screen_thickness_a, + stroke_width = 0, + fill_opacity = 1.0) + screen2.rotate(-TAU/4) + screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) + + self.play( + FadeIn(screen2) + ) + self.add_foreground_mobject(screen2) + + if not show_detail: + self.add_foreground_mobject(morty) + + # the same scene adding sequence as before + ls2.set_screen(screen2) + screen_tracker2 = ScreenTracker(ls2) + self.add(screen_tracker2) + + if not show_detail: + self.play( + SwitchOn(ls2.ambient_light) + ) + + self.wait() + + self.play( + SwitchOn(ls2.spotlight), + SwitchOff(ls2.ambient_light) + ) + + + + # now move the light source to the height point + # while shifting scaling the screen + screen2p = screen2.deepcopy() + screen2pp = screen2.deepcopy() + angle = np.arccos(length_a / length_c) + screen2p.stretch_to_fit_height(screen_width_ap) + screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) + screen2p.rotate(angle, about_point = C + screen_width_a * UP) + # we can reuse the translation vector + # screen2p.shift(vector) + + self.play( + ls2.move_source_to,H, + SwitchOff(ls1.ambient_light), + Transform(screen2,screen2p) + ) + + # now transform both screens back + self.play( + Transform(screen1, screen1pp), + Transform(screen2, screen2pp), + ) + +class IPTScene2(Scene): + + def construct(self): + + intensity1 = 0.3 + intensity2 = 0.2 + formula_scale = 01.2 + indy_radius = 1 + + indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy1.set_intensity(intensity1) + reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) + indy1.add(reading1) + + indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy2.set_intensity(intensity2) + reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) + indy2.add(reading2) + + indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) + indy3.set_intensity(intensity1 + intensity2) + reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) + indy3.add(reading3) + + plus_sign = TexMobject("+").scale(formula_scale) + equals_sign = TexMobject("=").scale(formula_scale) + + plus_sign.next_to(indy1, RIGHT) + indy2.next_to(plus_sign, RIGHT) + equals_sign.next_to(indy2, RIGHT) + indy3.next_to(equals_sign, RIGHT) + + + formula = VGroup( + indy1, plus_sign, indy2, equals_sign, indy3 + ) + + formula.move_to(ORIGIN) + + self.play(FadeIn(indy1)) + self.play(FadeIn(plus_sign), FadeIn(indy2)) + self.play(FadeIn(equals_sign), FadeIn(indy3)) + + buffer = 1.5 + box = Rectangle(width = formula.get_width() * buffer, + height = formula.get_height() * buffer) + box.move_to(formula) + text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) + text.next_to(box,UP) + self.play(ShowCreation(box),Write(text)) + +class PondScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + baseline = VMobject() + baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + + obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + # Morty and indicator + morty = Mortimer().scale(0.3) + morty.next_to(OBSERVER_POINT,DOWN) + indicator = LightIndicator(precision = 2, + radius = INDICATOR_RADIUS, + show_reading = False, + color = LIGHT_COLOR + ) + indicator.next_to(morty,LEFT) + + # first lighthouse + ls0 = LightSource() + ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + + self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) + + self.wait() + + + # shore arcs + arc_left = Arc(-TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_left.next_to(arc_left,LEFT) + + + arc_right = Arc(TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_right.next_to(arc_right,RIGHT) + + self.play( + ShowCreation(arc_left), + Write(one_left), + ShowCreation(arc_right), + Write(one_right), + ) + + + self.play( + SwitchOn(ls0.ambient_light), + lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, + ) + + self.play(FadeIn(indicator)) + + self.play( + indicator.set_intensity,0.5 + ) + + # diameter + diameter = DoubleArrow(OBSERVER_POINT, + ls0.get_source_point(), + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + ShowCreation(diameter), + Write(diameter_text), + #FadeOut(obs_dot), + FadeOut(ls0_dot) + ) + + indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator.reading.move_to(indicator) + + self.play( + FadeIn(indicator.reading) + ) + + # replace d with its value + new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text.color = LAKE_COLOR + new_diameter_text.move_to(diameter_text) + self.play( + Transform(diameter_text,new_diameter_text) + ) + + # insert into indicator reading + new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) + new_reading.move_to(indicator) + + self.play( + Transform(indicator.reading,new_reading) + ) + + self.play( + FadeOut(one_left), + FadeOut(one_right), + FadeOut(diameter_text), + FadeOut(arc_left), + FadeOut(arc_right) + ) + + + + + def indicator_wiggle(): + INDICATOR_WIGGLE_FACTOR = 1.3 + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + + def angle_for_index(i,step): + return -TAU/4 + TAU/2**step * (i + 0.5) + + + def position_for_index(i, step, scaled_down = False): + + theta = angle_for_index(i,step) + radial_vector = np.array([np.cos(theta),np.sin(theta),0]) + position = self.lake_center + self.lake_radius * radial_vector + + if scaled_down: + return position.scale_about_point(OBSERVER_POINT,0.5) + else: + return position + + + def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) + + hyp = VMobject() + hyp1 = Line(self.lake_center,ls_new_loc1) + hyp2 = Line(self.lake_center,ls_new_loc2) + hyp.add(hyp2,hyp1) + self.new_hypotenuses.append(hyp) + + if show_steps == True: + self.play( + ShowCreation(hyp, run_time = run_time) + ) + + leg1 = Line(OBSERVER_POINT,ls_new_loc1) + leg2 = Line(OBSERVER_POINT,ls_new_loc2) + self.new_legs_1.append(leg1) + self.new_legs_2.append(leg2) + + if show_steps == True: + self.play( + ShowCreation(leg1, run_time = run_time), + ShowCreation(leg2, run_time = run_time), + ) + + ls1 = self.light_sources_array[i] + ls2 = ls1.copy() + self.add(ls2) + self.additional_light_sources.append(ls2) + + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.all(np.abs(ls_old_loc) < 10) + onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + show_animation = (onscreen_old or onscreen_1 or onscreen_2) + + if show_animation: + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + + + + def construction_step(n, scale_down = True, show_steps = True, run_time = 1, + simultaneous_splitting = False, ls_radius = 1): + + # we assume that the scene contains: + # an inner lake, self.inner_lake + # an outer lake, self.outer_lake + # light sources, self.light_sources + # legs from the observer point to each light source + # self.legs + # altitudes from the observer point to the + # locations of the light sources in the previous step + # self.altitudes + # hypotenuses connecting antipodal light sources + # self.hypotenuses + + # these are mobjects! + + + # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + else: + self.play( + FadeOut(self.inner_lake) + ) + + # create a new, outer lake + + new_outer_lake = Circle(radius = self.lake_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(self.lake_center) + + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) + + self.wait() + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + ls_radius = ls_radius + ) + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = VMobject() + for leg in self.new_legs_1: + self.legs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + + self.hypotenuses = VMobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + + # update scene + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) + + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, + ) + + + self.wait() + + if show_steps == True: + self.play(FadeOut(ls0_dot)) + + # scale down + if scale_down: + + indicator_wiggle() + + if show_steps == True: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + self.legs.scale_about_point,0.5,OBSERVER_POINT, + self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, + self.altitudes.scale_about_point,0.5,OBSERVER_POINT, + ) + else: + self.play( + ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), + self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, + self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, + ) + + # update the radii bc they haven't done so themselves + # bc reasons... + for ls in self.light_sources_array: + r = ls.radius + ls.set_radius(r*0.5) + + else: + # update the lake center and the radius + self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP + self.lake_radius *= 2 + + + + + + + + + self.lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = VMobject() + self.outer_lake = lake0 + self.legs = VMobject() + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) + self.altitudes = VMobject() + self.hypotenuses = VMobject() + self.light_sources_array = [ls0] + self.light_sources = VMobject() + self.light_sources.add(ls0) + + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + + self.add(self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + ls_radius = 25.0 + + for i in range(3): + construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) + + return + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + for i in range(3,5): + construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, + simultaneous_splitting = True, ls_radius = ls_radius/2**3) + + + + + # Now create a straight number line and transform into it + MAX_N = 17 + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1), + unit_size = LAKE0_RADIUS * TAU/4 / 4, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(2.5 * DOWN) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.wait() + + origin_point = self.number_line.number_to_point(0) + nl_sources = VMobject() + pond_sources = VMobject() + + for i in range(-MAX_N,MAX_N+1): + anchor = self.number_line.number_to_point(2*i + 1) + ls = self.light_sources_array[i].copy() + ls.move_source_to(anchor) + nl_sources.add(ls) + pond_sources.add(self.light_sources_array[i].copy()) + + self.add(pond_sources) + self.remove(self.light_sources) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + width = 20, + height = 10, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + ).flip().next_to(origin_point,UP,buff = 0) + + + + self.play( + Transform(pond_sources,nl_sources), + Transform(self.outer_lake,open_sea), + FadeOut(self.inner_lake) + ) + self.play(FadeIn(self.number_line)) + +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 1.3 + + Arc.__init__(self,angle,**kwargs) + + label = DecimalNumber(self.length, num_decimal_points = 0) + r = BUFFER * self.radius + theta = self.start_angle + self.angle/2 + label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) + + label.move_to(label_pos) + self.add(label) + +class ArcHighlightOverlayScene(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + FLASH_TIME = 0.25 + + def flash_arcs(n): + + angle = TAU/2**n + arcs = [] + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) + + for i in range(1,2**n): + arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) + + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) + + self.play( + FadeIn(arcs[0], run_time = FLASH_TIME) + ) + + for i in range(1,2**n + 1): + self.play( + FadeOut(arcs[i-1], run_time = FLASH_TIME), + FadeIn(arcs[i], run_time = FLASH_TIME) + ) + + self.play( + FadeOut(arcs[2**n], run_time = FLASH_TIME), + ) + + + flash_arcs(3) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 60a47d0e2e9024c8c4a950c01db633c091cbbac2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 16:15:03 -0800 Subject: [PATCH 43/73] Reverted basel.py to original pre-grant state --- active_projects/basel.py | 1531 +++++++++++++++++++++++--------------- 1 file changed, 950 insertions(+), 581 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index c975060e..5d72c415 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -18,7 +18,6 @@ from topics.characters import * from topics.functions import * from topics.number_line import * from topics.numerals import * -#from topics.combinatorics import * from scene import Scene from camera import Camera from mobject.svg_mobject import * @@ -40,14 +39,19 @@ FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -NUM_LEVELS = 30 NUM_CONES = 7 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -AMBIENT_FULL = 0.5 -AMBIENT_DIMMED = 0.2 -SPOTLIGHT_FULL = 0.9 + +NUM_LEVELS = 20 +AMBIENT_FULL = 0.8 +AMBIENT_DIMMED = 0.5 +AMBIENT_SCALE = 1.0 +AMBIENT_RADIUS = 20.0 +SPOTLIGHT_FULL = 0.8 SPOTLIGHT_DIMMED = 0.2 +SPOTLIGHT_SCALE = 1.0 +SPOTLIGHT_RADIUS = 20.0 LIGHT_COLOR = YELLOW DEGREES = TAU/360 @@ -95,6 +99,10 @@ class AngleUpdater(ContinualAnimation): self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) + + + + class LightIndicator(Mobject): CONFIG = { "radius": 0.5, @@ -144,6 +152,9 @@ class LightIndicator(Mobject): print "Indicator cannot update, reason: no light source found" self.set_intensity(self.measured_intensity()) + + + class UpdateLightIndicator(AnimationGroup): def __init__(self, indicator, intensity, **kwargs): @@ -158,11 +169,13 @@ class UpdateLightIndicator(AnimationGroup): AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator + class ContinualLightIndicatorUpdate(ContinualAnimation): def update_mobject(self,dt): self.mobject.continual_update() + def copy_func(f): """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, @@ -188,13 +201,13 @@ class ScaleLightSources(Transform): new_sp.scale(factor,about_point = about_point) submob.move_source_to(new_sp.get_location()) - #ambient_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: ambient_of(r/factor) - #submob.ambient_light.opacity_function = new_of + # ambient_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: ambient_of(r / factor) + # submob.ambient_light.change_opacity_function(new_of) - #spotlight_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: spotlight_of(r/factor) - #submob.spotlight.change_opacity_function(new_of) + # spotlight_of = copy_func(submob.ambient_light.opacity_function) + # new_of = lambda r: spotlight_of(r / factor) + # submob.spotlight.change_opacity_function(new_of) new_r = factor * submob.radius submob.set_radius(new_r) @@ -212,59 +225,48 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) -### -class ThinkAboutPondScene(PiCreatureScene): - CONFIG = { - "default_pi_creature_class" : Randolph, - } - def construct(self): - randy = self.pi_creature - randy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble( - width = 9, - height = 5, - ) - circles = bubble[:3] - angle = -15*DEGREES - circles.rotate(angle, about_point = bubble.get_bubble_center()) - circles.shift(LARGE_BUFF*LEFT) - for circle in circles: - circle.rotate(-angle) - bubble.pin_to(randy) - bubble.shift_onto_screen() - self.play( - randy.change, "thinking", - ShowCreation(bubble) - ) - self.wait(2) - self.play(randy.change, "happy", bubble) - self.wait(2) - self.play(randy.change, "hooray", bubble) - self.wait(2) + + + + + + + + + + + + + class IntroScene(PiCreatureScene): CONFIG = { - "rect_height" : 0.1, - "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF, - "n_rects_to_show" : 30, + "rect_height" : 0.2, + "duration" : 0.5, + "eq_spacing" : 6 * MED_LARGE_BUFF } def construct(self): + randy = self.get_primary_pi_creature() randy.scale(0.7).to_corner(DOWN+RIGHT) self.build_up_euler_sum() - self.show_history() - # self.other_pi_formulas() - # self.refocus_on_euler_sum() + self.build_up_sum_on_number_line() + self.show_pi_answer() + self.other_pi_formulas() + self.refocus_on_euler_sum() + + + + def build_up_euler_sum(self): - morty = self.pi_creature - euler_sum = self.euler_sum = TexMobject( + + self.euler_sum = TexMobject( "1", "+", "{1 \\over 4}", "+", "{1 \\over 9}", "+", @@ -273,27 +275,70 @@ class IntroScene(PiCreatureScene): "\\cdots", "=", arg_separator = " \\, " ) - equals_sign = euler_sum.get_part_by_tex("=") - plusses = euler_sum.get_parts_by_tex("+") - term_mobjects = euler_sum.get_parts_by_tex("1") self.euler_sum.to_edge(UP) self.euler_sum.shift(2*LEFT) - max_n = self.n_rects_to_show - terms = [1./(n**2) for n in range(1, max_n + 1)] - series_terms = list(np.cumsum(terms)) - series_terms.append(np.pi**2/6) ##Just force this up there + terms = [1./n**2 for n in range(1,6)] + partial_results_values = np.cumsum(terms) - partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( - series_terms[1], - num_decimal_points = 2 + self.play( + FadeIn(self.euler_sum[0], run_time = self.duration) ) - partial_sum_decimal.next_to(equals_sign, RIGHT) - ## Number line + equals_sign = self.euler_sum.get_part_by_tex("=") - number_line = self.number_line = NumberLine( + self.partial_sum_decimal = DecimalNumber(partial_results_values[1], + num_decimal_points = 2) + self.partial_sum_decimal.next_to(equals_sign, RIGHT) + + + + for i in range(4): + + FadeIn(self.partial_sum_decimal, run_time = self.duration) + + if i == 0: + + self.play( + FadeIn(self.euler_sum[1], run_time = self.duration), + FadeIn(self.euler_sum[2], run_time = self.duration), + FadeIn(equals_sign, run_time = self.duration), + FadeIn(self.partial_sum_decimal, run_time = self.duration) + ) + + else: + self.play( + FadeIn(self.euler_sum[2*i+1], run_time = self.duration), + FadeIn(self.euler_sum[2*i+2], run_time = self.duration), + ChangeDecimalToValue( + self.partial_sum_decimal, + partial_results_values[i+1], + run_time = self.duration, + num_decimal_points = 6, + show_ellipsis = True, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ) + + self.wait() + + self.q_marks = TextMobject("???").highlight(LIGHT_COLOR) + self.q_marks.move_to(self.partial_sum_decimal) + + self.play( + FadeIn(self.euler_sum[-3], run_time = self.duration), # + + FadeIn(self.euler_sum[-2], run_time = self.duration), # ... + ReplacementTransform(self.partial_sum_decimal, self.q_marks) + ) + + self.wait() + + + + def build_up_sum_on_number_line(self): + + self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1, @@ -303,234 +348,139 @@ class IntroScene(PiCreatureScene): unit_size = 5, tick_frequency = 0.2, line_to_number_buff = MED_LARGE_BUFF + ).shift(LEFT) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.play( + FadeIn(self.number_line), + FadeIn(self.number_line_labels) ) - number_line.add_numbers() - number_line.to_edge(LEFT) - number_line.shift(MED_LARGE_BUFF*UP) + self.wait() # create slabs for series terms - lines = VGroup() - rects = self.rects = VGroup() - rect_labels = VGroup() - slab_colors = it.cycle([YELLOW, BLUE]) - rect_anims = [] - rect_label_anims = [] + max_n1 = 10 + max_n2 = 100 - for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): - color = slab_colors.next() - line = Line(*map(number_line.number_to_point, [t1, t2])) - rect = Rectangle( - stroke_width = 0, - fill_opacity = 1, - fill_color = color + terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)] + series_terms = np.cumsum(terms) + lines = VGroup() + self.rects = VGroup() + slab_colors = [YELLOW, BLUE] * (max_n2 / 2) + + for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): + line = Line(*map(self.number_line.number_to_point, [t1, t2])) + rect = Rectangle() + rect.stroke_width = 0 + rect.fill_opacity = 1 + rect.highlight(color) + rect.stretch_to_fit_height( + self.rect_height, ) - rect.match_width(line) - rect.stretch_to_fit_height(self.rect_height) + rect.stretch_to_fit_width(0.5 * line.get_width()) rect.move_to(line) - if i <= 5: - if i == 1: - rect_label = TexMobject("1") - else: - rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) - rect_label.scale(0.75) - max_width = 0.7*rect.get_width() - if rect_label.get_width() > max_width: - rect_label.scale_to_fit_width(max_width) - rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + self.rects.add(rect) + lines.add(line) - term_mobject = term_mobjects[i-1] - rect_anim = GrowFromPoint(rect, term_mobject.get_center()) - rect_label_anim = ReplacementTransform( - term_mobject.copy(), rect_label + #self.rects.radial_gradient_highlight(ORIGIN, 5, YELLOW, BLUE) + + self.little_euler_terms = VGroup() + for i in range(1,7): + if i == 1: + term = TexMobject("1", fill_color = slab_colors[i-1]) + else: + term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1]) + term.scale(0.4) + self.little_euler_terms.add(term) + + + for i in range(5): + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), + run_time = 1) + ) + term = self.little_euler_terms.submobjects[i] + term.next_to(self.rects[i], UP) + self.play(FadeIn(term)) + + self.ellipsis = TexMobject("\cdots") + self.ellipsis.scale(0.4) + + for i in range(5, max_n1): + + if i == 5: + self.ellipsis.next_to(self.rects[i+3], UP) + self.play( + FadeIn(self.ellipsis), + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) ) else: - rect_label = VectorizedPoint() - rect_anim = GrowFromPoint(rect, rect.get_left()) - rect_label_anim = FadeIn(rect_label) + self.play( + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.5) + ) - rects.add(rect) - rect_labels.add(rect_label) - rect_anims.append(rect_anim) - rect_label_anims.append(rect_label_anim) - lines.add(line) - dots = TexMobject("\\dots").scale(0.5) - last_rect = rect_anims[-1].target_mobject - dots.scale_to_fit_width(0.9*last_rect.get_width()) - dots.move_to(last_rect, UP+RIGHT) - rects.submobjects[-1] = dots - rect_anims[-1] = FadeIn(dots) - - self.add(number_line) - self.play(FadeIn(euler_sum[0])) - self.play( - rect_anims[0], - rect_label_anims[0] - ) - for i in range(4): + for i in range(max_n1, max_n2): self.play( - FadeIn(term_mobjects[i+1]), - FadeIn(plusses[i]), - ) - anims = [ - rect_anims[i+1], - rect_label_anims[i+1], - ] - if i == 0: - anims += [ - FadeIn(equals_sign), - FadeIn(partial_sum_decimal) - ] - elif i <= 5: - anims += [ - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - run_time = 1, - num_decimal_points = 6, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ] - self.play(*anims) + GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), + run_time = 0.01) + ) - for i in range(4, len(series_terms)-2): - anims = [ - rect_anims[i+1], - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - num_decimal_points = 6, - ), - ] - if i == 5: - anims += [ - FadeIn(euler_sum[-3]), # + - FadeIn(euler_sum[-2]), # ... - ] - self.play(*anims, run_time = 2./i) - - brace = self.brace = Brace(partial_sum_decimal, DOWN) - q_marks = self.q_marks = TextMobject("???") - q_marks.next_to(brace, DOWN) - q_marks.highlight(LIGHT_COLOR) - - self.play( - GrowFromCenter(brace), - Write(q_marks), - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[-1], - num_decimal_points = 6, - ), - morty.change, "confused", - ) self.wait() - self.number_line_group = VGroup( - number_line, rects, rect_labels + PI = TAU/2 + P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT + Q = self.rects[-1].get_center() + 0.2 * UP + self.arrow = CurvedArrow(P, Q, + angle = TAU/12, + color = YELLOW ) - def show_history(self): - # Pietro Mengoli in 1644 - morty = self.pi_creature - pietro = ImageMobject("Pietro_Mengoli") - euler = ImageMobject("Euler") + self.play(FadeIn(self.arrow)) - pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") - pietro_words.scale(0.75) - pietro_words.next_to(pietro, DOWN) - pietro.add(pietro_words) - - euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") - euler_words.scale(0.75) - euler_words.next_to(euler, DOWN) - euler.add(euler_words) - - pietro.next_to(SPACE_WIDTH*LEFT, LEFT) - euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) - - pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") - pi_answer.highlight(YELLOW) - pi_answer.move_to(self.partial_sum_decimal, LEFT) - equals_sign = TexMobject("=") - equals_sign.next_to(pi_answer, RIGHT) - pi_answer.shift(SMALL_BUFF*UP) - self.partial_sum_decimal.generate_target() - self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) - - pi = pi_answer[0] - pi_rect = SurroundingRectangle(pi, color = RED) - pi_rect.save_state() - pi_rect.scale_to_fit_height(SPACE_HEIGHT) - pi_rect.center() - pi_rect.set_stroke(width = 0) - squared = pi_answer[1] - squared_rect = SurroundingRectangle(squared, color = BLUE) - - brace = Brace( - VGroup(self.euler_sum, self.partial_sum_decimal.target), - DOWN, buff = SMALL_BUFF - ) - basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) - - self.number_line_group.save_state() - self.play( - pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, - self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, - morty.change, "pondering", - ) - self.wait(2) - self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) - self.wait(2) - self.play( - ReplacementTransform(self.q_marks, pi_answer), - FadeIn(equals_sign), - FadeOut(self.brace), - MoveToTarget(self.partial_sum_decimal) - ) self.wait() - self.play(morty.change, "surprised") - self.play(pi_rect.restore) + + + def show_pi_answer(self): + + self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) + self.pi_answer.move_to(self.partial_sum_decimal) + self.pi_answer.next_to(self.euler_sum[-1], RIGHT, buff = 1, + submobject_to_align = self.pi_answer[-2]) + self.play(ReplacementTransform(self.q_marks, self.pi_answer)) + self.wait() - self.play(Transform(pi_rect, squared_rect)) - self.play(FadeOut(pi_rect)) - self.play(morty.change, "hesitant") - self.wait(2) - self.play( - GrowFromCenter(brace), - euler.to_edge, DOWN, - pietro.to_edge, DOWN, - self.number_line_group.restore, - self.number_line_group.shift, LARGE_BUFF*RIGHT, - ) - self.play(Write(basel_text)) - self.play(morty.change, "happy") - self.wait(4) + def other_pi_formulas(self): self.play( FadeOut(self.rects), - FadeOut(self.number_line) + FadeOut(self.number_line_labels), + FadeOut(self.number_line), + FadeOut(self.little_euler_terms), + FadeOut(self.ellipsis), + FadeOut(self.arrow) ) self.leibniz_sum = TexMobject( "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") + "=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ") self.wallis_product = TexMobject( "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") + "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ") self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, + buff = 2, submobject_to_align = self.leibniz_sum.get_part_by_tex("=") ) self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, + buff = 2, submobject_to_align = self.wallis_product.get_part_by_tex("=") ) @@ -542,6 +492,8 @@ class IntroScene(PiCreatureScene): Write(self.wallis_product) ) + + def refocus_on_euler_sum(self): self.euler_sum.add(self.pi_answer) @@ -556,7 +508,11 @@ class IntroScene(PiCreatureScene): # focus on pi squared pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] self.play( - ScaleInPlace(pi_squared,2,rate_func = wiggle) + WiggleOutThenIn(pi_squared, + scale_value = 4, + angle = 0.003 * TAU, + run_time = 2 + ) ) @@ -566,204 +522,37 @@ class IntroScene(PiCreatureScene): q_circle = Circle( stroke_color = YELLOW, fill_color = YELLOW, - fill_opacity = 0.5, - radius = 0.4, - stroke_width = 10.0 + fill_opacity = 0.25, + radius = 0.5, + stroke_width = 3.0 ) q_mark = TexMobject("?") q_mark.next_to(q_circle) thought = Group(q_circle, q_mark) - q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + q_mark.scale_to_fit_height(0.6 * q_circle.get_height()) + + self.look_at(pi_squared) self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 2, "width" : 3 }) + bubble_kwargs = { "height" : 2.5, "width" : 5 }) + self.look_at(pi_squared) self.wait() -class PiHidingWrapper(Scene): - def construct(self): - title = TextMobject("Pi hiding in prime regularities") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(screen)) - self.wait(2) -class MathematicalWebOfConnections(PiCreatureScene): - def construct(self): - self.complain_that_pi_is_not_about_circles() - self.show_other_pi_formulas() - self.question_fundamental() - self.draw_circle() - self.remove_all_but_basel_sum() - self.show_web_of_connections() - self.show_light() - def complain_that_pi_is_not_about_circles(self): - jerk, randy = self.pi_creatures - words = self.words = TextMobject( - "$\\pi$ is not", - "fundamentally \\\\", - "about circles" - ) - words.highlight_by_tex("fundamentally", YELLOW) - self.play(PiCreatureSays( - jerk, words, - target_mode = "angry" - )) - self.play(randy.change, "guilty") - self.wait(2) - def show_other_pi_formulas(self): - jerk, randy = self.pi_creatures - words = self.words - basel_sum = TexMobject( - "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", - "=", "{\\pi^2 \\over 6}" - ) - leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") - wallis_product = TexMobject( - "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + - "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") - basel_sum.move_to(randy) - basel_sum.to_edge(UP) - basel_equals = basel_sum.get_part_by_tex("=") - formulas = VGroup(basel_sum, leibniz_sum, wallis_product) - formulas.scale(0.75) - formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) - for formula in formulas: - basel_equals_x = basel_equals.get_center()[0] - formula_equals_x = formula.get_part_by_tex("=").get_center()[0] - formula.shift((basel_equals_x - formula_equals_x)*RIGHT) - formulas.move_to(randy) - formulas.to_edge(UP) - formulas.shift_onto_screen() - self.formulas = formulas - self.play( - jerk.change, "sassy", - randy.change, "raise_right_hand", - FadeOut(jerk.bubble), - words.next_to, jerk, UP, - FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) - ) - for formula in formulas[1:]: - self.play( - FadeIn( - formula, - submobject_mode = "lagged_start", - run_time = 3 - ), - ) - self.wait() - def question_fundamental(self): - jerk, randy = self.pi_creatures - words = self.words - fundamentally = words.get_part_by_tex("fundamentally") - words.remove(fundamentally) - self.play( - fundamentally.move_to, self.pi_creatures, - fundamentally.shift, UP, - FadeOut(words), - jerk.change, "pondering", - randy.change, "pondering", - ) - self.wait() - question = TextMobject("Does this mean \\\\ anything?") - question.scale(0.8) - question.set_stroke(WHITE, 0.5) - question.next_to(fundamentally, DOWN, LARGE_BUFF) - arrow = Arrow(question, fundamentally) - arrow.highlight(WHITE) - - self.play( - FadeIn(question), - GrowArrow(arrow) - ) - self.wait() - - fundamentally.add(question, arrow) - self.fundamentally = fundamentally - - def draw_circle(self): - semi_circle = Arc(angle = np.pi, radius = 2) - radius = Line(ORIGIN, semi_circle.points[0]) - radius.highlight(BLUE) - semi_circle.highlight(YELLOW) - - VGroup(radius, semi_circle).move_to( - SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, - ) - - decimal = DecimalNumber(0) - def decimal_position_update_func(decimal): - decimal.move_to(semi_circle.points[-1]) - decimal.shift(0.3*radius.get_vector()) - - one = TexMobject("1") - one.next_to(radius, UP) - - self.play(ShowCreation(radius), FadeIn(one)) - self.play( - Rotate(radius, np.pi, about_point = radius.get_start()), - ShowCreation(semi_circle), - ChangeDecimalToValue( - decimal, np.pi, - position_update_func = decimal_position_update_func - ), - MaintainPositionRelativeTo(one, radius), - run_time = 3, - ) - self.wait(2) - - self.circle_group = VGroup(semi_circle, radius, one, decimal) - - def remove_all_but_basel_sum(self): - to_shift_down = VGroup( - self.circle_group, self.pi_creatures, - self.fundamentally, self.formulas[1:], - ) - to_shift_down.generate_target() - for part in to_shift_down.target: - part.move_to(2*SPACE_HEIGHT*DOWN) - - basel_sum = self.formulas[0] - - self.play( - MoveToTarget(to_shift_down), - basel_sum.scale, 1.5, - basel_sum.move_to, 2*DOWN, - ) - - def show_web_of_connections(self): - pass - - def show_light(self): - pass - - ### - - def create_pi_creatures(self): - jerk = PiCreature(color = GREEN_D) - randy = Randolph().flip() - jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) - randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) - - return VGroup(jerk, randy) @@ -819,7 +608,7 @@ class FirstLighthouseScene(PiCreatureScene): width = 2.5, height = 3.5) bubble.next_to(randy,LEFT+UP) bubble.add_content(light_indicator) - + self.wait() self.play( randy.change, "wave_2", ShowCreation(bubble), @@ -844,15 +633,15 @@ class FirstLighthouseScene(PiCreatureScene): for i in range(1,NUM_CONES+1): light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1), num_levels = NUM_LEVELS, - radius = 12.0, + radius = AMBIENT_RADIUS, ) point = self.number_line.number_to_point(i) light_source.move_source_to(point) light_sources.append(light_source) - + self.wait() for ls in light_sources: self.add_foreground_mobject(ls.lighthouse) @@ -866,7 +655,7 @@ class FirstLighthouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size + indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) @@ -879,10 +668,12 @@ class FirstLighthouseScene(PiCreatureScene): # this last line *technically* fades in the last term, but it is off-screen ChangeDecimalToValue(light_indicator.reading,intensities[i], rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) + ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i], + rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME) ) if i == 0: + self.wait() # move a copy out of the thought bubble for comparison light_indicator_copy = light_indicator.copy() old_y = light_indicator_copy.get_center()[1] @@ -891,6 +682,10 @@ class FirstLighthouseScene(PiCreatureScene): light_indicator_copy.shift,[0, new_y - old_y,0] ) + self.wait() + + self.wait() + # quickly switch on off-screen light cones and increment indicator for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size @@ -925,6 +720,28 @@ class FirstLighthouseScene(PiCreatureScene): self.wait() + + + + + + + + + + + + + + + + + + + + + + class SingleLighthouseScene(PiCreatureScene): def construct(self): @@ -932,7 +749,7 @@ class SingleLighthouseScene(PiCreatureScene): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates self.rotate_screen() - self.morph_lighthouse_into_sun() + #self.morph_lighthouse_into_sun() def setup_elements(self): @@ -947,10 +764,12 @@ class SingleLighthouseScene(PiCreatureScene): # Light source self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), num_levels = NUM_LEVELS, radius = 10, - max_opacity_ambient = AMBIENT_FULL + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, + ) self.light_source.move_source_to(source_point) @@ -973,7 +792,7 @@ class SingleLighthouseScene(PiCreatureScene): # Screen self.screen = Rectangle( - width = 0.1, + width = 0.06, height = 2, mark_paths_closed = True, fill_color = WHITE, @@ -990,26 +809,39 @@ class SingleLighthouseScene(PiCreatureScene): self.play(FadeIn(self.screen)) - self.light_source.set_max_opacity_spotlight(0.001) - self.add(self.light_source.spotlight) + #self.light_source.set_max_opacity_spotlight(0.001) + #self.play(SwitchOn(self.light_source.spotlight)) - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) self.wait() + + # just calling .dim_ambient via ApplyMethod does not work, why? dimmed_ambient_light = self.light_source.ambient_light.deepcopy() dimmed_ambient_light.dimming(AMBIENT_DIMMED) + self.light_source.update_shadow() self.play( - Transform(self.light_source.ambient_light,dimmed_ambient_light), - self.light_source.set_max_opacity_spotlight,1.0, - FadeIn(self.light_source.shadow) + FadeIn(self.light_source.shadow), + ) + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) + + self.play( + self.light_source.dim_ambient, + #Transform(self.light_source.ambient_light,dimmed_ambient_light), + #self.light_source.set_max_opacity_spotlight,1.0, + ) + self.play( + FadeIn(self.light_source.spotlight) ) - self.add_foreground_mobject(morty) + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() @@ -1036,7 +868,9 @@ class SingleLighthouseScene(PiCreatureScene): self.angle_indicator = DecimalNumber(arc_angle / DEGREES, num_decimal_points = 0, - unit = "^\\circ") + unit = "^\\circ", + fill_opacity = 1.0, + fill_color = WHITE) self.angle_indicator.next_to(self.angle_arc,RIGHT) angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES @@ -1059,6 +893,7 @@ class SingleLighthouseScene(PiCreatureScene): self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) + self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) self.wait() @@ -1073,47 +908,123 @@ class SingleLighthouseScene(PiCreatureScene): ### but it doesn't work - def morph_lighthouse_into_sun(self): +class MorphIntoSunScene(PiCreatureScene): + + def construct(self): + + self.setup_elements() + self.morph_lighthouse_into_sun() + def setup_elements(self): - sun_position = [-100,0,0] + self.remove(self.get_primary_pi_creature()) + SCREEN_SIZE = 3.0 + DISTANCE_FROM_LIGHTHOUSE = 10.0 + source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] + observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + + # Light source + + self.light_source = LightSource( + opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_FULL, - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) ) - self.sun = self.light_source.deepcopy() + self.light_source.move_source_to(source_point) - #self.sun.num_levels = NUM_LEVELS, - #self.sun.set_radius(150) - #self.sun.set_max_opacity_ambient(AMBIENT_FULL) + + # Pi Creature + + morty = self.get_primary_pi_creature() + morty.scale(0.5) + morty.move_to(observer_point) + morty.shift(2*OUT) + self.add_foreground_mobject(morty) + + self.add(self.light_source.lighthouse,self.light_source.ambient_light) + # Screen - self.sun.spotlight.change_opacity_function(lambda r: 0.5) - self.sun.set_radius(150) - self.sun.move_source_to(sun_position) + self.screen = Rectangle( + width = 0.06, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) - # self.sun.update() + self.screen.next_to(morty,LEFT) - # self.add(self.sun) - # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) + self.light_source.set_screen(self.screen) + self.add(self.screen,self.light_source.shadow) + + self.add_foreground_mobject(self.light_source.shadow) + self.add_foreground_mobject(morty) - #print self.sun.spotlight.get_source_point() + self.light_source.dim_ambient + self.add(self.light_source.spotlight) + + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + def morph_lighthouse_into_sun(self): + + sun_position = np.array([-100,0,0]) + + + + + # Why does none of this change the opacity function??? + + self.sun = self.light_source.copy() + + self.sun.change_spotlight_opacity_function(lambda r: 0.1) + # self.sun.spotlight.opacity_function = lambda r: 0.1 + # for submob in self.sun.spotlight.submobjects: + # submob.set_fill(opacity = 0.1) + + #self.sun.move_source_to(sun_position) + #self.sun.set_radius(120) + + self.sun.spotlight.generate_points() + + self.wait() self.play( - #self.light_source.spotlight.move_source_to,sun_position, Transform(self.light_source,self.sun) ) - #self.add(ScreenTracker(self.sun)) - self.wait() + + + + + + + + + + + + + + + + + + class EarthScene(Scene): def construct(self): @@ -1126,8 +1037,8 @@ class EarthScene(Scene): # screen self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) self.screen.set_points_as_corners([ - [3,-self.screen_height/2,0], - [3,self.screen_height/2,0] + [0,-self.screen_height/2,0], + [0,self.screen_height/2,0] ]) # Earth @@ -1136,6 +1047,7 @@ class EarthScene(Scene): earth_center = [earth_center_x,0,0] earth_radius = 3 earth = Circle(radius = earth_radius) + earth.add(self.screen) earth.move_to(earth_center) #self.remove(self.screen_tracker) @@ -1144,8 +1056,15 @@ class EarthScene(Scene): theta1 = theta0 + dtheta theta = (theta0 + theta1)/2 - earth.add(self.screen) + self.add_foreground_mobject(self.screen) + # background Earth + background_earth = SVGMobject( + file_name = "earth", + width = 2 * earth_radius, + fill_color = BLUE, + ) + background_earth.move_to(earth_center) # Morty morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) @@ -1178,8 +1097,13 @@ class EarthScene(Scene): self.wait() - self.play(FadeIn(earth)) - self.bring_to_back(earth) + self.play( + FadeIn(earth), + FadeIn(background_earth) + ) + self.add_foreground_mobject(earth) + self.add_foreground_mobject(self.screen) + # move screen onto Earth screen_on_earth = self.screen.deepcopy() @@ -1191,6 +1115,7 @@ class EarthScene(Scene): 0])) polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) + polar_morty.highlight(BLUE_C) self.play( Transform(self.screen, screen_on_earth), @@ -1202,6 +1127,8 @@ class EarthScene(Scene): tropical_morty = polar_morty.copy() tropical_morty.move_to(np.array([0,0,0])) + tropical_morty.highlight(RED) + morty.target = tropical_morty # move screen to equator @@ -1211,6 +1138,30 @@ class EarthScene(Scene): MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), ) + + + + + + + + + + + + + + + + + + + + + + + + class ScreenShapingScene(ThreeDScene): @@ -1656,6 +1607,9 @@ class ScreenShapingScene(ThreeDScene): FadeIn(reading39), ) + + + class IndicatorScalingScene(Scene): def construct(self): @@ -1693,6 +1647,11 @@ class IndicatorScalingScene(Scene): self.play(FadeIn(reading3)) self.wait() + + + + + class BackToEulerSumScene(PiCreatureScene): @@ -1911,6 +1870,10 @@ class BackToEulerSumScene(PiCreatureScene): self.wait() + + + + class TwoLightSourcesScene(PiCreatureScene): def construct(self): @@ -2088,6 +2051,8 @@ class TwoLightSourcesScene(PiCreatureScene): Write(theorem_name), ) + + class IPTScene1(PiCreatureScene): def construct(self): @@ -2278,6 +2243,8 @@ class IPTScene1(PiCreatureScene): Transform(screen2, screen2pp), ) + + class IPTScene2(Scene): def construct(self): @@ -2329,7 +2296,17 @@ class IPTScene2(Scene): text.next_to(box,UP) self.play(ShowCreation(box),Write(text)) -class PondScene(Scene): + + + + + + + +class PondScene(ThreeDScene): + + + def construct(self): @@ -2346,12 +2323,52 @@ class PondScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 5 + LIGHT_CUTOFF = 1 + + self.cumulated_zoom_factor = 1 + + #self.force_skipping() + + + def zoom_out_scene(factor): + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary - obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - + self.unzoomable_mobs.add(self.obs_dot, ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2360,6 +2377,7 @@ class PondScene(Scene): fill_opacity = LAKE_OPACITY ) lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) # Morty and indicator morty = Mortimer().scale(0.3) @@ -2370,12 +2388,15 @@ class PondScene(Scene): color = LIGHT_COLOR ) indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) # first lighthouse - ls0 = LightSource() + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func) ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,self.obs_dot,ls0_dot, ls0.lighthouse) self.wait() @@ -2435,15 +2456,16 @@ class PondScene(Scene): self.play( ShowCreation(diameter), Write(diameter_text), - #FadeOut(obs_dot), + #FadeOut(self.obs_dot), FadeOut(ls0_dot) ) - indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator.reading.move_to(indicator) + indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) self.play( - FadeIn(indicator.reading) + FadeIn(indicator_reading) ) # replace d with its value @@ -2459,7 +2481,7 @@ class PondScene(Scene): new_reading.move_to(indicator) self.play( - Transform(indicator.reading,new_reading) + Transform(indicator_reading,new_reading) ) self.play( @@ -2478,7 +2500,7 @@ class PondScene(Scene): self.play( ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) ) @@ -2493,12 +2515,12 @@ class PondScene(Scene): position = self.lake_center + self.lake_radius * radial_vector if scaled_down: - return position.scale_about_point(OBSERVER_POINT,0.5) + return position.scale_about_point(self.obs_dot.get_center(),0.5) else: return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + def split_light_source(i, step, show_steps = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2514,8 +2536,8 @@ class PondScene(Scene): ShowCreation(hyp, run_time = run_time) ) - leg1 = Line(OBSERVER_POINT,ls_new_loc1) - leg2 = Line(OBSERVER_POINT,ls_new_loc2) + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) @@ -2526,15 +2548,17 @@ class PondScene(Scene): ) ls1 = self.light_sources_array[i] + + ls2 = ls1.copy() self.add(ls2) self.additional_light_sources.append(ls2) # check if the light sources are on screen ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) if show_animation: @@ -2550,8 +2574,8 @@ class PondScene(Scene): - def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_radius = 1): + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -2569,18 +2593,22 @@ class PondScene(Scene): # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) self.play( FadeOut(self.hypotenuses), FadeOut(self.altitudes), FadeOut(self.inner_lake) ) else: + self.zoomable_mobs.remove(self.inner_lake) self.play( FadeOut(self.inner_lake) ) # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP new_outer_lake = Circle(radius = self.lake_radius, stroke_width = LAKE_STROKE_WIDTH, @@ -2605,18 +2633,18 @@ class PondScene(Scene): self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] - self.new_hypotenuses = [] + self.new_hypotenuses = [] for i in range(2**n): split_light_source(i, step = n, show_steps = show_steps, - run_time = run_time, - ls_radius = ls_radius + run_time = run_time ) @@ -2627,16 +2655,23 @@ class PondScene(Scene): self.legs = VMobject() for leg in self.new_legs_1: self.legs.add(leg) + self.zoomable_mobs.add(leg) for leg in self.new_legs_2: self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) self.hypotenuses = VMobject() for hyp in self.new_hypotenuses: self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) for ls in self.additional_light_sources: self.light_sources.add(ls) self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) # update scene self.add( @@ -2644,6 +2679,7 @@ class PondScene(Scene): self.inner_lake, self.outer_lake, ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) if show_steps == True: self.add( @@ -2651,6 +2687,7 @@ class PondScene(Scene): self.hypotenuses, self.altitudes, ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) self.wait() @@ -2658,37 +2695,8 @@ class PondScene(Scene): if show_steps == True: self.play(FadeOut(ls0_dot)) - # scale down - if scale_down: - - indicator_wiggle() - - if show_steps == True: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - ) - else: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - ) - - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - - else: - # update the lake center and the radius - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP - self.lake_radius *= 2 + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 @@ -2711,6 +2719,8 @@ class PondScene(Scene): self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + self.add(self.inner_lake, self.outer_lake, self.legs, @@ -2725,29 +2735,44 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 - for i in range(3): - construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) + construction_step(0) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(1) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + + - return self.play( FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) - ) - - for i in range(3,5): - construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True, ls_radius = ls_radius/2**3) + ) + max_it = 6 + scale = 2**(max_it - 4) + TEX_SCALE *= scale + for i in range(3,max_it + 1): + construction_step(i, show_steps = False, run_time = 4.0/2**i) + #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it MAX_N = 17 + origin_point = self.obs_dot.get_center() + self.number_line = NumberLine( x_min = -MAX_N, x_max = MAX_N + 1, @@ -2755,13 +2780,13 @@ class PondScene(Scene): number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = range(-MAX_N,MAX_N + 1), - unit_size = LAKE0_RADIUS * TAU/4 / 4, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(2.5 * DOWN) + ).shift(scale * 2.5 * DOWN) self.number_line.label_direction = DOWN @@ -2786,8 +2811,8 @@ class PondScene(Scene): # open sea open_sea = Rectangle( - width = 20, - height = 10, + width = 20 * scale, + height = 10 * scale, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, @@ -2797,12 +2822,389 @@ class PondScene(Scene): self.play( - Transform(pond_sources,nl_sources), - Transform(self.outer_lake,open_sea), + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), FadeOut(self.inner_lake) ) self.play(FadeIn(self.number_line)) + + self.wait() + + v = 5 * scale * UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + #self.remove(self.obs_dot) + self.play( + indicator.move_to, origin_point + scale * UP, + indicator_reading.move_to, origin_point + scale * UP, + FadeOut(open_sea), + FadeOut(morty), + FadeIn(self.number_line_labels) + ) + + two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ + "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ + "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ + "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ + "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") + + nb_symbols = len(two_sided_sum.submobjects) + + two_sided_sum.scale(TEX_SCALE) + + for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.2 * scale * DOWN) + + self.play(Write(two_sided_sum)) + + covering_rectangle = Rectangle( + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1, + ) + covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + for i in range(10): + self.add_foreground_mobject(nl_sources.submobjects[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator_reading) + + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(indicator) + + central_plus_sign = two_sided_sum[13] + + self.play( + FadeIn(covering_rectangle), + ReplacementTransform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) + ) + + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + + self.play( + indicator.move_to,p, + half_indicator_reading.move_to,p, + FadeIn(equals_sign) + ) + + # show Randy admiring the result + randy = Randolph().scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + + + +class WaitScene(TeacherStudentsScene): + + def construct(self): + + self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!")) + + student_q = TextMobject("What about") + full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?") + full_sum.next_to(student_q,RIGHT) + student_q.add(full_sum) + + + self.student_says(student_q, target_mode = "angry") + + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + + unit_length = 1.5 + vertical_spacing = 2.5 * DOWN + switch_on_time = 0.2 + + sum_vertical_spacing = 1.5 + + randy = self.get_primary_pi_creature() + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + + ls_template = LightSource( + radius = 2, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.5,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_to_show = odd_range, + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_LARGE_BUFF, + include_tip = True + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0) + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1), + ) + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2), + ) + + + + for ls in full_lights.submobjects: + self.play( + FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights) + ) + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + Transform(even_lights,even_lights_copy) + ) + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + + + full_terms = VMobject() + for i in full_range: + if i == 1: + term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + else: + term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + buffer = 2 + result_box = Rectangle(width = 15, + height = buffer*equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + equation.add(result_box) + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + ShowCreation(result_box) + ) + + self.play(equation.shift, -equation.get_center()[1] * UP + UP) + + + + + + + + + + + + + + + + + + class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -2822,6 +3224,12 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) + + + + + + class ArcHighlightOverlayScene(Scene): def construct(self): @@ -2867,43 +3275,4 @@ class ArcHighlightOverlayScene(Scene): ) - flash_arcs(3) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + flash_arcs(3) \ No newline at end of file From a302a6bf04a69dd45bb00f1f363772d2952a081c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 17:12:47 -0800 Subject: [PATCH 44/73] Finished MathematicalWebOfConnections scene --- active_projects/basel2.py | 117 ++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index c975060e..804b2896 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -245,9 +245,8 @@ class ThinkAboutPondScene(PiCreatureScene): self.wait(2) class IntroScene(PiCreatureScene): - CONFIG = { - "rect_height" : 0.1, + "rect_height" : 0.075, "duration" : 1.0, "eq_spacing" : 3 * MED_LARGE_BUFF, "n_rects_to_show" : 30, @@ -334,11 +333,11 @@ class IntroScene(PiCreatureScene): rect_label = TexMobject("1") else: rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) - rect_label.scale(0.75) + rect_label.scale(0.75) max_width = 0.7*rect.get_width() if rect_label.get_width() > max_width: rect_label.scale_to_fit_width(max_width) - rect_label.next_to(rect, UP, MED_SMALL_BUFF/(i+1)) + rect_label.next_to(rect, UP, buff = MED_LARGE_BUFF/(i+1)) term_mobject = term_mobjects[i-1] rect_anim = GrowFromPoint(rect, term_mobject.get_center()) @@ -746,14 +745,115 @@ class MathematicalWebOfConnections(PiCreatureScene): self.play( MoveToTarget(to_shift_down), basel_sum.scale, 1.5, - basel_sum.move_to, 2*DOWN, + basel_sum.move_to, 1.5*DOWN, ) + self.basel_sum = basel_sum + def show_web_of_connections(self): - pass + self.remove(self.pi_creatures) + title = TextMobject("Interconnected web of mathematics") + title.to_edge(UP) + basel_sum = self.basel_sum + + dots = VGroup(*[ + Dot(radius = 0.1).move_to( + (j - 0.5*(i%2))*RIGHT + \ + (np.sqrt(3)/2.0)* i*DOWN + \ + 0.5*(random.random()*RIGHT + random.random()*UP), + ) + for i in range(4) + for j in range(7+(i%2)) + ]) + dots.scale_to_fit_height(3) + dots.next_to(title, DOWN, MED_LARGE_BUFF) + edges = VGroup() + for x in range(100): + d1, d2 = random.sample(dots, 2) + edge = Line(d1.get_center(), d2.get_center()) + edge.set_stroke(YELLOW, 0.5) + edges.add(edge) + + ## Choose special path + path_dots = VGroup( + dots[-7], + dots[-14], + dots[9], + dots[19], + dots[14], + ) + path_edges = VGroup(*[ + Line( + d1.get_center(), d2.get_center(), + color = RED + ) + for d1, d2 in zip(path_dots, path_dots[1:]) + ]) + + circle = Circle(color = YELLOW, radius = 1) + radius = Line(circle.get_center(), circle.get_right()) + radius.highlight(BLUE) + VGroup(circle, radius).next_to(path_dots[-1], RIGHT) + + self.play( + Write(title), + LaggedStart(ShowCreation, edges, run_time = 3), + LaggedStart(GrowFromCenter, dots, run_time = 3) + ) + self.play(path_dots[0].highlight, RED) + for dot, edge in zip(path_dots[1:], path_edges): + self.play( + ShowCreation(edge), + dot.highlight, RED + ) + self.play(ShowCreation(radius)) + radius.set_points_as_corners(radius.get_anchors()) + self.play( + ShowCreation(circle), + Rotate(radius, angle = 0.999*TAU, about_point = radius.get_start()), + run_time = 2 + ) + self.wait() + + graph = VGroup(dots, edges, path_edges, title) + circle.add(radius) + basel_sum.generate_target() + basel_sum.target.to_edge(UP) + + arrow = Arrow( + UP, DOWN, + rectangular_stem_width = 0.1, + tip_length = 0.45, + color = RED, + ) + arrow.next_to(basel_sum.target, DOWN, buff = MED_LARGE_BUFF) + + self.play( + MoveToTarget(basel_sum), + graph.next_to, basel_sum.target, UP, LARGE_BUFF, + circle.next_to, arrow, DOWN, MED_LARGE_BUFF, + ) + self.play(GrowArrow(arrow)) + self.wait() + + self.arrow = arrow + self.circle = circle def show_light(self): - pass + light = AmbientLight( + num_levels = 500, radius = 13, + opacity_function = lambda r : 1.0/(r+1), + ) + pi = self.basel_sum[-1][0] + pi.set_stroke(BLACK, 0.5) + light.move_to(pi) + self.play( + SwitchOn(light, run_time = 3), + Animation(self.arrow), + Animation(self.circle), + Animation(self.basel_sum), + ) + self.wait() ### @@ -766,9 +866,6 @@ class MathematicalWebOfConnections(PiCreatureScene): return VGroup(jerk, randy) - - - class FirstLighthouseScene(PiCreatureScene): def construct(self): From 226f0153bd41a8c1aa5e3f19ae53ed5ead92a3ab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:01:44 -0800 Subject: [PATCH 45/73] Enabled coloring of the point in a GrowFromPoint animation --- animation/transform.py | 6 ++++++ topics/numerals.py | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/animation/transform.py b/animation/transform.py index 433650df..aec862b0 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -101,9 +101,15 @@ class Swap(CyclicReplace): pass #Renaming, more understandable for two entries class GrowFromPoint(Transform): + CONFIG = { + "point_color" : None, + } def __init__(self, mobject, point, **kwargs): + digest_config(self, kwargs) target = mobject.copy() point_mob = Point(point) + if self.point_color: + point_mob.highlight(self.point_color) mobject.replace(point_mob) mobject.highlight(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs) diff --git a/topics/numerals.py b/topics/numerals.py index 1bcc61fe..1390081c 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -144,7 +144,6 @@ class ContinualChangingDecimal(ContinualAnimation): def update_mobject(self, dt): self.anim.update(self.internal_time) - From 14f531086ccabff34ac5e0abdc69447235740892 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:02:12 -0800 Subject: [PATCH 46/73] Finished FirstLighthouseScene --- active_projects/basel2.py | 314 +++++++++++++++++++++++++------------- topics/light.py | 12 +- 2 files changed, 207 insertions(+), 119 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 804b2896..adb4065f 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -865,161 +865,259 @@ class MathematicalWebOfConnections(PiCreatureScene): return VGroup(jerk, randy) - class FirstLighthouseScene(PiCreatureScene): - + CONFIG = { + "num_levels" : 100, + "opacity_function" : inverse_quadratic(1,2,1), + } def construct(self): - self.remove(self.get_primary_pi_creature()) + self.remove(self.pi_creature) self.show_lighthouses_on_number_line() - - + self.describe_brightness_of_each() + self.ask_about_rearrangements() def show_lighthouses_on_number_line(self): - - self.number_line = NumberLine( + number_line = self.number_line = NumberLine( x_min = 0, color = WHITE, number_at_center = 1.6, stroke_width = 1, - numbers_with_elongated_ticks = range(1,5), - numbers_to_show = range(1,5), + numbers_with_elongated_ticks = range(1,6), + numbers_to_show = range(1,6), unit_size = 2, tick_frequency = 0.2, line_to_number_buff = LARGE_BUFF, - label_direction = UP, + label_direction = DOWN, ) - self.number_line.label_direction = DOWN + number_line.add_numbers() + self.add(number_line) - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() + origin_point = number_line.number_to_point(0) - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + morty = self.pi_creature + morty.scale(0.75) + morty.flip() + right_pupil = morty.eyes[1] + morty.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + light_sources = VGroup() + for i in range(1,NUM_CONES+1): + light_source = LightSource( + opacity_function = self.opacity_function, + num_levels = self.num_levels, + radius = 12.0, + ) + point = number_line.number_to_point(i) + light_source.move_source_to(point) + light_sources.add(light_source) - light_indicator = LightIndicator(radius = INDICATOR_RADIUS, + lighthouses = self.lighthouses = VGroup(*[ + ls.lighthouse + for ls in light_sources[:NUM_VISIBLE_CONES+1] + ]) + + morty.save_state() + morty.scale(3) + morty.fade(1) + morty.center() + self.play(morty.restore) + self.play( + morty.change, "pondering", + LaggedStart( + FadeIn, lighthouses, + run_time = 1 + ) + ) + self.play(LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources + ]), + run_time = 5, + lag_ratio = 0.1, + rate_func = rush_into, + ), Animation(lighthouses)) + + self.light_sources = light_sources + + def describe_brightness_of_each(self): + number_line = self.number_line + morty = self.pi_creature + light_sources = self.light_sources + lighthouses = self.lighthouses + + light_indicator = LightIndicator( + radius = INDICATOR_RADIUS, opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR) + color = LIGHT_COLOR + ) light_indicator.reading.scale(0.8) + light_indicator.set_intensity(0) + intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) + opacities = intensities * light_indicator.opacity_for_unit_intensity - bubble = ThoughtBubble(direction = RIGHT, - width = 2.5, height = 3.5) - bubble.next_to(randy,LEFT+UP) + bubble = ThoughtBubble( + direction = RIGHT, + width = 2.5, height = 3.5 + ) + bubble.pin_to(morty) bubble.add_content(light_indicator) - self.play( - randy.change, "wave_2", - ShowCreation(bubble), - FadeIn(light_indicator) + euler_sum_above = TexMobject( + "1", "+", + "{1\over 4}", "+", + "{1\over 9}", "+", + "{1\over 16}", "+", + "{1\over 25}", "+", + "{1\over 36}" ) + euler_sum_terms = euler_sum_above[::2] + plusses = euler_sum_above[1::2] - light_sources = [] - - - euler_sum_above = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") - - for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above): + for i, term in enumerate(euler_sum_above): #horizontal alignment with tick marks - term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2) + term.next_to(number_line.number_to_point(0.5*i+1), UP , buff = 2) # vertical alignment with light indicator old_y = term.get_center()[1] new_y = light_indicator.get_center()[1] term.shift([0,new_y - old_y,0]) - - - - for i in range(1,NUM_CONES+1): - light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = NUM_LEVELS, - radius = 12.0, - ) - point = self.number_line.number_to_point(i) - light_source.move_source_to(point) - light_sources.append(light_source) - - - for ls in light_sources: - self.add_foreground_mobject(ls.lighthouse) - - light_indicator.set_intensity(0) - - intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) - opacities = intensities * light_indicator.opacity_for_unit_intensity - - self.remove_foreground_mobjects(light_indicator) - - - # slowly switch on visible light cones and increment indicator - for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func( - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light), - FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - # this last line *technically* fades in the last term, but it is off-screen - ChangeDecimalToValue(light_indicator.reading,intensities[i], - rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) - ) - - if i == 0: - # move a copy out of the thought bubble for comparison - light_indicator_copy = light_indicator.copy() - old_y = light_indicator_copy.get_center()[1] - new_y = self.number_line.get_center()[1] - self.play( - light_indicator_copy.shift,[0, new_y - old_y,0] - ) - - # quickly switch on off-screen light cones and increment indicator - for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ChangeDecimalToValue(light_indicator.reading,intensities[i-1], - rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1]) - ) - # show limit value in light indicator and an equals sign limit_reading = TexMobject("{\pi^2 \over 6}") limit_reading.move_to(light_indicator.reading) equals_sign = TexMobject("=") - equals_sign.next_to(randy, UP) + equals_sign.next_to(morty, UP) old_y = equals_sign.get_center()[1] new_y = euler_sum_above.get_center()[1] equals_sign.shift([0,new_y - old_y,0]) + #Triangle of light to morty's eye + ls0 = light_sources[0] + ls0.save_state() + eye = morty.eyes[1] + triangle = Polygon( + number_line.number_to_point(1), + eye.get_top(), eye.get_bottom(), + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 1, + ) + triangle_anim = GrowFromPoint( + triangle, triangle.get_right(), + point_color = YELLOW + ) + + # First lighthouse has apparent reading + self.play(LaggedStart(FadeOut, light_sources[1:])) + self.wait() + self.play( + triangle_anim, + # Animation(eye) + ) + for x in range(4): + triangle_copy = triangle.copy() + self.play( + FadeOut(triangle.copy()), + triangle_anim, + ) + self.play( + FadeOut(triangle), + ShowCreation(bubble), + FadeIn(light_indicator), + ) + self.play( + UpdateLightIndicator(light_indicator, 1), + FadeIn(euler_sum_terms[0]) + ) + self.wait(2) + + # Second lighthouse is 1/4, third is 1/9, etc. + for i in range(1, 5): + self.play( + ApplyMethod( + ls0.move_to, light_sources[i], + run_time = 3 + ), + UpdateLightIndicator(light_indicator, 1./(i+1)**2, run_time = 3), + FadeIn( + euler_sum_terms[i], + run_time = 3, + rate_func = squish_rate_func(smooth, 0.5, 1) + ), + ) + self.wait() + self.play( + ApplyMethod(ls0.restore), + UpdateLightIndicator(light_indicator, 1) + ) + + #Switch them all on + self.play( + LaggedStart(FadeIn, lighthouses[1:]), + morty.change, "hooray", + ) + self.play( + LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources[1:] + ]), + run_time = 5, + rate_func = rush_into, + ), + Animation(lighthouses), + Animation(euler_sum_above), + Write(plusses), + UpdateLightIndicator(light_indicator, np.pi**2/6, run_time = 5), + morty.change, "happy", + ) + self.wait() self.play( FadeOut(light_indicator.reading), FadeIn(limit_reading), - FadeIn(equals_sign), + morty.change, "confused", + ) + self.play(Write(equals_sign)) + self.wait() + + def ask_about_rearrangements(self): + light_sources = self.light_sources + origin = self.number_line.number_to_point(0) + morty = self.pi_creature + + self.play( + LaggedStart( + Rotate, light_sources, + lambda m : (m, (2*random.random()-1)*90*DEGREES), + about_point = origin, + rate_func = lambda t : wiggle(t, 4), + run_time = 10, + lag_ratio = 0.9, + ), + morty.change, "pondering", ) - +class RearrangeWords(Scene): + def construct(self): + words = TextMobject("Rearrange without changing \\\\ the apparent brightness") + self.play(Write(words)) + self.wait(5) +class ThatJustSeemsUseless(TeacherStudentsScene): + def construct(self): + self.student_says( + "How would \\\\ that help?", + target_mode = "sassy", + student_index = 2, + bubble_kwargs = {"direction" : LEFT}, + ) + self.play( + self.teacher.change, "guilty", + self.get_student_changes(*3*['sassy']) + ) self.wait() class SingleLighthouseScene(PiCreatureScene): diff --git a/topics/light.py b/topics/light.py index 3bbcd9e5..4de886b2 100644 --- a/topics/light.py +++ b/topics/light.py @@ -128,7 +128,6 @@ class LightSource(VMobject): self.camera_mob = new_cam_mob self.spotlight.camera_mob = new_cam_mob - def set_screen(self, new_screen): if self.has_screen(): self.spotlight.screen = new_screen @@ -158,9 +157,6 @@ class LightSource(VMobject): # in any case self.screen = new_screen - - - def move_source_to(self,point): apoint = np.array(point) v = apoint - self.get_source_point() @@ -190,7 +186,6 @@ class LightSource(VMobject): self.spotlight.update_sectors() self.update_shadow() - def update_lighthouse(self): new_lh = Lighthouse() new_lh.move_to(ORIGIN) @@ -198,7 +193,6 @@ class LightSource(VMobject): new_lh.shift(self.get_source_point()) self.lighthouse.submobjects = new_lh.submobjects - def update_ambient(self): new_ambient_light = AmbientLight( source_point = VectorizedPoint(location = ORIGIN), @@ -212,12 +206,9 @@ class LightSource(VMobject): new_ambient_light.move_source_to(self.get_source_point()) self.ambient_light.submobjects = new_ambient_light.submobjects - - def get_source_point(self): return self.source_point.get_location() - def rotation_matrix(self): if self.camera_mob == None: @@ -242,7 +233,6 @@ class LightSource(VMobject): R = np.dot(R2, R1) return R - def update_shadow(self): point = self.get_source_point() @@ -370,7 +360,7 @@ class AmbientLight(VMobject): "opacity_function" : lambda r : 1.0/(r+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, - "num_levels" : 10, + "num_levels" : NUM_LEVELS, "radius" : 5.0 } From ec610a9152c27ce242bab666dbb74700ee9faac4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 19:07:57 -0800 Subject: [PATCH 47/73] Really dumb copy-pasting of extract_scene.py and scene.py from master to get what I want. Not sure what's going on here. --- extract_scene.py | 6 +++--- scene/scene.py | 11 ++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/extract_scene.py b/extract_scene.py index 0d21e8a7..bbc17b91 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -18,7 +18,6 @@ from camera import Camera HELP_MESSAGE = """ Usage: python extract_scene.py [] - -p preview in low quality -s show and save picture of last frame -w write result to file [this is default if nothing else is stated] @@ -35,7 +34,6 @@ SCENE_NOT_FOUND_MESSAGE = """ CHOOSE_NUMBER_MESSAGE = """ Choose number corresponding to desired scene/arguments. (Use comma separated list for multiple entries) - Choice(s): """ INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit" @@ -95,6 +93,7 @@ def get_configuration(): "save_pngs" : args.save_pngs, #If -t is passed in (for transparent), this will be RGBA "saved_image_mode": "RGBA" if args.transparent else "RGB", + "movie_file_extension" : ".mov" if args.transparent else ".mp4", "quiet" : args.quiet or args.write_all, "ignore_waits" : args.preview, "write_all" : args.write_all, @@ -237,6 +236,7 @@ def main(): "write_to_movie", "output_directory", "save_pngs", + "movie_file_extension", "start_at_animation_number", "end_at_animation_number", ] @@ -260,4 +260,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/scene/scene.py b/scene/scene.py index 6dc31d5f..5168db25 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -82,7 +82,7 @@ class Scene(Container): def setup(self): """ This is meant to be implement by any scenes which - are commonly subclassed, and have some common setup + are comonly subclassed, and have some common setup involved before the construct method is called. """ pass @@ -366,7 +366,6 @@ class Scene(Container): Each arg can either be an animation, or a mobject method followed by that methods arguments (and potentially follow by a dict of kwargs for that method). - This animation list is built by going through the args list, and each animation is simply added, but when a mobject method s hit, a MoveToTarget animation is built using the args that @@ -387,7 +386,7 @@ class Scene(Container): animations.pop() #method should already have target then. else: - mobject.target = mobject.deepcopy() + mobject.generate_target() # if len(state["method_args"]) > 0 and isinstance(state["method_args"][-1], dict): method_kwargs = state["method_args"].pop() @@ -578,17 +577,12 @@ class Scene(Container): FFMPEG_BIN, '-y', # overwrite output file if it exists '-f', 'rawvideo', - '-vcodec','rawvideo', '-s', '%dx%d'%(width, height), # size of one frame '-pix_fmt', 'rgba', '-r', str(fps), # frames per second '-i', '-', # The imput comes from a pipe '-an', # Tells FFMPEG not to expect any audio - '-vcodec', 'mpeg', - '-c:v', 'libx264', - '-pix_fmt', 'yuv420p', '-loglevel', 'error', - temp_file_path, ] if self.movie_file_extension == ".mov": # This is if the background of the exported video @@ -624,4 +618,3 @@ class EndSceneEarlyException(Exception): - From c2b3e3f3e03eccd12d4c9a4a0ef1aa23f5a3b22e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:34:42 -0800 Subject: [PATCH 48/73] Changed mobject of ScreenTracker --- topics/light.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/topics/light.py b/topics/light.py index 4de886b2..23721ef1 100644 --- a/topics/light.py +++ b/topics/light.py @@ -314,8 +314,9 @@ class SwitchOn(LaggedStart): def __init__(self, light, **kwargs): if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): raise Exception("Only AmbientLights and Spotlights can be switched on") - LaggedStart.__init__(self, - FadeIn, light, **kwargs) + LaggedStart.__init__( + self, FadeIn, light, **kwargs + ) class SwitchOff(LaggedStart): @@ -333,8 +334,6 @@ class SwitchOff(LaggedStart): light.submobjects = light.submobjects[::-1] - - class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -344,7 +343,6 @@ class Lighthouse(SVGMobject): def move_to(self,point): self.next_to(point, DOWN, buff = 0) - class AmbientLight(VMobject): # Parameters are: @@ -421,19 +419,6 @@ class AmbientLight(VMobject): submob.set_fill(opacity = new_submob_alpha) - - - - - - - - - - - - - class Spotlight(VMobject): CONFIG = { @@ -590,7 +575,6 @@ class Spotlight(VMobject): - def dimming(self,new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha @@ -623,7 +607,27 @@ class Spotlight(VMobject): class ScreenTracker(ContinualAnimation): + def __init__(self, light_source, **kwargs): + self.light_source = light_source + dummy_mob = Mobject() + ContinualAnimation.__init__(self, dummy_mob, **kwargs) def update_mobject(self, dt): - self.mobject.update() + self.light_source.update() + + + + + + + + + + + + + + + + From 3afdce521defa6db1cc16b4009aa99e5f28ae772 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:35:13 -0800 Subject: [PATCH 49/73] Up to EarthScene of basel2 --- active_projects/basel2.py | 450 +++++++++++++++++++++----------------- 1 file changed, 250 insertions(+), 200 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index adb4065f..225e4c42 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -92,8 +92,10 @@ class AngleUpdater(ContinualAnimation): new_arc.generate_points() new_arc.move_arc_center_to(self.spotlight.get_source_point()) self.angle_arc.points = new_arc.points - self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, - at_start = True, at_end = True) + self.angle_arc.add_tip( + tip_length = ARC_TIP_LENGTH, + at_start = True, at_end = True + ) class LightIndicator(Mobject): CONFIG = { @@ -939,6 +941,7 @@ class FirstLighthouseScene(PiCreatureScene): lag_ratio = 0.1, rate_func = rush_into, ), Animation(lighthouses)) + self.wait() self.light_sources = light_sources @@ -1120,55 +1123,47 @@ class ThatJustSeemsUseless(TeacherStudentsScene): ) self.wait() -class SingleLighthouseScene(PiCreatureScene): - +class AskAboutBrightness(TeacherStudentsScene): def construct(self): + self.student_says( + "What do you mean \\\\ by ``brightness''?" + ) + self.play(self.teacher.change, "happy") + self.wait(3) +class IntroduceScreen(Scene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + "num_rays" : 250, + "min_ray_angle" : 0, + "max_ray_angle" : TAU, + } + def construct(self): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates self.rotate_screen() - self.morph_lighthouse_into_sun() - + # self.morph_lighthouse_into_sun() def setup_elements(self): - - self.remove(self.get_primary_pi_creature()) - SCREEN_SIZE = 3.0 - DISTANCE_FROM_LIGHTHOUSE = 10.0 - source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] - observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] - + source_point = self.source_point = 2.5*LEFT + observer_point = 3.5*RIGHT # Light source - self.light_source = LightSource( + light_source = self.light_source = LightSource( opacity_function = inverse_quadratic(1,2,1), - num_levels = NUM_LEVELS, - radius = 10, - max_opacity_ambient = AMBIENT_FULL + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, ) - self.light_source.move_source_to(source_point) - - - # Pi Creature - - morty = self.get_primary_pi_creature() - morty.scale(0.5) - morty.move_to(observer_point) - morty.shift(2*OUT) - self.add_foreground_mobject(morty) - - self.add(self.light_source.lighthouse) - - self.play( - SwitchOn(self.light_source.ambient_light) - ) + light_source.move_source_to(source_point) # Screen - self.screen = Rectangle( - width = 0.1, + screen = self.screen = Rectangle( + width = 0.05, height = 2, mark_paths_closed = True, fill_color = WHITE, @@ -1176,70 +1171,105 @@ class SingleLighthouseScene(PiCreatureScene): stroke_width = 0.0 ) - self.screen.rotate(-TAU/6) - self.screen.next_to(morty,LEFT) + screen.next_to(observer_point, LEFT) - self.light_source.set_screen(self.screen) - - # Animations - - self.play(FadeIn(self.screen)) - - self.light_source.set_max_opacity_spotlight(0.001) - self.add(self.light_source.spotlight) - - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) - - self.wait() - - - # just calling .dim_ambient via ApplyMethod does not work, why? - dimmed_ambient_light = self.light_source.ambient_light.deepcopy() - dimmed_ambient_light.dimming(AMBIENT_DIMMED) - - self.play( - Transform(self.light_source.ambient_light,dimmed_ambient_light), - self.light_source.set_max_opacity_spotlight,1.0, - FadeIn(self.light_source.shadow) + screen_label = TextMobject("Screen") + screen_label.next_to(screen, UP+LEFT) + screen_arrow = Arrow( + screen_label.get_bottom(), + screen.get_center(), ) - self.add_foreground_mobject(morty) + # Pi creature + morty = Mortimer() + morty.shift(screen.get_center() - morty.eyes.get_left()) + morty.look_at(source_point) + # Camera + camera = SVGMobject(file_name = "camera") + camera.rotate(TAU/4) + camera.scale_to_fit_height(1.5) + camera.move_to(morty.eyes, LEFT) + # Animations + light_source.set_max_opacity_spotlight(0.001) + screen_tracker = self.screen_tracker = ScreenTracker(light_source) + self.add(light_source.lighthouse) + self.play(SwitchOn(light_source.ambient_light)) + self.play( + Write(screen_label), + GrowArrow(screen_arrow), + FadeIn(screen) + ) + self.wait() + self.play(*map(FadeOut, [screen_label, screen_arrow])) + screen.save_state() + self.play( + FadeIn(morty), + screen.match_height, morty.eyes, + screen.next_to, morty.eyes, LEFT, SMALL_BUFF + ) + self.play(Blink(morty)) + self.play( + FadeOut(morty), + FadeIn(camera), + screen.scale, 2, {"about_edge" : UP}, + ) + self.wait() + self.play( + FadeOut(camera), + screen.restore, + ) + + light_source.set_screen(screen) + light_source.spotlight.opacity_function = lambda r : 0.2/(r+1) + screen_tracker.update(0) + + ## Ask about proportion + self.add_foreground_mobjects(light_source.shadow, screen) + self.shoot_rays() + + ## + self.play(SwitchOn(light_source.spotlight)) def setup_angle(self): self.wait() - - pointing_screen_at_source = Rotate(self.screen,TAU/6) - self.play(pointing_screen_at_source) - # angle msmt (arc) - arc_angle = self.light_source.spotlight.opening_angle() # draw arc arrows to show the opening angle - self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), - angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) + self.angle_arc = Arc( + radius = 3, + start_angle = self.light_source.spotlight.start_angle(), + angle = self.light_source.spotlight.opening_angle(), + tip_length = ARC_TIP_LENGTH + ) #angle_arc.add_tip(at_start = True, at_end = True) self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) # angle msmt (decimal number) - self.angle_indicator = DecimalNumber(arc_angle / DEGREES, + self.angle_indicator = DecimalNumber( + arc_angle / DEGREES, num_decimal_points = 0, - unit = "^\\circ") - self.angle_indicator.next_to(self.angle_arc,RIGHT) + unit = "^\\circ" + ) + self.angle_indicator.next_to(self.angle_arc, RIGHT) angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES - ca1 = ContinualChangingDecimal(self.angle_indicator,angle_update_func) - self.add(ca1) + angle_tracker = ContinualChangingDecimal( + self.angle_indicator, angle_update_func + ) + self.add(angle_tracker) - ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight) - self.add(ca2) + arc_tracker = AngleUpdater( + self.angle_arc, + self.light_source.spotlight + ) + self.add(arc_tracker) self.play( ShowCreation(self.angle_arc), @@ -1249,162 +1279,182 @@ class SingleLighthouseScene(PiCreatureScene): self.wait() def rotate_screen(self): - - - - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, TAU/4)) - -### The following is supposed to morph the scene into the Earth scene, -### but it doesn't work - - - def morph_lighthouse_into_sun(self): - - - - sun_position = [-100,0,0] - - - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) + self.add( + ContinualUpdateFromFunc( + self.light_source, + lambda m : m.update() + ), ) + def rotate_screen(angle): + self.play( + Rotate(self.light_source.spotlight.screen, angle), + Animation(self.angle_indicator), + Animation(self.angle_arc), + run_time = 2, + ) + for angle in TAU/8, -TAU/4, TAU/8, TAU/6: + rotate_screen(angle) + self.wait() + self.shoot_rays() + rotate_screen(-TAU/6) - self.sun = self.light_source.deepcopy() + ## - #self.sun.num_levels = NUM_LEVELS, - #self.sun.set_radius(150) - #self.sun.set_max_opacity_ambient(AMBIENT_FULL) - + def shoot_rays(self, show_creation_kwargs = None): + if show_creation_kwargs is None: + show_creation_kwargs = {} + source_point = self.source_point + screen = self.screen + # Rays + step_size = (self.max_ray_angle - self.min_ray_angle)/self.num_rays + rays = VGroup(*[ + Line(ORIGIN, self.radius*rotate_vector(RIGHT, angle)) + for angle in np.arange( + self.min_ray_angle, + self.max_ray_angle, + step_size + ) + ]) + rays.shift(source_point) + rays.set_stroke(YELLOW, 1) + max_angle = np.max([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + min_angle = np.min([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + for ray in rays: + if min_angle <= ray.get_angle() <= max_angle: + ray.target_color = GREEN + else: + ray.target_color = RED - self.sun.spotlight.change_opacity_function(lambda r: 0.5) - self.sun.set_radius(150) - self.sun.move_source_to(sun_position) - - # self.sun.update() - - # self.add(self.sun) - # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) - - #print self.sun.spotlight.get_source_point() - - self.play( - #self.light_source.spotlight.move_source_to,sun_position, - Transform(self.light_source,self.sun) - ) - - #self.add(ScreenTracker(self.sun)) - + self.play(*[ + ShowCreation(ray, run_time = 3, **show_creation_kwargs) + for ray in rays + ]) + self.play(*[ + ApplyMethod(ray.highlight, ray.target_color) + for ray in rays + ]) self.wait() + self.play(FadeOut(rays)) -class EarthScene(Scene): - +class EarthScene(IntroduceScreen): + CONFIG = { + "screen_height" : 0.5, + "screen_thickness" : 0, + "radius" : 100 + SPACE_WIDTH, + "source_point" : 100*LEFT, + "min_ray_angle" : -1.65*DEGREES, + "max_ray_angle" : 1.65*DEGREES, + "num_rays" : 100, + } def construct(self): + # Earth + earth_radius = 3 + earth = ImageMobject("earth") + earth_circle = Circle(radius = earth_radius) + earth_circle.to_edge(RIGHT) + earth.replace(earth_circle) - SCREEN_THICKNESS = 10 + black_rect = Rectangle( + height = 2*SPACE_HEIGHT, + width = earth_radius + LARGE_BUFF, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1 + ) + black_rect.move_to(earth.get_center(), LEFT) - self.screen_height = 2.0 - self.brightness_rect_height = 1.0 + self.add_foreground_mobjects(black_rect, earth) # screen - self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) - self.screen.set_points_as_corners([ - [3,-self.screen_height/2,0], - [3,self.screen_height/2,0] - ]) + screen = self.screen = Line( + self.screen_height*UP, ORIGIN, + stroke_color = WHITE, + stroke_width = self.screen_thickness, + ) + screen.move_to(earth.get_left()) + screen.generate_target() + screen.target.rotate( + -60*DEGREES, about_point = earth_circle.get_center() + ) - # Earth - - earth_center_x = 2 - earth_center = [earth_center_x,0,0] - earth_radius = 3 - earth = Circle(radius = earth_radius) - earth.move_to(earth_center) - #self.remove(self.screen_tracker) - - theta0 = 70 * DEGREES - dtheta = 10 * DEGREES - theta1 = theta0 + dtheta - theta = (theta0 + theta1)/2 - - earth.add(self.screen) - - # Morty - - morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) - self.add_foreground_mobject(morty) + equator_arrow = Vector( + DOWN+2*RIGHT, color = WHITE, + use_rectangular_stem = False, + ) + equator_arrow.next_to(screen.get_center(), UP+LEFT, SMALL_BUFF) + pole_arrow = Vector( + UP+3*RIGHT, + color = WHITE, + use_rectangular_stem = False, + path_arc = -60*DEGREES, + ) + pole_arrow.shift( + screen.target.get_center()+SMALL_BUFF*LEFT - \ + pole_arrow.get_end() + ) + for arrow in equator_arrow, pole_arrow: + arrow.pointwise_become_partial(arrow, 0, 0.95) + equator_words = TextMobject("Some", "unit of area") + pole_words = TextMobject("The same\\\\", "unit of area") + pole_words.next_to(pole_arrow.get_start(), DOWN) + equator_words.next_to(equator_arrow.get_start(), UP) # Light source (far-away Sun) - sun_position = [-100,0,0] - - self.sun = LightSource( + sun = sun = LightSource( opacity_function = lambda r : 0.5, max_opacity_ambient = 0, max_opacity_spotlight = 0.5, - num_levels = NUM_LEVELS, - radius = 150, - screen = self.screen + num_levels = 5, + radius = self.radius, + screen = screen ) + sun.move_source_to(self.source_point) + sunlight = sun.spotlight + sunlight.opacity_function = lambda r : 5./(r+1) - self.sun.move_source_to(sun_position) - + screen_tracker = ScreenTracker(sun) # Add elements to scene - self.add(self.sun,self.screen) - self.bring_to_back(self.sun.shadow) - screen_tracker = ScreenTracker(self.sun) - + self.add(screen) + self.play(SwitchOn( + sunlight, + rate_func = squish_rate_func(smooth, 0.7, 0.8), + )) self.add(screen_tracker) - - self.wait() - - self.play(FadeIn(earth)) - self.bring_to_back(earth) - - # move screen onto Earth - screen_on_earth = self.screen.deepcopy() - screen_on_earth.rotate(-theta) - screen_on_earth.scale(0.3) - screen_on_earth.move_to(np.array([ - earth_center_x - earth_radius * np.cos(theta), - earth_radius * np.sin(theta), - 0])) - - polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) - self.play( - Transform(self.screen, screen_on_earth), - Transform(morty,polar_morty) + Write(equator_words), + GrowArrow(equator_arrow) ) - + self.add_foreground_mobjects(equator_words, equator_arrow) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + }) self.wait() - - - tropical_morty = polar_morty.copy() - tropical_morty.move_to(np.array([0,0,0])) - morty.target = tropical_morty - - # move screen to equator - + # Point to patch self.play( - Rotate(earth, theta0 + dtheta/2,run_time = 3), - MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), + MoveToTarget(screen), + Transform(equator_arrow, pole_arrow), + Transform( + equator_words, pole_words, + rate_func = squish_rate_func(smooth, 0.6, 1), + ), + Animation(sunlight), + run_time = 3, ) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + }) + self.wait() class ScreenShapingScene(ThreeDScene): From c521a021fe768b888a4cffff3e93fc1e9e609a85 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 26 Feb 2018 23:35:40 -0800 Subject: [PATCH 50/73] Changed order of args in paths searched for svg files --- mobject/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 8d0fbbc8..8a3fa899 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -37,9 +37,9 @@ class SVGMobject(VMobject): if self.file_name is None: raise Exception("Must specify file for SVGMobject") possible_paths = [ - self.file_name, os.path.join(SVG_IMAGE_DIR, self.file_name), os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"), + self.file_name, ] for path in possible_paths: if os.path.exists(path): From 342d6eaa8272045b084cd1acf08a94b7d25d4eff Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 27 Feb 2018 18:48:35 +0100 Subject: [PATCH 51/73] resolved conflicts --- camera/camera.py | 7 ------- helpers.py | 7 +------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 332bf17a..f599eb09 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -438,12 +438,6 @@ class Camera(object): ] out_a = src_a + dst_a*(1.0-src_a) -<<<<<<< HEAD - out_rgb = fdiv( - src_rgb*src_a[..., None] + \ - dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]), - out_a[..., None] -======= # When the output alpha is 0 for full transparency, # we have a choice over what RGB value to use in our @@ -453,7 +447,6 @@ class Camera(object): dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]), out_a[..., None], zero_over_zero_value = 0 ->>>>>>> master ) self.pixel_array[..., :3] = out_rgb*self.rgb_max_val diff --git a/helpers.py b/helpers.py index 2d73bc58..aff9a365 100644 --- a/helpers.py +++ b/helpers.py @@ -693,11 +693,6 @@ class DictAsObject(object): def __init__(self, dict): self.__dict__ = dict -# Just to have a less heavyweight name for this extremely common operation -<<<<<<< HEAD -def fdiv(a, b): - return np.true_divide(a,b) -======= # # We may wish to have more fine-grained control over division by zero behavior # in the future (separate specifiable values for 0/0 and x/0 with x != 0), @@ -711,7 +706,7 @@ def fdiv(a, b, zero_over_zero_value = None): where = True return np.true_divide(a, b, out = out, where = where) ->>>>>>> master + def add_extension_if_not_present(file_name, extension): # This could conceivably be smarter about handling existing differing extensions From 80b35918cff5328886cea64fca86425ce007c350 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 27 Feb 2018 18:48:55 +0100 Subject: [PATCH 52/73] tweaking PondScene --- active_projects/basel.py | 883 ++++++++++++++++++++++++++++++++++++++- topics/light.py | 4 +- 2 files changed, 878 insertions(+), 9 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 1854360a..4bc48b4f 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -103,7 +103,7 @@ class AngleUpdater(ContinualAnimation): -class LightIndicator(Mobject): +class LightIndicator(VMobject): CONFIG = { "radius": 0.5, "intensity": 0, @@ -2303,7 +2303,7 @@ class IPTScene2(Scene): -class PondScene(ThreeDScene): +class InscribedAngleScene(ThreeDScene): @@ -2329,11 +2329,14 @@ class PondScene(ThreeDScene): self.cumulated_zoom_factor = 1 - #self.force_skipping() + self.force_skipping() def zoom_out_scene(factor): + if self.mobjects.contains(self.ls0_dot): + self.play(FadeOut(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() @@ -2349,6 +2352,9 @@ class PondScene(ThreeDScene): self.cumulated_zoom_factor *= factor + if not self.mobjects.contains(self.ls0_dot): + self.play(FadeIn(self.ls0_dot)) + def shift_scene(v): self.play( @@ -2368,7 +2374,7 @@ class PondScene(ThreeDScene): self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot, ls0_dot) + self.unzoomable_mobs.add(self.obs_dot) #, ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2752,6 +2758,852 @@ class PondScene(ThreeDScene): + self.revert_to_original_skipping_status() + + + ANGLE_COLOR1 = RED + ANGLE_COLOR2 = GREEN + + + for mob in self.mobjects: + mob.fade(1.0) + + for hyp in self.hypotenuses: + hyp.set_stroke(width = 0) + for alt in self.altitudes: + alt.set_stroke(width = 0) + for leg in self.legs: + leg.set_stroke(width = 0) + self.inner_lake.set_stroke(width = 0) + self.outer_lake.set_stroke(width = 0) + + self.wait() + + inner_lake_center = self.inner_lake.get_center() + inner_lake_radius = self.lake_radius * 0.25 + inner_ls = VGroup() + for i in range(4): + theta = -TAU/4 + (i+0.5) * TAU/4 + point = inner_lake_center + inner_lake_radius * np.array([np.cos(theta), np.sin(theta),0]) + dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3) + inner_ls.add(dot) + + self.add(inner_ls) + + inner_ls1 = inner_ls.submobjects[0] + inner_ls2 = inner_ls.submobjects[1] + inner_ls1_center = inner_ls1.get_center() + inner_ls2_center = inner_ls2.get_center() + + outer_lake_center = self.outer_lake.get_center() + outer_lake_radius = self.lake_radius * 0.5 + outer_ls = VGroup() + for i in range(8): + theta = -TAU/4 + (i+0.5) * TAU/8 + point = outer_lake_center + outer_lake_radius * np.array([np.cos(theta), np.sin(theta),0]) + dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3) + outer_ls.add(dot) + + self.add(outer_ls) + + outer_ls1 = outer_ls.submobjects[0] + outer_ls2 = outer_ls.submobjects[1] + outer_ls1_center = outer_ls1.get_center() + outer_ls2_center = outer_ls2.get_center() + + self.wait() + + arc_radius = 2.0 + + line1 = Line(inner_lake_center, inner_ls1_center, color = WHITE) + line2 = Line(inner_lake_center, inner_ls2_center, color = WHITE) + + + #arc_point1 = interpolate(inner_lake_center, inner_ls1_center, 0.2) + #arc_point2 = interpolate(inner_lake_center, inner_ls2_center, 0.2) + #inner_angle_arc = ArcBetweenPoints(arc_point1, arc_point2, angle = TAU/4) + inner_angle_arc = Arc(angle = TAU/4, start_angle = -TAU/8, radius = arc_radius, + stroke_color = ANGLE_COLOR1) + inner_angle_arc.move_arc_center_to(inner_lake_center) + + inner_label = TexMobject("\\theta", fill_color = ANGLE_COLOR1).scale(3).next_to(inner_angle_arc, LEFT, buff = -0.1) + + self.play( + ShowCreation(line1), + ShowCreation(line2), + ) + self.play( + ShowCreation(inner_angle_arc), + FadeIn(inner_label) + ) + + + + + self.wait() + + + + line3 = Line(outer_lake_center, inner_ls1_center, color = WHITE) + line4 = Line(outer_lake_center, inner_ls2_center, color = WHITE) + outer_angle_arc = Arc(angle = TAU/8, start_angle = -3*TAU/16, radius = arc_radius, + stroke_color = ANGLE_COLOR2) + outer_angle_arc.move_arc_center_to(outer_lake_center) + + outer_label = TexMobject("{\\theta \over 2}", color = ANGLE_COLOR2).scale(2.5).move_to(outer_angle_arc) + outer_label.shift([-2,-1,0]) + + self.play( + ShowCreation(line3), + ShowCreation(line4), + ) + self.play( + ShowCreation(outer_angle_arc), + FadeIn(outer_label) + ) + + + + self.wait() + + + + line5 = Line(outer_lake_center, outer_ls1_center, color = WHITE) + line6 = Line(outer_lake_center, outer_ls2_center, color = WHITE) + + self.play( + ShowCreation(line5), + ShowCreation(line6) + ) + + + self.wait() + + self.play( + FadeOut(line1), + FadeOut(line2), + FadeOut(line3), + FadeOut(line4), + FadeOut(line5), + FadeOut(line6), + FadeOut(inner_angle_arc), + FadeOut(outer_angle_arc), + FadeOut(inner_label), + FadeOut(outer_label), + ) + + + self.wait() + + inner_lines = VGroup() + inner_arcs = VGroup() + + for i in range(-2,2): + + theta = -TAU/4 + (i+0.5)*TAU/4 + ls_point = inner_lake_center + inner_lake_radius * np.array([ + np.cos(theta), np.sin(theta),0]) + line = Line(inner_lake_center, ls_point, color = WHITE) + inner_lines.add(line) + + arc = Arc(angle = TAU/4, start_angle = theta, radius = arc_radius, + stroke_color = ANGLE_COLOR1) + arc.move_arc_center_to(inner_lake_center) + inner_arcs.add(arc) + + if i == 1: + arc.set_stroke(width = 0) + + for line in inner_lines.submobjects: + self.play( + ShowCreation(line), + ) + self.add_foreground_mobject(inner_lines) + for arc in inner_arcs.submobjects: + self.play( + ShowCreation(arc) + ) + + self.wait() + + outer_lines = VGroup() + outer_arcs = VGroup() + + for i in range(-2,2): + + theta = -TAU/4 + (i+0.5)*TAU/4 + + ls_point = inner_lake_center + inner_lake_radius * np.array([ + np.cos(theta), np.sin(theta),0]) + line = Line(outer_lake_center, ls_point, color = WHITE) + outer_lines.add(line) + + theta = -TAU/4 + (i+0.5)*TAU/8 + arc = Arc(angle = TAU/8, start_angle = theta, radius = arc_radius, + stroke_color = ANGLE_COLOR2) + arc.move_arc_center_to(outer_lake_center) + outer_arcs.add(arc) + + if i == 1: + arc.set_stroke(width = 0) + + + for line in outer_lines.submobjects: + self.play( + ShowCreation(line), + ) + self.add_foreground_mobject(outer_lines) + for arc in outer_arcs.submobjects: + self.play( + ShowCreation(arc) + ) + + self.wait() + + self.play( + FadeOut(inner_lines), + FadeOut(inner_arcs) + ) + + + outer_lines2 = VGroup() + + for i in range(-2,2): + + theta = -TAU/4 + (i+0.5)*TAU/8 + ls_point = outer_lake_center + outer_lake_radius * np.array([ + np.cos(theta), np.sin(theta),0]) + line = Line(outer_lake_center, ls_point, color = WHITE) + outer_lines2.add(line) + + self.play( + ShowCreation(outer_lines2), + ) + + self.wait() + + outer_lines3 = outer_lines2.copy().rotate(TAU/2, about_point = outer_lake_center) + outer_arcs3 = outer_arcs.copy().rotate(TAU/2, about_point = outer_lake_center) + + self.play( + ShowCreation(outer_lines3), + ) + self.add_foreground_mobject(outer_lines3) + for arc in outer_arcs3.submobjects: + self.play( + ShowCreation(arc) + ) + + last_arc = outer_arcs3.submobjects[0].copy() + last_arc.rotate(-TAU/8, about_point = outer_lake_center) + last_arc2 = last_arc.copy() + last_arc2.rotate(TAU/2, about_point = outer_lake_center) + + self.play( + ShowCreation(last_arc), + ShowCreation(last_arc2), + ) + + self.wait() + + self.play( + FadeOut(outer_lines2), + FadeOut(outer_lines3), + FadeOut(outer_arcs), + FadeOut(outer_arcs3), + FadeOut(last_arc), + FadeOut(last_arc2), + ) + + self.play( + FadeOut(inner_ls), + FadeOut(outer_ls), + ) + + + self.wait() + + +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.2 + 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 = 5 + LIGHT_CUTOFF = 1 + + RIGHT_ANGLE_SIZE = 0.3 + + + self.cumulated_zoom_factor = 1 + + #self.force_skipping() + + + def right_angle(pointA, pointB, pointC): + + v1 = pointA - pointB + v1 = RIGHT_ANGLE_SIZE * v1/np.linalg.norm(v1) + v2 = pointC - pointB + v2 = RIGHT_ANGLE_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): + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + + + baseline = VMobject() + baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary + + self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot, ls0_dot) + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + 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().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) + ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) + + self.add(lake0,morty,self.obs_dot,ls0_dot, ls0.lighthouse) + self.add_foreground_mobject(morty) + self.add_foreground_mobject(self.obs_dot) + self.add_foreground_mobject(ls0_dot) + self.wait() + + + # shore arcs + arc_left = Arc(-TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_left.next_to(arc_left,LEFT) + + + arc_right = Arc(TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_right.next_to(arc_right,RIGHT) + + self.play( + ShowCreation(arc_left), + Write(one_left), + ShowCreation(arc_right), + Write(one_right), + ) + + + self.play( + SwitchOn(ls0.ambient_light), + lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, + ) + + self.play(FadeIn(indicator)) + self.add_foreground_mobject(indicator) + + self.play( + indicator.set_intensity,0.5 + ) + + diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.05) + diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.95) + + # 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( + ShowCreation(diameter), + Write(diameter_text), + #FadeOut(self.obs_dot), + FadeOut(ls0_dot) + ) + + indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) + + self.play( + FadeIn(indicator_reading) + ) + self.add_foreground_mobject(indicator_reading) + + # replace d with its value + new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text.color = LAKE_COLOR + new_diameter_text.move_to(diameter_text) + self.play( + Transform(diameter_text,new_diameter_text) + ) + + # insert into indicator reading + new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) + new_reading.move_to(indicator) + + self.play( + Transform(indicator_reading,new_reading) + ) + + self.wait() + + self.play( + FadeOut(one_left), + FadeOut(one_right), + FadeOut(diameter_text), + FadeOut(arc_left), + FadeOut(arc_right) + ) + + + + + def indicator_wiggle(): + INDICATOR_WIGGLE_FACTOR = 1.3 + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + + def angle_for_index(i,step): + return -TAU/4 + TAU/2**step * (i + 0.5) + + + def position_for_index(i, step, scaled_down = False): + + theta = angle_for_index(i,step) + radial_vector = np.array([np.cos(theta),np.sin(theta),0]) + position = self.lake_center + self.lake_radius * radial_vector + + if scaled_down: + return position.scale_about_point(self.obs_dot.get_center(),0.5) + else: + return position + + + def split_light_source(i, step, show_steps = True, run_time = 1): + + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) + + 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() + 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: + self.play( + ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), + ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + + + + def construction_step(n, 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(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 = [] + + 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 + ) + + 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 + triangle2 = triangle( + B, H, C + ) + + right_angle2 = right_angle( + B, H, C + ) + + self.play( + FadeIn(triangle2), + FadeIn(right_angle2) + ) + + self.wait() + + self.play( + FadeOut(triangle2), + FadeOut(right_angle2) + ) + + self.wait() + + + # 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(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(), + ) + + self.play( + FadeIn(angle_sign1), + FadeIn(my_triangle) + ) + + angle_sign2 = right_angle( + self.light_sources[1].get_source_point(), + self.lake_center, + OBSERVER_POINT, + ) + + self.play( + FadeIn(angle_sign2) + ) + + self.wait() + + self.play( + FadeOut(angle_sign1), + FadeOut(angle_sign2), + FadeOut(my_triangle) + ) + + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + self.play(FadeIn(ls0_dot)) + + construction_step(1) + indicator_wiggle() + #self.play(FadeOut(ls0_dot)) + zoom_out_scene(2) + + return + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(ls0_dot)) + + + self.play( FadeOut(self.altitudes), @@ -2839,6 +3691,7 @@ class PondScene(ThreeDScene): indicator.shift,v, indicator_reading.shift,v, open_sea.shift,v, + self.obs_dot.shift,v, ) self.number_line_labels.shift(v) @@ -2869,6 +3722,13 @@ class PondScene(ThreeDScene): self.play(Write(two_sided_sum)) + for i in range(0, MAX_N): + self.play(SwitchOff(nl_sources.submobjects[i].ambient_light) + ) + for i in range(MAX_N, 2 * MAX_N - 1): + self.play(SwitchOn(nl_sources.submobjects[i].ambient_light) + ) + covering_rectangle = Rectangle( width = SPACE_WIDTH * scale, height = 2 * SPACE_HEIGHT * scale, @@ -2883,6 +3743,7 @@ class PondScene(ThreeDScene): 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) @@ -2890,18 +3751,19 @@ class PondScene(ThreeDScene): self.play( FadeIn(covering_rectangle), - ReplacementTransform(indicator_reading, half_indicator_reading), + 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 + p = 2 * scale * LEFT + 0.3 * UP self.play( indicator.move_to,p, - half_indicator_reading.move_to,p, - FadeIn(equals_sign) + indicator_reading.move_to,p, + FadeIn(equals_sign), ) # show Randy admiring the result @@ -2911,6 +3773,11 @@ class PondScene(ThreeDScene): + + + + + class WaitScene(TeacherStudentsScene): def construct(self): diff --git a/topics/light.py b/topics/light.py index 5b95f63e..144f998a 100644 --- a/topics/light.py +++ b/topics/light.py @@ -353,7 +353,9 @@ class SwitchOff(LaggedStart): class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", - "height" : LIGHTHOUSE_HEIGHT + "height" : LIGHTHOUSE_HEIGHT, + "fill_color" : WHITE, + "fill_opacity" : 1.0, } def move_to(self,point): From 0aaa8c18771edb0d821c69ab18313af62f1732a6 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 27 Feb 2018 19:12:34 +0100 Subject: [PATCH 53/73] removed return --- active_projects/basel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 4bc48b4f..a44ebcc5 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -3596,7 +3596,6 @@ class PondScene(ThreeDScene): #self.play(FadeOut(ls0_dot)) zoom_out_scene(2) - return construction_step(2) indicator_wiggle() From ad13082b73f0b29b1e615440a94011f4a81d2461 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:55:38 -0800 Subject: [PATCH 54/73] Added Mobject.stretch_to_fit_depth --- mobject/mobject.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mobject/mobject.py b/mobject/mobject.py index 6618d1aa..8bb2f268 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -389,6 +389,9 @@ class Mobject(Container): def stretch_to_fit_height(self, height, **kwargs): return self.rescale_to_fit(height, 1, stretch = True, **kwargs) + def stretch_to_fit_depth(self, depth, **kwargs): + return self.rescale_to_fit(depth, 1, stretch = True, **kwargs) + def scale_to_fit_width(self, width, **kwargs): return self.rescale_to_fit(width, 0, stretch = False, **kwargs) From ec4bf7b504b15d6f3edbe1518edcc80dde6f931a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:56:22 -0800 Subject: [PATCH 55/73] Fixed(?) Spotlight.update_sectors --- topics/light.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/topics/light.py b/topics/light.py index 23721ef1..5fc0b268 100644 --- a/topics/light.py +++ b/topics/light.py @@ -234,7 +234,6 @@ class LightSource(VMobject): return R def update_shadow(self): - point = self.get_source_point() projected_screen_points = [] if not self.has_screen(): @@ -304,7 +303,6 @@ class LightSource(VMobject): self.shadow.mark_paths_closed = True - class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -318,7 +316,6 @@ class SwitchOn(LaggedStart): self, FadeIn, light, **kwargs ) - class SwitchOff(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -333,7 +330,6 @@ class SwitchOff(LaggedStart): FadeOut, light, **kwargs) light.submobjects = light.submobjects[::-1] - class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -473,11 +469,6 @@ class Spotlight(VMobject): def new_sector(self,r,dr,lower_angle,upper_angle): - # Note: I'm not looking _too_ closely at the implementation - # of these updates based on viewing angles and such. It seems to - # behave as intended, but let me know if you'd like more thorough - # scrutiny - alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( inner_radius = r, @@ -564,15 +555,17 @@ class Spotlight(VMobject): def update_sectors(self): if self.screen == None: return - for submob in self.submobject_family(): + for submob in self.submobjects: if type(submob) == AnnularSector: lower_angle, upper_angle = self.viewing_angles(self.screen) #dr = submob.outer_radius - submob.inner_radius dr = self.radius / self.num_levels - new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle) - submob.points = new_submob.points - submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) - + new_submob = self.new_sector( + submob.inner_radius, dr, lower_angle, upper_angle + ) + # submob.points = new_submob.points + # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) + Transform(submob, new_submob).update(1) def dimming(self,new_alpha): From 0bf9f0b64431a4932518b25598106d6c87d194ce Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 13:56:55 -0800 Subject: [PATCH 56/73] Finished InverseSquareLaw scene --- active_projects/basel2.py | 677 +++++++++++++++----------------------- 1 file changed, 264 insertions(+), 413 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 225e4c42..8a14b248 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -100,8 +100,10 @@ class AngleUpdater(ContinualAnimation): class LightIndicator(Mobject): CONFIG = { "radius": 0.5, + "reading_height" : 0.25, "intensity": 0, "opacity_for_unit_intensity": 1, + "fill_color" : YELLOW, "precision": 3, "show_reading": True, "measurement_point": ORIGIN, @@ -110,13 +112,18 @@ class LightIndicator(Mobject): def generate_points(self): self.background = Circle(color=BLACK, radius = self.radius) - self.background.set_fill(opacity=1.0) + self.background.set_fill(opacity = 1.0) self.foreground = Circle(color=self.color, radius = self.radius) - self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) + self.foreground.set_stroke( + color=INDICATOR_STROKE_COLOR, + width=INDICATOR_STROKE_WIDTH + ) + self.foreground.set_fill(color = self.fill_color) self.add(self.background, self.foreground) self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) self.reading.set_fill(color=INDICATOR_TEXT_COLOR) + self.reading.scale_to_fit_height(self.reading_height) self.reading.move_to(self.get_center()) if self.show_reading: self.add(self.reading) @@ -126,6 +133,10 @@ class LightIndicator(Mobject): new_opacity = min(1, new_int * self.opacity_for_unit_intensity) self.foreground.set_fill(opacity=new_opacity) ChangeDecimalToValue(self.reading, new_int).update(1) + if new_int > 1.1: + self.reading.set_fill(color = BLACK) + else: + self.reading.set_fill(color = WHITE) return self def get_measurement_point(self): @@ -134,10 +145,11 @@ class LightIndicator(Mobject): else: return self.get_center() - def measured_intensity(self): - distance = np.linalg.norm(self.get_measurement_point() - - self.light_source.get_source_point()) + distance = np.linalg.norm( + self.get_measurement_point() - + self.light_source.get_source_point() + ) intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity return intensity @@ -157,11 +169,11 @@ class UpdateLightIndicator(AnimationGroup): indicator.foreground, target_foreground ) changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) + AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) self.mobject = indicator class ContinualLightIndicatorUpdate(ContinualAnimation): - def update_mobject(self,dt): self.mobject.continual_update() @@ -1266,7 +1278,7 @@ class IntroduceScreen(Scene): self.add(angle_tracker) arc_tracker = AngleUpdater( - self.angle_arc, + self.angle_arc, self.light_source.spotlight ) self.add(arc_tracker) @@ -1285,18 +1297,24 @@ class IntroduceScreen(Scene): lambda m : m.update() ), ) + self.add( + ContinualUpdateFromFunc( + self.angle_indicator, + lambda m : m.set_stroke(width = 0).set_fill(opacity = 1) + ) + ) + self.remove(self.light_source.ambient_light) def rotate_screen(angle): self.play( Rotate(self.light_source.spotlight.screen, angle), - Animation(self.angle_indicator), Animation(self.angle_arc), run_time = 2, ) - for angle in TAU/8, -TAU/4, TAU/8, TAU/6: + for angle in TAU/8, -TAU/4, TAU/8, -TAU/6: rotate_screen(angle) self.wait() self.shoot_rays() - rotate_screen(-TAU/6) + rotate_screen(TAU/6) ## @@ -1437,7 +1455,7 @@ class EarthScene(IntroduceScreen): ) self.add_foreground_mobjects(equator_words, equator_arrow) self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) }) self.wait() # Point to patch @@ -1452,454 +1470,287 @@ class EarthScene(IntroduceScreen): run_time = 3, ) self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda r : interpolate(0.98, 1, smooth(t)) + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) }) self.wait() -class ScreenShapingScene(ThreeDScene): - - - # TODO: Morph from Earth Scene into this scene - +class InverseSquareLaw(ThreeDScene): + CONFIG = { + "screen_height" : 1.0, + "source_point" : 5*LEFT, + "unit_distance" : 4, + "num_levels" : 100, + } def construct(self): - - #self.force_skipping() - self.setup_elements() - self.deform_screen() - self.create_brightness_rect() - self.slant_screen() - self.unslant_screen() - self.left_shift_screen_while_showing_light_indicator() - self.add_distance_arrow() - self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() - self.left_shift_again() - #self.revert_to_original_skipping_status() - + self.move_screen_farther_away() self.morph_into_3d() - self.prove_inverse_square_law() - - def setup_elements(self): - - SCREEN_THICKNESS = 10 - - self.screen_height = 1.0 - self.brightness_rect_height = 1.0 + def move_screen_farther_away(self): + source_point = self.source_point + unit_distance = self.unit_distance # screen - self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], - path_arc = 0, num_arc_anchors = 10) + screen = self.screen = Line(self.screen_height*UP, ORIGIN) + screen.get_reference_point = screen.get_center + screen.shift( + source_point + unit_distance*RIGHT -\ + screen.get_reference_point() + ) # light source - self.light_source = LightSource( - opacity_function = inverse_quadratic(1,5,1), - num_levels = NUM_LEVELS, + light_source = self.light_source = LightSource( + # opacity_function = inverse_quadratic(1,5,1), + opacity_function = lambda r : 1./(r+1), + num_levels = self.num_levels, radius = 10, max_opacity = 0.2 - #screen = self.screen ) - self.light_source.set_max_opacity_spotlight(0.2) + light_source.set_max_opacity_spotlight(0.2) - self.light_source.set_screen(self.screen) - self.light_source.move_source_to([-5,0,0]) + light_source.set_screen(screen) + light_source.move_source_to(source_point) # abbreviations - self.ambient_light = self.light_source.ambient_light - self.spotlight = self.light_source.spotlight - self.lighthouse = self.light_source.lighthouse - - - #self.add_foreground_mobject(self.light_source.shadow) + ambient_light = light_source.ambient_light + spotlight = light_source.spotlight + lighthouse = light_source.lighthouse + shadow = light_source.shadow # Morty - self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) + morty = self.morty = Mortimer().scale(0.3) + morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF) - # Add everything to the scene - self.add(self.lighthouse) - + #Screen tracker + def update_spotlight(spotlight): + spotlight.update_sectors() + + spotlight_update = ContinualUpdateFromFunc(spotlight, update_spotlight) + shadow_update = ContinualUpdateFromFunc( + shadow, lambda m : light_source.update_shadow() + ) + + # Light indicator + light_indicator = self.light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + ) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(screen.get_reference_point() - source_point) + light_indicator.set_intensity(1.0/(distance/unit_distance)**2) + light_indicator.next_to(morty, UP, MED_LARGE_BUFF) + light_indicator_update = ContinualUpdateFromFunc( + light_indicator, update_light_indicator + ) + light_indicator_update.update(0) + + continual_updates = self.continual_updates = [ + spotlight_update, light_indicator_update, shadow_update + ] + + # Distance indicators + + one_arrow = DoubleArrow(ORIGIN, unit_distance*RIGHT, buff = 0) + two_arrow = DoubleArrow(ORIGIN, 2*unit_distance*RIGHT, buff = 0) + arrows = VGroup(one_arrow, two_arrow) + arrows.highlight(WHITE) + one_arrow.move_to(source_point + DOWN, LEFT) + two_arrow.move_to(source_point + 1.75*DOWN, LEFT) + one = Integer(1).next_to(one_arrow, UP, SMALL_BUFF) + two = Integer(2).next_to(two_arrow, DOWN, SMALL_BUFF) + arrow_group = VGroup(one_arrow, one, two_arrow, two) + + # Animations + + self.add_foreground_mobjects(lighthouse, screen, morty) + self.add(shadow_update) + + self.play( + SwitchOn(ambient_light), + morty.change, "pondering" + ) + self.play( + SwitchOn(spotlight), + FadeIn(light_indicator) + ) + # self.remove(spotlight) + self.add(*continual_updates) self.wait() - self.play(FadeIn(self.screen)) + for distance in -0.5, 0.5: + self.shift_by_distance(distance) + self.wait() + self.add_foreground_mobjects(one_arrow, one) + self.play(GrowFromCenter(one_arrow), Write(one)) + self.wait() + self.add_foreground_mobjects(two_arrow, two) + self.shift_by_distance(1, + GrowFromPoint(two_arrow, two_arrow.get_left()), + Write(two, rate_func = squish_rate_func(smooth, 0.5, 1)) + ) self.wait() - self.add_foreground_mobject(self.screen) - self.add_foreground_mobject(self.morty) - - self.play(SwitchOn(self.ambient_light)) - + q_marks = TextMobject("???") + q_marks.next_to(light_indicator, UP) self.play( - SwitchOn(self.spotlight), - self.light_source.dim_ambient + Write(q_marks), + morty.change, "confused", q_marks ) - - screen_tracker = ScreenTracker(self.light_source) - self.add(screen_tracker) - - + self.play(Blink(morty)) + self.play(FadeOut(q_marks), morty.change, "pondering") self.wait() + self.shift_by_distance(-1, arrow_group.shift, DOWN) - - - def deform_screen(self): - - self.wait() - - self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, 0)) - - - - - def create_brightness_rect(self): - - # in preparation for the slanting, create a rectangle that shows the brightness - - # a rect a zero width overlaying the screen - # so we can morph it into the brightness rect above - brightness_rect0 = Rectangle(width = 0, - height = self.screen_height).move_to(self.screen.get_center()) - self.add_foreground_mobject(brightness_rect0) - - self.brightness_rect = Rectangle(width = self.brightness_rect_height, - height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) - - self.brightness_rect.next_to(self.screen, UP, buff = 1) - - self.play( - ReplacementTransform(brightness_rect0,self.brightness_rect) - ) - - self.unslanted_screen = self.screen.deepcopy() - self.unslanted_brightness_rect = self.brightness_rect.copy() - # for unslanting the screen later - - - def slant_screen(self): - - SLANTING_AMOUNT = 0.1 - - lower_screen_point, upper_screen_point = self.screen.get_start_and_end() - - lower_slanted_screen_point = interpolate( - lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT - ) - upper_slanted_screen_point = interpolate( - upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT - ) - - self.slanted_brightness_rect = self.brightness_rect.copy() - self.slanted_brightness_rect.width *= 2 - self.slanted_brightness_rect.generate_points() - self.slanted_brightness_rect.set_fill(opacity = 0.25) - - self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, - path_arc = 0, num_arc_anchors = 10) - self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) - - self.play( - Transform(self.screen,self.slanted_screen), - Transform(self.brightness_rect,self.slanted_brightness_rect), - ) - - - - def unslant_screen(self): - - self.wait() - self.play( - Transform(self.screen,self.unslanted_screen), - Transform(self.brightness_rect,self.unslanted_brightness_rect), - ) - - - - - def left_shift_screen_while_showing_light_indicator(self): - - # Scene 5: constant screen size, changing opening angle - - OPACITY_FOR_UNIT_INTENSITY = 1 - - # let's use an actual light indicator instead of just rects - - self.indicator_intensity = 0.25 - indicator_height = 1.25 * self.screen_height - - self.indicator = LightIndicator(radius = indicator_height/2, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR, - precision = 2) - self.indicator.set_intensity(self.indicator_intensity) - - self.indicator.move_to(self.brightness_rect.get_center()) - - self.play( - FadeOut(self.brightness_rect), - FadeIn(self.indicator) - ) - - # Here some digits of the indicator disappear... - - self.add_foreground_mobject(self.indicator.reading) - - - self.unit_indicator_intensity = 1.0 # intensity at distance 1 - # (where we are about to move to) - - self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 - - self.play( - self.screen.shift,[-self.left_shift,0,0], - self.morty.shift,[-self.left_shift,0,0], - self.indicator.shift,[-self.left_shift,0,0], - self.indicator.set_intensity,self.unit_indicator_intensity, - ) - - - - def add_distance_arrow(self): - - # distance arrow (length 1) - left_x = self.spotlight.get_source_point()[0] - right_x = self.screen.get_center()[0] - arrow_y = -2 - arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) - arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) - arrow1.set_fill(color = WHITE) - arrow2.set_fill(color = WHITE) - distance_decimal = Integer(1).next_to(arrow1,DOWN) - self.arrow = VGroup(arrow1, arrow2,distance_decimal) - self.add(self.arrow) - - - # distance arrow (length 2) - # will be morphed into - self.distance_to_source = right_x - left_x - new_right_x = left_x + 2 * self.distance_to_source - new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) - new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) - new_arrow1.set_fill(color = WHITE) - new_arrow2.set_fill(color = WHITE) - new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) - self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) - # don't add it yet - - - def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): - - self.wait() - - self.play( - ReplacementTransform(self.arrow,self.new_arrow), - ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), - ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), - - ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), - # this should trigger ChangingDecimal, but it doesn't - # maybe bc it's an anim within an anim? - - ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), - ) - - - def left_shift_again(self): - - self.wait() - - self.play( - ReplacementTransform(self.new_arrow,self.arrow), - ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), - #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), - ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), - ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), + self.set_variables_as_attrs( + ambient_light, spotlight, shadow, lighthouse, + morty, arrow_group, + *continual_updates ) def morph_into_3d(self): + # axes = ThreeDAxes() + old_screen = self.screen + spotlight = self.spotlight + source_point = self.source_point + ambient_light = self.ambient_light + unit_distance = self.unit_distance + light_indicator = self.light_indicator + morty = self.morty + dr = ambient_light.radius/ambient_light.num_levels - - self.play(FadeOut(self.morty)) - - axes = ThreeDAxes() - self.add(axes) - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - phi1 = 60 * DEGREES # angle from zenith (0 to 180) - theta1 = -135 * DEGREES # azimuth (0 to 360) - distance1 = distance0 - target_point = self.camera.get_spherical_coords(phi1, theta1, distance1) - - dphi = phi1 - phi0 - dtheta = theta1 - theta0 - - camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) - projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) - - new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen0.rotate(TAU/4,axis = DOWN) - new_screen0.move_to(self.screen.get_center()) - self.add(new_screen0) - self.remove(self.screen) - self.light_source.set_screen(new_screen0) - - self.light_source.set_camera(self.camera) - - - new_screen = Rectangle(height = self.screen_height, - width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen.rotate(TAU/4,axis = DOWN) - new_screen.move_to(self.screen.get_center()) - - self.add_foreground_mobject(self.ambient_light) - self.add_foreground_mobject(self.spotlight) - self.add_foreground_mobject(self.light_source.shadow) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - + new_screen = Square( + side_length = self.screen_height, + stroke_color = WHITE, + stroke_width = 1, + fill_color = WHITE, + fill_opacity = 0.5 ) - self.remove(self.spotlight) + new_screen.rotate(TAU/4, UP) + new_screen.move_to(old_screen, IN) + old_screen.fade(1) + screen_group = VGroup(old_screen, new_screen) - self.play(Transform(new_screen0,new_screen)) + cone = VGroup(*[VGroup() for x in range(4)]) + cone.set_stroke(width = 0) + cone.set_fill(YELLOW, opacity = 0.5) + corner_directions = [OUT+UP, OUT+DOWN, IN+DOWN, IN+UP] + def update_cone(cone): + corners = map(new_screen.get_corner, corner_directions) + distance = np.linalg.norm(old_screen.get_reference_point() - self.source_point) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for face, (c1, c2) in zip(cone, adjacent_pairs(corners)): + face.submobjects = [] + for a1, a2 in zip(alphas, alphas[1:]): + face.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = YELLOW, + fill_opacity = ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + cone_update_anim = ContinualUpdateFromFunc(cone, update_cone) + cone_update_anim.update(0) + self.remove(self.spotlight_update, self.light_indicator_update) + self.add( + ContinualAnimation(new_screen), + cone_update_anim + ) + self.remove(spotlight) + self.move_camera( + phi = 60*DEGREES, + theta = -145*DEGREES, + added_anims = [ + # ApplyMethod( + # old_screen.scale, 1.8, {"about_edge" : DOWN}, + # run_time = 2, + # ), + ApplyFunction( + lambda m : m.fade(1).shift(1.5*DOWN), + light_indicator, + remover = True + ), + FadeOut(morty) + ], + run_time = 2, + ) + self.wait() + self.screen = screen_group + self.shift_by_distance(1) + self.shift_by_distance(-1) self.wait() - self.unit_screen = new_screen0 # better name + ## Create screen copies + screen_copy = new_screen.copy() + four_copies = VGroup(*[new_screen.copy() for x in range(4)]) + nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) + def update_four_copies(four_copies): + for mob, corner_direction in zip(four_copies, corner_directions): + mob.move_to(new_screen, corner_direction) + four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) + edge_directions = [ + UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN + ] + def update_nine_copies(nine_copies): + for mob, corner_direction in zip(nine_copies, edge_directions): + mob.move_to(new_screen, corner_direction) + nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) + three_arrow = DoubleArrow( + source_point + 4*DOWN, + source_point + 4*DOWN + 3*unit_distance*RIGHT, + buff = 0, + color = WHITE + ) + three = Integer(3) + three.next_to(three_arrow, DOWN) - - def prove_inverse_square_law(self): - - def orientate(mob): - mob.move_to(self.unit_screen) - mob.rotate(TAU/4, axis = LEFT) - mob.rotate(TAU/4, axis = OUT) - mob.rotate(TAU/2, axis = LEFT) - return mob - - unit_screen_copy = self.unit_screen.copy() - fourfold_screen = self.unit_screen.copy() - fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) - - self.remove(self.spotlight) - - - reading1 = TexMobject("1") - orientate(reading1) - - self.play(FadeIn(reading1)) + new_screen.fade(1) + self.add( + ContinualAnimation(screen_copy), + ContinualAnimation(four_copies), + ) + self.play( + screen_group.scale, 2, {"about_edge" : IN + DOWN}, + screen_group.shift, unit_distance*RIGHT, + four_copies_update_anim, + screen_copy.shift, 0.25*OUT, #WHY? + run_time = 2, + ) self.wait() - self.play(FadeOut(reading1)) - - - self.play( - Transform(self.unit_screen, fourfold_screen) + self.move_camera( + phi = 75*DEGREES, + theta = -155*DEGREES, + distance = 7, ) - - reading21 = TexMobject("{1\over 4}").scale(0.8) - orientate(reading21) - reading22 = reading21.deepcopy() - reading23 = reading21.deepcopy() - reading24 = reading21.deepcopy() - reading21.shift(0.5*OUT + 0.5*UP) - reading22.shift(0.5*OUT + 0.5*DOWN) - reading23.shift(0.5*IN + 0.5*UP) - reading24.shift(0.5*IN + 0.5*DOWN) - - - corners = fourfold_screen.get_anchors() - midpoint1 = (corners[0] + corners[1])/2 - midpoint2 = (corners[1] + corners[2])/2 - midpoint3 = (corners[2] + corners[3])/2 - midpoint4 = (corners[3] + corners[0])/2 - midline1 = Line(midpoint1, midpoint3) - midline2 = Line(midpoint2, midpoint4) - + self.begin_ambient_camera_rotation(rate = -0.01) + self.add(ContinualAnimation(nine_copies)) self.play( - ShowCreation(midline1), - ShowCreation(midline2) + screen_group.scale, 3./2, {"about_edge" : IN + DOWN}, + screen_group.shift, unit_distance*RIGHT, + nine_copies_update_anim, + UpdateFromAlphaFunc( + nine_copies, + lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) + ), + GrowFromPoint(three_arrow, three_arrow.get_left()), + Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), + run_time = 2, ) - - self.play( - FadeIn(reading21), - FadeIn(reading22), - FadeIn(reading23), - FadeIn(reading24), - ) - self.wait() - self.play( - FadeOut(reading21), - FadeOut(reading22), - FadeOut(reading23), - FadeOut(reading24), - FadeOut(midline1), - FadeOut(midline2) - ) - ninefold_screen = unit_screen_copy.copy() - ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) + ### - self.play( - Transform(self.unit_screen, ninefold_screen) - ) + def shift_by_distance(self, distance, *added_anims): + anims = [ + self.screen.shift, self.unit_distance*distance*RIGHT, + ] + if self.morty in self.mobjects: + anims.append(MaintainPositionRelativeTo(self.morty, self.screen)) + anims += added_anims + self.play(*anims, run_time = 2) - reading31 = TexMobject("{1\over 9}").scale(0.8) - orientate(reading31) - reading32 = reading31.deepcopy() - reading33 = reading31.deepcopy() - reading34 = reading31.deepcopy() - reading35 = reading31.deepcopy() - reading36 = reading31.deepcopy() - reading37 = reading31.deepcopy() - reading38 = reading31.deepcopy() - reading39 = reading31.deepcopy() - reading31.shift(IN + UP) - reading32.shift(IN) - reading33.shift(IN + DOWN) - reading34.shift(UP) - reading35.shift(ORIGIN) - reading36.shift(DOWN) - reading37.shift(OUT + UP) - reading38.shift(OUT) - reading39.shift(OUT + DOWN) - - corners = ninefold_screen.get_anchors() - midpoint11 = (2*corners[0] + corners[1])/3 - midpoint12 = (corners[0] + 2*corners[1])/3 - midpoint21 = (2*corners[1] + corners[2])/3 - midpoint22 = (corners[1] + 2*corners[2])/3 - midpoint31 = (2*corners[2] + corners[3])/3 - midpoint32 = (corners[2] + 2*corners[3])/3 - midpoint41 = (2*corners[3] + corners[0])/3 - midpoint42 = (corners[3] + 2*corners[0])/3 - midline11 = Line(midpoint11, midpoint32) - midline12 = Line(midpoint12, midpoint31) - midline21 = Line(midpoint21, midpoint42) - midline22 = Line(midpoint22, midpoint41) - - self.play( - ShowCreation(midline11), - ShowCreation(midline12), - ShowCreation(midline21), - ShowCreation(midline22), - ) - - self.play( - FadeIn(reading31), - FadeIn(reading32), - FadeIn(reading33), - FadeIn(reading34), - FadeIn(reading35), - FadeIn(reading36), - FadeIn(reading37), - FadeIn(reading38), - FadeIn(reading39), - ) class IndicatorScalingScene(Scene): From 3a597953304802cd9343a198285680c5796e9e84 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 15:12:04 -0800 Subject: [PATCH 57/73] Added ManipulateLightsourceSetups --- active_projects/basel2.py | 357 ++++++++++++-------------------------- 1 file changed, 108 insertions(+), 249 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 8a14b248..06246add 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -56,7 +56,6 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) - A = np.array([5.,-3.,0.]) B = np.array([-5.,3.,0.]) C = np.array([-5.,-3.,0.]) @@ -74,9 +73,6 @@ prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) H2 = np.linalg.solve(prelim_matrix,prelim_vector) H = np.append(H2, 0.) - - - class AngleUpdater(ContinualAnimation): def __init__(self, angle_arc, spotlight, **kwargs): self.angle_arc = angle_arc @@ -1713,6 +1709,10 @@ class InverseSquareLaw(ThreeDScene): self.play( screen_group.scale, 2, {"about_edge" : IN + DOWN}, screen_group.shift, unit_distance*RIGHT, + UpdateFromAlphaFunc( + four_copies, + lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) + ), four_copies_update_anim, screen_copy.shift, 0.25*OUT, #WHY? run_time = 2, @@ -1737,8 +1737,7 @@ class InverseSquareLaw(ThreeDScene): Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), run_time = 2, ) - self.wait() - + self.wait(10) ### @@ -1751,261 +1750,121 @@ class InverseSquareLaw(ThreeDScene): anims += added_anims self.play(*anims, run_time = 2) - -class IndicatorScalingScene(Scene): - +class ScreensIntroWrapper(TeacherStudentsScene): def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.play(self.teacher.change, "raise_right_hand") + self.change_student_modes( + "pondering", "erm", "confused", + look_at_arg = point, + ) + self.play(self.teacher.look_at, point) + self.wait(5) - unit_intensity = 0.6 - - indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator1.set_intensity(unit_intensity) - reading1 = TexMobject("1") - reading1.move_to(indicator1) - - - indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator2.shift(2*RIGHT) - indicator2.set_intensity(unit_intensity/4) - reading2 = TexMobject("{1\over 4}").scale(0.8) - reading2.move_to(indicator2) - - indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator3.shift(4*RIGHT) - indicator3.set_intensity(unit_intensity/9) - reading3 = TexMobject("{1\over 9}").scale(0.8) - reading3.move_to(indicator3) - - - self.play(FadeIn(indicator1)) - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - self.play(Transform(indicator1, indicator2)) - self.play(FadeIn(reading2)) - self.wait() - self.play(FadeOut(reading2)) - self.play(Transform(indicator1, indicator3)) - self.play(FadeIn(reading3)) - self.wait() - -class BackToEulerSumScene(PiCreatureScene): - - +class ManipulateLightsourceSetups(PiCreatureScene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + } def construct(self): - self.remove(self.get_primary_pi_creature()) + unit_distance = 3 - NUM_CONES = 7 - NUM_VISIBLE_CONES = 6 - INDICATOR_RADIUS = 0.5 - OPACITY_FOR_UNIT_INTENSITY = 1.0 + # Morty + morty = self.pi_creature + morty.flip() + morty.scale(0.5) + morty.move_to(2*LEFT + SPACE_HEIGHT*DOWN/2) + observer_point = morty.eyes[1].get_center() - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1.6, - stroke_width = 1, - numbers_with_elongated_ticks = range(1,5), - numbers_to_show = range(1,5), - unit_size = 2, - tick_frequency = 0.2, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, + bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) + bubble.set_fill(BLACK, 1) + bubble.pin_to(morty) + + # Indicator + light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + fill_color = YELLOW, + radius = 0.4, + reading_height = 0.2, ) + light_indicator.move_to(bubble.get_bubble_center()) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(light_source.get_source_point()-observer_point) + light_indicator.set_intensity((unit_distance/distance)**2) - self.number_line.label_direction = DOWN - #self.number_line.shift(3*UP) + #Light source + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_to(observer_point + unit_distance*RIGHT) - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) + #Light source copies + light_source_copies = VGroup(*[light_source.copy() for x in range(2)]) + for lsc, vect in zip(light_source_copies, [RIGHT, UP]): + lsc.move_to(observer_point + np.sqrt(2)*unit_distance*vect) + + self.add(light_source) + self.add_foreground_mobjects(morty, bubble, light_indicator) + self.add(ContinualUpdateFromFunc(light_indicator, update_light_indicator)) + self.play( + ApplyMethod( + light_source.shift, 0.66*unit_distance*LEFT, + rate_func = wiggle, + run_time = 5, + ), + morty.change, "erm", + ) + self.play( + UpdateFromAlphaFunc( + light_source, + lambda ls, a : ls.move_to( + observer_point + rotate_vector( + unit_distance*RIGHT, (1+1./8)*a*TAU + ) + ), + run_time = 6, + rate_func = bezier([0, 0, 1, 1]) + ), + morty.change, "pondering", + UpdateFromFunc(morty, lambda m : m.look_at(light_source)) + ) self.wait() - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) - - randy_copy = randy.copy() - randy_copy.target = randy.copy().shift(DOWN) - - - - bubble = ThoughtBubble(direction = RIGHT, - width = 4, height = 3, - file_name = "Bubbles_thought.svg") - bubble.next_to(randy,LEFT+UP) - bubble.set_fill(color = BLACK, opacity = 1) - + plus = TexMobject("+") + point = light_indicator.get_center() + plus.move_to(point) + light_indicator_copy = light_indicator.copy() + self.add_foreground_mobjects(plus, light_indicator_copy) self.play( - randy.change, "wave_2", - ShowCreation(bubble), + ReplacementTransform( + light_source, light_source_copies[0] + ), + ReplacementTransform( + light_source.copy().fade(1), + light_source_copies[1] + ), + FadeIn(plus), + UpdateFromFunc( + light_indicator_copy, + lambda li : update_light_indicator(li), + ), + UpdateFromAlphaFunc( + light_indicator, lambda m, a : m.move_to( + point + a*0.75*RIGHT, + ) + ), + UpdateFromAlphaFunc( + light_indicator_copy, lambda m, a : m.move_to( + point + a*0.75*LEFT, + ) + ), + run_time = 2 ) - - - euler_sum = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ") - # the last entry is a dummy element which makes looping easier - # used just for putting the fractions into the light indicators - - intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)]) - opacities = intensities * OPACITY_FOR_UNIT_INTENSITY - - # repeat: - - # fade in lighthouse - # switch on / fade in ambient light - # show creation / write light indicator - # move indicator onto origin - # while morphing and dimming - # move indicator into thought bubble - # while indicators already inside shift to the back - # and while term appears in the series below - - point = self.number_line.number_to_point(1) - v = point - self.number_line.number_to_point(0) - light_source = LightSource() - light_source.move_source_to(point) - #light_source.ambient_light.move_source_to(point) - #light_source.lighthouse.move_to(point) - - self.play(FadeIn(light_source.lighthouse)) - self.play(SwitchOn(light_source.ambient_light)) - - - # create an indicator that will move along the number line - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - indicator_reading = euler_sum[0] - indicator_reading.scale_to_fit_height(0.5 * indicator.get_height()) - indicator_reading.move_to(indicator.get_center()) - indicator.add(indicator_reading) - indicator.tex_reading = indicator_reading - # the TeX reading is too bright at full intensity - indicator.tex_reading.set_fill(color = BLACK) - indicator.foreground.set_fill(None,opacities[0]) - - - indicator.move_to(point) - indicator.set_intensity(intensities[0]) - - self.play(FadeIn(indicator)) - self.add_foreground_mobject(indicator) - - collection_point = np.array([-6.,2.,0.]) - left_shift = 0.2*LEFT - collected_indicators = Mobject() - - - for i in range(2, NUM_VISIBLE_CONES + 1): - - previous_point = self.number_line.number_to_point(i - 1) - point = self.number_line.number_to_point(i) - - - v = point - previous_point - #print v - # Create and position the target indicator (next on number line). - indicator_target = indicator.deepcopy() - indicator_target.shift(v) - - - # Here we make a copy that will move into the thought bubble. - bubble_indicator = indicator.deepcopy() - # And its target - bubble_indicator_target = bubble_indicator.deepcopy() - bubble_indicator_target.set_intensity(intensities[i - 2]) - - # give the target the appropriate reading - euler_sum[2*i-4].move_to(bubble_indicator_target) - bubble_indicator_target.remove(bubble_indicator_target.tex_reading) - bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() - bubble_indicator_target.add(bubble_indicator_target.tex_reading) - # center it in the indicator - - if bubble_indicator_target.tex_reading.get_tex_string() != "1": - bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height()) - # the target is less bright, possibly switch to a white text color - if bubble_indicator_target.intensity < 0.7: - bubble_indicator.tex_reading.set_fill(color = WHITE) - - # position the target in the thought bubble - bubble_indicator_target.move_to(collection_point) - - - self.add_foreground_mobject(bubble_indicator) - - - self.wait() - - self.play( - Transform(bubble_indicator,bubble_indicator_target), - collected_indicators.shift,left_shift, - ) - - collected_indicators.add(bubble_indicator) - - new_light = light_source.deepcopy() - w = new_light.get_source_point() - new_light.move_source_to(w + (i-2)*v) - w2 = new_light.get_source_point() - - self.add(new_light.lighthouse) - self.play( - Transform(indicator,indicator_target), - new_light.lighthouse.shift,v, - ) - new_light.move_source_to(w + (i-1)*v) - new_light.lighthouse.move_to(w + (i-1)*v) - - self.play(SwitchOn(new_light.ambient_light), - ) - - - - - # quickly switch on off-screen light cones - for i in range(NUM_VISIBLE_CONES,NUM_CONES): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - ls = LightSource() - point = point = self.number_line.number_to_point(i) - ls.move_source_to(point) - self.play( - SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ) - - # and morph indicator stack into limit value - - sum_indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - sum_indicator.set_intensity(intensities[0] * np.pi**2/6) - sum_indicator_reading = TexMobject("{\pi^2 \over 6}") - sum_indicator_reading.set_fill(color = BLACK) - sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) - sum_indicator.add(sum_indicator_reading) - sum_indicator.move_to(collection_point) - - self.play( - FadeOut(collected_indicators), - FadeIn(sum_indicator) - ) - - - - self.wait() + self.play(morty.change, "hooray") + self.wait(2) class TwoLightSourcesScene(PiCreatureScene): From ae943ba795ade00759288b3c348f63050381641f Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 28 Feb 2018 00:26:08 +0100 Subject: [PATCH 58/73] tweaked PondScene and FinalSumManipulationScene --- active_projects/basel.py | 216 ++++++++++++++++++++++++--------------- 1 file changed, 134 insertions(+), 82 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index a44ebcc5..80cf903d 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -43,10 +43,10 @@ NUM_CONES = 7 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -NUM_LEVELS = 20 +NUM_LEVELS = 150 AMBIENT_FULL = 0.8 AMBIENT_DIMMED = 0.5 -AMBIENT_SCALE = 1.0 +AMBIENT_SCALE = 2.0 AMBIENT_RADIUS = 20.0 SPOTLIGHT_FULL = 0.8 SPOTLIGHT_DIMMED = 0.2 @@ -2315,7 +2315,7 @@ class InscribedAngleScene(ThreeDScene): LAKE0_RADIUS = 1.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 + LIGHTHOUSE_HEIGHT = 0.3 LAKE_COLOR = BLUE LAKE_OPACITY = 0.15 LAKE_STROKE_WIDTH = 5.0 @@ -2329,13 +2329,9 @@ class InscribedAngleScene(ThreeDScene): self.cumulated_zoom_factor = 1 - self.force_skipping() - def zoom_out_scene(factor): - if self.mobjects.contains(self.ls0_dot): - self.play(FadeOut(self.ls0_dot)) phi0 = self.camera.get_phi() # default is 0 degs theta0 = self.camera.get_theta() # default is -90 degs @@ -2352,9 +2348,6 @@ class InscribedAngleScene(ThreeDScene): self.cumulated_zoom_factor *= factor - if not self.mobjects.contains(self.ls0_dot): - self.play(FadeIn(self.ls0_dot)) - def shift_scene(v): self.play( @@ -2373,8 +2366,8 @@ class InscribedAngleScene(ThreeDScene): self.zoomable_mobs.add(baseline) # prob not necessary self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot) #, ls0_dot) + 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, @@ -2397,12 +2390,12 @@ class InscribedAngleScene(ThreeDScene): 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) + original_op_func = inverse_quadratic(LIGHT_MAX_INT,AMBIENT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func, num_levels = 150) ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,self.obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse) self.wait() @@ -2463,7 +2456,7 @@ class InscribedAngleScene(ThreeDScene): ShowCreation(diameter), Write(diameter_text), #FadeOut(self.obs_dot), - FadeOut(ls0_dot) + FadeOut(self.ls0_dot) ) indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) @@ -2627,7 +2620,7 @@ class InscribedAngleScene(ThreeDScene): if show_steps == True: self.play( FadeIn(new_outer_lake, run_time = run_time), - FadeIn(ls0_dot) + FadeIn(self.ls0_dot) ) else: self.play( @@ -2699,7 +2692,7 @@ class InscribedAngleScene(ThreeDScene): self.wait() if show_steps == True: - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP self.lake_radius *= 2 @@ -2744,25 +2737,27 @@ class InscribedAngleScene(ThreeDScene): construction_step(0) indicator_wiggle() - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) zoom_out_scene(2) + return + construction_step(1) indicator_wiggle() - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) zoom_out_scene(2) construction_step(2) indicator_wiggle() - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) self.revert_to_original_skipping_status() - ANGLE_COLOR1 = RED - ANGLE_COLOR2 = GREEN + ANGLE_COLOR1 = BLUE_C + ANGLE_COLOR2 = GREEN_D for mob in self.mobjects: @@ -3037,7 +3032,7 @@ class PondScene(ThreeDScene): LAKE0_RADIUS = 1.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 + LIGHTHOUSE_HEIGHT = 0.5 LAKE_COLOR = BLUE LAKE_OPACITY = 0.15 LAKE_STROKE_WIDTH = 5.0 @@ -3046,7 +3041,7 @@ class PondScene(ThreeDScene): DOT_COLOR = BLUE LIGHT_MAX_INT = 1 - LIGHT_SCALE = 5 + LIGHT_SCALE = 2.5 LIGHT_CUTOFF = 1 RIGHT_ANGLE_SIZE = 0.3 @@ -3054,15 +3049,14 @@ class PondScene(ThreeDScene): self.cumulated_zoom_factor = 1 - #self.force_skipping() - def right_angle(pointA, pointB, pointC): + def right_angle(pointA, pointB, pointC, size = 1): v1 = pointA - pointB - v1 = RIGHT_ANGLE_SIZE * v1/np.linalg.norm(v1) + v1 = size * v1/np.linalg.norm(v1) v2 = pointC - pointB - v2 = RIGHT_ANGLE_SIZE * v2/np.linalg.norm(v2) + v2 = size * v2/np.linalg.norm(v2) P = pointB Q = pointB + v1 @@ -3088,6 +3082,9 @@ class PondScene(ThreeDScene): 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() @@ -3103,6 +3100,17 @@ class PondScene(ThreeDScene): 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( @@ -3121,8 +3129,8 @@ class PondScene(ThreeDScene): self.zoomable_mobs.add(baseline) # prob not necessary self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot, ls0_dot) + 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, @@ -3134,7 +3142,7 @@ class PondScene(ThreeDScene): self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Mortimer().scale(0.3) + morty = Randolph().scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -3146,14 +3154,16 @@ class PondScene(ThreeDScene): # first lighthouse original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) - ls0 = LightSource(opacity_function = original_op_func) + 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,self.obs_dot,ls0_dot, ls0.lighthouse) + self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse) self.add_foreground_mobject(morty) self.add_foreground_mobject(self.obs_dot) - self.add_foreground_mobject(ls0_dot) + self.add_foreground_mobject(self.ls0_dot) self.wait() @@ -3201,8 +3211,8 @@ class PondScene(ThreeDScene): indicator.set_intensity,0.5 ) - diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.05) - diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.95) + 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, @@ -3217,7 +3227,7 @@ class PondScene(ThreeDScene): ShowCreation(diameter), Write(diameter_text), #FadeOut(self.obs_dot), - FadeOut(ls0_dot) + FadeOut(self.ls0_dot) ) indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) @@ -3384,7 +3394,7 @@ class PondScene(ThreeDScene): if show_steps == True: self.play( FadeIn(new_outer_lake, run_time = run_time), - FadeIn(ls0_dot) + FadeIn(self.ls0_dot) ) else: self.play( @@ -3421,7 +3431,7 @@ class PondScene(ThreeDScene): A, C, B ) right_angle1 = right_angle( - A, C, B + A, C, B, size = 2 * RIGHT_ANGLE_SIZE ) self.play( @@ -3439,12 +3449,13 @@ class PondScene(ThreeDScene): self.wait() H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT + L = self.outer_lake.get_center() triangle2 = triangle( - B, H, C + L, H, C ) right_angle2 = right_angle( - B, H, C + L, H, C, size = 2 * RIGHT_ANGLE_SIZE ) self.play( @@ -3506,7 +3517,7 @@ class PondScene(ThreeDScene): self.wait() if show_steps == True: - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP self.lake_radius *= 2 @@ -3561,7 +3572,8 @@ class PondScene(ThreeDScene): 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), @@ -3572,7 +3584,8 @@ class PondScene(ThreeDScene): self.light_sources[1].get_source_point(), self.lake_center, OBSERVER_POINT, - ) + size = RIGHT_ANGLE_SIZE + ) self.play( FadeIn(angle_sign2) @@ -3587,19 +3600,19 @@ class PondScene(ThreeDScene): ) indicator_wiggle() - self.play(FadeOut(ls0_dot)) + self.remove(self.ls0_dot) zoom_out_scene(2) - self.play(FadeIn(ls0_dot)) + construction_step(1) indicator_wiggle() - #self.play(FadeOut(ls0_dot)) + #self.play(FadeOut(self.ls0_dot)) zoom_out_scene(2) construction_step(2) indicator_wiggle() - self.play(FadeOut(ls0_dot)) + self.play(FadeOut(self.ls0_dot)) @@ -3682,7 +3695,7 @@ class PondScene(ThreeDScene): self.wait() - v = 5 * scale * UP + v = 4 * scale * UP self.play( nl_sources.shift,v, morty.shift,v, @@ -3717,16 +3730,17 @@ class PondScene(ThreeDScene): for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions - submob.shift(0.2 * scale * DOWN) + submob.shift(0.3 * scale * DOWN) self.play(Write(two_sided_sum)) - for i in range(0, MAX_N): - self.play(SwitchOff(nl_sources.submobjects[i].ambient_light) - ) - for i in range(MAX_N, 2 * MAX_N - 1): - self.play(SwitchOn(nl_sources.submobjects[i].ambient_light) - ) + for i in range(MAX_N - 5, MAX_N): + self.remove(nl_sources.submobjects[i].ambient_light) + + for i in range(MAX_N, MAX_N + 5): + self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light) + + self.wait() covering_rectangle = Rectangle( width = SPACE_WIDTH * scale, @@ -3757,7 +3771,7 @@ class PondScene(ThreeDScene): equals_sign = TexMobject("=").scale(TEX_SCALE) equals_sign.move_to(central_plus_sign) - p = 2 * scale * LEFT + 0.3 * UP + p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP self.play( indicator.move_to,p, @@ -3765,10 +3779,13 @@ class PondScene(ThreeDScene): FadeIn(equals_sign), ) + self.revert_to_original_skipping_status() + # show Randy admiring the result - randy = Randolph().scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + 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") @@ -3781,6 +3798,7 @@ class WaitScene(TeacherStudentsScene): def construct(self): + self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!")) student_q = TextMobject("What about") @@ -3812,12 +3830,16 @@ class FinalSumManipulationScene(PiCreatureScene): sum_vertical_spacing = 1.5 randy = self.get_primary_pi_creature() + randy.highlight(MAROON_E) + randy.color = MAROON_E randy.scale(0.7).flip().to_edge(DOWN + LEFT) + self.wait() ls_template = LightSource( - radius = 2, + radius = 4, + num_levels = 40, max_opacity_ambient = 0.5, - opacity_function = inverse_quadratic(1,0.5,1) + opacity_function = inverse_quadratic(1,0.75,1) ) @@ -3839,7 +3861,7 @@ class FinalSumManipulationScene(PiCreatureScene): include_tip = True ) - self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0) + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) odd_lights = VMobject() for i in odd_range: @@ -3851,13 +3873,17 @@ class FinalSumManipulationScene(PiCreatureScene): self.play( ShowCreation(self.number_line1), ) + 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) + 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) + 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) @@ -3870,7 +3896,8 @@ class FinalSumManipulationScene(PiCreatureScene): Write(term, run_time = switch_on_time) ) - result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR) + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, + stroke_color = LIGHT_COLOR) result1.next_to(self.number_line1, LEFT, buff = 0.5) self.play(Write(result1)) @@ -3893,6 +3920,8 @@ class FinalSumManipulationScene(PiCreatureScene): self.play( ShowCreation(self.number_line2), ) + self.wait() + @@ -3906,7 +3935,7 @@ class FinalSumManipulationScene(PiCreatureScene): even_terms = VMobject() for i in even_range: - term = TexMobject("+\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2) + 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) @@ -3925,6 +3954,7 @@ class FinalSumManipulationScene(PiCreatureScene): SwitchOn(ls.ambient_light, run_time = switch_on_time), Write(term) ) + self.wait() @@ -3937,6 +3967,15 @@ class FinalSumManipulationScene(PiCreatureScene): Transform(even_lights,full_lights) ) + + self.wait() + + for i in range(5): + 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() @@ -3944,22 +3983,22 @@ class FinalSumManipulationScene(PiCreatureScene): Q2 = interpolate(P1, P2, 0.8) quarter_arrow = Arrow(Q1, Q2, color = LIGHT_COLOR2) - quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2) + quarter_label = 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), - Transform(even_lights,even_lights_copy) ) + 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) + 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) @@ -3967,9 +4006,10 @@ class FinalSumManipulationScene(PiCreatureScene): 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) + 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) @@ -3977,6 +4017,7 @@ class FinalSumManipulationScene(PiCreatureScene): ReplacementTransform(three_quarters_arrow, four_thirds_arrow), ReplacementTransform(three_quarters_label, four_thirds_label) ) + self.wait() self.play( FadeOut(quarter_label), @@ -3985,14 +4026,17 @@ class FinalSumManipulationScene(PiCreatureScene): FadeOut(even_terms) ) + self.wait() full_terms = VMobject() - for i in full_range: + for i in range(1,8): #full_range: if i == 1: - term = TexMobject("\phantom{+\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3) + 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) + 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) @@ -4004,6 +4048,7 @@ class FinalSumManipulationScene(PiCreatureScene): FadeOut(full_lights), FadeIn(full_terms) ) + self.wait() v = (sum_vertical_spacing + 0.5) * UP self.play( @@ -4023,24 +4068,21 @@ class FinalSumManipulationScene(PiCreatureScene): FadeIn(arrow_copy), FadeIn(label_copy) ) + self.wait() - final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3) + 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) - buffer = 2 - result_box = Rectangle(width = 15, - height = buffer*equation.get_height(), color = LIGHT_COLOR3) - result_box.move_to(equation) - equation.add(result_box) self.play( FadeOut(result1), @@ -4049,11 +4091,21 @@ class FinalSumManipulationScene(PiCreatureScene): 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.play(equation.shift, -equation.get_center()[1] * UP + UP) - + self.wait() From 59d81feb269d64139b61865e23167ff1138d3b58 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 17:27:39 -0800 Subject: [PATCH 59/73] Finished TwoLightSourcesScene --- active_projects/basel2.py | 415 ++++++++++++++++++++++++-------------- 1 file changed, 259 insertions(+), 156 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 06246add..3dbcf12b 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -56,22 +56,22 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) -A = np.array([5.,-3.,0.]) -B = np.array([-5.,3.,0.]) -C = np.array([-5.,-3.,0.]) -xA = A[0] -yA = A[1] -xB = B[0] -yB = B[1] -xC = C[0] -yC = C[1] +# A = np.array([5.,-3.,0.]) +# B = np.array([-5.,3.,0.]) +# C = np.array([-5.,-3.,0.]) +# xA = A[0] +# yA = A[1] +# xB = B[0] +# yB = B[1] +# xC = C[0] +# yC = C[1] # find the coords of the altitude point H # as the solution of a certain LSE -prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic -prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) -H2 = np.linalg.solve(prelim_matrix,prelim_vector) -H = np.append(H2, 0.) +# prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic +# prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) +# H2 = np.linalg.solve(prelim_matrix,prelim_vector) +# H = np.append(H2, 0.) class AngleUpdater(ContinualAnimation): def __init__(self, angle_arc, spotlight, **kwargs): @@ -136,7 +136,7 @@ class LightIndicator(Mobject): return self def get_measurement_point(self): - if self.measurement_point != None: + if self.measurement_point is not None: return self.measurement_point else: return self.get_center() @@ -1765,15 +1765,13 @@ class ManipulateLightsourceSetups(PiCreatureScene): CONFIG = { "num_levels" : 100, "radius" : 10, + "pi_creature_point" : 2*LEFT + 2*DOWN, } def construct(self): unit_distance = 3 # Morty morty = self.pi_creature - morty.flip() - morty.scale(0.5) - morty.move_to(2*LEFT + SPACE_HEIGHT*DOWN/2) observer_point = morty.eyes[1].get_center() bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) @@ -1866,182 +1864,287 @@ class ManipulateLightsourceSetups(PiCreatureScene): self.play(morty.change, "hooray") self.wait(2) -class TwoLightSourcesScene(PiCreatureScene): + ## + def create_pi_creature(self): + morty = Mortimer() + morty.flip() + morty.scale(0.5) + morty.move_to(self.pi_creature_point) + return morty + +class TwoLightSourcesScene(ManipulateLightsourceSetups): + CONFIG = { + "num_levels" : 200, + "radius" : 15, + "a" : 9, + "b" : 5, + } def construct(self): - MAX_OPACITY = 0.4 INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 + origin_point = 5*LEFT + 2.5*DOWN - morty = self.get_primary_pi_creature() - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - horizontal = VMobject(stroke_width = 1) - horizontal.set_points_as_corners([C,A]) - vertical = VMobject(stroke_width = 1) - vertical.set_points_as_corners([C,B]) - - self.play( - ShowCreation(horizontal), - ShowCreation(vertical) + #Morty + morty = self.pi_creature + morty.change("hooray") # From last scen + morty.generate_target() + morty.target.change("plain") + morty.target.scale(0.6) + morty.target.next_to( + origin_point, LEFT, buff = 0, + submobject_to_align = morty.target.eyes[1] ) - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = True, - precision = 2 + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, ) + axes.shift(origin_point) - indicator.next_to(morty,LEFT) - - self.play( - Write(indicator) + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) - - ls1 = LightSource(radius = 20, num_levels = 50) - ls2 = ls1.deepcopy() - ls1.move_source_to(A) - ls2.move_source_to(B) - - self.play( - FadeIn(ls1.lighthouse), - FadeIn(ls2.lighthouse), - SwitchOn(ls1.ambient_light), - SwitchOn(ls2.ambient_light) + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), ) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) - distance1 = np.linalg.norm(C - ls1.get_source_point()) - intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity - distance2 = np.linalg.norm(C - ls2.get_source_point()) - intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) - self.play( - UpdateLightIndicator(indicator,intensity) - ) + #Identical lighthouse labels + identical_lighthouses_words = TextMobject("All identical \\\\ lighthouses") + identical_lighthouses_words.to_corner(UP+RIGHT) + identical_lighthouses_words.shift(LEFT) + identical_lighthouses_arrows = VGroup(*[ + Arrow( + identical_lighthouses_words.get_bottom(), + ls.get_source_point(), + buff = SMALL_BUFF, + color = WHITE, + ) + for ls in lsA, lsB, lsC + ]) - self.wait() - - ls3 = ls1.deepcopy() - ls3.move_to(np.array([6,3.5,0])) - - new_indicator = indicator.copy() - new_indicator.light_source = ls3 - new_indicator.measurement_point = C - self.add(new_indicator) - self.play( - indicator.shift, 2 * UP - ) - - - - #intensity = intensity_for_light_source(ls3) - - - self.play( - SwitchOff(ls1.ambient_light), - #FadeOut(ls1.lighthouse), - SwitchOff(ls2.ambient_light), - #FadeOut(ls2.lighthouse), - UpdateLightIndicator(new_indicator,0.0) - ) - - # create a *continual* animation for the replacement source - updater = ContinualLightIndicatorUpdate(new_indicator) - self.add(updater) - - self.play( - SwitchOn(ls3.ambient_light), - FadeIn(ls3.lighthouse), - - ) - - self.wait() - - # move the light source around - # TODO: moving along a path arc - - location = np.array([-3,-2.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([6.,1.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([5.,2.,0.]) - self.play(ls3.move_source_to,location) - closer_location = interpolate(location, C, 0.5) - self.play(ls3.move_source_to,closer_location) - self.play(ls3.move_source_to,location) - - # maybe move in a circle around C using a loop? - - - - self.play(ls3.move_source_to,H) - - - - # draw lines to complete the geometric picture - # and label the lengths - - line_a = VMobject() - line_a.set_points_as_corners([B,C]) - line_b = VMobject() - line_b.set_points_as_corners([A,C]) - line_c = VMobject() - line_c.set_points_as_corners([A,B]) - line_h = VMobject() - line_h.set_points_as_corners([H,C]) + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + # perp_mark.highlight(BLACK) + + #Indicators + indicator = LightIndicator( + color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = True, + precision = 2, + ) + indicator.next_to(origin_point, UP+LEFT) + def update_indicator(indicator): + intensity = 0 + for ls in lsA, lsB, lsC: + if ls in self.mobjects: + distance = np.linalg.norm(ls.get_source_point() - origin_point) + d_indensity = fdiv( + 3./(distance**2), + indicator.opacity_for_unit_intensity + ) + d_indensity *= ls.ambient_light.submobjects[1].get_fill_opacity() + intensity += d_indensity + indicator.set_intensity(intensity) + indicator_update_anim = ContinualUpdateFromFunc(indicator, update_indicator) + + new_indicator = indicator.copy() + new_indicator.light_source = lsC + new_indicator.measurement_point = C + + #Note sure what this is... + distance1 = np.linalg.norm(origin_point - lsA.get_source_point()) + intensity = lsA.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(origin_point - lsB.get_source_point()) + intensity += lsB.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + + #Transition from last_scene self.play( - ShowCreation(line_a), - Write(label_a) + ShowCreation(axes, run_time = 2), + MoveToTarget(morty), + FadeIn(indicator), ) + #Move lsC around + self.add(lsC) + indicator_update_anim.update(0) + intensity = indicator.reading.number self.play( - ShowCreation(line_b), - Write(label_b) - ) - - self.play( - ShowCreation(line_c), + SwitchOn(lsC.ambient_light), + FadeIn(lsC.lighthouse), + UpdateFromAlphaFunc( + indicator, lambda i, a : i.set_intensity(a*intensity) + ) ) + self.add(indicator_update_anim) + for point in axes.coords_to_point(5, 2), H: + self.play( + lsC.move_source_to, point, + path_arc = TAU/4, + run_time = 1.5, + ) + self.wait() + # Draw line self.play( ShowCreation(line_h), - Write(label_h) + morty.change, "pondering" ) - - - # state the IPT - theorem_location = np.array([3.,2.,0.]) - theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") - theorem_name = TextMobject("Inverse Pythagorean Theorem") - buffer = 1.2 - theorem_box = Rectangle(width = buffer*theorem.get_width(), - height = buffer*theorem.get_height()) - - theorem.move_to(theorem_location) - theorem_box.move_to(theorem_location) - theorem_name.next_to(theorem_box,UP) - + self.wait() self.play( - Write(theorem), + ShowCreation(line_c), + ShowCreation(perp_mark) ) + self.wait() + self.add_foreground_mobjects(line_c, line_h) + #Add alternate light_sources + for ls in lsA, lsB: + ls.save_state() + ls.move_to(lsC) + ls.fade(1) + self.add(ls) + self.play( + ls.restore, + run_time = 2 + ) + self.wait() + A_label.save_state() + A_label.center().fade(1) + self.play(A_label.restore) + self.wait() + self.play(ReplacementTransform( + A_label.copy().fade(1), B_label + )) + self.wait(2) + + #Compare combined of laA + lsB with lsC + rect = SurroundingRectangle(indicator, color = RED) + self.play( + FadeOut(lsA), + FadeOut(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.play(FadeOut(lsC)) + self.add(lsA, lsB) + self.play( + FadeIn(lsA), + FadeIn(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.wait(2) + + # All standard lighthouses + self.add(lsC) + self.play(FadeIn(lsC)) + self.play( + Write(identical_lighthouses_words), + LaggedStart(GrowArrow, identical_lighthouses_arrows) + ) + self.wait() + self.play(*map(FadeOut, [ + identical_lighthouses_words, + identical_lighthouses_arrows, + ])) + + #Show labels of lengths + self.play(ShowCreation(line_a), Write(label_a)) + self.wait() + self.play(ShowCreation(line_b), Write(label_b)) + self.wait() + self.play(Write(label_h)) + self.wait() + + #Write IPT + self.play(Write(theorem)) + self.wait() self.play( - ShowCreation(theorem_box), Write(theorem_name), + ShowCreation(theorem_box) ) + self.play(morty.change, "confused") + self.wait(2) + + + class IPTScene1(PiCreatureScene): From d7b84b05079c39dd21b6de4e27705ee619855d4c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 22:39:50 -0800 Subject: [PATCH 60/73] Simpler move_lighthouse_to --- topics/light.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/topics/light.py b/topics/light.py index 5fc0b268..4d19ba27 100644 --- a/topics/light.py +++ b/topics/light.py @@ -187,11 +187,12 @@ class LightSource(VMobject): self.update_shadow() def update_lighthouse(self): - new_lh = Lighthouse() - new_lh.move_to(ORIGIN) - new_lh.apply_matrix(self.rotation_matrix()) - new_lh.shift(self.get_source_point()) - self.lighthouse.submobjects = new_lh.submobjects + self.lighthouse.move_to(self.get_source_point()) + # new_lh = Lighthouse() + # new_lh.move_to(ORIGIN) + # new_lh.apply_matrix(self.rotation_matrix()) + # new_lh.shift(self.get_source_point()) + # self.lighthouse.submobjects = new_lh.submobjects def update_ambient(self): new_ambient_light = AmbientLight( @@ -416,7 +417,6 @@ class AmbientLight(VMobject): class Spotlight(VMobject): - CONFIG = { "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, @@ -445,13 +445,10 @@ class Spotlight(VMobject): w = project_along_vector(point,v) return w - def get_source_point(self): return self.source_point.get_location() - def generate_points(self): - self.submobjects = [] self.add(self.source_point) @@ -467,7 +464,6 @@ class Spotlight(VMobject): new_sector = self.new_sector(r,dr,lower_angle,upper_angle) self.add(new_sector) - def new_sector(self,r,dr,lower_angle,upper_angle): alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( @@ -504,7 +500,6 @@ class Spotlight(VMobject): else: return -absolute_angle - def viewing_angles(self,screen): screen_points = screen.get_anchors() @@ -531,7 +526,6 @@ class Spotlight(VMobject): return lower_ray, upper_ray - def opening_angle(self): l,u = self.viewing_angles(self.screen) return u - l @@ -551,7 +545,6 @@ class Spotlight(VMobject): self.update_sectors() return self - def update_sectors(self): if self.screen == None: return @@ -567,7 +560,6 @@ class Spotlight(VMobject): # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) Transform(submob, new_submob).update(1) - def dimming(self,new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha From 004d3603f25d836e8001b4c491aa7e7bce61be29 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 27 Feb 2018 22:40:37 -0800 Subject: [PATCH 61/73] IPTScene in basel2 --- active_projects/basel2.py | 392 +++++++++++++++++++++++++++++++++++++- 1 file changed, 388 insertions(+), 4 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 3dbcf12b..1bfde062 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -20,6 +20,7 @@ from topics.number_line import * from topics.numerals import * #from topics.combinatorics import * from scene import Scene +from scene.zoomed_scene import * from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * @@ -1879,13 +1880,13 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): "radius" : 15, "a" : 9, "b" : 5, + "origin_point" : 5*LEFT + 2.5*DOWN } def construct(self): MAX_OPACITY = 0.4 INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 - origin_point = 5*LEFT + 2.5*DOWN - + origin_point = self.origin_point #Morty morty = self.pi_creature @@ -1951,7 +1952,7 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): identical_lighthouses_words.shift(LEFT) identical_lighthouses_arrows = VGroup(*[ Arrow( - identical_lighthouses_words.get_bottom(), + identical_lighthouses_words.get_corner(DOWN+LEFT), ls.get_source_point(), buff = SMALL_BUFF, color = WHITE, @@ -2053,6 +2054,7 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): ) ) self.add(indicator_update_anim) + self.play(Animation(lsC), run_time = 0) #Why is this needed? for point in axes.coords_to_point(5, 2), H: self.play( lsC.move_source_to, point, @@ -2143,11 +2145,393 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): self.play(morty.change, "confused") self.wait(2) +class MathologerVideoWrapper(Scene): + def construct(self): + title = TextMobject(""" + Mathologer's excellent video on \\\\ + the many Pythagorean theorem cousins + """) + # title.scale(0.7) + title.to_edge(UP) + logo = ImageMobject("mathologer_logo") + logo.scale_to_fit_height(1) + logo.to_corner(UP+LEFT) + logo.shift(2*SPACE_WIDTH*RIGHT) + screen = ScreenRectangle(height = 5.5) + screen.next_to(title, DOWN) + + self.play( + logo.shift, 2*SPACE_WIDTH*LEFT, + LaggedStart(FadeIn, title), + run_time = 2 + ) + self.play(ShowCreation(screen)) + self.wait(5) + +class SimpleIPTProof(Scene): + def construct(self): + A = 5*RIGHT + B = 3*UP + C = ORIGIN + #Dumb and inefficient + alphas = np.linspace(0, 1, 500) + i = np.argmin(map( + lambda a : np.linalg.norm(interpolate(A, B, a)), + alphas + )) + H = interpolate(A, B, alphas[i]) + triangle = VGroup( + Line(C, A, color = BLUE), + Line(C, B, color = RED), + Line(A, B, color = WHITE), + Line(C, H, color = GREEN) + ) + for line, char in zip(triangle, ["a", "b", "c", "h"]): + label = TexMobject(char) + label.match_color(line) + vect = line.get_center() - triangle.get_center() + vect /= np.linalg.norm(vect) + label.next_to(line.get_center(), vect) + triangle.add(label) + if char == "h": + label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) + + triangle.to_corner(UP+LEFT) + self.add(triangle) + + argument_lines = VGroup( + TexMobject( + "\\text{Area} = ", + "{1 \\over 2}", "a", "b", "=", + "{1 \\over 2}", "c", "h" + ), + TexMobject("\\Downarrow"), + TexMobject("a^2", "b^2", "=", "c^2", "h^2"), + TexMobject("\\Downarrow"), + TexMobject( + "a^2", "b^2", "=", + "(", "a^2", "+", "b^2", ")", "h^2" + ), + TexMobject("\\Downarrow"), + TexMobject( + "{1 \\over ", "h^2}", "=", + "{1 \\over ", "b^2}", "+", + "{1 \\over ", "a^2}", + ), + ) + argument_lines.arrange_submobjects(DOWN) + for line in argument_lines: + line.highlight_by_tex_to_color_map({ + "a" : BLUE, + "b" : RED, + "h" : GREEN, + "Area" : WHITE, + "Downarrow" : WHITE, + }) + all_equals = line.get_parts_by_tex("=") + if all_equals: + line.alignment_mob = all_equals[-1] + else: + line.alignment_mob = line[0] + line.shift(-line.alignment_mob.get_center()[0]*RIGHT) + argument_lines.next_to(triangle, RIGHT) + argument_lines.to_edge(UP) + + prev_line = argument_lines[0] + self.play(FadeIn(prev_line)) + for arrow, line in zip(argument_lines[1::2], argument_lines[2::2]): + line.save_state() + line.shift( + prev_line.alignment_mob.get_center() - \ + line.alignment_mob.get_center() + ) + line.fade(1) + self.play( + line.restore, + GrowFromPoint(arrow, arrow.get_top()) + ) + self.wait() + prev_line = line + +class WeCanHaveMoreFunThanThat(TeacherStudentsScene): + def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.teacher_says( + "We can have \\\\ more fun than that!", + target_mode = "hooray" + ) + self.change_student_modes(*3*["erm"], look_at_arg = point) + self.wait() + self.play( + RemovePiCreatureBubble( + self.teacher, + target_mode = "raise_right_hand", + look_at_arg = point, + ), + self.get_student_changes(*3*["pondering"], look_at_arg = point) + ) + self.wait(3) + +class IPTScene(TwoLightSourcesScene, ZoomedScene): + CONFIG = { + "max_opacity_ambient" : 0.2, + "num_levels" : 200, + } + def construct(self): + #Copy pasting from TwoLightSourcesScene....Very bad... + origin_point = self.origin_point + self.remove(self.pi_creature) + + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, + ) + axes.shift(origin_point) + + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] + ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) + + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), + max_opacity_ambient = self.max_opacity_ambient, + ) + lsA.lighthouse.scale(0.5, about_edge = UP) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) + + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) + + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) + + label_a = TexMobject("a") + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) + label_b = TexMobject("b") + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) + label_h = TexMobject("h") + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + + # Mini triangle + m_hyp_a = Line(H, A) + m_a = line_a.copy() + m_hyp_b = Line(H, B) + m_b = line_b.copy() + mini_triangle = VGroup(m_a, m_hyp_a, m_b, m_hyp_b) + mini_triangle.set_stroke(width = 5) + + mini_triangle.generate_target() + mini_triangle.target.scale(0.1, about_point = origin_point) + for part, part_target in zip(mini_triangle, mini_triangle.target): + part.target = part_target + + # Screen label + screen_word = TextMobject("Screen") + screen_word.next_to(mini_triangle.target, UP+RIGHT, LARGE_BUFF) + screen_arrow = Arrow( + screen_word.get_bottom(), + mini_triangle.target.get_center(), + color = WHITE, + ) + + # Setup spotlights + spotlight_a = VGroup() + spotlight_a.screen = m_hyp_a + spotlight_b = VGroup() + spotlight_b.screen = m_hyp_b + for spotlight in spotlight_a, spotlight_b: + spotlight.get_source_point = lsC.get_source_point + dr = lsC.ambient_light.radius/lsC.ambient_light.num_levels + def update_spotlight(spotlight): + spotlight.submobjects = [] + source_point = spotlight.get_source_point() + c1, c2 = spotlight.screen.get_start(), spotlight.screen.get_end() + distance = max( + np.linalg.norm(c1 - source_point), + np.linalg.norm(c2 - source_point), + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for a1, a2 in zip(alphas, alphas[1:]): + spotlight.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = YELLOW, + fill_opacity = 2*lsC.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + + def update_spotlights(spotlights): + for spotlight in spotlights: + update_spotlight(spotlight) + + def get_spotlight_triangle(spotlight): + sp = spotlight.get_source_point() + c1 = spotlight.screen.get_start() + c2 = spotlight.screen.get_end() + return Polygon( + sp, c1, c2, + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 0.5, + ) + + spotlights = VGroup(spotlight_a, spotlight_b) + spotlights_update_anim = ContinualUpdateFromFunc( + spotlights, update_spotlights + ) + + # Add components + self.add( + axes, + lsA.ambient_light, + lsB.ambient_light, + lsC.ambient_light, + line_c, + ) + self.add_foreground_mobjects( + lsA.lighthouse, A_label, + lsB.lighthouse, B_label, + lsC.lighthouse, line_h, + ) + + # Show miniature triangle + self.play(ShowCreation(mini_triangle, submobject_mode = "all_at_once")) + self.play( + MoveToTarget(mini_triangle), + run_time = 2, + ) + self.add_foreground_mobject(mini_triangle) + + # Show beams of light + self.play( + Write(screen_word), + GrowArrow(screen_arrow), + ) + self.wait() + spotlights_update_anim.update(0) + self.play( + LaggedStart(FadeIn, spotlight_a), + LaggedStart(FadeIn, spotlight_b), + Animation(screen_arrow), + ) + self.add(spotlights_update_anim) + self.play(*map(FadeOut, [screen_word, screen_arrow])) + self.wait() + + # Reshape screen + m_hyps = [m_hyp_a, m_hyp_b] + for hyp, line in (m_hyp_a, m_a), (m_hyp_b, m_b): + hyp.save_state() + hyp.alt_version = line.copy() + hyp.alt_version.highlight(WHITE) + + for x in range(2): + self.play(*[ + Transform(m, m.alt_version) + for m in m_hyps + ]) + self.wait() + self.play(*[m.restore for m in m_hyps]) + self.wait() + + # Show spotlight a key point + def show_key_point(spotlight, new_point): + screen = spotlight.screen + update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) + self.play( + Transform(screen, screen.alt_version), + update_spotlight_anim, + ) + triangle = get_spotlight_triangle(spotlight) + for x in range(3): + anims = [] + if x > 0: + anims.append(FadeOut(triangle.copy())) + anims.append(GrowFromPoint(triangle, H)) + self.play(*anims) + self.play(FadeOut(triangle)) + self.play(screen.restore, update_spotlight_anim) + self.wait() + self.play( + lsC.move_source_to, new_point, + Transform(screen, screen.alt_version), + update_spotlight_anim, + run_time = 2 + ) + self.wait() + self.play( + lsC.move_source_to, H, + screen.restore, + update_spotlight_anim, + run_time = 2 + ) + self.wait() + + self.remove(spotlights_update_anim) + self.add(spotlight_b) + self.play(*map(FadeOut, [ + spotlight_a, lsA.ambient_light, lsB.ambient_light + ])) + show_key_point(spotlight_b, A) + self.play( + FadeOut(spotlight_b), + FadeIn(spotlight_a), + ) + show_key_point(spotlight_a, B) + self.wait() -class IPTScene1(PiCreatureScene): +class IPTScene1(Scene): def construct(self): show_detail = True From 7e4bd54da784f2361202f1c82c15fb5993de9f8a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 09:34:38 -0800 Subject: [PATCH 62/73] Tweaks to IPTScene --- active_projects/basel2.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 1bfde062..bb920165 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -2483,6 +2483,17 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): self.wait() # Show spotlight a key point + def show_beaming_light(spotlight): + triangle = get_spotlight_triangle(spotlight) + for x in range(3): + anims = [] + if x > 0: + anims.append(FadeOut(triangle.copy())) + anims.append(GrowFromPoint(triangle, triangle.points[0])) + self.play(*anims) + self.play(FadeOut(triangle)) + pass + def show_key_point(spotlight, new_point): screen = spotlight.screen update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) @@ -2490,14 +2501,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): Transform(screen, screen.alt_version), update_spotlight_anim, ) - triangle = get_spotlight_triangle(spotlight) - for x in range(3): - anims = [] - if x > 0: - anims.append(FadeOut(triangle.copy())) - anims.append(GrowFromPoint(triangle, H)) - self.play(*anims) - self.play(FadeOut(triangle)) + show_beaming_light(spotlight) self.play(screen.restore, update_spotlight_anim) self.wait() self.play( @@ -2506,6 +2510,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): update_spotlight_anim, run_time = 2 ) + show_beaming_light(spotlight) self.wait() self.play( lsC.move_source_to, H, From 16cdc0100369751ffcdef4e95a61ac280046a93c Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 28 Feb 2018 18:35:28 +0100 Subject: [PATCH 63/73] even more tweaking, added ThumbnailScene --- active_projects/basel.py | 315 ++++++++++++++++++++++++++++----------- 1 file changed, 232 insertions(+), 83 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 80cf903d..c11b6989 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -43,7 +43,7 @@ NUM_CONES = 7 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -NUM_LEVELS = 150 +NUM_LEVELS = 15 AMBIENT_FULL = 0.8 AMBIENT_DIMMED = 0.5 AMBIENT_SCALE = 2.0 @@ -56,6 +56,26 @@ SPOTLIGHT_RADIUS = 20.0 LIGHT_COLOR = YELLOW DEGREES = TAU/360 + +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 + 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) @@ -2355,6 +2375,7 @@ class InscribedAngleScene(ThreeDScene): self.unzoomable_mobs.shift,v ) + self.force_skipping() self.zoomable_mobs = VMobject() self.unzoomable_mobs = VMobject() @@ -2391,7 +2412,7 @@ class InscribedAngleScene(ThreeDScene): # first lighthouse original_op_func = inverse_quadratic(LIGHT_MAX_INT,AMBIENT_SCALE,LIGHT_CUTOFF) - ls0 = LightSource(opacity_function = original_op_func, num_levels = 150) + ls0 = LightSource(opacity_function = original_op_func, num_levels = NUM_LEVELS) ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) @@ -2734,6 +2755,7 @@ class InscribedAngleScene(ThreeDScene): self.new_legs_2 = [] self.new_hypotenuses = [] + self.revert_to_original_skipping_status() construction_step(0) indicator_wiggle() @@ -3046,7 +3068,6 @@ class PondScene(ThreeDScene): RIGHT_ANGLE_SIZE = 0.3 - self.cumulated_zoom_factor = 1 @@ -3293,7 +3314,7 @@ class PondScene(ThreeDScene): return position - def split_light_source(i, step, show_steps = True, run_time = 1): + def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -3324,7 +3345,9 @@ class PondScene(ThreeDScene): ls2 = ls1.copy() - self.add(ls2) + if animate == True: + self.add(ls2) + self.additional_light_sources.append(ls2) # check if the light sources are on screen @@ -3334,7 +3357,7 @@ class PondScene(ThreeDScene): onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) - if show_animation: + if show_animation or animate: self.play( ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), @@ -3411,66 +3434,94 @@ class PondScene(ThreeDScene): self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] - self.new_hypotenuses = [] + self.new_hypotenuses = [] - for i in range(2**n): - - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time + 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), ) - 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() # collect the newly created mobs (in arrays) @@ -3627,8 +3678,44 @@ class PondScene(ThreeDScene): 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) + + + # 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) + + + return #self.revert_to_original_skipping_status() @@ -3836,8 +3923,8 @@ class FinalSumManipulationScene(PiCreatureScene): self.wait() ls_template = LightSource( - radius = 4, - num_levels = 40, + radius = 1, + num_levels = 10, max_opacity_ambient = 0.5, opacity_function = inverse_quadratic(1,0.75,1) ) @@ -3854,14 +3941,18 @@ class FinalSumManipulationScene(PiCreatureScene): number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, - numbers_to_show = odd_range, + #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_LARGE_BUFF, - include_tip = True + 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: @@ -3871,7 +3962,7 @@ class FinalSumManipulationScene(PiCreatureScene): odd_lights.add(ls) self.play( - ShowCreation(self.number_line1), + ShowCreation(self.number_line1, run_time = 5), ) self.wait() @@ -3899,6 +3990,7 @@ class FinalSumManipulationScene(PiCreatureScene): 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)) @@ -3907,6 +3999,7 @@ class FinalSumManipulationScene(PiCreatureScene): 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() @@ -3918,18 +4011,30 @@ class FinalSumManipulationScene(PiCreatureScene): full_lights.add(ls) self.play( - ShowCreation(self.number_line2), + 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), + ) - 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), - ) + 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), + # ) @@ -3964,13 +4069,13 @@ class FinalSumManipulationScene(PiCreatureScene): self.play( - Transform(even_lights,full_lights) + Transform(even_lights,full_lights, run_time = 2) ) self.wait() - for i in range(5): + for i in range(6): self.play( Transform(even_lights[i], even_lights_copy[i]) ) @@ -4013,11 +4118,6 @@ class FinalSumManipulationScene(PiCreatureScene): four_thirds_label.scale(0.7) four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) - self.play( - ReplacementTransform(three_quarters_arrow, four_thirds_arrow), - ReplacementTransform(three_quarters_label, four_thirds_label) - ) - self.wait() self.play( FadeOut(quarter_label), @@ -4028,6 +4128,11 @@ class FinalSumManipulationScene(PiCreatureScene): ) 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: @@ -4041,6 +4146,8 @@ class FinalSumManipulationScene(PiCreatureScene): term.move_to(self.number_line2.number_to_point(i)) full_terms.add(term) + #return + self.play( FadeOut(self.number_line1), FadeOut(odd_lights), @@ -4053,6 +4160,7 @@ class FinalSumManipulationScene(PiCreatureScene): 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, @@ -4198,6 +4306,47 @@ class ArcHighlightOverlayScene(Scene): +class ThumbnailScene(Scene): + + def construct(self): + + equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") + equation.scale(2.) + equation.to_edge(UP) + q_mark = TexMobject("?").scale(5) + q_mark.next_to(equation) + + lake_radius = 2 + lake_center = DOWN + op_scale = 0.5 + + lake = Circle( + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + radius = lake_radius, + stroke_color = LAKE_STROKE_COLOR, + stroke_width = LAKE_STROKE_WIDTH, + ) + lake.move_to(lake_center) + + for i in range(8): + theta = -TAU/4 + (i + 0.5) * TAU/8 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource( + radius = 5.0, + num_levels = 100, + max_opacity_ambient = 0.8, + opacity_function = inverse_quadratic(1,op_scale,1) + ) + ls.move_source_to(pos) + lake.add(ls.ambient_light, ls.lighthouse) + + self.add(lake) + + self.add(equation) #, q_mark) + + self.wait() + From 56f74388a016e4d042b1c694c72c35d92d729eb6 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 1 Mar 2018 00:15:01 +0100 Subject: [PATCH 64/73] PondScene, created InfiniteCircleScene --- active_projects/basel.py | 212 ++++++++++++++++++++++++++++++--------- 1 file changed, 166 insertions(+), 46 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index c11b6989..73089baa 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -3070,6 +3070,7 @@ class PondScene(ThreeDScene): self.cumulated_zoom_factor = 1 + #self.force_skipping() def right_angle(pointA, pointB, pointC, size = 1): @@ -3163,7 +3164,7 @@ class PondScene(ThreeDScene): self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Randolph().scale(0.3) + morty = Randolph(color = MAROON_D).scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -3436,6 +3437,7 @@ class PondScene(ThreeDScene): self.new_legs_2 = [] self.new_hypotenuses = [] + # WE ALWAYS USE THIS CASE BRANCH if simultaneous_splitting == False: for i in range(2**n): @@ -3497,6 +3499,9 @@ class PondScene(ThreeDScene): self.wait() + + + # WE DON'T USE THIS CASE BRANCH ANYMORE else: # simultaneous splitting old_lake = self.outer_lake.copy() @@ -3685,14 +3690,16 @@ class PondScene(ThreeDScene): # simultaneous_splitting = True) + #print "starting simultaneous expansion" # simultaneous expansion of light sources from now on self.play(FadeOut(self.inner_lake)) for n in range(3,max_it + 1): - + print "working on n = ", n, "..." new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) - for ls in self.light_sources_array: + for (i,ls) in enumerate(self.light_sources_array[:2**n]): + #print i lsp = ls.copy() self.light_sources.add(lsp) self.add(lsp) @@ -3701,26 +3708,32 @@ class PondScene(ThreeDScene): new_lake_center = new_lake.get_center() new_lake_radius = 0.5 * new_lake.get_width() - shift_list = (Transform(self.outer_lake,new_lake),) - + shift_list = [Transform(self.outer_lake,new_lake)] + #print shift_list for i in range(2**n): - theta = -TAU/4 + (i + 0.5) * TAU / 2**n + #print "===========" + #print i + theta = -TAU/4 + (i + 0.5) * TAU / 2**(n+1) 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) + ls1 = self.light_sources.submobjects[i] + ls2 = self.light_sources.submobjects[i+2**n] + shift_list.append(ls1.move_source_to) + shift_list.append(pos1) + shift_list.append(ls2.move_source_to) + shift_list.append(pos2) + #print shift_list self.play(*shift_list) + print "...done" - return - #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it - MAX_N = 17 + MAX_N = 7 origin_point = self.obs_dot.get_center() @@ -3731,16 +3744,17 @@ class PondScene(ThreeDScene): 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), + numbers_with_elongated_ticks = [], + numbers_to_show = range(-MAX_N,MAX_N + 1),#,2), unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, + tick_size = LAKE_STROKE_WIDTH, + number_scale_val = 3, line_to_number_buff = LARGE_BUFF, label_direction = UP, ).shift(scale * 2.5 * DOWN) - self.number_line.label_direction = DOWN - + self.number_line.tick_marks.fade(1) self.number_line_labels = self.number_line.get_number_mobjects() self.wait() @@ -3757,25 +3771,29 @@ class PondScene(ThreeDScene): self.add(pond_sources) self.remove(self.light_sources) + for ls in self.light_sources_array: + self.remove(ls) self.outer_lake.rotate(TAU/8) # open sea open_sea = Rectangle( - width = 20 * scale, - height = 10 * scale, + width = 200 * scale, + height = 100 * 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.revert_to_original_skipping_status() self.play( ReplacementTransform(pond_sources,nl_sources), + #FadeOut(pond_sources), + #FadeIn(nl_sources), ReplacementTransform(self.outer_lake,open_sea), - FadeOut(self.inner_lake) + #FadeOut(self.inner_lake) ) self.play(FadeIn(self.number_line)) @@ -3797,11 +3815,12 @@ class PondScene(ThreeDScene): 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, + indicator.move_to, origin_point + scale * UP + 2 * UP, + indicator_reading.move_to, origin_point + scale * UP + 2 * UP, FadeOut(open_sea), FadeOut(morty), - FadeIn(self.number_line_labels) + FadeIn(self.number_line_labels), + FadeIn(self.number_line.tick_marks), ) two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ @@ -3821,13 +3840,20 @@ class PondScene(ThreeDScene): 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() + + for ls in nl_sources.submobjects: + if ls.get_source_point()[0] < 0: + self.remove_foreground_mobject(ls.ambient_light) + self.remove(ls.ambient_light) + else: + self.add_foreground_mobject(ls.ambient_light) + + for label in self.number_line_labels.submobjects: + if label.get_center()[0] <= 0: + self.remove(label) + - self.wait() covering_rectangle = Rectangle( width = SPACE_WIDTH * scale, @@ -3837,8 +3863,8 @@ class PondScene(ThreeDScene): fill_opacity = 1, ) covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) - for i in range(10): - self.add_foreground_mobject(nl_sources.submobjects[i]) + #for i in range(10): + # self.add_foreground_mobject(nl_sources.submobjects[i]) self.add_foreground_mobject(indicator) self.add_foreground_mobject(indicator_reading) @@ -3869,7 +3895,7 @@ class PondScene(ThreeDScene): 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) + randy = Randolph(color = MAROON_D).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) self.play(FadeIn(randy)) self.play(randy.change,"happy") self.play(randy.change,"hooray") @@ -3917,8 +3943,8 @@ class FinalSumManipulationScene(PiCreatureScene): sum_vertical_spacing = 1.5 randy = self.get_primary_pi_creature() - randy.highlight(MAROON_E) - randy.color = MAROON_E + randy.highlight(MAROON_D) + randy.color = MAROON_D randy.scale(0.7).flip().to_edge(DOWN + LEFT) self.wait() @@ -4311,14 +4337,17 @@ class ThumbnailScene(Scene): def construct(self): equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") - equation.scale(2.) - equation.to_edge(UP) - q_mark = TexMobject("?").scale(5) - q_mark.next_to(equation) + equation.scale(1.5) + equation.move_to(1.5 * UP) + q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) + q_mark.next_to(equation, DOWN, buff = 1.5) + #equation.move_to(2 * UP) + #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #q_mark.next_to(equation, DOWN, buff = 1) - lake_radius = 2 - lake_center = DOWN - op_scale = 0.5 + lake_radius = 6 + lake_center = ORIGIN + op_scale = 0.4 lake = Circle( fill_color = LAKE_COLOR, @@ -4329,13 +4358,13 @@ class ThumbnailScene(Scene): ) lake.move_to(lake_center) - for i in range(8): - theta = -TAU/4 + (i + 0.5) * TAU/8 + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) ls = LightSource( - radius = 5.0, - num_levels = 100, - max_opacity_ambient = 0.8, + radius = 15.0, + num_levels = 150, + max_opacity_ambient = 1.0, opacity_function = inverse_quadratic(1,op_scale,1) ) ls.move_source_to(pos) @@ -4343,7 +4372,87 @@ class ThumbnailScene(Scene): self.add(lake) - self.add(equation) #, q_mark) + self.add(equation, q_mark) + + self.wait() + + + + +class InfiniteCircleScene(PiCreatureScene): + + def construct(self): + + morty = self.get_primary_pi_creature() + morty.highlight(MAROON_D).flip() + morty.color = MAROON_D + morty.scale(0.5).move_to(ORIGIN) + + arrow = Arrow(ORIGIN, 2.4 * RIGHT) + dot = Dot(color = BLUE).next_to(arrow) + ellipsis = TexMobject("\dots") + + infsum = VGroup() + infsum.add(ellipsis.copy()) + + for i in range(3): + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(dot.copy().next_to(infsum.submobjects[-1])) + + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) + + infsum.next_to(morty,DOWN, buff = 1) + + self.wait() + self.play( + LaggedStart(FadeIn,infsum,lag_ratio = 0.2) + ) + self.wait() + + A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT + B = A + RIGHT + 1.3 * UP + 0.025 * LEFT + right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, + stroke_width = 8).apply_complex_function(np.exp) + right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) + right_tip_line = Arrow(B - UP, B, color = WHITE) + right_tip_line.add_tip() + right_tip = right_tip_line.get_tip() + right_tip.set_fill(color = YELLOW) + right_arc.add(right_tip) + + + C = B + 3.2 * UP + right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, + stroke_width = 8) + + ru_arc = right_arc.copy().rotate(angle = TAU/4) + ru_arc.remove(ru_arc.submobjects[-1]) + ru_arc.to_edge(UP+RIGHT, buff = 0.15) + + D = np.array([5.85, 3.85,0]) + E = np.array([-D[0],D[1],0]) + up_line = DashedLine(D, E, stroke_color = YELLOW, + stroke_width = 8) + + lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) + left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) + + left_arc = right_arc.copy().rotate(-TAU/4) + left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) + + right_arc.shift(0.2 * RIGHT) + right_line.shift(0.2 * RIGHT) + + self.play(FadeIn(right_arc)) + self.play(ShowCreation(right_line)) + self.play(FadeIn(ru_arc)) + self.play(ShowCreation(up_line)) + self.play(FadeIn(lu_arc)) + self.play(ShowCreation(left_line)) + self.play(FadeIn(left_arc)) + + self.wait() @@ -4353,3 +4462,14 @@ class ThumbnailScene(Scene): + + + + + + + + + + + From 74f3beca7c5f033fd29b7a71d9747060458cc082 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 21:39:25 -0800 Subject: [PATCH 65/73] Makes Patron names show up lower to start --- topics/common_scenes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 3e7369c1..4d1354f6 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -176,7 +176,7 @@ class PatreonEndScreen(PatreonThanks): aligned_edge = UP, ) columns.scale_to_fit_width(total_width - 1) - columns.next_to(black_rect, DOWN, LARGE_BUFF) + columns.next_to(black_rect, DOWN, 3*LARGE_BUFF) columns.to_edge(RIGHT) self.play( From ebc418d2bdb3c7af7f5522d87f527cd8c786919d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 28 Feb 2018 21:39:38 -0800 Subject: [PATCH 66/73] Many additions while editing video together --- active_projects/basel2.py | 1762 +++++++++++++++++++++++++++++-------- 1 file changed, 1374 insertions(+), 388 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index bb920165..fb7239d9 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- from helpers import * @@ -25,8 +26,9 @@ from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.three_dimensions import * - from topics.light import * +from topics.objects import * +from topics.common_scenes import * import types import functools @@ -233,8 +235,8 @@ class ThinkAboutPondScene(PiCreatureScene): randy = self.pi_creature randy.to_corner(DOWN+LEFT) bubble = ThoughtBubble( - width = 9, - height = 5, + width = 11, + height = 7, ) circles = bubble[:3] angle = -15*DEGREES @@ -243,7 +245,6 @@ class ThinkAboutPondScene(PiCreatureScene): for circle in circles: circle.rotate(-angle) bubble.pin_to(randy) - bubble.shift_onto_screen() self.play( randy.change, "thinking", @@ -1133,12 +1134,29 @@ class ThatJustSeemsUseless(TeacherStudentsScene): self.wait() class AskAboutBrightness(TeacherStudentsScene): + CONFIG = { + "num_levels" : 200, + "radius" : 10, + } def construct(self): + light_source = LightSource( + num_levels = self.num_levels, + radius = self.radius, + opacity_function = inverse_quadratic(1,2,1), + ) + light_source.lighthouse.scale(0.5, about_edge = UP) + light_source.move_source_to(5*LEFT + 2*UP) + + self.add_foreground_mobjects(self.pi_creatures) self.student_says( - "What do you mean \\\\ by ``brightness''?" + "What do you mean \\\\ by ``brightness''?", + added_anims = [ + SwitchOn(light_source.ambient_light), + Animation(light_source.lighthouse) + ] ) self.play(self.teacher.change, "happy") - self.wait(3) + self.wait(4) class IntroduceScreen(Scene): CONFIG = { @@ -1723,6 +1741,7 @@ class InverseSquareLaw(ThreeDScene): phi = 75*DEGREES, theta = -155*DEGREES, distance = 7, + run_time = 10, ) self.begin_ambient_camera_rotation(rate = -0.01) self.add(ContinualAnimation(nine_copies)) @@ -1751,6 +1770,40 @@ class InverseSquareLaw(ThreeDScene): anims += added_anims self.play(*anims, run_time = 2) +class OtherInstanceOfInverseSquareLaw(Scene): + def construct(self): + title = TextMobject("Where the inverse square law shows up") + title.to_edge(UP) + h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH) + h_line.next_to(title, DOWN) + self.add(title, h_line) + + items = VGroup(*[ + TextMobject("- %s"%s).scale(1) + for s in [ + "Heat", "Sound", "Radio waves", "Electric fields", + ] + ]) + items.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) + items.next_to(h_line, DOWN, LARGE_BUFF) + items.to_edge(LEFT) + + dot = Dot() + dot.move_to(4*RIGHT) + self.add(dot) + def get_broadcast(): + return Broadcast(dot, big_radius = 5, run_time = 5) + + self.play( + LaggedStart(FadeIn, items, run_time = 4, lag_ratio = 0.7), + Succession(*[ + get_broadcast() + for x in range(2) + ]) + ) + self.play(get_broadcast()) + self.wait() + class ScreensIntroWrapper(TeacherStudentsScene): def construct(self): point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) @@ -2379,6 +2432,21 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): color = WHITE, ) + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + # Setup spotlights spotlight_a = VGroup() spotlight_a.screen = m_hyp_a @@ -2440,6 +2508,7 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): lsA.lighthouse, A_label, lsB.lighthouse, B_label, lsC.lighthouse, line_h, + theorem, theorem_name, theorem_box, ) # Show miniature triangle @@ -2533,259 +2602,177 @@ class IPTScene(TwoLightSourcesScene, ZoomedScene): show_key_point(spotlight_a, B) self.wait() - - - -class IPTScene1(Scene): +class HomeworkWrapper(Scene): def construct(self): + title = TextMobject("Homework") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.center() + self.add(title) + self.play(ShowCreation(screen)) + self.wait(5) - show_detail = True - - SCREEN_SCALE = 0.1 - SCREEN_THICKNESS = 0.2 - - - # use the following for the zoomed inset - if show_detail: - self.camera.space_shape = (0.02 * SPACE_HEIGHT, 0.02 * SPACE_WIDTH) - self.camera.space_center = C - SCREEN_SCALE = 0.01 - SCREEN_THICKNESS = 0.02 - - - - - - morty = self.get_primary_pi_creature() - self.remove(morty) - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - - if not show_detail: - self.add_foreground_mobject(morty) - - stroke_width = 6 - line_a = Line(B,C,stroke_width = stroke_width) - line_b = Line(A,C,stroke_width = stroke_width) - line_c = Line(A,B,stroke_width = stroke_width) - line_h = Line(C,H,stroke_width = stroke_width) - - length_a = line_a.get_length() - length_b = line_b.get_length() - length_c = line_c.get_length() - length_h = line_h.get_length() - - label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) - label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) - label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) - - self.add_foreground_mobject(line_a) - self.add_foreground_mobject(line_b) - self.add_foreground_mobject(line_c) - self.add_foreground_mobject(line_h) - self.add_foreground_mobject(label_a) - self.add_foreground_mobject(label_b) - self.add_foreground_mobject(label_h) - - if not show_detail: - self.add_foreground_mobject(morty) - - ls1 = LightSource(radius = 10) - ls1.move_source_to(B) - - self.add(ls1.lighthouse) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - +class HeresWhereThingsGetGood(TeacherStudentsScene): + def construct(self): + self.teacher_says("Now for the \\\\ good part!") + self.change_student_modes(*["hooray"]*3) + self.change_student_modes(*["happy"]*3) self.wait() - # adding the first screen - - screen_width_a = SCREEN_SCALE * length_a - screen_width_b = SCREEN_SCALE * length_b - screen_width_ap = screen_width_a * length_a / length_c - screen_width_bp = screen_width_b * length_b / length_c - screen_width_c = SCREEN_SCALE * length_c - - screen_thickness_a = SCREEN_THICKNESS - screen_thickness_b = SCREEN_THICKNESS - - screen1 = Rectangle(width = screen_width_b, - height = screen_thickness_b, - stroke_width = 0, - fill_opacity = 1.0) - screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) - - if not show_detail: - self.add_foreground_mobject(morty) - - self.play( - FadeIn(screen1) - ) - self.add_foreground_mobject(screen1) - - ls1.set_screen(screen1) - screen_tracker = ScreenTracker(ls1) - self.add(screen_tracker) - #self.add(ls1.shadow) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - - self.play( - SwitchOn(ls1.spotlight), - SwitchOff(ls1.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen1p = screen1.deepcopy() - screen1pp = screen1.deepcopy() - #self.add(screen1p) - angle = np.arccos(length_b / length_c) - - screen1p.stretch_to_fit_width(screen_width_bp) - screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) - screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) - - - self.play( - ls1.move_source_to,H, - Transform(screen1,screen1p) - ) - - # add and move the second light source and screen - ls2 = ls1.deepcopy() - ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, - height = screen_thickness_a, - stroke_width = 0, - fill_opacity = 1.0) - screen2.rotate(-TAU/4) - screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) - - self.play( - FadeIn(screen2) - ) - self.add_foreground_mobject(screen2) - - if not show_detail: - self.add_foreground_mobject(morty) - - # the same scene adding sequence as before - ls2.set_screen(screen2) - screen_tracker2 = ScreenTracker(ls2) - self.add(screen_tracker2) - - if not show_detail: - self.play( - SwitchOn(ls2.ambient_light) - ) - - self.wait() - - self.play( - SwitchOn(ls2.spotlight), - SwitchOff(ls2.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen2p = screen2.deepcopy() - screen2pp = screen2.deepcopy() - angle = np.arccos(length_a / length_c) - screen2p.stretch_to_fit_height(screen_width_ap) - screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) - screen2p.rotate(angle, about_point = C + screen_width_a * UP) - # we can reuse the translation vector - # screen2p.shift(vector) - - self.play( - ls2.move_source_to,H, - SwitchOff(ls1.ambient_light), - Transform(screen2,screen2p) - ) - - # now transform both screens back - self.play( - Transform(screen1, screen1pp), - Transform(screen2, screen2pp), - ) - -class IPTScene2(Scene): - +class DiameterTheorem(TeacherStudentsScene): def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) - intensity1 = 0.3 - intensity2 = 0.2 - formula_scale = 01.2 - indy_radius = 1 + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) - indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy1.set_intensity(intensity1) - reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) - indy1.add(reading1) + diameter_word = TextMobject("Diameter") + diameter_word.next_to(center, DOWN, SMALL_BUFF) - indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy2.set_intensity(intensity2) - reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) - indy2.add(reading2) - - indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy3.set_intensity(intensity1 + intensity2) - reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) - indy3.add(reading3) - - plus_sign = TexMobject("+").scale(formula_scale) - equals_sign = TexMobject("=").scale(formula_scale) - - plus_sign.next_to(indy1, RIGHT) - indy2.next_to(plus_sign, RIGHT) - equals_sign.next_to(indy2, RIGHT) - indy3.next_to(equals_sign, RIGHT) - - - formula = VGroup( - indy1, plus_sign, indy2, equals_sign, indy3 + point = VectorizedPoint(circle.get_top()) + triangle = Polygon(LEFT, RIGHT, UP) + triangle.set_stroke(BLUE) + triangle.set_fill(WHITE, 0.5) + def update_triangle(triangle): + triangle.set_points_as_corners([ + circle.get_left(), circle.get_right(), + point.get_center(), circle.get_left(), + ]) + triangle_update_anim = ContinualUpdateFromFunc( + triangle, update_triangle ) + triangle_update_anim.update(0) - formula.move_to(ORIGIN) + perp_mark = VMobject() + perp_mark.set_points_as_corners([LEFT, DOWN, RIGHT]) + perp_mark.shift(DOWN) + perp_mark.scale(0.15, about_point = ORIGIN) + perp_mark.shift(point.get_center()) + perp_mark.add(point.copy()) - self.play(FadeIn(indy1)) - self.play(FadeIn(plus_sign), FadeIn(indy2)) - self.play(FadeIn(equals_sign), FadeIn(indy3)) + self.play( + self.teacher.change, "raise_right_hand", + DrawBorderThenFill(triangle), + Write(diameter_word), + ) + self.play( + ShowCreation(perp_mark), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(perp_mark) + self.add(triangle_update_anim) + for angle in 0.2*TAU, -0.4*TAU, 0.3*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) - buffer = 1.5 - box = Rectangle(width = formula.get_width() * buffer, - height = formula.get_height() * buffer) - box.move_to(formula) - text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) - text.next_to(box,UP) - self.play(ShowCreation(box),Write(text)) + perp_mark.generate_target() + perp_mark.target.rotate(angle/2) + perp_mark.target.shift( + point.target.get_center() - \ + perp_mark.target[1].get_center() + ) -class PondScene(Scene): + self.play( + MoveToTarget(point), + MoveToTarget(perp_mark), + path_arc = angle, + run_time = 3, + ) +class InscribedeAngleThreorem(TeacherStudentsScene): + def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) + + title = TextMobject("Inscribed angle \\\\ theorem") + title.to_corner(UP+LEFT) + self.add(title) + + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) + + point = VectorizedPoint(circle.get_left()) + shape = Polygon(UP+LEFT, ORIGIN, DOWN+LEFT, RIGHT) + shape.set_stroke(BLUE) + def update_shape(shape): + shape.set_points_as_corners([ + point.get_center(), + circle.point_from_proportion(7./8), + circle.get_center(), + circle.point_from_proportion(1./8), + point.get_center(), + ]) + shape_update_anim = ContinualUpdateFromFunc( + shape, update_shape + ) + shape_update_anim.update(0) + + angle_mark = Arc(start_angle = -TAU/8, angle = TAU/4) + angle_mark.scale(0.3, about_point = ORIGIN) + angle_mark.shift(circle.get_center()) + theta = TexMobject("\\theta").highlight(RED) + theta.next_to(angle_mark, RIGHT, MED_SMALL_BUFF) + angle_mark.match_color(theta) + + half_angle_mark = Arc(start_angle = -TAU/16, angle = TAU/8) + half_angle_mark.scale(0.3, about_point = ORIGIN) + half_angle_mark.shift(point.get_center()) + half_angle_mark.add(point.copy()) + theta_halves = TexMobject("\\theta/2").highlight(GREEN) + theta_halves.scale(0.7) + half_angle_mark.match_color(theta_halves) + theta_halves_update = UpdateFromFunc( + theta_halves, lambda m : m.move_to(interpolate( + point.get_center(), + half_angle_mark.point_from_proportion(0.5), + 2.5, + )) + ) + theta_halves_update.update(0) + + self.play( + self.teacher.change, "raise_right_hand", + ShowCreation(shape, rate_func = None), + ) + self.play(*map(FadeIn, [angle_mark, theta])) + self.play( + ShowCreation(half_angle_mark), + Write(theta_halves), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(half_angle_mark, theta_halves) + self.add(shape_update_anim) + for angle in 0.25*TAU, -0.4*TAU, 0.3*TAU, -0.35*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) + + half_angle_mark.generate_target() + half_angle_mark.target.rotate(angle/2) + half_angle_mark.target.shift( + point.target.get_center() - \ + half_angle_mark.target[1].get_center() + ) + + self.play( + MoveToTarget(point), + MoveToTarget(half_angle_mark), + theta_halves_update, + path_arc = angle, + run_time = 3, + ) + +class PondScene(ThreeDScene): def construct(self): BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] + OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) LAKE0_RADIUS = 1.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 + LIGHTHOUSE_HEIGHT = 0.5 LAKE_COLOR = BLUE LAKE_OPACITY = 0.15 LAKE_STROKE_WIDTH = 5.0 @@ -2793,12 +2780,89 @@ class PondScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 2.5 + LIGHT_CUTOFF = 1 + + RIGHT_ANGLE_SIZE = 0.3 + + self.cumulated_zoom_factor = 1 + + def right_angle(pointA, pointB, pointC, size = 1): + + v1 = pointA - pointB + v1 = size * v1/np.linalg.norm(v1) + v2 = pointC - pointB + v2 = size * v2/np.linalg.norm(v2) + + P = pointB + Q = pointB + v1 + R = Q + v2 + S = R - v1 + angle_sign = VMobject() + angle_sign.set_points_as_corners([P,Q,R,S,P]) + angle_sign.mark_paths_closed = True + angle_sign.set_fill(color = WHITE, opacity = 1) + angle_sign.set_stroke(width = 0) + return angle_sign + + def triangle(pointA, pointB, pointC): + + mob = VMobject() + mob.set_points_as_corners([pointA, pointB, pointC, pointA]) + mob.mark_paths_closed = True + mob.set_fill(color = WHITE, opacity = 0.5) + mob.set_stroke(width = 0) + return mob + + def zoom_out_scene(factor): + + self.remove_foreground_mobject(self.ls0_dot) + self.remove(self.ls0_dot) + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + # place ls0_dot by hand + #old_radius = self.ls0_dot.radius + #self.ls0_dot.radius = 2 * old_radius + + #v = self.ls0_dot.get_center() - self.obs_dot.get_center() + #self.ls0_dot.shift(v) + #self.ls0_dot.move_to(self.outer_lake.get_center()) + self.ls0_dot.scale(2, about_point = ORIGIN) + + #self.add_foreground_mobject(self.ls0_dot) + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + baseline = VMobject() baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary - obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - + obs_dot = self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) # lake lake0 = Circle(radius = LAKE0_RADIUS, @@ -2807,9 +2871,10 @@ class PondScene(Scene): fill_opacity = LAKE_OPACITY ) lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Mortimer().scale(0.3) + morty = Randolph().scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -2817,15 +2882,17 @@ class PondScene(Scene): color = LIGHT_COLOR ) indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) # first lighthouse - ls0 = LightSource() + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 150) + ls0.lighthouse.scale_to_fit_height(LIGHTHOUSE_HEIGHT) + ls0.lighthouse.height = LIGHTHOUSE_HEIGHT ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - self.add(lake0,morty,obs_dot,ls0_dot, ls0.lighthouse) - - self.wait() - + # self.add(lake0, morty, obs_dot, ls0_dot, ls0.lighthouse) # shore arcs arc_left = Arc(-TAU/2, @@ -2838,8 +2905,7 @@ class PondScene(Scene): one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_left.next_to(arc_left,LEFT) - - + arc_right = Arc(TAU/2, radius = LAKE0_RADIUS, start_angle = -TAU/4, @@ -2851,28 +2917,76 @@ class PondScene(Scene): one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) one_right.next_to(arc_right,RIGHT) + # New introduction + lake0.save_state() + morty.save_state() + lake0.scale_to_fit_height(6) + morty.to_corner(UP+LEFT) + morty.fade(1) + lake0.center() + + lake_word = TextMobject("Lake") + lake_word.scale(2) + lake_word.move_to(lake0) + + self.play( + DrawBorderThenFill(lake0, stroke_width = 1), + Write(lake_word) + ) + self.play( + lake0.restore, + lake_word.scale, 0.5, {"about_point" : lake0.get_bottom()}, + lake_word.fade, 1 + ) + self.remove(lake_word) + self.play(morty.restore) + self.play( + GrowFromCenter(obs_dot), + GrowFromCenter(ls0_dot), + FadeIn(ls0.lighthouse) + ) + self.add_foreground_mobjects(ls0.lighthouse, obs_dot, ls0_dot) + self.play( + SwitchOn(ls0.ambient_light), + Animation(ls0.lighthouse), + ) + self.wait() + self.play( + morty.move_to, ls0.lighthouse, + run_time = 3, + path_arc = TAU/2, + rate_func = there_and_back + ) + self.play( - ShowCreation(arc_left), - Write(one_left), ShowCreation(arc_right), Write(one_right), ) - - self.play( - SwitchOn(ls0.ambient_light), - lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, + ShowCreation(arc_left), + Write(one_left), ) + self.play( + lake0.set_stroke, { + "color": LAKE_STROKE_COLOR, + "width" : LAKE_STROKE_WIDTH + }, + ) + self.wait() + self.add_foreground_mobjects(morty) + + # Show indicator self.play(FadeIn(indicator)) - self.play( - indicator.set_intensity,0.5 - ) + self.play(indicator.set_intensity, 0.5) + + diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02) + diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) # diameter - diameter = DoubleArrow(OBSERVER_POINT, - ls0.get_source_point(), + diameter = DoubleArrow(diameter_start, + diameter_stop, buff = 0, color = WHITE, ) @@ -2880,55 +2994,70 @@ class PondScene(Scene): diameter_text.next_to(diameter,RIGHT) self.play( - ShowCreation(diameter), + GrowFromCenter(diameter), Write(diameter_text), - #FadeOut(obs_dot), + #FadeOut(self.obs_dot), FadeOut(ls0_dot) ) + self.wait() - indicator.reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator.reading.move_to(indicator) + indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) self.play( - FadeIn(indicator.reading) + ReplacementTransform( + diameter_text[0].copy(), + indicator_reading[2], + ), + FadeIn(indicator_reading) ) + self.wait() # replace d with its value - new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) + new_diameter_text = TexMobject("{2 \over \pi}").scale(TEX_SCALE) new_diameter_text.color = LAKE_COLOR new_diameter_text.move_to(diameter_text) - self.play( - Transform(diameter_text,new_diameter_text) - ) + self.play(FadeOut(diameter_text)) + self.play(FadeIn(new_diameter_text)) + self.wait(2) # insert into indicator reading new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) new_reading.move_to(indicator) + new_diameter_text_copy = new_diameter_text.copy() + new_diameter_text_copy.submobjects.reverse() self.play( - Transform(indicator.reading,new_reading) + FadeOut(indicator_reading), + ReplacementTransform( + new_diameter_text_copy, + new_reading, + parth_arc = 30*DEGREES + ) ) + indicator_reading = new_reading + + self.wait(2) self.play( FadeOut(one_left), FadeOut(one_right), - FadeOut(diameter_text), + FadeOut(new_diameter_text), FadeOut(arc_left), FadeOut(arc_right) ) - - - + self.add_foreground_mobjects(indicator, indicator_reading) + self.unzoomable_mobs.add(indicator_reading) def indicator_wiggle(): INDICATOR_WIGGLE_FACTOR = 1.3 self.play( ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator.reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) ) - def angle_for_index(i,step): return -TAU/4 + TAU/2**step * (i + 0.5) @@ -2940,12 +3069,12 @@ class PondScene(Scene): position = self.lake_center + self.lake_radius * radial_vector if scaled_down: - return position.scale_about_point(OBSERVER_POINT,0.5) + return position.scale_about_point(self.obs_dot.get_center(),0.5) else: return position - def split_light_source(i, step, show_steps = True, run_time = 1, ls_radius = 1): + def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): ls_new_loc1 = position_for_index(i,step + 1) ls_new_loc2 = position_for_index(i + 2**step,step + 1) @@ -2961,8 +3090,8 @@ class PondScene(Scene): ShowCreation(hyp, run_time = run_time) ) - leg1 = Line(OBSERVER_POINT,ls_new_loc1) - leg2 = Line(OBSERVER_POINT,ls_new_loc2) + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) self.new_legs_1.append(leg1) self.new_legs_2.append(leg2) @@ -2973,32 +3102,38 @@ class PondScene(Scene): ) ls1 = self.light_sources_array[i] + + ls2 = ls1.copy() - self.add(ls2) + if animate == True: + self.add(ls2) + self.additional_light_sources.append(ls2) # check if the light sources are on screen ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc) < 10) - onscreen_1 = np.all(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.all(np.abs(ls_new_loc2) < 10) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) show_animation = (onscreen_old or onscreen_1 or onscreen_2) - if show_animation: + if show_animation or animate: + ls1.generate_target() + ls2.generate_target() + ls1.target.move_source_to(ls_new_loc1) + ls2.target.move_source_to(ls_new_loc2) + ls1.fade(1) self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), + MoveToTarget(ls1), MoveToTarget(ls2), + run_time = run_time ) else: ls1.move_source_to(ls_new_loc1) ls2.move_source_to(ls_new_loc1) - - - - def construction_step(n, scale_down = True, show_steps = True, run_time = 1, - simultaneous_splitting = False, ls_radius = 1): + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): # we assume that the scene contains: # an inner lake, self.inner_lake @@ -3016,18 +3151,22 @@ class PondScene(Scene): # first, fade out all of the hypotenuses and altitudes + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) self.play( FadeOut(self.hypotenuses), FadeOut(self.altitudes), FadeOut(self.inner_lake) ) else: + self.zoomable_mobs.remove(self.inner_lake) self.play( FadeOut(self.inner_lake) ) # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP new_outer_lake = Circle(radius = self.lake_radius, stroke_width = LAKE_STROKE_WIDTH, @@ -3040,7 +3179,7 @@ class PondScene(Scene): if show_steps == True: self.play( FadeIn(new_outer_lake, run_time = run_time), - FadeIn(ls0_dot) + FadeIn(self.ls0_dot) ) else: self.play( @@ -3052,38 +3191,124 @@ class PondScene(Scene): self.inner_lake = self.outer_lake self.outer_lake = new_outer_lake self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() self.additional_light_sources = [] self.new_legs_1 = [] self.new_legs_2 = [] self.new_hypotenuses = [] - for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time, - ls_radius = ls_radius + if simultaneous_splitting == False: + + for i in range(2**n): + + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time + ) + + if n == 1 and i == 0: + # show again where the right angles are + A = self.light_sources[0].get_center() + B = self.additional_light_sources[0].get_center() + C = self.obs_dot.get_center() + + triangle1 = triangle( + A, C, B + ) + right_angle1 = right_angle( + A, C, B, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle1), + FadeIn(right_angle1) + ) + + self.wait() + + self.play( + FadeOut(triangle1), + FadeOut(right_angle1) + ) + + self.wait() + + H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT + L = self.outer_lake.get_center() + triangle2 = triangle( + L, H, C + ) + + right_angle2 = right_angle( + L, H, C, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle2), + FadeIn(right_angle2) + ) + + self.wait() + + self.play( + FadeOut(triangle2), + FadeOut(right_angle2) + ) + + self.wait() + + else: # simultaneous splitting + + old_lake = self.outer_lake.copy() + old_ls = self.light_sources.copy() + old_ls2 = old_ls.copy() + for submob in old_ls2.submobjects: + old_ls.add(submob) + + self.remove(self.outer_lake, self.light_sources) + self.add(old_lake, old_ls) + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + animate = False + ) + + self.play( + ReplacementTransform(old_ls, self.light_sources, run_time = run_time), + ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), ) + # collect the newly created mobs (in arrays) # into the appropriate Mobject containers self.legs = VMobject() for leg in self.new_legs_1: self.legs.add(leg) + self.zoomable_mobs.add(leg) for leg in self.new_legs_2: self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) self.hypotenuses = VMobject() for hyp in self.new_hypotenuses: self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) for ls in self.additional_light_sources: self.light_sources.add(ls) self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) # update scene self.add( @@ -3091,6 +3316,7 @@ class PondScene(Scene): self.inner_lake, self.outer_lake, ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) if show_steps == True: self.add( @@ -3098,51 +3324,16 @@ class PondScene(Scene): self.hypotenuses, self.altitudes, ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) self.wait() if show_steps == True: - self.play(FadeOut(ls0_dot)) - - # scale down - if scale_down: - - indicator_wiggle() - - if show_steps == True: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - self.legs.scale_about_point,0.5,OBSERVER_POINT, - self.hypotenuses.scale_about_point,0.5,OBSERVER_POINT, - self.altitudes.scale_about_point,0.5,OBSERVER_POINT, - ) - else: - self.play( - ScaleLightSources(self.light_sources,0.5,about_point = OBSERVER_POINT), - self.inner_lake.scale_about_point,0.5,OBSERVER_POINT, - self.outer_lake.scale_about_point,0.5,OBSERVER_POINT, - ) - - # update the radii bc they haven't done so themselves - # bc reasons... - for ls in self.light_sources_array: - r = ls.radius - ls.set_radius(r*0.5) - - else: - # update the lake center and the radius - self.lake_center = ls0_loc = self.outer_lake.get_center() + self.lake_radius * UP - self.lake_radius *= 2 - - - - - - + self.play(FadeOut(self.ls0_dot)) + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 self.lake_center = ls0_loc = ls0.get_source_point() @@ -3158,7 +3349,10 @@ class PondScene(Scene): self.lake_radius = 2 * LAKE0_RADIUS # don't ask... - self.add(self.inner_lake, + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + + self.add( + self.inner_lake, self.outer_lake, self.legs, self.altitudes, @@ -3172,29 +3366,116 @@ class PondScene(Scene): self.new_legs_2 = [] self.new_hypotenuses = [] - ls_radius = 25.0 + construction_step(0) + + my_triangle = triangle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point() + ) + + angle_sign1 = right_angle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point(), + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign1), + FadeIn(my_triangle) + ) + + angle_sign2 = right_angle( + self.light_sources[1].get_source_point(), + self.lake_center, + OBSERVER_POINT, + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign2) + ) + + self.wait() + + self.play( + FadeOut(angle_sign1), + FadeOut(angle_sign2), + FadeOut(my_triangle) + ) + + indicator_wiggle() + self.remove(self.ls0_dot) + zoom_out_scene(2) + + + construction_step(1) + indicator_wiggle() + #self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + - for i in range(3): - construction_step(i, scale_down = True, ls_radius = ls_radius/2**i) - return self.play( FadeOut(self.altitudes), FadeOut(self.hypotenuses), FadeOut(self.legs) - ) + ) - for i in range(3,5): - construction_step(i, scale_down = False, show_steps = False, run_time = 1.0/2**i, - simultaneous_splitting = True, ls_radius = ls_radius/2**3) + max_it = 6 + scale = 2**(max_it - 4) + TEX_SCALE *= scale + # for i in range(3,max_it + 1): + # construction_step(i, show_steps = False, run_time = 4.0/2**i, + # simultaneous_splitting = True) + + + + # simultaneous expansion of light sources from now on + self.play(FadeOut(self.inner_lake)) + + for n in range(3,max_it + 1): + + new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) + for ls in self.light_sources_array: + lsp = ls.copy() + self.light_sources.add(lsp) + self.add(lsp) + self.light_sources_array.append(lsp) + + new_lake_center = new_lake.get_center() + new_lake_radius = 0.5 * new_lake.get_width() + + shift_list = (Transform(self.outer_lake,new_lake),) + + + for i in range(2**n): + theta = -TAU/4 + (i + 0.5) * TAU / 2**n + v = np.array([np.cos(theta), np.sin(theta),0]) + pos1 = new_lake_center + new_lake_radius * v + pos2 = new_lake_center - new_lake_radius * v + shift_list += (self.light_sources.submobjects[i].move_source_to,pos1) + shift_list += (self.light_sources.submobjects[i+2**n].move_source_to,pos2) + + self.play(*shift_list) + + #self.revert_to_original_skipping_status() # Now create a straight number line and transform into it MAX_N = 17 + origin_point = self.obs_dot.get_center() + self.number_line = NumberLine( x_min = -MAX_N, x_max = MAX_N + 1, @@ -3202,13 +3483,13 @@ class PondScene(Scene): number_at_center = 0, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = range(-MAX_N,MAX_N + 1), - unit_size = LAKE0_RADIUS * TAU/4 / 4, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, tick_frequency = 1, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(2.5 * DOWN) + ).shift(scale * 2.5 * DOWN) self.number_line.label_direction = DOWN @@ -3233,23 +3514,465 @@ class PondScene(Scene): # open sea open_sea = Rectangle( - width = 20, - height = 10, + width = 20 * scale, + height = 10 * scale, stroke_width = LAKE_STROKE_WIDTH, stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, fill_opacity = LAKE_OPACITY, ).flip().next_to(origin_point,UP,buff = 0) - - self.play( - Transform(pond_sources,nl_sources), - Transform(self.outer_lake,open_sea), + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), FadeOut(self.inner_lake) ) self.play(FadeIn(self.number_line)) + self.wait() + + v = 4 * scale * UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + self.obs_dot.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + #self.remove(self.obs_dot) + self.play( + indicator.move_to, origin_point + scale * UP, + indicator_reading.move_to, origin_point + scale * UP, + FadeOut(open_sea), + FadeOut(morty), + FadeIn(self.number_line_labels) + ) + + two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ + "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ + "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ + "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ + "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") + + nb_symbols = len(two_sided_sum.submobjects) + + two_sided_sum.scale(TEX_SCALE) + + for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.3 * scale * DOWN) + + self.play(Write(two_sided_sum)) + + for i in range(MAX_N - 5, MAX_N): + self.remove(nl_sources.submobjects[i].ambient_light) + + for i in range(MAX_N, MAX_N + 5): + self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light) + + self.wait() + + covering_rectangle = Rectangle( + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1, + ) + covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + for i in range(10): + self.add_foreground_mobject(nl_sources.submobjects[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator_reading) + + + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(indicator) + + central_plus_sign = two_sided_sum[13] + + self.play( + FadeIn(covering_rectangle), + Transform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) + ) + + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP + + self.play( + indicator.move_to,p, + indicator_reading.move_to,p, + FadeIn(equals_sign), + ) + + self.revert_to_original_skipping_status() + + # show Randy admiring the result + randy = Randolph(color = MAROON_E).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + self.play(randy.change,"hooray") + +class WalkThroughOneMoreStep(TeacherStudentsScene): + def construct(self): + self.student_says(""" + Wait...can you walk \\\\ + through one more step? + """) + self.play(self.teacher.change, "happy") + self.wait(4) + +class ButWait(TeacherStudentsScene): + def construct(self): + self.student_says( + "But wait!", + target_mode = "angry", + run_time = 1, + ) + self.change_student_modes( + "sassy", "angry", "sassy", + added_anims = [self.teacher.change, "guilty"], + run_time = 1 + ) + self.student_says( + """ + You promised us \\\\ + $1+{1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots$ + """, + target_mode = "sassy", + ) + self.wait(3) + self.teacher_says("Yes, but that's \\\\ very close.") + self.change_student_modes(*["plain"]*3) + self.wait(2) + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + + unit_length = 1.5 + vertical_spacing = 2.5 * DOWN + switch_on_time = 0.2 + + sum_vertical_spacing = 1.5 + + randy = self.get_primary_pi_creature() + randy.highlight(MAROON_D) + randy.color = MAROON_D + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + self.wait() + + ls_template = LightSource( + radius = 1, + num_levels = 10, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.75,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_to_show = full_range, + number_scale_val = 0.5, + numbers_with_elongated_ticks = [], + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_SMALL_BUFF, + include_tip = True, + label_direction = UP, + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) + self.number_line1.add_numbers() + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1, run_time = 5), + ) + self.wait() + + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, + stroke_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + result1.shift(0.87 * vertical_spacing) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + self.number_line2.add_numbers() + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2, run_time = 5), + ) + self.wait() + + + full_lighthouses = VMobject() + full_ambient_lights = VMobject() + for ls in full_lights: + full_lighthouses.add(ls.lighthouse) + full_ambient_lights.add(ls.ambient_light) + + self.play( + LaggedStart(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), + ) + + self.play( + LaggedStart(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) + ) + + # for ls in full_lights.submobjects: + # self.play( + # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + # ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + self.wait() + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights, run_time = 2) + ) + + + self.wait() + + for i in range(6): + self.play( + Transform(even_lights[i], even_lights_copy[i]) + ) + self.wait() + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + ) + self.wait() + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + self.wait() + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + self.wait() + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + self.wait() + + full_terms = VMobject() + for i in range(1,8): #full_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + elif i == 7: + term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + #return + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + self.wait() + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + result1.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + self.wait() + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + self.wait() + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + full_terms.shift,LEFT, + ) + self.wait() + + self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) + + result_box = Rectangle(width = 1.1 * equation.get_width(), + height = 2 * equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + + + self.play( + ShowCreation(result_box) + ) + self.wait() + class LabeledArc(Arc): CONFIG = { "length" : 1 @@ -3257,7 +3980,7 @@ class LabeledArc(Arc): def __init__(self, angle, **kwargs): - BUFFER = 1.3 + BUFFER = 0.8 Arc.__init__(self,angle,**kwargs) @@ -3269,13 +3992,14 @@ class LabeledArc(Arc): label.move_to(label_pos) self.add(label) -class ArcHighlightOverlayScene(Scene): - +class ArcHighlightOverlaySceneCircumferenceEight(Scene): + CONFIG = { + "n" : 2, + } def construct(self): - BASELINE_YPOS = -2.5 OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 + LAKE0_RADIUS = 2.5 INDICATOR_RADIUS = 0.6 TICK_SIZE = 0.5 LIGHTHOUSE_HEIGHT = 0.2 @@ -3286,7 +4010,7 @@ class ArcHighlightOverlayScene(Scene): TEX_SCALE = 0.8 DOT_COLOR = BLUE - FLASH_TIME = 0.25 + FLASH_TIME = 1 def flash_arcs(n): @@ -3313,31 +4037,293 @@ class ArcHighlightOverlayScene(Scene): FadeOut(arcs[2**n], run_time = FLASH_TIME), ) + flash_arcs(self.n) - flash_arcs(3) - - - - - - - - - - - - - - - - - - - - - - - +class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight): + CONFIG = { + "n" : 3, + } + +class ThumbnailScene(Scene): + + def construct(self): + + equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") + equation.scale(1.5) + equation.move_to(1.5 * UP) + q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) + q_mark.next_to(equation, DOWN, buff = 1.5) + #equation.move_to(2 * UP) + #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #q_mark.next_to(equation, DOWN, buff = 1) + + lake_radius = 6 + lake_center = ORIGIN + op_scale = 0.4 + + lake = Circle( + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + radius = lake_radius, + stroke_color = LAKE_STROKE_COLOR, + stroke_width = LAKE_STROKE_WIDTH, + ) + lake.move_to(lake_center) + + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource( + radius = 15.0, + num_levels = 150, + max_opacity_ambient = 1.0, + opacity_function = inverse_quadratic(1,op_scale,1) + ) + ls.move_source_to(pos) + lake.add(ls.ambient_light, ls.lighthouse) + + self.add(lake) + + self.add(equation, q_mark) + + self.wait() + +class InfiniteCircleScene(PiCreatureScene): + + def construct(self): + + morty = self.get_primary_pi_creature() + morty.highlight(MAROON_D).flip() + morty.color = MAROON_D + morty.scale(0.5).move_to(ORIGIN) + + arrow = Arrow(ORIGIN, 2.4 * RIGHT) + dot = Dot(color = BLUE).next_to(arrow) + ellipsis = TexMobject("\dots") + + infsum = VGroup() + infsum.add(ellipsis.copy()) + + for i in range(3): + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(dot.copy().next_to(infsum.submobjects[-1])) + + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) + + infsum.next_to(morty,DOWN, buff = 1) + + self.wait() + self.play( + LaggedStart(FadeIn,infsum,lag_ratio = 0.2) + ) + self.wait() + + A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT + B = A + RIGHT + 1.3 * UP + 0.025 * LEFT + right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, + stroke_width = 8).apply_complex_function(np.exp) + right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) + right_tip_line = Arrow(B - UP, B, color = WHITE) + right_tip_line.add_tip() + right_tip = right_tip_line.get_tip() + right_tip.set_fill(color = YELLOW) + right_arc.add(right_tip) + + + C = B + 3.2 * UP + right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, + stroke_width = 8) + + ru_arc = right_arc.copy().rotate(angle = TAU/4) + ru_arc.remove(ru_arc.submobjects[-1]) + ru_arc.to_edge(UP+RIGHT, buff = 0.15) + + D = np.array([5.85, 3.85,0]) + E = np.array([-D[0],D[1],0]) + up_line = DashedLine(D, E, stroke_color = YELLOW, + stroke_width = 8) + + lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) + left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) + + left_arc = right_arc.copy().rotate(-TAU/4) + left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) + + right_arc.shift(0.2 * RIGHT) + right_line.shift(0.2 * RIGHT) + + self.play(FadeIn(right_arc)) + self.play(ShowCreation(right_line)) + self.play(FadeIn(ru_arc)) + self.play(ShowCreation(up_line)) + self.play(FadeIn(lu_arc)) + self.play(ShowCreation(left_line)) + self.play(FadeIn(left_arc)) + + + + self.wait() + +class BaselPatreonThanks(PatreonEndScreen): + CONFIG = { + "specific_patrons" : [ + "CrypticSwarm ", + "Ali Yahya", + "Juan Benet", + "Markus Persson", + "Damion Kistler", + "Burt Humburg", + "Yu Jun", + "Dave Nicponski", + "Kaustuv DeBiswas", + "Joseph John Cox", + "Luc Ritchie", + "Achille Brighton", + "Rish Kundalia", + "Yana Chernobilsky", + "Shìmín kuāng", + "Mathew Bramson", + "Jerry Ling", + "Mustafa Mahdi", + "Meshal Alshammari", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Robert Teed", + "Samantha D. Suplee", + "Mark Govea", + "John Haley", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Desmos ", + "Boris Veselinovich", + "Ryan Dahl", + "Ripta Pasay", + "Eric Lavault", + "Randall Hunt", + "Andrew Busey", + "Mads Elvheim", + "Tianyu Ge", + "Awoo", + "Dr. David G. Stork", + "Linh Tran", + "Jason Hise", + "Bernd Sing", + "James H. Park", + "Ankalagon ", + "Devin Scott", + "Mathias Jansson", + "David Clark", + "Ted Suzman", + "Eric Chow", + "Michael Gardner", + "David Kedmey", + "Jonathan Eppele", + "Clark Gaebel", + "Jordan Scales", + "Ryan Atallah", + "supershabam ", + "1stViewMaths ", + "Jacob Magnuson", + "Chloe Zhou", + "Ross Garber", + "Thomas Tarler", + "Isak Hietala", + "Egor Gumenuk", + "Waleed Hamied", + "Oliver Steele", + "Yaw Etse", + "David B", + "Delton Ding", + "James Thornton", + "Felix Tripier", + "Arthur Zey", + "George Chiesa", + "Norton Wang", + "Kevin Le", + "Alexander Feldman", + "David MacCumber", + "Jacob Kohl", + "Sergei ", + "Frank Secilia", + "Patrick Mézard", + "George John", + "Akash Kumar", + "Britt Selvitelle", + "Jonathan Wilson", + "Ignacio Freiberg", + "Zhilong Yang", + "Karl Niu", + "Dan Esposito", + "Michael Kunze", + "Giovanni Filippi", + "Eric Younge", + "Prasant Jagannath", + "Andrejs Olins", + "Cody Brocious", + ], + } + def construct(self): + next_video = TextMobject("$\\uparrow$ Next video $\\uparrow$") + next_video.to_edge(RIGHT, buff = 1.5) + next_video.shift(MED_SMALL_BUFF*UP) + next_video.highlight(YELLOW) + self.add_foreground_mobject(next_video) + PatreonEndScreen.construct(self) + + + +class Thumbnail(Scene): + CONFIG = { + "light_source_config" : { + "num_levels" : 250, + "radius" : 10.0, + "max_opacity_ambient" : 1.0, + "opacity_function" : inverse_quadratic(1,0.5,1) + } + } + def construct(self): + equation = TexMobject( + "1", "+", "{1\over 4}", "+", + "{1\over 9}","+", "{1\over 16}","+", + "{1\over 25}", "+", "\cdots" + ) + equation.scale(1.8) + equation.move_to(2*UP) + equation.set_stroke(BLACK, 1) + answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) + answer.scale(3) + answer.set_stroke(RED, 1) + # answer.next_to(equation, DOWN, buff = 1) + answer.move_to(1.25*DOWN) + #equation.move_to(2 * UP) + #answer = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #answer.next_to(equation, DOWN, buff = 1) + + lake_radius = 6 + lake_center = ORIGIN + + lake = Circle( + fill_color = BLUE, + fill_opacity = 0.15, + radius = lake_radius, + stroke_color = BLUE_D, + stroke_width = 3, + ) + lake.move_to(lake_center) + + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource(**self.light_source_config) + ls.move_source_to(pos) + lake.add(ls.ambient_light) + lake.add(ls.lighthouse) + + self.add(lake) + self.add(equation, answer) + self.wait() From ee889d6be4fe73b8395f8936e14b7821b99c7d97 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 1 Mar 2018 18:28:16 +0100 Subject: [PATCH 67/73] tweaking PondScene, added RightAngleOverlay --- active_projects/basel.py | 131 ++++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 24 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 73089baa..8a069ddf 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -2576,12 +2576,13 @@ class InscribedAngleScene(ThreeDScene): # 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) + onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 ** 2**step) + onscreen_1 = np.all(np.abs(ls_new_loc1[:2][:2]) < 10 ** 2**step) + onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 ** 2**step) show_animation = (onscreen_old or onscreen_1 or onscreen_2) if show_animation: + print "animating (", i, ",", step, ")" self.play( ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), @@ -3070,6 +3071,9 @@ class PondScene(ThreeDScene): self.cumulated_zoom_factor = 1 + STEP_RUN_TIME = 0.5 + + #self.force_skipping() @@ -3176,7 +3180,7 @@ class PondScene(ThreeDScene): # 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 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 15) ls0.lighthouse.scale_to_fit_height(LIGHTHOUSE_HEIGHT) ls0.lighthouse.height = LIGHTHOUSE_HEIGHT ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) @@ -3353,12 +3357,13 @@ class PondScene(ThreeDScene): # 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) + onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**3) + onscreen_1 = np.all(np.abs(ls_new_loc1[:2]) < 10 * 2**3) + onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 * 2**3) show_animation = (onscreen_old or onscreen_1 or onscreen_2) if show_animation or animate: + print "animating (", i, ",", step, ")" self.play( ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), @@ -3616,7 +3621,7 @@ class PondScene(ThreeDScene): self.new_hypotenuses = [] - construction_step(0) + construction_step(0, run_time = STEP_RUN_TIME) my_triangle = triangle( self.light_sources[0].get_source_point(), @@ -3660,13 +3665,13 @@ class PondScene(ThreeDScene): zoom_out_scene(2) - construction_step(1) + construction_step(1, run_time = STEP_RUN_TIME) indicator_wiggle() #self.play(FadeOut(self.ls0_dot)) zoom_out_scene(2) - construction_step(2) + construction_step(2, run_time = STEP_RUN_TIME) indicator_wiggle() self.play(FadeOut(self.ls0_dot)) @@ -3679,8 +3684,8 @@ class PondScene(ThreeDScene): FadeOut(self.legs) ) - max_it = 6 - scale = 2**(max_it - 4) + max_it = 10 + scale = 2**(max_it - 5) TEX_SCALE *= scale @@ -3708,8 +3713,8 @@ class PondScene(ThreeDScene): new_lake_center = new_lake.get_center() new_lake_radius = 0.5 * new_lake.get_width() - shift_list = [Transform(self.outer_lake,new_lake)] - #print shift_list + self.play(Transform(self.outer_lake,new_lake)) + shift_list = [] for i in range(2**n): #print "===========" @@ -3720,10 +3725,26 @@ class PondScene(ThreeDScene): pos2 = new_lake_center - new_lake_radius * v ls1 = self.light_sources.submobjects[i] ls2 = self.light_sources.submobjects[i+2**n] - shift_list.append(ls1.move_source_to) - shift_list.append(pos1) - shift_list.append(ls2.move_source_to) - shift_list.append(pos2) + + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**2) + onscreen_1 = np.all(np.abs(pos1[:2]) < 10 * 2**2) + onscreen_2 = np.all(np.abs(pos2[:2]) < 10 * 2**2) + + if onscreen_old or onscreen_1: + print "anim1 for step", n, "part", i + print "------------------ moving from", ls_old_loc[:2], "to", pos1[:2] + shift_list.append(ApplyMethod(ls1.move_source_to, pos1, run_time = STEP_RUN_TIME)) + else: + ls1.move_source_to(pos1) + if onscreen_old or onscreen_2: + print "anim2 for step", n, "part", i + print "------------------ moving from", ls_old_loc[:2], "to", pos2[:2] + shift_list.append(ApplyMethod(ls2.move_source_to, pos2, run_time = STEP_RUN_TIME)) + else: + ls2.move_source_to(pos2) + + #print shift_list self.play(*shift_list) @@ -3752,7 +3773,11 @@ class PondScene(ThreeDScene): number_scale_val = 3, line_to_number_buff = LARGE_BUFF, label_direction = UP, - ).shift(scale * 2.5 * DOWN) + ).shift(origin_point - self.number_line.number_to_point(0)) # .shift(scale * 2.5 * DOWN) + + print "scale ", scale + print "number line at", self.number_line.get_center() + print "should be at", origin_point, "or", OBSERVER_POINT self.number_line.tick_marks.fade(1) self.number_line_labels = self.number_line.get_number_mobjects() @@ -3784,7 +3809,7 @@ class PondScene(ThreeDScene): stroke_color = LAKE_STROKE_COLOR, fill_color = LAKE_COLOR, fill_opacity = LAKE_OPACITY, - ).flip().next_to(origin_point,UP,buff = 0) + ).flip().next_to(self.obs_dot.get_center(),UP,buff = 0) self.revert_to_original_skipping_status() @@ -3797,7 +3822,6 @@ class PondScene(ThreeDScene): ) self.play(FadeIn(self.number_line)) - self.wait() v = 4 * scale * UP @@ -3862,7 +3886,7 @@ class PondScene(ThreeDScene): fill_color = BLACK, fill_opacity = 1, ) - covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + covering_rectangle.next_to(ORIGIN, LEFT, buff = 0) #for i in range(10): # self.add_foreground_mobject(nl_sources.submobjects[i]) @@ -4459,8 +4483,67 @@ class InfiniteCircleScene(PiCreatureScene): - - +class RightAnglesOverlay(Scene): + + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 1.5 * 2 + 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 + + RIGHT_ANGLE_SIZE = 0.3 + + + 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 + + + lake_center = OBSERVER_POINT + LAKE0_RADIUS * UP + points = [] + lines = VGroup() + for i in range(4): + theta = -TAU/4 + (i+0.5)*TAU/4 + v = np.array([np.cos(theta), np.sin(theta), 0]) + P = lake_center + LAKE0_RADIUS * v + points.append(P) + lines.add(Line(lake_center, P, stroke_width = 8)) + + self.play(FadeIn(lines)) + + self.wait() + + for i in range(4): + sign = right_angle(points[i-1], lake_center, points[i],RIGHT_ANGLE_SIZE) + self.play(FadeIn(sign)) + self.play(FadeOut(sign)) + + self.wait() + + self.play(FadeOut(lines)) From 8b1643f55d3d82f1466aa8e18711e3bd305285d4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 4 Mar 2018 10:43:23 -0800 Subject: [PATCH 68/73] Finished Basel project --- active_projects/basel2.py | 581 +++++++++++++++++++++++++++++--------- 1 file changed, 445 insertions(+), 136 deletions(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index fb7239d9..8d8c804b 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -224,6 +224,45 @@ class ScaleLightSources(Transform): Transform.__init__(self,light_sources_mob,ls_target,**kwargs) +class ThreeDSpotlight(VGroup): + CONFIG = { + "fill_color" : YELLOW, + } + def __init__(self, screen, ambient_light, source_point_func, **kwargs): + self.screen = screen + self.ambient_light = ambient_light + self.source_point_func = source_point_func + self.dr = ambient_light.radius/ambient_light.num_levels + VGroup.__init__(self, **kwargs) + + def update(self): + screen = self.screen + source_point = self.source_point_func() + dr = self.dr + corners = screen.get_anchors() + self.submobjects = [VGroup() for a in screen.get_anchors()] + + distance = np.linalg.norm( + screen.get_center() - source_point + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for face, (c1, c2) in zip(self, adjacent_pairs(corners)): + face.submobjects = [] + for a1, a2 in zip(alphas, alphas[1:]): + face.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = self.fill_color, + fill_opacity = self.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + +class ContinualThreeDLightConeUpdate(ContinualAnimation): + def update(self, dt): + self.mobject.update() ### @@ -236,7 +275,7 @@ class ThinkAboutPondScene(PiCreatureScene): randy.to_corner(DOWN+LEFT) bubble = ThoughtBubble( width = 11, - height = 7, + height = 8, ) circles = bubble[:3] angle = -15*DEGREES @@ -245,6 +284,12 @@ class ThinkAboutPondScene(PiCreatureScene): for circle in circles: circle.rotate(-angle) bubble.pin_to(randy) + bubble.shift(DOWN) + bubble[:3].rotate(np.pi, axis = UP+2*RIGHT, about_edge = UP+LEFT) + bubble[:3].scale(0.7, about_edge = DOWN+RIGHT) + bubble[:3].shift(1.5*DOWN) + for oval in bubble[:3]: + oval.rotate(TAU/3) self.play( randy.change, "thinking", @@ -252,7 +297,7 @@ class ThinkAboutPondScene(PiCreatureScene): ) self.wait(2) self.play(randy.change, "happy", bubble) - self.wait(2) + self.wait(4) self.play(randy.change, "hooray", bubble) self.wait(2) @@ -615,7 +660,7 @@ class MathematicalWebOfConnections(PiCreatureScene): jerk, randy = self.pi_creatures words = self.words = TextMobject( - "$\\pi$ is not", + "I am not", "fundamentally \\\\", "about circles" ) @@ -657,9 +702,8 @@ class MathematicalWebOfConnections(PiCreatureScene): formula_equals_x = formula.get_part_by_tex("=").get_center()[0] formula.shift((basel_equals_x - formula_equals_x)*RIGHT) - formulas.move_to(randy) - formulas.to_edge(UP) - formulas.shift_onto_screen() + formulas.to_corner(UP+RIGHT) + formulas.shift(2*LEFT) self.formulas = formulas self.play( @@ -1165,6 +1209,9 @@ class IntroduceScreen(Scene): "num_rays" : 250, "min_ray_angle" : 0, "max_ray_angle" : TAU, + "source_point" : 2.5*LEFT, + "observer_point" : 3.5*RIGHT, + "screen_height" : 2, } def construct(self): self.setup_elements() @@ -1174,24 +1221,17 @@ class IntroduceScreen(Scene): def setup_elements(self): SCREEN_SIZE = 3.0 - source_point = self.source_point = 2.5*LEFT - observer_point = 3.5*RIGHT + source_point = self.source_point + observer_point = self.observer_point, + # Light source - - light_source = self.light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = self.num_levels, - radius = self.radius, - max_opacity_ambient = AMBIENT_FULL, - ) - - light_source.move_source_to(source_point) + light_source = self.light_source = self.get_light_source() # Screen screen = self.screen = Rectangle( width = 0.05, - height = 2, + height = self.screen_height, mark_paths_closed = True, fill_color = WHITE, fill_opacity = 1.0, @@ -1333,6 +1373,16 @@ class IntroduceScreen(Scene): ## + def get_light_source(self): + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_source_to(self.source_point) + return light_source + def shoot_rays(self, show_creation_kwargs = None): if show_creation_kwargs is None: show_creation_kwargs = {} @@ -1489,6 +1539,73 @@ class EarthScene(IntroduceScreen): }) self.wait() +class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene): + CONFIG = { + "num_levels" : 200, + } + def construct(self): + light_source = self.get_light_source() + screens = VGroup( + Square(), + RegularPolygon(8), + Circle().insert_n_anchor_points(25), + ) + for screen in screens: + screen.scale_to_fit_height(self.screen_height) + screens.rotate(TAU/4, UP) + screens.next_to(self.observer_point, LEFT) + screens.set_stroke(WHITE, 2) + screens.set_fill(WHITE, 0.5) + screen = screens[0] + + cone = ThreeDSpotlight( + screen, light_source.ambient_light, + light_source.get_source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + + self.add(light_source, screen, cone) + self.add(cone_update_anim) + self.move_camera( + phi = 60*DEGREES, + theta = -155*DEGREES, + run_time = 3, + ) + self.begin_ambient_camera_rotation() + kwargs = {"run_time" : 2} + self.play(screen.stretch, 0.5, 1, **kwargs) + self.play(screen.stretch, 2, 2, **kwargs) + self.play(Rotate( + screen, TAU/4, + axis = UP+OUT, + rate_func = there_and_back, + run_time = 3, + )) + self.play(Transform(screen, screens[1], **kwargs)) + self.play(screen.stretch, 0.5, 2, **kwargs) + self.play(Transform(screen, screens[2], **kwargs)) + self.wait(2) + self.play( + screen.stretch, 0.5, 1, + screen.stretch, 2, 2, + **kwargs + ) + self.play( + screen.stretch, 3, 1, + screen.stretch, 0.7, 2, + **kwargs + ) + self.wait(2) + +class LightInThreeDimensionsOverlay(Scene): + def construct(self): + words = TextMobject(""" + ``Solid angle'' \\\\ + (measured in ``steradians'') + """) + self.play(Write(words)) + self.wait() + class InverseSquareLaw(ThreeDScene): CONFIG = { "screen_height" : 1.0, @@ -1628,7 +1745,6 @@ class InverseSquareLaw(ThreeDScene): unit_distance = self.unit_distance light_indicator = self.light_indicator morty = self.morty - dr = ambient_light.radius/ambient_light.num_levels new_screen = Square( side_length = self.screen_height, @@ -1640,31 +1756,13 @@ class InverseSquareLaw(ThreeDScene): new_screen.rotate(TAU/4, UP) new_screen.move_to(old_screen, IN) old_screen.fade(1) - screen_group = VGroup(old_screen, new_screen) - cone = VGroup(*[VGroup() for x in range(4)]) - cone.set_stroke(width = 0) - cone.set_fill(YELLOW, opacity = 0.5) - corner_directions = [OUT+UP, OUT+DOWN, IN+DOWN, IN+UP] - def update_cone(cone): - corners = map(new_screen.get_corner, corner_directions) - distance = np.linalg.norm(old_screen.get_reference_point() - self.source_point) - n_parts = np.ceil(distance/dr) - alphas = np.linspace(0, 1, n_parts+1) - for face, (c1, c2) in zip(cone, adjacent_pairs(corners)): - face.submobjects = [] - for a1, a2 in zip(alphas, alphas[1:]): - face.add(Polygon( - interpolate(source_point, c1, a1), - interpolate(source_point, c1, a2), - interpolate(source_point, c2, a2), - interpolate(source_point, c2, a1), - fill_color = YELLOW, - fill_opacity = ambient_light.opacity_function(a1*distance), - stroke_width = 0 - )) - cone_update_anim = ContinualUpdateFromFunc(cone, update_cone) - cone_update_anim.update(0) + cone = ThreeDSpotlight( + new_screen, ambient_light, + source_point_func = lambda : source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + self.remove(self.spotlight_update, self.light_indicator_update) self.add( @@ -1690,26 +1788,41 @@ class InverseSquareLaw(ThreeDScene): run_time = 2, ) self.wait() - self.screen = screen_group - self.shift_by_distance(1) - self.shift_by_distance(-1) - self.wait() ## Create screen copies - screen_copy = new_screen.copy() - four_copies = VGroup(*[new_screen.copy() for x in range(4)]) - nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) - def update_four_copies(four_copies): - for mob, corner_direction in zip(four_copies, corner_directions): - mob.move_to(new_screen, corner_direction) - four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) - edge_directions = [ - UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN - ] - def update_nine_copies(nine_copies): - for mob, corner_direction in zip(nine_copies, edge_directions): - mob.move_to(new_screen, corner_direction) - nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) + def get_screen_copy_group(distance): + n = int(distance)**2 + copies = VGroup(*[new_screen.copy() for x in range(n)]) + copies.rotate(-TAU/4, axis = UP) + copies.arrange_submobjects_in_grid(buff = 0) + copies.rotate(TAU/4, axis = UP) + copies.move_to(source_point, IN) + copies.shift(distance*RIGHT*unit_distance) + return copies + screen_copy_groups = map(get_screen_copy_group, range(1, 8)) + def get_screen_copy_group_anim(n): + group = screen_copy_groups[n] + prev_group = screen_copy_groups[n-1] + group.save_state() + group.fade(1) + group.replace(prev_group, dim_to_match = 1) + return ApplyMethod(group.restore) + + # corner_directions = [UP+OUT, DOWN+OUT, DOWN+IN, UP+IN] + # edge_directions = [ + # UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN + # ] + + # four_copies = VGroup(*[new_screen.copy() for x in range(4)]) + # nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) + # def update_four_copies(four_copies): + # for mob, corner_direction in zip(four_copies, corner_directions): + # mob.move_to(new_screen, corner_direction) + # four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) + # def update_nine_copies(nine_copies): + # for mob, corner_direction in zip(nine_copies, edge_directions): + # mob.move_to(new_screen, corner_direction) + # nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) three_arrow = DoubleArrow( source_point + 4*DOWN, @@ -1721,19 +1834,17 @@ class InverseSquareLaw(ThreeDScene): three.next_to(three_arrow, DOWN) new_screen.fade(1) - self.add( - ContinualAnimation(screen_copy), - ContinualAnimation(four_copies), - ) + # self.add( + # ContinualAnimation(screen_copy), + # ContinualAnimation(four_copies), + # ) + + self.add(ContinualAnimation(screen_copy_groups[0])) + self.add(ContinualAnimation(screen_copy_groups[1])) self.play( - screen_group.scale, 2, {"about_edge" : IN + DOWN}, - screen_group.shift, unit_distance*RIGHT, - UpdateFromAlphaFunc( - four_copies, - lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) - ), - four_copies_update_anim, - screen_copy.shift, 0.25*OUT, #WHY? + new_screen.scale, 2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(1), run_time = 2, ) self.wait() @@ -1744,20 +1855,40 @@ class InverseSquareLaw(ThreeDScene): run_time = 10, ) self.begin_ambient_camera_rotation(rate = -0.01) - self.add(ContinualAnimation(nine_copies)) + self.add(ContinualAnimation(screen_copy_groups[2])) self.play( - screen_group.scale, 3./2, {"about_edge" : IN + DOWN}, - screen_group.shift, unit_distance*RIGHT, - nine_copies_update_anim, - UpdateFromAlphaFunc( - nine_copies, - lambda nc, a : nc.set_stroke(width = a).set_fill(opacity = 0.5*a) - ), + new_screen.scale, 3./2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(2), GrowFromPoint(three_arrow, three_arrow.get_left()), Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), run_time = 2, ) - self.wait(10) + self.begin_ambient_camera_rotation(rate = -0.01) + self.play(LaggedStart( + ApplyMethod, screen_copy_groups[2], + lambda m : (m.highlight, RED), + run_time = 5, + rate_func = there_and_back, + )) + self.wait(2) + self.move_camera(distance = 18) + self.play(*[ + ApplyMethod(mob.fade, 1) + for mob in screen_copy_groups[:2] + ]) + last_group = screen_copy_groups[2] + for n in range(4, len(screen_copy_groups)+1): + group = screen_copy_groups[n-1] + self.add(ContinualAnimation(group)) + self.play( + new_screen.scale, float(n)/(n-1), {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(n-1), + last_group.fade, 1, + ) + last_group = group + self.wait() ### @@ -2189,7 +2320,25 @@ class TwoLightSourcesScene(ManipulateLightsourceSetups): self.wait() #Write IPT - self.play(Write(theorem)) + a_part = theorem[:2] + b_part = theorem[2:5] + h_part = theorem[5:] + for part in a_part, b_part, h_part: + part.save_state() + part.scale(3) + part.fade(1) + a_part.move_to(lsA) + b_part.move_to(lsB) + h_part.move_to(lsC) + + self.play(*map(FadeOut, [lsA, lsB, lsC, indicator])) + for ls, part in (lsA, a_part), (lsB, b_part), (lsC, h_part): + self.add(ls) + self.play( + SwitchOn(ls.ambient_light, run_time = 2), + FadeIn(ls.lighthouse), + part.restore + ) self.wait() self.play( Write(theorem_name), @@ -2874,7 +3023,7 @@ class PondScene(ThreeDScene): self.zoomable_mobs.add(lake0) # Morty and indicator - morty = Randolph().scale(0.3) + morty = Mortimer().flip().scale(0.3) morty.next_to(OBSERVER_POINT,DOWN) indicator = LightIndicator(precision = 2, radius = INDICATOR_RADIUS, @@ -3622,6 +3771,42 @@ class PondScene(ThreeDScene): self.play(randy.change,"happy") self.play(randy.change,"hooray") +class CircumferenceText(Scene): + CONFIG = {"n" : 16} + def construct(self): + words = TextMobject("Circumference %d"%self.n) + words.scale(1.25) + words.to_corner(UP+LEFT) + self.add(words) + +class CenterOfLargerCircleOverlayText(Scene): + def construct(self): + words = TextMobject("Center of \\\\ larger circle") + arrow = Vector(DOWN+LEFT, color = WHITE) + arrow.shift(words.get_bottom() + SMALL_BUFF*DOWN - arrow.get_start()) + group = VGroup(words, arrow) + group.scale_to_fit_height(2*SPACE_HEIGHT - 1) + group.to_edge(UP) + self.add(group) + +class DiameterWordOverlay(Scene): + def construct(self): + word = TextMobject("Diameter") + word.scale_to_fit_width(SPACE_WIDTH) + word.rotate(-45*DEGREES) + self.play(Write(word)) + self.wait() + +class YayIPTApplies(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Heyo! The Inverse \\\\ Pythagorean Theorem \\\\ applies!", + bubble_kwargs = {"width" : 5}, + target_mode = "surprised" + ) + self.change_student_modes(*3*["hooray"]) + self.wait(2) + class WalkThroughOneMoreStep(TeacherStudentsScene): def construct(self): self.student_says(""" @@ -3631,6 +3816,113 @@ class WalkThroughOneMoreStep(TeacherStudentsScene): self.play(self.teacher.change, "happy") self.wait(4) +class ThinkBackToHowAmazingThisIs(ThreeDScene): + CONFIG = { + "x_radius" : 100, + "max_shown_n" : 20, + } + def construct(self): + self.show_sum() + self.show_giant_circle() + + def show_sum(self): + number_line = NumberLine( + x_min = -self.x_radius, + x_max = self.x_radius, + numbers_to_show = range(-self.max_shown_n, self.max_shown_n), + ) + number_line.add_numbers() + number_line.shift(2*DOWN) + + positive_dots, negative_dots = [ + VGroup(*[ + Dot(number_line.number_to_point(u*x)) + for x in range(1, int(self.x_radius), 2) + ]) + for u in 1, -1 + ] + dot_pairs = it.starmap(VGroup, zip(positive_dots, negative_dots)) + + # Decimal + decimal = DecimalNumber(0, num_decimal_points = 6) + decimal.to_edge(UP) + terms = [2./(n**2) for n in range(1, 100, 2)] + partial_sums = np.cumsum(terms) + + # pi^2/4 label + brace = Brace(decimal, DOWN) + pi_term = TexMobject("\pi^2 \over 4") + pi_term.next_to(brace, DOWN) + + term_mobjects = VGroup() + for n in range(1, self.max_shown_n, 2): + p_term = TexMobject("\\left(\\frac{1}{%d}\\right)^2"%n) + n_term = TexMobject("\\left(\\frac{-1}{%d}\\right)^2"%n) + group = VGroup(p_term, n_term) + group.scale(0.7) + p_term.next_to(number_line.number_to_point(n), UP, LARGE_BUFF) + n_term.next_to(number_line.number_to_point(-n), UP, LARGE_BUFF) + term_mobjects.add(group) + term_mobjects.gradient_highlight(BLUE, YELLOW) + plusses = VGroup(*[ + VGroup(*[ + TexMobject("+").next_to( + number_line.number_to_point(u*n), UP, buff = 1.25, + ) + for u in -1, 1 + ]) + for n in range(0, self.max_shown_n, 2) + ]) + + zoom_out = AmbientMovement( + self.camera.rotation_mobject, + direction = OUT, rate = 0.4 + ) + def update_decimal(decimal): + z = self.camera.rotation_mobject.get_center()[2] + decimal.scale_to_fit_height(0.07*z) + decimal.move_to(0.7*z*UP) + scale_decimal = ContinualUpdateFromFunc(decimal, update_decimal) + + + self.add(number_line, *dot_pairs) + self.add(zoom_out, scale_decimal) + + tuples = zip(term_mobjects, plusses, partial_sums) + run_time = 1 + for term_mobs, plus_pair, partial_sum in tuples: + self.play( + FadeIn(term_mobs), + Write(plus_pair, run_time = 1), + ChangeDecimalToValue(decimal, partial_sum), + run_time = run_time + ) + self.wait(run_time) + run_time *= 0.9 + self.play(ChangeDecimalToValue(decimal, np.pi**2/4, run_time = 5)) + zoom_out.begin_wind_down() + self.wait() + self.remove(zoom_out, scale_decimal) + self.play(*map(FadeOut, it.chain( + term_mobjects, plusses, + number_line.numbers, [decimal] + ))) + + self.number_line = number_line + + def show_giant_circle(self): + self.number_line.main_line.insert_n_anchor_points(10000) + everything = VGroup(*self.mobjects) + circle = everything.copy() + circle.move_to(ORIGIN) + circle.apply_function( + lambda (x, y, z) : complex_to_R3(7*np.exp(complex(0, 0.0315*x))) + ) + circle.rotate(-TAU/4, about_point = ORIGIN) + circle.center() + + self.play(Transform(everything, circle, run_time = 6)) + class ButWait(TeacherStudentsScene): def construct(self): self.student_says( @@ -4044,50 +4336,6 @@ class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircu "n" : 3, } -class ThumbnailScene(Scene): - - def construct(self): - - equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") - equation.scale(1.5) - equation.move_to(1.5 * UP) - q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) - q_mark.next_to(equation, DOWN, buff = 1.5) - #equation.move_to(2 * UP) - #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) - #q_mark.next_to(equation, DOWN, buff = 1) - - lake_radius = 6 - lake_center = ORIGIN - op_scale = 0.4 - - lake = Circle( - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - radius = lake_radius, - stroke_color = LAKE_STROKE_COLOR, - stroke_width = LAKE_STROKE_WIDTH, - ) - lake.move_to(lake_center) - - for i in range(16): - theta = -TAU/4 + (i + 0.5) * TAU/16 - pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) - ls = LightSource( - radius = 15.0, - num_levels = 150, - max_opacity_ambient = 1.0, - opacity_function = inverse_quadratic(1,op_scale,1) - ) - ls.move_source_to(pos) - lake.add(ls.ambient_light, ls.lighthouse) - - self.add(lake) - - self.add(equation, q_mark) - - self.wait() - class InfiniteCircleScene(PiCreatureScene): def construct(self): @@ -4165,6 +4413,73 @@ class InfiniteCircleScene(PiCreatureScene): self.wait() +class Credits(Scene): + def construct(self): + credits = VGroup(*[ + VGroup(*map(TextMobject, pair)) + for pair in [ + ("Primary writer and animator:", "Ben Hambrecht"), + ("Editing, advising, narrating:", "Grant Sanderson"), + ("Based on a paper originally by:", "Johan Wästlund"), + ] + ]) + for credit, color in zip(credits, [MAROON_D, BLUE_D, WHITE]): + credit[1].highlight(color) + credit.arrange_submobjects(DOWN, buff = SMALL_BUFF) + + credits.arrange_submobjects(DOWN, buff = LARGE_BUFF) + + credits.center() + patreon_logo = PatreonLogo() + patreon_logo.to_edge(UP) + + for credit in credits: + self.play(LaggedStart(FadeIn, credit[0])) + self.play(FadeIn(credit[1])) + self.wait() + self.play( + credits.next_to, patreon_logo.get_bottom(), DOWN, MED_LARGE_BUFF, + DrawBorderThenFill(patreon_logo) + ) + self.wait() + +class Promotion(PiCreatureScene): + CONFIG = { + "seconds_to_blink" : 5, + } + def construct(self): + url = TextMobject("https://brilliant.org/3b1b/") + url.to_corner(UP+LEFT) + + rect = Rectangle(height = 9, width = 16) + rect.scale_to_fit_height(5.5) + rect.next_to(url, DOWN) + rect.to_edge(LEFT) + + self.play( + Write(url), + self.pi_creature.change, "raise_right_hand" + ) + self.play(ShowCreation(rect)) + self.wait(2) + self.change_mode("thinking") + self.wait() + self.look_at(url) + self.wait(10) + self.change_mode("happy") + self.wait(10) + self.change_mode("raise_right_hand") + self.wait(10) + + self.remove(rect) + self.play( + url.next_to, self.pi_creature, UP+LEFT + ) + url_rect = SurroundingRectangle(url) + self.play(ShowCreation(url_rect)) + self.play(FadeOut(url_rect)) + self.wait(3) + class BaselPatreonThanks(PatreonEndScreen): CONFIG = { "specific_patrons" : [ @@ -4182,7 +4497,7 @@ class BaselPatreonThanks(PatreonEndScreen): "Achille Brighton", "Rish Kundalia", "Yana Chernobilsky", - "Shìmín kuāng", + "Shìmín Ku$\\overline{\\text{a}}$ng", "Mathew Bramson", "Jerry Ling", "Mustafa Mahdi", @@ -4272,8 +4587,6 @@ class BaselPatreonThanks(PatreonEndScreen): self.add_foreground_mobject(next_video) PatreonEndScreen.construct(self) - - class Thumbnail(Scene): CONFIG = { "light_source_config" : { @@ -4291,7 +4604,7 @@ class Thumbnail(Scene): ) equation.scale(1.8) equation.move_to(2*UP) - equation.set_stroke(BLACK, 1) + equation.set_stroke(RED, 1) answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) answer.scale(3) answer.set_stroke(RED, 1) @@ -4336,7 +4649,3 @@ class Thumbnail(Scene): - - - - From d0b32a9eb9289d612521bc5feb5716278d7390ab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 4 Mar 2018 10:43:35 -0800 Subject: [PATCH 69/73] Randomize patron names --- topics/common_scenes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 4d1354f6..265e210c 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -123,8 +123,11 @@ class PatreonEndScreen(PatreonThanks): "n_patron_columns" : 3, "max_patron_width" : 3, "run_time" : 20, + "randomize_order" : True, } def construct(self): + if self.randomize_order: + random.shuffle(self.specific_patrons) self.add_title() self.scroll_through_patrons() @@ -141,7 +144,6 @@ class PatreonEndScreen(PatreonThanks): pi.next_to(title, vect, buff = MED_LARGE_BUFF) self.add_foreground_mobjects(title, randy, morty) - def scroll_through_patrons(self): logo_box = Square(side_length = 2.5) logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) From d4d6c1ee20b2c0ee09ee16e01572936f46d9be23 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 5 Mar 2018 19:37:03 -0800 Subject: [PATCH 70/73] Changed thumbnail lighthouse brightness --- active_projects/basel2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_projects/basel2.py b/active_projects/basel2.py index 8d8c804b..dd3049a9 100644 --- a/active_projects/basel2.py +++ b/active_projects/basel2.py @@ -4593,7 +4593,7 @@ class Thumbnail(Scene): "num_levels" : 250, "radius" : 10.0, "max_opacity_ambient" : 1.0, - "opacity_function" : inverse_quadratic(1,0.5,1) + "opacity_function" : inverse_quadratic(1,0.25,1) } } def construct(self): From 86f81a2ede230ebf33794454a42f9b6b122497fc Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 5 Mar 2018 20:14:43 -0800 Subject: [PATCH 71/73] Add warning about light_source --- topics/light.py | 580 ++++++++++++++++++++++++------------------------ 1 file changed, 286 insertions(+), 294 deletions(-) diff --git a/topics/light.py b/topics/light.py index 2e239654..587d9c08 100644 --- a/topics/light.py +++ b/topics/light.py @@ -42,11 +42,293 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) +class SwitchOn(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } -# Note: Overall, this class seems perfectly reasonable to me, the main -# thing to be wary of is that calling self.add(submob) puts that submob -# at the end of the submobjects list, and hence on top of everything else -# which is why the shadow might sometimes end up behind the spotlight + def __init__(self, light, **kwargs): + if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): + raise Exception("Only AmbientLights and Spotlights can be switched on") + LaggedStart.__init__( + self, FadeIn, light, **kwargs + ) + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): + raise Exception("Only AmbientLights and Spotlights can be switched off") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + +class Lighthouse(SVGMobject): + CONFIG = { + "file_name" : "lighthouse", + "height" : LIGHTHOUSE_HEIGHT, + "fill_color" : WHITE, + "fill_opacity" : 1.0, + } + + def move_to(self,point): + self.next_to(point, DOWN, buff = 0) + +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * an opacity function + # * a light color + # * a max opacity + # * a radius (larger than the opacity's dropoff length) + # * the number of subdivisions (levels, annuli) + + CONFIG = { + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : NUM_LEVELS, + "radius" : 5.0 + } + + def generate_points(self): + # in theory, this method is only called once, right? + # so removing submobs shd not be necessary + # + # Note: Usually, yes, it is only called within Mobject.__init__, + # but there is no strong guarantee of that, and you may want certain + # update functions to regenerate points here and there. + for submob in self.submobjects: + self.remove(submob) + + self.add(self.source_point) + + # create annuli + self.radius = float(self.radius) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_to(self.get_source_point()) + self.add(annulus) + + + + def move_source_to(self,point): + #old_source_point = self.get_source_point() + #self.shift(point - old_source_point) + self.move_to(point) + + return self + + + + + + def get_source_point(self): + return self.source_point.get_location() + + + + + + + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha / old_alpha + submob.set_fill(opacity = new_submob_alpha) + +class Spotlight(VMobject): + CONFIG = { + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, + "color" : GREEN, # LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 10.0, + "screen" : None, + "camera_mob": None + } + + def projection_direction(self): + # Note: This seems reasonable, though for it to work you'd + # need to be sure that any 3d scene including a spotlight + # somewhere assigns that spotlights "camera" attribute + # to be the camera associated with that scene. + if self.camera_mob == None: + return OUT + else: + [phi, theta, r] = self.camera_mob.get_center() + v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)]) + return v #/np.linalg.norm(v) + + def project(self,point): + v = self.projection_direction() + w = project_along_vector(point,v) + return w + + def get_source_point(self): + return self.source_point.get_location() + + def generate_points(self): + self.submobjects = [] + + self.add(self.source_point) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + self.radius = float(self.radius) + dr = self.radius / self.num_levels + lower_ray, upper_ray = self.viewing_rays(self.screen) + + for r in np.arange(0, self.radius, dr): + new_sector = self.new_sector(r,dr,lower_angle,upper_angle) + self.add(new_sector) + + def new_sector(self,r,dr,lower_angle,upper_angle): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + # rotate (not project) it into the viewing plane + rotation_matrix = z_to_vector(self.projection_direction()) + annular_sector.apply_matrix(rotation_matrix) + # now rotate it inside that plane + rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T) + projected_RIGHT = self.project(RIGHT) + omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT) + annular_sector.rotate(omega, axis = self.projection_direction()) + annular_sector.move_arc_center_to(self.get_source_point()) + + return annular_sector + + def viewing_angle_of_point(self,point): + # as measured from the positive x-axis + v1 = self.project(RIGHT) + v2 = self.project(np.array(point) - self.get_source_point()) + absolute_angle = angle_between_vectors(v1, v2) + # determine the angle's sign depending on their plane's + # choice of orientation. That choice is set by the camera + # position, i. e. projection direction + + if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0: + return absolute_angle + else: + return -absolute_angle + + def viewing_angles(self,screen): + + screen_points = screen.get_anchors() + projected_screen_points = map(self.project,screen_points) + + viewing_angles = np.array(map(self.viewing_angle_of_point, + projected_screen_points)) + + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: + lower_angle = np.min(viewing_angles) + upper_angle = np.max(viewing_angles) + + if upper_angle - lower_angle > TAU/2: + lower_angle, upper_angle = upper_angle, lower_angle + TAU + return lower_angle, upper_angle + + def viewing_rays(self,screen): + + lower_angle, upper_angle = self.viewing_angles(screen) + projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT)) + lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction()) + upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction()) + + return lower_ray, upper_ray + + def opening_angle(self): + l,u = self.viewing_angles(self.screen) + return u - l + + def start_angle(self): + l,u = self.viewing_angles(self.screen) + return l + + def stop_angle(self): + l,u = self.viewing_angles(self.screen) + return u + + def move_source_to(self,point): + self.source_point.set_location(np.array(point)) + #self.source_point.move_to(np.array(point)) + #self.move_to(point) + self.update_sectors() + return self + + def update_sectors(self): + if self.screen == None: + return + for submob in self.submobjects: + if type(submob) == AnnularSector: + lower_angle, upper_angle = self.viewing_angles(self.screen) + #dr = submob.outer_radius - submob.inner_radius + dr = self.radius / self.num_levels + new_submob = self.new_sector( + submob.inner_radius, dr, lower_angle, upper_angle + ) + # submob.points = new_submob.points + # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) + Transform(submob, new_submob).update(1) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + # Note: Maybe it'd be best to have a Shadow class so that the + # type can be checked directly? + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + def change_opacity_function(self,new_f): + self.opacity_function = new_f + dr = self.radius/self.num_levels + + sectors = [] + for submob in self.submobjects: + if type(submob) == AnnularSector: + sectors.append(submob) + + for (r,submob) in zip(np.arange(0,self.radius,dr),sectors): + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + alpha = self.opacity_function(r) + submob.set_fill(opacity = alpha) + +# Warning: This class is likely quite buggy. class LightSource(VMobject): # combines: # a lighthouse @@ -308,296 +590,6 @@ class LightSource(VMobject): # shift it closer to the camera so it is in front of the spotlight self.shadow.mark_paths_closed = True - -class SwitchOn(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): - raise Exception("Only AmbientLights and Spotlights can be switched on") - LaggedStart.__init__( - self, FadeIn, light, **kwargs - ) - -class SwitchOff(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): - raise Exception("Only AmbientLights and Spotlights can be switched off") - light.submobjects = light.submobjects[::-1] - LaggedStart.__init__(self, - FadeOut, light, **kwargs) - light.submobjects = light.submobjects[::-1] - -class Lighthouse(SVGMobject): - CONFIG = { - "file_name" : "lighthouse", - "height" : LIGHTHOUSE_HEIGHT, - "fill_color" : WHITE, - "fill_opacity" : 1.0, - } - - def move_to(self,point): - self.next_to(point, DOWN, buff = 0) - -class AmbientLight(VMobject): - - # Parameters are: - # * a source point - # * an opacity function - # * a light color - # * a max opacity - # * a radius (larger than the opacity's dropoff length) - # * the number of subdivisions (levels, annuli) - - CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : NUM_LEVELS, - "radius" : 5.0 - } - - def generate_points(self): - # in theory, this method is only called once, right? - # so removing submobs shd not be necessary - # - # Note: Usually, yes, it is only called within Mobject.__init__, - # but there is no strong guarantee of that, and you may want certain - # update functions to regenerate points here and there. - for submob in self.submobjects: - self.remove(submob) - - self.add(self.source_point) - - # create annuli - self.radius = float(self.radius) - dr = self.radius / self.num_levels - for r in np.arange(0, self.radius, dr): - alpha = self.max_opacity * self.opacity_function(r) - annulus = Annulus( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha - ) - annulus.move_to(self.get_source_point()) - self.add(annulus) - - - - def move_source_to(self,point): - #old_source_point = self.get_source_point() - #self.shift(point - old_source_point) - self.move_to(point) - - return self - - - - - - def get_source_point(self): - return self.source_point.get_location() - - - - - - - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha / old_alpha - submob.set_fill(opacity = new_submob_alpha) - - -class Spotlight(VMobject): - CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), - "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, - "color" : GREEN, # LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 10.0, - "screen" : None, - "camera_mob": None - } - - def projection_direction(self): - # Note: This seems reasonable, though for it to work you'd - # need to be sure that any 3d scene including a spotlight - # somewhere assigns that spotlights "camera" attribute - # to be the camera associated with that scene. - if self.camera_mob == None: - return OUT - else: - [phi, theta, r] = self.camera_mob.get_center() - v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)]) - return v #/np.linalg.norm(v) - - def project(self,point): - v = self.projection_direction() - w = project_along_vector(point,v) - return w - - def get_source_point(self): - return self.source_point.get_location() - - def generate_points(self): - self.submobjects = [] - - self.add(self.source_point) - - if self.screen != None: - # look for the screen and create annular sectors - lower_angle, upper_angle = self.viewing_angles(self.screen) - self.radius = float(self.radius) - dr = self.radius / self.num_levels - lower_ray, upper_ray = self.viewing_rays(self.screen) - - for r in np.arange(0, self.radius, dr): - new_sector = self.new_sector(r,dr,lower_angle,upper_angle) - self.add(new_sector) - - def new_sector(self,r,dr,lower_angle,upper_angle): - alpha = self.max_opacity * self.opacity_function(r) - annular_sector = AnnularSector( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha, - start_angle = lower_angle, - angle = upper_angle - lower_angle - ) - # rotate (not project) it into the viewing plane - rotation_matrix = z_to_vector(self.projection_direction()) - annular_sector.apply_matrix(rotation_matrix) - # now rotate it inside that plane - rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T) - projected_RIGHT = self.project(RIGHT) - omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT) - annular_sector.rotate(omega, axis = self.projection_direction()) - annular_sector.move_arc_center_to(self.get_source_point()) - - return annular_sector - - def viewing_angle_of_point(self,point): - # as measured from the positive x-axis - v1 = self.project(RIGHT) - v2 = self.project(np.array(point) - self.get_source_point()) - absolute_angle = angle_between_vectors(v1, v2) - # determine the angle's sign depending on their plane's - # choice of orientation. That choice is set by the camera - # position, i. e. projection direction - - if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0: - return absolute_angle - else: - return -absolute_angle - - def viewing_angles(self,screen): - - screen_points = screen.get_anchors() - projected_screen_points = map(self.project,screen_points) - - viewing_angles = np.array(map(self.viewing_angle_of_point, - projected_screen_points)) - - lower_angle = upper_angle = 0 - if len(viewing_angles) != 0: - lower_angle = np.min(viewing_angles) - upper_angle = np.max(viewing_angles) - - if upper_angle - lower_angle > TAU/2: - lower_angle, upper_angle = upper_angle, lower_angle + TAU - return lower_angle, upper_angle - - def viewing_rays(self,screen): - - lower_angle, upper_angle = self.viewing_angles(screen) - projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT)) - lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction()) - upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction()) - - return lower_ray, upper_ray - - def opening_angle(self): - l,u = self.viewing_angles(self.screen) - return u - l - - def start_angle(self): - l,u = self.viewing_angles(self.screen) - return l - - def stop_angle(self): - l,u = self.viewing_angles(self.screen) - return u - - def move_source_to(self,point): - self.source_point.set_location(np.array(point)) - #self.source_point.move_to(np.array(point)) - #self.move_to(point) - self.update_sectors() - return self - - def update_sectors(self): - if self.screen == None: - return - for submob in self.submobjects: - if type(submob) == AnnularSector: - lower_angle, upper_angle = self.viewing_angles(self.screen) - #dr = submob.outer_radius - submob.inner_radius - dr = self.radius / self.num_levels - new_submob = self.new_sector( - submob.inner_radius, dr, lower_angle, upper_angle - ) - # submob.points = new_submob.points - # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) - Transform(submob, new_submob).update(1) - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - # Note: Maybe it'd be best to have a Shadow class so that the - # type can be checked directly? - if type(submob) != AnnularSector: - # it's the shadow, don't dim it - continue - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) - - def change_opacity_function(self,new_f): - self.opacity_function = new_f - dr = self.radius/self.num_levels - - sectors = [] - for submob in self.submobjects: - if type(submob) == AnnularSector: - sectors.append(submob) - - for (r,submob) in zip(np.arange(0,self.radius,dr),sectors): - if type(submob) != AnnularSector: - # it's the shadow, don't dim it - continue - alpha = self.opacity_function(r) - submob.set_fill(opacity = alpha) - - - class ScreenTracker(ContinualAnimation): def __init__(self, light_source, **kwargs): self.light_source = light_source From 53521a623f007c8fe570dc54b3dbc818a3b065cf Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 5 Mar 2018 20:15:58 -0800 Subject: [PATCH 72/73] Retiring basel animations to old_projects --- {active_projects => old_projects/basel}/basel.py | 0 {active_projects => old_projects/basel}/basel2.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {active_projects => old_projects/basel}/basel.py (100%) rename {active_projects => old_projects/basel}/basel2.py (100%) diff --git a/active_projects/basel.py b/old_projects/basel/basel.py similarity index 100% rename from active_projects/basel.py rename to old_projects/basel/basel.py diff --git a/active_projects/basel2.py b/old_projects/basel/basel2.py similarity index 100% rename from active_projects/basel2.py rename to old_projects/basel/basel2.py From aba43479f0ea6997114151bf2f40224dfb89c29a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 5 Mar 2018 20:25:01 -0800 Subject: [PATCH 73/73] Poor man's merge --- animation/simple_animations.py | 28 +++++++++++++++------------- mobject/vectorized_mobject.py | 13 +++++++------ topics/number_line.py | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 484e161f..f035353a 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -358,7 +358,6 @@ class Succession(Animation): Each arg will either be an animation, or an animation class followed by its arguments (and potentially a dict for configuration). - For example, Succession( ShowCreation(circle), @@ -443,13 +442,12 @@ class Succession(Animation): # Beware: This does NOT take care of calling update(0) on the subanimation. # This was important to avoid a pernicious possibility in which subanimations were called - # with update(0) twice, which could in turn call a sub-Succession with update(0) four times, + # with update twice, which could in turn call a sub-Succession with update four times, # continuing exponentially. def jump_to_start_of_anim(self, index): if index != self.current_anim_index: self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method... - for m in self.scene_mobjects_at_time[index].submobjects: - self.mobject.add(m) + self.mobject.add(*self.scene_mobjects_at_time[index].submobjects) self.mobject.add(self.animations[index].mobject) for i in range(index): @@ -459,10 +457,9 @@ class Succession(Animation): self.current_alpha = self.critical_alphas[index] def update_mobject(self, alpha): - if alpha == self.current_alpha: - return - if self.num_anims == 0: + # This probably doesn't matter for anything, but just in case, + # we want it in the future, we set current_alpha even in this case self.current_alpha = alpha return @@ -505,16 +502,14 @@ class AnimationGroup(Animation): "rate_func" : None } def __init__(self, *sub_anims, **kwargs): - digest_config(self, kwargs, locals()) sub_anims = filter (lambda x : not(x.empty), sub_anims) + digest_config(self, locals()) + self.update_config(**kwargs) # Handles propagation to self.sub_anims + if len(sub_anims) == 0: self.empty = True self.run_time = 0 else: - for anim in sub_anims: - # If AnimationGroup is called with any configuration, - # it is propagated to the sub_animations - anim.update_config(**kwargs) self.run_time = max([a.run_time for a in sub_anims]) everything = Mobject(*[a.mobject for a in sub_anims]) Animation.__init__(self, everything, **kwargs) @@ -527,6 +522,14 @@ class AnimationGroup(Animation): for anim in self.sub_anims: anim.clean_up(*args, **kwargs) + def update_config(self, **kwargs): + Animation.update_config(self, **kwargs) + + # If AnimationGroup is called with any configuration, + # it is propagated to the sub_animations + for anim in self.sub_anims: + anim.update_config(**kwargs) + class EmptyAnimation(Animation): CONFIG = { "run_time" : 0, @@ -535,4 +538,3 @@ class EmptyAnimation(Animation): def __init__(self, *args, **kwargs): return Animation.__init__(self, Group(), *args, **kwargs) - diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index a3ac3372..0b13a3bb 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -54,10 +54,14 @@ class VMobject(Mobject): if fill_opacity is not None: self.fill_opacity = fill_opacity if family: - kwargs = locals() - kwargs.pop("self") for mob in self.submobjects: - mob.set_style_data(**kwargs) + mob.set_style_data( + stroke_color = stroke_color, + stroke_width = stroke_width, + fill_color = fill_color, + fill_opacity = fill_opacity, + family = family + ) return self def set_fill(self, color = None, opacity = None, family = True): @@ -244,7 +248,6 @@ class VMobject(Mobject): a single "path", in the svg sense of the word. However, one such path may really consist of separate continuous components if there is a move_to command. - These other portions of the path will be treated as submobjects, but will be tracked in a separate special list for when it comes time to display. @@ -287,7 +290,6 @@ class VMobject(Mobject): If the distance between a given handle point H and its associated anchor point A is d, then it changes H to be a distances factor*d away from A, but so that the line from A to H doesn't change. - This is mostly useful in the context of applying a (differentiable) function, to preserve tangency properties. One would pull all the handles closer to their anchors, apply the function then push them out @@ -481,4 +483,3 @@ class VectorizedPoint(VMobject): def set_location(self,new_loc): self.set_points(np.array([new_loc])) - diff --git a/topics/number_line.py b/topics/number_line.py index 3848feba..cd8da287 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -222,6 +222,28 @@ class Axes(VGroup): return graph def input_to_graph_point(self, x, graph): + if hasattr(graph, "underlying_function"): + return self.coords_to_point(x, graph.underlying_function(x)) + else: + #binary search + lh, rh = 0, 1 + while abs(lh - rh) > 0.001: + mh = np.mean([lh, rh]) + hands = [lh, mh, rh] + points = map(graph.point_from_proportion, hands) + lx, mx, rx = map(self.x_axis.point_to_number, points) + if lx <= x and rx >= x: + if mx > x: + rh = mh + else: + lh = mh + elif lx <= x and rx <= x: + return points[2] + elif lx >= x and rx >= x: + return points[0] + elif lx > x and rx < x: + lh, rh = rh, lh + return points[1] return self.coords_to_point(x, graph.underlying_function(x)) class ThreeDAxes(Axes):