From 963956cca71e66127488e9edff222566eaa69093 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 23 Jan 2018 21:09:53 +0100 Subject: [PATCH 01/33] 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 ea18d984ce6a76c2be4e2da60db8c2b175aeeeac Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 24 Jan 2018 18:50:01 +0100 Subject: [PATCH 02/33] 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 03/33] 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 ad4ceb3f1a884c99861cea254d7ec6216e3f0022 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 16:09:37 -0800 Subject: [PATCH 04/33] A few bits of cleanup --- mobject/vectorized_mobject.py | 4 ++-- topics/geometry.py | 7 ++++--- topics/number_line.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 4b1a37e6..2fb398c2 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -100,12 +100,12 @@ class VMobject(Mobject): #match styles accordingly submobs1, submobs2 = self.submobjects, vmobject.submobjects if len(submobs1) == 0: - return + return self elif len(submobs2) == 0: submobs2 = [vmobject] for sm1, sm2 in zip(*make_even(submobs1, submobs2)): sm1.match_style(sm2) - return + return self def fade(self, darkness = 0.5): for submob in self.submobject_family(): diff --git a/topics/geometry.py b/topics/geometry.py index bbbd12a5..3605d8b6 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -97,9 +97,6 @@ class Arc(VMobject): return self - - - class Circle(Arc): CONFIG = { "color" : RED, @@ -511,6 +508,7 @@ class Arrow(Line): Line.put_start_and_end_on(self, *args, **kwargs) self.set_tip_points(self.tip, preserve_normal = False) self.set_rectangular_stem_points() + return self def scale(self, scale_factor, **kwargs): Line.scale(self, scale_factor, **kwargs) @@ -520,6 +518,9 @@ class Arrow(Line): self.set_rectangular_stem_points() return self + def copy(self): + return self.deepcopy() + class Vector(Arrow): CONFIG = { "color" : YELLOW, diff --git a/topics/number_line.py b/topics/number_line.py index 7aa5fa9b..623abf4e 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -200,14 +200,21 @@ class Axes(VGroup): self.y_axis.point_to_number(point), ) - def get_graph(self, function, num_graph_points = None, **kwargs): + def get_graph( + self, function, num_graph_points = None, + x_min = None, + x_max = None, + **kwargs + ): kwargs["fill_opacity"] = kwargs.get("fill_opacity", 0) kwargs["num_anchor_points"] = \ num_graph_points or self.default_num_graph_points + x_min = x_min or self.x_min + x_max = x_max or self.x_max graph = ParametricFunction( lambda t : self.coords_to_point(t, function(t)), - t_min = self.x_min, - t_max = self.x_max, + t_min = x_min, + t_max = x_max, **kwargs ) graph.underlying_function = function From 27c6658eb1bda5c3ee277deda668f0618ae8069c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 24 Jan 2018 16:09:49 -0800 Subject: [PATCH 05/33] Finished ScaleUpCenterOfMass of fourier --- active_projects/fourier.py | 318 ++++++++++++++++++++++++++++++++++--- 1 file changed, 300 insertions(+), 18 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index f7c1d745..cd17cf71 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -1474,6 +1474,9 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): def change_frequency(self, new_freq, **kwargs): kwargs["run_time"] = kwargs.get("run_time", 3) + kwargs["rate_func"] = kwargs.get( + "rate_func", bezier([0, 0, 1, 1]) + ) added_anims = kwargs.get("added_anims", []) freq_label = filter( lambda sm : isinstance(sm, DecimalNumber), @@ -2425,11 +2428,12 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): circle_plane.add(circle) time_axes = self.get_time_axes() - time_axes.add_to_back(BackgroundRectangle( + time_axes.background_rectangle = BackgroundRectangle( time_axes, fill_opacity = 0.9, buff = MED_SMALL_BUFF, - )) + ) + time_axes.add_to_back(time_axes.background_rectangle) time_axes.scale(self.time_axes_scale_val) time_axes.to_corner(UP+LEFT, buff = 0) time_axes.set_stroke(color = WHITE, width = 1) @@ -2956,7 +2960,6 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): ) return VGroup(time_graph.dots, pol_graph.dots) - class WhyAreYouTellingUsThis(TeacherStudentsScene): def construct(self): self.student_says("Why are you \\\\ telling us this?") @@ -3010,19 +3013,23 @@ class BuildUpExpressionStepByStep(TeacherStudentsScene): class ScaleUpCenterOfMass(WriteComplexExponentialExpression): CONFIG = { "time_axes_scale_val" : 0.6, - "initial_winding_frequency" : 1.95 + "initial_winding_frequency" : 2.05 } def construct(self): self.remove(self.pi_creature) self.setup_plane() self.setup_graph() - self.add_expression() self.add_center_of_mass_dot() + self.add_expression() self.cross_out_denominator() self.scale_up_center_of_mass() - self.what_this_means_for_various_winding_frequencies() + self.comment_on_current_signal() + 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 add_expression(self): expression = TexMobject( @@ -3035,30 +3042,305 @@ class ScaleUpCenterOfMass(WriteComplexExponentialExpression): 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) - + self.winding_freq_label.align_to( + self.circle_plane.coords_to_point(0, 0.1), DOWN + ) def cross_out_denominator(self): frac = self.expression[0] - integral = VGroup(*self.expression[1:]) - + integral = self.expression[1:] + for mob in frac, integral: + mob.add_to_back(BackgroundRectangle(mob)) + self.add(mob) + cross = Cross(frac) + brace = Brace(integral, DOWN) + label = brace.get_text("The actual \\\\ Fourier transform") + label.add_background_rectangle() + label.shift_onto_screen() + rect = SurroundingRectangle(integral) + + self.play(ShowCreation(cross)) + self.wait() + self.play(ShowCreation(rect)) + self.play( + GrowFromCenter(brace), + FadeIn(label) + ) + self.wait(2) + + self.integral = integral + self.frac = frac + self.frac_cross = cross + self.integral_rect = rect + self.integral_brace = brace + self.integral_label = label def scale_up_center_of_mass(self): - pass + plane = self.circle_plane + origin = plane.coords_to_point(0, 0) + com_dot = self.center_of_mass_dot + com_vector = Arrow( + origin, com_dot.get_center(), + buff = 0 + ) + com_vector.match_style(com_dot) + vector_to_scale = com_vector.copy() + def get_com_vector_copies(n): + com_vector_copies = VGroup(*[ + com_vector.copy().shift(x*com_vector.get_vector()) + for x in range(1, n+1) + ]) + com_vector_copies.highlight(TEAL) + return com_vector_copies + com_vector_update = UpdateFromFunc( + com_vector, + lambda v : v.put_start_and_end_on(origin, com_dot.get_center()) + ) + + circle = Circle(color = TEAL) + circle.surround(com_dot, buffer_factor = 1.2) + + time_span = Rectangle( + stroke_width = 0, + fill_color = TEAL, + fill_opacity = 0.4 + ) + axes = self.time_axes + time_span.replace( + Line(axes.coords_to_point(0, 0), axes.coords_to_point(3, 1.5)), + stretch = True + ) + time_span.save_state() + time_span.stretch(0, 0, about_edge = LEFT) + + graph = self.graph + short_graph, long_graph = [ + axes.get_graph( + graph.underlying_function, x_min = 0, x_max = t_max, + ).match_style(graph) + for t_max in 3, 6 + ] + for g in short_graph, long_graph: + self.get_polarized_mobject(g, freq = self.initial_winding_frequency) + + self.play( + FocusOn(circle, run_time = 2), + Succession( + ShowCreation, circle, + FadeOut, circle, + ), + ) + self.play( + com_dot.fade, 0.5, + FadeIn(vector_to_scale) + ) + self.wait() + self.play(vector_to_scale.scale, 4, {"about_point" : origin}) + self.wait() + self.play( + FadeOut(vector_to_scale), + FadeIn(com_vector), + ) + self.remove(graph.polarized_mobject) + self.play( + com_dot.move_to, + center_of_mass(short_graph.polarized_mobject.points), + com_vector_update, + time_span.restore, + ShowCreation(short_graph.polarized_mobject), + ) + self.wait() + # dot = Dot(fill_opacity = 0.5).move_to(time_span) + # self.play( + # dot.move_to, com_vector, + # dot.set_fill, {"opacity" : 0}, + # remover = True + # ) + com_vector_copies = get_com_vector_copies(2) + self.play(*[ + ReplacementTransform( + com_vector.copy(), cvc, + path_arc = -TAU/10 + ) + for cvc in com_vector_copies + ]) + self.wait() + + #Squish_graph + to_squish = VGroup( + axes, graph, + time_span, + ) + to_squish.generate_target() + squish_factor = 0.75 + to_squish.target.stretch(squish_factor, 0, about_edge = LEFT) + pairs = zip( + to_squish.family_members_with_points(), + to_squish.target.family_members_with_points() + ) + to_unsquish = list(axes.x_axis.numbers) + list(axes.labels) + for sm, tsm in pairs: + if sm in to_unsquish: + tsm.stretch(1/squish_factor, 0) + if sm is axes.background_rectangle: + tsm.stretch(1/squish_factor, 0, about_edge = LEFT) + + long_graph.stretch(squish_factor, 0) + self.play( + MoveToTarget(to_squish), + FadeOut(com_vector_copies) + ) + long_graph.move_to(graph, LEFT) + self.play( + com_dot.move_to, + center_of_mass(long_graph.polarized_mobject.points), + com_vector_update, + time_span.stretch, 2, 0, {"about_edge" : LEFT}, + *[ + ShowCreation( + mob, + rate_func = lambda a : interpolate( + 0.5, 1, smooth(a) + ) + ) + for mob in long_graph, long_graph.polarized_mobject + ], + run_time = 2 + ) + self.remove(graph, short_graph.polarized_mobject) + self.graph = long_graph + self.wait() + self.play(FocusOn(com_dot)) + com_vector_copies = get_com_vector_copies(5) + self.play(*[ + ReplacementTransform( + com_vector.copy(), cvc, + path_arc = -TAU/10 + ) + for cvc in com_vector_copies + ]) + self.wait() + + # Scale graph out even longer + to_shift = VGroup(self.integral, self.integral_rect) + to_fade = VGroup( + self.integral_brace, self.integral_label, + self.frac, self.frac_cross + ) + self.play( + to_shift.shift, 2*DOWN, + FadeOut(to_fade), + axes.background_rectangle.stretch, 2, 0, {"about_edge" : LEFT}, + Animation(axes), + Animation(self.graph), + FadeOut(com_vector_copies), + ) + self.change_frequency(2.0, added_anims = [com_vector_update]) + very_long_graph = axes.get_graph( + graph.underlying_function, + x_min = 0, x_max = 12, + ) + very_long_graph.match_style(graph) + self.get_polarized_mobject(very_long_graph, freq = 2.0) + self.play( + com_dot.move_to, + center_of_mass(very_long_graph.polarized_mobject.points), + com_vector_update, + ShowCreation( + very_long_graph, + rate_func = lambda a : interpolate(0.5, 1, a) + ), + ShowCreation(very_long_graph.polarized_mobject) + ) + self.remove(graph, graph.polarized_mobject) + self.graph = very_long_graph + self.wait() + self.play( + com_vector.scale, 12, {"about_point" : origin}, + run_time = 2 + ) + # com_vector_copies = get_com_vector_copies(11) + # self.play(ReplacementTransform( + # VGroup(com_vector.copy()), + # com_vector_copies, + # path_arc = TAU/10, + # run_time = 1.5, + # submobject_mode = "lagged_start" + # )) + self.wait() + + self.com_vector = com_vector + self.com_vector_update = com_vector_update + self.com_vector_copies = com_vector_copies + + def comment_on_current_signal(self): + graph = self.graph + com_dot = self.center_of_mass_dot + com_vector = self.com_vector + com_vector_update = self.com_vector_update + axes = self.time_axes + origin = self.circle_plane.coords_to_point(0, 0) + wps_label = self.winding_freq_label + + new_com_vector_update = UpdateFromFunc( + com_vector, lambda v : v.put_start_and_end_on( + origin, com_dot.get_center() + ).scale(12, about_point = origin) + ) + + v_lines = self.get_v_lines_indicating_periods( + freq = 1.0, n_lines = 3 + )[:2] + graph_portion = axes.get_graph( + graph.underlying_function, x_min = 1, x_max = 2 + ) + graph_portion.highlight(TEAL) + bps_label = TextMobject("2 beats per second") + bps_label.scale(0.75) + bps_label.next_to(graph_portion, UP, aligned_edge = LEFT) + bps_label.shift(SMALL_BUFF*RIGHT) + bps_label.add_background_rectangle() + + self.play( + ShowCreation(v_lines, submobject_mode = "all_at_once"), + ShowCreation(graph_portion), + FadeIn(bps_label), + ) + self.wait() + self.play(ReplacementTransform( + bps_label[1][0].copy(), wps_label[1] + )) + self.wait() + self.play( + com_vector.scale, 0.5, {"about_point" : origin}, + rate_func = there_and_back, + run_time = 2 + ) + self.wait(2) + self.change_frequency(2.5, + added_anims = [new_com_vector_update], + run_time = 20, + rate_func = None, + ) + self.wait() + +class TakeAStepBack(TeacherStudentsScene): + def construct(self): + self.student_says( + "Hang on, go over \\\\ that again?", + target_mode = "confused" + ), + self.change_student_modes(*["confused"]*3) + self.play(self.teacher.change, "happy") + self.wait(3) + - def what_this_means_for_various_winding_frequencies(self): - pass class CloseWithAPuzzle(TeacherStudentsScene): From 0fa5727d587c64398917ac12c8e223f2b50781d0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Jan 2018 13:13:39 -0800 Subject: [PATCH 06/33] Up to BoundsAtInfinity in fourier --- active_projects/fourier.py | 533 +++++++++++++++++++++++++++++++++++-- 1 file changed, 513 insertions(+), 20 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index cd17cf71..d3846268 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -47,6 +47,88 @@ def get_fourier_transform( ## +class Introduction(TeacherStudentsScene): + def construct(self): + title = TextMobject("Fourier Transform") + title.scale(1.2) + title.to_edge(UP, buff = MED_SMALL_BUFF) + + func = lambda t : np.cos(2*TAU*t) + np.cos(3*TAU*t) + graph = FunctionGraph(func, x_min = 0, x_max = 5) + graph.stretch(0.25, 1) + graph.next_to(title, DOWN) + graph.to_edge(LEFT) + graph.highlight(BLUE) + fourier_graph = FunctionGraph( + get_fourier_transform(func, 0, 5), + x_min = 0, x_max = 5 + ) + fourier_graph.move_to(graph) + fourier_graph.to_edge(RIGHT) + fourier_graph.highlight(RED) + arrow = Arrow(graph, fourier_graph, color = WHITE) + self.add(title, graph) + + self.student_thinks( + "What's that?", + look_at_arg = title, + target_mode = "confused", + student_index = 1, + ) + self.play( + GrowArrow(arrow), + ReplacementTransform(graph.copy(), fourier_graph) + ) + self.wait(2) + self.student_thinks( + "Pssht, I got this", + target_mode = "tease", + student_index = 2, + added_anims = [RemovePiCreatureBubble(self.students[1])] + ) + self.play(self.teacher.change, "hesitant") + self.wait(2) + +class TODOInsertUnmixingSound(TODOStub): + CONFIG = { + "message" : "Show unmixing sound" + } + +class OtherContexts(PiCreatureScene): + def construct(self): + items = VGroup(*map(TextMobject, [ + "Extracting frequencies from sound", + "Uncertainty principle", + "Riemann Zeta function and primes", + "Differential equations", + ])) + items.arrange_submobjects( + DOWN, buff = MED_LARGE_BUFF, + aligned_edge = LEFT + ) + items.to_corner(UP+LEFT) + items[1:].set_fill(opacity = 0.2) + + morty = self.pi_creature + morty.to_corner(UP+RIGHT) + + self.add(items) + for item in items[1:]: + self.play( + LaggedStart( + ApplyMethod, item, + lambda m : (m.set_fill, {"opacity" : 1}), + ), + morty.change, "thinking", + ) + self.wait() + + +class TODOInsertCosineWrappingAroundCircle(TODOStub): + CONFIG = { + "message" : "Give a picture-in-picture \\\\ of cosine wrapping around circle", + } + class AddingPureFrequencies(PiCreatureScene): CONFIG = { "A_frequency" : 2.1, @@ -721,13 +803,14 @@ class FourierMachineScene(Scene): "x_max" : 5.0, "x_axis_config" : { "unit_size" : 1.4, + "numbers_to_show" : range(1, 6), }, "y_min" : -1.0, "y_max" : 1.0, "y_axis_config" : { "unit_size" : 1.8, "tick_frequency" : 0.5, - "line_to_number_vect" : LEFT, + "label_direction" : LEFT, }, "color" : TEAL, }, @@ -783,7 +866,7 @@ class FourierMachineScene(Scene): def get_frequency_axes(self): frequency_axes = Axes(**self.frequency_axes_config) - frequency_axes.x_axis.add_numbers(*range(1, 6)) + frequency_axes.x_axis.add_numbers() frequency_axes.y_axis.add_numbers( *frequency_axes.y_axis.get_tick_numbers() ) @@ -1089,7 +1172,7 @@ class WrapCosineGraphAroundCircle(FourierMachineScene): freq_label = self.winding_freq_label[0] count = 0 - for target_freq in [1.23, 0.2, 1.55, self.signal_frequency]: + for target_freq in [1.23, 0.2, 0.79, 1.55, self.signal_frequency]: self.play( Transform( v_lines, @@ -1466,10 +1549,14 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): ) return self.fourier_graph_drawing_update_anim - def generate_center_of_mass_dot_update_anim(self): + def generate_center_of_mass_dot_update_anim(self, multiplier = 1): + origin = self.circle_plane.coords_to_point(0, 0) + com = self.get_pol_graph_center_of_mass self.center_of_mass_dot_anim = UpdateFromFunc( self.center_of_mass_dot, - lambda d : d.move_to(self.get_pol_graph_center_of_mass()) + lambda d : d.move_to( + multiplier*(com()-origin)+origin + ) ) def change_frequency(self, new_freq, **kwargs): @@ -1478,16 +1565,13 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): "rate_func", bezier([0, 0, 1, 1]) ) added_anims = kwargs.get("added_anims", []) - freq_label = filter( - lambda sm : isinstance(sm, DecimalNumber), - self.winding_freq_label - )[0] - anims = [ - ChangeDecimalToValue(freq_label, new_freq), - self.get_frequency_change_animation( - self.graph, new_freq - ) - ] + anims = [self.get_frequency_change_animation(self.graph, new_freq)] + if hasattr(self, "winding_freq_label"): + freq_label = filter( + lambda sm : isinstance(sm, DecimalNumber), + self.winding_freq_label + )[0] + anims.append(ChangeDecimalToValue(freq_label, new_freq)) if hasattr(self, "v_lines_indicating_periods"): anims.append(self.get_period_v_lines_update_anim()) if hasattr(self, "center_of_mass_dot"): @@ -2401,6 +2485,11 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): "default_num_v_lines_indicating_periods" : 0, "time_axes_scale_val" : 0.7, "initial_winding_frequency" : 0.1, + "circle_plane_config" : { + "unit_size" : 2, + "y_radius" : SPACE_HEIGHT+LARGE_BUFF, + "x_radius" : SPACE_WIDTH+LARGE_BUFF + } } def construct(self): self.remove(self.pi_creature) @@ -2413,11 +2502,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): self.find_center_of_mass() def setup_plane(self): - circle_plane = ComplexPlane( - unit_size = 2, - y_radius = SPACE_HEIGHT+LARGE_BUFF, - x_radius = SPACE_WIDTH+LARGE_BUFF - ) + circle_plane = ComplexPlane(**self.circle_plane_config) circle_plane.shift(DOWN+LEFT) circle = DashedLine(ORIGIN, TAU*UP) circle.apply_complex_function( @@ -3339,8 +3424,416 @@ class TakeAStepBack(TeacherStudentsScene): self.play(self.teacher.change, "happy") self.wait(3) +class SimpleCosineWrappingAroundCircle(WriteComplexExponentialExpression): + CONFIG = { + "initial_winding_frequency" : 0, + "circle_plane_config" : { + "unit_size" : 3, + }, + } + def construct(self): + self.setup_plane() + self.setup_graph() + self.remove(self.pi_creature) + self.winding_freq_label.shift(7*LEFT) + VGroup(self.time_axes, self.graph).shift(4*UP) + VGroup( + self.circle_plane, + self.graph.polarized_mobject + ).move_to(ORIGIN) + self.add(self.get_center_of_mass_dot()) + self.generate_center_of_mass_dot_update_anim() + + self.change_frequency( + 2.0, + rate_func = None, + run_time = 30 + ) + self.wait() + +class SummarizeTheFullTransform(DrawFrequencyPlot): + CONFIG = { + "time_axes_config" : { + "x_max" : 4.5, + "x_axis_config" : { + "unit_size" : 1.2, + "tick_frequency" : 0.5, + # "numbers_with_elongated_ticks" : range(0, 10, 2), + # "numbers_to_show" : range(0, 10, 2), + } + }, + "frequency_axes_config" : { + "x_max" : 5, + "x_axis_config" : { + "unit_size" : 1, + "numbers_to_show" : range(1, 5), + }, + "y_max" : 2, + "y_min" : -2, + "y_axis_config" : { + "unit_size" : 0.75, + "tick_frequency" : 1, + }, + }, + } + def construct(self): + self.setup_all_axes() + self.show_transform_function() + self.show_winding() + + def setup_all_axes(self): + time_axes = self.get_time_axes() + time_label, intensity_label = time_axes.labels + time_label.next_to( + time_axes.x_axis.get_right(), + DOWN, SMALL_BUFF + ) + intensity_label.next_to(time_axes.y_axis, UP, buff = SMALL_BUFF) + intensity_label.to_edge(LEFT) + + frequency_axes = self.get_frequency_axes() + frequency_axes.to_corner(UP+RIGHT) + frequency_axes.shift(RIGHT) + fy_axis = frequency_axes.y_axis + for number in fy_axis.numbers: + number.add_background_rectangle() + fy_axis.remove(*fy_axis.numbers[1::2]) + frequency_axes.remove(frequency_axes.box) + frequency_axes.label.shift_onto_screen() + + circle_plane = self.get_circle_plane() + + self.set_variables_as_attrs(time_axes, frequency_axes, circle_plane) + self.add(time_axes) + + def show_transform_function(self): + time_axes = self.time_axes + frequency_axes = self.frequency_axes + def func(t): + return 0.5*(2+np.cos(2*TAU*t) + np.cos(3*TAU*t)) + fourier_func = get_fourier_transform( + func, + t_min = time_axes.x_min, + t_max = time_axes.x_max, + use_almost_fourier = False, + ) + + graph = time_axes.get_graph(func) + graph.highlight(GREEN) + fourier_graph = frequency_axes.get_graph(fourier_func) + fourier_graph.highlight(RED) + + g_t = TexMobject("g(t)") + g_t[-2].match_color(graph) + g_t.next_to(graph, UP) + g_hat_f = TexMobject("\\hat g(f)") + g_hat_f[-2].match_color(fourier_graph) + g_hat_f.next_to( + frequency_axes.input_to_graph_point(2, fourier_graph), + UP + ) + + morty = self.pi_creature + + time_label = time_axes.labels[0] + frequency_label = frequency_axes.label + for label in time_label, frequency_label: + label.rect = SurroundingRectangle(label) + time_label.rect.match_style(graph) + frequency_label.rect.match_style(fourier_graph) + + self.add(graph) + g_t.save_state() + g_t.move_to(morty, UP+LEFT) + g_t.fade(1) + self.play( + morty.change, "raise_right_hand", + g_t.restore, + ) + self.wait() + self.play(Write(frequency_axes, run_time = 1)) + self.play( + ReplacementTransform(graph.copy(), fourier_graph), + ReplacementTransform(g_t.copy(), g_hat_f), + ) + self.wait(2) + for label in time_label, frequency_label: + self.play( + ShowCreation(label.rect), + morty.change, "thinking" + ) + self.play(FadeOut(label.rect)) + self.wait() + + self.set_variables_as_attrs( + graph, fourier_graph, + g_t, g_hat_f + ) + + def show_winding(self): + plane = self.circle_plane + graph = self.graph + fourier_graph = self.fourier_graph + morty = self.pi_creature + g_hat_f = self.g_hat_f + g_hat_f_rect = SurroundingRectangle(g_hat_f) + g_hat_f_rect.highlight(TEAL) + g_hat_rect = SurroundingRectangle(g_hat_f[0]) + g_hat_rect.match_style(g_hat_f_rect) + + g_hat_f.generate_target() + g_hat_f.target.next_to(plane, RIGHT) + g_hat_f.target.shift(UP) + arrow = Arrow( + g_hat_f.target.get_left(), + plane.coords_to_point(0, 0), + color = self.center_of_mass_color, + ) + + frequency_axes = self.frequency_axes + imaginary_fourier_graph = frequency_axes.get_graph( + get_fourier_transform( + graph.underlying_function, + t_min = self.time_axes.x_min, + t_max = self.time_axes.x_max, + real_part = False, + use_almost_fourier = False, + ) + ) + imaginary_fourier_graph.highlight(BLUE) + imaginary_fourier_graph.shift( + frequency_axes.x_axis.main_line.get_right() - \ + imaginary_fourier_graph.points[-1], + ) + + real_part = TextMobject( + "Real part of", "$\\hat g(f)$" + ) + real_part[1].match_style(g_hat_f) + real_part.move_to(g_hat_f) + real_part.to_edge(RIGHT) + + self.get_polarized_mobject(graph, freq = 0) + update_pol_graph = UpdateFromFunc( + graph.polarized_mobject, + lambda m : m.set_stroke(width = 2) + ) + com_dot = self.get_center_of_mass_dot() + + winding_run_time = 40.0 + g_hat_f_indication = Succession( + Animation, Mobject(), {"run_time" : 4}, + FocusOn, g_hat_f, + ShowCreation, g_hat_f_rect, + Animation, Mobject(), + Transform, g_hat_f_rect, g_hat_rect, + Animation, Mobject(), + FadeOut, g_hat_f_rect, + Animation, Mobject(), + MoveToTarget, g_hat_f, + UpdateFromAlphaFunc, com_dot, lambda m, a : m.set_fill(opacity = a), + Animation, Mobject(), {"run_time" : 2}, + GrowArrow, arrow, + FadeOut, arrow, + Animation, Mobject(), {"run_time" : 5}, + Transform, g_hat_f.target.copy().fade(1), real_part[1].copy().fade(1), + Write, real_part[0], {"run_time" : 1}, + Animation, Mobject(), {"run_time" : 3}, + ShowCreation, imaginary_fourier_graph, {"run_time" : 3}, + rate_func = squish_rate_func( + lambda x : x, 0, 31./winding_run_time + ), + run_time = winding_run_time + ) + + self.play( + FadeIn(plane), + ReplacementTransform( + graph.copy(), graph.polarized_mobject + ), + morty.change, "happy", + ) + self.generate_center_of_mass_dot_update_anim(multiplier = 4.5) + self.generate_fourier_dot_transform(fourier_graph) + self.change_frequency( + 5.0, + rate_func = None, + run_time = winding_run_time, + added_anims = [ + g_hat_f_indication, + update_pol_graph, + Animation(frequency_axes.x_axis.numbers), + Animation(self.fourier_graph_dot), + ] + ) + self.wait() + +class SummarizeFormula(Scene): + def construct(self): + expression = self.get_expression() + screen_rect = ScreenRectangle(height = 5) + screen_rect.to_edge(DOWN) + + exp_rect, g_exp_rect, int_rect = [ + SurroundingRectangle(VGroup( + expression.get_part_by_tex(p1), + expression.get_part_by_tex(p2), + )) + for p1, p2 in ("e", "t}"), ("g({}", "t}"), ("\\int", "dt") + ] + + self.add(expression) + self.wait() + self.play( + ShowCreation(screen_rect), + ShowCreation(exp_rect), + ) + self.wait(2) + self.play(Transform(exp_rect, g_exp_rect)) + self.wait(2) + self.play(Transform(exp_rect, int_rect)) + self.wait(2) + + def get_expression(self): + expression = TexMobject( + "\\hat g(", "f", ")", "=", "\\int", "_{t_1}", "^{t_2}", + "g({}", "t", ")", "e", "^{2\\pi i", "f", "t}", "dt" + ) + expression.highlight_by_tex( + "t", YELLOW, substring = False, + ) + expression.highlight_by_tex("t}", YELLOW) + expression.highlight_by_tex( + "f", RED, substring = False, + ) + expression.scale(1.2) + expression.to_edge(UP) + return expression +class OneSmallNote(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Just one \\\\ small note...", + # target_mode = + ) + self.change_student_modes("erm", "happy", "sassy") + self.wait(2) + +class BoundsAtInfinity(SummarizeFormula): + def construct(self): + expression = self.get_expression() + self.add(expression) + self.add_graph() + axes = self.axes + graph = self.graph + + time_interval = self.get_time_interval(-2, 2) + wide_interval = self.get_time_interval(-SPACE_WIDTH, SPACE_WIDTH) + bounds = VGroup(*reversed(expression.get_parts_by_tex("t_"))) + bound_rects = VGroup(*[ + SurroundingRectangle(b, buff = 0.5*SMALL_BUFF) + for b in bounds + ]) + bound_rects.highlight(TEAL) + inf_bounds = VGroup(*[ + VGroup(TexMobject(s + "\\infty")) + for s in "-", "+" + ]) + decimal_bounds = VGroup(*[DecimalNumber(0) for x in range(2)]) + for bound, inf_bound, d_bound in zip(bounds, inf_bounds, decimal_bounds): + for new_bound in inf_bound, d_bound: + new_bound.scale(0.7) + new_bound.move_to(bound, LEFT) + new_bound.bound = bound + def get_db_num_update(vect): + return lambda a : axes.x_axis.point_to_number( + time_interval.get_edge_center(vect) + ) + decimal_updates = [ + ChangingDecimal( + db, get_db_num_update(vect), + position_update_func = lambda m : m.move_to( + m.bound, LEFT + ) + ) + for db, vect in zip(decimal_bounds, [LEFT, RIGHT]) + ] + for update in decimal_updates: + update.update(1) + + time_interval.save_state() + self.wait() + self.play(ReplacementTransform( + self.get_time_interval(0, 0.01), time_interval + )) + self.play(LaggedStart(ShowCreation, bound_rects)) + self.wait() + self.play(FadeOut(bound_rects)) + self.play(ReplacementTransform(bounds, inf_bounds)) + self.play(Transform( + time_interval, wide_interval, + run_time = 4, + rate_func = there_and_back + )) + self.play( + ReplacementTransform(inf_bounds, decimal_bounds), + time_interval.restore, + ) + self.play( + VGroup(axes, graph).stretch, 0.05, 0, + Transform(time_interval, wide_interval), + UpdateFromAlphaFunc( + axes.x_axis.numbers, + lambda m, a : m.set_fill(opacity = 1-a) + ), + *decimal_updates, + run_time = 8, + rate_func = bezier([0, 0, 1, 1]) + ) + self.wait() + + + def add_graph(self): + axes = Axes( + x_min = -140, + x_max = 140, + y_min = -2, + y_max = 2, + number_line_config = { + "include_tip" : False, + }, + default_num_graph_points = 1000, + ) + axes.x_axis.add_numbers(*filter( + lambda x : x != 0, + range(-8, 10, 2), + )) + axes.shift(DOWN) + self.add(axes) + + def func(x): + return np.exp(-0.1*x**2)*(1 + np.cos(TAU*x)) + graph = axes.get_graph(func) + self.add(graph) + graph.highlight(YELLOW) + + self.set_variables_as_attrs(axes, graph) + + def get_time_interval(self, t1, t2): + line = Line(*[ + self.axes.coords_to_point(t, 0) + for t in t1, t2 + ]) + rect = Rectangle( + stroke_width = 0, + fill_color = TEAL, + fill_opacity = 0.5, + ) + rect.match_width(line) + rect.stretch_to_fit_height(2.5) + rect.move_to(line, DOWN) + return rect class CloseWithAPuzzle(TeacherStudentsScene): From afc0c0d5ae7aad57cf03beb0f1a651c38c33fd99 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 25 Jan 2018 23:23:04 +0100 Subject: [PATCH 07/33] 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 08/33] 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): From 3e34c29509e804e029e9b215a1eb947a5e164035 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Jan 2018 23:50:32 -0800 Subject: [PATCH 09/33] Bug fix to PiCreatureScene handling bubbles --- topics/characters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/topics/characters.py b/topics/characters.py index 3e2214a2..5f454c00 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -443,7 +443,9 @@ class PiCreatureScene(Scene): added_anims = kwargs.pop("added_anims", []) anims = [] - on_screen_mobjects = self.get_mobjects() + on_screen_mobjects = self.camera.extract_mobject_family_members( + self.get_mobjects() + ) def has_bubble(pi): return hasattr(pi, "bubble") and \ pi.bubble is not None and \ @@ -478,7 +480,7 @@ class PiCreatureScene(Scene): ] anims += added_anims - self.play(*anims) + self.play(*anims, **kwargs) def pi_creature_says(self, *args, **kwargs): self.introduce_bubble( From 5d811d2e817887f4eeaf2ba2d22c0223765fb332 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Jan 2018 23:50:52 -0800 Subject: [PATCH 10/33] Added kwargs to match_width, etc. methods --- mobject/mobject.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index f079b330..a6dfc7e4 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -454,14 +454,14 @@ class Mobject(object): **kwargs ) - def match_width(self, mobject): - return self.match_dim(mobject, 0) + def match_width(self, mobject, **kwargs): + return self.match_dim(mobject, 0, **kwargs) - def match_height(self, mobject): - return self.match_dim(mobject, 1) + def match_height(self, mobject, **kwargs): + return self.match_dim(mobject, 1, **kwargs) - def match_depth(self, mobject): - return self.match_dim(mobject, 2) + def match_depth(self, mobject, **kwargs): + return self.match_dim(mobject, 2, **kwargs) ## Color functions From 4d7bb5c3f1634aa73290f530d9206410cdd51ce7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Jan 2018 23:51:36 -0800 Subject: [PATCH 11/33] Updated stage_scenes to write to a directory unique to the animation file --- stage_scenes.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/stage_scenes.py b/stage_scenes.py index e79d341c..7177d614 100644 --- a/stage_scenes.py +++ b/stage_scenes.py @@ -18,9 +18,7 @@ def get_sorted_scene_names(module_name): for line_no in sorted(line_to_scene.keys()) ] - - -def stage_animaions(module_name): +def stage_animations(module_name): scene_names = get_sorted_scene_names(module_name) animation_dir = os.path.join( ANIMATIONS_DIR, module_name.replace(".py", "") @@ -32,19 +30,27 @@ def stage_animaions(module_name): sorted_files.append( os.path.join(animation_dir, clip) ) - for f in os.listdir(STAGED_SCENES_DIR): - os.remove(os.path.join(STAGED_SCENES_DIR, f)) - for f, count in zip(sorted_files, it.count()): + staged_scenes_dir = os.path.join(animation_dir, "staged_scenes") + count = 0 + while True: + staged_scenes_dir = os.path.join( + animation_dir, "staged_scenes_%d"%count + ) + if not os.path.exists(staged_scenes_dir): + os.makedirs(staged_scenes_dir) + break + #Otherwise, keep trying new names until + #there is a free one + count += 1 + for count, f in enumerate(sorted_files): symlink_name = os.path.join( - STAGED_SCENES_DIR, + staged_scenes_dir, "Scene_%03d"%count + f.split(os.sep)[-1] ) os.symlink(f, symlink_name) - - if __name__ == "__main__": if len(sys.argv) < 2: raise Exception("No module given.") module_name = sys.argv[1] - stage_animaions(module_name) \ No newline at end of file + stage_animations(module_name) \ No newline at end of file From dc3b43591d04afe94f81ceb8bda08e11743cd5ab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Jan 2018 23:51:51 -0800 Subject: [PATCH 12/33] Preliminary end to fourier project --- active_projects/fourier.py | 310 ++++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 7 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index d3846268..8cbcf953 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from helpers import * import scipy @@ -1064,6 +1065,7 @@ class WrapCosineGraphAroundCircle(FourierMachineScene): } def construct(self): self.show_initial_signal() + self.show_finite_interval() self.wrap_around_circle() self.show_time_sweeps() self.compare_two_frequencies() @@ -1101,6 +1103,38 @@ class WrapCosineGraphAroundCircle(FourierMachineScene): self.beats_per_second_label = words self.graph = graph + def show_finite_interval(self): + axes = self.time_axes + v_line = DashedLine( + axes.coords_to_point(0, 0), + axes.coords_to_point(0, axes.y_max), + color = RED, + stroke_width = 6, + ) + h_line = Line( + axes.coords_to_point(0, 0), + axes.coords_to_point(axes.x_max, 0), + ) + rect = Rectangle( + stroke_width = 0, + fill_color = TEAL, + fill_opacity = 0.5, + ) + rect.match_height(v_line) + rect.match_width(h_line, stretch = True) + rect.move_to(v_line, DOWN+LEFT) + right_v_line = v_line.copy() + right_v_line.move_to(rect, RIGHT) + + rect.save_state() + rect.stretch(0, 0, about_edge = ORIGIN) + self.play(rect.restore, run_time = 2) + self.play(FadeOut(rect)) + for line in v_line, right_v_line: + self.play(ShowCreation(line)) + self.play(FadeOut(line)) + self.wait() + def wrap_around_circle(self): graph = self.graph freq = self.initial_winding_frequency @@ -1338,7 +1372,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): com_label = self.center_of_mass_label com_label.add_background_rectangle() frequency_axes = self.get_frequency_axes() - x_coord_label = TextMobject("$x$-coordiante for center of mass") + x_coord_label = TextMobject("$x$-coordiate for center of mass") x_coord_label.highlight(self.center_of_mass_color) x_coord_label.scale(self.text_scale_val) x_coord_label.next_to( @@ -1594,6 +1628,20 @@ class StudentsHorrifiedAtScene(TeacherStudentsScene): ) self.wait(4) +class AskAboutAlmostFouierName(TeacherStudentsScene): + def construct(self): + self.student_says( + "``Almost'' Fourier transform?", + target_mode = "sassy" + ) + self.change_student_modes("confused", "sassy", "confused") + self.wait() + self.teacher_says( + "We'll get to the real \\\\ one in a few minutes", + added_anims = [self.get_student_changes(*["plain"]*3)] + ) + self.wait(2) + class ShowLowerFrequency(DrawFrequencyPlot): CONFIG = { "signal_frequency" : 2.0, @@ -2126,6 +2174,15 @@ class ShowCommutativeDiagram(ShowLinearity): spike_rect.set_fill(YELLOW, 0.5) return spike_rect +class PauseAndPonder(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Pause and \\\\ ponder!", + target_mode = "hooray" + ) + self.change_student_modes(*["thinking"]*3) + self.wait(4) + class BeforeGettingToTheFullMath(TeacherStudentsScene): def construct(self): formula = TexMobject( @@ -2786,7 +2843,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): Write(time_label), GrowArrow(time_label.arrow), ) - self.wait(6.5) #Leave time to say let's slow down + self.wait(12.5) #Leave time to say let's slow down self.remove(ambient_ghost_dot_movement) self.play( FadeOut(time_label), @@ -3045,6 +3102,16 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot): ) return VGroup(time_graph.dots, pol_graph.dots) +class EulersFormulaViaGroupTheoryWrapper(Scene): + def construct(self): + title = TextMobject("Euler's formula with introductory group theory") + title.to_edge(UP) + screen_rect = ScreenRectangle(height = 6) + screen_rect.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen_rect)) + self.wait(2) + class WhyAreYouTellingUsThis(TeacherStudentsScene): def construct(self): self.student_says("Why are you \\\\ telling us this?") @@ -3636,8 +3703,7 @@ class SummarizeTheFullTransform(DrawFrequencyPlot): GrowArrow, arrow, FadeOut, arrow, Animation, Mobject(), {"run_time" : 5}, - Transform, g_hat_f.target.copy().fade(1), real_part[1].copy().fade(1), - Write, real_part[0], {"run_time" : 1}, + Write, real_part, {"run_time" : 2}, Animation, Mobject(), {"run_time" : 3}, ShowCreation, imaginary_fourier_graph, {"run_time" : 3}, rate_func = squish_rate_func( @@ -3710,7 +3776,6 @@ class SummarizeFormula(Scene): expression.to_edge(UP) return expression - class OneSmallNote(TeacherStudentsScene): def construct(self): self.teacher_says( @@ -3788,7 +3853,7 @@ class BoundsAtInfinity(SummarizeFormula): lambda m, a : m.set_fill(opacity = 1-a) ), *decimal_updates, - run_time = 8, + run_time = 12, rate_func = bezier([0, 0, 1, 1]) ) self.wait() @@ -3835,6 +3900,155 @@ class BoundsAtInfinity(SummarizeFormula): rect.move_to(line, DOWN) return rect +class MoreToCover(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Much more to say...", + target_mode = "hooray", + run_time = 1, + ) + self.wait() + self.teacher_says( + "SO MUCH!", + target_mode = "surprised", + added_anims = [self.get_student_changes(*3*["happy"])], + run_time = 0.5 + ) + self.wait(2) + +class ShowUncertaintyPrinciple(Scene): + def construct(self): + title = TextMobject("Uncertainty principle") + self.add(title) + top_axes = Axes( + x_min = -SPACE_WIDTH, + x_max = SPACE_WIDTH, + y_min = 0, + y_max = 3, + y_axis_config = { + "unit_size" : 0.6, + "include_tip" : False, + } + ) + bottom_axes = top_axes.deepcopy() + arrow = Vector(DOWN, color = WHITE) + group = VGroup( + title, top_axes, arrow, bottom_axes + ) + group.arrange_submobjects(DOWN) + title.shift(MED_SMALL_BUFF*UP) + group.to_edge(UP) + fourier_word = TextMobject("Fourier transform") + fourier_word.next_to(arrow, RIGHT) + self.add(group, fourier_word) + + ghost_dot = Dot(RIGHT, fill_opacity = 0) + def get_bell_func(factor = 1): + return lambda x : 2*np.exp(-factor*x**2) + top_graph = top_axes.get_graph(get_bell_func()) + top_graph.highlight(YELLOW) + bottom_graph = bottom_axes.get_graph(get_bell_func()) + bottom_graph.highlight(RED) + def get_update_func(axes): + def update_graph(graph): + f = ghost_dot.get_center()[0] + if axes == bottom_axes: + f = 1./f + new_graph = axes.get_graph(get_bell_func(f)) + graph.points = new_graph.points + return update_graph + + factors = [0.3, 0.1, 2, 10, 100, 0.01, 0.5] + + self.play(ShowCreation(top_graph)) + self.play(ReplacementTransform( + top_graph.copy(), + bottom_graph, + )) + self.wait(2) + self.add(*[ + ContinualUpdateFromFunc(graph, get_update_func(axes)) + for graph, axes in (top_graph, top_axes), (bottom_graph, bottom_axes) + ]) + for factor in factors: + self.play( + ghost_dot.move_to, factor*RIGHT, + run_time = 2 + ) + self.wait() + +class XCoordinateLabelTypoFix(Scene): + def construct(self): + words = TextMobject("$x$-coordinate for center of mass") + words.highlight(RED) + self.add(words) + +class NextVideoWrapper(Scene): + def construct(self): + title = TextMobject("Next video") + title.to_edge(UP) + screen_rect = ScreenRectangle(height = 6) + screen_rect.next_to(title, DOWN) + self.add(title) + self.play(ShowCreation(screen_rect)) + self.wait(2) + +class SubscribeOrBinge(PiCreatureScene): + def construct(self): + morty = self.pi_creature + morty.center().to_edge(DOWN, LARGE_BUFF) + subscribe = TextMobject("Subscribe") + subscribe.highlight(RED) + subscribe.next_to(morty, UP+RIGHT) + binge = TextMobject("Binge") + binge.highlight(BLUE) + binge.next_to(morty, UP+LEFT) + + videos = VGroup(*[VideoIcon() for x in range(30)]) + colors = it.cycle([BLUE_D, BLUE_E, BLUE_C, GREY_BROWN]) + for video, color in zip(videos, colors): + video.highlight(color) + videos.move_to(binge.get_bottom(), UP) + video_anim = LaggedStart( + Succession, videos, + lambda v : ( + FadeIn, v, + ApplyMethod, v.shift, 5*DOWN, {"run_time" : 6}, + ), + run_time = 10 + ) + sub_arrow = Arrow( + subscribe.get_bottom(), + Dot().to_corner(DOWN+RIGHT, buff = LARGE_BUFF), + color = RED + ) + + for word in subscribe, binge: + word.save_state() + word.shift(DOWN) + word.set_fill(opacity = 0) + + self.play( + subscribe.restore, + morty.change, "raise_left_hand" + ) + self.play(GrowArrow(sub_arrow)) + self.wait() + self.play( + video_anim, + Succession( + AnimationGroup( + ApplyMethod(binge.restore), + ApplyMethod(morty.change, "raise_right_hand", binge), + ), + Blink, morty, + ApplyMethod, morty.change, "shruggie", videos, + Animation, Mobject(), {"run_time" : 2}, + Blink, morty, + Animation, Mobject(), {"run_time" : 4} + ) + ) + class CloseWithAPuzzle(TeacherStudentsScene): def construct(self): @@ -3888,7 +4102,89 @@ class SponsorScreenGrab(PiCreatureScene): self.play(morty.change, mode, screen) self.wait(4) - +class FourierEndScreen(PatreonEndScreen): + CONFIG = { + "specific_patrons" : [ + "CrypticSwarm", + "Ali Yahya", + "Juan Benet", + "Markus Persson", + "Damion Kistler", + "Burt Humburg", + "Yu Jun", + "Dave Nicponski", + "Kaustuv DeBiswas", + "Joseph John Cox", + "Luc Ritchie", + "Achille Brighton", + "Rish Kundalia", + "Yana Chernobilsky", + "Shìmín Kuang", + "Mathew Bramson", + "Jerry Ling", + "Mustafa Mahdi", + "Meshal Alshammari", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Robert Teed", + "One on Epsilon", + "Samantha D. Suplee", + "Mark Govea", + "John Haley", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Boris Veselinovich", + "Ryan Dahl", + "Ripta Pasay", + "Eric Lavault", + "Mads Elvheim", + "Andrew Busey", + "Randall Hunt", + "Desmos", + "Tianyu Ge", + "Awoo", + "Dr David G. Stork", + "Linh Tran", + "Jason Hise", + "Bernd Sing", + "Ankalagon", + "Mathias Jansson", + "David Clark", + "Ted Suzman", + "Eric Chow", + "Michael Gardner", + "Jonathan Eppele", + "Clark Gaebel", + "David Kedmey", + "Jordan Scales", + "Ryan Atallah", + "supershabam", + "1stViewMaths", + "Jacob Magnuson", + "Thomas Tarler", + "Isak Hietala", + "James Thornton", + "Egor Gumenuk", + "Waleed Hamied", + "Oliver Steele", + "Yaw Etse", + "David B", + "Julio Cesar Campo Neto", + "Delton Ding", + "George Chiesa", + "Chloe Zhou", + "Alexander Nye", + "Ross Garber", + "Wang HaoRan", + "Felix Tripier", + "Arthur Zey", + "Norton", + "Kevin Le", + "Alexander Feldman", + "David MacCumber", + ], + } From 3589a292cb5ffd0839fc3e06d1cf14fc7edf1fd8 Mon Sep 17 00:00:00 2001 From: bhbr Date: Fri, 26 Jan 2018 19:33:22 +0100 Subject: [PATCH 13/33] gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/ From 2fd0687b596f47c0c2bfde4f1fa9b6ba5e3c2f2f Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:49:15 +0100 Subject: [PATCH 14/33] Added my playground files (for syncing btw my machines) --- .gitignore | 3 - ben_cairo_test.py | 54 +++++++++ ben_playground.py | 303 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 ben_cairo_test.py create mode 100644 ben_playground.py diff --git a/.gitignore b/.gitignore index f0f3ca3f..29cac87a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ -ben_playground.py - -ben_cairo_test.py diff --git a/ben_cairo_test.py b/ben_cairo_test.py new file mode 100644 index 00000000..85af2269 --- /dev/null +++ b/ben_cairo_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +"""demonstrate pycairo and pygame""" + +from __future__ import print_function + +import math +import sys + +import cairo +import pygame + + +def draw(surface): + x, y, radius = (250, 250, 200) + ctx = cairo.Context(surface) + ctx.set_line_width(15) + ctx.arc(x, y, radius, 0, 2.0 * math.pi) + ctx.set_source_rgb(0.8, 0.8, 0.8) + ctx.fill_preserve() + ctx.set_source_rgb(1, 1, 1) + ctx.stroke() + + +def input(events): + for event in events: + if event.type == pygame.QUIT: + sys.exit(0) + else: + print(event) + + +def main(): + width, height = 512, 512 + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + + pygame.init() + pygame.display.set_mode((width, height)) + screen = pygame.display.get_surface() + + draw(surface) + + # Create PyGame surface from Cairo Surface + buf = surface.get_data() + image = pygame.image.frombuffer(buf, (width, height), "ARGB") + # Tranfer to Screen + screen.blit(image, (0, 0)) + pygame.display.flip() + + while True: + input(pygame.event.get()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ben_playground.py b/ben_playground.py new file mode 100644 index 00000000..1b7445fd --- /dev/null +++ b/ben_playground.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * +from mobject.point_cloud_mobject import PointCloudDot + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.continual_animation import * +from animation.playground import * + +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.combinatorics import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + +from mobject.vectorized_mobject import * + +## To watch one of these scenes, run the following: +## python extract_scene.py -p file_name + +LIGHT_COLOR = YELLOW +DEGREES = 360/TAU +SWITCH_ON_RUN_TIME = 1.5 + + +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * possibly a target + # * an opacity function and its inverse + # * a light color + # * a max opacity + + # If the target is None, create annuli. + # If there is a target, create annular sectors. + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0 + } + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + # create annuli + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_arc_center_to(self.source_point) + self.add(annulus) + + # else: + + # # look for the screen and create annular sectors + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # dr = self.radius / self.num_levels + # for r in np.arange(0, self.radius, dr): + # alpha = self.max_opacity * self.opacity_function(r) + # annular_sector = AnnularSector( + # inner_radius = r, + # outer_radius = r + dr, + # color = self.color, + # fill_opacity = alpha, + # start_angle = lower_angle, + # angle = upper_angle - lower_angle + # ) + # annular_sector.move_arc_center_to(self.source_point) + # self.add(annular_sector) + + + + + + # def redraw(self): + # if self.screen != None: + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # for submob in self.submobjects: + # if type(submob) == AnnularSector: + # submob.start_angle = lower_angle + # submob.angle = upper_angle - lower_angle + # submob.generate_points() + # submob.move_arc_center_to(self.source_point) + + + #submob.generate_points() + + + def move_source_to(self,point): + self.source_point = np.array(point) + for submob in self.submobjects: + if type(submob) == Annulus: + submob.shift(self.source_point - submob.get_center()) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + +class Spotlight(VMobject): + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0, + "screen" : None + } + + def track_screen(self): + self.generate_points() + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + annular_sector.move_arc_center_to(self.source_point) + self.add(annular_sector) + + + def viewing_angle_of_point(self,point): + distance_vector = point - self.source_point + angle = angle_of_vector(distance_vector) + return angle + + def viewing_angles(self,screen): + + viewing_angles = np.array(map(self.viewing_angle_of_point, + screen.get_anchors())) + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: + lower_angle = np.min(viewing_angles) + upper_angle = np.max(viewing_angles) + + return lower_angle, upper_angle + + def move_source_to(self,point): + self.source_point = np.array(point) + self.shift(np.array(point) - self.source_point) + self.generate_points() + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + + +class SwitchOn(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + LaggedStart.__init__(self, + FadeIn, light, **kwargs) + + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + + +# class LightSource(Mobject): + +# # A light source is composed of: +# # * a lighthouse +# # * possibly a screen +# # * and two light fields: +# # * an undirected one (annuli) +# # * one directed at the screen (annular sectors) + +# CONFIG = { +# "location" : ORIGIN, +# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), +# "ambient_light" : LightField(), +# "spot_light_field" : LightField(), +# } + +# def __init__(self,**kwargs): +# Mobject.__init__(self,**kwargs) +# self.icon.next_to(self.location, DOWN, buff = 0) +# self.spot_light_field.max_opacity = 0 +# self.ambient_light_field.move_source_to(self.location) +# self.spot_light_field.move_source_to(self.location) +# self.add(self.icon,self.ambient_light_field,self.spot_light_field) + +# def dim_ambient(self,new_alpha): +# self.ambient_light_field.dimming(new_alpha) + + + +class ScreenTracker(ContinualAnimation): + def __init__(self, spotlight, screen, **kwargs): + self.spotlight = spotlight + self.screen = screen + ContinualAnimation.__init__(self, self.spotlight, **kwargs) + +# def update_mobject(self, dt): + #self.spotlight.generate_points() + + + + +class IntroScene(Scene): + def construct(self): + + screen = Square().shift([4,0,0]) + #ambient_light = AmbientLight( + # source_point = np.array([-1,1,0]), + # max_opacity = 1.0, + # opacity_function = lambda r: 1.0/(r/1+1)**2, + # num_levels = 10, + #) + + #ambient_light.move_source_to([-5,0,0]) + self.add(screen)#,ambient_light) + + spotlight = Spotlight( + source_point = np.array([-1,1,0]), + max_opacity = 1.0, + opacity_function = lambda r: 1.0/(r/2+1)**2, + num_levels = 10, + screen = screen, + ) + + self.add(spotlight) + + ca = ScreenTracker(spotlight,screen) + self.add(ca) + + #self.play(SwitchOn(ambient_light)) + #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) + #self.play(SwitchOn(spotlight)) + #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) + #self.play(ApplyMethod(spotlight.dimming,0.2)) + self.play(screen.rotate, TAU/8, run_time = 3) + self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) + + self.wait() + + + From daadc857c45ab608a8815f45b635def9fcc8c70d Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:50:12 +0100 Subject: [PATCH 15/33] Revert "Added my playground files (for syncing btw my machines)" This reverts commit 2fd0687b596f47c0c2bfde4f1fa9b6ba5e3c2f2f. --- .gitignore | 3 + ben_cairo_test.py | 54 --------- ben_playground.py | 303 ---------------------------------------------- 3 files changed, 3 insertions(+), 357 deletions(-) delete mode 100644 ben_cairo_test.py delete mode 100644 ben_playground.py diff --git a/.gitignore b/.gitignore index 29cac87a..f0f3ca3f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ +ben_playground.py + +ben_cairo_test.py diff --git a/ben_cairo_test.py b/ben_cairo_test.py deleted file mode 100644 index 85af2269..00000000 --- a/ben_cairo_test.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -"""demonstrate pycairo and pygame""" - -from __future__ import print_function - -import math -import sys - -import cairo -import pygame - - -def draw(surface): - x, y, radius = (250, 250, 200) - ctx = cairo.Context(surface) - ctx.set_line_width(15) - ctx.arc(x, y, radius, 0, 2.0 * math.pi) - ctx.set_source_rgb(0.8, 0.8, 0.8) - ctx.fill_preserve() - ctx.set_source_rgb(1, 1, 1) - ctx.stroke() - - -def input(events): - for event in events: - if event.type == pygame.QUIT: - sys.exit(0) - else: - print(event) - - -def main(): - width, height = 512, 512 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - - pygame.init() - pygame.display.set_mode((width, height)) - screen = pygame.display.get_surface() - - draw(surface) - - # Create PyGame surface from Cairo Surface - buf = surface.get_data() - image = pygame.image.frombuffer(buf, (width, height), "ARGB") - # Tranfer to Screen - screen.blit(image, (0, 0)) - pygame.display.flip() - - while True: - input(pygame.event.get()) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/ben_playground.py b/ben_playground.py deleted file mode 100644 index 1b7445fd..00000000 --- a/ben_playground.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env python - -from helpers import * - -from mobject.tex_mobject import TexMobject -from mobject import Mobject -from mobject.image_mobject import ImageMobject -from mobject.vectorized_mobject import * -from mobject.point_cloud_mobject import PointCloudDot - -from animation.animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.continual_animation import * -from animation.playground import * - -from topics.geometry import * -from topics.characters import * -from topics.functions import * -from topics.number_line import * -from topics.combinatorics import * -from scene import Scene -from camera import Camera -from mobject.svg_mobject import * -from mobject.tex_mobject import * - -from mobject.vectorized_mobject import * - -## To watch one of these scenes, run the following: -## python extract_scene.py -p file_name - -LIGHT_COLOR = YELLOW -DEGREES = 360/TAU -SWITCH_ON_RUN_TIME = 1.5 - - -class AmbientLight(VMobject): - - # Parameters are: - # * a source point - # * possibly a target - # * an opacity function and its inverse - # * a light color - # * a max opacity - - # If the target is None, create annuli. - # If there is a target, create annular sectors. - - CONFIG = { - "source_point" : ORIGIN, - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 5.0 - } - - def generate_points(self): - - for submob in self.submobjects: - self.remove(submob) - - # create annuli - dr = self.radius / self.num_levels - for r in np.arange(0, self.radius, dr): - alpha = self.max_opacity * self.opacity_function(r) - annulus = Annulus( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha - ) - annulus.move_arc_center_to(self.source_point) - self.add(annulus) - - # else: - - # # look for the screen and create annular sectors - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # dr = self.radius / self.num_levels - # for r in np.arange(0, self.radius, dr): - # alpha = self.max_opacity * self.opacity_function(r) - # annular_sector = AnnularSector( - # inner_radius = r, - # outer_radius = r + dr, - # color = self.color, - # fill_opacity = alpha, - # start_angle = lower_angle, - # angle = upper_angle - lower_angle - # ) - # annular_sector.move_arc_center_to(self.source_point) - # self.add(annular_sector) - - - - - - # def redraw(self): - # if self.screen != None: - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # for submob in self.submobjects: - # if type(submob) == AnnularSector: - # submob.start_angle = lower_angle - # submob.angle = upper_angle - lower_angle - # submob.generate_points() - # submob.move_arc_center_to(self.source_point) - - - #submob.generate_points() - - - def move_source_to(self,point): - self.source_point = np.array(point) - for submob in self.submobjects: - if type(submob) == Annulus: - submob.shift(self.source_point - submob.get_center()) - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) - - -class Spotlight(VMobject): - - CONFIG = { - "source_point" : ORIGIN, - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 5.0, - "screen" : None - } - - def track_screen(self): - self.generate_points() - - def generate_points(self): - - for submob in self.submobjects: - self.remove(submob) - - if self.screen != None: - # look for the screen and create annular sectors - lower_angle, upper_angle = self.viewing_angles(self.screen) - dr = self.radius / self.num_levels - for r in np.arange(0, self.radius, dr): - alpha = self.max_opacity * self.opacity_function(r) - annular_sector = AnnularSector( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha, - start_angle = lower_angle, - angle = upper_angle - lower_angle - ) - annular_sector.move_arc_center_to(self.source_point) - self.add(annular_sector) - - - def viewing_angle_of_point(self,point): - distance_vector = point - self.source_point - angle = angle_of_vector(distance_vector) - return angle - - def viewing_angles(self,screen): - - viewing_angles = np.array(map(self.viewing_angle_of_point, - screen.get_anchors())) - lower_angle = upper_angle = 0 - if len(viewing_angles) != 0: - lower_angle = np.min(viewing_angles) - upper_angle = np.max(viewing_angles) - - return lower_angle, upper_angle - - def move_source_to(self,point): - self.source_point = np.array(point) - self.shift(np.array(point) - self.source_point) - self.generate_points() - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) - - - -class SwitchOn(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): - raise Exception("Only LightCones and Candles can be switched on") - LaggedStart.__init__(self, - FadeIn, light, **kwargs) - - -class SwitchOff(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): - raise Exception("Only LightCones and Candles can be switched on") - light.submobjects = light.submobjects[::-1] - LaggedStart.__init__(self, - FadeOut, light, **kwargs) - light.submobjects = light.submobjects[::-1] - - -# class LightSource(Mobject): - -# # A light source is composed of: -# # * a lighthouse -# # * possibly a screen -# # * and two light fields: -# # * an undirected one (annuli) -# # * one directed at the screen (annular sectors) - -# CONFIG = { -# "location" : ORIGIN, -# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), -# "ambient_light" : LightField(), -# "spot_light_field" : LightField(), -# } - -# def __init__(self,**kwargs): -# Mobject.__init__(self,**kwargs) -# self.icon.next_to(self.location, DOWN, buff = 0) -# self.spot_light_field.max_opacity = 0 -# self.ambient_light_field.move_source_to(self.location) -# self.spot_light_field.move_source_to(self.location) -# self.add(self.icon,self.ambient_light_field,self.spot_light_field) - -# def dim_ambient(self,new_alpha): -# self.ambient_light_field.dimming(new_alpha) - - - -class ScreenTracker(ContinualAnimation): - def __init__(self, spotlight, screen, **kwargs): - self.spotlight = spotlight - self.screen = screen - ContinualAnimation.__init__(self, self.spotlight, **kwargs) - -# def update_mobject(self, dt): - #self.spotlight.generate_points() - - - - -class IntroScene(Scene): - def construct(self): - - screen = Square().shift([4,0,0]) - #ambient_light = AmbientLight( - # source_point = np.array([-1,1,0]), - # max_opacity = 1.0, - # opacity_function = lambda r: 1.0/(r/1+1)**2, - # num_levels = 10, - #) - - #ambient_light.move_source_to([-5,0,0]) - self.add(screen)#,ambient_light) - - spotlight = Spotlight( - source_point = np.array([-1,1,0]), - max_opacity = 1.0, - opacity_function = lambda r: 1.0/(r/2+1)**2, - num_levels = 10, - screen = screen, - ) - - self.add(spotlight) - - ca = ScreenTracker(spotlight,screen) - self.add(ca) - - #self.play(SwitchOn(ambient_light)) - #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) - #self.play(SwitchOn(spotlight)) - #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) - #self.play(ApplyMethod(spotlight.dimming,0.2)) - self.play(screen.rotate, TAU/8, run_time = 3) - self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) - - self.wait() - - - From 698373c428a9986bae2106e2b98aa8f7c13dfa24 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:51:08 +0100 Subject: [PATCH 16/33] Added my playground for syncing btw my machines --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index f0f3ca3f..29cac87a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ -ben_playground.py - -ben_cairo_test.py From 49ed0ab4f48ad0071f65644dbaa52bb966a4c12c Mon Sep 17 00:00:00 2001 From: bhbr Date: Fri, 26 Jan 2018 19:56:49 +0100 Subject: [PATCH 17/33] Added my playground --- ben_playground.py | 303 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 ben_playground.py diff --git a/ben_playground.py b/ben_playground.py new file mode 100644 index 00000000..1b7445fd --- /dev/null +++ b/ben_playground.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * +from mobject.point_cloud_mobject import PointCloudDot + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.continual_animation import * +from animation.playground import * + +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.combinatorics import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + +from mobject.vectorized_mobject import * + +## To watch one of these scenes, run the following: +## python extract_scene.py -p file_name + +LIGHT_COLOR = YELLOW +DEGREES = 360/TAU +SWITCH_ON_RUN_TIME = 1.5 + + +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * possibly a target + # * an opacity function and its inverse + # * a light color + # * a max opacity + + # If the target is None, create annuli. + # If there is a target, create annular sectors. + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0 + } + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + # create annuli + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_arc_center_to(self.source_point) + self.add(annulus) + + # else: + + # # look for the screen and create annular sectors + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # dr = self.radius / self.num_levels + # for r in np.arange(0, self.radius, dr): + # alpha = self.max_opacity * self.opacity_function(r) + # annular_sector = AnnularSector( + # inner_radius = r, + # outer_radius = r + dr, + # color = self.color, + # fill_opacity = alpha, + # start_angle = lower_angle, + # angle = upper_angle - lower_angle + # ) + # annular_sector.move_arc_center_to(self.source_point) + # self.add(annular_sector) + + + + + + # def redraw(self): + # if self.screen != None: + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # for submob in self.submobjects: + # if type(submob) == AnnularSector: + # submob.start_angle = lower_angle + # submob.angle = upper_angle - lower_angle + # submob.generate_points() + # submob.move_arc_center_to(self.source_point) + + + #submob.generate_points() + + + def move_source_to(self,point): + self.source_point = np.array(point) + for submob in self.submobjects: + if type(submob) == Annulus: + submob.shift(self.source_point - submob.get_center()) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + +class Spotlight(VMobject): + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0, + "screen" : None + } + + def track_screen(self): + self.generate_points() + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + annular_sector.move_arc_center_to(self.source_point) + self.add(annular_sector) + + + def viewing_angle_of_point(self,point): + distance_vector = point - self.source_point + angle = angle_of_vector(distance_vector) + return angle + + def viewing_angles(self,screen): + + viewing_angles = np.array(map(self.viewing_angle_of_point, + screen.get_anchors())) + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: + lower_angle = np.min(viewing_angles) + upper_angle = np.max(viewing_angles) + + return lower_angle, upper_angle + + def move_source_to(self,point): + self.source_point = np.array(point) + self.shift(np.array(point) - self.source_point) + self.generate_points() + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + + +class SwitchOn(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + LaggedStart.__init__(self, + FadeIn, light, **kwargs) + + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + + +# class LightSource(Mobject): + +# # A light source is composed of: +# # * a lighthouse +# # * possibly a screen +# # * and two light fields: +# # * an undirected one (annuli) +# # * one directed at the screen (annular sectors) + +# CONFIG = { +# "location" : ORIGIN, +# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), +# "ambient_light" : LightField(), +# "spot_light_field" : LightField(), +# } + +# def __init__(self,**kwargs): +# Mobject.__init__(self,**kwargs) +# self.icon.next_to(self.location, DOWN, buff = 0) +# self.spot_light_field.max_opacity = 0 +# self.ambient_light_field.move_source_to(self.location) +# self.spot_light_field.move_source_to(self.location) +# self.add(self.icon,self.ambient_light_field,self.spot_light_field) + +# def dim_ambient(self,new_alpha): +# self.ambient_light_field.dimming(new_alpha) + + + +class ScreenTracker(ContinualAnimation): + def __init__(self, spotlight, screen, **kwargs): + self.spotlight = spotlight + self.screen = screen + ContinualAnimation.__init__(self, self.spotlight, **kwargs) + +# def update_mobject(self, dt): + #self.spotlight.generate_points() + + + + +class IntroScene(Scene): + def construct(self): + + screen = Square().shift([4,0,0]) + #ambient_light = AmbientLight( + # source_point = np.array([-1,1,0]), + # max_opacity = 1.0, + # opacity_function = lambda r: 1.0/(r/1+1)**2, + # num_levels = 10, + #) + + #ambient_light.move_source_to([-5,0,0]) + self.add(screen)#,ambient_light) + + spotlight = Spotlight( + source_point = np.array([-1,1,0]), + max_opacity = 1.0, + opacity_function = lambda r: 1.0/(r/2+1)**2, + num_levels = 10, + screen = screen, + ) + + self.add(spotlight) + + ca = ScreenTracker(spotlight,screen) + self.add(ca) + + #self.play(SwitchOn(ambient_light)) + #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) + #self.play(SwitchOn(spotlight)) + #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) + #self.play(ApplyMethod(spotlight.dimming,0.2)) + self.play(screen.rotate, TAU/8, run_time = 3) + self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) + + self.wait() + + + From 3f939248810a39e571bace438c85250943a012eb Mon Sep 17 00:00:00 2001 From: bhbr Date: Fri, 26 Jan 2018 22:05:09 +0100 Subject: [PATCH 18/33] cleanup --- ben_playground.py | 104 ++++++++++------------------------------------ 1 file changed, 22 insertions(+), 82 deletions(-) diff --git a/ben_playground.py b/ben_playground.py index 1b7445fd..f6fe1805 100644 --- a/ben_playground.py +++ b/ben_playground.py @@ -38,13 +38,11 @@ class AmbientLight(VMobject): # Parameters are: # * a source point - # * possibly a target - # * an opacity function and its inverse + # * an opacity function # * a light color # * a max opacity - - # If the target is None, create annuli. - # If there is a target, create annular sectors. + # * a radius (larger than the opacity's dropoff length) + # * the number of subdivisions (levels, annuli) CONFIG = { "source_point" : ORIGIN, @@ -57,6 +55,8 @@ class AmbientLight(VMobject): def generate_points(self): + # in theory, this method is only called once, right? + # so removing submobs shd not be necessary for submob in self.submobjects: self.remove(submob) @@ -73,47 +73,14 @@ class AmbientLight(VMobject): annulus.move_arc_center_to(self.source_point) self.add(annulus) - # else: - - # # look for the screen and create annular sectors - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # dr = self.radius / self.num_levels - # for r in np.arange(0, self.radius, dr): - # alpha = self.max_opacity * self.opacity_function(r) - # annular_sector = AnnularSector( - # inner_radius = r, - # outer_radius = r + dr, - # color = self.color, - # fill_opacity = alpha, - # start_angle = lower_angle, - # angle = upper_angle - lower_angle - # ) - # annular_sector.move_arc_center_to(self.source_point) - # self.add(annular_sector) - - - - - - # def redraw(self): - # if self.screen != None: - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # for submob in self.submobjects: - # if type(submob) == AnnularSector: - # submob.start_angle = lower_angle - # submob.angle = upper_angle - lower_angle - # submob.generate_points() - # submob.move_arc_center_to(self.source_point) - - - #submob.generate_points() def move_source_to(self,point): + self.shift(point - self.source_point) self.source_point = np.array(point) - for submob in self.submobjects: - if type(submob) == Annulus: - submob.shift(self.source_point - submob.get_center()) + # for submob in self.submobjects: + # if type(submob) == Annulus: + # submob.shift(self.source_point - submob.get_center()) def dimming(self,new_alpha): old_alpha = self.max_opacity @@ -221,32 +188,6 @@ class SwitchOff(LaggedStart): light.submobjects = light.submobjects[::-1] -# class LightSource(Mobject): - -# # A light source is composed of: -# # * a lighthouse -# # * possibly a screen -# # * and two light fields: -# # * an undirected one (annuli) -# # * one directed at the screen (annular sectors) - -# CONFIG = { -# "location" : ORIGIN, -# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), -# "ambient_light" : LightField(), -# "spot_light_field" : LightField(), -# } - -# def __init__(self,**kwargs): -# Mobject.__init__(self,**kwargs) -# self.icon.next_to(self.location, DOWN, buff = 0) -# self.spot_light_field.max_opacity = 0 -# self.ambient_light_field.move_source_to(self.location) -# self.spot_light_field.move_source_to(self.location) -# self.add(self.icon,self.ambient_light_field,self.spot_light_field) - -# def dim_ambient(self,new_alpha): -# self.ambient_light_field.dimming(new_alpha) @@ -266,15 +207,14 @@ class IntroScene(Scene): def construct(self): screen = Square().shift([4,0,0]) - #ambient_light = AmbientLight( - # source_point = np.array([-1,1,0]), - # max_opacity = 1.0, - # opacity_function = lambda r: 1.0/(r/1+1)**2, - # num_levels = 10, - #) + self.add(screen) - #ambient_light.move_source_to([-5,0,0]) - self.add(screen)#,ambient_light) + ambient_light = AmbientLight( + source_point = np.array([-1,1,0]), + max_opacity = 1.0, + opacity_function = lambda r: 1.0/(r/2+1)**2, + num_levels = 10, + ) spotlight = Spotlight( source_point = np.array([-1,1,0]), @@ -284,18 +224,18 @@ class IntroScene(Scene): screen = screen, ) - self.add(spotlight) + #self.add(ambient_light) - ca = ScreenTracker(spotlight,screen) - self.add(ca) + #ca = ScreenTracker(spotlight,screen) + #self.add(ca) - #self.play(SwitchOn(ambient_light)) + self.play(SwitchOn(ambient_light)) #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) #self.play(SwitchOn(spotlight)) #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) #self.play(ApplyMethod(spotlight.dimming,0.2)) - self.play(screen.rotate, TAU/8, run_time = 3) - self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) + #self.play(screen.rotate, TAU/8, run_time = 3) + #self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) self.wait() From d62a14e6e57c5a3c602b3e54d2dfb525fcdd6d27 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 26 Jan 2018 13:43:26 -0800 Subject: [PATCH 19/33] Finished fourier project --- active_projects/fourier.py | 72 ++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 8cbcf953..fde2569c 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -124,7 +124,6 @@ class OtherContexts(PiCreatureScene): ) self.wait() - class TODOInsertCosineWrappingAroundCircle(TODOStub): CONFIG = { "message" : "Give a picture-in-picture \\\\ of cosine wrapping around circle", @@ -1358,9 +1357,11 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): self.center_of_mass_label = words def change_to_various_frequencies(self): - for new_freq in [0.5, 0.2, 1.04, 2.21, 3.0]: - self.change_frequency(new_freq) - self.wait() + self.change_frequency( + 3.0, run_time = 30, + rate_func = bezier([0, 0, 1, 1]) + ) + self.wait() self.play( *self.get_vector_animations(self.graph), run_time = 15 @@ -1372,7 +1373,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): com_label = self.center_of_mass_label com_label.add_background_rectangle() frequency_axes = self.get_frequency_axes() - x_coord_label = TextMobject("$x$-coordiate for center of mass") + x_coord_label = TextMobject("$x$-coordinate for center of mass") x_coord_label.highlight(self.center_of_mass_color) x_coord_label.scale(self.text_scale_val) x_coord_label.next_to( @@ -3122,7 +3123,7 @@ 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" + "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) @@ -3186,7 +3187,7 @@ class ScaleUpCenterOfMass(WriteComplexExponentialExpression): 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" + "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) @@ -3763,7 +3764,7 @@ class SummarizeFormula(Scene): def get_expression(self): expression = TexMobject( "\\hat g(", "f", ")", "=", "\\int", "_{t_1}", "^{t_2}", - "g({}", "t", ")", "e", "^{2\\pi i", "f", "t}", "dt" + "g({}", "t", ")", "e", "^{-2\\pi i", "f", "t}", "dt" ) expression.highlight_by_tex( "t", YELLOW, substring = False, @@ -4186,12 +4187,55 @@ class FourierEndScreen(PatreonEndScreen): ], } - - - - - - +class Thumbnail(Scene): + def construct(self): + title = TextMobject("Fourier\\\\", "Visualized") + title[1].highlight(YELLOW) + title[1].set_stroke(RED, 1) + title.highlight(YELLOW) + title.set_stroke(RED, 1) + + title.scale(2.5) + title.add_background_rectangle() + # title.to_edge(UP) + # self.add(title) + def func(t): + return np.cos(2*TAU*t) + np.cos(3*TAU*t) + np.cos(5*t) + fourier = get_fourier_transform(func, -5, 5) + + graph = FunctionGraph(func, x_min = -5, x_max = 5) + graph.highlight(BLUE) + fourier_graph = FunctionGraph(fourier, x_min = 0, x_max = 6) + fourier_graph.highlight(RED_C) + for g in graph, fourier_graph: + g.stretch_to_fit_height(2) + g.stretch_to_fit_width(10) + g.set_stroke(width = 8) + arrow = Vector( + 2.5*DOWN, + rectangular_stem_width = 0.2, + tip_length = 0.5, + color = WHITE + ) + q_mark = TextMobject("?").scale(2) + q_mark.highlight(YELLOW) + q_mark.set_stroke(RED, 1) + arrows = VGroup(*it.chain(*zip( + [q_mark.copy() for x in range(5)], + [arrow.copy() for x in range(5)] + ))) + # arrows.submobjects.pop() + # arrows.arrange_submobjects(RIGHT, buff = MED_LARGE_BUFF) + group = VGroup(graph, title, fourier_graph) + arrows = VGroup(*[arrow.copy() for x in range(2)]) + arrows.arrange_submobjects(RIGHT, buff = 6*LARGE_BUFF) + # group = VGroup(graph, arrows, fourier_graph) + group.arrange_submobjects(DOWN) + # group.next_to(title, DOWN, MED_LARGE_BUFF) + arrows.move_to(title) + title.shift(MED_SMALL_BUFF*UP) + graph.shift(SMALL_BUFF*UP) + self.add(arrows, group) From a9f3bbd4a3d21a565371a8da4c1bf0591173d157 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:54:20 +0100 Subject: [PATCH 20/33] Revert "Added my playground for syncing btw my machines" This reverts commit 698373c428a9986bae2106e2b98aa8f7c13dfa24. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 29cac87a..f0f3ca3f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ +ben_playground.py + +ben_cairo_test.py From c05174990551f46284123d47d74264e0a5ad6b87 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 13:29:20 +0100 Subject: [PATCH 21/33] old Integer implem lingering around --- topics/numerals.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index 0be11a49..e53e20e9 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -54,7 +54,7 @@ class DecimalNumber(VMobject): unit_sign.next_to(self.submobjects[-1],RIGHT, buff = self.digit_to_digit_buff) - if self.unit == "^\\circ": + if self.unit.startswith("^"): unit_sign.align_to(self,UP) else: unit_sign.align_to(self,DOWN) @@ -66,27 +66,6 @@ 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: - 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 = { From 526929098a6d2416647ce4de36c30bd5f21fecb4 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 13:29:39 +0100 Subject: [PATCH 22/33] added method for setting path arc of a Line --- topics/geometry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/topics/geometry.py b/topics/geometry.py index b5113ee5..b9695f46 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -234,6 +234,10 @@ class Line(VMobject): ]) self.account_for_buff() + def set_path_arc(self,new_value): + self.path_arc = new_value + self.generate_points() + def account_for_buff(self): length = self.get_arc_length() if length < 2*self.buff or self.buff == 0: From 68c08f0a5a123cf3aee928d74c9e16f14fb2a70a Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 13:30:25 +0100 Subject: [PATCH 23/33] Added working light, scenes 3 and 4 --- active_projects/basel.py | 643 +++++++++++++++++++++++++-------------- 1 file changed, 410 insertions(+), 233 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index b6263cd0..bfe9cfac 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -30,9 +30,9 @@ from mobject.vectorized_mobject import * ## To watch one of these scenes, run the following: ## python extract_scene.py -p file_name -inverse_power_law = lambda maxint,cutoff,exponent: \ - (lambda r: maxint * (cutoff/(r+cutoff))**exponent) -inverse_quadratic = lambda maxint,cutoff: inverse_power_law(maxint,cutoff,2) +inverse_power_law = lambda maxint,scale,cutoff,exponent: \ + (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) +inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) LIGHT_COLOR = YELLOW INDICATOR_RADIUS = 0.7 @@ -44,12 +44,13 @@ FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 SWITCH_ON_RUN_TIME = 2.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -LIGHT_CONE_NUM_SECTORS = 30 +NUM_LEVELS = 30 NUM_CONES = 50 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 DIM_OPACITY = 0.2 +DEGREES = TAU/360 def show_line_length(line): v = line.points[1] - line.points[0] @@ -67,7 +68,7 @@ class AngleUpdater(ContinualAnimation): def update_mobject(self, dt): # angle arc new_arc = self.angle_arc.copy().set_bound_angles( - start = self.lc.start_angle, + start = self.lc.start_angle(), stop = self.lc.stop_angle() ) new_arc.generate_points() @@ -77,55 +78,167 @@ class AngleUpdater(ContinualAnimation): +LIGHT_COLOR = YELLOW +DEGREES = 360/TAU +SWITCH_ON_RUN_TIME = 1.5 -class LightScreen(VMobject): - # A light screen is composed of a VMobject and a light cone. - # It has knowledge of the light source point. - # As the screen changes, it calculates the viewing angle from - # the source and updates the light cone. - def __init__(self, light_source = ORIGIN, screen = None, light_cone = None): - Mobject.__init__(self) - self.light_cone = light_cone - self.light_source = light_source - self.screen = screen - self.light_cone.move_source_to(self.light_source) - self.shadow = VMobject(fill_color = BLACK, stroke_width = 0, fill_opacity = 1.0) - self.add(self.light_cone, self.screen, self.shadow) - self.update_shadow(self.shadow) +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * an opacity function + # * a light color + # * a max opacity + # * a radius (larger than the opacity's dropoff length) + # * the number of subdivisions (levels, annuli) + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0 + } + + def generate_points(self): + + # in theory, this method is only called once, right? + # so removing submobs shd not be necessary + for submob in self.submobjects: + self.remove(submob) + + # create annuli + self.radius = float(self.radius) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_arc_center_to(self.source_point) + self.add(annulus) + + + + def move_source_to(self,point): + self.shift(point - self.source_point) + self.source_point = np.array(point) + # for submob in self.submobjects: + # if type(submob) == Annulus: + # submob.shift(self.source_point - submob.get_center()) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + +class Spotlight(VMobject): + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0, + "screen" : None, + "shadow" : VMobject(fill_color = BLACK, stroke_width = 0, fill_opacity = 1.0) + } + + def track_screen(self): + self.generate_points() + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + self.radius = float(self.radius) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + annular_sector.move_arc_center_to(self.source_point) + self.add(annular_sector) + + self.update_shadow(point = self.source_point) + self.add(self.shadow) - def update_light_cone(self,lc): - lower_angle, upper_angle = self.viewing_angles() - #print lower_angle, upper_angle - self.light_cone.update_opening(start_angle = lower_angle, - stop_angle = upper_angle) - return self def viewing_angle_of_point(self,point): - distance_vector = point - self.light_source + distance_vector = point - self.source_point angle = angle_of_vector(distance_vector) return angle - def viewing_angles(self): - all_points = [] + def viewing_angles(self,screen): - for submob in self.family_members_with_points(): - all_points.extend(submob.get_anchors()) - - viewing_angles = np.array(map(self.viewing_angle_of_point, self.screen.get_anchors())) - - if len(viewing_angles) == 0: - lower_angle = upper_angle = 0 - else: + viewing_angles = np.array(map(self.viewing_angle_of_point, + screen.get_anchors())) + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: lower_angle = np.min(viewing_angles) upper_angle = np.max(viewing_angles) - + return lower_angle, upper_angle - def update_shadow(self,sh): + def opening_angle(self): + l,u = self.viewing_angles(self.screen) + return u - l + + def start_angle(self): + l,u = self.viewing_angles(self.screen) + return l + + def stop_angle(self): + l,u = self.viewing_angles(self.screen) + return u + + def move_source_to(self,point): + print "moving source" + self.source_point = np.array(point) + self.recalculate_sectors(point = point, screen = self.screen) + self.update_shadow(point = point) + + + def recalculate_sectors(self, point = ORIGIN, screen = None): + if screen == None: + return + for submob in self.submobject_family(): + if type(submob) == AnnularSector: + lower_angle, upper_angle = self.viewing_angles(screen) + new_submob = AnnularSector( + start_angle = lower_angle, + angle = upper_angle - lower_angle, + inner_radius = submob.inner_radius, + outer_radius = submob.outer_radius + ) + new_submob.move_arc_center_to(point) + submob.points = new_submob.points + + def update_shadow(self,point = ORIGIN): + use_point = point #self.source_point self.shadow.points = self.screen.points - ray1 = self.screen.points[0] - self.light_source - ray2 = self.screen.points[-1] - self.light_source + ray1 = self.screen.points[0] - use_point + ray2 = self.screen.points[-1] - use_point ray1 = ray1/np.linalg.norm(ray1) * 100 ray1 = rotate_vector(ray1,-TAU/16) ray2 = ray2/np.linalg.norm(ray2) * 100 @@ -135,121 +248,36 @@ class LightScreen(VMobject): self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]]) self.shadow.mark_paths_closed = True - def move_source_to(self,new_point): - self.light_source = new_point - #self.update_light_cone(self.light_cone) + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + def change_opacity_function(self,new_f): + self.radius = 120 + self.opacity_function = new_f + dr = self.radius/self.num_levels -class LightCone(VGroup): - CONFIG = { - "start_angle": 0, - "angle" : TAU/8, - "radius" : 10, - "brightness" : 1, - "opacity_function" : lambda r : 1./max(r, 0.01), - "num_sectors" : 10, - "color": LIGHT_COLOR, - } - - def generate_points(self): - radii = np.linspace(0, self.radius, self.num_sectors+1) - sectors = [ - AnnularSector( - start_angle = self.start_angle, - angle = self.angle, - inner_radius = r1, - outer_radius = r2, - stroke_width = 0, - stroke_color = self.color, - fill_color = self.color, - fill_opacity = self.brightness * self.opacity_function(r1), - ) - for r1, r2 in zip(radii, radii[1:]) - ] - self.add(*sectors) - - def get_source_point(self): - if len(self.submobjects) == 0: - return None - source = self.submobjects[0].get_arc_center() - return source - - def move_source_to(self,point): - if len(self.submobjects) == 0: - return - source = self.submobjects[0].get_arc_center() - self.shift(point - source) - self.generate_points() - - def update_opening(self, start_angle, stop_angle): - self.start_angle = start_angle - self.angle = stop_angle - start_angle - source_point = self.get_source_point() + sectors = [] for submob in self.submobjects: if type(submob) == AnnularSector: + sectors.append(submob) - submob.start_angle = self.start_angle - submob.angle = self.angle - submob.generate_points() - submob.shift(source_point - submob.get_arc_center()) + print self.num_levels, len(sectors) + for (r,submob) in zip(np.arange(0,self.radius,dr),sectors): + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue + alpha = self.opacity_function(r) + submob.set_fill(opacity = alpha) - def set_brightness(self,new_brightness): - self.brightness = new_brightness - radii = np.linspace(0, self.radius, self.num_sectors+1) - for (r1,sector) in zip(radii,self.submobjects): - sector.set_fill(opacity = self.brightness * self.opacity_function(r1)) - - def stop_angle(self): - return self.start_angle + self.angle - - - - - - - -class Candle(VGroup): - CONFIG = { - "radius" : 5, - "brightness" : 1.0, - "opacity_function" : lambda r : 1./max(r, 0.01), - "num_annuli" : 10, - "color": LIGHT_COLOR, - } - - def generate_points(self): - radii = np.linspace(0, self.radius, self.num_annuli+1) - annuli = [ - Annulus( - inner_radius = r1, - outer_radius = r2, - stroke_width = 0, - stroke_color = self.color, - fill_color = self.color, - fill_opacity = self.brightness * self.opacity_function(r1), - ) - for r1, r2 in zip(radii, radii[1:]) - ] - self.add(*annuli) - - def get_source_point(self): - if len(self.submobjects) == 0: - return None - source = self.submobjects[0].get_center() - return source - - def move_source_to(self,point): - if len(self.submobjects) == 0: - return - source = self.submobjects[0].get_center() - self.shift(point - source) - - def set_brightness(self,new_brightness): - self.brightness = new_brightness - radii = np.linspace(0, self.radius, self.num_annuli+1) - for (r1,annulus) in zip(radii,self.submobjects): - annulus.set_fill(opacity = self.brightness * self.opacity_function(r1)) @@ -261,11 +289,42 @@ class SwitchOn(LaggedStart): } def __init__(self, light, **kwargs): - if not isinstance(light,LightCone) and not isinstance(light,Candle): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): raise Exception("Only LightCones and Candles can be switched on") LaggedStart.__init__(self, FadeIn, light, **kwargs) + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + + + + + +class ScreenTracker(ContinualAnimation): + def __init__(self, mobject, **kwargs): + ContinualAnimation.__init__(self, mobject, **kwargs) + + def update_mobject(self, dt): + self.mobject.recalculate_sectors( + point = self.mobject.source_point, + screen = self.mobject.screen) + self.mobject.update_shadow(self.mobject.source_point) + + + class LightHouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", @@ -606,7 +665,7 @@ class FirstLightHouseScene(PiCreatureScene): lighthouses = [] lighthouse_pos = [] - light_cones = [] + ambient_lights = [] euler_sum_above = TexMobject("1", "+", "{1\over 4}", @@ -625,15 +684,15 @@ class FirstLightHouseScene(PiCreatureScene): for i in range(1,NUM_CONES+1): lighthouse = LightHouse() point = self.number_line.number_to_point(i) - light_cone = Candle( - opacity_function = inverse_quadratic(1,1), - num_annuli = LIGHT_CONE_NUM_SECTORS, - radius = 12) + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, + radius = 12.0) - light_cone.move_source_to(point) + ambient_light.move_source_to(point) lighthouse.next_to(point,DOWN,0) lighthouses.append(lighthouse) - light_cones.append(light_cone) + ambient_lights.append(ambient_light) lighthouse_pos.append(point) @@ -649,13 +708,13 @@ class FirstLightHouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator - for (i,lc) in zip(range(NUM_VISIBLE_CONES),light_cones[:NUM_VISIBLE_CONES]): - indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES),ambient_lights[:NUM_VISIBLE_CONES]): + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/ambient_light.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) self.play( - SwitchOn(lc), + SwitchOn(ambient_light), FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, rate_func = indicator_rate_func), FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, @@ -675,13 +734,13 @@ class FirstLightHouseScene(PiCreatureScene): ) # quickly switch on off-screen light cones and increment indicator - for (i,lc) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_cones[NUM_VISIBLE_CONES:NUM_CONES]): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),ambient_lights[NUM_VISIBLE_CONES:NUM_CONES]): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/ambient_light.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) smooth,indicator_start_time,indicator_stop_time) self.play( - SwitchOn(lc, run_time = FAST_SWITCH_ON_RUN_TIME), + SwitchOn(ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), ChangeDecimalToValue(light_indicator.reading,intensities[i], rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i]) @@ -726,72 +785,58 @@ class SingleLightHouseScene(PiCreatureScene): observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] lighthouse = LightHouse() - candle = Candle( - opacity_function = inverse_quadratic(1,1), - num_annuli = LIGHT_CONE_NUM_SECTORS, + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, radius = 10, brightness = 1, ) lighthouse.scale(2).next_to(source_point, DOWN, buff = 0) - candle.move_to(source_point) + ambient_light.move_to(source_point) morty = self.get_primary_pi_creature() morty.scale(0.5) morty.move_to(observer_point) self.add(lighthouse) self.play( - SwitchOn(candle) + SwitchOn(ambient_light) ) - light_cone = LightCone( - opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, + + screen = Line([0,-1,0],[0,1,0]) + screen.rotate(-TAU/6) + screen.next_to(morty, LEFT, buff = 1) + + spotlight = Spotlight( + opacity_function = inverse_quadratic(1,2,1), + num_levels = NUM_LEVELS, radius = 10, brightness = 5, + screen = screen ) - light_cone.move_source_to(source_point) - screen = Line([0,-1,0],[0,1,0]) - show_line_length(screen) - - screen.rotate_in_place(-TAU/6) - show_line_length(screen) - - screen.next_to(morty, LEFT, buff = 1) - - light_screen = LightScreen(light_source = source_point, - screen = screen, light_cone = light_cone) - light_screen.screen.color = WHITE - light_screen.screen.fill_opacity = 1 - light_screen.update_light_cone(light_cone) - # self.play( - # FadeIn(light_screen, run_time = 2), - # # dim the light that misses the screen - # ApplyMethod(candle.set_brightness,0.3), - # ApplyMethod(light_screen.update_shadow,light_screen.shadow), - # FadeIn(light_cone), - # ) + spotlight.move_source_to(source_point) - lc_updater = lambda lc: light_screen.update_light_cone(lc) - sh_updater = lambda sh: light_screen.update_shadow(sh) + self.play( + ApplyMethod(ambient_light.dimming,0.2), + FadeIn(spotlight)) + self.add(spotlight.shadow) - ca1 = ContinualUpdateFromFunc(light_screen.light_cone, - lc_updater) - ca15 = ContinualUpdateFromFunc(light_screen, - lc_updater) - ca2 = ContinualUpdateFromFunc(light_screen.shadow, - sh_updater) - - self.add(ca1, ca15, ca2) self.add_foreground_mobject(morty) - pointing_screen_at_source = ApplyMethod(screen.rotate,TAU/6) - #self.play(pointing_screen_at_source) - #self.wait() + screen_tracker = ScreenTracker(spotlight) + # activate ONLY when spotlight is moving! - arc_angle = light_cone.angle + self.add(screen_tracker) + pointing_screen_at_source = ApplyMethod(spotlight.screen.rotate,TAU/6) + self.play(pointing_screen_at_source) + + + + + arc_angle = spotlight.opening_angle() # draw arc arrows to show the opening angle - angle_arc = Arc(radius = 5, start_angle = light_cone.start_angle, - angle = light_cone.angle, tip_length = ARC_TIP_LENGTH) + angle_arc = Arc(radius = 5, start_angle = spotlight.start_angle(), + angle = spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) #angle_arc.add_tip(at_start = True, at_end = True) angle_arc.move_arc_center_to(source_point) @@ -803,31 +848,28 @@ class SingleLightHouseScene(PiCreatureScene): angle_indicator.next_to(angle_arc,RIGHT) self.add_foreground_mobject(angle_indicator) - angle_update_func = lambda x: light_cone.angle/TAU * 360 + angle_update_func = lambda x: spotlight.opening_angle()/TAU * 360 ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func) self.add(ca3) - #ca4 = ContinualUpdateFromFunc(angle_arc,update_angle_arc) - ca4 = AngleUpdater(angle_arc, light_screen.light_cone) + ca4 = AngleUpdater(angle_arc, spotlight) self.add(ca4) - rotating_screen = ApplyMethod(light_screen.screen.rotate, + rotating_screen = ApplyMethod(spotlight.screen.rotate, TAU/8, run_time=1.5) #self.wait(2) - rotating_screen_2 = ApplyMethod(light_screen.screen.rotate, + rotating_screen_2 = ApplyMethod(spotlight.screen.rotate, -TAU/4, run_time=3, rate_func = there_and_back) #self.wait(2) - rotating_screen_3 = ApplyMethod(light_screen.screen.rotate, + rotating_screen_3 = ApplyMethod(spotlight.screen.rotate, TAU/8, run_time=1.5) - #self.play(rotating_screen) - #self.play(rotating_screen_2) - #self.play(rotating_screen_3) + self.play(rotating_screen) + self.play(rotating_screen_2) + self.play(rotating_screen_3) - #rotating_screen_back = ApplyMethod(light_screen.screen.rotate_in_place, -TAU/6) #, run_time=3, rate_func = wiggle) - #self.play(rotating_screen_back) - self.wait() + #self.wait() @@ -836,10 +878,14 @@ class SingleLightHouseScene(PiCreatureScene): globe = Circle(radius = 3) globe.move_to([2,0,0]) sun_position = [-100,0,0] + #self.add(screen_tracker) + print "tuet" + self.remove(screen_tracker) + new_opacity_function = lambda r: 0.5 self.play( - #ApplyMethod(lighthouse.move_to,sun_position), - #ApplyMethod(candle.move_to,sun_position), - ApplyMethod(light_screen.move_source_to,sun_position), + ApplyMethod(lighthouse.move_to,sun_position), + ApplyMethod(ambient_light.move_to,sun_position), + ApplyMethod(spotlight.move_source_to,sun_position), #FadeOut(angle_arc), #FadeOut(angle_indicator), #FadeIn(globe), @@ -847,6 +893,148 @@ class SingleLightHouseScene(PiCreatureScene): #ApplyMethod(morty.move_to,[1,0,0]) ) + self.play( + ApplyMethod(spotlight.change_opacity_function,new_opacity_function)) + + self.add(screen_tracker) + + + + + +class EarthScene(Scene): + + def construct(self): + + DEGREES = TAU/360 + radius = 2.5 + center_x = 3 + theta0 = 80 * DEGREES + dtheta = 10 * DEGREES + theta1 = theta0 + dtheta + screen = Line([center_x - radius * np.cos(theta0),radius * np.sin(theta0),0], + [center_x - radius * np.cos(theta1),radius * np.sin(theta1),0]) + screen.set_stroke(color = RED, width = 5) + + globe = Circle(radius = radius, stroke_width = 0) + globe.move_to([center_x,0,0]) + foreground_globe = globe.copy() # above the shadow + foreground_globe.radius -= 0.2 + foreground_globe.set_stroke(color = WHITE, width = 1) + self.add_foreground_mobject(foreground_globe) + globe.add(screen) + + morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) + self.add_foreground_mobject(morty) + + sun = Spotlight( + opacity_function = lambda r : 0.5, + num_levels = NUM_LEVELS, + radius = 100, + brightness = 5, + screen = screen + ) + + sun.move_source_to([-90,0,0]) + self.add(globe,sun,screen) + + screen_tracker = ScreenTracker(sun) + + self.add(screen_tracker) + self.play( + ApplyMethod(globe.rotate,theta0 + dtheta/2), + ApplyMethod(morty.move_to,[1.5,0,0]) + ) + + + +class ScreenShapingScene(Scene): + + def construct(self): + + DEGREES = TAU / 360 + + screen = Line([2,-1,0],[2,1,0], path_arc = 0, num_arc_anchors = 10) + + source = Spotlight( + opacity_function = inverse_quadratic(1,5,1), + num_levels = NUM_LEVELS, + radius = 10, + brightness = 5, + screen = screen + ) + + source.move_source_to([-5,0,0]) + + lighthouse = LightHouse() + ambient_light = AmbientLight( + opacity_function = inverse_quadratic(1,1,1), + num_levels = NUM_LEVELS, + radius = 10, + brightness = 1, + ) + lighthouse.scale(2).next_to(source.source_point,DOWN,buff=0) + ambient_light.move_source_to(source.source_point) + + self.add(lighthouse, ambient_light,source,screen) + + morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) + self.add_foreground_mobject(morty) + + self.wait() + + screen_tracker = ScreenTracker(source) + + self.add(screen_tracker) + + self.play( + ApplyMethod(screen.set_path_arc, 45 * DEGREES), + ) + + self.play( + ApplyMethod(screen.set_path_arc, -90 * DEGREES), + ) + + self.play( + ApplyMethod(screen.set_path_arc, 0), + ) + + + # in preparation for the slanting, create a rectangle that show the brightness + + rect_origin = Rectangle(width = 0, height = 2).move_to(screen.get_center()) + self.add_foreground_mobject(rect_origin) + + brightness_rect = Rectangle(width = 2, height = 2, fill_color = YELLOW, fill_opacity = 0.5) + + brightness_rect.move_to([3,3,0]) + + self.play( + ReplacementTransform(rect_origin,brightness_rect) + ) + + lower_screen_point, upper_screen_point = screen.get_start_and_end() + + lower_slanted_screen_point = interpolate( + lower_screen_point, source.source_point, 0.2 + ) + upper_slanted_screen_point = interpolate( + upper_screen_point, source.source_point, -0.2 + ) + + slanted_brightness_rect = brightness_rect.copy() + slanted_brightness_rect.width *= 2 + slanted_brightness_rect.generate_points() + slanted_brightness_rect.set_fill(opacity = 0.25) + + slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, path_arc = 0, num_arc_anchors = 10) + + self.play( + ReplacementTransform(screen,slanted_screen), + #ApplyMethod(brightness_rect.stretch,2,0), # factor = 2, dim = 0 (x) + ReplacementTransform(brightness_rect,slanted_brightness_rect), + ApplyMethod(brightness_rect.set_fill,{"opacity" : 0.25}), + ) @@ -858,17 +1046,6 @@ class SingleLightHouseScene(PiCreatureScene): - - - - - - - - - - - From e1edb98997ce0d16a7441616953fbea8dea3f52f Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 14:07:37 +0100 Subject: [PATCH 24/33] playing with lights --- ben_playground.py | 87 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/ben_playground.py b/ben_playground.py index f6fe1805..c03c8abe 100644 --- a/ben_playground.py +++ b/ben_playground.py @@ -100,7 +100,8 @@ class Spotlight(VMobject): "max_opacity" : 1.0, "num_levels" : 10, "radius" : 5.0, - "screen" : None + "screen" : None, + "shadow" : VMobject(fill_color = RED, stroke_width = 0, fill_opacity = 1.0) } def track_screen(self): @@ -128,6 +129,9 @@ class Spotlight(VMobject): annular_sector.move_arc_center_to(self.source_point) self.add(annular_sector) + self.update_shadow(point = self.source_point) + self.add(self.shadow) + def viewing_angle_of_point(self,point): distance_vector = point - self.source_point @@ -147,19 +151,54 @@ class Spotlight(VMobject): def move_source_to(self,point): self.source_point = np.array(point) - self.shift(np.array(point) - self.source_point) - self.generate_points() + self.recalculate_sectors(point = point, screen = self.screen) + self.update_shadow(point = point) + + def recalculate_sectors(self, point = ORIGIN, screen = None): + if screen == None: + return + for submob in self.submobject_family(): + if type(submob) == AnnularSector: + lower_angle, upper_angle = self.viewing_angles(screen) + new_submob = AnnularSector( + start_angle = lower_angle, + angle = upper_angle - lower_angle, + inner_radius = submob.inner_radius, + outer_radius = submob.outer_radius + ) + new_submob.move_arc_center_to(point) + submob.points = new_submob.points + + def update_shadow(self,point = ORIGIN): + print "updating shadow" + use_point = point #self.source_point + self.shadow.points = self.screen.points + ray1 = self.screen.points[0] - use_point + ray2 = self.screen.points[-1] - use_point + ray1 = ray1/np.linalg.norm(ray1) * 100 + ray1 = rotate_vector(ray1,-TAU/16) + ray2 = ray2/np.linalg.norm(ray2) * 100 + 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 + def dimming(self,new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha for submob in self.submobjects: + if type(submob) != AnnularSector: + # it's the shadow, don't dim it + continue old_submob_alpha = submob.fill_opacity new_submob_alpha = old_submob_alpha * new_alpha/old_alpha submob.set_fill(opacity = new_submob_alpha) + class SwitchOn(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -192,52 +231,58 @@ class SwitchOff(LaggedStart): class ScreenTracker(ContinualAnimation): - def __init__(self, spotlight, screen, **kwargs): - self.spotlight = spotlight - self.screen = screen - ContinualAnimation.__init__(self, self.spotlight, **kwargs) - -# def update_mobject(self, dt): - #self.spotlight.generate_points() - + def __init__(self, mobject, **kwargs): + ContinualAnimation.__init__(self, mobject, **kwargs) + def update_mobject(self, dt): + self.mobject.recalculate_sectors( + point = self.mobject.source_point, + screen = self.mobject.screen) + self.mobject.update_shadow(self.mobject.source_point) + class IntroScene(Scene): def construct(self): - screen = Square().shift([4,0,0]) + screen = Line([2,-2,0],[1,2,0]).shift([1,0,0]) self.add(screen) ambient_light = AmbientLight( source_point = np.array([-1,1,0]), max_opacity = 1.0, opacity_function = lambda r: 1.0/(r/2+1)**2, - num_levels = 10, + num_levels = 4, ) spotlight = Spotlight( source_point = np.array([-1,1,0]), max_opacity = 1.0, opacity_function = lambda r: 1.0/(r/2+1)**2, - num_levels = 10, + num_levels = 4, screen = screen, ) - #self.add(ambient_light) + self.add(spotlight) - #ca = ScreenTracker(spotlight,screen) + screen_updater = ScreenTracker(spotlight) #self.add(ca) - self.play(SwitchOn(ambient_light)) + #self.play(SwitchOn(ambient_light)) #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) #self.play(SwitchOn(spotlight)) - #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) - #self.play(ApplyMethod(spotlight.dimming,0.2)) - #self.play(screen.rotate, TAU/8, run_time = 3) + + self.add(screen_updater) + self.play(ApplyMethod(spotlight.screen.rotate,TAU/8)) + self.remove(screen_updater) + self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) + self.add(screen_updater) + spotlight.source_point = [-3,-1,0] + + self.play(ApplyMethod(spotlight.dimming,0.2)) #self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) - self.wait() + #self.wait() From 7ca95b41953c68ef65875c38453cbb818ecb64f7 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 15:17:31 +0100 Subject: [PATCH 25/33] added scene 5, but shd be done w light indicators instead --- active_projects/basel.py | 153 ++++++++++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 44 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index bfe9cfac..8b1497e3 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -48,7 +48,8 @@ NUM_LEVELS = 30 NUM_CONES = 50 # in first lighthouse scene NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 -DIM_OPACITY = 0.2 +AMBIENT_FULL = 1.0 +AMBIENT_DIMMED = 0.2 DEGREES = TAU/360 @@ -786,7 +787,7 @@ class SingleLightHouseScene(PiCreatureScene): lighthouse = LightHouse() ambient_light = AmbientLight( - opacity_function = inverse_quadratic(1,2,1), + opacity_function = inverse_quadratic(AMBIENT_FULL,2,1), num_levels = NUM_LEVELS, radius = 10, brightness = 1, @@ -817,7 +818,7 @@ class SingleLightHouseScene(PiCreatureScene): self.play( - ApplyMethod(ambient_light.dimming,0.2), + ApplyMethod(ambient_light.dimming,AMBIENT_DIMMED), FadeIn(spotlight)) self.add(spotlight.shadow) @@ -872,31 +873,29 @@ class SingleLightHouseScene(PiCreatureScene): #self.wait() +### The following is supposed to morph the scene into the Earth scene, +### but it doesn't work - # morph into Earth scene - globe = Circle(radius = 3) - globe.move_to([2,0,0]) - sun_position = [-100,0,0] - #self.add(screen_tracker) - print "tuet" - self.remove(screen_tracker) - new_opacity_function = lambda r: 0.5 - self.play( - ApplyMethod(lighthouse.move_to,sun_position), - ApplyMethod(ambient_light.move_to,sun_position), - ApplyMethod(spotlight.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]) + # # morph into Earth scene - ) - self.play( - ApplyMethod(spotlight.change_opacity_function,new_opacity_function)) + # globe = Circle(radius = 3) + # globe.move_to([2,0,0]) + # sun_position = [-100,0,0] + # #self.add(screen_tracker) + # print "tuet" + # self.remove(screen_tracker) + # new_opacity_function = lambda r: 0.5 + # self.play( + # ApplyMethod(lighthouse.move_to,sun_position), + # ApplyMethod(ambient_light.move_to,sun_position), + # ApplyMethod(spotlight.move_source_to,sun_position), - self.add(screen_tracker) + # ) + # self.play( + # ApplyMethod(spotlight.change_opacity_function,new_opacity_function)) + + # self.add(screen_tracker) @@ -954,7 +953,10 @@ class ScreenShapingScene(Scene): DEGREES = TAU / 360 - screen = Line([2,-1,0],[2,1,0], path_arc = 0, num_arc_anchors = 10) + screen_height = 1.0 + brightness_rect_height = 1.0 + + screen = Line([3,-screen_height/2,0],[3,screen_height/2,0], path_arc = 0, num_arc_anchors = 10) source = Spotlight( opacity_function = inverse_quadratic(1,5,1), @@ -968,7 +970,7 @@ class ScreenShapingScene(Scene): lighthouse = LightHouse() ambient_light = AmbientLight( - opacity_function = inverse_quadratic(1,1,1), + opacity_function = inverse_quadratic(AMBIENT_DIMMED,1,1), num_levels = NUM_LEVELS, radius = 10, brightness = 1, @@ -1002,17 +1004,22 @@ class ScreenShapingScene(Scene): # in preparation for the slanting, create a rectangle that show the brightness - rect_origin = Rectangle(width = 0, height = 2).move_to(screen.get_center()) + rect_origin = Rectangle(width = 0, height = screen_height).move_to(screen.get_center()) self.add_foreground_mobject(rect_origin) - brightness_rect = Rectangle(width = 2, height = 2, fill_color = YELLOW, fill_opacity = 0.5) + brightness_rect = Rectangle(width = brightness_rect_height, + height = brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) - brightness_rect.move_to([3,3,0]) + brightness_rect.next_to(screen, UP, buff = 1) self.play( ReplacementTransform(rect_origin,brightness_rect) ) + original_screen = screen.copy() + original_brightness_rect = brightness_rect.copy() + # for unslanting the screen later + lower_screen_point, upper_screen_point = screen.get_start_and_end() lower_slanted_screen_point = interpolate( @@ -1027,13 +1034,81 @@ class ScreenShapingScene(Scene): slanted_brightness_rect.generate_points() slanted_brightness_rect.set_fill(opacity = 0.25) - slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, path_arc = 0, num_arc_anchors = 10) + slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, + path_arc = 0, num_arc_anchors = 10) + slanted_brightness_rect.move_to(brightness_rect.get_center()) self.play( - ReplacementTransform(screen,slanted_screen), - #ApplyMethod(brightness_rect.stretch,2,0), # factor = 2, dim = 0 (x) - ReplacementTransform(brightness_rect,slanted_brightness_rect), - ApplyMethod(brightness_rect.set_fill,{"opacity" : 0.25}), + ReplacementTransform(screen,slanted_screen), + ReplacementTransform(brightness_rect,slanted_brightness_rect), + ) + + self.wait() + + # Scene 5: constant screen size, changing opening angle + + screen = slanted_screen + source.screen = screen + self.remove(slanted_screen) + + brightness_rect = slanted_brightness_rect + self.remove(slanted_brightness_rect) + + + self.play( + ReplacementTransform(screen,original_screen), + ReplacementTransform(brightness_rect,original_brightness_rect), + ) + + self.remove(original_brightness_rect) + + shifted_brightness_rect = brightness_rect.copy() + shifted_brightness_rect.shift([-3,0,0]).set_fill(opacity = 0.8) + + self.play( + ApplyMethod(screen.shift,[-3,0,0]), + ApplyMethod(morty.shift,[-3,0,0]), + #ApplyMethod(brightness_rect.shift,[-3,0,0]), + #ApplyMethod(brightness_rect.set_fill,{"opacity": 0.8}) + ReplacementTransform(brightness_rect,shifted_brightness_rect) + ) + + self.remove(original_screen) # was still hiding behind the shadow + self.remove(shifted_brightness_rect) # also one too many + + # add distance indicator + + left_x = source.source_point[0] + right_x = screen.get_center()[0] + indicator_y = -2 + line1 = Arrow([left_x,indicator_y,0],[right_x,indicator_y,0]) + line2 = Arrow([right_x,indicator_y,0],[left_x,indicator_y,0]) + line1.set_fill(color = WHITE) + line2.set_fill(color = WHITE) + distance_decimal = Integer(1).next_to(line1,DOWN) + line = VGroup(line1, line2,distance_decimal) + self.add(line) + + + # move everything away + distance_to_source = right_x - left_x + new_right_x = left_x + 2*distance_to_source + new_line1 = Arrow([left_x,indicator_y,0],[new_right_x,indicator_y,0]) + new_line2 = Arrow([new_right_x,indicator_y,0],[left_x,indicator_y,0]) + new_line1.set_fill(color = WHITE) + new_line2.set_fill(color = WHITE) + new_distance_decimal = Integer(2).next_to(new_line1,DOWN) + new_line = VGroup(new_line1, new_line2,new_distance_decimal) + + new_brightness_rect = brightness_rect.copy() + new_brightness_rect.shift([distance_to_source,0,0]) + new_brightness_rect.set_fill(opacity = 0.2) + + self.play( + ReplacementTransform(line,new_line), + ApplyMethod(screen.shift,[distance_to_source,0,0]), + Transform(brightness_rect,new_brightness_rect), + ApplyMethod(morty.shift,[distance_to_source,0,0]), ) @@ -1045,13 +1120,3 @@ class ScreenShapingScene(Scene): - - - - - - - - - - From 5169d6b297b0cbe7ef071cf5d0dfeefe3f42b6fb Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 19:09:29 +0100 Subject: [PATCH 26/33] Scenes 2-5 mostly finished --- active_projects/basel.py | 79 +++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 8b1497e3..71135f30 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -241,9 +241,9 @@ class Spotlight(VMobject): ray1 = self.screen.points[0] - use_point ray2 = self.screen.points[-1] - use_point ray1 = ray1/np.linalg.norm(ray1) * 100 - ray1 = rotate_vector(ray1,-TAU/16) + ray1 = rotate_vector(ray1,-TAU/100) ray2 = ray2/np.linalg.norm(ray2) * 100 - ray2 = rotate_vector(ray2,TAU/16) + ray2 = rotate_vector(ray2,TAU/100) outpoint1 = self.screen.points[0] + ray1 outpoint2 = self.screen.points[-1] + ray2 self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]]) @@ -336,7 +336,8 @@ class LightIndicator(Mobject): CONFIG = { "radius": 0.5, "intensity": 0, - "opacity_for_unit_intensity": 1 + "opacity_for_unit_intensity": 1, + "precision": 3 } def generate_points(self): @@ -346,7 +347,7 @@ class LightIndicator(Mobject): self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) self.add(self.background, self.foreground) - self.reading = DecimalNumber(self.intensity,num_decimal_points = 3) + self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision) self.reading.set_fill(color=INDICATOR_TEXT_COLOR) self.reading.move_to(self.get_center()) self.add(self.reading) @@ -873,7 +874,7 @@ class SingleLightHouseScene(PiCreatureScene): #self.wait() -### The following is supposed to morph the scene into the Earth scene, +### The following is supposed to morph the scene into the Earth scene, ### but it doesn't work @@ -1045,7 +1046,7 @@ class ScreenShapingScene(Scene): self.wait() - # Scene 5: constant screen size, changing opening angle + screen = slanted_screen source.screen = screen @@ -1062,27 +1063,57 @@ class ScreenShapingScene(Scene): self.remove(original_brightness_rect) - shifted_brightness_rect = brightness_rect.copy() - shifted_brightness_rect.shift([-3,0,0]).set_fill(opacity = 0.8) + + # Scene 5: constant screen size, changing opening angle + + # let's use an actual light indicator instead of just rects + + indicator_intensity = 0.25 + indicator_height = 1.25 * screen_height + + # indicator_origin = Ellipse(width = 0, height = screen_height).move_to(screen.get_center()) + # indicator_origin.set_fill(opacity = 0) + # self.add_foreground_mobject(indicator_origin) + + indicator = LightIndicator(radius = indicator_height/2, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + color = LIGHT_COLOR, + precision = 2) + indicator.set_intensity(indicator_intensity) + + indicator.move_to(slanted_brightness_rect.get_center()) + self.play( - ApplyMethod(screen.shift,[-3,0,0]), - ApplyMethod(morty.shift,[-3,0,0]), - #ApplyMethod(brightness_rect.shift,[-3,0,0]), - #ApplyMethod(brightness_rect.set_fill,{"opacity": 0.8}) - ReplacementTransform(brightness_rect,shifted_brightness_rect) + FadeOut(slanted_brightness_rect), + FadeIn(indicator) + ) + + self.add_foreground_mobject(indicator.reading) + + new_indicator_intensity = 1.0 + + # shifted_brightness_rect = brightness_rect.copy() + # shifted_brightness_rect.shift([-3,0,0]).set_fill(opacity = 0.8) + + left_shift = (screen.get_center()[0] - source.source_point[0])/2 + + self.play( + ApplyMethod(screen.shift,[-left_shift,0,0]), + ApplyMethod(morty.shift,[-left_shift,0,0]), + ApplyMethod(indicator.shift,[-left_shift,0,0]), + ApplyMethod(indicator.set_intensity,new_indicator_intensity), ) self.remove(original_screen) # was still hiding behind the shadow - self.remove(shifted_brightness_rect) # also one too many - + # add distance indicator left_x = source.source_point[0] right_x = screen.get_center()[0] - indicator_y = -2 - line1 = Arrow([left_x,indicator_y,0],[right_x,indicator_y,0]) - line2 = Arrow([right_x,indicator_y,0],[left_x,indicator_y,0]) + line_y = -2 + line1 = Arrow([left_x,line_y,0],[right_x,line_y,0]) + line2 = Arrow([right_x,line_y,0],[left_x,line_y,0]) line1.set_fill(color = WHITE) line2.set_fill(color = WHITE) distance_decimal = Integer(1).next_to(line1,DOWN) @@ -1093,21 +1124,19 @@ class ScreenShapingScene(Scene): # move everything away distance_to_source = right_x - left_x new_right_x = left_x + 2*distance_to_source - new_line1 = Arrow([left_x,indicator_y,0],[new_right_x,indicator_y,0]) - new_line2 = Arrow([new_right_x,indicator_y,0],[left_x,indicator_y,0]) + new_line1 = Arrow([left_x,line_y,0],[new_right_x,line_y,0]) + new_line2 = Arrow([new_right_x,line_y,0],[left_x,line_y,0]) new_line1.set_fill(color = WHITE) new_line2.set_fill(color = WHITE) new_distance_decimal = Integer(2).next_to(new_line1,DOWN) - new_line = VGroup(new_line1, new_line2,new_distance_decimal) + new_line = VGroup(new_line1, new_line2, new_distance_decimal) - new_brightness_rect = brightness_rect.copy() - new_brightness_rect.shift([distance_to_source,0,0]) - new_brightness_rect.set_fill(opacity = 0.2) self.play( ReplacementTransform(line,new_line), ApplyMethod(screen.shift,[distance_to_source,0,0]), - Transform(brightness_rect,new_brightness_rect), + ApplyMethod(indicator.shift,[left_shift,0,0]), + ApplyMethod(indicator.set_intensity,indicator_intensity), ApplyMethod(morty.shift,[distance_to_source,0,0]), ) From e139d54ba98086d001f82f1e022052a68c422490 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Sat, 27 Jan 2018 19:09:59 +0100 Subject: [PATCH 27/33] Added Ellipse --- topics/geometry.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/topics/geometry.py b/topics/geometry.py index b9695f46..a802b199 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -128,6 +128,19 @@ class Dot(Circle): self.shift(point) self.init_colors() +class Ellipse(VMobject): + CONFIG = { + "width" : 2, + "height" : 1 + } + + def generate_points(self): + circle = Circle(radius = 1) + circle = circle.stretch_to_fit_width(self.width) + circle = circle.stretch_to_fit_height(self.height) + self.points = circle.points + + class AnnularSector(VMobject): CONFIG = { "inner_radius" : 1, From 1a44b0e52d992933242328dbb935deb872737bad Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 28 Jan 2018 16:26:00 -0800 Subject: [PATCH 28/33] Final touches to Fourier Thumbnail --- active_projects/fourier.py | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index fde2569c..429b5041 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -4190,15 +4190,11 @@ class FourierEndScreen(PatreonEndScreen): class Thumbnail(Scene): def construct(self): title = TextMobject("Fourier\\\\", "Visualized") - title[1].highlight(YELLOW) - title[1].set_stroke(RED, 1) title.highlight(YELLOW) - title.set_stroke(RED, 1) - + title.set_stroke(RED, 2) title.scale(2.5) title.add_background_rectangle() - # title.to_edge(UP) - # self.add(title) + def func(t): return np.cos(2*TAU*t) + np.cos(3*TAU*t) + np.cos(5*t) fourier = get_fourier_transform(func, -5, 5) @@ -4206,36 +4202,40 @@ class Thumbnail(Scene): graph = FunctionGraph(func, x_min = -5, x_max = 5) graph.highlight(BLUE) fourier_graph = FunctionGraph(fourier, x_min = 0, x_max = 6) - fourier_graph.highlight(RED_C) + fourier_graph.highlight(YELLOW) for g in graph, fourier_graph: g.stretch_to_fit_height(2) g.stretch_to_fit_width(10) g.set_stroke(width = 8) - arrow = Vector( - 2.5*DOWN, - rectangular_stem_width = 0.2, - tip_length = 0.5, - color = WHITE - ) - q_mark = TextMobject("?").scale(2) - q_mark.highlight(YELLOW) - q_mark.set_stroke(RED, 1) - arrows = VGroup(*it.chain(*zip( - [q_mark.copy() for x in range(5)], - [arrow.copy() for x in range(5)] - ))) - # arrows.submobjects.pop() - # arrows.arrange_submobjects(RIGHT, buff = MED_LARGE_BUFF) - group = VGroup(graph, title, fourier_graph) - arrows = VGroup(*[arrow.copy() for x in range(2)]) - arrows.arrange_submobjects(RIGHT, buff = 6*LARGE_BUFF) - # group = VGroup(graph, arrows, fourier_graph) - group.arrange_submobjects(DOWN) - # group.next_to(title, DOWN, MED_LARGE_BUFF) - arrows.move_to(title) - title.shift(MED_SMALL_BUFF*UP) - graph.shift(SMALL_BUFF*UP) - self.add(arrows, group) + + pol_graphs = VGroup() + for f in np.linspace(1.98, 2.02, 7): + pol_graph = ParametricFunction( + lambda t : complex_to_R3( + (2+np.cos(2*TAU*t)+np.cos(3*TAU*t))*np.exp(-complex(0, TAU*f*t)) + ), + t_min = -5, + t_max = 5, + num_graph_points = 200, + ) + pol_graph.match_color(graph) + pol_graph.scale_to_fit_height(2) + pol_graphs.add(pol_graph) + pol_graphs.arrange_submobjects(RIGHT, buff = LARGE_BUFF) + pol_graphs.gradient_highlight(BLUE_C, YELLOW) + pol_graphs.match_width(graph) + pol_graphs.set_stroke(width = 2) + + + self.clear() + title.center().to_edge(UP) + pol_graphs.scale_to_fit_width(2*SPACE_WIDTH - 1) + pol_graphs.center() + title.move_to(pol_graphs) + title.shift(SMALL_BUFF*LEFT) + graph.next_to(title, UP) + fourier_graph.next_to(title, DOWN) + self.add(pol_graphs, title, graph, fourier_graph) From 32b46e078c6ab01988054e2c607c66e403291ced Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 28 Jan 2018 16:27:07 -0800 Subject: [PATCH 29/33] Small adjustments to VMobject.insert_n_anchor_points --- mobject/vectorized_mobject.py | 3 ++- topics/geometry.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 2fb398c2..f663367e 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -353,7 +353,8 @@ class VMobject(Mobject): for index in range(num_curves): curr_bezier_points = self.points[3*index:3*index+4] num_inter_curves = sum(index_allocation == index) - alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves) + alphas = np.linspace(0, 1, num_inter_curves+1) + # alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves) for a, b in zip(alphas, alphas[1:]): new_points = partial_bezier_points( curr_bezier_points, a, b diff --git a/topics/geometry.py b/topics/geometry.py index 3605d8b6..42cec5a9 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -333,6 +333,17 @@ class Line(VMobject): self.shift(new_start - self.get_start()) return self + def insert_n_anchor_points(self, n): + if not self.path_arc: + n_anchors = self.get_num_anchor_points() + new_num_points = 3*(n_anchors + n)+1 + self.points = np.array([ + self.point_from_proportion(alpha) + for alpha in np.linspace(0, 1, new_num_points) + ]) + else: + VMobject.insert_n_anchor_points(self, n) + class DashedLine(Line): CONFIG = { "dashed_segment_length" : 0.05 From b38a8dade674b1fc1fb4c991e0fae6f69183579f Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Mon, 29 Jan 2018 15:20:57 +0100 Subject: [PATCH 30/33] refactoring scenes 2-5, started on scene 6 (faux 3D) --- active_projects/basel.py | 596 +++++++++++++++++++++++++++------------ 1 file changed, 410 insertions(+), 186 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 71135f30..a29a14bb 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -50,12 +50,9 @@ NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 AMBIENT_FULL = 1.0 AMBIENT_DIMMED = 0.2 - +LIGHT_COLOR = YELLOW DEGREES = TAU/360 - -def show_line_length(line): - v = line.points[1] - line.points[0] - print v[0]**2 + v[1]**2 +SWITCH_ON_RUN_TIME = 1.5 class AngleUpdater(ContinualAnimation): @@ -79,9 +76,18 @@ class AngleUpdater(ContinualAnimation): -LIGHT_COLOR = YELLOW -DEGREES = 360/TAU -SWITCH_ON_RUN_TIME = 1.5 + + + + + + + + + + + + class AmbientLight(VMobject): @@ -214,7 +220,6 @@ class Spotlight(VMobject): return u def move_source_to(self,point): - print "moving source" self.source_point = np.array(point) self.recalculate_sectors(point = point, screen = self.screen) self.update_shadow(point = point) @@ -375,6 +380,28 @@ class UpdateLightIndicator(AnimationGroup): self.mobject = indicator + + + + + + + + + + + + + + + + + + + + + + class IntroScene(PiCreatureScene): CONFIG = { @@ -388,16 +415,10 @@ class IntroScene(PiCreatureScene): randy = self.get_primary_pi_creature() randy.scale(0.7).to_corner(DOWN+RIGHT) - self.force_skipping() - self.build_up_euler_sum() self.build_up_sum_on_number_line() self.show_pi_answer() self.other_pi_formulas() - - self.revert_to_original_skipping_status() - - self.refocus_on_euler_sum() @@ -595,20 +616,41 @@ class IntroScene(PiCreatureScene): ScaleInPlace(pi_squared,2,rate_func = wiggle) ) - q_circle = Circle(color=WHITE,radius=0.8) - q_mark = TexMobject("?") - q_mark.next_to(q_circle) - thought = Group(q_circle, q_mark) - q_mark.height *= 2 - self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 1.5, "width" : 2 }) - self.wait() + # Morty thinks of a circle + + # q_circle = Circle(color=WHITE,radius=0.8) + # q_mark = TexMobject("?") + # q_mark.next_to(q_circle) + + # thought = Group(q_circle, q_mark) + # q_mark.height *= 2 + # self.pi_creature_thinks(thought,target_mode = "confused", + # bubble_kwargs = { "height" : 1.5, "width" : 2 }) + + # self.wait() -class FirstLightHouseScene(PiCreatureScene): + + + + + + + + + + + + + + + + + +class FirstLighthouseScene(PiCreatureScene): def construct(self): self.remove(self.get_primary_pi_creature()) @@ -771,21 +813,43 @@ class FirstLightHouseScene(PiCreatureScene): -class SingleLightHouseScene(PiCreatureScene): + + + + + + + + + + + + + + + + + + +class SingleLighthouseScene(PiCreatureScene): def construct(self): - self.create_light_source_and_creature() + self.setup_elements() + self.setup_trackers() # spotlight and angle msmt change when screen rotates + self.rotate_screen() - def create_light_source_and_creature(self): + def setup_elements(self): SCREEN_SIZE = 3.0 DISTANCE_FROM_LIGHTHOUSE = 10.0 source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] + # Lighthouse + lighthouse = LightHouse() ambient_light = AmbientLight( opacity_function = inverse_quadratic(AMBIENT_FULL,2,1), @@ -795,6 +859,9 @@ class SingleLightHouseScene(PiCreatureScene): ) lighthouse.scale(2).next_to(source_point, DOWN, buff = 0) ambient_light.move_to(source_point) + + # Pi Creature + morty = self.get_primary_pi_creature() morty.scale(0.5) morty.move_to(observer_point) @@ -803,72 +870,95 @@ class SingleLightHouseScene(PiCreatureScene): SwitchOn(ambient_light) ) + # Screen - screen = Line([0,-1,0],[0,1,0]) - screen.rotate(-TAU/6) - screen.next_to(morty, LEFT, buff = 1) + self.screen = Line([0,-1,0],[0,1,0]) + self.screen.rotate(-TAU/6) + self.screen.next_to(morty, LEFT, buff = 1) - spotlight = Spotlight( + # Spotlight + + self.spotlight = Spotlight( opacity_function = inverse_quadratic(1,2,1), num_levels = NUM_LEVELS, radius = 10, brightness = 5, - screen = screen + screen = self.screen ) - spotlight.move_source_to(source_point) + self.spotlight.move_source_to(source_point) + # Animations + self.play( ApplyMethod(ambient_light.dimming,AMBIENT_DIMMED), - FadeIn(spotlight)) - self.add(spotlight.shadow) + FadeIn(self.spotlight) + ) + self.add(self.spotlight.shadow) self.add_foreground_mobject(morty) - screen_tracker = ScreenTracker(spotlight) - # activate ONLY when spotlight is moving! + + + + def setup_trackers(self): + + # Make spotlight follow the screen + + screen_tracker = ScreenTracker(self.spotlight) self.add(screen_tracker) - pointing_screen_at_source = ApplyMethod(spotlight.screen.rotate,TAU/6) + pointing_screen_at_source = Rotate(self.screen,TAU/6) self.play(pointing_screen_at_source) - + # angle msmt (arc) - arc_angle = spotlight.opening_angle() + arc_angle = self.spotlight.opening_angle() # draw arc arrows to show the opening angle - angle_arc = Arc(radius = 5, start_angle = spotlight.start_angle(), - angle = spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) + angle_arc = Arc(radius = 5, start_angle = self.spotlight.start_angle(), + angle = self.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) #angle_arc.add_tip(at_start = True, at_end = True) - angle_arc.move_arc_center_to(source_point) + angle_arc.move_arc_center_to(self.spotlight.source_point) self.add(angle_arc) + # angle msmt (decimal number) + 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: spotlight.opening_angle()/TAU * 360 - ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func) - self.add(ca3) + angle_update_func = lambda x: self.spotlight.opening_angle()/TAU * 360 + ca1 = ContinualChangingDecimal(angle_indicator,angle_update_func) + self.add(ca1) - ca4 = AngleUpdater(angle_arc, spotlight) - self.add(ca4) + ca2 = AngleUpdater(angle_arc, self.spotlight) + self.add(ca2) - rotating_screen = ApplyMethod(spotlight.screen.rotate, - TAU/8, run_time=1.5) - #self.wait(2) - rotating_screen_2 = ApplyMethod(spotlight.screen.rotate, - -TAU/4, run_time=3, rate_func = there_and_back) - #self.wait(2) - rotating_screen_3 = ApplyMethod(spotlight.screen.rotate, - TAU/8, run_time=1.5) - self.play(rotating_screen) + def rotate_screen(self): + + # rotating_screen_1 = Rotate(self.screen, + # TAU/8, run_time=1.5) + # #self.wait(2) + # rotating_screen_2 = Rotate(self.screen, + # -TAU/4, run_time=3) + # #self.wait(2) + # rotating_screen_3 = Rotate(self.screen, + # TAU/8, run_time=1.5) + + # self.play(rotating_screen_1) + # self.play(rotating_screen_2) + # self.play(rotating_screen_3) + + rotating_screen_1 = Rotate(self.screen, TAU/8, rate_func = there_and_back) + rotating_screen_2 = Rotate(self.screen, -TAU/8, rate_func = there_and_back) + self.play(rotating_screen_1) self.play(rotating_screen_2) - self.play(rotating_screen_3) + #self.wait() @@ -880,8 +970,8 @@ class SingleLightHouseScene(PiCreatureScene): # # morph into Earth scene - # globe = Circle(radius = 3) - # globe.move_to([2,0,0]) + # earth = Circle(radius = 3) + # earth.move_to([2,0,0]) # sun_position = [-100,0,0] # #self.add(screen_tracker) # print "tuet" @@ -900,248 +990,382 @@ class SingleLightHouseScene(PiCreatureScene): + + + + + + + + + + + + + + + + + class EarthScene(Scene): def construct(self): - DEGREES = TAU/360 radius = 2.5 center_x = 3 - theta0 = 80 * DEGREES + theta0 = 70 * DEGREES dtheta = 10 * DEGREES theta1 = theta0 + dtheta + + + # screen + screen = Line([center_x - radius * np.cos(theta0),radius * np.sin(theta0),0], [center_x - radius * np.cos(theta1),radius * np.sin(theta1),0]) screen.set_stroke(color = RED, width = 5) - globe = Circle(radius = radius, stroke_width = 0) - globe.move_to([center_x,0,0]) - foreground_globe = globe.copy() # above the shadow - foreground_globe.radius -= 0.2 - foreground_globe.set_stroke(color = WHITE, width = 1) - self.add_foreground_mobject(foreground_globe) - globe.add(screen) + # Earth + + earth = Circle(radius = radius, stroke_width = 0) + earth.move_to([center_x,0,0]) + foreground_earth = earth.copy() # above the shadow + foreground_earth.radius -= 0.2 + foreground_earth.set_stroke(color = WHITE, width = 1) + self.add_foreground_mobject(foreground_earth) + earth.add(screen) + + # Morty morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) self.add_foreground_mobject(morty) + + # Light source (far-away Sun) + sun = Spotlight( opacity_function = lambda r : 0.5, num_levels = NUM_LEVELS, - radius = 100, + radius = 1100, brightness = 5, screen = screen ) - sun.move_source_to([-90,0,0]) - self.add(globe,sun,screen) + sun.move_source_to([-1000,0,0]) + # Add elements to scene + + self.add(earth,sun,screen) screen_tracker = ScreenTracker(sun) - self.add(screen_tracker) + + + # move screen to equator + self.play( - ApplyMethod(globe.rotate,theta0 + dtheta/2), - ApplyMethod(morty.move_to,[1.5,0,0]) + Rotate(earth,theta0 + dtheta/2,run_time = 3), + ApplyMethod(morty.move_to,[1.5,0,0], run_time = 3), ) + + + + + + + + + + + + + + + + + + + + + + class ScreenShapingScene(Scene): def construct(self): - DEGREES = TAU / 360 + self.setup_elements() + self.deform_screen() + self.create_brightness_rect() + self.slant_screen() + self.unslant_screen() + self.left_shift_screen_while_showing_light_indicator() + self.add_distance_arrow() + self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() + self.left_shift_again() + self.morph_into_faux_3d() - screen_height = 1.0 - brightness_rect_height = 1.0 - screen = Line([3,-screen_height/2,0],[3,screen_height/2,0], path_arc = 0, num_arc_anchors = 10) + def setup_elements(self): - source = Spotlight( + self.screen_height = 1.0 + self.brightness_rect_height = 1.0 + + # screen + self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], + path_arc = 0, num_arc_anchors = 10) + + # spotlight + self.spotlight = Spotlight( opacity_function = inverse_quadratic(1,5,1), num_levels = NUM_LEVELS, radius = 10, brightness = 5, - screen = screen + screen = self.screen ) - source.move_source_to([-5,0,0]) + self.spotlight.move_source_to([-5,0,0]) + screen_tracker = ScreenTracker(self.spotlight) + # lighthouse lighthouse = LightHouse() + lighthouse.scale(2).next_to(self.spotlight.source_point,DOWN,buff=0) + + # ambient light ambient_light = AmbientLight( opacity_function = inverse_quadratic(AMBIENT_DIMMED,1,1), num_levels = NUM_LEVELS, radius = 10, brightness = 1, ) - lighthouse.scale(2).next_to(source.source_point,DOWN,buff=0) - ambient_light.move_source_to(source.source_point) + ambient_light.move_source_to(self.spotlight.source_point) - self.add(lighthouse, ambient_light,source,screen) - - morty = Mortimer().scale(0.3).next_to(screen, RIGHT, buff = 0.5) - self.add_foreground_mobject(morty) - - self.wait() - - screen_tracker = ScreenTracker(source) + # Morty + self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) + # Add everything to the scene + self.add(lighthouse, ambient_light,self.spotlight,self.screen) + self.add_foreground_mobject(self.morty) self.add(screen_tracker) - self.play( - ApplyMethod(screen.set_path_arc, 45 * DEGREES), - ) + + + + def deform_screen(self): + + self.wait() + + self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) + self.play(ApplyMethod(self.screen.set_path_arc, 0)) + + + + + def create_brightness_rect(self): + + # in preparation for the slanting, create a rectangle that shows the brightness + + # a rect a zero width overlaying the screen + # so we can morph it into the brightness rect above + brightness_rect0 = Rectangle(width = 0, + height = self.screen_height).move_to(self.screen.get_center()) + self.add_foreground_mobject(brightness_rect0) + + self.brightness_rect = Rectangle(width = self.brightness_rect_height, + height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) + + self.brightness_rect.next_to(self.screen, UP, buff = 1) self.play( - ApplyMethod(screen.set_path_arc, -90 * DEGREES), + ReplacementTransform(brightness_rect0,self.brightness_rect) ) - self.play( - ApplyMethod(screen.set_path_arc, 0), - ) - - - # in preparation for the slanting, create a rectangle that show the brightness - - rect_origin = Rectangle(width = 0, height = screen_height).move_to(screen.get_center()) - self.add_foreground_mobject(rect_origin) - - brightness_rect = Rectangle(width = brightness_rect_height, - height = brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) - - brightness_rect.next_to(screen, UP, buff = 1) - - self.play( - ReplacementTransform(rect_origin,brightness_rect) - ) - - original_screen = screen.copy() - original_brightness_rect = brightness_rect.copy() + self.original_screen = self.screen.copy() + self.original_brightness_rect = self.brightness_rect.copy() # for unslanting the screen later - lower_screen_point, upper_screen_point = screen.get_start_and_end() + + def slant_screen(self): + + lower_screen_point, upper_screen_point = self.screen.get_start_and_end() lower_slanted_screen_point = interpolate( - lower_screen_point, source.source_point, 0.2 + lower_screen_point, self.spotlight.source_point, 0.2 ) upper_slanted_screen_point = interpolate( - upper_screen_point, source.source_point, -0.2 + upper_screen_point, self.spotlight.source_point, -0.2 ) - slanted_brightness_rect = brightness_rect.copy() - slanted_brightness_rect.width *= 2 - slanted_brightness_rect.generate_points() - slanted_brightness_rect.set_fill(opacity = 0.25) + self.slanted_brightness_rect = self.brightness_rect.copy() + self.slanted_brightness_rect.width *= 2 + self.slanted_brightness_rect.generate_points() + self.slanted_brightness_rect.set_fill(opacity = 0.25) - slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, + self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, path_arc = 0, num_arc_anchors = 10) - slanted_brightness_rect.move_to(brightness_rect.get_center()) + self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) self.play( - ReplacementTransform(screen,slanted_screen), - ReplacementTransform(brightness_rect,slanted_brightness_rect), + ReplacementTransform(self.screen,self.slanted_screen), + ReplacementTransform(self.brightness_rect,self.slanted_brightness_rect), ) + + + def unslant_screen(self): + self.wait() - - screen = slanted_screen - source.screen = screen - self.remove(slanted_screen) - - brightness_rect = slanted_brightness_rect - self.remove(slanted_brightness_rect) + self.remove(self.slanted_screen) + self.remove(self.slanted_brightness_rect) self.play( - ReplacementTransform(screen,original_screen), - ReplacementTransform(brightness_rect,original_brightness_rect), + ReplacementTransform(self.screen,self.original_screen), + ReplacementTransform(self.slanted_brightness_rect,self.original_brightness_rect), ) - self.remove(original_brightness_rect) + #self.remove(self.original_brightness_rect) + + + def left_shift_screen_while_showing_light_indicator(self): # Scene 5: constant screen size, changing opening angle # let's use an actual light indicator instead of just rects - indicator_intensity = 0.25 - indicator_height = 1.25 * screen_height + self.indicator_intensity = 0.25 + indicator_height = 1.25 * self.screen_height - # indicator_origin = Ellipse(width = 0, height = screen_height).move_to(screen.get_center()) - # indicator_origin.set_fill(opacity = 0) - # self.add_foreground_mobject(indicator_origin) + # indicator0 = Ellipse(width = 0, height = screen_height).move_to(self.screen.get_center()) + # indicator0.set_fill(opacity = 0) + # self.add_foreground_mobject(indicator0) - indicator = LightIndicator(radius = indicator_height/2, + self.indicator = LightIndicator(radius = indicator_height/2, opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, color = LIGHT_COLOR, precision = 2) - indicator.set_intensity(indicator_intensity) + self.indicator.set_intensity(self.indicator_intensity) - indicator.move_to(slanted_brightness_rect.get_center()) + + + self.indicator.move_to(self.original_brightness_rect.get_center()) self.play( - FadeOut(slanted_brightness_rect), - FadeIn(indicator) + FadeOut(self.original_brightness_rect), + FadeIn(self.indicator) ) - self.add_foreground_mobject(indicator.reading) + self.add_foreground_mobject(self.indicator.reading) - new_indicator_intensity = 1.0 + self.unit_indicator_intensity = 1.0 - # shifted_brightness_rect = brightness_rect.copy() - # shifted_brightness_rect.shift([-3,0,0]).set_fill(opacity = 0.8) - - left_shift = (screen.get_center()[0] - source.source_point[0])/2 + self.left_shift = (self.screen.get_center()[0] - self.spotlight.source_point[0])/2 self.play( - ApplyMethod(screen.shift,[-left_shift,0,0]), - ApplyMethod(morty.shift,[-left_shift,0,0]), - ApplyMethod(indicator.shift,[-left_shift,0,0]), - ApplyMethod(indicator.set_intensity,new_indicator_intensity), + ApplyMethod(self.screen.shift,[-self.left_shift,0,0]), + ApplyMethod(self.morty.shift,[-self.left_shift,0,0]), + #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), + ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), ) - self.remove(original_screen) # was still hiding behind the shadow + self.remove(self.original_screen) # was still hiding behind the shadow - # add distance indicator - - left_x = source.source_point[0] - right_x = screen.get_center()[0] - line_y = -2 - line1 = Arrow([left_x,line_y,0],[right_x,line_y,0]) - line2 = Arrow([right_x,line_y,0],[left_x,line_y,0]) - line1.set_fill(color = WHITE) - line2.set_fill(color = WHITE) - distance_decimal = Integer(1).next_to(line1,DOWN) - line = VGroup(line1, line2,distance_decimal) - self.add(line) - # move everything away - distance_to_source = right_x - left_x - new_right_x = left_x + 2*distance_to_source - new_line1 = Arrow([left_x,line_y,0],[new_right_x,line_y,0]) - new_line2 = Arrow([new_right_x,line_y,0],[left_x,line_y,0]) - new_line1.set_fill(color = WHITE) - new_line2.set_fill(color = WHITE) - new_distance_decimal = Integer(2).next_to(new_line1,DOWN) - new_line = VGroup(new_line1, new_line2, new_distance_decimal) + def add_distance_arrow(self): + # distance arrow (length 1) + left_x = self.spotlight.source_point[0] + right_x = self.screen.get_center()[0] + arrow_y = -2 + arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) + arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) + arrow1.set_fill(color = WHITE) + arrow2.set_fill(color = WHITE) + distance_decimal = Integer(1).next_to(arrow1,DOWN) + self.arrow = VGroup(arrow1, arrow2,distance_decimal) + self.add(self.arrow) + + + # distance arrow (length 2) + # will be morphed into + self.distance_to_source = right_x - left_x + new_right_x = left_x + 2 * self.distance_to_source + new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) + new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) + new_arrow1.set_fill(color = WHITE) + new_arrow2.set_fill(color = WHITE) + new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) + self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) + # don't add it yet + + + def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): + + self.wait() self.play( - ReplacementTransform(line,new_line), - ApplyMethod(screen.shift,[distance_to_source,0,0]), - ApplyMethod(indicator.shift,[left_shift,0,0]), - ApplyMethod(indicator.set_intensity,indicator_intensity), - ApplyMethod(morty.shift,[distance_to_source,0,0]), + ReplacementTransform(self.arrow,self.new_arrow), + ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), + #ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), + + ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), + # this should trigger ChangingDecimal, but it doesn't + # maybe bc it's an anim within an anim? + + ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), ) - + def left_shift_again(self): + + self.wait() + + self.play( + ReplacementTransform(self.new_arrow,self.arrow), + ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), + #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), + ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), + ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), + ) + + def morph_into_faux_3d(self): + + p0_lower, p0_upper = self.screen.get_start_and_end() + + p01 = p0_lower + p02 = p0_lower + p03 = p0_upper + p04 = p0_upper + + screen3d0 = Polygon(p01,p02,p03,p04) + screen3d0.set_stroke(width = 1, color = WHITE) + + screen3d0_edge = VMobject() + screen3d0_edge.set_anchor_points([p03,p04,p01]) + + perspective_v = 0.5 * np.array([1,-1,0]) + p1 = p01 + 0.5 * perspective_v + p2 = p02 - 0.5 * perspective_v + p3 = p03 - 0.5 * perspective_v + p4 = p04 + 0.5 * perspective_v + + screen3d = Polygon(p1,p2,p3,p4) + screen3d.set_fill(color = WHITE, opacity = 0.5) + + screen3d_edge = VMobject() + screen3d_edge.set_anchor_points([p3,p4,p1]) + + self.spotlight.screen = screen3d0_edge + + self.play(Transform(self.spotlight.screen,screen3d_edge)) From 36c13661240ddc3b27db24b22a7594041f1e6e85 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:49:15 +0100 Subject: [PATCH 31/33] Added my playground files (for syncing btw my machines) --- .gitignore | 3 - ben_cairo_test.py | 54 +++++++++ ben_playground.py | 303 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 ben_cairo_test.py create mode 100644 ben_playground.py diff --git a/.gitignore b/.gitignore index f0f3ca3f..29cac87a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ -ben_playground.py - -ben_cairo_test.py diff --git a/ben_cairo_test.py b/ben_cairo_test.py new file mode 100644 index 00000000..85af2269 --- /dev/null +++ b/ben_cairo_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +"""demonstrate pycairo and pygame""" + +from __future__ import print_function + +import math +import sys + +import cairo +import pygame + + +def draw(surface): + x, y, radius = (250, 250, 200) + ctx = cairo.Context(surface) + ctx.set_line_width(15) + ctx.arc(x, y, radius, 0, 2.0 * math.pi) + ctx.set_source_rgb(0.8, 0.8, 0.8) + ctx.fill_preserve() + ctx.set_source_rgb(1, 1, 1) + ctx.stroke() + + +def input(events): + for event in events: + if event.type == pygame.QUIT: + sys.exit(0) + else: + print(event) + + +def main(): + width, height = 512, 512 + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + + pygame.init() + pygame.display.set_mode((width, height)) + screen = pygame.display.get_surface() + + draw(surface) + + # Create PyGame surface from Cairo Surface + buf = surface.get_data() + image = pygame.image.frombuffer(buf, (width, height), "ARGB") + # Tranfer to Screen + screen.blit(image, (0, 0)) + pygame.display.flip() + + while True: + input(pygame.event.get()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ben_playground.py b/ben_playground.py new file mode 100644 index 00000000..1b7445fd --- /dev/null +++ b/ben_playground.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * +from mobject.point_cloud_mobject import PointCloudDot + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.continual_animation import * +from animation.playground import * + +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.combinatorics import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + +from mobject.vectorized_mobject import * + +## To watch one of these scenes, run the following: +## python extract_scene.py -p file_name + +LIGHT_COLOR = YELLOW +DEGREES = 360/TAU +SWITCH_ON_RUN_TIME = 1.5 + + +class AmbientLight(VMobject): + + # Parameters are: + # * a source point + # * possibly a target + # * an opacity function and its inverse + # * a light color + # * a max opacity + + # If the target is None, create annuli. + # If there is a target, create annular sectors. + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0 + } + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + # create annuli + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annulus = Annulus( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha + ) + annulus.move_arc_center_to(self.source_point) + self.add(annulus) + + # else: + + # # look for the screen and create annular sectors + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # dr = self.radius / self.num_levels + # for r in np.arange(0, self.radius, dr): + # alpha = self.max_opacity * self.opacity_function(r) + # annular_sector = AnnularSector( + # inner_radius = r, + # outer_radius = r + dr, + # color = self.color, + # fill_opacity = alpha, + # start_angle = lower_angle, + # angle = upper_angle - lower_angle + # ) + # annular_sector.move_arc_center_to(self.source_point) + # self.add(annular_sector) + + + + + + # def redraw(self): + # if self.screen != None: + # lower_angle, upper_angle = self.viewing_angles(self.screen) + # for submob in self.submobjects: + # if type(submob) == AnnularSector: + # submob.start_angle = lower_angle + # submob.angle = upper_angle - lower_angle + # submob.generate_points() + # submob.move_arc_center_to(self.source_point) + + + #submob.generate_points() + + + def move_source_to(self,point): + self.source_point = np.array(point) + for submob in self.submobjects: + if type(submob) == Annulus: + submob.shift(self.source_point - submob.get_center()) + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + +class Spotlight(VMobject): + + CONFIG = { + "source_point" : ORIGIN, + "opacity_function" : lambda r : 1.0/(r+1.0)**2, + "color" : LIGHT_COLOR, + "max_opacity" : 1.0, + "num_levels" : 10, + "radius" : 5.0, + "screen" : None + } + + def track_screen(self): + self.generate_points() + + def generate_points(self): + + for submob in self.submobjects: + self.remove(submob) + + if self.screen != None: + # look for the screen and create annular sectors + lower_angle, upper_angle = self.viewing_angles(self.screen) + dr = self.radius / self.num_levels + for r in np.arange(0, self.radius, dr): + alpha = self.max_opacity * self.opacity_function(r) + annular_sector = AnnularSector( + inner_radius = r, + outer_radius = r + dr, + color = self.color, + fill_opacity = alpha, + start_angle = lower_angle, + angle = upper_angle - lower_angle + ) + annular_sector.move_arc_center_to(self.source_point) + self.add(annular_sector) + + + def viewing_angle_of_point(self,point): + distance_vector = point - self.source_point + angle = angle_of_vector(distance_vector) + return angle + + def viewing_angles(self,screen): + + viewing_angles = np.array(map(self.viewing_angle_of_point, + screen.get_anchors())) + lower_angle = upper_angle = 0 + if len(viewing_angles) != 0: + lower_angle = np.min(viewing_angles) + upper_angle = np.max(viewing_angles) + + return lower_angle, upper_angle + + def move_source_to(self,point): + self.source_point = np.array(point) + self.shift(np.array(point) - self.source_point) + self.generate_points() + + def dimming(self,new_alpha): + old_alpha = self.max_opacity + self.max_opacity = new_alpha + for submob in self.submobjects: + old_submob_alpha = submob.fill_opacity + new_submob_alpha = old_submob_alpha * new_alpha/old_alpha + submob.set_fill(opacity = new_submob_alpha) + + + +class SwitchOn(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + LaggedStart.__init__(self, + FadeIn, light, **kwargs) + + +class SwitchOff(LaggedStart): + CONFIG = { + "lag_ratio": 0.2, + "run_time": SWITCH_ON_RUN_TIME + } + + def __init__(self, light, **kwargs): + if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): + raise Exception("Only LightCones and Candles can be switched on") + light.submobjects = light.submobjects[::-1] + LaggedStart.__init__(self, + FadeOut, light, **kwargs) + light.submobjects = light.submobjects[::-1] + + +# class LightSource(Mobject): + +# # A light source is composed of: +# # * a lighthouse +# # * possibly a screen +# # * and two light fields: +# # * an undirected one (annuli) +# # * one directed at the screen (annular sectors) + +# CONFIG = { +# "location" : ORIGIN, +# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), +# "ambient_light" : LightField(), +# "spot_light_field" : LightField(), +# } + +# def __init__(self,**kwargs): +# Mobject.__init__(self,**kwargs) +# self.icon.next_to(self.location, DOWN, buff = 0) +# self.spot_light_field.max_opacity = 0 +# self.ambient_light_field.move_source_to(self.location) +# self.spot_light_field.move_source_to(self.location) +# self.add(self.icon,self.ambient_light_field,self.spot_light_field) + +# def dim_ambient(self,new_alpha): +# self.ambient_light_field.dimming(new_alpha) + + + +class ScreenTracker(ContinualAnimation): + def __init__(self, spotlight, screen, **kwargs): + self.spotlight = spotlight + self.screen = screen + ContinualAnimation.__init__(self, self.spotlight, **kwargs) + +# def update_mobject(self, dt): + #self.spotlight.generate_points() + + + + +class IntroScene(Scene): + def construct(self): + + screen = Square().shift([4,0,0]) + #ambient_light = AmbientLight( + # source_point = np.array([-1,1,0]), + # max_opacity = 1.0, + # opacity_function = lambda r: 1.0/(r/1+1)**2, + # num_levels = 10, + #) + + #ambient_light.move_source_to([-5,0,0]) + self.add(screen)#,ambient_light) + + spotlight = Spotlight( + source_point = np.array([-1,1,0]), + max_opacity = 1.0, + opacity_function = lambda r: 1.0/(r/2+1)**2, + num_levels = 10, + screen = screen, + ) + + self.add(spotlight) + + ca = ScreenTracker(spotlight,screen) + self.add(ca) + + #self.play(SwitchOn(ambient_light)) + #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) + #self.play(SwitchOn(spotlight)) + #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) + #self.play(ApplyMethod(spotlight.dimming,0.2)) + self.play(screen.rotate, TAU/8, run_time = 3) + self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) + + self.wait() + + + From d6f1eca25c0bdef753b12c9ef22a2790df10cce3 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 26 Jan 2018 19:50:12 +0100 Subject: [PATCH 32/33] Revert "Added my playground files (for syncing btw my machines)" This reverts commit 2fd0687b596f47c0c2bfde4f1fa9b6ba5e3c2f2f. --- .gitignore | 3 + ben_cairo_test.py | 54 --------- ben_playground.py | 303 ---------------------------------------------- 3 files changed, 3 insertions(+), 357 deletions(-) delete mode 100644 ben_cairo_test.py delete mode 100644 ben_playground.py diff --git a/.gitignore b/.gitignore index 29cac87a..f0f3ca3f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ playground.py special_animations.py prettiness_hall_of_fame.py files/ +ben_playground.py + +ben_cairo_test.py diff --git a/ben_cairo_test.py b/ben_cairo_test.py deleted file mode 100644 index 85af2269..00000000 --- a/ben_cairo_test.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -"""demonstrate pycairo and pygame""" - -from __future__ import print_function - -import math -import sys - -import cairo -import pygame - - -def draw(surface): - x, y, radius = (250, 250, 200) - ctx = cairo.Context(surface) - ctx.set_line_width(15) - ctx.arc(x, y, radius, 0, 2.0 * math.pi) - ctx.set_source_rgb(0.8, 0.8, 0.8) - ctx.fill_preserve() - ctx.set_source_rgb(1, 1, 1) - ctx.stroke() - - -def input(events): - for event in events: - if event.type == pygame.QUIT: - sys.exit(0) - else: - print(event) - - -def main(): - width, height = 512, 512 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - - pygame.init() - pygame.display.set_mode((width, height)) - screen = pygame.display.get_surface() - - draw(surface) - - # Create PyGame surface from Cairo Surface - buf = surface.get_data() - image = pygame.image.frombuffer(buf, (width, height), "ARGB") - # Tranfer to Screen - screen.blit(image, (0, 0)) - pygame.display.flip() - - while True: - input(pygame.event.get()) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/ben_playground.py b/ben_playground.py deleted file mode 100644 index 1b7445fd..00000000 --- a/ben_playground.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env python - -from helpers import * - -from mobject.tex_mobject import TexMobject -from mobject import Mobject -from mobject.image_mobject import ImageMobject -from mobject.vectorized_mobject import * -from mobject.point_cloud_mobject import PointCloudDot - -from animation.animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.continual_animation import * -from animation.playground import * - -from topics.geometry import * -from topics.characters import * -from topics.functions import * -from topics.number_line import * -from topics.combinatorics import * -from scene import Scene -from camera import Camera -from mobject.svg_mobject import * -from mobject.tex_mobject import * - -from mobject.vectorized_mobject import * - -## To watch one of these scenes, run the following: -## python extract_scene.py -p file_name - -LIGHT_COLOR = YELLOW -DEGREES = 360/TAU -SWITCH_ON_RUN_TIME = 1.5 - - -class AmbientLight(VMobject): - - # Parameters are: - # * a source point - # * possibly a target - # * an opacity function and its inverse - # * a light color - # * a max opacity - - # If the target is None, create annuli. - # If there is a target, create annular sectors. - - CONFIG = { - "source_point" : ORIGIN, - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 5.0 - } - - def generate_points(self): - - for submob in self.submobjects: - self.remove(submob) - - # create annuli - dr = self.radius / self.num_levels - for r in np.arange(0, self.radius, dr): - alpha = self.max_opacity * self.opacity_function(r) - annulus = Annulus( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha - ) - annulus.move_arc_center_to(self.source_point) - self.add(annulus) - - # else: - - # # look for the screen and create annular sectors - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # dr = self.radius / self.num_levels - # for r in np.arange(0, self.radius, dr): - # alpha = self.max_opacity * self.opacity_function(r) - # annular_sector = AnnularSector( - # inner_radius = r, - # outer_radius = r + dr, - # color = self.color, - # fill_opacity = alpha, - # start_angle = lower_angle, - # angle = upper_angle - lower_angle - # ) - # annular_sector.move_arc_center_to(self.source_point) - # self.add(annular_sector) - - - - - - # def redraw(self): - # if self.screen != None: - # lower_angle, upper_angle = self.viewing_angles(self.screen) - # for submob in self.submobjects: - # if type(submob) == AnnularSector: - # submob.start_angle = lower_angle - # submob.angle = upper_angle - lower_angle - # submob.generate_points() - # submob.move_arc_center_to(self.source_point) - - - #submob.generate_points() - - - def move_source_to(self,point): - self.source_point = np.array(point) - for submob in self.submobjects: - if type(submob) == Annulus: - submob.shift(self.source_point - submob.get_center()) - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) - - -class Spotlight(VMobject): - - CONFIG = { - "source_point" : ORIGIN, - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 5.0, - "screen" : None - } - - def track_screen(self): - self.generate_points() - - def generate_points(self): - - for submob in self.submobjects: - self.remove(submob) - - if self.screen != None: - # look for the screen and create annular sectors - lower_angle, upper_angle = self.viewing_angles(self.screen) - dr = self.radius / self.num_levels - for r in np.arange(0, self.radius, dr): - alpha = self.max_opacity * self.opacity_function(r) - annular_sector = AnnularSector( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha, - start_angle = lower_angle, - angle = upper_angle - lower_angle - ) - annular_sector.move_arc_center_to(self.source_point) - self.add(annular_sector) - - - def viewing_angle_of_point(self,point): - distance_vector = point - self.source_point - angle = angle_of_vector(distance_vector) - return angle - - def viewing_angles(self,screen): - - viewing_angles = np.array(map(self.viewing_angle_of_point, - screen.get_anchors())) - lower_angle = upper_angle = 0 - if len(viewing_angles) != 0: - lower_angle = np.min(viewing_angles) - upper_angle = np.max(viewing_angles) - - return lower_angle, upper_angle - - def move_source_to(self,point): - self.source_point = np.array(point) - self.shift(np.array(point) - self.source_point) - self.generate_points() - - def dimming(self,new_alpha): - old_alpha = self.max_opacity - self.max_opacity = new_alpha - for submob in self.submobjects: - old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) - - - -class SwitchOn(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): - raise Exception("Only LightCones and Candles can be switched on") - LaggedStart.__init__(self, - FadeIn, light, **kwargs) - - -class SwitchOff(LaggedStart): - CONFIG = { - "lag_ratio": 0.2, - "run_time": SWITCH_ON_RUN_TIME - } - - def __init__(self, light, **kwargs): - if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight): - raise Exception("Only LightCones and Candles can be switched on") - light.submobjects = light.submobjects[::-1] - LaggedStart.__init__(self, - FadeOut, light, **kwargs) - light.submobjects = light.submobjects[::-1] - - -# class LightSource(Mobject): - -# # A light source is composed of: -# # * a lighthouse -# # * possibly a screen -# # * and two light fields: -# # * an undirected one (annuli) -# # * one directed at the screen (annular sectors) - -# CONFIG = { -# "location" : ORIGIN, -# "icon" : SVGMobject(file_name = 'lighthouse', height = 0.5), -# "ambient_light" : LightField(), -# "spot_light_field" : LightField(), -# } - -# def __init__(self,**kwargs): -# Mobject.__init__(self,**kwargs) -# self.icon.next_to(self.location, DOWN, buff = 0) -# self.spot_light_field.max_opacity = 0 -# self.ambient_light_field.move_source_to(self.location) -# self.spot_light_field.move_source_to(self.location) -# self.add(self.icon,self.ambient_light_field,self.spot_light_field) - -# def dim_ambient(self,new_alpha): -# self.ambient_light_field.dimming(new_alpha) - - - -class ScreenTracker(ContinualAnimation): - def __init__(self, spotlight, screen, **kwargs): - self.spotlight = spotlight - self.screen = screen - ContinualAnimation.__init__(self, self.spotlight, **kwargs) - -# def update_mobject(self, dt): - #self.spotlight.generate_points() - - - - -class IntroScene(Scene): - def construct(self): - - screen = Square().shift([4,0,0]) - #ambient_light = AmbientLight( - # source_point = np.array([-1,1,0]), - # max_opacity = 1.0, - # opacity_function = lambda r: 1.0/(r/1+1)**2, - # num_levels = 10, - #) - - #ambient_light.move_source_to([-5,0,0]) - self.add(screen)#,ambient_light) - - spotlight = Spotlight( - source_point = np.array([-1,1,0]), - max_opacity = 1.0, - opacity_function = lambda r: 1.0/(r/2+1)**2, - num_levels = 10, - screen = screen, - ) - - self.add(spotlight) - - ca = ScreenTracker(spotlight,screen) - self.add(ca) - - #self.play(SwitchOn(ambient_light)) - #self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0])) - #self.play(SwitchOn(spotlight)) - #self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0])) - #self.play(ApplyMethod(spotlight.dimming,0.2)) - self.play(screen.rotate, TAU/8, run_time = 3) - self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0])) - - self.wait() - - - From 28ba8fea9ce9c16e199ed7738441307d741739b2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 29 Jan 2018 12:05:59 -0800 Subject: [PATCH 33/33] Fixed alignment of Decimal unit --- topics/numerals.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/topics/numerals.py b/topics/numerals.py index 44e28323..0a1337a0 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -12,7 +12,7 @@ class DecimalNumber(VMobject): "num_decimal_points" : 2, "digit_to_digit_buff" : 0.05, "show_ellipsis" : False, - "unit" : None, + "unit" : None, #Aligned to bottom unless it starts with "^" "include_background_rectangle" : False, } def __init__(self, number, **kwargs): @@ -54,7 +54,7 @@ class DecimalNumber(VMobject): 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": + if self.unit.startswith("^"): self[-1].align_to(self, UP) # if self.include_background_rectangle: @@ -80,7 +80,7 @@ class ChangingDecimal(Animation): "num_decimal_points" : None, "show_ellipsis" : None, "position_update_func" : None, - "tracked_mobject" : None + "tracked_mobject" : None, } def __init__(self, decimal_number_mobject, number_update_func, **kwargs): digest_config(self, kwargs, locals())