@js-draw/math 1.11.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Vec3.d.ts +21 -0
- package/dist/cjs/Vec3.js +28 -0
- package/dist/cjs/lib.d.ts +2 -2
- package/dist/cjs/lib.js +16 -3
- package/dist/cjs/rounding/cleanUpNumber.d.ts +3 -0
- package/dist/cjs/rounding/cleanUpNumber.js +35 -0
- package/dist/cjs/rounding/constants.d.ts +1 -0
- package/dist/cjs/rounding/constants.js +4 -0
- package/dist/cjs/rounding/getLenAfterDecimal.d.ts +10 -0
- package/dist/cjs/rounding/getLenAfterDecimal.js +30 -0
- package/dist/cjs/rounding/lib.d.ts +1 -0
- package/dist/cjs/rounding/lib.js +5 -0
- package/dist/cjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
- package/dist/cjs/rounding/toRoundedString.js +54 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.d.ts +2 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.js +58 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
- package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +15 -5
- package/dist/cjs/shapes/BezierJSWrapper.js +135 -18
- package/dist/cjs/shapes/LineSegment2.d.ts +34 -5
- package/dist/cjs/shapes/LineSegment2.js +63 -10
- package/dist/cjs/shapes/Parameterized2DShape.d.ts +31 -0
- package/dist/cjs/shapes/Parameterized2DShape.js +15 -0
- package/dist/cjs/shapes/Path.d.ts +40 -6
- package/dist/cjs/shapes/Path.js +181 -22
- package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/cjs/shapes/PointShape2D.js +28 -5
- package/dist/cjs/shapes/QuadraticBezier.d.ts +4 -0
- package/dist/cjs/shapes/QuadraticBezier.js +19 -4
- package/dist/cjs/shapes/Rect2.d.ts +3 -0
- package/dist/cjs/shapes/Rect2.js +4 -1
- package/dist/mjs/Vec3.d.ts +21 -0
- package/dist/mjs/Vec3.mjs +28 -0
- package/dist/mjs/lib.d.ts +2 -2
- package/dist/mjs/lib.mjs +1 -1
- package/dist/mjs/rounding/cleanUpNumber.d.ts +3 -0
- package/dist/mjs/rounding/cleanUpNumber.mjs +31 -0
- package/dist/mjs/rounding/cleanUpNumber.test.d.ts +1 -0
- package/dist/mjs/rounding/constants.d.ts +1 -0
- package/dist/mjs/rounding/constants.mjs +1 -0
- package/dist/mjs/rounding/getLenAfterDecimal.d.ts +10 -0
- package/dist/mjs/rounding/getLenAfterDecimal.mjs +26 -0
- package/dist/mjs/rounding/lib.d.ts +1 -0
- package/dist/mjs/rounding/lib.mjs +1 -0
- package/dist/mjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
- package/dist/mjs/rounding/toRoundedString.mjs +47 -0
- package/dist/mjs/rounding/toRoundedString.test.d.ts +1 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.d.ts +2 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.mjs +51 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
- package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +15 -5
- package/dist/mjs/shapes/BezierJSWrapper.mjs +133 -18
- package/dist/mjs/shapes/LineSegment2.d.ts +34 -5
- package/dist/mjs/shapes/LineSegment2.mjs +63 -10
- package/dist/mjs/shapes/Parameterized2DShape.d.ts +31 -0
- package/dist/mjs/shapes/Parameterized2DShape.mjs +8 -0
- package/dist/mjs/shapes/Path.d.ts +40 -6
- package/dist/mjs/shapes/Path.mjs +175 -16
- package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/mjs/shapes/PointShape2D.mjs +28 -5
- package/dist/mjs/shapes/QuadraticBezier.d.ts +4 -0
- package/dist/mjs/shapes/QuadraticBezier.mjs +19 -4
- package/dist/mjs/shapes/Rect2.d.ts +3 -0
- package/dist/mjs/shapes/Rect2.mjs +4 -1
- package/package.json +5 -5
- package/src/Vec3.test.ts +26 -7
- package/src/Vec3.ts +30 -0
- package/src/lib.ts +3 -1
- package/src/rounding/cleanUpNumber.test.ts +15 -0
- package/src/rounding/cleanUpNumber.ts +38 -0
- package/src/rounding/constants.ts +3 -0
- package/src/rounding/getLenAfterDecimal.ts +29 -0
- package/src/rounding/lib.ts +2 -0
- package/src/rounding/toRoundedString.test.ts +32 -0
- package/src/rounding/toRoundedString.ts +57 -0
- package/src/rounding/toStringOfSamePrecision.test.ts +21 -0
- package/src/rounding/toStringOfSamePrecision.ts +63 -0
- package/src/shapes/Abstract2DShape.ts +3 -0
- package/src/shapes/BezierJSWrapper.ts +154 -14
- package/src/shapes/LineSegment2.test.ts +35 -1
- package/src/shapes/LineSegment2.ts +79 -11
- package/src/shapes/Parameterized2DShape.ts +39 -0
- package/src/shapes/Path.test.ts +63 -3
- package/src/shapes/Path.ts +211 -26
- package/src/shapes/PointShape2D.ts +33 -6
- package/src/shapes/QuadraticBezier.test.ts +48 -12
- package/src/shapes/QuadraticBezier.ts +23 -5
- package/src/shapes/Rect2.ts +4 -1
- package/dist/cjs/rounding.js +0 -146
- package/dist/mjs/rounding.mjs +0 -139
- package/src/rounding.test.ts +0 -65
- package/src/rounding.ts +0 -168
- /package/dist/cjs/{rounding.test.d.ts → rounding/cleanUpNumber.test.d.ts} +0 -0
- /package/dist/{mjs/rounding.test.d.ts → cjs/rounding/toRoundedString.test.d.ts} +0 -0
|
@@ -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
|
|
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
|
|
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):
|
|
56
|
-
closestPointTo(target: Point2):
|
|
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
|
|
9
|
+
const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
|
|
10
10
|
/** Represents a line segment. A `LineSegment2` is immutable. */
|
|
11
|
-
class LineSegment2 extends
|
|
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.
|
|
113
|
-
const resultToP2 = resultPoint.
|
|
114
|
-
const resultToP3 = resultPoint.
|
|
115
|
-
const resultToP4 = resultPoint.
|
|
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;
|
|
@@ -2,7 +2,7 @@ import LineSegment2 from './LineSegment2';
|
|
|
2
2
|
import Mat33 from '../Mat33';
|
|
3
3
|
import Rect2 from './Rect2';
|
|
4
4
|
import { Point2 } from '../Vec2';
|
|
5
|
-
import
|
|
5
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
|
6
6
|
export declare enum PathCommandType {
|
|
7
7
|
LineTo = 0,
|
|
8
8
|
MoveTo = 1,
|
|
@@ -29,12 +29,23 @@ export interface MoveToPathCommand {
|
|
|
29
29
|
point: Point2;
|
|
30
30
|
}
|
|
31
31
|
export type PathCommand = CubicBezierPathCommand | QuadraticBezierPathCommand | MoveToPathCommand | LinePathCommand;
|
|
32
|
-
interface IntersectionResult {
|
|
33
|
-
curve:
|
|
34
|
-
|
|
32
|
+
export interface IntersectionResult {
|
|
33
|
+
curve: Parameterized2DShape;
|
|
34
|
+
curveIndex: number;
|
|
35
|
+
/** Parameter value for the closest point **on** the path to the intersection. @internal @deprecated */
|
|
35
36
|
parameterValue?: number;
|
|
37
|
+
/** Point at which the intersection occured. */
|
|
36
38
|
point: Point2;
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Allows indexing a particular part of a path.
|
|
42
|
+
*
|
|
43
|
+
* @see {@link Path.at} {@link Path.tangentAt}
|
|
44
|
+
*/
|
|
45
|
+
export interface CurveIndexRecord {
|
|
46
|
+
curveIndex: number;
|
|
47
|
+
parameterValue: number;
|
|
48
|
+
}
|
|
38
49
|
/**
|
|
39
50
|
* Represents a union of lines and curves.
|
|
40
51
|
*/
|
|
@@ -57,7 +68,7 @@ export declare class Path {
|
|
|
57
68
|
constructor(startPoint: Point2, parts: Readonly<PathCommand>[]);
|
|
58
69
|
getExactBBox(): Rect2;
|
|
59
70
|
private cachedGeometry;
|
|
60
|
-
get geometry():
|
|
71
|
+
get geometry(): Parameterized2DShape[];
|
|
61
72
|
/**
|
|
62
73
|
* Iterates through the start/end points of each component in this path.
|
|
63
74
|
*
|
|
@@ -86,10 +97,31 @@ export declare class Path {
|
|
|
86
97
|
* **Note**: `strokeRadius` is half of a stroke's width.
|
|
87
98
|
*/
|
|
88
99
|
intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
|
|
100
|
+
/**
|
|
101
|
+
* @returns the nearest point on this path to the given `point`.
|
|
102
|
+
*
|
|
103
|
+
* @internal
|
|
104
|
+
* @beta
|
|
105
|
+
*/
|
|
106
|
+
nearestPointTo(point: Point2): IntersectionResult;
|
|
107
|
+
at(index: CurveIndexRecord): import("../Vec3").Vec3;
|
|
108
|
+
tangentAt(index: CurveIndexRecord): import("../Vec3").Vec3;
|
|
89
109
|
private static mapPathCommand;
|
|
90
110
|
mapPoints(mapping: (point: Point2) => Point2): Path;
|
|
91
111
|
transformedBy(affineTransfm: Mat33): Path;
|
|
92
|
-
union(other: Path | null
|
|
112
|
+
union(other: Path | null, options?: {
|
|
113
|
+
allowReverse?: boolean;
|
|
114
|
+
}): Path;
|
|
115
|
+
/**
|
|
116
|
+
* @returns a version of this path with the direction reversed.
|
|
117
|
+
*
|
|
118
|
+
* Example:
|
|
119
|
+
* ```ts,runnable,console
|
|
120
|
+
* import {Path} from '@js-draw/math';
|
|
121
|
+
* console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
reversed(): Path;
|
|
93
125
|
private getEndPoint;
|
|
94
126
|
/**
|
|
95
127
|
* Like {@link closedRoughlyIntersects} except takes stroke width into account.
|
|
@@ -103,6 +135,8 @@ export declare class Path {
|
|
|
103
135
|
*/
|
|
104
136
|
roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean;
|
|
105
137
|
closedRoughlyIntersects(rect: Rect2): boolean;
|
|
138
|
+
/** @returns true if all points on this are equivalent to the points on `other` */
|
|
139
|
+
eq(other: Path, tolerance?: number): boolean;
|
|
106
140
|
/**
|
|
107
141
|
* Returns a path that outlines `rect`.
|
|
108
142
|
*
|
package/dist/cjs/shapes/Path.js
CHANGED
|
@@ -4,13 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Path = exports.PathCommandType = void 0;
|
|
7
|
-
const rounding_1 = require("../rounding");
|
|
8
7
|
const LineSegment2_1 = __importDefault(require("./LineSegment2"));
|
|
9
8
|
const Rect2_1 = __importDefault(require("./Rect2"));
|
|
10
9
|
const Vec2_1 = require("../Vec2");
|
|
11
10
|
const CubicBezier_1 = __importDefault(require("./CubicBezier"));
|
|
12
11
|
const QuadraticBezier_1 = __importDefault(require("./QuadraticBezier"));
|
|
13
12
|
const PointShape2D_1 = __importDefault(require("./PointShape2D"));
|
|
13
|
+
const toRoundedString_1 = __importDefault(require("../rounding/toRoundedString"));
|
|
14
|
+
const toStringOfSamePrecision_1 = __importDefault(require("../rounding/toStringOfSamePrecision"));
|
|
14
15
|
var PathCommandType;
|
|
15
16
|
(function (PathCommandType) {
|
|
16
17
|
PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
|
|
@@ -235,7 +236,7 @@ class Path {
|
|
|
235
236
|
for (const { part, distFn, bbox } of uncheckedDistFunctions) {
|
|
236
237
|
// Skip if impossible for the distance to the target to be lesser than
|
|
237
238
|
// the current minimum.
|
|
238
|
-
if (!bbox.grownBy(minDist).containsPoint(point)) {
|
|
239
|
+
if (isFinite(minDist) && !bbox.grownBy(minDist).containsPoint(point)) {
|
|
239
240
|
continue;
|
|
240
241
|
}
|
|
241
242
|
const currentDist = distFn(point);
|
|
@@ -273,7 +274,7 @@ class Path {
|
|
|
273
274
|
});
|
|
274
275
|
const result = [];
|
|
275
276
|
const stoppingThreshold = strokeRadius / 1000;
|
|
276
|
-
// Returns the maximum
|
|
277
|
+
// Returns the maximum parameter value explored
|
|
277
278
|
const raymarchFrom = (startPoint,
|
|
278
279
|
// Direction to march in (multiplies line.direction)
|
|
279
280
|
directionMultiplier,
|
|
@@ -317,9 +318,14 @@ class Path {
|
|
|
317
318
|
if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
|
|
318
319
|
result.push({
|
|
319
320
|
point: currentPoint,
|
|
320
|
-
parameterValue: NaN,
|
|
321
|
+
parameterValue: NaN, // lastPart.nearestPointTo(currentPoint).parameterValue,
|
|
321
322
|
curve: lastPart,
|
|
323
|
+
curveIndex: this.geometry.indexOf(lastPart),
|
|
322
324
|
});
|
|
325
|
+
// Slightly increase the parameter value to prevent the same point from being
|
|
326
|
+
// added to the results twice.
|
|
327
|
+
const parameterIncrease = strokeRadius / 20 / line.length;
|
|
328
|
+
lastParameter += isFinite(parameterIncrease) ? parameterIncrease : 0;
|
|
323
329
|
}
|
|
324
330
|
return lastParameter;
|
|
325
331
|
};
|
|
@@ -352,14 +358,18 @@ class Path {
|
|
|
352
358
|
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius ?? 0))) {
|
|
353
359
|
return [];
|
|
354
360
|
}
|
|
361
|
+
let index = 0;
|
|
355
362
|
for (const part of this.geometry) {
|
|
356
|
-
const
|
|
357
|
-
|
|
363
|
+
const intersections = part.argIntersectsLineSegment(line);
|
|
364
|
+
for (const intersection of intersections) {
|
|
358
365
|
result.push({
|
|
359
366
|
curve: part,
|
|
360
|
-
|
|
367
|
+
curveIndex: index,
|
|
368
|
+
point: part.at(intersection),
|
|
369
|
+
parameterValue: intersection,
|
|
361
370
|
});
|
|
362
371
|
}
|
|
372
|
+
index++;
|
|
363
373
|
}
|
|
364
374
|
// If given a non-zero strokeWidth, attempt to raymarch.
|
|
365
375
|
// Even if raymarching, we need to collect starting points.
|
|
@@ -372,6 +382,42 @@ class Path {
|
|
|
372
382
|
}
|
|
373
383
|
return result;
|
|
374
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* @returns the nearest point on this path to the given `point`.
|
|
387
|
+
*
|
|
388
|
+
* @internal
|
|
389
|
+
* @beta
|
|
390
|
+
*/
|
|
391
|
+
nearestPointTo(point) {
|
|
392
|
+
// Find the closest point on this
|
|
393
|
+
let closestSquareDist = Infinity;
|
|
394
|
+
let closestPartIndex = 0;
|
|
395
|
+
let closestParameterValue = 0;
|
|
396
|
+
let closestPoint = this.startPoint;
|
|
397
|
+
for (let i = 0; i < this.geometry.length; i++) {
|
|
398
|
+
const current = this.geometry[i];
|
|
399
|
+
const nearestPoint = current.nearestPointTo(point);
|
|
400
|
+
const sqareDist = nearestPoint.point.squareDistanceTo(point);
|
|
401
|
+
if (i === 0 || sqareDist < closestSquareDist) {
|
|
402
|
+
closestPartIndex = i;
|
|
403
|
+
closestSquareDist = sqareDist;
|
|
404
|
+
closestParameterValue = nearestPoint.parameterValue;
|
|
405
|
+
closestPoint = nearestPoint.point;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
curve: this.geometry[closestPartIndex],
|
|
410
|
+
curveIndex: closestPartIndex,
|
|
411
|
+
parameterValue: closestParameterValue,
|
|
412
|
+
point: closestPoint,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
at(index) {
|
|
416
|
+
return this.geometry[index.curveIndex].at(index.parameterValue);
|
|
417
|
+
}
|
|
418
|
+
tangentAt(index) {
|
|
419
|
+
return this.geometry[index.curveIndex].tangentAt(index.parameterValue);
|
|
420
|
+
}
|
|
375
421
|
static mapPathCommand(part, mapping) {
|
|
376
422
|
switch (part.kind) {
|
|
377
423
|
case PathCommandType.MoveTo:
|
|
@@ -415,18 +461,85 @@ class Path {
|
|
|
415
461
|
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
|
416
462
|
}
|
|
417
463
|
// Creates a new path by joining [other] to the end of this path
|
|
418
|
-
union(other
|
|
464
|
+
union(other,
|
|
465
|
+
// allowReverse: true iff reversing other or this is permitted if it means
|
|
466
|
+
// no moveTo command is necessary when unioning the paths.
|
|
467
|
+
options = { allowReverse: true }) {
|
|
419
468
|
if (!other) {
|
|
420
469
|
return this;
|
|
421
470
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
471
|
+
const thisEnd = this.getEndPoint();
|
|
472
|
+
let newParts = [];
|
|
473
|
+
if (thisEnd.eq(other.startPoint)) {
|
|
474
|
+
newParts = this.parts.concat(other.parts);
|
|
475
|
+
}
|
|
476
|
+
else if (options.allowReverse && this.startPoint.eq(other.getEndPoint())) {
|
|
477
|
+
return other.union(this, { allowReverse: false });
|
|
478
|
+
}
|
|
479
|
+
else if (options.allowReverse && this.startPoint.eq(other.startPoint)) {
|
|
480
|
+
return this.union(other.reversed(), { allowReverse: false });
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
newParts = [
|
|
484
|
+
...this.parts,
|
|
485
|
+
{
|
|
486
|
+
kind: PathCommandType.MoveTo,
|
|
487
|
+
point: other.startPoint,
|
|
488
|
+
},
|
|
489
|
+
...other.parts,
|
|
490
|
+
];
|
|
491
|
+
}
|
|
492
|
+
return new Path(this.startPoint, newParts);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* @returns a version of this path with the direction reversed.
|
|
496
|
+
*
|
|
497
|
+
* Example:
|
|
498
|
+
* ```ts,runnable,console
|
|
499
|
+
* import {Path} from '@js-draw/math';
|
|
500
|
+
* console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
reversed() {
|
|
504
|
+
const newStart = this.getEndPoint();
|
|
505
|
+
const newParts = [];
|
|
506
|
+
let lastPoint = this.startPoint;
|
|
507
|
+
for (const part of this.parts) {
|
|
508
|
+
switch (part.kind) {
|
|
509
|
+
case PathCommandType.LineTo:
|
|
510
|
+
case PathCommandType.MoveTo:
|
|
511
|
+
newParts.push({
|
|
512
|
+
kind: part.kind,
|
|
513
|
+
point: lastPoint,
|
|
514
|
+
});
|
|
515
|
+
lastPoint = part.point;
|
|
516
|
+
break;
|
|
517
|
+
case PathCommandType.CubicBezierTo:
|
|
518
|
+
newParts.push({
|
|
519
|
+
kind: part.kind,
|
|
520
|
+
controlPoint1: part.controlPoint2,
|
|
521
|
+
controlPoint2: part.controlPoint1,
|
|
522
|
+
endPoint: lastPoint,
|
|
523
|
+
});
|
|
524
|
+
lastPoint = part.endPoint;
|
|
525
|
+
break;
|
|
526
|
+
case PathCommandType.QuadraticBezierTo:
|
|
527
|
+
newParts.push({
|
|
528
|
+
kind: part.kind,
|
|
529
|
+
controlPoint: part.controlPoint,
|
|
530
|
+
endPoint: lastPoint,
|
|
531
|
+
});
|
|
532
|
+
lastPoint = part.endPoint;
|
|
533
|
+
break;
|
|
534
|
+
default:
|
|
535
|
+
{
|
|
536
|
+
const exhaustivenessCheck = part;
|
|
537
|
+
return exhaustivenessCheck;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
newParts.reverse();
|
|
542
|
+
return new Path(newStart, newParts);
|
|
430
543
|
}
|
|
431
544
|
getEndPoint() {
|
|
432
545
|
if (this.parts.length === 0) {
|
|
@@ -518,6 +631,52 @@ class Path {
|
|
|
518
631
|
// Even? Probably no intersection.
|
|
519
632
|
return false;
|
|
520
633
|
}
|
|
634
|
+
/** @returns true if all points on this are equivalent to the points on `other` */
|
|
635
|
+
eq(other, tolerance) {
|
|
636
|
+
if (other.parts.length !== this.parts.length) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
for (let i = 0; i < this.parts.length; i++) {
|
|
640
|
+
const part1 = this.parts[i];
|
|
641
|
+
const part2 = other.parts[i];
|
|
642
|
+
switch (part1.kind) {
|
|
643
|
+
case PathCommandType.LineTo:
|
|
644
|
+
case PathCommandType.MoveTo:
|
|
645
|
+
if (part1.kind !== part2.kind) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
else if (!part1.point.eq(part2.point, tolerance)) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
case PathCommandType.CubicBezierTo:
|
|
653
|
+
if (part1.kind !== part2.kind) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance)
|
|
657
|
+
|| !part1.controlPoint2.eq(part2.controlPoint2, tolerance)
|
|
658
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
case PathCommandType.QuadraticBezierTo:
|
|
663
|
+
if (part1.kind !== part2.kind) {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance)
|
|
667
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
break;
|
|
671
|
+
default:
|
|
672
|
+
{
|
|
673
|
+
const exhaustivenessCheck = part1;
|
|
674
|
+
return exhaustivenessCheck;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
521
680
|
/**
|
|
522
681
|
* Returns a path that outlines `rect`.
|
|
523
682
|
*
|
|
@@ -591,15 +750,15 @@ class Path {
|
|
|
591
750
|
const absoluteCommandParts = [];
|
|
592
751
|
const relativeCommandParts = [];
|
|
593
752
|
const makeAbsCommand = !prevPoint || onlyAbsCommands;
|
|
594
|
-
const roundedPrevX = prevPoint ? (0,
|
|
595
|
-
const roundedPrevY = prevPoint ? (0,
|
|
753
|
+
const roundedPrevX = prevPoint ? (0, toRoundedString_1.default)(prevPoint.x) : '';
|
|
754
|
+
const roundedPrevY = prevPoint ? (0, toRoundedString_1.default)(prevPoint.y) : '';
|
|
596
755
|
for (const point of points) {
|
|
597
|
-
const xComponent = (0,
|
|
598
|
-
const yComponent = (0,
|
|
756
|
+
const xComponent = (0, toRoundedString_1.default)(point.x);
|
|
757
|
+
const yComponent = (0, toRoundedString_1.default)(point.y);
|
|
599
758
|
// Relative commands are often shorter as strings than absolute commands.
|
|
600
759
|
if (!makeAbsCommand) {
|
|
601
|
-
const xComponentRelative = (0,
|
|
602
|
-
const yComponentRelative = (0,
|
|
760
|
+
const xComponentRelative = (0, toStringOfSamePrecision_1.default)(point.x - prevPoint.x, xComponent, roundedPrevX, roundedPrevY);
|
|
761
|
+
const yComponentRelative = (0, toStringOfSamePrecision_1.default)(point.y - prevPoint.y, yComponent, roundedPrevX, roundedPrevY);
|
|
603
762
|
// No need for an additional separator if it starts with a '-'
|
|
604
763
|
if (yComponentRelative.charAt(0) === '-') {
|
|
605
764
|
relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import { Point2 } from '../Vec2';
|
|
2
2
|
import Vec3 from '../Vec3';
|
|
3
|
-
import Abstract2DShape from './Abstract2DShape';
|
|
4
3
|
import LineSegment2 from './LineSegment2';
|
|
4
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
|
5
5
|
import Rect2 from './Rect2';
|
|
6
6
|
/**
|
|
7
7
|
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
|
8
8
|
*
|
|
9
9
|
* Access the internal `Point2` using the `p` property.
|
|
10
10
|
*/
|
|
11
|
-
declare class PointShape2D extends
|
|
11
|
+
declare class PointShape2D extends Parameterized2DShape {
|
|
12
12
|
readonly p: Point2;
|
|
13
13
|
constructor(p: Point2);
|
|
14
14
|
signedDistance(point: Vec3): number;
|
|
15
|
-
|
|
15
|
+
argIntersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): number[];
|
|
16
16
|
getTightBoundingBox(): Rect2;
|
|
17
|
+
at(_t: number): Vec3;
|
|
18
|
+
/**
|
|
19
|
+
* Returns an arbitrary unit-length vector.
|
|
20
|
+
*/
|
|
21
|
+
normalAt(_t: number): Vec3;
|
|
22
|
+
tangentAt(_t: number): Vec3;
|
|
23
|
+
splitAt(_t: number): [PointShape2D];
|
|
24
|
+
nearestPointTo(_point: Point2): {
|
|
25
|
+
point: Vec3;
|
|
26
|
+
parameterValue: number;
|
|
27
|
+
};
|
|
17
28
|
}
|
|
18
29
|
export default PointShape2D;
|