@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
@@ -35,11 +35,31 @@ export declare class Vec3 {
35
35
  length(): number;
36
36
  magnitude(): number;
37
37
  magnitudeSquared(): number;
38
+ /**
39
+ * Interpreting this vector as a point in ℝ^3, computes the square distance
40
+ * to another point, `p`.
41
+ *
42
+ * Equivalent to `.minus(p).magnitudeSquared()`.
43
+ */
44
+ squareDistanceTo(p: Vec3): number;
45
+ /**
46
+ * Interpreting this vector as a point in ℝ³, returns the distance to the point
47
+ * `p`.
48
+ *
49
+ * Equivalent to `.minus(p).magnitude()`.
50
+ */
51
+ distanceTo(p: Vec3): number;
38
52
  /**
39
53
  * Returns the entry of this with the greatest magnitude.
40
54
  *
41
55
  * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
42
56
  * all entries of this vector.
57
+ *
58
+ * **Example**:
59
+ * ```ts,runnable,console
60
+ * import { Vec3 } from '@js-draw/math';
61
+ * console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
62
+ * ```
43
63
  */
44
64
  maximumEntryMagnitude(): number;
45
65
  /**
@@ -50,6 +70,7 @@ export declare class Vec3 {
50
70
  * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
51
71
  * the resultant angle is in the range $[-\pi, pi]$.
52
72
  *
73
+ * **Example**:
53
74
  * ```ts,runnable,console
54
75
  * import { Vec2 } from '@js-draw/math';
55
76
  * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
package/dist/cjs/Vec3.js CHANGED
@@ -58,11 +58,38 @@ class Vec3 {
58
58
  magnitudeSquared() {
59
59
  return this.dot(this);
60
60
  }
61
+ /**
62
+ * Interpreting this vector as a point in ℝ^3, computes the square distance
63
+ * to another point, `p`.
64
+ *
65
+ * Equivalent to `.minus(p).magnitudeSquared()`.
66
+ */
67
+ squareDistanceTo(p) {
68
+ const dx = this.x - p.x;
69
+ const dy = this.y - p.y;
70
+ const dz = this.z - p.z;
71
+ return dx * dx + dy * dy + dz * dz;
72
+ }
73
+ /**
74
+ * Interpreting this vector as a point in ℝ³, returns the distance to the point
75
+ * `p`.
76
+ *
77
+ * Equivalent to `.minus(p).magnitude()`.
78
+ */
79
+ distanceTo(p) {
80
+ return Math.sqrt(this.squareDistanceTo(p));
81
+ }
61
82
  /**
62
83
  * Returns the entry of this with the greatest magnitude.
63
84
  *
64
85
  * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
65
86
  * all entries of this vector.
87
+ *
88
+ * **Example**:
89
+ * ```ts,runnable,console
90
+ * import { Vec3 } from '@js-draw/math';
91
+ * console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
92
+ * ```
66
93
  */
67
94
  maximumEntryMagnitude() {
68
95
  return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
@@ -75,6 +102,7 @@ class Vec3 {
75
102
  * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
76
103
  * the resultant angle is in the range $[-\pi, pi]$.
77
104
  *
105
+ * **Example**:
78
106
  * ```ts,runnable,console
79
107
  * import { Vec2 } from '@js-draw/math';
80
108
  * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
package/dist/cjs/lib.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * @packageDocumentation
18
18
  */
19
19
  export { LineSegment2 } from './shapes/LineSegment2';
20
- export { Path, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
20
+ export { Path, IntersectionResult as PathIntersectionResult, CurveIndexRecord as PathCurveIndex, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
21
21
  export { Rect2 } from './shapes/Rect2';
22
22
  export { QuadraticBezier } from './shapes/QuadraticBezier';
23
23
  export { Abstract2DShape } from './shapes/Abstract2DShape';
@@ -38,6 +38,9 @@ export declare abstract class Abstract2DShape {
38
38
  containsPoint(point: Point2, epsilon?: number): boolean;
39
39
  /**
40
40
  * Returns a bounding box that precisely fits the content of this shape.
41
+ *
42
+ * **Note**: This bounding box should aligned with the x/y axes. (Thus, it may be
43
+ * possible to find a tighter bounding box not axes-aligned).
41
44
  */
42
45
  abstract getTightBoundingBox(): Rect2;
43
46
  /**
@@ -1,21 +1,22 @@
1
1
  import { Bezier } from 'bezier-js';
2
2
  import { Point2, Vec2 } from '../Vec2';
3
- import Abstract2DShape from './Abstract2DShape';
4
3
  import LineSegment2 from './LineSegment2';
5
4
  import Rect2 from './Rect2';
5
+ import Parameterized2DShape from './Parameterized2DShape';
6
6
  /**
7
7
  * A lazy-initializing wrapper around Bezier-js.
8
8
  *
9
9
  * Subclasses may override `at`, `derivativeAt`, and `normal` with functions
10
10
  * that do not initialize a `bezier-js` `Bezier`.
11
11
  *
12
- * Do not use this class directly. It may be removed/replaced in a future release.
12
+ * **Do not use this class directly.** It may be removed/replaced in a future release.
13
13
  * @internal
14
14
  */
15
- declare abstract class BezierJSWrapper extends Abstract2DShape {
15
+ export declare abstract class BezierJSWrapper extends Parameterized2DShape {
16
16
  #private;
17
+ protected constructor(bezierJsBezier?: Bezier);
17
18
  /** Returns the start, control points, and end point of this Bézier. */
18
- abstract getPoints(): Point2[];
19
+ abstract getPoints(): readonly Point2[];
19
20
  protected getBezier(): Bezier;
20
21
  signedDistance(point: Point2): number;
21
22
  /**
@@ -29,8 +30,17 @@ declare abstract class BezierJSWrapper extends Abstract2DShape {
29
30
  */
30
31
  at(t: number): Point2;
31
32
  derivativeAt(t: number): Point2;
33
+ secondDerivativeAt(t: number): Point2;
32
34
  normal(t: number): Vec2;
35
+ normalAt(t: number): Vec2;
36
+ tangentAt(t: number): Vec2;
33
37
  getTightBoundingBox(): Rect2;
34
- intersectsLineSegment(line: LineSegment2): Point2[];
38
+ argIntersectsLineSegment(line: LineSegment2): number[];
39
+ splitAt(t: number): [BezierJSWrapper] | [BezierJSWrapper, BezierJSWrapper];
40
+ nearestPointTo(point: Point2): {
41
+ parameterValue: number;
42
+ point: import("../Vec3").Vec3;
43
+ };
44
+ toString(): string;
35
45
  }
36
46
  export default BezierJSWrapper;
@@ -1,37 +1,41 @@
1
1
  "use strict";
2
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
- 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");
5
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
- };
7
2
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
8
3
  if (kind === "m") throw new TypeError("Private method is not writable");
9
4
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
10
5
  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");
11
6
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
7
  };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ 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");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
16
  var _BezierJSWrapper_bezierJs;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.BezierJSWrapper = void 0;
18
19
  const bezier_js_1 = require("bezier-js");
19
20
  const Vec2_1 = require("../Vec2");
20
- const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
21
21
  const Rect2_1 = __importDefault(require("./Rect2"));
22
+ const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
22
23
  /**
23
24
  * A lazy-initializing wrapper around Bezier-js.
24
25
  *
25
26
  * Subclasses may override `at`, `derivativeAt`, and `normal` with functions
26
27
  * that do not initialize a `bezier-js` `Bezier`.
27
28
  *
28
- * Do not use this class directly. It may be removed/replaced in a future release.
29
+ * **Do not use this class directly.** It may be removed/replaced in a future release.
29
30
  * @internal
30
31
  */
31
- class BezierJSWrapper extends Abstract2DShape_1.default {
32
- constructor() {
33
- super(...arguments);
32
+ class BezierJSWrapper extends Parameterized2DShape_1.default {
33
+ constructor(bezierJsBezier) {
34
+ super();
34
35
  _BezierJSWrapper_bezierJs.set(this, null);
36
+ if (bezierJsBezier) {
37
+ __classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, bezierJsBezier, "f");
38
+ }
35
39
  }
36
40
  getBezier() {
37
41
  if (!__classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f")) {
@@ -41,7 +45,7 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
41
45
  }
42
46
  signedDistance(point) {
43
47
  // .d: Distance
44
- return this.getBezier().project(point.xy).d;
48
+ return this.nearestPointTo(point).point.distanceTo(point);
45
49
  }
46
50
  /**
47
51
  * @returns the (more) exact distance from `point` to this.
@@ -61,34 +65,147 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
61
65
  derivativeAt(t) {
62
66
  return Vec2_1.Vec2.ofXY(this.getBezier().derivative(t));
63
67
  }
68
+ secondDerivativeAt(t) {
69
+ return Vec2_1.Vec2.ofXY(this.getBezier().dderivative(t));
70
+ }
64
71
  normal(t) {
65
72
  return Vec2_1.Vec2.ofXY(this.getBezier().normal(t));
66
73
  }
74
+ normalAt(t) {
75
+ return this.normal(t);
76
+ }
77
+ tangentAt(t) {
78
+ return this.derivativeAt(t).normalized();
79
+ }
67
80
  getTightBoundingBox() {
68
81
  const bbox = this.getBezier().bbox();
69
82
  const width = bbox.x.max - bbox.x.min;
70
83
  const height = bbox.y.max - bbox.y.min;
71
84
  return new Rect2_1.default(bbox.x.min, bbox.y.min, width, height);
72
85
  }
73
- intersectsLineSegment(line) {
86
+ argIntersectsLineSegment(line) {
74
87
  const bezier = this.getBezier();
75
- const intersectionPoints = bezier.intersects(line).map(t => {
88
+ return bezier.intersects(line).map(t => {
76
89
  // We're using the .intersects(line) function, which is documented
77
90
  // to always return numbers. However, to satisfy the type checker (and
78
91
  // possibly improperly-defined types),
79
92
  if (typeof t === 'string') {
80
93
  t = parseFloat(t);
81
94
  }
82
- const point = Vec2_1.Vec2.ofXY(bezier.get(t));
95
+ const point = Vec2_1.Vec2.ofXY(this.at(t));
83
96
  // Ensure that the intersection is on the line segment
84
- if (point.minus(line.p1).magnitude() > line.length
85
- || point.minus(line.p2).magnitude() > line.length) {
97
+ if (point.distanceTo(line.p1) > line.length
98
+ || point.distanceTo(line.p2) > line.length) {
86
99
  return null;
87
100
  }
88
- return point;
101
+ return t;
89
102
  }).filter(entry => entry !== null);
90
- return intersectionPoints;
103
+ }
104
+ splitAt(t) {
105
+ if (t <= 0 || t >= 1) {
106
+ return [this];
107
+ }
108
+ const bezier = this.getBezier();
109
+ const split = bezier.split(t);
110
+ return [
111
+ new BezierJSWrapperImpl(split.left.points.map(point => Vec2_1.Vec2.ofXY(point)), split.left),
112
+ new BezierJSWrapperImpl(split.right.points.map(point => Vec2_1.Vec2.ofXY(point)), split.right),
113
+ ];
114
+ }
115
+ nearestPointTo(point) {
116
+ // One implementation could be similar to this:
117
+ // const projection = this.getBezier().project(point);
118
+ // return {
119
+ // point: Vec2.ofXY(projection),
120
+ // parameterValue: projection.t!,
121
+ // };
122
+ // However, Bezier-js is rather impercise (and relies on a lookup table).
123
+ // Thus, we instead use Newton's Method:
124
+ // We want to find t such that f(t) = |B(t) - p|² is minimized.
125
+ // Expanding,
126
+ // f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
127
+ // ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
128
+ // ⇒ f'(t) = 2(Bₓ(t) - pₓ)(Bₓ'(t)) + 2(Bᵧ(t) - pᵧ)(Bᵧ'(t))
129
+ // = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
130
+ // ⇒ f''(t)= 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t) + 2Bᵧ'(t)Bᵧ'(t)
131
+ // + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
132
+ // Because f'(t) = 0 at relative extrema, we can use Newton's Method
133
+ // to improve on an initial guess.
134
+ const sqrDistAt = (t) => point.squareDistanceTo(this.at(t));
135
+ const yIntercept = sqrDistAt(0);
136
+ let t = 0;
137
+ let minSqrDist = yIntercept;
138
+ // Start by testing a few points:
139
+ const pointsToTest = 4;
140
+ for (let i = 0; i < pointsToTest; i++) {
141
+ const testT = i / (pointsToTest - 1);
142
+ const testMinSqrDist = sqrDistAt(testT);
143
+ if (testMinSqrDist < minSqrDist) {
144
+ t = testT;
145
+ minSqrDist = testMinSqrDist;
146
+ }
147
+ }
148
+ // To use Newton's Method, we need to evaluate the second derivative of the distance
149
+ // function:
150
+ const secondDerivativeAt = (t) => {
151
+ // f''(t) = 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t)
152
+ // + 2Bᵧ'(t)Bᵧ'(t) + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
153
+ const b = this.at(t);
154
+ const bPrime = this.derivativeAt(t);
155
+ const bPrimePrime = this.secondDerivativeAt(t);
156
+ return (2 * bPrime.x * bPrime.x + 2 * b.x * bPrimePrime.x - 2 * point.x * bPrimePrime.x
157
+ + 2 * bPrime.y * bPrime.y + 2 * b.y * bPrimePrime.y - 2 * point.y * bPrimePrime.y);
158
+ };
159
+ // Because we're zeroing f'(t), we also need to be able to compute it:
160
+ const derivativeAt = (t) => {
161
+ // f'(t) = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
162
+ const b = this.at(t);
163
+ const bPrime = this.derivativeAt(t);
164
+ return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x
165
+ + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
166
+ };
167
+ const iterate = () => {
168
+ const slope = secondDerivativeAt(t);
169
+ // We intersect a line through the point on f'(t) at t with the x-axis:
170
+ // y = m(x - x₀) + y₀
171
+ // ⇒ x - x₀ = (y - y₀) / m
172
+ // ⇒ x = (y - y₀) / m + x₀
173
+ //
174
+ // Thus, when zeroed,
175
+ // tN = (0 - f'(t)) / m + t
176
+ const newT = (0 - derivativeAt(t)) / slope + t;
177
+ //const distDiff = sqrDistAt(newT) - sqrDistAt(t);
178
+ //console.assert(distDiff <= 0, `${-distDiff} >= 0`);
179
+ t = newT;
180
+ if (t > 1) {
181
+ t = 1;
182
+ }
183
+ else if (t < 0) {
184
+ t = 0;
185
+ }
186
+ };
187
+ for (let i = 0; i < 12; i++) {
188
+ iterate();
189
+ }
190
+ return { parameterValue: t, point: this.at(t) };
191
+ }
192
+ toString() {
193
+ return `Bézier(${this.getPoints().map(point => point.toString()).join(', ')})`;
91
194
  }
92
195
  }
196
+ exports.BezierJSWrapper = BezierJSWrapper;
93
197
  _BezierJSWrapper_bezierJs = new WeakMap();
198
+ /**
199
+ * Private concrete implementation of `BezierJSWrapper`, used by methods above that need to return a wrapper
200
+ * around a `Bezier`.
201
+ */
202
+ class BezierJSWrapperImpl extends BezierJSWrapper {
203
+ constructor(controlPoints, curve) {
204
+ super(curve);
205
+ this.controlPoints = controlPoints;
206
+ }
207
+ getPoints() {
208
+ return this.controlPoints;
209
+ }
210
+ }
94
211
  exports.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;
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.LineSegment2 = void 0;
7
7
  const Rect2_1 = __importDefault(require("./Rect2"));
8
8
  const Vec2_1 = require("../Vec2");
9
- const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
9
+ const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
10
10
  /** Represents a line segment. A `LineSegment2` is immutable. */
11
- class LineSegment2 extends Abstract2DShape_1.default {
11
+ class LineSegment2 extends Parameterized2DShape_1.default {
12
12
  /** Creates a new `LineSegment2` from its endpoints. */
13
13
  constructor(point1, point2) {
14
14
  super();
@@ -32,8 +32,11 @@ class LineSegment2 extends Abstract2DShape_1.default {
32
32
  get p2() {
33
33
  return this.point2;
34
34
  }
35
+ get center() {
36
+ return this.point1.lerp(this.point2, 0.5);
37
+ }
35
38
  /**
36
- * Gets a point a distance `t` along this line.
39
+ * Gets a point a **distance** `t` along this line.
37
40
  *
38
41
  * @deprecated
39
42
  */
@@ -50,7 +53,31 @@ class LineSegment2 extends Abstract2DShape_1.default {
50
53
  at(t) {
51
54
  return this.get(t * this.length);
52
55
  }
56
+ normalAt(_t) {
57
+ return this.direction.orthog();
58
+ }
59
+ tangentAt(_t) {
60
+ return this.direction;
61
+ }
62
+ splitAt(t) {
63
+ if (t <= 0 || t >= 1) {
64
+ return [this];
65
+ }
66
+ return [
67
+ new LineSegment2(this.point1, this.at(t)),
68
+ new LineSegment2(this.at(t), this.point2),
69
+ ];
70
+ }
71
+ /**
72
+ * Returns the intersection of this with another line segment.
73
+ *
74
+ * **WARNING**: The parameter value returned by this method does not range from 0 to 1 and
75
+ * is currently a length.
76
+ * This will change in a future release.
77
+ * @deprecated
78
+ */
53
79
  intersection(other) {
80
+ // TODO(v2.0.0): Make this return a `t` value from `0` to `1`.
54
81
  // We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
55
82
  // Observe that
56
83
  // x = this.point1.x + this.direction.x · t₁
@@ -109,10 +136,10 @@ class LineSegment2 extends Abstract2DShape_1.default {
109
136
  resultT = (xIntersect - this.point1.x) / this.direction.x;
110
137
  }
111
138
  // Ensure the result is in this/the other segment.
112
- const resultToP1 = resultPoint.minus(this.point1).magnitude();
113
- const resultToP2 = resultPoint.minus(this.point2).magnitude();
114
- const resultToP3 = resultPoint.minus(other.point1).magnitude();
115
- const resultToP4 = resultPoint.minus(other.point2).magnitude();
139
+ const resultToP1 = resultPoint.distanceTo(this.point1);
140
+ const resultToP2 = resultPoint.distanceTo(this.point2);
141
+ const resultToP3 = resultPoint.distanceTo(other.point1);
142
+ const resultToP4 = resultPoint.distanceTo(other.point2);
116
143
  if (resultToP1 > this.length
117
144
  || resultToP2 > this.length
118
145
  || resultToP3 > other.length
@@ -127,6 +154,13 @@ class LineSegment2 extends Abstract2DShape_1.default {
127
154
  intersects(other) {
128
155
  return this.intersection(other) !== null;
129
156
  }
157
+ argIntersectsLineSegment(lineSegment) {
158
+ const intersection = this.intersection(lineSegment);
159
+ if (intersection) {
160
+ return [intersection.t / this.length];
161
+ }
162
+ return [];
163
+ }
130
164
  /**
131
165
  * Returns the points at which this line segment intersects the
132
166
  * given line segment.
@@ -144,18 +178,21 @@ class LineSegment2 extends Abstract2DShape_1.default {
144
178
  }
145
179
  // Returns the closest point on this to [target]
146
180
  closestPointTo(target) {
181
+ return this.nearestPointTo(target).point;
182
+ }
183
+ nearestPointTo(target) {
147
184
  // Distance from P1 along this' direction.
148
185
  const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
149
186
  const projectedDistFromP2 = this.length - projectedDistFromP1;
150
187
  const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
151
188
  if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
152
- return projection;
189
+ return { point: projection, parameterValue: projectedDistFromP1 / this.length };
153
190
  }
154
191
  if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
155
- return this.p2;
192
+ return { point: this.p2, parameterValue: 1 };
156
193
  }
157
194
  else {
158
- return this.p1;
195
+ return { point: this.p1, parameterValue: 0 };
159
196
  }
160
197
  }
161
198
  /**
@@ -178,6 +215,22 @@ class LineSegment2 extends Abstract2DShape_1.default {
178
215
  toString() {
179
216
  return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
180
217
  }
218
+ /**
219
+ * Returns `true` iff this is equivalent to `other`.
220
+ *
221
+ * **Options**:
222
+ * - `tolerance`: The maximum difference between endpoints. (Default: 0)
223
+ * - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
224
+ */
225
+ eq(other, options) {
226
+ if (!(other instanceof LineSegment2)) {
227
+ return false;
228
+ }
229
+ const tolerance = options?.tolerance;
230
+ const ignoreDirection = options?.ignoreDirection ?? true;
231
+ return ((other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance))
232
+ || (ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance)));
233
+ }
181
234
  }
182
235
  exports.LineSegment2 = LineSegment2;
183
236
  exports.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,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Parameterized2DShape = void 0;
7
+ const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
8
+ /** A 2-dimensional path with parameter interval $t \in [0, 1]$. */
9
+ class Parameterized2DShape extends Abstract2DShape_1.default {
10
+ intersectsLineSegment(line) {
11
+ return this.argIntersectsLineSegment(line).map(t => this.at(t));
12
+ }
13
+ }
14
+ exports.Parameterized2DShape = Parameterized2DShape;
15
+ exports.default = Parameterized2DShape;