manim 0.17.3__py3-none-any.whl → 0.18.0.post0__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 +1 -0
- manim/__main__.py +2 -0
- manim/_config/__init__.py +0 -1
- manim/_config/logger_utils.py +1 -0
- manim/_config/utils.py +14 -5
- manim/animation/changing.py +9 -5
- manim/animation/creation.py +8 -3
- manim/animation/indication.py +4 -4
- manim/animation/speedmodifier.py +2 -4
- manim/animation/updaters/mobject_update_utils.py +134 -16
- manim/camera/camera.py +31 -17
- manim/cli/checkhealth/__init__.py +0 -0
- manim/cli/checkhealth/checks.py +173 -0
- manim/cli/checkhealth/commands.py +81 -0
- manim/cli/render/global_options.py +6 -0
- manim/constants.py +58 -54
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +126 -91
- manim/mobject/geometry/boolean_ops.py +6 -10
- manim/mobject/geometry/labeled.py +155 -0
- manim/mobject/geometry/line.py +66 -50
- manim/mobject/geometry/polygram.py +23 -15
- manim/mobject/geometry/shape_matchers.py +24 -15
- manim/mobject/geometry/tips.py +62 -40
- manim/mobject/graph.py +3 -4
- manim/mobject/graphing/coordinate_systems.py +190 -139
- manim/mobject/graphing/number_line.py +5 -2
- manim/mobject/graphing/probability.py +4 -3
- manim/mobject/graphing/scale.py +7 -7
- manim/mobject/logo.py +108 -22
- manim/mobject/matrix.py +33 -37
- manim/mobject/mobject.py +327 -260
- manim/mobject/opengl/opengl_image_mobject.py +1 -1
- manim/mobject/opengl/opengl_mobject.py +18 -12
- manim/mobject/opengl/opengl_point_cloud_mobject.py +1 -1
- manim/mobject/opengl/opengl_surface.py +1 -1
- manim/mobject/opengl/opengl_vectorized_mobject.py +21 -17
- manim/mobject/svg/brace.py +3 -1
- manim/mobject/svg/svg_mobject.py +9 -11
- manim/mobject/table.py +50 -54
- manim/mobject/text/numbers.py +48 -6
- manim/mobject/text/tex_mobject.py +8 -12
- manim/mobject/text/text_mobject.py +32 -24
- manim/mobject/three_d/three_d_utils.py +13 -8
- manim/mobject/three_d/three_dimensions.py +61 -43
- manim/mobject/types/image_mobject.py +5 -4
- manim/mobject/types/point_cloud_mobject.py +8 -6
- manim/mobject/types/vectorized_mobject.py +385 -258
- manim/mobject/vector_field.py +19 -11
- manim/plugins/import_plugins.py +1 -1
- manim/plugins/plugins_flags.py +1 -6
- manim/renderer/shader.py +2 -2
- manim/scene/scene.py +15 -7
- manim/scene/scene_file_writer.py +1 -2
- manim/scene/three_d_scene.py +1 -1
- manim/scene/vector_space_scene.py +17 -7
- manim/typing.py +133 -0
- manim/utils/bezier.py +267 -83
- manim/utils/color/AS2700.py +234 -0
- manim/utils/color/BS381.py +315 -0
- manim/utils/color/X11.py +530 -0
- manim/utils/color/XKCD.py +949 -0
- manim/utils/color/__init__.py +58 -0
- manim/utils/color/core.py +1036 -0
- manim/utils/color/manim_colors.py +220 -0
- manim/utils/docbuild/autocolor_directive.py +92 -0
- manim/utils/docbuild/manim_directive.py +40 -6
- manim/utils/file_ops.py +1 -1
- manim/utils/hashing.py +1 -1
- manim/utils/iterables.py +1 -1
- manim/utils/rate_functions.py +33 -0
- manim/utils/simple_functions.py +0 -18
- manim/utils/space_ops.py +55 -42
- manim/utils/testing/frames_comparison.py +9 -0
- manim/utils/tex.py +2 -0
- manim/utils/tex_file_writing.py +29 -2
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/METADATA +14 -14
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/RECORD +82 -71
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/WHEEL +1 -1
- manim/communitycolors.py +0 -9
- manim/utils/color.py +0 -552
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/LICENSE +0 -0
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/LICENSE.community +0 -0
- {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/entry_points.txt +0 -0
manim/utils/bezier.py
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from manim.typing import (
|
|
6
|
+
BezierPoints,
|
|
7
|
+
ColVector,
|
|
8
|
+
MatrixMN,
|
|
9
|
+
Point3D,
|
|
10
|
+
Point3D_Array,
|
|
11
|
+
PointDType,
|
|
12
|
+
QuadraticBezierPoints,
|
|
13
|
+
QuadraticBezierPoints_Array,
|
|
14
|
+
)
|
|
15
|
+
|
|
5
16
|
__all__ = [
|
|
6
17
|
"bezier",
|
|
7
18
|
"partial_bezier_points",
|
|
@@ -20,11 +31,11 @@ __all__ = [
|
|
|
20
31
|
]
|
|
21
32
|
|
|
22
33
|
|
|
23
|
-
import typing
|
|
24
34
|
from functools import reduce
|
|
25
|
-
from typing import
|
|
35
|
+
from typing import Any, Callable, Sequence, overload
|
|
26
36
|
|
|
27
37
|
import numpy as np
|
|
38
|
+
import numpy.typing as npt
|
|
28
39
|
from scipy import linalg
|
|
29
40
|
|
|
30
41
|
from ..utils.simple_functions import choose
|
|
@@ -32,8 +43,8 @@ from ..utils.space_ops import cross2d, find_intersection
|
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
def bezier(
|
|
35
|
-
points:
|
|
36
|
-
) ->
|
|
46
|
+
points: Sequence[Point3D] | Point3D_Array,
|
|
47
|
+
) -> Callable[[float], Point3D]:
|
|
37
48
|
"""Classic implementation of a bezier curve.
|
|
38
49
|
|
|
39
50
|
Parameters
|
|
@@ -43,34 +54,39 @@ def bezier(
|
|
|
43
54
|
|
|
44
55
|
Returns
|
|
45
56
|
-------
|
|
46
|
-
typing.Callable[[float], typing.Union[int, typing.Iterable]]
|
|
47
57
|
function describing the bezier curve.
|
|
58
|
+
You can pass a t value between 0 and 1 to get the corresponding point on the curve.
|
|
48
59
|
"""
|
|
49
60
|
n = len(points) - 1
|
|
50
|
-
|
|
51
61
|
# Cubic Bezier curve
|
|
52
62
|
if n == 3:
|
|
53
|
-
return (
|
|
54
|
-
|
|
63
|
+
return lambda t: np.asarray(
|
|
64
|
+
(1 - t) ** 3 * points[0]
|
|
55
65
|
+ 3 * t * (1 - t) ** 2 * points[1]
|
|
56
66
|
+ 3 * (1 - t) * t**2 * points[2]
|
|
57
|
-
+ t**3 * points[3]
|
|
67
|
+
+ t**3 * points[3],
|
|
68
|
+
dtype=PointDType,
|
|
58
69
|
)
|
|
59
70
|
# Quadratic Bezier curve
|
|
60
71
|
if n == 2:
|
|
61
|
-
return (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
+ t**2 * points[2]
|
|
72
|
+
return lambda t: np.asarray(
|
|
73
|
+
(1 - t) ** 2 * points[0] + 2 * t * (1 - t) * points[1] + t**2 * points[2],
|
|
74
|
+
dtype=PointDType,
|
|
65
75
|
)
|
|
66
76
|
|
|
67
|
-
return lambda t:
|
|
68
|
-
(
|
|
69
|
-
|
|
77
|
+
return lambda t: np.asarray(
|
|
78
|
+
np.asarray(
|
|
79
|
+
[
|
|
80
|
+
(((1 - t) ** (n - k)) * (t**k) * choose(n, k) * point)
|
|
81
|
+
for k, point in enumerate(points)
|
|
82
|
+
],
|
|
83
|
+
dtype=PointDType,
|
|
84
|
+
).sum(axis=0)
|
|
70
85
|
)
|
|
71
86
|
|
|
72
87
|
|
|
73
|
-
|
|
88
|
+
# !TODO: This function has still a weird implementation with the overlapping points
|
|
89
|
+
def partial_bezier_points(points: BezierPoints, a: float, b: float) -> BezierPoints:
|
|
74
90
|
"""Given an array of points which define bezier curve, and two numbers 0<=a<b<=1, return an array of the same size,
|
|
75
91
|
which describes the portion of the original bezier curve on the interval [a, b].
|
|
76
92
|
|
|
@@ -90,23 +106,31 @@ def partial_bezier_points(points: np.ndarray, a: float, b: float) -> np.ndarray:
|
|
|
90
106
|
np.ndarray
|
|
91
107
|
Set of points defining the partial bezier curve.
|
|
92
108
|
"""
|
|
109
|
+
_len = len(points)
|
|
93
110
|
if a == 1:
|
|
94
|
-
return [points[-1]] *
|
|
111
|
+
return np.asarray([points[-1]] * _len, dtype=PointDType)
|
|
95
112
|
|
|
96
|
-
a_to_1 = np.
|
|
113
|
+
a_to_1 = np.asarray(
|
|
114
|
+
[bezier(points[i:])(a) for i in range(_len)],
|
|
115
|
+
dtype=PointDType,
|
|
116
|
+
)
|
|
97
117
|
end_prop = (b - a) / (1.0 - a)
|
|
98
|
-
return np.
|
|
118
|
+
return np.asarray(
|
|
119
|
+
[bezier(a_to_1[: i + 1])(end_prop) for i in range(_len)],
|
|
120
|
+
dtype=PointDType,
|
|
121
|
+
)
|
|
99
122
|
|
|
100
123
|
|
|
101
124
|
# Shortened version of partial_bezier_points just for quadratics,
|
|
102
125
|
# since this is called a fair amount
|
|
103
|
-
def partial_quadratic_bezier_points(
|
|
104
|
-
points
|
|
126
|
+
def partial_quadratic_bezier_points(
|
|
127
|
+
points: QuadraticBezierPoints, a: float, b: float
|
|
128
|
+
) -> QuadraticBezierPoints:
|
|
105
129
|
if a == 1:
|
|
106
|
-
return 3 * [points[-1]]
|
|
130
|
+
return np.asarray(3 * [points[-1]])
|
|
107
131
|
|
|
108
|
-
def curve(t):
|
|
109
|
-
return (
|
|
132
|
+
def curve(t: float) -> Point3D:
|
|
133
|
+
return np.asarray(
|
|
110
134
|
points[0] * (1 - t) * (1 - t)
|
|
111
135
|
+ 2 * points[1] * t * (1 - t)
|
|
112
136
|
+ points[2] * t * t
|
|
@@ -118,10 +142,10 @@ def partial_quadratic_bezier_points(points, a, b):
|
|
|
118
142
|
h1_prime = (1 - a) * points[1] + a * points[2]
|
|
119
143
|
end_prop = (b - a) / (1.0 - a)
|
|
120
144
|
h1 = (1 - end_prop) * h0 + end_prop * h1_prime
|
|
121
|
-
return
|
|
145
|
+
return np.asarray((h0, h1, h2))
|
|
122
146
|
|
|
123
147
|
|
|
124
|
-
def split_quadratic_bezier(points:
|
|
148
|
+
def split_quadratic_bezier(points: QuadraticBezierPoints, t: float) -> BezierPoints:
|
|
125
149
|
"""Split a quadratic Bézier curve at argument ``t`` into two quadratic curves.
|
|
126
150
|
|
|
127
151
|
Parameters
|
|
@@ -143,10 +167,10 @@ def split_quadratic_bezier(points: np.ndarray, t: float) -> np.ndarray:
|
|
|
143
167
|
s2 = interpolate(h1, a2, t)
|
|
144
168
|
p = interpolate(s1, s2, t)
|
|
145
169
|
|
|
146
|
-
return np.array(
|
|
170
|
+
return np.array((a1, s1, p, p, s2, a2))
|
|
147
171
|
|
|
148
172
|
|
|
149
|
-
def subdivide_quadratic_bezier(points:
|
|
173
|
+
def subdivide_quadratic_bezier(points: QuadraticBezierPoints, n: int) -> BezierPoints:
|
|
150
174
|
"""Subdivide a quadratic Bézier curve into ``n`` subcurves which have the same shape.
|
|
151
175
|
|
|
152
176
|
The points at which the curve is split are located at the
|
|
@@ -178,8 +202,8 @@ def subdivide_quadratic_bezier(points: Iterable[float], n: int) -> np.ndarray:
|
|
|
178
202
|
|
|
179
203
|
|
|
180
204
|
def quadratic_bezier_remap(
|
|
181
|
-
triplets:
|
|
182
|
-
):
|
|
205
|
+
triplets: QuadraticBezierPoints_Array, new_number_of_curves: int
|
|
206
|
+
) -> QuadraticBezierPoints_Array:
|
|
183
207
|
"""Remaps the number of curves to a higher amount by splitting bezier curves
|
|
184
208
|
|
|
185
209
|
Parameters
|
|
@@ -234,7 +258,21 @@ def quadratic_bezier_remap(
|
|
|
234
258
|
|
|
235
259
|
|
|
236
260
|
# Linear interpolation variants
|
|
237
|
-
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@overload
|
|
264
|
+
def interpolate(start: float, end: float, alpha: float) -> float:
|
|
265
|
+
...
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@overload
|
|
269
|
+
def interpolate(start: Point3D, end: Point3D, alpha: float) -> Point3D:
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def interpolate(
|
|
274
|
+
start: int | float | Point3D, end: int | float | Point3D, alpha: float | Point3D
|
|
275
|
+
) -> float | Point3D:
|
|
238
276
|
return (1 - alpha) * start + alpha * end
|
|
239
277
|
|
|
240
278
|
|
|
@@ -244,52 +282,192 @@ def integer_interpolate(
|
|
|
244
282
|
alpha: float,
|
|
245
283
|
) -> tuple[int, float]:
|
|
246
284
|
"""
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
285
|
+
This is a variant of interpolate that returns an integer and the residual
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
start
|
|
290
|
+
The start of the range
|
|
291
|
+
end
|
|
292
|
+
The end of the range
|
|
293
|
+
alpha
|
|
294
|
+
a float between 0 and 1.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
tuple[int, float]
|
|
299
|
+
This returns an integer between start and end (inclusive) representing
|
|
300
|
+
appropriate interpolation between them, along with a
|
|
301
|
+
"residue" representing a new proportion between the
|
|
302
|
+
returned integer and the next one of the
|
|
303
|
+
list.
|
|
304
|
+
|
|
305
|
+
Example
|
|
306
|
+
-------
|
|
307
|
+
|
|
308
|
+
.. code-block:: pycon
|
|
309
|
+
|
|
310
|
+
>>> integer, residue = integer_interpolate(start=0, end=10, alpha=0.46)
|
|
311
|
+
>>> np.allclose((integer, residue), (4, 0.6))
|
|
312
|
+
True
|
|
256
313
|
"""
|
|
257
314
|
if alpha >= 1:
|
|
258
|
-
return (end - 1, 1.0)
|
|
315
|
+
return (int(end - 1), 1.0)
|
|
259
316
|
if alpha <= 0:
|
|
260
|
-
return (start, 0)
|
|
317
|
+
return (int(start), 0)
|
|
261
318
|
value = int(interpolate(start, end, alpha))
|
|
262
319
|
residue = ((end - start) * alpha) % 1
|
|
263
320
|
return (value, residue)
|
|
264
321
|
|
|
265
322
|
|
|
323
|
+
@overload
|
|
266
324
|
def mid(start: float, end: float) -> float:
|
|
325
|
+
...
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@overload
|
|
329
|
+
def mid(start: Point3D, end: Point3D) -> Point3D:
|
|
330
|
+
...
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def mid(start: float | Point3D, end: float | Point3D) -> float | Point3D:
|
|
334
|
+
"""Returns the midpoint between two values.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
start
|
|
339
|
+
The first value
|
|
340
|
+
end
|
|
341
|
+
The second value
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
The midpoint between the two values
|
|
346
|
+
"""
|
|
267
347
|
return (start + end) / 2.0
|
|
268
348
|
|
|
269
349
|
|
|
270
|
-
|
|
350
|
+
@overload
|
|
351
|
+
def inverse_interpolate(start: float, end: float, value: float) -> float:
|
|
352
|
+
...
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@overload
|
|
356
|
+
def inverse_interpolate(start: float, end: float, value: Point3D) -> Point3D:
|
|
357
|
+
...
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@overload
|
|
361
|
+
def inverse_interpolate(start: Point3D, end: Point3D, value: Point3D) -> Point3D:
|
|
362
|
+
...
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def inverse_interpolate(
|
|
366
|
+
start: float | Point3D, end: float | Point3D, value: float | Point3D
|
|
367
|
+
) -> float | Point3D:
|
|
368
|
+
"""Perform inverse interpolation to determine the alpha
|
|
369
|
+
values that would produce the specified ``value``
|
|
370
|
+
given the ``start`` and ``end`` values or points.
|
|
371
|
+
|
|
372
|
+
Parameters
|
|
373
|
+
----------
|
|
374
|
+
start
|
|
375
|
+
The start value or point of the interpolation.
|
|
376
|
+
end
|
|
377
|
+
The end value or point of the interpolation.
|
|
378
|
+
value
|
|
379
|
+
The value or point for which the alpha value
|
|
380
|
+
should be determined.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
The alpha values producing the given input
|
|
385
|
+
when interpolating between ``start`` and ``end``.
|
|
386
|
+
|
|
387
|
+
Example
|
|
388
|
+
-------
|
|
389
|
+
|
|
390
|
+
.. code-block:: pycon
|
|
391
|
+
|
|
392
|
+
>>> inverse_interpolate(start=2, end=6, value=4)
|
|
393
|
+
0.5
|
|
394
|
+
|
|
395
|
+
>>> start = np.array([1, 2, 1])
|
|
396
|
+
>>> end = np.array([7, 8, 11])
|
|
397
|
+
>>> value = np.array([4, 5, 5])
|
|
398
|
+
>>> inverse_interpolate(start, end, value)
|
|
399
|
+
array([0.5, 0.5, 0.4])
|
|
400
|
+
"""
|
|
271
401
|
return np.true_divide(value - start, end - start)
|
|
272
402
|
|
|
273
403
|
|
|
404
|
+
@overload
|
|
274
405
|
def match_interpolate(
|
|
275
406
|
new_start: float,
|
|
276
407
|
new_end: float,
|
|
277
408
|
old_start: float,
|
|
278
409
|
old_end: float,
|
|
279
410
|
old_value: float,
|
|
280
|
-
) ->
|
|
411
|
+
) -> float:
|
|
412
|
+
...
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@overload
|
|
416
|
+
def match_interpolate(
|
|
417
|
+
new_start: float,
|
|
418
|
+
new_end: float,
|
|
419
|
+
old_start: float,
|
|
420
|
+
old_end: float,
|
|
421
|
+
old_value: Point3D,
|
|
422
|
+
) -> Point3D:
|
|
423
|
+
...
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def match_interpolate(
|
|
427
|
+
new_start: float,
|
|
428
|
+
new_end: float,
|
|
429
|
+
old_start: float,
|
|
430
|
+
old_end: float,
|
|
431
|
+
old_value: float | Point3D,
|
|
432
|
+
) -> float | Point3D:
|
|
433
|
+
"""Interpolate a value from an old range to a new range.
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
new_start
|
|
438
|
+
The start of the new range.
|
|
439
|
+
new_end
|
|
440
|
+
The end of the new range.
|
|
441
|
+
old_start
|
|
442
|
+
The start of the old range.
|
|
443
|
+
old_end
|
|
444
|
+
The end of the old range.
|
|
445
|
+
old_value
|
|
446
|
+
The value within the old range whose corresponding
|
|
447
|
+
value in the new range (with the same alpha value)
|
|
448
|
+
is desired.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
The interpolated value within the new range.
|
|
453
|
+
|
|
454
|
+
Examples
|
|
455
|
+
--------
|
|
456
|
+
>>> match_interpolate(0, 100, 10, 20, 15)
|
|
457
|
+
50.0
|
|
458
|
+
"""
|
|
459
|
+
old_alpha = inverse_interpolate(old_start, old_end, old_value)
|
|
281
460
|
return interpolate(
|
|
282
461
|
new_start,
|
|
283
462
|
new_end,
|
|
284
|
-
|
|
463
|
+
old_alpha, # type: ignore
|
|
285
464
|
)
|
|
286
465
|
|
|
287
466
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
points = np.array(points)
|
|
467
|
+
def get_smooth_cubic_bezier_handle_points(
|
|
468
|
+
points: Point3D_Array,
|
|
469
|
+
) -> tuple[BezierPoints, BezierPoints]:
|
|
470
|
+
points = np.asarray(points)
|
|
293
471
|
num_handles = len(points) - 1
|
|
294
472
|
dim = points.shape[1]
|
|
295
473
|
if num_handles < 1:
|
|
@@ -301,7 +479,7 @@ def get_smooth_cubic_bezier_handle_points(points):
|
|
|
301
479
|
# diag is a representation of the matrix in diagonal form
|
|
302
480
|
# See https://www.particleincell.com/2012/bezier-splines/
|
|
303
481
|
# for how to arrive at these equations
|
|
304
|
-
diag = np.zeros((l + u + 1, 2 * num_handles))
|
|
482
|
+
diag: MatrixMN = np.zeros((l + u + 1, 2 * num_handles))
|
|
305
483
|
diag[0, 1::2] = -1
|
|
306
484
|
diag[0, 2::2] = 1
|
|
307
485
|
diag[1, 0::2] = 2
|
|
@@ -314,13 +492,13 @@ def get_smooth_cubic_bezier_handle_points(points):
|
|
|
314
492
|
# This is the b as in Ax = b, where we are solving for x,
|
|
315
493
|
# and A is represented using diag. However, think of entries
|
|
316
494
|
# to x and b as being points in space, not numbers
|
|
317
|
-
b = np.zeros((2 * num_handles, dim))
|
|
495
|
+
b: Point3D_Array = np.zeros((2 * num_handles, dim))
|
|
318
496
|
b[1::2] = 2 * points[1:]
|
|
319
497
|
b[0] = points[0]
|
|
320
498
|
b[-1] = points[-1]
|
|
321
499
|
|
|
322
|
-
def solve_func(b):
|
|
323
|
-
return linalg.solve_banded((l, u), diag, b)
|
|
500
|
+
def solve_func(b: ColVector) -> ColVector | MatrixMN:
|
|
501
|
+
return linalg.solve_banded((l, u), diag, b) # type: ignore
|
|
324
502
|
|
|
325
503
|
use_closed_solve_function = is_closed(points)
|
|
326
504
|
if use_closed_solve_function:
|
|
@@ -334,8 +512,8 @@ def get_smooth_cubic_bezier_handle_points(points):
|
|
|
334
512
|
b[0] = 2 * points[0]
|
|
335
513
|
b[-1] = np.zeros(dim)
|
|
336
514
|
|
|
337
|
-
def closed_curve_solve_func(b):
|
|
338
|
-
return linalg.solve(matrix, b)
|
|
515
|
+
def closed_curve_solve_func(b: ColVector) -> ColVector | MatrixMN:
|
|
516
|
+
return linalg.solve(matrix, b) # type: ignore
|
|
339
517
|
|
|
340
518
|
handle_pairs = np.zeros((2 * num_handles, dim))
|
|
341
519
|
for i in range(dim):
|
|
@@ -347,8 +525,8 @@ def get_smooth_cubic_bezier_handle_points(points):
|
|
|
347
525
|
|
|
348
526
|
|
|
349
527
|
def get_smooth_handle_points(
|
|
350
|
-
points:
|
|
351
|
-
) -> tuple[
|
|
528
|
+
points: BezierPoints,
|
|
529
|
+
) -> tuple[BezierPoints, BezierPoints]:
|
|
352
530
|
"""Given some anchors (points), compute handles so the resulting bezier curve is smooth.
|
|
353
531
|
|
|
354
532
|
Parameters
|
|
@@ -362,7 +540,7 @@ def get_smooth_handle_points(
|
|
|
362
540
|
Computed handles.
|
|
363
541
|
"""
|
|
364
542
|
# NOTE points here are anchors.
|
|
365
|
-
points = np.
|
|
543
|
+
points = np.asarray(points)
|
|
366
544
|
num_handles = len(points) - 1
|
|
367
545
|
dim = points.shape[1]
|
|
368
546
|
if num_handles < 1:
|
|
@@ -374,7 +552,7 @@ def get_smooth_handle_points(
|
|
|
374
552
|
# diag is a representation of the matrix in diagonal form
|
|
375
553
|
# See https://www.particleincell.com/2012/bezier-splines/
|
|
376
554
|
# for how to arrive at these equations
|
|
377
|
-
diag = np.zeros((l + u + 1, 2 * num_handles))
|
|
555
|
+
diag: MatrixMN = np.zeros((l + u + 1, 2 * num_handles))
|
|
378
556
|
diag[0, 1::2] = -1
|
|
379
557
|
diag[0, 2::2] = 1
|
|
380
558
|
diag[1, 0::2] = 2
|
|
@@ -392,8 +570,8 @@ def get_smooth_handle_points(
|
|
|
392
570
|
b[0] = points[0]
|
|
393
571
|
b[-1] = points[-1]
|
|
394
572
|
|
|
395
|
-
def solve_func(b:
|
|
396
|
-
return linalg.solve_banded((l, u), diag, b)
|
|
573
|
+
def solve_func(b: ColVector) -> ColVector | MatrixMN:
|
|
574
|
+
return linalg.solve_banded((l, u), diag, b) # type: ignore
|
|
397
575
|
|
|
398
576
|
use_closed_solve_function = is_closed(points)
|
|
399
577
|
if use_closed_solve_function:
|
|
@@ -407,8 +585,8 @@ def get_smooth_handle_points(
|
|
|
407
585
|
b[0] = 2 * points[0]
|
|
408
586
|
b[-1] = np.zeros(dim)
|
|
409
587
|
|
|
410
|
-
def closed_curve_solve_func(b:
|
|
411
|
-
return linalg.solve(matrix, b)
|
|
588
|
+
def closed_curve_solve_func(b: ColVector) -> ColVector | MatrixMN:
|
|
589
|
+
return linalg.solve(matrix, b) # type: ignore
|
|
412
590
|
|
|
413
591
|
handle_pairs = np.zeros((2 * num_handles, dim))
|
|
414
592
|
for i in range(dim):
|
|
@@ -419,7 +597,9 @@ def get_smooth_handle_points(
|
|
|
419
597
|
return handle_pairs[0::2], handle_pairs[1::2]
|
|
420
598
|
|
|
421
599
|
|
|
422
|
-
def diag_to_matrix(
|
|
600
|
+
def diag_to_matrix(
|
|
601
|
+
l_and_u: tuple[int, int], diag: npt.NDArray[Any]
|
|
602
|
+
) -> npt.NDArray[Any]:
|
|
423
603
|
"""
|
|
424
604
|
Converts array whose rows represent diagonal
|
|
425
605
|
entries of a matrix into the matrix itself.
|
|
@@ -438,7 +618,9 @@ def diag_to_matrix(l_and_u: tuple[int, int], diag: np.ndarray) -> np.ndarray:
|
|
|
438
618
|
|
|
439
619
|
# Given 4 control points for a cubic bezier curve (or arrays of such)
|
|
440
620
|
# return control points for 2 quadratics (or 2n quadratics) approximating them.
|
|
441
|
-
def get_quadratic_approximation_of_cubic(
|
|
621
|
+
def get_quadratic_approximation_of_cubic(
|
|
622
|
+
a0: Point3D, h0: Point3D, h1: Point3D, a1: Point3D
|
|
623
|
+
) -> BezierPoints:
|
|
442
624
|
a0 = np.array(a0, ndmin=2)
|
|
443
625
|
h0 = np.array(h0, ndmin=2)
|
|
444
626
|
h1 = np.array(h1, ndmin=2)
|
|
@@ -486,9 +668,9 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1):
|
|
|
486
668
|
m, n = a0.shape
|
|
487
669
|
t_mid = t_mid.repeat(n).reshape((m, n))
|
|
488
670
|
|
|
489
|
-
# Compute bezier point and tangent at the chosen value of t
|
|
490
|
-
mid = bezier([a0, h0, h1, a1])(t_mid)
|
|
491
|
-
Tm = bezier([h0 - a0, h1 - h0, a1 - h1])(t_mid)
|
|
671
|
+
# Compute bezier point and tangent at the chosen value of t (these are vectorized)
|
|
672
|
+
mid = bezier([a0, h0, h1, a1])(t_mid) # type: ignore
|
|
673
|
+
Tm = bezier([h0 - a0, h1 - h0, a1 - h1])(t_mid) # type: ignore
|
|
492
674
|
|
|
493
675
|
# Intersection between tangent lines at end points
|
|
494
676
|
# and tangent in the middle
|
|
@@ -506,15 +688,15 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1):
|
|
|
506
688
|
return result
|
|
507
689
|
|
|
508
690
|
|
|
509
|
-
def is_closed(points:
|
|
510
|
-
return np.allclose(points[0], points[-1])
|
|
691
|
+
def is_closed(points: Point3D_Array) -> bool:
|
|
692
|
+
return np.allclose(points[0], points[-1]) # type: ignore
|
|
511
693
|
|
|
512
694
|
|
|
513
695
|
def proportions_along_bezier_curve_for_point(
|
|
514
|
-
point:
|
|
515
|
-
control_points:
|
|
516
|
-
round_to: float
|
|
517
|
-
) ->
|
|
696
|
+
point: Point3D,
|
|
697
|
+
control_points: BezierPoints,
|
|
698
|
+
round_to: float = 1e-6,
|
|
699
|
+
) -> npt.NDArray[Any]:
|
|
518
700
|
"""Obtains the proportion along the bezier curve corresponding to a given point
|
|
519
701
|
given the bezier curve's control points.
|
|
520
702
|
|
|
@@ -583,21 +765,23 @@ def proportions_along_bezier_curve_for_point(
|
|
|
583
765
|
# Roots will be none, but in this specific instance, we don't need to consider that.
|
|
584
766
|
continue
|
|
585
767
|
bezier_polynom = np.polynomial.Polynomial(terms[::-1])
|
|
586
|
-
polynom_roots = bezier_polynom.roots()
|
|
768
|
+
polynom_roots = bezier_polynom.roots() # type: ignore
|
|
587
769
|
if len(polynom_roots) > 0:
|
|
588
770
|
polynom_roots = np.around(polynom_roots, int(np.log10(1 / round_to)))
|
|
589
771
|
roots.append(polynom_roots)
|
|
590
772
|
|
|
591
773
|
roots = [[root for root in rootlist if root.imag == 0] for rootlist in roots]
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
774
|
+
# Get common roots
|
|
775
|
+
# arg-type: ignore
|
|
776
|
+
roots = reduce(np.intersect1d, roots) # type: ignore
|
|
777
|
+
result = np.asarray([r.real for r in roots if 0 <= r.real <= 1])
|
|
778
|
+
return result
|
|
595
779
|
|
|
596
780
|
|
|
597
781
|
def point_lies_on_bezier(
|
|
598
|
-
point:
|
|
599
|
-
control_points:
|
|
600
|
-
round_to: float
|
|
782
|
+
point: Point3D,
|
|
783
|
+
control_points: BezierPoints,
|
|
784
|
+
round_to: float = 1e-6,
|
|
601
785
|
) -> bool:
|
|
602
786
|
"""Checks if a given point lies on the bezier curves with the given control points.
|
|
603
787
|
|