@js-draw/math 1.16.0 → 1.18.0

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