2022-02-15 14:37:15 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-06-09 21:24:42 -07:00
|
|
|
import math
|
|
|
|
|
2022-04-22 16:42:45 +08:00
|
|
|
import numpy as np
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
from manimlib.constants import BLUE, BLUE_D, BLUE_E, GREY_A, BLACK
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.constants import IN, ORIGIN, OUT, RIGHT
|
|
|
|
from manimlib.constants import PI, TAU
|
2022-12-16 09:56:38 -08:00
|
|
|
from manimlib.mobject.mobject import Mobject
|
2020-06-05 13:37:06 -07:00
|
|
|
from manimlib.mobject.types.surface import SGroup
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.mobject.types.surface import Surface
|
2020-06-06 13:23:30 -07:00
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
2021-12-13 16:03:46 -08:00
|
|
|
from manimlib.mobject.geometry import Polygon
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.mobject.geometry import Square
|
2021-11-08 21:47:26 -08:00
|
|
|
from manimlib.utils.bezier import interpolate
|
2022-03-17 12:00:10 -07:00
|
|
|
from manimlib.utils.iterables import adjacent_pairs
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.space_ops import compass_directions
|
2020-06-05 13:24:26 -07:00
|
|
|
from manimlib.utils.space_ops import get_norm
|
|
|
|
from manimlib.utils.space_ops import z_to_vector
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from typing import Tuple, TypeVar
|
2023-01-16 19:33:57 -08:00
|
|
|
from manimlib.typing import ManimColor, Vect3, Sequence
|
2022-12-16 09:56:38 -08:00
|
|
|
|
|
|
|
T = TypeVar("T", bound=Mobject)
|
|
|
|
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2020-06-06 13:23:30 -07:00
|
|
|
class SurfaceMesh(VGroup):
|
2022-12-16 09:56:38 -08:00
|
|
|
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,
|
|
|
|
depth_test: bool = True,
|
2023-01-10 11:06:41 -08:00
|
|
|
joint_type: str = 'no_joint',
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs
|
|
|
|
):
|
2020-06-06 13:23:30 -07:00
|
|
|
self.uv_surface = uv_surface
|
2022-12-16 09:56:38 -08:00
|
|
|
self.resolution = resolution
|
|
|
|
self.normal_nudge = normal_nudge
|
|
|
|
|
|
|
|
super().__init__(
|
|
|
|
stroke_color=stroke_color,
|
|
|
|
stroke_width=stroke_width,
|
|
|
|
depth_test=depth_test,
|
2023-01-10 11:06:41 -08:00
|
|
|
joint_type=joint_type,
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2020-06-06 13:23:30 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def init_points(self) -> None:
|
2020-06-06 13:23:30 -07:00
|
|
|
uv_surface = self.uv_surface
|
|
|
|
|
|
|
|
full_nu, full_nv = uv_surface.resolution
|
|
|
|
part_nu, part_nv = self.resolution
|
2021-11-08 21:47:26 -08:00
|
|
|
# 'indices' are treated as floats. Later, there will be
|
|
|
|
# an interpolation between the floor and ceiling of these
|
|
|
|
# indices
|
2021-11-18 17:52:17 -08:00
|
|
|
u_indices = np.linspace(0, full_nu - 1, part_nu)
|
|
|
|
v_indices = np.linspace(0, full_nv - 1, part_nv)
|
2020-06-06 13:23:30 -07:00
|
|
|
|
2023-01-30 20:37:15 -08:00
|
|
|
points = uv_surface.get_points()
|
2020-06-06 13:23:30 -07:00
|
|
|
normals = uv_surface.get_unit_normals()
|
2021-07-28 07:32:16 -07:00
|
|
|
nudge = self.normal_nudge
|
2020-06-06 13:23:30 -07:00
|
|
|
nudged_points = points + nudge * normals
|
|
|
|
|
|
|
|
for ui in u_indices:
|
|
|
|
path = VMobject()
|
2021-11-08 21:47:26 -08:00
|
|
|
low_ui = full_nv * int(math.floor(ui))
|
|
|
|
high_ui = full_nv * int(math.ceil(ui))
|
|
|
|
path.set_points_smoothly(interpolate(
|
|
|
|
nudged_points[low_ui:low_ui + full_nv],
|
|
|
|
nudged_points[high_ui:high_ui + full_nv],
|
|
|
|
ui % 1
|
|
|
|
))
|
2020-06-06 13:23:30 -07:00
|
|
|
self.add(path)
|
|
|
|
for vi in v_indices:
|
|
|
|
path = VMobject()
|
2021-11-08 21:47:26 -08:00
|
|
|
path.set_points_smoothly(interpolate(
|
|
|
|
nudged_points[int(math.floor(vi))::full_nv],
|
|
|
|
nudged_points[int(math.ceil(vi))::full_nv],
|
|
|
|
vi % 1
|
|
|
|
))
|
2020-06-06 13:23:30 -07:00
|
|
|
self.add(path)
|
|
|
|
|
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
# 3D shapes
|
2018-08-15 16:23:29 -07:00
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
class Sphere(Surface):
|
2022-12-16 09:56:38 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
u_range: Tuple[float, float] = (0, TAU),
|
2024-11-25 12:39:41 -07:00
|
|
|
v_range: Tuple[float, float] = (0, PI),
|
2022-12-16 09:56:38 -08:00
|
|
|
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
|
|
|
|
)
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
2020-06-04 15:41:20 -07:00
|
|
|
return self.radius * np.array([
|
2022-12-16 09:56:38 -08:00
|
|
|
math.cos(u) * math.sin(v),
|
|
|
|
math.sin(u) * math.sin(v),
|
|
|
|
-math.cos(v)
|
2018-08-15 16:23:29 -07:00
|
|
|
])
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
class Torus(Surface):
|
2022-12-16 09:56:38 -08:00
|
|
|
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,
|
|
|
|
)
|
2020-06-17 17:12:23 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
2020-06-17 17:12:23 -07:00
|
|
|
P = np.array([math.cos(u), math.sin(u), 0])
|
2022-12-16 09:56:38 -08:00
|
|
|
return (self.r1 - self.r2 * math.cos(v)) * P - self.r2 * math.sin(v) * OUT
|
2020-06-17 17:12:23 -07:00
|
|
|
|
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
class Cylinder(Surface):
|
2022-12-16 09:56:38 -08:00
|
|
|
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,
|
2022-12-16 20:35:26 -08:00
|
|
|
axis: Vect3 = OUT,
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
self.height = height
|
|
|
|
self.radius = radius
|
|
|
|
self.axis = axis
|
|
|
|
super().__init__(
|
|
|
|
u_range=u_range,
|
|
|
|
v_range=v_range,
|
|
|
|
resolution=resolution,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
2020-06-05 13:24:26 -07:00
|
|
|
def init_points(self):
|
|
|
|
super().init_points()
|
|
|
|
self.scale(self.radius)
|
|
|
|
self.set_depth(self.height, stretch=True)
|
|
|
|
self.apply_matrix(z_to_vector(self.axis))
|
2020-06-05 11:12:52 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
|
|
|
return np.array([np.cos(u), np.sin(u), v])
|
2020-06-05 13:24:26 -07:00
|
|
|
|
|
|
|
|
2024-11-08 14:28:17 -06:00
|
|
|
class Cone(Cylinder):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
u_range: Tuple[float, float] = (0, TAU),
|
|
|
|
v_range: Tuple[float, float] = (0, 1),
|
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
super().__init__(u_range=u_range, v_range=v_range, *args, **kwargs)
|
|
|
|
|
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
|
|
|
return np.array([(1 - v) * np.cos(u), (1 - v) * np.sin(u), v])
|
|
|
|
|
|
|
|
|
2020-06-05 13:24:26 -07:00
|
|
|
class Line3D(Cylinder):
|
2022-12-16 09:56:38 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3,
|
|
|
|
end: Vect3,
|
2022-12-16 09:56:38 -08:00
|
|
|
width: float = 0.05,
|
|
|
|
resolution: Tuple[int, int] = (21, 25),
|
|
|
|
**kwargs
|
|
|
|
):
|
2020-06-05 13:24:26 -07:00
|
|
|
axis = end - start
|
|
|
|
super().__init__(
|
|
|
|
height=get_norm(axis),
|
2022-12-16 09:56:38 -08:00
|
|
|
radius=width / 2,
|
|
|
|
axis=axis,
|
2023-01-31 14:19:24 -08:00
|
|
|
resolution=resolution,
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs
|
2020-06-05 13:24:26 -07:00
|
|
|
)
|
2020-06-17 17:12:23 -07:00
|
|
|
self.shift((start + end) / 2)
|
2020-06-05 11:12:52 -07:00
|
|
|
|
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
class Disk3D(Surface):
|
2022-12-16 09:56:38 -08:00
|
|
|
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)
|
2020-06-05 13:24:26 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
|
|
|
return np.array([
|
2022-12-16 09:56:38 -08:00
|
|
|
u * math.cos(v),
|
|
|
|
u * math.sin(v),
|
2020-06-05 13:24:26 -07:00
|
|
|
0
|
2022-02-15 14:37:15 +08:00
|
|
|
])
|
2020-06-05 13:24:26 -07:00
|
|
|
|
|
|
|
|
2021-02-04 12:36:35 -08:00
|
|
|
class Square3D(Surface):
|
2022-12-16 09:56:38 -08:00
|
|
|
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)
|
2020-06-05 13:37:06 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def uv_func(self, u: float, v: float) -> np.ndarray:
|
|
|
|
return np.array([u, v, 0])
|
2020-06-05 13:37:06 -07:00
|
|
|
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
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
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
|
|
|
|
class Cube(SGroup):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
color: ManimColor = BLUE,
|
|
|
|
opacity: float = 1,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading: Tuple[float, float, float] = (0.1, 0.5, 0.1),
|
2022-12-16 09:56:38 -08:00
|
|
|
square_resolution: Tuple[int, int] = (2, 2),
|
|
|
|
side_length: float = 2,
|
|
|
|
**kwargs,
|
|
|
|
):
|
2021-08-19 08:37:01 -07:00
|
|
|
face = Square3D(
|
2022-12-16 09:56:38 -08:00
|
|
|
resolution=square_resolution,
|
|
|
|
side_length=side_length,
|
|
|
|
color=color,
|
|
|
|
opacity=opacity,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading=shading,
|
2022-12-16 09:56:38 -08:00
|
|
|
)
|
2023-01-16 19:33:57 -08:00
|
|
|
super().__init__(*square_to_cube_faces(face), **kwargs)
|
2021-08-19 08:37:01 -07:00
|
|
|
|
|
|
|
|
2022-03-29 20:34:14 -07:00
|
|
|
class Prism(Cube):
|
2022-12-16 09:56:38 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
width: float = 3.0,
|
|
|
|
height: float = 2.0,
|
|
|
|
depth: float = 1.0,
|
|
|
|
**kwargs
|
|
|
|
):
|
2022-03-29 20:34:14 -07:00
|
|
|
super().__init__(**kwargs)
|
|
|
|
for dim, value in enumerate([width, height, depth]):
|
|
|
|
self.rescale_to_fit(value, dim, stretch=True)
|
|
|
|
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
class VGroup3D(VGroup):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
*vmobjects: VMobject,
|
|
|
|
depth_test: bool = True,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading: Tuple[float, float, float] = (0.2, 0.2, 0.2),
|
2023-01-10 11:06:41 -08:00
|
|
|
joint_type: str = "no_joint",
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(*vmobjects, **kwargs)
|
2023-01-16 19:33:57 -08:00
|
|
|
self.set_shading(*shading)
|
2022-12-16 09:56:38 -08:00
|
|
|
self.set_joint_type(joint_type)
|
|
|
|
if depth_test:
|
|
|
|
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)
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2022-03-29 20:34:14 -07:00
|
|
|
class VPrism(VCube):
|
2022-12-16 09:56:38 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
width: float = 3.0,
|
|
|
|
height: float = 2.0,
|
|
|
|
depth: float = 1.0,
|
|
|
|
**kwargs
|
|
|
|
):
|
2022-03-29 20:34:14 -07:00
|
|
|
super().__init__(**kwargs)
|
|
|
|
for dim, value in enumerate([width, height, depth]):
|
|
|
|
self.rescale_to_fit(value, dim, stretch=True)
|
|
|
|
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
class Dodecahedron(VGroup3D):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
fill_color: ManimColor = BLUE_E,
|
|
|
|
fill_opacity: float = 1,
|
|
|
|
stroke_color: ManimColor = BLUE_E,
|
|
|
|
stroke_width: float = 1,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading: Tuple[float, float, float] = (0.2, 0.2, 0.2),
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
style = dict(
|
|
|
|
fill_color=fill_color,
|
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
stroke_color=stroke_color,
|
|
|
|
stroke_width=stroke_width,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading=shading,
|
2022-12-16 09:56:38 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2021-12-13 16:03:46 -08:00
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
# Start by creating two of the pentagons, meeting
|
2021-12-13 16:03:46 -08:00
|
|
|
# back to back on the positive x-axis
|
|
|
|
phi = (1 + math.sqrt(5)) / 2
|
|
|
|
x, y, z = np.identity(3)
|
|
|
|
pentagon1 = Polygon(
|
2022-12-16 09:56:38 -08:00
|
|
|
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
|
2021-12-13 16:03:46 -08:00
|
|
|
)
|
|
|
|
pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)
|
|
|
|
pentagon2.reverse_points()
|
|
|
|
x_pair = VGroup(pentagon1, pentagon2)
|
|
|
|
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)
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
pentagons = [*x_pair, *y_pair, *z_pair]
|
|
|
|
for pentagon in list(pentagons):
|
2021-12-13 16:03:46 -08:00
|
|
|
pc = pentagon.copy()
|
|
|
|
pc.apply_function(lambda p: -p)
|
|
|
|
pc.reverse_points()
|
2022-12-16 09:56:38 -08:00
|
|
|
pentagons.append(pc)
|
|
|
|
|
|
|
|
super().__init__(*pentagons, **style)
|
2021-12-13 16:03:46 -08:00
|
|
|
|
|
|
|
|
2022-12-16 09:56:38 -08:00
|
|
|
class Prismify(VGroup3D):
|
2022-03-17 12:00:10 -07:00
|
|
|
def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs):
|
|
|
|
# At the moment, this assume stright edges
|
|
|
|
vect = depth * direction
|
2022-12-16 09:56:38 -08:00
|
|
|
pieces = [vmobject.copy()]
|
2023-01-11 19:52:45 -08:00
|
|
|
points = vmobject.get_anchors()
|
2022-03-17 12:00:10 -07:00
|
|
|
for p1, p2 in adjacent_pairs(points):
|
|
|
|
wall = VMobject()
|
|
|
|
wall.match_style(vmobject)
|
|
|
|
wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect])
|
2022-12-16 09:56:38 -08:00
|
|
|
pieces.append(wall)
|
2023-01-11 19:52:45 -08:00
|
|
|
top = vmobject.copy()
|
|
|
|
top.shift(vect)
|
|
|
|
top.reverse_points()
|
|
|
|
pieces.append(top)
|
2022-12-16 09:56:38 -08:00
|
|
|
super().__init__(*pieces, **kwargs)
|