Performance improvements to 3d constructs

This commit is contained in:
Grant Sanderson
2018-08-16 22:25:51 -07:00
parent daffec0034
commit 6f57a8bc5e
6 changed files with 50 additions and 33 deletions

View File

@ -25,6 +25,7 @@ class ThreeDCamera(Camera):
"gamma": 0, # Rotation about normal vector to camera "gamma": 0, # Rotation about normal vector to camera
"light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT, "light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
"frame_center": ORIGIN, "frame_center": ORIGIN,
"should_apply_shading": True,
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -50,6 +51,8 @@ class ThreeDCamera(Camera):
] ]
def modified_rgbas(self, vmobject, rgbas): def modified_rgbas(self, vmobject, rgbas):
if not self.should_apply_shading:
return rgbas
is_3d = isinstance(vmobject, ThreeDVMobject) is_3d = isinstance(vmobject, ThreeDVMobject)
has_points = (vmobject.get_num_points() > 0) has_points = (vmobject.get_num_points() > 0)
if is_3d and has_points: if is_3d and has_points:
@ -156,7 +159,8 @@ class ThreeDCamera(Camera):
points -= fc points -= fc
rot_matrix = self.get_rotation_matrix() rot_matrix = self.get_rotation_matrix()
points = np.dot(points, rot_matrix.T) points = np.dot(points, rot_matrix.T)
zs = np.clip(points[:, 2], -np.inf, distance - 0.001) zs = points[:, 2]
zs[zs >= distance] = distance - 0.001
for i in 0, 1: for i in 0, 1:
points[:, i] *= distance / (distance - zs) points[:, i] *= distance / (distance - zs)
points += fc points += fc

View File

@ -707,9 +707,9 @@ class Mobject(Container):
all_points = self.get_all_points() all_points = self.get_all_points()
for dim in range(self.dim): for dim in range(self.dim):
if direction[dim] <= 0: if direction[dim] <= 0:
min_val = np.min(all_points[:, dim]) min_val = min(all_points[:, dim])
if direction[dim] >= 0: if direction[dim] >= 0:
max_val = np.max(all_points[:, dim]) max_val = max(all_points[:, dim])
if direction[dim] == 0: if direction[dim] == 0:
result[dim] = (max_val + min_val) / 2 result[dim] = (max_val + min_val) / 2

View File

