diff --git a/active_projects/basel.py b/active_projects/basel.py index b6263cd0..bfe9cfac 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -30,9 +30,9 @@ from mobject.vectorized_mobject import * ## To watch one of these scenes, run the following: ## python extract_scene.py -p file_name -inverse_power_law = lambda maxint,cutoff,exponent: \ - (lambda r: maxint * (cutoff/(r+cutoff))**exponent) -inverse_quadratic = lambda maxint,cutoff: inverse_power_law(maxint,cutoff,2) +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) LIGHT_COLOR = YELLOW INDICATOR_RADIUS = 0.7 @@ -44,12 +44,13 @@ FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 SWITCH_ON_RUN_TIME = 2.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -LIGHT_CONE_NUM_SECTORS = 30 +NUM_LEVELS = 30 NUM_CONES = 50 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 DIM_OPACITY = 0.2 +DEGREES = TAU/360 def show_line_length(line): v = line.points[1] - line.points[0] @@ -67,7 +68,7 @@ class AngleUpdater(ContinualAnimation): def update_mobject(self, dt): # angle arc new_arc = self.angle_arc.copy().set_bound_angles( - start = self.lc.start_angle, + start = self.lc.start_angle(), stop = self.lc.stop_angle() ) new_arc.generate_points() @@ -77,55 +78,167 @@ class AngleUpdater(ContinualAnimation): +LIGHT_COLOR = YELLOW +DEGREES = 360/TAU +SWITCH_ON_RUN_TIME = 1.5 -class LightScreen(VMobject): - # A light screen is composed of a VMobject and a light cone. - # It has knowledge of the light source point. - # As the screen changes, it calculates the viewing angle from - # the source and updates the light cone. - def __init__(self, light_source = ORIGIN, screen = None, light_cone = None): - Mobject.__init__(self) - self.light_cone = light_cone - self.light_source = light_source - self.screen = screen - self.light_cone.move_source_to(self.light_source) - self.shadow = VMobject(fill_color = BLACK, stroke_width = 0, fill_opacity = 1.0) - self.add(self.light_cone, self.screen, self.shadow) - self.update_shadow(self.shadow) +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * an opacity function + # * a light color + # * a max opacity + # * a radius (larger than the opacity's dropoff length) + # * the number of subdivisions (levels, annuli) + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0 + } + + def generate_points(self): + + # in theory, this method is only called once, right? + # so removing submobs shd not be necessary + for submob in self.submobjects: + self.remove(submob) + + # create annuli + self.radius = float(self.radius) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_arc_center_to(self.source_point) + self.add(annulus) + + + + def move_source_to(self,point): + self.shift(point - self.source_point) + self.source_point = np.array(point) + # for submob in self.submobjects: + # if type(submob) == Annulus: + # submob.shift(self.source_point - submob.get_center()) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + +class Spotlight(VMobject): + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0, + "screen" : None, + "shadow" : VMobject(fill_color = BLACK, stroke_width = 0, fill_opacity = 1.0) + } + + def track_screen(self): + self.generate_points() + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + self.radius = float(self.radius) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + annular_sector.move_arc_center_to(self.source_point) + self.add(annular_sector) + + self.update_shadow(point = self.source_point) + self.add(self.shadow) - def update_light_cone(self,lc): - lower_angle, upper_angle = self.viewing_angles() - #print lower_angle, upper_angle - self.light_cone.update_opening(start_angle = lower_angle, - stop_angle = upper_angle) - return self def viewing_angle_of_point(self,point): - distance_vector = point - self.light_source + distance_vector = point - self.source_point angle = angle_of_vector(distance_vector) return angle - def viewing_angles(self): - all_points = [] + def viewing_angles(self,screen): - for submob in self.family_members_with_points(): - all_points.extend(submob.get_anchors()) - - viewing_angles = np.array(map(self.viewing_angle_of_point, self.screen.get_anchors())) - - if len(viewing_angles) == 0: - lower_angle = upper_angle = 0 - else: + viewing_angles = np.array(map(self.viewing_angle_of_point, + screen.get_anchors())) + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: lower_angle = np.min(viewing_angles) upper_angle = np.max(viewing_angles) - + return lower_angle, upper_angle - def update_shadow(self,sh): + def opening_angle(self): + l,u = self.viewing_angles(self.screen) + return u - l + + def start_angle(self): + l,u = self.viewing_angles(self.screen) + return l + + def stop_angle(self): + l,u = self.viewing_angles(self.screen) + return u + + def move_source_to(self,point): + print "moving source" + self.source_point = np.array(point) + self.recalculate_sectors(point = point, screen = self.screen) + self.update_shadow(point = point) + + + def recalculate_sectors(self, point = ORIGIN, screen = None): + if screen == None: + return + for submob in self.submobject_family(): + if type(submob) == AnnularSector: + lower_angle, upper_angle = self.viewing_angles(screen) + new_submob = AnnularSector( + start_angle = lower_angle, + angle = upper_angle - lower_angle, + inner_radius = submob.inner_radius, + outer_radius = submob.outer_radius + ) + new_submob.move_arc_center_to(point) + submob.points = new_submob.points + + def update_shadow(self,point = ORIGIN): + use_point = point #self.source_point self.shadow.points = self.screen.points - ray1 = self.screen.points[0] - self.light_source - ray2 = self.screen.points[-1] - self.light_source + ray1 = self.screen.points[0] - use_point + ray2 = self.screen.points[-1] - use_point ray1 = ray1/np.linalg.norm(ray1) * 100 ray1 = rotate_vector(ray1,-TAU/16) ray2 = ray2/np.linalg.norm(ray2) * 100 @@ -135,121 +248,36 @@ class LightScreen(VMobject): self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]]) self.shadow.mark_paths_closed = True - def move_source_to(self,new_point): - self.light_source = new_point - #self.update_light_cone(self.light_cone) + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + def change_opacity_function(self,new_f): + self.radius = 120 + self.opacity_function = new_f + dr = self.radius/self.num_levels -class LightCone(VGroup): - CONFIG = { - "start_angle": 0, - "angle" : TAU/8, - "radius" : 10, - "brightness" : 1, - "opacity_function" : lambda r : 1./max(r, 0.01), - "num_sectors" : 10, - "color": LIGHT_COLOR, - } - - def generate_points(self): - radii = np.linspace(0, self.radius, self.num_sectors+1) - sectors = [ - AnnularSector( - start_angle = self.start_angle, - angle = self.angle, - inner_radius = r1, - outer_radius = r2, - stroke_width = 0, - stroke_color = self.color, - fill_color = self.color, - fill_opacity = self.brightness * self.opacity_function(r1), - ) - for r1, r2 in zip(radii, radii[1:]) - ] - self.add(*sectors) - - def get_source_point(self): - if len(self.submobjects) == 0: - return None - source = self.submobjects[0].get_arc_center() - return source - - def move_source_to(self,point): - if len(self.submobjects) == 0: - return - source = self.submobjects[0].get_arc_center() - self.shift(point - source) - self.generate_points() - - def update_opening(self, start_angle, stop_angle): - self.start_angle = start_angle - self.angle = stop_angle - start_angle - source_point = self.get_source_point() + sectors = [] for submob in self.submobjects: if type(submob) == AnnularSector: + sectors.append(submob) - submob.start_angle = self.start_angle - submob.angle = self.angle - submob.generate_points() - submob.shift(source_point - submob.get_arc_center()) + print self.num_levels, len(sectors) + for (r,submob) in zip(np.arange(0,self.radius,dr),sectors): + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + alpha = self.opacity_function(r) + submob.set_fill(opacity = alpha) - def set_brightness(self,new_brightness): - self.brightness = new_brightness - radii = np.linspace(0, self.radius, self.num_sectors+1) - for (r1,sector) in zip(radii,self.submobjects): - sector.set_fill(opacity = self.brightness * self.opacity_function(r1)) - - def stop_angle(self): - return self.start_angle + self.angle - - - - - - - -class Candle(VGroup): - CONFIG = { - "radius" : 5, - "brightness" : 1.0, - "opacity_function" : lambda r : 1./max(r, 0.01), - "num_annuli" : 10, - "color": LIGHT_COLOR, - } - - def generate_points(self): - radii = np.linspace(0, self.radius, self.num_annuli+1) - annuli = [ - Annulus( - inner_radius = r1, - outer_radius = r2, - stroke_width = 0, - stroke_color = self.color, - fill_color = self.color, - fill_opacity = self.brightness * self.opacity_function(r1), - ) - for r1, r2 in zip(radii, radii[1:]) - ] - self.add(*annuli) - - def get_source_point(self): - if len(self.submobjects) == 0: - return None - source = self.submobjects[0].get_center() - return source - - def move_source_to(self,point): - if len(self.submobjects) == 0: - return - source = self.submobjects[0].get_center() - self.shift(point - source) - - def set_brightness(self,new_brightness): - self.brightness = new_brightness - radii = np.linspace(0, self.radius, self.num_annuli+1) - for (r1,annulus) in zip(radii,self.submobjects): - annulus.set_fill(opacity = self.brightness * self.opacity_function(r1)) @@ -261,11 +289,42 @@ class SwitchOn(LaggedStart): } def __init__(self, light, **kwargs): - if not isinstance(light,LightCone) and not isinstance(light,Candle): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): raise Exception("Only LightCones and Candles can be switched on") LaggedStart.__init__(self, FadeIn, light, **kwargs) + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + + + + + +class ScreenTracker(ContinualAnimation): + def __init__(self, mobject, **kwargs): + ContinualAnimation.__init__(self, mobject, **kwargs) + + def update_mobject(self, dt): + self.mobject.recalculate_sectors( + point = self.mobject.source_point, + screen = self.mobject.screen) + self.mobject.update_shadow(self.mobject.source_point) + + + class LightHouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -606,7 +665,7 @@ class FirstLightHouseScene(PiCreatureScene): lighthouses = [] lighthouse_pos = [] - light_cones = [] + ambient_lights = [] euler_sum_above = TexMobject("1", "+", "{1\over 4}", @@ -625,15 +684,15 @@ class FirstLightHouseScene(PiCreatureScene): for i in range(1,NUM_CONES+1): lighthouse = LightHouse() point = self.number_line.number_to_point(i) - light_cone = Candle( - opacity_function = inverse_quadratic(1,1), - num_annuli = LIGHT_CONE_NUM_SECTORS, - radius = 12) + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 12.0) - light_cone.move_source_to(point) + ambient_light.move_source_to(point) lighthouse.next_to(point,DOWN,0) lighthouses.append(lighthouse) - light_cones.append(light_cone) + ambient_lights.append(ambient_light) lighthouse_pos.append(point) @@ -649,13 +708,13 @@ class FirstLightHouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator - for (i,lc) in zip(range(NUM_VISIBLE_CONES),light_cones[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES),ambient_lights[:NUM_VISIBLE_CONES]): + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/ambient_light.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(lc), + SwitchOn(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, @@ -675,13 +734,13 @@ class FirstLightHouseScene(PiCreatureScene): ) # quickly switch on off-screen light cones and increment indicator - for (i,lc) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_cones[NUM_VISIBLE_CONES:NUM_CONES]): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),ambient_lights[NUM_VISIBLE_CONES:NUM_CONES]): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/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) self.play( - SwitchOn(lc, run_time = FAST_SWITCH_ON_RUN_TIME), + SwitchOn(ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), ChangeDecimalToValue(light_indicator.reading,intensities[i], rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) @@ -726,72 +785,58 @@ class SingleLightHouseScene(PiCreatureScene): observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] lighthouse = LightHouse() - candle = Candle( - opacity_function = inverse_quadratic(1,1), - num_annuli = LIGHT_CONE_NUM_SECTORS, + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, radius = 10, brightness = 1, ) lighthouse.scale(2).next_to(source_point, DOWN, buff = 0) - candle.move_to(source_point) + ambient_light.move_to(source_point) morty = self.get_primary_pi_creature() morty.scale(0.5) morty.move_to(observer_point) self.add(lighthouse) self.play( - SwitchOn(candle) + SwitchOn(ambient_light) ) - light_cone = LightCone( - opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, + + screen = Line([0,-1,0],[0,1,0]) + screen.rotate(-TAU/6) + screen.next_to(morty, LEFT, buff = 1) + + spotlight = Spotlight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, radius = 10, brightness = 5, + screen = screen ) - light_cone.move_source_to(source_point) - screen = Line([0,-1,0],[0,1,0]) - show_line_length(screen) - - screen.rotate_in_place(-TAU/6) - show_line_length(screen) - - screen.next_to(morty, LEFT, buff = 1) - - light_screen = LightScreen(light_source = source_point, - screen = screen, light_cone = light_cone) - light_screen.screen.color = WHITE - light_screen.screen.fill_opacity = 1 - light_screen.update_light_cone(light_cone) - # self.play( - # FadeIn(light_screen, run_time = 2), - # # dim the light that misses the screen - # ApplyMethod(candle.set_brightness,0.3), - # ApplyMethod(light_screen.update_shadow,light_screen.shadow), - # FadeIn(light_cone), - # ) + spotlight.move_source_to(source_point) - lc_updater = lambda lc: light_screen.update_light_cone(lc) - sh_updater = lambda sh: light_screen.update_shadow(sh) + self.play( + ApplyMethod(ambient_light.dimming,0.2), + FadeIn(spotlight)) + self.add(spotlight.shadow) - ca1 = ContinualUpdateFromFunc(light_screen.light_cone, - lc_updater) - ca15 = ContinualUpdateFromFunc(light_screen, - lc_updater) - ca2 = ContinualUpdateFromFunc(light_screen.shadow, - sh_updater) - - self.add(ca1, ca15, ca2) self.add_foreground_mobject(morty) - pointing_screen_at_source = ApplyMethod(screen.rotate,TAU/6) - #self.play(pointing_screen_at_source) - #self.wait() + screen_tracker = ScreenTracker(spotlight) + # activate ONLY when spotlight is moving! - arc_angle = light_cone.angle + self.add(screen_tracker) + pointing_screen_at_source = ApplyMethod(spotlight.screen.rotate,TAU/6) + self.play(pointing_screen_at_source) + + + + + arc_angle = spotlight.opening_angle() # draw arc arrows to show the opening angle - angle_arc = Arc(radius = 5, start_angle = light_cone.start_angle, - angle = light_cone.angle, tip_length = ARC_TIP_LENGTH) + angle_arc = Arc(radius = 5, start_angle = spotlight.start_angle(), + angle = spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) #angle_arc.add_tip(at_start = True, at_end = True) angle_arc.move_arc_center_to(source_point) @@ -803,31 +848,28 @@ class SingleLightHouseScene(PiCreatureScene): angle_indicator.next_to(angle_arc,RIGHT) self.add_foreground_mobject(angle_indicator) - angle_update_func = lambda x: light_cone.angle/TAU * 360 + angle_update_func = lambda x: spotlight.opening_angle()/TAU * 360 ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func) self.add(ca3) - #ca4 = ContinualUpdateFromFunc(angle_arc,update_angle_arc) - ca4 = AngleUpdater(angle_arc, light_screen.light_cone) + ca4 = AngleUpdater(angle_arc, spotlight) self.add(ca4) - rotating_screen = ApplyMethod(light_screen.screen.rotate, + rotating_screen = ApplyMethod(spotlight.screen.rotate, TAU/8, run_time=1.5) #self.wait(2) - rotating_screen_2 = ApplyMethod(light_screen.screen.rotate, + rotating_screen_2 = ApplyMethod(spotlight.screen.rotate, -TAU/4, run_time=3, rate_func = there_and_back) #self.wait(2) - rotating_screen_3 = ApplyMethod(light_screen.screen.rotate, + rotating_screen_3 = ApplyMethod(spotlight.screen.rotate, TAU/8, run_time=1.5) - #self.play(rotating_screen) - #self.play(rotating_screen_2) - #self.play(rotating_screen_3) + self.play(rotating_screen) + self.play(rotating_screen_2) + self.play(rotating_screen_3) - #rotating_screen_back = ApplyMethod(light_screen.screen.rotate_in_place, -TAU/6) #, run_time=3, rate_func = wiggle) - #self.play(rotating_screen_back) - self.wait() + #self.wait() @@ -836,10 +878,14 @@ class SingleLightHouseScene(PiCreatureScene): globe = Circle(radius = 3) globe.move_to([2,0,0]) sun_position = [-100,0,0] + #self.add(screen_tracker) + print "tuet" + self.remove(screen_tracker) + new_opacity_function = lambda r: 0.5 self.play( - #ApplyMethod(lighthouse.move_to,sun_position), - #ApplyMethod(candle.move_to,sun_position), - ApplyMethod(light_screen.move_source_to,sun_position), + ApplyMethod(lighthouse.move_to,sun_position), + ApplyMethod(ambient_light.move_to,sun_position), + ApplyMethod(spotlight.move_source_to,sun_position), #FadeOut(angle_arc), #FadeOut(angle_indicator), #FadeIn(globe), @@ -847,6 +893,148 @@ class SingleLightHouseScene(PiCreatureScene): #ApplyMethod(morty.move_to,[1,0,0]) ) + self.play( + ApplyMethod(spotlight.change_opacity_function,new_opacity_function)) + + self.add(screen_tracker) + + + + + +class EarthScene(Scene): + + def construct(self): + + DEGREES = TAU/360 + radius = 2.5 + center_x = 3 + theta0 = 80 * DEGREES + dtheta = 10 * DEGREES + theta1 = theta0 + dtheta + screen = Line([center_x - radius * np.cos(theta0),radius * np.sin(theta0),0], + [center_x - radius * np.cos(theta1),radius * np.sin(theta1),0]) + screen.set_stroke(color = RED, width = 5) + + globe = Circle(radius = radius, stroke_width = 0) + globe.move_to([center_x,0,0]) + foreground_globe = globe.copy() # above the shadow + foreground_globe.radius -= 0.2 + foreground_globe.set_stroke(color = WHITE, width = 1) + self.add_foreground_mobject(foreground_globe) + globe.add(screen) + + morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) + self.add_foreground_mobject(morty) + + sun = Spotlight( + opacity_function = lambda r : 0.5, + num_levels = NUM_LEVELS, + radius = 100, + brightness = 5, + screen = screen + ) + + sun.move_source_to([-90,0,0]) + self.add(globe,sun,screen) + + screen_tracker = ScreenTracker(sun) + + self.add(screen_tracker) + self.play( + ApplyMethod(globe.rotate,theta0 + dtheta/2), + ApplyMethod(morty.move_to,[1.5,0,0]) + ) + + + +class ScreenShapingScene(Scene): + + def construct(self): + + DEGREES = TAU / 360 + + screen = Line([2,-1,0],[2,1,0], path_arc = 0, num_arc_anchors = 10) + + source = Spotlight( + opacity_function = inverse_quadratic(1,5,1), + num_levels = NUM_LEVELS, + radius = 10, + brightness = 5, + screen = screen + ) + + source.move_source_to([-5,0,0]) + + lighthouse = LightHouse() + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,1,1), + num_levels = NUM_LEVELS, + radius = 10, + brightness = 1, + ) + lighthouse.scale(2).next_to(source.source_point,DOWN,buff=0) + ambient_light.move_source_to(source.source_point) + + self.add(lighthouse, ambient_light,source,screen) + + morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) + self.add_foreground_mobject(morty) + + self.wait() + + screen_tracker = ScreenTracker(source) + + self.add(screen_tracker) + + self.play( + ApplyMethod(screen.set_path_arc, 45 * DEGREES), + ) + + self.play( + ApplyMethod(screen.set_path_arc, -90 * DEGREES), + ) + + self.play( + ApplyMethod(screen.set_path_arc, 0), + ) + + + # in preparation for the slanting, create a rectangle that show the brightness + + rect_origin = Rectangle(width = 0, height = 2).move_to(screen.get_center()) + self.add_foreground_mobject(rect_origin) + + brightness_rect = Rectangle(width = 2, height = 2, fill_color = YELLOW, fill_opacity = 0.5) + + brightness_rect.move_to([3,3,0]) + + self.play( + ReplacementTransform(rect_origin,brightness_rect) + ) + + lower_screen_point, upper_screen_point = screen.get_start_and_end() + + lower_slanted_screen_point = interpolate( + lower_screen_point, source.source_point, 0.2 + ) + upper_slanted_screen_point = interpolate( + upper_screen_point, source.source_point, -0.2 + ) + + slanted_brightness_rect = brightness_rect.copy() + slanted_brightness_rect.width *= 2 + slanted_brightness_rect.generate_points() + slanted_brightness_rect.set_fill(opacity = 0.25) + + slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, path_arc = 0, num_arc_anchors = 10) + + self.play( + ReplacementTransform(screen,slanted_screen), + #ApplyMethod(brightness_rect.stretch,2,0), # factor = 2, dim = 0 (x) + ReplacementTransform(brightness_rect,slanted_brightness_rect), + ApplyMethod(brightness_rect.set_fill,{"opacity" : 0.25}), + ) @@ -858,17 +1046,6 @@ class SingleLightHouseScene(PiCreatureScene): - - - - - - - - - - -