curvy 0.3.1 → 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,42 +67,193 @@ 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
- const keys = Object.keys(a);
72
- const deltas = keys.map((key) => a[key] - b[key]);
166
+ const deltas = [];
167
+ for (const key of Object.keys(a)) {
168
+ deltas.push(a[key] - b[key]);
169
+ }
73
170
  return Math.hypot(...deltas);
74
171
  }
75
- function createLengthLookup(positionAt) {
76
- const count = 512;
77
- 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) {
78
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
+ }
79
230
  let lastPoint = positionAt(0);
80
231
  let length = 0;
81
232
  lengthLookup.set(0, 0);
82
- for (const t of orderedSamples.slice(1)) {
233
+ while (sampleBuffer.size > 0) {
234
+ const t = Math.min(...sampleBuffer.keys());
83
235
  const point = positionAt(t);
84
236
  length = length + distance(lastPoint, point);
85
237
  lengthLookup.set(length, t);
86
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
+ }
87
249
  }
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
- );
250
+ return {
251
+ curve: createLengthLookup({ length, lookup: lengthLookup }),
252
+ axes: axesLookups
102
253
  };
103
254
  }
104
255
 
105
- function createCurve(points, spline) {
256
+ function convertPointsToAxes(points, spline) {
106
257
  const axisKeys = Object.keys(points[0]);
107
258
  const axisPoints = {};
108
259
  for (const key of axisKeys) {
@@ -118,6 +269,10 @@ function createCurve(points, spline) {
118
269
  for (const key of axisKeys) {
119
270
  axes[key] = axis.createCurveAxis(spline.chunkCoefficients(axisPoints[key]));
120
271
  }
272
+ return axes;
273
+ }
274
+ function createCurve(axes) {
275
+ const axisKeys = Object.keys(axes);
121
276
  const solveT = (t, precision = 12) => {
122
277
  const rounded = util.round(t);
123
278
  invariant__default(
@@ -130,7 +285,7 @@ function createCurve(points, spline) {
130
285
  }
131
286
  return position;
132
287
  };
133
- const lengthLookup = createLengthLookup(solveT);
288
+ const lengthLookups = createCurveLengthLookups(axes, solveT);
134
289
  const positionAt = (input, normalize = 1, precision = 12) => {
135
290
  invariant__default(
136
291
  input >= 0 && input <= 1,
@@ -138,7 +293,23 @@ function createCurve(points, spline) {
138
293
  );
139
294
  let t = input;
140
295
  if (normalize > 0) {
141
- 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);
142
313
  if (normalize === 1) {
143
314
  t = denormalizedT;
144
315
  } else {
@@ -155,38 +326,40 @@ function createCurve(points, spline) {
155
326
  typeof t === "number",
156
327
  `Could not solve for ${axis} = ${position}`
157
328
  );
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;
329
+ return positionAt(t, 0, precision);
165
330
  };
166
331
  return {
167
332
  axes,
168
333
  positionAt,
334
+ solveAlong,
169
335
  solveWhere
170
336
  };
171
337
  }
172
338
  function createBasisCurve(points, triplicateEndpoints = true) {
173
- return createCurve(points, splines.splines.basis(triplicateEndpoints));
339
+ return createCurve(
340
+ convertPointsToAxes(points, splines.splines.basis(triplicateEndpoints))
341
+ );
174
342
  }
175
343
  function createBezierCurve(points) {
176
- return createCurve(points, splines.splines.bezier);
344
+ return createCurve(convertPointsToAxes(points, splines.splines.bezier));
177
345
  }
178
346
  function createCardinalCurve(points, tension = 0.5, duplicateEndpoints = true) {
179
- return createCurve(points, splines.splines.cardinal(tension, duplicateEndpoints));
347
+ return createCurve(
348
+ convertPointsToAxes(points, splines.splines.cardinal(tension, duplicateEndpoints))
349
+ );
180
350
  }
181
351
  function createCatmullRomCurve(points, duplicateEndpoints = true) {
182
- return createCurve(points, splines.splines.catmullRom(duplicateEndpoints));
352
+ return createCurve(
353
+ convertPointsToAxes(points, splines.splines.catmullRom(duplicateEndpoints))
354
+ );
183
355
  }
184
356
  function createHermiteCurve(points) {
185
- return createCurve(points, splines.splines.hermite);
357
+ return createCurve(convertPointsToAxes(points, splines.splines.hermite));
186
358
  }
187
359
 
188
360
  exports.createBasisCurve = createBasisCurve;
189
361
  exports.createBezierCurve = createBezierCurve;
190
362
  exports.createCardinalCurve = createCardinalCurve;
191
363
  exports.createCatmullRomCurve = createCatmullRomCurve;
364
+ exports.createCurve = createCurve;
192
365
  exports.createHermiteCurve = createHermiteCurve;
package/dist/curve.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CurveAxis } from './axis.cjs';
2
- import './shared/curvy.0f1371d7.cjs';
2
+ import './shared/curvy.df73166c.cjs';
3
3
 
4
4
  type Point<Axis extends string | number> = Readonly<{
5
5
  [A in Axis]: number;
@@ -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 solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
10
+ readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
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
@@ -1,5 +1,5 @@
1
1
  import { CurveAxis } from './axis.mjs';
2
- import './shared/curvy.0f1371d7.mjs';
2
+ import './shared/curvy.df73166c.mjs';
3
3
 
4
4
  type Point<Axis extends string | number> = Readonly<{
5
5
  [A in Axis]: number;
@@ -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 solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
10
+ readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
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
@@ -1,5 +1,5 @@
1
1
  import { CurveAxis } from './axis.js';
2
- import './shared/curvy.0f1371d7.js';
2
+ import './shared/curvy.df73166c.js';
3
3
 
4
4
  type Point<Axis extends string | number> = Readonly<{
5
5
  [A in Axis]: number;
@@ -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 solveWhere: <SolveAxis extends Axis>(axis: SolveAxis, position: number, precsiion?: number) => Point<Axis>;
10
+ readonly solveAlong: <SolveAxis extends Axis>(axis: SolveAxis, input: number, normalize?: number, precision?: number) => Point<Axis>;
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 };