curvy 0.1.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/LICENSE.md +21 -0
- package/dist/index.cjs +670 -0
- package/dist/index.d.cts +95 -0
- package/dist/index.d.mts +95 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.mjs +636 -0
- package/package.json +50 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Tim Morris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
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;
|