@js-draw/math 1.16.0 → 1.18.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 (64) hide show
  1. package/dist/cjs/Mat33.js +6 -1
  2. package/dist/cjs/Vec3.d.ts +23 -1
  3. package/dist/cjs/Vec3.js +33 -7
  4. package/dist/cjs/lib.d.ts +2 -1
  5. package/dist/cjs/lib.js +5 -1
  6. package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
  7. package/dist/cjs/shapes/BezierJSWrapper.d.ts +19 -5
  8. package/dist/cjs/shapes/BezierJSWrapper.js +170 -18
  9. package/dist/cjs/shapes/LineSegment2.d.ts +45 -5
  10. package/dist/cjs/shapes/LineSegment2.js +89 -11
  11. package/dist/cjs/shapes/Parameterized2DShape.d.ts +36 -0
  12. package/dist/cjs/shapes/Parameterized2DShape.js +20 -0
  13. package/dist/cjs/shapes/Path.d.ts +131 -13
  14. package/dist/cjs/shapes/Path.js +507 -26
  15. package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
  16. package/dist/cjs/shapes/PointShape2D.js +28 -5
  17. package/dist/cjs/shapes/QuadraticBezier.d.ts +6 -3
  18. package/dist/cjs/shapes/QuadraticBezier.js +21 -7
  19. package/dist/cjs/shapes/Rect2.d.ts +9 -1
  20. package/dist/cjs/shapes/Rect2.js +9 -2
  21. package/dist/cjs/utils/convexHull2Of.d.ts +9 -0
  22. package/dist/cjs/utils/convexHull2Of.js +61 -0
  23. package/dist/cjs/utils/convexHull2Of.test.d.ts +1 -0
  24. package/dist/mjs/Mat33.mjs +6 -1
  25. package/dist/mjs/Vec3.d.ts +23 -1
  26. package/dist/mjs/Vec3.mjs +33 -7
  27. package/dist/mjs/lib.d.ts +2 -1
  28. package/dist/mjs/lib.mjs +2 -1
  29. package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
  30. package/dist/mjs/shapes/BezierJSWrapper.d.ts +19 -5
  31. package/dist/mjs/shapes/BezierJSWrapper.mjs +168 -18
  32. package/dist/mjs/shapes/LineSegment2.d.ts +45 -5
  33. package/dist/mjs/shapes/LineSegment2.mjs +89 -11
  34. package/dist/mjs/shapes/Parameterized2DShape.d.ts +36 -0
  35. package/dist/mjs/shapes/Parameterized2DShape.mjs +13 -0
  36. package/dist/mjs/shapes/Path.d.ts +131 -13
  37. package/dist/mjs/shapes/Path.mjs +504 -25
  38. package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
  39. package/dist/mjs/shapes/PointShape2D.mjs +28 -5
  40. package/dist/mjs/shapes/QuadraticBezier.d.ts +6 -3
  41. package/dist/mjs/shapes/QuadraticBezier.mjs +21 -7
  42. package/dist/mjs/shapes/Rect2.d.ts +9 -1
  43. package/dist/mjs/shapes/Rect2.mjs +9 -2
  44. package/dist/mjs/utils/convexHull2Of.d.ts +9 -0
  45. package/dist/mjs/utils/convexHull2Of.mjs +59 -0
  46. package/dist/mjs/utils/convexHull2Of.test.d.ts +1 -0
  47. package/package.json +5 -5
  48. package/src/Mat33.ts +8 -2
  49. package/src/Vec3.test.ts +42 -7
  50. package/src/Vec3.ts +37 -8
  51. package/src/lib.ts +5 -0
  52. package/src/shapes/Abstract2DShape.ts +3 -0
  53. package/src/shapes/BezierJSWrapper.ts +195 -14
  54. package/src/shapes/LineSegment2.test.ts +61 -1
  55. package/src/shapes/LineSegment2.ts +110 -12
  56. package/src/shapes/Parameterized2DShape.ts +44 -0
  57. package/src/shapes/Path.test.ts +233 -5
  58. package/src/shapes/Path.ts +593 -37
  59. package/src/shapes/PointShape2D.ts +33 -6
  60. package/src/shapes/QuadraticBezier.test.ts +69 -12
  61. package/src/shapes/QuadraticBezier.ts +25 -8
  62. package/src/shapes/Rect2.ts +10 -3
  63. package/src/utils/convexHull2Of.test.ts +43 -0
  64. package/src/utils/convexHull2Of.ts +71 -0
