diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index b3967ae4..5fc3fe68 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -1,4 +1,6 @@ + from helpers import * + from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint from topics.geometry import Square, Line from scene import Scene @@ -6,32 +8,38 @@ from camera import Camera from animation.continual_animation import AmbientMovement from animation.transform import ApplyMethod import numpy as np - class CameraWithPerspective(Camera): - CONFIG = {'camera_distance': 20} - + CONFIG = { + "camera_distance" : 20, + } def points_to_pixel_coords(self, points): - distance_ratios = np.divide(self.camera_distance, self.camera_distance - points[:, 2]) + distance_ratios = np.divide( + self.camera_distance, + self.camera_distance - points[:,2] + ) scale_factors = interpolate(0, 1, distance_ratios) adjusted_points = np.array(points) - for i in (0, 1): - adjusted_points[(:, i)] *= scale_factors + for i in 0, 1: + adjusted_points[:,i] *= scale_factors return Camera.points_to_pixel_coords(self, adjusted_points) - class ThreeDCamera(CameraWithPerspective): - CONFIG = {'sun_vect': 5 * UP + LEFT, - 'shading_factor': 0.2, - 'distance': 5.0, - 'default_distance': 5.0, - 'phi': 0, - 'theta': -TAU / 4} - + CONFIG = { + "sun_vect" : 5*UP+LEFT, + "shading_factor" : 0.2, + "distance" : 5., + "default_distance" : 5., + "phi" : 0, #Angle off z axis + "theta" : -TAU/4, #Rotation about z axis + } def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) - self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) + self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) + ## 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) @@ -48,56 +56,64 @@ class ThreeDCamera(CameraWithPerspective): return self.modified_rgb(vmobject, vmobject.get_fill_rgb()) def get_shaded_rgb(self, rgb, normal_vect): - brightness = np.dot(normal_vect, self.unit_sun_vect) ** 2 + brightness = np.dot(normal_vect, self.unit_sun_vect)**2 if brightness > 0: - alpha = self.shading_factor * brightness + alpha = self.shading_factor*brightness return interpolate(rgb, np.ones(3), alpha) else: - alpha = -self.shading_factor * brightness + 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]) + 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 + return normal/length def display_multiple_vectorized_mobjects(self, vmobjects): - camera_point = self.spherical_coords_to_point(*self.get_spherical_coords()) - + camera_point = self.spherical_coords_to_point( + *self.get_spherical_coords() + ) def z_cmp(*vmobs): + # Compare to three dimensional mobjects based on + # how close they are to the camera + # return cmp(*[ + # -np.linalg.norm(vm.get_center()-camera_point) + # for vm in vmobs + # ]) three_d_status = map(should_shade_in_3d, vmobs) - has_points = [ vm.get_num_points() > 0 for vm in vmobs ] + has_points = [vm.get_num_points() > 0 for vm in vmobs] if all(three_d_status) and all(has_points): cmp_vect = self.get_unit_normal_vect(vmobs[1]) - return cmp(*[ np.dot(vm.get_center(), cmp_vect) for vm in vmobs ]) + return cmp(*[ + np.dot(vm.get_center(), cmp_vect) + for vm in vmobs + ]) else: return 0 - - Camera.display_multiple_vectorized_mobjects(self, sorted(vmobjects, cmp=z_cmp)) + Camera.display_multiple_vectorized_mobjects( + self, sorted(vmobjects, cmp = z_cmp) + ) def get_spherical_coords(self, phi = None, theta = None, distance = None): curr_phi, curr_theta, curr_d = self.rotation_mobject.points[0] - if phi is None: - phi = curr_phi - if theta is None: - theta = curr_theta - if distance is None: - distance = curr_d + if phi is None: phi = curr_phi + if theta is None: theta = curr_theta + if distance is None: distance = curr_d return np.array([phi, theta, distance]) def get_cartesian_coords(self, phi = None, theta = None, distance = None): - spherical_coords_array = self.get_spherical_coords(phi, theta, distance) + spherical_coords_array = self.get_spherical_coords(phi,theta,distance) phi2 = spherical_coords_array[0] theta2 = spherical_coords_array[1] d2 = spherical_coords_array[2] - return self.spherical_coords_to_point(phi2, theta2, d2) + return self.spherical_coords_to_point(phi2,theta2,d2) def get_phi(self): return self.get_spherical_coords()[0] @@ -109,7 +125,11 @@ class ThreeDCamera(CameraWithPerspective): return self.get_spherical_coords()[2] def spherical_coords_to_point(self, phi, theta, distance): - return distance * np.array([np.sin(phi) * np.cos(theta), np.sin(phi) * np.sin(theta), np.cos(phi)]) + return distance*np.array([ + np.sin(phi)*np.cos(theta), + np.sin(phi)*np.sin(theta), + 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] @@ -121,7 +141,8 @@ class ThreeDCamera(CameraWithPerspective): z = curr_z return np.array([x, y, z]) - def set_position(self, phi = None, theta = None, distance = None, center_x = None, center_y = None, center_z = 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 @@ -130,24 +151,34 @@ class ThreeDCamera(CameraWithPerspective): self.space_center = self.moving_center.points[0] def get_view_transformation_matrix(self): - return self.default_distance / self.get_distance() * np.dot(rotation_matrix(self.get_phi(), LEFT), rotation_about_z(-self.get_theta() - np.pi / 2)) + return (self.default_distance / self.get_distance()) * np.dot( + rotation_matrix(self.get_phi(), LEFT), + rotation_about_z(-self.get_theta() - np.pi/2), + ) 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): - CONFIG = {'camera_class': ThreeDCamera, - 'ambient_camera_rotation': None} + CONFIG = { + "camera_class" : ThreeDCamera, + "ambient_camera_rotation" : None, + } - def set_camera_position(self, phi = None, theta = None, distance = None, center_x = None, center_y = None, center_z = None): + 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(self.camera.rotation_mobject, direction=UP, rate=rate) + self.ambient_camera_rotation = AmbientMovement( + self.camera.rotation_mobject, + direction = UP, + rate = rate + ) self.add(self.ambient_camera_rotation) def stop_ambient_camera_rotation(self): @@ -155,11 +186,25 @@ class ThreeDScene(Scene): self.remove(self.ambient_camera_rotation) self.ambient_camera_rotation = None - def move_camera(self, phi = None, theta = None, distance = None, center_x = None, center_y = None, center_z = None, added_anims = [], **kwargs): + def move_camera( + self, + phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None, + added_anims = [], + **kwargs + ): target_point = self.camera.get_spherical_coords(phi, theta, distance) - movement = ApplyMethod(self.camera.rotation_mobject.move_to, target_point, **kwargs) + movement = ApplyMethod( + self.camera.rotation_mobject.move_to, + 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) + 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) @@ -174,34 +219,33 @@ class ThreeDScene(Scene): return list_update(self.mobjects, moving_mobjects) return moving_mobjects +############## def should_shade_in_3d(mobject): - return hasattr(mobject, 'shade_in_3d') and mobject.shade_in_3d - + return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d def shade_in_3d(mobject): for submob in mobject.submobject_family(): submob.shade_in_3d = True - def turn_off_3d_shading(mobject): for submob in mobject.submobject_family(): submob.shade_in_3d = False - class ThreeDMobject(VMobject): - def __init__(self, *args, **kwargs): VMobject.__init__(self, *args, **kwargs) shade_in_3d(self) class Cube(ThreeDMobject): - CONFIG = {'fill_opacity': 0.75, - 'fill_color': BLUE, - 'stroke_width': 0, - 'propagate_style_to_family': True, - 'side_length': 2} + CONFIG = { + 'fill_opacity': 0.75, + 'fill_color': BLUE, + 'stroke_width': 0, + 'propagate_style_to_family': True, + 'side_length': 2 + } def generate_points(self): for vect in (IN, @@ -216,34 +260,82 @@ class Cube(ThreeDMobject): self.add(face) -class SphereThreeD(ThreeDMobject): - - def __init__(self, r, eps): +class Sphere(ThreeDMobject): + CONFIG = { + 'fill_opacity': .75, + 'fill_color': BLUE, + 'stroke_width': 0, + 'propagate_style_to_family': True, + 'side_length': 2 + } + def __init__(self, r, eps, opacity = .75): self.r = r self.eps = eps + self.CONFIG['fill_opacity'] = opacity ThreeDMobject.__init__(self) - CONFIG = {'fill_opacity': 0.75, - 'fill_color': BLUE, - 'stroke_width': 0, - 'propagate_style_to_family': True, - 'side_length': 2} - def generate_points(self): - points = [ (self.r * (np.sin(phi) * np.cos(theta)), self.r * (np.sin(phi) * np.sin(theta)), self.r * np.cos(phi)) for phi in np.arange(0, 2 * np.pi, self.eps) for theta in np.arange(0, 2 * np.pi, self.eps) ] + points = [ + ( + self.r * (np.sin(phi) * np.cos(theta)), + self.r * (np.sin(phi) * np.sin(theta)), + self.r * np.cos(phi) + ) + for phi in np.arange(0, 2 * np.pi, self.eps) + for theta in np.arange(0, 2 * np.pi, self.eps) + ] for vect in points: face = Square(side_length=self.eps) + scalefactor = np.linalg.norm(vect) + face.shift(scalefactor * OUT / 2.0) face.apply_function(lambda p: np.dot(p, z_to_vector(vect).T)) self.add(face) + shade_in_3d(self) + +class Torus(ThreeDMobject): + CONFIG = { + 'fill_opacity': .75, + 'fill_color': BLUE, + 'stroke_width': 0, + 'propagate_style_to_family': True, + 'side_length': 2 + } + def __init__(self, r1, r2, eps, opacity=.75): + self.r1 = r1 + self.r2 = r2 + self.eps = eps + self.CONFIG['fill_opacity'] = opacity + ThreeDMobject.__init__(self) + + + def generate_points(self): + points = [ + ( + (self.r1 + self.r2 * np.cos(theta)) * np.cos(phi), + (self.r1 + self.r2 * np.cos(theta)) * np.sin(phi), + self.r2 * np.sin(theta) + ) + for phi in np.arange(0, 2 * np.pi, self.eps) + for theta in np.arange(0, 2 * np.pi, self.eps) + ] + for vect in points: + face = Square(side_length=self.eps) + scalefactor = np.linalg.norm(vect) + face.shift(scalefactor * OUT / 2.0) + face.apply_function(lambda p: np.dot(p, z_to_vector(vect).T)) + self.add(face) + shade_in_3d(self) class Parametric3D(ThreeDMobject): - CONFIG = {'fill_opacity': 0.75, - 'fill_color': BLUE, - 'stroke_width': 0, - 'propagate_style_to_family': True} + CONFIG = { + 'fill_opacity': 0.75, + 'fill_color': BLUE, + 'stroke_width': 0, + 'propagate_style_to_family': True + } - def __init__(self, f, g, h, phi_min, phi_max, theta_min, theta_max, eps): + def __init__(self, f, g, h, phi_min, phi_max, theta_min, theta_max, eps, opacity = .75): self.f = f self.g = g self.h = h @@ -252,16 +344,26 @@ class Parametric3D(ThreeDMobject): self.theta_min = theta_min self.theta_max = theta_max self.eps = eps + self.CONFIG['fill_opacity'] = opacity ThreeDMobject.__init__(self) def generate_points(self): - points = [ (self.f(phi, theta), self.g(phi, theta), self.h(phi, theta)) for phi in np.arange(self.phi_min, self.phi_max, self.eps) for theta in np.arange(self.theta_min, self.theta_max, self.eps) ] + points = [ + ( + self.f(phi, theta), + self.g(phi, theta), + self.h(phi, theta) + ) + for phi in np.arange(self.phi_min, self.phi_max, self.eps) + for theta in np.arange(self.theta_min, self.theta_max, self.eps) + ] for vect in points: face = Square(side_length=self.eps) scalefactor = np.linalg.norm(vect) face.shift(scalefactor * OUT / 2.0) face.apply_function(lambda p: np.dot(p, z_to_vector(vect).T)) self.add(face) + shade_in_3d(self) class Prism(Cube):