mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Kill CONFIG in three_dimensions.py
This commit is contained in:
parent
880aaf913f
commit
c2cf261c81
1 changed files with 242 additions and 142 deletions
|
@ -4,9 +4,10 @@ import math
|
|||
|
||||
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 PI, TAU
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.types.surface import SGroup
|
||||
from manimlib.mobject.types.surface import Surface
|
||||
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 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):
|
||||
CONFIG = {
|
||||
"resolution": (21, 11),
|
||||
"stroke_width": 1,
|
||||
"normal_nudge": 1e-2,
|
||||
"depth_test": True,
|
||||
"flat_stroke": False,
|
||||
}
|
||||
|
||||
def __init__(self, uv_surface: Surface, **kwargs):
|
||||
if not isinstance(uv_surface, Surface):
|
||||
raise Exception("uv_surface must be of type Surface")
|
||||
def __init__(
|
||||
self,
|
||||
uv_surface: Surface,
|
||||
resolution: Tuple[int, int] = (21, 11),
|
||||
stroke_width: float = 1,
|
||||
stroke_color: ManimColor = GREY_A,
|
||||
normal_nudge: float = 1e-2,
|
||||
flat_stroke: bool = False,
|
||||
depth_test: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
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:
|
||||
uv_surface = self.uv_surface
|
||||
|
@ -75,43 +92,73 @@ class SurfaceMesh(VGroup):
|
|||
# 3D shapes
|
||||
|
||||
class Sphere(Surface):
|
||||
CONFIG = {
|
||||
"resolution": (101, 51),
|
||||
"radius": 1,
|
||||
"u_range": (0, TAU),
|
||||
"v_range": (0, PI),
|
||||
}
|
||||
def __init__(
|
||||
self,
|
||||
u_range: Tuple[float, float] = (0, TAU),
|
||||
v_range: Tuple[float, float] = (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:
|
||||
return self.radius * np.array([
|
||||
np.cos(u) * np.sin(v),
|
||||
np.sin(u) * np.sin(v),
|
||||
-np.cos(v)
|
||||
math.cos(u) * math.sin(v),
|
||||
math.sin(u) * math.sin(v),
|
||||
-math.cos(v)
|
||||
])
|
||||
|
||||
|
||||
class Torus(Surface):
|
||||
CONFIG = {
|
||||
"u_range": (0, TAU),
|
||||
"v_range": (0, TAU),
|
||||
"r1": 3,
|
||||
"r2": 1,
|
||||
}
|
||||
def __init__(
|
||||
self,
|
||||
u_range: Tuple[float, float] = (0, TAU),
|
||||
v_range: Tuple[float, float] = (0, TAU),
|
||||
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:
|
||||
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):
|
||||
CONFIG = {
|
||||
"height": 2,
|
||||
"radius": 1,
|
||||
"axis": OUT,
|
||||
"u_range": (0, TAU),
|
||||
"v_range": (-1, 1),
|
||||
"resolution": (101, 11),
|
||||
}
|
||||
def __init__(
|
||||
self,
|
||||
u_range: Tuple[float, float] = (0, TAU),
|
||||
v_range: Tuple[float, float] = (-1, 1),
|
||||
resolution: Tuple[int, int] = (101, 11),
|
||||
height: float = 2,
|
||||
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):
|
||||
super().init_points()
|
||||
|
@ -125,146 +172,201 @@ class Cylinder(Surface):
|
|||
|
||||
|
||||
class Line3D(Cylinder):
|
||||
CONFIG = {
|
||||
"width": 0.05,
|
||||
"resolution": (21, 25)
|
||||
}
|
||||
|
||||
def __init__(self, start: np.ndarray, end: np.ndarray, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
def __init__(
|
||||
self,
|
||||
start: np_vector,
|
||||
end: np_vector,
|
||||
width: float = 0.05,
|
||||
resolution: Tuple[int, int] = (21, 25),
|
||||
**kwargs
|
||||
):
|
||||
axis = end - start
|
||||
super().__init__(
|
||||
height=get_norm(axis),
|
||||
radius=self.width / 2,
|
||||
axis=axis
|
||||
radius=width / 2,
|
||||
axis=axis,
|
||||
**kwargs
|
||||
)
|
||||
self.shift((start + end) / 2)
|
||||
|
||||
|
||||
class Disk3D(Surface):
|
||||
CONFIG = {
|
||||
"radius": 1,
|
||||
"u_range": (0, 1),
|
||||
"v_range": (0, TAU),
|
||||
"resolution": (2, 25),
|
||||
}
|
||||
|
||||
def init_points(self) -> None:
|
||||
super().init_points()
|
||||
self.scale(self.radius)
|
||||
def __init__(
|
||||
self,
|
||||
radius: float = 1,
|
||||
u_range: Tuple[float, float] = (0, 1),
|
||||
v_range: Tuple[float, float] = (0, TAU),
|
||||
resolution: Tuple[int, int] = (2, 100),
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(
|
||||
u_range=u_range,
|
||||
v_range=v_range,
|
||||
resolution=resolution,
|
||||
**kwargs,
|
||||
)
|
||||
self.scale(radius)
|
||||
|
||||
def uv_func(self, u: float, v: float) -> np.ndarray:
|
||||
return np.array([
|
||||
u * np.cos(v),
|
||||
u * np.sin(v),
|
||||
u * math.cos(v),
|
||||
u * math.sin(v),
|
||||
0
|
||||
])
|
||||
|
||||
|
||||
class Square3D(Surface):
|
||||
CONFIG = {
|
||||
"side_length": 2,
|
||||
"u_range": (-1, 1),
|
||||
"v_range": (-1, 1),
|
||||
"resolution": (2, 2),
|
||||
}
|
||||
|
||||
def init_points(self) -> None:
|
||||
super().init_points()
|
||||
self.scale(self.side_length / 2)
|
||||
def __init__(
|
||||
self,
|
||||
side_length: float = 2.0,
|
||||
u_range: Tuple[float, float] = (-1, 1),
|
||||
v_range: Tuple[float, float] = (-1, 1),
|
||||
resolution: Tuple[int, int] = (2, 2),
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
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:
|
||||
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):
|
||||
CONFIG = {
|
||||
"color": BLUE,
|
||||
"opacity": 1,
|
||||
"gloss": 0.5,
|
||||
"square_resolution": (2, 2),
|
||||
"side_length": 2,
|
||||
"square_class": Square3D,
|
||||
}
|
||||
|
||||
def init_points(self) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
color: ManimColor = BLUE,
|
||||
opacity: float = 1,
|
||||
gloss: float = 0.5,
|
||||
square_resolution: Tuple[int, int] = (2, 2),
|
||||
side_length: float = 2,
|
||||
**kwargs,
|
||||
):
|
||||
face = Square3D(
|
||||
resolution=self.square_resolution,
|
||||
side_length=self.side_length,
|
||||
resolution=square_resolution,
|
||||
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):
|
||||
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)
|
||||
for dim, value in enumerate([width, height, depth]):
|
||||
self.rescale_to_fit(value, dim, stretch=True)
|
||||
|
||||
|
||||
class VCube(VGroup):
|
||||
CONFIG = {
|
||||
"fill_color": BLUE_D,
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0,
|
||||
"gloss": 0.5,
|
||||
"shadow": 0.5,
|
||||
"joint_type": "round",
|
||||
}
|
||||
class VGroup3D(VGroup):
|
||||
def __init__(
|
||||
self,
|
||||
*vmobjects: VMobject,
|
||||
depth_test: bool = True,
|
||||
gloss: float = 0.2,
|
||||
shadow: float = 0.2,
|
||||
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)
|
||||
super().__init__(*Cube.square_to_cube_faces(face), **kwargs)
|
||||
self.init_colors()
|
||||
self.set_joint_type(self.joint_type)
|
||||
self.apply_depth_test()
|
||||
|
||||
class VCube(VGroup3D):
|
||||
def __init__(
|
||||
self,
|
||||
side_length: float = 2.0,
|
||||
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()
|
||||
|
||||
|
||||
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)
|
||||
for dim, value in enumerate([width, height, depth]):
|
||||
self.rescale_to_fit(value, dim, stretch=True)
|
||||
|
||||
|
||||
class Dodecahedron(VGroup):
|
||||
CONFIG = {
|
||||
"fill_color": BLUE_E,
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 1,
|
||||
"reflectiveness": 0.2,
|
||||
"gloss": 0.3,
|
||||
"shadow": 0.2,
|
||||
"depth_test": True,
|
||||
}
|
||||
class Dodecahedron(VGroup3D):
|
||||
def __init__(
|
||||
self,
|
||||
fill_color: ManimColor = BLUE_E,
|
||||
fill_opacity: float = 1,
|
||||
stroke_color: ManimColor = BLUE_E,
|
||||
stroke_width: float = 1,
|
||||
reflectiveness: float = 0.2,
|
||||
**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:
|
||||
# Star by creating two of the pentagons, meeting
|
||||
# Start by creating two of the pentagons, meeting
|
||||
# back to back on the positive x-axis
|
||||
phi = (1 + math.sqrt(5)) / 2
|
||||
x, y, z = np.identity(3)
|
||||
pentagon1 = Polygon(
|
||||
[phi, 1 / phi, 0],
|
||||
[1, 1, 1],
|
||||
[1 / phi, 0, phi],
|
||||
[1, -1, 1],
|
||||
[phi, -1 / phi, 0],
|
||||
np.array([phi, 1 / phi, 0]),
|
||||
np.array([1, 1, 1]),
|
||||
np.array([1 / phi, 0, phi]),
|
||||
np.array([1, -1, 1]),
|
||||
np.array([phi, -1 / phi, 0]),
|
||||
**style
|
||||
)
|
||||
pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)
|
||||
pentagon2.reverse_points()
|
||||
|
@ -272,12 +374,14 @@ class Dodecahedron(VGroup):
|
|||
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)
|
||||
|
||||
self.add(*x_pair, *y_pair, *z_pair)
|
||||
for pentagon in list(self):
|
||||
pentagons = [*x_pair, *y_pair, *z_pair]
|
||||
for pentagon in list(pentagons):
|
||||
pc = pentagon.copy()
|
||||
pc.apply_function(lambda p: -p)
|
||||
pc.reverse_points()
|
||||
self.add(pc)
|
||||
pentagons.append(pc)
|
||||
|
||||
super().__init__(*pentagons, **style)
|
||||
|
||||
# # Rotate those two pentagons by all the axis permuations to fill
|
||||
# # out the dodecahedron
|
||||
|
@ -290,20 +394,16 @@ class Dodecahedron(VGroup):
|
|||
# self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN))
|
||||
|
||||
|
||||
class Prismify(VGroup):
|
||||
CONFIG = {
|
||||
"apply_depth_test": True,
|
||||
}
|
||||
|
||||
class Prismify(VGroup3D):
|
||||
def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs):
|
||||
# At the moment, this assume stright edges
|
||||
super().__init__(**kwargs)
|
||||
vect = depth * direction
|
||||
self.add(vmobject.copy())
|
||||
pieces = [vmobject.copy()]
|
||||
points = vmobject.get_points()[::vmobject.n_points_per_curve]
|
||||
for p1, p2 in adjacent_pairs(points):
|
||||
wall = VMobject()
|
||||
wall.match_style(vmobject)
|
||||
wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect])
|
||||
self.add(wall)
|
||||
self.add(vmobject.copy().shift(vect).reverse_points())
|
||||
pieces.append(wall)
|
||||
pieces.append(vmobject.copy().shift(vect).reverse_points())
|
||||
super().__init__(*pieces, **kwargs)
|
||||
|
|
Loading…
Add table
Reference in a new issue