package/dist/cjs/Mat33.js CHANGED
@@ -340,7 +340,11 @@ class Mat33 {
340
340
  return Mat33.identity;
341
341
  }
342
342
  const parseArguments = (argumentString) => {
343
- return argumentString.split(/[, \t\n]+/g).map(argString => {
343
+ const parsed = argumentString.split(/[, \t\n]+/g).map(argString => {
344
+ // Handle trailing spaces/commands
345
+ if (argString.trim() === '') {
346
+ return null;
347
+ }
344
348
  let isPercentage = false;
345
349
  if (argString.endsWith('%')) {
346
350
  isPercentage = true;
@@ -361,6 +365,7 @@ class Mat33 {
361
365
  }
362
366
  return argNumber;
363
367
  });
368
+ return parsed.filter(n => n !== null);
364
369
  };
365
370
  const keywordToAction = {
366
371
  matrix: (matrixData) => {
@@ -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)
@@ -113,7 +134,8 @@ export declare class Vec3 {
113
134
  * Returns a vector with each component acted on by `fn`.
114
135
  *
115
136
  * @example
116
- * ```
137
+ * ```ts,runnable,console
138
+ * import { Vec3 } from '@js-draw/math';
117
139
  * console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
118
140
  * ```
119
141
  */
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)
@@ -178,7 +206,8 @@ class Vec3 {
178
206
  * Returns a vector with each component acted on by `fn`.
179
207
  *
180
208
  * @example
181
- * ```
209
+ * ```ts,runnable,console
210
+ * import { Vec3 } from '@js-draw/math';
182
211
  * console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
183
212
  * ```
184
213
  */
@@ -202,12 +231,9 @@ class Vec3 {
202
231
  * ```
203
232
  */
204
233
  eq(other, fuzz = 1e-10) {
205
- for (let i = 0; i < 3; i++) {
206
- if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
207
- return false;
208
- }
209
- }
210
- return true;
234
+ return (Math.abs(other.x - this.x) <= fuzz
235
+ && Math.abs(other.y - this.y) <= fuzz
236
+ && Math.abs(other.z - this.z) <= fuzz);
211
237
  }
212
238
  toString() {
213
239
  return `Vec(${this.x}, ${this.y}, ${this.z})`;
package/dist/cjs/lib.d.ts CHANGED
@@ -17,8 +17,9 @@
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, stepCurveIndexBy as stepPathIndexBy, compareCurveIndices as comparePathIndices, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
21
21
  export { Rect2 } from './shapes/Rect2';
22
+ export { Parameterized2DShape } from './shapes/Parameterized2DShape';
22
23
  export { QuadraticBezier } from './shapes/QuadraticBezier';
23
24
  export { Abstract2DShape } from './shapes/Abstract2DShape';
24
25
  export { Mat33, Mat33Array } from './Mat33';
package/dist/cjs/lib.js CHANGED
@@ -32,14 +32,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
32
32
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
35
+ exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Parameterized2DShape = exports.Rect2 = exports.PathCommandType = exports.comparePathIndices = exports.stepPathIndexBy = exports.Path = exports.LineSegment2 = void 0;
36
36
  var LineSegment2_1 = require("./shapes/LineSegment2");
37
37
  Object.defineProperty(exports, "LineSegment2", { enumerable: true, get: function () { return LineSegment2_1.LineSegment2; } });
38
38
  var Path_1 = require("./shapes/Path");
39
39
  Object.defineProperty(exports, "Path", { enumerable: true, get: function () { return Path_1.Path; } });
40
+ Object.defineProperty(exports, "stepPathIndexBy", { enumerable: true, get: function () { return Path_1.stepCurveIndexBy; } });
41
+ Object.defineProperty(exports, "comparePathIndices", { enumerable: true, get: function () { return Path_1.compareCurveIndices; } });
40
42
  Object.defineProperty(exports, "PathCommandType", { enumerable: true, get: function () { return Path_1.PathCommandType; } });
41
43
  var Rect2_1 = require("./shapes/Rect2");
42
44
  Object.defineProperty(exports, "Rect2", { enumerable: true, get: function () { return Rect2_1.Rect2; } });
45
+ var Parameterized2DShape_1 = require("./shapes/Parameterized2DShape");
46
+ Object.defineProperty(exports, "Parameterized2DShape", { enumerable: true, get: function () { return Parameterized2DShape_1.Parameterized2DShape; } });
43
47
  var QuadraticBezier_1 = require("./shapes/QuadraticBezier");
44
48
  Object.defineProperty(exports, "QuadraticBezier", { enumerable: true, get: function () { return QuadraticBezier_1.QuadraticBezier; } });
45
49
  var Abstract2DShape_1 = require("./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,21 @@ 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
+ intersectsBezier(other: BezierJSWrapper): {
45
+ parameterValue: number;
46
+ point: import("../Vec3").Vec3;
47
+ }[];
48
+ toString(): string;
35
49
  }
36
50
  export default BezierJSWrapper;
@@ -1,37 +1,42 @@
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
+ const LineSegment2_1 = __importDefault(require("./LineSegment2"));
21
22
  const Rect2_1 = __importDefault(require("./Rect2"));
23
+ const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
22
24
  /**
23
25
  * A lazy-initializing wrapper around Bezier-js.
24
26
  *
25
27
  * Subclasses may override `at`, `derivativeAt`, and `normal` with functions
26
28
  * that do not initialize a `bezier-js` `Bezier`.
27
29
  *
28
- * Do not use this class directly. It may be removed/replaced in a future release.
30
+ * **Do not use this class directly.** It may be removed/replaced in a future release.
29
31
  * @internal
30
32
  */
31
- class BezierJSWrapper extends Abstract2DShape_1.default {
32
- constructor() {
33
- super(...arguments);
33
+ class BezierJSWrapper extends Parameterized2DShape_1.default {
34
+ constructor(bezierJsBezier) {
35
+ super();
34
36
  _BezierJSWrapper_bezierJs.set(this, null);
37
+ if (bezierJsBezier) {
38
+ __classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, bezierJsBezier, "f");
39
+ }
35
40
  }
36
41
  getBezier() {
37
42
  if (!__classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f")) {
@@ -41,7 +46,7 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
41
46
  }
42
47
  signedDistance(point) {
43
48
  // .d: Distance
44
- return this.getBezier().project(point.xy).d;
49
+ return this.nearestPointTo(point).point.distanceTo(point);
45
50
  }
46
51
  /**
47
52
  * @returns the (more) exact distance from `point` to this.
@@ -61,34 +66,181 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
61
66
  derivativeAt(t) {
62
67
  return Vec2_1.Vec2.ofXY(this.getBezier().derivative(t));
63
68
  }
69
+ secondDerivativeAt(t) {
70
+ return Vec2_1.Vec2.ofXY(this.getBezier().dderivative(t));
71
+ }
64
72
  normal(t) {
65
73
  return Vec2_1.Vec2.ofXY(this.getBezier().normal(t));
66
74
  }
75
+ normalAt(t) {
76
+ return this.normal(t);
77
+ }
78
+ tangentAt(t) {
79
+ return this.derivativeAt(t).normalized();
80
+ }
67
81
  getTightBoundingBox() {
68
82
  const bbox = this.getBezier().bbox();
69
83
  const width = bbox.x.max - bbox.x.min;
70
84
  const height = bbox.y.max - bbox.y.min;
71
85
  return new Rect2_1.default(bbox.x.min, bbox.y.min, width, height);
72
86
  }
73
- intersectsLineSegment(line) {
87
+ argIntersectsLineSegment(line) {
88
+ // Bezier-js has a bug when all control points of a Bezier curve lie on
89
+ // a line. Our solution involves converting the Bezier into a line, then
90
+ // finding the parameter value that produced the intersection.
91
+ //
92
+ // TODO: This is unnecessarily slow. A better solution would be to fix
93
+ // the bug upstream.
94
+ const asLine = LineSegment2_1.default.ofSmallestContainingPoints(this.getPoints());
95
+ if (asLine) {
96
+ const intersection = asLine.intersectsLineSegment(line);
97
+ return intersection.map(p => this.nearestPointTo(p).parameterValue);
98
+ }
74
99
  const bezier = this.getBezier();
75
- const intersectionPoints = bezier.intersects(line).map(t => {
100
+ return bezier.intersects(line).map(t => {
76
101
  // We're using the .intersects(line) function, which is documented
77
102
  // to always return numbers. However, to satisfy the type checker (and
78
103
  // possibly improperly-defined types),
79
104
  if (typeof t === 'string') {
80
105
  t = parseFloat(t);
81
106
  }
82
- const point = Vec2_1.Vec2.ofXY(bezier.get(t));
107
+ const point = Vec2_1.Vec2.ofXY(this.at(t));
83
108
  // 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) {
109
+ if (point.distanceTo(line.p1) > line.length
110
+ || point.distanceTo(line.p2) > line.length) {
86
111
  return null;
87
112
  }
88
- return point;
113
+ return t;
89
114
  }).filter(entry => entry !== null);
90
- return intersectionPoints;
115
+ }
116
+ splitAt(t) {
117
+ if (t <= 0 || t >= 1) {
118
+ return [this];
119
+ }
120
+ const bezier = this.getBezier();
121
+ const split = bezier.split(t);
122
+ return [
123
+ new BezierJSWrapperImpl(split.left.points.map(point => Vec2_1.Vec2.ofXY(point)), split.left),
124
+ new BezierJSWrapperImpl(split.right.points.map(point => Vec2_1.Vec2.ofXY(point)), split.right),
125
+ ];
126
+ }
127
+ nearestPointTo(point) {
128
+ // One implementation could be similar to this:
129
+ // const projection = this.getBezier().project(point);
130
+ // return {
131
+ // point: Vec2.ofXY(projection),
132
+ // parameterValue: projection.t!,
133
+ // };
134
+ // However, Bezier-js is rather impercise (and relies on a lookup table).
135
+ // Thus, we instead use Newton's Method:
136
+ // We want to find t such that f(t) = |B(t) - p|² is minimized.
137
+ // Expanding,
138
+ // f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
139
+ // ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
140
+ // ⇒ f'(t) = 2(Bₓ(t) - pₓ)(Bₓ'(t)) + 2(Bᵧ(t) - pᵧ)(Bᵧ'(t))
141
+ // = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
142
+ // ⇒ f''(t)= 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t) + 2Bᵧ'(t)Bᵧ'(t)
143
+ // + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
144
+ // Because f'(t) = 0 at relative extrema, we can use Newton's Method
145
+ // to improve on an initial guess.
146
+ const sqrDistAt = (t) => point.squareDistanceTo(this.at(t));
147
+ const yIntercept = sqrDistAt(0);
148
+ let t = 0;
149
+ let minSqrDist = yIntercept;
150
+ // Start by testing a few points:
151
+ const pointsToTest = 4;
152
+ for (let i = 0; i < pointsToTest; i++) {
153
+ const testT = i / (pointsToTest - 1);
154
+ const testMinSqrDist = sqrDistAt(testT);
155
+ if (testMinSqrDist < minSqrDist) {
156
+ t = testT;
157
+ minSqrDist = testMinSqrDist;
158
+ }
159
+ }
160
+ // To use Newton's Method, we need to evaluate the second derivative of the distance
161
+ // function:
162
+ const secondDerivativeAt = (t) => {
163
+ // f''(t) = 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t)
164
+ // + 2Bᵧ'(t)Bᵧ'(t) + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
165
+ const b = this.at(t);
166
+ const bPrime = this.derivativeAt(t);
167
+ const bPrimePrime = this.secondDerivativeAt(t);
168
+ return (2 * bPrime.x * bPrime.x + 2 * b.x * bPrimePrime.x - 2 * point.x * bPrimePrime.x
169
+ + 2 * bPrime.y * bPrime.y + 2 * b.y * bPrimePrime.y - 2 * point.y * bPrimePrime.y);
170
+ };
171
+ // Because we're zeroing f'(t), we also need to be able to compute it:
172
+ const derivativeAt = (t) => {
173
+ // f'(t) = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
174
+ const b = this.at(t);
175
+ const bPrime = this.derivativeAt(t);
176
+ return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x
177
+ + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
178
+ };
179
+ const iterate = () => {
180
+ const slope = secondDerivativeAt(t);
181
+ if (slope === 0)
182
+ return;
183
+ // We intersect a line through the point on f'(t) at t with the x-axis:
184
+ // y = m(x - x₀) + y₀
185
+ // ⇒ x - x₀ = (y - y₀) / m
186
+ // ⇒ x = (y - y₀) / m + x₀
187
+ //
188
+ // Thus, when zeroed,
189
+ // tN = (0 - f'(t)) / m + t
190
+ const newT = (0 - derivativeAt(t)) / slope + t;
191
+ //const distDiff = sqrDistAt(newT) - sqrDistAt(t);
192
+ //console.assert(distDiff <= 0, `${-distDiff} >= 0`);
193
+ t = newT;
194
+ if (t > 1) {
195
+ t = 1;
196
+ }
197
+ else if (t < 0) {
198
+ t = 0;
199
+ }
200
+ };
201
+ for (let i = 0; i < 12; i++) {
202
+ iterate();
203
+ }
204
+ return { parameterValue: t, point: this.at(t) };
205
+ }
206
+ intersectsBezier(other) {
207
+ const intersections = this.getBezier().intersects(other.getBezier());
208
+ if (!intersections || intersections.length === 0) {
209
+ return [];
210
+ }
211
+ const result = [];
212
+ for (const intersection of intersections) {
213
+ // From http://pomax.github.io/bezierjs/#intersect-curve,
214
+ // .intersects returns an array of 't1/t2' pairs, where curve1.at(t1) gives the point.
215
+ const match = /^([-0-9.eE]+)\/([-0-9.eE]+)$/.exec(intersection);
216
+ if (!match) {
217
+ throw new Error(`Incorrect format returned by .intersects: ${intersections} should be array of "number/number"!`);
218
+ }
219
+ const t = parseFloat(match[1]);
220
+ result.push({
221
+ parameterValue: t,
222
+ point: this.at(t),
223
+ });
224
+ }
225
+ return result;
226
+ }
227
+ toString() {
228
+ return `Bézier(${this.getPoints().map(point => point.toString()).join(', ')})`;
91
229
  }
92
230
  }
231
+ exports.BezierJSWrapper = BezierJSWrapper;
93
232
  _BezierJSWrapper_bezierJs = new WeakMap();
233
+ /**
234
+ * Private concrete implementation of `BezierJSWrapper`, used by methods above that need to return a wrapper
235
+ * around a `Bezier`.
236
+ */
237
+ class BezierJSWrapperImpl extends BezierJSWrapper {
238
+ constructor(controlPoints, curve) {
239
+ super(curve);
240
+ this.controlPoints = controlPoints;
241
+ }
242
+ getPoints() {
243
+ return this.controlPoints;
244
+ }
245
+ }
94
246
  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
  /**
@@ -24,12 +25,24 @@ export declare class LineSegment2 extends Abstract2DShape {
24
25
  readonly bbox: Rect2;
25
26
  /** Creates a new `LineSegment2` from its endpoints. */
26
27
  constructor(point1: Point2, point2: Point2);
28
+ /**
29
+ * Returns the smallest line segment that contains all points in `points`, or `null`
30
+ * if no such line segment exists.
31
+ *
32
+ * @example
33
+ * ```ts,runnable
34
+ * import {LineSegment2, Vec2} from '@js-draw/math';
35
+ * console.log(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 0), Vec2.of(0, 1)]));
36
+ * ```
37
+ */
38
+ static ofSmallestContainingPoints(points: readonly Point2[]): LineSegment2 | null;
27
39
  /** Alias for `point1`. */
28
40
  get p1(): Point2;
29
41
  /** Alias for `point2`. */
30
42
  get p2(): Point2;
43
+ get center(): Point2;
31
44
  /**
32
- * Gets a point a distance `t` along this line.
45
+ * Gets a point a **distance** `t` along this line.
33
46
  *
34
47
  * @deprecated
35
48
  */
@@ -42,8 +55,20 @@ export declare class LineSegment2 extends Abstract2DShape {
42
55
  * `t` should be in `[0, 1]`.
43
56
  */
44
57
  at(t: number): Point2;
58
+ normalAt(_t: number): Vec2;
59
+ tangentAt(_t: number): Vec3;
60
+ splitAt(t: number): [LineSegment2] | [LineSegment2, LineSegment2];
61
+ /**
62
+ * Returns the intersection of this with another line segment.
63
+ *
64
+ * **WARNING**: The parameter value returned by this method does not range from 0 to 1 and
65
+ * is currently a length.
66
+ * This will change in a future release.
67
+ * @deprecated
68
+ */
45
69
  intersection(other: LineSegment2): IntersectionResult | null;
46
70
  intersects(other: LineSegment2): boolean;
71
+ argIntersectsLineSegment(lineSegment: LineSegment2): number[];
47
72
  /**
48
73
  * Returns the points at which this line segment intersects the
49
74
  * given line segment.
@@ -52,8 +77,12 @@ export declare class LineSegment2 extends Abstract2DShape {
52
77
  * line segment. This method, by contrast, returns **the point** at which the intersection
53
78
  * occurs, if such a point exists.
54
79
  */
55
- intersectsLineSegment(lineSegment: LineSegment2): import("../Vec3").Vec3[];
56
- closestPointTo(target: Point2): import("../Vec3").Vec3;
80
+ intersectsLineSegment(lineSegment: LineSegment2): Vec3[];
81
+ closestPointTo(target: Point2): Vec3;
82
+ nearestPointTo(target: Vec3): {
83
+ point: Vec3;
84
+ parameterValue: number;
85
+ };
57
86
  /**
58
87
  * Returns the distance from this line segment to `target`.
59
88
  *
@@ -66,5 +95,16 @@ export declare class LineSegment2 extends Abstract2DShape {
66
95
  /** @inheritdoc */
67
96
  getTightBoundingBox(): Rect2;
68
97
  toString(): string;
98
+ /**
99
+ * Returns `true` iff this is equivalent to `other`.
100
+ *
101
+ * **Options**:
102
+ * - `tolerance`: The maximum difference between endpoints. (Default: 0)
103
+ * - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
104
+ */
105
+ eq(other: LineSegment2, options?: {
106
+ tolerance?: number;
107
+ ignoreDirection?: boolean;
108
+ }): boolean;
69
109
  }
70
110
  export default LineSegment2;