curvy 0.1.1 → 0.3.1
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 +325 -0
- package/dist/axis.d.cts +20 -0
- package/dist/axis.d.mts +20 -0
- package/dist/axis.d.ts +20 -0
- package/dist/axis.mjs +314 -0
- package/dist/convert.cjs +74 -0
- package/dist/convert.d.cts +6 -0
- package/dist/convert.d.mts +6 -0
- package/dist/convert.d.ts +6 -0
- package/dist/convert.mjs +68 -0
- package/dist/curve.cjs +192 -0
- package/dist/curve.d.cts +18 -0
- package/dist/curve.d.mts +18 -0
- package/dist/curve.d.ts +18 -0
- package/dist/curve.mjs +182 -0
- package/dist/shared/curvy.0f1371d7.d.cts +38 -0
- package/dist/shared/curvy.0f1371d7.d.mts +38 -0
- package/dist/shared/curvy.0f1371d7.d.ts +38 -0
- package/dist/shared/curvy.262f6070.cjs +16 -0
- package/dist/shared/curvy.9cd8b44c.mjs +13 -0
- package/dist/splines.cjs +150 -0
- package/dist/splines.d.cts +27 -0
- package/dist/splines.d.mts +27 -0
- package/dist/splines.d.ts +27 -0
- package/dist/splines.mjs +138 -0
- package/package.json +24 -12
- package/LICENSE.md +0 -21
- package/dist/index.cjs +0 -670
- package/dist/index.d.cts +0 -95
- package/dist/index.d.mts +0 -95
- package/dist/index.d.ts +0 -95
- package/dist/index.mjs +0 -636
package/dist/index.cjs
DELETED
|
@@ -1,670 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const invariant = require('tiny-invariant');
|
|
4
|
-
|
|
5
|
-
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
|
-
|
|
7
|
-
const invariant__default = /*#__PURE__*/_interopDefaultCompat(invariant);
|
|
8
|
-
|
|
9
|
-
function toCubicScalars(values, stride) {
|
|
10
|
-
const chunks = [];
|
|
11
|
-
invariant__default(values.length >= 4, "Values must have at least 4 elements");
|
|
12
|
-
invariant__default(
|
|
13
|
-
stride === 1 || stride === 2 || stride === 3,
|
|
14
|
-
"Stride must be 1, 2, or 3"
|
|
15
|
-
);
|
|
16
|
-
if (stride === 3) {
|
|
17
|
-
invariant__default(
|
|
18
|
-
values.length % stride === 1,
|
|
19
|
-
"Values length must be a multiple of 3 plus 1"
|
|
20
|
-
);
|
|
21
|
-
} else {
|
|
22
|
-
invariant__default(
|
|
23
|
-
values.length % stride === 0,
|
|
24
|
-
`Values length must be a multiple of ${stride}`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
for (let i = 0; i <= values.length - 4; i += stride) {
|
|
28
|
-
chunks.push([
|
|
29
|
-
values[i],
|
|
30
|
-
values[i + 1],
|
|
31
|
-
values[i + 2],
|
|
32
|
-
values[i + 3]
|
|
33
|
-
]);
|
|
34
|
-
}
|
|
35
|
-
return chunks;
|
|
36
|
-
}
|
|
37
|
-
const bezier = [
|
|
38
|
-
[-1, 3, -3, 1],
|
|
39
|
-
[3, -6, 3, 0],
|
|
40
|
-
[-3, 3, 0, 0],
|
|
41
|
-
[1, 0, 0, 0]
|
|
42
|
-
];
|
|
43
|
-
function toBezierSegments(values) {
|
|
44
|
-
return toCubicScalars(values, 3);
|
|
45
|
-
}
|
|
46
|
-
const hermite = [
|
|
47
|
-
[2, 1, -2, 1],
|
|
48
|
-
[-3, -2, 3, -1],
|
|
49
|
-
[0, 1, 0, 0],
|
|
50
|
-
[1, 0, 0, 0]
|
|
51
|
-
];
|
|
52
|
-
function toHermiteSegments(values) {
|
|
53
|
-
return toCubicScalars(values, 2);
|
|
54
|
-
}
|
|
55
|
-
const cardinal = (a) => [
|
|
56
|
-
[-a, 2 - a, a - 2, a],
|
|
57
|
-
[2 * a, a - 3, 3 - 2 * a, -a],
|
|
58
|
-
[-a, 0, a, 0],
|
|
59
|
-
[0, 1, 0, 0]
|
|
60
|
-
];
|
|
61
|
-
function toCardinalSegments(values, duplicateEndpoints = true) {
|
|
62
|
-
return toCubicScalars(
|
|
63
|
-
duplicateEndpoints ? [values[0], ...values, values[values.length - 1]] : values,
|
|
64
|
-
1
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
const catmullRom = [
|
|
68
|
-
[-0.5, 1.5, -1.5, 0.5],
|
|
69
|
-
[1, -2.5, 2, -0.5],
|
|
70
|
-
[-0.5, 0, 0.5, 0],
|
|
71
|
-
[0, 1, 0, 0]
|
|
72
|
-
];
|
|
73
|
-
function toCatmullRomSegments(values, duplicateEndpoints = true) {
|
|
74
|
-
return toCubicScalars(
|
|
75
|
-
duplicateEndpoints ? [values[0], ...values, values[values.length - 1]] : values,
|
|
76
|
-
1
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
const ONE_SIXTH = 1 / 6;
|
|
80
|
-
const basis = [
|
|
81
|
-
[-ONE_SIXTH, 0.5, -0.5, ONE_SIXTH],
|
|
82
|
-
[0.5, -1, 0.5, 0],
|
|
83
|
-
[-0.5, 0, 0.5, 0],
|
|
84
|
-
[ONE_SIXTH, 4 * ONE_SIXTH, ONE_SIXTH, 0]
|
|
85
|
-
];
|
|
86
|
-
function toBasisSegments(values, triplicateEndpoints = true) {
|
|
87
|
-
return toCubicScalars(
|
|
88
|
-
triplicateEndpoints ? [
|
|
89
|
-
values[0],
|
|
90
|
-
values[0],
|
|
91
|
-
...values,
|
|
92
|
-
values[values.length - 1],
|
|
93
|
-
values[values.length - 1]
|
|
94
|
-
] : values,
|
|
95
|
-
1
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const precision = 12;
|
|
100
|
-
const scale = 10 ** precision;
|
|
101
|
-
function round(value) {
|
|
102
|
-
return Math.round(value * scale) / scale;
|
|
103
|
-
}
|
|
104
|
-
function remap(x, x1, x2, y1, y2) {
|
|
105
|
-
return round(y1 + (x - x1) / (x2 - x1) * (y2 - y1));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function getDerivativeUnitRoots(derivative) {
|
|
109
|
-
const roots = /* @__PURE__ */ new Set();
|
|
110
|
-
if (derivative[0] !== 0) {
|
|
111
|
-
const discriminant = derivative[1] ** 2 - 4 * derivative[0] * derivative[2];
|
|
112
|
-
if (discriminant > 0) {
|
|
113
|
-
const sqrtDiscriminant = Math.sqrt(discriminant);
|
|
114
|
-
const denominator = 2 * derivative[0];
|
|
115
|
-
const root1 = (-derivative[1] - sqrtDiscriminant) / denominator;
|
|
116
|
-
const root2 = (-derivative[1] + sqrtDiscriminant) / denominator;
|
|
117
|
-
if (root1 > 0 && root1 < 1) {
|
|
118
|
-
roots.add(root1);
|
|
119
|
-
}
|
|
120
|
-
if (root2 > 0 && root2 < 1) {
|
|
121
|
-
roots.add(root2);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
} else if (derivative[1] !== 0) {
|
|
125
|
-
const root = -derivative[2] / derivative[1];
|
|
126
|
-
if (root >= 0 && root <= 1) {
|
|
127
|
-
roots.add(root);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return roots;
|
|
131
|
-
}
|
|
132
|
-
function getLocalExtrema(derivativeRoots, polynomial) {
|
|
133
|
-
const localExtrema = /* @__PURE__ */ new Map();
|
|
134
|
-
localExtrema.set(0, polynomial[3]);
|
|
135
|
-
for (const root of derivativeRoots) {
|
|
136
|
-
const t2 = root * root;
|
|
137
|
-
const t3 = t2 * root;
|
|
138
|
-
localExtrema.set(
|
|
139
|
-
root,
|
|
140
|
-
round(
|
|
141
|
-
polynomial[0] * t3 + polynomial[1] * t2 + polynomial[2] * root + polynomial[3]
|
|
142
|
-
)
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
localExtrema.set(
|
|
146
|
-
1,
|
|
147
|
-
round(polynomial[0] + polynomial[1] + polynomial[2] + polynomial[3])
|
|
148
|
-
);
|
|
149
|
-
return localExtrema;
|
|
150
|
-
}
|
|
151
|
-
function getMonotonicity(localExtrema, derivative) {
|
|
152
|
-
if (localExtrema.size > 2) {
|
|
153
|
-
return "none";
|
|
154
|
-
}
|
|
155
|
-
if (derivative[0] === 0 && derivative[1] === 0 && derivative[2] === 0) {
|
|
156
|
-
return "constant";
|
|
157
|
-
}
|
|
158
|
-
let test = derivative[0] / 4 + derivative[1] / 2 + derivative[2];
|
|
159
|
-
if (test === 0) {
|
|
160
|
-
test = derivative[2];
|
|
161
|
-
}
|
|
162
|
-
return Math.sign(test) > 0 ? "increasing" : "decreasing";
|
|
163
|
-
}
|
|
164
|
-
function getPolynomial(matrix, points) {
|
|
165
|
-
return [
|
|
166
|
-
round(
|
|
167
|
-
points[0] * matrix[0][0] + points[1] * matrix[0][1] + points[2] * matrix[0][2] + points[3] * matrix[0][3]
|
|
168
|
-
),
|
|
169
|
-
round(
|
|
170
|
-
points[0] * matrix[1][0] + points[1] * matrix[1][1] + points[2] * matrix[1][2] + points[3] * matrix[1][3]
|
|
171
|
-
),
|
|
172
|
-
round(
|
|
173
|
-
points[0] * matrix[2][0] + points[1] * matrix[2][1] + points[2] * matrix[2][2] + points[3] * matrix[2][3]
|
|
174
|
-
),
|
|
175
|
-
round(
|
|
176
|
-
points[0] * matrix[3][0] + points[1] * matrix[3][1] + points[2] * matrix[3][2] + points[3] * matrix[3][3]
|
|
177
|
-
)
|
|
178
|
-
];
|
|
179
|
-
}
|
|
180
|
-
function createCurveSegment(matrix, points) {
|
|
181
|
-
const polynomial = getPolynomial(matrix, points);
|
|
182
|
-
const derivative = [
|
|
183
|
-
round(polynomial[0] * 3),
|
|
184
|
-
round(polynomial[1] * 2),
|
|
185
|
-
polynomial[2]
|
|
186
|
-
];
|
|
187
|
-
const derivativeRoots = getDerivativeUnitRoots(derivative);
|
|
188
|
-
const localExtrema = getLocalExtrema(derivativeRoots, polynomial);
|
|
189
|
-
const max = Math.max(...localExtrema.values());
|
|
190
|
-
const min = Math.min(...localExtrema.values());
|
|
191
|
-
const monotonicity = getMonotonicity(localExtrema, derivative);
|
|
192
|
-
return {
|
|
193
|
-
localExtrema,
|
|
194
|
-
monotonicity,
|
|
195
|
-
max,
|
|
196
|
-
min,
|
|
197
|
-
points,
|
|
198
|
-
polynomial,
|
|
199
|
-
derivative,
|
|
200
|
-
solve: (t) => {
|
|
201
|
-
const t1 = round(t);
|
|
202
|
-
const t2 = t1 * t1;
|
|
203
|
-
const t3 = t2 * t1;
|
|
204
|
-
return round(
|
|
205
|
-
polynomial[0] * t3 + polynomial[1] * t2 + polynomial[2] * t1 + polynomial[3]
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function createBezierSegment(points) {
|
|
211
|
-
return createCurveSegment(bezier, points);
|
|
212
|
-
}
|
|
213
|
-
function createHermiteSegment(points) {
|
|
214
|
-
return createCurveSegment(hermite, points);
|
|
215
|
-
}
|
|
216
|
-
function createCardinalSegment(points, tension = 0.5) {
|
|
217
|
-
return createCurveSegment(cardinal(tension), points);
|
|
218
|
-
}
|
|
219
|
-
function createCatmullRomSegment(points) {
|
|
220
|
-
return createCurveSegment(catmullRom, points);
|
|
221
|
-
}
|
|
222
|
-
function createBasisSegment(points) {
|
|
223
|
-
return createCurveSegment(basis, points);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function determinant(matrix) {
|
|
227
|
-
const size = matrix.length;
|
|
228
|
-
invariant__default(
|
|
229
|
-
matrix.every((row) => row.length === size),
|
|
230
|
-
"matrix must be square"
|
|
231
|
-
);
|
|
232
|
-
let result = 0;
|
|
233
|
-
if (size === 1) {
|
|
234
|
-
result = matrix[0][0];
|
|
235
|
-
} else if (size === 2) {
|
|
236
|
-
result = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
|
|
237
|
-
} else {
|
|
238
|
-
for (const [columnIndex, value] of matrix[0].entries()) {
|
|
239
|
-
const subMatrix = [];
|
|
240
|
-
for (const row of matrix.slice(1)) {
|
|
241
|
-
const subMatrixRow = [];
|
|
242
|
-
for (const [subColumnIndex, subColumnValue] of row.entries()) {
|
|
243
|
-
if (subColumnIndex !== columnIndex) {
|
|
244
|
-
subMatrixRow.push(subColumnValue);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
subMatrix.push(subMatrixRow);
|
|
248
|
-
}
|
|
249
|
-
result = result + (-1) ** (columnIndex + 2) * value * determinant(subMatrix);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
function solveLinearSystem(coefficients, solutions) {
|
|
255
|
-
const coefficientDeterminant = determinant(coefficients);
|
|
256
|
-
invariant__default(
|
|
257
|
-
coefficientDeterminant !== 0,
|
|
258
|
-
"Cannot solve system of equations when coefficient matrix determinant is zero"
|
|
259
|
-
);
|
|
260
|
-
const variables = [0, 0, 0, 0];
|
|
261
|
-
for (let index = 0; index < solutions.length; index++) {
|
|
262
|
-
const solutionMatrix = [];
|
|
263
|
-
for (const [rowIndex, row] of coefficients.entries()) {
|
|
264
|
-
const solutionMatrixRow = [];
|
|
265
|
-
for (const [columnIndex, column] of row.entries()) {
|
|
266
|
-
solutionMatrixRow.push(
|
|
267
|
-
columnIndex === index ? solutions[rowIndex] : column
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
solutionMatrix.push(solutionMatrixRow);
|
|
271
|
-
}
|
|
272
|
-
variables[index] = determinant(solutionMatrix) / coefficientDeterminant;
|
|
273
|
-
}
|
|
274
|
-
return variables;
|
|
275
|
-
}
|
|
276
|
-
function convertScalars(points, sourceSpline, targetSpline) {
|
|
277
|
-
const solutions = solveLinearSystem(
|
|
278
|
-
targetSpline,
|
|
279
|
-
getPolynomial(sourceSpline, points)
|
|
280
|
-
);
|
|
281
|
-
return [
|
|
282
|
-
round(solutions[0]),
|
|
283
|
-
round(solutions[1]),
|
|
284
|
-
round(solutions[2]),
|
|
285
|
-
round(solutions[3])
|
|
286
|
-
];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function filterExtrema(extrema) {
|
|
290
|
-
const extremaArray = [...extrema.entries()];
|
|
291
|
-
for (let index = 1; index < extremaArray.length - 1; index++) {
|
|
292
|
-
const [t, currentValue] = extremaArray[index];
|
|
293
|
-
const previousValue = extremaArray[index - 1][1];
|
|
294
|
-
const nextValue = extremaArray[index + 1][1];
|
|
295
|
-
if (previousValue <= currentValue && currentValue <= nextValue || previousValue >= currentValue && currentValue >= nextValue) {
|
|
296
|
-
extrema.delete(t);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function createCurve(matrix, segmentScalars) {
|
|
301
|
-
const segments = [];
|
|
302
|
-
let min = 0;
|
|
303
|
-
let max = 0;
|
|
304
|
-
let monotonicity = "none";
|
|
305
|
-
const normalizedToSegment = segmentScalars.length;
|
|
306
|
-
const segmentToNormalized = 1 / normalizedToSegment;
|
|
307
|
-
const extrema = /* @__PURE__ */ new Map();
|
|
308
|
-
for (const [index, points] of segmentScalars.entries()) {
|
|
309
|
-
const segment = createCurveSegment(matrix, points);
|
|
310
|
-
segments.push(segment);
|
|
311
|
-
if (index === 0) {
|
|
312
|
-
min = segment.min;
|
|
313
|
-
max = segment.max;
|
|
314
|
-
monotonicity = segment.monotonicity;
|
|
315
|
-
} else {
|
|
316
|
-
min = Math.min(min, segment.min);
|
|
317
|
-
max = Math.max(max, segment.max);
|
|
318
|
-
monotonicity = segment.monotonicity === monotonicity ? monotonicity : "none";
|
|
319
|
-
}
|
|
320
|
-
for (const [t, value] of segment.localExtrema) {
|
|
321
|
-
const curveT = round((t + index) * segmentToNormalized);
|
|
322
|
-
extrema.set(curveT, value);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (extrema.size > 2) {
|
|
326
|
-
filterExtrema(extrema);
|
|
327
|
-
}
|
|
328
|
-
return {
|
|
329
|
-
extrema,
|
|
330
|
-
max,
|
|
331
|
-
min,
|
|
332
|
-
monotonicity,
|
|
333
|
-
segments,
|
|
334
|
-
solve: (t) => {
|
|
335
|
-
invariant__default(t >= 0 && t <= 1, "t must be between 0 and 1");
|
|
336
|
-
const t1 = round(t);
|
|
337
|
-
if (t1 === 1) {
|
|
338
|
-
return segments[segments.length - 1].solve(1);
|
|
339
|
-
}
|
|
340
|
-
const segmentT = round(t1 * segments.length);
|
|
341
|
-
const segmentIndex = Math.floor(segmentT);
|
|
342
|
-
const segment = segments[segmentIndex];
|
|
343
|
-
return segment.solve(segmentT - segmentIndex);
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
function createBezierCurve(values) {
|
|
348
|
-
return createCurve(bezier, toBezierSegments(values));
|
|
349
|
-
}
|
|
350
|
-
function createHermiteCurve(values) {
|
|
351
|
-
return createCurve(hermite, toHermiteSegments(values));
|
|
352
|
-
}
|
|
353
|
-
const defaultCardinalCurveOptions = {
|
|
354
|
-
a: 0.5,
|
|
355
|
-
duplicateEndpoints: true
|
|
356
|
-
};
|
|
357
|
-
function createCardinalCurve(values, options = {}) {
|
|
358
|
-
const { a, duplicateEndpoints } = {
|
|
359
|
-
...defaultCardinalCurveOptions,
|
|
360
|
-
...options
|
|
361
|
-
};
|
|
362
|
-
return createCurve(
|
|
363
|
-
cardinal(a),
|
|
364
|
-
toCardinalSegments(values, duplicateEndpoints)
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
const defaultCatmullRomCurveOptions = {
|
|
368
|
-
duplicateEndpoints: true
|
|
369
|
-
};
|
|
370
|
-
function createCatmullRomCurve(values, options = {}) {
|
|
371
|
-
const { duplicateEndpoints } = {
|
|
372
|
-
...defaultCatmullRomCurveOptions,
|
|
373
|
-
...options
|
|
374
|
-
};
|
|
375
|
-
return createCurve(
|
|
376
|
-
catmullRom,
|
|
377
|
-
toCatmullRomSegments(values, duplicateEndpoints)
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
const defaultBasisCurveOptions = {
|
|
381
|
-
triplicateEndpoints: true
|
|
382
|
-
};
|
|
383
|
-
function createBasisCurve(values, options = {}) {
|
|
384
|
-
const { triplicateEndpoints } = {
|
|
385
|
-
...defaultBasisCurveOptions,
|
|
386
|
-
...options
|
|
387
|
-
};
|
|
388
|
-
return createCurve(basis, toBasisSegments(values, triplicateEndpoints));
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function createNode(interval) {
|
|
392
|
-
return {
|
|
393
|
-
interval,
|
|
394
|
-
left: null,
|
|
395
|
-
right: null
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
function takeCenterInterval(boundaries) {
|
|
399
|
-
const mid = Math.floor((boundaries.length - 1) / 2);
|
|
400
|
-
const left = mid > 0 ? boundaries.slice(0, mid + 1) : null;
|
|
401
|
-
const right = mid + 1 < boundaries.length - 1 ? boundaries.slice(mid + 1) : null;
|
|
402
|
-
return {
|
|
403
|
-
center: [boundaries[mid], boundaries[mid + 1]],
|
|
404
|
-
left,
|
|
405
|
-
right
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
function createIntervalTree(boundaries) {
|
|
409
|
-
const container = createNode([0, 0]);
|
|
410
|
-
const branches = [{ parent: container, direction: "left", boundaries }];
|
|
411
|
-
for (const branch of branches) {
|
|
412
|
-
const { parent, direction, boundaries: boundaries2 } = branch;
|
|
413
|
-
const { center, left, right } = takeCenterInterval(boundaries2);
|
|
414
|
-
const node = createNode(center);
|
|
415
|
-
if (direction === "left") {
|
|
416
|
-
parent.left = node;
|
|
417
|
-
} else {
|
|
418
|
-
parent.right = node;
|
|
419
|
-
}
|
|
420
|
-
if (left !== null) {
|
|
421
|
-
branches.push({ parent: node, direction: "left", boundaries: left });
|
|
422
|
-
}
|
|
423
|
-
if (right !== null) {
|
|
424
|
-
branches.push({ parent: node, direction: "right", boundaries: right });
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const root = container.left;
|
|
428
|
-
function searchNode(node, query) {
|
|
429
|
-
if (node.interval[0] <= query && node.interval[1] >= query) {
|
|
430
|
-
return node.interval;
|
|
431
|
-
}
|
|
432
|
-
if (node.interval[0] > query && node.left) {
|
|
433
|
-
return searchNode(node.left, query);
|
|
434
|
-
}
|
|
435
|
-
if (node.interval[1] < query && node.right) {
|
|
436
|
-
return searchNode(node.right, query);
|
|
437
|
-
}
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
function search(query) {
|
|
441
|
-
return searchNode(root, query);
|
|
442
|
-
}
|
|
443
|
-
return {
|
|
444
|
-
root,
|
|
445
|
-
search
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function createSamples(count) {
|
|
450
|
-
return Array.from({ length: count }, (_, i) => round(i / (count - 1)));
|
|
451
|
-
}
|
|
452
|
-
function cubicRootsInUnitInterval(polynomial) {
|
|
453
|
-
const roots = /* @__PURE__ */ new Set();
|
|
454
|
-
const a2 = polynomial[1] / polynomial[0];
|
|
455
|
-
const a1 = polynomial[2] / polynomial[0];
|
|
456
|
-
const a0 = polynomial[3] / polynomial[0];
|
|
457
|
-
const q = a1 / 3 - a2 ** 2 / 9;
|
|
458
|
-
const r = (a1 * a2 - 3 * a0) / 6 - a2 ** 3 / 27;
|
|
459
|
-
const discriminant = q ** 3 + r ** 2;
|
|
460
|
-
if (discriminant > 0) {
|
|
461
|
-
const a = Math.cbrt(Math.abs(r) + Math.sqrt(discriminant));
|
|
462
|
-
const t = r < 0 ? q / a - a : a - q / a;
|
|
463
|
-
const root = round(t - a2 / 3);
|
|
464
|
-
if (0 <= root && root <= 1) {
|
|
465
|
-
roots.add(root);
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
const theta = Math.acos(r / Math.sqrt((-q) ** 3));
|
|
469
|
-
const theta1 = theta / 3;
|
|
470
|
-
const theta2 = theta1 - 2 * Math.PI / 3;
|
|
471
|
-
const theta3 = theta1 + 2 * Math.PI / 3;
|
|
472
|
-
const twoRootQ = 2 * Math.sqrt(-q);
|
|
473
|
-
const a2over3 = a2 / 3;
|
|
474
|
-
const root1 = round(twoRootQ * Math.cos(theta1) - a2over3);
|
|
475
|
-
const root2 = round(twoRootQ * Math.cos(theta2) - a2over3);
|
|
476
|
-
const root3 = round(twoRootQ * Math.cos(theta3) - a2over3);
|
|
477
|
-
if (0 <= root1 && root1 <= 1) {
|
|
478
|
-
roots.add(root1);
|
|
479
|
-
}
|
|
480
|
-
if (0 <= root2 && root2 <= 1) {
|
|
481
|
-
roots.add(root2);
|
|
482
|
-
}
|
|
483
|
-
if (0 <= root3 && root3 <= 1) {
|
|
484
|
-
roots.add(root3);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return roots;
|
|
488
|
-
}
|
|
489
|
-
function addPointsOfInterest(samples, curve) {
|
|
490
|
-
const segmentToNormalized = 1 / curve.segments.length;
|
|
491
|
-
for (const [index, segment] of curve.segments.entries()) {
|
|
492
|
-
samples.set(
|
|
493
|
-
index * segmentToNormalized,
|
|
494
|
-
curve.solve(index * segmentToNormalized)
|
|
495
|
-
);
|
|
496
|
-
const extrema = Array.from(segment.localExtrema.entries());
|
|
497
|
-
for (const [t, extreme] of extrema) {
|
|
498
|
-
samples.set(round((index + t) * segmentToNormalized), extreme);
|
|
499
|
-
}
|
|
500
|
-
if (extrema.length === 4) {
|
|
501
|
-
const segmentInflectionX = (extrema[1][0] + extrema[2][0]) / 2;
|
|
502
|
-
const segmentInflectionY = (extrema[1][1] + extrema[2][1]) / 2;
|
|
503
|
-
samples.set(
|
|
504
|
-
round((index + segmentInflectionX) * segmentToNormalized),
|
|
505
|
-
round(segmentInflectionY)
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
const roots = cubicRootsInUnitInterval(segment.polynomial);
|
|
509
|
-
for (const root of roots) {
|
|
510
|
-
samples.set(round((index + root) * segmentToNormalized), 0);
|
|
511
|
-
}
|
|
512
|
-
samples.set(
|
|
513
|
-
(index + 1) * segmentToNormalized,
|
|
514
|
-
curve.solve((index + 1) * segmentToNormalized)
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
function getLookupTableIntegral(x1, x2, y1, y2) {
|
|
519
|
-
const m = (y2 - y1) / (x2 - x1);
|
|
520
|
-
const b = y1 - m * x1;
|
|
521
|
-
const s1 = x1 ** 2 * m / 2 + b * x1;
|
|
522
|
-
const s2 = x2 ** 2 * m / 2 + b * x2;
|
|
523
|
-
return Math.abs(s2 - s1);
|
|
524
|
-
}
|
|
525
|
-
function getCurveSegmentIntegral(segment, start, end) {
|
|
526
|
-
const a = segment.polynomial[0] / 4;
|
|
527
|
-
const b = segment.polynomial[1] / 3;
|
|
528
|
-
const c = segment.polynomial[2] / 2;
|
|
529
|
-
const d = segment.polynomial[3];
|
|
530
|
-
const s1 = a * start ** 4 + b * start ** 3 + c * start ** 2 + d * start;
|
|
531
|
-
const s2 = a * end ** 4 + b * end ** 3 + c * end ** 2 + d * end;
|
|
532
|
-
return Math.abs(s2 - s1);
|
|
533
|
-
}
|
|
534
|
-
function enforceMaxError(curve, samples, maxError) {
|
|
535
|
-
const intervalBoundaries = Array.from(samples.keys()).sort((a, b) => a - b);
|
|
536
|
-
const normalizedToSegment = curve.segments.length;
|
|
537
|
-
const queue = [...intervalBoundaries];
|
|
538
|
-
while (queue.length > 1) {
|
|
539
|
-
const start = queue[0];
|
|
540
|
-
const end = queue[1];
|
|
541
|
-
const y1 = curve.solve(start);
|
|
542
|
-
const y2 = curve.solve(end);
|
|
543
|
-
const segment = curve.segments[Math.floor(start * normalizedToSegment)];
|
|
544
|
-
const lutIntegral = getLookupTableIntegral(start, end, y1, y2);
|
|
545
|
-
const curveIntegral = getCurveSegmentIntegral(segment, start, end);
|
|
546
|
-
const error = Math.abs(lutIntegral - curveIntegral);
|
|
547
|
-
if (error > maxError) {
|
|
548
|
-
queue.splice(1, 0, (start + end) / 2);
|
|
549
|
-
} else {
|
|
550
|
-
samples.set(start, y1);
|
|
551
|
-
queue.shift();
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
samples.set(queue[0], curve.solve(queue[0]));
|
|
555
|
-
}
|
|
556
|
-
function sampleCurveLength(curve, minSamples, maxError) {
|
|
557
|
-
const samples = /* @__PURE__ */ new Map();
|
|
558
|
-
for (const t of createSamples(minSamples)) {
|
|
559
|
-
samples.set(t, curve.solve(t));
|
|
560
|
-
}
|
|
561
|
-
if (maxError !== false) {
|
|
562
|
-
addPointsOfInterest(samples, curve);
|
|
563
|
-
enforceMaxError(curve, samples, maxError);
|
|
564
|
-
}
|
|
565
|
-
const sortedKeys = Array.from(samples.keys()).sort((a, b) => a - b);
|
|
566
|
-
return new Map(sortedKeys.map((key) => [key, samples.get(key)]));
|
|
567
|
-
}
|
|
568
|
-
const defaultOptions = {
|
|
569
|
-
minSamples: 33,
|
|
570
|
-
maxError: 1e-5
|
|
571
|
-
};
|
|
572
|
-
function createLengthLookup(curve, options = {}) {
|
|
573
|
-
const { minSamples, maxError } = { ...defaultOptions, ...options };
|
|
574
|
-
invariant__default(minSamples >= 2, "minSamples must be at least 2");
|
|
575
|
-
invariant__default(
|
|
576
|
-
maxError === false || maxError > 0,
|
|
577
|
-
"maxError must be positive or false"
|
|
578
|
-
);
|
|
579
|
-
const samples = sampleCurveLength(curve, minSamples, maxError);
|
|
580
|
-
let totalLength = 0;
|
|
581
|
-
const lengths = /* @__PURE__ */ new Map();
|
|
582
|
-
let previousT = null;
|
|
583
|
-
for (const tValue of samples.keys()) {
|
|
584
|
-
if (previousT !== null) {
|
|
585
|
-
const yValue = samples.get(tValue);
|
|
586
|
-
const previousYValue = samples.get(previousT);
|
|
587
|
-
const length = Math.sqrt(
|
|
588
|
-
(tValue - previousT) ** 2 + (yValue - previousYValue) ** 2
|
|
589
|
-
);
|
|
590
|
-
totalLength += length;
|
|
591
|
-
}
|
|
592
|
-
lengths.set(tValue, totalLength);
|
|
593
|
-
previousT = tValue;
|
|
594
|
-
}
|
|
595
|
-
const toNormalizedLength = 1 / totalLength;
|
|
596
|
-
const remappedSamples = /* @__PURE__ */ new Map();
|
|
597
|
-
for (const [t, length] of lengths.entries()) {
|
|
598
|
-
remappedSamples.set(round(length * toNormalizedLength), t);
|
|
599
|
-
}
|
|
600
|
-
return remappedSamples;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const defaultSolverOptions = {
|
|
604
|
-
minSamples: 64,
|
|
605
|
-
maxError: 1e-3,
|
|
606
|
-
lengthAdjust: 1
|
|
607
|
-
};
|
|
608
|
-
function createSolver(curve, options = {}) {
|
|
609
|
-
const {
|
|
610
|
-
maxError,
|
|
611
|
-
minSamples,
|
|
612
|
-
lengthAdjust: defaultLengthAdjust
|
|
613
|
-
} = {
|
|
614
|
-
...defaultSolverOptions,
|
|
615
|
-
...options
|
|
616
|
-
};
|
|
617
|
-
const lengthSamples = createLengthLookup(curve, { maxError, minSamples });
|
|
618
|
-
const { search } = createIntervalTree(Array.from(lengthSamples.keys()));
|
|
619
|
-
return function solve(input, lengthAdjust = defaultLengthAdjust) {
|
|
620
|
-
const query = round(input);
|
|
621
|
-
invariant__default(query >= 0 && query <= 1, "input must be between 0 and 1");
|
|
622
|
-
let t;
|
|
623
|
-
if (lengthAdjust === 0) {
|
|
624
|
-
t = query;
|
|
625
|
-
} else {
|
|
626
|
-
const lengthRange = search(query);
|
|
627
|
-
invariant__default(lengthRange, "lengthRange must be defined");
|
|
628
|
-
const [length1, length2] = lengthRange;
|
|
629
|
-
const t1 = lengthSamples.get(length1);
|
|
630
|
-
const t2 = lengthSamples.get(length2);
|
|
631
|
-
const lengthT = remap(query, length1, length2, t1, t2);
|
|
632
|
-
if (lengthAdjust === 1) {
|
|
633
|
-
t = lengthT;
|
|
634
|
-
} else {
|
|
635
|
-
t = (1 - lengthAdjust) * query + lengthAdjust * lengthT;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return curve.solve(t);
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
exports.basis = basis;
|
|
643
|
-
exports.bezier = bezier;
|
|
644
|
-
exports.cardinal = cardinal;
|
|
645
|
-
exports.catmullRom = catmullRom;
|
|
646
|
-
exports.convertScalars = convertScalars;
|
|
647
|
-
exports.createBasisCurve = createBasisCurve;
|
|
648
|
-
exports.createBasisSegment = createBasisSegment;
|
|
649
|
-
exports.createBezierCurve = createBezierCurve;
|
|
650
|
-
exports.createBezierSegment = createBezierSegment;
|
|
651
|
-
exports.createCardinalCurve = createCardinalCurve;
|
|
652
|
-
exports.createCardinalSegment = createCardinalSegment;
|
|
653
|
-
exports.createCatmullRomCurve = createCatmullRomCurve;
|
|
654
|
-
exports.createCatmullRomSegment = createCatmullRomSegment;
|
|
655
|
-
exports.createCurve = createCurve;
|
|
656
|
-
exports.createCurveSegment = createCurveSegment;
|
|
657
|
-
exports.createHermiteCurve = createHermiteCurve;
|
|
658
|
-
exports.createHermiteSegment = createHermiteSegment;
|
|
659
|
-
exports.createIntervalTree = createIntervalTree;
|
|
660
|
-
exports.createLengthLookup = createLengthLookup;
|
|
661
|
-
exports.createSolver = createSolver;
|
|
662
|
-
exports.hermite = hermite;
|
|
663
|
-
exports.remap = remap;
|
|
664
|
-
exports.round = round;
|
|
665
|
-
exports.toBasisSegments = toBasisSegments;
|
|
666
|
-
exports.toBezierSegments = toBezierSegments;
|
|
667
|
-
exports.toCardinalSegments = toCardinalSegments;
|
|
668
|
-
exports.toCatmullRomSegments = toCatmullRomSegments;
|
|
669
|
-
exports.toCubicScalars = toCubicScalars;
|
|
670
|
-
exports.toHermiteSegments = toHermiteSegments;
|