@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.
Files changed (46) hide show
  1. package/README.md +5 -0
  2. package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.d.ts → b-curve.d.ts} +42 -5
  3. package/curve.tsbuildinfo +1 -0
  4. package/{dist/index.js → index.js} +412 -18
  5. package/index.js.map +1 -0
  6. package/package.json +11 -11
  7. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.js +0 -878
  8. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/b-curve.js.map +0 -1
  9. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.js +0 -215
  10. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.js.map +0 -1
  11. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/curve.tsbuildinfo +0 -1
  12. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.js +0 -5
  13. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.js.map +0 -1
  14. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.js +0 -74
  15. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.js.map +0 -1
  16. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.js +0 -65
  17. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.js.map +0 -1
  18. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/utils.js +0 -43
  19. package/.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/utils.js.map +0 -1
  20. package/dist/b-curve.d.ts +0 -151
  21. package/dist/composite-curve.d.ts +0 -33
  22. package/dist/curve.tsbuildinfo +0 -1
  23. package/dist/index.d.ts +0 -4
  24. package/dist/index.js.map +0 -1
  25. package/dist/line.d.ts +0 -35
  26. package/dist/package.json +0 -26
  27. package/dist/path.d.ts +0 -16
  28. package/dist/utils.d.ts +0 -3
  29. package/jest.config.js +0 -18
  30. package/project.json +0 -33
  31. package/rollup.config.js +0 -20
  32. package/src/b-curve.ts +0 -1006
  33. package/src/composite-curve.ts +0 -243
  34. package/src/index.ts +0 -4
  35. package/src/line.ts +0 -98
  36. package/src/path.ts +0 -75
  37. package/src/utils.ts +0 -47
  38. package/test/b-curve.test.ts +0 -715
  39. package/test/line.test.ts +0 -38
  40. package/tsconfig.json +0 -21
  41. package/tsconfig.spec.json +0 -12
  42. /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/composite-curve.d.ts → composite-curve.d.ts} +0 -0
  43. /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/index.d.ts → index.d.ts} +0 -0
  44. /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/line.d.ts → line.d.ts} +0 -0
  45. /package/{.rollup.cache/home/runner/work/ue-too/ue-too/packages/curve/dist/path.d.ts → path.d.ts} +0 -0
  46. /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
- this.arcLengthLUT = this.getArcLengthLUT(1000);
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
- return z * sum;
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 = 100) {
201
- let res = [];
202
- let tSteps = 1 / steps;
203
- for (let tVal = 0; tVal <= 1; tVal += tSteps) {
204
- res.push({ tVal: tVal, length: this.lengthAtT(tVal) });
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 { controlPoints: this.controlPoints, arcLengthLUT: res };
259
+ return this.arcLengthLUT;
207
260
  }
208
- split(tVal, tVal2) {
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", remainLenth: targetLength - this.fullLength };
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
- let points = [...this.arcLengthLUT.arcLengthLUT];
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
- let mid = Math.floor((low + high) / 2);
559
- if (points[mid].length == targetLength) {
560
- const point = this.get((mid + 1) / points.length);
561
- return { type: "withinCurve", tVal: points[mid].tVal, point: point };
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 (points[mid].length < targetLength) {
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
- return low >= points.length ? { type: "withinCurve", tVal: 1, point: this.get(1) } : { type: "withinCurve", tVal: (low + 1) / points.length, point: this.get((low + 1) / points.length) };
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