diff --git a/animation/animation.py b/animation/animation.py index 82b05c8f..75732e91 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -30,32 +30,6 @@ class Animation(object): def __str__(self): return self.name - def get_points_and_rgbs(self): - """ - It is the responsibility of this class to only emit points within - the space. Returns np array of points and corresponding np array - of rgbs - """ - #TODO, I don't think this should be necessary. This should happen - #under the individual mobjects. - points = self.mobject.points - rgbs = self.mobject.rgbs - #Filters out what is out of bounds. - admissibles = (abs(points[:,0]) < self.restricted_width) * \ - (abs(points[:,1]) < self.restricted_height) - for filter_function in self.filter_functions: - admissibles *= ~filter_function(points) - if any(self.spatial_center): - points += self.spatial_center - #Filter out points pushed off the edge - admissibles *= (abs(points[:,0]) < SPACE_WIDTH) * \ - (abs(points[:,1]) < SPACE_HEIGHT) - if rgbs.shape[0] < points.shape[0]: - #TODO, this shouldn't be necessary, find what's happening. - points = points[:rgbs.shape[0], :] - admissibles = admissibles[:rgbs.shape[0]] - return points[admissibles, :], rgbs[admissibles, :] - def update(self, alpha): if alpha < 0: alpha = 0.0 diff --git a/animation/meta_animations.py b/animation/meta_animations.py index 5673b7c1..c5e2fcaf 100644 --- a/animation/meta_animations.py +++ b/animation/meta_animations.py @@ -4,6 +4,7 @@ import itertools as it from helpers import * from animation import Animation from transform import Transform +from mobject import Mobject class DelayByOrder(Animation): @@ -43,6 +44,7 @@ class TransformAnimations(Transform): "alpha_func" : squish_alpha_func(smooth) } def __init__(self, start_anim, end_anim, **kwargs): + digest_config(self, kwargs, locals()) if "run_time" in kwargs: self.run_time = kwargs.pop("run_time") else: diff --git a/animation/simple_animations.py b/animation/simple_animations.py index ed837cec..98117f46 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -10,15 +10,27 @@ from transform import Transform class Rotating(Animation): DEFAULT_CONFIG = { - "axes" : [RIGHT, UP], - "radians" : 2*np.pi, - "run_time" : 20.0, + "axes" : [RIGHT, UP], + "axis" : None, + "radians" : 2*np.pi, + "run_time" : 20.0, "alpha_func" : None, + "in_place" : True, } def update_mobject(self, alpha): - self.mobject.points = self.starting_mobject.points - for axis in self.axes: - self.mobject.rotate(self.radians * alpha, axis) + axes = [self.axis] if self.axis is not None else self.axes + families = [ + self.mobject.get_full_submobject_family(), + self.starting_mobject.get_full_submobject_family() + ] + for mob, start in zip(*families): + mob.points = np.array(start.points) + if self.in_place: + method = self.mobject.rotate_in_place + else: + method = self.mobject.rotate + method(alpha*self.radians, axes = axes) + class FadeOut(Animation): def update_mobject(self, alpha): diff --git a/animation/transform.py b/animation/transform.py index 896a9d43..5a977437 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -103,9 +103,17 @@ class ApplyMethod(Transform): ) class Rotate(ApplyMethod): - def __init__(self, mobject, angle = np.pi, **kwargs): + DEFAULT_CONFIG = { + "in_place" : False, + } + def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs): kwargs["interpolation_function"] = path_along_arc(angle) - ApplyMethod.__init__(self, mobject.rotate, angle, **kwargs) + digest_config(self, kwargs, locals()) + if self.in_place: + method = mobject.rotate_in_place + else: + method = mobject.rotate + ApplyMethod.__init__(self, method, angle, axis, **kwargs) class ApplyPointwiseFunction(ApplyMethod): diff --git a/mobject/mobject.py b/mobject/mobject.py index a113982d..7dc8861d 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -120,12 +120,17 @@ class Mobject(object): mob.points *= scale_factor return self - def rotate(self, angle, axis = OUT): - t_rotation_matrix = np.transpose(rotation_matrix(angle, axis)) + def rotate(self, angle, axis = OUT, axes = []): + if len(axes) == 0: + axes = [axis] + rot_matrix = np.identity(self.DIM) + for axis in axes: + rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis)) + t_rot_matrix = np.transpose(rot_matrix) for mob in self.get_full_submobject_family(): - mob.points = np.dot(mob.points, t_rotation_matrix) + mob.points = np.dot(mob.points, t_rot_matrix) if mob.has_normals: - mob.unit_normals = np.dot(mob.unit_normals, t_rotation_matrix) + mob.unit_normals = np.dot(mob.unit_normals, t_rot_matrix) return self def stretch(self, factor, dim): @@ -165,6 +170,8 @@ class Mobject(object): def filter_out(self, condition): for mob in self.get_full_submobject_family(): + if len(mob.points) == 0: + continue to_eliminate = ~np.apply_along_axis(condition, 1, mob.points) mob.points = mob.points[to_eliminate] mob.rgbs = mob.rgbs[to_eliminate] @@ -205,8 +212,8 @@ class Mobject(object): self.shift(center) return self - def rotate_in_place(self, angle, axis = OUT): - self.do_in_place(self.rotate, angle, axis) + def rotate_in_place(self, angle, axis = OUT, axes = []): + self.do_in_place(self.rotate, angle, axis, axes) return self def scale_in_place(self, scale_factor): @@ -237,7 +244,7 @@ class Mobject(object): def to_edge(self, edge = LEFT, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): return self.align_on_border(edge, buff) - def next_to(self, mobject, + def next_to(self, mobject, direction = RIGHT, buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, aligned_edge = ORIGIN): @@ -314,13 +321,10 @@ class Mobject(object): return 0 def get_merged_array(self, array_attr): - return reduce( - lambda a1, a2 : np.append(a1, a2, axis = 0), - [getattr(self, array_attr)] + [ - mob.get_merged_array(array_attr) - for mob in self.sub_mobjects - ] - ) + result = np.zeros((0, self.DIM)) + for mob in self.get_full_submobject_family(): + result = np.append(result, getattr(mob, array_attr), 0) + return result def get_all_points(self): return self.get_merged_array("points") diff --git a/topics/number_line.py b/topics/number_line.py index 5599c551..1f052a22 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -229,6 +229,7 @@ class XYZAxes(Mobject1D): self.z_axis = self.x_axis.copy().rotate(np.pi/2, DOWN) self.digest_mobject_attrs() + self.pose_at_angle() class SpaceGrid(Mobject1D): @@ -246,6 +247,7 @@ class SpaceGrid(Mobject1D): start = np.array([a, b, -self.radius])[perm] end = np.array([a, b, self.radius])[perm] self.add_line(start, end) + self.pose_at_angle() diff --git a/transform_article.py b/transform_article.py index 7811fc4a..d87a24b9 100644 --- a/transform_article.py +++ b/transform_article.py @@ -7,7 +7,9 @@ def half_plane(): x_radius = SPACE_WIDTH/2, x_unit_to_spatial_width = 0.5, y_unit_to_spatial_height = 0.5, - density = 2*DEFAULT_POINT_DENSITY_1D, + x_faded_line_frequency = 0, + y_faded_line_frequency = 0, + density = 4*DEFAULT_POINT_DENSITY_1D, ) plane.add_coordinates( x_vals = range(-6, 7, 2), @@ -71,12 +73,12 @@ class SingleVariableFunction(Scene): class LineToPlaneFunction(Scene): args_list = [ - (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", {}), - (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", { - "0" : 0, - "\\frac{\\pi}{2}" : np.pi/2, - "\\pi" : np.pi - }) + (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", []), + (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", [ + ("0", "(1, 0)", 0), + ("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2), + ("\\pi", "(-1, 0)", np.pi), + ]) ] @staticmethod @@ -97,6 +99,10 @@ class LineToPlaneFunction(Scene): line.add_numbers(*range(0, 14, 2)) divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) plane = half_plane() + plane.sub_mobjects = [] + plane.filter_out( + lambda (x, y, z) : abs(x) > 0.1 and abs(y) > 0.1 + ) plane.shift(0.5*SPACE_WIDTH*RIGHT) self.add(line, divider, plane) @@ -110,7 +116,7 @@ class LineToPlaneFunction(Scene): anims = [Transform(line_copy, target, **anim_config)] colors = iter([BLUE_B, GREEN_D, RED_D]) - for tex, number in numbers_to_follow.items(): + for input_tex, output_tex, number in numbers_to_follow: center = line.number_to_point(number) dot = Dot(center, color = colors.next()) anims.append(ApplyMethod( @@ -118,7 +124,7 @@ class LineToPlaneFunction(Scene): point_function(center) - center, **anim_config )) - label = TexMobject(tex) + label = TexMobject(input_tex) label.shift(center + 2*UP) arrow = Arrow(label, dot) self.add(label) @@ -131,6 +137,17 @@ class LineToPlaneFunction(Scene): self.play(*anims) self.dither() + for input_tex, output_tex, number in numbers_to_follow: + point = plane.num_pair_to_point(func(number)) + label = TexMobject(output_tex) + side_shift = LEFT if number == np.pi else RIGHT + label.shift(point, 2*UP, side_shift) + arrow = Arrow(label, point) + self.add(label) + self.play(ShowCreation(arrow)) + self.dither(2) + self.remove(arrow, label) + class PlaneToPlaneFunctionSeparatePlanes(Scene): args_list = [ (lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic") @@ -185,9 +202,119 @@ class PlaneToPlaneFunction(Scene): self.play(ApplyPointwiseFunction(point_function, plane, **anim_config)) self.dither(3) - - - +class PlaneToLineFunction(Scene): + args_list = [ + (lambda (x, y) : x**2 + y**2, "Bowl"), + ] + + @staticmethod + def args_to_string(func, name): + return name + + def construct(self, func, name): + line = NumberLine( + color = GREEN, + unit_length_to_spatial_width = 0.5, + tick_frequency = 1, + number_at_center = 6, + numerical_radius = 6, + numbers_with_elongated_ticks = [0, 12], + ).to_edge(RIGHT) + line.add_numbers() + plane = half_plane().to_edge(LEFT, buff = 0) + + divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) + line_left = line.number_to_point(0) + def point_function(point): + shifter = 0.5*SPACE_WIDTH*RIGHT + return func((point+shifter)[:2])*RIGHT + line_left + + self.add(line, plane, divider) + self.dither() + plane.sub_mobjects = [] + self.play(ApplyPointwiseFunction(point_function, plane)) + self.dither() + + + +class PlaneToSpaceFunction(Scene): + args_list = [ + (lambda (x, y) : (x*x, x*y, y*y), "Quadratic"), + ] + + @staticmethod + def args_to_string(func, name): + return name + + def construct(self, func, name): + plane = half_plane().shift(0.5*SPACE_WIDTH*LEFT) + divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) + axes = XYZAxes() + axes.filter_out(lambda p : np.linalg.norm(p) > 3) + rot_kwargs = { + "run_time" : 3, + "radians" : 0.3*np.pi, + "axis" : [0.1, 1, 0.1], + } + axes.to_edge(RIGHT).shift(DOWN) + dampening_factor = 0.1 + def point_function((x, y, z)): + return dampening_factor*np.array(func((x, y))) + target = NumberPlane().apply_function(point_function) + target.highlight("yellow") + target.shift(axes.get_center()) + + self.add(plane, divider, axes) + self.play(Rotating(axes, **rot_kwargs)) + + target.rotate_in_place(rot_kwargs["radians"]) + self.play( + TransformAnimations( + Animation(plane.copy()), + Rotating(target, **rot_kwargs), + alpha_func = smooth + ), + Rotating(axes, **rot_kwargs) + ) + axes.add(target) + self.clear() + self.add(plane, divider, axes) + self.play(Rotating(axes, **rot_kwargs)) + self.clear() + for i in range(5): + self.play(Rotating(axes, **rot_kwargs)) + + +class SpaceToSpaceFunction(Scene): + args_list = [ + (lambda (x, y, z) : (y*z, x*z, x*y), "Quadratic"), + ] + + @staticmethod + def args_to_string(func, name): + return name + + def construct(self, func, name): + space = SpaceGrid() + rot_kwargs = { + "run_time" : 10, + "radians" : 2*np.pi/5, + "axis" : [0.1, 1, 0.1], + "in_place" : False, + } + axes = XYZAxes() + target = space.copy().apply_function(func) + + self.play( + TransformAnimations( + Rotating(space, **rot_kwargs), + Rotating(target, **rot_kwargs), + alpha_func = squish_alpha_func(smooth, 0.3, 0.7) + ), + Rotating(axes, **rot_kwargs) + ) + axes.add(space) + self.play(Rotating(axes, **rot_kwargs))