@js-draw/math 1.16.0 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
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;