@redwilly/anima 0.1.0
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.
- package/dist/cli/SceneLoader.d.ts +22 -0
- package/dist/cli/SceneLoader.js +47 -0
- package/dist/cli/commands/export-frame.d.ts +13 -0
- package/dist/cli/commands/export-frame.js +60 -0
- package/dist/cli/commands/list-scenes.d.ts +5 -0
- package/dist/cli/commands/list-scenes.js +22 -0
- package/dist/cli/commands/preview.d.ts +5 -0
- package/dist/cli/commands/preview.js +11 -0
- package/dist/cli/commands/render.d.ts +16 -0
- package/dist/cli/commands/render.js +76 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +63 -0
- package/dist/core/animations/Animation.d.ts +41 -0
- package/dist/core/animations/Animation.js +76 -0
- package/dist/core/animations/camera/Follow.d.ts +70 -0
- package/dist/core/animations/camera/Follow.js +69 -0
- package/dist/core/animations/camera/Shake.d.ts +90 -0
- package/dist/core/animations/camera/Shake.js +87 -0
- package/dist/core/animations/camera/index.d.ts +2 -0
- package/dist/core/animations/camera/index.js +2 -0
- package/dist/core/animations/categories/ExitAnimation.d.ts +17 -0
- package/dist/core/animations/categories/ExitAnimation.js +15 -0
- package/dist/core/animations/categories/IntroductoryAnimation.d.ts +16 -0
- package/dist/core/animations/categories/IntroductoryAnimation.js +14 -0
- package/dist/core/animations/categories/TransformativeAnimation.d.ts +25 -0
- package/dist/core/animations/categories/TransformativeAnimation.js +25 -0
- package/dist/core/animations/categories/index.d.ts +3 -0
- package/dist/core/animations/categories/index.js +3 -0
- package/dist/core/animations/composition/Parallel.d.ts +37 -0
- package/dist/core/animations/composition/Parallel.js +79 -0
- package/dist/core/animations/composition/Sequence.d.ts +41 -0
- package/dist/core/animations/composition/Sequence.js +95 -0
- package/dist/core/animations/composition/index.d.ts +2 -0
- package/dist/core/animations/composition/index.js +3 -0
- package/dist/core/animations/draw/Draw.d.ts +30 -0
- package/dist/core/animations/draw/Draw.js +122 -0
- package/dist/core/animations/draw/Unwrite.d.ts +30 -0
- package/dist/core/animations/draw/Unwrite.js +120 -0
- package/dist/core/animations/draw/Write.d.ts +35 -0
- package/dist/core/animations/draw/Write.js +119 -0
- package/dist/core/animations/draw/index.d.ts +3 -0
- package/dist/core/animations/draw/index.js +3 -0
- package/dist/core/animations/draw/partialPath.d.ts +6 -0
- package/dist/core/animations/draw/partialPath.js +138 -0
- package/dist/core/animations/easing/bounce.d.ts +13 -0
- package/dist/core/animations/easing/bounce.js +37 -0
- package/dist/core/animations/easing/index.d.ts +7 -0
- package/dist/core/animations/easing/index.js +11 -0
- package/dist/core/animations/easing/manim.d.ts +46 -0
- package/dist/core/animations/easing/manim.js +102 -0
- package/dist/core/animations/easing/registry.d.ts +8 -0
- package/dist/core/animations/easing/registry.js +25 -0
- package/dist/core/animations/easing/standard.d.ts +113 -0
- package/dist/core/animations/easing/standard.js +151 -0
- package/dist/core/animations/easing/types.d.ts +6 -0
- package/dist/core/animations/easing/types.js +0 -0
- package/dist/core/animations/fade/FadeIn.d.ts +17 -0
- package/dist/core/animations/fade/FadeIn.js +22 -0
- package/dist/core/animations/fade/FadeOut.d.ts +17 -0
- package/dist/core/animations/fade/FadeOut.js +23 -0
- package/dist/core/animations/fade/index.d.ts +2 -0
- package/dist/core/animations/fade/index.js +2 -0
- package/dist/core/animations/index.d.ts +11 -0
- package/dist/core/animations/index.js +17 -0
- package/dist/core/animations/keyframes/KeyframeAnimation.d.ts +33 -0
- package/dist/core/animations/keyframes/KeyframeAnimation.js +40 -0
- package/dist/core/animations/keyframes/KeyframeTrack.d.ts +31 -0
- package/dist/core/animations/keyframes/KeyframeTrack.js +83 -0
- package/dist/core/animations/keyframes/index.d.ts +4 -0
- package/dist/core/animations/keyframes/index.js +5 -0
- package/dist/core/animations/keyframes/types.d.ts +25 -0
- package/dist/core/animations/keyframes/types.js +6 -0
- package/dist/core/animations/morph/MorphTo.d.ts +22 -0
- package/dist/core/animations/morph/MorphTo.js +42 -0
- package/dist/core/animations/morph/index.d.ts +1 -0
- package/dist/core/animations/morph/index.js +1 -0
- package/dist/core/animations/transform/MoveTo.d.ts +24 -0
- package/dist/core/animations/transform/MoveTo.js +38 -0
- package/dist/core/animations/transform/Rotate.d.ts +23 -0
- package/dist/core/animations/transform/Rotate.js +34 -0
- package/dist/core/animations/transform/Scale.d.ts +23 -0
- package/dist/core/animations/transform/Scale.js +35 -0
- package/dist/core/animations/transform/index.d.ts +3 -0
- package/dist/core/animations/transform/index.js +3 -0
- package/dist/core/animations/types.d.ts +52 -0
- package/dist/core/animations/types.js +6 -0
- package/dist/core/camera/Camera.d.ts +87 -0
- package/dist/core/camera/Camera.js +175 -0
- package/dist/core/camera/CameraFrame.d.ts +242 -0
- package/dist/core/camera/CameraFrame.js +322 -0
- package/dist/core/camera/index.d.ts +4 -0
- package/dist/core/camera/index.js +3 -0
- package/dist/core/camera/types.d.ts +17 -0
- package/dist/core/camera/types.js +1 -0
- package/dist/core/errors/AnimationErrors.d.ts +12 -0
- package/dist/core/errors/AnimationErrors.js +37 -0
- package/dist/core/errors/index.d.ts +1 -0
- package/dist/core/errors/index.js +1 -0
- package/dist/core/math/Vector2/Vector2.d.ts +23 -0
- package/dist/core/math/Vector2/Vector2.js +46 -0
- package/dist/core/math/Vector2/index.d.ts +1 -0
- package/dist/core/math/Vector2/index.js +1 -0
- package/dist/core/math/bezier/BezierPath.d.ts +38 -0
- package/dist/core/math/bezier/BezierPath.js +264 -0
- package/dist/core/math/bezier/evaluators.d.ts +9 -0
- package/dist/core/math/bezier/evaluators.js +36 -0
- package/dist/core/math/bezier/index.d.ts +8 -0
- package/dist/core/math/bezier/index.js +6 -0
- package/dist/core/math/bezier/length.d.ts +5 -0
- package/dist/core/math/bezier/length.js +27 -0
- package/dist/core/math/bezier/morphing.d.ts +16 -0
- package/dist/core/math/bezier/morphing.js +151 -0
- package/dist/core/math/bezier/sampling.d.ts +7 -0
- package/dist/core/math/bezier/sampling.js +153 -0
- package/dist/core/math/bezier/split.d.ts +19 -0
- package/dist/core/math/bezier/split.js +44 -0
- package/dist/core/math/bezier/types.d.ts +8 -0
- package/dist/core/math/bezier/types.js +0 -0
- package/dist/core/math/color/Color.d.ts +28 -0
- package/dist/core/math/color/Color.js +60 -0
- package/dist/core/math/color/conversions.d.ts +17 -0
- package/dist/core/math/color/conversions.js +100 -0
- package/dist/core/math/color/index.d.ts +2 -0
- package/dist/core/math/color/index.js +2 -0
- package/dist/core/math/index.d.ts +4 -0
- package/dist/core/math/index.js +5 -0
- package/dist/core/math/matrix/Matrix3x3.d.ts +23 -0
- package/dist/core/math/matrix/Matrix3x3.js +91 -0
- package/dist/core/math/matrix/factories.d.ts +12 -0
- package/dist/core/math/matrix/factories.js +44 -0
- package/dist/core/math/matrix/index.d.ts +2 -0
- package/dist/core/math/matrix/index.js +2 -0
- package/dist/core/renderer/FrameRenderer.d.ts +37 -0
- package/dist/core/renderer/FrameRenderer.js +75 -0
- package/dist/core/renderer/ProgressReporter.d.ts +19 -0
- package/dist/core/renderer/ProgressReporter.js +58 -0
- package/dist/core/renderer/Renderer.d.ts +36 -0
- package/dist/core/renderer/Renderer.js +102 -0
- package/dist/core/renderer/drawMobject.d.ts +8 -0
- package/dist/core/renderer/drawMobject.js +109 -0
- package/dist/core/renderer/formats/index.d.ts +3 -0
- package/dist/core/renderer/formats/index.js +3 -0
- package/dist/core/renderer/formats/png.d.ts +5 -0
- package/dist/core/renderer/formats/png.js +7 -0
- package/dist/core/renderer/formats/sprite.d.ts +6 -0
- package/dist/core/renderer/formats/sprite.js +24 -0
- package/dist/core/renderer/formats/video.d.ts +8 -0
- package/dist/core/renderer/formats/video.js +51 -0
- package/dist/core/renderer/index.d.ts +7 -0
- package/dist/core/renderer/index.js +9 -0
- package/dist/core/renderer/types.d.ts +87 -0
- package/dist/core/renderer/types.js +13 -0
- package/dist/core/scene/Scene.d.ts +104 -0
- package/dist/core/scene/Scene.js +225 -0
- package/dist/core/scene/index.d.ts +2 -0
- package/dist/core/scene/index.js +1 -0
- package/dist/core/scene/types.d.ts +23 -0
- package/dist/core/scene/types.js +0 -0
- package/dist/core/serialization/animation.d.ts +23 -0
- package/dist/core/serialization/animation.js +176 -0
- package/dist/core/serialization/easingLookup.d.ts +13 -0
- package/dist/core/serialization/easingLookup.js +65 -0
- package/dist/core/serialization/index.d.ts +23 -0
- package/dist/core/serialization/index.js +29 -0
- package/dist/core/serialization/mobject.d.ts +23 -0
- package/dist/core/serialization/mobject.js +248 -0
- package/dist/core/serialization/prettyPrint.d.ts +12 -0
- package/dist/core/serialization/prettyPrint.js +16 -0
- package/dist/core/serialization/primitives.d.ts +24 -0
- package/dist/core/serialization/primitives.js +98 -0
- package/dist/core/serialization/registry.d.ts +29 -0
- package/dist/core/serialization/registry.js +39 -0
- package/dist/core/serialization/scene.d.ts +28 -0
- package/dist/core/serialization/scene.js +114 -0
- package/dist/core/serialization/types.d.ts +152 -0
- package/dist/core/serialization/types.js +6 -0
- package/dist/core/timeline/Timeline.d.ts +70 -0
- package/dist/core/timeline/Timeline.js +144 -0
- package/dist/core/timeline/index.d.ts +5 -0
- package/dist/core/timeline/index.js +4 -0
- package/dist/core/timeline/types.d.ts +29 -0
- package/dist/core/timeline/types.js +0 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +22 -0
- package/dist/mobjects/Mobject.d.ts +98 -0
- package/dist/mobjects/Mobject.js +343 -0
- package/dist/mobjects/VGroup/VGroup.d.ts +51 -0
- package/dist/mobjects/VGroup/VGroup.js +142 -0
- package/dist/mobjects/VGroup/index.d.ts +3 -0
- package/dist/mobjects/VGroup/index.js +2 -0
- package/dist/mobjects/VGroup/layout.d.ts +20 -0
- package/dist/mobjects/VGroup/layout.js +139 -0
- package/dist/mobjects/VMobject.d.ts +106 -0
- package/dist/mobjects/VMobject.js +216 -0
- package/dist/mobjects/geometry/Arc.d.ts +8 -0
- package/dist/mobjects/geometry/Arc.js +46 -0
- package/dist/mobjects/geometry/Arrow.d.ts +7 -0
- package/dist/mobjects/geometry/Arrow.js +34 -0
- package/dist/mobjects/geometry/Circle.d.ts +4 -0
- package/dist/mobjects/geometry/Circle.js +10 -0
- package/dist/mobjects/geometry/Line.d.ts +8 -0
- package/dist/mobjects/geometry/Line.js +19 -0
- package/dist/mobjects/geometry/Point.d.ts +5 -0
- package/dist/mobjects/geometry/Point.js +11 -0
- package/dist/mobjects/geometry/Polygon.d.ts +7 -0
- package/dist/mobjects/geometry/Polygon.js +21 -0
- package/dist/mobjects/geometry/Rectangle.d.ts +6 -0
- package/dist/mobjects/geometry/Rectangle.js +18 -0
- package/dist/mobjects/geometry/index.d.ts +7 -0
- package/dist/mobjects/geometry/index.js +7 -0
- package/dist/mobjects/graph/Graph.d.ts +28 -0
- package/dist/mobjects/graph/Graph.js +119 -0
- package/dist/mobjects/graph/GraphEdge.d.ts +26 -0
- package/dist/mobjects/graph/GraphEdge.js +64 -0
- package/dist/mobjects/graph/GraphNode.d.ts +19 -0
- package/dist/mobjects/graph/GraphNode.js +63 -0
- package/dist/mobjects/graph/index.d.ts +5 -0
- package/dist/mobjects/graph/index.js +5 -0
- package/dist/mobjects/graph/layouts/circular.d.ts +8 -0
- package/dist/mobjects/graph/layouts/circular.js +23 -0
- package/dist/mobjects/graph/layouts/forceDirected.d.ts +9 -0
- package/dist/mobjects/graph/layouts/forceDirected.js +102 -0
- package/dist/mobjects/graph/layouts/index.d.ts +3 -0
- package/dist/mobjects/graph/layouts/index.js +3 -0
- package/dist/mobjects/graph/layouts/tree.d.ts +9 -0
- package/dist/mobjects/graph/layouts/tree.js +99 -0
- package/dist/mobjects/graph/types.d.ts +35 -0
- package/dist/mobjects/graph/types.js +0 -0
- package/dist/mobjects/index.d.ts +6 -0
- package/dist/mobjects/index.js +6 -0
- package/dist/mobjects/text/Glyph.d.ts +11 -0
- package/dist/mobjects/text/Glyph.js +72 -0
- package/dist/mobjects/text/Text.d.ts +19 -0
- package/dist/mobjects/text/Text.js +76 -0
- package/dist/mobjects/text/index.d.ts +4 -0
- package/dist/mobjects/text/index.js +3 -0
- package/dist/mobjects/text/types.d.ts +12 -0
- package/dist/mobjects/text/types.js +8 -0
- package/package.json +51 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
import { getTangentAtPath } from './sampling';
|
|
3
|
+
import { getQuadraticLength, getCubicLength } from './length';
|
|
4
|
+
import { evaluateQuadratic, evaluateCubic } from './evaluators';
|
|
5
|
+
import { toCubicCommands, subdividePath } from './morphing';
|
|
6
|
+
/**
|
|
7
|
+
* A class representing a Bezier path, capable of storing standard path commands
|
|
8
|
+
* (move, line, quadratic curve, cubic curve, close).
|
|
9
|
+
*/
|
|
10
|
+
export class BezierPath {
|
|
11
|
+
commands = [];
|
|
12
|
+
currentPoint = Vector2.ZERO;
|
|
13
|
+
startPoint = Vector2.ZERO;
|
|
14
|
+
// Caching for O(1) length and O(log N) point sampling
|
|
15
|
+
cachedLength = null;
|
|
16
|
+
segmentLengths = [];
|
|
17
|
+
segmentCDF = [];
|
|
18
|
+
/** Invalidates the cached length data. Called after any path modification. */
|
|
19
|
+
invalidateCache() {
|
|
20
|
+
this.cachedLength = null;
|
|
21
|
+
this.segmentLengths = [];
|
|
22
|
+
this.segmentCDF = [];
|
|
23
|
+
}
|
|
24
|
+
/** Builds the cache if not already valid. */
|
|
25
|
+
ensureCache() {
|
|
26
|
+
if (this.cachedLength !== null)
|
|
27
|
+
return;
|
|
28
|
+
this.segmentLengths = [];
|
|
29
|
+
this.segmentCDF = [];
|
|
30
|
+
let totalLength = 0;
|
|
31
|
+
let cursor = Vector2.ZERO;
|
|
32
|
+
let subpathStart = Vector2.ZERO;
|
|
33
|
+
for (const cmd of this.commands) {
|
|
34
|
+
let segmentLength = 0;
|
|
35
|
+
switch (cmd.type) {
|
|
36
|
+
case 'Move':
|
|
37
|
+
cursor = cmd.end;
|
|
38
|
+
subpathStart = cmd.end;
|
|
39
|
+
break;
|
|
40
|
+
case 'Line':
|
|
41
|
+
segmentLength = cursor.subtract(cmd.end).length();
|
|
42
|
+
cursor = cmd.end;
|
|
43
|
+
break;
|
|
44
|
+
case 'Quadratic':
|
|
45
|
+
if (cmd.control1) {
|
|
46
|
+
segmentLength = getQuadraticLength(cursor, cmd.control1, cmd.end);
|
|
47
|
+
}
|
|
48
|
+
cursor = cmd.end;
|
|
49
|
+
break;
|
|
50
|
+
case 'Cubic':
|
|
51
|
+
if (cmd.control1 && cmd.control2) {
|
|
52
|
+
segmentLength = getCubicLength(cursor, cmd.control1, cmd.control2, cmd.end);
|
|
53
|
+
}
|
|
54
|
+
cursor = cmd.end;
|
|
55
|
+
break;
|
|
56
|
+
case 'Close':
|
|
57
|
+
segmentLength = cursor.subtract(subpathStart).length();
|
|
58
|
+
cursor = subpathStart;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
this.segmentLengths.push(segmentLength);
|
|
62
|
+
totalLength += segmentLength;
|
|
63
|
+
this.segmentCDF.push(totalLength);
|
|
64
|
+
}
|
|
65
|
+
this.cachedLength = totalLength;
|
|
66
|
+
}
|
|
67
|
+
/** Starts a new subpath at the specified point. */
|
|
68
|
+
moveTo(point) {
|
|
69
|
+
this.invalidateCache();
|
|
70
|
+
this.commands.push({ type: 'Move', end: point });
|
|
71
|
+
this.currentPoint = point;
|
|
72
|
+
this.startPoint = point;
|
|
73
|
+
}
|
|
74
|
+
lineTo(point) {
|
|
75
|
+
this.invalidateCache();
|
|
76
|
+
this.commands.push({ type: 'Line', end: point });
|
|
77
|
+
this.currentPoint = point;
|
|
78
|
+
}
|
|
79
|
+
quadraticTo(control, end) {
|
|
80
|
+
this.invalidateCache();
|
|
81
|
+
this.commands.push({ type: 'Quadratic', control1: control, end: end });
|
|
82
|
+
this.currentPoint = end;
|
|
83
|
+
}
|
|
84
|
+
cubicTo(control1, control2, end) {
|
|
85
|
+
this.invalidateCache();
|
|
86
|
+
this.commands.push({
|
|
87
|
+
type: 'Cubic',
|
|
88
|
+
control1: control1,
|
|
89
|
+
control2: control2,
|
|
90
|
+
end: end
|
|
91
|
+
});
|
|
92
|
+
this.currentPoint = end;
|
|
93
|
+
}
|
|
94
|
+
closePath() {
|
|
95
|
+
this.invalidateCache();
|
|
96
|
+
this.commands.push({ type: 'Close', end: this.startPoint });
|
|
97
|
+
this.currentPoint = this.startPoint;
|
|
98
|
+
}
|
|
99
|
+
getCommands() {
|
|
100
|
+
return [...this.commands];
|
|
101
|
+
}
|
|
102
|
+
getLength() {
|
|
103
|
+
this.ensureCache();
|
|
104
|
+
return this.cachedLength;
|
|
105
|
+
}
|
|
106
|
+
/** Returns the point on the path at the normalized position t (0-1). */
|
|
107
|
+
getPointAt(t) {
|
|
108
|
+
this.ensureCache();
|
|
109
|
+
const totalLength = this.cachedLength;
|
|
110
|
+
if (totalLength === 0 || this.commands.length === 0) {
|
|
111
|
+
return this.commands.length > 0 ? this.commands[this.commands.length - 1].end : Vector2.ZERO;
|
|
112
|
+
}
|
|
113
|
+
t = Math.max(0, Math.min(1, t));
|
|
114
|
+
const targetDistance = t * totalLength;
|
|
115
|
+
// Binary search for the segment containing targetDistance
|
|
116
|
+
let low = 0;
|
|
117
|
+
let high = this.segmentCDF.length - 1;
|
|
118
|
+
while (low < high) {
|
|
119
|
+
const mid = (low + high) >>> 1;
|
|
120
|
+
if (this.segmentCDF[mid] < targetDistance) {
|
|
121
|
+
low = mid + 1;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
high = mid;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const segmentIndex = low;
|
|
128
|
+
const cmd = this.commands[segmentIndex];
|
|
129
|
+
if (!cmd)
|
|
130
|
+
return Vector2.ZERO;
|
|
131
|
+
// Calculate the starting cursor for this segment
|
|
132
|
+
let cursor = Vector2.ZERO;
|
|
133
|
+
let subpathStart = Vector2.ZERO;
|
|
134
|
+
for (let i = 0; i < segmentIndex; i++) {
|
|
135
|
+
const c = this.commands[i];
|
|
136
|
+
if (c.type === 'Move') {
|
|
137
|
+
cursor = c.end;
|
|
138
|
+
subpathStart = c.end;
|
|
139
|
+
}
|
|
140
|
+
else if (c.type === 'Close') {
|
|
141
|
+
cursor = subpathStart;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
cursor = c.end;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Calculate local t within this segment
|
|
148
|
+
const segmentStart = segmentIndex > 0 ? this.segmentCDF[segmentIndex - 1] : 0;
|
|
149
|
+
const segmentLength = this.segmentLengths[segmentIndex];
|
|
150
|
+
const localT = segmentLength > 0 ? (targetDistance - segmentStart) / segmentLength : 0;
|
|
151
|
+
// Evaluate the point at localT
|
|
152
|
+
switch (cmd.type) {
|
|
153
|
+
case 'Move':
|
|
154
|
+
return cmd.end;
|
|
155
|
+
case 'Line':
|
|
156
|
+
return cursor.lerp(cmd.end, localT);
|
|
157
|
+
case 'Quadratic':
|
|
158
|
+
if (cmd.control1) {
|
|
159
|
+
return evaluateQuadratic(cursor, cmd.control1, cmd.end, localT);
|
|
160
|
+
}
|
|
161
|
+
return cmd.end;
|
|
162
|
+
case 'Cubic':
|
|
163
|
+
if (cmd.control1 && cmd.control2) {
|
|
164
|
+
return evaluateCubic(cursor, cmd.control1, cmd.control2, cmd.end, localT);
|
|
165
|
+
}
|
|
166
|
+
return cmd.end;
|
|
167
|
+
case 'Close':
|
|
168
|
+
return cursor.lerp(subpathStart, localT);
|
|
169
|
+
default:
|
|
170
|
+
return cursor;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
getTangentAt(t) {
|
|
174
|
+
return getTangentAtPath(this.commands, t);
|
|
175
|
+
}
|
|
176
|
+
getPoints(count) {
|
|
177
|
+
const points = [];
|
|
178
|
+
if (count <= 0)
|
|
179
|
+
return points;
|
|
180
|
+
if (count === 1)
|
|
181
|
+
return [this.getPointAt(0)];
|
|
182
|
+
for (let i = 0; i < count; i++) {
|
|
183
|
+
const t = i / (count - 1);
|
|
184
|
+
points.push(this.getPointAt(t));
|
|
185
|
+
}
|
|
186
|
+
return points;
|
|
187
|
+
}
|
|
188
|
+
getPointCount() {
|
|
189
|
+
return this.commands.length;
|
|
190
|
+
}
|
|
191
|
+
clone() {
|
|
192
|
+
const newPath = new BezierPath();
|
|
193
|
+
newPath.commands = this.commands.map(cmd => ({ ...cmd }));
|
|
194
|
+
newPath.currentPoint = this.currentPoint;
|
|
195
|
+
newPath.startPoint = this.startPoint;
|
|
196
|
+
return newPath;
|
|
197
|
+
}
|
|
198
|
+
/** Returns a new BezierPath where all segments are converted to Cubic curves. */
|
|
199
|
+
toCubic() {
|
|
200
|
+
const newPath = new BezierPath();
|
|
201
|
+
const cubicCmds = toCubicCommands(this.commands);
|
|
202
|
+
for (const cmd of cubicCmds) {
|
|
203
|
+
if (cmd.type === 'Move') {
|
|
204
|
+
newPath.moveTo(cmd.end);
|
|
205
|
+
}
|
|
206
|
+
else if (cmd.type === 'Cubic' && cmd.control1 && cmd.control2) {
|
|
207
|
+
newPath.cubicTo(cmd.control1, cmd.control2, cmd.end);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return newPath;
|
|
211
|
+
}
|
|
212
|
+
static interpolate(path1, path2, t) {
|
|
213
|
+
const [p1, p2] = BezierPath.matchPoints(path1, path2);
|
|
214
|
+
const result = new BezierPath();
|
|
215
|
+
const cmds1 = p1.commands;
|
|
216
|
+
const cmds2 = p2.commands;
|
|
217
|
+
for (let i = 0; i < cmds1.length; i++) {
|
|
218
|
+
const c1 = cmds1[i];
|
|
219
|
+
const c2 = cmds2[i];
|
|
220
|
+
if (c1.type === 'Move' && c2.type === 'Move') {
|
|
221
|
+
result.moveTo(c1.end.lerp(c2.end, t));
|
|
222
|
+
}
|
|
223
|
+
else if (c1.type === 'Cubic' && c2.type === 'Cubic') {
|
|
224
|
+
if (c1.control1 && c1.control2 && c2.control1 && c2.control2) {
|
|
225
|
+
result.cubicTo(c1.control1.lerp(c2.control1, t), c1.control2.lerp(c2.control2, t), c1.end.lerp(c2.end, t));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
result.moveTo(c1.end.lerp(c2.end, t));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/** Matches the number of points/commands in two paths for morphing. */
|
|
235
|
+
static matchPoints(path1, path2) {
|
|
236
|
+
let p1 = path1.toCubic();
|
|
237
|
+
let p2 = path2.toCubic();
|
|
238
|
+
const count1 = p1.commands.length;
|
|
239
|
+
const count2 = p2.commands.length;
|
|
240
|
+
if (count1 === count2)
|
|
241
|
+
return [p1, p2];
|
|
242
|
+
if (count1 < count2) {
|
|
243
|
+
const subdivided = subdividePath(p1.commands, count2);
|
|
244
|
+
p1 = BezierPath.fromCommands(subdivided);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const subdivided = subdividePath(p2.commands, count1);
|
|
248
|
+
p2 = BezierPath.fromCommands(subdivided);
|
|
249
|
+
}
|
|
250
|
+
return [p1, p2];
|
|
251
|
+
}
|
|
252
|
+
static fromCommands(commands) {
|
|
253
|
+
const path = new BezierPath();
|
|
254
|
+
for (const cmd of commands) {
|
|
255
|
+
if (cmd.type === 'Move') {
|
|
256
|
+
path.moveTo(cmd.end);
|
|
257
|
+
}
|
|
258
|
+
else if (cmd.type === 'Cubic' && cmd.control1 && cmd.control2) {
|
|
259
|
+
path.cubicTo(cmd.control1, cmd.control2, cmd.end);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return path;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
/** Evaluates a point on a quadratic Bezier curve at parameter t (0-1). */
|
|
3
|
+
export declare function evaluateQuadratic(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2;
|
|
4
|
+
/** Evaluates a point on a cubic Bezier curve at parameter t (0-1). */
|
|
5
|
+
export declare function evaluateCubic(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: number): Vector2;
|
|
6
|
+
/** Evaluates the derivative of a quadratic Bezier curve at parameter t (0-1). */
|
|
7
|
+
export declare function evaluateQuadraticDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2;
|
|
8
|
+
/** Evaluates the derivative of a cubic Bezier curve at parameter t (0-1). */
|
|
9
|
+
export declare function evaluateCubicDerivative(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: number): Vector2;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Evaluates a point on a quadratic Bezier curve at parameter t (0-1). */
|
|
2
|
+
export function evaluateQuadratic(p0, p1, p2, t) {
|
|
3
|
+
const oneMinusT = 1 - t;
|
|
4
|
+
// (1-t)^2 * p0 + 2(1-t)t * p1 + t^2 * p2
|
|
5
|
+
return p0.multiply(oneMinusT * oneMinusT)
|
|
6
|
+
.add(p1.multiply(2 * oneMinusT * t))
|
|
7
|
+
.add(p2.multiply(t * t));
|
|
8
|
+
}
|
|
9
|
+
/** Evaluates a point on a cubic Bezier curve at parameter t (0-1). */
|
|
10
|
+
export function evaluateCubic(p0, p1, p2, p3, t) {
|
|
11
|
+
const oneMinusT = 1 - t;
|
|
12
|
+
const oneMinusT2 = oneMinusT * oneMinusT;
|
|
13
|
+
const oneMinusT3 = oneMinusT2 * oneMinusT;
|
|
14
|
+
const t2 = t * t;
|
|
15
|
+
const t3 = t2 * t;
|
|
16
|
+
// (1-t)^3 * p0 + 3(1-t)^2 * t * p1 + 3(1-t) * t^2 * p2 + t^3 * p3
|
|
17
|
+
return p0.multiply(oneMinusT3)
|
|
18
|
+
.add(p1.multiply(3 * oneMinusT2 * t))
|
|
19
|
+
.add(p2.multiply(3 * oneMinusT * t2))
|
|
20
|
+
.add(p3.multiply(t3));
|
|
21
|
+
}
|
|
22
|
+
/** Evaluates the derivative of a quadratic Bezier curve at parameter t (0-1). */
|
|
23
|
+
export function evaluateQuadraticDerivative(p0, p1, p2, t) {
|
|
24
|
+
// 2(1-t)(p1-p0) + 2t(p2-p1)
|
|
25
|
+
const oneMinusT = 1 - t;
|
|
26
|
+
return p1.subtract(p0).multiply(2 * oneMinusT)
|
|
27
|
+
.add(p2.subtract(p1).multiply(2 * t));
|
|
28
|
+
}
|
|
29
|
+
/** Evaluates the derivative of a cubic Bezier curve at parameter t (0-1). */
|
|
30
|
+
export function evaluateCubicDerivative(p0, p1, p2, p3, t) {
|
|
31
|
+
// 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2)
|
|
32
|
+
const oneMinusT = 1 - t;
|
|
33
|
+
return p1.subtract(p0).multiply(3 * oneMinusT * oneMinusT)
|
|
34
|
+
.add(p2.subtract(p1).multiply(6 * oneMinusT * t))
|
|
35
|
+
.add(p3.subtract(p2).multiply(3 * t * t));
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { PathCommandType, PathCommand } from './types';
|
|
2
|
+
export { BezierPath } from './BezierPath';
|
|
3
|
+
export { evaluateQuadratic, evaluateCubic, evaluateQuadraticDerivative, evaluateCubicDerivative } from './evaluators';
|
|
4
|
+
export { getQuadraticLength, getCubicLength } from './length';
|
|
5
|
+
export { getPathLength, getPointAtPath, getTangentAtPath } from './sampling';
|
|
6
|
+
export { toCubicCommands, splitCubic, subdividePath } from './morphing';
|
|
7
|
+
export { splitCubicAt, splitQuadraticAt } from './split';
|
|
8
|
+
export type { CubicSegment, QuadraticSegment } from './split';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { BezierPath } from './BezierPath';
|
|
2
|
+
export { evaluateQuadratic, evaluateCubic, evaluateQuadraticDerivative, evaluateCubicDerivative } from './evaluators';
|
|
3
|
+
export { getQuadraticLength, getCubicLength } from './length';
|
|
4
|
+
export { getPathLength, getPointAtPath, getTangentAtPath } from './sampling';
|
|
5
|
+
export { toCubicCommands, splitCubic, subdividePath } from './morphing';
|
|
6
|
+
export { splitCubicAt, splitQuadraticAt } from './split';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
/** Calculates approximate length of a quadratic Bezier curve via subdivision (10 steps). */
|
|
3
|
+
export declare function getQuadraticLength(p0: Vector2, p1: Vector2, p2: Vector2): number;
|
|
4
|
+
/** Calculates approximate length of a cubic Bezier curve via subdivision (20 steps). */
|
|
5
|
+
export declare function getCubicLength(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { evaluateQuadratic, evaluateCubic } from './evaluators';
|
|
2
|
+
/** Calculates approximate length of a quadratic Bezier curve via subdivision (10 steps). */
|
|
3
|
+
export function getQuadraticLength(p0, p1, p2) {
|
|
4
|
+
const steps = 10;
|
|
5
|
+
let length = 0;
|
|
6
|
+
let prev = p0;
|
|
7
|
+
for (let i = 1; i <= steps; i++) {
|
|
8
|
+
const t = i / steps;
|
|
9
|
+
const current = evaluateQuadratic(p0, p1, p2, t);
|
|
10
|
+
length += prev.subtract(current).length();
|
|
11
|
+
prev = current;
|
|
12
|
+
}
|
|
13
|
+
return length;
|
|
14
|
+
}
|
|
15
|
+
/** Calculates approximate length of a cubic Bezier curve via subdivision (20 steps). */
|
|
16
|
+
export function getCubicLength(p0, p1, p2, p3) {
|
|
17
|
+
const steps = 20;
|
|
18
|
+
let length = 0;
|
|
19
|
+
let prev = p0;
|
|
20
|
+
for (let i = 1; i <= steps; i++) {
|
|
21
|
+
const t = i / steps;
|
|
22
|
+
const current = evaluateCubic(p0, p1, p2, p3, t);
|
|
23
|
+
length += prev.subtract(current).length();
|
|
24
|
+
prev = current;
|
|
25
|
+
}
|
|
26
|
+
return length;
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
import type { PathCommand } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a path to use only cubic Bezier curves.
|
|
5
|
+
* Lines and quadratics are converted to equivalent cubics.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toCubicCommands(commands: PathCommand[]): PathCommand[];
|
|
8
|
+
/**
|
|
9
|
+
* Splits a cubic Bezier curve at parameter t using De Casteljau's algorithm.
|
|
10
|
+
*/
|
|
11
|
+
export declare function splitCubic(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: number): [PathCommand, PathCommand];
|
|
12
|
+
/**
|
|
13
|
+
* Subdivides a path to match a target command count.
|
|
14
|
+
* Uses iterative approach to avoid stack overflow on complex paths.
|
|
15
|
+
*/
|
|
16
|
+
export declare function subdividePath(commands: PathCommand[], targetCount: number): PathCommand[];
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a path to use only cubic Bezier curves.
|
|
4
|
+
* Lines and quadratics are converted to equivalent cubics.
|
|
5
|
+
*/
|
|
6
|
+
export function toCubicCommands(commands) {
|
|
7
|
+
const result = [];
|
|
8
|
+
let cursor = new Vector2(0, 0);
|
|
9
|
+
let subpathStart = new Vector2(0, 0);
|
|
10
|
+
for (const cmd of commands) {
|
|
11
|
+
switch (cmd.type) {
|
|
12
|
+
case 'Move':
|
|
13
|
+
result.push({ type: 'Move', end: cmd.end });
|
|
14
|
+
cursor = cmd.end;
|
|
15
|
+
subpathStart = cmd.end;
|
|
16
|
+
break;
|
|
17
|
+
case 'Line': {
|
|
18
|
+
// Convert Line to Cubic: P0, P1 -> P0, P0+(P1-P0)/3, P1-(P1-P0)/3, P1
|
|
19
|
+
const delta = cmd.end.subtract(cursor);
|
|
20
|
+
const c1 = cursor.add(delta.multiply(1 / 3));
|
|
21
|
+
const c2 = cmd.end.subtract(delta.multiply(1 / 3));
|
|
22
|
+
result.push({ type: 'Cubic', control1: c1, control2: c2, end: cmd.end });
|
|
23
|
+
cursor = cmd.end;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'Quadratic':
|
|
27
|
+
if (cmd.control1) {
|
|
28
|
+
// Convert Quadratic to Cubic
|
|
29
|
+
// CP1 = P0 + 2/3 * (QP1 - P0)
|
|
30
|
+
// CP2 = P1 + 2/3 * (QP1 - P1)
|
|
31
|
+
const qp1 = cmd.control1;
|
|
32
|
+
const cubicC1 = cursor.add(qp1.subtract(cursor).multiply(2 / 3));
|
|
33
|
+
const cubicC2 = cmd.end.add(qp1.subtract(cmd.end).multiply(2 / 3));
|
|
34
|
+
result.push({ type: 'Cubic', control1: cubicC1, control2: cubicC2, end: cmd.end });
|
|
35
|
+
}
|
|
36
|
+
cursor = cmd.end;
|
|
37
|
+
break;
|
|
38
|
+
case 'Cubic':
|
|
39
|
+
if (cmd.control1 && cmd.control2) {
|
|
40
|
+
result.push({
|
|
41
|
+
type: 'Cubic',
|
|
42
|
+
control1: cmd.control1,
|
|
43
|
+
control2: cmd.control2,
|
|
44
|
+
end: cmd.end
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
cursor = cmd.end;
|
|
48
|
+
break;
|
|
49
|
+
case 'Close': {
|
|
50
|
+
// Convert close to cubic line back to start
|
|
51
|
+
const delta = subpathStart.subtract(cursor);
|
|
52
|
+
const c1 = cursor.add(delta.multiply(1 / 3));
|
|
53
|
+
const c2 = subpathStart.subtract(delta.multiply(1 / 3));
|
|
54
|
+
result.push({ type: 'Cubic', control1: c1, control2: c2, end: subpathStart });
|
|
55
|
+
cursor = subpathStart;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Splits a cubic Bezier curve at parameter t using De Casteljau's algorithm.
|
|
64
|
+
*/
|
|
65
|
+
export function splitCubic(p0, p1, p2, p3, t) {
|
|
66
|
+
const p01 = p0.lerp(p1, t);
|
|
67
|
+
const p12 = p1.lerp(p2, t);
|
|
68
|
+
const p23 = p2.lerp(p3, t);
|
|
69
|
+
const p012 = p01.lerp(p12, t);
|
|
70
|
+
const p123 = p12.lerp(p23, t);
|
|
71
|
+
const p0123 = p012.lerp(p123, t);
|
|
72
|
+
const cmd1 = {
|
|
73
|
+
type: 'Cubic',
|
|
74
|
+
control1: p01,
|
|
75
|
+
control2: p012,
|
|
76
|
+
end: p0123
|
|
77
|
+
};
|
|
78
|
+
const cmd2 = {
|
|
79
|
+
type: 'Cubic',
|
|
80
|
+
control1: p123,
|
|
81
|
+
control2: p23,
|
|
82
|
+
end: p3
|
|
83
|
+
};
|
|
84
|
+
return [cmd1, cmd2];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Subdivides a path to match a target command count.
|
|
88
|
+
* Uses iterative approach to avoid stack overflow on complex paths.
|
|
89
|
+
*/
|
|
90
|
+
export function subdividePath(commands, targetCount) {
|
|
91
|
+
let currentCommands = commands;
|
|
92
|
+
while (currentCommands.length < targetCount) {
|
|
93
|
+
const needed = targetCount - currentCommands.length;
|
|
94
|
+
const result = [];
|
|
95
|
+
// Identify cubic indices for splitting
|
|
96
|
+
const cubicIndices = [];
|
|
97
|
+
for (let i = 0; i < currentCommands.length; i++) {
|
|
98
|
+
if (currentCommands[i].type === 'Cubic') {
|
|
99
|
+
cubicIndices.push(i);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (cubicIndices.length === 0) {
|
|
103
|
+
// No curves to split - add degenerate cubics at last point
|
|
104
|
+
const lastCmd = currentCommands[currentCommands.length - 1];
|
|
105
|
+
if (lastCmd) {
|
|
106
|
+
const pt = lastCmd.end;
|
|
107
|
+
const resultCmds = [...currentCommands];
|
|
108
|
+
for (let k = 0; k < needed; k++) {
|
|
109
|
+
resultCmds.push({ type: 'Cubic', control1: pt, control2: pt, end: pt });
|
|
110
|
+
}
|
|
111
|
+
return resultCmds;
|
|
112
|
+
}
|
|
113
|
+
return currentCommands;
|
|
114
|
+
}
|
|
115
|
+
let splitsPerformed = 0;
|
|
116
|
+
let cursor = new Vector2(0, 0);
|
|
117
|
+
for (let i = 0; i < currentCommands.length; i++) {
|
|
118
|
+
const cmd = currentCommands[i];
|
|
119
|
+
if (cmd.type === 'Move') {
|
|
120
|
+
result.push({ type: 'Move', end: cmd.end });
|
|
121
|
+
cursor = cmd.end;
|
|
122
|
+
}
|
|
123
|
+
else if (cmd.type === 'Cubic' && splitsPerformed < needed) {
|
|
124
|
+
if (cmd.control1 && cmd.control2) {
|
|
125
|
+
const [c1, c2] = splitCubic(cursor, cmd.control1, cmd.control2, cmd.end, 0.5);
|
|
126
|
+
result.push(c1);
|
|
127
|
+
result.push(c2);
|
|
128
|
+
splitsPerformed++;
|
|
129
|
+
cursor = cmd.end;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
result.push(cmd);
|
|
133
|
+
cursor = cmd.end;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (cmd.type === 'Cubic' && cmd.control1 && cmd.control2) {
|
|
138
|
+
result.push({
|
|
139
|
+
type: 'Cubic',
|
|
140
|
+
control1: cmd.control1,
|
|
141
|
+
control2: cmd.control2,
|
|
142
|
+
end: cmd.end
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
cursor = cmd.end;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
currentCommands = result;
|
|
149
|
+
}
|
|
150
|
+
return currentCommands;
|
|
151
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Vector2 } from '../Vector2/Vector2';
|
|
2
|
+
import type { PathCommand } from './types';
|
|
3
|
+
export declare function getPathLength(commands: PathCommand[]): number;
|
|
4
|
+
/** Point on the path at normalized position t (0-1). */
|
|
5
|
+
export declare function getPointAtPath(commands: PathCommand[], t: number): Vector2;
|
|
6
|
+
/** Tangent vector on the path at normalized position t (0-1). */
|
|
7
|
+
export declare function getTangentAtPath(commands: PathCommand[], t: number): Vector2;
|