@js-draw/math 1.21.3 → 1.22.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/build-config.json +1 -1
- package/dist/cjs/Color4.js +2 -2
- package/dist/cjs/Mat33.d.ts +1 -11
- package/dist/cjs/Mat33.js +8 -24
- package/dist/cjs/Vec3.js +9 -7
- package/dist/cjs/shapes/BezierJSWrapper.js +20 -13
- package/dist/cjs/shapes/LineSegment2.js +13 -17
- package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
- package/dist/cjs/shapes/Path.js +49 -47
- package/dist/cjs/shapes/Rect2.js +13 -15
- package/dist/cjs/shapes/Triangle.js +4 -5
- package/dist/cjs/utils/convexHull2Of.js +3 -3
- package/dist/mjs/Color4.mjs +2 -2
- package/dist/mjs/Mat33.d.ts +1 -11
- package/dist/mjs/Mat33.mjs +8 -24
- package/dist/mjs/Vec3.mjs +9 -7
- package/dist/mjs/shapes/BezierJSWrapper.mjs +20 -13
- package/dist/mjs/shapes/LineSegment2.mjs +13 -17
- package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
- package/dist/mjs/shapes/Path.mjs +49 -47
- package/dist/mjs/shapes/Rect2.mjs +13 -15
- package/dist/mjs/shapes/Triangle.mjs +4 -5
- package/dist/mjs/utils/convexHull2Of.mjs +3 -3
- package/dist-test/test_imports/test-require.cjs +1 -1
- package/package.json +3 -3
- package/src/Color4.test.ts +16 -21
- package/src/Color4.ts +22 -17
- package/src/Mat33.fromCSSMatrix.test.ts +31 -45
- package/src/Mat33.test.ts +58 -96
- package/src/Mat33.ts +61 -104
- package/src/Vec2.test.ts +3 -3
- package/src/Vec3.test.ts +2 -3
- package/src/Vec3.ts +34 -58
- package/src/lib.ts +0 -2
- package/src/polynomial/solveQuadratic.test.ts +39 -13
- package/src/polynomial/solveQuadratic.ts +5 -6
- package/src/rounding/cleanUpNumber.test.ts +1 -1
- package/src/rounding/constants.ts +1 -3
- package/src/rounding/getLenAfterDecimal.ts +1 -2
- package/src/rounding/lib.ts +1 -2
- package/src/rounding/toRoundedString.test.ts +1 -1
- package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
- package/src/rounding/toStringOfSamePrecision.ts +1 -1
- package/src/shapes/BezierJSWrapper.ts +54 -37
- package/src/shapes/CubicBezier.ts +3 -3
- package/src/shapes/LineSegment2.test.ts +24 -17
- package/src/shapes/LineSegment2.ts +26 -29
- package/src/shapes/Parameterized2DShape.ts +5 -4
- package/src/shapes/Path.fromString.test.ts +5 -5
- package/src/shapes/Path.test.ts +122 -120
- package/src/shapes/Path.toString.test.ts +7 -7
- package/src/shapes/Path.ts +378 -352
- package/src/shapes/PointShape2D.ts +3 -3
- package/src/shapes/QuadraticBezier.test.ts +27 -21
- package/src/shapes/QuadraticBezier.ts +4 -9
- package/src/shapes/Rect2.test.ts +44 -75
- package/src/shapes/Rect2.ts +30 -35
- package/src/shapes/Triangle.test.ts +31 -29
- package/src/shapes/Triangle.ts +17 -18
- package/src/utils/convexHull2Of.test.ts +54 -15
- package/src/utils/convexHull2Of.ts +9 -7
- package/tsconfig.json +1 -3
- package/typedoc.json +2 -2
package/dist/mjs/Color4.mjs
CHANGED
@@ -55,7 +55,7 @@ export class Color4 {
|
|
55
55
|
// Each character is a component
|
56
56
|
const components = hexString.split('');
|
57
57
|
// Convert to RRGGBBAA or RRGGBB format
|
58
|
-
hexString = components.map(component => `${component}0`).join('');
|
58
|
+
hexString = components.map((component) => `${component}0`).join('');
|
59
59
|
}
|
60
60
|
if (hexString.length === 6) {
|
61
61
|
// Alpha component
|
@@ -165,7 +165,7 @@ export class Color4 {
|
|
165
165
|
// - https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
166
166
|
// - https://stackoverflow.com/a/9733420
|
167
167
|
// Normalize the components, as per above
|
168
|
-
const components = [this.r, this.g, this.b].map(component => {
|
168
|
+
const components = [this.r, this.g, this.b].map((component) => {
|
169
169
|
if (component < 0.03928) {
|
170
170
|
return component / 12.92;
|
171
171
|
}
|
package/dist/mjs/Mat33.d.ts
CHANGED
@@ -3,17 +3,7 @@ import Vec3 from './Vec3';
|
|
3
3
|
/**
|
4
4
|
* See {@link Mat33.toArray}.
|
5
5
|
*/
|
6
|
-
export type Mat33Array = [
|
7
|
-
number,
|
8
|
-
number,
|
9
|
-
number,
|
10
|
-
number,
|
11
|
-
number,
|
12
|
-
number,
|
13
|
-
number,
|
14
|
-
number,
|
15
|
-
number
|
16
|
-
];
|
6
|
+
export type Mat33Array = [number, number, number, number, number, number, number, number, number];
|
17
7
|
/**
|
18
8
|
* Represents a three dimensional linear transformation or
|
19
9
|
* a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
package/dist/mjs/Mat33.mjs
CHANGED
@@ -70,11 +70,7 @@ export class Mat33 {
|
|
70
70
|
this.c2 = c2;
|
71
71
|
this.c3 = c3;
|
72
72
|
this.cachedInverse = undefined;
|
73
|
-
this.rows = [
|
74
|
-
Vec3.of(a1, a2, a3),
|
75
|
-
Vec3.of(b1, b2, b3),
|
76
|
-
Vec3.of(c1, c2, c3),
|
77
|
-
];
|
73
|
+
this.rows = [Vec3.of(a1, a2, a3), Vec3.of(b1, b2, b3), Vec3.of(c1, c2, c3)];
|
78
74
|
}
|
79
75
|
/**
|
80
76
|
* Creates a matrix from the given rows:
|
@@ -106,16 +102,8 @@ export class Mat33 {
|
|
106
102
|
if (this.cachedInverse !== undefined) {
|
107
103
|
return this.cachedInverse;
|
108
104
|
}
|
109
|
-
const toIdentity = [
|
110
|
-
|
111
|
-
this.rows[1],
|
112
|
-
this.rows[2],
|
113
|
-
];
|
114
|
-
const toResult = [
|
115
|
-
Vec3.unitX,
|
116
|
-
Vec3.unitY,
|
117
|
-
Vec3.unitZ,
|
118
|
-
];
|
105
|
+
const toIdentity = [this.rows[0], this.rows[1], this.rows[2]];
|
106
|
+
const toResult = [Vec3.unitX, Vec3.unitY, Vec3.unitZ];
|
119
107
|
// Convert toIdentity to the identity matrix and
|
120
108
|
// toResult to the inverse through elementary row operations
|
121
109
|
for (let cursor = 0; cursor < 3; cursor++) {
|
@@ -311,11 +299,7 @@ export class Mat33 {
|
|
311
299
|
* ```
|
312
300
|
*/
|
313
301
|
toArray() {
|
314
|
-
return [
|
315
|
-
this.a1, this.a2, this.a3,
|
316
|
-
this.b1, this.b2, this.b3,
|
317
|
-
this.c1, this.c2, this.c3,
|
318
|
-
];
|
302
|
+
return [this.a1, this.a2, this.a3, this.b1, this.b2, this.b3, this.c1, this.c2, this.c3];
|
319
303
|
}
|
320
304
|
/**
|
321
305
|
* Returns a new `Mat33` where each entry is the output of the function
|
@@ -421,7 +405,7 @@ export class Mat33 {
|
|
421
405
|
return Mat33.identity;
|
422
406
|
}
|
423
407
|
const parseArguments = (argumentString) => {
|
424
|
-
const parsed = argumentString.split(/[, \t\n]+/g).map(argString => {
|
408
|
+
const parsed = argumentString.split(/[, \t\n]+/g).map((argString) => {
|
425
409
|
// Handle trailing spaces/commands
|
426
410
|
if (argString.trim() === '') {
|
427
411
|
return null;
|
@@ -432,7 +416,7 @@ export class Mat33 {
|
|
432
416
|
argString = argString.substring(0, argString.length - 1);
|
433
417
|
}
|
434
418
|
// Remove trailing px units.
|
435
|
-
argString = argString.replace(/px$/
|
419
|
+
argString = argString.replace(/px$/gi, '');
|
436
420
|
const numberExp = /^[-]?\d*(?:\.\d*)?(?:[eE][-+]?\d+)?$/i;
|
437
421
|
if (!numberExp.exec(argString)) {
|
438
422
|
throw new Error(`All arguments to transform functions must be numeric (state: ${JSON.stringify({
|
@@ -446,7 +430,7 @@ export class Mat33 {
|
|
446
430
|
}
|
447
431
|
return argNumber;
|
448
432
|
});
|
449
|
-
return parsed.filter(n => n !== null);
|
433
|
+
return parsed.filter((n) => n !== null);
|
450
434
|
};
|
451
435
|
const keywordToAction = {
|
452
436
|
matrix: (matrixData) => {
|
@@ -496,7 +480,7 @@ export class Mat33 {
|
|
496
480
|
};
|
497
481
|
// A command (\w+)
|
498
482
|
// followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
|
499
|
-
const partRegex = /\s*(\w+)\s*\(([^)]*)\)/
|
483
|
+
const partRegex = /\s*(\w+)\s*\(([^)]*)\)/gi;
|
500
484
|
let match;
|
501
485
|
let matrix = null;
|
502
486
|
while ((match = partRegex.exec(cssString)) !== null) {
|
package/dist/mjs/Vec3.mjs
CHANGED
@@ -103,9 +103,9 @@ class Vec3Impl {
|
|
103
103
|
return [this.x, this.y, this.z];
|
104
104
|
}
|
105
105
|
eq(other, fuzz = defaultEqlTolerance) {
|
106
|
-
return (Math.abs(other.x - this.x) <= fuzz
|
107
|
-
|
108
|
-
|
106
|
+
return (Math.abs(other.x - this.x) <= fuzz &&
|
107
|
+
Math.abs(other.y - this.y) <= fuzz &&
|
108
|
+
Math.abs(other.z - this.z) <= fuzz);
|
109
109
|
}
|
110
110
|
toString() {
|
111
111
|
return `Vec(${this.x}, ${this.y}, ${this.z})`;
|
@@ -116,7 +116,9 @@ class Vec2Impl {
|
|
116
116
|
this.x = x;
|
117
117
|
this.y = y;
|
118
118
|
}
|
119
|
-
get z() {
|
119
|
+
get z() {
|
120
|
+
return 0;
|
121
|
+
}
|
120
122
|
get xy() {
|
121
123
|
// Useful for APIs that behave differently if .z is present.
|
122
124
|
return {
|
@@ -213,9 +215,9 @@ class Vec2Impl {
|
|
213
215
|
return [this.x, this.y, 0];
|
214
216
|
}
|
215
217
|
eq(other, fuzz = defaultEqlTolerance) {
|
216
|
-
return (Math.abs(other.x - this.x) <= fuzz
|
217
|
-
|
218
|
-
|
218
|
+
return (Math.abs(other.x - this.x) <= fuzz &&
|
219
|
+
Math.abs(other.y - this.y) <= fuzz &&
|
220
|
+
Math.abs(other.z) <= fuzz);
|
219
221
|
}
|
220
222
|
toString() {
|
221
223
|
return `Vec(${this.x}, ${this.y})`;
|
@@ -34,7 +34,7 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
34
34
|
}
|
35
35
|
getBezier() {
|
36
36
|
if (!__classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f")) {
|
37
|
-
__classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, new Bezier(this.getPoints().map(p => p.xy)), "f");
|
37
|
+
__classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, new Bezier(this.getPoints().map((p) => p.xy)), "f");
|
38
38
|
}
|
39
39
|
return __classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f");
|
40
40
|
}
|
@@ -88,10 +88,12 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
88
88
|
const asLine = LineSegment2.ofSmallestContainingPoints(this.getPoints());
|
89
89
|
if (asLine) {
|
90
90
|
const intersection = asLine.intersectsLineSegment(line);
|
91
|
-
return intersection.map(p => this.nearestPointTo(p).parameterValue);
|
91
|
+
return intersection.map((p) => this.nearestPointTo(p).parameterValue);
|
92
92
|
}
|
93
93
|
const bezier = this.getBezier();
|
94
|
-
return bezier
|
94
|
+
return bezier
|
95
|
+
.intersects(line)
|
96
|
+
.map((t) => {
|
95
97
|
// We're using the .intersects(line) function, which is documented
|
96
98
|
// to always return numbers. However, to satisfy the type checker (and
|
97
99
|
// possibly improperly-defined types),
|
@@ -100,12 +102,12 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
100
102
|
}
|
101
103
|
const point = Vec2.ofXY(this.at(t));
|
102
104
|
// Ensure that the intersection is on the line segment
|
103
|
-
if (point.distanceTo(line.p1) > line.length
|
104
|
-
|| point.distanceTo(line.p2) > line.length) {
|
105
|
+
if (point.distanceTo(line.p1) > line.length || point.distanceTo(line.p2) > line.length) {
|
105
106
|
return null;
|
106
107
|
}
|
107
108
|
return t;
|
108
|
-
})
|
109
|
+
})
|
110
|
+
.filter((entry) => entry !== null);
|
109
111
|
}
|
110
112
|
splitAt(t) {
|
111
113
|
if (t <= 0 || t >= 1) {
|
@@ -114,8 +116,8 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
114
116
|
const bezier = this.getBezier();
|
115
117
|
const split = bezier.split(t);
|
116
118
|
return [
|
117
|
-
new BezierJSWrapperImpl(split.left.points.map(point => Vec2.ofXY(point)), split.left),
|
118
|
-
new BezierJSWrapperImpl(split.right.points.map(point => Vec2.ofXY(point)), split.right),
|
119
|
+
new BezierJSWrapperImpl(split.left.points.map((point) => Vec2.ofXY(point)), split.left),
|
120
|
+
new BezierJSWrapperImpl(split.right.points.map((point) => Vec2.ofXY(point)), split.right),
|
119
121
|
];
|
120
122
|
}
|
121
123
|
nearestPointTo(point) {
|
@@ -159,16 +161,19 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
159
161
|
const b = this.at(t);
|
160
162
|
const bPrime = this.derivativeAt(t);
|
161
163
|
const bPrimePrime = this.secondDerivativeAt(t);
|
162
|
-
return (2 * bPrime.x * bPrime.x +
|
163
|
-
|
164
|
+
return (2 * bPrime.x * bPrime.x +
|
165
|
+
2 * b.x * bPrimePrime.x -
|
166
|
+
2 * point.x * bPrimePrime.x +
|
167
|
+
2 * bPrime.y * bPrime.y +
|
168
|
+
2 * b.y * bPrimePrime.y -
|
169
|
+
2 * point.y * bPrimePrime.y);
|
164
170
|
};
|
165
171
|
// Because we're zeroing f'(t), we also need to be able to compute it:
|
166
172
|
const derivativeAt = (t) => {
|
167
173
|
// f'(t) = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
|
168
174
|
const b = this.at(t);
|
169
175
|
const bPrime = this.derivativeAt(t);
|
170
|
-
return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x
|
171
|
-
+ 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
|
176
|
+
return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
|
172
177
|
};
|
173
178
|
const iterate = () => {
|
174
179
|
const slope = secondDerivativeAt(t);
|
@@ -219,7 +224,9 @@ export class BezierJSWrapper extends Parameterized2DShape {
|
|
219
224
|
return result;
|
220
225
|
}
|
221
226
|
toString() {
|
222
|
-
return `Bézier(${this.getPoints()
|
227
|
+
return `Bézier(${this.getPoints()
|
228
|
+
.map((point) => point.toString())
|
229
|
+
.join(', ')})`;
|
223
230
|
}
|
224
231
|
}
|
225
232
|
_BezierJSWrapper_bezierJs = new WeakMap();
|
@@ -40,7 +40,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
40
40
|
static ofSmallestContainingPoints(points) {
|
41
41
|
if (points.length <= 1)
|
42
42
|
return null;
|
43
|
-
const sorted = [...points].sort((a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y);
|
43
|
+
const sorted = [...points].sort((a, b) => (a.x !== b.x ? a.x - b.x : a.y - b.y));
|
44
44
|
const line = new LineSegment2(sorted[0], sorted[sorted.length - 1]);
|
45
45
|
for (const point of sorted) {
|
46
46
|
if (!line.containsPoint(point)) {
|
@@ -90,10 +90,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
90
90
|
if (t <= 0 || t >= 1) {
|
91
91
|
return [this];
|
92
92
|
}
|
93
|
-
return [
|
94
|
-
new LineSegment2(this.point1, this.at(t)),
|
95
|
-
new LineSegment2(this.at(t), this.point2),
|
96
|
-
];
|
93
|
+
return [new LineSegment2(this.point1, this.at(t)), new LineSegment2(this.at(t), this.point2)];
|
97
94
|
}
|
98
95
|
/**
|
99
96
|
* Returns the intersection of this with another line segment.
|
@@ -143,18 +140,17 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
143
140
|
return null;
|
144
141
|
}
|
145
142
|
const xIntersect = this.point1.x;
|
146
|
-
const yIntersect = (this.point1.x - other.point1.x) * other.direction.y / other.direction.x + other.point1.y;
|
143
|
+
const yIntersect = ((this.point1.x - other.point1.x) * other.direction.y) / other.direction.x + other.point1.y;
|
147
144
|
resultPoint = Vec2.of(xIntersect, yIntersect);
|
148
145
|
resultT = (yIntersect - this.point1.y) / this.direction.y;
|
149
146
|
}
|
150
147
|
else {
|
151
148
|
// From above,
|
152
149
|
// x = ((o₁ᵧ - o₂ᵧ)(d₁ₓd₂ₓ) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
|
153
|
-
const numerator = (
|
154
|
-
|
155
|
-
|
156
|
-
const denominator =
|
157
|
-
- this.direction.y * other.direction.x);
|
150
|
+
const numerator = (this.point1.y - other.point1.y) * this.direction.x * other.direction.x +
|
151
|
+
this.direction.x * other.direction.y * other.point1.x -
|
152
|
+
this.direction.y * other.direction.x * this.point1.x;
|
153
|
+
const denominator = other.direction.y * this.direction.x - this.direction.y * other.direction.x;
|
158
154
|
// Avoid dividing by zero. It means there is no intersection
|
159
155
|
if (denominator === 0) {
|
160
156
|
return null;
|
@@ -170,10 +166,10 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
170
166
|
const resultToP2 = resultPoint.distanceTo(this.point2);
|
171
167
|
const resultToP3 = resultPoint.distanceTo(other.point1);
|
172
168
|
const resultToP4 = resultPoint.distanceTo(other.point2);
|
173
|
-
if (resultToP1 > this.length
|
174
|
-
|
175
|
-
|
176
|
-
|
169
|
+
if (resultToP1 > this.length ||
|
170
|
+
resultToP2 > this.length ||
|
171
|
+
resultToP3 > other.length ||
|
172
|
+
resultToP4 > other.length) {
|
177
173
|
return null;
|
178
174
|
}
|
179
175
|
return {
|
@@ -258,8 +254,8 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
258
254
|
}
|
259
255
|
const tolerance = options?.tolerance;
|
260
256
|
const ignoreDirection = options?.ignoreDirection ?? true;
|
261
|
-
return ((other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance))
|
262
|
-
|
257
|
+
return ((other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance)) ||
|
258
|
+
(ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance)));
|
263
259
|
}
|
264
260
|
}
|
265
261
|
export default LineSegment2;
|
@@ -7,7 +7,7 @@ import Abstract2DShape from './Abstract2DShape.mjs';
|
|
7
7
|
*/
|
8
8
|
export class Parameterized2DShape extends Abstract2DShape {
|
9
9
|
intersectsLineSegment(line) {
|
10
|
-
return this.argIntersectsLineSegment(line).map(t => this.at(t));
|
10
|
+
return this.argIntersectsLineSegment(line).map((t) => this.at(t));
|
11
11
|
}
|
12
12
|
}
|
13
13
|
export default Parameterized2DShape;
|
package/dist/mjs/shapes/Path.mjs
CHANGED
@@ -319,9 +319,7 @@ export class Path {
|
|
319
319
|
const maxRaymarchSteps = 8;
|
320
320
|
// Start raymarching from each of these points. This allows detection of multiple
|
321
321
|
// intersections.
|
322
|
-
const startPoints = [
|
323
|
-
line.p1, ...additionalRaymarchStartPoints, line.p2
|
324
|
-
];
|
322
|
+
const startPoints = [line.p1, ...additionalRaymarchStartPoints, line.p2];
|
325
323
|
// Converts a point ON THE LINE to a parameter
|
326
324
|
const pointToParameter = (point) => {
|
327
325
|
// Because line.direction is a unit vector, this computes the length
|
@@ -427,7 +425,9 @@ export class Path {
|
|
427
425
|
return [];
|
428
426
|
}
|
429
427
|
if (this.parts.length === 0) {
|
430
|
-
return new Path(this.startPoint, [
|
428
|
+
return new Path(this.startPoint, [
|
429
|
+
{ kind: PathCommandType.MoveTo, point: this.startPoint },
|
430
|
+
]).intersection(line, strokeRadius);
|
431
431
|
}
|
432
432
|
let index = 0;
|
433
433
|
for (const part of this.geometry) {
|
@@ -448,7 +448,7 @@ export class Path {
|
|
448
448
|
const doRaymarching = strokeRadius && strokeRadius > 1e-8;
|
449
449
|
if (doRaymarching) {
|
450
450
|
// Starting points for raymarching (in addition to the end points of the line).
|
451
|
-
const startPoints = result.map(intersection => intersection.point);
|
451
|
+
const startPoints = result.map((intersection) => intersection.point);
|
452
452
|
result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
|
453
453
|
}
|
454
454
|
return result;
|
@@ -501,7 +501,8 @@ export class Path {
|
|
501
501
|
*/
|
502
502
|
spliced(deleteFrom, deleteTo, insert, options) {
|
503
503
|
const isBeforeOrEqual = (a, b) => {
|
504
|
-
return a.curveIndex < b.curveIndex ||
|
504
|
+
return (a.curveIndex < b.curveIndex ||
|
505
|
+
(a.curveIndex === b.curveIndex && a.parameterValue <= b.parameterValue));
|
505
506
|
};
|
506
507
|
if (isBeforeOrEqual(deleteFrom, deleteTo)) {
|
507
508
|
// deleteFrom deleteTo
|
@@ -539,15 +540,15 @@ export class Path {
|
|
539
540
|
//
|
540
541
|
// Bounds checking & reversal.
|
541
542
|
//
|
542
|
-
while (splitAt.length > 0
|
543
|
-
|
544
|
-
|
543
|
+
while (splitAt.length > 0 &&
|
544
|
+
splitAt[splitAt.length - 1].curveIndex >= this.parts.length - 1 &&
|
545
|
+
splitAt[splitAt.length - 1].parameterValue >= 1) {
|
545
546
|
splitAt.pop();
|
546
547
|
}
|
547
548
|
splitAt.reverse(); // .reverse() <-- We're `.pop`ing from the end
|
548
|
-
while (splitAt.length > 0
|
549
|
-
|
550
|
-
|
549
|
+
while (splitAt.length > 0 &&
|
550
|
+
splitAt[splitAt.length - 1].curveIndex <= 0 &&
|
551
|
+
splitAt[splitAt.length - 1].parameterValue <= 0) {
|
551
552
|
splitAt.pop();
|
552
553
|
}
|
553
554
|
if (splitAt.length === 0 || this.parts.length === 0) {
|
@@ -738,7 +739,7 @@ export class Path {
|
|
738
739
|
if (affineTransfm.isIdentity()) {
|
739
740
|
return this;
|
740
741
|
}
|
741
|
-
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
742
|
+
return this.mapPoints((point) => affineTransfm.transformVec2(point));
|
742
743
|
}
|
743
744
|
/**
|
744
745
|
* @internal
|
@@ -827,11 +828,10 @@ export class Path {
|
|
827
828
|
});
|
828
829
|
lastPoint = part.endPoint;
|
829
830
|
break;
|
830
|
-
default:
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
}
|
831
|
+
default: {
|
832
|
+
const exhaustivenessCheck = part;
|
833
|
+
return exhaustivenessCheck;
|
834
|
+
}
|
835
835
|
}
|
836
836
|
}
|
837
837
|
newParts.reverse();
|
@@ -843,7 +843,8 @@ export class Path {
|
|
843
843
|
return this.startPoint;
|
844
844
|
}
|
845
845
|
const lastPart = this.parts[this.parts.length - 1];
|
846
|
-
if (lastPart.kind === PathCommandType.QuadraticBezierTo ||
|
846
|
+
if (lastPart.kind === PathCommandType.QuadraticBezierTo ||
|
847
|
+
lastPart.kind === PathCommandType.CubicBezierTo) {
|
847
848
|
return lastPart.endPoint;
|
848
849
|
}
|
849
850
|
else {
|
@@ -952,9 +953,9 @@ export class Path {
|
|
952
953
|
if (part1.kind !== part2.kind) {
|
953
954
|
return false;
|
954
955
|
}
|
955
|
-
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance)
|
956
|
-
|
957
|
-
|
956
|
+
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance) ||
|
957
|
+
!part1.controlPoint2.eq(part2.controlPoint2, tolerance) ||
|
958
|
+
!part1.endPoint.eq(part2.endPoint, tolerance)) {
|
958
959
|
return false;
|
959
960
|
}
|
960
961
|
break;
|
@@ -962,16 +963,15 @@ export class Path {
|
|
962
963
|
if (part1.kind !== part2.kind) {
|
963
964
|
return false;
|
964
965
|
}
|
965
|
-
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance)
|
966
|
-
|
966
|
+
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance) ||
|
967
|
+
!part1.endPoint.eq(part2.endPoint, tolerance)) {
|
967
968
|
return false;
|
968
969
|
}
|
969
970
|
break;
|
970
|
-
default:
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
}
|
971
|
+
default: {
|
972
|
+
const exhaustivenessCheck = part1;
|
973
|
+
return exhaustivenessCheck;
|
974
|
+
}
|
975
975
|
}
|
976
976
|
}
|
977
977
|
return true;
|
@@ -993,11 +993,7 @@ export class Path {
|
|
993
993
|
const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
|
994
994
|
const innerRect = Rect2.fromCorners(rect.topLeft.plus(cornerToEdge), rect.bottomRight.minus(cornerToEdge));
|
995
995
|
const outerRect = Rect2.fromCorners(rect.topLeft.minus(cornerToEdge), rect.bottomRight.plus(cornerToEdge));
|
996
|
-
corners = [
|
997
|
-
innerRect.corners[3],
|
998
|
-
...innerRect.corners,
|
999
|
-
...outerRect.corners.reverse(),
|
1000
|
-
];
|
996
|
+
corners = [innerRect.corners[3], ...innerRect.corners, ...outerRect.corners.reverse()];
|
1001
997
|
startPoint = outerRect.corners[3];
|
1002
998
|
}
|
1003
999
|
else {
|
@@ -1180,19 +1176,23 @@ export class Path {
|
|
1180
1176
|
});
|
1181
1177
|
};
|
1182
1178
|
const commandArgCounts = {
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1179
|
+
m: 1,
|
1180
|
+
l: 1,
|
1181
|
+
c: 3,
|
1182
|
+
q: 2,
|
1183
|
+
z: 0,
|
1184
|
+
h: 1,
|
1185
|
+
v: 1,
|
1190
1186
|
};
|
1191
1187
|
// Each command: Command character followed by anything that isn't a command character
|
1192
|
-
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/
|
1188
|
+
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/gi;
|
1193
1189
|
let current;
|
1194
1190
|
while ((current = commandExp.exec(pathString)) !== null) {
|
1195
|
-
const argParts = current[2]
|
1191
|
+
const argParts = current[2]
|
1192
|
+
.trim()
|
1193
|
+
.split(/[^0-9Ee.-]/)
|
1194
|
+
.filter((part) => part.length > 0)
|
1195
|
+
.reduce((accumualtor, current) => {
|
1196
1196
|
// As of 09/2022, iOS Safari doesn't support support lookbehind in regular
|
1197
1197
|
// expressions. As such, we need an alternative.
|
1198
1198
|
// Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
|
@@ -1202,10 +1202,10 @@ export class Path {
|
|
1202
1202
|
if (parts[0] !== '') {
|
1203
1203
|
accumualtor.push(parts[0]);
|
1204
1204
|
}
|
1205
|
-
accumualtor.push(...parts.slice(1).map(part => `-${part}`));
|
1205
|
+
accumualtor.push(...parts.slice(1).map((part) => `-${part}`));
|
1206
1206
|
return accumualtor;
|
1207
1207
|
}, []);
|
1208
|
-
let numericArgs = argParts.map(arg => parseFloat(arg));
|
1208
|
+
let numericArgs = argParts.map((arg) => parseFloat(arg));
|
1209
1209
|
let commandChar = current[1].toLowerCase();
|
1210
1210
|
let uppercaseCommand = current[1] !== commandChar;
|
1211
1211
|
// Convert commands that don't take points into commands that do.
|
@@ -1233,7 +1233,8 @@ export class Path {
|
|
1233
1233
|
commandChar = 'l';
|
1234
1234
|
}
|
1235
1235
|
const commandArgCount = commandArgCounts[commandChar] ?? 0;
|
1236
|
-
const allArgs = numericArgs
|
1236
|
+
const allArgs = numericArgs
|
1237
|
+
.reduce((accumulator, current, index, parts) => {
|
1237
1238
|
if (index % 2 !== 0) {
|
1238
1239
|
const currentAsFloat = current;
|
1239
1240
|
const prevAsFloat = parts[index - 1];
|
@@ -1242,7 +1243,8 @@ export class Path {
|
|
1242
1243
|
else {
|
1243
1244
|
return accumulator;
|
1244
1245
|
}
|
1245
|
-
}, [])
|
1246
|
+
}, [])
|
1247
|
+
.map((coordinate, index) => {
|
1246
1248
|
// Lowercase commands are relative, uppercase commands use absolute
|
1247
1249
|
// positioning
|
1248
1250
|
let newPos;
|
@@ -34,13 +34,16 @@ export class Rect2 extends Abstract2DShape {
|
|
34
34
|
return new Rect2(this.x, this.y, size.x, size.y);
|
35
35
|
}
|
36
36
|
containsPoint(other) {
|
37
|
-
return this.x <= other.x &&
|
38
|
-
|
37
|
+
return (this.x <= other.x &&
|
38
|
+
this.y <= other.y &&
|
39
|
+
this.x + this.w >= other.x &&
|
40
|
+
this.y + this.h >= other.y);
|
39
41
|
}
|
40
42
|
containsRect(other) {
|
41
|
-
return this.x <= other.x &&
|
42
|
-
|
43
|
-
|
43
|
+
return (this.x <= other.x &&
|
44
|
+
this.y <= other.y &&
|
45
|
+
this.x + this.w >= other.x + other.w &&
|
46
|
+
this.y + this.h >= other.y + other.h);
|
44
47
|
}
|
45
48
|
/**
|
46
49
|
* @returns true iff this and `other` overlap
|
@@ -125,7 +128,7 @@ export class Rect2 extends Abstract2DShape {
|
|
125
128
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
126
129
|
}
|
127
130
|
getClosestPointOnBoundaryTo(target) {
|
128
|
-
const closestEdgePoints = this.getEdges().map(edge => {
|
131
|
+
const closestEdgePoints = this.getEdges().map((edge) => {
|
129
132
|
return edge.closestPointTo(target);
|
130
133
|
});
|
131
134
|
let closest = null;
|
@@ -152,15 +155,10 @@ export class Rect2 extends Abstract2DShape {
|
|
152
155
|
return false;
|
153
156
|
}
|
154
157
|
const squareRadius = radius * radius;
|
155
|
-
return this.corners.every(corner => corner.minus(point).magnitudeSquared() < squareRadius);
|
158
|
+
return this.corners.every((corner) => corner.minus(point).magnitudeSquared() < squareRadius);
|
156
159
|
}
|
157
160
|
get corners() {
|
158
|
-
return [
|
159
|
-
this.bottomRight,
|
160
|
-
this.topRight,
|
161
|
-
this.topLeft,
|
162
|
-
this.bottomLeft,
|
163
|
-
];
|
161
|
+
return [this.bottomRight, this.topRight, this.topLeft, this.bottomLeft];
|
164
162
|
}
|
165
163
|
get maxDimension() {
|
166
164
|
return Math.max(this.w, this.h);
|
@@ -201,7 +199,7 @@ export class Rect2 extends Abstract2DShape {
|
|
201
199
|
const result = [];
|
202
200
|
for (const edge of this.getEdges()) {
|
203
201
|
const intersection = edge.intersectsLineSegment(lineSegment);
|
204
|
-
intersection.forEach(point => result.push(point));
|
202
|
+
intersection.forEach((point) => result.push(point));
|
205
203
|
}
|
206
204
|
return result;
|
207
205
|
}
|
@@ -219,7 +217,7 @@ export class Rect2 extends Abstract2DShape {
|
|
219
217
|
// [affineTransform] is a transformation matrix that both scales and **translates**.
|
220
218
|
// the bounding box of this' four corners after transformed by the given affine transformation.
|
221
219
|
transformedBoundingBox(affineTransform) {
|
222
|
-
return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
|
220
|
+
return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
|
223
221
|
}
|
224
222
|
/** @return true iff this is equal to `other ± tolerance` */
|
225
223
|
eq(other, tolerance = 0) {
|
@@ -39,12 +39,12 @@ class Triangle extends Abstract2DShape {
|
|
39
39
|
}
|
40
40
|
// Transform, treating this as composed of 2D points.
|
41
41
|
transformed2DBy(affineTransform) {
|
42
|
-
return this.map(vertex => affineTransform.transformVec2(vertex));
|
42
|
+
return this.map((vertex) => affineTransform.transformVec2(vertex));
|
43
43
|
}
|
44
44
|
// Transforms this by a linear transform --- verticies are treated as
|
45
45
|
// 3D points.
|
46
46
|
transformedBy(linearTransform) {
|
47
|
-
return this.map(vertex => linearTransform.transformVec3(vertex));
|
47
|
+
return this.map((vertex) => linearTransform.transformVec3(vertex));
|
48
48
|
}
|
49
49
|
/**
|
50
50
|
* Returns the sides of this triangle, as an array of `LineSegment2`s.
|
@@ -66,8 +66,7 @@ class Triangle extends Abstract2DShape {
|
|
66
66
|
intersectsLineSegment(lineSegment) {
|
67
67
|
const result = [];
|
68
68
|
for (const edge of this.getEdges()) {
|
69
|
-
edge.intersectsLineSegment(lineSegment)
|
70
|
-
.forEach(point => result.push(point));
|
69
|
+
edge.intersectsLineSegment(lineSegment).forEach((point) => result.push(point));
|
71
70
|
}
|
72
71
|
return result;
|
73
72
|
}
|
@@ -101,7 +100,7 @@ class Triangle extends Abstract2DShape {
|
|
101
100
|
*/
|
102
101
|
signedDistance(point) {
|
103
102
|
const sides = this.getEdges();
|
104
|
-
const distances = sides.map(side => side.distance(point));
|
103
|
+
const distances = sides.map((side) => side.distance(point));
|
105
104
|
const distance = Math.min(...distances);
|
106
105
|
// If the point is in this' interior, signedDistance must return a negative
|
107
106
|
// number.
|
@@ -10,9 +10,9 @@ const convexHull2Of = (points) => {
|
|
10
10
|
return [];
|
11
11
|
}
|
12
12
|
// 1. Start with a vertex on the hull
|
13
|
-
const lowestPoint = points.reduce((lowest, current) => current.y < lowest.y ? current : lowest, points[0]);
|
13
|
+
const lowestPoint = points.reduce((lowest, current) => (current.y < lowest.y ? current : lowest), points[0]);
|
14
14
|
const vertices = [lowestPoint];
|
15
|
-
let toProcess = [...points.filter(p => !p.eq(lowestPoint))];
|
15
|
+
let toProcess = [...points.filter((p) => !p.eq(lowestPoint))];
|
16
16
|
let lastBaseDirection = Vec2.of(-1, 0);
|
17
17
|
// 2. Find the point with greatest angle from the vertex:
|
18
18
|
//
|
@@ -38,7 +38,7 @@ const convexHull2Of = (points) => {
|
|
38
38
|
smallestDotProductSoFar = currentDotProduct;
|
39
39
|
}
|
40
40
|
}
|
41
|
-
toProcess = toProcess.filter(p => !p.eq(furthestPointSoFar));
|
41
|
+
toProcess = toProcess.filter((p) => !p.eq(furthestPointSoFar));
|
42
42
|
const newBaseDirection = furthestPointSoFar.minus(lastVertex).normalized();
|
43
43
|
// If the last vertex is on the same edge as the current, there's no need to include
|
44
44
|
// the previous one.
|