@js-draw/math 1.8.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Henry Heino
3
+ Copyright (c) 2023 Henry Heino
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -46,6 +46,15 @@ export declare class Vec3 {
46
46
  * Return this' angle in the XY plane (treats this as a Vec2).
47
47
  *
48
48
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
49
+ *
50
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
51
+ * the resultant angle is in the range $[-\pi, pi]$.
52
+ *
53
+ * ```ts,runnable,console
54
+ * import { Vec2 } from '@js-draw/math';
55
+ * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
56
+ * console.log(Vec2.of(-1, 0).angle()); // atan2(0, -1)
57
+ * ```
49
58
  */
50
59
  angle(): number;
51
60
  /**
package/dist/cjs/Vec3.js CHANGED
@@ -71,6 +71,15 @@ class Vec3 {
71
71
  * Return this' angle in the XY plane (treats this as a Vec2).
72
72
  *
73
73
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
74
+ *
75
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
76
+ * the resultant angle is in the range $[-\pi, pi]$.
77
+ *
78
+ * ```ts,runnable,console
79
+ * import { Vec2 } from '@js-draw/math';
80
+ * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
81
+ * console.log(Vec2.of(-1, 0).angle()); // atan2(0, -1)
82
+ * ```
74
83
  */
75
84
  angle() {
76
85
  return Math.atan2(this.y, this.x);
package/dist/cjs/lib.d.ts CHANGED
@@ -20,6 +20,7 @@ export { LineSegment2 } from './shapes/LineSegment2';
20
20
  export { Path, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
21
21
  export { Rect2 } from './shapes/Rect2';
22
22
  export { QuadraticBezier } from './shapes/QuadraticBezier';
23
+ export { Abstract2DShape } from './shapes/Abstract2DShape';
23
24
  export { Mat33, Mat33Array } from './Mat33';
24
25
  export { Point2, Vec2 } from './Vec2';
25
26
  export { Vec3 } from './Vec3';
package/dist/cjs/lib.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * @packageDocumentation
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.toRoundedString = exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
21
+ exports.toRoundedString = exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
22
22
  var LineSegment2_1 = require("./shapes/LineSegment2");
23
23
  Object.defineProperty(exports, "LineSegment2", { enumerable: true, get: function () { return LineSegment2_1.LineSegment2; } });
24
24
  var Path_1 = require("./shapes/Path");
@@ -28,6 +28,8 @@ var Rect2_1 = require("./shapes/Rect2");
28
28
  Object.defineProperty(exports, "Rect2", { enumerable: true, get: function () { return Rect2_1.Rect2; } });
29
29
  var QuadraticBezier_1 = require("./shapes/QuadraticBezier");
30
30
  Object.defineProperty(exports, "QuadraticBezier", { enumerable: true, get: function () { return QuadraticBezier_1.QuadraticBezier; } });
31
+ var Abstract2DShape_1 = require("./shapes/Abstract2DShape");
32
+ Object.defineProperty(exports, "Abstract2DShape", { enumerable: true, get: function () { return Abstract2DShape_1.Abstract2DShape; } });
31
33
  var Mat33_1 = require("./Mat33");
32
34
  Object.defineProperty(exports, "Mat33", { enumerable: true, get: function () { return Mat33_1.Mat33; } });
33
35
  var Vec2_1 = require("./Vec2");
@@ -1,7 +1,10 @@
1
1
  import LineSegment2 from './LineSegment2';
2
2
  import { Point2 } from '../Vec2';
3
3
  import Rect2 from './Rect2';
4
- declare abstract class Abstract2DShape {
4
+ /**
5
+ * An abstract base class for 2D shapes.
6
+ */
7
+ export declare abstract class Abstract2DShape {
5
8
  protected static readonly smallValue = 1e-12;
6
9
  /**
7
10
  * @returns the distance from `point` to this shape. If `point` is within this shape,
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Abstract2DShape = void 0;
4
+ /**
5
+ * An abstract base class for 2D shapes.
6
+ */
3
7
  class Abstract2DShape {
4
8
  /**
5
9
  * @returns the distance from `point` to this shape. If `point` is within this shape,
@@ -34,5 +38,7 @@ class Abstract2DShape {
34
38
  return this.getTightBoundingBox();
35
39
  }
36
40
  }
41
+ exports.Abstract2DShape = Abstract2DShape;
42
+ // @internal
37
43
  Abstract2DShape.smallValue = 1e-12;
38
44
  exports.default = Abstract2DShape;
@@ -35,24 +35,29 @@ interface IntersectionResult {
35
35
  parameterValue?: number;
36
36
  point: Point2;
37
37
  }
38
- type GeometryType = Abstract2DShape;
39
- type GeometryArrayType = Array<GeometryType>;
40
38
  /**
41
39
  * Represents a union of lines and curves.
42
40
  */
43
41
  export declare class Path {
44
42
  readonly startPoint: Point2;
45
- readonly parts: PathCommand[];
46
43
  /**
47
44
  * A rough estimate of the bounding box of the path.
48
45
  * A slight overestimate.
49
46
  * See {@link getExactBBox}
50
47
  */
51
48
  readonly bbox: Rect2;
52
- constructor(startPoint: Point2, parts: PathCommand[]);
49
+ /** The individual shapes that make up this path. */
50
+ readonly parts: Readonly<PathCommand>[];
51
+ /**
52
+ * Creates a new `Path` that starts at `startPoint` and is made up of the path commands,
53
+ * `parts`.
54
+ *
55
+ * See also {@link fromString}
56
+ */
57
+ constructor(startPoint: Point2, parts: Readonly<PathCommand>[]);
53
58
  getExactBBox(): Rect2;
54
59
  private cachedGeometry;
55
- get geometry(): GeometryArrayType;
60
+ get geometry(): Abstract2DShape[];
56
61
  /**
57
62
  * Iterates through the start/end points of each component in this path.
58
63
  *
@@ -77,6 +82,8 @@ export declare class Path {
77
82
  * intersections are approximated with the surface `strokeRadius` away from this.
78
83
  *
79
84
  * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
85
+ *
86
+ * **Note**: `strokeRadius` is half of a stroke's width.
80
87
  */
81
88
  intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
82
89
  private static mapPathCommand;
@@ -84,6 +91,16 @@ export declare class Path {
84
91
  transformedBy(affineTransfm: Mat33): Path;
85
92
  union(other: Path | null): Path;
86
93
  private getEndPoint;
94
+ /**
95
+ * Like {@link closedRoughlyIntersects} except takes stroke width into account.
96
+ *
97
+ * This is intended to be a very fast and rough approximation. Use {@link intersection}
98
+ * and {@link signedDistance} for more accurate (but much slower) intersection calculations.
99
+ *
100
+ * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`).
101
+ *
102
+ * `strokeRadius` is half of `strokeWidth`.
103
+ */
87
104
  roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean;
88
105
  closedRoughlyIntersects(rect: Rect2): boolean;
89
106
  /**
@@ -95,16 +112,32 @@ export declare class Path {
95
112
  */
96
113
  static fromRect(rect: Rect2, lineWidth?: number | null): Path;
97
114
  private cachedStringVersion;
98
- toString(useNonAbsCommands?: boolean): string;
115
+ /**
116
+ * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
117
+ *
118
+ * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of
119
+ * absolute commands (e.g. `L10,0`).
120
+ *
121
+ * See also {@link fromString}.
122
+ */
123
+ toString(useNonAbsCommands?: boolean, ignoreCache?: boolean): string;
99
124
  serialize(): string;
100
125
  static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
101
126
  /**
102
- * Create a Path from a SVG path specification.
127
+ * Create a `Path` from a subset of the SVG path specification.
103
128
  *
104
129
  * ## To-do
105
130
  * - TODO: Support a larger subset of SVG paths
106
131
  * - Elliptical arcs are currently unsupported.
107
132
  * - TODO: Support `s`,`t` commands shorthands.
133
+ *
134
+ * @example
135
+ * ```ts,runnable,console
136
+ * import { Path } from '@js-draw/math';
137
+ *
138
+ * const path = Path.fromString('m0,0l100,100');
139
+ * console.log(path.toString(true)); // true: Prefer relative to absolute path commands
140
+ * ```
108
141
  */
109
142
  static fromString(pathString: string): Path;
110
143
  static empty: Path;
@@ -22,17 +22,23 @@ var PathCommandType;
22
22
  * Represents a union of lines and curves.
23
23
  */
24
24
  class Path {
25
+ /**
26
+ * Creates a new `Path` that starts at `startPoint` and is made up of the path commands,
27
+ * `parts`.
28
+ *
29
+ * See also {@link fromString}
30
+ */
25
31
  constructor(startPoint, parts) {
26
32
  this.startPoint = startPoint;
27
- this.parts = parts;
28
33
  this.cachedGeometry = null;
29
34
  this.cachedPolylineApproximation = null;
30
35
  this.cachedStringVersion = null;
36
+ this.parts = parts;
31
37
  // Initial bounding box contains one point: the start point.
32
38
  this.bbox = Rect2_1.default.bboxOf([startPoint]);
33
39
  // Convert into a representation of the geometry (cache for faster intersection
34
40
  // calculation)
35
- for (const part of parts) {
41
+ for (const part of this.parts) {
36
42
  this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
37
43
  }
38
44
  }
@@ -337,6 +343,8 @@ class Path {
337
343
  * intersections are approximated with the surface `strokeRadius` away from this.
338
344
  *
339
345
  * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
346
+ *
347
+ * **Note**: `strokeRadius` is half of a stroke's width.
340
348
  */
341
349
  intersection(line, strokeRadius) {
342
350
  let result = [];
@@ -432,6 +440,16 @@ class Path {
432
440
  return lastPart.point;
433
441
  }
434
442
  }
443
+ /**
444
+ * Like {@link closedRoughlyIntersects} except takes stroke width into account.
445
+ *
446
+ * This is intended to be a very fast and rough approximation. Use {@link intersection}
447
+ * and {@link signedDistance} for more accurate (but much slower) intersection calculations.
448
+ *
449
+ * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`).
450
+ *
451
+ * `strokeRadius` is half of `strokeWidth`.
452
+ */
435
453
  roughlyIntersects(rect, strokeWidth = 0) {
436
454
  if (this.parts.length === 0) {
437
455
  return rect.containsPoint(this.startPoint);
@@ -459,7 +477,7 @@ class Path {
459
477
  }
460
478
  return false;
461
479
  }
462
- // Treats this as a closed path and returns true if part of `rect` is roughly within
480
+ // Treats this as a closed path and returns true if part of `rect` is *roughly* within
463
481
  // this path's interior.
464
482
  //
465
483
  // Note: Assumes that this is a closed, non-self-intersecting path.
@@ -541,8 +559,16 @@ class Path {
541
559
  });
542
560
  return new Path(startPoint, commands);
543
561
  }
544
- toString(useNonAbsCommands) {
545
- if (this.cachedStringVersion) {
562
+ /**
563
+ * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
564
+ *
565
+ * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of
566
+ * absolute commands (e.g. `L10,0`).
567
+ *
568
+ * See also {@link fromString}.
569
+ */
570
+ toString(useNonAbsCommands, ignoreCache = false) {
571
+ if (this.cachedStringVersion && !ignoreCache) {
546
572
  return this.cachedStringVersion;
547
573
  }
548
574
  if (useNonAbsCommands === undefined) {
@@ -632,12 +658,20 @@ class Path {
632
658
  return result.join('');
633
659
  }
634
660
  /**
635
- * Create a Path from a SVG path specification.
661
+ * Create a `Path` from a subset of the SVG path specification.
636
662
  *
637
663
  * ## To-do
638
664
  * - TODO: Support a larger subset of SVG paths
639
665
  * - Elliptical arcs are currently unsupported.
640
666
  * - TODO: Support `s`,`t` commands shorthands.
667
+ *
668
+ * @example
669
+ * ```ts,runnable,console
670
+ * import { Path } from '@js-draw/math';
671
+ *
672
+ * const path = Path.fromString('m0,0l100,100');
673
+ * console.log(path.toString(true)); // true: Prefer relative to absolute path commands
674
+ * ```
641
675
  */
642
676
  static fromString(pathString) {
643
677
  // See the MDN reference:
@@ -46,6 +46,15 @@ export declare class Vec3 {
46
46
  * Return this' angle in the XY plane (treats this as a Vec2).
47
47
  *
48
48
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
49
+ *
50
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
51
+ * the resultant angle is in the range $[-\pi, pi]$.
52
+ *
53
+ * ```ts,runnable,console
54
+ * import { Vec2 } from '@js-draw/math';
55
+ * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
56
+ * console.log(Vec2.of(-1, 0).angle()); // atan2(0, -1)
57
+ * ```
49
58
  */
50
59
  angle(): number;
51
60
  /**
package/dist/mjs/Vec3.mjs CHANGED
@@ -68,6 +68,15 @@ export class Vec3 {
68
68
  * Return this' angle in the XY plane (treats this as a Vec2).
69
69
  *
70
70
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
71
+ *
72
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
73
+ * the resultant angle is in the range $[-\pi, pi]$.
74
+ *
75
+ * ```ts,runnable,console
76
+ * import { Vec2 } from '@js-draw/math';
77
+ * console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
78
+ * console.log(Vec2.of(-1, 0).angle()); // atan2(0, -1)
79
+ * ```
71
80
  */
72
81
  angle() {
73
82
  return Math.atan2(this.y, this.x);
package/dist/mjs/lib.d.ts CHANGED
@@ -20,6 +20,7 @@ export { LineSegment2 } from './shapes/LineSegment2';
20
20
  export { Path, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
21
21
  export { Rect2 } from './shapes/Rect2';
22
22
  export { QuadraticBezier } from './shapes/QuadraticBezier';
23
+ export { Abstract2DShape } from './shapes/Abstract2DShape';
23
24
  export { Mat33, Mat33Array } from './Mat33';
24
25
  export { Point2, Vec2 } from './Vec2';
25
26
  export { Vec3 } from './Vec3';
package/dist/mjs/lib.mjs CHANGED
@@ -20,6 +20,7 @@ export { LineSegment2 } from './shapes/LineSegment2.mjs';
20
20
  export { Path, PathCommandType, } from './shapes/Path.mjs';
21
21
  export { Rect2 } from './shapes/Rect2.mjs';
22
22
  export { QuadraticBezier } from './shapes/QuadraticBezier.mjs';
23
+ export { Abstract2DShape } from './shapes/Abstract2DShape.mjs';
23
24
  export { Mat33 } from './Mat33.mjs';
24
25
  export { Vec2 } from './Vec2.mjs';
25
26
  export { Vec3 } from './Vec3.mjs';
@@ -1,7 +1,10 @@
1
1
  import LineSegment2 from './LineSegment2';
2
2
  import { Point2 } from '../Vec2';
3
3
  import Rect2 from './Rect2';
4
- declare abstract class Abstract2DShape {
4
+ /**
5
+ * An abstract base class for 2D shapes.
6
+ */
7
+ export declare abstract class Abstract2DShape {
5
8
  protected static readonly smallValue = 1e-12;
6
9
  /**
7
10
  * @returns the distance from `point` to this shape. If `point` is within this shape,
@@ -1,4 +1,7 @@
1
- class Abstract2DShape {
1
+ /**
2
+ * An abstract base class for 2D shapes.
3
+ */
4
+ export class Abstract2DShape {
2
5
  /**
3
6
  * @returns the distance from `point` to this shape. If `point` is within this shape,
4
7
  * this returns the distance from `point` to the edge of this shape.
@@ -32,5 +35,6 @@ class Abstract2DShape {
32
35
  return this.getTightBoundingBox();
33
36
  }
34
37
  }
38
+ // @internal
35
39
  Abstract2DShape.smallValue = 1e-12;
36
40
  export default Abstract2DShape;
@@ -35,24 +35,29 @@ interface IntersectionResult {
35
35
  parameterValue?: number;
36
36
  point: Point2;
37
37
  }
38
- type GeometryType = Abstract2DShape;
39
- type GeometryArrayType = Array<GeometryType>;
40
38
  /**
41
39
  * Represents a union of lines and curves.
42
40
  */
43
41
  export declare class Path {
44
42
  readonly startPoint: Point2;
45
- readonly parts: PathCommand[];
46
43
  /**
47
44
  * A rough estimate of the bounding box of the path.
48
45
  * A slight overestimate.
49
46
  * See {@link getExactBBox}
50
47
  */
51
48
  readonly bbox: Rect2;
52
- constructor(startPoint: Point2, parts: PathCommand[]);
49
+ /** The individual shapes that make up this path. */
50
+ readonly parts: Readonly<PathCommand>[];
51
+ /**
52
+ * Creates a new `Path` that starts at `startPoint` and is made up of the path commands,
53
+ * `parts`.
54
+ *
55
+ * See also {@link fromString}
56
+ */
57
+ constructor(startPoint: Point2, parts: Readonly<PathCommand>[]);
53
58
  getExactBBox(): Rect2;
54
59
  private cachedGeometry;
55
- get geometry(): GeometryArrayType;
60
+ get geometry(): Abstract2DShape[];
56
61
  /**
57
62
  * Iterates through the start/end points of each component in this path.
58
63
  *
@@ -77,6 +82,8 @@ export declare class Path {
77
82
  * intersections are approximated with the surface `strokeRadius` away from this.
78
83
  *
79
84
  * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
85
+ *
86
+ * **Note**: `strokeRadius` is half of a stroke's width.
80
87
  */
81
88
  intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
82
89
  private static mapPathCommand;
@@ -84,6 +91,16 @@ export declare class Path {
84
91
  transformedBy(affineTransfm: Mat33): Path;
85
92
  union(other: Path | null): Path;
86
93
  private getEndPoint;
94
+ /**
95
+ * Like {@link closedRoughlyIntersects} except takes stroke width into account.
96
+ *
97
+ * This is intended to be a very fast and rough approximation. Use {@link intersection}
98
+ * and {@link signedDistance} for more accurate (but much slower) intersection calculations.
99
+ *
100
+ * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`).
101
+ *
102
+ * `strokeRadius` is half of `strokeWidth`.
103
+ */
87
104
  roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean;
88
105
  closedRoughlyIntersects(rect: Rect2): boolean;
89
106
  /**
@@ -95,16 +112,32 @@ export declare class Path {
95
112
  */
96
113
  static fromRect(rect: Rect2, lineWidth?: number | null): Path;
97
114
  private cachedStringVersion;
98
- toString(useNonAbsCommands?: boolean): string;
115
+ /**
116
+ * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
117
+ *
118
+ * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of
119
+ * absolute commands (e.g. `L10,0`).
120
+ *
121
+ * See also {@link fromString}.
122
+ */
123
+ toString(useNonAbsCommands?: boolean, ignoreCache?: boolean): string;
99
124
  serialize(): string;
100
125
  static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
101
126
  /**
102
- * Create a Path from a SVG path specification.
127
+ * Create a `Path` from a subset of the SVG path specification.
103
128
  *
104
129
  * ## To-do
105
130
  * - TODO: Support a larger subset of SVG paths
106
131
  * - Elliptical arcs are currently unsupported.
107
132
  * - TODO: Support `s`,`t` commands shorthands.
133
+ *
134
+ * @example
135
+ * ```ts,runnable,console
136
+ * import { Path } from '@js-draw/math';
137
+ *
138
+ * const path = Path.fromString('m0,0l100,100');
139
+ * console.log(path.toString(true)); // true: Prefer relative to absolute path commands
140
+ * ```
108
141
  */
109
142
  static fromString(pathString: string): Path;
110
143
  static empty: Path;
@@ -16,17 +16,23 @@ export var PathCommandType;
16
16
  * Represents a union of lines and curves.
17
17
  */
18
18
  export class Path {
19
+ /**
20
+ * Creates a new `Path` that starts at `startPoint` and is made up of the path commands,
21
+ * `parts`.
22
+ *
23
+ * See also {@link fromString}
24
+ */
19
25
  constructor(startPoint, parts) {
20
26
  this.startPoint = startPoint;
21
- this.parts = parts;
22
27
  this.cachedGeometry = null;
23
28
  this.cachedPolylineApproximation = null;
24
29
  this.cachedStringVersion = null;
30
+ this.parts = parts;
25
31
  // Initial bounding box contains one point: the start point.
26
32
  this.bbox = Rect2.bboxOf([startPoint]);
27
33
  // Convert into a representation of the geometry (cache for faster intersection
28
34
  // calculation)
29
- for (const part of parts) {
35
+ for (const part of this.parts) {
30
36
  this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
31
37
  }
32
38
  }
@@ -331,6 +337,8 @@ export class Path {
331
337
  * intersections are approximated with the surface `strokeRadius` away from this.
332
338
  *
333
339
  * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
340
+ *
341
+ * **Note**: `strokeRadius` is half of a stroke's width.
334
342
  */
335
343
  intersection(line, strokeRadius) {
336
344
  let result = [];
@@ -426,6 +434,16 @@ export class Path {
426
434
  return lastPart.point;
427
435
  }
428
436
  }
437
+ /**
438
+ * Like {@link closedRoughlyIntersects} except takes stroke width into account.
439
+ *
440
+ * This is intended to be a very fast and rough approximation. Use {@link intersection}
441
+ * and {@link signedDistance} for more accurate (but much slower) intersection calculations.
442
+ *
443
+ * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`).
444
+ *
445
+ * `strokeRadius` is half of `strokeWidth`.
446
+ */
429
447
  roughlyIntersects(rect, strokeWidth = 0) {
430
448
  if (this.parts.length === 0) {
431
449
  return rect.containsPoint(this.startPoint);
@@ -453,7 +471,7 @@ export class Path {
453
471
  }
454
472
  return false;
455
473
  }
456
- // Treats this as a closed path and returns true if part of `rect` is roughly within
474
+ // Treats this as a closed path and returns true if part of `rect` is *roughly* within
457
475
  // this path's interior.
458
476
  //
459
477
  // Note: Assumes that this is a closed, non-self-intersecting path.
@@ -535,8 +553,16 @@ export class Path {
535
553
  });
536
554
  return new Path(startPoint, commands);
537
555
  }
538
- toString(useNonAbsCommands) {
539
- if (this.cachedStringVersion) {
556
+ /**
557
+ * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
558
+ *
559
+ * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of
560
+ * absolute commands (e.g. `L10,0`).
561
+ *
562
+ * See also {@link fromString}.
563
+ */
564
+ toString(useNonAbsCommands, ignoreCache = false) {
565
+ if (this.cachedStringVersion && !ignoreCache) {
540
566
  return this.cachedStringVersion;
541
567
  }
542
568
  if (useNonAbsCommands === undefined) {
@@ -626,12 +652,20 @@ export class Path {
626
652
  return result.join('');
627
653
  }
628
654
  /**
629
- * Create a Path from a SVG path specification.
655
+ * Create a `Path` from a subset of the SVG path specification.
630
656
  *
631
657
  * ## To-do
632
658
  * - TODO: Support a larger subset of SVG paths
633
659
  * - Elliptical arcs are currently unsupported.
634
660
  * - TODO: Support `s`,`t` commands shorthands.
661
+ *
662
+ * @example
663
+ * ```ts,runnable,console
664
+ * import { Path } from '@js-draw/math';
665
+ *
666
+ * const path = Path.fromString('m0,0l100,100');
667
+ * console.log(path.toString(true)); // true: Prefer relative to absolute path commands
668
+ * ```
635
669
  */
636
670
  static fromString(pathString) {
637
671
  // See the MDN reference:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-draw/math",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "A math library for js-draw. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -19,7 +19,7 @@
19
19
  "license": "MIT",
20
20
  "private": false,
21
21
  "scripts": {
22
- "dist-test": "npm run build && cd dist-test/test_imports && npm install && npm run test",
22
+ "dist-test": "cd dist-test/test_imports && npm install && npm run test",
23
23
  "dist": "npm run build && npm run dist-test",
24
24
  "build": "rm -rf ./dist && mkdir dist && build-tool build",
25
25
  "watch": "rm -rf ./dist/* && mkdir -p dist && build-tool watch"
@@ -45,5 +45,5 @@
45
45
  "svg",
46
46
  "math"
47
47
  ],
48
- "gitHead": "89f7991833dec5c17ce0890321286198ff7b1900"
48
+ "gitHead": "ccf1d0634e902c731fcd794df11cd001c3a30585"
49
49
  }
package/src/lib.ts CHANGED
@@ -30,6 +30,7 @@ export {
30
30
  } from './shapes/Path';
31
31
  export { Rect2 } from './shapes/Rect2';
32
32
  export { QuadraticBezier } from './shapes/QuadraticBezier';
33
+ export { Abstract2DShape } from './shapes/Abstract2DShape';
33
34
 
34
35
  export { Mat33, Mat33Array } from './Mat33';
35
36
  export { Point2, Vec2 } from './Vec2';
@@ -2,7 +2,11 @@ import LineSegment2 from './LineSegment2';
2
2
  import { Point2 } from '../Vec2';
3
3
  import Rect2 from './Rect2';
4
4
 
5
- abstract class Abstract2DShape {
5
+ /**
6
+ * An abstract base class for 2D shapes.
7
+ */
8
+ export abstract class Abstract2DShape {
9
+ // @internal
6
10
  protected static readonly smallValue = 1e-12;
7
11
 
8
12
  /**
@@ -47,9 +47,9 @@ describe('Path.toString', () => {
47
47
 
48
48
  it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
49
49
  const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
50
- path1['cachedStringVersion'] = null; // Clear the cache.
50
+ const ignoreCache = true;
51
51
 
52
- expect(path1.toString()).toBe([
52
+ expect(path1.toString(undefined, ignoreCache)).toBe([
53
53
  'M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'
54
54
  ].join(''));
55
55
  });
@@ -58,9 +58,9 @@ describe('Path.toString', () => {
58
58
  const pathStr = 'M184.2,52.3l-.2-.2q-2.7,2.4 -3.2,3.5q-2.8,7 -.9,6.1q4.3-2.6 4.8-6.1q1.2-8.8 .4-8.3q-4.2,5.2 -3.9,3.9q.2-1.6 .3-2.1q.2-1.3 -.2-1q-3.8,6.5 -3.2,3.3q.6-4.1 1.1-5.3q4.1-10 3.3-8.3q-5.3,13.1 -6.6,14.1q-3.3,2.8 -1.8-1.5q2.8-9.7 2.7-8.4q0,.3 0,.4q-1.4,7.1 -2.7,8.5q-2.6,3.2 -2.5,2.9q-.3-1.9 -.7-1.9q-4.1,4.4 -2.9,1.9q1.1-3 .3-2.6q-1.8,2 -2.5,2.4q-4.5,2.8 -4.2,1.9q.3-1.6 .2-1.4q1.5,2.2 1.3,2.9q-.8,3.9 -.5,3.3q.8-7.6 2.5-13.3q2.6-9.2 2.9-6.9q.3,1.4 .3,1.2q-.7-.4 -.9,0q-2.2,11.6 -7.6,13.6q-3.9,1.6 -2.1-1.3q3-5.5 2.6-3.4q-.2,1.8 -.5,1.8q-3.2,.5 -4.1,1.2q-2.6,2.6 -1.9,2.5q4.7-4.4 3.7-5.5q-1.1-.9 -1.6-.6q-7.2,7.5 -3.9,6.5q.3-.1 .4-.4q.6-5.3 -.2-4.9q-2.8,2.3 -3.1,2.4q-3.7,1.5 -3.5,.5q.3-3.6 1.4-3.3q3.5,.7 1.9,2.4q-1.7,2.3 -1.6,.8q0-3.5 -.9-3.1q-5.1,3.3 -4.9,2.8q.1-4 -.8-3.5q-4.3,3.4 -4.6,2.5q-1-2.1 .5-8.7l-.2,0q-1.6,6.6 -.7,8.9q.7,1.2 5.2-2.3q.4-.5 .2,3.1q.1,1 5.5-2.4q.4-.4 .3,2.7q.1,2 2.4-.4q1.7-2.3 -2.1-3.2q-1.7-.3 -2,3.7q0,1.4 4.1-.1q.3-.1 3.1-2.4q.3-.5 -.4,4.5q0-.1 -.2,0q-2.6,1.2 4.5-5.7q0-.2 .8,.6q.9,.6 -3.7,4.7q-.5,1 2.7-1.7q.6-.7 3.7-1.2q.7-.2 .9-2.2q.1-2.7 -3.4,3.2q-1.8,3.4 2.7,1.9q5.6-2.1 7.8-14q-.1,.1 .3,.4q.6,.1 .3-1.6q-.7-2.8 -3.7,6.7q-1.8,5.8 -2.5,13.5q.1,1.1 1.3-3.1q.2-1 -1.3-3.3q-.5-.5 -1,1.6q-.1,1.3 4.8-1.5q1-1 3-2q.1-.4 -1.1,2q-1.1,3.1 3.7-1.3q-.4,0 -.1,1.5q.3,.8 3.3-2.5q1.3-1.6 2.7-8.9q0-.1 0-.4q-.3-1.9 -3.5,8.2q-1.3,4.9 2.4,2.1q1.4-1.2 6.6-14.3q.8-2.4 -3.9,7.9q-.6,1.3 -1.1,5.5q-.3,3.7 4-3.1q-.2,0 -.6,.6q-.2,.6 -.3,2.3q0,1.8 4.7-3.5q.1-.5 -1.2,7.9q-.5,3.2 -4.6,5.7q-1.3,1 1.5-5.5q.4-1.1 3.01-3.5';
59
59
 
60
60
  const path1 = Path.fromString(pathStr);
61
- path1['cachedStringVersion'] = null; // Clear the cache.
62
- const path = Path.fromString(path1.toString(true));
63
- path1['cachedStringVersion'] = null; // Clear the cache.
61
+ const ignoreCache = true;
62
+ const path = Path.fromString(path1.toString(true, ignoreCache));
63
+ path1['cachedStringVersion'] = null; // Clear the cache manually
64
64
 
65
65
  expect(path.toString(true)).toBe(path1.toString(true));
66
66
  });
@@ -51,9 +51,6 @@ interface IntersectionResult {
51
51
  point: Point2;
52
52
  }
53
53
 
54
- type GeometryType = Abstract2DShape;
55
- type GeometryArrayType = Array<GeometryType>;
56
-
57
54
  /**
58
55
  * Represents a union of lines and curves.
59
56
  */
@@ -65,13 +62,27 @@ export class Path {
65
62
  */
66
63
  public readonly bbox: Rect2;
67
64
 
68
- public constructor(public readonly startPoint: Point2, public readonly parts: PathCommand[]) {
65
+ /** The individual shapes that make up this path. */
66
+ public readonly parts: Readonly<PathCommand>[];
67
+
68
+ /**
69
+ * Creates a new `Path` that starts at `startPoint` and is made up of the path commands,
70
+ * `parts`.
71
+ *
72
+ * See also {@link fromString}
73
+ */
74
+ public constructor(
75
+ public readonly startPoint: Point2,
76
+ parts: Readonly<PathCommand>[],
77
+ ) {
78
+ this.parts = parts;
79
+
69
80
  // Initial bounding box contains one point: the start point.
70
81
  this.bbox = Rect2.bboxOf([startPoint]);
71
82
 
72
83
  // Convert into a representation of the geometry (cache for faster intersection
73
84
  // calculation)
74
- for (const part of parts) {
85
+ for (const part of this.parts) {
75
86
  this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
76
87
  }
77
88
  }
@@ -85,16 +96,16 @@ export class Path {
85
96
  return Rect2.union(...bboxes);
86
97
  }
87
98
 
88
- private cachedGeometry: GeometryArrayType|null = null;
99
+ private cachedGeometry: Abstract2DShape[]|null = null;
89
100
 
90
101
  // Lazy-loads and returns this path's geometry
91
- public get geometry(): GeometryArrayType {
102
+ public get geometry(): Abstract2DShape[] {
92
103
  if (this.cachedGeometry) {
93
104
  return this.cachedGeometry;
94
105
  }
95
106
 
96
107
  let startPoint = this.startPoint;
97
- const geometry: GeometryArrayType = [];
108
+ const geometry: Abstract2DShape[] = [];
98
109
 
99
110
  for (const part of this.parts) {
100
111
  let exhaustivenessCheck: never;
@@ -258,7 +269,7 @@ export class Path {
258
269
 
259
270
  type DistanceFunction = (point: Point2) => number;
260
271
  type DistanceFunctionRecord = {
261
- part: GeometryType,
272
+ part: Abstract2DShape,
262
273
  bbox: Rect2,
263
274
  distFn: DistanceFunction,
264
275
  };
@@ -297,7 +308,7 @@ export class Path {
297
308
 
298
309
  // Returns the minimum distance to a part in this stroke, where only parts that the given
299
310
  // line could intersect are considered.
300
- const sdf = (point: Point2): [GeometryType|null, number] => {
311
+ const sdf = (point: Point2): [Abstract2DShape|null, number] => {
301
312
  let minDist = Infinity;
302
313
  let minDistPart: Abstract2DShape|null = null;
303
314
 
@@ -466,6 +477,8 @@ export class Path {
466
477
  * intersections are approximated with the surface `strokeRadius` away from this.
467
478
  *
468
479
  * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
480
+ *
481
+ * **Note**: `strokeRadius` is half of a stroke's width.
469
482
  */
470
483
  public intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[] {
471
484
  let result: IntersectionResult[] = [];
@@ -576,6 +589,16 @@ export class Path {
576
589
  }
577
590
  }
578
591
 
592
+ /**
593
+ * Like {@link closedRoughlyIntersects} except takes stroke width into account.
594
+ *
595
+ * This is intended to be a very fast and rough approximation. Use {@link intersection}
596
+ * and {@link signedDistance} for more accurate (but much slower) intersection calculations.
597
+ *
598
+ * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`).
599
+ *
600
+ * `strokeRadius` is half of `strokeWidth`.
601
+ */
579
602
  public roughlyIntersects(rect: Rect2, strokeWidth: number = 0) {
580
603
  if (this.parts.length === 0) {
581
604
  return rect.containsPoint(this.startPoint);
@@ -609,7 +632,7 @@ export class Path {
609
632
  return false;
610
633
  }
611
634
 
612
- // Treats this as a closed path and returns true if part of `rect` is roughly within
635
+ // Treats this as a closed path and returns true if part of `rect` is *roughly* within
613
636
  // this path's interior.
614
637
  //
615
638
  // Note: Assumes that this is a closed, non-self-intersecting path.
@@ -713,8 +736,16 @@ export class Path {
713
736
 
714
737
  private cachedStringVersion: string|null = null;
715
738
 
716
- public toString(useNonAbsCommands?: boolean): string {
717
- if (this.cachedStringVersion) {
739
+ /**
740
+ * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
741
+ *
742
+ * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of
743
+ * absolute commands (e.g. `L10,0`).
744
+ *
745
+ * See also {@link fromString}.
746
+ */
747
+ public toString(useNonAbsCommands?: boolean, ignoreCache: boolean = false): string {
748
+ if (this.cachedStringVersion && !ignoreCache) {
718
749
  return this.cachedStringVersion;
719
750
  }
720
751
 
@@ -817,12 +848,20 @@ export class Path {
817
848
  }
818
849
 
819
850
  /**
820
- * Create a Path from a SVG path specification.
851
+ * Create a `Path` from a subset of the SVG path specification.
821
852
  *
822
853
  * ## To-do
823
854
  * - TODO: Support a larger subset of SVG paths
824
855
  * - Elliptical arcs are currently unsupported.
825
856
  * - TODO: Support `s`,`t` commands shorthands.
857
+ *
858
+ * @example
859
+ * ```ts,runnable,console
860
+ * import { Path } from '@js-draw/math';
861
+ *
862
+ * const path = Path.fromString('m0,0l100,100');
863
+ * console.log(path.toString(true)); // true: Prefer relative to absolute path commands
864
+ * ```
826
865
  */
827
866
  public static fromString(pathString: string): Path {
828
867
  // See the MDN reference: