mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 13:34:19 +08:00
Added preliminary 3d animation abilities with (as of now poorly shaded) parametric curves
This commit is contained in:
@ -264,7 +264,7 @@ class Camera(object):
|
|||||||
(VMobject, self.display_multiple_vectorized_mobjects),
|
(VMobject, self.display_multiple_vectorized_mobjects),
|
||||||
(PMobject, self.display_multiple_point_cloud_mobjects),
|
(PMobject, self.display_multiple_point_cloud_mobjects),
|
||||||
(AbstractImageMobject, self.display_multiple_image_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):
|
def get_mobject_type(mobject):
|
||||||
@ -370,10 +370,11 @@ class Camera(object):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
points = vmobject.get_gradient_start_and_end_points()
|
points = vmobject.get_gradient_start_and_end_points()
|
||||||
|
points = self.transform_points_pre_display(points)
|
||||||
pat = cairo.LinearGradient(*it.chain(*[
|
pat = cairo.LinearGradient(*it.chain(*[
|
||||||
point[:2] for point in points
|
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):
|
for rgba, offset in zip(rgbas, offsets):
|
||||||
pat.add_color_stop_rgba(
|
pat.add_color_stop_rgba(
|
||||||
offset, *rgba[2::-1], rgba[3]
|
offset, *rgba[2::-1], rgba[3]
|
||||||
|
@ -4,61 +4,74 @@ import numpy as np
|
|||||||
|
|
||||||
from constants import *
|
from constants import *
|
||||||
|
|
||||||
from camera.moving_camera import MovingCamera
|
from camera.camera import Camera
|
||||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
from mobject.types.point_cloud_mobject import Point
|
||||||
from mobject.three_dimensions import should_shade_in_3d
|
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_about_z
|
||||||
from utils.space_ops import rotation_matrix
|
from utils.space_ops import rotation_matrix
|
||||||
|
|
||||||
# TODO: Make sure this plays well with latest camera updates
|
|
||||||
|
|
||||||
|
class ThreeDCamera(Camera):
|
||||||
# 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):
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"sun_vect": 5 * UP + LEFT,
|
"sun_vect": 5 * UP + LEFT,
|
||||||
"shading_factor": 0.2,
|
"shading_factor": 0.2,
|
||||||
"distance": 5.0,
|
"distance": 20.0,
|
||||||
"default_distance": 5.0,
|
"default_distance": 5.0,
|
||||||
"phi": 0, # Angle off z axis
|
"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):
|
def __init__(self, *args, **kwargs):
|
||||||
MovingCamera.__init__(self, *args, **kwargs)
|
Camera.__init__(self, *args, **kwargs)
|
||||||
self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect)
|
self.phi_tracker = ValueTracker(self.phi)
|
||||||
# rotation_mobject lives in the phi-theta-distance space
|
self.theta_tracker = ValueTracker(self.theta)
|
||||||
# TODO, use ValueTracker for this instead
|
self.distance_tracker = ValueTracker(self.distance)
|
||||||
self.rotation_mobject = VectorizedPoint()
|
self.gamma_tracker = ValueTracker(self.gamma)
|
||||||
# Moving_center lives in the x-y-z space
|
self.light_source = Point(self.light_source_start_point)
|
||||||
# It representes the center of rotation
|
self.frame_center = Point(self.frame_center)
|
||||||
self.moving_center = VectorizedPoint(self.frame_center)
|
self.reset_rotation_matrix()
|
||||||
self.set_position(self.phi, self.theta, self.distance)
|
|
||||||
|
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):
|
def modified_rgbas(self, vmobject, rgbas):
|
||||||
if should_shade_in_3d(vmobject):
|
is_3d = isinstance(vmobject, ThreeDVMobject)
|
||||||
return self.get_shaded_rgbas(rgbas, self.get_unit_normal_vect(vmobject))
|
has_points = (vmobject.get_num_points() > 0)
|
||||||
else:
|
if is_3d and has_points:
|
||||||
return rgbas
|
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):
|
def get_stroke_rgbas(self, vmobject, background=False):
|
||||||
return self.modified_rgbas(
|
return self.modified_rgbas(
|
||||||
@ -70,111 +83,81 @@ class ThreeDCamera(MovingCamera):
|
|||||||
vmobject, vmobject.get_fill_rgbas()
|
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):
|
def display_multiple_vectorized_mobjects(self, vmobjects, pixel_array):
|
||||||
# camera_point = self.spherical_coords_to_point(
|
rot_matrix = self.get_rotation_matrix()
|
||||||
# *self.get_spherical_coords()
|
|
||||||
# )
|
|
||||||
|
|
||||||
def z_key(vmob):
|
def z_key(vmob):
|
||||||
# Assign a number to a three dimensional mobjects
|
# Assign a number to a three dimensional mobjects
|
||||||
# based on how close it is to the camera
|
# based on how close it is to the camera
|
||||||
three_d_status = should_shade_in_3d(vmob)
|
if isinstance(vmob, ThreeDVMobject):
|
||||||
has_points = vmob.get_num_points() > 0
|
return np.dot(
|
||||||
if three_d_status and has_points:
|
vmob.get_center(),
|
||||||
return vmob.get_center()[2]
|
rot_matrix.T
|
||||||
|
)[2]
|
||||||
else:
|
else:
|
||||||
return 0
|
return np.inf
|
||||||
MovingCamera.display_multiple_vectorized_mobjects(
|
Camera.display_multiple_vectorized_mobjects(
|
||||||
self, sorted(vmobjects, key=z_key), pixel_array
|
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):
|
def get_phi(self):
|
||||||
return self.get_spherical_coords()[0]
|
return self.phi_tracker.get_value()
|
||||||
|
|
||||||
def get_theta(self):
|
def get_theta(self):
|
||||||
return self.get_spherical_coords()[1]
|
return self.theta_tracker.get_value()
|
||||||
|
|
||||||
def get_distance(self):
|
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):
|
def get_gamma(self):
|
||||||
return distance * np.array([
|
return self.gamma_tracker.get_value()
|
||||||
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_frame_center(self):
|
def get_frame_center(self):
|
||||||
return self.moving_center.points[0]
|
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
|
||||||
|
@ -681,12 +681,13 @@ class Mobject(Container):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get_merged_array(self, array_attr):
|
def get_merged_array(self, array_attr):
|
||||||
result = None
|
result = getattr(self, array_attr)
|
||||||
for mob in self.family_members_with_points():
|
for submob in self.submobjects:
|
||||||
if result is None:
|
result = np.append(
|
||||||
result = getattr(mob, array_attr)
|
result, submob.get_merged_array(array_attr),
|
||||||
else:
|
axis=0
|
||||||
result = np.append(result, getattr(mob, array_attr), 0)
|
)
|
||||||
|
submob.get_merged_array(array_attr)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_all_points(self):
|
def get_all_points(self):
|
||||||
@ -816,11 +817,11 @@ class Mobject(Container):
|
|||||||
if n_rows is not None:
|
if n_rows is not None:
|
||||||
v1 = RIGHT
|
v1 = RIGHT
|
||||||
v2 = DOWN
|
v2 = DOWN
|
||||||
n = len(submobs) / n_rows
|
n = len(submobs) // n_rows
|
||||||
elif n_cols is not None:
|
elif n_cols is not None:
|
||||||
v1 = DOWN
|
v1 = DOWN
|
||||||
v2 = RIGHT
|
v2 = RIGHT
|
||||||
n = len(submobs) / n_cols
|
n = len(submobs) // n_cols
|
||||||
Group(*[
|
Group(*[
|
||||||
Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs)
|
Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs)
|
||||||
for i in range(0, len(submobs), n)
|
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]):
|
def sort_submobjects(self, point_to_num_func=lambda p: p[0]):
|
||||||
self.submobjects.sort(
|
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
|
return self
|
||||||
|
|
||||||
|
@ -3,34 +3,165 @@
|
|||||||
from constants import *
|
from constants import *
|
||||||
|
|
||||||
from mobject.types.vectorized_mobject import VMobject
|
from mobject.types.vectorized_mobject import VMobject
|
||||||
|
from mobject.types.vectorized_mobject import VGroup
|
||||||
from mobject.geometry import Square
|
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 z_to_vector
|
||||||
|
from utils.space_ops import get_unit_normal
|
||||||
|
|
||||||
##############
|
##############
|
||||||
|
|
||||||
|
|
||||||
def should_shade_in_3d(mobject):
|
class ThreeDVMobject(VMobject):
|
||||||
return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d
|
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):
|
class ParametricSurface(VGroup):
|
||||||
for submob in mobject.submobject_family():
|
CONFIG = {
|
||||||
submob.shade_in_3d = True
|
"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):
|
# Specific shapes
|
||||||
for submob in mobject.submobject_family():
|
|
||||||
submob.shade_in_3d = False
|
|
||||||
|
|
||||||
|
|
||||||
class ThreeDMobject(VMobject):
|
class Sphere(ParametricSurface):
|
||||||
def __init__(self, *args, **kwargs):
|
CONFIG = {
|
||||||
VMobject.__init__(self, *args, **kwargs)
|
"resolution": 15,
|
||||||
shade_in_3d(self)
|
"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 = {
|
CONFIG = {
|
||||||
"fill_opacity": 0.75,
|
"fill_opacity": 0.75,
|
||||||
"fill_color": BLUE,
|
"fill_color": BLUE,
|
||||||
@ -41,9 +172,13 @@ class Cube(ThreeDMobject):
|
|||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN:
|
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.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)
|
self.add(face)
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class VMobject(Mobject):
|
|||||||
)
|
)
|
||||||
return self
|
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.
|
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.
|
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):
|
def update_rgbas_array(self, array_name, color=None, opacity=None):
|
||||||
passed_color = color or BLACK
|
passed_color = color or BLACK
|
||||||
passed_opacity = opacity or 0
|
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):
|
if not hasattr(self, array_name):
|
||||||
setattr(self, array_name, rgbas)
|
setattr(self, array_name, rgbas)
|
||||||
return self
|
return self
|
||||||
@ -113,7 +113,7 @@ class VMobject(Mobject):
|
|||||||
)
|
)
|
||||||
setattr(self, array_name, curr_rgbas)
|
setattr(self, array_name, curr_rgbas)
|
||||||
elif len(rgbas) < len(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
|
# Only update rgb if color was not None, and only
|
||||||
# update alpha channel if opacity was passed in
|
# update alpha channel if opacity was passed in
|
||||||
if color is not None:
|
if color is not None:
|
||||||
@ -266,8 +266,9 @@ class VMobject(Mobject):
|
|||||||
# already be handled above
|
# already be handled above
|
||||||
self.set_sheen_direction(direction, family=False)
|
self.set_sheen_direction(direction, family=False)
|
||||||
# Reset color to put sheen into effect
|
# Reset color to put sheen into effect
|
||||||
self.set_stroke(self.get_stroke_color(), family=family)
|
if factor != 0:
|
||||||
self.set_fill(self.get_fill_color(), family=family)
|
self.set_stroke(self.get_stroke_color(), family=family)
|
||||||
|
self.set_fill(self.get_fill_color(), family=family)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_sheen_direction(self):
|
def get_sheen_direction(self):
|
||||||
@ -284,7 +285,7 @@ class VMobject(Mobject):
|
|||||||
for vect in [RIGHT, UP, OUT]
|
for vect in [RIGHT, UP, OUT]
|
||||||
]).transpose()
|
]).transpose()
|
||||||
offset = np.dot(bases, direction)
|
offset = np.dot(bases, direction)
|
||||||
return (c + offset, c - offset)
|
return (c - offset, c + offset)
|
||||||
|
|
||||||
def color_using_background_image(self, background_image_file):
|
def color_using_background_image(self, background_image_file):
|
||||||
self.background_image_file = background_image_file
|
self.background_image_file = background_image_file
|
||||||
@ -366,7 +367,7 @@ class VMobject(Mobject):
|
|||||||
|
|
||||||
def change_anchor_mode(self, mode):
|
def change_anchor_mode(self, mode):
|
||||||
for submob in self.family_members_with_points():
|
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)
|
submob.set_anchor_points(anchors, mode=mode)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -426,15 +427,14 @@ class VMobject(Mobject):
|
|||||||
handles closer to their anchors, apply the function then push them out
|
handles closer to their anchors, apply the function then push them out
|
||||||
again.
|
again.
|
||||||
"""
|
"""
|
||||||
if self.get_num_points() == 0:
|
for submob in self.family_members_with_points():
|
||||||
return
|
anchors, handles1, handles2 = submob.get_anchors_and_handles()
|
||||||
anchors, handles1, handles2 = self.get_anchors_and_handles()
|
# print len(anchors), len(handles1), len(handles2)
|
||||||
# print len(anchors), len(handles1), len(handles2)
|
a_to_h1 = handles1 - anchors[:-1]
|
||||||
a_to_h1 = handles1 - anchors[:-1]
|
a_to_h2 = handles2 - anchors[1:]
|
||||||
a_to_h2 = handles2 - anchors[1:]
|
handles1 = anchors[:-1] + factor * a_to_h1
|
||||||
handles1 = anchors[:-1] + factor * a_to_h1
|
handles2 = anchors[1:] + factor * a_to_h2
|
||||||
handles2 = anchors[1:] + factor * a_to_h2
|
submob.set_anchors_and_handles(anchors, handles1, handles2)
|
||||||
self.set_anchors_and_handles(anchors, handles1, handles2)
|
|
||||||
|
|
||||||
# Information about line
|
# Information about line
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from constants import *
|
from mobject.mobject import Mobject
|
||||||
|
from utils.bezier import interpolate
|
||||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
|
||||||
|
|
||||||
# TODO: Rather than using VectorizedPoint, there should be some UndisplayedPointSet type
|
|
||||||
|
|
||||||
|
|
||||||
class ValueTracker(VectorizedPoint):
|
class ValueTracker(Mobject):
|
||||||
"""
|
"""
|
||||||
Note meant to be displayed. Instead the position encodes some
|
Note meant to be displayed. Instead the position encodes some
|
||||||
number, often one which another animation or continual_animation
|
number, often one which another animation or continual_animation
|
||||||
@ -18,14 +14,15 @@ class ValueTracker(VectorizedPoint):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, value=0, **kwargs):
|
def __init__(self, value=0, **kwargs):
|
||||||
VectorizedPoint.__init__(self, **kwargs)
|
Mobject.__init__(self, **kwargs)
|
||||||
|
self.points = np.zeros((1, 3))
|
||||||
self.set_value(value)
|
self.set_value(value)
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return self.get_center()[0]
|
return self.points[0, 0]
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.move_to(value * RIGHT)
|
self.points[0, 0] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def increment_value(self, d_value):
|
def increment_value(self, d_value):
|
||||||
@ -40,8 +37,7 @@ class ExponentialValueTracker(ValueTracker):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return np.exp(self.get_center()[0])
|
return np.exp(ValueTracker.get_value(self))
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.move_to(np.log(value) * RIGHT)
|
return ValueTracker.set_value(self, np.log(value))
|
||||||
return self
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
from constants import *
|
from constants import *
|
||||||
|
|
||||||
from continual_animation.continual_animation import ContinualMovement
|
from continual_animation.update import ContinualGrowValue
|
||||||
from animation.transform import ApplyMethod
|
from animation.transform import ApplyMethod
|
||||||
from camera.three_d_camera import ThreeDCamera
|
from camera.three_d_camera import ThreeDCamera
|
||||||
from scene.scene import Scene
|
from scene.scene import Scene
|
||||||
|
|
||||||
from utils.iterables import list_update
|
|
||||||
|
|
||||||
|
|
||||||
class ThreeDScene(Scene):
|
class ThreeDScene(Scene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
@ -16,17 +12,19 @@ class ThreeDScene(Scene):
|
|||||||
"ambient_camera_rotation": None,
|
"ambient_camera_rotation": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_camera_position(self, phi=None, theta=None, distance=None,
|
def set_camera_orientation(self, phi=None, theta=None, distance=None, gamma=None):
|
||||||
center_x=None, center_y=None, center_z=None):
|
if phi is not None:
|
||||||
self.camera.set_position(
|
self.camera.set_phi(phi)
|
||||||
phi, theta, distance,
|
if theta is not None:
|
||||||
center_x, center_y, center_z
|
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):
|
def begin_ambient_camera_rotation(self, rate=0.1):
|
||||||
self.ambient_camera_rotation = ContinualMovement(
|
self.ambient_camera_rotation = ContinualGrowValue(
|
||||||
self.camera.rotation_mobject,
|
self.camera.theta_tracker,
|
||||||
direction=UP,
|
|
||||||
rate=rate
|
rate=rate
|
||||||
)
|
)
|
||||||
self.add(self.ambient_camera_rotation)
|
self.add(self.ambient_camera_rotation)
|
||||||
@ -36,36 +34,33 @@ class ThreeDScene(Scene):
|
|||||||
self.remove(self.ambient_camera_rotation)
|
self.remove(self.ambient_camera_rotation)
|
||||||
self.ambient_camera_rotation = None
|
self.ambient_camera_rotation = None
|
||||||
|
|
||||||
def move_camera(
|
def move_camera(self,
|
||||||
self,
|
phi=None, theta=None,
|
||||||
phi=None, theta=None, distance=None,
|
distance=None, gamma=None,
|
||||||
center_x=None, center_y=None, center_z=None,
|
added_anims=[],
|
||||||
added_anims=[],
|
**kwargs):
|
||||||
**kwargs
|
anims = []
|
||||||
):
|
value_tracker_pairs = [
|
||||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
(phi, self.camera.phi_tracker),
|
||||||
movement = ApplyMethod(
|
(theta, self.camera.theta_tracker),
|
||||||
self.camera.rotation_mobject.move_to,
|
(distance, self.camera.distance_tracker),
|
||||||
target_point,
|
(gamma, self.camera.gamma_tracker),
|
||||||
**kwargs
|
]
|
||||||
)
|
for value, tracker in value_tracker_pairs:
|
||||||
target_center = self.camera.get_center_of_rotation(
|
if value is not None:
|
||||||
center_x, center_y, center_z)
|
anims.append(
|
||||||
movement_center = ApplyMethod(
|
ApplyMethod(tracker.set_value, value, **kwargs)
|
||||||
self.camera.moving_center.move_to,
|
)
|
||||||
target_center,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
||||||
if is_camera_rotating:
|
if is_camera_rotating:
|
||||||
self.remove(self.ambient_camera_rotation)
|
self.remove(self.ambient_camera_rotation)
|
||||||
self.play(movement, movement_center, *added_anims)
|
self.play(*anims + added_anims)
|
||||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
|
||||||
if is_camera_rotating:
|
if is_camera_rotating:
|
||||||
self.add(self.ambient_camera_rotation)
|
self.add(self.ambient_camera_rotation)
|
||||||
|
|
||||||
def get_moving_mobjects(self, *animations):
|
def get_moving_mobjects(self, *animations):
|
||||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||||
if self.camera.rotation_mobject in moving_mobjects:
|
camera_mobjects = self.camera.get_value_trackers()
|
||||||
return list_update(self.mobjects, moving_mobjects)
|
if any([cm in moving_mobjects for cm in camera_mobjects]):
|
||||||
|
return self.mobjects
|
||||||
return moving_mobjects
|
return moving_mobjects
|
||||||
|
@ -6,6 +6,7 @@ from constants import WHITE
|
|||||||
from constants import PALETTE
|
from constants import PALETTE
|
||||||
|
|
||||||
from utils.bezier import interpolate
|
from utils.bezier import interpolate
|
||||||
|
from utils.space_ops import get_norm
|
||||||
|
|
||||||
|
|
||||||
def color_to_rgb(color):
|
def color_to_rgb(color):
|
||||||
@ -82,3 +83,12 @@ def random_bright_color():
|
|||||||
|
|
||||||
def random_color():
|
def random_color():
|
||||||
return random.choice(PALETTE)
|
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)
|
||||||
|
@ -92,6 +92,31 @@ def project_along_vector(point, vector):
|
|||||||
matrix = np.identity(3) - np.outer(vector, vector)
|
matrix = np.identity(3) - np.outer(vector, vector)
|
||||||
return np.dot(point, matrix.T)
|
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))
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user