@js-draw/math 1.11.1 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. package/dist/cjs/Vec3.d.ts +21 -0
  2. package/dist/cjs/Vec3.js +28 -0
  3. package/dist/cjs/lib.d.ts +2 -2
  4. package/dist/cjs/lib.js +16 -3
  5. package/dist/cjs/rounding/cleanUpNumber.d.ts +3 -0
  6. package/dist/cjs/rounding/cleanUpNumber.js +35 -0
  7. package/dist/cjs/rounding/constants.d.ts +1 -0
  8. package/dist/cjs/rounding/constants.js +4 -0
  9. package/dist/cjs/rounding/getLenAfterDecimal.d.ts +10 -0
  10. package/dist/cjs/rounding/getLenAfterDecimal.js +30 -0
  11. package/dist/cjs/rounding/lib.d.ts +1 -0
  12. package/dist/cjs/rounding/lib.js +5 -0
  13. package/dist/cjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  14. package/dist/cjs/rounding/toRoundedString.js +54 -0
  15. package/dist/cjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  16. package/dist/cjs/rounding/toStringOfSamePrecision.js +58 -0
  17. package/dist/cjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  18. package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
  19. package/dist/cjs/shapes/BezierJSWrapper.d.ts +15 -5
  20. package/dist/cjs/shapes/BezierJSWrapper.js +135 -18
  21. package/dist/cjs/shapes/LineSegment2.d.ts +34 -5
  22. package/dist/cjs/shapes/LineSegment2.js +63 -10
  23. package/dist/cjs/shapes/Parameterized2DShape.d.ts +31 -0
  24. package/dist/cjs/shapes/Parameterized2DShape.js +15 -0
  25. package/dist/cjs/shapes/Path.d.ts +40 -6
  26. package/dist/cjs/shapes/Path.js +181 -22
  27. package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
  28. package/dist/cjs/shapes/PointShape2D.js +28 -5
  29. package/dist/cjs/shapes/QuadraticBezier.d.ts +4 -0
  30. package/dist/cjs/shapes/QuadraticBezier.js +19 -4
  31. package/dist/cjs/shapes/Rect2.d.ts +3 -0
  32. package/dist/cjs/shapes/Rect2.js +4 -1
  33. package/dist/mjs/Vec3.d.ts +21 -0
  34. package/dist/mjs/Vec3.mjs +28 -0
  35. package/dist/mjs/lib.d.ts +2 -2
  36. package/dist/mjs/lib.mjs +1 -1
  37. package/dist/mjs/rounding/cleanUpNumber.d.ts +3 -0
  38. package/dist/mjs/rounding/cleanUpNumber.mjs +31 -0
  39. package/dist/mjs/rounding/cleanUpNumber.test.d.ts +1 -0
  40. package/dist/mjs/rounding/constants.d.ts +1 -0
  41. package/dist/mjs/rounding/constants.mjs +1 -0
  42. package/dist/mjs/rounding/getLenAfterDecimal.d.ts +10 -0
  43. package/dist/mjs/rounding/getLenAfterDecimal.mjs +26 -0
  44. package/dist/mjs/rounding/lib.d.ts +1 -0
  45. package/dist/mjs/rounding/lib.mjs +1 -0
  46. package/dist/mjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  47. package/dist/mjs/rounding/toRoundedString.mjs +47 -0
  48. package/dist/mjs/rounding/toRoundedString.test.d.ts +1 -0
  49. package/dist/mjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  50. package/dist/mjs/rounding/toStringOfSamePrecision.mjs +51 -0
  51. package/dist/mjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  52. package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
  53. package/dist/mjs/shapes/BezierJSWrapper.d.ts +15 -5
  54. package/dist/mjs/shapes/BezierJSWrapper.mjs +133 -18
  55. package/dist/mjs/shapes/LineSegment2.d.ts +34 -5
  56. package/dist/mjs/shapes/LineSegment2.mjs +63 -10
  57. package/dist/mjs/shapes/Parameterized2DShape.d.ts +31 -0
  58. package/dist/mjs/shapes/Parameterized2DShape.mjs +8 -0
  59. package/dist/mjs/shapes/Path.d.ts +40 -6
  60. package/dist/mjs/shapes/Path.mjs +175 -16
  61. package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
  62. package/dist/mjs/shapes/PointShape2D.mjs +28 -5
  63. package/dist/mjs/shapes/QuadraticBezier.d.ts +4 -0
  64. package/dist/mjs/shapes/QuadraticBezier.mjs +19 -4
  65. package/dist/mjs/shapes/Rect2.d.ts +3 -0
  66. package/dist/mjs/shapes/Rect2.mjs +4 -1
  67. package/package.json +5 -5
  68. package/src/Vec3.test.ts +26 -7
  69. package/src/Vec3.ts +30 -0
  70. package/src/lib.ts +3 -1
  71. package/src/rounding/cleanUpNumber.test.ts +15 -0
  72. package/src/rounding/cleanUpNumber.ts +38 -0
  73. package/src/rounding/constants.ts +3 -0
  74. package/src/rounding/getLenAfterDecimal.ts +29 -0
  75. package/src/rounding/lib.ts +2 -0
  76. package/src/rounding/toRoundedString.test.ts +32 -0
  77. package/src/rounding/toRoundedString.ts +57 -0
  78. package/src/rounding/toStringOfSamePrecision.test.ts +21 -0
  79. package/src/rounding/toStringOfSamePrecision.ts +63 -0
  80. package/src/shapes/Abstract2DShape.ts +3 -0
  81. package/src/shapes/BezierJSWrapper.ts +154 -14
  82. package/src/shapes/LineSegment2.test.ts +35 -1
  83. package/src/shapes/LineSegment2.ts +79 -11
  84. package/src/shapes/Parameterized2DShape.ts +39 -0
  85. package/src/shapes/Path.test.ts +63 -3
  86. package/src/shapes/Path.ts +211 -26
  87. package/src/shapes/PointShape2D.ts +33 -6
  88. package/src/shapes/QuadraticBezier.test.ts +48 -12
  89. package/src/shapes/QuadraticBezier.ts +23 -5
  90. package/src/shapes/Rect2.ts +4 -1
  91. package/dist/cjs/rounding.js +0 -146
  92. package/dist/mjs/rounding.mjs +0 -139
  93. package/src/rounding.test.ts +0 -65
  94. package/src/rounding.ts +0 -168
  95. /package/dist/cjs/{rounding.test.d.ts → rounding/cleanUpNumber.test.d.ts} +0 -0
  96. /package/dist/{mjs/rounding.test.d.ts → cjs/rounding/toRoundedString.test.d.ts} +0 -0
