diff --git a/camera/three_d_camera.py b/camera/three_d_camera.py index 2e0fb91e..f2a8af03 100644 --- a/camera/three_d_camera.py +++ b/camera/three_d_camera.py @@ -25,6 +25,7 @@ class ThreeDCamera(Camera): "gamma": 0, # Rotation about normal vector to camera "light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT, "frame_center": ORIGIN, + "should_apply_shading": True, } def __init__(self, *args, **kwargs): @@ -50,6 +51,8 @@ class ThreeDCamera(Camera): ] def modified_rgbas(self, vmobject, rgbas): + if not self.should_apply_shading: + return rgbas is_3d = isinstance(vmobject, ThreeDVMobject) has_points = (vmobject.get_num_points() > 0) if is_3d and has_points: @@ -156,7 +159,8 @@ class ThreeDCamera(Camera): points -= fc rot_matrix = self.get_rotation_matrix() 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: points[:, i] *= distance / (distance - zs) points += fc diff --git a/mobject/mobject.py b/mobject/mobject.py index 2bb4327c..1bba3f61 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -707,9 +707,9 @@ class Mobject(Container): all_points = self.get_all_points() for dim in range(self.dim): if direction[dim] <= 0: - min_val = np.min(all_points[:, dim]) + min_val = min(all_points[:, dim]) if direction[dim] >= 0: - max_val = np.max(all_points[:, dim]) + max_val = max(all_points[:, dim]) if direction[dim] == 0: result[dim] = (max_val + min_val) / 2 diff --git a/mobject/three_dimensions.py b/mobject/three_dimensions.py index bdfd6e2a..61335c29 100644 --- a/mobject/three_dimensions.py +++ b/mobject/three_dimensions.py @@ -7,6 +7,7 @@ from mobject.types.vectorized_mobject import VGroup from mobject.geometry import Square 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 get_unit_normal @@ -75,8 +76,6 @@ class ParametricSurface(VGroup): "v_min": 0, "v_max": 1, "resolution": 32, - "u_resolution": None, - "v_resolution": None, "surface_piece_config": {}, "fill_color": BLUE_D, "fill_opacity": 1.0, @@ -94,27 +93,34 @@ class ParametricSurface(VGroup): self.make_jagged() 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_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([ + for i in range(u_res): + for j in range(v_res): + u1, u2 = u_values[i:i + 2] + v1, v2 = v_values[j:j + 2] + face = ThreeDVMobject() + face.set_points_as_corners([ [u1, v1, 0], [u2, v1, 0], [u2, v2, 0], [u1, v2, 0], [u1, v1, 0], ]) - faces.add(piece) + faces.add(face) + face.u_index = i + face.v_index = j faces.set_fill( color=self.fill_color, opacity=self.fill_opacity @@ -128,16 +134,11 @@ class ParametricSurface(VGroup): if self.checkerboard_colors: self.set_fill_by_checkerboard(*self.checkerboard_colors) - def set_fill_by_checkerboard(self, color1, color2): - u_res = self.u_resolution or self.resolution - v_res = self.v_resolution or self.resolution - for i in range(u_res): - for j in range(v_res): - face = self[i * v_res + j] - if (i + j) % 2 == 0: - face.set_fill(color1) - else: - face.set_fill(color2) + def set_fill_by_checkerboard(self, *colors, opacity=None): + n_colors = len(colors) + for face in self: + c_index = (face.u_index + face.v_index) % n_colors + face.set_fill(colors[c_index], opacity=opacity) # Specific shapes @@ -145,15 +146,15 @@ class ParametricSurface(VGroup): class Sphere(ParametricSurface): CONFIG = { - "resolution": 12, + "resolution": (12, 24), "radius": 3, "u_min": 0.001, + "u_max": PI - 0.001, + "v_min": 0, + "v_max": TAU, } 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 ) @@ -161,9 +162,9 @@ class Sphere(ParametricSurface): 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) + np.cos(v) * np.sin(u), + np.sin(v) * np.sin(u), + np.cos(u) ]) diff --git a/mobject/types/vectorized_mobject.py b/mobject/types/vectorized_mobject.py index 0a277713..029a1a60 100644 --- a/mobject/types/vectorized_mobject.py +++ b/mobject/types/vectorized_mobject.py @@ -13,6 +13,7 @@ from utils.color import color_to_rgba from utils.iterables import make_even from utils.iterables import tuplify from utils.iterables import stretch_array_to_length +from utils.simple_functions import clip_in_place class VMobject(Mobject): @@ -93,7 +94,7 @@ class VMobject(Mobject): if sheen != 0 and len(rgbas) == 1: light_rgbas = np.array(rgbas) 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) return rgbas @@ -183,7 +184,7 @@ class VMobject(Mobject): def get_fill_rgbas(self): try: - return np.clip(self.fill_rgbas, 0, 1) + return self.fill_rgbas except AttributeError: return np.zeros((1, 4)) @@ -216,7 +217,7 @@ class VMobject(Mobject): rgbas = self.background_stroke_rgbas else: rgbas = self.stroke_rgbas - return np.clip(rgbas, 0, 1) + return rgbas except AttributeError: return np.zeros((1, 4)) diff --git a/utils/color.py b/utils/color.py index ecb8962c..500d5b9c 100644 --- a/utils/color.py +++ b/utils/color.py @@ -7,6 +7,7 @@ from constants import PALETTE from utils.bezier import interpolate from utils.space_ops import normalize +from utils.simple_functions import clip_in_place 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 if factor < 0: factor *= 0.5 - return np.clip(rgb + factor, 0, 1) + result = rgb + factor + clip_in_place(rgb + factor, 0, 1) + return result diff --git a/utils/simple_functions.py b/utils/simple_functions.py index 48f88e4f..23669e70 100644 --- a/utils/simple_functions.py +++ b/utils/simple_functions.py @@ -28,6 +28,14 @@ def get_num_args(function): # 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): if zero_over_zero_value is not None: out = np.full_like(a, zero_over_zero_value)