curvy 0.3.3 → 1.0.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/LICENSE.md +21 -0
- package/dist/curve/cubic2d.d.mts +27 -0
- package/dist/curve/cubic2d.d.ts +27 -0
- package/dist/curve/cubic2d.mjs +85 -0
- package/dist/curve/linear2d.d.mts +27 -0
- package/dist/curve/linear2d.d.ts +27 -0
- package/dist/curve/linear2d.mjs +46 -0
- package/dist/curve/quadratic2d.d.mts +27 -0
- package/dist/curve/quadratic2d.d.ts +27 -0
- package/dist/curve/quadratic2d.mjs +74 -0
- package/dist/interval.d.mts +58 -0
- package/dist/interval.d.ts +58 -0
- package/dist/interval.mjs +88 -0
- package/dist/matrix/matrix2x2.d.mts +49 -0
- package/dist/matrix/matrix2x2.d.ts +49 -0
- package/dist/matrix/matrix2x2.mjs +23 -0
- package/dist/matrix/matrix3x3.d.mts +63 -0
- package/dist/matrix/matrix3x3.d.ts +63 -0
- package/dist/matrix/matrix3x3.mjs +26 -0
- package/dist/matrix/matrix4x4.d.mts +72 -0
- package/dist/matrix/matrix4x4.d.ts +72 -0
- package/dist/matrix/matrix4x4.mjs +214 -0
- package/dist/path/cubic2d.d.mts +29 -0
- package/dist/path/cubic2d.d.ts +29 -0
- package/dist/path/cubic2d.mjs +60 -0
- package/dist/path/linear2d.d.mts +29 -0
- package/dist/path/linear2d.d.ts +29 -0
- package/dist/path/linear2d.mjs +54 -0
- package/dist/path/quadratic2d.d.mts +29 -0
- package/dist/path/quadratic2d.d.ts +29 -0
- package/dist/path/quadratic2d.mjs +60 -0
- package/dist/polynomial/cubic.d.mts +7 -0
- package/dist/polynomial/cubic.d.ts +7 -0
- package/dist/polynomial/cubic.mjs +139 -0
- package/dist/polynomial/linear.d.mts +7 -0
- package/dist/polynomial/linear.d.ts +7 -0
- package/dist/polynomial/linear.mjs +19 -0
- package/dist/polynomial/quadratic.d.mts +7 -0
- package/dist/polynomial/quadratic.d.ts +7 -0
- package/dist/polynomial/quadratic.mjs +22 -0
- package/dist/shared/curvy.BAW_cKLu.mjs +123 -0
- package/dist/shared/curvy.BN-oFBsc.d.mts +26 -0
- package/dist/shared/curvy.BN-oFBsc.d.ts +26 -0
- package/dist/shared/curvy.Bxxwly94.d.mts +144 -0
- package/dist/shared/curvy.C-sUk7ux.mjs +119 -0
- package/dist/shared/curvy.CLzGuqek.mjs +124 -0
- package/dist/shared/curvy.CO0Wclt0.d.ts +144 -0
- package/dist/shared/curvy.CT38g_II.d.mts +22 -0
- package/dist/shared/curvy.CT38g_II.d.ts +22 -0
- package/dist/shared/curvy.ChkqgKuv.mjs +119 -0
- package/dist/shared/curvy.Cty3LrOR.mjs +70 -0
- package/dist/shared/curvy.D7bhnwZy.mjs +52 -0
- package/dist/shared/curvy.DOLqYbKE.mjs +48 -0
- package/dist/shared/curvy.DgRsieUW.mjs +76 -0
- package/dist/shared/curvy.DmVFTi0N.mjs +34 -0
- package/dist/splines/bezier2d.d.mts +35 -0
- package/dist/splines/bezier2d.d.ts +35 -0
- package/dist/splines/bezier2d.mjs +122 -0
- package/dist/vector/vector2.d.mts +37 -0
- package/dist/vector/vector2.d.ts +37 -0
- package/dist/vector/vector2.mjs +16 -0
- package/dist/vector/vector3.d.mts +38 -0
- package/dist/vector/vector3.d.ts +38 -0
- package/dist/vector/vector3.mjs +16 -0
- package/dist/vector/vector4.d.mts +39 -0
- package/dist/vector/vector4.d.ts +39 -0
- package/dist/vector/vector4.mjs +16 -0
- package/package.json +78 -37
- package/dist/axis.cjs +0 -14
- package/dist/axis.d.cts +0 -20
- package/dist/axis.d.mts +0 -20
- package/dist/axis.d.ts +0 -20
- package/dist/axis.mjs +0 -3
- package/dist/convert.cjs +0 -74
- package/dist/convert.d.cts +0 -6
- package/dist/convert.d.mts +0 -6
- package/dist/convert.d.ts +0 -6
- package/dist/convert.mjs +0 -68
- package/dist/curve.cjs +0 -365
- package/dist/curve.d.cts +0 -20
- package/dist/curve.d.mts +0 -20
- package/dist/curve.d.ts +0 -20
- package/dist/curve.mjs +0 -354
- package/dist/shared/curvy.262f6070.cjs +0 -16
- package/dist/shared/curvy.7876e09e.mjs +0 -323
- package/dist/shared/curvy.9cd8b44c.mjs +0 -13
- package/dist/shared/curvy.db103082.cjs +0 -335
- package/dist/shared/curvy.df73166c.d.cts +0 -38
- package/dist/shared/curvy.df73166c.d.mts +0 -38
- package/dist/shared/curvy.df73166c.d.ts +0 -38
- package/dist/splines.cjs +0 -150
- package/dist/splines.d.cts +0 -27
- package/dist/splines.d.mts +0 -27
- package/dist/splines.d.ts +0 -27
- package/dist/splines.mjs +0 -138
package/dist/curve.mjs
DELETED
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
import invariant from 'tiny-invariant';
|
|
2
|
-
import { c as computeCubicAntiderivative, a as createCurveAxis } from './shared/curvy.7876e09e.mjs';
|
|
3
|
-
import { r as remap, a as round } from './shared/curvy.9cd8b44c.mjs';
|
|
4
|
-
import { splines } from './splines.mjs';
|
|
5
|
-
|
|
6
|
-
function createNode(interval) {
|
|
7
|
-
return {
|
|
8
|
-
interval,
|
|
9
|
-
left: null,
|
|
10
|
-
right: null
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function takeCenterInterval(boundaries) {
|
|
14
|
-
const mid = Math.floor((boundaries.length - 1) / 2);
|
|
15
|
-
const left = mid > 0 ? boundaries.slice(0, mid + 1) : null;
|
|
16
|
-
const right = mid + 1 < boundaries.length - 1 ? boundaries.slice(mid + 1) : null;
|
|
17
|
-
return {
|
|
18
|
-
center: [boundaries[mid], boundaries[mid + 1]],
|
|
19
|
-
left,
|
|
20
|
-
right
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function createIntervalTree(boundaries) {
|
|
24
|
-
const container = createNode([0, 0]);
|
|
25
|
-
const branches = [{ parent: container, direction: "left", boundaries }];
|
|
26
|
-
for (const branch of branches) {
|
|
27
|
-
const { parent, direction, boundaries: boundaries2 } = branch;
|
|
28
|
-
const { center, left, right } = takeCenterInterval(boundaries2);
|
|
29
|
-
const node = createNode(center);
|
|
30
|
-
if (direction === "left") {
|
|
31
|
-
parent.left = node;
|
|
32
|
-
} else {
|
|
33
|
-
parent.right = node;
|
|
34
|
-
}
|
|
35
|
-
if (left !== null) {
|
|
36
|
-
branches.push({ parent: node, direction: "left", boundaries: left });
|
|
37
|
-
}
|
|
38
|
-
if (right !== null) {
|
|
39
|
-
branches.push({ parent: node, direction: "right", boundaries: right });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const root = container.left;
|
|
43
|
-
function searchNode(node, query) {
|
|
44
|
-
if (node.interval[0] <= query && node.interval[1] >= query) {
|
|
45
|
-
return node.interval;
|
|
46
|
-
}
|
|
47
|
-
if (node.interval[0] > query && node.left) {
|
|
48
|
-
return searchNode(node.left, query);
|
|
49
|
-
}
|
|
50
|
-
if (node.interval[1] < query && node.right) {
|
|
51
|
-
return searchNode(node.right, query);
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
function search(query) {
|
|
56
|
-
return searchNode(root, query);
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
root,
|
|
60
|
-
search
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function collectPointsOfInterest(axis) {
|
|
65
|
-
const samples = new Set(Array.from({ length: 33 }, (_, i) => i / 32));
|
|
66
|
-
const segmentToNormalized = 1 / axis.segments.length;
|
|
67
|
-
for (const [index, segment] of axis.segments.entries()) {
|
|
68
|
-
samples.add(index * segmentToNormalized);
|
|
69
|
-
samples.add((index + 1) * segmentToNormalized);
|
|
70
|
-
for (const t of segment.extrema) {
|
|
71
|
-
samples.add((index + t) * segmentToNormalized);
|
|
72
|
-
}
|
|
73
|
-
for (const root of segment.roots) {
|
|
74
|
-
samples.add((index + root) * segmentToNormalized);
|
|
75
|
-
}
|
|
76
|
-
for (const root of segment.derivative.roots) {
|
|
77
|
-
samples.add((index + root) * segmentToNormalized);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return Array.from(samples).sort((a, b) => a - b);
|
|
81
|
-
}
|
|
82
|
-
function takeLinearIntegral(x1, x2, y1, y2) {
|
|
83
|
-
const m = (y2 - y1) / (x2 - x1);
|
|
84
|
-
const b = y1 - m * x1;
|
|
85
|
-
const s1 = x1 ** 2 * m / 2 + b * x1;
|
|
86
|
-
const s2 = x2 ** 2 * m / 2 + b * x2;
|
|
87
|
-
return Math.abs(s2 - s1);
|
|
88
|
-
}
|
|
89
|
-
function takePolynomialIntegral(coefficients, start, end) {
|
|
90
|
-
let s1 = 0;
|
|
91
|
-
let s2 = 0;
|
|
92
|
-
for (let i = 0; i < coefficients.length; i++) {
|
|
93
|
-
const power = coefficients.length - i;
|
|
94
|
-
s1 += coefficients[i] * start ** power / power;
|
|
95
|
-
s2 += coefficients[i] * end ** power / power;
|
|
96
|
-
}
|
|
97
|
-
return Math.abs(s2 - s1);
|
|
98
|
-
}
|
|
99
|
-
function* createAxisSampler(key, axis) {
|
|
100
|
-
const queue = collectPointsOfInterest(axis);
|
|
101
|
-
let totalLength = 0;
|
|
102
|
-
const lengthLookup = /* @__PURE__ */ new Map();
|
|
103
|
-
while (queue.length > 0) {
|
|
104
|
-
const current = queue[0];
|
|
105
|
-
const next = queue[1];
|
|
106
|
-
if (next === void 0) {
|
|
107
|
-
yield current;
|
|
108
|
-
lengthLookup.set(totalLength, current);
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
const segment = axis.segments[Math.floor(current * axis.segments.length)];
|
|
112
|
-
const currentPosition = axis.solvePosition(current);
|
|
113
|
-
const nextPosition = axis.solvePosition(next);
|
|
114
|
-
const currentVelocity = axis.solveVelocity(current);
|
|
115
|
-
const nextVelocity = axis.solveVelocity(next);
|
|
116
|
-
const lutPositionIntegral = takeLinearIntegral(
|
|
117
|
-
current,
|
|
118
|
-
next,
|
|
119
|
-
currentPosition,
|
|
120
|
-
nextPosition
|
|
121
|
-
);
|
|
122
|
-
const curvePositionIntegral = takePolynomialIntegral(
|
|
123
|
-
computeCubicAntiderivative(segment.coefficients, 0),
|
|
124
|
-
current,
|
|
125
|
-
next
|
|
126
|
-
);
|
|
127
|
-
const lutVelocityIntegral = takeLinearIntegral(
|
|
128
|
-
current,
|
|
129
|
-
next,
|
|
130
|
-
currentVelocity,
|
|
131
|
-
nextVelocity
|
|
132
|
-
);
|
|
133
|
-
const curveVelocityIntegral = takePolynomialIntegral(
|
|
134
|
-
segment.derivative.coefficients,
|
|
135
|
-
current,
|
|
136
|
-
next
|
|
137
|
-
);
|
|
138
|
-
const error = Math.max(
|
|
139
|
-
Math.abs(lutPositionIntegral - curvePositionIntegral),
|
|
140
|
-
Math.abs(lutVelocityIntegral - curveVelocityIntegral)
|
|
141
|
-
);
|
|
142
|
-
if (error > 1) {
|
|
143
|
-
queue.splice(1, 0, (current + next) / 2);
|
|
144
|
-
} else {
|
|
145
|
-
yield current;
|
|
146
|
-
lengthLookup.set(totalLength, current);
|
|
147
|
-
totalLength += Math.hypot(current - next, currentPosition - nextPosition);
|
|
148
|
-
queue.shift();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return [
|
|
152
|
-
key,
|
|
153
|
-
{
|
|
154
|
-
length: totalLength,
|
|
155
|
-
lookup: lengthLookup
|
|
156
|
-
}
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
function distance(a, b) {
|
|
160
|
-
const deltas = [];
|
|
161
|
-
for (const key of Object.keys(a)) {
|
|
162
|
-
deltas.push(a[key] - b[key]);
|
|
163
|
-
}
|
|
164
|
-
return Math.hypot(...deltas);
|
|
165
|
-
}
|
|
166
|
-
function createSampleBuffer() {
|
|
167
|
-
const values = /* @__PURE__ */ new Map();
|
|
168
|
-
return {
|
|
169
|
-
get size() {
|
|
170
|
-
return values.size;
|
|
171
|
-
},
|
|
172
|
-
add(key, value) {
|
|
173
|
-
if (!values.has(key)) {
|
|
174
|
-
values.set(key, /* @__PURE__ */ new Set());
|
|
175
|
-
}
|
|
176
|
-
values.get(key)?.add(value);
|
|
177
|
-
},
|
|
178
|
-
get(key) {
|
|
179
|
-
return values.get(key);
|
|
180
|
-
},
|
|
181
|
-
remove(key, value) {
|
|
182
|
-
values.get(key)?.delete(value);
|
|
183
|
-
if (values.get(key)?.size === 0) {
|
|
184
|
-
values.delete(key);
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
keys() {
|
|
188
|
-
return Array.from(values.keys());
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function createLengthLookup({
|
|
193
|
-
length: totalLength,
|
|
194
|
-
lookup
|
|
195
|
-
}) {
|
|
196
|
-
const { search } = createIntervalTree(Array.from(lookup.keys()));
|
|
197
|
-
return (normalized) => {
|
|
198
|
-
invariant(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
199
|
-
const length = normalized * totalLength;
|
|
200
|
-
const result = search(length);
|
|
201
|
-
invariant(result, "Length not found");
|
|
202
|
-
return remap(
|
|
203
|
-
length,
|
|
204
|
-
result[0],
|
|
205
|
-
result[1],
|
|
206
|
-
lookup.get(result[0]),
|
|
207
|
-
lookup.get(result[1])
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function createCurveLengthLookups(axes, positionAt) {
|
|
212
|
-
const lengthLookup = /* @__PURE__ */ new Map();
|
|
213
|
-
const axesLookups = {};
|
|
214
|
-
const sampleBuffer = createSampleBuffer();
|
|
215
|
-
for (const [key, axis] of Object.entries(axes)) {
|
|
216
|
-
const sampler = createAxisSampler(key, axis);
|
|
217
|
-
const first = sampler.next();
|
|
218
|
-
if (first.done) {
|
|
219
|
-
axesLookups[first.value[0]] = createLengthLookup(first.value[1]);
|
|
220
|
-
} else {
|
|
221
|
-
sampleBuffer.add(first.value, sampler);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
let lastPoint = positionAt(0);
|
|
225
|
-
let length = 0;
|
|
226
|
-
lengthLookup.set(0, 0);
|
|
227
|
-
while (sampleBuffer.size > 0) {
|
|
228
|
-
const t = Math.min(...sampleBuffer.keys());
|
|
229
|
-
const point = positionAt(t);
|
|
230
|
-
length = length + distance(lastPoint, point);
|
|
231
|
-
lengthLookup.set(length, t);
|
|
232
|
-
lastPoint = point;
|
|
233
|
-
const samplers = sampleBuffer.get(t);
|
|
234
|
-
for (const sampler of samplers) {
|
|
235
|
-
const next = sampler.next();
|
|
236
|
-
if (next.done) {
|
|
237
|
-
axesLookups[next.value[0]] = createLengthLookup(next.value[1]);
|
|
238
|
-
} else {
|
|
239
|
-
sampleBuffer.add(next.value, sampler);
|
|
240
|
-
}
|
|
241
|
-
sampleBuffer.remove(t, sampler);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
curve: createLengthLookup({ length, lookup: lengthLookup }),
|
|
246
|
-
axes: axesLookups
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function convertPointsToAxes(points, spline) {
|
|
251
|
-
const axisKeys = Object.keys(points[0]);
|
|
252
|
-
const axisPoints = {};
|
|
253
|
-
for (const key of axisKeys) {
|
|
254
|
-
axisPoints[key] = [];
|
|
255
|
-
}
|
|
256
|
-
for (const point of points) {
|
|
257
|
-
for (const key of axisKeys) {
|
|
258
|
-
invariant(key in point, `Point ${point} is missing axis "${key}"`);
|
|
259
|
-
axisPoints[key].push(point[key]);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const axes = {};
|
|
263
|
-
for (const key of axisKeys) {
|
|
264
|
-
axes[key] = createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
|
|
265
|
-
}
|
|
266
|
-
return axes;
|
|
267
|
-
}
|
|
268
|
-
function createCurve(axes) {
|
|
269
|
-
const axisKeys = Object.keys(axes);
|
|
270
|
-
const solveT = (t, precision = 12) => {
|
|
271
|
-
const rounded = round(t);
|
|
272
|
-
invariant(
|
|
273
|
-
rounded >= 0 && rounded <= 1,
|
|
274
|
-
`t must be between 0 and 1, got ${rounded}`
|
|
275
|
-
);
|
|
276
|
-
const position = {};
|
|
277
|
-
for (const key of axisKeys) {
|
|
278
|
-
position[key] = round(axes[key].solvePosition(rounded), precision);
|
|
279
|
-
}
|
|
280
|
-
return position;
|
|
281
|
-
};
|
|
282
|
-
const lengthLookups = createCurveLengthLookups(axes, solveT);
|
|
283
|
-
const positionAt = (input, normalize = 1, precision = 12) => {
|
|
284
|
-
invariant(
|
|
285
|
-
input >= 0 && input <= 1,
|
|
286
|
-
`Input must be between 0 and 1, got ${input}`
|
|
287
|
-
);
|
|
288
|
-
let t = input;
|
|
289
|
-
if (normalize > 0) {
|
|
290
|
-
const denormalizedT = lengthLookups.curve(input);
|
|
291
|
-
if (normalize === 1) {
|
|
292
|
-
t = denormalizedT;
|
|
293
|
-
} else {
|
|
294
|
-
t = (1 - normalize) * input + normalize * denormalizedT;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return solveT(t, precision);
|
|
298
|
-
};
|
|
299
|
-
const solveAlong = (axis, input, normalize = 1, precision = 12) => {
|
|
300
|
-
invariant(
|
|
301
|
-
input >= 0 && input <= 1,
|
|
302
|
-
`Input must be between 0 and 1, got ${input}`
|
|
303
|
-
);
|
|
304
|
-
let t = input;
|
|
305
|
-
if (normalize > 0) {
|
|
306
|
-
const denormalizedT = lengthLookups.axes[axis](input);
|
|
307
|
-
if (normalize === 1) {
|
|
308
|
-
t = denormalizedT;
|
|
309
|
-
} else {
|
|
310
|
-
t = (1 - normalize) * input + normalize * denormalizedT;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return solveT(t, precision);
|
|
314
|
-
};
|
|
315
|
-
const solveWhere = (axis, position, precision = 12) => {
|
|
316
|
-
const curveAxis = axes[axis];
|
|
317
|
-
invariant(curveAxis.monotonicity !== "none", "Axis is not monotonic");
|
|
318
|
-
const t = curveAxis.solveT(position, curveAxis.domain)[0];
|
|
319
|
-
invariant(
|
|
320
|
-
typeof t === "number",
|
|
321
|
-
`Could not solve for ${axis} = ${position}`
|
|
322
|
-
);
|
|
323
|
-
return positionAt(t, 0, precision);
|
|
324
|
-
};
|
|
325
|
-
return {
|
|
326
|
-
axes,
|
|
327
|
-
positionAt,
|
|
328
|
-
solveAlong,
|
|
329
|
-
solveWhere
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
function createBasisCurve(points, triplicateEndpoints = true) {
|
|
333
|
-
return createCurve(
|
|
334
|
-
convertPointsToAxes(points, splines.basis(triplicateEndpoints))
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
function createBezierCurve(points) {
|
|
338
|
-
return createCurve(convertPointsToAxes(points, splines.bezier));
|
|
339
|
-
}
|
|
340
|
-
function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
|
|
341
|
-
return createCurve(
|
|
342
|
-
convertPointsToAxes(points, splines.cardinal(tension, duplicateEndpoints))
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
function createCatmullRomCurve(points, duplicateEndpoints = true) {
|
|
346
|
-
return createCurve(
|
|
347
|
-
convertPointsToAxes(points, splines.catmullRom(duplicateEndpoints))
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
function createHermiteCurve(points) {
|
|
351
|
-
return createCurve(convertPointsToAxes(points, splines.hermite));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export { createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createCurve, createHermiteCurve };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function round(value, precision = 12) {
|
|
4
|
-
const scale = 10 ** precision;
|
|
5
|
-
const result = Math.round(value * scale) / scale;
|
|
6
|
-
if (result === 0) {
|
|
7
|
-
return 0;
|
|
8
|
-
}
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
|
-
function remap(x, x1, x2, y1, y2) {
|
|
12
|
-
return y1 + (x - x1) / (x2 - x1) * (y2 - y1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
exports.remap = remap;
|
|
16
|
-
exports.round = round;
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import invariant from 'tiny-invariant';
|
|
2
|
-
import { splines } from '../splines.mjs';
|
|
3
|
-
|
|
4
|
-
const UNIT_INTERVAL = [0, 1];
|
|
5
|
-
function createLinearPolynomial(coefficients, domain = UNIT_INTERVAL) {
|
|
6
|
-
const [a, b] = coefficients;
|
|
7
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
8
|
-
let root = a === 0 ? null : b === 0 ? 0 : -b / a;
|
|
9
|
-
if (root !== null && (root < domain[0] || root > domain[1])) {
|
|
10
|
-
root = null;
|
|
11
|
-
}
|
|
12
|
-
const range = a >= 0 ? [a * domain[0] + b, a * domain[1] + b] : [a * domain[1] + b, a * domain[0] + b];
|
|
13
|
-
const solve = (x) => {
|
|
14
|
-
invariant(x >= domain[0] && x <= domain[1], "x out of domain");
|
|
15
|
-
return a * x + b;
|
|
16
|
-
};
|
|
17
|
-
const solveInverse = (y) => {
|
|
18
|
-
invariant(y >= range[0] && y <= range[1], "y out of range");
|
|
19
|
-
return a === 0 ? 0 : (y - b) / a;
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
domain,
|
|
23
|
-
range,
|
|
24
|
-
monotonicity: a === 0 ? "constant" : a > 0 ? "increasing" : "decreasing",
|
|
25
|
-
coefficients,
|
|
26
|
-
root,
|
|
27
|
-
solve,
|
|
28
|
-
solveInverse
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function createQuadraticRootSolver(coefficients, defaultDomain, range) {
|
|
32
|
-
const [a, b, c] = coefficients;
|
|
33
|
-
if (a !== 0) {
|
|
34
|
-
return function solveInverse(y, domain = defaultDomain) {
|
|
35
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
36
|
-
invariant(y >= range[0] && y <= range[1], "y out of range");
|
|
37
|
-
const discriminant = b ** 2 - 4 * a * (c - y);
|
|
38
|
-
if (discriminant < 0) {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
if (discriminant === 0) {
|
|
42
|
-
const root = -b / (2 * a);
|
|
43
|
-
return root < domain[0] || root > domain[1] ? [] : [root];
|
|
44
|
-
}
|
|
45
|
-
const sqrtDiscriminant = Math.sqrt(discriminant);
|
|
46
|
-
const denominator = 2 * a;
|
|
47
|
-
const left = (-b - sqrtDiscriminant) / denominator;
|
|
48
|
-
const right = (-b + sqrtDiscriminant) / denominator;
|
|
49
|
-
const leftInDomain = left >= domain[0] && left <= domain[1];
|
|
50
|
-
const rightInDomain = right >= domain[0] && right <= domain[1];
|
|
51
|
-
if (leftInDomain && rightInDomain) {
|
|
52
|
-
return left < right ? [left, right] : [right, left];
|
|
53
|
-
}
|
|
54
|
-
if (leftInDomain) {
|
|
55
|
-
return [left];
|
|
56
|
-
}
|
|
57
|
-
if (rightInDomain) {
|
|
58
|
-
return [right];
|
|
59
|
-
}
|
|
60
|
-
return [];
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (b !== 0) {
|
|
64
|
-
return function solveInverse(y, domain = defaultDomain) {
|
|
65
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
66
|
-
invariant(y >= range[0] && y <= range[1], "y out of range");
|
|
67
|
-
const root = -(c - y) / b;
|
|
68
|
-
return root < domain[0] || root > domain[1] ? [] : [root];
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return function solveInverse() {
|
|
72
|
-
return [];
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function createQuadraticPolynomial(coefficients, domain = UNIT_INTERVAL) {
|
|
76
|
-
const [a, b, c] = coefficients;
|
|
77
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
78
|
-
const derivative = createLinearPolynomial([2 * a, b], domain);
|
|
79
|
-
const extreme = derivative.root;
|
|
80
|
-
const solve = (x) => {
|
|
81
|
-
invariant(x >= domain[0] && x <= domain[1], "x out of domain");
|
|
82
|
-
return a * x ** 2 + b * x + c;
|
|
83
|
-
};
|
|
84
|
-
const rangeCandidates = extreme === null ? [solve(domain[0]), solve(domain[1])] : [solve(domain[0]), solve(extreme), solve(domain[1])];
|
|
85
|
-
const range = [
|
|
86
|
-
Math.min(...rangeCandidates),
|
|
87
|
-
Math.max(...rangeCandidates)
|
|
88
|
-
];
|
|
89
|
-
const solveInverse = createQuadraticRootSolver(coefficients, domain, range);
|
|
90
|
-
const roots = range[0] > 0 || range[1] < 0 ? [] : solveInverse(0);
|
|
91
|
-
return {
|
|
92
|
-
domain,
|
|
93
|
-
range,
|
|
94
|
-
coefficients,
|
|
95
|
-
monotonicity: a !== 0 ? "none" : b === 0 ? "constant" : b > 0 ? "increasing" : "decreasing",
|
|
96
|
-
extreme,
|
|
97
|
-
roots,
|
|
98
|
-
derivative,
|
|
99
|
-
solve,
|
|
100
|
-
solveInverse
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const TWO_THIRDS_PI = 2 * Math.PI / 3;
|
|
104
|
-
function createCubicRootSolver(coefficients, defaultDomain, range) {
|
|
105
|
-
if (coefficients[0] === 0) {
|
|
106
|
-
return createQuadraticRootSolver(
|
|
107
|
-
[coefficients[1], coefficients[2], coefficients[3]],
|
|
108
|
-
defaultDomain,
|
|
109
|
-
range
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
const oneOverA = 1 / coefficients[0];
|
|
113
|
-
const a0 = coefficients[1] * oneOverA;
|
|
114
|
-
const a1 = coefficients[2] * oneOverA;
|
|
115
|
-
const a2OverThree = a0 / 3;
|
|
116
|
-
const q = a1 / 3 - a0 ** 2 / 9;
|
|
117
|
-
const r0 = a1 * a0 / 6 - a0 ** 3 / 27;
|
|
118
|
-
return function solveInverse(y, domain = defaultDomain) {
|
|
119
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
120
|
-
invariant(y >= range[0] && y <= range[1], "y out of range");
|
|
121
|
-
const a02 = (coefficients[3] - y) * oneOverA;
|
|
122
|
-
const r = r0 - a02 * 0.5;
|
|
123
|
-
const discriminant = q ** 3 + r ** 2;
|
|
124
|
-
if (discriminant > 0) {
|
|
125
|
-
const a = Math.cbrt(Math.abs(r) + Math.sqrt(discriminant));
|
|
126
|
-
const root = (r < 0 ? q / a - a : a - q / a) - a2OverThree;
|
|
127
|
-
return root < domain[0] || root > domain[1] ? [] : [root];
|
|
128
|
-
}
|
|
129
|
-
const theta = Math.acos(r / Math.sqrt((-q) ** 3)) / 3;
|
|
130
|
-
const twoRootQ = 2 * Math.sqrt(-q);
|
|
131
|
-
const root1 = twoRootQ * Math.cos(theta - TWO_THIRDS_PI) - a2OverThree;
|
|
132
|
-
const root2 = twoRootQ * Math.cos(theta) - a2OverThree;
|
|
133
|
-
const root3 = twoRootQ * Math.cos(theta + TWO_THIRDS_PI) - a2OverThree;
|
|
134
|
-
const candidates = [root1, root2, root3];
|
|
135
|
-
const roots = [];
|
|
136
|
-
for (const candidate of candidates) {
|
|
137
|
-
if (candidate >= domain[0] && candidate <= domain[1] && !roots.includes(candidate)) {
|
|
138
|
-
roots.push(candidate);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
roots.sort((a, b) => a - b);
|
|
142
|
-
return roots;
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function getMonotonicity(extrema, derivative, domain) {
|
|
146
|
-
if (derivative.coefficients[0] === 0 && derivative.coefficients[1] === 0 && derivative.coefficients[2] === 0) {
|
|
147
|
-
return "constant";
|
|
148
|
-
}
|
|
149
|
-
let hasInnerExtrema = false;
|
|
150
|
-
for (const extreme of extrema) {
|
|
151
|
-
if (extreme > domain[0] && extreme > domain[1]) {
|
|
152
|
-
hasInnerExtrema = true;
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (hasInnerExtrema) {
|
|
157
|
-
return "none";
|
|
158
|
-
}
|
|
159
|
-
let test = derivative.solve(domain[0]);
|
|
160
|
-
if (test === 0) {
|
|
161
|
-
test = derivative.solve((domain[0] + domain[1]) * 0.5);
|
|
162
|
-
}
|
|
163
|
-
return Math.sign(test) > 0 ? "increasing" : "decreasing";
|
|
164
|
-
}
|
|
165
|
-
function createCubicPolynomial(coefficients, domain = UNIT_INTERVAL) {
|
|
166
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
167
|
-
const [a, b, c, d] = coefficients;
|
|
168
|
-
const derivative = createQuadraticPolynomial([3 * a, 2 * b, c], domain);
|
|
169
|
-
const extrema = derivative.roots.length === 1 && derivative.coefficients[0] !== 0 ? [] : derivative.roots;
|
|
170
|
-
const solve = (x) => {
|
|
171
|
-
invariant(x >= domain[0] && x <= domain[1], "x out of domain");
|
|
172
|
-
return a * x ** 3 + b * x ** 2 + c * x + d;
|
|
173
|
-
};
|
|
174
|
-
const rangeCandidates = [
|
|
175
|
-
solve(domain[0]),
|
|
176
|
-
...extrema.map((extreme) => solve(extreme)),
|
|
177
|
-
solve(domain[1])
|
|
178
|
-
];
|
|
179
|
-
const range = [
|
|
180
|
-
Math.min(...rangeCandidates),
|
|
181
|
-
Math.max(...rangeCandidates)
|
|
182
|
-
];
|
|
183
|
-
const solveInverse = createCubicRootSolver(coefficients, domain, range);
|
|
184
|
-
const roots = range[0] > 0 || range[1] < 0 ? [] : solveInverse(0, domain);
|
|
185
|
-
const monotonicity = getMonotonicity(extrema, derivative, domain);
|
|
186
|
-
return {
|
|
187
|
-
domain,
|
|
188
|
-
range,
|
|
189
|
-
monotonicity,
|
|
190
|
-
coefficients,
|
|
191
|
-
extrema,
|
|
192
|
-
roots,
|
|
193
|
-
derivative,
|
|
194
|
-
solve,
|
|
195
|
-
solveInverse
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function computeCubicAntiderivative(coefficients, integrationConstant) {
|
|
199
|
-
return [
|
|
200
|
-
coefficients[0] / 4,
|
|
201
|
-
coefficients[1] / 3,
|
|
202
|
-
coefficients[2] / 2,
|
|
203
|
-
coefficients[3],
|
|
204
|
-
integrationConstant
|
|
205
|
-
];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function getCurveExtrema(lookup) {
|
|
209
|
-
const extremaLookup = new Map(lookup);
|
|
210
|
-
const extremaCandidates = Array.from(extremaLookup.keys());
|
|
211
|
-
for (const [index, t] of extremaCandidates.entries()) {
|
|
212
|
-
if (index === 0 || index === extremaCandidates.length - 1) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
const previous = extremaLookup.get(extremaCandidates[index - 1]);
|
|
216
|
-
const current = extremaLookup.get(t);
|
|
217
|
-
const next = extremaLookup.get(extremaCandidates[index + 1]);
|
|
218
|
-
if (previous <= current && current < next || previous >= current && current > next) {
|
|
219
|
-
extremaLookup.delete(t);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
const extrema = Array.from(extremaLookup.keys());
|
|
223
|
-
return extrema;
|
|
224
|
-
}
|
|
225
|
-
function createCurveAxis(coefficients) {
|
|
226
|
-
invariant(coefficients.length > 0, "Curve must have at least one segment");
|
|
227
|
-
const segments = [];
|
|
228
|
-
const extremaLookup = /* @__PURE__ */ new Map();
|
|
229
|
-
let monotonicity = "none";
|
|
230
|
-
const normalizedToCurve = coefficients.length;
|
|
231
|
-
const curveToNormalized = 1 / coefficients.length;
|
|
232
|
-
for (let i = 0; i < coefficients.length; i++) {
|
|
233
|
-
const segment = createCubicPolynomial(coefficients[i], UNIT_INTERVAL);
|
|
234
|
-
segments.push(segment);
|
|
235
|
-
extremaLookup.set(i * curveToNormalized, segment.solve(0));
|
|
236
|
-
for (const extreme of segment.extrema) {
|
|
237
|
-
extremaLookup.set(
|
|
238
|
-
(i + extreme) * curveToNormalized,
|
|
239
|
-
segment.solve(extreme)
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
if (i === 0) {
|
|
243
|
-
monotonicity = segment.monotonicity;
|
|
244
|
-
} else if (monotonicity !== segment.monotonicity) {
|
|
245
|
-
monotonicity = "none";
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
extremaLookup.set(1, segments[segments.length - 1].solve(1));
|
|
249
|
-
const extrema = getCurveExtrema(extremaLookup);
|
|
250
|
-
const range = [
|
|
251
|
-
Math.min(...extremaLookup.values()),
|
|
252
|
-
Math.max(...extremaLookup.values())
|
|
253
|
-
];
|
|
254
|
-
const getSegmentAndInput = (t) => {
|
|
255
|
-
invariant(t >= 0 && t <= 1, "t out of range");
|
|
256
|
-
if (t === 1) {
|
|
257
|
-
return { segment: segments[segments.length - 1], input: 1 };
|
|
258
|
-
}
|
|
259
|
-
const curveT = t * normalizedToCurve;
|
|
260
|
-
const index = Math.floor(curveT);
|
|
261
|
-
const segment = segments[index];
|
|
262
|
-
const input = curveT - index;
|
|
263
|
-
return { segment, input };
|
|
264
|
-
};
|
|
265
|
-
const solvePosition = (t) => {
|
|
266
|
-
const { segment, input } = getSegmentAndInput(t);
|
|
267
|
-
return segment.solve(input);
|
|
268
|
-
};
|
|
269
|
-
const solveVelocity = (t) => {
|
|
270
|
-
const { segment, input } = getSegmentAndInput(t);
|
|
271
|
-
return segment.derivative.solve(input);
|
|
272
|
-
};
|
|
273
|
-
const solveT = (position, domain) => {
|
|
274
|
-
invariant(
|
|
275
|
-
position >= range[0] && position <= range[1],
|
|
276
|
-
"position out of range"
|
|
277
|
-
);
|
|
278
|
-
invariant(domain[0] <= domain[1], "invalid domain");
|
|
279
|
-
const result = [];
|
|
280
|
-
for (const [index, segment] of segments.entries()) {
|
|
281
|
-
if (position >= segment.range[0] && position <= segment.range[1]) {
|
|
282
|
-
const t = segment.solveInverse(position, domain);
|
|
283
|
-
for (const tValue of t) {
|
|
284
|
-
result.push((index + tValue) * curveToNormalized);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return result;
|
|
289
|
-
};
|
|
290
|
-
return {
|
|
291
|
-
domain: UNIT_INTERVAL,
|
|
292
|
-
range,
|
|
293
|
-
monotonicity,
|
|
294
|
-
extrema,
|
|
295
|
-
segments,
|
|
296
|
-
solvePosition,
|
|
297
|
-
solveVelocity,
|
|
298
|
-
solveT
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function createBasisAxis(parameters, triplicateEndpoints = true) {
|
|
302
|
-
return createCurveAxis(
|
|
303
|
-
splines.basis(triplicateEndpoints).chunkCoefficients(parameters)
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
function createBezierAxis(parameters) {
|
|
307
|
-
return createCurveAxis(splines.bezier.chunkCoefficients(parameters));
|
|
308
|
-
}
|
|
309
|
-
function createCardinalAxis(parameters, a = 0.5, duplicateEndpoints = true) {
|
|
310
|
-
return createCurveAxis(
|
|
311
|
-
splines.cardinal(a, duplicateEndpoints).chunkCoefficients(parameters)
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
function createCatmullRomAxis(parameters, duplicateEndpoints = true) {
|
|
315
|
-
return createCurveAxis(
|
|
316
|
-
splines.catmullRom(duplicateEndpoints).chunkCoefficients(parameters)
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
function createHermiteAxis(parameters) {
|
|
320
|
-
return createCurveAxis(splines.hermite.chunkCoefficients(parameters));
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export { createCurveAxis as a, createBasisAxis as b, computeCubicAntiderivative as c, createBezierAxis as d, createCardinalAxis as e, createCatmullRomAxis as f, createHermiteAxis as g };
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
function round(value, precision = 12) {
|
|
2
|
-
const scale = 10 ** precision;
|
|
3
|
-
const result = Math.round(value * scale) / scale;
|
|
4
|
-
if (result === 0) {
|
|
5
|
-
return 0;
|
|
6
|
-
}
|
|
7
|
-
return result;
|
|
8
|
-
}
|
|
9
|
-
function remap(x, x1, x2, y1, y2) {
|
|
10
|
-
return y1 + (x - x1) / (x2 - x1) * (y2 - y1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export { round as a, remap as r };
|