@js-draw/math 1.11.1 → 1.17.0
Sign up to get free protection for your applications and to get access to all the features.
- 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;
|