@@ -1,32 +1,35 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
1
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
2
  if (kind === "m") throw new TypeError("Private method is not writable");
8
3
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
4
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
5
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
6
  };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
12
  var _BezierJSWrapper_bezierJs;
13
13
  import { Bezier } from 'bezier-js';
14
14
  import { Vec2 } from '../Vec2.mjs';
15
- import Abstract2DShape from './Abstract2DShape.mjs';
16
15
  import Rect2 from './Rect2.mjs';
16
+ import Parameterized2DShape from './Parameterized2DShape.mjs';
17
17
  /**
18
18
  * A lazy-initializing wrapper around Bezier-js.
19
19
  *
20
20
  * Subclasses may override `at`, `derivativeAt`, and `normal` with functions
21
21
  * that do not initialize a `bezier-js` `Bezier`.
22
22
  *
23
- * Do not use this class directly. It may be removed/replaced in a future release.
23
+ * **Do not use this class directly.** It may be removed/replaced in a future release.
24
24
  * @internal
25
25
  */
26
- class BezierJSWrapper extends Abstract2DShape {
27
- constructor() {
28
- super(...arguments);
26
+ export class BezierJSWrapper extends Parameterized2DShape {
27
+ constructor(bezierJsBezier) {
28
+ super();
29
29
  _BezierJSWrapper_bezierJs.set(this, null);
30
+ if (bezierJsBezier) {
31
+ __classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, bezierJsBezier, "f");
32
+ }
30
33
  }
31
34
  getBezier() {
32
35
  if (!__classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f")) {
@@ -36,7 +39,7 @@ class BezierJSWrapper extends Abstract2DShape {
36
39
  }
37
40
  signedDistance(point) {
38
41
  // .d: Distance
39
- return this.getBezier().project(point.xy).d;
42
+ return this.nearestPointTo(point).point.distanceTo(point);
40
43
  }
41
44
  /**
42
45
  * @returns the (more) exact distance from `point` to this.
@@ -56,34 +59,146 @@ class BezierJSWrapper extends Abstract2DShape {
56
59
  derivativeAt(t) {
57
60
  return Vec2.ofXY(this.getBezier().derivative(t));
58
61
  }
62
+ secondDerivativeAt(t) {
63
+ return Vec2.ofXY(this.getBezier().dderivative(t));
64
+ }
59
65
  normal(t) {
60
66
  return Vec2.ofXY(this.getBezier().normal(t));
61
67
  }
68
+ normalAt(t) {
69
+ return this.normal(t);
70
+ }
71
+ tangentAt(t) {
72
+ return this.derivativeAt(t).normalized();
73
+ }
62
74
  getTightBoundingBox() {
63
75
  const bbox = this.getBezier().bbox();
64
76
  const width = bbox.x.max - bbox.x.min;
65
77
  const height = bbox.y.max - bbox.y.min;
66
78
  return new Rect2(bbox.x.min, bbox.y.min, width, height);
67
79
  }
68
- intersectsLineSegment(line) {
80
+ argIntersectsLineSegment(line) {
69
81
  const bezier = this.getBezier();
70
- const intersectionPoints = bezier.intersects(line).map(t => {
82
+ return bezier.intersects(line).map(t => {
71
83
  // We're using the .intersects(line) function, which is documented
72
84
  // to always return numbers. However, to satisfy the type checker (and
73
85
  // possibly improperly-defined types),
74
86
  if (typeof t === 'string') {
75
87
  t = parseFloat(t);
76
88
  }
77
- const point = Vec2.ofXY(bezier.get(t));
89
+ const point = Vec2.ofXY(this.at(t));
78
90
  // Ensure that the intersection is on the line segment
79
- if (point.minus(line.p1).magnitude() > line.length
80
- || point.minus(line.p2).magnitude() > line.length) {
91
+ if (point.distanceTo(line.p1) > line.length
92
+ || point.distanceTo(line.p2) > line.length) {
81
93
  return null;
82
94
  }
83
- return point;
95
+ return t;
84
96
  }).filter(entry => entry !== null);
85
- return intersectionPoints;
97
+ }
98
+ splitAt(t) {
99
+ if (t <= 0 || t >= 1) {
100
+ return [this];
101
+ }
102
+ const bezier = this.getBezier();
103
+ const split = bezier.split(t);
104
+ return [
105
+ new BezierJSWrapperImpl(split.left.points.map(point => Vec2.ofXY(point)), split.left),
106
+ new BezierJSWrapperImpl(split.right.points.map(point => Vec2.ofXY(point)), split.right),
107
+ ];
108
+ }
109
+ nearestPointTo(point) {
110
+ // One implementation could be similar to this:
111
+ // const projection = this.getBezier().project(point);
112
+ // return {
113
+ // point: Vec2.ofXY(projection),
114
+ // parameterValue: projection.t!,
115
+ // };
116
+ // However, Bezier-js is rather impercise (and relies on a lookup table).
117
+ // Thus, we instead use Newton's Method:
118
+ // We want to find t such that f(t) = |B(t) - p|² is minimized.
119
+ // Expanding,
120
+ // f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
121
+ // ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
122
+ // ⇒ f'(t) = 2(Bₓ(t) - pₓ)(Bₓ'(t)) + 2(Bᵧ(t) - pᵧ)(Bᵧ'(t))
123
+ // = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
124
+ // ⇒ f''(t)= 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t) + 2Bᵧ'(t)Bᵧ'(t)
125
+ // + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
126
+ // Because f'(t) = 0 at relative extrema, we can use Newton's Method
127
+ // to improve on an initial guess.
128
+ const sqrDistAt = (t) => point.squareDistanceTo(this.at(t));
129
+ const yIntercept = sqrDistAt(0);
130
+ let t = 0;
131
+ let minSqrDist = yIntercept;
132
+ // Start by testing a few points:
133
+ const pointsToTest = 4;
134
+ for (let i = 0; i < pointsToTest; i++) {
135
+ const testT = i / (pointsToTest - 1);
136
+ const testMinSqrDist = sqrDistAt(testT);
137
+ if (testMinSqrDist < minSqrDist) {
138
+ t = testT;
139
+ minSqrDist = testMinSqrDist;
140
+ }
141
+ }
142
+ // To use Newton's Method, we need to evaluate the second derivative of the distance
143
+ // function:
144
+ const secondDerivativeAt = (t) => {
145
+ // f''(t) = 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t)
146
+ // + 2Bᵧ'(t)Bᵧ'(t) + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
147
+ const b = this.at(t);
148
+ const bPrime = this.derivativeAt(t);
149
+ const bPrimePrime = this.secondDerivativeAt(t);
150
+ return (2 * bPrime.x * bPrime.x + 2 * b.x * bPrimePrime.x - 2 * point.x * bPrimePrime.x
151
+ + 2 * bPrime.y * bPrime.y + 2 * b.y * bPrimePrime.y - 2 * point.y * bPrimePrime.y);
152
+ };
153
+ // Because we're zeroing f'(t), we also need to be able to compute it:
154
+ const derivativeAt = (t) => {
155
+ // f'(t) = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
156
+ const b = this.at(t);
157
+ const bPrime = this.derivativeAt(t);
158
+ return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x
159
+ + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
160
+ };
161
+ const iterate = () => {
162
+ const slope = secondDerivativeAt(t);
163
+ // We intersect a line through the point on f'(t) at t with the x-axis:
164
+ // y = m(x - x₀) + y₀
165
+ // ⇒ x - x₀ = (y - y₀) / m
166
+ // ⇒ x = (y - y₀) / m + x₀
167
+ //
168
+ // Thus, when zeroed,
169
+ // tN = (0 - f'(t)) / m + t
170
+ const newT = (0 - derivativeAt(t)) / slope + t;
171
+ //const distDiff = sqrDistAt(newT) - sqrDistAt(t);
172
+ //console.assert(distDiff <= 0, `${-distDiff} >= 0`);
173
+ t = newT;
174
+ if (t > 1) {
175
+ t = 1;
176
+ }
177
+ else if (t < 0) {
178
+ t = 0;
179
+ }
180
+ };
181
+ for (let i = 0; i < 12; i++) {
182
+ iterate();
183
+ }
184
+ return { parameterValue: t, point: this.at(t) };
185
+ }
186
+ toString() {
187
+ return `Bézier(${this.getPoints().map(point => point.toString()).join(', ')})`;
86
188
  }
87
189
  }
88
190
  _BezierJSWrapper_bezierJs = new WeakMap();
191
+ /**
192
+ * Private concrete implementation of `BezierJSWrapper`, used by methods above that need to return a wrapper
193
+ * around a `Bezier`.
194
+ */
195
+ class BezierJSWrapperImpl extends BezierJSWrapper {
196
+ constructor(controlPoints, curve) {
197
+ super(curve);
198
+ this.controlPoints = controlPoints;
199
+ }
200
+ getPoints() {
201
+ return this.controlPoints;
202
+ }
203
+ }
89
204
  export default BezierJSWrapper;
@@ -1,13 +1,14 @@
1
1
  import Mat33 from '../Mat33';
2
2
  import Rect2 from './Rect2';
3
3
  import { Vec2, Point2 } from '../Vec2';
4
- import Abstract2DShape from './Abstract2DShape';
4
+ import Parameterized2DShape from './Parameterized2DShape';
5
+ import Vec3 from '../Vec3';
5
6
  interface IntersectionResult {
6
7
  point: Point2;
7
8
  t: number;
8
9
  }
9
10
  /** Represents a line segment. A `LineSegment2` is immutable. */
10
- export declare class LineSegment2 extends Abstract2DShape {
11
+ export declare class LineSegment2 extends Parameterized2DShape {
11
12
  private readonly point1;
12
13
  private readonly point2;
13
14
  /**
@@ -28,8 +29,9 @@ export declare class LineSegment2 extends Abstract2DShape {
28
29
  get p1(): Point2;
29
30
  /** Alias for `point2`. */
30
31
  get p2(): Point2;
32
+ get center(): Point2;
31
33
  /**
32
- * Gets a point a distance `t` along this line.
34
+ * Gets a point a **distance** `t` along this line.
33
35
  *
34
36
  * @deprecated
35
37
  */
@@ -42,8 +44,20 @@ export declare class LineSegment2 extends Abstract2DShape {
42
44
  * `t` should be in `[0, 1]`.
43
45
  */
44
46
  at(t: number): Point2;
47
+ normalAt(_t: number): Vec2;
48
+ tangentAt(_t: number): Vec3;
49
+ splitAt(t: number): [LineSegment2] | [LineSegment2, LineSegment2];
50
+ /**
51
+ * Returns the intersection of this with another line segment.
52
+ *
53
+ * **WARNING**: The parameter value returned by this method does not range from 0 to 1 and
54
+ * is currently a length.
55
+ * This will change in a future release.
56
+ * @deprecated
57
+ */
45
58
  intersection(other: LineSegment2): IntersectionResult | null;
46
59
  intersects(other: LineSegment2): boolean;
60
+ argIntersectsLineSegment(lineSegment: LineSegment2): number[];
47
61
  /**
48
62
  * Returns the points at which this line segment intersects the
49
63
  * given line segment.
@@ -52,8 +66,12 @@ export declare class LineSegment2 extends Abstract2DShape {
52
66
  * line segment. This method, by contrast, returns **the point** at which the intersection
53
67
  * occurs, if such a point exists.
54
68
  */
55
- intersectsLineSegment(lineSegment: LineSegment2): import("../Vec3").Vec3[];
56
- closestPointTo(target: Point2): import("../Vec3").Vec3;
69
+ intersectsLineSegment(lineSegment: LineSegment2): Vec3[];
70
+ closestPointTo(target: Point2): Vec3;
71
+ nearestPointTo(target: Vec3): {
72
+ point: Vec3;
73
+ parameterValue: number;
74
+ };
57
75
  /**
58
76
  * Returns the distance from this line segment to `target`.
59
77
  *
@@ -66,5 +84,16 @@ export declare class LineSegment2 extends Abstract2DShape {
66
84
  /** @inheritdoc */
67
85
  getTightBoundingBox(): Rect2;
68
86
  toString(): string;
87
+ /**
88
+ * Returns `true` iff this is equivalent to `other`.
89
+ *
90
+ * **Options**:
91
+ * - `tolerance`: The maximum difference between endpoints. (Default: 0)
92
+ * - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
93
+ */
94
+ eq(other: LineSegment2, options?: {
95
+ tolerance?: number;
96
+ ignoreDirection?: boolean;
97
+ }): boolean;
69
98
  }
70
99
  export default LineSegment2;
@@ -1,8 +1,8 @@
1
1
  import Rect2 from './Rect2.mjs';
2
2
  import { Vec2 } from '../Vec2.mjs';
3
- import Abstract2DShape from './Abstract2DShape.mjs';
3
+ import Parameterized2DShape from './Parameterized2DShape.mjs';
4
4
  /** Represents a line segment. A `LineSegment2` is immutable. */
5
- export class LineSegment2 extends Abstract2DShape {
5
+ export class LineSegment2 extends Parameterized2DShape {
6
6
  /** Creates a new `LineSegment2` from its endpoints. */
7
7
  constructor(point1, point2) {
8
8
  super();
@@ -26,8 +26,11 @@ export class LineSegment2 extends Abstract2DShape {
26
26
  get p2() {
27
27
  return this.point2;
28
28
  }
29
+ get center() {
30
+ return this.point1.lerp(this.point2, 0.5);
31
+ }
29
32
  /**
30
- * Gets a point a distance `t` along this line.
33
+ * Gets a point a **distance** `t` along this line.
31
34
  *
32
35
  * @deprecated
33
36
  */
@@ -44,7 +47,31 @@ export class LineSegment2 extends Abstract2DShape {
44
47
  at(t) {
45
48
  return this.get(t * this.length);
46
49
  }
50
+ normalAt(_t) {
51
+ return this.direction.orthog();
52
+ }
53
+ tangentAt(_t) {
54
+ return this.direction;
55
+ }
56
+ splitAt(t) {
57
+ if (t <= 0 || t >= 1) {
58
+ return [this];
59
+ }
60
+ return [
61
+ new LineSegment2(this.point1, this.at(t)),
62
+ new LineSegment2(this.at(t), this.point2),
63
+ ];
64
+ }
65
+ /**
66
+ * Returns the intersection of this with another line segment.
67
+ *
68
+ * **WARNING**: The parameter value returned by this method does not range from 0 to 1 and
69
+ * is currently a length.
70
+ * This will change in a future release.
71
+ * @deprecated
72
+ */
47
73
  intersection(other) {
74
+ // TODO(v2.0.0): Make this return a `t` value from `0` to `1`.
48
75
  // We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
49
76
  // Observe that
50
77
  // x = this.point1.x + this.direction.x · t₁
@@ -103,10 +130,10 @@ export class LineSegment2 extends Abstract2DShape {
103
130
  resultT = (xIntersect - this.point1.x) / this.direction.x;
104
131
  }
105
132
  // Ensure the result is in this/the other segment.
106
- const resultToP1 = resultPoint.minus(this.point1).magnitude();
107
- const resultToP2 = resultPoint.minus(this.point2).magnitude();
108
- const resultToP3 = resultPoint.minus(other.point1).magnitude();
109
- const resultToP4 = resultPoint.minus(other.point2).magnitude();
133
+ const resultToP1 = resultPoint.distanceTo(this.point1);
134
+ const resultToP2 = resultPoint.distanceTo(this.point2);
135
+ const resultToP3 = resultPoint.distanceTo(other.point1);
136
+ const resultToP4 = resultPoint.distanceTo(other.point2);
110
137
  if (resultToP1 > this.length
111
138
  || resultToP2 > this.length
112
139
  || resultToP3 > other.length
@@ -121,6 +148,13 @@ export class LineSegment2 extends Abstract2DShape {
121
148
  intersects(other) {
122
149
  return this.intersection(other) !== null;
123
150
  }
151
+ argIntersectsLineSegment(lineSegment) {
152
+ const intersection = this.intersection(lineSegment);
153
+ if (intersection) {
154
+ return [intersection.t / this.length];
155
+ }
156
+ return [];
157
+ }
124
158
  /**
125
159
  * Returns the points at which this line segment intersects the
126
160
  * given line segment.
@@ -138,18 +172,21 @@ export class LineSegment2 extends Abstract2DShape {
138
172
  }
139
173
  // Returns the closest point on this to [target]
140
174
  closestPointTo(target) {
175
+ return this.nearestPointTo(target).point;
176
+ }
177
+ nearestPointTo(target) {
141
178
  // Distance from P1 along this' direction.
142
179
  const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
143
180
  const projectedDistFromP2 = this.length - projectedDistFromP1;
144
181
  const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
145
182
  if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
146
- return projection;
183
+ return { point: projection, parameterValue: projectedDistFromP1 / this.length };
147
184
  }
148
185
  if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
149
- return this.p2;
186
+ return { point: this.p2, parameterValue: 1 };
150
187
  }
151
188
  else {
152
- return this.p1;
189
+ return { point: this.p1, parameterValue: 0 };
153
190
  }
154
191
  }
155
192
  /**
@@ -172,5 +209,21 @@ export class LineSegment2 extends Abstract2DShape {
172
209
  toString() {
173
210
  return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
174
211
  }
212
+ /**
213
+ * Returns `true` iff this is equivalent to `other`.
214
+ *
215
+ * **Options**:
216
+ * - `tolerance`: The maximum difference between endpoints. (Default: 0)
217
+ * - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
218
+ */
219
+ eq(other, options) {
220
+ if (!(other instanceof LineSegment2)) {
221
+ return false;
222
+ }
223
+ const tolerance = options?.tolerance;
224
+ const ignoreDirection = options?.ignoreDirection ?? true;
225
+ return ((other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance))
226
+ || (ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance)));
227
+ }
175
228
  }
176
229
  export default LineSegment2;
@@ -0,0 +1,31 @@
1
+ import { Point2, Vec2 } from '../Vec2';
2
+ import Abstract2DShape from './Abstract2DShape';
3
+ import LineSegment2 from './LineSegment2';
4
+ /** A 2-dimensional path with parameter interval $t \in [0, 1]$. */
5
+ export declare abstract class Parameterized2DShape extends Abstract2DShape {
6
+ /** Returns this at a given parameter. $t \in [0, 1]$ */
7
+ abstract at(t: number): Point2;
8
+ /** Computes the unit normal vector at $t$. */
9
+ abstract normalAt(t: number): Vec2;
10
+ abstract tangentAt(t: number): Vec2;
11
+ /**
12
+ * Divides this shape into two separate shapes at parameter value $t$.
13
+ */
14
+ abstract splitAt(t: number): [Parameterized2DShape] | [Parameterized2DShape, Parameterized2DShape];
15
+ /**
16
+ * Returns the nearest point on `this` to `point` and the `parameterValue` at which
17
+ * that point occurs.
18
+ */
19
+ abstract nearestPointTo(point: Point2): {
20
+ point: Point2;
21
+ parameterValue: number;
22
+ };
23
+ /**
24
+ * Returns the **parameter values** at which `lineSegment` intersects this shape.
25
+ *
26
+ * See also {@link intersectsLineSegment}
27
+ */
28
+ abstract argIntersectsLineSegment(lineSegment: LineSegment2): number[];
29
+ intersectsLineSegment(line: LineSegment2): Point2[];
30
+ }
31
+ export default Parameterized2DShape;
@@ -0,0 +1,8 @@
1
+ import Abstract2DShape from './Abstract2DShape.mjs';
2
+ /** A 2-dimensional path with parameter interval $t \in [0, 1]$. */
3
+ export class Parameterized2DShape extends Abstract2DShape {
4
+ intersectsLineSegment(line) {
5
+ return this.argIntersectsLineSegment(line).map(t => this.at(t));
6
+ }
7
+ }
8
+ export default Parameterized2DShape;
@@ -2,7 +2,7 @@ import LineSegment2 from './LineSegment2';
2
2
  import Mat33 from '../Mat33';
3
3
  import Rect2 from './Rect2';
4
4
  import { Point2 } from '../Vec2';
5
- import Abstract2DShape from './Abstract2DShape';
5
+ import Parameterized2DShape from './Parameterized2DShape';
6
6
  export declare enum PathCommandType {
7
7
  LineTo = 0,
8
8
  MoveTo = 1,
@@ -29,12 +29,23 @@ export interface MoveToPathCommand {
29
29
  point: Point2;
30
30
  }
31
31
  export type PathCommand = CubicBezierPathCommand | QuadraticBezierPathCommand | MoveToPathCommand | LinePathCommand;
32
- interface IntersectionResult {
33
- curve: Abstract2DShape;
34
- /** @internal @deprecated */
32
+ export interface IntersectionResult {
33
+ curve: Parameterized2DShape;
34
+ curveIndex: number;
35
+ /** Parameter value for the closest point **on** the path to the intersection. @internal @deprecated */
35
36
  parameterValue?: number;
37
+ /** Point at which the intersection occured. */
36
38
  point: Point2;
37
39
  }
40
+ /**
41
+ * Allows indexing a particular part of a path.
42
+ *
43
+ * @see {@link Path.at} {@link Path.tangentAt}
44
+ */
45
+ export interface CurveIndexRecord {
46
+ curveIndex: number;
47
+ parameterValue: number;
48
+ }
38
49
  /**
39
50
  * Represents a union of lines and curves.
40
51
  */
@@ -57,7 +68,7 @@ export declare class Path {
57
68
  constructor(startPoint: Point2, parts: Readonly<PathCommand>[]);
58
69
  getExactBBox(): Rect2;
59
70
  private cachedGeometry;
60
- get geometry(): Abstract2DShape[];
71
+ get geometry(): Parameterized2DShape[];
61
72
  /**
62
73
  * Iterates through the start/end points of each component in this path.
63
74
  *
@@ -86,10 +97,31 @@ export declare class Path {
86
97
  * **Note**: `strokeRadius` is half of a stroke's width.
87
98
  */
88
99
  intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
100
+ /**
101
+ * @returns the nearest point on this path to the given `point`.
102
+ *
103
+ * @internal
104
+ * @beta
105
+ */
106
+ nearestPointTo(point: Point2): IntersectionResult;
107
+ at(index: CurveIndexRecord): import("../Vec3").Vec3;
108
+ tangentAt(index: CurveIndexRecord): import("../Vec3").Vec3;
89
109
  private static mapPathCommand;
90
110
  mapPoints(mapping: (point: Point2) => Point2): Path;
91
111
  transformedBy(affineTransfm: Mat33): Path;
92
- union(other: Path | null): Path;
112
+ union(other: Path | null, options?: {
113
+ allowReverse?: boolean;
114
+ }): Path;
115
+ /**
116
+ * @returns a version of this path with the direction reversed.
117
+ *
118
+ * Example:
119
+ * ```ts,runnable,console
120
+ * import {Path} from '@js-draw/math';
121
+ * console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
122
+ * ```
123
+ */
124
+ reversed(): Path;
93
125
  private getEndPoint;
94
126
  /**
95
127
  * Like {@link closedRoughlyIntersects} except takes stroke width into account.
@@ -103,6 +135,8 @@ export declare class Path {
103
135
  */
104
136
  roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean;
105
137
  closedRoughlyIntersects(rect: Rect2): boolean;
138
+ /** @returns true if all points on this are equivalent to the points on `other` */
139
+ eq(other: Path, tolerance?: number): boolean;
106
140
  /**
107
141
  * Returns a path that outlines `rect`.
108
142
  *