diff --git a/animation/animation.py b/animation/animation.py index 9ca1a6ee..4051805d 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -64,14 +64,17 @@ class Animation(object): #Typically ipmlemented by subclass pass - def get_all_families_zipped(self): + def get_all_mobjects(self): """ Ordering must match the ording of arguments to update_submobject """ - return zip( - self.mobject.submobject_family(), - self.starting_mobject.submobject_family() - ) + return self.mobject, self.starting_mobject + + def get_all_families_zipped(self): + return zip(*map( + Mobject.family_members_with_points, + self.get_all_mobjects() + )) def get_sub_alpha(self, alpha, index, num_submobjects): if self.submobject_mode in ["lagged_start", "smoothed_lagged_start"]: diff --git a/animation/transform.py b/animation/transform.py index e18c2c75..24fbcb30 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -13,6 +13,7 @@ from mobject import Mobject, Point, VMobject, Group class Transform(Animation): CONFIG = { "path_arc" : 0, + "path_arc_axis" : OUT, "path_func" : None, "submobject_mode" : "all_at_once", "replace_mobject_with_target_in_scene" : False, @@ -31,7 +32,10 @@ class Transform(Animation): def update_config(self, **kwargs): Animation.update_config(self, **kwargs) if "path_arc" in kwargs: - self.path_func = path_along_arc(kwargs["path_arc"]) + self.path_func = path_along_arc( + kwargs["path_arc"], + kwargs["path_arc_axis"] + ) def init_path_func(self): if self.path_func is not None: @@ -39,13 +43,13 @@ class Transform(Animation): if self.path_arc == 0: self.path_func = straight_path else: - self.path_func = path_along_arc(self.path_arc) + self.path_func = path_along_arc( + self.path_arc, + self.path_arc_axis, + ) - def get_all_families_zipped(self): - return zip(*map( - Mobject.submobject_family, - [self.mobject, self.starting_mobject, self.target_mobject] - )) + def get_all_mobjects(self): + return self.mobject, self.starting_mobject, self.target_mobject def update_submobject(self, submob, start, end, alpha): submob.interpolate(start, end, alpha, self.path_func) @@ -169,6 +173,8 @@ class Rotate(ApplyMethod): def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs): if "path_arc" not in kwargs: kwargs["path_arc"] = angle + if "path_arc_axis" not in kwargs: + kwargs["path_arc_axis"] = axis digest_config(self, kwargs, locals()) target = mobject.copy() if self.in_place: diff --git a/camera.py b/camera.py index e645cdda..8d714af7 100644 --- a/camera.py +++ b/camera.py @@ -287,47 +287,6 @@ class MovingCamera(Camera): self.resize_space_shape( 0 if self.aligned_dimension == "height" else 1 ) - -class ShadingCamera(Camera): - CONFIG = { - # "sun_vect" : OUT+LEFT+UP, - "sun_vect" : UP+LEFT, - "shading_factor" : 0.5, - } - def __init__(self, *args, **kwargs): - Camera.__init__(self, *args, **kwargs) - self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) - - def get_stroke_color(self, vmobject): - return Color(rgb = self.get_shaded_rgb( - color_to_rgb(vmobject.get_stroke_color()), - normal_vect = self.get_unit_normal_vect(vmobject) - )) - - def get_fill_color(self, vmobject): - return Color(rgb = self.get_shaded_rgb( - color_to_rgb(vmobject.get_fill_color()), - normal_vect = self.get_unit_normal_vect(vmobject) - )) - - def get_shaded_rgb(self, rgb, normal_vect): - brightness = np.dot(normal_vect, self.unit_sun_vect) - if brightness > 0: - alpha = self.shading_factor*brightness - return interpolate(rgb, np.ones(3), alpha) - else: - alpha = -self.shading_factor*brightness - return interpolate(rgb, np.zeros(3), alpha) - - def get_unit_normal_vect(self, vmobject): - anchors = vmobject.get_anchors() - if len(anchors) < 3: - return OUT - normal = np.cross(anchors[1]-anchors[0], anchors[2]-anchors[1]) - length = np.linalg.norm(normal) - if length == 0: - return OUT - return normal/length diff --git a/eoc/chapter1.py b/eoc/chapter1.py index e4ee4a64..389657ac 100644 --- a/eoc/chapter1.py +++ b/eoc/chapter1.py @@ -2597,7 +2597,6 @@ class PatreonThanks(Scene): special_thanks.to_edge(UP) patreon_logo = PatreonLogo() - # patreon_logo.scale_to_fit_width(morty.get_width()) patreon_logo.next_to(morty, UP, buff = MED_LARGE_BUFF) left_patrons = VGroup(*map(TextMobject, diff --git a/helpers.py b/helpers.py index 229e68d0..3bb54223 100644 --- a/helpers.py +++ b/helpers.py @@ -318,7 +318,7 @@ def random_color(): def straight_path(start_points, end_points, alpha): return interpolate(start_points, end_points, alpha) -def path_along_arc(arc_angle): +def path_along_arc(arc_angle, axis = OUT): """ If vect is vector from start to end, [vect[:,1], -vect[:,0]] is perpendicualr to vect in the left direction. @@ -333,7 +333,7 @@ def path_along_arc(arc_angle): centers[:,i] += 0.5*b*vects[:,1-i]/np.tan(arc_angle/2) return centers + np.dot( start_points-centers, - np.transpose(rotation_about_z(alpha*arc_angle)) + np.transpose(rotation_matrix(alpha*arc_angle, axis)) ) return path diff --git a/borsuk.py b/old_projects/borsuk.py similarity index 99% rename from borsuk.py rename to old_projects/borsuk.py index e24d67b4..bf304f91 100644 --- a/borsuk.py +++ b/old_projects/borsuk.py @@ -19,7 +19,7 @@ from topics.numerals import * from topics.three_dimensions import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene -from camera import Camera, ShadingCamera +from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * @@ -148,9 +148,8 @@ class CheckOutMathologer(PiCreatureScene): logo.highlight(BLACK) return ApplyMethod(logo.restore) -class IntroduceStolenNecklaceProblem(Scene): +class IntroduceStolenNecklaceProblem(ThreeDScene): CONFIG = { - "camera_class" : ShadingCamera, "jewel_colors" : [BLUE, GREEN, WHITE, RED], "num_per_jewel" : [8, 10, 4, 6], "num_shuffles" : 1, @@ -994,10 +993,7 @@ class FormLoopTransverseToEquator(ExternallyAnimatedScene): class AntipodalWalkAroundTransverseLoop(ExternallyAnimatedScene): pass -class MentionGenerality(TeacherStudentsScene): - CONFIG = { - "camera_class" : ShadingCamera, - } +class MentionGenerality(TeacherStudentsScene, ThreeDScene): def construct(self): necklace = Necklace(width = SPACE_WIDTH) necklace.shift(2*UP) @@ -1274,7 +1270,6 @@ class MentionMakingNecklaceProblemContinuous(TeacherStudentsScene): class MakeTwoJewelCaseContinuous(IntroduceStolenNecklaceProblem): CONFIG = { - "camera_class" : ShadingCamera, "jewel_colors" : [BLUE, GREEN], "num_per_jewel" : [8, 10], "random_seed" : 2, @@ -2170,9 +2165,8 @@ class NecklaceDivisionSphereAssociation(ChoicesInNecklaceCutting): class SimpleRotatingSphereWithAntipodes(ExternallyAnimatedScene): pass -class TotalLengthOfEachJewelEquals(NecklaceDivisionSphereAssociation): +class TotalLengthOfEachJewelEquals(NecklaceDivisionSphereAssociation, ThreeDScene): CONFIG = { - "camera_class" : ShadingCamera, "random_seed" : 1, "thief_box_offset" : 1.2, } @@ -2310,7 +2304,7 @@ class ExclaimBorsukUlam(TeacherStudentsScene): class ShowFunctionDiagram(TotalLengthOfEachJewelEquals, ReconfigurableScene): CONFIG = { "necklace_center" : ORIGIN, - "camera_class" : ShadingCamera, + "camera_class" : ThreeDCamera, "thief_box_offset" : 0.3, "make_up_fair_division_indices" : False, } @@ -2407,7 +2401,7 @@ class ShowFunctionDiagram(TotalLengthOfEachJewelEquals, ReconfigurableScene): class JewelPairPlane(GraphScene): CONFIG = { - "camera_class" : ShadingCamera, + "camera_class" : ThreeDCamera, "x_labeled_nums" : [], "y_labeled_nums" : [], "thief_number" : 1, @@ -2639,7 +2633,7 @@ class MortyLookingAtRectangle(Scene): class RotatingThreeDSphereProjection(Scene): CONFIG = { - "camera_class" : ShadingCamera, + "camera_class" : ThreeDCamera, } def construct(self): sphere = VGroup(*[ @@ -2659,8 +2653,21 @@ class FourDSphereProjectTo4D(ExternallyAnimatedScene): pass - - +class Test(Scene): + CONFIG = { + "camera_class" : ThreeDCamera, + } + def construct(self): + randy = Randolph() + necklace = Necklace() + necklace.insert_n_anchor_points(20) + # necklace.apply_function( + # lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP + # ) + necklace.scale_to_fit_width(randy.get_width() + 1) + necklace.move_to(randy) + + self.add(randy, necklace) diff --git a/old_projects/three_dimensions.py b/old_projects/three_dimensions.py new file mode 100644 index 00000000..c41302f5 --- /dev/null +++ b/old_projects/three_dimensions.py @@ -0,0 +1,113 @@ +import numpy as np +import itertools as it + +from mobject import Mobject, Mobject1D, Mobject2D, Mobject +from geometry import Line +from helpers import * + +class Stars(Mobject1D): + CONFIG = { + "stroke_width" : 1, + "radius" : SPACE_WIDTH, + "num_points" : 1000, + } + def generate_points(self): + radii, phis, thetas = [ + scalar*np.random.random(self.num_points) + for scalar in [self.radius, np.pi, 2*np.pi] + ] + self.add_points([ + ( + r * np.sin(phi)*np.cos(theta), + r * np.sin(phi)*np.sin(theta), + r * np.cos(phi) + ) + for r, phi, theta in zip(radii, phis, thetas) + ]) + +class CubeWithFaces(Mobject2D): + def generate_points(self): + self.add_points([ + sgn * np.array(coords) + for x in np.arange(-1, 1, self.epsilon) + for y in np.arange(x, 1, self.epsilon) + for coords in it.permutations([x, y, 1]) + for sgn in [-1, 1] + ]) + self.pose_at_angle() + self.set_color(BLUE) + + def unit_normal(self, coords): + return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords)) + +class Cube(Mobject1D): + def generate_points(self): + self.add_points([ + ([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]]) + for p in [(0, 1, 2), (2, 0, 1), (1, 2, 0)] + for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon)) + ]) + self.pose_at_angle() + self.set_color(YELLOW) + +class Octohedron(Mobject1D): + def generate_points(self): + x = np.array([1, 0, 0]) + y = np.array([0, 1, 0]) + z = np.array([0, 0, 1]) + vertex_pairs = [(x+y, x-y), (x+y,-x+y), (-x-y,-x+y), (-x-y,x-y)] + vertex_pairs += [ + (b[0]*x+b[1]*y, b[2]*np.sqrt(2)*z) + for b in it.product(*[(-1, 1)]*3) + ] + for pair in vertex_pairs: + self.add_points( + Line(pair[0], pair[1], density = 1/self.epsilon).points + ) + self.pose_at_angle() + self.set_color(MAROON_D) + +class Dodecahedron(Mobject1D): + def generate_points(self): + phi = (1 + np.sqrt(5)) / 2 + x = np.array([1, 0, 0]) + y = np.array([0, 1, 0]) + z = np.array([0, 0, 1]) + v1, v2 = (phi, 1/phi, 0), (phi, -1/phi, 0) + vertex_pairs = [ + (v1, v2), + (x+y+z, v1), + (x+y-z, v1), + (x-y+z, v2), + (x-y-z, v2), + ] + five_lines_points = Mobject(*[ + Line(pair[0], pair[1], density = 1.0/self.epsilon) + for pair in vertex_pairs + ]).points + #Rotate those 5 edges into all 30. + for i in range(3): + perm = map(lambda j : j%3, range(i, i+3)) + for b in [-1, 1]: + matrix = b*np.array([x[perm], y[perm], z[perm]]) + self.add_points(np.dot(five_lines_points, matrix)) + self.pose_at_angle() + self.set_color(GREEN) + +class Sphere(Mobject2D): + def generate_points(self): + self.add_points([ + ( + np.sin(phi) * np.cos(theta), + np.sin(phi) * np.sin(theta), + np.cos(phi) + ) + for phi in np.arange(self.epsilon, np.pi, self.epsilon) + for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi)) + ]) + self.set_color(BLUE) + + def unit_normal(self, coords): + return np.array(coords) / np.linalg.norm(coords) + + \ No newline at end of file diff --git a/scene/scene.py b/scene/scene.py index 2acdcba7..eff7b9c7 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -424,8 +424,6 @@ class Scene(object): - - diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index e8891a18..94b186eb 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -1,114 +1,91 @@ -import numpy as np -import itertools as it -from mobject import Mobject, Mobject1D, Mobject2D, Mobject -from geometry import Line from helpers import * +from scene import Scene +from camera import Camera -class Stars(Mobject1D): +class ThreeDCamera(Camera): CONFIG = { - "stroke_width" : 1, - "radius" : SPACE_WIDTH, - "num_points" : 1000, + "sun_vect" : UP+LEFT, + "shading_factor" : 0.5, } - def generate_points(self): - radii, phis, thetas = [ - scalar*np.random.random(self.num_points) - for scalar in [self.radius, np.pi, 2*np.pi] - ] - self.add_points([ - ( - r * np.sin(phi)*np.cos(theta), - r * np.sin(phi)*np.sin(theta), - r * np.cos(phi) - ) - for r, phi, theta in zip(radii, phis, thetas) - ]) + def __init__(self, *args, **kwargs): + Camera.__init__(self, *args, **kwargs) + self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) + + def display_multiple_vectorized_mobjects(self, vmobjects): + def cmp_vmobs(vm1, vm2): + return cmp(vm1.get_center()[2], vm2.get_center()[2]) + Camera.display_multiple_vectorized_mobjects( + self, + sorted(vmobjects, cmp = cmp_vmobs) + ) + + def get_stroke_color(self, vmobject): + return Color(rgb = self.get_shaded_rgb( + color_to_rgb(vmobject.get_stroke_color()), + normal_vect = self.get_unit_normal_vect(vmobject) + )) + + def get_fill_color(self, vmobject): + return Color(rgb = self.get_shaded_rgb( + color_to_rgb(vmobject.get_fill_color()), + normal_vect = self.get_unit_normal_vect(vmobject) + )) + + def get_shaded_rgb(self, rgb, normal_vect): + brightness = np.dot(normal_vect, self.unit_sun_vect) + if brightness > 0: + alpha = self.shading_factor*brightness + return interpolate(rgb, np.ones(3), alpha) + else: + alpha = -self.shading_factor*brightness + return interpolate(rgb, np.zeros(3), alpha) + + def get_unit_normal_vect(self, vmobject): + anchors = vmobject.get_anchors() + if len(anchors) < 3: + return OUT + normal = np.cross(anchors[1]-anchors[0], anchors[2]-anchors[1]) + if normal[2] < 0: + normal = -normal + length = np.linalg.norm(normal) + if length == 0: + return OUT + return normal/length + + +class ThreeDScene(Scene): + CONFIG = { + "camera_class" : ThreeDCamera, + } + + + + + + + + + + + + + + + + + + + + + + -class CubeWithFaces(Mobject2D): - def generate_points(self): - self.add_points([ - sgn * np.array(coords) - for x in np.arange(-1, 1, self.epsilon) - for y in np.arange(x, 1, self.epsilon) - for coords in it.permutations([x, y, 1]) - for sgn in [-1, 1] - ]) - self.pose_at_angle() - self.set_color(BLUE) - def unit_normal(self, coords): - return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords)) -class Cube(Mobject1D): - def generate_points(self): - self.add_points([ - ([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]]) - for p in [(0, 1, 2), (2, 0, 1), (1, 2, 0)] - for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon)) - ]) - self.pose_at_angle() - self.set_color(YELLOW) -class Octohedron(Mobject1D): - def generate_points(self): - x = np.array([1, 0, 0]) - y = np.array([0, 1, 0]) - z = np.array([0, 0, 1]) - vertex_pairs = [(x+y, x-y), (x+y,-x+y), (-x-y,-x+y), (-x-y,x-y)] - vertex_pairs += [ - (b[0]*x+b[1]*y, b[2]*np.sqrt(2)*z) - for b in it.product(*[(-1, 1)]*3) - ] - for pair in vertex_pairs: - self.add_points( - Line(pair[0], pair[1], density = 1/self.epsilon).points - ) - self.pose_at_angle() - self.set_color(MAROON_D) -class Dodecahedron(Mobject1D): - def generate_points(self): - phi = (1 + np.sqrt(5)) / 2 - x = np.array([1, 0, 0]) - y = np.array([0, 1, 0]) - z = np.array([0, 0, 1]) - v1, v2 = (phi, 1/phi, 0), (phi, -1/phi, 0) - vertex_pairs = [ - (v1, v2), - (x+y+z, v1), - (x+y-z, v1), - (x-y+z, v2), - (x-y-z, v2), - ] - five_lines_points = Mobject(*[ - Line(pair[0], pair[1], density = 1.0/self.epsilon) - for pair in vertex_pairs - ]).points - #Rotate those 5 edges into all 30. - for i in range(3): - perm = map(lambda j : j%3, range(i, i+3)) - for b in [-1, 1]: - matrix = b*np.array([x[perm], y[perm], z[perm]]) - self.add_points(np.dot(five_lines_points, matrix)) - self.pose_at_angle() - self.set_color(GREEN) -class Sphere(Mobject2D): - def generate_points(self): - self.add_points([ - ( - np.sin(phi) * np.cos(theta), - np.sin(phi) * np.sin(theta), - np.cos(phi) - ) - for phi in np.arange(self.epsilon, np.pi, self.epsilon) - for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi)) - ]) - self.set_color(BLUE) - def unit_normal(self, coords): - return np.array(coords) / np.linalg.norm(coords) - \ No newline at end of file