From 40d0fcc974ba0bae912a51d15b815eed3c91489a Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 10:34:08 +0100 Subject: [PATCH 01/13] [3D CAMERA] New feature: give the possibility to change the center of rotation. --- topics/three_dimensions.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index b0c18645..809979f5 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -36,8 +36,11 @@ class ThreeDCamera(CameraWithPerspective): def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) - ## Lives in the phi-theta-distance space + ## rotation_mobject lives in the phi-theta-distance space self.rotation_mobject = VectorizedPoint() + ## moving_center lives in the x-y-z space + ## It representes the center of rotation + self.moving_center = VectorizedPoint(self.space_center) self.set_position(self.phi, self.theta, self.distance) def get_color(self, method): @@ -126,6 +129,16 @@ class ThreeDCamera(CameraWithPerspective): np.cos(phi) ]) + def get_center_of_rotation(self, x = None, y = None, z = None): + curr_x, curr_y, curr_z = self.moving_center.points[0] + if x is None: + x = curr_x + if y is None: + y = curr_y + if z is None: + z = curr_z + return np.array([x, y, z]) + def set_position(self, phi = None, theta = None, distance = None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) @@ -140,6 +153,8 @@ class ThreeDCamera(CameraWithPerspective): def points_to_pixel_coords(self, points): matrix = self.get_view_transformation_matrix() new_points = np.dot(points, matrix.T) + self.space_center = self.moving_center.points[0] + return Camera.points_to_pixel_coords(self, new_points) class ThreeDScene(Scene): @@ -167,6 +182,7 @@ class ThreeDScene(Scene): def move_camera( self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None, added_anims = [], **kwargs ): @@ -176,10 +192,17 @@ class ThreeDScene(Scene): target_point, **kwargs ) + target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z) + movement_center = ApplyMethod( + self.camera.moving_center.move_to, + target_center, + **kwargs + ) is_camera_rotating = self.ambient_camera_rotation in self.continual_animations if is_camera_rotating: self.remove(self.ambient_camera_rotation) - self.play(movement, *added_anims) + self.play(movement, movement_center, *added_anims) + target_point = self.camera.get_spherical_coords(phi, theta, distance) if is_camera_rotating: self.add(self.ambient_camera_rotation) From fb458de37d4531a6998af8d03134ba558768af49 Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 11:10:17 +0100 Subject: [PATCH 02/13] [3D CAMERA] Add an example 'Rotation3d' to see the feature. --- example_scenes.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/example_scenes.py b/example_scenes.py index 6855b3cd..4561c1fc 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -23,6 +23,8 @@ from mobject.tex_mobject import * from mobject.vectorized_mobject import * +from topics.three_dimensions import * + # To watch one of these scenes, run the following: # python extract_scene.py file_name -p # @@ -58,6 +60,67 @@ class WriteStuff(Scene): self.play(Write(TextMobject("Stuff").scale(3))) +class Rotation3d(ThreeDScene): + def construct(self): + # STEP 1 + # Build two cube in the 3D scene, one for around the origin, + # the other shifted along the vector RIGHT + UP + OUT + cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1., + side_length = 1., fill_color = WHITE) + + # RIGHT side: Red + # UP side: Green + # OUT side: Blue + orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN] + for face, orient in zip(cube_origin.family_members_with_points(), orientations): + if np.array_equal(orient, RIGHT): + face.set_style_data(fill_color = RED) + elif np.array_equal(orient, UP): + face.set_style_data(fill_color = GREEN) + elif np.array_equal(orient, OUT): + face.set_style_data(fill_color = BLUE) + + cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1., + side_length = 1., fill_color = BLUE) + shift_vec = 2*(RIGHT + UP + OUT) + cube_shifted.shift(shift_vec) + + # STEP 2 + # Add the cubes in the 3D scene + self.add(cube_origin) + self.add(cube_shifted) + + # STEP 3 + # Setup the camera position + phi, theta, distance = ThreeDCamera().get_spherical_coords() + angle_factor = 0.9 + phi += 2*np.pi/4*angle_factor + theta += 3*2*np.pi/8 + self.set_camera_position(phi, theta, distance) + self.wait() + + # STEP 4 + # Animation + # Animation 1: rotation around the Z-axis with the ORIGIN of the space + # as center of rotation + theta += 2*np.pi + self.move_camera(phi, theta, distance, + run_time = 5) + + # Animation 2: shift the space in order of to get the center of the shifted cube + # as the next center of rotation + cube_center = cube_shifted.get_center() + self.move_camera(center_x = cube_center[0], + center_y = cube_center[1], + center_z = cube_center[2], + run_time = 2) + + # Animation 3: rotation around the Z-axis with the center of the shifted cube + # as center of rotation + theta += 2*np.pi + self.move_camera(phi, theta, distance, + run_time = 5) + From 2c85f66dddd470656d773c9c45800e10296b2b48 Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 15:12:07 +0100 Subject: [PATCH 03/13] [3D CAMERA] Add parameter to set the camera position. --- topics/three_dimensions.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 809979f5..ba9611cb 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -139,10 +139,14 @@ class ThreeDCamera(CameraWithPerspective): z = curr_z return np.array([x, y, z]) - def set_position(self, phi = None, theta = None, distance = None): + def set_position(self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) self.phi, self.theta, self.distance = point + center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z) + self.moving_center.move_to(center_of_rotation) + self.space_center = self.moving_center.points[0] def get_view_transformation_matrix(self): return (self.default_distance / self.get_distance()) * np.dot( @@ -163,8 +167,9 @@ class ThreeDScene(Scene): "ambient_camera_rotation" : None, } - def set_camera_position(self, phi = None, theta = None, distance = None): - self.camera.set_position(phi, theta, distance) + def set_camera_position(self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None): + self.camera.set_position(phi, theta, distance, center_x, center_y, center_z) def begin_ambient_camera_rotation(self, rate = 0.01): self.ambient_camera_rotation = AmbientMovement( @@ -180,9 +185,9 @@ class ThreeDScene(Scene): self.ambient_camera_rotation = None def move_camera( - self, + self, phi = None, theta = None, distance = None, - center_x = None, center_y = None, center_z = None, + center_x = None, center_y = None, center_z = None, added_anims = [], **kwargs ): From e92e718122ba14f79cf7fdfd4e971f875f038104 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 22:46:53 -0800 Subject: [PATCH 04/13] uncertainty progress --- active_projects/uncertainty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py index 31da08af..62bdcc05 100644 --- a/active_projects/uncertainty.py +++ b/active_projects/uncertainty.py @@ -327,7 +327,7 @@ class FourierTradeoff(Scene): #Draw items self.add(time_axes, frequency_axes) - self.play(ShowCreation(wave_packet)) + self.play(ShowCreation(wave_packet, rate_func = double_smooth)) self.play( ReplacementTransform( wave_packet.copy(), From ebc53e3948927708aa9432cd9997d0f0b5908fbd Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:33:41 +0100 Subject: [PATCH 05/13] add requirements for BraceLabel --- topics/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/objects.py b/topics/objects.py index a91c2b33..2ef461fe 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -3,10 +3,10 @@ from helpers import * from mobject import Mobject from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint from mobject.svg_mobject import SVGMobject -from mobject.tex_mobject import TextMobject, TexMobject +from mobject.tex_mobject import TextMobject, TexMobject, Brace from animation import Animation -from animation.simple_animations import Rotating, LaggedStart +from animation.simple_animations import Rotating, LaggedStart, AnimationGroup from animation.transform import ApplyMethod, FadeIn, GrowFromCenter from topics.geometry import Circle, Line, Rectangle, Square, \ From bc83c8a4f33f0afc5cf6ca77946f05a0772e0670 Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:35:51 +0100 Subject: [PATCH 06/13] SVG transforms hopefully finally handled properly --- mobject/svg_mobject.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 0b3b0b6c..8d0fbbc8 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -159,31 +159,52 @@ class SVGMobject(VMobject): x = float(element.getAttribute('x')) #Flip y y = -float(element.getAttribute('y')) + mobject.shift(x*RIGHT+y*UP) except: pass - try: - transform = element.getAttribute('transform') + transform = element.getAttribute('transform') + + try: # transform matrix prefix = "matrix(" suffix = ")" if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() transform = transform[len(prefix):-len(suffix)] transform = string_to_numbers(transform) transform = np.array(transform).reshape([3,2]) - x += transform[2][0] - y -= transform[2][1] + x = transform[2][0] + y = -transform[2][1] matrix = np.identity(self.dim) matrix[:2,:2] = transform[:2,:] - t_matrix = np.transpose(matrix) + matrix[1] *= -1 + matrix[:,1] *= -1 for mob in mobject.family_members_with_points(): - mob.points = np.dot(mob.points, t_matrix) - + mob.points = np.dot(mob.points, matrix) + mobject.shift(x*RIGHT+y*UP) except: pass - mobject.shift(x*RIGHT+y*UP) - #TODO, transforms + try: # transform scale + prefix = "scale(" + suffix = ")" + if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + transform = transform[len(prefix):-len(suffix)] + scale_x, scale_y = string_to_numbers(transform) + mobject.scale(np.array([scale_x, scale_y, 1])) + except: + pass + + try: # transform translate + prefix = "translate(" + suffix = ")" + if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + transform = transform[len(prefix):-len(suffix)] + x, y = string_to_numbers(transform) + mobject.shift(x*RIGHT + y*DOWN) + except: + pass + #TODO, ... def update_ref_to_element(self, defs): new_refs = dict([ From 6b39ba0502786d63d13671325a78351f89dfd3be Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:41:34 +0100 Subject: [PATCH 07/13] Feature: argument coor_mask for move_to / next_to: setting coor_mask = X_AXIS or Y_AXIS makes the alignment in just one coordinate --- constants.py | 3 +++ mobject/mobject.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/constants.py b/constants.py index 2efb773f..cac15811 100644 --- a/constants.py +++ b/constants.py @@ -53,6 +53,9 @@ RIGHT = np.array(( 1., 0., 0.)) LEFT = np.array((-1., 0., 0.)) IN = np.array(( 0., 0.,-1.)) OUT = np.array(( 0., 0., 1.)) +X_AXIS = np.array(( 1., 0., 0.)) +Y_AXIS = np.array(( 0., 1., 0.)) +Z_AXIS = np.array(( 0., 0., 1.)) TOP = SPACE_HEIGHT*UP BOTTOM = SPACE_HEIGHT*DOWN diff --git a/mobject/mobject.py b/mobject/mobject.py index cf01b713..6618d1aa 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -296,6 +296,7 @@ class Mobject(Container): aligned_edge = ORIGIN, submobject_to_align = None, index_of_submobject_to_align = None, + coor_mask = np.array([1,1,1]), ): if isinstance(mobject_or_point, Mobject): mob = mobject_or_point @@ -315,7 +316,7 @@ class Mobject(Container): else: aligner = self point_to_align = aligner.get_critical_point(aligned_edge - direction) - self.shift(target_point - point_to_align + buff*direction) + self.shift((target_point - point_to_align + buff*direction)*coor_mask) return self def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): @@ -403,13 +404,14 @@ class Mobject(Container): submob.scale(1./factor) return self - def move_to(self, point_or_mobject, aligned_edge = ORIGIN): + def move_to(self, point_or_mobject, aligned_edge = ORIGIN, + coor_mask = np.array([1,1,1])): if isinstance(point_or_mobject, Mobject): target = point_or_mobject.get_critical_point(aligned_edge) else: target = point_or_mobject point_to_align = self.get_critical_point(aligned_edge) - self.shift(target - point_to_align) + self.shift((target - point_to_align)*coor_mask) return self def replace(self, mobject, dim_to_match = 0, stretch = False): From 6f829c98fb9a9ddba36bfa6c27207160ef66dbb0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 12 Feb 2018 20:14:07 -0800 Subject: [PATCH 08/13] Tie the render quality of a scene to its name so that files don't overwrite each other --- scene/scene.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scene/scene.py b/scene/scene.py index 1cb81bff..9611dbd0 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -54,7 +54,8 @@ class Scene(Container): self.frame_num = 0 self.current_scene_time = 0 if self.name is None: - self.name = self.__class__.__name__ + quality = self.camera.pixel_shape[0] + self.name = self.__class__.__name__ + str(quality) if self.random_seed is not None: random.seed(self.random_seed) np.random.seed(self.random_seed) From 3b8bbbccab0edc465bb6844f5c10c4fcf99f495b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 12 Feb 2018 22:49:15 -0800 Subject: [PATCH 09/13] Beginning of ShowPlan in Uncertainty --- active_projects/uncertainty.py | 186 ++++++++++++++++++++++++--------- 1 file changed, 139 insertions(+), 47 deletions(-) diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py index 62bdcc05..421282ec 100644 --- a/active_projects/uncertainty.py +++ b/active_projects/uncertainty.py @@ -39,55 +39,46 @@ class GaussianDistributionWrapper(Line): """ This is meant to encode a 2d normal distribution as a mobject (so as to be able to have it be interpolated - during animations). It is a line whose start_point coordinates - encode the coordinates of mu, and whose end_point - start_point - encodes the coordinates of sigma. + during animations). It is a line whose center is the mean + mu of a distribution, and whose radial vector (center to end) + is the distribution's standard deviation """ CONFIG = { "stroke_width" : 0, - "mu_x" : 0, - "sigma_x" : 1, - "mu_y" : 0, - "sigma_y" : 0, + "mu" : ORIGIN, + "sigma" : RIGHT, } def __init__(self, **kwargs): Line.__init__(self, ORIGIN, RIGHT, **kwargs) - self.change_parameters(self.mu_x, self.mu_y, self.sigma_x, self.sigma_y) + self.change_parameters(self.mu, self.sigma) - def change_parameters(self, mu_x = None, mu_y = None, sigma_x = None, sigma_y = None): - curr_parameters = self.get_parameteters() - args = [mu_x, mu_y, sigma_x, sigma_y] - new_parameters = [ - arg or curr - for curr, arg in zip(curr_parameters, args) - ] - mu_x, mu_y, sigma_x, sigma_y = new_parameters - mu_point = mu_x*RIGHT + mu_y*UP - sigma_vect = sigma_x*RIGHT + sigma_y*UP - self.put_start_and_end_on(mu_point, mu_point + sigma_vect) + def change_parameters(self, mu = None, sigma = None): + curr_mu, curr_sigma = self.get_parameters() + mu = mu if mu is not None else curr_mu + sigma = sigma if sigma is not None else curr_sigma + self.put_start_and_end_on(mu - sigma, mu + sigma) return self - def get_parameteters(self): + def get_parameters(self): """ Return mu_x, mu_y, sigma_x, sigma_y""" - start, end = self.get_start_and_end() - return tuple(it.chain(start[:2], (end - start)[:2])) + center, end = self.get_center(), self.get_end() + return center, end-center def get_random_points(self, size = 1): - mu_x, mu_y, sigma_x, sigma_y = self.get_parameteters() - x_vals = np.random.normal(mu_x, sigma_x, size) - y_vals = np.random.normal(mu_y, sigma_y, size) + mu, sigma = self.get_parameters() return np.array([ - x*RIGHT + y*UP - for x, y in zip(x_vals, y_vals) + np.array([ + np.random.normal(mu_coord, sigma_coord) + for mu_coord, sigma_coord in zip(mu, sigma) + ]) + for x in range(size) ]) class ProbabalisticMobjectCloud(ContinualAnimation): CONFIG = { "fill_opacity" : 0.25, "n_copies" : 100, - "gaussian_distribution_wrapper_config" : { - "sigma_x" : 1, - } + "gaussian_distribution_wrapper_config" : {} } def __init__(self, prototype, **kwargs): digest_config(self, kwargs) @@ -142,6 +133,14 @@ class ProbabalisticVectorCloud(ProbabalisticMobjectCloud): point ) +class RadarDish(SVGMobject): + CONFIG = { + "file_name" : "radar_dish", + "color" : LIGHT_GREY, + } + + + ################### class MentionUncertaintyPrinciple(TeacherStudentsScene): @@ -152,32 +151,33 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): dot_cloud = ProbabalisticDotCloud() vector_cloud = ProbabalisticVectorCloud( gaussian_distribution_wrapper_config = {"sigma_x" : 0.2}, - center_func = dot_cloud.gaussian_distribution_wrapper.get_start, + center_func = lambda : dot_cloud.gaussian_distribution_wrapper.get_parameters()[0], ) for cloud in dot_cloud, vector_cloud: - gdw = cloud.gaussian_distribution_wrapper - gdw.move_to(title.get_center(), LEFT) - gdw.shift(2*DOWN) + cloud.gaussian_distribution_wrapper.next_to( + title, DOWN, 2*LARGE_BUFF + ) vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT) - def get_brace_text_group_update(gdw, vect, text): + def get_brace_text_group_update(gdw, vect, text, color): brace = Brace(gdw, vect) - text = brace.get_tex("\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF) + text = brace.get_tex("2\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF) group = VGroup(brace, text) def update_group(group): brace, text = group brace.match_width(gdw, stretch = True) brace.next_to(gdw, vect) text.next_to(brace, vect, buff = SMALL_BUFF) + group.highlight(color) return ContinualUpdateFromFunc(group, update_group) dot_brace_anim = get_brace_text_group_update( dot_cloud.gaussian_distribution_wrapper, - DOWN, "position", + DOWN, "position", dot_cloud.color ) vector_brace_anim = get_brace_text_group_update( vector_cloud.gaussian_distribution_wrapper, - UP, "momentum", + UP, "momentum", vector_cloud.color ) self.add(title) @@ -195,7 +195,7 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): # self.wait(2) self.play( dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 0.1}, + {"sigma" : 0.1*RIGHT}, run_time = 2, ) self.wait() @@ -206,7 +206,7 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): self.add(vector_brace_anim) self.play( vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 1}, + {"sigma" : RIGHT}, self.get_student_changes(*3*["confused"]), run_time = 3, ) @@ -214,17 +214,17 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): for x in range(2): self.play( dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 2}, + {"sigma" : 2*RIGHT}, vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 0.1}, + {"sigma" : 0.1*RIGHT}, run_time = 3, ) self.change_student_modes("thinking", "erm", "sassy") self.play( dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 0.1}, + {"sigma" : 0.1*RIGHT}, vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 1}, + {"sigma" : 1*RIGHT}, run_time = 3, ) self.wait() @@ -299,7 +299,8 @@ class FourierTradeoff(Scene): t_min = time_mean - time_radius, t_max = time_mean + time_radius, n_samples = 2*time_radius*17, - complex_to_real_func = abs, + # complex_to_real_func = abs, + complex_to_real_func = lambda z : z.real, color = FREQUENCY_COLOR, ) @@ -351,8 +352,99 @@ class FourierTradeoff(Scene): self.wait() self.wait() - - +class ShowPlan(PiCreatureScene): + def construct(self): + self.add_title() + words = self.get_words() + self.play_sound_anims(words[0]) + self.play_doppler_anims(words[1], words[0]) + self.play_quantum_anims(words[2], words[1]) + + def add_title(self): + title = TextMobject("The plan") + title.scale(1.5) + title.to_edge(UP) + h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH) + h_line.next_to(title, DOWN) + self.add(title, h_line) + + def get_words(self): + colors = [YELLOW, GREEN, BLUE] + topics = ["sound waves", "Doppler radar", "quantum particles"] + words = VGroup() + for topic, color in zip(topics, colors): + word = TextMobject("Uncertainty for", topic) + word[1].highlight(color) + words.add(word) + words.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = LARGE_BUFF) + words.to_edge(LEFT) + + return words + + def play_sound_anims(self, word): + morty = self.pi_creature + wave = FunctionGraph( + lambda x : 0.3*np.sin(15*x)*np.sin(0.5*x), + x_min = 0, x_max = 30, + num_anchor_points = 500, + ) + wave.next_to(word, RIGHT) + rect = BackgroundRectangle(wave, fill_opacity = 1) + rect.stretch(2, 1) + rect.next_to(wave, LEFT, buff = 0) + wave_shift = AmbientMovement( + wave, direction = LEFT, rate = 5 + ) + wave_fader = UpdateFromAlphaFunc( + wave, + lambda w, a : w.set_stroke(width = 3*a) + ) + checkmark = self.get_checkmark(word) + + self.add(wave_shift) + self.add_foreground_mobjects(rect, word) + self.play( + Animation(word), + wave_fader, + morty.change, "raise_right_hand", word + ) + self.wait(2) + wave_fader.rate_func = lambda a : 1-smooth(a) + self.add_foreground_mobjects(checkmark) + self.play( + Write(checkmark), + morty.change, "happy", + wave_fader, + ) + self.remove_foreground_mobjects(rect, word) + self.add(word) + self.wait() + + def play_doppler_anims(self, word, to_fade): + morty = self.pi_creature + + radar_dish = RadarDish() + radar_dish.next_to(to_fade, RIGHT) + self.add(radar_dish) + + self.play( + to_fade.fade, 0.5, + Write(word), + morty.change, "pondering", + run_time = 1 + ) + + def play_quantum_anims(self, word, to_fade): + pass + + ## + + def get_checkmark(self, word): + checkmark = TexMobject("\\checkmark") + checkmark.highlight(GREEN) + checkmark.scale(1.5) + checkmark.next_to(word, UP+RIGHT, buff = 0) + return checkmark From 1bea7fc8c51c7c5f0ebcf04e82ce0edcbe36abeb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 13 Feb 2018 11:44:04 -0800 Subject: [PATCH 10/13] Made naming by quality optional --- scene/scene.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scene/scene.py b/scene/scene.py index 9611dbd0..769ce11a 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -41,6 +41,7 @@ class Scene(Container): "random_seed" : 0, "start_at_animation_number" : None, "end_at_animation_number" : None, + "include_render_quality_in_name" : False, #TODO, nothing uses this right now } def __init__(self, **kwargs): Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter? @@ -54,8 +55,9 @@ class Scene(Container): self.frame_num = 0 self.current_scene_time = 0 if self.name is None: - quality = self.camera.pixel_shape[0] - self.name = self.__class__.__name__ + str(quality) + self.name = self.__class__.__name__ + if self.include_render_quality_in_name: + self.name += str(self.camera.pixel_shape[0]) if self.random_seed is not None: random.seed(self.random_seed) np.random.seed(self.random_seed) From e78cb80701693cad48994147f1dc51f6de204ad8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 13 Feb 2018 11:44:36 -0800 Subject: [PATCH 11/13] Implmented doppler radar pluse for uncertainty --- active_projects/uncertainty.py | 126 +++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py index 421282ec..267923d4 100644 --- a/active_projects/uncertainty.py +++ b/active_projects/uncertainty.py @@ -136,10 +136,97 @@ class ProbabalisticVectorCloud(ProbabalisticMobjectCloud): class RadarDish(SVGMobject): CONFIG = { "file_name" : "radar_dish", - "color" : LIGHT_GREY, + "fill_color" : LIGHT_GREY, + "stroke_color" : WHITE, + "stroke_width" : 1, + "height" : 1, } - +class RadarPulseSingleton(ContinualAnimation): + CONFIG = { + "speed" : 3.0, + "direction" : RIGHT, + "start_up_time" : 0, + "fade_in_time" : 0.5, + "color" : WHITE, + "stroke_width" : 3, + } + def __init__(self, radar_dish, target, **kwargs): + digest_config(self, kwargs) + self.direction = self.direction/np.linalg.norm(self.direction) + self.radar_dish = radar_dish + self.target = target + self.reflection_distance = None + self.arc = Arc( + start_angle = -30*DEGREES, + angle = 60*DEGREES, + ) + self.arc.scale_to_fit_height(0.75*radar_dish.get_height()) + self.arc.move_to(radar_dish, UP+RIGHT) + self.start_points = np.array(self.arc.points) + self.start_center = self.arc.get_center() + self.finished = False + + ContinualAnimation.__init__(self, self.arc, **kwargs) + + def update_mobject(self, dt): + arc = self.arc + total_distance = self.speed*self.internal_time + arc.points = np.array(self.start_points) + arc.shift(total_distance*self.direction) + + if self.internal_time < self.fade_in_time: + alpha = np.clip(self.internal_time/self.fade_in_time, 0, 1) + arc.set_stroke(self.color, alpha*self.stroke_width) + + if self.reflection_distance is None: + #Check if reflection is happening + arc_point = arc.get_edge_center(self.direction) + target_point = self.target.get_edge_center(-self.direction) + arc_distance = np.dot(arc_point, self.direction) + target_distance = np.dot(target_point, self.direction) + if arc_distance > target_distance: + self.reflection_distance = target_distance + #Don't use elif in case the above code creates reflection_distance + if self.reflection_distance is not None: + delta_distance = total_distance - self.reflection_distance + point_distances = np.dot(self.direction, arc.points.T) + diffs = point_distances - self.reflection_distance + shift_vals = np.outer(-2*np.maximum(diffs, 0), self.direction) + arc.points += shift_vals + + #Check if done + arc_point = arc.get_edge_center(-self.direction) + if np.dot(arc_point, self.direction) < np.dot(self.start_center, self.direction): + self.finished = True + self.arc.fade(1) + + def is_finished(self): + return self.finished + +class RadarPulse(ContinualAnimation): + CONFIG = { + "n_pulse_singletons" : 8, + "frequency" : 0.05, + "colors" : [BLUE, YELLOW] + } + def __init__(self, *args, **kwargs): + digest_config(self, kwargs) + colors = color_gradient(self.colors, self.n_pulse_singletons) + self.pulse_singletons = [ + RadarPulseSingleton(*args, color = color, **kwargs) + for color in colors + ] + pluse_mobjects = VGroup(*[ps.mobject for ps in self.pulse_singletons]) + ContinualAnimation.__init__(self, pluse_mobjects, **kwargs) + + def update_mobject(self, dt): + for i, ps in enumerate(self.pulse_singletons): + ps.internal_time = self.internal_time - i*self.frequency + ps.update_mobject(dt) + + def is_finished(self): + return all([ps.is_finished() for ps in self.pulse_singletons]) ################### @@ -424,15 +511,46 @@ class ShowPlan(PiCreatureScene): morty = self.pi_creature radar_dish = RadarDish() - radar_dish.next_to(to_fade, RIGHT) - self.add(radar_dish) + radar_dish.next_to(word, DOWN, aligned_edge = LEFT) + target = Square(stroke_width = 0) + target.set_fill(LIGHT_GREY, 1) + target.match_height(radar_dish) + target.next_to(radar_dish, RIGHT, buff = 0) + target_fade = UpdateFromAlphaFunc( + target, lambda m, a : m.set_fill(opacity = a) + ) + target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.5) + pulse = RadarPulse(radar_dish, target) + + checkmark = self.get_checkmark(word) + + self.add(target_movement) self.play( to_fade.fade, 0.5, Write(word), + DrawBorderThenFill(radar_dish), + target_fade, morty.change, "pondering", run_time = 1 ) + self.add(pulse) + while not pulse.is_finished(): + self.play( + morty.look_at, pulse.mobject, + run_time = 0.5 + ) + self.wait() + target_fade.rate_func = lambda a : smooth(1-a) + self.play( + Write(checkmark), + target_fade, + morty.change, "happy" + ) + self.wait() + + + def play_quantum_anims(self, word, to_fade): pass From e07fecc2f94569aa878e871cd098a4eaec807fc8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 13 Feb 2018 15:39:31 -0800 Subject: [PATCH 12/13] ThreeDMobject color fix --- topics/three_dimensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 55de2925..2d38cd55 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -44,7 +44,7 @@ class ThreeDCamera(CameraWithPerspective): if should_shade_in_3d(vmobject): return self.get_shaded_rgb(rgb, self.get_unit_normal_vect(vmobject)) else: - return color + return rgb def get_stroke_rgb(self, vmobject): return self.modified_rgb(vmobject, vmobject.get_stroke_rgb()) From fbec114f676184a1ff59373e686ab914a6e819f0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 13 Feb 2018 15:39:57 -0800 Subject: [PATCH 13/13] Small uncertainty additions --- active_projects/uncertainty.py | 37 ++++++++++++++++++++++------------ topics/geometry.py | 3 --- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py index 267923d4..fcbe9b22 100644 --- a/active_projects/uncertainty.py +++ b/active_projects/uncertainty.py @@ -142,6 +142,16 @@ class RadarDish(SVGMobject): "height" : 1, } +class Plane(SVGMobject): + CONFIG = { + "file_name" : "plane", + "color" : GREY, + "height" : 1, + } + def __init__(self, **kwargs): + SVGMobject.__init__(self, **kwargs) + self.rotate(-TAU/8) + class RadarPulseSingleton(ContinualAnimation): CONFIG = { "speed" : 3.0, @@ -512,14 +522,10 @@ class ShowPlan(PiCreatureScene): radar_dish = RadarDish() radar_dish.next_to(word, DOWN, aligned_edge = LEFT) - target = Square(stroke_width = 0) - target.set_fill(LIGHT_GREY, 1) - target.match_height(radar_dish) - target.next_to(radar_dish, RIGHT, buff = 0) - target_fade = UpdateFromAlphaFunc( - target, lambda m, a : m.set_fill(opacity = a) - ) - target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.5) + target = Plane() + # target.match_height(radar_dish) + target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF) + target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.25) pulse = RadarPulse(radar_dish, target) @@ -530,21 +536,26 @@ class ShowPlan(PiCreatureScene): to_fade.fade, 0.5, Write(word), DrawBorderThenFill(radar_dish), - target_fade, + UpdateFromAlphaFunc( + target, lambda m, a : m.set_fill(opacity = a) + ), morty.change, "pondering", run_time = 1 ) + self.wait() self.add(pulse) - while not pulse.is_finished(): + count = it.count() #TODO, this is not a great hack... + while not pulse.is_finished() and count.next() < 15: self.play( morty.look_at, pulse.mobject, run_time = 0.5 ) - self.wait() - target_fade.rate_func = lambda a : smooth(1-a) self.play( Write(checkmark), - target_fade, + UpdateFromAlphaFunc( + target, lambda m, a : m.set_fill(opacity = 1-a) + ), + FadeOut(radar_dish), morty.change, "happy" ) self.wait() diff --git a/topics/geometry.py b/topics/geometry.py index 8e3f910b..ec1b3f73 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -96,7 +96,6 @@ class Arc(VMobject): return self - class Circle(Arc): CONFIG = { "color" : RED, @@ -137,7 +136,6 @@ class Ellipse(VMobject): circle = circle.stretch_to_fit_height(self.height) self.points = circle.points - class AnnularSector(VMobject): CONFIG = { "inner_radius" : 1, @@ -187,7 +185,6 @@ class AnnularSector(VMobject): self.shift(v) return self - class Sector(AnnularSector): CONFIG = { "outer_radius" : 1,