Kill CONFIG in three_dimensions.py

This commit is contained in:
Grant Sanderson 2022-12-16 09:56:38 -08:00
parent 880aaf913f
commit c2cf261c81

View file

@ -4,9 +4,10 @@ import math
import numpy as np import numpy as np
from manimlib.constants import BLUE, BLUE_D, BLUE_E from manimlib.constants import BLUE, BLUE_D, BLUE_E, GREY_A, BLACK
from manimlib.constants import IN, ORIGIN, OUT, RIGHT from manimlib.constants import IN, ORIGIN, OUT, RIGHT
from manimlib.constants import PI, TAU from manimlib.constants import PI, TAU
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.surface import SGroup from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.surface import Surface from manimlib.mobject.types.surface import Surface
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
@ -20,21 +21,37 @@ from manimlib.utils.space_ops import compass_directions
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector from manimlib.utils.space_ops import z_to_vector
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Tuple, TypeVar
from manimlib.constants import ManimColor, np_vector
T = TypeVar("T", bound=Mobject)
class SurfaceMesh(VGroup): class SurfaceMesh(VGroup):
CONFIG = { def __init__(
"resolution": (21, 11), self,
"stroke_width": 1, uv_surface: Surface,
"normal_nudge": 1e-2, resolution: Tuple[int, int] = (21, 11),
"depth_test": True, stroke_width: float = 1,
"flat_stroke": False, stroke_color: ManimColor = GREY_A,
} normal_nudge: float = 1e-2,
flat_stroke: bool = False,
def __init__(self, uv_surface: Surface, **kwargs): depth_test: bool = True,
if not isinstance(uv_surface, Surface): **kwargs
raise Exception("uv_surface must be of type Surface") ):
self.uv_surface = uv_surface self.uv_surface = uv_surface
super().__init__(**kwargs) self.resolution = resolution
self.normal_nudge = normal_nudge
self.flat_stroke = flat_stroke
super().__init__(
stroke_color=stroke_color,
stroke_width=stroke_width,
depth_test=depth_test,
**kwargs
)
def init_points(self) -> None: def init_points(self) -> None:
uv_surface = self.uv_surface uv_surface = self.uv_surface
@ -75,43 +92,73 @@ class SurfaceMesh(VGroup):
# 3D shapes # 3D shapes
class Sphere(Surface): class Sphere(Surface):
CONFIG = { def __init__(
"resolution": (101, 51), self,
"radius": 1, u_range: Tuple[float, float] = (0, TAU),
"u_range": (0, TAU), v_range: Tuple[float, float] = (0, PI),
"v_range": (0, PI), resolution: Tuple[int, int] = (101, 51),
} radius: float = 1.0,
**kwargs,
):
self.radius = radius
super().__init__(
u_range=u_range,
v_range=v_range,
resolution=resolution,
**kwargs
)
def uv_func(self, u: float, v: float) -> np.ndarray: def uv_func(self, u: float, v: float) -> np.ndarray:
return self.radius * np.array([ return self.radius * np.array([
np.cos(u) * np.sin(v), math.cos(u) * math.sin(v),
np.sin(u) * np.sin(v), math.sin(u) * math.sin(v),
-np.cos(v) -math.cos(v)
]) ])
class Torus(Surface): class Torus(Surface):
CONFIG = { def __init__(
"u_range": (0, TAU), self,
"v_range": (0, TAU), u_range: Tuple[float, float] = (0, TAU),
"r1": 3, v_range: Tuple[float, float] = (0, TAU),
"r2": 1, r1: float = 3.0,
} r2: float = 1.0,
**kwargs,
):
self.r1 = r1
self.r2 = r2
super().__init__(
u_range=u_range,
v_range=v_range,
**kwargs,
)
def uv_func(self, u: float, v: float) -> np.ndarray: def uv_func(self, u: float, v: float) -> np.ndarray:
P = np.array([math.cos(u), math.sin(u), 0]) P = np.array([math.cos(u), math.sin(u), 0])
return (self.r1 - self.r2 * math.cos(v)) * P - math.sin(v) * OUT return (self.r1 - self.r2 * math.cos(v)) * P - self.r2 * math.sin(v) * OUT
class Cylinder(Surface): class Cylinder(Surface):
CONFIG = { def __init__(
"height": 2, self,
"radius": 1, u_range: Tuple[float, float] = (0, TAU),
"axis": OUT, v_range: Tuple[float, float] = (-1, 1),
"u_range": (0, TAU), resolution: Tuple[int, int] = (101, 11),
"v_range": (-1, 1), height: float = 2,
"resolution": (101, 11), radius: float = 1,
} axis: np_vector = OUT,
**kwargs,
):
self.height = height
self.radius = radius
self.axis = axis
super().__init__(
u_range=u_range,
v_range=v_range,
resolution=resolution,
**kwargs
)
def init_points(self): def init_points(self):
super().init_points() super().init_points()
@ -125,146 +172,201 @@ class Cylinder(Surface):
class Line3D(Cylinder): class Line3D(Cylinder):
CONFIG = { def __init__(
"width": 0.05, self,
"resolution": (21, 25) start: np_vector,
} end: np_vector,
width: float = 0.05,
def __init__(self, start: np.ndarray, end: np.ndarray, **kwargs): resolution: Tuple[int, int] = (21, 25),
digest_config(self, kwargs) **kwargs
):
axis = end - start axis = end - start
super().__init__( super().__init__(
height=get_norm(axis), height=get_norm(axis),
radius=self.width / 2, radius=width / 2,
axis=axis axis=axis,
**kwargs
) )
self.shift((start + end) / 2) self.shift((start + end) / 2)
class Disk3D(Surface): class Disk3D(Surface):
CONFIG = { def __init__(
"radius": 1, self,
"u_range": (0, 1), radius: float = 1,
"v_range": (0, TAU), u_range: Tuple[float, float] = (0, 1),
"resolution": (2, 25), v_range: Tuple[float, float] = (0, TAU),
} resolution: Tuple[int, int] = (2, 100),
**kwargs
def init_points(self) -> None: ):
super().init_points() super().__init__(
self.scale(self.radius) u_range=u_range,
v_range=v_range,
resolution=resolution,
**kwargs,
)
self.scale(radius)
def uv_func(self, u: float, v: float) -> np.ndarray: def uv_func(self, u: float, v: float) -> np.ndarray:
return np.array([ return np.array([
u * np.cos(v), u * math.cos(v),
u * np.sin(v), u * math.sin(v),
0 0
]) ])
class Square3D(Surface): class Square3D(Surface):
CONFIG = { def __init__(
"side_length": 2, self,
"u_range": (-1, 1), side_length: float = 2.0,
"v_range": (-1, 1), u_range: Tuple[float, float] = (-1, 1),
"resolution": (2, 2), v_range: Tuple[float, float] = (-1, 1),
} resolution: Tuple[int, int] = (2, 2),
**kwargs,
def init_points(self) -> None: ):
super().init_points() super().__init__(
self.scale(self.side_length / 2) u_range=u_range,
v_range=v_range,
resolution=resolution,
**kwargs
)
self.scale(side_length / 2)
def uv_func(self, u: float, v: float) -> np.ndarray: def uv_func(self, u: float, v: float) -> np.ndarray:
return np.array([u, v, 0]) return np.array([u, v, 0])
def square_to_cube_faces(square: T) -> list[T]:
radius = square.get_height() / 2
square.move_to(radius * OUT)
result = [square.copy()]
result.extend([
square.copy().rotate(PI / 2, axis=vect, about_point=ORIGIN)
for vect in compass_directions(4)
])
result.append(square.copy().rotate(PI, RIGHT, about_point=ORIGIN))
return result
class Cube(SGroup): class Cube(SGroup):
CONFIG = { def __init__(
"color": BLUE, self,
"opacity": 1, color: ManimColor = BLUE,
"gloss": 0.5, opacity: float = 1,
"square_resolution": (2, 2), gloss: float = 0.5,
"side_length": 2, square_resolution: Tuple[int, int] = (2, 2),
"square_class": Square3D, side_length: float = 2,
} **kwargs,
):
def init_points(self) -> None:
face = Square3D( face = Square3D(
resolution=self.square_resolution, resolution=square_resolution,
side_length=self.side_length, side_length=side_length,
color=color,
opacity=opacity,
)
super().__init__(
*square_to_cube_faces(face),
gloss=gloss,
**kwargs
) )
self.add(*self.square_to_cube_faces(face))
@staticmethod
def square_to_cube_faces(square: Square3D) -> list[Square3D]:
radius = square.get_height() / 2
square.move_to(radius * OUT)
result = [square]
result.extend([
square.copy().rotate(PI / 2, axis=vect, about_point=ORIGIN)
for vect in compass_directions(4)
])
result.append(square.copy().rotate(PI, RIGHT, about_point=ORIGIN))
return result
def _get_face(self) -> Square3D:
return Square3D(resolution=self.square_resolution)
class Prism(Cube): class Prism(Cube):
def __init__(self, width: float = 3.0, height: float = 2.0, depth: float = 1.0, **kwargs): def __init__(
self,
width: float = 3.0,
height: float = 2.0,
depth: float = 1.0,
**kwargs
):
super().__init__(**kwargs) super().__init__(**kwargs)
for dim, value in enumerate([width, height, depth]): for dim, value in enumerate([width, height, depth]):
self.rescale_to_fit(value, dim, stretch=True) self.rescale_to_fit(value, dim, stretch=True)
class VCube(VGroup): class VGroup3D(VGroup):
CONFIG = { def __init__(
"fill_color": BLUE_D, self,
"fill_opacity": 1, *vmobjects: VMobject,
"stroke_width": 0, depth_test: bool = True,
"gloss": 0.5, gloss: float = 0.2,
"shadow": 0.5, shadow: float = 0.2,
"joint_type": "round", reflectiveness: float = 0.2,
} joint_type: str = "round",
**kwargs
):
super().__init__(*vmobjects, **kwargs)
self.set_gloss(gloss)
self.set_shadow(shadow)
self.set_reflectiveness(reflectiveness)
self.set_joint_type(joint_type)
if depth_test:
self.apply_depth_test()
def __init__(self, side_length: float = 2.0, **kwargs):
face = Square(side_length=side_length) class VCube(VGroup3D):
super().__init__(*Cube.square_to_cube_faces(face), **kwargs) def __init__(
self.init_colors() self,
self.set_joint_type(self.joint_type) side_length: float = 2.0,
self.apply_depth_test() fill_color: ManimColor = BLUE_D,
fill_opacity: float = 1,
stroke_width: float = 0,
**kwargs
):
style = dict(
fill_color=fill_color,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
**kwargs
)
face = Square(side_length=side_length, **style)
super().__init__(*square_to_cube_faces(face), **style)
self.refresh_unit_normal() self.refresh_unit_normal()
class VPrism(VCube): class VPrism(VCube):
def __init__(self, width: float = 3.0, height: float = 2.0, depth: float = 1.0, **kwargs): def __init__(
self,
width: float = 3.0,
height: float = 2.0,
depth: float = 1.0,
**kwargs
):
super().__init__(**kwargs) super().__init__(**kwargs)
for dim, value in enumerate([width, height, depth]): for dim, value in enumerate([width, height, depth]):
self.rescale_to_fit(value, dim, stretch=True) self.rescale_to_fit(value, dim, stretch=True)
class Dodecahedron(VGroup): class Dodecahedron(VGroup3D):
CONFIG = { def __init__(
"fill_color": BLUE_E, self,
"fill_opacity": 1, fill_color: ManimColor = BLUE_E,
"stroke_width": 1, fill_opacity: float = 1,
"reflectiveness": 0.2, stroke_color: ManimColor = BLUE_E,
"gloss": 0.3, stroke_width: float = 1,
"shadow": 0.2, reflectiveness: float = 0.2,
"depth_test": True, **kwargs,
} ):
style = dict(
fill_color=fill_color,
fill_opacity=fill_opacity,
stroke_color=stroke_color,
stroke_width=stroke_width,
reflectiveness=reflectiveness,
**kwargs
)
def init_points(self) -> None: # Start by creating two of the pentagons, meeting
# Star by creating two of the pentagons, meeting
# back to back on the positive x-axis # back to back on the positive x-axis
phi = (1 + math.sqrt(5)) / 2 phi = (1 + math.sqrt(5)) / 2
x, y, z = np.identity(3) x, y, z = np.identity(3)
pentagon1 = Polygon( pentagon1 = Polygon(
[phi, 1 / phi, 0], np.array([phi, 1 / phi, 0]),
[1, 1, 1], np.array([1, 1, 1]),
[1 / phi, 0, phi], np.array([1 / phi, 0, phi]),
[1, -1, 1], np.array([1, -1, 1]),
[phi, -1 / phi, 0], np.array([phi, -1 / phi, 0]),
**style
) )
pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN) pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)
pentagon2.reverse_points() pentagon2.reverse_points()
@ -272,12 +374,14 @@ class Dodecahedron(VGroup):
z_pair = x_pair.copy().apply_matrix(np.array([z, -x, -y]).T) z_pair = x_pair.copy().apply_matrix(np.array([z, -x, -y]).T)
y_pair = x_pair.copy().apply_matrix(np.array([y, z, x]).T) y_pair = x_pair.copy().apply_matrix(np.array([y, z, x]).T)
self.add(*x_pair, *y_pair, *z_pair) pentagons = [*x_pair, *y_pair, *z_pair]
for pentagon in list(self): for pentagon in list(pentagons):
pc = pentagon.copy() pc = pentagon.copy()
pc.apply_function(lambda p: -p) pc.apply_function(lambda p: -p)
pc.reverse_points() pc.reverse_points()
self.add(pc) pentagons.append(pc)
super().__init__(*pentagons, **style)
# # Rotate those two pentagons by all the axis permuations to fill # # Rotate those two pentagons by all the axis permuations to fill
# # out the dodecahedron # # out the dodecahedron
@ -290,20 +394,16 @@ class Dodecahedron(VGroup):
# self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN)) # self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN))
class Prismify(VGroup): class Prismify(VGroup3D):
CONFIG = {
"apply_depth_test": True,
}
def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs): def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs):
# At the moment, this assume stright edges # At the moment, this assume stright edges
super().__init__(**kwargs)
vect = depth * direction vect = depth * direction
self.add(vmobject.copy()) pieces = [vmobject.copy()]
points = vmobject.get_points()[::vmobject.n_points_per_curve] points = vmobject.get_points()[::vmobject.n_points_per_curve]
for p1, p2 in adjacent_pairs(points): for p1, p2 in adjacent_pairs(points):
wall = VMobject() wall = VMobject()
wall.match_style(vmobject) wall.match_style(vmobject)
wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect]) wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect])
self.add(wall) pieces.append(wall)
self.add(vmobject.copy().shift(vect).reverse_points()) pieces.append(vmobject.copy().shift(vect).reverse_points())
super().__init__(*pieces, **kwargs)