@ue-too/curve 0.5.2 → 0.7.0
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/README.md +5 -0
- package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.d.ts → b-curve.d.ts} +42 -5
- package/curve.tsbuildinfo +1 -0
- package/{dist/index.js → index.js} +412 -18
- package/index.js.map +1 -0
- package/package.json +11 -11
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.js +0 -878
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.js.map +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.js +0 -215
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.js.map +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/curve.tsbuildinfo +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.js +0 -5
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.js.map +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.js +0 -74
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.js.map +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.js +0 -65
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.js.map +0 -1
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/utils.js +0 -43
- package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/utils.js.map +0 -1
- package/dist/b-curve.d.ts +0 -151
- package/dist/composite-curve.d.ts +0 -33
- package/dist/curve.tsbuildinfo +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js.map +0 -1
- package/dist/line.d.ts +0 -35
- package/dist/package.json +0 -26
- package/dist/path.d.ts +0 -16
- package/dist/utils.d.ts +0 -3
- package/jest.config.js +0 -18
- package/project.json +0 -33
- package/rollup.config.js +0 -20
- package/src/b-curve.ts +0 -1006
- package/src/composite-curve.ts +0 -243
- package/src/index.ts +0 -4
- package/src/line.ts +0 -98
- package/src/path.ts +0 -75
- package/src/utils.ts +0 -47
- package/test/b-curve.test.ts +0 -715
- package/test/line.test.ts +0 -38
- package/tsconfig.json +0 -21
- package/tsconfig.spec.json +0 -12
- /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.d.ts → composite-curve.d.ts} +0 -0
- /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.d.ts → index.d.ts} +0 -0
- /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.d.ts → line.d.ts} +0 -0
- /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.d.ts → path.d.ts} +0 -0
- /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/utils.d.ts → utils.d.ts} +0 -0
|
@@ -53,13 +53,39 @@ const C = [
|
|
|
53
53
|
0.0123412297999871995468056670700372915759,
|
|
54
54
|
];
|
|
55
55
|
class BCurve {
|
|
56
|
+
/**
|
|
57
|
+
* Gets cache statistics for performance monitoring
|
|
58
|
+
* @returns Object containing cache size and hit rate information
|
|
59
|
+
*/
|
|
60
|
+
getCacheStats() {
|
|
61
|
+
return {
|
|
62
|
+
size: this.lengthCache.size,
|
|
63
|
+
hitRate: 0 // This would need to be tracked separately if needed
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pre-warms the cache with commonly used t values for better performance
|
|
68
|
+
* @param steps Number of steps to pre-cache (default: 100)
|
|
69
|
+
*/
|
|
70
|
+
preWarmCache(steps = 100) {
|
|
71
|
+
const tSteps = 1 / steps;
|
|
72
|
+
for (let tVal = 0; tVal <= 1; tVal += tSteps) {
|
|
73
|
+
this.lengthAtT(tVal);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
clearCache() {
|
|
77
|
+
this.lengthCache.clear();
|
|
78
|
+
}
|
|
56
79
|
constructor(controlPoints) {
|
|
57
80
|
this.dControlPoints = [];
|
|
58
81
|
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
82
|
+
this.lengthCache = new Map(); // Cache for lengthAtT results
|
|
59
83
|
this.controlPoints = controlPoints;
|
|
60
84
|
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
61
85
|
this._fullLength = this.calculateFullLength();
|
|
62
|
-
|
|
86
|
+
// Make arc length LUT lazy - only compute when needed
|
|
87
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
88
|
+
this.clearCache(); // Clear cache on initialization
|
|
63
89
|
}
|
|
64
90
|
getPointbyPercentage(percentage) {
|
|
65
91
|
// this leaves room for optimization
|
|
@@ -104,6 +130,14 @@ class BCurve {
|
|
|
104
130
|
getControlPoints() {
|
|
105
131
|
return this.controlPoints;
|
|
106
132
|
}
|
|
133
|
+
setControlPoints(controlPoints) {
|
|
134
|
+
this.controlPoints = controlPoints;
|
|
135
|
+
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
136
|
+
this._fullLength = this.calculateFullLength();
|
|
137
|
+
// Reset LUT to trigger lazy computation when needed
|
|
138
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
139
|
+
this.clearCache(); // Clear cache on control point change
|
|
140
|
+
}
|
|
107
141
|
setControlPointAtIndex(index, newPoint) {
|
|
108
142
|
if (index < 0 || index >= this.controlPoints.length) {
|
|
109
143
|
return false;
|
|
@@ -111,6 +145,9 @@ class BCurve {
|
|
|
111
145
|
this.controlPoints[index] = newPoint;
|
|
112
146
|
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
113
147
|
this._fullLength = this.calculateFullLength();
|
|
148
|
+
// Reset LUT to trigger lazy computation when needed
|
|
149
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
150
|
+
this.clearCache(); // Clear cache on control point change
|
|
114
151
|
return true;
|
|
115
152
|
}
|
|
116
153
|
compute(tVal) {
|
|
@@ -183,13 +220,21 @@ class BCurve {
|
|
|
183
220
|
}
|
|
184
221
|
lengthAtT(tVal) {
|
|
185
222
|
this.validateTVal(tVal);
|
|
223
|
+
// Check cache first
|
|
224
|
+
const cacheKey = Math.round(tVal * 1000000) / 1000000; // Round to 6 decimal places for cache key
|
|
225
|
+
if (this.lengthCache.has(cacheKey)) {
|
|
226
|
+
return this.lengthCache.get(cacheKey);
|
|
227
|
+
}
|
|
186
228
|
const z = tVal / 2, len = T.length;
|
|
187
229
|
let sum = 0;
|
|
188
230
|
for (let i = 0, t; i < len; i++) {
|
|
189
231
|
t = z * T[i] + z;
|
|
190
232
|
sum += C[i] * PointCal.magnitude(this.derivative(t));
|
|
191
233
|
}
|
|
192
|
-
|
|
234
|
+
const result = z * sum;
|
|
235
|
+
// Cache the result
|
|
236
|
+
this.lengthCache.set(cacheKey, result);
|
|
237
|
+
return result;
|
|
193
238
|
}
|
|
194
239
|
derivative(tVal) {
|
|
195
240
|
return computeWithControlPoints(tVal, this.dControlPoints);
|
|
@@ -197,15 +242,23 @@ class BCurve {
|
|
|
197
242
|
derivativeNormalized(tVal) {
|
|
198
243
|
return PointCal.unitVector(computeWithControlPoints(tVal, this.dControlPoints));
|
|
199
244
|
}
|
|
200
|
-
getArcLengthLUT(steps =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
245
|
+
getArcLengthLUT(steps = 50) {
|
|
246
|
+
// Check if we need to recompute the LUT
|
|
247
|
+
const controlPointsChanged = this.arcLengthLUT.controlPoints.length !== this.controlPoints.length ||
|
|
248
|
+
this.arcLengthLUT.controlPoints.some((cp, index) => !PointCal.isEqual(cp, this.controlPoints[index]));
|
|
249
|
+
if (controlPointsChanged || this.arcLengthLUT.arcLengthLUT.length === 0) {
|
|
250
|
+
// Clear cache when regenerating LUT to ensure consistency
|
|
251
|
+
this.clearCache();
|
|
252
|
+
let res = [];
|
|
253
|
+
let tSteps = 1 / steps;
|
|
254
|
+
for (let tVal = 0; tVal <= 1; tVal += tSteps) {
|
|
255
|
+
res.push({ tVal: tVal, length: this.lengthAtT(tVal) });
|
|
256
|
+
}
|
|
257
|
+
this.arcLengthLUT = { controlPoints: [...this.controlPoints], arcLengthLUT: res };
|
|
205
258
|
}
|
|
206
|
-
return
|
|
259
|
+
return this.arcLengthLUT;
|
|
207
260
|
}
|
|
208
|
-
split(tVal
|
|
261
|
+
split(tVal) {
|
|
209
262
|
this.validateTVal(tVal);
|
|
210
263
|
if (this.controlPoints.length == 3) {
|
|
211
264
|
let newControlPoint1 = this.controlPoints[0];
|
|
@@ -229,6 +282,30 @@ class BCurve {
|
|
|
229
282
|
let newControlPoint7 = this.controlPoints[3];
|
|
230
283
|
return [[newControlPoint1, newControlPoint2, newControlPoint3, newControlPoint4], [newControlPoint4, newControlPoint5, newControlPoint6, newControlPoint7]];
|
|
231
284
|
}
|
|
285
|
+
splitIn3WithControlPoints(tVal, tVal2) {
|
|
286
|
+
if (tVal2 < tVal) {
|
|
287
|
+
console.warn("tVal2 is less than tVal, swapping them");
|
|
288
|
+
[tVal, tVal2] = [tVal2, tVal];
|
|
289
|
+
}
|
|
290
|
+
const firstSplit = this.split(tVal);
|
|
291
|
+
const secondHalf = new BCurve(firstSplit[1]);
|
|
292
|
+
const secondSplit = secondHalf.split(tVal2);
|
|
293
|
+
return [firstSplit[0], secondSplit[0], secondSplit[1]];
|
|
294
|
+
}
|
|
295
|
+
splitIn3Curves(tVal, tVal2) {
|
|
296
|
+
if (tVal2 < tVal) {
|
|
297
|
+
console.warn("tVal2 is less than tVal, swapping them");
|
|
298
|
+
[tVal, tVal2] = [tVal2, tVal];
|
|
299
|
+
}
|
|
300
|
+
const firstSplit = this.split(tVal);
|
|
301
|
+
const secondHalf = new BCurve(firstSplit[1]);
|
|
302
|
+
const secondSplit = secondHalf.split(tVal2);
|
|
303
|
+
return [new BCurve(firstSplit[0]), new BCurve(secondSplit[0]), new BCurve(secondSplit[1])];
|
|
304
|
+
}
|
|
305
|
+
splitAndTakeMidCurve(tVal, tVal2) {
|
|
306
|
+
const [firstSplit, secondSplit, thirdSplit] = this.splitIn3Curves(tVal, tVal2);
|
|
307
|
+
return secondSplit;
|
|
308
|
+
}
|
|
232
309
|
getProjection(point) {
|
|
233
310
|
const threshold = 0.00001;
|
|
234
311
|
let distance = Number.MAX_VALUE;
|
|
@@ -388,6 +465,9 @@ class BCurve {
|
|
|
388
465
|
return NaN;
|
|
389
466
|
return numerator / denominator;
|
|
390
467
|
}
|
|
468
|
+
secondDerivative(tVal) {
|
|
469
|
+
return computeWithControlPoints(tVal, this.getDerivativeControlPoints(this.dControlPoints));
|
|
470
|
+
}
|
|
391
471
|
getCoefficientOfTTerms() {
|
|
392
472
|
return this.getCoefficientOfTTermsWithControlPoints(this.controlPoints);
|
|
393
473
|
}
|
|
@@ -545,29 +625,99 @@ class BCurve {
|
|
|
545
625
|
advanceAtTWithLength(tVal, length) {
|
|
546
626
|
const currentLength = this.lengthAtT(tVal);
|
|
547
627
|
const targetLength = currentLength + length;
|
|
628
|
+
// Handle edge cases first
|
|
629
|
+
if (tVal === 0 && length < 0) {
|
|
630
|
+
return { type: "beforeCurve", remainLength: -length };
|
|
631
|
+
}
|
|
632
|
+
if (tVal === 1 && length > 0) {
|
|
633
|
+
return { type: "afterCurve", remainLength: length };
|
|
634
|
+
}
|
|
548
635
|
if (targetLength > this.fullLength) {
|
|
549
|
-
return { type: "afterCurve",
|
|
636
|
+
return { type: "afterCurve", remainLength: targetLength - this.fullLength };
|
|
550
637
|
}
|
|
551
638
|
else if (targetLength < 0) {
|
|
552
639
|
return { type: "beforeCurve", remainLength: -targetLength };
|
|
553
640
|
}
|
|
554
|
-
|
|
641
|
+
// Use LUT for binary search
|
|
642
|
+
if (this.arcLengthLUT.arcLengthLUT.length === 0) {
|
|
643
|
+
this.arcLengthLUT = this.getArcLengthLUT(1000);
|
|
644
|
+
}
|
|
645
|
+
const points = this.arcLengthLUT.arcLengthLUT;
|
|
555
646
|
let low = 0;
|
|
556
647
|
let high = points.length - 1;
|
|
648
|
+
// Binary search to find the interval containing targetLength
|
|
557
649
|
while (low <= high) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
650
|
+
const mid = Math.floor((low + high) / 2);
|
|
651
|
+
const midLength = points[mid].length;
|
|
652
|
+
if (Math.abs(midLength - targetLength) < 1e-10) {
|
|
653
|
+
// Found exact match
|
|
654
|
+
const resultTVal = points[mid].tVal;
|
|
655
|
+
const point = this.get(resultTVal);
|
|
656
|
+
return { type: "withinCurve", tVal: resultTVal, point: point };
|
|
562
657
|
}
|
|
563
|
-
else if (
|
|
658
|
+
else if (midLength < targetLength) {
|
|
564
659
|
low = mid + 1;
|
|
565
660
|
}
|
|
566
661
|
else {
|
|
567
662
|
high = mid - 1;
|
|
568
663
|
}
|
|
569
664
|
}
|
|
570
|
-
|
|
665
|
+
// After binary search, 'high' points to the largest index with length < targetLength
|
|
666
|
+
// and 'low' points to the smallest index with length >= targetLength
|
|
667
|
+
// Handle edge cases
|
|
668
|
+
if (high < 0) {
|
|
669
|
+
// targetLength is smaller than the first point's length
|
|
670
|
+
const point = this.get(0);
|
|
671
|
+
return { type: "withinCurve", tVal: 0, point: point };
|
|
672
|
+
}
|
|
673
|
+
if (low >= points.length) {
|
|
674
|
+
// targetLength is larger than the last point's length
|
|
675
|
+
const point = this.get(1);
|
|
676
|
+
return { type: "withinCurve", tVal: 1, point: point };
|
|
677
|
+
}
|
|
678
|
+
// Interpolate between points[high] and points[low]
|
|
679
|
+
const p1 = points[high];
|
|
680
|
+
const p2 = points[low];
|
|
681
|
+
// Linear interpolation
|
|
682
|
+
const lengthRange = p2.length - p1.length;
|
|
683
|
+
const tRange = p2.tVal - p1.tVal;
|
|
684
|
+
if (lengthRange === 0) {
|
|
685
|
+
// Both points have the same length, use the first one
|
|
686
|
+
const point = this.get(p1.tVal);
|
|
687
|
+
return { type: "withinCurve", tVal: p1.tVal, point: point };
|
|
688
|
+
}
|
|
689
|
+
const ratio = (targetLength - p1.length) / lengthRange;
|
|
690
|
+
const interpolatedT = p1.tVal + ratio * tRange;
|
|
691
|
+
// Clamp to valid range
|
|
692
|
+
const clampedT = Math.max(0, Math.min(1, interpolatedT));
|
|
693
|
+
const point = this.get(clampedT);
|
|
694
|
+
return { type: "withinCurve", tVal: clampedT, point: point };
|
|
695
|
+
}
|
|
696
|
+
advanceByDistance(startT, distance) {
|
|
697
|
+
let currentT = startT;
|
|
698
|
+
let remainingDistance = distance;
|
|
699
|
+
const stepSize = 0.01; // Adjust for precision vs performance
|
|
700
|
+
if (distance > this.fullLength) {
|
|
701
|
+
return { type: "afterCurve", remainLength: distance - this.fullLength };
|
|
702
|
+
}
|
|
703
|
+
else if (distance < 0) {
|
|
704
|
+
return { type: "beforeCurve", remainLength: -distance };
|
|
705
|
+
}
|
|
706
|
+
while (remainingDistance > 0 && currentT < 1) {
|
|
707
|
+
const currentPoint = this.get(currentT);
|
|
708
|
+
const nextT = Math.min(currentT + stepSize, 1);
|
|
709
|
+
const nextPoint = this.get(nextT);
|
|
710
|
+
const segmentLength = Math.sqrt(Math.pow(nextPoint.x - currentPoint.x, 2) +
|
|
711
|
+
Math.pow(nextPoint.y - currentPoint.y, 2));
|
|
712
|
+
if (segmentLength >= remainingDistance) {
|
|
713
|
+
// Interpolate within this segment
|
|
714
|
+
const ratio = remainingDistance / segmentLength;
|
|
715
|
+
return { type: "withinCurve", tVal: currentT + ratio * (nextT - currentT), point: this.get(currentT + ratio * (nextT - currentT)) };
|
|
716
|
+
}
|
|
717
|
+
remainingDistance -= segmentLength;
|
|
718
|
+
currentT = nextT;
|
|
719
|
+
}
|
|
720
|
+
return { type: "withinCurve", tVal: currentT, point: this.get(currentT) };
|
|
571
721
|
}
|
|
572
722
|
refineBinary(curve, x, y, LUT, i, targetDistance = 0, epsilon = 0.01) {
|
|
573
723
|
let q = LUT[i], count = 1, distance = Number.MAX_SAFE_INTEGER;
|
|
@@ -643,6 +793,225 @@ class BCurve {
|
|
|
643
793
|
});
|
|
644
794
|
return { min: min, max: max };
|
|
645
795
|
}
|
|
796
|
+
normal(tVal) {
|
|
797
|
+
const d = this.derivative(tVal);
|
|
798
|
+
const q = Math.sqrt(d.x * d.x + d.y * d.y);
|
|
799
|
+
return { tVal, direction: { x: -d.y / q, y: d.x / q } };
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function reduce(curve) {
|
|
803
|
+
let i, t1 = 0, t2 = 0, step = 0.01, segment, pass1 = [], pass2 = [];
|
|
804
|
+
// first pass: split on extrema
|
|
805
|
+
let extrema = curve.getExtrema().x;
|
|
806
|
+
if (extrema.indexOf(0) === -1) {
|
|
807
|
+
extrema = [0].concat(extrema);
|
|
808
|
+
}
|
|
809
|
+
if (extrema.indexOf(1) === -1) {
|
|
810
|
+
extrema.push(1);
|
|
811
|
+
}
|
|
812
|
+
for (t1 = extrema[0], i = 1; i < extrema.length; i++) {
|
|
813
|
+
t2 = extrema[i];
|
|
814
|
+
segment = curve.splitAndTakeMidCurve(t1, t2);
|
|
815
|
+
pass1.push(segment);
|
|
816
|
+
t1 = t2;
|
|
817
|
+
}
|
|
818
|
+
// second pass: further reduce these segments to simple segments
|
|
819
|
+
pass1.forEach(p1 => {
|
|
820
|
+
t1 = 0;
|
|
821
|
+
t2 = 0;
|
|
822
|
+
while (t2 <= 1) {
|
|
823
|
+
for (t2 = t1 + step; t2 <= 1 + step; t2 += step) {
|
|
824
|
+
// Clamp t2 to valid range
|
|
825
|
+
const clampedT2 = Math.min(t2, 1);
|
|
826
|
+
segment = p1.splitAndTakeMidCurve(t1, clampedT2);
|
|
827
|
+
if (!curveIsSimple(segment)) {
|
|
828
|
+
t2 -= step;
|
|
829
|
+
if (Math.abs(t1 - t2) < step) {
|
|
830
|
+
// we can never form a reduction
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
const finalT2 = Math.min(t2, 1);
|
|
834
|
+
segment = p1.splitAndTakeMidCurve(t1, finalT2);
|
|
835
|
+
pass2.push(segment);
|
|
836
|
+
t1 = finalT2;
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (t1 < 1) {
|
|
842
|
+
segment = p1.splitAndTakeMidCurve(t1, 1);
|
|
843
|
+
pass2.push(segment);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
return pass2;
|
|
847
|
+
}
|
|
848
|
+
function raiseCurveOrder(curve) {
|
|
849
|
+
const p = curve.getControlPoints(), np = [p[0]], k = p.length;
|
|
850
|
+
for (let i = 1; i < k; i++) {
|
|
851
|
+
const pi = p[i];
|
|
852
|
+
const pim = p[i - 1];
|
|
853
|
+
np[i] = {
|
|
854
|
+
x: ((k - i) / k) * pi.x + (i / k) * pim.x,
|
|
855
|
+
y: ((k - i) / k) * pi.y + (i / k) * pim.y,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
np[k] = p[k - 1];
|
|
859
|
+
return new BCurve(np);
|
|
860
|
+
}
|
|
861
|
+
function offset(curve, t, d) {
|
|
862
|
+
if (d !== undefined) {
|
|
863
|
+
const c = curve.get(t), n = curve.normal(t).direction;
|
|
864
|
+
const ret = {
|
|
865
|
+
c: c,
|
|
866
|
+
n: n,
|
|
867
|
+
x: c.x + n.x * d,
|
|
868
|
+
y: c.y + n.y * d,
|
|
869
|
+
};
|
|
870
|
+
// if (this._3d) {
|
|
871
|
+
// ret.z = c.z + n.z * d;
|
|
872
|
+
// }
|
|
873
|
+
return ret;
|
|
874
|
+
}
|
|
875
|
+
// Native offset implementation based on bezier-js algorithm
|
|
876
|
+
const points = curve.getControlPoints();
|
|
877
|
+
const linear = curveIsLinear(curve);
|
|
878
|
+
if (linear) {
|
|
879
|
+
const nv = curve.normal(0).direction, coords = points.map(function (p) {
|
|
880
|
+
const ret = {
|
|
881
|
+
x: p.x + t * nv.x,
|
|
882
|
+
y: p.y + t * nv.y,
|
|
883
|
+
};
|
|
884
|
+
return ret;
|
|
885
|
+
});
|
|
886
|
+
return [new BCurve(coords)];
|
|
887
|
+
}
|
|
888
|
+
// For non-linear curves, reduce to simple segments and scale each
|
|
889
|
+
return reduce(curve).map(function (s) {
|
|
890
|
+
if (curveIsLinear(s)) {
|
|
891
|
+
return offset(s, t)[0];
|
|
892
|
+
}
|
|
893
|
+
return scaleCurve(s, t);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function offset2(curve, d) {
|
|
897
|
+
const lut = curve.getLUTWithTVal(100);
|
|
898
|
+
const res = lut.map((item) => {
|
|
899
|
+
const derivative = PointCal.unitVector(curve.derivative(item.tVal));
|
|
900
|
+
const normal = { x: -derivative.y, y: derivative.x };
|
|
901
|
+
const offsetPoint = { x: item.point.x + normal.x * d, y: item.point.y + normal.y * d };
|
|
902
|
+
return offsetPoint;
|
|
903
|
+
});
|
|
904
|
+
return res;
|
|
905
|
+
}
|
|
906
|
+
// Helper function for line-line intersection (ported from bezier-js utils)
|
|
907
|
+
function lli4(p1, p2, p3, p4) {
|
|
908
|
+
const x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y;
|
|
909
|
+
return lli8(x1, y1, x2, y2, x3, y3, x4, y4);
|
|
910
|
+
}
|
|
911
|
+
function lli8(x1, y1, x2, y2, x3, y3, x4, y4) {
|
|
912
|
+
const nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
|
|
913
|
+
const ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
|
|
914
|
+
const d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
915
|
+
if (d == 0) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
return { x: nx / d, y: ny / d };
|
|
919
|
+
}
|
|
920
|
+
function curveIsLinear(curve) {
|
|
921
|
+
const order = curve.getControlPoints().length - 1;
|
|
922
|
+
const points = curve.getControlPoints();
|
|
923
|
+
const alignedPoints = alignPointsToLine(points, { p1: points[0], p2: points[order] });
|
|
924
|
+
const baseLength = PointCal.distanceBetweenPoints(points[0], points[order]);
|
|
925
|
+
// Sum of distances from the line (y coordinates in aligned space)
|
|
926
|
+
const linear = alignedPoints.reduce((t, p) => t + Math.abs(p.y), 0) < baseLength / 50;
|
|
927
|
+
return linear;
|
|
928
|
+
}
|
|
929
|
+
function scaleCurve(curve, d) {
|
|
930
|
+
const order = curve.getControlPoints().length - 1;
|
|
931
|
+
let distanceFn = undefined;
|
|
932
|
+
if (typeof d === "function") {
|
|
933
|
+
distanceFn = d;
|
|
934
|
+
}
|
|
935
|
+
if (distanceFn && order === 2) {
|
|
936
|
+
return scaleCurve(raiseCurveOrder(curve), distanceFn);
|
|
937
|
+
}
|
|
938
|
+
const points = curve.getControlPoints();
|
|
939
|
+
// Check if curve is linear
|
|
940
|
+
if (curveIsLinear(curve)) {
|
|
941
|
+
return translate(curve, curve.normal(0).direction, distanceFn ? distanceFn(0) : d, distanceFn ? distanceFn(1) : d);
|
|
942
|
+
}
|
|
943
|
+
const r1 = distanceFn ? distanceFn(0) : d;
|
|
944
|
+
const r2 = distanceFn ? distanceFn(1) : d;
|
|
945
|
+
// Get offset points at endpoints to find the scaling origin
|
|
946
|
+
const v = [offset(curve, 0, 10), offset(curve, 1, 10)];
|
|
947
|
+
const np = [];
|
|
948
|
+
const o = lli4(v[0], v[0].c, v[1], v[1].c);
|
|
949
|
+
if (!o) {
|
|
950
|
+
// Fallback: use simple translation for problematic curves
|
|
951
|
+
return translate(curve, curve.normal(0).direction, r1, r2);
|
|
952
|
+
}
|
|
953
|
+
// Move endpoint control points by distance along normal
|
|
954
|
+
[0, 1].forEach(function (t) {
|
|
955
|
+
const p = JSON.parse(JSON.stringify(points[t * order]));
|
|
956
|
+
const vt = v[t];
|
|
957
|
+
p.x += (t ? r2 : r1) * vt.n.x;
|
|
958
|
+
p.y += (t ? r2 : r1) * vt.n.y;
|
|
959
|
+
np[t * order] = p;
|
|
960
|
+
});
|
|
961
|
+
if (!distanceFn) {
|
|
962
|
+
// Move control points to lie on the intersection of the offset
|
|
963
|
+
// derivative vector, and the origin-through-control vector
|
|
964
|
+
[0, 1].forEach((t) => {
|
|
965
|
+
if (order === 2 && !!t)
|
|
966
|
+
return;
|
|
967
|
+
const p = np[t * order];
|
|
968
|
+
const derivativeAtT = curve.derivative(t);
|
|
969
|
+
const p2 = { x: p.x + derivativeAtT.x, y: p.y + derivativeAtT.y };
|
|
970
|
+
const intersection = lli4(p, p2, o, points[t + 1]);
|
|
971
|
+
if (intersection) {
|
|
972
|
+
np[t + 1] = intersection;
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
// Fallback: use original control point with simple offset
|
|
976
|
+
const originalPoint = points[t + 1];
|
|
977
|
+
const normal = curve.normal((t + 1) / order).direction;
|
|
978
|
+
np[t + 1] = {
|
|
979
|
+
x: originalPoint.x + (t ? r2 : r1) * normal.x,
|
|
980
|
+
y: originalPoint.y + (t ? r2 : r1) * normal.y
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
return new BCurve(np);
|
|
985
|
+
}
|
|
986
|
+
// For function-based distances, move control points by distance
|
|
987
|
+
// to ensure correct tangent at endpoints
|
|
988
|
+
[0, 1].forEach(function (t) {
|
|
989
|
+
if (order === 2 && !!t)
|
|
990
|
+
return;
|
|
991
|
+
const p = points[t + 1];
|
|
992
|
+
const ov = {
|
|
993
|
+
x: p.x - o.x,
|
|
994
|
+
y: p.y - o.y,
|
|
995
|
+
};
|
|
996
|
+
let rc = distanceFn((t + 1) / order);
|
|
997
|
+
const m = Math.sqrt(ov.x * ov.x + ov.y * ov.y);
|
|
998
|
+
ov.x /= m;
|
|
999
|
+
ov.y /= m;
|
|
1000
|
+
np[t + 1] = {
|
|
1001
|
+
x: p.x + rc * ov.x,
|
|
1002
|
+
y: p.y + rc * ov.y,
|
|
1003
|
+
};
|
|
1004
|
+
});
|
|
1005
|
+
return new BCurve(np);
|
|
1006
|
+
}
|
|
1007
|
+
function alignPointsToLine(points, line) {
|
|
1008
|
+
const tx = line.p1.x, ty = line.p1.y, a = -Math.atan2(line.p2.y - ty, line.p2.x - tx), d = function (v) {
|
|
1009
|
+
return {
|
|
1010
|
+
x: (v.x - tx) * Math.cos(a) - (v.y - ty) * Math.sin(a),
|
|
1011
|
+
y: (v.x - tx) * Math.sin(a) + (v.y - ty) * Math.cos(a),
|
|
1012
|
+
};
|
|
1013
|
+
};
|
|
1014
|
+
return points.map(d);
|
|
646
1015
|
}
|
|
647
1016
|
class TValOutofBoundError extends Error {
|
|
648
1017
|
constructor(message) {
|
|
@@ -866,6 +1235,31 @@ function computeWithControlPoints(tVal, controlPoints) {
|
|
|
866
1235
|
}
|
|
867
1236
|
return points[0];
|
|
868
1237
|
}
|
|
1238
|
+
function curveIsSimple(curve) {
|
|
1239
|
+
if (curve.getControlPoints().length === 4) {
|
|
1240
|
+
const points = curve.getControlPoints();
|
|
1241
|
+
const p0ToP3Vector = PointCal.subVector(points[3], points[0]);
|
|
1242
|
+
const p0ToP1Vector = PointCal.subVector(points[1], points[0]);
|
|
1243
|
+
const p0ToP2Vector = PointCal.subVector(points[2], points[0]);
|
|
1244
|
+
const a1 = PointCal.angleFromA2B(p0ToP3Vector, p0ToP1Vector);
|
|
1245
|
+
const a2 = PointCal.angleFromA2B(p0ToP3Vector, p0ToP2Vector);
|
|
1246
|
+
if ((a1 > 0 && a2 < 0) || (a1 < 0 && a2 > 0))
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
const n1 = curve.normal(0).direction;
|
|
1250
|
+
const n2 = curve.normal(1).direction;
|
|
1251
|
+
let s = n1.x * n2.x + n1.y * n2.y;
|
|
1252
|
+
// if (this._3d) {
|
|
1253
|
+
// s += n1.z * n2.z;
|
|
1254
|
+
// }
|
|
1255
|
+
return Math.abs(Math.acos(s)) < Math.PI / 3;
|
|
1256
|
+
}
|
|
1257
|
+
function translate(curve, vector, d1, d2) {
|
|
1258
|
+
const order = curve.getControlPoints().length - 1;
|
|
1259
|
+
const points = curve.getControlPoints();
|
|
1260
|
+
const d = points.map((_, i) => (1 - i / order) * d1 + (i / order) * d2);
|
|
1261
|
+
return new BCurve(points.map((p, i) => ({ x: p.x + d[i] * vector.x, y: p.y + d[i] * vector.y })));
|
|
1262
|
+
}
|
|
869
1263
|
|
|
870
1264
|
class Line {
|
|
871
1265
|
constructor(startPoint, endPoint) {
|
|
@@ -1219,5 +1613,5 @@ class Path {
|
|
|
1219
1613
|
}
|
|
1220
1614
|
}
|
|
1221
1615
|
|
|
1222
|
-
export { AABBIntersects, BCurve, CompositeBCurve, ControlPoint, Line, Path, TValOutofBoundError, accept, approximately, computeWithControlPoints, cuberoot, cuberoot2, getCubicRoots, getIntersectionsBetweenCurves, getLineIntersection, projectPointOntoLine, solveCubic };
|
|
1616
|
+
export { AABBIntersects, BCurve, CompositeBCurve, ControlPoint, Line, Path, TValOutofBoundError, accept, approximately, computeWithControlPoints, cuberoot, cuberoot2, getCubicRoots, getIntersectionsBetweenCurves, getLineIntersection, offset, offset2, projectPointOntoLine, reduce, solveCubic };
|
|
1223
1617
|
//# sourceMappingURL=index.js.map
|