From 66e001d77a636415f27c4f29f76ae0a8f230bc25 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 23 Jan 2018 10:53:24 -0800 Subject: [PATCH] Added units to DecimalNumer --- active_projects/basel.py | 183 ++++++++++++++++++++++++++++++--------- topics/geometry.py | 20 ++++- topics/numerals.py | 22 ++++- 3 files changed, 181 insertions(+), 44 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 298b34aa..909fd1cc 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -42,11 +42,37 @@ 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 = 2.0 +SWITCH_ON_RUN_TIME = 2.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -LIGHT_CONE_NUM_SECTORS = 10 -NUM_CONES = 10 -NUM_VISIBLE_CONES = 6 +LIGHT_CONE_NUM_SECTORS = 30 +NUM_CONES = 50 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 + + +def show_line_length(line): + v = line.points[1] - line.points[0] + print v[0]**2 + v[1]**2 + + +class AngleUpdater(ContinualAnimation): + def __init__(self, angle_arc, lc, **kwargs): + self.angle_arc = angle_arc + self.source_point = angle_arc.get_arc_center() + self.lc = lc + #self.angle_decimal = angle_decimal + ContinualAnimation.__init__(self, self.angle_arc, **kwargs) + + def update_mobject(self, dt): + # angle arc + new_arc = self.angle_arc.copy().set_bound_angles( + start = self.lc.start_angle, + stop = self.lc.stop_angle() + ) + new_arc.generate_points() + new_arc.move_arc_center_to(self.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) @@ -99,14 +125,13 @@ class LightScreen(VMobject): ray1 = self.screen.points[0] - self.light_source ray2 = self.screen.points[-1] - self.light_source ray1 = ray1/np.linalg.norm(ray1) * 100 - ray1 = rotate_vector(ray1,TAU/16) + ray1 = rotate_vector(ray1,-TAU/16) ray2 = ray2/np.linalg.norm(ray2) * 100 - ray2 = rotate_vector(ray2,-TAU/16) + ray2 = rotate_vector(ray2,TAU/16) outpoint1 = self.screen.points[0] + ray1 outpoint2 = self.screen.points[-1] + ray2 self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]]) self.shadow.mark_paths_closed = True - class LightCone(VGroup): @@ -114,6 +139,7 @@ class LightCone(VGroup): "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, @@ -130,7 +156,7 @@ class LightCone(VGroup): stroke_width = 0, stroke_color = self.color, fill_color = self.color, - fill_opacity = self.opacity_function(r1), + fill_opacity = self.brightness * self.opacity_function(r1), ) for r1, r2 in zip(radii, radii[1:]) ] @@ -160,6 +186,15 @@ class LightCone(VGroup): submob.generate_points() submob.shift(source_point - submob.get_arc_center()) + 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 + @@ -169,13 +204,14 @@ class LightCone(VGroup): class Candle(VGroup): CONFIG = { "radius" : 5, + "brightness" : 1.0, "opacity_function" : lambda r : 1./max(r, 0.01), - "num_sectors" : 10, + "num_annuli" : 10, "color": LIGHT_COLOR, } def generate_points(self): - radii = np.linspace(0, self.radius, self.num_sectors+1) + radii = np.linspace(0, self.radius, self.num_annuli+1) annuli = [ Annulus( inner_radius = r1, @@ -183,7 +219,7 @@ class Candle(VGroup): stroke_width = 0, stroke_color = self.color, fill_color = self.color, - fill_opacity = self.opacity_function(r1), + fill_opacity = self.brightness * self.opacity_function(r1), ) for r1, r2 in zip(radii, radii[1:]) ] @@ -201,6 +237,14 @@ class Candle(VGroup): 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)) + + + class SwitchOn(LaggedStart): CONFIG = { @@ -558,8 +602,7 @@ class FirstLightHouseScene(PiCreatureScene): euler_sum_above = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25 }", "+", "{1\over 36}") - euler_sum_above.fill_color = YELLOW + "+", "{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 @@ -568,7 +611,7 @@ class FirstLightHouseScene(PiCreatureScene): 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): @@ -576,8 +619,8 @@ class FirstLightHouseScene(PiCreatureScene): point = self.number_line.number_to_point(i) light_cone = Candle( opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, - radius = 10) + num_annuli = LIGHT_CONE_NUM_SECTORS, + radius = 12) light_cone.move_source_to(point) lighthouse.next_to(point,DOWN,0) @@ -599,10 +642,9 @@ 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]): - print i - indicator_start_time = 0.5 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) self.play( SwitchOn(lc), @@ -624,11 +666,8 @@ class FirstLightHouseScene(PiCreatureScene): light_indicator_copy.shift,[0, new_y - old_y,0] ) - print "fast now" - # 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]): - print i indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/lc.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) @@ -644,10 +683,17 @@ class FirstLightHouseScene(PiCreatureScene): # 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(indicator.reading), - FadeIn(limit_reading) -# Transform(light_indicator.reading,limit_reading) + FadeOut(light_indicator.reading), + FadeIn(limit_reading), + FadeIn(equals_sign), ) @@ -674,36 +720,46 @@ class SingleLightHouseScene(PiCreatureScene): lighthouse = LightHouse() candle = Candle( opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, - radius = 10 + num_annuli = LIGHT_CONE_NUM_SECTORS, + radius = 10, + brightness = 1, ) lighthouse.scale(2).next_to(source_point, DOWN, buff = 0) candle.move_to(source_point) morty = self.get_primary_pi_creature() morty.scale(0.5) morty.move_to(observer_point) - self.add(lighthouse, candle) - self.wait() + self.add(lighthouse) self.play( SwitchOn(candle) ) - light_cone = LightCone() + light_cone = LightCone( + opacity_function = inverse_quadratic(1,1), + num_sectors = LIGHT_CONE_NUM_SECTORS, + radius = 10, + brightness = 5, + ) light_cone.move_source_to(source_point) - screen = Arc(TAU/4).rotate_in_place(TAU/2).shift(3*RIGHT) - screen.radius = 4 - screen.start_angle = -TAU/5 - screen.next_to(morty, LEFT) + 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.add(light_screen) - # dim the light that misses the screen self.play( - ApplyMethod(light_cone.set_intensity,0.3) + 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), ) lc_updater = lambda lc: light_screen.update_light_cone(lc) @@ -717,9 +773,58 @@ class SingleLightHouseScene(PiCreatureScene): self.add(ca1, ca2) self.add_foreground_mobject(morty) - moving_screen = ApplyMethod(screen.move_to, [1,0,0], run_time=3) + pointing_screen_at_source = ApplyMethod(screen.rotate_in_place,TAU/6) + self.play(pointing_screen_at_source) - self.play(moving_screen) + arc_angle = light_cone.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.add_tip(at_start = True, at_end = True) + angle_arc.move_arc_center_to(source_point) + + self.add(angle_arc) + + angle_indicator = DecimalNumber(arc_angle/TAU*360, + num_decimal_points = 0, + unit = "^\\circ") + angle_indicator.next_to(angle_arc,RIGHT) + self.add_foreground_mobject(angle_indicator) + + angle_update_func = lambda x: light_cone.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) + self.add(ca4) + + rotating_screen = ApplyMethod(light_screen.screen.rotate_in_place, TAU/6, run_time=3, rate_func = wiggle) + self.play(rotating_screen) + + #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() + + + + # morph into Earth scene + + globe = Circle(radius = 3) + globe.move_to([2,0,0]) + sun_position = [-100,0,0] + self.play( + ApplyMethod(lighthouse.move_to,sun_position), + ApplyMethod(candle.move_to,sun_position), + ApplyMethod(light_cone.move_source_to,sun_position), + FadeOut(angle_arc), + FadeOut(angle_indicator), + FadeIn(globe), + ApplyMethod(light_screen.move_to,[0,0,0]), + ApplyMethod(morty.move_to,[1,0,0]) + + ) diff --git a/topics/geometry.py b/topics/geometry.py index a91d23e7..57bd1778 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -37,20 +37,27 @@ class Arc(VMobject): self.scale(self.radius, about_point = ORIGIN) def add_tip(self, tip_length = 0.25, at_start = False, at_end = True): + # clear out any old tips + for submob in self.submobjects: + if submob.mark_paths_closed == True: # is a tip + self.remove(submob) + #TODO, do this a better way p1 = p2 = p3 = p4 = None start_arrow = end_arrow = None if at_start: - p1, p2 = self.points[-2:] + p1, p2 = self.points[-3:-1] + # self.points[-2:] did overshoot start_arrow = Arrow( p1, 2*p2 - p1, tip_length = tip_length, max_tip_length_to_length_ratio = 2.0 ) - self.add(start_arrow.split()[-1]) + self.add(start_arrow.split()[-1]) # just the tip if at_end: - p4, p3 = self.points[:2] + p4, p3 = self.points[1:3] + # self.points[:2] did overshoot end_arrow = Arrow( p3, 2*p4 - p3, tip_length = tip_length, @@ -83,6 +90,13 @@ class Arc(VMobject): def stop_angle(self): return self.start_angle + self.angle + def set_bound_angles(self,start=0,stop=np.pi): + self.start_angle = start + self.angle = stop - start + + return self + + diff --git a/topics/numerals.py b/topics/numerals.py index f8ee064f..d3601839 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -10,7 +10,8 @@ class DecimalNumber(VMobject): CONFIG = { "num_decimal_points" : 2, "digit_to_digit_buff" : 0.05, - "show_ellipsis" : False + "show_ellipsis" : False, + "unit" : None } def __init__(self, number, **kwargs): digest_config(self, kwargs, locals()) @@ -25,11 +26,13 @@ class DecimalNumber(VMobject): if self.show_ellipsis: self.add(TexMobject("\\dots")) + self.arrange_submobjects( buff = self.digit_to_digit_buff, aligned_edge = DOWN ) + if num_string.startswith("-"): minus = self.submobjects[0] minus.next_to( @@ -37,6 +40,20 @@ class DecimalNumber(VMobject): buff = self.digit_to_digit_buff ) + + if self.unit != None: + unit_sign = TexMobject(self.unit) + unit_sign.next_to(self.submobjects[-1],RIGHT, + buff = self.digit_to_digit_buff) + + if self.unit == "^\\circ": + unit_sign.align_to(self,UP) + else: + unit_sign.align_to(self,DOWN) + self.add(unit_sign) + + + class Integer(VGroup): CONFIG = { "digit_buff" : 0.8*SMALL_BUFF @@ -85,7 +102,8 @@ class ChangingDecimal(Animation): new_decimal = DecimalNumber( new_number, num_decimal_points = self.num_decimal_points, - show_ellipsis = self.show_ellipsis + show_ellipsis = self.show_ellipsis, + unit = self.decimal_number_mobject.unit ) new_decimal.replace(decimal, dim_to_match = 1) new_decimal.highlight(decimal.get_color())