From 6e1c84a30512dbf8311902cd197c63af5795bdd8 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 22 Jan 2018 16:07:53 -0800 Subject: [PATCH 01/25] Arcs can now have tips on either side Todo: tips on both sides, eliminate overshoot --- topics/geometry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/topics/geometry.py b/topics/geometry.py index 080ca212..73318cc7 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -36,9 +36,12 @@ class Arc(VMobject): ) self.scale(self.radius, about_point = ORIGIN) - def add_tip(self, tip_length = 0.25): + def add_tip(self, tip_length = 0.25, end = 0): #TODO, do this a better way - p1, p2 = self.points[-2:] + if end == 1: + p1, p2 = self.points[-2:] + else: + p2, p1 = self.points[:2] arrow = Arrow( p1, 2*p2 - p1, tip_length = tip_length, From c7f92d53cd34a8ca15f2d97c8c6c208137b14f78 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 22 Jan 2018 16:30:07 -0800 Subject: [PATCH 02/25] Arcs can now have arrows on both sides --- topics/geometry.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/topics/geometry.py b/topics/geometry.py index 73318cc7..a91d23e7 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -36,18 +36,32 @@ class Arc(VMobject): ) self.scale(self.radius, about_point = ORIGIN) - def add_tip(self, tip_length = 0.25, end = 0): + def add_tip(self, tip_length = 0.25, at_start = False, at_end = True): #TODO, do this a better way - if end == 1: + p1 = p2 = p3 = p4 = None + start_arrow = end_arrow = None + if at_start: p1, p2 = self.points[-2:] - else: - p2, p1 = self.points[:2] - arrow = Arrow( - p1, 2*p2 - p1, - tip_length = tip_length, - max_tip_length_to_length_ratio = 2.0 - ) - self.add(arrow.split()[-1]) + 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]) + + if at_end: + p4, p3 = self.points[:2] + end_arrow = Arrow( + p3, 2*p4 - p3, + tip_length = tip_length, + max_tip_length_to_length_ratio = 2.0 + ) + self.add(end_arrow.split()[-1]) + + + + + self.highlight(self.get_color()) return self From 66e001d77a636415f27c4f29f76ae0a8f230bc25 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 23 Jan 2018 10:53:24 -0800 Subject: [PATCH 03/25] 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()) From 245ee91f02d37e0062c1ba3d66ce4724cb6c6a84 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:04:05 -0800 Subject: [PATCH 04/25] Added initial_config to digest_config, so that there is a way to recall how a mobject/animation/scene was initialized --- helpers.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/helpers.py b/helpers.py index 079b0b5e..85ad584e 100644 --- a/helpers.py +++ b/helpers.py @@ -252,14 +252,14 @@ def get_all_descendent_classes(Class): result.append(Child) return result -def filtered_locals(local_args): - result = local_args.copy() +def filtered_locals(caller_locals): + result = caller_locals.copy() ignored_local_args = ["self", "kwargs"] for arg in ignored_local_args: - result.pop(arg, local_args) + result.pop(arg, caller_locals) return result -def digest_config(obj, kwargs, local_args = {}): +def digest_config(obj, kwargs, caller_locals = {}): """ Sets init args and CONFIG values as local variables @@ -268,19 +268,25 @@ def digest_config(obj, kwargs, local_args = {}): be easily passed into instantiation, and is attached as an attribute of the object. """ - ### Assemble list of CONFIGs from all super classes + + # Assemble list of CONFIGs from all super classes classes_in_hierarchy = [obj.__class__] - configs = [] + static_configs = [] while len(classes_in_hierarchy) > 0: Class = classes_in_hierarchy.pop() classes_in_hierarchy += Class.__bases__ if hasattr(Class, "CONFIG"): - configs.append(Class.CONFIG) + static_configs.append(Class.CONFIG) #Order matters a lot here, first dicts have higher priority - all_dicts = [kwargs, filtered_locals(local_args), obj.__dict__] - all_dicts += configs + caller_locals = filtered_locals(caller_locals) + all_dicts = [kwargs, caller_locals, obj.__dict__] + all_dicts += static_configs + all_new_dicts = [kwargs, caller_locals] + static_configs obj.__dict__ = merge_config(all_dicts) + #Keep track of the configuration of objects upon + #instantiation + obj.initial_config = merge_config(all_new_dicts) def merge_config(all_dicts): all_config = reduce(op.add, [d.items() for d in all_dicts]) @@ -295,6 +301,15 @@ def merge_config(all_dicts): config[key] = merge_config([config[key], value]) return config +def soft_dict_update(d1, d2): + """ + Adds key values pairs of d2 to d1 only when d1 doesn't + already have that key + """ + for key, value in d2.items(): + if key not in d1: + d1[key] = value + def digest_locals(obj, keys = None): caller_locals = filtered_locals( inspect.currentframe().f_back.f_locals From 9ded033c7c80174e24801d3a006ace91622324fb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:04:31 -0800 Subject: [PATCH 05/25] Slightly improved match_style in vmobject, but it's styll suboptimal --- mobject/vectorized_mobject.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index ec1da178..21194a71 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -85,15 +85,17 @@ class VMobject(Mobject): return self def match_style(self, vmobject): - #TODO: Should this be smart about matching the - #style of the family members, if they happen to - #be different? self.set_style_data( stroke_color = vmobject.get_stroke_color(), stroke_width = vmobject.get_stroke_width(), fill_color = vmobject.get_fill_color(), fill_opacity = vmobject.get_fill_opacity(), + family = False ) + #TODO: This behaviro may not be optimal when submobject + #lists dont' have the same length + for sm1, sm2 in zip(self.submobjects, vmobject.submobjects): + sm1.match_style(sm2) return def fade(self, darkness = 0.5): From 0c7698da557ed45fe5ff0ae72df89683c9c2d6d5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:04:56 -0800 Subject: [PATCH 06/25] Added ability to give a unit to a decimal, and changing decimal --- topics/numerals.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index f8ee064f..18b18b2e 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,7 +26,9 @@ class DecimalNumber(VMobject): if self.show_ellipsis: self.add(TexMobject("\\dots")) - + if self.unit is not None: + self.add(TexMobject(self.unit)) + self.arrange_submobjects( buff = self.digit_to_digit_buff, aligned_edge = DOWN @@ -36,6 +39,8 @@ class DecimalNumber(VMobject): self.submobjects[1], LEFT, buff = self.digit_to_digit_buff ) + if self.unit == "\\circ": + self[-1].align_to(self, UP) class Integer(VGroup): CONFIG = { @@ -55,20 +60,18 @@ class ChangingDecimal(Animation): CONFIG = { "num_decimal_points" : None, "show_ellipsis" : None, - "spare_parts" : 2, "position_update_func" : None, "tracked_mobject" : None } def __init__(self, decimal_number_mobject, number_update_func, **kwargs): digest_config(self, kwargs, locals()) - if self.num_decimal_points is None: - self.num_decimal_points = decimal_number_mobject.num_decimal_points - if self.show_ellipsis is None: - self.show_ellipsis = decimal_number_mobject.show_ellipsis - decimal_number_mobject.add(*[ - VectorizedPoint(decimal_number_mobject.get_corner(DOWN+LEFT)) - for x in range(self.spare_parts)] + self.decimal_number_config = dict( + decimal_number_mobject.initial_config ) + for attr in "num_decimal_points", "show_ellipsis": + value = getattr(self, attr) + if value is not None: + self.decimal_number_config[attr] = value if self.tracked_mobject: dmc = decimal_number_mobject.get_center() tmc = self.tracked_mobject.get_center() @@ -83,12 +86,11 @@ class ChangingDecimal(Animation): decimal = self.decimal_number_mobject new_number = self.number_update_func(alpha) new_decimal = DecimalNumber( - new_number, - num_decimal_points = self.num_decimal_points, - show_ellipsis = self.show_ellipsis + new_number, **self.decimal_number_config ) - new_decimal.replace(decimal, dim_to_match = 1) - new_decimal.highlight(decimal.get_color()) + new_decimal.match_height(decimal) + new_decimal.move_to(decimal) + new_decimal.match_style(decimal) decimal.submobjects = new_decimal.submobjects decimal.number = new_number From d9f0c31eb1d40953ff9931f0a96e4f93e714f20c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:05:17 -0800 Subject: [PATCH 07/25] Began setup of WhiteComplexExponentialExpression in fourier --- active_projects/fourier.py | 45 +++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index f51029f9..395d834c 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2455,18 +2455,57 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.generate_center_of_mass_dot_update_anim() self.add(graph, pol_graph, wps_label) + self.set_variables_as_attrs(pol_graph, wps_label) + self.time_axes_group = VGroup(self.time_axes, graph) def show_winding_with_both_coordinates(self): - #TODO, tie dashed lines to dot + com_dot = self.center_of_mass_dot + plane = self.circle_plane + v_line = Line(ORIGIN, UP) + h_line = Line(ORIGIN, RIGHT) + lines = VGroup(v_line, h_line) + lines.highlight(PINK) + def lines_update(lines): + point = com_dot.get_center() + x, y = plane.point_to_coords(point) + h_line.put_start_and_end_on( + plane.coords_to_point(0, y), point + ) + v_line.put_start_and_end_on( + plane.coords_to_point(x, 0), point + ) + lines_update_anim = UpdateFromFunc(lines, lines_update) + lines_update_anim.update(0) + self.add(lines) self.change_frequency( - 2.0, run_time = 15, + 2.04, + added_anims = [ + lines_update_anim, + self.center_of_mass_dot_anim, + ], + run_time = 15, rate_func = bezier([0, 0, 1, 1]) ) self.wait() + self.dot_component_anim = lines_update_anim + def show_plane_as_complex_plane(self): - pass + to_fade = VGroup( + self.time_axes_group, self.pol_graph, self.wps_label + ) + plane = self.circle_plane + complex_plane_title = TextMobject("Complex plane") + complex_plane_title.add_background_rectangle() + complex_plane_title.to_edge(UP) + coordinate_labels = plane.get_coordinate_labels() + + self.play(FadeOut(to_fade)) + self.play(Write(complex_plane_title)) + self.play(Write(coordinate_labels)) + self.wait() + def show_eulers_formula(self): pass From 963956cca71e66127488e9edff222566eaa69093 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 23 Jan 2018 21:09:53 +0100 Subject: [PATCH 08/25] resolved merge conflicts (#85) --- topics/numerals.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index d3601839..1970e6de 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -11,7 +11,7 @@ class DecimalNumber(VMobject): "num_decimal_points" : 2, "digit_to_digit_buff" : 0.05, "show_ellipsis" : False, - "unit" : None + "unit" : None, } def __init__(self, number, **kwargs): digest_config(self, kwargs, locals()) @@ -27,6 +27,8 @@ class DecimalNumber(VMobject): if self.show_ellipsis: self.add(TexMobject("\\dots")) + if self.unit is not None: + self.add(TexMobject(self.unit)) self.arrange_submobjects( buff = self.digit_to_digit_buff, @@ -40,7 +42,6 @@ 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, From eabe989eda7d42b6b3ee92ca686a6b196ad48167 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:18:19 -0800 Subject: [PATCH 09/25] Resolved a few merge conflicts --- scene/scene.py | 7 ++++++- topics/geometry.py | 6 ------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scene/scene.py b/scene/scene.py index fb4ba6ca..6f19ad58 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -32,8 +32,12 @@ class Scene(object): "save_frames" : False, "save_pngs" : False, "pngs_mode" : "RGBA", +<<<<<<< Updated upstream "output_directory" : ANIMATIONS_DIR, "movie_file_extension" : ".mp4", +======= + "output_directory" : MOVIE_DIR, +>>>>>>> Stashed changes "name" : None, "always_continually_update" : False, "random_seed" : 0, @@ -482,6 +486,8 @@ class Scene(object): def preview(self): TkSceneRoot(self) + + def save_image(self, name = None, mode = "RGB", dont_update = False): def get_image_file_path(self, name = None, dont_update = False): folder = "images" @@ -540,7 +546,6 @@ class Scene(object): '-loglevel', 'error', temp_file_path, ] - # self.writing_process = sp.Popen(command, stdin=sp.PIPE, shell=True) self.writing_process = sp.Popen(command, stdin=sp.PIPE) diff --git a/topics/geometry.py b/topics/geometry.py index 0ef70f20..bbbd12a5 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -172,18 +172,12 @@ class AnnularSector(VMobject): arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center -<<<<<<< HEAD def move_arc_center_to(self,point): v = point - self.get_arc_center() self.shift(v) return self - - -======= ->>>>>>> master class Sector(AnnularSector): - CONFIG = { "outer_radius" : 1, "inner_radius" : 0 From b93e28754f5d0f282692f6bb803a7b322374a2be Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:19:56 -0800 Subject: [PATCH 10/25] Whoops, some lingering changes that should have been in last commit --- scene/scene.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scene/scene.py b/scene/scene.py index 6f19ad58..fbf2d2ff 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -32,12 +32,8 @@ class Scene(object): "save_frames" : False, "save_pngs" : False, "pngs_mode" : "RGBA", -<<<<<<< Updated upstream "output_directory" : ANIMATIONS_DIR, "movie_file_extension" : ".mp4", -======= - "output_directory" : MOVIE_DIR, ->>>>>>> Stashed changes "name" : None, "always_continually_update" : False, "random_seed" : 0, @@ -487,8 +483,6 @@ class Scene(object): def preview(self): TkSceneRoot(self) - def save_image(self, name = None, mode = "RGB", dont_update = False): - def get_image_file_path(self, name = None, dont_update = False): folder = "images" if dont_update: From 47c41139bfe7d47e9b453baf8e0cc58ee470ca89 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 12:23:29 -0800 Subject: [PATCH 11/25] Final merge conflict cleanup --- scene/scene.py | 5 +++++ topics/geometry.py | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scene/scene.py b/scene/scene.py index fb4ba6ca..34aef3cc 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -482,6 +482,11 @@ class Scene(object): def preview(self): TkSceneRoot(self) + + def save_image(self, name = None, mode = "RGB", dont_update = False): + folder = "images" + if dont_update: + folder = str(self) def get_image_file_path(self, name = None, dont_update = False): folder = "images" diff --git a/topics/geometry.py b/topics/geometry.py index 0ef70f20..4ed297b3 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -172,16 +172,11 @@ class AnnularSector(VMobject): arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center -<<<<<<< HEAD def move_arc_center_to(self,point): v = point - self.get_arc_center() self.shift(v) return self - - -======= ->>>>>>> master class Sector(AnnularSector): CONFIG = { From 64b5399bc5fe2dbe1946bf568506539390e86ca5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 13:41:14 -0800 Subject: [PATCH 12/25] Extended Mobject.align_to to handle aligning centers of mobjects --- mobject/mobject.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 6e135c95..c8bfc5d3 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -316,14 +316,29 @@ class Mobject(object): self.shift(target_point - point_to_align + buff*direction) return self - def align_to(self, mobject_or_point, direction = UP): + def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): + """ + Examples: + mob1.align_to(mob2, UP) moves mob1 vertically so that its + top edge lines ups with mob2's top edge. + + mob1.align_to(mob2, alignment_vector = RIGHT) moves mob1 + horizontally so that it's center is directly above/below + the center of mob2 + """ if isinstance(mobject_or_point, Mobject): mob = mobject_or_point - point = mob.get_edge_center(direction) + target_point = mob.get_critical_point(direction) else: - point = mobject_or_point - diff = point - self.get_edge_center(direction) - self.shift(direction*np.dot(diff, direction)) + target_point = mobject_or_point + direction_norm = np.linalg.norm(direction) + if direction_norm > 0: + alignment_vect = np.array(direction)/direction_norm + reference_point = self.get_critical_point(direction) + else: + reference_point = self.get_center() + diff = target_point - reference_point + self.shift(alignment_vect*np.dot(diff, alignment_vect)) return self def shift_onto_screen(self, **kwargs): From bf5480c33b59eba8888041ff8cb7848ac2bfbdd9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 13:41:43 -0800 Subject: [PATCH 13/25] Improved VMobject.match_style method to handle submobjects in a smarter way --- mobject/vectorized_mobject.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 21194a71..264181fd 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -92,9 +92,15 @@ class VMobject(Mobject): fill_opacity = vmobject.get_fill_opacity(), family = False ) - #TODO: This behaviro may not be optimal when submobject - #lists dont' have the same length - for sm1, sm2 in zip(self.submobjects, vmobject.submobjects): + + #Does its best to match up submobject lists, and + #match styles accordingly + submobs1, submobs2 = self.submobjects, vmobject.submobjects + if len(submobs1) == 0: + return + elif len(submobs2) == 0: + submobs2 = [vmobject] + for sm1, sm2 in zip(*make_even(submobs1, submobs2)): sm1.match_style(sm2) return From a389e5353f63e163e62c09a67a5d154e15ee6915 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 13:42:18 -0800 Subject: [PATCH 14/25] Extended DecimalNumber to handles complex numbers and background rectangles --- topics/numerals.py | 65 +++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index 10f7c51c..e1e049dd 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -3,6 +3,7 @@ from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint from mobject.tex_mobject import TexMobject from animation import Animation from animation.continual_animation import ContinualAnimation +from topics.geometry import BackgroundRectangle from scene import Scene from helpers import * @@ -12,18 +13,31 @@ class DecimalNumber(VMobject): "digit_to_digit_buff" : 0.05, "show_ellipsis" : False, "unit" : None, + "include_background_rectangle" : False, } def __init__(self, number, **kwargs): - digest_config(self, kwargs, locals()) - num_string = '%.*f'%(self.num_decimal_points, number) - negative_zero_string = "-%.*f"%(self.num_decimal_points, 0.) - if num_string == negative_zero_string: - num_string = num_string[1:] - VMobject.__init__(self, *[ - TexMobject(char) - for char in num_string - ], **kwargs) + VMobject.__init__(self, **kwargs) + self.number = number + ndp = self.num_decimal_points + #Build number string + if isinstance(number, complex): + num_string = '%.*f%s%.*fi'%( + ndp, number.real, + "-" if number.imag < 0 else "+", + ndp, abs(number.imag) + ) + else: + num_string = '%.*f'%(ndp, number) + negative_zero_string = "-%.*f"%(ndp, 0.) + if num_string == negative_zero_string: + num_string = num_string[1:] + self.add(*[ + TexMobject(char, **kwargs) + for char in num_string + ]) + + #Add non-numerical bits if self.show_ellipsis: self.add(TexMobject("\\dots")) @@ -35,28 +49,27 @@ class DecimalNumber(VMobject): aligned_edge = DOWN ) - if num_string.startswith("-"): - minus = self.submobjects[0] - minus.next_to( - self.submobjects[1], LEFT, - buff = self.digit_to_digit_buff - ) + #Handle alignment of parts that should be aligned + #to the bottom + for i, c in enumerate(num_string): + if c == "-" and len(num_string) > i+1: + self[i].align_to(self[i+1], alignment_vect = UP) if self.unit == "\\circ": self[-1].align_to(self, UP) + # + if self.include_background_rectangle: + #TODO, is this the best way to handle + #background rectangles? + self.background_rectangle = BackgroundRectangle(self) + self.submobjects = [ + self.background_rectangle, + VGroup(*self.submobjects) + ] -class Integer(VGroup): +class Integer(DecimalNumber): CONFIG = { - "digit_buff" : 0.8*SMALL_BUFF + "num_decimal_points" : 0, } - def __init__(self, integer, **kwargs): - self.number = integer - num_str = str(integer) - VGroup.__init__(self, *map(TexMobject, num_str), **kwargs) - self.arrange_submobjects( - RIGHT, buff = self.digit_buff, aligned_edge = DOWN - ) - if num_str[0] == "-": - self[0].next_to(self[1], LEFT, buff = SMALL_BUFF) class ChangingDecimal(Animation): CONFIG = { From fb23644b5af2b29ef75585c3d4750c90f92cf213 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 23 Jan 2018 14:03:25 -0800 Subject: [PATCH 15/25] Further fourier work --- .gitignore | 2 +- active_projects/fourier.py | 50 +++++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ea54dace..f0f3ca3f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .DS_Store homeless.py ka_playgrounds/ -grant_playground.py +playground.py special_animations.py prettiness_hall_of_fame.py files/ diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 395d834c..c3a513d4 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2474,14 +2474,13 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): v_line.put_start_and_end_on( plane.coords_to_point(x, 0), point ) - lines_update_anim = UpdateFromFunc(lines, lines_update) + lines_update_anim = ContinualUpdateFromFunc(lines, lines_update) lines_update_anim.update(0) - self.add(lines) + self.add(lines_update_anim) self.change_frequency( 2.04, added_anims = [ - lines_update_anim, self.center_of_mass_dot_anim, ], run_time = 15, @@ -2496,15 +2495,60 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.time_axes_group, self.pol_graph, self.wps_label ) plane = self.circle_plane + dot = self.center_of_mass_dot complex_plane_title = TextMobject("Complex plane") complex_plane_title.add_background_rectangle() complex_plane_title.to_edge(UP) coordinate_labels = plane.get_coordinate_labels() + number_label = DecimalNumber( + 0, include_background_rectangle = True, + ) + number_label_update_anim = ContinualChangingDecimal( + number_label, + lambda a : plane.point_to_number(dot.get_center()), + position_update_func = lambda l : l.next_to( + # plane.coords_to_point(1, 1), DOWN+RIGHT, + dot, DOWN+RIGHT, + buff = SMALL_BUFF + ), + ) + number_label_update_anim.update(0) + arrow = Arrow(RIGHT, LEFT, color = RED) + def update_arrow(arrow): + arrow.put_start_and_end_on( + number_label.get_left(), + dot.get_center(), + ) + arrow.scale(0.9) + return arrow + arrow_update_anim = ContinualUpdateFromFunc(arrow, update_arrow) + arrow_update_anim.update(0) + flower_path = ParametricFunction( + lambda t : plane.coords_to_point( + np.sin(2*t)*np.cos(t), + np.sin(2*t)*np.sin(t), + ), + t_min = 0, t_max = TAU, + ) + flower_path.move_to(self.center_of_mass_dot) self.play(FadeOut(to_fade)) self.play(Write(complex_plane_title)) self.play(Write(coordinate_labels)) self.wait() + self.play( + FadeIn(number_label), + # GrowArrow(arrow) + ) + self.add(number_label_update_anim) + # self.add(arrow_update_anim) + self.play(MoveAlongPath( + dot, flower_path, + run_time = 10, + rate_func = bezier([0, 0, 1, 1]) + )) + self.wait() + def show_eulers_formula(self): From 82905e38bda4024cbcfb6fc5ce5d60106ef91245 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 08:54:36 -0800 Subject: [PATCH 16/25] Removed axes config from Rotating animation --- animation/simple_animations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index bb703443..853f218f 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -12,20 +12,19 @@ from transform import Transform class Rotating(Animation): CONFIG = { - "axes" : [], "axis" : OUT, "radians" : 2*np.pi, "run_time" : 5, "rate_func" : None, "in_place" : True, "about_point" : None, + "about_edge" : None, } def update_submobject(self, submobject, starting_submobject, alpha): submobject.points = np.array(starting_submobject.points) def update_mobject(self, alpha): Animation.update_mobject(self, alpha) - axes = self.axes if self.axes else [self.axis] about_point = None if self.about_point is not None: about_point = self.about_point @@ -33,8 +32,9 @@ class Rotating(Animation): self.about_point = self.mobject.get_center() self.mobject.rotate( alpha*self.radians, - axes = axes, - about_point = self.about_point + axis = self.axis, + about_point = self.about_point, + about_edge = self.about_edge, ) class ShowPartial(Animation): From b3d6ab9d47c5683ae38e7811ef6e5324928abd46 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 08:55:26 -0800 Subject: [PATCH 17/25] Added background rectangle to DecimalNumber --- topics/numerals.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index e1e049dd..44e28323 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -58,13 +58,17 @@ class DecimalNumber(VMobject): self[-1].align_to(self, UP) # if self.include_background_rectangle: - #TODO, is this the best way to handle - #background rectangles? - self.background_rectangle = BackgroundRectangle(self) - self.submobjects = [ - self.background_rectangle, - VGroup(*self.submobjects) - ] + self.add_background_rectangle() + + def add_background_rectangle(self): + #TODO, is this the best way to handle + #background rectangles? + self.background_rectangle = BackgroundRectangle(self) + self.submobjects = [ + self.background_rectangle, + VGroup(*self.submobjects) + ] + return self class Integer(DecimalNumber): CONFIG = { @@ -87,6 +91,8 @@ class ChangingDecimal(Animation): value = getattr(self, attr) if value is not None: self.decimal_number_config[attr] = value + if hasattr(self.decimal_number_mobject, "background_rectangle"): + self.decimal_number_config["include_background_rectangle"] = True if self.tracked_mobject: dmc = decimal_number_mobject.get_center() tmc = self.tracked_mobject.get_center() From 239addea7233e62996047ee693f2596eca41c8f9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 08:55:53 -0800 Subject: [PATCH 18/25] Finished WriteComplexExponentialExpression of fourier --- active_projects/fourier.py | 489 +++++++++++++++++++++++++++++++++++-- 1 file changed, 464 insertions(+), 25 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index c3a513d4..8b44f9fd 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2392,7 +2392,7 @@ class ApplyFourierToFourier(DrawFrequencyPlot): kwargs["scale_val"] = 1.0 return DrawFrequencyPlot.get_cosine_wave(self, freq, **kwargs) -class WhiteComplexExponentialExpression(DrawFrequencyPlot): +class WriteComplexExponentialExpression(DrawFrequencyPlot): CONFIG = { "signal_frequency" : 2.0, "default_num_v_lines_indicating_periods" : 0, @@ -2404,15 +2404,16 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.show_winding_with_both_coordinates() self.show_plane_as_complex_plane() self.show_eulers_formula() - self.reference_other_video() self.show_winding_graph_expression() + self.find_center_of_mass() def setup_plane(self): circle_plane = ComplexPlane( unit_size = 2, - y_radius = SPACE_HEIGHT+LARGE_BUFF + y_radius = SPACE_HEIGHT+LARGE_BUFF, + x_radius = SPACE_WIDTH+LARGE_BUFF ) - circle_plane.shift(DOWN) + circle_plane.shift(DOWN+LEFT) circle = DashedLine(ORIGIN, TAU*UP) circle.apply_complex_function( lambda z : R3_to_complex( @@ -2507,22 +2508,11 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): number_label, lambda a : plane.point_to_number(dot.get_center()), position_update_func = lambda l : l.next_to( - # plane.coords_to_point(1, 1), DOWN+RIGHT, dot, DOWN+RIGHT, buff = SMALL_BUFF ), ) number_label_update_anim.update(0) - arrow = Arrow(RIGHT, LEFT, color = RED) - def update_arrow(arrow): - arrow.put_start_and_end_on( - number_label.get_left(), - dot.get_center(), - ) - arrow.scale(0.9) - return arrow - arrow_update_anim = ContinualUpdateFromFunc(arrow, update_arrow) - arrow_update_anim.update(0) flower_path = ParametricFunction( lambda t : plane.coords_to_point( np.sin(2*t)*np.cos(t), @@ -2536,32 +2526,481 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.play(Write(complex_plane_title)) self.play(Write(coordinate_labels)) self.wait() - self.play( - FadeIn(number_label), - # GrowArrow(arrow) - ) + self.play(FadeIn(number_label)) self.add(number_label_update_anim) - # self.add(arrow_update_anim) self.play(MoveAlongPath( dot, flower_path, run_time = 10, rate_func = bezier([0, 0, 1, 1]) )) self.wait() + self.play(ShowCreation( + self.pol_graph, run_time = 3, + )) + self.play(FadeOut(self.pol_graph)) + self.wait() + self.play(FadeOut(VGroup( + dot, self.dot_component_anim.mobject, number_label + ))) + self.remove(self.dot_component_anim) + self.remove(number_label_update_anim) - + self.set_variables_as_attrs( + number_label, + number_label_update_anim, + complex_plane_title, + ) def show_eulers_formula(self): - pass + plane = self.circle_plane - def reference_other_video(self): - pass + ghost_dot = Dot(ORIGIN, fill_opacity = 0) + def get_t(): + return ghost_dot.get_center()[0] + def get_circle_point(scalar = 1, t_shift = 0): + return plane.number_to_point( + scalar*np.exp(complex(0, get_t()+t_shift)) + ) + vector = Vector(plane.number_to_point(1), color = GREEN) + exp_base = TexMobject("e").scale(1.3) + exp_base.add_background_rectangle() + exp_decimal = DecimalNumber(0, unit = "i", include_background_rectangle = True) + exp_decimal.scale(0.75) + VGroup(exp_base, exp_decimal).match_color(vector) + exp_decimal_update = ContinualChangingDecimal( + exp_decimal, lambda a : get_t(), + position_update_func = lambda d : d.move_to( + exp_base.get_corner(UP+RIGHT), DOWN+LEFT + ) + ) + exp_base_update = ContinualUpdateFromFunc( + exp_base, lambda e : e.move_to(get_circle_point( + scalar = 1.1, t_shift = 0.01*TAU + )) + ) + vector_update = ContinualUpdateFromFunc( + vector, lambda v : v.put_start_and_end_on( + plane.number_to_point(0), get_circle_point() + ) + ) + updates = [exp_base_update, exp_decimal_update, vector_update] + for update in updates: + update.update(0) + + #Show initial vector + self.play( + GrowArrow(vector), + FadeIn(exp_base), + Write(exp_decimal) + ) + self.add(*updates) + self.play(ghost_dot.shift, 2*RIGHT, run_time = 3) + self.wait() + + #Show arc + arc, circle = [ + Line(ORIGIN, t*UP) + for t in get_t(), TAU + ] + for mob in arc, circle: + mob.insert_n_anchor_points(20) + mob.set_stroke(RED, 4) + mob.apply_function( + lambda p : plane.number_to_point( + np.exp(R3_to_complex(p)) + ) + ) + distance_label = DecimalNumber( + exp_decimal.number, + unit = "\\text{units}" + ) + distance_label[-1].shift(SMALL_BUFF*RIGHT) + distance_label.match_color(arc) + distance_label.add_background_rectangle() + distance_label.move_to( + plane.number_to_point( + 1.1*np.exp(complex(0, 0.4*get_t())), + ), + DOWN+LEFT + ) + + self.play(ShowCreation(arc)) + self.play(ReplacementTransform( + exp_decimal.copy(), distance_label + )) + self.wait() + self.play(FadeOut(distance_label)) + + #Show full cycle + self.remove(arc) + self.play( + ghost_dot.move_to, TAU*RIGHT, + ShowCreation( + circle, + rate_func = lambda a : interpolate( + 2.0/TAU, 1, smooth(a) + ), + ), + run_time = 6, + ) + self.wait() + + #Write exponential expression + exp_expression = TexMobject("e", "^{-", "2\\pi i", "f", "t}") + e, minus, two_pi_i, f, t = exp_expression + exp_expression.next_to( + plane.coords_to_point(1, 1), + UP+RIGHT + ) + f.highlight(RED) + t.highlight(YELLOW) + exp_expression.add_background_rectangle() + two_pi_i_f_t_group = VGroup(two_pi_i, f, t) + two_pi_i_f_t_group.save_state() + two_pi_i_f_t_group.move_to(minus, LEFT) + exp_expression[1].remove(minus) + t.save_state() + t.align_to(f, LEFT) + exp_expression[1].remove(f) + + labels = VGroup() + for sym, word in (t, "Time"), (f, "Frequency"): + label = TextMobject(word) + label.match_style(sym) + label.next_to(sym, UP, buff = MED_LARGE_BUFF) + label.add_background_rectangle() + label.arrow = Arrow(label, sym, buff = SMALL_BUFF) + label.arrow.match_style(sym) + labels.add(label) + time_label, frequency_label = labels + example_frequency = TexMobject("f = 1/10") + example_frequency.add_background_rectangle() + example_frequency.match_style(frequency_label) + example_frequency.move_to(frequency_label, DOWN) + + self.play(ReplacementTransform( + VGroup(exp_base[1], exp_decimal[1]).copy(), + exp_expression + )) + self.play(FadeOut(circle)) + self.wait() + + ghost_dot.move_to(ORIGIN) + ambient_ghost_dot_movement = AmbientMovement( + ghost_dot, rate = TAU + ) + self.add(ambient_ghost_dot_movement) + + self.play( + Write(time_label), + GrowArrow(time_label.arrow), + ) + self.wait(6.5) #Leave time to say let's slow down + self.remove(ambient_ghost_dot_movement) + self.play( + FadeOut(time_label), + FadeIn(frequency_label), + t.restore, + GrowFromPoint(f, frequency_label.get_center()), + ReplacementTransform( + time_label.arrow, + frequency_label.arrow, + ) + ) + ghost_dot.move_to(ORIGIN) + ambient_ghost_dot_movement = AmbientMovement( + ghost_dot, rate = 0.1*TAU + ) + self.add(ambient_ghost_dot_movement) + self.wait(3) + self.play( + FadeOut(frequency_label), + FadeIn(example_frequency) + ) + self.wait(15) #Give time to reference other video + #Reverse directions + ambient_ghost_dot_movement.rate *= -1 + self.play( + FadeOut(example_frequency), + FadeOut(frequency_label.arrow), + GrowFromCenter(minus), + two_pi_i_f_t_group.restore + ) + self.wait(4) + + ambient_ghost_dot_movement.rate = 0 + self.remove(*updates) + self.play(*map(FadeOut, [ + update.mobject + for update in updates + if update.mobject is not vector + ])) + self.play(ghost_dot.move_to, ORIGIN) + + exp_expression[1].add(minus, f) + exp_expression[1].sort_submobjects(lambda p : p[0]) + + self.set_variables_as_attrs( + ambient_ghost_dot_movement, ghost_dot, + vector, vector_update, exp_expression + ) def show_winding_graph_expression(self): - pass + ambient_ghost_dot_movement = self.ambient_ghost_dot_movement + ghost_dot = self.ghost_dot + vector = self.vector + exp_expression = self.exp_expression + plane = self.circle_plane + time_axes_group = self.time_axes_group + graph = self.graph + pol_graph = self.get_polarized_mobject(graph, freq = 0.2) + g_label = TexMobject("g(t)") + g_label.match_color(graph) + g_label.next_to(graph, UP) + g_label.add_background_rectangle() + g_scalar = g_label.copy() + g_scalar.move_to(exp_expression, DOWN+LEFT) + + vector_animations = self.get_vector_animations(graph) + vector_animations[1].mobject = vector + graph_y_vector = vector_animations[0].mobject + + self.play( + FadeIn(time_axes_group), + FadeOut(self.complex_plane_title) + ) + self.play(Write(g_label)) + self.wait() + self.play( + ReplacementTransform(g_label.copy(), g_scalar), + exp_expression.next_to, g_scalar, RIGHT, SMALL_BUFF, + exp_expression.shift, 0.5*SMALL_BUFF*UP, + ) + self.play(*vector_animations, run_time = 15) + self.add(*self.mobjects_from_last_animation) + self.wait() + + integrand = VGroup(g_scalar, exp_expression) + rect = SurroundingRectangle(integrand) + morty = Mortimer() + morty.next_to(rect, DOWN+RIGHT) + morty.shift_onto_screen() + self.play( + ShowCreation(rect), + FadeIn(morty) + ) + self.play(morty.change, "raise_right_hand") + self.play(Blink(morty)) + self.play(morty.change, "hooray", rect) + self.wait(2) + self.play(*map(FadeOut, [ + morty, rect, graph_y_vector, vector + ])) + + self.integrand = integrand + + def find_center_of_mass(self): + integrand = self.integrand + integrand.generate_target() + integrand.target.to_edge(RIGHT, buff = LARGE_BUFF) + integrand.target.shift(MED_LARGE_BUFF*DOWN) + sum_expr = TexMobject( + "{1", "\\over", "N}", + "\\sum", "_{k = 1}", "^N", + ) + sum_expr.add_background_rectangle() + sum_expr.shift(SMALL_BUFF*(UP+5*RIGHT)) + sum_expr.next_to(integrand.target, LEFT) + + integral = TexMobject( + "{1", "\\over", "t_2 - t_1}", + "\\int", "_{t_1}", "^{t_2}" + ) + integral.move_to(sum_expr, RIGHT) + time_interval_indicator = SurroundingRectangle(integral[2]) + integral.add_background_rectangle() + axes = self.time_axes + time_interval = Line( + axes.coords_to_point(axes.x_min, 0), + axes.coords_to_point(axes.x_max, 0), + ) + time_interval.match_style(time_interval_indicator) + time_interval_indicator.add(time_interval) + dt_mob = TexMobject("dt") + dt_mob.next_to(integrand.target, RIGHT, SMALL_BUFF, DOWN) + dt_mob.add_background_rectangle() + + dots = self.show_center_of_mass_sampling(20) + self.wait() + self.play( + Write(sum_expr), + MoveToTarget(integrand), + ) + + #Add k subscript to t's + t1 = integrand[0][1][2] + t2 = integrand[1][1][-1] + t_mobs = VGroup(t1, t2) + t_mobs.save_state() + t_mobs.generate_target() + for i, t_mob in enumerate(t_mobs.target): + k = TexMobject("k") + k.match_style(t_mob) + k.match_height(t_mob) + k.scale(0.5) + k.move_to(t_mob.get_corner(DOWN+RIGHT), LEFT) + k.add_background_rectangle() + t_mob.add(k) + if i == 0: + t_mob.shift(0.5*SMALL_BUFF*LEFT) + + self.play(MoveToTarget(t_mobs)) + self.play(LaggedStart( + Indicate, dots[1], + rate_func = there_and_back, + color = TEAL, + )) + self.show_center_of_mass_sampling(100) + dots = self.show_center_of_mass_sampling(500) + self.wait() + self.play(FadeOut(dots)) + self.play( + ReplacementTransform(sum_expr, integral), + FadeIn(dt_mob), + t_mobs.restore, + ) + self.wait() + self.play(ShowCreation(time_interval_indicator)) + self.wait() + self.play(FadeOut(time_interval_indicator)) + self.wait() + + #Show confusion + randy = Randolph() + randy.flip() + randy.next_to(integrand, DOWN, LARGE_BUFF) + randy.to_edge(RIGHT) + full_expression_rect = SurroundingRectangle( + VGroup(integral, dt_mob), color = RED + ) + com_dot = self.center_of_mass_dot + self.center_of_mass_dot_anim.update(0) + com_arrow = Arrow( + full_expression_rect.get_left(), com_dot, + buff = SMALL_BUFF + ) + com_arrow.match_color(com_dot) + self.play(FadeIn(randy)) + self.play(randy.change, "confused", integral) + self.play(Blink(randy)) + self.wait(2) + self.play(ShowCreation(full_expression_rect)) + self.play( + randy.change, "thinking", self.pol_graph, + GrowArrow(com_arrow), + GrowFromCenter(com_dot), + ) + self.play(Blink(randy)) + self.wait(2) + def show_center_of_mass_sampling(self, n_dots): + time_graph = self.graph + pol_graph = self.graph.polarized_mobject + axes = self.time_axes + + dot = Dot(radius = 0.05, color = PINK) + pre_dots = VGroup(*[ + dot.copy().move_to(axes.coords_to_point(t, 0)) + for t in np.linspace(axes.x_min, axes.x_max, n_dots) + ]) + pre_dots.set_fill(opacity = 0) + for graph in time_graph, pol_graph: + if hasattr(graph, "dots"): + graph.dot_fade_anims = [FadeOut(graph.dots)] + else: + graph.dot_fade_anims = [] + graph.save_state() + graph.generate_target() + if not hasattr(graph, "is_faded"): + graph.target.fade(0.7) + graph.is_faded = True + graph.dots = VGroup(*[ + dot.copy().move_to(graph.point_from_proportion(a)) + for a in np.linspace(0, 1, n_dots) + ]) + + self.play( + ReplacementTransform( + pre_dots, time_graph.dots, + submobject_mode = "lagged_start", + run_time = 2, + ), + MoveToTarget(time_graph), + *time_graph.dot_fade_anims + ) + self.play( + ReplacementTransform( + time_graph.copy(), pol_graph.target + ), + MoveToTarget(pol_graph), + ReplacementTransform( + time_graph.dots.copy(), + pol_graph.dots, + ), + *pol_graph.dot_fade_anims, + run_time = 2 + ) + return VGroup(time_graph.dots, pol_graph.dots) + + +class WhyAreYouTellingUsThis(TeacherStudentsScene): + def construct(self): + self.student_says("Why are you \\\\ telling us this?") + self.play(self.teacher.change, "happy") + self.wait(2) + +class BuildUpExpressionStepByStep(TeacherStudentsScene): + def construct(self): + expression = TexMobject( + "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", + "g(t)", "e", "^{2\\pi i", "f", "t}", "dt" + ) + frac, integral, g, e, two_pi_i, f, t, dt = expression + expression.next_to(self.teacher, UP+LEFT) + t.highlight(YELLOW) + f.highlight(RED) + t.save_state() + t.move_to(f, LEFT) + + self.play( + self.teacher.change, "raise_right_hand", + FadeIn(e), + FadeIn(two_pi_i), + ) + self.play( + self.get_student_changes(*["pondering"]*3), + FadeIn(t), + ) + self.play( + FadeIn(f), + t.restore, + ) + self.wait() + self.play(FadeIn(g), Blink(self.students[1])) + self.wait() + self.play( + FadeIn(integral), + FadeIn(frac), + FadeIn(dt), + ) + self.wait(3) + self.teacher_says( + "Just one final \\\\ distinction.", + added_anims = [expression.to_corner, UP+LEFT] + ) + self.wait(3) From ea18d984ce6a76c2be4e2da60db8c2b175aeeeac Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 24 Jan 2018 18:50:01 +0100 Subject: [PATCH 19/25] Not my changes, but I have to commit them?! --- active_projects/fourier.py | 45 ++++++++++++++++++++++++++++++++--- helpers.py | 33 ++++++++++++++++++------- mobject/vectorized_mobject.py | 8 ++++--- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index f51029f9..395d834c 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2455,18 +2455,57 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.generate_center_of_mass_dot_update_anim() self.add(graph, pol_graph, wps_label) + self.set_variables_as_attrs(pol_graph, wps_label) + self.time_axes_group = VGroup(self.time_axes, graph) def show_winding_with_both_coordinates(self): - #TODO, tie dashed lines to dot + com_dot = self.center_of_mass_dot + plane = self.circle_plane + v_line = Line(ORIGIN, UP) + h_line = Line(ORIGIN, RIGHT) + lines = VGroup(v_line, h_line) + lines.highlight(PINK) + def lines_update(lines): + point = com_dot.get_center() + x, y = plane.point_to_coords(point) + h_line.put_start_and_end_on( + plane.coords_to_point(0, y), point + ) + v_line.put_start_and_end_on( + plane.coords_to_point(x, 0), point + ) + lines_update_anim = UpdateFromFunc(lines, lines_update) + lines_update_anim.update(0) + self.add(lines) self.change_frequency( - 2.0, run_time = 15, + 2.04, + added_anims = [ + lines_update_anim, + self.center_of_mass_dot_anim, + ], + run_time = 15, rate_func = bezier([0, 0, 1, 1]) ) self.wait() + self.dot_component_anim = lines_update_anim + def show_plane_as_complex_plane(self): - pass + to_fade = VGroup( + self.time_axes_group, self.pol_graph, self.wps_label + ) + plane = self.circle_plane + complex_plane_title = TextMobject("Complex plane") + complex_plane_title.add_background_rectangle() + complex_plane_title.to_edge(UP) + coordinate_labels = plane.get_coordinate_labels() + + self.play(FadeOut(to_fade)) + self.play(Write(complex_plane_title)) + self.play(Write(coordinate_labels)) + self.wait() + def show_eulers_formula(self): pass diff --git a/helpers.py b/helpers.py index 079b0b5e..85ad584e 100644 --- a/helpers.py +++ b/helpers.py @@ -252,14 +252,14 @@ def get_all_descendent_classes(Class): result.append(Child) return result -def filtered_locals(local_args): - result = local_args.copy() +def filtered_locals(caller_locals): + result = caller_locals.copy() ignored_local_args = ["self", "kwargs"] for arg in ignored_local_args: - result.pop(arg, local_args) + result.pop(arg, caller_locals) return result -def digest_config(obj, kwargs, local_args = {}): +def digest_config(obj, kwargs, caller_locals = {}): """ Sets init args and CONFIG values as local variables @@ -268,19 +268,25 @@ def digest_config(obj, kwargs, local_args = {}): be easily passed into instantiation, and is attached as an attribute of the object. """ - ### Assemble list of CONFIGs from all super classes + + # Assemble list of CONFIGs from all super classes classes_in_hierarchy = [obj.__class__] - configs = [] + static_configs = [] while len(classes_in_hierarchy) > 0: Class = classes_in_hierarchy.pop() classes_in_hierarchy += Class.__bases__ if hasattr(Class, "CONFIG"): - configs.append(Class.CONFIG) + static_configs.append(Class.CONFIG) #Order matters a lot here, first dicts have higher priority - all_dicts = [kwargs, filtered_locals(local_args), obj.__dict__] - all_dicts += configs + caller_locals = filtered_locals(caller_locals) + all_dicts = [kwargs, caller_locals, obj.__dict__] + all_dicts += static_configs + all_new_dicts = [kwargs, caller_locals] + static_configs obj.__dict__ = merge_config(all_dicts) + #Keep track of the configuration of objects upon + #instantiation + obj.initial_config = merge_config(all_new_dicts) def merge_config(all_dicts): all_config = reduce(op.add, [d.items() for d in all_dicts]) @@ -295,6 +301,15 @@ def merge_config(all_dicts): config[key] = merge_config([config[key], value]) return config +def soft_dict_update(d1, d2): + """ + Adds key values pairs of d2 to d1 only when d1 doesn't + already have that key + """ + for key, value in d2.items(): + if key not in d1: + d1[key] = value + def digest_locals(obj, keys = None): caller_locals = filtered_locals( inspect.currentframe().f_back.f_locals diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index ec1da178..21194a71 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -85,15 +85,17 @@ class VMobject(Mobject): return self def match_style(self, vmobject): - #TODO: Should this be smart about matching the - #style of the family members, if they happen to - #be different? self.set_style_data( stroke_color = vmobject.get_stroke_color(), stroke_width = vmobject.get_stroke_width(), fill_color = vmobject.get_fill_color(), fill_opacity = vmobject.get_fill_opacity(), + family = False ) + #TODO: This behaviro may not be optimal when submobject + #lists dont' have the same length + for sm1, sm2 in zip(self.submobjects, vmobject.submobjects): + sm1.match_style(sm2) return def fade(self, darkness = 0.5): From a4a146a54eecd57b33feb4a4c86e930ef73cc28c Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 24 Jan 2018 18:50:44 +0100 Subject: [PATCH 20/25] Merge branch 'master' into lighthouse # Conflicts: # topics/numerals.py --- mobject/mobject.py | 25 +++++++++++--- mobject/vectorized_mobject.py | 12 +++++-- scene/scene.py | 3 +- topics/geometry.py | 6 ---- topics/numerals.py | 64 ++++++++++++++++++++++++----------- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 6e135c95..c8bfc5d3 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -316,14 +316,29 @@ class Mobject(object): self.shift(target_point - point_to_align + buff*direction) return self - def align_to(self, mobject_or_point, direction = UP): + def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): + """ + Examples: + mob1.align_to(mob2, UP) moves mob1 vertically so that its + top edge lines ups with mob2's top edge. + + mob1.align_to(mob2, alignment_vector = RIGHT) moves mob1 + horizontally so that it's center is directly above/below + the center of mob2 + """ if isinstance(mobject_or_point, Mobject): mob = mobject_or_point - point = mob.get_edge_center(direction) + target_point = mob.get_critical_point(direction) else: - point = mobject_or_point - diff = point - self.get_edge_center(direction) - self.shift(direction*np.dot(diff, direction)) + target_point = mobject_or_point + direction_norm = np.linalg.norm(direction) + if direction_norm > 0: + alignment_vect = np.array(direction)/direction_norm + reference_point = self.get_critical_point(direction) + else: + reference_point = self.get_center() + diff = target_point - reference_point + self.shift(alignment_vect*np.dot(diff, alignment_vect)) return self def shift_onto_screen(self, **kwargs): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 21194a71..264181fd 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -92,9 +92,15 @@ class VMobject(Mobject): fill_opacity = vmobject.get_fill_opacity(), family = False ) - #TODO: This behaviro may not be optimal when submobject - #lists dont' have the same length - for sm1, sm2 in zip(self.submobjects, vmobject.submobjects): + + #Does its best to match up submobject lists, and + #match styles accordingly + submobs1, submobs2 = self.submobjects, vmobject.submobjects + if len(submobs1) == 0: + return + elif len(submobs2) == 0: + submobs2 = [vmobject] + for sm1, sm2 in zip(*make_even(submobs1, submobs2)): sm1.match_style(sm2) return diff --git a/scene/scene.py b/scene/scene.py index fb4ba6ca..fbf2d2ff 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -482,7 +482,7 @@ class Scene(object): def preview(self): TkSceneRoot(self) - + def get_image_file_path(self, name = None, dont_update = False): folder = "images" if dont_update: @@ -540,7 +540,6 @@ class Scene(object): '-loglevel', 'error', temp_file_path, ] - # self.writing_process = sp.Popen(command, stdin=sp.PIPE, shell=True) self.writing_process = sp.Popen(command, stdin=sp.PIPE) diff --git a/topics/geometry.py b/topics/geometry.py index 0ef70f20..bbbd12a5 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -172,18 +172,12 @@ class AnnularSector(VMobject): arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center -<<<<<<< HEAD def move_arc_center_to(self,point): v = point - self.get_arc_center() self.shift(v) return self - - -======= ->>>>>>> master class Sector(AnnularSector): - CONFIG = { "outer_radius" : 1, "inner_radius" : 0 diff --git a/topics/numerals.py b/topics/numerals.py index 8125436b..d5a9bae3 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -3,6 +3,7 @@ from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint from mobject.tex_mobject import TexMobject from animation import Animation from animation.continual_animation import ContinualAnimation +from topics.geometry import BackgroundRectangle from scene import Scene from helpers import * @@ -12,18 +13,31 @@ class DecimalNumber(VMobject): "digit_to_digit_buff" : 0.05, "show_ellipsis" : False, "unit" : None, + "include_background_rectangle" : False, } def __init__(self, number, **kwargs): - digest_config(self, kwargs, locals()) - num_string = '%.*f'%(self.num_decimal_points, number) - negative_zero_string = "-%.*f"%(self.num_decimal_points, 0.) - if num_string == negative_zero_string: - num_string = num_string[1:] - VMobject.__init__(self, *[ - TexMobject(char) - for char in num_string - ], **kwargs) + VMobject.__init__(self, **kwargs) + self.number = number + ndp = self.num_decimal_points + #Build number string + if isinstance(number, complex): + num_string = '%.*f%s%.*fi'%( + ndp, number.real, + "-" if number.imag < 0 else "+", + ndp, abs(number.imag) + ) + else: + num_string = '%.*f'%(ndp, number) + negative_zero_string = "-%.*f"%(ndp, 0.) + if num_string == negative_zero_string: + num_string = num_string[1:] + self.add(*[ + TexMobject(char, **kwargs) + for char in num_string + ]) + + #Add non-numerical bits if self.show_ellipsis: self.add(TexMobject("\\dots")) @@ -35,6 +49,7 @@ class DecimalNumber(VMobject): aligned_edge = DOWN ) +<<<<<<< HEAD if num_string.startswith("-"): minus = self.submobjects[0] minus.next_to( @@ -55,18 +70,29 @@ class DecimalNumber(VMobject): class Integer(VGroup): +======= + #Handle alignment of parts that should be aligned + #to the bottom + for i, c in enumerate(num_string): + if c == "-" and len(num_string) > i+1: + self[i].align_to(self[i+1], alignment_vect = UP) + if self.unit == "\\circ": + self[-1].align_to(self, UP) + # + if self.include_background_rectangle: + #TODO, is this the best way to handle + #background rectangles? + self.background_rectangle = BackgroundRectangle(self) + self.submobjects = [ + self.background_rectangle, + VGroup(*self.submobjects) + ] + +class Integer(DecimalNumber): +>>>>>>> master CONFIG = { - "digit_buff" : 0.8*SMALL_BUFF + "num_decimal_points" : 0, } - def __init__(self, integer, **kwargs): - self.number = integer - num_str = str(integer) - VGroup.__init__(self, *map(TexMobject, num_str), **kwargs) - self.arrange_submobjects( - RIGHT, buff = self.digit_buff, aligned_edge = DOWN - ) - if num_str[0] == "-": - self[0].next_to(self[1], LEFT, buff = SMALL_BUFF) class ChangingDecimal(Animation): CONFIG = { From c7fa8c59adf7d18e4591e398c5418ab58a6dde92 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 11:25:55 -0800 Subject: [PATCH 21/25] Made slicing into mobjects return a Group or VGroup instead of a list --- mobject/mobject.py | 19 +++++++++++++------ mobject/vectorized_mobject.py | 3 +++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index c8bfc5d3..f079b330 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -15,11 +15,11 @@ class Mobject(object): Mathematical Object """ CONFIG = { - "color" : WHITE, + "color" : WHITE, "stroke_width" : DEFAULT_POINT_THICKNESS, - "name" : None, - "dim" : 3, - "target" : None, + "name" : None, + "dim" : 3, + "target" : None, } def __init__(self, *submobjects, **kwargs): digest_config(self, kwargs) @@ -663,8 +663,12 @@ class Mobject(object): ## Family matters - def __getitem__(self, index): - return self.split()[index] + def __getitem__(self, value): + self_list = self.split() + if isinstance(value, slice): + GroupClass = self.get_group_class() + return GroupClass(*self_list.__getitem__(value)) + return self_list.__getitem__(value) def __iter__(self): return iter(self.split()) @@ -672,6 +676,9 @@ class Mobject(object): def __len__(self): return len(self.split()) + def get_group_class(self): + return Group + def split(self): result = [self] if len(self.points) > 0 else [] return result + self.submobjects diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 264181fd..4b1a37e6 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -19,6 +19,9 @@ class VMobject(Mobject): "make_smooth_after_applying_functions" : False, } + def get_group_class(self): + return VGroup + ## Colors def init_colors(self): self.set_style_data( From ebf44f9bdbbec7c34a29c0301ac15ec8fd712914 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 11:26:13 -0800 Subject: [PATCH 22/25] Scaffolding for ScaleUpCenterOfMass scene in fourier --- active_projects/fourier.py | 61 +++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 8b44f9fd..f7c1d745 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2396,6 +2396,8 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): CONFIG = { "signal_frequency" : 2.0, "default_num_v_lines_indicating_periods" : 0, + "time_axes_scale_val" : 0.7, + "initial_winding_frequency" : 0.1, } def construct(self): self.remove(self.pi_creature) @@ -2428,7 +2430,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): fill_opacity = 0.9, buff = MED_SMALL_BUFF, )) - time_axes.scale(0.7) + time_axes.scale(self.time_axes_scale_val) time_axes.to_corner(UP+LEFT, buff = 0) time_axes.set_stroke(color = WHITE, width = 1) @@ -2445,7 +2447,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): scale_val = 0.5, shift_val = 0.75, ) - freq = 0.1 + freq = self.initial_winding_frequency pol_graph = self.get_polarized_mobject(graph, freq = freq) wps_label = self.get_winding_frequency_label() ChangeDecimalToValue(wps_label[0], freq).update(1) @@ -2970,7 +2972,9 @@ class BuildUpExpressionStepByStep(TeacherStudentsScene): frac, integral, g, e, two_pi_i, f, t, dt = expression expression.next_to(self.teacher, UP+LEFT) t.highlight(YELLOW) - f.highlight(RED) + g[2].highlight(YELLOW) + dt[1].highlight(YELLOW) + f.highlight(GREEN) t.save_state() t.move_to(f, LEFT) @@ -2998,14 +3002,63 @@ class BuildUpExpressionStepByStep(TeacherStudentsScene): self.wait(3) self.teacher_says( "Just one final \\\\ distinction.", - added_anims = [expression.to_corner, UP+LEFT] + bubble_kwargs = {"height" : 2.5, "width" : 3.5}, + added_anims = [expression.to_corner, UP+RIGHT] ) self.wait(3) +class ScaleUpCenterOfMass(WriteComplexExponentialExpression): + CONFIG = { + "time_axes_scale_val" : 0.6, + "initial_winding_frequency" : 1.95 + } + def construct(self): + self.remove(self.pi_creature) + self.setup_plane() + self.setup_graph() + self.add_expression() + self.add_center_of_mass_dot() + + self.cross_out_denominator() + self.scale_up_center_of_mass() + self.what_this_means_for_various_winding_frequencies() + def add_expression(self): + expression = TexMobject( + "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", + "g(t)", "e", "^{2\\pi i", "f", "t}", "dt" + ) + frac, integral, g, e, two_pi_i, f, t, dt = expression + expression.to_corner(UP+RIGHT) + t.highlight(YELLOW) + g[2].highlight(YELLOW) + dt[1].highlight(YELLOW) + f.highlight(GREEN) + expression.add_background_rectangle() + self.expression = expression + self.add(expression) + + self.winding_freq_label.to_edge(RIGHT) + self.winding_freq_label[1].match_color(f) + + def add_center_of_mass_dot(self): + self.center_of_mass_dot = self.get_center_of_mass_dot() + self.generate_center_of_mass_dot_update_anim() + self.add(self.center_of_mass_dot) + def cross_out_denominator(self): + frac = self.expression[0] + integral = VGroup(*self.expression[1:]) + + + def scale_up_center_of_mass(self): + pass + + + def what_this_means_for_various_winding_frequencies(self): + pass class CloseWithAPuzzle(TeacherStudentsScene): From 3c86caa48830119983ec49e31c861c88a2ccc443 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 12:14:37 -0800 Subject: [PATCH 23/25] Fixed bug with MappingCamera set to allow_object_intrusion --- camera.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/camera.py b/camera.py index 8737a54b..f5a6f09a 100644 --- a/camera.py +++ b/camera.py @@ -99,10 +99,7 @@ class Camera(object): ]) )) - def capture_mobject(self, mobject): - return self.capture_mobjects([mobject]) - - def capture_mobjects( + def get_mobjects_to_display( self, mobjects, include_submobjects = True, excluded_mobjects = None, @@ -116,6 +113,13 @@ class Camera(object): excluded_mobjects ) mobjects = list_difference_update(mobjects, all_excluded) + return mobjects + + def capture_mobject(self, mobject, **kwargs): + return self.capture_mobjects([mobject], **kwargs) + + def capture_mobjects(self, mobjects, **kwargs): + mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: if isinstance(mobject, VMobject): @@ -411,7 +415,7 @@ class MovingCamera(Camera): class MappingCamera(Camera): CONFIG = { "mapping_func" : lambda p : p, - "min_anchor_points" : 20, + "min_anchor_points" : 50, "allow_object_intrusion" : False } @@ -419,6 +423,7 @@ class MappingCamera(Camera): return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points)) def capture_mobjects(self, mobjects, **kwargs): + mobjects = self.get_mobjects_to_display(mobjects, **kwargs) if self.allow_object_intrusion: mobject_copies = mobjects else: @@ -427,7 +432,11 @@ class MappingCamera(Camera): if isinstance(mobject, VMobject) and \ 0 < mobject.get_num_anchor_points() < self.min_anchor_points: mobject.insert_n_anchor_points(self.min_anchor_points) - Camera.capture_mobjects(self, mobject_copies, **kwargs) + Camera.capture_mobjects( + self, mobject_copies, + include_submobjects = False, + excluded_mobjects = None, + ) # TODO: Put this in different utility/helpers file? Convenient for me (Sridhar); I like it. class DictAsObject(object): From afc0c0d5ae7aad57cf03beb0f1a651c38c33fd99 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 25 Jan 2018 23:23:04 +0100 Subject: [PATCH 24/25] Trying to make light cones update correctly --- active_projects/basel.py | 64 +++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 909fd1cc..b6263cd0 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -48,6 +48,7 @@ LIGHT_CONE_NUM_SECTORS = 30 NUM_CONES = 50 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 +DIM_OPACITY = 0.2 def show_line_length(line): @@ -95,6 +96,7 @@ class LightScreen(VMobject): 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 @@ -133,6 +135,11 @@ 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) + + class LightCone(VGroup): CONFIG = { @@ -173,6 +180,7 @@ class LightCone(VGroup): 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 @@ -754,27 +762,31 @@ class SingleLightHouseScene(PiCreatureScene): 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), - ) + # 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), + # ) + lc_updater = lambda lc: light_screen.update_light_cone(lc) sh_updater = lambda sh: light_screen.update_shadow(sh) ca1 = ContinualUpdateFromFunc(light_screen.light_cone, lc_updater) + ca15 = ContinualUpdateFromFunc(light_screen, + lc_updater) ca2 = ContinualUpdateFromFunc(light_screen.shadow, sh_updater) - self.add(ca1, ca2) + self.add(ca1, ca15, ca2) self.add_foreground_mobject(morty) - pointing_screen_at_source = ApplyMethod(screen.rotate_in_place,TAU/6) - self.play(pointing_screen_at_source) + pointing_screen_at_source = ApplyMethod(screen.rotate,TAU/6) + #self.play(pointing_screen_at_source) + #self.wait() arc_angle = light_cone.angle # draw arc arrows to show the opening angle @@ -791,7 +803,7 @@ 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: light_cone.angle/TAU * 360 ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func) self.add(ca3) @@ -799,8 +811,18 @@ class SingleLightHouseScene(PiCreatureScene): 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 = ApplyMethod(light_screen.screen.rotate, + TAU/8, run_time=1.5) + #self.wait(2) + rotating_screen_2 = ApplyMethod(light_screen.screen.rotate, + -TAU/4, run_time=3, rate_func = there_and_back) + #self.wait(2) + rotating_screen_3 = ApplyMethod(light_screen.screen.rotate, + TAU/8, run_time=1.5) + + #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) @@ -815,14 +837,14 @@ class SingleLightHouseScene(PiCreatureScene): 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]) + #ApplyMethod(lighthouse.move_to,sun_position), + #ApplyMethod(candle.move_to,sun_position), + ApplyMethod(light_screen.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]) ) From 9e57ac2be6109ee5101f4fa73e89431a75aa5938 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 25 Jan 2018 23:23:43 +0100 Subject: [PATCH 25/25] Annulus: symmetrized code --- topics/geometry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/topics/geometry.py b/topics/geometry.py index bbbd12a5..b1152f0d 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -203,10 +203,12 @@ class Annulus(Circle): } def generate_points(self): + self.points = [] self.radius = self.outer_radius - Circle.generate_points(self) + outer_circle = Circle(radius = self.outer_radius) inner_circle = Circle(radius=self.inner_radius) inner_circle.flip() + self.points = outer_circle.points self.add_subpath(inner_circle.points) class Line(VMobject):