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/curve.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const invariant = require('tiny-invariant');
4
- const axis = require('./axis.cjs');
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 createLengthLookup(positionAt) {
78
- const count = 512;
79
- const orderedSamples = Array.from({ length: count + 1 }, (_, i) => i / count);
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
- for (const t of orderedSamples.slice(1)) {
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
- const totalLength = length;
91
- const { search } = createIntervalTree(Array.from(lengthLookup.keys()));
92
- return (normalized) => {
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 createCurve(points, spline) {
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 lengthLookup = createLengthLookup(solveT);
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 = lengthLookup(input);
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
- const result = { [axis]: util.round(position, precision) };
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(points, splines.splines.basis(triplicateEndpoints));
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(points, splines.splines.cardinal(tension, duplicateEndpoints));
347
+ return createCurve(
348
+ convertPointsToAxes(points, splines.splines.cardinal(tension, duplicateEndpoints))
349
+ );
182
350
  }
183
351
  function createCatmullRomCurve(points, duplicateEndpoints = true) {
184
- return createCurve(points, splines.splines.catmullRom(duplicateEndpoints));
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 './axis.mjs';
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 createLengthLookup(positionAt) {
72
- const count = 512;
73
- const orderedSamples = Array.from({ length: count + 1 }, (_, i) => i / count);
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
- for (const t of orderedSamples.slice(1)) {
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
- const totalLength = length;
85
- const { search } = createIntervalTree(Array.from(lengthLookup.keys()));
86
- return (normalized) => {
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 createCurve(points, spline) {
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 lengthLookup = createLengthLookup(solveT);
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 = lengthLookup(input);
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
- const result = { [axis]: round(position, precision) };
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(points, splines.basis(triplicateEndpoints));
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(points, splines.cardinal(tension, duplicateEndpoints));
341
+ return createCurve(
342
+ convertPointsToAxes(points, splines.cardinal(tension, duplicateEndpoints))
343
+ );
176
344
  }
177
345
  function createCatmullRomCurve(points, duplicateEndpoints = true) {
178
- return createCurve(points, splines.catmullRom(duplicateEndpoints));
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 };