manim 0.17.0__py3-none-any.whl → 0.19.1__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.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- 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 +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- 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 +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- 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 +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
manim/utils/opengl.py
CHANGED
|
@@ -1,24 +1,67 @@
|
|
|
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
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import numpy.typing as npt
|
|
13
|
+
from typing_extensions import TypeAlias
|
|
14
|
+
|
|
15
|
+
from manim.typing import MatrixMN, Point3D
|
|
9
16
|
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
depth = 20
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"matrix_to_shader_input",
|
|
22
|
+
"orthographic_projection_matrix",
|
|
23
|
+
"perspective_projection_matrix",
|
|
24
|
+
"translation_matrix",
|
|
25
|
+
"x_rotation_matrix",
|
|
26
|
+
"y_rotation_matrix",
|
|
27
|
+
"z_rotation_matrix",
|
|
28
|
+
"rotate_in_place_matrix",
|
|
29
|
+
"rotation_matrix",
|
|
30
|
+
"scale_matrix",
|
|
31
|
+
"view_matrix",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
FlattenedMatrix4x4: TypeAlias = tuple[
|
|
35
|
+
float,
|
|
36
|
+
float,
|
|
37
|
+
float,
|
|
38
|
+
float,
|
|
39
|
+
float,
|
|
40
|
+
float,
|
|
41
|
+
float,
|
|
42
|
+
float,
|
|
43
|
+
float,
|
|
44
|
+
float,
|
|
45
|
+
float,
|
|
46
|
+
float,
|
|
47
|
+
float,
|
|
48
|
+
float,
|
|
49
|
+
float,
|
|
50
|
+
float,
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def matrix_to_shader_input(matrix: MatrixMN) -> FlattenedMatrix4x4:
|
|
12
55
|
return tuple(matrix.T.ravel())
|
|
13
56
|
|
|
14
57
|
|
|
15
58
|
def orthographic_projection_matrix(
|
|
16
|
-
width=None,
|
|
17
|
-
height=None,
|
|
18
|
-
near=1,
|
|
19
|
-
far=depth + 1,
|
|
20
|
-
|
|
21
|
-
):
|
|
59
|
+
width: float | None = None,
|
|
60
|
+
height: float | None = None,
|
|
61
|
+
near: float = 1,
|
|
62
|
+
far: float = depth + 1,
|
|
63
|
+
format_: bool = True,
|
|
64
|
+
) -> MatrixMN | FlattenedMatrix4x4:
|
|
22
65
|
if width is None:
|
|
23
66
|
width = config["frame_width"]
|
|
24
67
|
if height is None:
|
|
@@ -31,13 +74,19 @@ def orthographic_projection_matrix(
|
|
|
31
74
|
[0, 0, 0, 1],
|
|
32
75
|
],
|
|
33
76
|
)
|
|
34
|
-
if
|
|
77
|
+
if format_:
|
|
35
78
|
return matrix_to_shader_input(projection_matrix)
|
|
36
79
|
else:
|
|
37
80
|
return projection_matrix
|
|
38
81
|
|
|
39
82
|
|
|
40
|
-
def perspective_projection_matrix(
|
|
83
|
+
def perspective_projection_matrix(
|
|
84
|
+
width: float | None = None,
|
|
85
|
+
height: float | None = None,
|
|
86
|
+
near: float = 2,
|
|
87
|
+
far: float = 50,
|
|
88
|
+
format_: bool = True,
|
|
89
|
+
) -> MatrixMN | FlattenedMatrix4x4:
|
|
41
90
|
if width is None:
|
|
42
91
|
width = config["frame_width"] / 6
|
|
43
92
|
if height is None:
|
|
@@ -50,13 +99,13 @@ def perspective_projection_matrix(width=None, height=None, near=2, far=50, forma
|
|
|
50
99
|
[0, 0, -1, 0],
|
|
51
100
|
],
|
|
52
101
|
)
|
|
53
|
-
if
|
|
102
|
+
if format_:
|
|
54
103
|
return matrix_to_shader_input(projection_matrix)
|
|
55
104
|
else:
|
|
56
105
|
return projection_matrix
|
|
57
106
|
|
|
58
107
|
|
|
59
|
-
def translation_matrix(x=0, y=0, z=0):
|
|
108
|
+
def translation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
|
|
60
109
|
return np.array(
|
|
61
110
|
[
|
|
62
111
|
[1, 0, 0, x],
|
|
@@ -64,10 +113,11 @@ def translation_matrix(x=0, y=0, z=0):
|
|
|
64
113
|
[0, 0, 1, z],
|
|
65
114
|
[0, 0, 0, 1],
|
|
66
115
|
],
|
|
116
|
+
dtype=ManimFloat,
|
|
67
117
|
)
|
|
68
118
|
|
|
69
119
|
|
|
70
|
-
def x_rotation_matrix(x=0):
|
|
120
|
+
def x_rotation_matrix(x: float = 0) -> MatrixMN:
|
|
71
121
|
return np.array(
|
|
72
122
|
[
|
|
73
123
|
[1, 0, 0, 0],
|
|
@@ -78,7 +128,7 @@ def x_rotation_matrix(x=0):
|
|
|
78
128
|
)
|
|
79
129
|
|
|
80
130
|
|
|
81
|
-
def y_rotation_matrix(y=0):
|
|
131
|
+
def y_rotation_matrix(y: float = 0) -> MatrixMN:
|
|
82
132
|
return np.array(
|
|
83
133
|
[
|
|
84
134
|
[np.cos(y), 0, np.sin(y), 0],
|
|
@@ -89,7 +139,7 @@ def y_rotation_matrix(y=0):
|
|
|
89
139
|
)
|
|
90
140
|
|
|
91
141
|
|
|
92
|
-
def z_rotation_matrix(z=0):
|
|
142
|
+
def z_rotation_matrix(z: float = 0) -> MatrixMN:
|
|
93
143
|
return np.array(
|
|
94
144
|
[
|
|
95
145
|
[np.cos(z), -np.sin(z), 0, 0],
|
|
@@ -101,7 +151,9 @@ def z_rotation_matrix(z=0):
|
|
|
101
151
|
|
|
102
152
|
|
|
103
153
|
# TODO: When rotating around the x axis, rotation eventually stops.
|
|
104
|
-
def rotate_in_place_matrix(
|
|
154
|
+
def rotate_in_place_matrix(
|
|
155
|
+
initial_position: Point3D, x: float = 0, y: float = 0, z: float = 0
|
|
156
|
+
) -> MatrixMN:
|
|
105
157
|
return np.matmul(
|
|
106
158
|
translation_matrix(*-initial_position),
|
|
107
159
|
np.matmul(
|
|
@@ -111,14 +163,14 @@ def rotate_in_place_matrix(initial_position, x=0, y=0, z=0):
|
|
|
111
163
|
)
|
|
112
164
|
|
|
113
165
|
|
|
114
|
-
def rotation_matrix(x=0, y=0, z=0):
|
|
166
|
+
def rotation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
|
|
115
167
|
return np.matmul(
|
|
116
168
|
np.matmul(x_rotation_matrix(x), y_rotation_matrix(y)),
|
|
117
169
|
z_rotation_matrix(z),
|
|
118
170
|
)
|
|
119
171
|
|
|
120
172
|
|
|
121
|
-
def scale_matrix(scale_factor=1):
|
|
173
|
+
def scale_matrix(scale_factor: float = 1) -> npt.NDArray:
|
|
122
174
|
return np.array(
|
|
123
175
|
[
|
|
124
176
|
[scale_factor, 0, 0, 0],
|
|
@@ -126,15 +178,16 @@ def scale_matrix(scale_factor=1):
|
|
|
126
178
|
[0, 0, scale_factor, 0],
|
|
127
179
|
[0, 0, 0, 1],
|
|
128
180
|
],
|
|
181
|
+
dtype=ManimFloat,
|
|
129
182
|
)
|
|
130
183
|
|
|
131
184
|
|
|
132
185
|
def view_matrix(
|
|
133
|
-
translation=None,
|
|
134
|
-
x_rotation=0,
|
|
135
|
-
y_rotation=0,
|
|
136
|
-
z_rotation=0,
|
|
137
|
-
):
|
|
186
|
+
translation: Point3D | None = None,
|
|
187
|
+
x_rotation: float = 0,
|
|
188
|
+
y_rotation: float = 0,
|
|
189
|
+
z_rotation: float = 0,
|
|
190
|
+
) -> MatrixMN:
|
|
138
191
|
if translation is None:
|
|
139
192
|
translation = np.array([0, 0, depth / 2 + 1])
|
|
140
193
|
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,27 @@ __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.
|
|
20
|
-
|
|
19
|
+
from ..utils.space_ops import normalize, rotation_matrix
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from manim.typing import (
|
|
23
|
+
PathFuncType,
|
|
24
|
+
Point3D_Array,
|
|
25
|
+
Point3DLike_Array,
|
|
26
|
+
Vector3DLike,
|
|
27
|
+
)
|
|
21
28
|
|
|
22
|
-
STRAIGHT_PATH_THRESHOLD = 0.01
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
STRAIGHT_PATH_THRESHOLD = 0.01
|
|
25
31
|
|
|
26
32
|
|
|
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:
|
|
33
|
+
def straight_path() -> PathFuncType:
|
|
35
34
|
"""Simplest path function. Each point in a set goes in a straight path toward its destination.
|
|
36
35
|
|
|
37
36
|
Examples
|
|
@@ -74,14 +73,12 @@ def straight_path(*args) -> PATH_FUNC_TYPE:
|
|
|
74
73
|
self.wait()
|
|
75
74
|
|
|
76
75
|
"""
|
|
77
|
-
if len(args) > 0:
|
|
78
|
-
return interpolate(*args)
|
|
79
76
|
return interpolate
|
|
80
77
|
|
|
81
78
|
|
|
82
79
|
def path_along_circles(
|
|
83
|
-
arc_angle: float, circles_centers:
|
|
84
|
-
) ->
|
|
80
|
+
arc_angle: float, circles_centers: Point3DLike_Array, axis: Vector3DLike = OUT
|
|
81
|
+
) -> PathFuncType:
|
|
85
82
|
"""This function transforms each point by moving it roughly along a circle, each with its own specified center.
|
|
86
83
|
|
|
87
84
|
The path may be seen as each point smoothly changing its orbit from its starting position to its destination.
|
|
@@ -140,11 +137,11 @@ def path_along_circles(
|
|
|
140
137
|
self.wait()
|
|
141
138
|
|
|
142
139
|
"""
|
|
143
|
-
|
|
144
|
-
axis = OUT
|
|
145
|
-
unit_axis = axis / np.linalg.norm(axis)
|
|
140
|
+
unit_axis = normalize(axis, fall_back=OUT)
|
|
146
141
|
|
|
147
|
-
def path(
|
|
142
|
+
def path(
|
|
143
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
144
|
+
) -> Point3D_Array:
|
|
148
145
|
detransformed_end_points = circles_centers + np.dot(
|
|
149
146
|
end_points - circles_centers, rotation_matrix(-arc_angle, unit_axis).T
|
|
150
147
|
)
|
|
@@ -158,7 +155,7 @@ def path_along_circles(
|
|
|
158
155
|
return path
|
|
159
156
|
|
|
160
157
|
|
|
161
|
-
def path_along_arc(arc_angle: float, axis:
|
|
158
|
+
def path_along_arc(arc_angle: float, axis: Vector3DLike = OUT) -> PathFuncType:
|
|
162
159
|
"""This function transforms each point by moving it along a circular arc.
|
|
163
160
|
|
|
164
161
|
Parameters
|
|
@@ -210,11 +207,11 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
210
207
|
"""
|
|
211
208
|
if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
|
|
212
209
|
return straight_path()
|
|
213
|
-
|
|
214
|
-
axis = OUT
|
|
215
|
-
unit_axis = axis / np.linalg.norm(axis)
|
|
210
|
+
unit_axis = normalize(axis, fall_back=OUT)
|
|
216
211
|
|
|
217
|
-
def path(
|
|
212
|
+
def path(
|
|
213
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
214
|
+
) -> Point3D_Array:
|
|
218
215
|
vects = end_points - start_points
|
|
219
216
|
centers = start_points + 0.5 * vects
|
|
220
217
|
if arc_angle != np.pi:
|
|
@@ -225,7 +222,7 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
225
222
|
return path
|
|
226
223
|
|
|
227
224
|
|
|
228
|
-
def clockwise_path() ->
|
|
225
|
+
def clockwise_path() -> PathFuncType:
|
|
229
226
|
"""This function transforms each point by moving clockwise around a half circle.
|
|
230
227
|
|
|
231
228
|
Examples
|
|
@@ -271,7 +268,7 @@ def clockwise_path() -> PATH_FUNC_TYPE:
|
|
|
271
268
|
return path_along_arc(-np.pi)
|
|
272
269
|
|
|
273
270
|
|
|
274
|
-
def counterclockwise_path() ->
|
|
271
|
+
def counterclockwise_path() -> PathFuncType:
|
|
275
272
|
"""This function transforms each point by moving counterclockwise around a half circle.
|
|
276
273
|
|
|
277
274
|
Examples
|
|
@@ -317,7 +314,7 @@ def counterclockwise_path() -> PATH_FUNC_TYPE:
|
|
|
317
314
|
return path_along_arc(np.pi)
|
|
318
315
|
|
|
319
316
|
|
|
320
|
-
def spiral_path(angle: float, axis:
|
|
317
|
+
def spiral_path(angle: float, axis: Vector3DLike = OUT) -> PathFuncType:
|
|
321
318
|
"""This function transforms each point by moving along a spiral to its destination.
|
|
322
319
|
|
|
323
320
|
Parameters
|
|
@@ -369,11 +366,11 @@ def spiral_path(angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
|
|
|
369
366
|
"""
|
|
370
367
|
if abs(angle) < STRAIGHT_PATH_THRESHOLD:
|
|
371
368
|
return straight_path()
|
|
372
|
-
|
|
373
|
-
axis = OUT
|
|
374
|
-
unit_axis = axis / np.linalg.norm(axis)
|
|
369
|
+
unit_axis = normalize(axis, fall_back=OUT)
|
|
375
370
|
|
|
376
|
-
def path(
|
|
371
|
+
def path(
|
|
372
|
+
start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
|
|
373
|
+
) -> Point3D_Array:
|
|
377
374
|
rot_matrix = rotation_matrix((alpha - 1) * angle, unit_axis)
|
|
378
375
|
return start_points + alpha * np.dot(end_points - start_points, rot_matrix.T)
|
|
379
376
|
|
manim/utils/polylabel.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
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 sequence of points, where each sequence represents the rings of the polygon.
|
|
29
|
+
Typically, multiple rings indicate holes in the polygon.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, rings: Sequence[Point2DLike_Array]) -> None:
|
|
33
|
+
np_rings: list[Point2D_Array] = [np.asarray(ring) for ring in rings]
|
|
34
|
+
# Flatten Array
|
|
35
|
+
csum = np.cumsum([ring.shape[0] for ring in np_rings])
|
|
36
|
+
self.array: Point2D_Array = np.concatenate(np_rings, axis=0)
|
|
37
|
+
|
|
38
|
+
# Compute Boundary
|
|
39
|
+
self.start: Point2D_Array = np.delete(self.array, csum - 1, axis=0)
|
|
40
|
+
self.stop: Point2D_Array = np.delete(self.array, csum % csum[-1], axis=0)
|
|
41
|
+
self.diff: Point2D_Array = np.delete(
|
|
42
|
+
np.diff(self.array, axis=0), csum[:-1] - 1, axis=0
|
|
43
|
+
)
|
|
44
|
+
self.norm: Point2D_Array = self.diff / np.einsum(
|
|
45
|
+
"ij,ij->i", self.diff, self.diff
|
|
46
|
+
).reshape(-1, 1)
|
|
47
|
+
|
|
48
|
+
# Compute Centroid
|
|
49
|
+
x, y = self.start[:, 0], self.start[:, 1]
|
|
50
|
+
xr, yr = self.stop[:, 0], self.stop[:, 1]
|
|
51
|
+
self.area: float = 0.5 * (np.dot(x, yr) - np.dot(xr, y))
|
|
52
|
+
if self.area:
|
|
53
|
+
factor = x * yr - xr * y
|
|
54
|
+
cx = np.sum((x + xr) * factor) / (6.0 * self.area)
|
|
55
|
+
cy = np.sum((y + yr) * factor) / (6.0 * self.area)
|
|
56
|
+
self.centroid = np.array([cx, cy])
|
|
57
|
+
|
|
58
|
+
def compute_distance(self, point: Point2DLike) -> float:
|
|
59
|
+
"""Compute the minimum distance from a point to the polygon."""
|
|
60
|
+
scalars = np.einsum("ij,ij->i", self.norm, point - self.start)
|
|
61
|
+
clips = np.clip(scalars, 0, 1).reshape(-1, 1)
|
|
62
|
+
d: float = np.min(
|
|
63
|
+
np.linalg.norm(self.start + self.diff * clips - point, axis=1)
|
|
64
|
+
)
|
|
65
|
+
return d if self.inside(point) else -d
|
|
66
|
+
|
|
67
|
+
def _is_point_on_segment(
|
|
68
|
+
self,
|
|
69
|
+
x_point: float,
|
|
70
|
+
y_point: float,
|
|
71
|
+
x0: float,
|
|
72
|
+
y0: float,
|
|
73
|
+
x1: float,
|
|
74
|
+
y1: float,
|
|
75
|
+
) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Check if a point is on the segment.
|
|
78
|
+
|
|
79
|
+
The segment is defined by (x0, y0) to (x1, y1).
|
|
80
|
+
"""
|
|
81
|
+
if min(x0, x1) <= x_point <= max(x0, x1) and min(y0, y1) <= y_point <= max(
|
|
82
|
+
y0, y1
|
|
83
|
+
):
|
|
84
|
+
dx = x1 - x0
|
|
85
|
+
dy = y1 - y0
|
|
86
|
+
cross = dx * (y_point - y0) - dy * (x_point - x0)
|
|
87
|
+
return bool(np.isclose(cross, 0.0))
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def _ray_crosses_segment(
|
|
91
|
+
self,
|
|
92
|
+
x_point: float,
|
|
93
|
+
y_point: float,
|
|
94
|
+
x0: float,
|
|
95
|
+
y0: float,
|
|
96
|
+
x1: float,
|
|
97
|
+
y1: float,
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Check if a horizontal ray to the right from point (x_point, y_point) crosses the segment.
|
|
101
|
+
|
|
102
|
+
The segment is defined by (x0, y0) to (x1, y1).
|
|
103
|
+
"""
|
|
104
|
+
if (y0 > y_point) != (y1 > y_point):
|
|
105
|
+
slope = (x1 - x0) / (y1 - y0)
|
|
106
|
+
x_intersect = slope * (y_point - y0) + x0
|
|
107
|
+
return bool(x_point < x_intersect)
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def inside(self, point: Point2DLike) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Check if a point is inside the polygon.
|
|
113
|
+
|
|
114
|
+
Uses ray casting algorithm and checks boundary points consistently.
|
|
115
|
+
"""
|
|
116
|
+
point_x, point_y = point
|
|
117
|
+
start_x, start_y = self.start[:, 0], self.start[:, 1]
|
|
118
|
+
stop_x, stop_y = self.stop[:, 0], self.stop[:, 1]
|
|
119
|
+
segment_count = len(start_x)
|
|
120
|
+
|
|
121
|
+
for i in range(segment_count):
|
|
122
|
+
if self._is_point_on_segment(
|
|
123
|
+
point_x,
|
|
124
|
+
point_y,
|
|
125
|
+
start_x[i],
|
|
126
|
+
start_y[i],
|
|
127
|
+
stop_x[i],
|
|
128
|
+
stop_y[i],
|
|
129
|
+
):
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
crossings = 0
|
|
133
|
+
for i in range(segment_count):
|
|
134
|
+
if self._ray_crosses_segment(
|
|
135
|
+
point_x,
|
|
136
|
+
point_y,
|
|
137
|
+
start_x[i],
|
|
138
|
+
start_y[i],
|
|
139
|
+
stop_x[i],
|
|
140
|
+
stop_y[i],
|
|
141
|
+
):
|
|
142
|
+
crossings += 1
|
|
143
|
+
|
|
144
|
+
return crossings % 2 == 1
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Cell:
|
|
148
|
+
"""
|
|
149
|
+
A square in a mesh covering the :class:`~.Polygon` passed as an argument.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
c
|
|
154
|
+
Center coordinates of the Cell.
|
|
155
|
+
h
|
|
156
|
+
Half-Size of the Cell.
|
|
157
|
+
polygon
|
|
158
|
+
:class:`~.Polygon` object for which the distance is computed.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, c: Point2DLike, h: float, polygon: Polygon) -> None:
|
|
162
|
+
self.c: Point2D = np.asarray(c)
|
|
163
|
+
self.h = h
|
|
164
|
+
self.d = polygon.compute_distance(self.c)
|
|
165
|
+
self.p = self.d + self.h * np.sqrt(2)
|
|
166
|
+
|
|
167
|
+
def __lt__(self, other: Cell) -> bool:
|
|
168
|
+
return self.d < other.d
|
|
169
|
+
|
|
170
|
+
def __gt__(self, other: Cell) -> bool:
|
|
171
|
+
return self.d > other.d
|
|
172
|
+
|
|
173
|
+
def __le__(self, other: Cell) -> bool:
|
|
174
|
+
return self.d <= other.d
|
|
175
|
+
|
|
176
|
+
def __ge__(self, other: Cell) -> bool:
|
|
177
|
+
return self.d >= other.d
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def polylabel(rings: Sequence[Point3DLike_Array], precision: float = 0.01) -> Cell:
|
|
181
|
+
"""
|
|
182
|
+
Finds the pole of inaccessibility (the point that is farthest from the edges of the polygon)
|
|
183
|
+
using an iterative grid-based approach.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
rings
|
|
188
|
+
A list of lists, where each list is a sequence of points representing the rings of the polygon.
|
|
189
|
+
Typically, multiple rings indicate holes in the polygon.
|
|
190
|
+
precision
|
|
191
|
+
The precision of the result (default is 0.01).
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
Cell
|
|
196
|
+
A Cell containing the pole of inaccessibility to a given precision.
|
|
197
|
+
"""
|
|
198
|
+
# Precompute Polygon Data
|
|
199
|
+
np_rings: list[Point2D_Array] = [np.asarray(ring)[:, :2] for ring in rings]
|
|
200
|
+
polygon = Polygon(np_rings)
|
|
201
|
+
|
|
202
|
+
# Bounding Box
|
|
203
|
+
mins = np.min(polygon.array, axis=0)
|
|
204
|
+
maxs = np.max(polygon.array, axis=0)
|
|
205
|
+
dims = maxs - mins
|
|
206
|
+
s = np.min(dims)
|
|
207
|
+
h = s / 2.0
|
|
208
|
+
|
|
209
|
+
# Initial Grid
|
|
210
|
+
queue: PriorityQueue[Cell] = PriorityQueue()
|
|
211
|
+
xv, yv = np.meshgrid(np.arange(mins[0], maxs[0], s), np.arange(mins[1], maxs[1], s))
|
|
212
|
+
for corner in np.vstack([xv.ravel(), yv.ravel()]).T:
|
|
213
|
+
queue.put(Cell(corner + h, h, polygon))
|
|
214
|
+
|
|
215
|
+
# Initial Guess
|
|
216
|
+
best = Cell(polygon.centroid, 0, polygon)
|
|
217
|
+
bbox = Cell(mins + (dims / 2), 0, polygon)
|
|
218
|
+
if bbox.d > best.d:
|
|
219
|
+
best = bbox
|
|
220
|
+
|
|
221
|
+
# While there are cells to consider...
|
|
222
|
+
directions = np.array([[-1, -1], [1, -1], [-1, 1], [1, 1]])
|
|
223
|
+
while not queue.empty():
|
|
224
|
+
cell = queue.get()
|
|
225
|
+
if cell > best:
|
|
226
|
+
best = cell
|
|
227
|
+
# If a cell is promising, subdivide!
|
|
228
|
+
if cell.p - best.d > precision:
|
|
229
|
+
h = cell.h / 2.0
|
|
230
|
+
offsets = cell.c + directions * h
|
|
231
|
+
queue.put(Cell(offsets[0], h, polygon))
|
|
232
|
+
queue.put(Cell(offsets[1], h, polygon))
|
|
233
|
+
queue.put(Cell(offsets[2], h, polygon))
|
|
234
|
+
queue.put(Cell(offsets[3], h, polygon))
|
|
235
|
+
return best
|