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/qhull.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from manim.typing import PointND, PointND_Array
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QuickHullPoint:
|
|
13
|
+
def __init__(self, coordinates: PointND_Array) -> None:
|
|
14
|
+
self.coordinates = coordinates
|
|
15
|
+
|
|
16
|
+
def __hash__(self) -> int:
|
|
17
|
+
return hash(self.coordinates.tobytes())
|
|
18
|
+
|
|
19
|
+
def __eq__(self, other: object) -> bool:
|
|
20
|
+
if not isinstance(other, QuickHullPoint):
|
|
21
|
+
raise ValueError
|
|
22
|
+
are_coordinates_equal: bool = np.array_equal(
|
|
23
|
+
self.coordinates, other.coordinates
|
|
24
|
+
)
|
|
25
|
+
return are_coordinates_equal
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SubFacet:
|
|
29
|
+
def __init__(self, coordinates: PointND_Array) -> None:
|
|
30
|
+
self.coordinates = coordinates
|
|
31
|
+
self.points = frozenset(QuickHullPoint(c) for c in coordinates)
|
|
32
|
+
|
|
33
|
+
def __hash__(self) -> int:
|
|
34
|
+
return hash(self.points)
|
|
35
|
+
|
|
36
|
+
def __eq__(self, other: object) -> bool:
|
|
37
|
+
if not isinstance(other, SubFacet):
|
|
38
|
+
raise ValueError
|
|
39
|
+
return self.points == other.points
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Facet:
|
|
43
|
+
def __init__(self, coordinates: PointND_Array, internal: PointND) -> None:
|
|
44
|
+
self.coordinates = coordinates
|
|
45
|
+
self.center: PointND = np.mean(coordinates, axis=0)
|
|
46
|
+
self.normal = self.compute_normal(internal)
|
|
47
|
+
self.subfacets = frozenset(
|
|
48
|
+
SubFacet(np.delete(self.coordinates, i, axis=0))
|
|
49
|
+
for i in range(self.coordinates.shape[0])
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def compute_normal(self, internal: PointND) -> PointND:
|
|
53
|
+
centered = self.coordinates - self.center
|
|
54
|
+
_, _, vh = np.linalg.svd(centered)
|
|
55
|
+
normal: PointND = vh[-1, :]
|
|
56
|
+
normal /= np.linalg.norm(normal)
|
|
57
|
+
|
|
58
|
+
# If the normal points towards the internal point, flip it!
|
|
59
|
+
if np.dot(normal, self.center - internal) < 0:
|
|
60
|
+
normal *= -1
|
|
61
|
+
|
|
62
|
+
return normal
|
|
63
|
+
|
|
64
|
+
def __hash__(self) -> int:
|
|
65
|
+
return hash(self.subfacets)
|
|
66
|
+
|
|
67
|
+
def __eq__(self, other: object) -> bool:
|
|
68
|
+
if not isinstance(other, Facet):
|
|
69
|
+
raise ValueError
|
|
70
|
+
return self.subfacets == other.subfacets
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Horizon:
|
|
74
|
+
def __init__(self) -> None:
|
|
75
|
+
self.facets: set[Facet] = set()
|
|
76
|
+
self.boundary: list[SubFacet] = []
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class QuickHull:
|
|
80
|
+
"""
|
|
81
|
+
QuickHull algorithm for constructing a convex hull from a set of points.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
tolerance
|
|
86
|
+
A tolerance threshold for determining when points lie on the convex hull (default is 1e-5).
|
|
87
|
+
|
|
88
|
+
Attributes
|
|
89
|
+
----------
|
|
90
|
+
facets
|
|
91
|
+
List of facets considered.
|
|
92
|
+
removed
|
|
93
|
+
Set of internal facets that have been removed from the hull during the construction process.
|
|
94
|
+
outside
|
|
95
|
+
Dictionary mapping each facet to its outside points and eye point.
|
|
96
|
+
neighbors
|
|
97
|
+
Mapping of subfacets to their neighboring facets. Each subfacet links precisely two neighbors.
|
|
98
|
+
unclaimed
|
|
99
|
+
Points that have not yet been classified as inside or outside the current hull.
|
|
100
|
+
internal
|
|
101
|
+
An internal point (i.e., the center of the initial simplex) used as a reference during hull construction.
|
|
102
|
+
tolerance
|
|
103
|
+
The tolerance used to determine if points are considered outside the current hull.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, tolerance: float = 1e-5) -> None:
|
|
107
|
+
self.facets: list[Facet] = []
|
|
108
|
+
self.removed: set[Facet] = set()
|
|
109
|
+
self.outside: dict[Facet, tuple[PointND_Array | None, PointND | None]] = {}
|
|
110
|
+
self.neighbors: dict[SubFacet, set[Facet]] = {}
|
|
111
|
+
self.unclaimed: PointND_Array | None = None
|
|
112
|
+
self.internal: PointND | None = None
|
|
113
|
+
self.tolerance = tolerance
|
|
114
|
+
|
|
115
|
+
def initialize(self, points: PointND_Array) -> None:
|
|
116
|
+
# Sample Points
|
|
117
|
+
simplex = points[
|
|
118
|
+
np.random.choice(points.shape[0], points.shape[1] + 1, replace=False)
|
|
119
|
+
]
|
|
120
|
+
self.unclaimed = points
|
|
121
|
+
new_internal: PointND = np.mean(simplex, axis=0)
|
|
122
|
+
self.internal = new_internal
|
|
123
|
+
|
|
124
|
+
# Build Simplex
|
|
125
|
+
for c in range(simplex.shape[0]):
|
|
126
|
+
facet = Facet(np.delete(simplex, c, axis=0), internal=new_internal)
|
|
127
|
+
self.classify(facet)
|
|
128
|
+
self.facets.append(facet)
|
|
129
|
+
|
|
130
|
+
# Attach Neighbors
|
|
131
|
+
for f in self.facets:
|
|
132
|
+
for sf in f.subfacets:
|
|
133
|
+
self.neighbors.setdefault(sf, set()).add(f)
|
|
134
|
+
|
|
135
|
+
def classify(self, facet: Facet) -> None:
|
|
136
|
+
assert self.unclaimed is not None, (
|
|
137
|
+
"Call .initialize() before using .classify()."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if not self.unclaimed.size:
|
|
141
|
+
self.outside[facet] = (None, None)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
# Compute Projections
|
|
145
|
+
projections = (self.unclaimed - facet.center) @ facet.normal
|
|
146
|
+
arg = np.argmax(projections)
|
|
147
|
+
mask = projections > self.tolerance
|
|
148
|
+
|
|
149
|
+
# Identify Eye and Outside Set
|
|
150
|
+
eye = self.unclaimed[arg] if projections[arg] > self.tolerance else None
|
|
151
|
+
outside = self.unclaimed[mask]
|
|
152
|
+
self.outside[facet] = (outside, eye)
|
|
153
|
+
self.unclaimed = self.unclaimed[~mask]
|
|
154
|
+
|
|
155
|
+
def compute_horizon(self, eye: PointND, start_facet: Facet) -> Horizon:
|
|
156
|
+
horizon = Horizon()
|
|
157
|
+
self._recursive_horizon(eye, start_facet, horizon)
|
|
158
|
+
return horizon
|
|
159
|
+
|
|
160
|
+
def _recursive_horizon(self, eye: PointND, facet: Facet, horizon: Horizon) -> bool:
|
|
161
|
+
visible = np.dot(facet.normal, eye - facet.center) > 0
|
|
162
|
+
if not visible:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
# If the eye is visible from the facet:
|
|
166
|
+
# Label the facet as visible and cross each edge
|
|
167
|
+
horizon.facets.add(facet)
|
|
168
|
+
for subfacet in facet.subfacets:
|
|
169
|
+
neighbor = (self.neighbors[subfacet] - {facet}).pop()
|
|
170
|
+
# If the neighbor is not visible, then the edge shared must be on the boundary
|
|
171
|
+
if neighbor not in horizon.facets and not self._recursive_horizon(
|
|
172
|
+
eye, neighbor, horizon
|
|
173
|
+
):
|
|
174
|
+
horizon.boundary.append(subfacet)
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
def build(self, points: PointND_Array) -> None:
|
|
178
|
+
num, dim = points.shape
|
|
179
|
+
if (dim == 0) or (num < dim + 1):
|
|
180
|
+
raise ValueError("Not enough points supplied to build Convex Hull!")
|
|
181
|
+
if dim == 1:
|
|
182
|
+
raise ValueError("The Convex Hull of 1D data is its min-max!")
|
|
183
|
+
|
|
184
|
+
self.initialize(points)
|
|
185
|
+
|
|
186
|
+
# This helps the type checker.
|
|
187
|
+
assert self.unclaimed is not None
|
|
188
|
+
assert self.internal is not None
|
|
189
|
+
|
|
190
|
+
while True:
|
|
191
|
+
updated = False
|
|
192
|
+
for facet in self.facets:
|
|
193
|
+
if facet in self.removed:
|
|
194
|
+
continue
|
|
195
|
+
outside, eye = self.outside[facet]
|
|
196
|
+
if eye is not None:
|
|
197
|
+
updated = True
|
|
198
|
+
horizon = self.compute_horizon(eye, facet)
|
|
199
|
+
for f in horizon.facets:
|
|
200
|
+
points_to_append = self.outside[f][0]
|
|
201
|
+
# TODO: is this always true?
|
|
202
|
+
assert points_to_append is not None
|
|
203
|
+
self.unclaimed = np.vstack((self.unclaimed, points_to_append))
|
|
204
|
+
self.removed.add(f)
|
|
205
|
+
for sf in f.subfacets:
|
|
206
|
+
self.neighbors[sf].discard(f)
|
|
207
|
+
if self.neighbors[sf] == set():
|
|
208
|
+
del self.neighbors[sf]
|
|
209
|
+
for sf in horizon.boundary:
|
|
210
|
+
nf = Facet(
|
|
211
|
+
np.vstack((sf.coordinates, eye)), internal=self.internal
|
|
212
|
+
)
|
|
213
|
+
self.classify(nf)
|
|
214
|
+
self.facets.append(nf)
|
|
215
|
+
for nsf in nf.subfacets:
|
|
216
|
+
self.neighbors.setdefault(nsf, set()).add(nf)
|
|
217
|
+
if not updated:
|
|
218
|
+
break
|
manim/utils/rate_functions.py
CHANGED
|
@@ -83,12 +83,14 @@ There are primarily 3 kinds of standard easing functions:
|
|
|
83
83
|
self.wait()
|
|
84
84
|
"""
|
|
85
85
|
|
|
86
|
-
|
|
87
86
|
from __future__ import annotations
|
|
88
87
|
|
|
89
88
|
__all__ = [
|
|
90
89
|
"linear",
|
|
91
90
|
"smooth",
|
|
91
|
+
"smoothstep",
|
|
92
|
+
"smootherstep",
|
|
93
|
+
"smoothererstep",
|
|
92
94
|
"rush_into",
|
|
93
95
|
"rush_from",
|
|
94
96
|
"slow_into",
|
|
@@ -103,21 +105,25 @@ __all__ = [
|
|
|
103
105
|
"exponential_decay",
|
|
104
106
|
]
|
|
105
107
|
|
|
106
|
-
import typing
|
|
107
108
|
from functools import wraps
|
|
108
109
|
from math import sqrt
|
|
110
|
+
from typing import Any, Protocol
|
|
109
111
|
|
|
110
112
|
import numpy as np
|
|
111
113
|
|
|
112
|
-
from
|
|
113
|
-
|
|
114
|
+
from manim.utils.simple_functions import sigmoid
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# TODO: rewrite this to use ParamSpec when Python 3.9 is out of life
|
|
118
|
+
class RateFunction(Protocol):
|
|
119
|
+
def __call__(self, t: float, *args: Any, **kwargs: Any) -> float: ...
|
|
114
120
|
|
|
115
121
|
|
|
116
122
|
# This is a decorator that makes sure any function it's used on will
|
|
117
123
|
# return 0 if t<0 and 1 if t>1.
|
|
118
|
-
def unit_interval(function):
|
|
124
|
+
def unit_interval(function: RateFunction) -> RateFunction:
|
|
119
125
|
@wraps(function)
|
|
120
|
-
def wrapper(t, *args, **kwargs):
|
|
126
|
+
def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
|
|
121
127
|
if 0 <= t <= 1:
|
|
122
128
|
return function(t, *args, **kwargs)
|
|
123
129
|
elif t < 0:
|
|
@@ -130,9 +136,9 @@ def unit_interval(function):
|
|
|
130
136
|
|
|
131
137
|
# This is a decorator that makes sure any function it's used on will
|
|
132
138
|
# return 0 if t<0 or t>1.
|
|
133
|
-
def zero(function):
|
|
139
|
+
def zero(function: RateFunction) -> RateFunction:
|
|
134
140
|
@wraps(function)
|
|
135
|
-
def wrapper(t, *args, **kwargs):
|
|
141
|
+
def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
|
|
136
142
|
if 0 <= t <= 1:
|
|
137
143
|
return function(t, *args, **kwargs)
|
|
138
144
|
else:
|
|
@@ -155,6 +161,33 @@ def smooth(t: float, inflection: float = 10.0) -> float:
|
|
|
155
161
|
)
|
|
156
162
|
|
|
157
163
|
|
|
164
|
+
@unit_interval
|
|
165
|
+
def smoothstep(t: float) -> float:
|
|
166
|
+
"""Implementation of the 1st order SmoothStep sigmoid function.
|
|
167
|
+
The 1st derivative (speed) is zero at the endpoints.
|
|
168
|
+
https://en.wikipedia.org/wiki/Smoothstep
|
|
169
|
+
"""
|
|
170
|
+
return 3 * t**2 - 2 * t**3
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@unit_interval
|
|
174
|
+
def smootherstep(t: float) -> float:
|
|
175
|
+
"""Implementation of the 2nd order SmoothStep sigmoid function.
|
|
176
|
+
The 1st and 2nd derivatives (speed and acceleration) are zero at the endpoints.
|
|
177
|
+
https://en.wikipedia.org/wiki/Smoothstep
|
|
178
|
+
"""
|
|
179
|
+
return 6 * t**5 - 15 * t**4 + 10 * t**3
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@unit_interval
|
|
183
|
+
def smoothererstep(t: float) -> float:
|
|
184
|
+
"""Implementation of the 3rd order SmoothStep sigmoid function.
|
|
185
|
+
The 1st, 2nd and 3rd derivatives (speed, acceleration and jerk) are zero at the endpoints.
|
|
186
|
+
https://en.wikipedia.org/wiki/Smoothstep
|
|
187
|
+
"""
|
|
188
|
+
return 35 * t**4 - 84 * t**5 + 70 * t**6 - 20 * t**7
|
|
189
|
+
|
|
190
|
+
|
|
158
191
|
@unit_interval
|
|
159
192
|
def rush_into(t: float, inflection: float = 10.0) -> float:
|
|
160
193
|
return 2 * smooth(t / 2.0, inflection)
|
|
@@ -167,7 +200,8 @@ def rush_from(t: float, inflection: float = 10.0) -> float:
|
|
|
167
200
|
|
|
168
201
|
@unit_interval
|
|
169
202
|
def slow_into(t: float) -> float:
|
|
170
|
-
|
|
203
|
+
val: float = np.sqrt(1 - (1 - t) * (1 - t))
|
|
204
|
+
return val
|
|
171
205
|
|
|
172
206
|
|
|
173
207
|
@unit_interval
|
|
@@ -186,7 +220,7 @@ def there_and_back(t: float, inflection: float = 10.0) -> float:
|
|
|
186
220
|
|
|
187
221
|
@zero
|
|
188
222
|
def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
|
|
189
|
-
a =
|
|
223
|
+
a = 2.0 / (1.0 - pause_ratio)
|
|
190
224
|
if t < 0.5 - pause_ratio / 2:
|
|
191
225
|
return smooth(a * t)
|
|
192
226
|
elif t < 0.5 + pause_ratio / 2:
|
|
@@ -199,40 +233,60 @@ def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
|
|
|
199
233
|
def running_start(
|
|
200
234
|
t: float,
|
|
201
235
|
pull_factor: float = -0.5,
|
|
202
|
-
) ->
|
|
203
|
-
|
|
236
|
+
) -> float:
|
|
237
|
+
t2 = t * t
|
|
238
|
+
t3 = t2 * t
|
|
239
|
+
t4 = t3 * t
|
|
240
|
+
t5 = t4 * t
|
|
241
|
+
t6 = t5 * t
|
|
242
|
+
mt = 1 - t
|
|
243
|
+
mt2 = mt * mt
|
|
244
|
+
mt3 = mt2 * mt
|
|
245
|
+
mt4 = mt3 * mt
|
|
246
|
+
|
|
247
|
+
# This is equivalent to creating a Bézier with [0, 0, pull_factor, pull_factor, 1, 1, 1]
|
|
248
|
+
# and evaluating it at t.
|
|
249
|
+
return (
|
|
250
|
+
15 * t2 * mt4 * pull_factor
|
|
251
|
+
+ 20 * t3 * mt3 * pull_factor
|
|
252
|
+
+ 15 * t4 * mt2
|
|
253
|
+
+ 6 * t5 * mt
|
|
254
|
+
+ t6
|
|
255
|
+
)
|
|
204
256
|
|
|
205
257
|
|
|
206
258
|
def not_quite_there(
|
|
207
|
-
func:
|
|
259
|
+
func: RateFunction = smooth,
|
|
208
260
|
proportion: float = 0.7,
|
|
209
|
-
) ->
|
|
210
|
-
def result(t):
|
|
211
|
-
return proportion * func(t)
|
|
261
|
+
) -> RateFunction:
|
|
262
|
+
def result(t: float, *args: Any, **kwargs: Any) -> float:
|
|
263
|
+
return proportion * func(t, *args, **kwargs)
|
|
212
264
|
|
|
213
265
|
return result
|
|
214
266
|
|
|
215
267
|
|
|
216
268
|
@zero
|
|
217
269
|
def wiggle(t: float, wiggles: float = 2) -> float:
|
|
218
|
-
|
|
270
|
+
val: float = np.sin(wiggles * np.pi * t)
|
|
271
|
+
return there_and_back(t) * val
|
|
219
272
|
|
|
220
273
|
|
|
221
274
|
def squish_rate_func(
|
|
222
|
-
func:
|
|
275
|
+
func: RateFunction,
|
|
223
276
|
a: float = 0.4,
|
|
224
277
|
b: float = 0.6,
|
|
225
|
-
) ->
|
|
226
|
-
def result(t):
|
|
278
|
+
) -> RateFunction:
|
|
279
|
+
def result(t: float, *args: Any, **kwargs: Any) -> float:
|
|
227
280
|
if a == b:
|
|
228
281
|
return a
|
|
229
282
|
|
|
230
283
|
if t < a:
|
|
231
|
-
|
|
284
|
+
new_t = 0.0
|
|
232
285
|
elif t > b:
|
|
233
|
-
|
|
286
|
+
new_t = 1.0
|
|
234
287
|
else:
|
|
235
|
-
|
|
288
|
+
new_t = (t - a) / (b - a)
|
|
289
|
+
return func(new_t, *args, **kwargs)
|
|
236
290
|
|
|
237
291
|
return result
|
|
238
292
|
|
|
@@ -245,29 +299,37 @@ def squish_rate_func(
|
|
|
245
299
|
|
|
246
300
|
@unit_interval
|
|
247
301
|
def lingering(t: float) -> float:
|
|
248
|
-
|
|
302
|
+
def identity(t: float) -> float:
|
|
303
|
+
return t
|
|
304
|
+
|
|
305
|
+
# TODO: Isn't this just 0.8 * t?
|
|
306
|
+
return squish_rate_func(identity, 0, 0.8)(t)
|
|
249
307
|
|
|
250
308
|
|
|
251
309
|
@unit_interval
|
|
252
310
|
def exponential_decay(t: float, half_life: float = 0.1) -> float:
|
|
253
311
|
# The half-life should be rather small to minimize
|
|
254
312
|
# the cut-off error at the end
|
|
255
|
-
|
|
313
|
+
val: float = 1 - np.exp(-t / half_life)
|
|
314
|
+
return val
|
|
256
315
|
|
|
257
316
|
|
|
258
317
|
@unit_interval
|
|
259
318
|
def ease_in_sine(t: float) -> float:
|
|
260
|
-
|
|
319
|
+
val: float = 1 - np.cos((t * np.pi) / 2)
|
|
320
|
+
return val
|
|
261
321
|
|
|
262
322
|
|
|
263
323
|
@unit_interval
|
|
264
324
|
def ease_out_sine(t: float) -> float:
|
|
265
|
-
|
|
325
|
+
val: float = np.sin((t * np.pi) / 2)
|
|
326
|
+
return val
|
|
266
327
|
|
|
267
328
|
|
|
268
329
|
@unit_interval
|
|
269
330
|
def ease_in_out_sine(t: float) -> float:
|
|
270
|
-
|
|
331
|
+
val: float = -(np.cos(np.pi * t) - 1) / 2
|
|
332
|
+
return val
|
|
271
333
|
|
|
272
334
|
|
|
273
335
|
@unit_interval
|
|
@@ -404,7 +466,8 @@ def ease_in_elastic(t: float) -> float:
|
|
|
404
466
|
elif t == 1:
|
|
405
467
|
return 1
|
|
406
468
|
else:
|
|
407
|
-
|
|
469
|
+
val: float = -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
|
|
470
|
+
return val
|
|
408
471
|
|
|
409
472
|
|
|
410
473
|
@unit_interval
|
|
@@ -415,7 +478,8 @@ def ease_out_elastic(t: float) -> float:
|
|
|
415
478
|
elif t == 1:
|
|
416
479
|
return 1
|
|
417
480
|
else:
|
|
418
|
-
|
|
481
|
+
val: float = pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
|
|
482
|
+
return val
|
|
419
483
|
|
|
420
484
|
|
|
421
485
|
@unit_interval
|
|
@@ -426,9 +490,11 @@ def ease_in_out_elastic(t: float) -> float:
|
|
|
426
490
|
elif t == 1:
|
|
427
491
|
return 1
|
|
428
492
|
elif t < 0.5:
|
|
429
|
-
|
|
493
|
+
val: float = -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
|
|
494
|
+
return val
|
|
430
495
|
else:
|
|
431
|
-
|
|
496
|
+
val = (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
|
|
497
|
+
return val
|
|
432
498
|
|
|
433
499
|
|
|
434
500
|
@unit_interval
|
manim/utils/simple_functions.py
CHANGED
|
@@ -6,27 +6,25 @@ __all__ = [
|
|
|
6
6
|
"binary_search",
|
|
7
7
|
"choose",
|
|
8
8
|
"clip",
|
|
9
|
-
"get_parameters",
|
|
10
9
|
"sigmoid",
|
|
11
10
|
]
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
import
|
|
13
|
+
from collections.abc import Callable
|
|
15
14
|
from functools import lru_cache
|
|
16
|
-
from
|
|
17
|
-
from typing import Callable
|
|
15
|
+
from typing import Any, Protocol, TypeVar
|
|
18
16
|
|
|
19
17
|
import numpy as np
|
|
20
18
|
from scipy import special
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def binary_search(
|
|
24
|
-
function: Callable[[
|
|
25
|
-
target:
|
|
26
|
-
lower_bound:
|
|
27
|
-
upper_bound:
|
|
28
|
-
tolerance:
|
|
29
|
-
) ->
|
|
22
|
+
function: Callable[[float], float],
|
|
23
|
+
target: float,
|
|
24
|
+
lower_bound: float,
|
|
25
|
+
upper_bound: float,
|
|
26
|
+
tolerance: float = 1e-4,
|
|
27
|
+
) -> float | None:
|
|
30
28
|
"""Searches for a value in a range by repeatedly dividing the range in half.
|
|
31
29
|
|
|
32
30
|
To be more precise, performs numerical binary search to determine the
|
|
@@ -43,10 +41,10 @@ def binary_search(
|
|
|
43
41
|
::
|
|
44
42
|
|
|
45
43
|
>>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5)
|
|
46
|
-
>>> abs(solution - 2) < 1e-4
|
|
44
|
+
>>> bool(abs(solution - 2) < 1e-4)
|
|
47
45
|
True
|
|
48
46
|
>>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5, tolerance=0.01)
|
|
49
|
-
>>> abs(solution - 2) < 0.01
|
|
47
|
+
>>> bool(abs(solution - 2) < 0.01)
|
|
50
48
|
True
|
|
51
49
|
|
|
52
50
|
Searching in the interval :math:`[0, 5]` for a target value of :math:`71`
|
|
@@ -57,7 +55,7 @@ def binary_search(
|
|
|
57
55
|
"""
|
|
58
56
|
lh = lower_bound
|
|
59
57
|
rh = upper_bound
|
|
60
|
-
mh = np.mean(np.array([lh, rh]))
|
|
58
|
+
mh: float = np.mean(np.array([lh, rh]))
|
|
61
59
|
while abs(rh - lh) > tolerance:
|
|
62
60
|
mh = np.mean(np.array([lh, rh]))
|
|
63
61
|
lx, mx, rx = (function(h) for h in (lh, mh, rh))
|
|
@@ -91,10 +89,20 @@ def choose(n: int, k: int) -> int:
|
|
|
91
89
|
- https://en.wikipedia.org/wiki/Combination
|
|
92
90
|
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html
|
|
93
91
|
"""
|
|
94
|
-
|
|
92
|
+
value: int = special.comb(n, k, exact=True)
|
|
93
|
+
return value
|
|
95
94
|
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
class Comparable(Protocol):
|
|
97
|
+
def __lt__(self, other: Any) -> bool: ...
|
|
98
|
+
|
|
99
|
+
def __gt__(self, other: Any) -> bool: ...
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
ComparableT = TypeVar("ComparableT", bound=Comparable)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def clip(a: ComparableT, min_a: ComparableT, max_a: ComparableT) -> ComparableT:
|
|
98
106
|
"""Clips ``a`` to the interval [``min_a``, ``max_a``].
|
|
99
107
|
|
|
100
108
|
Accepts any comparable objects (i.e. those that support <, >).
|
|
@@ -117,23 +125,6 @@ def clip(a, min_a, max_a):
|
|
|
117
125
|
return a
|
|
118
126
|
|
|
119
127
|
|
|
120
|
-
def get_parameters(function: Callable) -> MappingProxyType[str, inspect.Parameter]:
|
|
121
|
-
"""Return the parameters of ``function`` as an ordered mapping of parameters'
|
|
122
|
-
names to their corresponding ``Parameter`` objects.
|
|
123
|
-
|
|
124
|
-
Examples
|
|
125
|
-
--------
|
|
126
|
-
::
|
|
127
|
-
|
|
128
|
-
>>> get_parameters(get_parameters)
|
|
129
|
-
mappingproxy(OrderedDict([('function', <Parameter "function: 'Callable'">)]))
|
|
130
|
-
|
|
131
|
-
>>> tuple(get_parameters(choose))
|
|
132
|
-
('n', 'k')
|
|
133
|
-
"""
|
|
134
|
-
return inspect.signature(function).parameters
|
|
135
|
-
|
|
136
|
-
|
|
137
128
|
def sigmoid(x: float) -> float:
|
|
138
129
|
r"""Returns the output of the logistic function.
|
|
139
130
|
|
|
@@ -145,4 +136,5 @@ def sigmoid(x: float) -> float:
|
|
|
145
136
|
- https://en.wikipedia.org/wiki/Sigmoid_function
|
|
146
137
|
- https://en.wikipedia.org/wiki/Logistic_function
|
|
147
138
|
"""
|
|
148
|
-
|
|
139
|
+
value: float = 1.0 / (1 + np.exp(-x))
|
|
140
|
+
return value
|
manim/utils/sounds.py
CHANGED
|
@@ -6,13 +6,19 @@ __all__ = [
|
|
|
6
6
|
"get_full_sound_file_path",
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
9
10
|
|
|
10
11
|
from .. import config
|
|
11
12
|
from ..utils.file_ops import seek_full_path_from_defaults
|
|
12
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from manim.typing import StrPath
|
|
18
|
+
|
|
13
19
|
|
|
14
20
|
# Still in use by add_sound() function in scene_file_writer.py
|
|
15
|
-
def get_full_sound_file_path(sound_file_name):
|
|
21
|
+
def get_full_sound_file_path(sound_file_name: StrPath) -> Path:
|
|
16
22
|
return seek_full_path_from_defaults(
|
|
17
23
|
sound_file_name,
|
|
18
24
|
default_dir=config.get_dir("assets_dir"),
|