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/convert.cjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const invariant = require('tiny-invariant');
|
|
4
|
+
const splines = require('./splines.cjs');
|
|
5
|
+
const util = require('./shared/curvy.262f6070.cjs');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
8
|
+
|
|
9
|
+
const invariant__default = /*#__PURE__*/_interopDefaultCompat(invariant);
|
|
10
|
+
|
|
11
|
+
function determinant(matrix) {
|
|
12
|
+
const size = matrix.length;
|
|
13
|
+
invariant__default(
|
|
14
|
+
matrix.every((row) => row.length === size),
|
|
15
|
+
"matrix must be square"
|
|
16
|
+
);
|
|
17
|
+
let result = 0;
|
|
18
|
+
if (size === 1) {
|
|
19
|
+
result = matrix[0][0];
|
|
20
|
+
} else if (size === 2) {
|
|
21
|
+
result = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
|
|
22
|
+
} else {
|
|
23
|
+
for (const [columnIndex, value] of matrix[0].entries()) {
|
|
24
|
+
const subMatrix = [];
|
|
25
|
+
for (const row of matrix.slice(1)) {
|
|
26
|
+
const subMatrixRow = [];
|
|
27
|
+
for (const [subColumnIndex, subColumnValue] of row.entries()) {
|
|
28
|
+
if (subColumnIndex !== columnIndex) {
|
|
29
|
+
subMatrixRow.push(subColumnValue);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
subMatrix.push(subMatrixRow);
|
|
33
|
+
}
|
|
34
|
+
result = result + (-1) ** (columnIndex + 2) * value * determinant(subMatrix);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function solveLinearSystem(coefficients, solutions) {
|
|
40
|
+
const coefficientDeterminant = determinant(coefficients);
|
|
41
|
+
invariant__default(
|
|
42
|
+
coefficientDeterminant !== 0,
|
|
43
|
+
"Cannot solve system of equations when coefficient matrix determinant is zero"
|
|
44
|
+
);
|
|
45
|
+
const variables = [0, 0, 0, 0];
|
|
46
|
+
for (let index = 0; index < solutions.length; index++) {
|
|
47
|
+
const solutionMatrix = [];
|
|
48
|
+
for (const [rowIndex, row] of coefficients.entries()) {
|
|
49
|
+
const solutionMatrixRow = [];
|
|
50
|
+
for (const [columnIndex, column] of row.entries()) {
|
|
51
|
+
solutionMatrixRow.push(
|
|
52
|
+
columnIndex === index ? solutions[rowIndex] : column
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
solutionMatrix.push(solutionMatrixRow);
|
|
56
|
+
}
|
|
57
|
+
variables[index] = determinant(solutionMatrix) / coefficientDeterminant;
|
|
58
|
+
}
|
|
59
|
+
return variables;
|
|
60
|
+
}
|
|
61
|
+
function convertScalars(points, sourceSpline, targetSpline) {
|
|
62
|
+
const solutions = solveLinearSystem(
|
|
63
|
+
targetSpline,
|
|
64
|
+
splines.pointsToCoefficients(sourceSpline, points)
|
|
65
|
+
);
|
|
66
|
+
return [
|
|
67
|
+
util.round(solutions[0]),
|
|
68
|
+
util.round(solutions[1]),
|
|
69
|
+
util.round(solutions[2]),
|
|
70
|
+
util.round(solutions[3])
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exports.convertScalars = convertScalars;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { a as CubicCoefficients } from './shared/curvy.0f1371d7.cjs';
|
|
2
|
+
import { Matrix4x4 } from './splines.cjs';
|
|
3
|
+
|
|
4
|
+
declare function convertScalars(points: CubicCoefficients, sourceSpline: Matrix4x4, targetSpline: Matrix4x4): CubicCoefficients;
|
|
5
|
+
|
|
6
|
+
export { convertScalars };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { a as CubicCoefficients } from './shared/curvy.0f1371d7.mjs';
|
|
2
|
+
import { Matrix4x4 } from './splines.mjs';
|
|
3
|
+
|
|
4
|
+
declare function convertScalars(points: CubicCoefficients, sourceSpline: Matrix4x4, targetSpline: Matrix4x4): CubicCoefficients;
|
|
5
|
+
|
|
6
|
+
export { convertScalars };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { a as CubicCoefficients } from './shared/curvy.0f1371d7.js';
|
|
2
|
+
import { Matrix4x4 } from './splines.js';
|
|
3
|
+
|
|
4
|
+
declare function convertScalars(points: CubicCoefficients, sourceSpline: Matrix4x4, targetSpline: Matrix4x4): CubicCoefficients;
|
|
5
|
+
|
|
6
|
+
export { convertScalars };
|
package/dist/convert.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import invariant from 'tiny-invariant';
|
|
2
|
+
import { pointsToCoefficients } from './splines.mjs';
|
|
3
|
+
import { a as round } from './shared/curvy.9cd8b44c.mjs';
|
|
4
|
+
|
|
5
|
+
function determinant(matrix) {
|
|
6
|
+
const size = matrix.length;
|
|
7
|
+
invariant(
|
|
8
|
+
matrix.every((row) => row.length === size),
|
|
9
|
+
"matrix must be square"
|
|
10
|
+
);
|
|
11
|
+
let result = 0;
|
|
12
|
+
if (size === 1) {
|
|
13
|
+
result = matrix[0][0];
|
|
14
|
+
} else if (size === 2) {
|
|
15
|
+
result = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
|
|
16
|
+
} else {
|
|
17
|
+
for (const [columnIndex, value] of matrix[0].entries()) {
|
|
18
|
+
const subMatrix = [];
|
|
19
|
+
for (const row of matrix.slice(1)) {
|
|
20
|
+
const subMatrixRow = [];
|
|
21
|
+
for (const [subColumnIndex, subColumnValue] of row.entries()) {
|
|
22
|
+
if (subColumnIndex !== columnIndex) {
|
|
23
|
+
subMatrixRow.push(subColumnValue);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
subMatrix.push(subMatrixRow);
|
|
27
|
+
}
|
|
28
|
+
result = result + (-1) ** (columnIndex + 2) * value * determinant(subMatrix);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function solveLinearSystem(coefficients, solutions) {
|
|
34
|
+
const coefficientDeterminant = determinant(coefficients);
|
|
35
|
+
invariant(
|
|
36
|
+
coefficientDeterminant !== 0,
|
|
37
|
+
"Cannot solve system of equations when coefficient matrix determinant is zero"
|
|
38
|
+
);
|
|
39
|
+
const variables = [0, 0, 0, 0];
|
|
40
|
+
for (let index = 0; index < solutions.length; index++) {
|
|
41
|
+
const solutionMatrix = [];
|
|
42
|
+
for (const [rowIndex, row] of coefficients.entries()) {
|
|
43
|
+
const solutionMatrixRow = [];
|
|
44
|
+
for (const [columnIndex, column] of row.entries()) {
|
|
45
|
+
solutionMatrixRow.push(
|
|
46
|
+
columnIndex === index ? solutions[rowIndex] : column
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
solutionMatrix.push(solutionMatrixRow);
|
|
50
|
+
}
|
|
51
|
+
variables[index] = determinant(solutionMatrix) / coefficientDeterminant;
|
|
52
|
+
}
|
|
53
|
+
return variables;
|
|
54
|
+
}
|
|
55
|
+
function convertScalars(points, sourceSpline, targetSpline) {
|
|
56
|
+
const solutions = solveLinearSystem(
|
|
57
|
+
targetSpline,
|
|
58
|
+
pointsToCoefficients(sourceSpline, points)
|
|
59
|
+
);
|
|
60
|
+
return [
|
|
61
|
+
round(solutions[0]),
|
|
62
|
+
round(solutions[1]),
|
|
63
|
+
round(solutions[2]),
|
|
64
|
+
round(solutions[3])
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { convertScalars };
|
package/dist/curve.cjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const invariant = require('tiny-invariant');
|
|
4
|
+
const axis = require('./axis.cjs');
|
|
5
|
+
const util = require('./shared/curvy.262f6070.cjs');
|
|
6
|
+
const splines = require('./splines.cjs');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
9
|
+
|
|
10
|
+
const invariant__default = /*#__PURE__*/_interopDefaultCompat(invariant);
|
|
11
|
+
|
|
12
|
+
function createNode(interval) {
|
|
13
|
+
return {
|
|
14
|
+
interval,
|
|
15
|
+
left: null,
|
|
16
|
+
right: null
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function takeCenterInterval(boundaries) {
|
|
20
|
+
const mid = Math.floor((boundaries.length - 1) / 2);
|
|
21
|
+
const left = mid > 0 ? boundaries.slice(0, mid + 1) : null;
|
|
22
|
+
const right = mid + 1 < boundaries.length - 1 ? boundaries.slice(mid + 1) : null;
|
|
23
|
+
return {
|
|
24
|
+
center: [boundaries[mid], boundaries[mid + 1]],
|
|
25
|
+
left,
|
|
26
|
+
right
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createIntervalTree(boundaries) {
|
|
30
|
+
const container = createNode([0, 0]);
|
|
31
|
+
const branches = [{ parent: container, direction: "left", boundaries }];
|
|
32
|
+
for (const branch of branches) {
|
|
33
|
+
const { parent, direction, boundaries: boundaries2 } = branch;
|
|
34
|
+
const { center, left, right } = takeCenterInterval(boundaries2);
|
|
35
|
+
const node = createNode(center);
|
|
36
|
+
if (direction === "left") {
|
|
37
|
+
parent.left = node;
|
|
38
|
+
} else {
|
|
39
|
+
parent.right = node;
|
|
40
|
+
}
|
|
41
|
+
if (left !== null) {
|
|
42
|
+
branches.push({ parent: node, direction: "left", boundaries: left });
|
|
43
|
+
}
|
|
44
|
+
if (right !== null) {
|
|
45
|
+
branches.push({ parent: node, direction: "right", boundaries: right });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const root = container.left;
|
|
49
|
+
function searchNode(node, query) {
|
|
50
|
+
if (node.interval[0] <= query && node.interval[1] >= query) {
|
|
51
|
+
return node.interval;
|
|
52
|
+
}
|
|
53
|
+
if (node.interval[0] > query && node.left) {
|
|
54
|
+
return searchNode(node.left, query);
|
|
55
|
+
}
|
|
56
|
+
if (node.interval[1] < query && node.right) {
|
|
57
|
+
return searchNode(node.right, query);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function search(query) {
|
|
62
|
+
return searchNode(root, query);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
root,
|
|
66
|
+
search
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function distance(a, b) {
|
|
71
|
+
const keys = Object.keys(a);
|
|
72
|
+
const deltas = keys.map((key) => a[key] - b[key]);
|
|
73
|
+
return Math.hypot(...deltas);
|
|
74
|
+
}
|
|
75
|
+
function createLengthLookup(positionAt) {
|
|
76
|
+
const count = 512;
|
|
77
|
+
const orderedSamples = Array.from({ length: count + 1 }, (_, i) => i / count);
|
|
78
|
+
const lengthLookup = /* @__PURE__ */ new Map();
|
|
79
|
+
let lastPoint = positionAt(0);
|
|
80
|
+
let length = 0;
|
|
81
|
+
lengthLookup.set(0, 0);
|
|
82
|
+
for (const t of orderedSamples.slice(1)) {
|
|
83
|
+
const point = positionAt(t);
|
|
84
|
+
length = length + distance(lastPoint, point);
|
|
85
|
+
lengthLookup.set(length, t);
|
|
86
|
+
lastPoint = point;
|
|
87
|
+
}
|
|
88
|
+
const totalLength = length;
|
|
89
|
+
const { search } = createIntervalTree(Array.from(lengthLookup.keys()));
|
|
90
|
+
return (normalized) => {
|
|
91
|
+
invariant__default(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
92
|
+
const length2 = normalized * totalLength;
|
|
93
|
+
const result = search(length2);
|
|
94
|
+
invariant__default(result, "Length not found");
|
|
95
|
+
return util.remap(
|
|
96
|
+
length2,
|
|
97
|
+
result[0],
|
|
98
|
+
result[1],
|
|
99
|
+
lengthLookup.get(result[0]),
|
|
100
|
+
lengthLookup.get(result[1])
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createCurve(points, spline) {
|
|
106
|
+
const axisKeys = Object.keys(points[0]);
|
|
107
|
+
const axisPoints = {};
|
|
108
|
+
for (const key of axisKeys) {
|
|
109
|
+
axisPoints[key] = [];
|
|
110
|
+
}
|
|
111
|
+
for (const point of points) {
|
|
112
|
+
for (const key of axisKeys) {
|
|
113
|
+
invariant__default(key in point, `Point ${point} is missing axis "${key}"`);
|
|
114
|
+
axisPoints[key].push(point[key]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const axes = {};
|
|
118
|
+
for (const key of axisKeys) {
|
|
119
|
+
axes[key] = axis.createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
|
|
120
|
+
}
|
|
121
|
+
const solveT = (t, precision = 12) => {
|
|
122
|
+
const rounded = util.round(t);
|
|
123
|
+
invariant__default(
|
|
124
|
+
rounded >= 0 && rounded <= 1,
|
|
125
|
+
`t must be between 0 and 1, got ${rounded}`
|
|
126
|
+
);
|
|
127
|
+
const position = {};
|
|
128
|
+
for (const key of axisKeys) {
|
|
129
|
+
position[key] = util.round(axes[key].solvePosition(rounded), precision);
|
|
130
|
+
}
|
|
131
|
+
return position;
|
|
132
|
+
};
|
|
133
|
+
const lengthLookup = createLengthLookup(solveT);
|
|
134
|
+
const positionAt = (input, normalize = 1, precision = 12) => {
|
|
135
|
+
invariant__default(
|
|
136
|
+
input >= 0 && input <= 1,
|
|
137
|
+
`Input must be between 0 and 1, got ${input}`
|
|
138
|
+
);
|
|
139
|
+
let t = input;
|
|
140
|
+
if (normalize > 0) {
|
|
141
|
+
const denormalizedT = lengthLookup(input);
|
|
142
|
+
if (normalize === 1) {
|
|
143
|
+
t = denormalizedT;
|
|
144
|
+
} else {
|
|
145
|
+
t = (1 - normalize) * input + normalize * denormalizedT;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return solveT(t, precision);
|
|
149
|
+
};
|
|
150
|
+
const solveWhere = (axis, position, precision = 12) => {
|
|
151
|
+
const curveAxis = axes[axis];
|
|
152
|
+
invariant__default(curveAxis.monotonicity !== "none", "Axis is not monotonic");
|
|
153
|
+
const t = curveAxis.solveT(position, curveAxis.domain)[0];
|
|
154
|
+
invariant__default(
|
|
155
|
+
typeof t === "number",
|
|
156
|
+
`Could not solve for ${axis} = ${position}`
|
|
157
|
+
);
|
|
158
|
+
const result = { [axis]: util.round(position, precision) };
|
|
159
|
+
for (const key of axisKeys) {
|
|
160
|
+
if (key !== axis) {
|
|
161
|
+
result[key] = util.round(axes[key].solvePosition(t), precision);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
axes,
|
|
168
|
+
positionAt,
|
|
169
|
+
solveWhere
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function createBasisCurve(points, triplicateEndpoints = true) {
|
|
173
|
+
return createCurve(points, splines.splines.basis(triplicateEndpoints));
|
|
174
|
+
}
|
|
175
|
+
function createBezierCurve(points) {
|
|
176
|
+
return createCurve(points, splines.splines.bezier);
|
|
177
|
+
}
|
|
178
|
+
function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
|
|
179
|
+
return createCurve(points, splines.splines.cardinal(tension, duplicateEndpoints));
|
|
180
|
+
}
|
|
181
|
+
function createCatmullRomCurve(points, duplicateEndpoints = true) {
|
|
182
|
+
return createCurve(points, splines.splines.catmullRom(duplicateEndpoints));
|
|
183
|
+
}
|
|
184
|
+
function createHermiteCurve(points) {
|
|
185
|
+
return createCurve(points, splines.splines.hermite);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exports.createBasisCurve = createBasisCurve;
|
|
189
|
+
exports.createBezierCurve = createBezierCurve;
|
|
190
|
+
exports.createCardinalCurve = createCardinalCurve;
|
|
191
|
+
exports.createCatmullRomCurve = createCatmullRomCurve;
|
|
192
|
+
exports.createHermiteCurve = createHermiteCurve;
|
package/dist/curve.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CurveAxis } from './axis.cjs';
|
|
2
|
+
import './shared/curvy.0f1371d7.cjs';
|
|
3
|
+
|
|
4
|
+
type Point<Axis extends string | number> = Readonly<{
|
|
5
|
+
[A in Axis]: number;
|
|
6
|
+
}>;
|
|
7
|
+
type Curve<Axis extends string | number> = {
|
|
8
|
+
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
|
+
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
|
|
11
|
+
};
|
|
12
|
+
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
|
+
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
|
+
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
|
+
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
|
+
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
|
+
|
|
18
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
package/dist/curve.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CurveAxis } from './axis.mjs';
|
|
2
|
+
import './shared/curvy.0f1371d7.mjs';
|
|
3
|
+
|
|
4
|
+
type Point<Axis extends string | number> = Readonly<{
|
|
5
|
+
[A in Axis]: number;
|
|
6
|
+
}>;
|
|
7
|
+
type Curve<Axis extends string | number> = {
|
|
8
|
+
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
|
+
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
|
|
11
|
+
};
|
|
12
|
+
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
|
+
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
|
+
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
|
+
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
|
+
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
|
+
|
|
18
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
package/dist/curve.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CurveAxis } from './axis.js';
|
|
2
|
+
import './shared/curvy.0f1371d7.js';
|
|
3
|
+
|
|
4
|
+
type Point<Axis extends string | number> = Readonly<{
|
|
5
|
+
[A in Axis]: number;
|
|
6
|
+
}>;
|
|
7
|
+
type Curve<Axis extends string | number> = {
|
|
8
|
+
readonly axes: Readonly<Record<Axis, CurveAxis>>;
|
|
9
|
+
readonly positionAt: (input: number, normalize?: number, precision?: number) => Point<Axis>;
|
|
10
|
+
readonly solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
|
|
11
|
+
};
|
|
12
|
+
declare function createBasisCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, triplicateEndpoints?: boolean): Curve<Axis>;
|
|
13
|
+
declare function createBezierCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
14
|
+
declare function createCardinalCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, tension?: number, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
15
|
+
declare function createCatmullRomCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>, duplicateEndpoints?: boolean): Curve<Axis>;
|
|
16
|
+
declare function createHermiteCurve<Axis extends string | number>(points: ReadonlyArray<Point<Axis>>): Curve<Axis>;
|
|
17
|
+
|
|
18
|
+
export { type Curve, type Point, createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
package/dist/curve.mjs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import invariant from 'tiny-invariant';
|
|
2
|
+
import { createCurveAxis } from './axis.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 distance(a, b) {
|
|
65
|
+
const keys = Object.keys(a);
|
|
66
|
+
const deltas = keys.map((key) => a[key] - b[key]);
|
|
67
|
+
return Math.hypot(...deltas);
|
|
68
|
+
}
|
|
69
|
+
function createLengthLookup(positionAt) {
|
|
70
|
+
const count = 512;
|
|
71
|
+
const orderedSamples = Array.from({ length: count + 1 }, (_, i) => i / count);
|
|
72
|
+
const lengthLookup = /* @__PURE__ */ new Map();
|
|
73
|
+
let lastPoint = positionAt(0);
|
|
74
|
+
let length = 0;
|
|
75
|
+
lengthLookup.set(0, 0);
|
|
76
|
+
for (const t of orderedSamples.slice(1)) {
|
|
77
|
+
const point = positionAt(t);
|
|
78
|
+
length = length + distance(lastPoint, point);
|
|
79
|
+
lengthLookup.set(length, t);
|
|
80
|
+
lastPoint = point;
|
|
81
|
+
}
|
|
82
|
+
const totalLength = length;
|
|
83
|
+
const { search } = createIntervalTree(Array.from(lengthLookup.keys()));
|
|
84
|
+
return (normalized) => {
|
|
85
|
+
invariant(normalized >= 0 && normalized <= 1, "Length out of bounds");
|
|
86
|
+
const length2 = normalized * totalLength;
|
|
87
|
+
const result = search(length2);
|
|
88
|
+
invariant(result, "Length not found");
|
|
89
|
+
return remap(
|
|
90
|
+
length2,
|
|
91
|
+
result[0],
|
|
92
|
+
result[1],
|
|
93
|
+
lengthLookup.get(result[0]),
|
|
94
|
+
lengthLookup.get(result[1])
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createCurve(points, spline) {
|
|
100
|
+
const axisKeys = Object.keys(points[0]);
|
|
101
|
+
const axisPoints = {};
|
|
102
|
+
for (const key of axisKeys) {
|
|
103
|
+
axisPoints[key] = [];
|
|
104
|
+
}
|
|
105
|
+
for (const point of points) {
|
|
106
|
+
for (const key of axisKeys) {
|
|
107
|
+
invariant(key in point, `Point ${point} is missing axis "${key}"`);
|
|
108
|
+
axisPoints[key].push(point[key]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const axes = {};
|
|
112
|
+
for (const key of axisKeys) {
|
|
113
|
+
axes[key] = createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
|
|
114
|
+
}
|
|
115
|
+
const solveT = (t, precision = 12) => {
|
|
116
|
+
const rounded = round(t);
|
|
117
|
+
invariant(
|
|
118
|
+
rounded >= 0 && rounded <= 1,
|
|
119
|
+
`t must be between 0 and 1, got ${rounded}`
|
|
120
|
+
);
|
|
121
|
+
const position = {};
|
|
122
|
+
for (const key of axisKeys) {
|
|
123
|
+
position[key] = round(axes[key].solvePosition(rounded), precision);
|
|
124
|
+
}
|
|
125
|
+
return position;
|
|
126
|
+
};
|
|
127
|
+
const lengthLookup = createLengthLookup(solveT);
|
|
128
|
+
const positionAt = (input, normalize = 1, precision = 12) => {
|
|
129
|
+
invariant(
|
|
130
|
+
input >= 0 && input <= 1,
|
|
131
|
+
`Input must be between 0 and 1, got ${input}`
|
|
132
|
+
);
|
|
133
|
+
let t = input;
|
|
134
|
+
if (normalize > 0) {
|
|
135
|
+
const denormalizedT = lengthLookup(input);
|
|
136
|
+
if (normalize === 1) {
|
|
137
|
+
t = denormalizedT;
|
|
138
|
+
} else {
|
|
139
|
+
t = (1 - normalize) * input + normalize * denormalizedT;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return solveT(t, precision);
|
|
143
|
+
};
|
|
144
|
+
const solveWhere = (axis, position, precision = 12) => {
|
|
145
|
+
const curveAxis = axes[axis];
|
|
146
|
+
invariant(curveAxis.monotonicity !== "none", "Axis is not monotonic");
|
|
147
|
+
const t = curveAxis.solveT(position, curveAxis.domain)[0];
|
|
148
|
+
invariant(
|
|
149
|
+
typeof t === "number",
|
|
150
|
+
`Could not solve for ${axis} = ${position}`
|
|
151
|
+
);
|
|
152
|
+
const result = { [axis]: round(position, precision) };
|
|
153
|
+
for (const key of axisKeys) {
|
|
154
|
+
if (key !== axis) {
|
|
155
|
+
result[key] = round(axes[key].solvePosition(t), precision);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
axes,
|
|
162
|
+
positionAt,
|
|
163
|
+
solveWhere
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function createBasisCurve(points, triplicateEndpoints = true) {
|
|
167
|
+
return createCurve(points, splines.basis(triplicateEndpoints));
|
|
168
|
+
}
|
|
169
|
+
function createBezierCurve(points) {
|
|
170
|
+
return createCurve(points, splines.bezier);
|
|
171
|
+
}
|
|
172
|
+
function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
|
|
173
|
+
return createCurve(points, splines.cardinal(tension, duplicateEndpoints));
|
|
174
|
+
}
|
|
175
|
+
function createCatmullRomCurve(points, duplicateEndpoints = true) {
|
|
176
|
+
return createCurve(points, splines.catmullRom(duplicateEndpoints));
|
|
177
|
+
}
|
|
178
|
+
function createHermiteCurve(points) {
|
|
179
|
+
return createCurve(points, splines.hermite);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { createBasisCurve, createBezierCurve, createCardinalCurve, createCatmullRomCurve, createHermiteCurve };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type Monotonicity = 'none' | 'increasing' | 'decreasing' | 'constant';
|
|
2
|
+
type Interval = readonly [number, number];
|
|
3
|
+
type LinearCoefficients = readonly [number, number];
|
|
4
|
+
type LinearPolynomial = {
|
|
5
|
+
readonly domain: Interval;
|
|
6
|
+
readonly range: Interval;
|
|
7
|
+
readonly monotonocity: Monotonicity;
|
|
8
|
+
readonly coefficients: LinearCoefficients;
|
|
9
|
+
readonly root: number | null;
|
|
10
|
+
readonly solve: (x: number) => number;
|
|
11
|
+
readonly solveInverse: (y: number) => number;
|
|
12
|
+
};
|
|
13
|
+
type QuadraticCoefficients = readonly [number, number, number];
|
|
14
|
+
type QuadraticPolynomial = {
|
|
15
|
+
readonly domain: Interval;
|
|
16
|
+
readonly range: Interval;
|
|
17
|
+
readonly monotonocity: Monotonicity;
|
|
18
|
+
readonly coefficients: QuadraticCoefficients;
|
|
19
|
+
readonly extreme: number | null;
|
|
20
|
+
readonly roots: ReadonlyArray<number>;
|
|
21
|
+
readonly derivative: LinearPolynomial;
|
|
22
|
+
readonly solve: (x: number) => number;
|
|
23
|
+
readonly solveInverse: (y: number, domain?: Interval) => ReadonlyArray<number>;
|
|
24
|
+
};
|
|
25
|
+
type CubicCoefficients = readonly [number, number, number, number];
|
|
26
|
+
type CubicPolynomial = {
|
|
27
|
+
readonly domain: Interval;
|
|
28
|
+
readonly range: Interval;
|
|
29
|
+
readonly monotonocity: Monotonicity;
|
|
30
|
+
readonly coefficients: CubicCoefficients;
|
|
31
|
+
readonly extrema: ReadonlyArray<number>;
|
|
32
|
+
readonly roots: ReadonlyArray<number>;
|
|
33
|
+
readonly derivative: QuadraticPolynomial;
|
|
34
|
+
readonly solve: (x: number) => number;
|
|
35
|
+
readonly solveInverse: (y: number, domain?: Interval) => ReadonlyArray<number>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type { CubicPolynomial as C, Interval as I, Monotonicity as M, CubicCoefficients as a };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type Monotonicity = 'none' | 'increasing' | 'decreasing' | 'constant';
|
|
2
|
+
type Interval = readonly [number, number];
|
|
3
|
+
type LinearCoefficients = readonly [number, number];
|
|
4
|
+
type LinearPolynomial = {
|
|
5
|
+
readonly domain: Interval;
|
|
6
|
+
readonly range: Interval;
|
|
7
|
+
readonly monotonocity: Monotonicity;
|
|
8
|
+
readonly coefficients: LinearCoefficients;
|
|
9
|
+
readonly root: number | null;
|
|
10
|
+
readonly solve: (x: number) => number;
|
|
11
|
+
readonly solveInverse: (y: number) => number;
|
|
12
|
+
};
|
|
13
|
+
type QuadraticCoefficients = readonly [number, number, number];
|
|
14
|
+
type QuadraticPolynomial = {
|
|
15
|
+
readonly domain: Interval;
|
|
16
|
+
readonly range: Interval;
|
|
17
|
+
readonly monotonocity: Monotonicity;
|
|
18
|
+
readonly coefficients: QuadraticCoefficients;
|
|
19
|
+
readonly extreme: number | null;
|
|
20
|
+
readonly roots: ReadonlyArray<number>;
|
|
21
|
+
readonly derivative: LinearPolynomial;
|
|
22
|
+
readonly solve: (x: number) => number;
|
|
23
|
+
readonly solveInverse: (y: number, domain?: Interval) => ReadonlyArray<number>;
|
|
24
|
+
};
|
|
25
|
+
type CubicCoefficients = readonly [number, number, number, number];
|
|
26
|
+
type CubicPolynomial = {
|
|
27
|
+
readonly domain: Interval;
|
|
28
|
+
readonly range: Interval;
|
|
29
|
+
readonly monotonocity: Monotonicity;
|
|
30
|
+
readonly coefficients: CubicCoefficients;
|
|
31
|
+
readonly extrema: ReadonlyArray<number>;
|
|
32
|
+
readonly roots: ReadonlyArray<number>;
|
|
33
|
+
readonly derivative: QuadraticPolynomial;
|
|
34
|
+
readonly solve: (x: number) => number;
|
|
35
|
+
readonly solveInverse: (y: number, domain?: Interval) => ReadonlyArray<number>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type { CubicPolynomial as C, Interval as I, Monotonicity as M, CubicCoefficients as a };
|