manim 0.18.0.post0__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of manim might be problematic. Click here for more details.
- manim/__init__.py +3 -6
- manim/__main__.py +61 -20
- manim/_config/__init__.py +6 -3
- manim/_config/cli_colors.py +16 -8
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +14 -8
- manim/_config/utils.py +651 -472
- manim/animation/animation.py +152 -5
- manim/animation/composition.py +80 -39
- manim/animation/creation.py +196 -14
- manim/animation/fading.py +5 -9
- manim/animation/indication.py +103 -47
- manim/animation/movement.py +22 -5
- manim/animation/rotation.py +3 -2
- manim/animation/specialized.py +4 -6
- manim/animation/speedmodifier.py +10 -5
- manim/animation/transform.py +4 -5
- manim/animation/transform_matching_parts.py +1 -1
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +15 -6
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +70 -44
- manim/cli/checkhealth/checks.py +93 -75
- manim/cli/checkhealth/commands.py +14 -5
- manim/cli/default_group.py +157 -25
- manim/cli/init/commands.py +32 -24
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +51 -15
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +97 -32
- manim/constants.py +65 -19
- manim/gui/gui.py +2 -0
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +112 -78
- manim/mobject/geometry/boolean_ops.py +32 -25
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +132 -64
- manim/mobject/geometry/polygram.py +126 -30
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +38 -29
- manim/mobject/graph.py +414 -133
- manim/mobject/graphing/coordinate_systems.py +126 -64
- manim/mobject/graphing/functions.py +25 -15
- manim/mobject/graphing/number_line.py +24 -10
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +314 -165
- manim/mobject/opengl/opengl_compatibility.py +2 -0
- manim/mobject/opengl/opengl_geometry.py +30 -9
- manim/mobject/opengl/opengl_image_mobject.py +2 -0
- manim/mobject/opengl/opengl_mobject.py +509 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
- manim/mobject/opengl/opengl_surface.py +3 -2
- manim/mobject/opengl/opengl_three_dimensions.py +2 -0
- manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
- manim/mobject/svg/brace.py +63 -13
- manim/mobject/svg/svg_mobject.py +4 -3
- manim/mobject/table.py +11 -13
- manim/mobject/text/code_mobject.py +186 -548
- manim/mobject/text/numbers.py +9 -7
- manim/mobject/text/tex_mobject.py +23 -14
- manim/mobject/text/text_mobject.py +70 -24
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_d_utils.py +4 -4
- manim/mobject/three_d/three_dimensions.py +62 -34
- manim/mobject/types/image_mobject.py +42 -24
- manim/mobject/types/point_cloud_mobject.py +105 -67
- manim/mobject/types/vectorized_mobject.py +496 -228
- manim/mobject/value_tracker.py +5 -4
- manim/mobject/vector_field.py +5 -5
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +14 -8
- manim/renderer/cairo_renderer.py +20 -10
- manim/renderer/opengl_renderer.py +21 -23
- manim/renderer/opengl_renderer_window.py +2 -0
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +5 -2
- manim/renderer/vectorized_mobject_rendering.py +5 -0
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +90 -43
- manim/scene/scene_file_writer.py +316 -165
- manim/scene/section.py +17 -15
- manim/scene/three_d_scene.py +13 -21
- manim/scene/vector_space_scene.py +22 -9
- manim/typing.py +830 -70
- manim/utils/bezier.py +1667 -399
- manim/utils/caching.py +13 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +3 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +3 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +844 -309
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +90 -40
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +234 -0
- manim/utils/docbuild/autocolor_directive.py +21 -17
- manim/utils/docbuild/manim_directive.py +50 -35
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +26 -16
- manim/utils/hashing.py +9 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +14 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +57 -19
- manim/utils/opengl.py +83 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +21 -23
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +74 -39
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +125 -69
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +33 -18
- manim/utils/testing/frames_comparison.py +27 -19
- manim/utils/tex.py +127 -197
- manim/utils/tex_file_writing.py +47 -45
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim/cli/new/__init__.py +0 -0
- manim/cli/new/group.py +0 -189
- manim/plugins/import_plugins.py +0 -43
- manim-0.18.0.post0.dist-info/RECORD +0 -217
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/utils/opengl.py
CHANGED
|
@@ -1,24 +1,73 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
import numpy as np
|
|
4
6
|
import numpy.linalg as linalg
|
|
5
7
|
|
|
6
|
-
from
|
|
8
|
+
from manim._config import config
|
|
9
|
+
from manim.typing import ManimFloat
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import numpy.typing as npt
|
|
13
|
+
from typing_extensions import TypeAlias
|
|
14
|
+
|
|
15
|
+
from manim.typing import MatrixMN, Point3D
|
|
7
16
|
|
|
8
|
-
depth = 20
|
|
9
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing_extensions import TypeAlias
|
|
20
|
+
|
|
21
|
+
from manim.typing import MatrixMN
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
depth = 20
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
__all__ = [
|
|
27
|
+
"matrix_to_shader_input",
|
|
28
|
+
"orthographic_projection_matrix",
|
|
29
|
+
"perspective_projection_matrix",
|
|
30
|
+
"translation_matrix",
|
|
31
|
+
"x_rotation_matrix",
|
|
32
|
+
"y_rotation_matrix",
|
|
33
|
+
"z_rotation_matrix",
|
|
34
|
+
"rotate_in_place_matrix",
|
|
35
|
+
"rotation_matrix",
|
|
36
|
+
"scale_matrix",
|
|
37
|
+
"view_matrix",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
FlattenedMatrix4x4: TypeAlias = tuple[
|
|
41
|
+
float,
|
|
42
|
+
float,
|
|
43
|
+
float,
|
|
44
|
+
float,
|
|
45
|
+
float,
|
|
46
|
+
float,
|
|
47
|
+
float,
|
|
48
|
+
float,
|
|
49
|
+
float,
|
|
50
|
+
float,
|
|
51
|
+
float,
|
|
52
|
+
float,
|
|
53
|
+
float,
|
|
54
|
+
float,
|
|
55
|
+
float,
|
|
56
|
+
float,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def matrix_to_shader_input(matrix: MatrixMN) -> FlattenedMatrix4x4:
|
|
12
61
|
return tuple(matrix.T.ravel())
|
|
13
62
|
|
|
14
63
|
|
|
15
64
|
def orthographic_projection_matrix(
|
|
16
|
-
width=None,
|
|
17
|
-
height=None,
|
|
18
|
-
near=1,
|
|
19
|
-
far=depth + 1,
|
|
20
|
-
|
|
21
|
-
):
|
|
65
|
+
width: float | None = None,
|
|
66
|
+
height: float | None = None,
|
|
67
|
+
near: float = 1,
|
|
68
|
+
far: float = depth + 1,
|
|
69
|
+
format_: bool = True,
|
|
70
|
+
) -> MatrixMN | FlattenedMatrix4x4:
|
|
22
71
|
if width is None:
|
|
23
72
|
width = config["frame_width"]
|
|
24
73
|
if height is None:
|
|
@@ -31,13 +80,19 @@ def orthographic_projection_matrix(
|
|
|
31
80
|
[0, 0, 0, 1],
|
|
32
81
|
],
|
|
33
82
|
)
|
|
34
|
-
if
|
|
83
|
+
if format_:
|
|
35
84
|
return matrix_to_shader_input(projection_matrix)
|
|
36
85
|
else:
|
|
37
86
|
return projection_matrix
|
|
38
87
|
|
|
39
88
|
|
|
40
|
-
def perspective_projection_matrix(
|
|
89
|
+
def perspective_projection_matrix(
|
|
90
|
+
width: float | None = None,
|
|
91
|
+
height: float | None = None,
|
|
92
|
+
near: float = 2,
|
|
93
|
+
far: float = 50,
|
|
94
|
+
format_: bool = True,
|
|
95
|
+
) -> MatrixMN | FlattenedMatrix4x4:
|
|
41
96
|
if width is None:
|
|
42
97
|
width = config["frame_width"] / 6
|
|
43
98
|
if height is None:
|
|
@@ -50,13 +105,13 @@ def perspective_projection_matrix(width=None, height=None, near=2, far=50, forma
|
|
|
50
105
|
[0, 0, -1, 0],
|
|
51
106
|
],
|
|
52
107
|
)
|
|
53
|
-
if
|
|
108
|
+
if format_:
|
|
54
109
|
return matrix_to_shader_input(projection_matrix)
|
|
55
110
|
else:
|
|
56
111
|
return projection_matrix
|
|
57
112
|
|
|
58
113
|
|
|
59
|
-
def translation_matrix(x=0, y=0, z=0):
|
|
114
|
+
def translation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
|
|
60
115
|
return np.array(
|
|
61
116
|
[
|
|
62
117
|
[1, 0, 0, x],
|
|
@@ -64,10 +119,11 @@ def translation_matrix(x=0, y=0, z=0):
|
|
|
64
119
|
[0, 0, 1, z],
|
|
65
120
|
[0, 0, 0, 1],
|
|
66
121
|
],
|
|
122
|
+
dtype=ManimFloat,
|
|
67
123
|
)
|
|
68
124
|
|
|
69
125
|
|
|
70
|
-
def x_rotation_matrix(x=0):
|
|
126
|
+
def x_rotation_matrix(x: float = 0) -> MatrixMN:
|
|
71
127
|
return np.array(
|
|
72
128
|
[
|
|
73
129
|
[1, 0, 0, 0],
|
|
@@ -78,7 +134,7 @@ def x_rotation_matrix(x=0):
|
|
|
78
134
|
)
|
|
79
135
|
|
|
80
136
|
|
|
81
|
-
def y_rotation_matrix(y=0):
|
|
137
|
+
def y_rotation_matrix(y: float = 0) -> MatrixMN:
|
|
82
138
|
return np.array(
|
|
83
139
|
[
|
|
84
140
|
[np.cos(y), 0, np.sin(y), 0],
|
|
@@ -89,7 +145,7 @@ def y_rotation_matrix(y=0):
|
|
|
89
145
|
)
|
|
90
146
|
|
|
91
147
|
|
|
92
|
-
def z_rotation_matrix(z=0):
|
|
148
|
+
def z_rotation_matrix(z: float = 0) -> MatrixMN:
|
|
93
149
|
return np.array(
|
|
94
150
|
[
|
|
95
151
|
[np.cos(z), -np.sin(z), 0, 0],
|
|
@@ -101,7 +157,9 @@ def z_rotation_matrix(z=0):
|
|
|
101
157
|
|
|
102
158
|
|
|
103
159
|
# TODO: When rotating around the x axis, rotation eventually stops.
|
|
104
|
-
def rotate_in_place_matrix(
|
|
160
|
+
def rotate_in_place_matrix(
|
|
161
|
+
initial_position: Point3D, x: float = 0, y: float = 0, z: float = 0
|
|
162
|
+
) -> MatrixMN:
|
|
105
163
|
return np.matmul(
|
|
106
164
|
translation_matrix(*-initial_position),
|
|
107
165
|
np.matmul(
|
|
@@ -111,14 +169,14 @@ def rotate_in_place_matrix(initial_position, x=0, y=0, z=0):
|
|
|
111
169
|
)
|
|
112
170
|
|
|
113
171
|
|
|
114
|
-
def rotation_matrix(x=0, y=0, z=0):
|
|
172
|
+
def rotation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
|
|
115
173
|
return np.matmul(
|
|
116
174
|
np.matmul(x_rotation_matrix(x), y_rotation_matrix(y)),
|
|
117
175
|
z_rotation_matrix(z),
|
|
118
176
|
)
|
|
119
177
|
|
|
120
178
|
|
|
121
|
-
def scale_matrix(scale_factor=1):
|
|
179
|
+
def scale_matrix(scale_factor: float = 1) -> npt.NDArray:
|
|
122
180
|
return np.array(
|
|
123
181
|
[
|
|
124
182
|
[scale_factor, 0, 0, 0],
|
|
@@ -126,15 +184,16 @@ def scale_matrix(scale_factor=1):
|
|
|
126
184
|
[0, 0, scale_factor, 0],
|
|
127
185
|
[0, 0, 0, 1],
|
|
128
186
|
],
|
|
187
|
+
dtype=ManimFloat,
|
|
129
188
|
)
|
|
130
189
|
|
|
131
190
|
|
|
132
191
|
def view_matrix(
|
|
133
|
-
translation=None,
|
|
134
|
-
x_rotation=0,
|
|
135
|
-
y_rotation=0,
|
|
136
|
-
z_rotation=0,
|
|
137
|
-
):
|
|
192
|
+
translation: Point3D | None = None,
|
|
193
|
+
x_rotation: float = 0,
|
|
194
|
+
y_rotation: float = 0,
|
|
195
|
+
z_rotation: float = 0,
|
|
196
|
+
) -> MatrixMN:
|
|
138
197
|
if translation is None:
|
|
139
198
|
translation = np.array([0, 0, depth / 2 + 1])
|
|
140
199
|
model_matrix = np.matmul(
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from types import GeneratorType
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def flatten_iterable_parameters(
|
|
11
|
+
args: Iterable[T | Iterable[T] | GeneratorType],
|
|
12
|
+
) -> list[T]:
|
|
13
|
+
"""Flattens an iterable of parameters into a list of parameters.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
args
|
|
18
|
+
The iterable of parameters to flatten.
|
|
19
|
+
[(generator), [], (), ...]
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
:class:`list`
|
|
24
|
+
The flattened list of parameters.
|
|
25
|
+
"""
|
|
26
|
+
flattened_parameters: list[T] = []
|
|
27
|
+
for arg in args:
|
|
28
|
+
if isinstance(arg, (Iterable, GeneratorType)):
|
|
29
|
+
flattened_parameters.extend(arg)
|
|
30
|
+
else:
|
|
31
|
+
flattened_parameters.append(arg)
|
|
32
|
+
return flattened_parameters
|
manim/utils/paths.py
CHANGED
|
@@ -10,28 +10,22 @@ __all__ = [
|
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
17
|
from ..constants import OUT
|
|
18
18
|
from ..utils.bezier import interpolate
|
|
19
|
-
from ..utils.deprecation import deprecated_params
|
|
20
19
|
from ..utils.space_ops import rotation_matrix
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from manim.typing import PathFuncType, Point3D_Array, Vector3D
|
|
23
|
+
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
STRAIGHT_PATH_THRESHOLD = 0.01
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
@deprecated_params(
|
|
29
|
-
params="start_points, end_points, alpha",
|
|
30
|
-
since="v0.14",
|
|
31
|
-
until="v0.15",
|
|
32
|
-
message="Straight path is now returning interpolating function to make it consistent with other path functions. Use straight_path()(a,b,c) instead of straight_path(a,b,c).",
|
|
33
|
-
)
|
|
34
|
-
def straight_path(*args) -> PATH_FUNC_TYPE:
|
|
28
|
+
def straight_path() -> PathFuncType:
|
|
35
29
|
"""Simplest path function. Each point in a set goes in a straight path toward its destination.
|
|
36
30
|
|
|
37
31
|
Examples
|
|
@@ -74,14 +68,12 @@ def straight_path(*args) -> PATH_FUNC_TYPE:
|
|
|
74
68
|
self.wait()
|
|
75
69
|
|
|
76
70
|
"""
|
|
77
|
-
if len(args) > 0:
|
|
78
|
-
return interpolate(*args)
|
|
79
71
|
return interpolate
|
|
80
72
|
|
|
81
73
|
|
|
82
74
|
def path_along_circles(
|
|
83
|
-
arc_angle: float, circles_centers: np.ndarray, axis:
|
|
84
|
-
) ->
|
|
75
|
+
arc_angle: float, circles_centers: np.ndarray, axis: Vector3D = OUT
|
|
76
|
+
) -> PathFuncType:
|
|
85
77
|
"""This function transforms each point by moving it roughly along a circle, each with its own specified center.
|
|
86
78
|
|
|
87
79
|
The path may be seen as each point smoothly changing its orbit from its starting position to its destination.
|
|
@@ -144,7 +136,9 @@ def path_along_circles(
|
|
|
144
136
|
axis = OUT
|
|
145
137
|
unit_axis = axis / np.linalg.norm(axis)
|
|
146
138
|
|
|
147
|
-
def path(
|
|
139
|
+
def path(
|
|
140
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
141
|
+
) -> Point3D_Array:
|
|
148
142
|
detransformed_end_points = circles_centers + np.dot(
|
|
149
143
|
end_points - circles_centers, rotation_matrix(-arc_angle, unit_axis).T
|
|
150
144
|
)
|
|
@@ -158,7 +152,7 @@ def path_along_circles(
|
|
|
158
152
|
return path
|
|
159
153
|
|
|
160
154
|
|
|
161
|
-
def path_along_arc(arc_angle: float, axis:
|
|
155
|
+
def path_along_arc(arc_angle: float, axis: Vector3D = OUT) -> PathFuncType:
|
|
162
156
|
"""This function transforms each point by moving it along a circular arc.
|
|
163
157
|
|
|
164
158
|
Parameters
|
|
@@ -214,7 +208,9 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
214
208
|
axis = OUT
|
|
215
209
|
unit_axis = axis / np.linalg.norm(axis)
|
|
216
210
|
|
|
217
|
-
def path(
|
|
211
|
+
def path(
|
|
212
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
213
|
+
) -> Point3D_Array:
|
|
218
214
|
vects = end_points - start_points
|
|
219
215
|
centers = start_points + 0.5 * vects
|
|
220
216
|
if arc_angle != np.pi:
|
|
@@ -225,7 +221,7 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
225
221
|
return path
|
|
226
222
|
|
|
227
223
|
|
|
228
|
-
def clockwise_path() ->
|
|
224
|
+
def clockwise_path() -> PathFuncType:
|
|
229
225
|
"""This function transforms each point by moving clockwise around a half circle.
|
|
230
226
|
|
|
231
227
|
Examples
|
|
@@ -271,7 +267,7 @@ def clockwise_path() -> PATH_FUNC_TYPE:
|
|
|
271
267
|
return path_along_arc(-np.pi)
|
|
272
268
|
|
|
273
269
|
|
|
274
|
-
def counterclockwise_path() ->
|
|
270
|
+
def counterclockwise_path() -> PathFuncType:
|
|
275
271
|
"""This function transforms each point by moving counterclockwise around a half circle.
|
|
276
272
|
|
|
277
273
|
Examples
|
|
@@ -317,7 +313,7 @@ def counterclockwise_path() -> PATH_FUNC_TYPE:
|
|
|
317
313
|
return path_along_arc(np.pi)
|
|
318
314
|
|
|
319
315
|
|
|
320
|
-
def spiral_path(angle: float, axis:
|
|
316
|
+
def spiral_path(angle: float, axis: Vector3D = OUT) -> PathFuncType:
|
|
321
317
|
"""This function transforms each point by moving along a spiral to its destination.
|
|
322
318
|
|
|
323
319
|
Parameters
|
|
@@ -373,7 +369,9 @@ def spiral_path(angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
373
369
|
axis = OUT
|
|
374
370
|
unit_axis = axis / np.linalg.norm(axis)
|
|
375
371
|
|
|
376
|
-
def path(
|
|
372
|
+
def path(
|
|
373
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
374
|
+
) -> Point3D_Array:
|
|
377
375
|
rot_matrix = rotation_matrix((alpha - 1) * angle, unit_axis)
|
|
378
376
|
return start_points + alpha * np.dot(end_points - start_points, rot_matrix.T)
|
|
379
377
|
|
manim/utils/polylabel.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from queue import PriorityQueue
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
from manim.typing import (
|
|
13
|
+
Point2D,
|
|
14
|
+
Point2D_Array,
|
|
15
|
+
Point2DLike,
|
|
16
|
+
Point2DLike_Array,
|
|
17
|
+
Point3DLike_Array,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Polygon:
|
|
22
|
+
"""
|
|
23
|
+
Initializes the Polygon with the given rings.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
rings
|
|
28
|
+
A collection of closed polygonal ring.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, rings: Sequence[Point2DLike_Array]) -> None:
|
|
32
|
+
np_rings: list[Point2D_Array] = [np.asarray(ring) for ring in rings]
|
|
33
|
+
# Flatten Array
|
|
34
|
+
csum = np.cumsum([ring.shape[0] for ring in np_rings])
|
|
35
|
+
self.array: Point2D_Array = np.concatenate(np_rings, axis=0)
|
|
36
|
+
|
|
37
|
+
# Compute Boundary
|
|
38
|
+
self.start: Point2D_Array = np.delete(self.array, csum - 1, axis=0)
|
|
39
|
+
self.stop: Point2D_Array = np.delete(self.array, csum % csum[-1], axis=0)
|
|
40
|
+
self.diff: Point2D_Array = np.delete(
|
|
41
|
+
np.diff(self.array, axis=0), csum[:-1] - 1, axis=0
|
|
42
|
+
)
|
|
43
|
+
self.norm: Point2D_Array = self.diff / np.einsum(
|
|
44
|
+
"ij,ij->i", self.diff, self.diff
|
|
45
|
+
).reshape(-1, 1)
|
|
46
|
+
|
|
47
|
+
# Compute Centroid
|
|
48
|
+
x, y = self.start[:, 0], self.start[:, 1]
|
|
49
|
+
xr, yr = self.stop[:, 0], self.stop[:, 1]
|
|
50
|
+
self.area: float = 0.5 * (np.dot(x, yr) - np.dot(xr, y))
|
|
51
|
+
if self.area:
|
|
52
|
+
factor = x * yr - xr * y
|
|
53
|
+
cx = np.sum((x + xr) * factor) / (6.0 * self.area)
|
|
54
|
+
cy = np.sum((y + yr) * factor) / (6.0 * self.area)
|
|
55
|
+
self.centroid = np.array([cx, cy])
|
|
56
|
+
|
|
57
|
+
def compute_distance(self, point: Point2DLike) -> float:
|
|
58
|
+
"""Compute the minimum distance from a point to the polygon."""
|
|
59
|
+
scalars = np.einsum("ij,ij->i", self.norm, point - self.start)
|
|
60
|
+
clips = np.clip(scalars, 0, 1).reshape(-1, 1)
|
|
61
|
+
d: float = np.min(
|
|
62
|
+
np.linalg.norm(self.start + self.diff * clips - point, axis=1)
|
|
63
|
+
)
|
|
64
|
+
return d if self.inside(point) else -d
|
|
65
|
+
|
|
66
|
+
def inside(self, point: Point2DLike) -> bool:
|
|
67
|
+
"""Check if a point is inside the polygon."""
|
|
68
|
+
# Views
|
|
69
|
+
px, py = point
|
|
70
|
+
x, y = self.start[:, 0], self.start[:, 1]
|
|
71
|
+
xr, yr = self.stop[:, 0], self.stop[:, 1]
|
|
72
|
+
|
|
73
|
+
# Count Crossings (enforce short-circuit)
|
|
74
|
+
c = (y > py) != (yr > py)
|
|
75
|
+
c = px < x[c] + (py - y[c]) * (xr[c] - x[c]) / (yr[c] - y[c])
|
|
76
|
+
c_sum: int = np.sum(c)
|
|
77
|
+
return c_sum % 2 == 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Cell:
|
|
81
|
+
"""
|
|
82
|
+
A square in a mesh covering the :class:`~.Polygon` passed as an argument.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
c
|
|
87
|
+
Center coordinates of the Cell.
|
|
88
|
+
h
|
|
89
|
+
Half-Size of the Cell.
|
|
90
|
+
polygon
|
|
91
|
+
:class:`~.Polygon` object for which the distance is computed.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, c: Point2DLike, h: float, polygon: Polygon) -> None:
|
|
95
|
+
self.c: Point2D = np.asarray(c)
|
|
96
|
+
self.h = h
|
|
97
|
+
self.d = polygon.compute_distance(self.c)
|
|
98
|
+
self.p = self.d + self.h * np.sqrt(2)
|
|
99
|
+
|
|
100
|
+
def __lt__(self, other: Cell) -> bool:
|
|
101
|
+
return self.d < other.d
|
|
102
|
+
|
|
103
|
+
def __gt__(self, other: Cell) -> bool:
|
|
104
|
+
return self.d > other.d
|
|
105
|
+
|
|
106
|
+
def __le__(self, other: Cell) -> bool:
|
|
107
|
+
return self.d <= other.d
|
|
108
|
+
|
|
109
|
+
def __ge__(self, other: Cell) -> bool:
|
|
110
|
+
return self.d >= other.d
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def polylabel(rings: Sequence[Point3DLike_Array], precision: float = 0.01) -> Cell:
|
|
114
|
+
"""
|
|
115
|
+
Finds the pole of inaccessibility (the point that is farthest from the edges of the polygon)
|
|
116
|
+
using an iterative grid-based approach.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
rings
|
|
121
|
+
A list of lists, where each list is a sequence of points representing the rings of the polygon.
|
|
122
|
+
Typically, multiple rings indicate holes in the polygon.
|
|
123
|
+
precision
|
|
124
|
+
The precision of the result (default is 0.01).
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
Cell
|
|
129
|
+
A Cell containing the pole of inaccessibility to a given precision.
|
|
130
|
+
"""
|
|
131
|
+
# Precompute Polygon Data
|
|
132
|
+
np_rings: list[Point2D_Array] = [np.asarray(ring)[:, :2] for ring in rings]
|
|
133
|
+
polygon = Polygon(np_rings)
|
|
134
|
+
|
|
135
|
+
# Bounding Box
|
|
136
|
+
mins = np.min(polygon.array, axis=0)
|
|
137
|
+
maxs = np.max(polygon.array, axis=0)
|
|
138
|
+
dims = maxs - mins
|
|
139
|
+
s = np.min(dims)
|
|
140
|
+
h = s / 2.0
|
|
141
|
+
|
|
142
|
+
# Initial Grid
|
|
143
|
+
queue: PriorityQueue[Cell] = PriorityQueue()
|
|
144
|
+
xv, yv = np.meshgrid(np.arange(mins[0], maxs[0], s), np.arange(mins[1], maxs[1], s))
|
|
145
|
+
for corner in np.vstack([xv.ravel(), yv.ravel()]).T:
|
|
146
|
+
queue.put(Cell(corner + h, h, polygon))
|
|
147
|
+
|
|
148
|
+
# Initial Guess
|
|
149
|
+
best = Cell(polygon.centroid, 0, polygon)
|
|
150
|
+
bbox = Cell(mins + (dims / 2), 0, polygon)
|
|
151
|
+
if bbox.d > best.d:
|
|
152
|
+
best = bbox
|
|
153
|
+
|
|
154
|
+
# While there are cells to consider...
|
|
155
|
+
directions = np.array([[-1, -1], [1, -1], [-1, 1], [1, 1]])
|
|
156
|
+
while not queue.empty():
|
|
157
|
+
cell = queue.get()
|
|
158
|
+
if cell > best:
|
|
159
|
+
best = cell
|
|
160
|
+
# If a cell is promising, subdivide!
|
|
161
|
+
if cell.p - best.d > precision:
|
|
162
|
+
h = cell.h / 2.0
|
|
163
|
+
offsets = cell.c + directions * h
|
|
164
|
+
queue.put(Cell(offsets[0], h, polygon))
|
|
165
|
+
queue.put(Cell(offsets[1], h, polygon))
|
|
166
|
+
queue.put(Cell(offsets[2], h, polygon))
|
|
167
|
+
queue.put(Cell(offsets[3], h, polygon))
|
|
168
|
+
return best
|