@js-draw/math 1.16.0 → 1.17.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 (51) 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 +1 -1
  4. package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
  5. package/dist/cjs/shapes/BezierJSWrapper.d.ts +15 -5
  6. package/dist/cjs/shapes/BezierJSWrapper.js +135 -18
  7. package/dist/cjs/shapes/LineSegment2.d.ts +34 -5
  8. package/dist/cjs/shapes/LineSegment2.js +63 -10
  9. package/dist/cjs/shapes/Parameterized2DShape.d.ts +31 -0
  10. package/dist/cjs/shapes/Parameterized2DShape.js +15 -0
  11. package/dist/cjs/shapes/Path.d.ts +40 -6
  12. package/dist/cjs/shapes/Path.js +173 -15
  13. package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
  14. package/dist/cjs/shapes/PointShape2D.js +28 -5
  15. package/dist/cjs/shapes/QuadraticBezier.d.ts +4 -0
  16. package/dist/cjs/shapes/QuadraticBezier.js +19 -4
  17. package/dist/cjs/shapes/Rect2.d.ts +3 -0
  18. package/dist/cjs/shapes/Rect2.js +4 -1
  19. package/dist/mjs/Vec3.d.ts +21 -0
  20. package/dist/mjs/Vec3.mjs +28 -0
  21. package/dist/mjs/lib.d.ts +1 -1
  22. package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
  23. package/dist/mjs/shapes/BezierJSWrapper.d.ts +15 -5
  24. package/dist/mjs/shapes/BezierJSWrapper.mjs +133 -18
  25. package/dist/mjs/shapes/LineSegment2.d.ts +34 -5
  26. package/dist/mjs/shapes/LineSegment2.mjs +63 -10
  27. package/dist/mjs/shapes/Parameterized2DShape.d.ts +31 -0
  28. package/dist/mjs/shapes/Parameterized2DShape.mjs +8 -0
  29. package/dist/mjs/shapes/Path.d.ts +40 -6
  30. package/dist/mjs/shapes/Path.mjs +173 -15
  31. package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
  32. package/dist/mjs/shapes/PointShape2D.mjs +28 -5
  33. package/dist/mjs/shapes/QuadraticBezier.d.ts +4 -0
  34. package/dist/mjs/shapes/QuadraticBezier.mjs +19 -4
  35. package/dist/mjs/shapes/Rect2.d.ts +3 -0
  36. package/dist/mjs/shapes/Rect2.mjs +4 -1
  37. package/package.json +5 -5
  38. package/src/Vec3.test.ts +26 -7
  39. package/src/Vec3.ts +30 -0
  40. package/src/lib.ts +2 -0
  41. package/src/shapes/Abstract2DShape.ts +3 -0
  42. package/src/shapes/BezierJSWrapper.ts +154 -14
  43. package/src/shapes/LineSegment2.test.ts +35 -1
  44. package/src/shapes/LineSegment2.ts +79 -11
  45. package/src/shapes/Parameterized2DShape.ts +39 -0
  46. package/src/shapes/Path.test.ts +63 -3
  47. package/src/shapes/Path.ts +209 -25
  48. package/src/shapes/PointShape2D.ts +33 -6
  49. package/src/shapes/QuadraticBezier.test.ts +48 -12
  50. package/src/shapes/QuadraticBezier.ts +23 -5
  51. package/src/shapes/Rect2.ts +4 -1
@@ -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
  *