@ -7,6 +7,7 @@ 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.config_ops import digest_config
from utils.iterables import tuplify
from utils.space_ops import z_to_vector from utils.space_ops import z_to_vector
from utils.space_ops import get_unit_normal from utils.space_ops import get_unit_normal
@ -75,8 +76,6 @@ class ParametricSurface(VGroup):
"v_min": 0, "v_min": 0,
"v_max": 1, "v_max": 1,
"resolution": 32, "resolution": 32,
"u_resolution": None,
"v_resolution": None,
"surface_piece_config": {}, "surface_piece_config": {},
"fill_color": BLUE_D, "fill_color": BLUE_D,
"fill_opacity": 1.0, "fill_opacity": 1.0,
@ -94,27 +93,34 @@ class ParametricSurface(VGroup):
self.make_jagged() self.make_jagged()
def setup_in_uv_space(self): def setup_in_uv_space(self):
res = tuplify(self.resolution)
if len(res) == 1:
u_res = v_res = res
else:
u_res, v_res = res
u_min = self.u_min u_min = self.u_min
u_max = self.u_max u_max = self.u_max
u_res = self.u_resolution or self.resolution
v_min = self.v_min v_min = self.v_min
v_max = self.v_max v_max = self.v_max
v_res = self.v_resolution or self.resolution
u_values = np.linspace(u_min, u_max, u_res + 1) u_values = np.linspace(u_min, u_max, u_res + 1)
v_values = np.linspace(v_min, v_max, v_res + 1) v_values = np.linspace(v_min, v_max, v_res + 1)
faces = VGroup() faces = VGroup()
for u1, u2 in zip(u_values[:-1], u_values[1:]): for i in range(u_res):
for v1, v2 in zip(v_values[:-1], v_values[1:]): for j in range(v_res):
piece = ThreeDVMobject() u1, u2 = u_values[i:i + 2]
piece.set_points_as_corners([ v1, v2 = v_values[j:j + 2]
face = ThreeDVMobject()
face.set_points_as_corners([
[u1, v1, 0], [u1, v1, 0],
[u2, v1, 0], [u2, v1, 0],
[u2, v2, 0], [u2, v2, 0],
[u1, v2, 0], [u1, v2, 0],
[u1, v1, 0], [u1, v1, 0],
]) ])
faces.add(piece) faces.add(face)
face.u_index = i
face.v_index = j
faces.set_fill( faces.set_fill(
color=self.fill_color, color=self.fill_color,
opacity=self.fill_opacity opacity=self.fill_opacity
@ -128,16 +134,11 @@ class ParametricSurface(VGroup):
if self.checkerboard_colors: if self.checkerboard_colors:
self.set_fill_by_checkerboard(*self.checkerboard_colors) self.set_fill_by_checkerboard(*self.checkerboard_colors)
def set_fill_by_checkerboard(self, color1, color2): def set_fill_by_checkerboard(self, *colors, opacity=None):
u_res = self.u_resolution or self.resolution n_colors = len(colors)
v_res = self.v_resolution or self.resolution for face in self:
for i in range(u_res): c_index = (face.u_index + face.v_index) % n_colors
for j in range(v_res): face.set_fill(colors[c_index], opacity=opacity)
face = self[i * v_res + j]
if (i + j) % 2 == 0:
face.set_fill(color1)
else:
face.set_fill(color2)
# Specific shapes # Specific shapes
@ -145,15 +146,15 @@ class ParametricSurface(VGroup):
class Sphere(ParametricSurface): class Sphere(ParametricSurface):
CONFIG = { CONFIG = {
"resolution": 12, "resolution": (12, 24),
"radius": 3, "radius": 3,
"u_min": 0.001, "u_min": 0.001,
"u_max": PI - 0.001,
"v_min": 0,
"v_max": TAU,
} }
def __init__(self, **kwargs): 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__( ParametricSurface.__init__(
self, self.func, **kwargs self, self.func, **kwargs
) )
@ -161,9 +162,9 @@ class Sphere(ParametricSurface):
def func(self, u, v): def func(self, u, v):
return np.array([ return np.array([
np.cos(TAU * v) * np.sin(PI * u), np.cos(v) * np.sin(u),
np.sin(TAU * v) * np.sin(PI * u), np.sin(v) * np.sin(u),
np.cos(PI * u) np.cos(u)
]) ])

View File

@ -13,6 +13,7 @@ from utils.color import color_to_rgba
from utils.iterables import make_even from utils.iterables import make_even
from utils.iterables import tuplify from utils.iterables import tuplify
from utils.iterables import stretch_array_to_length from utils.iterables import stretch_array_to_length
from utils.simple_functions import clip_in_place
class VMobject(Mobject): class VMobject(Mobject):
@ -93,7 +94,7 @@ class VMobject(Mobject):
if sheen != 0 and len(rgbas) == 1: if sheen != 0 and len(rgbas) == 1:
light_rgbas = np.array(rgbas) light_rgbas = np.array(rgbas)
light_rgbas[:, :3] += sheen light_rgbas[:, :3] += sheen
light_rgbas = np.clip(light_rgbas, 0, 1) clip_in_place(light_rgbas, 0, 1)
rgbas = np.append(rgbas, light_rgbas, axis=0) rgbas = np.append(rgbas, light_rgbas, axis=0)
return rgbas return rgbas
@ -183,7 +184,7 @@ class VMobject(Mobject):
def get_fill_rgbas(self): def get_fill_rgbas(self):
try: try:
return np.clip(self.fill_rgbas, 0, 1) return self.fill_rgbas
except AttributeError: except AttributeError:
return np.zeros((1, 4)) return np.zeros((1, 4))
@ -216,7 +217,7 @@ class VMobject(Mobject):
rgbas = self.background_stroke_rgbas rgbas = self.background_stroke_rgbas
else: else:
rgbas = self.stroke_rgbas rgbas = self.stroke_rgbas
return np.clip(rgbas, 0, 1) return rgbas
except AttributeError: except AttributeError:
return np.zeros((1, 4)) return np.zeros((1, 4))

View File

@ -7,6 +7,7 @@ from constants import PALETTE
from utils.bezier import interpolate from utils.bezier import interpolate
from utils.space_ops import normalize from utils.space_ops import normalize
from utils.simple_functions import clip_in_place
def color_to_rgb(color): def color_to_rgb(color):
@ -92,4 +93,6 @@ def get_shaded_rgb(rgb, point, unit_normal_vect, light_source):
factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3 factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3
if factor < 0: if factor < 0:
factor *= 0.5 factor *= 0.5
return np.clip(rgb + factor, 0, 1) result = rgb + factor
clip_in_place(rgb + factor, 0, 1)
return result

View File

@ -28,6 +28,14 @@ def get_num_args(function):
# but for now, we just allow the option to handle indeterminate 0/0. # but for now, we just allow the option to handle indeterminate 0/0.
def clip_in_place(array, min_val=None, max_val=None):
if max_val is not None:
array[array > max_val] = max_val
if min_val is not None:
array[array < min_val] = min_val
return array
def fdiv(a, b, zero_over_zero_value=None): def fdiv(a, b, zero_over_zero_value=None):
if zero_over_zero_value is not None: if zero_over_zero_value is not None:
out = np.full_like(a, zero_over_zero_value) out = np.full_like(a, zero_over_zero_value)