diff --git a/camera/camera.py b/camera/camera.py index 03e2b658..4256e60a 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -264,7 +264,7 @@ class Camera(object): (VMobject, self.display_multiple_vectorized_mobjects), (PMobject, self.display_multiple_point_cloud_mobjects), (AbstractImageMobject, self.display_multiple_image_mobjects), - (Mobject, lambda batch: batch), # Do nothing + (Mobject, lambda batch, pa: batch), # Do nothing ] def get_mobject_type(mobject): @@ -370,10 +370,11 @@ class Camera(object): ) else: points = vmobject.get_gradient_start_and_end_points() + points = self.transform_points_pre_display(points) pat = cairo.LinearGradient(*it.chain(*[ point[:2] for point in points ])) - offsets = np.linspace(1, 0, len(rgbas)) + offsets = np.linspace(0, 1, len(rgbas)) for rgba, offset in zip(rgbas, offsets): pat.add_color_stop_rgba( offset, *rgba[2::-1], rgba[3] diff --git a/camera/three_d_camera.py b/camera/three_d_camera.py index c899c6cc..a081f0dd 100644 --- a/camera/three_d_camera.py +++ b/camera/three_d_camera.py @@ -4,61 +4,74 @@ import numpy as np from constants import * -from camera.moving_camera import MovingCamera -from mobject.types.vectorized_mobject import VectorizedPoint -from mobject.three_dimensions import should_shade_in_3d +from camera.camera import Camera +from mobject.types.point_cloud_mobject import Point +from mobject.three_dimensions import ThreeDVMobject +from mobject.value_tracker import ValueTracker -from utils.bezier import interpolate +from utils.color import get_shaded_rgb from utils.space_ops import rotation_about_z from utils.space_ops import rotation_matrix -# TODO: Make sure this plays well with latest camera updates - -# class CameraWithPerspective(Camera): -# CONFIG = { -# "camera_distance": 20, -# } - -# def points_to_pixel_coords(self, points): -# 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 - -# return Camera.points_to_pixel_coords(self, adjusted_points) - - -class ThreeDCamera(MovingCamera): +class ThreeDCamera(Camera): CONFIG = { "sun_vect": 5 * UP + LEFT, "shading_factor": 0.2, - "distance": 5.0, + "distance": 20.0, "default_distance": 5.0, "phi": 0, # Angle off z axis - "theta": -TAU / 4, # Rotation about z axis + "theta": -90 * DEGREES, # Rotation about z axis + "gamma": 0, # Rotation about normal vector to camera + "light_source_start_point": 10 * DOWN + 7 * LEFT + 5 * OUT, + "frame_center": ORIGIN, } def __init__(self, *args, **kwargs): - MovingCamera.__init__(self, *args, **kwargs) - self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) - # rotation_mobject lives in the phi-theta-distance space - # TODO, use ValueTracker for this instead - self.rotation_mobject = VectorizedPoint() - # Moving_center lives in the x-y-z space - # It representes the center of rotation - self.moving_center = VectorizedPoint(self.frame_center) - self.set_position(self.phi, self.theta, self.distance) + Camera.__init__(self, *args, **kwargs) + self.phi_tracker = ValueTracker(self.phi) + self.theta_tracker = ValueTracker(self.theta) + self.distance_tracker = ValueTracker(self.distance) + self.gamma_tracker = ValueTracker(self.gamma) + self.light_source = Point(self.light_source_start_point) + self.frame_center = Point(self.frame_center) + self.reset_rotation_matrix() + + def capture_mobjects(self, mobjects, **kwargs): + self.reset_rotation_matrix() + Camera.capture_mobjects(self, mobjects, **kwargs) + + def get_value_trackers(self): + return [ + self.phi_tracker, + self.theta_tracker, + self.distance_tracker, + self.gamma_tracker, + ] def modified_rgbas(self, vmobject, rgbas): - if should_shade_in_3d(vmobject): - return self.get_shaded_rgbas(rgbas, self.get_unit_normal_vect(vmobject)) - else: - return rgbas + is_3d = isinstance(vmobject, ThreeDVMobject) + has_points = (vmobject.get_num_points() > 0) + if is_3d and has_points: + light_source_point = self.light_source.points[0] + if len(rgbas) < 2: + shaded_rgbas = rgbas.repeat(2, axis=0) + else: + shaded_rgbas = np.array(rgbas[:2]) + shaded_rgbas[0, :3] = get_shaded_rgb( + shaded_rgbas[0, :3], + vmobject.get_start_corner(), + vmobject.get_start_corner_unit_normal(), + light_source_point, + ) + shaded_rgbas[1, :3] = get_shaded_rgb( + shaded_rgbas[1, :3], + vmobject.get_end_corner(), + vmobject.get_end_corner_unit_normal(), + light_source_point, + ) + return shaded_rgbas + return rgbas def get_stroke_rgbas(self, vmobject, background=False): return self.modified_rgbas( @@ -70,111 +83,81 @@ class ThreeDCamera(MovingCamera): vmobject, vmobject.get_fill_rgbas() ) - def get_shaded_rgbas(self, rgbas, normal_vect): - brightness = np.dot(normal_vect, self.unit_sun_vect)**2 - target = np.ones(rgbas.shape) - target[:, 3] = rgbas[:, 3] - if brightness > 0: - alpha = self.shading_factor * brightness - return interpolate(rgbas, target, alpha) - else: - target[:, :3] = 0 - alpha = -self.shading_factor * brightness - return interpolate(rgbas, target, 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 - def display_multiple_vectorized_mobjects(self, vmobjects, pixel_array): - # camera_point = self.spherical_coords_to_point( - # *self.get_spherical_coords() - # ) + rot_matrix = self.get_rotation_matrix() def z_key(vmob): # Assign a number to a three dimensional mobjects # based on how close it is to the camera - three_d_status = should_shade_in_3d(vmob) - has_points = vmob.get_num_points() > 0 - if three_d_status and has_points: - return vmob.get_center()[2] + if isinstance(vmob, ThreeDVMobject): + return np.dot( + vmob.get_center(), + rot_matrix.T + )[2] else: - return 0 - MovingCamera.display_multiple_vectorized_mobjects( + return np.inf + Camera.display_multiple_vectorized_mobjects( self, sorted(vmobjects, key=z_key), pixel_array ) - 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 - 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) - phi2 = spherical_coords_array[0] - theta2 = spherical_coords_array[1] - d2 = spherical_coords_array[2] - return self.spherical_coords_to_point(phi2, theta2, d2) - def get_phi(self): - return self.get_spherical_coords()[0] + return self.phi_tracker.get_value() def get_theta(self): - return self.get_spherical_coords()[1] + return self.theta_tracker.get_value() def get_distance(self): - return self.get_spherical_coords()[2] + return self.distance_tracker.get_value() - 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) - ]) - - 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, - 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.frame_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), - ) - - def transform_points_pre_display(self, points): - matrix = self.get_view_transformation_matrix() - return np.dot(points, matrix.T) + def get_gamma(self): + return self.gamma_tracker.get_value() def get_frame_center(self): - return self.moving_center.points[0] \ No newline at end of file + return self.frame_center.points[0] + + def set_phi(self, value): + self.phi_tracker.set_value(value) + + def set_theta(self, value): + self.theta_tracker.set_value(value) + + def set_distance(self, value): + self.distance_tracker.set_value(value) + + def set_gamma(self, value): + self.gamma_tracker.set_value(value) + + def set_frame_center(self, point): + self.frame_center.move_to(point) + + def reset_rotation_matrix(self): + self.rotation_matrix = self.generate_rotation_matrix() + + def get_rotation_matrix(self): + return self.rotation_matrix + + def generate_rotation_matrix(self): + phi = self.get_phi() + theta = self.get_theta() + gamma = self.get_gamma() + matrices = [ + rotation_about_z(-theta - 90 * DEGREES), + rotation_matrix(-phi, RIGHT), + rotation_about_z(gamma), + ] + result = np.identity(3) + for matrix in matrices: + result = np.dot(matrix, result) + return result + + def transform_points_pre_display(self, points): + fc = self.get_frame_center() + distance = self.get_distance() + points -= fc + rot_matrix = self.get_rotation_matrix() + points = np.dot(points, rot_matrix.T) + zs = points[:, 2] + points[:, 0] *= (distance + zs) / distance + points[:, 1] *= (distance + zs) / distance + points += fc + return points diff --git a/mobject/mobject.py b/mobject/mobject.py index 75232f96..c9541efc 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -681,12 +681,13 @@ class Mobject(Container): ] def get_merged_array(self, array_attr): - result = None - for mob in self.family_members_with_points(): - if result is None: - result = getattr(mob, array_attr) - else: - result = np.append(result, getattr(mob, array_attr), 0) + result = getattr(self, array_attr) + for submob in self.submobjects: + result = np.append( + result, submob.get_merged_array(array_attr), + axis=0 + ) + submob.get_merged_array(array_attr) return result def get_all_points(self): @@ -816,11 +817,11 @@ class Mobject(Container): if n_rows is not None: v1 = RIGHT v2 = DOWN - n = len(submobs) / n_rows + n = len(submobs) // n_rows elif n_cols is not None: v1 = DOWN v2 = RIGHT - n = len(submobs) / n_cols + n = len(submobs) // n_cols Group(*[ Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs) for i in range(0, len(submobs), n) @@ -829,7 +830,7 @@ class Mobject(Container): def sort_submobjects(self, point_to_num_func=lambda p: p[0]): self.submobjects.sort( - key=lambda m: point_to_num_func(mob.get_center()) + key=lambda m: point_to_num_func(m.get_center()) ) return self diff --git a/mobject/three_dimensions.py b/mobject/three_dimensions.py index a7a9bf4f..4b67a299 100644 --- a/mobject/three_dimensions.py +++ b/mobject/three_dimensions.py @@ -3,34 +3,165 @@ from constants import * from mobject.types.vectorized_mobject import VMobject +from mobject.types.vectorized_mobject import VGroup from mobject.geometry import Square +from utils.config_ops import digest_config from utils.space_ops import z_to_vector +from utils.space_ops import get_unit_normal ############## -def should_shade_in_3d(mobject): - return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d +class ThreeDVMobject(VMobject): + CONFIG = {} + + def __init__(self, vmobject=None, **kwargs): + VMobject.__init__(self, **kwargs) + if vmobject is not None: + self.points = np.array(vmobject.points) + self.match_style(vmobject) + self.submobjects = map( + ThreeDVMobject, vmobject.submobjects + ) + + def get_gradient_start_and_end_points(self): + return self.get_start_corner(), self.get_end_corner() + + def get_start_corner_index(self): + return 0 + + def get_end_corner_index(self): + return ((len(self.points) - 1) // 6) * 3 + # return ((len(self.points) - 1) // 12) * 3 + + def get_start_corner(self): + if self.get_num_points() == 0: + return np.array(ORIGIN) + return self.points[self.get_start_corner_index()] + + def get_end_corner(self): + if self.get_num_points() == 0: + return np.array(ORIGIN) + return self.points[self.get_end_corner_index()] + + def get_unit_normal(self, point_index): + n_points = self.get_num_points() + if self.get_num_points() == 0: + return np.array(ORIGIN) + i = point_index + im1 = i - 1 if i > 0 else (n_points - 2) + ip1 = i + 1 if i < (n_points - 1) else 1 + return get_unit_normal( + self.points[ip1] - self.points[i], + self.points[im1] - self.points[i], + ) + + def get_start_corner_unit_normal(self): + return self.get_unit_normal( + self.get_start_corner_index() + ) + + def get_end_corner_unit_normal(self): + return self.get_unit_normal( + self.get_end_corner_index() + ) -def shade_in_3d(mobject): - for submob in mobject.submobject_family(): - submob.shade_in_3d = True +class ParametricSurface(VGroup): + CONFIG = { + "u_min": 0, + "u_max": 1, + "v_min": 0, + "v_max": 1, + "resolution": 10, + "u_resolution": None, + "v_resolution": None, + "surface_piece_config": {}, + "fill_color": BLUE_D, + "fill_opacity": 1.0, + "stroke_color": LIGHT_GREY, + "stroke_width": 0.5, + "should_make_jagged": False, + } + + def __init__(self, func, **kwargs): + VGroup.__init__(self, **kwargs) + self.setup_in_uv_space() + self.apply_function(lambda p: func(p[0], p[1])) + if self.should_make_jagged: + self.make_jagged() + + def setup_in_uv_space(self): + u_min = self.u_min + u_max = self.u_max + u_res = self.u_resolution or self.resolution + v_min = self.v_min + v_max = self.v_max + v_res = self.v_resolution or self.resolution + + u_values = np.linspace(u_min, u_max, u_res + 1) + v_values = np.linspace(v_min, v_max, v_res + 1) + faces = VGroup() + for u1, u2 in zip(u_values[:-1], u_values[1:]): + for v1, v2 in zip(v_values[:-1], v_values[1:]): + piece = ThreeDVMobject() + piece.set_points_as_corners([ + [u1, v1, 0], + [u2, v1, 0], + [u2, v2, 0], + [u1, v2, 0], + [u1, v1, 0], + ]) + faces.add(piece) + faces.set_stroke(width=0) + faces.set_fill( + color=self.fill_color, + opacity=self.fill_opacity + ) + + # TODO + mesh = VGroup() + + mesh.set_stroke( + color=self.stroke_color, + width=self.stroke_width, + opacity=self.stroke_opacity, + ) + + self.faces = faces + self.mesh = mesh + self.add(self.faces, self.mesh) -def turn_off_3d_shading(mobject): - for submob in mobject.submobject_family(): - submob.shade_in_3d = False +# Specific shapes -class ThreeDMobject(VMobject): - def __init__(self, *args, **kwargs): - VMobject.__init__(self, *args, **kwargs) - shade_in_3d(self) +class Sphere(ParametricSurface): + CONFIG = { + "resolution": 15, + "radius": 3, + "u_min": 0.001, + } + + def __init__(self, **kwargs): + digest_config(self, kwargs) + kwargs["u_resolution"] = self.u_resolution or self.resolution + kwargs["v_resolution"] = self.u_resolution or 2 * self.resolution + ParametricSurface.__init__( + self, self.func, **kwargs + ) + self.scale(self.radius) + + def func(self, u, v): + return np.array([ + np.cos(TAU * v) * np.sin(PI * u), + np.sin(TAU * v) * np.sin(PI * u), + np.cos(PI * u) + ]) -class Cube(ThreeDMobject): +class Cube(VGroup): CONFIG = { "fill_opacity": 0.75, "fill_color": BLUE, @@ -41,9 +172,13 @@ class Cube(ThreeDMobject): def generate_points(self): for vect in IN, OUT, LEFT, RIGHT, UP, DOWN: - face = Square(side_length=self.side_length) + face = ThreeDVMobject( + Square(side_length=self.side_length) + ) + face.make_jagged() + face.flip() face.shift(self.side_length * OUT / 2.0) - face.apply_function(lambda p: np.dot(p, z_to_vector(vect).T)) + face.apply_matrix(z_to_vector(vect)) self.add(face) diff --git a/mobject/types/vectorized_mobject.py b/mobject/types/vectorized_mobject.py index 79363051..0a277713 100644 --- a/mobject/types/vectorized_mobject.py +++ b/mobject/types/vectorized_mobject.py @@ -74,7 +74,7 @@ class VMobject(Mobject): ) return self - def get_rgbas_array(self, color, opacity): + def generate_rgbas_array(self, color, opacity): """ First arg can be either a color, or a tuple/list of colors. Likewise, opacity can either be a float, or a tuple of floats. @@ -100,7 +100,7 @@ class VMobject(Mobject): def update_rgbas_array(self, array_name, color=None, opacity=None): passed_color = color or BLACK passed_opacity = opacity or 0 - rgbas = self.get_rgbas_array(passed_color, passed_opacity) + rgbas = self.generate_rgbas_array(passed_color, passed_opacity) if not hasattr(self, array_name): setattr(self, array_name, rgbas) return self @@ -113,7 +113,7 @@ class VMobject(Mobject): ) setattr(self, array_name, curr_rgbas) elif len(rgbas) < len(curr_rgbas): - rgbas = stretch_array_to_length(len(curr_rgbas)) + rgbas = stretch_array_to_length(rgbas, len(curr_rgbas)) # Only update rgb if color was not None, and only # update alpha channel if opacity was passed in if color is not None: @@ -266,8 +266,9 @@ class VMobject(Mobject): # already be handled above self.set_sheen_direction(direction, family=False) # Reset color to put sheen into effect - self.set_stroke(self.get_stroke_color(), family=family) - self.set_fill(self.get_fill_color(), family=family) + if factor != 0: + self.set_stroke(self.get_stroke_color(), family=family) + self.set_fill(self.get_fill_color(), family=family) return self def get_sheen_direction(self): @@ -284,7 +285,7 @@ class VMobject(Mobject): for vect in [RIGHT, UP, OUT] ]).transpose() offset = np.dot(bases, direction) - return (c + offset, c - offset) + return (c - offset, c + offset) def color_using_background_image(self, background_image_file): self.background_image_file = background_image_file @@ -366,7 +367,7 @@ class VMobject(Mobject): def change_anchor_mode(self, mode): for submob in self.family_members_with_points(): - anchors, h1, h2 = submob.get_anchors_and_handles() + anchors = submob.get_anchors() submob.set_anchor_points(anchors, mode=mode) return self @@ -426,15 +427,14 @@ class VMobject(Mobject): handles closer to their anchors, apply the function then push them out again. """ - if self.get_num_points() == 0: - return - anchors, handles1, handles2 = self.get_anchors_and_handles() - # print len(anchors), len(handles1), len(handles2) - a_to_h1 = handles1 - anchors[:-1] - a_to_h2 = handles2 - anchors[1:] - handles1 = anchors[:-1] + factor * a_to_h1 - handles2 = anchors[1:] + factor * a_to_h2 - self.set_anchors_and_handles(anchors, handles1, handles2) + for submob in self.family_members_with_points(): + anchors, handles1, handles2 = submob.get_anchors_and_handles() + # print len(anchors), len(handles1), len(handles2) + a_to_h1 = handles1 - anchors[:-1] + a_to_h2 = handles2 - anchors[1:] + handles1 = anchors[:-1] + factor * a_to_h1 + handles2 = anchors[1:] + factor * a_to_h2 + submob.set_anchors_and_handles(anchors, handles1, handles2) # Information about line diff --git a/mobject/value_tracker.py b/mobject/value_tracker.py index 7c8ee584..18450868 100644 --- a/mobject/value_tracker.py +++ b/mobject/value_tracker.py @@ -1,15 +1,11 @@ - import numpy as np -from constants import * - -from mobject.types.vectorized_mobject import VectorizedPoint - -# TODO: Rather than using VectorizedPoint, there should be some UndisplayedPointSet type +from mobject.mobject import Mobject +from utils.bezier import interpolate -class ValueTracker(VectorizedPoint): +class ValueTracker(Mobject): """ Note meant to be displayed. Instead the position encodes some number, often one which another animation or continual_animation @@ -18,14 +14,15 @@ class ValueTracker(VectorizedPoint): """ def __init__(self, value=0, **kwargs): - VectorizedPoint.__init__(self, **kwargs) + Mobject.__init__(self, **kwargs) + self.points = np.zeros((1, 3)) self.set_value(value) def get_value(self): - return self.get_center()[0] + return self.points[0, 0] def set_value(self, value): - self.move_to(value * RIGHT) + self.points[0, 0] = value return self def increment_value(self, d_value): @@ -40,8 +37,7 @@ class ExponentialValueTracker(ValueTracker): """ def get_value(self): - return np.exp(self.get_center()[0]) + return np.exp(ValueTracker.get_value(self)) def set_value(self, value): - self.move_to(np.log(value) * RIGHT) - return self + return ValueTracker.set_value(self, np.log(value)) diff --git a/scene/three_d_scene.py b/scene/three_d_scene.py index 58f79051..20de182c 100644 --- a/scene/three_d_scene.py +++ b/scene/three_d_scene.py @@ -1,14 +1,10 @@ - - from constants import * -from continual_animation.continual_animation import ContinualMovement +from continual_animation.update import ContinualGrowValue from animation.transform import ApplyMethod from camera.three_d_camera import ThreeDCamera from scene.scene import Scene -from utils.iterables import list_update - class ThreeDScene(Scene): CONFIG = { @@ -16,17 +12,19 @@ class ThreeDScene(Scene): "ambient_camera_rotation": 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 set_camera_orientation(self, phi=None, theta=None, distance=None, gamma=None): + if phi is not None: + self.camera.set_phi(phi) + if theta is not None: + self.camera.set_theta(theta) + if distance is not None: + self.camera.set_distance(distance) + if gamma is not None: + self.camera.set_gamma(gamma) - def begin_ambient_camera_rotation(self, rate=0.01): - self.ambient_camera_rotation = ContinualMovement( - self.camera.rotation_mobject, - direction=UP, + def begin_ambient_camera_rotation(self, rate=0.1): + self.ambient_camera_rotation = ContinualGrowValue( + self.camera.theta_tracker, rate=rate ) self.add(self.ambient_camera_rotation) @@ -36,36 +34,33 @@ 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 - ): - target_point = self.camera.get_spherical_coords(phi, theta, distance) - 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 - ) + def move_camera(self, + phi=None, theta=None, + distance=None, gamma=None, + added_anims=[], + **kwargs): + anims = [] + value_tracker_pairs = [ + (phi, self.camera.phi_tracker), + (theta, self.camera.theta_tracker), + (distance, self.camera.distance_tracker), + (gamma, self.camera.gamma_tracker), + ] + for value, tracker in value_tracker_pairs: + if value is not None: + anims.append( + ApplyMethod(tracker.set_value, value, **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, movement_center, *added_anims) - target_point = self.camera.get_spherical_coords(phi, theta, distance) + self.play(*anims + added_anims) if is_camera_rotating: self.add(self.ambient_camera_rotation) def get_moving_mobjects(self, *animations): moving_mobjects = Scene.get_moving_mobjects(self, *animations) - if self.camera.rotation_mobject in moving_mobjects: - return list_update(self.mobjects, moving_mobjects) + camera_mobjects = self.camera.get_value_trackers() + if any([cm in moving_mobjects for cm in camera_mobjects]): + return self.mobjects return moving_mobjects diff --git a/utils/color.py b/utils/color.py index b5f308ba..2533c720 100644 --- a/utils/color.py +++ b/utils/color.py @@ -6,6 +6,7 @@ from constants import WHITE from constants import PALETTE from utils.bezier import interpolate +from utils.space_ops import get_norm def color_to_rgb(color): @@ -82,3 +83,12 @@ def random_bright_color(): def random_color(): return random.choice(PALETTE) + + +def get_shaded_rgb(rgb, point, unit_normal_vect, light_source): + to_sun = light_source - point + to_sun /= get_norm(to_sun) + factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3 + if factor < 0: + factor *= 0.5 + return np.clip(rgb + factor, 0, 1) diff --git a/utils/space_ops.py b/utils/space_ops.py index 46573c39..e1f7ac42 100644 --- a/utils/space_ops.py +++ b/utils/space_ops.py @@ -92,6 +92,31 @@ def project_along_vector(point, vector): matrix = np.identity(3) - np.outer(vector, vector) return np.dot(point, matrix.T) + +def get_norm(vect): + return sum([x**2 for x in vect])**0.5 + + +def normalize(vect): + norm = get_norm(vect) + if norm > 0: + return vect / norm + else: + return np.zeros(len(vect)) + + +def cross(v1, v2): + return np.array([ + v1[1] * v2[2] - v1[2] * v2[1], + v1[2] * v2[0] - v1[0] * v2[2], + v1[0] * v2[1] - v1[1] * v2[0] + ]) + + +def get_unit_normal(v1, v2): + return normalize(cross(v1, v2)) + + ###