diff --git a/README.md b/README.md index 946a8d5f..abedff72 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ pip install -r requirements.txt ``` Note: pip will install the python module `aggdraw` from -https://github.com/scottopell/aggdraw-64bits/ and it might requires additional +https://github.com/scottopell/aggdraw-64bits/ and it might have additional dependencies. This doesn't install freetype, but I don't think it's required for this project diff --git a/active_projects/basel.py b/active_projects/basel.py deleted file mode 100644 index d08b7be3..00000000 --- a/active_projects/basel.py +++ /dev/null @@ -1,1659 +0,0 @@ -#!/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 * - - -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) - - -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() - - - - - - - - - - - - - - - - - - - - - - - - -class IntroScene(PiCreatureScene): - - CONFIG = { - "rect_height" : 0.2, - "duration" : 1.0, - "eq_spacing" : 3 * 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.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( - "1", "+", - "{1 \\over 4}", "+", - "{1 \\over 9}", "+", - "{1 \\over 16}", "+", - "{1 \\over 25}", "+", - "\\cdots", "=", - arg_separator = " \\, " - ) - - self.euler_sum.to_edge(UP) - self.euler_sum.shift(2*LEFT) - - terms = [1./n**2 for n in range(1,6)] - partial_results_values = np.cumsum(terms) - - self.play( - FadeIn(self.euler_sum[0], run_time = self.duration) - ) - - equals_sign = self.euler_sum.get_part_by_tex("=") - - self.partial_sum_decimal = DecimalNumber(partial_results_values[1], - num_decimal_points = 2) - self.partial_sum_decimal.next_to(equals_sign, RIGHT) - - - - for i in range(4): - - FadeIn(self.partial_sum_decimal, run_time = self.duration) - - if i == 0: - - self.play( - FadeIn(self.euler_sum[1], run_time = self.duration), - FadeIn(self.euler_sum[2], run_time = self.duration), - FadeIn(equals_sign, run_time = self.duration), - FadeIn(self.partial_sum_decimal, run_time = self.duration) - ) - - else: - self.play( - FadeIn(self.euler_sum[2*i+1], run_time = self.duration), - FadeIn(self.euler_sum[2*i+2], run_time = self.duration), - ChangeDecimalToValue( - self.partial_sum_decimal, - partial_results_values[i+1], - run_time = self.duration, - num_decimal_points = 6, - show_ellipsis = True, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ) - - self.wait() - - self.q_marks = TextMobject("???").highlight(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): - - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1, - stroke_width = 1, - numbers_with_elongated_ticks = [0,1,2,3], - numbers_to_show = np.arange(0,5), - unit_size = 5, - tick_frequency = 0.2, - line_to_number_buff = MED_LARGE_BUFF - ) - - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() - - # create slabs for series terms - - max_n = 10 - - terms = [0] + [1./(n**2) for n in range(1, max_n + 1)] - series_terms = np.cumsum(terms) - lines = VGroup() - self.rects = VGroup() - slab_colors = [YELLOW, BLUE] * (max_n / 2) - - for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): - line = Line(*map(self.number_line.number_to_point, [t1, t2])) - rect = Rectangle() - rect.stroke_width = 0 - rect.fill_opacity = 1 - rect.highlight(color) - rect.stretch_to_fit_height( - self.rect_height, - ) - rect.stretch_to_fit_width(line.get_width()) - rect.move_to(line) - - self.rects.add(rect) - lines.add(line) - - #self.rects.radial_gradient_highlight(ORIGIN, 5, 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) - ) - - - def show_pi_answer(self): - - self.pi_answer = TexMobject("{\\pi^2 \\over 6}").highlight(YELLOW) - self.pi_answer.move_to(self.partial_sum_decimal) - self.pi_answer.next_to(self.euler_sum[-1], RIGHT, - submobject_to_align = self.pi_answer[-2]) - self.play(ReplacementTransform(self.q_marks, self.pi_answer)) - - - def other_pi_formulas(self): - - self.play( - FadeOut(self.rects), - FadeOut(self.number_line_labels), - FadeOut(self.number_line) - ) - - self.leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") - - self.wallis_product = TexMobject( - "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + - "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") - - self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, - submobject_to_align = self.leibniz_sum.get_part_by_tex("=") - ) - - self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, - submobject_to_align = self.wallis_product.get_part_by_tex("=") - ) - - - self.play( - Write(self.leibniz_sum) - ) - self.play( - Write(self.wallis_product) - ) - - - - def refocus_on_euler_sum(self): - - self.euler_sum.add(self.pi_answer) - - self.play( - FadeOut(self.leibniz_sum), - FadeOut(self.wallis_product), - ApplyMethod(self.euler_sum.shift, - ORIGIN + 2*UP - self.euler_sum.get_center()) - ) - - # focus on pi squared - pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] - self.play( - ScaleInPlace(pi_squared,2,rate_func = wiggle) - ) - - - - # Morty 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 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.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.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() - - - 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, - #screen = self.screen - ) - 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 - - 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.wait() - self.play(FadeIn(self.screen)) - self.wait() - - self.add_foreground_mobject(self.screen) - - 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), - ) - - 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.source_point, SLANTING_AMOUNT - ) - upper_slanted_screen_point = interpolate( - upper_screen_point, self.spotlight.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.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.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): - - #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() - - # 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 - 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.5, 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 - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - # #Transform(new_screen0,new_screen) - ) - - 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( - Transform(collected_indicators,sum_indicator) - ) - - - - self.wait() - - - - - -class TwoLightSourcesScene(PiCreatureScene): - - def construct(self): - - MAX_OPACITY = 0.4 - 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] - 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() - #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), - 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? - - # 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) - - - - # 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), - ) - - - - - - - - - diff --git a/animation/simple_animations.py b/animation/simple_animations.py index c489391d..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), @@ -539,4 +538,3 @@ class EmptyAnimation(Animation): def __init__(self, *args, **kwargs): return Animation.__init__(self, Group(), *args, **kwargs) - diff --git a/animation/transform.py b/animation/transform.py index d8a9a617..ec0cb1f0 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -101,9 +101,15 @@ class Swap(CyclicReplace): pass #Renaming, more understandable for two entries class GrowFromPoint(Transform): + CONFIG = { + "point_color" : None, + } def __init__(self, mobject, point, **kwargs): + digest_config(self, kwargs) target = mobject.copy() point_mob = Point(point) + if self.point_color: + point_mob.highlight(self.point_color) mobject.replace(point_mob) mobject.highlight(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs) diff --git a/extract_scene.py b/extract_scene.py index cec13bdf..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" @@ -262,4 +260,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/mobject/mobject.py b/mobject/mobject.py index e45f3bee..2deaeeb5 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -389,6 +389,9 @@ class Mobject(Container): def stretch_to_fit_height(self, height, **kwargs): return self.rescale_to_fit(height, 1, stretch = True, **kwargs) + def stretch_to_fit_depth(self, depth, **kwargs): + return self.rescale_to_fit(depth, 1, stretch = True, **kwargs) + def scale_to_fit_width(self, width, **kwargs): return self.rescale_to_fit(width, 0, stretch = False, **kwargs) diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 8d0fbbc8..8a3fa899 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -37,9 +37,9 @@ class SVGMobject(VMobject): if self.file_name is None: raise Exception("Must specify file for SVGMobject") possible_paths = [ - self.file_name, os.path.join(SVG_IMAGE_DIR, self.file_name), os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"), + self.file_name, ] for path in possible_paths: if os.path.exists(path): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index a59f1c13..ef482bbd 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -247,7 +247,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. @@ -290,7 +289,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 @@ -484,4 +482,3 @@ class VectorizedPoint(VMobject): def set_location(self,new_loc): self.set_points(np.array([new_loc])) - diff --git a/old_projects/basel/basel.py b/old_projects/basel/basel.py new file mode 100644 index 00000000..20814be5 --- /dev/null +++ b/old_projects/basel/basel.py @@ -0,0 +1,4536 @@ +#!/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 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_CONES = 7 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 + +NUM_LEVELS = 15 +AMBIENT_FULL = 0.8 +AMBIENT_DIMMED = 0.5 +AMBIENT_SCALE = 2.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 + + +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) + + +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(VMobject): + 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.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 + + 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 IntroScene(PiCreatureScene): + + CONFIG = { + "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.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( + "1", "+", + "{1 \\over 4}", "+", + "{1 \\over 9}", "+", + "{1 \\over 16}", "+", + "{1 \\over 25}", "+", + "\\cdots", "=", + arg_separator = " \\, " + ) + + self.euler_sum.to_edge(UP) + self.euler_sum.shift(2*LEFT) + + terms = [1./n**2 for n in range(1,6)] + partial_results_values = np.cumsum(terms) + + self.play( + FadeIn(self.euler_sum[0], run_time = self.duration) + ) + + equals_sign = self.euler_sum.get_part_by_tex("=") + + self.partial_sum_decimal = DecimalNumber(partial_results_values[1], + num_decimal_points = 2) + self.partial_sum_decimal.next_to(equals_sign, RIGHT) + + + + for i in range(4): + + FadeIn(self.partial_sum_decimal, run_time = self.duration) + + if i == 0: + + self.play( + FadeIn(self.euler_sum[1], run_time = self.duration), + FadeIn(self.euler_sum[2], run_time = self.duration), + FadeIn(equals_sign, run_time = self.duration), + FadeIn(self.partial_sum_decimal, run_time = self.duration) + ) + + else: + self.play( + FadeIn(self.euler_sum[2*i+1], run_time = self.duration), + FadeIn(self.euler_sum[2*i+2], run_time = self.duration), + ChangeDecimalToValue( + self.partial_sum_decimal, + partial_results_values[i+1], + run_time = self.duration, + num_decimal_points = 6, + show_ellipsis = True, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ) + + self.wait() + + self.q_marks = TextMobject("???").highlight(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, + 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 + ).shift(LEFT) + + self.number_line_labels = self.number_line.get_number_mobjects() + self.play( + FadeIn(self.number_line), + FadeIn(self.number_line_labels) + ) + self.wait() + + # create slabs for series terms + + max_n1 = 10 + max_n2 = 100 + + 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.stretch_to_fit_width(0.5 * line.get_width()) + rect.move_to(line) + + self.rects.add(rect) + lines.add(line) + + #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: + 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 = 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, 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.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", + "=", "\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", + "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ") + + self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, + 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 = 2, + 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( + WiggleOutThenIn(pi_squared, + scale_value = 4, + angle = 0.003 * TAU, + run_time = 2 + ) + ) + + + + # Morty thinks of a circle + + q_circle = Circle( + stroke_color = YELLOW, + fill_color = YELLOW, + 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.6 * q_circle.get_height()) + + self.look_at(pi_squared) + self.pi_creature_thinks(thought,target_mode = "confused", + bubble_kwargs = { "height" : 2.5, "width" : 5 }) + self.look_at(pi_squared) + + self.wait() + + + + + + + + + + + + + + + + + + + + +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.wait() + 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,AMBIENT_SCALE,1), + num_levels = NUM_LEVELS, + 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) + + 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 = 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) + 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], + 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] + new_y = self.number_line.get_center()[1] + self.play( + 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 + 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,SPOTLIGHT_SCALE,1), + num_levels = NUM_LEVELS, + radius = 10, + max_opacity_ambient = AMBIENT_FULL, + max_opacity_spotlight = SPOTLIGHT_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.06, + 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.play(SwitchOn(self.light_source.spotlight)) + + + 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( + 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.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) + + self.wait() + + + + + 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", + 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 + 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 + + +class MorphIntoSunScene(PiCreatureScene): + def construct(self): + self.setup_elements() + self.morph_lighthouse_into_sun() + + def setup_elements(self): + self.remove(self.get_primary_pi_creature()) + + SCREEN_SIZE = 3.0 + 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.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.light_source.ambient_light) + + + # Screen + + self.screen = Rectangle( + width = 0.06, + height = 2, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) + + 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() + + + + + + + + + + + + + + + + + + + +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([ + [0,-self.screen_height/2,0], + [0,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.add(self.screen) + earth.move_to(earth_center) + #self.remove(self.screen_tracker) + + theta0 = 70 * DEGREES + dtheta = 10 * DEGREES + theta1 = theta0 + dtheta + theta = (theta0 + theta1)/2 + + 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) + 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), + FadeIn(background_earth) + ) + self.add_foreground_mobject(earth) + self.add_foreground_mobject(self.screen) + + + # 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) + polar_morty.highlight(BLUE_C) + + 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])) + tropical_morty.highlight(RED) + + 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 InscribedAngleScene(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.3 + 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 + + + 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.force_skipping() + + 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) + self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot) #, self.ls0_dot) + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) + + # Morty and indicator + morty = Mortimer().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,AMBIENT_SCALE,LIGHT_CUTOFF) + 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) + + self.add(lake0,morty,self.obs_dot,self.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(self.obs_dot), + FadeOut(self.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) + ) + + # 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(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.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), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + + + + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): + + # we assume that the scene contains: + # an inner lake, self.inner_lake + # an outer lake, self.outer_lake + # light sources, self.light_sources + # legs from the observer point to each light source + # self.legs + # altitudes from the observer point to the + # locations of the light sources in the previous step + # self.altitudes + # hypotenuses connecting antipodal light sources + # self.hypotenuses + + # these are mobjects! + + + # first, fade out all of the hypotenuses and altitudes + + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + else: + self.zoomable_mobs.remove(self.inner_lake) + self.play( + FadeOut(self.inner_lake) + ) + + # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP + + new_outer_lake = Circle(radius = self.lake_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(self.lake_center) + + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(self.ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) + + self.wait() + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time + ) + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = VMobject() + for leg in self.new_legs_1: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) + + self.hypotenuses = VMobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) + + # update scene + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) + + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, + ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) + + + self.wait() + + if show_steps == True: + self.play(FadeOut(self.ls0_dot)) + + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 + + + + + + + + + self.lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = VMobject() + self.outer_lake = lake0 + self.legs = VMobject() + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) + self.altitudes = VMobject() + self.hypotenuses = VMobject() + self.light_sources_array = [ls0] + self.light_sources = VMobject() + self.light_sources.add(ls0) + + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + + self.add(self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + self.revert_to_original_skipping_status() + + construction_step(0) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + return + + construction_step(1) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + + + + self.revert_to_original_skipping_status() + + + ANGLE_COLOR1 = BLUE_C + ANGLE_COLOR2 = GREEN_D + + + 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.5 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 2.5 + LIGHT_CUTOFF = 1 + + RIGHT_ANGLE_SIZE = 0.3 + + self.cumulated_zoom_factor = 1 + + STEP_RUN_TIME = 0.5 + + + #self.force_skipping() + + + 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 + + self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) + + # Morty and indicator + morty = Randolph(color = MAROON_D).scale(0.3) + morty.next_to(OBSERVER_POINT,DOWN) + indicator = LightIndicator(precision = 2, + radius = INDICATOR_RADIUS, + show_reading = False, + color = LIGHT_COLOR + ) + indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) + + # first lighthouse + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 15) + 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,self.ls0_dot, ls0.lighthouse) + self.add_foreground_mobject(morty) + self.add_foreground_mobject(self.obs_dot) + self.add_foreground_mobject(self.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.02) + diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) + + # diameter + diameter = DoubleArrow(diameter_start, + diameter_stop, + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + ShowCreation(diameter), + Write(diameter_text), + #FadeOut(self.obs_dot), + FadeOut(self.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, animate = True, run_time = 1): + + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) + + hyp = VMobject() + hyp1 = Line(self.lake_center,ls_new_loc1) + hyp2 = Line(self.lake_center,ls_new_loc2) + hyp.add(hyp2,hyp1) + self.new_hypotenuses.append(hyp) + + if show_steps == True: + self.play( + ShowCreation(hyp, run_time = run_time) + ) + + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) + self.new_legs_1.append(leg1) + self.new_legs_2.append(leg2) + + if show_steps == True: + self.play( + ShowCreation(leg1, run_time = run_time), + ShowCreation(leg2, run_time = run_time), + ) + + ls1 = self.light_sources_array[i] + + + ls2 = ls1.copy() + if animate == True: + self.add(ls2) + + self.additional_light_sources.append(ls2) + + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.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), + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + + + + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): + + # we assume that the scene contains: + # an inner lake, self.inner_lake + # an outer lake, self.outer_lake + # light sources, self.light_sources + # legs from the observer point to each light source + # self.legs + # altitudes from the observer point to the + # locations of the light sources in the previous step + # self.altitudes + # hypotenuses connecting antipodal light sources + # self.hypotenuses + + # these are mobjects! + + + # first, fade out all of the hypotenuses and altitudes + + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + else: + self.zoomable_mobs.remove(self.inner_lake) + self.play( + FadeOut(self.inner_lake) + ) + + # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP + + new_outer_lake = Circle(radius = self.lake_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(self.lake_center) + + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(self.ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) + + self.wait() + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + # WE ALWAYS USE THIS CASE BRANCH + 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() + + + + # WE DON'T USE THIS CASE BRANCH ANYMORE + else: # simultaneous splitting + + old_lake = self.outer_lake.copy() + old_ls = self.light_sources.copy() + old_ls2 = old_ls.copy() + for submob in old_ls2.submobjects: + old_ls.add(submob) + + self.remove(self.outer_lake, self.light_sources) + self.add(old_lake, old_ls) + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + animate = False + ) + + self.play( + ReplacementTransform(old_ls, self.light_sources, run_time = run_time), + ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), + ) + + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = VMobject() + for leg in self.new_legs_1: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) + + self.hypotenuses = VMobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) + + # update scene + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) + + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, + ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) + + + self.wait() + + if show_steps == True: + self.play(FadeOut(self.ls0_dot)) + + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 + + + + + + + + + self.lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = VMobject() + self.outer_lake = lake0 + self.legs = VMobject() + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) + self.altitudes = VMobject() + self.hypotenuses = VMobject() + self.light_sources_array = [ls0] + self.light_sources = VMobject() + self.light_sources.add(ls0) + + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + + self.add(self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + + construction_step(0, run_time = STEP_RUN_TIME) + + 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, run_time = STEP_RUN_TIME) + indicator_wiggle() + #self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + + construction_step(2, run_time = STEP_RUN_TIME) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + + + + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + max_it = 10 + scale = 2**(max_it - 5) + 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) + + + #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 (i,ls) in enumerate(self.light_sources_array[:2**n]): + #print i + 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() + + self.play(Transform(self.outer_lake,new_lake)) + shift_list = [] + + for i in range(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 + ls1 = self.light_sources.submobjects[i] + ls2 = self.light_sources.submobjects[i+2**n] + + 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) + print "...done" + + + #self.revert_to_original_skipping_status() + + # Now create a straight number line and transform into it + MAX_N = 7 + + origin_point = self.obs_dot.get_center() + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + numbers_with_elongated_ticks = [], + 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(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() + 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) + for ls in self.light_sources_array: + self.remove(ls) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + 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(self.obs_dot.get_center(),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) + ) + 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 + 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.tick_marks), + ) + + 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)) + + 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) + + + + 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_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") + + + + + + + + +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.highlight(MAROON_D) + randy.color = MAROON_D + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + self.wait() + + ls_template = LightSource( + radius = 1, + num_levels = 10, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.75,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_to_show = full_range, + number_scale_val = 0.5, + numbers_with_elongated_ticks = [], + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_SMALL_BUFF, + include_tip = True, + label_direction = UP, + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) + self.number_line1.add_numbers() + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1, run_time = 5), + ) + self.wait() + + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, + stroke_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + result1.shift(0.87 * vertical_spacing) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + self.number_line2.add_numbers() + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2, run_time = 5), + ) + self.wait() + + + full_lighthouses = VMobject() + full_ambient_lights = VMobject() + for ls in full_lights: + full_lighthouses.add(ls.lighthouse) + full_ambient_lights.add(ls.ambient_light) + + self.play( + LaggedStart(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), + ) + + self.play( + LaggedStart(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) + ) + + # for ls in full_lights.submobjects: + # self.play( + # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + # ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + self.wait() + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights, run_time = 2) + ) + + + self.wait() + + for i in range(6): + self.play( + Transform(even_lights[i], even_lights_copy[i]) + ) + self.wait() + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + ) + self.wait() + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + self.wait() + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + self.wait() + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + self.wait() + + full_terms = VMobject() + for i in range(1,8): #full_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + elif i == 7: + term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + #return + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + self.wait() + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + result1.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + self.wait() + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + self.wait() + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + full_terms.shift,LEFT, + ) + self.wait() + + self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) + + result_box = Rectangle(width = 1.1 * equation.get_width(), + height = 2 * equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + + + self.play( + ShowCreation(result_box) + ) + self.wait() + + + + + + + + + + + + + + + + + +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 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), + ) + + + +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 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)) + + flash_arcs(3) diff --git a/old_projects/basel/basel2.py b/old_projects/basel/basel2.py new file mode 100644 index 00000000..dd3049a9 --- /dev/null +++ b/old_projects/basel/basel2.py @@ -0,0 +1,4651 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.continual_animation import * + +from animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.numerals import * +#from topics.combinatorics import * +from scene import Scene +from scene.zoomed_scene import * +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from topics.three_dimensions import * +from topics.light import * +from topics.objects import * +from topics.common_scenes import * + +import types +import functools + +LIGHT_COLOR = YELLOW +INDICATOR_RADIUS = 0.7 +INDICATOR_STROKE_WIDTH = 1 +INDICATOR_STROKE_COLOR = WHITE +INDICATOR_TEXT_COLOR = WHITE +INDICATOR_UPDATE_TIME = 0.2 +FAST_INDICATOR_UPDATE_TIME = 0.1 +OPACITY_FOR_UNIT_INTENSITY = 0.2 +SWITCH_ON_RUN_TIME = 1.5 +FAST_SWITCH_ON_RUN_TIME = 0.1 +NUM_LEVELS = 30 +NUM_CONES = 7 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 +AMBIENT_FULL = 0.5 +AMBIENT_DIMMED = 0.2 +SPOTLIGHT_FULL = 0.9 +SPOTLIGHT_DIMMED = 0.2 + +LIGHT_COLOR = YELLOW +DEGREES = TAU/360 + +inverse_power_law = lambda maxint,scale,cutoff,exponent: \ + (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) +inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) + +# A = np.array([5.,-3.,0.]) +# B = np.array([-5.,3.,0.]) +# C = np.array([-5.,-3.,0.]) +# xA = A[0] +# yA = A[1] +# xB = B[0] +# yB = B[1] +# xC = C[0] +# yC = C[1] + +# find the coords of the altitude point H +# as the solution of a certain LSE +# prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic +# prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) +# H2 = np.linalg.solve(prelim_matrix,prelim_vector) +# H = np.append(H2, 0.) + +class AngleUpdater(ContinualAnimation): + def __init__(self, angle_arc, spotlight, **kwargs): + self.angle_arc = angle_arc + + self.spotlight = spotlight + ContinualAnimation.__init__(self, self.angle_arc, **kwargs) + + def update_mobject(self, dt): + new_arc = self.angle_arc.copy().set_bound_angles( + start = self.spotlight.start_angle(), + stop = self.spotlight.stop_angle() + ) + new_arc.generate_points() + new_arc.move_arc_center_to(self.spotlight.get_source_point()) + self.angle_arc.points = new_arc.points + self.angle_arc.add_tip( + tip_length = ARC_TIP_LENGTH, + at_start = True, at_end = True + ) + +class LightIndicator(Mobject): + CONFIG = { + "radius": 0.5, + "reading_height" : 0.25, + "intensity": 0, + "opacity_for_unit_intensity": 1, + "fill_color" : YELLOW, + "precision": 3, + "show_reading": True, + "measurement_point": ORIGIN, + "light_source": None + } + + def generate_points(self): + self.background = Circle(color=BLACK, radius = self.radius) + self.background.set_fill(opacity = 1.0) + self.foreground = Circle(color=self.color, radius = self.radius) + self.foreground.set_stroke( + color=INDICATOR_STROKE_COLOR, + width=INDICATOR_STROKE_WIDTH + ) + self.foreground.set_fill(color = self.fill_color) + + self.add(self.background, self.foreground) + self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) + self.reading.set_fill(color=INDICATOR_TEXT_COLOR) + self.reading.scale_to_fit_height(self.reading_height) + self.reading.move_to(self.get_center()) + if self.show_reading: + self.add(self.reading) + + def set_intensity(self, new_int): + self.intensity = new_int + new_opacity = min(1, new_int * self.opacity_for_unit_intensity) + self.foreground.set_fill(opacity=new_opacity) + ChangeDecimalToValue(self.reading, new_int).update(1) + if new_int > 1.1: + self.reading.set_fill(color = BLACK) + else: + self.reading.set_fill(color = WHITE) + return self + + def get_measurement_point(self): + if self.measurement_point is not None: + return self.measurement_point + else: + return self.get_center() + + def measured_intensity(self): + distance = np.linalg.norm( + self.get_measurement_point() - + self.light_source.get_source_point() + ) + intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity + return intensity + + def continual_update(self): + if self.light_source == None: + print "Indicator cannot update, reason: no light source found" + self.set_intensity(self.measured_intensity()) + +class UpdateLightIndicator(AnimationGroup): + + def __init__(self, indicator, intensity, **kwargs): + if not isinstance(indicator,LightIndicator): + raise Exception("This transform applies only to LightIndicator") + + target_foreground = indicator.copy().set_intensity(intensity).foreground + change_opacity = Transform( + indicator.foreground, target_foreground + ) + changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) + + AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) + self.mobject = indicator + +class ContinualLightIndicatorUpdate(ContinualAnimation): + def update_mobject(self,dt): + self.mobject.continual_update() + +def copy_func(f): + """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" + g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name, + argdefs=f.func_defaults, + closure=f.func_closure) + g = functools.update_wrapper(g, f) + return g + +class ScaleLightSources(Transform): + + def __init__(self, light_sources_mob, factor, about_point = None, **kwargs): + + if about_point == None: + about_point = light_sources_mob.get_center() + + ls_target = light_sources_mob.copy() + + for submob in ls_target: + + if type(submob) == LightSource: + + new_sp = submob.source_point.copy() # a mob + new_sp.scale(factor,about_point = about_point) + submob.move_source_to(new_sp.get_location()) + + #ambient_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: ambient_of(r/factor) + #submob.ambient_light.opacity_function = new_of + + #spotlight_of = copy_func(submob.ambient_light.opacity_function) + #new_of = lambda r: spotlight_of(r/factor) + #submob.spotlight.change_opacity_function(new_of) + + new_r = factor * submob.radius + submob.set_radius(new_r) + + new_r = factor * submob.ambient_light.radius + submob.ambient_light.radius = new_r + + new_r = factor * submob.spotlight.radius + submob.spotlight.radius = new_r + + submob.ambient_light.scale_about_point(factor, new_sp.get_center()) + submob.spotlight.scale_about_point(factor, new_sp.get_center()) + + + Transform.__init__(self,light_sources_mob,ls_target,**kwargs) + +class ThreeDSpotlight(VGroup): + CONFIG = { + "fill_color" : YELLOW, + } + def __init__(self, screen, ambient_light, source_point_func, **kwargs): + self.screen = screen + self.ambient_light = ambient_light + self.source_point_func = source_point_func + self.dr = ambient_light.radius/ambient_light.num_levels + VGroup.__init__(self, **kwargs) + + def update(self): + screen = self.screen + source_point = self.source_point_func() + dr = self.dr + corners = screen.get_anchors() + self.submobjects = [VGroup() for a in screen.get_anchors()] + + distance = np.linalg.norm( + screen.get_center() - source_point + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for face, (c1, c2) in zip(self, adjacent_pairs(corners)): + face.submobjects = [] + for a1, a2 in zip(alphas, alphas[1:]): + face.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = self.fill_color, + fill_opacity = self.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + +class ContinualThreeDLightConeUpdate(ContinualAnimation): + def update(self, dt): + self.mobject.update() + +### + +class ThinkAboutPondScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_class" : Randolph, + } + def construct(self): + randy = self.pi_creature + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble( + width = 11, + height = 8, + ) + circles = bubble[:3] + angle = -15*DEGREES + circles.rotate(angle, about_point = bubble.get_bubble_center()) + circles.shift(LARGE_BUFF*LEFT) + for circle in circles: + circle.rotate(-angle) + bubble.pin_to(randy) + bubble.shift(DOWN) + bubble[:3].rotate(np.pi, axis = UP+2*RIGHT, about_edge = UP+LEFT) + bubble[:3].scale(0.7, about_edge = DOWN+RIGHT) + bubble[:3].shift(1.5*DOWN) + for oval in bubble[:3]: + oval.rotate(TAU/3) + + self.play( + randy.change, "thinking", + ShowCreation(bubble) + ) + self.wait(2) + self.play(randy.change, "happy", bubble) + self.wait(4) + self.play(randy.change, "hooray", bubble) + self.wait(2) + +class IntroScene(PiCreatureScene): + CONFIG = { + "rect_height" : 0.075, + "duration" : 1.0, + "eq_spacing" : 3 * MED_LARGE_BUFF, + "n_rects_to_show" : 30, + } + + def construct(self): + randy = self.get_primary_pi_creature() + randy.scale(0.7).to_corner(DOWN+RIGHT) + + self.build_up_euler_sum() + self.show_history() + # self.other_pi_formulas() + # self.refocus_on_euler_sum() + + def build_up_euler_sum(self): + morty = self.pi_creature + euler_sum = self.euler_sum = TexMobject( + "1", "+", + "{1 \\over 4}", "+", + "{1 \\over 9}", "+", + "{1 \\over 16}", "+", + "{1 \\over 25}", "+", + "\\cdots", "=", + arg_separator = " \\, " + ) + equals_sign = euler_sum.get_part_by_tex("=") + plusses = euler_sum.get_parts_by_tex("+") + term_mobjects = euler_sum.get_parts_by_tex("1") + + self.euler_sum.to_edge(UP) + self.euler_sum.shift(2*LEFT) + + max_n = self.n_rects_to_show + terms = [1./(n**2) for n in range(1, max_n + 1)] + series_terms = list(np.cumsum(terms)) + series_terms.append(np.pi**2/6) ##Just force this up there + + partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( + series_terms[1], + num_decimal_points = 2 + ) + partial_sum_decimal.next_to(equals_sign, RIGHT) + + ## Number line + + number_line = self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1, + stroke_width = 1, + numbers_with_elongated_ticks = [0,1,2,3], + numbers_to_show = np.arange(0,5), + unit_size = 5, + tick_frequency = 0.2, + line_to_number_buff = MED_LARGE_BUFF + ) + number_line.add_numbers() + number_line.to_edge(LEFT) + number_line.shift(MED_LARGE_BUFF*UP) + + # create slabs for series terms + + lines = VGroup() + rects = self.rects = VGroup() + rect_labels = VGroup() + slab_colors = it.cycle([YELLOW, BLUE]) + rect_anims = [] + rect_label_anims = [] + + for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): + color = slab_colors.next() + line = Line(*map(number_line.number_to_point, [t1, t2])) + rect = Rectangle( + stroke_width = 0, + fill_opacity = 1, + fill_color = color + ) + rect.match_width(line) + rect.stretch_to_fit_height(self.rect_height) + rect.move_to(line) + + if i <= 5: + if i == 1: + rect_label = TexMobject("1") + else: + rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) + rect_label.scale(0.75) + max_width = 0.7*rect.get_width() + if rect_label.get_width() > max_width: + rect_label.scale_to_fit_width(max_width) + rect_label.next_to(rect, UP, buff = MED_LARGE_BUFF/(i+1)) + + term_mobject = term_mobjects[i-1] + rect_anim = GrowFromPoint(rect, term_mobject.get_center()) + rect_label_anim = ReplacementTransform( + term_mobject.copy(), rect_label + ) + else: + rect_label = VectorizedPoint() + rect_anim = GrowFromPoint(rect, rect.get_left()) + rect_label_anim = FadeIn(rect_label) + + rects.add(rect) + rect_labels.add(rect_label) + rect_anims.append(rect_anim) + rect_label_anims.append(rect_label_anim) + lines.add(line) + dots = TexMobject("\\dots").scale(0.5) + last_rect = rect_anims[-1].target_mobject + dots.scale_to_fit_width(0.9*last_rect.get_width()) + dots.move_to(last_rect, UP+RIGHT) + rects.submobjects[-1] = dots + rect_anims[-1] = FadeIn(dots) + + self.add(number_line) + self.play(FadeIn(euler_sum[0])) + self.play( + rect_anims[0], + rect_label_anims[0] + ) + for i in range(4): + self.play( + FadeIn(term_mobjects[i+1]), + FadeIn(plusses[i]), + ) + anims = [ + rect_anims[i+1], + rect_label_anims[i+1], + ] + if i == 0: + anims += [ + FadeIn(equals_sign), + FadeIn(partial_sum_decimal) + ] + elif i <= 5: + anims += [ + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + run_time = 1, + num_decimal_points = 6, + position_update_func = lambda m: m.next_to(equals_sign, RIGHT) + ) + ] + self.play(*anims) + + for i in range(4, len(series_terms)-2): + anims = [ + rect_anims[i+1], + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[i+1], + num_decimal_points = 6, + ), + ] + if i == 5: + anims += [ + FadeIn(euler_sum[-3]), # + + FadeIn(euler_sum[-2]), # ... + ] + self.play(*anims, run_time = 2./i) + + brace = self.brace = Brace(partial_sum_decimal, DOWN) + q_marks = self.q_marks = TextMobject("???") + q_marks.next_to(brace, DOWN) + q_marks.highlight(LIGHT_COLOR) + + self.play( + GrowFromCenter(brace), + Write(q_marks), + ChangeDecimalToValue( + partial_sum_decimal, + series_terms[-1], + num_decimal_points = 6, + ), + morty.change, "confused", + ) + self.wait() + + self.number_line_group = VGroup( + number_line, rects, rect_labels + ) + + def show_history(self): + # Pietro Mengoli in 1644 + morty = self.pi_creature + pietro = ImageMobject("Pietro_Mengoli") + euler = ImageMobject("Euler") + + pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") + pietro_words.scale(0.75) + pietro_words.next_to(pietro, DOWN) + pietro.add(pietro_words) + + euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") + euler_words.scale(0.75) + euler_words.next_to(euler, DOWN) + euler.add(euler_words) + + pietro.next_to(SPACE_WIDTH*LEFT, LEFT) + euler.next_to(SPACE_WIDTH*RIGHT, RIGHT) + + pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") + pi_answer.highlight(YELLOW) + pi_answer.move_to(self.partial_sum_decimal, LEFT) + equals_sign = TexMobject("=") + equals_sign.next_to(pi_answer, RIGHT) + pi_answer.shift(SMALL_BUFF*UP) + self.partial_sum_decimal.generate_target() + self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) + + pi = pi_answer[0] + pi_rect = SurroundingRectangle(pi, color = RED) + pi_rect.save_state() + pi_rect.scale_to_fit_height(SPACE_HEIGHT) + pi_rect.center() + pi_rect.set_stroke(width = 0) + squared = pi_answer[1] + squared_rect = SurroundingRectangle(squared, color = BLUE) + + brace = Brace( + VGroup(self.euler_sum, self.partial_sum_decimal.target), + DOWN, buff = SMALL_BUFF + ) + basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) + + self.number_line_group.save_state() + self.play( + pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, + self.number_line_group.next_to, SPACE_HEIGHT*DOWN, DOWN, + morty.change, "pondering", + ) + self.wait(2) + self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) + self.wait(2) + self.play( + ReplacementTransform(self.q_marks, pi_answer), + FadeIn(equals_sign), + FadeOut(self.brace), + MoveToTarget(self.partial_sum_decimal) + ) + self.wait() + self.play(morty.change, "surprised") + self.play(pi_rect.restore) + self.wait() + self.play(Transform(pi_rect, squared_rect)) + self.play(FadeOut(pi_rect)) + self.play(morty.change, "hesitant") + self.wait(2) + self.play( + GrowFromCenter(brace), + euler.to_edge, DOWN, + pietro.to_edge, DOWN, + self.number_line_group.restore, + self.number_line_group.shift, LARGE_BUFF*RIGHT, + ) + self.play(Write(basel_text)) + self.play(morty.change, "happy") + self.wait(4) + + def other_pi_formulas(self): + + self.play( + FadeOut(self.rects), + FadeOut(self.number_line) + ) + + self.leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + self.wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.leibniz_sum.get_part_by_tex("=") + ) + + self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, + buff = self.eq_spacing, + submobject_to_align = self.wallis_product.get_part_by_tex("=") + ) + + + self.play( + Write(self.leibniz_sum) + ) + self.play( + Write(self.wallis_product) + ) + + def refocus_on_euler_sum(self): + + self.euler_sum.add(self.pi_answer) + + self.play( + FadeOut(self.leibniz_sum), + FadeOut(self.wallis_product), + ApplyMethod(self.euler_sum.shift, + ORIGIN + 2*UP - self.euler_sum.get_center()) + ) + + # focus on pi squared + pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] + self.play( + ScaleInPlace(pi_squared,2,rate_func = wiggle) + ) + + + + # Morty thinks of a circle + + q_circle = Circle( + stroke_color = YELLOW, + fill_color = YELLOW, + fill_opacity = 0.5, + radius = 0.4, + stroke_width = 10.0 + ) + q_mark = TexMobject("?") + q_mark.next_to(q_circle) + + thought = Group(q_circle, q_mark) + q_mark.scale_to_fit_height(0.8 * q_circle.get_height()) + self.pi_creature_thinks(thought,target_mode = "confused", + bubble_kwargs = { "height" : 2, "width" : 3 }) + + self.wait() + +class PiHidingWrapper(Scene): + def construct(self): + title = TextMobject("Pi hiding in prime regularities") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen)) + self.wait(2) + +class MathematicalWebOfConnections(PiCreatureScene): + def construct(self): + self.complain_that_pi_is_not_about_circles() + self.show_other_pi_formulas() + self.question_fundamental() + self.draw_circle() + self.remove_all_but_basel_sum() + self.show_web_of_connections() + self.show_light() + + def complain_that_pi_is_not_about_circles(self): + jerk, randy = self.pi_creatures + + words = self.words = TextMobject( + "I am not", + "fundamentally \\\\", + "about circles" + ) + words.highlight_by_tex("fundamentally", YELLOW) + + self.play(PiCreatureSays( + jerk, words, + target_mode = "angry" + )) + self.play(randy.change, "guilty") + self.wait(2) + + def show_other_pi_formulas(self): + jerk, randy = self.pi_creatures + words = self.words + + basel_sum = TexMobject( + "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", + "=", "{\\pi^2 \\over 6}" + ) + leibniz_sum = TexMobject( + "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", + "=", "{\\pi \\over 4}") + + wallis_product = TexMobject( + "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + + "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", + "=", "{\\pi \\over 2}") + + basel_sum.move_to(randy) + basel_sum.to_edge(UP) + basel_equals = basel_sum.get_part_by_tex("=") + + formulas = VGroup(basel_sum, leibniz_sum, wallis_product) + formulas.scale(0.75) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + for formula in formulas: + basel_equals_x = basel_equals.get_center()[0] + formula_equals_x = formula.get_part_by_tex("=").get_center()[0] + formula.shift((basel_equals_x - formula_equals_x)*RIGHT) + + formulas.to_corner(UP+RIGHT) + formulas.shift(2*LEFT) + self.formulas = formulas + + self.play( + jerk.change, "sassy", + randy.change, "raise_right_hand", + FadeOut(jerk.bubble), + words.next_to, jerk, UP, + FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3) + ) + for formula in formulas[1:]: + self.play( + FadeIn( + formula, + submobject_mode = "lagged_start", + run_time = 3 + ), + ) + self.wait() + + def question_fundamental(self): + jerk, randy = self.pi_creatures + words = self.words + fundamentally = words.get_part_by_tex("fundamentally") + words.remove(fundamentally) + + self.play( + fundamentally.move_to, self.pi_creatures, + fundamentally.shift, UP, + FadeOut(words), + jerk.change, "pondering", + randy.change, "pondering", + ) + self.wait() + + question = TextMobject("Does this mean \\\\ anything?") + question.scale(0.8) + question.set_stroke(WHITE, 0.5) + question.next_to(fundamentally, DOWN, LARGE_BUFF) + arrow = Arrow(question, fundamentally) + arrow.highlight(WHITE) + + self.play( + FadeIn(question), + GrowArrow(arrow) + ) + self.wait() + + fundamentally.add(question, arrow) + self.fundamentally = fundamentally + + def draw_circle(self): + semi_circle = Arc(angle = np.pi, radius = 2) + radius = Line(ORIGIN, semi_circle.points[0]) + radius.highlight(BLUE) + semi_circle.highlight(YELLOW) + + VGroup(radius, semi_circle).move_to( + SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2, + ) + + decimal = DecimalNumber(0) + def decimal_position_update_func(decimal): + decimal.move_to(semi_circle.points[-1]) + decimal.shift(0.3*radius.get_vector()) + + one = TexMobject("1") + one.next_to(radius, UP) + + self.play(ShowCreation(radius), FadeIn(one)) + self.play( + Rotate(radius, np.pi, about_point = radius.get_start()), + ShowCreation(semi_circle), + ChangeDecimalToValue( + decimal, np.pi, + position_update_func = decimal_position_update_func + ), + MaintainPositionRelativeTo(one, radius), + run_time = 3, + ) + self.wait(2) + + self.circle_group = VGroup(semi_circle, radius, one, decimal) + + def remove_all_but_basel_sum(self): + to_shift_down = VGroup( + self.circle_group, self.pi_creatures, + self.fundamentally, self.formulas[1:], + ) + to_shift_down.generate_target() + for part in to_shift_down.target: + part.move_to(2*SPACE_HEIGHT*DOWN) + + basel_sum = self.formulas[0] + + self.play( + MoveToTarget(to_shift_down), + basel_sum.scale, 1.5, + basel_sum.move_to, 1.5*DOWN, + ) + + self.basel_sum = basel_sum + + def show_web_of_connections(self): + self.remove(self.pi_creatures) + title = TextMobject("Interconnected web of mathematics") + title.to_edge(UP) + basel_sum = self.basel_sum + + dots = VGroup(*[ + Dot(radius = 0.1).move_to( + (j - 0.5*(i%2))*RIGHT + \ + (np.sqrt(3)/2.0)* i*DOWN + \ + 0.5*(random.random()*RIGHT + random.random()*UP), + ) + for i in range(4) + for j in range(7+(i%2)) + ]) + dots.scale_to_fit_height(3) + dots.next_to(title, DOWN, MED_LARGE_BUFF) + edges = VGroup() + for x in range(100): + d1, d2 = random.sample(dots, 2) + edge = Line(d1.get_center(), d2.get_center()) + edge.set_stroke(YELLOW, 0.5) + edges.add(edge) + + ## Choose special path + path_dots = VGroup( + dots[-7], + dots[-14], + dots[9], + dots[19], + dots[14], + ) + path_edges = VGroup(*[ + Line( + d1.get_center(), d2.get_center(), + color = RED + ) + for d1, d2 in zip(path_dots, path_dots[1:]) + ]) + + circle = Circle(color = YELLOW, radius = 1) + radius = Line(circle.get_center(), circle.get_right()) + radius.highlight(BLUE) + VGroup(circle, radius).next_to(path_dots[-1], RIGHT) + + self.play( + Write(title), + LaggedStart(ShowCreation, edges, run_time = 3), + LaggedStart(GrowFromCenter, dots, run_time = 3) + ) + self.play(path_dots[0].highlight, RED) + for dot, edge in zip(path_dots[1:], path_edges): + self.play( + ShowCreation(edge), + dot.highlight, RED + ) + self.play(ShowCreation(radius)) + radius.set_points_as_corners(radius.get_anchors()) + self.play( + ShowCreation(circle), + Rotate(radius, angle = 0.999*TAU, about_point = radius.get_start()), + run_time = 2 + ) + self.wait() + + graph = VGroup(dots, edges, path_edges, title) + circle.add(radius) + basel_sum.generate_target() + basel_sum.target.to_edge(UP) + + arrow = Arrow( + UP, DOWN, + rectangular_stem_width = 0.1, + tip_length = 0.45, + color = RED, + ) + arrow.next_to(basel_sum.target, DOWN, buff = MED_LARGE_BUFF) + + self.play( + MoveToTarget(basel_sum), + graph.next_to, basel_sum.target, UP, LARGE_BUFF, + circle.next_to, arrow, DOWN, MED_LARGE_BUFF, + ) + self.play(GrowArrow(arrow)) + self.wait() + + self.arrow = arrow + self.circle = circle + + def show_light(self): + light = AmbientLight( + num_levels = 500, radius = 13, + opacity_function = lambda r : 1.0/(r+1), + ) + pi = self.basel_sum[-1][0] + pi.set_stroke(BLACK, 0.5) + light.move_to(pi) + self.play( + SwitchOn(light, run_time = 3), + Animation(self.arrow), + Animation(self.circle), + Animation(self.basel_sum), + ) + self.wait() + + ### + + def create_pi_creatures(self): + jerk = PiCreature(color = GREEN_D) + randy = Randolph().flip() + jerk.move_to(0.5*SPACE_WIDTH*LEFT).to_edge(DOWN) + randy.move_to(0.5*SPACE_WIDTH*RIGHT).to_edge(DOWN) + + return VGroup(jerk, randy) + +class FirstLighthouseScene(PiCreatureScene): + CONFIG = { + "num_levels" : 100, + "opacity_function" : inverse_quadratic(1,2,1), + } + def construct(self): + self.remove(self.pi_creature) + self.show_lighthouses_on_number_line() + self.describe_brightness_of_each() + self.ask_about_rearrangements() + + def show_lighthouses_on_number_line(self): + number_line = self.number_line = NumberLine( + x_min = 0, + color = WHITE, + number_at_center = 1.6, + stroke_width = 1, + numbers_with_elongated_ticks = range(1,6), + numbers_to_show = range(1,6), + unit_size = 2, + tick_frequency = 0.2, + line_to_number_buff = LARGE_BUFF, + label_direction = DOWN, + ) + + number_line.add_numbers() + self.add(number_line) + + origin_point = number_line.number_to_point(0) + + morty = self.pi_creature + morty.scale(0.75) + morty.flip() + right_pupil = morty.eyes[1] + morty.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) + + + light_sources = VGroup() + for i in range(1,NUM_CONES+1): + light_source = LightSource( + opacity_function = self.opacity_function, + num_levels = self.num_levels, + radius = 12.0, + ) + point = number_line.number_to_point(i) + light_source.move_source_to(point) + light_sources.add(light_source) + + lighthouses = self.lighthouses = VGroup(*[ + ls.lighthouse + for ls in light_sources[:NUM_VISIBLE_CONES+1] + ]) + + morty.save_state() + morty.scale(3) + morty.fade(1) + morty.center() + self.play(morty.restore) + self.play( + morty.change, "pondering", + LaggedStart( + FadeIn, lighthouses, + run_time = 1 + ) + ) + self.play(LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources + ]), + run_time = 5, + lag_ratio = 0.1, + rate_func = rush_into, + ), Animation(lighthouses)) + self.wait() + + self.light_sources = light_sources + + def describe_brightness_of_each(self): + number_line = self.number_line + morty = self.pi_creature + light_sources = self.light_sources + lighthouses = self.lighthouses + + light_indicator = LightIndicator( + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR + ) + light_indicator.reading.scale(0.8) + light_indicator.set_intensity(0) + intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) + opacities = intensities * light_indicator.opacity_for_unit_intensity + + bubble = ThoughtBubble( + direction = RIGHT, + width = 2.5, height = 3.5 + ) + bubble.pin_to(morty) + bubble.add_content(light_indicator) + + euler_sum_above = TexMobject( + "1", "+", + "{1\over 4}", "+", + "{1\over 9}", "+", + "{1\over 16}", "+", + "{1\over 25}", "+", + "{1\over 36}" + ) + euler_sum_terms = euler_sum_above[::2] + plusses = euler_sum_above[1::2] + + for i, term in enumerate(euler_sum_above): + #horizontal alignment with tick marks + term.next_to(number_line.number_to_point(0.5*i+1), UP , buff = 2) + # vertical alignment with light indicator + old_y = term.get_center()[1] + new_y = light_indicator.get_center()[1] + term.shift([0,new_y - old_y,0]) + + # show limit value in light indicator and an equals sign + limit_reading = TexMobject("{\pi^2 \over 6}") + limit_reading.move_to(light_indicator.reading) + + equals_sign = TexMobject("=") + equals_sign.next_to(morty, UP) + old_y = equals_sign.get_center()[1] + new_y = euler_sum_above.get_center()[1] + equals_sign.shift([0,new_y - old_y,0]) + + #Triangle of light to morty's eye + ls0 = light_sources[0] + ls0.save_state() + eye = morty.eyes[1] + triangle = Polygon( + number_line.number_to_point(1), + eye.get_top(), eye.get_bottom(), + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 1, + ) + triangle_anim = GrowFromPoint( + triangle, triangle.get_right(), + point_color = YELLOW + ) + + # First lighthouse has apparent reading + self.play(LaggedStart(FadeOut, light_sources[1:])) + self.wait() + self.play( + triangle_anim, + # Animation(eye) + ) + for x in range(4): + triangle_copy = triangle.copy() + self.play( + FadeOut(triangle.copy()), + triangle_anim, + ) + self.play( + FadeOut(triangle), + ShowCreation(bubble), + FadeIn(light_indicator), + ) + self.play( + UpdateLightIndicator(light_indicator, 1), + FadeIn(euler_sum_terms[0]) + ) + self.wait(2) + + # Second lighthouse is 1/4, third is 1/9, etc. + for i in range(1, 5): + self.play( + ApplyMethod( + ls0.move_to, light_sources[i], + run_time = 3 + ), + UpdateLightIndicator(light_indicator, 1./(i+1)**2, run_time = 3), + FadeIn( + euler_sum_terms[i], + run_time = 3, + rate_func = squish_rate_func(smooth, 0.5, 1) + ), + ) + self.wait() + self.play( + ApplyMethod(ls0.restore), + UpdateLightIndicator(light_indicator, 1) + ) + + #Switch them all on + self.play( + LaggedStart(FadeIn, lighthouses[1:]), + morty.change, "hooray", + ) + self.play( + LaggedStart( + SwitchOn, VGroup(*[ + ls.ambient_light + for ls in light_sources[1:] + ]), + run_time = 5, + rate_func = rush_into, + ), + Animation(lighthouses), + Animation(euler_sum_above), + Write(plusses), + UpdateLightIndicator(light_indicator, np.pi**2/6, run_time = 5), + morty.change, "happy", + ) + self.wait() + self.play( + FadeOut(light_indicator.reading), + FadeIn(limit_reading), + morty.change, "confused", + ) + self.play(Write(equals_sign)) + self.wait() + + def ask_about_rearrangements(self): + light_sources = self.light_sources + origin = self.number_line.number_to_point(0) + morty = self.pi_creature + + self.play( + LaggedStart( + Rotate, light_sources, + lambda m : (m, (2*random.random()-1)*90*DEGREES), + about_point = origin, + rate_func = lambda t : wiggle(t, 4), + run_time = 10, + lag_ratio = 0.9, + ), + morty.change, "pondering", + ) + +class RearrangeWords(Scene): + def construct(self): + words = TextMobject("Rearrange without changing \\\\ the apparent brightness") + self.play(Write(words)) + self.wait(5) + +class ThatJustSeemsUseless(TeacherStudentsScene): + def construct(self): + self.student_says( + "How would \\\\ that help?", + target_mode = "sassy", + student_index = 2, + bubble_kwargs = {"direction" : LEFT}, + ) + self.play( + self.teacher.change, "guilty", + self.get_student_changes(*3*['sassy']) + ) + self.wait() + +class AskAboutBrightness(TeacherStudentsScene): + CONFIG = { + "num_levels" : 200, + "radius" : 10, + } + def construct(self): + light_source = LightSource( + num_levels = self.num_levels, + radius = self.radius, + opacity_function = inverse_quadratic(1,2,1), + ) + light_source.lighthouse.scale(0.5, about_edge = UP) + light_source.move_source_to(5*LEFT + 2*UP) + + self.add_foreground_mobjects(self.pi_creatures) + self.student_says( + "What do you mean \\\\ by ``brightness''?", + added_anims = [ + SwitchOn(light_source.ambient_light), + Animation(light_source.lighthouse) + ] + ) + self.play(self.teacher.change, "happy") + self.wait(4) + +class IntroduceScreen(Scene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + "num_rays" : 250, + "min_ray_angle" : 0, + "max_ray_angle" : TAU, + "source_point" : 2.5*LEFT, + "observer_point" : 3.5*RIGHT, + "screen_height" : 2, + } + def construct(self): + self.setup_elements() + self.setup_angle() # spotlight and angle msmt change when screen rotates + self.rotate_screen() + # self.morph_lighthouse_into_sun() + + def setup_elements(self): + SCREEN_SIZE = 3.0 + source_point = self.source_point + observer_point = self.observer_point, + + # Light source + light_source = self.light_source = self.get_light_source() + + # Screen + + screen = self.screen = Rectangle( + width = 0.05, + height = self.screen_height, + mark_paths_closed = True, + fill_color = WHITE, + fill_opacity = 1.0, + stroke_width = 0.0 + ) + + screen.next_to(observer_point, LEFT) + + screen_label = TextMobject("Screen") + screen_label.next_to(screen, UP+LEFT) + screen_arrow = Arrow( + screen_label.get_bottom(), + screen.get_center(), + ) + + # Pi creature + morty = Mortimer() + morty.shift(screen.get_center() - morty.eyes.get_left()) + morty.look_at(source_point) + + # Camera + camera = SVGMobject(file_name = "camera") + camera.rotate(TAU/4) + camera.scale_to_fit_height(1.5) + camera.move_to(morty.eyes, LEFT) + + # Animations + light_source.set_max_opacity_spotlight(0.001) + screen_tracker = self.screen_tracker = ScreenTracker(light_source) + + self.add(light_source.lighthouse) + self.play(SwitchOn(light_source.ambient_light)) + self.play( + Write(screen_label), + GrowArrow(screen_arrow), + FadeIn(screen) + ) + self.wait() + self.play(*map(FadeOut, [screen_label, screen_arrow])) + screen.save_state() + self.play( + FadeIn(morty), + screen.match_height, morty.eyes, + screen.next_to, morty.eyes, LEFT, SMALL_BUFF + ) + self.play(Blink(morty)) + self.play( + FadeOut(morty), + FadeIn(camera), + screen.scale, 2, {"about_edge" : UP}, + ) + self.wait() + self.play( + FadeOut(camera), + screen.restore, + ) + + light_source.set_screen(screen) + light_source.spotlight.opacity_function = lambda r : 0.2/(r+1) + screen_tracker.update(0) + + ## Ask about proportion + self.add_foreground_mobjects(light_source.shadow, screen) + self.shoot_rays() + + ## + self.play(SwitchOn(light_source.spotlight)) + + def setup_angle(self): + + self.wait() + + # angle msmt (arc) + arc_angle = self.light_source.spotlight.opening_angle() + # draw arc arrows to show the opening angle + self.angle_arc = Arc( + radius = 3, + start_angle = self.light_source.spotlight.start_angle(), + angle = self.light_source.spotlight.opening_angle(), + tip_length = ARC_TIP_LENGTH + ) + #angle_arc.add_tip(at_start = True, at_end = True) + self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) + + + # angle msmt (decimal number) + + self.angle_indicator = DecimalNumber( + arc_angle / DEGREES, + num_decimal_points = 0, + unit = "^\\circ" + ) + self.angle_indicator.next_to(self.angle_arc, RIGHT) + + angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES + angle_tracker = ContinualChangingDecimal( + self.angle_indicator, angle_update_func + ) + self.add(angle_tracker) + + arc_tracker = AngleUpdater( + self.angle_arc, + self.light_source.spotlight + ) + self.add(arc_tracker) + + self.play( + ShowCreation(self.angle_arc), + ShowCreation(self.angle_indicator) + ) + + self.wait() + + def rotate_screen(self): + self.add( + ContinualUpdateFromFunc( + self.light_source, + lambda m : m.update() + ), + ) + self.add( + ContinualUpdateFromFunc( + self.angle_indicator, + lambda m : m.set_stroke(width = 0).set_fill(opacity = 1) + ) + ) + self.remove(self.light_source.ambient_light) + def rotate_screen(angle): + self.play( + Rotate(self.light_source.spotlight.screen, angle), + Animation(self.angle_arc), + run_time = 2, + ) + for angle in TAU/8, -TAU/4, TAU/8, -TAU/6: + rotate_screen(angle) + self.wait() + self.shoot_rays() + rotate_screen(TAU/6) + + ## + + def get_light_source(self): + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_source_to(self.source_point) + return light_source + + def shoot_rays(self, show_creation_kwargs = None): + if show_creation_kwargs is None: + show_creation_kwargs = {} + source_point = self.source_point + screen = self.screen + + # Rays + step_size = (self.max_ray_angle - self.min_ray_angle)/self.num_rays + rays = VGroup(*[ + Line(ORIGIN, self.radius*rotate_vector(RIGHT, angle)) + for angle in np.arange( + self.min_ray_angle, + self.max_ray_angle, + step_size + ) + ]) + rays.shift(source_point) + rays.set_stroke(YELLOW, 1) + max_angle = np.max([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + min_angle = np.min([ + angle_of_vector(point - source_point) + for point in screen.points + ]) + for ray in rays: + if min_angle <= ray.get_angle() <= max_angle: + ray.target_color = GREEN + else: + ray.target_color = RED + + self.play(*[ + ShowCreation(ray, run_time = 3, **show_creation_kwargs) + for ray in rays + ]) + self.play(*[ + ApplyMethod(ray.highlight, ray.target_color) + for ray in rays + ]) + self.wait() + self.play(FadeOut(rays)) + +class EarthScene(IntroduceScreen): + CONFIG = { + "screen_height" : 0.5, + "screen_thickness" : 0, + "radius" : 100 + SPACE_WIDTH, + "source_point" : 100*LEFT, + "min_ray_angle" : -1.65*DEGREES, + "max_ray_angle" : 1.65*DEGREES, + "num_rays" : 100, + } + def construct(self): + # Earth + earth_radius = 3 + earth = ImageMobject("earth") + earth_circle = Circle(radius = earth_radius) + earth_circle.to_edge(RIGHT) + earth.replace(earth_circle) + + black_rect = Rectangle( + height = 2*SPACE_HEIGHT, + width = earth_radius + LARGE_BUFF, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1 + ) + black_rect.move_to(earth.get_center(), LEFT) + + self.add_foreground_mobjects(black_rect, earth) + + # screen + screen = self.screen = Line( + self.screen_height*UP, ORIGIN, + stroke_color = WHITE, + stroke_width = self.screen_thickness, + ) + screen.move_to(earth.get_left()) + screen.generate_target() + screen.target.rotate( + -60*DEGREES, about_point = earth_circle.get_center() + ) + + equator_arrow = Vector( + DOWN+2*RIGHT, color = WHITE, + use_rectangular_stem = False, + ) + equator_arrow.next_to(screen.get_center(), UP+LEFT, SMALL_BUFF) + pole_arrow = Vector( + UP+3*RIGHT, + color = WHITE, + use_rectangular_stem = False, + path_arc = -60*DEGREES, + ) + pole_arrow.shift( + screen.target.get_center()+SMALL_BUFF*LEFT - \ + pole_arrow.get_end() + ) + for arrow in equator_arrow, pole_arrow: + arrow.pointwise_become_partial(arrow, 0, 0.95) + equator_words = TextMobject("Some", "unit of area") + pole_words = TextMobject("The same\\\\", "unit of area") + pole_words.next_to(pole_arrow.get_start(), DOWN) + equator_words.next_to(equator_arrow.get_start(), UP) + + + # Light source (far-away Sun) + + sun = sun = LightSource( + opacity_function = lambda r : 0.5, + max_opacity_ambient = 0, + max_opacity_spotlight = 0.5, + num_levels = 5, + radius = self.radius, + screen = screen + ) + sun.move_source_to(self.source_point) + sunlight = sun.spotlight + sunlight.opacity_function = lambda r : 5./(r+1) + + screen_tracker = ScreenTracker(sun) + + # Add elements to scene + + self.add(screen) + self.play(SwitchOn( + sunlight, + rate_func = squish_rate_func(smooth, 0.7, 0.8), + )) + self.add(screen_tracker) + self.play( + Write(equator_words), + GrowArrow(equator_arrow) + ) + self.add_foreground_mobjects(equator_words, equator_arrow) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) + }) + self.wait() + # Point to patch + self.play( + MoveToTarget(screen), + Transform(equator_arrow, pole_arrow), + Transform( + equator_words, pole_words, + rate_func = squish_rate_func(smooth, 0.6, 1), + ), + Animation(sunlight), + run_time = 3, + ) + self.shoot_rays(show_creation_kwargs = { + "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) + }) + self.wait() + +class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene): + CONFIG = { + "num_levels" : 200, + } + def construct(self): + light_source = self.get_light_source() + screens = VGroup( + Square(), + RegularPolygon(8), + Circle().insert_n_anchor_points(25), + ) + for screen in screens: + screen.scale_to_fit_height(self.screen_height) + screens.rotate(TAU/4, UP) + screens.next_to(self.observer_point, LEFT) + screens.set_stroke(WHITE, 2) + screens.set_fill(WHITE, 0.5) + screen = screens[0] + + cone = ThreeDSpotlight( + screen, light_source.ambient_light, + light_source.get_source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + + self.add(light_source, screen, cone) + self.add(cone_update_anim) + self.move_camera( + phi = 60*DEGREES, + theta = -155*DEGREES, + run_time = 3, + ) + self.begin_ambient_camera_rotation() + kwargs = {"run_time" : 2} + self.play(screen.stretch, 0.5, 1, **kwargs) + self.play(screen.stretch, 2, 2, **kwargs) + self.play(Rotate( + screen, TAU/4, + axis = UP+OUT, + rate_func = there_and_back, + run_time = 3, + )) + self.play(Transform(screen, screens[1], **kwargs)) + self.play(screen.stretch, 0.5, 2, **kwargs) + self.play(Transform(screen, screens[2], **kwargs)) + self.wait(2) + self.play( + screen.stretch, 0.5, 1, + screen.stretch, 2, 2, + **kwargs + ) + self.play( + screen.stretch, 3, 1, + screen.stretch, 0.7, 2, + **kwargs + ) + self.wait(2) + +class LightInThreeDimensionsOverlay(Scene): + def construct(self): + words = TextMobject(""" + ``Solid angle'' \\\\ + (measured in ``steradians'') + """) + self.play(Write(words)) + self.wait() + +class InverseSquareLaw(ThreeDScene): + CONFIG = { + "screen_height" : 1.0, + "source_point" : 5*LEFT, + "unit_distance" : 4, + "num_levels" : 100, + } + def construct(self): + self.move_screen_farther_away() + self.morph_into_3d() + + def move_screen_farther_away(self): + source_point = self.source_point + unit_distance = self.unit_distance + + # screen + screen = self.screen = Line(self.screen_height*UP, ORIGIN) + screen.get_reference_point = screen.get_center + screen.shift( + source_point + unit_distance*RIGHT -\ + screen.get_reference_point() + ) + + # light source + light_source = self.light_source = LightSource( + # opacity_function = inverse_quadratic(1,5,1), + opacity_function = lambda r : 1./(r+1), + num_levels = self.num_levels, + radius = 10, + max_opacity = 0.2 + ) + light_source.set_max_opacity_spotlight(0.2) + + light_source.set_screen(screen) + light_source.move_source_to(source_point) + + # abbreviations + ambient_light = light_source.ambient_light + spotlight = light_source.spotlight + lighthouse = light_source.lighthouse + shadow = light_source.shadow + + # Morty + morty = self.morty = Mortimer().scale(0.3) + morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF) + + #Screen tracker + def update_spotlight(spotlight): + spotlight.update_sectors() + + spotlight_update = ContinualUpdateFromFunc(spotlight, update_spotlight) + shadow_update = ContinualUpdateFromFunc( + shadow, lambda m : light_source.update_shadow() + ) + + # Light indicator + light_indicator = self.light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + ) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(screen.get_reference_point() - source_point) + light_indicator.set_intensity(1.0/(distance/unit_distance)**2) + light_indicator.next_to(morty, UP, MED_LARGE_BUFF) + light_indicator_update = ContinualUpdateFromFunc( + light_indicator, update_light_indicator + ) + light_indicator_update.update(0) + + continual_updates = self.continual_updates = [ + spotlight_update, light_indicator_update, shadow_update + ] + + # Distance indicators + + one_arrow = DoubleArrow(ORIGIN, unit_distance*RIGHT, buff = 0) + two_arrow = DoubleArrow(ORIGIN, 2*unit_distance*RIGHT, buff = 0) + arrows = VGroup(one_arrow, two_arrow) + arrows.highlight(WHITE) + one_arrow.move_to(source_point + DOWN, LEFT) + two_arrow.move_to(source_point + 1.75*DOWN, LEFT) + one = Integer(1).next_to(one_arrow, UP, SMALL_BUFF) + two = Integer(2).next_to(two_arrow, DOWN, SMALL_BUFF) + arrow_group = VGroup(one_arrow, one, two_arrow, two) + + # Animations + + self.add_foreground_mobjects(lighthouse, screen, morty) + self.add(shadow_update) + + self.play( + SwitchOn(ambient_light), + morty.change, "pondering" + ) + self.play( + SwitchOn(spotlight), + FadeIn(light_indicator) + ) + # self.remove(spotlight) + self.add(*continual_updates) + self.wait() + for distance in -0.5, 0.5: + self.shift_by_distance(distance) + self.wait() + self.add_foreground_mobjects(one_arrow, one) + self.play(GrowFromCenter(one_arrow), Write(one)) + self.wait() + self.add_foreground_mobjects(two_arrow, two) + self.shift_by_distance(1, + GrowFromPoint(two_arrow, two_arrow.get_left()), + Write(two, rate_func = squish_rate_func(smooth, 0.5, 1)) + ) + self.wait() + + q_marks = TextMobject("???") + q_marks.next_to(light_indicator, UP) + self.play( + Write(q_marks), + morty.change, "confused", q_marks + ) + self.play(Blink(morty)) + self.play(FadeOut(q_marks), morty.change, "pondering") + self.wait() + self.shift_by_distance(-1, arrow_group.shift, DOWN) + + self.set_variables_as_attrs( + ambient_light, spotlight, shadow, lighthouse, + morty, arrow_group, + *continual_updates + ) + + def morph_into_3d(self): + # axes = ThreeDAxes() + old_screen = self.screen + spotlight = self.spotlight + source_point = self.source_point + ambient_light = self.ambient_light + unit_distance = self.unit_distance + light_indicator = self.light_indicator + morty = self.morty + + new_screen = Square( + side_length = self.screen_height, + stroke_color = WHITE, + stroke_width = 1, + fill_color = WHITE, + fill_opacity = 0.5 + ) + new_screen.rotate(TAU/4, UP) + new_screen.move_to(old_screen, IN) + old_screen.fade(1) + + cone = ThreeDSpotlight( + new_screen, ambient_light, + source_point_func = lambda : source_point + ) + cone_update_anim = ContinualThreeDLightConeUpdate(cone) + + + self.remove(self.spotlight_update, self.light_indicator_update) + self.add( + ContinualAnimation(new_screen), + cone_update_anim + ) + self.remove(spotlight) + self.move_camera( + phi = 60*DEGREES, + theta = -145*DEGREES, + added_anims = [ + # ApplyMethod( + # old_screen.scale, 1.8, {"about_edge" : DOWN}, + # run_time = 2, + # ), + ApplyFunction( + lambda m : m.fade(1).shift(1.5*DOWN), + light_indicator, + remover = True + ), + FadeOut(morty) + ], + run_time = 2, + ) + self.wait() + + ## Create screen copies + def get_screen_copy_group(distance): + n = int(distance)**2 + copies = VGroup(*[new_screen.copy() for x in range(n)]) + copies.rotate(-TAU/4, axis = UP) + copies.arrange_submobjects_in_grid(buff = 0) + copies.rotate(TAU/4, axis = UP) + copies.move_to(source_point, IN) + copies.shift(distance*RIGHT*unit_distance) + return copies + screen_copy_groups = map(get_screen_copy_group, range(1, 8)) + def get_screen_copy_group_anim(n): + group = screen_copy_groups[n] + prev_group = screen_copy_groups[n-1] + group.save_state() + group.fade(1) + group.replace(prev_group, dim_to_match = 1) + return ApplyMethod(group.restore) + + # corner_directions = [UP+OUT, DOWN+OUT, DOWN+IN, UP+IN] + # edge_directions = [ + # UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN + # ] + + # four_copies = VGroup(*[new_screen.copy() for x in range(4)]) + # nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) + # def update_four_copies(four_copies): + # for mob, corner_direction in zip(four_copies, corner_directions): + # mob.move_to(new_screen, corner_direction) + # four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) + # def update_nine_copies(nine_copies): + # for mob, corner_direction in zip(nine_copies, edge_directions): + # mob.move_to(new_screen, corner_direction) + # nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) + + three_arrow = DoubleArrow( + source_point + 4*DOWN, + source_point + 4*DOWN + 3*unit_distance*RIGHT, + buff = 0, + color = WHITE + ) + three = Integer(3) + three.next_to(three_arrow, DOWN) + + new_screen.fade(1) + # self.add( + # ContinualAnimation(screen_copy), + # ContinualAnimation(four_copies), + # ) + + self.add(ContinualAnimation(screen_copy_groups[0])) + self.add(ContinualAnimation(screen_copy_groups[1])) + self.play( + new_screen.scale, 2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(1), + run_time = 2, + ) + self.wait() + self.move_camera( + phi = 75*DEGREES, + theta = -155*DEGREES, + distance = 7, + run_time = 10, + ) + self.begin_ambient_camera_rotation(rate = -0.01) + self.add(ContinualAnimation(screen_copy_groups[2])) + self.play( + new_screen.scale, 3./2, {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(2), + GrowFromPoint(three_arrow, three_arrow.get_left()), + Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), + run_time = 2, + ) + self.begin_ambient_camera_rotation(rate = -0.01) + self.play(LaggedStart( + ApplyMethod, screen_copy_groups[2], + lambda m : (m.highlight, RED), + run_time = 5, + rate_func = there_and_back, + )) + self.wait(2) + self.move_camera(distance = 18) + self.play(*[ + ApplyMethod(mob.fade, 1) + for mob in screen_copy_groups[:2] + ]) + last_group = screen_copy_groups[2] + for n in range(4, len(screen_copy_groups)+1): + group = screen_copy_groups[n-1] + self.add(ContinualAnimation(group)) + self.play( + new_screen.scale, float(n)/(n-1), {"about_edge" : IN}, + new_screen.shift, unit_distance*RIGHT, + get_screen_copy_group_anim(n-1), + last_group.fade, 1, + ) + last_group = group + self.wait() + + ### + + def shift_by_distance(self, distance, *added_anims): + anims = [ + self.screen.shift, self.unit_distance*distance*RIGHT, + ] + if self.morty in self.mobjects: + anims.append(MaintainPositionRelativeTo(self.morty, self.screen)) + anims += added_anims + self.play(*anims, run_time = 2) + +class OtherInstanceOfInverseSquareLaw(Scene): + def construct(self): + title = TextMobject("Where the inverse square law shows up") + title.to_edge(UP) + h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH) + h_line.next_to(title, DOWN) + self.add(title, h_line) + + items = VGroup(*[ + TextMobject("- %s"%s).scale(1) + for s in [ + "Heat", "Sound", "Radio waves", "Electric fields", + ] + ]) + items.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) + items.next_to(h_line, DOWN, LARGE_BUFF) + items.to_edge(LEFT) + + dot = Dot() + dot.move_to(4*RIGHT) + self.add(dot) + def get_broadcast(): + return Broadcast(dot, big_radius = 5, run_time = 5) + + self.play( + LaggedStart(FadeIn, items, run_time = 4, lag_ratio = 0.7), + Succession(*[ + get_broadcast() + for x in range(2) + ]) + ) + self.play(get_broadcast()) + self.wait() + +class ScreensIntroWrapper(TeacherStudentsScene): + def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.play(self.teacher.change, "raise_right_hand") + self.change_student_modes( + "pondering", "erm", "confused", + look_at_arg = point, + ) + self.play(self.teacher.look_at, point) + self.wait(5) + +class ManipulateLightsourceSetups(PiCreatureScene): + CONFIG = { + "num_levels" : 100, + "radius" : 10, + "pi_creature_point" : 2*LEFT + 2*DOWN, + } + def construct(self): + unit_distance = 3 + + # Morty + morty = self.pi_creature + observer_point = morty.eyes[1].get_center() + + bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) + bubble.set_fill(BLACK, 1) + bubble.pin_to(morty) + + # Indicator + light_indicator = LightIndicator( + opacity_for_unit_intensity = 0.5, + fill_color = YELLOW, + radius = 0.4, + reading_height = 0.2, + ) + light_indicator.move_to(bubble.get_bubble_center()) + def update_light_indicator(light_indicator): + distance = np.linalg.norm(light_source.get_source_point()-observer_point) + light_indicator.set_intensity((unit_distance/distance)**2) + + #Light source + light_source = LightSource( + opacity_function = inverse_quadratic(1,2,1), + num_levels = self.num_levels, + radius = self.radius, + max_opacity_ambient = AMBIENT_FULL, + ) + light_source.move_to(observer_point + unit_distance*RIGHT) + + #Light source copies + light_source_copies = VGroup(*[light_source.copy() for x in range(2)]) + for lsc, vect in zip(light_source_copies, [RIGHT, UP]): + lsc.move_to(observer_point + np.sqrt(2)*unit_distance*vect) + + self.add(light_source) + self.add_foreground_mobjects(morty, bubble, light_indicator) + self.add(ContinualUpdateFromFunc(light_indicator, update_light_indicator)) + self.play( + ApplyMethod( + light_source.shift, 0.66*unit_distance*LEFT, + rate_func = wiggle, + run_time = 5, + ), + morty.change, "erm", + ) + self.play( + UpdateFromAlphaFunc( + light_source, + lambda ls, a : ls.move_to( + observer_point + rotate_vector( + unit_distance*RIGHT, (1+1./8)*a*TAU + ) + ), + run_time = 6, + rate_func = bezier([0, 0, 1, 1]) + ), + morty.change, "pondering", + UpdateFromFunc(morty, lambda m : m.look_at(light_source)) + ) + self.wait() + + plus = TexMobject("+") + point = light_indicator.get_center() + plus.move_to(point) + light_indicator_copy = light_indicator.copy() + self.add_foreground_mobjects(plus, light_indicator_copy) + self.play( + ReplacementTransform( + light_source, light_source_copies[0] + ), + ReplacementTransform( + light_source.copy().fade(1), + light_source_copies[1] + ), + FadeIn(plus), + UpdateFromFunc( + light_indicator_copy, + lambda li : update_light_indicator(li), + ), + UpdateFromAlphaFunc( + light_indicator, lambda m, a : m.move_to( + point + a*0.75*RIGHT, + ) + ), + UpdateFromAlphaFunc( + light_indicator_copy, lambda m, a : m.move_to( + point + a*0.75*LEFT, + ) + ), + run_time = 2 + ) + self.play(morty.change, "hooray") + self.wait(2) + + ## + + def create_pi_creature(self): + morty = Mortimer() + morty.flip() + morty.scale(0.5) + morty.move_to(self.pi_creature_point) + return morty + +class TwoLightSourcesScene(ManipulateLightsourceSetups): + CONFIG = { + "num_levels" : 200, + "radius" : 15, + "a" : 9, + "b" : 5, + "origin_point" : 5*LEFT + 2.5*DOWN + } + def construct(self): + MAX_OPACITY = 0.4 + INDICATOR_RADIUS = 0.6 + OPACITY_FOR_UNIT_INTENSITY = 0.5 + origin_point = self.origin_point + + #Morty + morty = self.pi_creature + morty.change("hooray") # From last scen + morty.generate_target() + morty.target.change("plain") + morty.target.scale(0.6) + morty.target.next_to( + origin_point, LEFT, buff = 0, + submobject_to_align = morty.target.eyes[1] + ) + + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, + ) + axes.shift(origin_point) + + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] + ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) + + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), + ) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) + + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) + + #Identical lighthouse labels + identical_lighthouses_words = TextMobject("All identical \\\\ lighthouses") + identical_lighthouses_words.to_corner(UP+RIGHT) + identical_lighthouses_words.shift(LEFT) + identical_lighthouses_arrows = VGroup(*[ + Arrow( + identical_lighthouses_words.get_corner(DOWN+LEFT), + ls.get_source_point(), + buff = SMALL_BUFF, + color = WHITE, + ) + for ls in lsA, lsB, lsC + ]) + + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) + + label_a = TexMobject("a") + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) + label_b = TexMobject("b") + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) + label_h = TexMobject("h") + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + # perp_mark.highlight(BLACK) + + #Indicators + indicator = LightIndicator( + color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = True, + precision = 2, + ) + indicator.next_to(origin_point, UP+LEFT) + def update_indicator(indicator): + intensity = 0 + for ls in lsA, lsB, lsC: + if ls in self.mobjects: + distance = np.linalg.norm(ls.get_source_point() - origin_point) + d_indensity = fdiv( + 3./(distance**2), + indicator.opacity_for_unit_intensity + ) + d_indensity *= ls.ambient_light.submobjects[1].get_fill_opacity() + intensity += d_indensity + indicator.set_intensity(intensity) + indicator_update_anim = ContinualUpdateFromFunc(indicator, update_indicator) + + new_indicator = indicator.copy() + new_indicator.light_source = lsC + new_indicator.measurement_point = C + + #Note sure what this is... + distance1 = np.linalg.norm(origin_point - lsA.get_source_point()) + intensity = lsA.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(origin_point - lsB.get_source_point()) + intensity += lsB.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity + + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + + #Transition from last_scene + self.play( + ShowCreation(axes, run_time = 2), + MoveToTarget(morty), + FadeIn(indicator), + ) + + #Move lsC around + self.add(lsC) + indicator_update_anim.update(0) + intensity = indicator.reading.number + self.play( + SwitchOn(lsC.ambient_light), + FadeIn(lsC.lighthouse), + UpdateFromAlphaFunc( + indicator, lambda i, a : i.set_intensity(a*intensity) + ) + ) + self.add(indicator_update_anim) + self.play(Animation(lsC), run_time = 0) #Why is this needed? + for point in axes.coords_to_point(5, 2), H: + self.play( + lsC.move_source_to, point, + path_arc = TAU/4, + run_time = 1.5, + ) + self.wait() + + # Draw line + self.play( + ShowCreation(line_h), + morty.change, "pondering" + ) + self.wait() + self.play( + ShowCreation(line_c), + ShowCreation(perp_mark) + ) + self.wait() + self.add_foreground_mobjects(line_c, line_h) + + #Add alternate light_sources + for ls in lsA, lsB: + ls.save_state() + ls.move_to(lsC) + ls.fade(1) + self.add(ls) + self.play( + ls.restore, + run_time = 2 + ) + self.wait() + A_label.save_state() + A_label.center().fade(1) + self.play(A_label.restore) + self.wait() + self.play(ReplacementTransform( + A_label.copy().fade(1), B_label + )) + self.wait(2) + + #Compare combined of laA + lsB with lsC + rect = SurroundingRectangle(indicator, color = RED) + self.play( + FadeOut(lsA), + FadeOut(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.play(FadeOut(lsC)) + self.add(lsA, lsB) + self.play( + FadeIn(lsA), + FadeIn(lsB), + ) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.wait(2) + + # All standard lighthouses + self.add(lsC) + self.play(FadeIn(lsC)) + self.play( + Write(identical_lighthouses_words), + LaggedStart(GrowArrow, identical_lighthouses_arrows) + ) + self.wait() + self.play(*map(FadeOut, [ + identical_lighthouses_words, + identical_lighthouses_arrows, + ])) + + #Show labels of lengths + self.play(ShowCreation(line_a), Write(label_a)) + self.wait() + self.play(ShowCreation(line_b), Write(label_b)) + self.wait() + self.play(Write(label_h)) + self.wait() + + #Write IPT + a_part = theorem[:2] + b_part = theorem[2:5] + h_part = theorem[5:] + for part in a_part, b_part, h_part: + part.save_state() + part.scale(3) + part.fade(1) + a_part.move_to(lsA) + b_part.move_to(lsB) + h_part.move_to(lsC) + + self.play(*map(FadeOut, [lsA, lsB, lsC, indicator])) + for ls, part in (lsA, a_part), (lsB, b_part), (lsC, h_part): + self.add(ls) + self.play( + SwitchOn(ls.ambient_light, run_time = 2), + FadeIn(ls.lighthouse), + part.restore + ) + self.wait() + self.play( + Write(theorem_name), + ShowCreation(theorem_box) + ) + self.play(morty.change, "confused") + self.wait(2) + +class MathologerVideoWrapper(Scene): + def construct(self): + title = TextMobject(""" + Mathologer's excellent video on \\\\ + the many Pythagorean theorem cousins + """) + # title.scale(0.7) + title.to_edge(UP) + logo = ImageMobject("mathologer_logo") + logo.scale_to_fit_height(1) + logo.to_corner(UP+LEFT) + logo.shift(2*SPACE_WIDTH*RIGHT) + screen = ScreenRectangle(height = 5.5) + screen.next_to(title, DOWN) + + self.play( + logo.shift, 2*SPACE_WIDTH*LEFT, + LaggedStart(FadeIn, title), + run_time = 2 + ) + self.play(ShowCreation(screen)) + self.wait(5) + +class SimpleIPTProof(Scene): + def construct(self): + A = 5*RIGHT + B = 3*UP + C = ORIGIN + #Dumb and inefficient + alphas = np.linspace(0, 1, 500) + i = np.argmin(map( + lambda a : np.linalg.norm(interpolate(A, B, a)), + alphas + )) + H = interpolate(A, B, alphas[i]) + triangle = VGroup( + Line(C, A, color = BLUE), + Line(C, B, color = RED), + Line(A, B, color = WHITE), + Line(C, H, color = GREEN) + ) + for line, char in zip(triangle, ["a", "b", "c", "h"]): + label = TexMobject(char) + label.match_color(line) + vect = line.get_center() - triangle.get_center() + vect /= np.linalg.norm(vect) + label.next_to(line.get_center(), vect) + triangle.add(label) + if char == "h": + label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) + + triangle.to_corner(UP+LEFT) + self.add(triangle) + + argument_lines = VGroup( + TexMobject( + "\\text{Area} = ", + "{1 \\over 2}", "a", "b", "=", + "{1 \\over 2}", "c", "h" + ), + TexMobject("\\Downarrow"), + TexMobject("a^2", "b^2", "=", "c^2", "h^2"), + TexMobject("\\Downarrow"), + TexMobject( + "a^2", "b^2", "=", + "(", "a^2", "+", "b^2", ")", "h^2" + ), + TexMobject("\\Downarrow"), + TexMobject( + "{1 \\over ", "h^2}", "=", + "{1 \\over ", "b^2}", "+", + "{1 \\over ", "a^2}", + ), + ) + argument_lines.arrange_submobjects(DOWN) + for line in argument_lines: + line.highlight_by_tex_to_color_map({ + "a" : BLUE, + "b" : RED, + "h" : GREEN, + "Area" : WHITE, + "Downarrow" : WHITE, + }) + all_equals = line.get_parts_by_tex("=") + if all_equals: + line.alignment_mob = all_equals[-1] + else: + line.alignment_mob = line[0] + line.shift(-line.alignment_mob.get_center()[0]*RIGHT) + argument_lines.next_to(triangle, RIGHT) + argument_lines.to_edge(UP) + + prev_line = argument_lines[0] + self.play(FadeIn(prev_line)) + for arrow, line in zip(argument_lines[1::2], argument_lines[2::2]): + line.save_state() + line.shift( + prev_line.alignment_mob.get_center() - \ + line.alignment_mob.get_center() + ) + line.fade(1) + self.play( + line.restore, + GrowFromPoint(arrow, arrow.get_top()) + ) + self.wait() + prev_line = line + +class WeCanHaveMoreFunThanThat(TeacherStudentsScene): + def construct(self): + point = VectorizedPoint(SPACE_WIDTH*LEFT/2 + SPACE_HEIGHT*UP/2) + self.teacher_says( + "We can have \\\\ more fun than that!", + target_mode = "hooray" + ) + self.change_student_modes(*3*["erm"], look_at_arg = point) + self.wait() + self.play( + RemovePiCreatureBubble( + self.teacher, + target_mode = "raise_right_hand", + look_at_arg = point, + ), + self.get_student_changes(*3*["pondering"], look_at_arg = point) + ) + self.wait(3) + +class IPTScene(TwoLightSourcesScene, ZoomedScene): + CONFIG = { + "max_opacity_ambient" : 0.2, + "num_levels" : 200, + } + def construct(self): + #Copy pasting from TwoLightSourcesScene....Very bad... + origin_point = self.origin_point + self.remove(self.pi_creature) + + #Axes + axes = Axes( + x_min = -1, x_max = 10.5, + y_min = -1, y_max = 6.5, + ) + axes.shift(origin_point) + + #Important reference points + A = axes.coords_to_point(self.a, 0) + B = axes.coords_to_point(0, self.b) + C = axes.coords_to_point(0, 0) + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + # find the coords of the altitude point H + # as the solution of a certain LSE + prelim_matrix = np.array([ + [yA - yB, xB - xA], + [xA - xB, yA - yB] + ]) # sic + prelim_vector = np.array( + [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] + ) + H2 = np.linalg.solve(prelim_matrix, prelim_vector) + H = np.append(H2, 0.) + + #Lightsources + lsA = LightSource( + radius = self.radius, + num_levels = self.num_levels, + opacity_function = inverse_power_law(2, 1, 1, 1.5), + max_opacity_ambient = self.max_opacity_ambient, + ) + lsA.lighthouse.scale(0.5, about_edge = UP) + lsB = lsA.deepcopy() + lsA.move_source_to(A) + lsB.move_source_to(B) + lsC = lsA.deepcopy() + lsC.move_source_to(H) + + #Lighthouse labels + A_label = TextMobject("A") + A_label.next_to(lsA.lighthouse, RIGHT) + B_label = TextMobject("B") + B_label.next_to(lsB.lighthouse, LEFT) + + #Lines + line_a = Line(C, A) + line_a.highlight(BLUE) + line_b = Line(C, B) + line_b.highlight(RED) + line_c = Line(A, B) + line_h = Line(H, C) + line_h.highlight(GREEN) + + label_a = TexMobject("a") + label_a.match_color(line_a) + label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) + label_b = TexMobject("b") + label_b.match_color(line_b) + label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) + label_h = TexMobject("h") + label_h.match_color(line_h) + label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) + + perp_mark = VMobject().set_points_as_corners([ + RIGHT, RIGHT+DOWN, DOWN + ]) + perp_mark.scale(0.25, about_point = ORIGIN) + perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) + perp_mark.shift(H) + + # Mini triangle + m_hyp_a = Line(H, A) + m_a = line_a.copy() + m_hyp_b = Line(H, B) + m_b = line_b.copy() + mini_triangle = VGroup(m_a, m_hyp_a, m_b, m_hyp_b) + mini_triangle.set_stroke(width = 5) + + mini_triangle.generate_target() + mini_triangle.target.scale(0.1, about_point = origin_point) + for part, part_target in zip(mini_triangle, mini_triangle.target): + part.target = part_target + + # Screen label + screen_word = TextMobject("Screen") + screen_word.next_to(mini_triangle.target, UP+RIGHT, LARGE_BUFF) + screen_arrow = Arrow( + screen_word.get_bottom(), + mini_triangle.target.get_center(), + color = WHITE, + ) + + # IPT Theorem + theorem = TexMobject( + "{1 \over ", "a^2}", "+", + "{1 \over", "b^2}", "=", "{1 \over","h^2}" + ) + theorem.highlight_by_tex_to_color_map({ + "a" : line_a.get_color(), + "b" : line_b.get_color(), + "h" : line_h.get_color(), + }) + theorem_name = TextMobject("Inverse Pythagorean Theorem") + theorem_name.to_corner(UP+RIGHT) + theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) + theorem_box = SurroundingRectangle(theorem, color = WHITE) + + # Setup spotlights + spotlight_a = VGroup() + spotlight_a.screen = m_hyp_a + spotlight_b = VGroup() + spotlight_b.screen = m_hyp_b + for spotlight in spotlight_a, spotlight_b: + spotlight.get_source_point = lsC.get_source_point + dr = lsC.ambient_light.radius/lsC.ambient_light.num_levels + def update_spotlight(spotlight): + spotlight.submobjects = [] + source_point = spotlight.get_source_point() + c1, c2 = spotlight.screen.get_start(), spotlight.screen.get_end() + distance = max( + np.linalg.norm(c1 - source_point), + np.linalg.norm(c2 - source_point), + ) + n_parts = np.ceil(distance/dr) + alphas = np.linspace(0, 1, n_parts+1) + for a1, a2 in zip(alphas, alphas[1:]): + spotlight.add(Polygon( + interpolate(source_point, c1, a1), + interpolate(source_point, c1, a2), + interpolate(source_point, c2, a2), + interpolate(source_point, c2, a1), + fill_color = YELLOW, + fill_opacity = 2*lsC.ambient_light.opacity_function(a1*distance), + stroke_width = 0 + )) + + def update_spotlights(spotlights): + for spotlight in spotlights: + update_spotlight(spotlight) + + def get_spotlight_triangle(spotlight): + sp = spotlight.get_source_point() + c1 = spotlight.screen.get_start() + c2 = spotlight.screen.get_end() + return Polygon( + sp, c1, c2, + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 0.5, + ) + + spotlights = VGroup(spotlight_a, spotlight_b) + spotlights_update_anim = ContinualUpdateFromFunc( + spotlights, update_spotlights + ) + + # Add components + self.add( + axes, + lsA.ambient_light, + lsB.ambient_light, + lsC.ambient_light, + line_c, + ) + self.add_foreground_mobjects( + lsA.lighthouse, A_label, + lsB.lighthouse, B_label, + lsC.lighthouse, line_h, + theorem, theorem_name, theorem_box, + ) + + # Show miniature triangle + self.play(ShowCreation(mini_triangle, submobject_mode = "all_at_once")) + self.play( + MoveToTarget(mini_triangle), + run_time = 2, + ) + self.add_foreground_mobject(mini_triangle) + + # Show beams of light + self.play( + Write(screen_word), + GrowArrow(screen_arrow), + ) + self.wait() + spotlights_update_anim.update(0) + self.play( + LaggedStart(FadeIn, spotlight_a), + LaggedStart(FadeIn, spotlight_b), + Animation(screen_arrow), + ) + self.add(spotlights_update_anim) + self.play(*map(FadeOut, [screen_word, screen_arrow])) + self.wait() + + # Reshape screen + m_hyps = [m_hyp_a, m_hyp_b] + for hyp, line in (m_hyp_a, m_a), (m_hyp_b, m_b): + hyp.save_state() + hyp.alt_version = line.copy() + hyp.alt_version.highlight(WHITE) + + for x in range(2): + self.play(*[ + Transform(m, m.alt_version) + for m in m_hyps + ]) + self.wait() + self.play(*[m.restore for m in m_hyps]) + self.wait() + + # Show spotlight a key point + def show_beaming_light(spotlight): + triangle = get_spotlight_triangle(spotlight) + for x in range(3): + anims = [] + if x > 0: + anims.append(FadeOut(triangle.copy())) + anims.append(GrowFromPoint(triangle, triangle.points[0])) + self.play(*anims) + self.play(FadeOut(triangle)) + pass + + def show_key_point(spotlight, new_point): + screen = spotlight.screen + update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) + self.play( + Transform(screen, screen.alt_version), + update_spotlight_anim, + ) + show_beaming_light(spotlight) + self.play(screen.restore, update_spotlight_anim) + self.wait() + self.play( + lsC.move_source_to, new_point, + Transform(screen, screen.alt_version), + update_spotlight_anim, + run_time = 2 + ) + show_beaming_light(spotlight) + self.wait() + self.play( + lsC.move_source_to, H, + screen.restore, + update_spotlight_anim, + run_time = 2 + ) + self.wait() + + self.remove(spotlights_update_anim) + self.add(spotlight_b) + self.play(*map(FadeOut, [ + spotlight_a, lsA.ambient_light, lsB.ambient_light + ])) + show_key_point(spotlight_b, A) + self.play( + FadeOut(spotlight_b), + FadeIn(spotlight_a), + ) + show_key_point(spotlight_a, B) + self.wait() + +class HomeworkWrapper(Scene): + def construct(self): + title = TextMobject("Homework") + title.to_edge(UP) + screen = ScreenRectangle(height = 6) + screen.center() + self.add(title) + self.play(ShowCreation(screen)) + self.wait(5) + +class HeresWhereThingsGetGood(TeacherStudentsScene): + def construct(self): + self.teacher_says("Now for the \\\\ good part!") + self.change_student_modes(*["hooray"]*3) + self.change_student_modes(*["happy"]*3) + self.wait() + +class DiameterTheorem(TeacherStudentsScene): + def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) + + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) + + diameter_word = TextMobject("Diameter") + diameter_word.next_to(center, DOWN, SMALL_BUFF) + + point = VectorizedPoint(circle.get_top()) + triangle = Polygon(LEFT, RIGHT, UP) + triangle.set_stroke(BLUE) + triangle.set_fill(WHITE, 0.5) + def update_triangle(triangle): + triangle.set_points_as_corners([ + circle.get_left(), circle.get_right(), + point.get_center(), circle.get_left(), + ]) + triangle_update_anim = ContinualUpdateFromFunc( + triangle, update_triangle + ) + triangle_update_anim.update(0) + + perp_mark = VMobject() + perp_mark.set_points_as_corners([LEFT, DOWN, RIGHT]) + perp_mark.shift(DOWN) + perp_mark.scale(0.15, about_point = ORIGIN) + perp_mark.shift(point.get_center()) + perp_mark.add(point.copy()) + + self.play( + self.teacher.change, "raise_right_hand", + DrawBorderThenFill(triangle), + Write(diameter_word), + ) + self.play( + ShowCreation(perp_mark), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(perp_mark) + self.add(triangle_update_anim) + for angle in 0.2*TAU, -0.4*TAU, 0.3*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) + + perp_mark.generate_target() + perp_mark.target.rotate(angle/2) + perp_mark.target.shift( + point.target.get_center() - \ + perp_mark.target[1].get_center() + ) + + self.play( + MoveToTarget(point), + MoveToTarget(perp_mark), + path_arc = angle, + run_time = 3, + ) + +class InscribedeAngleThreorem(TeacherStudentsScene): + def construct(self): + circle = Circle(radius = 2, color = WHITE) + circle.next_to(self.students[2], UP) + self.add(circle) + + title = TextMobject("Inscribed angle \\\\ theorem") + title.to_corner(UP+LEFT) + self.add(title) + + center = Dot(circle.get_center(), color = WHITE) + self.add_foreground_mobject(center) + + point = VectorizedPoint(circle.get_left()) + shape = Polygon(UP+LEFT, ORIGIN, DOWN+LEFT, RIGHT) + shape.set_stroke(BLUE) + def update_shape(shape): + shape.set_points_as_corners([ + point.get_center(), + circle.point_from_proportion(7./8), + circle.get_center(), + circle.point_from_proportion(1./8), + point.get_center(), + ]) + shape_update_anim = ContinualUpdateFromFunc( + shape, update_shape + ) + shape_update_anim.update(0) + + angle_mark = Arc(start_angle = -TAU/8, angle = TAU/4) + angle_mark.scale(0.3, about_point = ORIGIN) + angle_mark.shift(circle.get_center()) + theta = TexMobject("\\theta").highlight(RED) + theta.next_to(angle_mark, RIGHT, MED_SMALL_BUFF) + angle_mark.match_color(theta) + + half_angle_mark = Arc(start_angle = -TAU/16, angle = TAU/8) + half_angle_mark.scale(0.3, about_point = ORIGIN) + half_angle_mark.shift(point.get_center()) + half_angle_mark.add(point.copy()) + theta_halves = TexMobject("\\theta/2").highlight(GREEN) + theta_halves.scale(0.7) + half_angle_mark.match_color(theta_halves) + theta_halves_update = UpdateFromFunc( + theta_halves, lambda m : m.move_to(interpolate( + point.get_center(), + half_angle_mark.point_from_proportion(0.5), + 2.5, + )) + ) + theta_halves_update.update(0) + + self.play( + self.teacher.change, "raise_right_hand", + ShowCreation(shape, rate_func = None), + ) + self.play(*map(FadeIn, [angle_mark, theta])) + self.play( + ShowCreation(half_angle_mark), + Write(theta_halves), + self.get_student_changes(*["pondering"]*3) + ) + self.add_foreground_mobjects(half_angle_mark, theta_halves) + self.add(shape_update_anim) + for angle in 0.25*TAU, -0.4*TAU, 0.3*TAU, -0.35*TAU: + point.generate_target() + point.target.rotate(angle, about_point = circle.get_center()) + + half_angle_mark.generate_target() + half_angle_mark.target.rotate(angle/2) + half_angle_mark.target.shift( + point.target.get_center() - \ + half_angle_mark.target[1].get_center() + ) + + self.play( + MoveToTarget(point), + MoveToTarget(half_angle_mark), + theta_halves_update, + path_arc = angle, + run_time = 3, + ) + +class PondScene(ThreeDScene): + def construct(self): + + BASELINE_YPOS = -2.5 + OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) + LAKE0_RADIUS = 1.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.5 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + LIGHT_MAX_INT = 1 + LIGHT_SCALE = 2.5 + LIGHT_CUTOFF = 1 + + RIGHT_ANGLE_SIZE = 0.3 + + self.cumulated_zoom_factor = 1 + + def right_angle(pointA, pointB, pointC, size = 1): + + v1 = pointA - pointB + v1 = size * v1/np.linalg.norm(v1) + v2 = pointC - pointB + v2 = size * v2/np.linalg.norm(v2) + + P = pointB + Q = pointB + v1 + R = Q + v2 + S = R - v1 + angle_sign = VMobject() + angle_sign.set_points_as_corners([P,Q,R,S,P]) + angle_sign.mark_paths_closed = True + angle_sign.set_fill(color = WHITE, opacity = 1) + angle_sign.set_stroke(width = 0) + return angle_sign + + def triangle(pointA, pointB, pointC): + + mob = VMobject() + mob.set_points_as_corners([pointA, pointB, pointC, pointA]) + mob.mark_paths_closed = True + mob.set_fill(color = WHITE, opacity = 0.5) + mob.set_stroke(width = 0) + return mob + + def zoom_out_scene(factor): + + self.remove_foreground_mobject(self.ls0_dot) + self.remove(self.ls0_dot) + + phi0 = self.camera.get_phi() # default is 0 degs + theta0 = self.camera.get_theta() # default is -90 degs + distance0 = self.camera.get_distance() + + distance1 = 2 * distance0 + camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) + + self.play( + ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), + self.zoomable_mobs.shift, self.obs_dot.get_center(), + self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, + ) + + self.cumulated_zoom_factor *= factor + + # place ls0_dot by hand + #old_radius = self.ls0_dot.radius + #self.ls0_dot.radius = 2 * old_radius + + #v = self.ls0_dot.get_center() - self.obs_dot.get_center() + #self.ls0_dot.shift(v) + #self.ls0_dot.move_to(self.outer_lake.get_center()) + self.ls0_dot.scale(2, about_point = ORIGIN) + + #self.add_foreground_mobject(self.ls0_dot) + + def shift_scene(v): + self.play( + self.zoomable_mobs.shift,v, + self.unzoomable_mobs.shift,v + ) + + self.zoomable_mobs = VMobject() + self.unzoomable_mobs = VMobject() + + baseline = VMobject() + baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) + baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene + self.zoomable_mobs.add(baseline) # prob not necessary + + obs_dot = self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) + ls0_dot = self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) + self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) + + # lake + lake0 = Circle(radius = LAKE0_RADIUS, + stroke_width = 0, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY + ) + lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + self.zoomable_mobs.add(lake0) + + # Morty and indicator + morty = Mortimer().flip().scale(0.3) + morty.next_to(OBSERVER_POINT,DOWN) + indicator = LightIndicator(precision = 2, + radius = INDICATOR_RADIUS, + show_reading = False, + color = LIGHT_COLOR + ) + indicator.next_to(morty,LEFT) + self.unzoomable_mobs.add(morty, indicator) + + # first lighthouse + original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) + ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 150) + ls0.lighthouse.scale_to_fit_height(LIGHTHOUSE_HEIGHT) + ls0.lighthouse.height = LIGHTHOUSE_HEIGHT + ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) + self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) + + # self.add(lake0, morty, obs_dot, ls0_dot, ls0.lighthouse) + + # shore arcs + arc_left = Arc(-TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_left.next_to(arc_left,LEFT) + + arc_right = Arc(TAU/2, + radius = LAKE0_RADIUS, + start_angle = -TAU/4, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR + ) + arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) + + one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) + one_right.next_to(arc_right,RIGHT) + + # New introduction + lake0.save_state() + morty.save_state() + lake0.scale_to_fit_height(6) + morty.to_corner(UP+LEFT) + morty.fade(1) + lake0.center() + + lake_word = TextMobject("Lake") + lake_word.scale(2) + lake_word.move_to(lake0) + + self.play( + DrawBorderThenFill(lake0, stroke_width = 1), + Write(lake_word) + ) + self.play( + lake0.restore, + lake_word.scale, 0.5, {"about_point" : lake0.get_bottom()}, + lake_word.fade, 1 + ) + self.remove(lake_word) + self.play(morty.restore) + self.play( + GrowFromCenter(obs_dot), + GrowFromCenter(ls0_dot), + FadeIn(ls0.lighthouse) + ) + self.add_foreground_mobjects(ls0.lighthouse, obs_dot, ls0_dot) + self.play( + SwitchOn(ls0.ambient_light), + Animation(ls0.lighthouse), + ) + self.wait() + self.play( + morty.move_to, ls0.lighthouse, + run_time = 3, + path_arc = TAU/2, + rate_func = there_and_back + ) + + self.play( + ShowCreation(arc_right), + Write(one_right), + ) + self.play( + ShowCreation(arc_left), + Write(one_left), + ) + self.play( + lake0.set_stroke, { + "color": LAKE_STROKE_COLOR, + "width" : LAKE_STROKE_WIDTH + }, + ) + self.wait() + self.add_foreground_mobjects(morty) + + + # Show indicator + self.play(FadeIn(indicator)) + + self.play(indicator.set_intensity, 0.5) + + diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02) + diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) + + # diameter + diameter = DoubleArrow(diameter_start, + diameter_stop, + buff = 0, + color = WHITE, + ) + diameter_text = TexMobject("d").scale(TEX_SCALE) + diameter_text.next_to(diameter,RIGHT) + + self.play( + GrowFromCenter(diameter), + Write(diameter_text), + #FadeOut(self.obs_dot), + FadeOut(ls0_dot) + ) + self.wait() + + indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE) + indicator_reading.move_to(indicator) + self.unzoomable_mobs.add(indicator_reading) + + self.play( + ReplacementTransform( + diameter_text[0].copy(), + indicator_reading[2], + ), + FadeIn(indicator_reading) + ) + self.wait() + + # replace d with its value + new_diameter_text = TexMobject("{2 \over \pi}").scale(TEX_SCALE) + new_diameter_text.color = LAKE_COLOR + new_diameter_text.move_to(diameter_text) + self.play(FadeOut(diameter_text)) + self.play(FadeIn(new_diameter_text)) + self.wait(2) + + # insert into indicator reading + new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) + new_reading.move_to(indicator) + new_diameter_text_copy = new_diameter_text.copy() + new_diameter_text_copy.submobjects.reverse() + + self.play( + FadeOut(indicator_reading), + ReplacementTransform( + new_diameter_text_copy, + new_reading, + parth_arc = 30*DEGREES + ) + ) + indicator_reading = new_reading + + self.wait(2) + + self.play( + FadeOut(one_left), + FadeOut(one_right), + FadeOut(new_diameter_text), + FadeOut(arc_left), + FadeOut(arc_right) + ) + self.add_foreground_mobjects(indicator, indicator_reading) + self.unzoomable_mobs.add(indicator_reading) + + def indicator_wiggle(): + INDICATOR_WIGGLE_FACTOR = 1.3 + + self.play( + ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), + ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) + ) + + def angle_for_index(i,step): + return -TAU/4 + TAU/2**step * (i + 0.5) + + + def position_for_index(i, step, scaled_down = False): + + theta = angle_for_index(i,step) + radial_vector = np.array([np.cos(theta),np.sin(theta),0]) + position = self.lake_center + self.lake_radius * radial_vector + + if scaled_down: + return position.scale_about_point(self.obs_dot.get_center(),0.5) + else: + return position + + + def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): + + ls_new_loc1 = position_for_index(i,step + 1) + ls_new_loc2 = position_for_index(i + 2**step,step + 1) + + hyp = VMobject() + hyp1 = Line(self.lake_center,ls_new_loc1) + hyp2 = Line(self.lake_center,ls_new_loc2) + hyp.add(hyp2,hyp1) + self.new_hypotenuses.append(hyp) + + if show_steps == True: + self.play( + ShowCreation(hyp, run_time = run_time) + ) + + leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) + leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) + self.new_legs_1.append(leg1) + self.new_legs_2.append(leg2) + + if show_steps == True: + self.play( + ShowCreation(leg1, run_time = run_time), + ShowCreation(leg2, run_time = run_time), + ) + + ls1 = self.light_sources_array[i] + + + ls2 = ls1.copy() + if animate == True: + self.add(ls2) + + self.additional_light_sources.append(ls2) + + # check if the light sources are on screen + ls_old_loc = np.array(ls1.get_source_point()) + onscreen_old = np.any(np.abs(ls_old_loc) < 10) + onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) + onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) + show_animation = (onscreen_old or onscreen_1 or onscreen_2) + + if show_animation or animate: + ls1.generate_target() + ls2.generate_target() + ls1.target.move_source_to(ls_new_loc1) + ls2.target.move_source_to(ls_new_loc2) + ls1.fade(1) + self.play( + MoveToTarget(ls1), MoveToTarget(ls2), + run_time = run_time + ) + else: + ls1.move_source_to(ls_new_loc1) + ls2.move_source_to(ls_new_loc1) + + + def construction_step(n, show_steps = True, run_time = 1, + simultaneous_splitting = False): + + # we assume that the scene contains: + # an inner lake, self.inner_lake + # an outer lake, self.outer_lake + # light sources, self.light_sources + # legs from the observer point to each light source + # self.legs + # altitudes from the observer point to the + # locations of the light sources in the previous step + # self.altitudes + # hypotenuses connecting antipodal light sources + # self.hypotenuses + + # these are mobjects! + + + # first, fade out all of the hypotenuses and altitudes + + if show_steps == True: + self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) + self.play( + FadeOut(self.hypotenuses), + FadeOut(self.altitudes), + FadeOut(self.inner_lake) + ) + else: + self.zoomable_mobs.remove(self.inner_lake) + self.play( + FadeOut(self.inner_lake) + ) + + # create a new, outer lake + self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP + + new_outer_lake = Circle(radius = self.lake_radius, + stroke_width = LAKE_STROKE_WIDTH, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + stroke_color = LAKE_STROKE_COLOR + ) + new_outer_lake.move_to(self.lake_center) + + if show_steps == True: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + FadeIn(self.ls0_dot) + ) + else: + self.play( + FadeIn(new_outer_lake, run_time = run_time), + ) + + self.wait() + + self.inner_lake = self.outer_lake + self.outer_lake = new_outer_lake + self.altitudes = self.legs + #self.lake_center = self.outer_lake.get_center() + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + if simultaneous_splitting == False: + + for i in range(2**n): + + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time + ) + + if n == 1 and i == 0: + # show again where the right angles are + A = self.light_sources[0].get_center() + B = self.additional_light_sources[0].get_center() + C = self.obs_dot.get_center() + + triangle1 = triangle( + A, C, B + ) + right_angle1 = right_angle( + A, C, B, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle1), + FadeIn(right_angle1) + ) + + self.wait() + + self.play( + FadeOut(triangle1), + FadeOut(right_angle1) + ) + + self.wait() + + H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT + L = self.outer_lake.get_center() + triangle2 = triangle( + L, H, C + ) + + right_angle2 = right_angle( + L, H, C, size = 2 * RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(triangle2), + FadeIn(right_angle2) + ) + + self.wait() + + self.play( + FadeOut(triangle2), + FadeOut(right_angle2) + ) + + self.wait() + + else: # simultaneous splitting + + old_lake = self.outer_lake.copy() + old_ls = self.light_sources.copy() + old_ls2 = old_ls.copy() + for submob in old_ls2.submobjects: + old_ls.add(submob) + + self.remove(self.outer_lake, self.light_sources) + self.add(old_lake, old_ls) + + for i in range(2**n): + split_light_source(i, + step = n, + show_steps = show_steps, + run_time = run_time, + animate = False + ) + + self.play( + ReplacementTransform(old_ls, self.light_sources, run_time = run_time), + ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), + ) + + + + + # collect the newly created mobs (in arrays) + # into the appropriate Mobject containers + + self.legs = VMobject() + for leg in self.new_legs_1: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + for leg in self.new_legs_2: + self.legs.add(leg) + self.zoomable_mobs.add(leg) + + for hyp in self.hypotenuses.submobjects: + self.zoomable_mobs.remove(hyp) + + self.hypotenuses = VMobject() + for hyp in self.new_hypotenuses: + self.hypotenuses.add(hyp) + self.zoomable_mobs.add(hyp) + + for ls in self.additional_light_sources: + self.light_sources.add(ls) + self.light_sources_array.append(ls) + self.zoomable_mobs.add(ls) + + # update scene + self.add( + self.light_sources, + self.inner_lake, + self.outer_lake, + ) + self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) + + if show_steps == True: + self.add( + self.legs, + self.hypotenuses, + self.altitudes, + ) + self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) + + + self.wait() + + if show_steps == True: + self.play(FadeOut(self.ls0_dot)) + + #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP + self.lake_radius *= 2 + + self.lake_center = ls0_loc = ls0.get_source_point() + + self.inner_lake = VMobject() + self.outer_lake = lake0 + self.legs = VMobject() + self.legs.add(Line(OBSERVER_POINT,self.lake_center)) + self.altitudes = VMobject() + self.hypotenuses = VMobject() + self.light_sources_array = [ls0] + self.light_sources = VMobject() + self.light_sources.add(ls0) + + self.lake_radius = 2 * LAKE0_RADIUS # don't ask... + + self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) + + self.add( + self.inner_lake, + self.outer_lake, + self.legs, + self.altitudes, + self.hypotenuses + ) + + self.play(FadeOut(diameter)) + + self.additional_light_sources = [] + self.new_legs_1 = [] + self.new_legs_2 = [] + self.new_hypotenuses = [] + + construction_step(0) + + my_triangle = triangle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point() + ) + + angle_sign1 = right_angle( + self.light_sources[0].get_source_point(), + OBSERVER_POINT, + self.light_sources[1].get_source_point(), + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign1), + FadeIn(my_triangle) + ) + + angle_sign2 = right_angle( + self.light_sources[1].get_source_point(), + self.lake_center, + OBSERVER_POINT, + size = RIGHT_ANGLE_SIZE + ) + + self.play( + FadeIn(angle_sign2) + ) + + self.wait() + + self.play( + FadeOut(angle_sign1), + FadeOut(angle_sign2), + FadeOut(my_triangle) + ) + + indicator_wiggle() + self.remove(self.ls0_dot) + zoom_out_scene(2) + + + construction_step(1) + indicator_wiggle() + #self.play(FadeOut(self.ls0_dot)) + zoom_out_scene(2) + + + construction_step(2) + indicator_wiggle() + self.play(FadeOut(self.ls0_dot)) + + + + + self.play( + FadeOut(self.altitudes), + FadeOut(self.hypotenuses), + FadeOut(self.legs) + ) + + max_it = 6 + scale = 2**(max_it - 4) + TEX_SCALE *= scale + + + + # for i in range(3,max_it + 1): + # construction_step(i, show_steps = False, run_time = 4.0/2**i, + # simultaneous_splitting = True) + + + + # simultaneous expansion of light sources from now on + self.play(FadeOut(self.inner_lake)) + + for n in range(3,max_it + 1): + + new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) + for ls in self.light_sources_array: + lsp = ls.copy() + self.light_sources.add(lsp) + self.add(lsp) + self.light_sources_array.append(lsp) + + new_lake_center = new_lake.get_center() + new_lake_radius = 0.5 * new_lake.get_width() + + shift_list = (Transform(self.outer_lake,new_lake),) + + + for i in range(2**n): + theta = -TAU/4 + (i + 0.5) * TAU / 2**n + v = np.array([np.cos(theta), np.sin(theta),0]) + pos1 = new_lake_center + new_lake_radius * v + pos2 = new_lake_center - new_lake_radius * v + shift_list += (self.light_sources.submobjects[i].move_source_to,pos1) + shift_list += (self.light_sources.submobjects[i+2**n].move_source_to,pos2) + + self.play(*shift_list) + + #self.revert_to_original_skipping_status() + + # Now create a straight number line and transform into it + MAX_N = 17 + + origin_point = self.obs_dot.get_center() + + self.number_line = NumberLine( + x_min = -MAX_N, + x_max = MAX_N + 1, + color = WHITE, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), + numbers_to_show = range(-MAX_N,MAX_N + 1,2), + unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, + tick_frequency = 1, + line_to_number_buff = LARGE_BUFF, + label_direction = UP, + ).shift(scale * 2.5 * DOWN) + + self.number_line.label_direction = DOWN + + self.number_line_labels = self.number_line.get_number_mobjects() + self.wait() + + origin_point = self.number_line.number_to_point(0) + nl_sources = VMobject() + pond_sources = VMobject() + + for i in range(-MAX_N,MAX_N+1): + anchor = self.number_line.number_to_point(2*i + 1) + ls = self.light_sources_array[i].copy() + ls.move_source_to(anchor) + nl_sources.add(ls) + pond_sources.add(self.light_sources_array[i].copy()) + + self.add(pond_sources) + self.remove(self.light_sources) + + self.outer_lake.rotate(TAU/8) + + # open sea + open_sea = Rectangle( + width = 20 * scale, + height = 10 * scale, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + fill_color = LAKE_COLOR, + fill_opacity = LAKE_OPACITY, + ).flip().next_to(origin_point,UP,buff = 0) + + self.play( + ReplacementTransform(pond_sources,nl_sources), + ReplacementTransform(self.outer_lake,open_sea), + FadeOut(self.inner_lake) + ) + self.play(FadeIn(self.number_line)) + + self.wait() + + v = 4 * scale * UP + self.play( + nl_sources.shift,v, + morty.shift,v, + self.number_line.shift,v, + indicator.shift,v, + indicator_reading.shift,v, + open_sea.shift,v, + self.obs_dot.shift,v, + ) + self.number_line_labels.shift(v) + + origin_point = self.number_line.number_to_point(0) + #self.remove(self.obs_dot) + self.play( + indicator.move_to, origin_point + scale * UP, + indicator_reading.move_to, origin_point + scale * UP, + FadeOut(open_sea), + FadeOut(morty), + FadeIn(self.number_line_labels) + ) + + two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ + "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ + "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ + "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ + "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") + + nb_symbols = len(two_sided_sum.submobjects) + + two_sided_sum.scale(TEX_SCALE) + + for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects): + submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) + if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions + submob.shift(0.3 * scale * DOWN) + + self.play(Write(two_sided_sum)) + + for i in range(MAX_N - 5, MAX_N): + self.remove(nl_sources.submobjects[i].ambient_light) + + for i in range(MAX_N, MAX_N + 5): + self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light) + + self.wait() + + covering_rectangle = Rectangle( + width = SPACE_WIDTH * scale, + height = 2 * SPACE_HEIGHT * scale, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 1, + ) + covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) + for i in range(10): + self.add_foreground_mobject(nl_sources.submobjects[i]) + + self.add_foreground_mobject(indicator) + self.add_foreground_mobject(indicator_reading) + + + half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) + half_indicator_reading.move_to(indicator) + + central_plus_sign = two_sided_sum[13] + + self.play( + FadeIn(covering_rectangle), + Transform(indicator_reading, half_indicator_reading), + FadeOut(central_plus_sign) + ) + + equals_sign = TexMobject("=").scale(TEX_SCALE) + equals_sign.move_to(central_plus_sign) + p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP + + self.play( + indicator.move_to,p, + indicator_reading.move_to,p, + FadeIn(equals_sign), + ) + + self.revert_to_original_skipping_status() + + # show Randy admiring the result + randy = Randolph(color = MAROON_E).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) + self.play(FadeIn(randy)) + self.play(randy.change,"happy") + self.play(randy.change,"hooray") + +class CircumferenceText(Scene): + CONFIG = {"n" : 16} + def construct(self): + words = TextMobject("Circumference %d"%self.n) + words.scale(1.25) + words.to_corner(UP+LEFT) + self.add(words) + +class CenterOfLargerCircleOverlayText(Scene): + def construct(self): + words = TextMobject("Center of \\\\ larger circle") + arrow = Vector(DOWN+LEFT, color = WHITE) + arrow.shift(words.get_bottom() + SMALL_BUFF*DOWN - arrow.get_start()) + group = VGroup(words, arrow) + group.scale_to_fit_height(2*SPACE_HEIGHT - 1) + group.to_edge(UP) + self.add(group) + +class DiameterWordOverlay(Scene): + def construct(self): + word = TextMobject("Diameter") + word.scale_to_fit_width(SPACE_WIDTH) + word.rotate(-45*DEGREES) + self.play(Write(word)) + self.wait() + +class YayIPTApplies(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Heyo! The Inverse \\\\ Pythagorean Theorem \\\\ applies!", + bubble_kwargs = {"width" : 5}, + target_mode = "surprised" + ) + self.change_student_modes(*3*["hooray"]) + self.wait(2) + +class WalkThroughOneMoreStep(TeacherStudentsScene): + def construct(self): + self.student_says(""" + Wait...can you walk \\\\ + through one more step? + """) + self.play(self.teacher.change, "happy") + self.wait(4) + +class ThinkBackToHowAmazingThisIs(ThreeDScene): + CONFIG = { + "x_radius" : 100, + "max_shown_n" : 20, + } + def construct(self): + self.show_sum() + self.show_giant_circle() + + def show_sum(self): + number_line = NumberLine( + x_min = -self.x_radius, + x_max = self.x_radius, + numbers_to_show = range(-self.max_shown_n, self.max_shown_n), + ) + number_line.add_numbers() + number_line.shift(2*DOWN) + + positive_dots, negative_dots = [ + VGroup(*[ + Dot(number_line.number_to_point(u*x)) + for x in range(1, int(self.x_radius), 2) + ]) + for u in 1, -1 + ] + dot_pairs = it.starmap(VGroup, zip(positive_dots, negative_dots)) + + # Decimal + decimal = DecimalNumber(0, num_decimal_points = 6) + decimal.to_edge(UP) + terms = [2./(n**2) for n in range(1, 100, 2)] + partial_sums = np.cumsum(terms) + + # pi^2/4 label + brace = Brace(decimal, DOWN) + pi_term = TexMobject("\pi^2 \over 4") + pi_term.next_to(brace, DOWN) + + term_mobjects = VGroup() + for n in range(1, self.max_shown_n, 2): + p_term = TexMobject("\\left(\\frac{1}{%d}\\right)^2"%n) + n_term = TexMobject("\\left(\\frac{-1}{%d}\\right)^2"%n) + group = VGroup(p_term, n_term) + group.scale(0.7) + p_term.next_to(number_line.number_to_point(n), UP, LARGE_BUFF) + n_term.next_to(number_line.number_to_point(-n), UP, LARGE_BUFF) + term_mobjects.add(group) + term_mobjects.gradient_highlight(BLUE, YELLOW) + plusses = VGroup(*[ + VGroup(*[ + TexMobject("+").next_to( + number_line.number_to_point(u*n), UP, buff = 1.25, + ) + for u in -1, 1 + ]) + for n in range(0, self.max_shown_n, 2) + ]) + + zoom_out = AmbientMovement( + self.camera.rotation_mobject, + direction = OUT, rate = 0.4 + ) + def update_decimal(decimal): + z = self.camera.rotation_mobject.get_center()[2] + decimal.scale_to_fit_height(0.07*z) + decimal.move_to(0.7*z*UP) + scale_decimal = ContinualUpdateFromFunc(decimal, update_decimal) + + + self.add(number_line, *dot_pairs) + self.add(zoom_out, scale_decimal) + + tuples = zip(term_mobjects, plusses, partial_sums) + run_time = 1 + for term_mobs, plus_pair, partial_sum in tuples: + self.play( + FadeIn(term_mobs), + Write(plus_pair, run_time = 1), + ChangeDecimalToValue(decimal, partial_sum), + run_time = run_time + ) + self.wait(run_time) + run_time *= 0.9 + self.play(ChangeDecimalToValue(decimal, np.pi**2/4, run_time = 5)) + zoom_out.begin_wind_down() + self.wait() + self.remove(zoom_out, scale_decimal) + self.play(*map(FadeOut, it.chain( + term_mobjects, plusses, + number_line.numbers, [decimal] + ))) + + self.number_line = number_line + + def show_giant_circle(self): + self.number_line.main_line.insert_n_anchor_points(10000) + everything = VGroup(*self.mobjects) + circle = everything.copy() + circle.move_to(ORIGIN) + circle.apply_function( + lambda (x, y, z) : complex_to_R3(7*np.exp(complex(0, 0.0315*x))) + ) + circle.rotate(-TAU/4, about_point = ORIGIN) + circle.center() + + self.play(Transform(everything, circle, run_time = 6)) + +class ButWait(TeacherStudentsScene): + def construct(self): + self.student_says( + "But wait!", + target_mode = "angry", + run_time = 1, + ) + self.change_student_modes( + "sassy", "angry", "sassy", + added_anims = [self.teacher.change, "guilty"], + run_time = 1 + ) + self.student_says( + """ + You promised us \\\\ + $1+{1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots$ + """, + target_mode = "sassy", + ) + self.wait(3) + self.teacher_says("Yes, but that's \\\\ very close.") + self.change_student_modes(*["plain"]*3) + self.wait(2) + +class FinalSumManipulationScene(PiCreatureScene): + + def construct(self): + + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + + LIGHT_COLOR2 = RED + LIGHT_COLOR3 = BLUE + + unit_length = 1.5 + vertical_spacing = 2.5 * DOWN + switch_on_time = 0.2 + + sum_vertical_spacing = 1.5 + + randy = self.get_primary_pi_creature() + randy.highlight(MAROON_D) + randy.color = MAROON_D + randy.scale(0.7).flip().to_edge(DOWN + LEFT) + self.wait() + + ls_template = LightSource( + radius = 1, + num_levels = 10, + max_opacity_ambient = 0.5, + opacity_function = inverse_quadratic(1,0.75,1) + ) + + + odd_range = np.arange(1,9,2) + even_range = np.arange(2,16,2) + full_range = np.arange(1,8,1) + + self.number_line1 = NumberLine( + x_min = 0, + x_max = 11, + color = LAKE_STROKE_COLOR, + number_at_center = 0, + stroke_width = LAKE_STROKE_WIDTH, + stroke_color = LAKE_STROKE_COLOR, + #numbers_to_show = full_range, + number_scale_val = 0.5, + numbers_with_elongated_ticks = [], + unit_size = unit_length, + tick_frequency = 1, + line_to_number_buff = MED_SMALL_BUFF, + include_tip = True, + label_direction = UP, + ) + + self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) + self.number_line1.add_numbers() + + odd_lights = VMobject() + for i in odd_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.move_source_to(pos) + odd_lights.add(ls) + + self.play( + ShowCreation(self.number_line1, run_time = 5), + ) + self.wait() + + + odd_terms = VMobject() + for i in odd_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", + fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) + odd_terms.add(term) + + + for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): + self.play( + FadeIn(ls.lighthouse, run_time = switch_on_time), + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term, run_time = switch_on_time) + ) + + result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, + stroke_color = LIGHT_COLOR) + result1.next_to(self.number_line1, LEFT, buff = 0.5) + result1.shift(0.87 * vertical_spacing) + self.play(Write(result1)) + + + + + self.number_line2 = self.number_line1.copy() + self.number_line2.numbers_to_show = full_range + self.number_line2.shift(2 * vertical_spacing) + self.number_line2.add_numbers() + + full_lights = VMobject() + + for i in full_range: + pos = self.number_line2.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR3 + ls.move_source_to(pos) + full_lights.add(ls) + + self.play( + ShowCreation(self.number_line2, run_time = 5), + ) + self.wait() + + + full_lighthouses = VMobject() + full_ambient_lights = VMobject() + for ls in full_lights: + full_lighthouses.add(ls.lighthouse) + full_ambient_lights.add(ls.ambient_light) + + self.play( + LaggedStart(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), + ) + + self.play( + LaggedStart(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) + ) + + # for ls in full_lights.submobjects: + # self.play( + # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), + # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), + # ) + + + + even_terms = VMobject() + for i in even_range: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) + term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) + even_terms.add(term) + + + even_lights = VMobject() + + for i in even_range: + pos = self.number_line1.number_to_point(i) + ls = ls_template.copy() + ls.color = LIGHT_COLOR2 + ls.move_source_to(pos) + even_lights.add(ls) + + for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): + self.play( + SwitchOn(ls.ambient_light, run_time = switch_on_time), + Write(term) + ) + self.wait() + + + + # now morph the even lights into the full lights + full_lights_copy = full_lights.copy() + even_lights_copy = even_lights.copy() + + + self.play( + Transform(even_lights,full_lights, run_time = 2) + ) + + + self.wait() + + for i in range(6): + self.play( + Transform(even_lights[i], even_lights_copy[i]) + ) + self.wait() + + # draw arrows + P1 = self.number_line2.number_to_point(1) + P2 = even_terms.submobjects[0].get_center() + Q1 = interpolate(P1, P2, 0.2) + Q2 = interpolate(P1, P2, 0.8) + quarter_arrow = Arrow(Q1, Q2, + color = LIGHT_COLOR2) + quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) + quarter_label.scale(0.7) + quarter_label.next_to(quarter_arrow.get_center(), RIGHT) + + self.play( + ShowCreation(quarter_arrow), + Write(quarter_label), + ) + self.wait() + + P3 = odd_terms.submobjects[0].get_center() + R1 = interpolate(P1, P3, 0.2) + R2 = interpolate(P1, P3, 0.8) + three_quarters_arrow = Arrow(R1, R2, + color = LIGHT_COLOR) + three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + three_quarters_label.scale(0.7) + three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) + + self.play( + ShowCreation(three_quarters_arrow), + Write(three_quarters_label) + ) + self.wait() + + four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) + four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) + four_thirds_label.scale(0.7) + four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) + + + self.play( + FadeOut(quarter_label), + FadeOut(quarter_arrow), + FadeOut(even_lights), + FadeOut(even_terms) + + ) + self.wait() + + self.play( + ReplacementTransform(three_quarters_arrow, four_thirds_arrow), + ReplacementTransform(three_quarters_label, four_thirds_label) + ) + self.wait() + + full_terms = VMobject() + for i in range(1,8): #full_range: + if i == 1: + term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + elif i == 7: + term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + else: + term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + + term.move_to(self.number_line2.number_to_point(i)) + full_terms.add(term) + + #return + + self.play( + FadeOut(self.number_line1), + FadeOut(odd_lights), + FadeOut(self.number_line2), + FadeOut(full_lights), + FadeIn(full_terms) + ) + self.wait() + + v = (sum_vertical_spacing + 0.5) * UP + self.play( + odd_terms.shift, v, + result1.shift, v, + four_thirds_arrow.shift, v, + four_thirds_label.shift, v, + odd_terms.shift, v, + full_terms.shift, v + ) + + arrow_copy = four_thirds_arrow.copy() + label_copy = four_thirds_label.copy() + arrow_copy.shift(2.5 * LEFT) + label_copy.shift(2.5 * LEFT) + + self.play( + FadeIn(arrow_copy), + FadeIn(label_copy) + ) + self.wait() + + final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) + final_result.next_to(arrow_copy, DOWN) + + self.play( + Write(final_result), + randy.change_mode,"hooray" + ) + self.wait() + + equation = VMobject() + equation.add(final_result) + equation.add(full_terms) + + + self.play( + FadeOut(result1), + FadeOut(odd_terms), + FadeOut(arrow_copy), + FadeOut(label_copy), + FadeOut(four_thirds_arrow), + FadeOut(four_thirds_label), + full_terms.shift,LEFT, + ) + self.wait() + + self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) + + result_box = Rectangle(width = 1.1 * equation.get_width(), + height = 2 * equation.get_height(), color = LIGHT_COLOR3) + result_box.move_to(equation) + + + self.play( + ShowCreation(result_box) + ) + self.wait() + +class LabeledArc(Arc): + CONFIG = { + "length" : 1 + } + + def __init__(self, angle, **kwargs): + + BUFFER = 0.8 + + Arc.__init__(self,angle,**kwargs) + + label = DecimalNumber(self.length, num_decimal_points = 0) + r = BUFFER * self.radius + theta = self.start_angle + self.angle/2 + label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) + + label.move_to(label_pos) + self.add(label) + +class ArcHighlightOverlaySceneCircumferenceEight(Scene): + CONFIG = { + "n" : 2, + } + def construct(self): + BASELINE_YPOS = -2.5 + OBSERVER_POINT = [0,BASELINE_YPOS,0] + LAKE0_RADIUS = 2.5 + INDICATOR_RADIUS = 0.6 + TICK_SIZE = 0.5 + LIGHTHOUSE_HEIGHT = 0.2 + LAKE_COLOR = BLUE + LAKE_OPACITY = 0.15 + LAKE_STROKE_WIDTH = 5.0 + LAKE_STROKE_COLOR = BLUE + TEX_SCALE = 0.8 + DOT_COLOR = BLUE + + FLASH_TIME = 1 + + def flash_arcs(n): + + angle = TAU/2**n + arcs = [] + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) + + for i in range(1,2**n): + arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) + + arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) + + self.play( + FadeIn(arcs[0], run_time = FLASH_TIME) + ) + + for i in range(1,2**n + 1): + self.play( + FadeOut(arcs[i-1], run_time = FLASH_TIME), + FadeIn(arcs[i], run_time = FLASH_TIME) + ) + + self.play( + FadeOut(arcs[2**n], run_time = FLASH_TIME), + ) + + flash_arcs(self.n) + +class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight): + CONFIG = { + "n" : 3, + } + +class InfiniteCircleScene(PiCreatureScene): + + def construct(self): + + morty = self.get_primary_pi_creature() + morty.highlight(MAROON_D).flip() + morty.color = MAROON_D + morty.scale(0.5).move_to(ORIGIN) + + arrow = Arrow(ORIGIN, 2.4 * RIGHT) + dot = Dot(color = BLUE).next_to(arrow) + ellipsis = TexMobject("\dots") + + infsum = VGroup() + infsum.add(ellipsis.copy()) + + for i in range(3): + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(dot.copy().next_to(infsum.submobjects[-1])) + + infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) + infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) + + infsum.next_to(morty,DOWN, buff = 1) + + self.wait() + self.play( + LaggedStart(FadeIn,infsum,lag_ratio = 0.2) + ) + self.wait() + + A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT + B = A + RIGHT + 1.3 * UP + 0.025 * LEFT + right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, + stroke_width = 8).apply_complex_function(np.exp) + right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) + right_tip_line = Arrow(B - UP, B, color = WHITE) + right_tip_line.add_tip() + right_tip = right_tip_line.get_tip() + right_tip.set_fill(color = YELLOW) + right_arc.add(right_tip) + + + C = B + 3.2 * UP + right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, + stroke_width = 8) + + ru_arc = right_arc.copy().rotate(angle = TAU/4) + ru_arc.remove(ru_arc.submobjects[-1]) + ru_arc.to_edge(UP+RIGHT, buff = 0.15) + + D = np.array([5.85, 3.85,0]) + E = np.array([-D[0],D[1],0]) + up_line = DashedLine(D, E, stroke_color = YELLOW, + stroke_width = 8) + + lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) + left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) + + left_arc = right_arc.copy().rotate(-TAU/4) + left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) + + right_arc.shift(0.2 * RIGHT) + right_line.shift(0.2 * RIGHT) + + self.play(FadeIn(right_arc)) + self.play(ShowCreation(right_line)) + self.play(FadeIn(ru_arc)) + self.play(ShowCreation(up_line)) + self.play(FadeIn(lu_arc)) + self.play(ShowCreation(left_line)) + self.play(FadeIn(left_arc)) + + + + self.wait() + +class Credits(Scene): + def construct(self): + credits = VGroup(*[ + VGroup(*map(TextMobject, pair)) + for pair in [ + ("Primary writer and animator:", "Ben Hambrecht"), + ("Editing, advising, narrating:", "Grant Sanderson"), + ("Based on a paper originally by:", "Johan Wästlund"), + ] + ]) + for credit, color in zip(credits, [MAROON_D, BLUE_D, WHITE]): + credit[1].highlight(color) + credit.arrange_submobjects(DOWN, buff = SMALL_BUFF) + + credits.arrange_submobjects(DOWN, buff = LARGE_BUFF) + + credits.center() + patreon_logo = PatreonLogo() + patreon_logo.to_edge(UP) + + for credit in credits: + self.play(LaggedStart(FadeIn, credit[0])) + self.play(FadeIn(credit[1])) + self.wait() + self.play( + credits.next_to, patreon_logo.get_bottom(), DOWN, MED_LARGE_BUFF, + DrawBorderThenFill(patreon_logo) + ) + self.wait() + +class Promotion(PiCreatureScene): + CONFIG = { + "seconds_to_blink" : 5, + } + def construct(self): + url = TextMobject("https://brilliant.org/3b1b/") + url.to_corner(UP+LEFT) + + rect = Rectangle(height = 9, width = 16) + rect.scale_to_fit_height(5.5) + rect.next_to(url, DOWN) + rect.to_edge(LEFT) + + self.play( + Write(url), + self.pi_creature.change, "raise_right_hand" + ) + self.play(ShowCreation(rect)) + self.wait(2) + self.change_mode("thinking") + self.wait() + self.look_at(url) + self.wait(10) + self.change_mode("happy") + self.wait(10) + self.change_mode("raise_right_hand") + self.wait(10) + + self.remove(rect) + self.play( + url.next_to, self.pi_creature, UP+LEFT + ) + url_rect = SurroundingRectangle(url) + self.play(ShowCreation(url_rect)) + self.play(FadeOut(url_rect)) + self.wait(3) + +class BaselPatreonThanks(PatreonEndScreen): + CONFIG = { + "specific_patrons" : [ + "CrypticSwarm ", + "Ali Yahya", + "Juan Benet", + "Markus Persson", + "Damion Kistler", + "Burt Humburg", + "Yu Jun", + "Dave Nicponski", + "Kaustuv DeBiswas", + "Joseph John Cox", + "Luc Ritchie", + "Achille Brighton", + "Rish Kundalia", + "Yana Chernobilsky", + "Shìmín Ku$\\overline{\\text{a}}$ng", + "Mathew Bramson", + "Jerry Ling", + "Mustafa Mahdi", + "Meshal Alshammari", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Robert Teed", + "Samantha D. Suplee", + "Mark Govea", + "John Haley", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Desmos ", + "Boris Veselinovich", + "Ryan Dahl", + "Ripta Pasay", + "Eric Lavault", + "Randall Hunt", + "Andrew Busey", + "Mads Elvheim", + "Tianyu Ge", + "Awoo", + "Dr. David G. Stork", + "Linh Tran", + "Jason Hise", + "Bernd Sing", + "James H. Park", + "Ankalagon ", + "Devin Scott", + "Mathias Jansson", + "David Clark", + "Ted Suzman", + "Eric Chow", + "Michael Gardner", + "David Kedmey", + "Jonathan Eppele", + "Clark Gaebel", + "Jordan Scales", + "Ryan Atallah", + "supershabam ", + "1stViewMaths ", + "Jacob Magnuson", + "Chloe Zhou", + "Ross Garber", + "Thomas Tarler", + "Isak Hietala", + "Egor Gumenuk", + "Waleed Hamied", + "Oliver Steele", + "Yaw Etse", + "David B", + "Delton Ding", + "James Thornton", + "Felix Tripier", + "Arthur Zey", + "George Chiesa", + "Norton Wang", + "Kevin Le", + "Alexander Feldman", + "David MacCumber", + "Jacob Kohl", + "Sergei ", + "Frank Secilia", + "Patrick Mézard", + "George John", + "Akash Kumar", + "Britt Selvitelle", + "Jonathan Wilson", + "Ignacio Freiberg", + "Zhilong Yang", + "Karl Niu", + "Dan Esposito", + "Michael Kunze", + "Giovanni Filippi", + "Eric Younge", + "Prasant Jagannath", + "Andrejs Olins", + "Cody Brocious", + ], + } + def construct(self): + next_video = TextMobject("$\\uparrow$ Next video $\\uparrow$") + next_video.to_edge(RIGHT, buff = 1.5) + next_video.shift(MED_SMALL_BUFF*UP) + next_video.highlight(YELLOW) + self.add_foreground_mobject(next_video) + PatreonEndScreen.construct(self) + +class Thumbnail(Scene): + CONFIG = { + "light_source_config" : { + "num_levels" : 250, + "radius" : 10.0, + "max_opacity_ambient" : 1.0, + "opacity_function" : inverse_quadratic(1,0.25,1) + } + } + def construct(self): + equation = TexMobject( + "1", "+", "{1\over 4}", "+", + "{1\over 9}","+", "{1\over 16}","+", + "{1\over 25}", "+", "\cdots" + ) + equation.scale(1.8) + equation.move_to(2*UP) + equation.set_stroke(RED, 1) + answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) + answer.scale(3) + answer.set_stroke(RED, 1) + # answer.next_to(equation, DOWN, buff = 1) + answer.move_to(1.25*DOWN) + #equation.move_to(2 * UP) + #answer = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) + #answer.next_to(equation, DOWN, buff = 1) + + lake_radius = 6 + lake_center = ORIGIN + + lake = Circle( + fill_color = BLUE, + fill_opacity = 0.15, + radius = lake_radius, + stroke_color = BLUE_D, + stroke_width = 3, + ) + lake.move_to(lake_center) + + for i in range(16): + theta = -TAU/4 + (i + 0.5) * TAU/16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + ls = LightSource(**self.light_source_config) + ls.move_source_to(pos) + lake.add(ls.ambient_light) + lake.add(ls.lighthouse) + + self.add(lake) + self.add(equation, answer) + self.wait() + + + + + + + + + + + + diff --git a/old_projects/uncertainty.py b/old_projects/uncertainty.py index 76b0c120..80ccc2c4 100644 --- a/old_projects/uncertainty.py +++ b/old_projects/uncertainty.py @@ -1842,7 +1842,7 @@ class IntroduceDopplerRadar(Scene): words = ["Original signal", "Echo"] for graph, word in zip([pulse_graph, echo_graph], words): arrow = Vector(DOWN) - arrow.next_to(graph.peak_point, UP, MED_SMALL_BUFF) + arrow.next_to(graph.peak_point, UP, SMALL_BUFF) arrow.match_color(graph) graph.arrow = arrow label = TextMobject(word) @@ -2400,7 +2400,6 @@ class RadarOperatorUncertainty(Scene): vector_gdw.move_to(plane_gdw) vector_gdw.shift(2*RIGHT) - self.add(randy, dish, bubble, plane_cloud, pulse) self.play(randy.change, "confused") self.wait(3) diff --git a/scene/scene.py b/scene/scene.py index bb20e964..5168db25 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -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 @@ -619,4 +618,3 @@ class EndSceneEarlyException(Exception): - diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 3e7369c1..265e210c 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -123,8 +123,11 @@ class PatreonEndScreen(PatreonThanks): "n_patron_columns" : 3, "max_patron_width" : 3, "run_time" : 20, + "randomize_order" : True, } def construct(self): + if self.randomize_order: + random.shuffle(self.specific_patrons) self.add_title() self.scroll_through_patrons() @@ -141,7 +144,6 @@ class PatreonEndScreen(PatreonThanks): pi.next_to(title, vect, buff = MED_LARGE_BUFF) self.add_foreground_mobjects(title, randy, morty) - def scroll_through_patrons(self): logo_box = Square(side_length = 2.5) logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) @@ -176,7 +178,7 @@ class PatreonEndScreen(PatreonThanks): aligned_edge = UP, ) columns.scale_to_fit_width(total_width - 1) - columns.next_to(black_rect, DOWN, LARGE_BUFF) + columns.next_to(black_rect, DOWN, 3*LARGE_BUFF) columns.to_edge(RIGHT) self.play( diff --git a/topics/geometry.py b/topics/geometry.py index 64b3bce1..d2e9363f 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -96,6 +96,57 @@ 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 + 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.arctan2(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): + # 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 = angle, **kwargs) + self.add_tip(at_start = True, at_end = True) + + class Circle(Arc): CONFIG = { "color" : RED, diff --git a/topics/light.py b/topics/light.py index 9ba8d75c..587d9c08 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 @@ -28,13 +29,12 @@ 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 -LIGHT_COLOR = YELLOW DEGREES = TAU/360 inverse_power_law = lambda maxint,scale,cutoff,exponent: \ @@ -42,222 +42,6 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \ inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) - -# 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 -class LightSource(VMobject): - # combines: - # a lighthouse - # an ambient light - # a spotlight - # and a shadow - CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), - "color": LIGHT_COLOR, - "num_levels": 10, - "radius": 5, - "screen": None, - "opacity_function": inverse_quadratic(1,2,1), - "max_opacity_ambient": AMBIENT_FULL, - "max_opacity_spotlight": SPOTLIGHT_FULL, - "camera": None - } - - def generate_points(self): - - self.add(self.source_point) - - self.lighthouse = Lighthouse() - self.ambient_light = AmbientLight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - opacity_function = self.opacity_function, - 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 - ) - else: - self.spotlight = Spotlight() - - self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK) - self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0) - self.ambient_light.move_source_to(self.get_source_point()) - - if self.has_screen(): - self.spotlight.move_source_to(self.get_source_point()) - self.update_shadow() - - self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow) - - def has_screen(self): - return (self.screen != None) - - def dim_ambient(self): - self.set_max_opacity_ambient(AMBIENT_DIMMED) - - def set_max_opacity_ambient(self,new_opacity): - self.max_opacity_ambient = new_opacity - self.ambient_light.dimming(new_opacity) - - def dim_spotlight(self): - self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED) - - def set_max_opacity_spotlight(self,new_opacity): - 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_screen(self, new_screen): - 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.spotlight.move_source_to(self.get_source_point()) - - # Note: This line will make spotlight show up at the end - # of the submojects list, which can make it show up on - # top of the shadow. To make it show up in the - # same spot, you could try the following line, - # where "index" is what I defined above: - self.submobjects.insert(index, self.spotlight) - #self.add(self.spotlight) - - # in any case - self.screen = new_screen - - - - - def move_source_to(self,point): - apoint = np.array(point) - v = apoint - self.get_source_point() - # Note: As discussed, things stand to behave better if source - # point is a submobject, so that it automatically interpolates - # during an animation, and other updates can be defined wrt - # that source point's location - self.source_point.set_location(apoint) - #self.lighthouse.next_to(apoint,DOWN,buff = 0) - #self.ambient_light.move_source_to(apoint) - self.lighthouse.shift(v) - #self.ambient_light.shift(v) - self.ambient_light.move_source_to(apoint) - if self.has_screen(): - self.spotlight.move_source_to(apoint) - self.update() - return self - - def set_radius(self,new_radius): - self.radius = new_radius - self.ambient_light.radius = new_radius - self.spotlight.radius = new_radius - - def update(self): - self.spotlight.update_sectors() - self.update_shadow() - - def get_source_point(self): - return self.source_point.get_location() - - def update_shadow(self): - - point = self.get_source_point() - projected_screen_points = [] - if not self.has_screen(): - return - for point in self.screen.get_anchors(): - projected_screen_points.append(self.spotlight.project(point)) - - - projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction()) - - projected_point_cloud_3d = np.append( - projected_screen_points, - 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 - - 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 - hull = [] - - # we also need the projected source point - source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2] - - index = 0 - for point in point_cloud_2d[hull_2d.vertices]: - if np.all(point - source_point_2d < 1.0e-6): - source_index = index - 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) - hull_mobject.apply_matrix(rotation_matrix) - - - anchors = hull_mobject.get_anchors() - - # add two control points for the outer cone - - - ray1 = anchors[index - 1] - projected_source - ray1 = ray1/np.linalg.norm(ray1) * 100 - ray2 = anchors[index] - projected_source - ray2 = ray2/np.linalg.norm(ray2) * 100 - outpoint1 = anchors[index - 1] + ray1 - outpoint2 = anchors[index] + ray2 - - new_anchors = anchors[:index] - new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0) - new_anchors = np.append(new_anchors,anchors[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 - - - class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -267,9 +51,9 @@ class SwitchOn(LaggedStart): def __init__(self, light, **kwargs): if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): raise Exception("Only AmbientLights and Spotlights can be switched on") - LaggedStart.__init__(self, - FadeIn, light, **kwargs) - + LaggedStart.__init__( + self, FadeIn, light, **kwargs + ) class SwitchOff(LaggedStart): CONFIG = { @@ -285,19 +69,17 @@ class SwitchOff(LaggedStart): FadeOut, light, **kwargs) light.submobjects = light.submobjects[::-1] - - - 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): self.next_to(point, DOWN, buff = 0) - class AmbientLight(VMobject): # Parameters are: @@ -313,7 +95,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 } @@ -373,31 +155,16 @@ class AmbientLight(VMobject): 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" : LIGHT_COLOR, + "color" : GREEN, # LIGHT_COLOR, "max_opacity" : 1.0, "num_levels" : 10, - "radius" : 5.0, + "radius" : 10.0, "screen" : None, - "camera": None + "camera_mob": None } def projection_direction(self): @@ -405,24 +172,22 @@ 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() 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) @@ -438,13 +203,7 @@ class Spotlight(VMobject): new_sector = self.new_sector(r,dr,lower_angle,upper_angle) self.add(new_sector) - def new_sector(self,r,dr,lower_angle,upper_angle): - # Note: I'm not looking _too_ closely at the implementation - # of these updates based on viewing angles and such. It seems to - # behave as intended, but let me know if you'd like more thorough - # scrutiny - alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( inner_radius = r, @@ -480,7 +239,6 @@ class Spotlight(VMobject): else: return -absolute_angle - def viewing_angles(self,screen): screen_points = screen.get_anchors() @@ -494,6 +252,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): @@ -505,7 +265,6 @@ class Spotlight(VMobject): return lower_ray, upper_ray - def opening_angle(self): l,u = self.viewing_angles(self.screen) return u - l @@ -525,21 +284,20 @@ class Spotlight(VMobject): self.update_sectors() return self - def update_sectors(self): if self.screen == None: return - for submob in self.submobject_family(): + for submob in self.submobjects: if type(submob) == AnnularSector: lower_angle, upper_angle = self.viewing_angles(self.screen) #dr = submob.outer_radius - submob.inner_radius dr = self.radius / self.num_levels - new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle) - submob.points = new_submob.points - submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) - # print "new opacity:", self.opacity_function(submob.outer_radius) - - + new_submob = self.new_sector( + submob.inner_radius, dr, lower_angle, upper_angle + ) + # submob.points = new_submob.points + # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) + Transform(submob, new_submob).update(1) def dimming(self,new_alpha): old_alpha = self.max_opacity @@ -570,18 +328,290 @@ class Spotlight(VMobject): alpha = self.opacity_function(r) submob.set_fill(opacity = alpha) +# Warning: This class is likely quite buggy. +class LightSource(VMobject): + # combines: + # a lighthouse + # an ambient light + # a spotlight + # and a shadow + CONFIG = { + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "color": LIGHT_COLOR, + "num_levels": 10, + "radius": 10.0, + "screen": None, + "opacity_function": inverse_quadratic(1,2,1), + "max_opacity_ambient": AMBIENT_FULL, + "max_opacity_spotlight": SPOTLIGHT_FULL, + "camera_mob": None + } -# 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. + def generate_points(self): + + self.add(self.source_point) + + self.lighthouse = Lighthouse() + self.ambient_light = AmbientLight( + source_point = VectorizedPoint(location = self.get_source_point()), + color = self.color, + num_levels = self.num_levels, + radius = self.radius, + opacity_function = self.opacity_function, + 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_mob = self.camera_mob + ) + else: + self.spotlight = Spotlight() + + self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK) + self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0) + self.ambient_light.move_source_to(self.get_source_point()) + + if self.has_screen(): + self.spotlight.move_source_to(self.get_source_point()) + self.update_shadow() + + self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow) + + def has_screen(self): + 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) + + def set_max_opacity_ambient(self,new_opacity): + self.max_opacity_ambient = new_opacity + self.ambient_light.dimming(new_opacity) + + def dim_spotlight(self): + self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED) + + def set_max_opacity_spotlight(self,new_opacity): + self.max_opacity_spotlight = new_opacity + self.spotlight.dimming(new_opacity) + + 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): + if self.has_screen(): + self.spotlight.screen = new_screen + else: + # Note: See below + index = self.submobjects.index(self.spotlight) + camera_mob = self.spotlight.camera_mob + 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_mob = self.camera_mob, + opacity_function = self.opacity_function, + max_opacity = self.max_opacity_spotlight, + ) + self.spotlight.move_source_to(self.get_source_point()) + + # Note: This line will make spotlight show up at the end + # of the submojects list, which can make it show up on + # top of the shadow. To make it show up in the + # same spot, you could try the following line, + # where "index" is what I defined above: + self.submobjects.insert(index, self.spotlight) + #self.add(self.spotlight) + + # in any case + self.screen = new_screen + + def move_source_to(self,point): + apoint = np.array(point) + v = apoint - self.get_source_point() + # Note: As discussed, things stand to behave better if source + # point is a submobject, so that it automatically interpolates + # during an animation, and other updates can be defined wrt + # that source point's location + self.source_point.set_location(apoint) + #self.lighthouse.next_to(apoint,DOWN,buff = 0) + #self.ambient_light.move_source_to(apoint) + self.lighthouse.shift(v) + #self.ambient_light.shift(v) + self.ambient_light.move_source_to(apoint) + if self.has_screen(): + 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 + self.ambient_light.radius = new_radius + 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): + 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( + 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() + projected_screen_points = [] + if not self.has_screen(): + return + for point in self.screen.get_anchors(): + projected_screen_points.append(self.spotlight.project(point)) + + + projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction()) + + projected_point_cloud_3d = np.append( + projected_screen_points, + np.reshape(projected_source,(1,3)), + axis = 0 + ) + 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 + hull = [] + + # we also need the projected source point + source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2] + + index = 0 + for point in point_cloud_2d[hull_2d.vertices]: + 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 + + + hull_mobject = VMobject() + hull_mobject.set_points_as_corners(hull) + hull_mobject.apply_matrix(rotation_matrix) + + + anchors = hull_mobject.get_anchors() + + # add two control points for the outer cone + if np.size(anchors) == 0: + self.shadow.points = [] + return + + 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 + outpoint2 = anchors[source_index] + ray2 + + new_anchors = anchors[:source_index] + new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0) + new_anchors = np.append(new_anchors,anchors[source_index:],axis = 0) + self.shadow.set_points_as_corners(new_anchors) + + # shift it closer to the camera so it is in front of the spotlight + self.shadow.mark_paths_closed = True class ScreenTracker(ContinualAnimation): + def __init__(self, light_source, **kwargs): + self.light_source = light_source + dummy_mob = Mobject() + ContinualAnimation.__init__(self, dummy_mob, **kwargs) def update_mobject(self, dt): - self.mobject.update() + self.light_source.update() + + + + + + + + + + + + + + + + diff --git a/topics/number_line.py b/topics/number_line.py index 9d02286c..cd8da287 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 @@ -241,7 +244,7 @@ class Axes(VGroup): 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 = { 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) -