curvy 0.3.2 → 0.3.3
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/axis.cjs +9 -320
- package/dist/axis.mjs +3 -314
- package/dist/curve.cjs +205 -34
- package/dist/curve.d.cts +3 -1
- package/dist/curve.d.mts +3 -1
- package/dist/curve.d.ts +3 -1
- package/dist/curve.mjs +205 -35
- package/dist/shared/curvy.7876e09e.mjs +323 -0
- package/dist/shared/curvy.db103082.cjs +335 -0
- package/package.json +4 -2
package/dist/curve.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const invariant = require('tiny-invariant');
|
|
4
|
-
const axis = require('./
|
|
4
|
+
const axis = require('./shared/curvy.db103082.cjs');
|
|
5
5
|
const util = require('./shared/curvy.262f6070.cjs');
|
|
6
6
|
const splines = require('./splines.cjs');
|
|
7
7
|
|
|
@@ -67,6 +67,101 @@ function createIntervalTree(boundaries) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function collectPointsOfInterest(axis) {
|
|
71
|
+
const samples = new Set(Array.from({ length: 33 }, (_, i) => i / 32));
|
|
72
|
+
const segmentToNormalized = 1 / axis.segments.length;
|
|
73
|
+
for (const [index, segment] of axis.segments.entries()) {
|
|
74
|
+
samples.add(index * segmentToNormalized);
|
|
75
|
+
samples.add((index + 1) * segmentToNormalized);
|
|
76
|
+
for (const t of segment.extrema) {
|
|
77
|
+
samples.add((index + t) * segmentToNormalized);
|
|
78
|
+
}
|
|
79
|
+
for (const root of segment.roots) {
|
|
80
|
+
samples.add((index + root) * segmentToNormalized);
|
|
81
|
+
}
|
|
82
|
+
for (const root of segment.derivative.roots) {
|
|
83
|
+
samples.add((index + root) * segmentToNormalized);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Array.from(samples).sort((a, b) => a - b);
|
|
87
|
+
}
|
|
88
|
+
function takeLinearIntegral(x1, x2, y1, y2) {
|
|
89
|
+
const m = (y2 - y1) / (x2 - x1);
|
|
90
|
+
const b = y1 - m * x1;
|
|
91
|
+
const s1 = x1 ** 2 * m / 2 + b * x1;
|
|
92
|
+
const s2 = x2 ** 2 * m / 2 + b * x2;
|
|
93
|
+
return Math.abs(s2 - s1);
|
|
94
|
+
}
|
|
95
|
+
function takePolynomialIntegral(coefficients, start, end) {
|
|
96
|
+
let s1 = 0;
|
|
97
|
+
let s2 = 0;
|
|
98
|
+
for (let i = 0; i < coefficients.length; i++) {
|
|
99
|
+
const power = coefficients.length - i;
|
|
100
|
+
s1 += coefficients[i] * start ** power / power;
|
|
101
|
+
s2 += coefficients[i] * end ** power / power;
|
|
102
|
+
}
|
|
103
|
+
return Math.abs(s2 - s1);
|
|
104
|
+
}
|
|
105
|
+
function* createAxisSampler(key, axis$1) {
|
|
106
|
+
const queue = collectPointsOfInterest(axis$1);
|
|
107
|
+
let totalLength = 0;
|
|
108
|
+
const lengthLookup = /* @__PURE__ */ new Map();
|
|
109
|
+
while (queue.length > 0) {
|
|
110
|
+
const current = queue[0];
|
|
111
|
+
const next = queue[1];
|
|
112
|
+
if (next === void 0) {
|
|
113
|
+
yield current;
|
|
114
|
+
lengthLookup.set(totalLength, current);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
const segment = axis$1.segments[Math.floor(current * axis$1.segments.length)];
|
|
118
|
+
const currentPosition = axis$1.solvePosition(current);
|
|
119
|
+
const nextPosition = axis$1.solvePosition(next);
|
|
120
|
+
const currentVelocity = axis$1.solveVelocity(current);
|
|
121
|
+
const nextVelocity = axis$1.solveVelocity(next);
|
|
122
|
+
const lutPositionIntegral = takeLinearIntegral(
|
|
123
|
+
current,
|
|
124
|
+
next,
|
|
125
|
+
currentPosition,
|
|
126
|
+
nextPosition
|
|
127
|
+
);
|
|
128
|
+
const curvePositionIntegral = takePolynomialIntegral(
|
|
129
|
+
axis.computeCubicAntiderivative(segment.coefficients, 0),
|
|
130
|
+
current,
|
|
131
|
+
next
|
|
132
|
+
);
|
|
133
|
+
const lutVelocityIntegral = takeLinearIntegral(
|
|
134
|
+
current,
|
|
135
|
+
next,
|
|
136
|
+
currentVelocity,
|
|
137
|
+
nextVelocity
|
|
138
|
+
);
|
|
139
|
+
const curveVelocityIntegral = takePolynomialIntegral(
|
|
140
|
+
segment.derivative.coefficients,
|
|
141
|
+
current,
|
|
142
|
+
next
|
|
143
|
+
);
|
|
144
|
+
const error = Math.max(
|
|
145
|
+
Math.abs(lutPositionIntegral - curvePositionIntegral),
|
|
146
|
+
Math.abs(lutVelocityIntegral - curveVelocityIntegral)
|
|
147
|
+
);
|
|
148
|
+
if (error > 1) {
|
|
149
|
+
queue.splice(1, 0, (current + next) / 2);
|
|
150
|
+
} else {
|
|
151
|
+
yield current;
|
|
152
|
+
lengthLookup.set(totalLength, current);
|
|
153
|
+
totalLength += Math.hypot(current - next, currentPosition - nextPosition);
|
|
154
|
+
queue.shift();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return [
|
|
158
|
+
key,
|
|
159
|
+
{
|
|
160
|
+
length: totalLength,
|
|
161
|
+
lookup: lengthLookup
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
}
|
|
70
165
|
function distance(a, b) {
|
|
71
166
|
const deltas = [];
|
|
72
167
|
for (const key of Object.keys(a)) {
|
|
@@ -74,37 +169,91 @@ function distance(a, b) {
|
|
|
74
169
|
}
|
|
75
170
|
return Math.hypot(...deltas);
|
|
76
171
|
}
|
|
77
|
-
function
|
|
78
|
-
const
|
|
79
|
-
|
|
172
|
+
function createSampleBuffer() {
|
|
173
|
+
const values = /* @__PURE__ */ new Map();
|
|
174
|
+
return {
|
|
175
|
+
get size() {
|
|
176
|
+
return values.size;
|
|
177
|
+
},
|
|
178
|
+
add(key, value) {
|
|
179
|
+
if (!values.has(key)) {
|
|
180
|
+
values.set(key, /* @__PURE__ */ new Set());
|
|
181
|
+
}
|
|
182
|
+
values.get(key)?.add(value);
|
|
183
|
+
},
|
|
184
|
+
get(key) {
|
|
185
|
+
return values.get(key);
|
|
186
|
+
},
|
|
187
|
+
remove(key, value) {
|
|
188
|
+
values.get(key)?.delete(value);
|
|
189
|
+
if (values.get(key)?.size === 0) {
|
|
190
|
+
values.delete(key);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
keys() {
|
|
194
|
+
return Array.from(values.keys());
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function createLengthLookup({
|
|
199
|
+
length: totalLength,
|
|
200
|
+
lookup
|
|
201
|
+
}) {
|
|
202
|
+
const { search } = createIntervalTree(Array.from(lookup.keys()));
|
|
203
|
+
return (normalized) => {
|
|
204
|
+
invariant__default(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
205
|
+
const length = normalized * totalLength;
|
|
206
|
+
const result = search(length);
|
|
207
|
+
invariant__default(result, "Length not found");
|
|
208
|
+
return util.remap(
|
|
209
|
+
length,
|
|
210
|
+
result[0],
|
|
211
|
+
result[1],
|
|
212
|
+
lookup.get(result[0]),
|
|
213
|
+
lookup.get(result[1])
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createCurveLengthLookups(axes, positionAt) {
|
|
80
218
|
const lengthLookup = /* @__PURE__ */ new Map();
|
|
219
|
+
const axesLookups = {};
|
|
220
|
+
const sampleBuffer = createSampleBuffer();
|
|
221
|
+
for (const [key, axis] of Object.entries(axes)) {
|
|
222
|
+
const sampler = createAxisSampler(key, axis);
|
|
223
|
+
const first = sampler.next();
|
|
224
|
+
if (first.done) {
|
|
225
|
+
axesLookups[first.value[0]] = createLengthLookup(first.value[1]);
|
|
226
|
+
} else {
|
|
227
|
+
sampleBuffer.add(first.value, sampler);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
81
230
|
let lastPoint = positionAt(0);
|
|
82
231
|
let length = 0;
|
|
83
232
|
lengthLookup.set(0, 0);
|
|
84
|
-
|
|
233
|
+
while (sampleBuffer.size > 0) {
|
|
234
|
+
const t = Math.min(...sampleBuffer.keys());
|
|
85
235
|
const point = positionAt(t);
|
|
86
236
|
length = length + distance(lastPoint, point);
|
|
87
237
|
lengthLookup.set(length, t);
|
|
88
238
|
lastPoint = point;
|
|
239
|
+
const samplers = sampleBuffer.get(t);
|
|
240
|
+
for (const sampler of samplers) {
|
|
241
|
+
const next = sampler.next();
|
|
242
|
+
if (next.done) {
|
|
243
|
+
axesLookups[next.value[0]] = createLengthLookup(next.value[1]);
|
|
244
|
+
} else {
|
|
245
|
+
sampleBuffer.add(next.value, sampler);
|
|
246
|
+
}
|
|
247
|
+
sampleBuffer.remove(t, sampler);
|
|
248
|
+
}
|
|
89
249
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
invariant__default(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
94
|
-
const length2 = normalized * totalLength;
|
|
95
|
-
const result = search(length2);
|
|
96
|
-
invariant__default(result, "Length not found");
|
|
97
|
-
return util.remap(
|
|
98
|
-
length2,
|
|
99
|
-
result[0],
|
|
100
|
-
result[1],
|
|
101
|
-
lengthLookup.get(result[0]),
|
|
102
|
-
lengthLookup.get(result[1])
|
|
103
|
-
);
|
|
250
|
+
return {
|
|
251
|
+
curve: createLengthLookup({ length, lookup: lengthLookup }),
|
|
252
|
+
axes: axesLookups
|
|
104
253
|
};
|
|
105
254
|
}
|
|
106
255
|
|
|
107
|
-
function
|
|
256
|
+
function convertPointsToAxes(points, spline) {
|
|
108
257
|
const axisKeys = Object.keys(points[0]);
|
|
109
258
|
const axisPoints = {};
|
|
110
259
|
for (const key of axisKeys) {
|
|
@@ -120,6 +269,10 @@ function createCurve(points, spline) {
|
|
|
120
269
|
for (const key of axisKeys) {
|
|
121
270
|
axes[key] = axis.createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
|
|
122
271
|
}
|
|
272
|
+
return axes;
|
|
273
|
+
}
|
|
274
|
+
function createCurve(axes) {
|
|
275
|
+
const axisKeys = Object.keys(axes);
|
|
123
276
|
const solveT = (t, precision = 12) => {
|
|
124
277
|
const rounded = util.round(t);
|
|
125
278
|
invariant__default(
|
|
@@ -132,7 +285,7 @@ function createCurve(points, spline) {
|
|
|
132
285
|
}
|
|
133
286
|
return position;
|
|
134
287
|
};
|
|
135
|
-
const
|
|
288
|
+
const lengthLookups = createCurveLengthLookups(axes, solveT);
|
|
136
289
|
const positionAt = (input, normalize = 1, precision = 12) => {
|
|
137
290
|
invariant__default(
|
|
138
291
|
input >= 0 && input <= 1,
|
|
@@ -140,7 +293,23 @@ function createCurve(points, spline) {
|
|
|
140
293
|
);
|
|
141
294
|
let t = input;
|
|
142
295
|
if (normalize > 0) {
|
|
143
|
-
const denormalizedT =
|
|
296
|
+
const denormalizedT = lengthLookups.curve(input);
|
|
297
|
+
if (normalize === 1) {
|
|
298
|
+
t = denormalizedT;
|
|
299
|
+
} else {
|
|
300
|
+
t = (1 - normalize) * input + normalize * denormalizedT;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return solveT(t, precision);
|
|
304
|
+
};
|
|
305
|
+
const solveAlong = (axis, input, normalize = 1, precision = 12) => {
|
|
306
|
+
invariant__default(
|
|
307
|
+
input >= 0 && input <= 1,
|
|
308
|
+
`Input must be between 0 and 1, got ${input}`
|
|
309
|
+
);
|
|
310
|
+
let t = input;
|
|
311
|
+
if (normalize > 0) {
|
|
312
|
+
const denormalizedT = lengthLookups.axes[axis](input);
|
|
144
313
|
if (normalize === 1) {
|
|
145
314
|
t = denormalizedT;
|
|
146
315
|
} else {
|
|
@@ -157,38 +326,40 @@ function createCurve(points, spline) {
|
|
|
157
326
|
typeof t === "number",
|
|
158
327
|
`Could not solve for ${axis} = ${position}`
|
|
159
328
|
);
|
|
160
|
-
|
|
161
|
-
for (const key of axisKeys) {
|
|
162
|
-
if (key !== axis) {
|
|
163
|
-
result[key] = util.round(axes[key].solvePosition(t), precision);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
329
|
+
return positionAt(t, 0, precision);
|
|
167
330
|
};
|
|
168
331
|
return {
|
|
169
332
|
axes,
|
|
170
333
|
positionAt,
|
|
334
|
+
solveAlong,
|
|
171
335
|
solveWhere
|
|
172
336
|
};
|
|
173
337
|
}
|
|
174
338
|
function createBasisCurve(points, triplicateEndpoints = true) {
|
|
175
|
-
return createCurve(
|
|
339
|
+
return createCurve(
|
|
340
|
+
convertPointsToAxes(points, splines.splines.basis(triplicateEndpoints))
|
|
341
|
+
);
|
|
176
342
|
}
|
|
177
343
|
function createBezierCurve(points) {
|
|
178
|
-
return createCurve(points, splines.splines.bezier);
|
|
344
|
+
return createCurve(convertPointsToAxes(points, splines.splines.bezier));
|
|
179
345
|
}
|
|
180
346
|
function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
|
|
181
|
-
return createCurve(
|
|
347
|
+
return createCurve(
|
|
348
|
+
convertPointsToAxes(points, splines.splines.cardinal(tension, duplicateEndpoints))
|
|
349
|
+
);
|
|
182
350
|
}
|
|
183
351
|
function createCatmullRomCurve(points, duplicateEndpoints = true) {
|
|
184
|
-
return createCurve(
|
|
352
|
+
return createCurve(
|
|
353
|
+
convertPointsToAxes(points, splines.splines.catmullRom(duplicateEndpoints))
|
|
354
|
+
);
|
|
185
355
|
}
|
|
186
356
|
function createHermiteCurve(points) {
|
|
187
|
-
return createCurve(points, splines.splines.hermite);
|
|
357
|
+
return createCurve(convertPointsToAxes(points, splines.splines.hermite));
|
|
188
358
|
}
|
|
189
359
|
|
|
190
360
|
exports.createBasisCurve = createBasisCurve;
|
|
191
361
|
exports.createBezierCurve = createBezierCurve;
|
|
192
362
|
exports.createCardinalCurve = createCardinalCurve;
|
|
193
363
|
exports.createCatmullRomCurve = createCatmullRomCurve;
|
|
364
|
+
exports.createCurve = createCurve;
|
|
194
365
|
exports.createHermiteCurve = createHermiteCurve;
|
package/dist/curve.d.cts
CHANGED
|
@@ -7,12 +7,14 @@ type Point<Axis extends string | number> = Readonly<{
|
|
|
7
7
|
type Curve<Axis extends string | number> = {
|
|
8
8
|
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
9
|
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
11
|
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precision?: number) => Point<Axis>;
|
|
11
12
|
};
|
|
13
|
+
declare function createCurve<Axis extends string | number>(axes: Record<Axis, CurveAxis>): Curve<Axis>;
|
|
12
14
|
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
15
|
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
16
|
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
17
|
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
18
|
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
19
|
|
|
18
|
-
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
|
20
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createCurve, createHermiteCurve };
|
package/dist/curve.d.mts
CHANGED
|
@@ -7,12 +7,14 @@ type Point<Axis extends string | number> = Readonly<{
|
|
|
7
7
|
type Curve<Axis extends string | number> = {
|
|
8
8
|
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
9
|
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
11
|
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precision?: number) => Point<Axis>;
|
|
11
12
|
};
|
|
13
|
+
declare function createCurve<Axis extends string | number>(axes: Record<Axis, CurveAxis>): Curve<Axis>;
|
|
12
14
|
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
15
|
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
16
|
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
17
|
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
18
|
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
19
|
|
|
18
|
-
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
|
20
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createCurve, createHermiteCurve };
|
package/dist/curve.d.ts
CHANGED
|
@@ -7,12 +7,14 @@ type Point<Axis extends string | number> = Readonly<{
|
|
|
7
7
|
type Curve<Axis extends string | number> = {
|
|
8
8
|
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
9
|
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
11
|
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precision?: number) => Point<Axis>;
|
|
11
12
|
};
|
|
13
|
+
declare function createCurve<Axis extends string | number>(axes: Record<Axis, CurveAxis>): Curve<Axis>;
|
|
12
14
|
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
15
|
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
16
|
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
17
|
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
18
|
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
19
|
|
|
18
|
-
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
|
20
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createCurve, createHermiteCurve };
|
package/dist/curve.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import invariant from 'tiny-invariant';
|
|
2
|
-
import { createCurveAxis } from './
|
|
2
|
+
import { c as computeCubicAntiderivative, a as createCurveAxis } from './shared/curvy.7876e09e.mjs';
|
|
3
3
|
import { r as remap, a as round } from './shared/curvy.9cd8b44c.mjs';
|
|
4
4
|
import { splines } from './splines.mjs';
|
|
5
5
|
|
|
@@ -61,6 +61,101 @@ function createIntervalTree(boundaries) {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
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
|
+
}
|
|
64
159
|
function distance(a, b) {
|
|
65
160
|
const deltas = [];
|
|
66
161
|
for (const key of Object.keys(a)) {
|
|
@@ -68,37 +163,91 @@ function distance(a, b) {
|
|
|
68
163
|
}
|
|
69
164
|
return Math.hypot(...deltas);
|
|
70
165
|
}
|
|
71
|
-
function
|
|
72
|
-
const
|
|
73
|
-
|
|
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) {
|
|
74
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
|
+
}
|
|
75
224
|
let lastPoint = positionAt(0);
|
|
76
225
|
let length = 0;
|
|
77
226
|
lengthLookup.set(0, 0);
|
|
78
|
-
|
|
227
|
+
while (sampleBuffer.size > 0) {
|
|
228
|
+
const t = Math.min(...sampleBuffer.keys());
|
|
79
229
|
const point = positionAt(t);
|
|
80
230
|
length = length + distance(lastPoint, point);
|
|
81
231
|
lengthLookup.set(length, t);
|
|
82
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
|
+
}
|
|
83
243
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
invariant(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
88
|
-
const length2 = normalized * totalLength;
|
|
89
|
-
const result = search(length2);
|
|
90
|
-
invariant(result, "Length not found");
|
|
91
|
-
return remap(
|
|
92
|
-
length2,
|
|
93
|
-
result[0],
|
|
94
|
-
result[1],
|
|
95
|
-
lengthLookup.get(result[0]),
|
|
96
|
-
lengthLookup.get(result[1])
|
|
97
|
-
);
|
|
244
|
+
return {
|
|
245
|
+
curve: createLengthLookup({ length, lookup: lengthLookup }),
|
|
246
|
+
axes: axesLookups
|
|
98
247
|
};
|
|
99
248
|
}
|
|
100
249
|
|
|
101
|
-
function
|
|
250
|
+
function convertPointsToAxes(points, spline) {
|
|
102
251
|
const axisKeys = Object.keys(points[0]);
|
|
103
252
|
const axisPoints = {};
|
|
104
253
|
for (const key of axisKeys) {
|
|
@@ -114,6 +263,10 @@ function createCurve(points, spline) {
|
|
|
114
263
|
for (const key of axisKeys) {
|
|
115
264
|
axes[key] = createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
|
|
116
265
|
}
|
|
266
|
+
return axes;
|
|
267
|
+
}
|
|
268
|
+
function createCurve(axes) {
|
|
269
|
+
const axisKeys = Object.keys(axes);
|
|
117
270
|
const solveT = (t, precision = 12) => {
|
|
118
271
|
const rounded = round(t);
|
|
119
272
|
invariant(
|
|
@@ -126,7 +279,7 @@ function createCurve(points, spline) {
|
|
|
126
279
|
}
|
|
127
280
|
return position;
|
|
128
281
|
};
|
|
129
|
-
const
|
|
282
|
+
const lengthLookups = createCurveLengthLookups(axes, solveT);
|
|
130
283
|
const positionAt = (input, normalize = 1, precision = 12) => {
|
|
131
284
|
invariant(
|
|
132
285
|
input >= 0 && input <= 1,
|
|
@@ -134,7 +287,23 @@ function createCurve(points, spline) {
|
|
|
134
287
|
);
|
|
135
288
|
let t = input;
|
|
136
289
|
if (normalize > 0) {
|
|
137
|
-
const denormalizedT =
|
|
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);
|
|
138
307
|
if (normalize === 1) {
|
|
139
308
|
t = denormalizedT;
|
|
140
309
|
} else {
|
|
@@ -151,34 +320,35 @@ function createCurve(points, spline) {
|
|
|
151
320
|
typeof t === "number",
|
|
152
321
|
`Could not solve for ${axis} = ${position}`
|
|
153
322
|
);
|
|
154
|
-
|
|
155
|
-
for (const key of axisKeys) {
|
|
156
|
-
if (key !== axis) {
|
|
157
|
-
result[key] = round(axes[key].solvePosition(t), precision);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
323
|
+
return positionAt(t, 0, precision);
|
|
161
324
|
};
|
|
162
325
|
return {
|
|
163
326
|
axes,
|
|
164
327
|
positionAt,
|
|
328
|
+
solveAlong,
|
|
165
329
|
solveWhere
|
|
166
330
|
};
|
|
167
331
|
}
|
|
168
332
|
function createBasisCurve(points, triplicateEndpoints = true) {
|
|
169
|
-
return createCurve(
|
|
333
|
+
return createCurve(
|
|
334
|
+
convertPointsToAxes(points, splines.basis(triplicateEndpoints))
|
|
335
|
+
);
|
|
170
336
|
}
|
|
171
337
|
function createBezierCurve(points) {
|
|
172
|
-
return createCurve(points, splines.bezier);
|
|
338
|
+
return createCurve(convertPointsToAxes(points, splines.bezier));
|
|
173
339
|
}
|
|
174
340
|
function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
|
|
175
|
-
return createCurve(
|
|
341
|
+
return createCurve(
|
|
342
|
+
convertPointsToAxes(points, splines.cardinal(tension, duplicateEndpoints))
|
|
343
|
+
);
|
|
176
344
|
}
|
|
177
345
|
function createCatmullRomCurve(points, duplicateEndpoints = true) {
|
|
178
|
-
return createCurve(
|
|
346
|
+
return createCurve(
|
|
347
|
+
convertPointsToAxes(points, splines.catmullRom(duplicateEndpoints))
|
|
348
|
+
);
|
|
179
349
|
}
|
|
180
350
|
function createHermiteCurve(points) {
|
|
181
|
-
return createCurve(points, splines.hermite);
|
|
351
|
+
return createCurve(convertPointsToAxes(points, splines.hermite));
|
|
182
352
|
}
|
|
183
353
|
|
|
184
|
-
export { createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
|
354
|
+
export { createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createCurve, createHermiteCurve };
|