@js-draw/math 1.21.3 → 1.23.1
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.d.ts +24 -1
- package/dist/cjs/Color4.js +35 -3
- package/dist/cjs/Mat33.d.ts +21 -11
- package/dist/cjs/Mat33.js +28 -24
- package/dist/cjs/Vec3.d.ts +12 -3
- package/dist/cjs/Vec3.js +20 -9
- package/dist/cjs/lib.d.ts +3 -0
- package/dist/cjs/lib.js +3 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +2 -0
- package/dist/cjs/shapes/BezierJSWrapper.js +22 -13
- package/dist/cjs/shapes/LineSegment2.js +13 -17
- package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
- package/dist/cjs/shapes/Path.d.ts +1 -0
- package/dist/cjs/shapes/Path.js +50 -47
- package/dist/cjs/shapes/QuadraticBezier.d.ts +19 -2
- package/dist/cjs/shapes/QuadraticBezier.js +26 -3
- package/dist/cjs/shapes/Rect2.d.ts +13 -0
- package/dist/cjs/shapes/Rect2.js +35 -16
- package/dist/cjs/shapes/Triangle.js +4 -5
- package/dist/cjs/utils/convexHull2Of.js +3 -3
- package/dist/mjs/Color4.d.ts +24 -1
- package/dist/mjs/Color4.mjs +35 -3
- package/dist/mjs/Mat33.d.ts +21 -11
- package/dist/mjs/Mat33.mjs +28 -24
- package/dist/mjs/Vec3.d.ts +12 -3
- package/dist/mjs/Vec3.mjs +20 -9
- package/dist/mjs/lib.d.ts +3 -0
- package/dist/mjs/lib.mjs +3 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +2 -0
- package/dist/mjs/shapes/BezierJSWrapper.mjs +22 -13
- package/dist/mjs/shapes/LineSegment2.mjs +13 -17
- package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
- package/dist/mjs/shapes/Path.d.ts +1 -0
- package/dist/mjs/shapes/Path.mjs +50 -47
- package/dist/mjs/shapes/QuadraticBezier.d.ts +19 -2
- package/dist/mjs/shapes/QuadraticBezier.mjs +26 -3
- package/dist/mjs/shapes/Rect2.d.ts +13 -0
- package/dist/mjs/shapes/Rect2.mjs +35 -16
- 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 +21 -21
- package/src/Color4.ts +61 -18
- package/src/Mat33.fromCSSMatrix.test.ts +32 -46
- package/src/Mat33.test.ts +64 -102
- package/src/Mat33.ts +81 -104
- package/src/Vec2.test.ts +3 -3
- package/src/Vec3.test.ts +2 -3
- package/src/Vec3.ts +46 -61
- package/src/lib.ts +3 -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 +56 -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 +379 -352
- package/src/shapes/PointShape2D.ts +3 -3
- package/src/shapes/QuadraticBezier.test.ts +27 -21
- package/src/shapes/QuadraticBezier.ts +26 -11
- package/src/shapes/Rect2.test.ts +44 -75
- package/src/shapes/Rect2.ts +47 -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/shapes/Path.mjs
CHANGED
@@ -7,6 +7,7 @@ import PointShape2D from './PointShape2D.mjs';
|
|
7
7
|
import toRoundedString from '../rounding/toRoundedString.mjs';
|
8
8
|
import toStringOfSamePrecision from '../rounding/toStringOfSamePrecision.mjs';
|
9
9
|
import convexHull2Of from '../utils/convexHull2Of.mjs';
|
10
|
+
/** Identifiers for different path commands. These commands can make up a {@link Path}. */
|
10
11
|
export var PathCommandType;
|
11
12
|
(function (PathCommandType) {
|
12
13
|
PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
|
@@ -319,9 +320,7 @@ export class Path {
|
|
319
320
|
const maxRaymarchSteps = 8;
|
320
321
|
// Start raymarching from each of these points. This allows detection of multiple
|
321
322
|
// intersections.
|
322
|
-
const startPoints = [
|
323
|
-
line.p1, ...additionalRaymarchStartPoints, line.p2
|
324
|
-
];
|
323
|
+
const startPoints = [line.p1, ...additionalRaymarchStartPoints, line.p2];
|
325
324
|
// Converts a point ON THE LINE to a parameter
|
326
325
|
const pointToParameter = (point) => {
|
327
326
|
// Because line.direction is a unit vector, this computes the length
|
@@ -427,7 +426,9 @@ export class Path {
|
|
427
426
|
return [];
|
428
427
|
}
|
429
428
|
if (this.parts.length === 0) {
|
430
|
-
return new Path(this.startPoint, [
|
429
|
+
return new Path(this.startPoint, [
|
430
|
+
{ kind: PathCommandType.MoveTo, point: this.startPoint },
|
431
|
+
]).intersection(line, strokeRadius);
|
431
432
|
}
|
432
433
|
let index = 0;
|
433
434
|
for (const part of this.geometry) {
|
@@ -448,7 +449,7 @@ export class Path {
|
|
448
449
|
const doRaymarching = strokeRadius && strokeRadius > 1e-8;
|
449
450
|
if (doRaymarching) {
|
450
451
|
// Starting points for raymarching (in addition to the end points of the line).
|
451
|
-
const startPoints = result.map(intersection => intersection.point);
|
452
|
+
const startPoints = result.map((intersection) => intersection.point);
|
452
453
|
result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
|
453
454
|
}
|
454
455
|
return result;
|
@@ -501,7 +502,8 @@ export class Path {
|
|
501
502
|
*/
|
502
503
|
spliced(deleteFrom, deleteTo, insert, options) {
|
503
504
|
const isBeforeOrEqual = (a, b) => {
|
504
|
-
return a.curveIndex < b.curveIndex ||
|
505
|
+
return (a.curveIndex < b.curveIndex ||
|
506
|
+
(a.curveIndex === b.curveIndex && a.parameterValue <= b.parameterValue));
|
505
507
|
};
|
506
508
|
if (isBeforeOrEqual(deleteFrom, deleteTo)) {
|
507
509
|
// deleteFrom deleteTo
|
@@ -539,15 +541,15 @@ export class Path {
|
|
539
541
|
//
|
540
542
|
// Bounds checking & reversal.
|
541
543
|
//
|
542
|
-
while (splitAt.length > 0
|
543
|
-
|
544
|
-
|
544
|
+
while (splitAt.length > 0 &&
|
545
|
+
splitAt[splitAt.length - 1].curveIndex >= this.parts.length - 1 &&
|
546
|
+
splitAt[splitAt.length - 1].parameterValue >= 1) {
|
545
547
|
splitAt.pop();
|
546
548
|
}
|
547
549
|
splitAt.reverse(); // .reverse() <-- We're `.pop`ing from the end
|
548
|
-
while (splitAt.length > 0
|
549
|
-
|
550
|
-
|
550
|
+
while (splitAt.length > 0 &&
|
551
|
+
splitAt[splitAt.length - 1].curveIndex <= 0 &&
|
552
|
+
splitAt[splitAt.length - 1].parameterValue <= 0) {
|
551
553
|
splitAt.pop();
|
552
554
|
}
|
553
555
|
if (splitAt.length === 0 || this.parts.length === 0) {
|
@@ -738,7 +740,7 @@ export class Path {
|
|
738
740
|
if (affineTransfm.isIdentity()) {
|
739
741
|
return this;
|
740
742
|
}
|
741
|
-
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
743
|
+
return this.mapPoints((point) => affineTransfm.transformVec2(point));
|
742
744
|
}
|
743
745
|
/**
|
744
746
|
* @internal
|
@@ -827,11 +829,10 @@ export class Path {
|
|
827
829
|
});
|
828
830
|
lastPoint = part.endPoint;
|
829
831
|
break;
|
830
|
-
default:
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
}
|
832
|
+
default: {
|
833
|
+
const exhaustivenessCheck = part;
|
834
|
+
return exhaustivenessCheck;
|
835
|
+
}
|
835
836
|
}
|
836
837
|
}
|
837
838
|
newParts.reverse();
|
@@ -843,7 +844,8 @@ export class Path {
|
|
843
844
|
return this.startPoint;
|
844
845
|
}
|
845
846
|
const lastPart = this.parts[this.parts.length - 1];
|
846
|
-
if (lastPart.kind === PathCommandType.QuadraticBezierTo ||
|
847
|
+
if (lastPart.kind === PathCommandType.QuadraticBezierTo ||
|
848
|
+
lastPart.kind === PathCommandType.CubicBezierTo) {
|
847
849
|
return lastPart.endPoint;
|
848
850
|
}
|
849
851
|
else {
|
@@ -952,9 +954,9 @@ export class Path {
|
|
952
954
|
if (part1.kind !== part2.kind) {
|
953
955
|
return false;
|
954
956
|
}
|
955
|
-
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance)
|
956
|
-
|
957
|
-
|
957
|
+
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance) ||
|
958
|
+
!part1.controlPoint2.eq(part2.controlPoint2, tolerance) ||
|
959
|
+
!part1.endPoint.eq(part2.endPoint, tolerance)) {
|
958
960
|
return false;
|
959
961
|
}
|
960
962
|
break;
|
@@ -962,16 +964,15 @@ export class Path {
|
|
962
964
|
if (part1.kind !== part2.kind) {
|
963
965
|
return false;
|
964
966
|
}
|
965
|
-
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance)
|
966
|
-
|
967
|
+
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance) ||
|
968
|
+
!part1.endPoint.eq(part2.endPoint, tolerance)) {
|
967
969
|
return false;
|
968
970
|
}
|
969
971
|
break;
|
970
|
-
default:
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
}
|
972
|
+
default: {
|
973
|
+
const exhaustivenessCheck = part1;
|
974
|
+
return exhaustivenessCheck;
|
975
|
+
}
|
975
976
|
}
|
976
977
|
}
|
977
978
|
return true;
|
@@ -993,11 +994,7 @@ export class Path {
|
|
993
994
|
const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
|
994
995
|
const innerRect = Rect2.fromCorners(rect.topLeft.plus(cornerToEdge), rect.bottomRight.minus(cornerToEdge));
|
995
996
|
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
|
-
];
|
997
|
+
corners = [innerRect.corners[3], ...innerRect.corners, ...outerRect.corners.reverse()];
|
1001
998
|
startPoint = outerRect.corners[3];
|
1002
999
|
}
|
1003
1000
|
else {
|
@@ -1180,19 +1177,23 @@ export class Path {
|
|
1180
1177
|
});
|
1181
1178
|
};
|
1182
1179
|
const commandArgCounts = {
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1180
|
+
m: 1,
|
1181
|
+
l: 1,
|
1182
|
+
c: 3,
|
1183
|
+
q: 2,
|
1184
|
+
z: 0,
|
1185
|
+
h: 1,
|
1186
|
+
v: 1,
|
1190
1187
|
};
|
1191
1188
|
// Each command: Command character followed by anything that isn't a command character
|
1192
|
-
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/
|
1189
|
+
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/gi;
|
1193
1190
|
let current;
|
1194
1191
|
while ((current = commandExp.exec(pathString)) !== null) {
|
1195
|
-
const argParts = current[2]
|
1192
|
+
const argParts = current[2]
|
1193
|
+
.trim()
|
1194
|
+
.split(/[^0-9Ee.-]/)
|
1195
|
+
.filter((part) => part.length > 0)
|
1196
|
+
.reduce((accumualtor, current) => {
|
1196
1197
|
// As of 09/2022, iOS Safari doesn't support support lookbehind in regular
|
1197
1198
|
// expressions. As such, we need an alternative.
|
1198
1199
|
// Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
|
@@ -1202,10 +1203,10 @@ export class Path {
|
|
1202
1203
|
if (parts[0] !== '') {
|
1203
1204
|
accumualtor.push(parts[0]);
|
1204
1205
|
}
|
1205
|
-
accumualtor.push(...parts.slice(1).map(part => `-${part}`));
|
1206
|
+
accumualtor.push(...parts.slice(1).map((part) => `-${part}`));
|
1206
1207
|
return accumualtor;
|
1207
1208
|
}, []);
|
1208
|
-
let numericArgs = argParts.map(arg => parseFloat(arg));
|
1209
|
+
let numericArgs = argParts.map((arg) => parseFloat(arg));
|
1209
1210
|
let commandChar = current[1].toLowerCase();
|
1210
1211
|
let uppercaseCommand = current[1] !== commandChar;
|
1211
1212
|
// Convert commands that don't take points into commands that do.
|
@@ -1233,7 +1234,8 @@ export class Path {
|
|
1233
1234
|
commandChar = 'l';
|
1234
1235
|
}
|
1235
1236
|
const commandArgCount = commandArgCounts[commandChar] ?? 0;
|
1236
|
-
const allArgs = numericArgs
|
1237
|
+
const allArgs = numericArgs
|
1238
|
+
.reduce((accumulator, current, index, parts) => {
|
1237
1239
|
if (index % 2 !== 0) {
|
1238
1240
|
const currentAsFloat = current;
|
1239
1241
|
const prevAsFloat = parts[index - 1];
|
@@ -1242,7 +1244,8 @@ export class Path {
|
|
1242
1244
|
else {
|
1243
1245
|
return accumulator;
|
1244
1246
|
}
|
1245
|
-
}, [])
|
1247
|
+
}, [])
|
1248
|
+
.map((coordinate, index) => {
|
1246
1249
|
// Lowercase commands are relative, uppercase commands use absolute
|
1247
1250
|
// positioning
|
1248
1251
|
let newPos;
|
@@ -2,9 +2,26 @@ import { Point2, Vec2 } from '../Vec2';
|
|
2
2
|
import BezierJSWrapper from './BezierJSWrapper';
|
3
3
|
import Rect2 from './Rect2';
|
4
4
|
/**
|
5
|
-
* Represents a 2D Bézier curve.
|
5
|
+
* Represents a 2D [Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
6
6
|
*
|
7
|
-
*
|
7
|
+
* Example:
|
8
|
+
* ```ts,runnable,console
|
9
|
+
* import { QuadraticBezier, Vec2 } from '@js-draw/math';
|
10
|
+
*
|
11
|
+
* const startPoint = Vec2.of(4, 3);
|
12
|
+
* const controlPoint = Vec2.of(1, 1);
|
13
|
+
* const endPoint = Vec2.of(1, 3);
|
14
|
+
*
|
15
|
+
* const curve = new QuadraticBezier(
|
16
|
+
* startPoint,
|
17
|
+
* controlPoint,
|
18
|
+
* endPoint,
|
19
|
+
* );
|
20
|
+
*
|
21
|
+
* console.log('Curve:', curve);
|
22
|
+
* ```
|
23
|
+
*
|
24
|
+
* **Note**: Some Bézier operations internally use the `bezier-js` library.
|
8
25
|
*/
|
9
26
|
export declare class QuadraticBezier extends BezierJSWrapper {
|
10
27
|
readonly p0: Point2;
|
@@ -3,12 +3,35 @@ import solveQuadratic from '../polynomial/solveQuadratic.mjs';
|
|
3
3
|
import BezierJSWrapper from './BezierJSWrapper.mjs';
|
4
4
|
import Rect2 from './Rect2.mjs';
|
5
5
|
/**
|
6
|
-
* Represents a 2D Bézier curve.
|
6
|
+
* Represents a 2D [Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
7
7
|
*
|
8
|
-
*
|
8
|
+
* Example:
|
9
|
+
* ```ts,runnable,console
|
10
|
+
* import { QuadraticBezier, Vec2 } from '@js-draw/math';
|
11
|
+
*
|
12
|
+
* const startPoint = Vec2.of(4, 3);
|
13
|
+
* const controlPoint = Vec2.of(1, 1);
|
14
|
+
* const endPoint = Vec2.of(1, 3);
|
15
|
+
*
|
16
|
+
* const curve = new QuadraticBezier(
|
17
|
+
* startPoint,
|
18
|
+
* controlPoint,
|
19
|
+
* endPoint,
|
20
|
+
* );
|
21
|
+
*
|
22
|
+
* console.log('Curve:', curve);
|
23
|
+
* ```
|
24
|
+
*
|
25
|
+
* **Note**: Some Bézier operations internally use the `bezier-js` library.
|
9
26
|
*/
|
10
27
|
export class QuadraticBezier extends BezierJSWrapper {
|
11
|
-
constructor(
|
28
|
+
constructor(
|
29
|
+
// Start point
|
30
|
+
p0,
|
31
|
+
// Control point
|
32
|
+
p1,
|
33
|
+
// End point
|
34
|
+
p2) {
|
12
35
|
super();
|
13
36
|
this.p0 = p0;
|
14
37
|
this.p1 = p1;
|
@@ -15,6 +15,18 @@ export interface RectTemplate {
|
|
15
15
|
/**
|
16
16
|
* Represents a rectangle in 2D space, parallel to the XY axes.
|
17
17
|
*
|
18
|
+
* **Example**:
|
19
|
+
* ```ts,runnable,console
|
20
|
+
* import { Rect2, Vec2 } from '@js-draw/math';
|
21
|
+
*
|
22
|
+
* const rect = Rect2.fromCorners(
|
23
|
+
* Vec2.of(0, 0),
|
24
|
+
* Vec2.of(10, 10),
|
25
|
+
* );
|
26
|
+
* console.log('area', rect.area);
|
27
|
+
* console.log('topLeft', rect.topLeft);
|
28
|
+
* ```
|
29
|
+
*
|
18
30
|
* `invariant: w ≥ 0, h ≥ 0, immutable`
|
19
31
|
*/
|
20
32
|
export declare class Rect2 extends Abstract2DShape {
|
@@ -29,6 +41,7 @@ export declare class Rect2 extends Abstract2DShape {
|
|
29
41
|
translatedBy(vec: Vec2): Rect2;
|
30
42
|
resizedTo(size: Vec2): Rect2;
|
31
43
|
containsPoint(other: Point2): boolean;
|
44
|
+
/** @returns true iff `other` is completely within this `Rect2`. */
|
32
45
|
containsRect(other: Rect2): boolean;
|
33
46
|
/**
|
34
47
|
* @returns true iff this and `other` overlap
|
@@ -4,10 +4,30 @@ import Abstract2DShape from './Abstract2DShape.mjs';
|
|
4
4
|
/**
|
5
5
|
* Represents a rectangle in 2D space, parallel to the XY axes.
|
6
6
|
*
|
7
|
+
* **Example**:
|
8
|
+
* ```ts,runnable,console
|
9
|
+
* import { Rect2, Vec2 } from '@js-draw/math';
|
10
|
+
*
|
11
|
+
* const rect = Rect2.fromCorners(
|
12
|
+
* Vec2.of(0, 0),
|
13
|
+
* Vec2.of(10, 10),
|
14
|
+
* );
|
15
|
+
* console.log('area', rect.area);
|
16
|
+
* console.log('topLeft', rect.topLeft);
|
17
|
+
* ```
|
18
|
+
*
|
7
19
|
* `invariant: w ≥ 0, h ≥ 0, immutable`
|
8
20
|
*/
|
9
21
|
export class Rect2 extends Abstract2DShape {
|
10
|
-
constructor(
|
22
|
+
constructor(
|
23
|
+
// Top left x coordinate
|
24
|
+
x,
|
25
|
+
// Top left y coordinate
|
26
|
+
y,
|
27
|
+
// Width
|
28
|
+
w,
|
29
|
+
// Height
|
30
|
+
h) {
|
11
31
|
super();
|
12
32
|
this.x = x;
|
13
33
|
this.y = y;
|
@@ -34,13 +54,17 @@ export class Rect2 extends Abstract2DShape {
|
|
34
54
|
return new Rect2(this.x, this.y, size.x, size.y);
|
35
55
|
}
|
36
56
|
containsPoint(other) {
|
37
|
-
return this.x <= other.x &&
|
38
|
-
|
57
|
+
return (this.x <= other.x &&
|
58
|
+
this.y <= other.y &&
|
59
|
+
this.x + this.w >= other.x &&
|
60
|
+
this.y + this.h >= other.y);
|
39
61
|
}
|
62
|
+
/** @returns true iff `other` is completely within this `Rect2`. */
|
40
63
|
containsRect(other) {
|
41
|
-
return this.x <= other.x &&
|
42
|
-
|
43
|
-
|
64
|
+
return (this.x <= other.x &&
|
65
|
+
this.y <= other.y &&
|
66
|
+
this.x + this.w >= other.x + other.w &&
|
67
|
+
this.y + this.h >= other.y + other.h);
|
44
68
|
}
|
45
69
|
/**
|
46
70
|
* @returns true iff this and `other` overlap
|
@@ -125,7 +149,7 @@ export class Rect2 extends Abstract2DShape {
|
|
125
149
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
126
150
|
}
|
127
151
|
getClosestPointOnBoundaryTo(target) {
|
128
|
-
const closestEdgePoints = this.getEdges().map(edge => {
|
152
|
+
const closestEdgePoints = this.getEdges().map((edge) => {
|
129
153
|
return edge.closestPointTo(target);
|
130
154
|
});
|
131
155
|
let closest = null;
|
@@ -152,15 +176,10 @@ export class Rect2 extends Abstract2DShape {
|
|
152
176
|
return false;
|
153
177
|
}
|
154
178
|
const squareRadius = radius * radius;
|
155
|
-
return this.corners.every(corner => corner.minus(point).magnitudeSquared() < squareRadius);
|
179
|
+
return this.corners.every((corner) => corner.minus(point).magnitudeSquared() < squareRadius);
|
156
180
|
}
|
157
181
|
get corners() {
|
158
|
-
return [
|
159
|
-
this.bottomRight,
|
160
|
-
this.topRight,
|
161
|
-
this.topLeft,
|
162
|
-
this.bottomLeft,
|
163
|
-
];
|
182
|
+
return [this.bottomRight, this.topRight, this.topLeft, this.bottomLeft];
|
164
183
|
}
|
165
184
|
get maxDimension() {
|
166
185
|
return Math.max(this.w, this.h);
|
@@ -201,7 +220,7 @@ export class Rect2 extends Abstract2DShape {
|
|
201
220
|
const result = [];
|
202
221
|
for (const edge of this.getEdges()) {
|
203
222
|
const intersection = edge.intersectsLineSegment(lineSegment);
|
204
|
-
intersection.forEach(point => result.push(point));
|
223
|
+
intersection.forEach((point) => result.push(point));
|
205
224
|
}
|
206
225
|
return result;
|
207
226
|
}
|
@@ -219,7 +238,7 @@ export class Rect2 extends Abstract2DShape {
|
|
219
238
|
// [affineTransform] is a transformation matrix that both scales and **translates**.
|
220
239
|
// the bounding box of this' four corners after transformed by the given affine transformation.
|
221
240
|
transformedBoundingBox(affineTransform) {
|
222
|
-
return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
|
241
|
+
return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
|
223
242
|
}
|
224
243
|
/** @return true iff this is equal to `other ± tolerance` */
|
225
244
|
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.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@js-draw/math",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.23.1",
|
4
4
|
"description": "A math library for js-draw. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -27,7 +27,7 @@
|
|
27
27
|
"bezier-js": "6.1.3"
|
28
28
|
},
|
29
29
|
"devDependencies": {
|
30
|
-
"@js-draw/build-tool": "^1.
|
30
|
+
"@js-draw/build-tool": "^1.23.1",
|
31
31
|
"@types/bezier-js": "4.1.0",
|
32
32
|
"@types/jest": "29.5.5",
|
33
33
|
"@types/jsdom": "21.1.3"
|
@@ -44,5 +44,5 @@
|
|
44
44
|
"svg",
|
45
45
|
"math"
|
46
46
|
],
|
47
|
-
"gitHead": "
|
47
|
+
"gitHead": "e0bb3336d5f3a94533c823906778d39a4880f4cf"
|
48
48
|
}
|
package/src/Color4.test.ts
CHANGED
@@ -15,7 +15,7 @@ describe('Color4', () => {
|
|
15
15
|
expect(Color4.fromString('rgb(0, 0, 0)')).objEq(Color4.black);
|
16
16
|
expect(Color4.fromString('rgb ( 255, 0,\t 0)')).objEq(Color4.ofRGBA(1, 0, 0, 1));
|
17
17
|
expect(Color4.fromString('rgba ( 255, 0,\t 0, 0.5)')).objEq(Color4.ofRGBA(1, 0, 0, 0.5));
|
18
|
-
expect(Color4.fromString('rgba( 0, 0, 128, 0)')).objEq(Color4.ofRGBA(0, 0, 128/255, 0));
|
18
|
+
expect(Color4.fromString('rgba( 0, 0, 128, 0)')).objEq(Color4.ofRGBA(0, 0, 128 / 255, 0));
|
19
19
|
});
|
20
20
|
|
21
21
|
it('should parse transparent/none as completely transparent', () => {
|
@@ -29,13 +29,11 @@ describe('Color4', () => {
|
|
29
29
|
});
|
30
30
|
|
31
31
|
it('should mix red and green to get yellow', () => {
|
32
|
-
expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.3)).objEq(
|
33
|
-
Color4.ofRGB(0.7, 0.3, 0)
|
34
|
-
);
|
32
|
+
expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.3)).objEq(Color4.ofRGB(0.7, 0.3, 0));
|
35
33
|
});
|
36
34
|
|
37
35
|
it('should mix red with nothing and get red', () => {
|
38
|
-
expect(Color4.average([
|
36
|
+
expect(Color4.average([Color4.red])).objEq(Color4.red);
|
39
37
|
});
|
40
38
|
|
41
39
|
it('different colors should be different', () => {
|
@@ -47,14 +45,11 @@ describe('Color4', () => {
|
|
47
45
|
it('should correctly convert to hsv', () => {
|
48
46
|
expect(Color4.red.asHSV()).objEq(Vec3.of(0, 1, 1));
|
49
47
|
expect(Color4.ofRGB(0.5, 0.5, 0.5).asHSV()).objEq(Vec3.of(0, 0, 0.5));
|
50
|
-
expect(Color4.ofRGB(0.5, 0.25, 0.5).asHSV()).objEq(Vec3.of(Math.PI * 5 / 3, 0.5, 0.5), 0.1);
|
48
|
+
expect(Color4.ofRGB(0.5, 0.25, 0.5).asHSV()).objEq(Vec3.of((Math.PI * 5) / 3, 0.5, 0.5), 0.1);
|
51
49
|
});
|
52
50
|
|
53
51
|
it('fromHSV(color.asHSV) should return the original color', () => {
|
54
|
-
const testColors = [
|
55
|
-
Color4.red, Color4.green, Color4.blue,
|
56
|
-
Color4.white, Color4.black,
|
57
|
-
];
|
52
|
+
const testColors = [Color4.red, Color4.green, Color4.blue, Color4.white, Color4.black];
|
58
53
|
|
59
54
|
const testWithColor = (color: Color4) => {
|
60
55
|
expect(Color4.fromHSV(...color.asHSV().asArray())).objEq(color);
|
@@ -65,8 +60,8 @@ describe('Color4', () => {
|
|
65
60
|
}
|
66
61
|
|
67
62
|
for (let i = 0; i <= 6; i++) {
|
68
|
-
testWithColor(Color4.fromHSV(i * Math.PI / 7, 0.5, 0.5));
|
69
|
-
testWithColor(Color4.fromHSV(i * Math.PI / 6, 0.5, 0.5));
|
63
|
+
testWithColor(Color4.fromHSV((i * Math.PI) / 7, 0.5, 0.5));
|
64
|
+
testWithColor(Color4.fromHSV((i * Math.PI) / 6, 0.5, 0.5));
|
70
65
|
}
|
71
66
|
});
|
72
67
|
|
@@ -78,17 +73,22 @@ describe('Color4', () => {
|
|
78
73
|
|
79
74
|
it('should return correct contrast ratios', () => {
|
80
75
|
// Expected values from https://webaim.org/resources/contrastchecker/
|
81
|
-
const testCases: [
|
82
|
-
[
|
83
|
-
[
|
84
|
-
[
|
85
|
-
[
|
86
|
-
[
|
87
|
-
[
|
76
|
+
const testCases: [Color4, Color4, number][] = [
|
77
|
+
[Color4.white, Color4.black, 21],
|
78
|
+
[Color4.fromHex('#FF0000'), Color4.black, 5.25],
|
79
|
+
[Color4.fromHex('#FF0000'), Color4.fromHex('#0000FF'), 2.14],
|
80
|
+
[Color4.fromHex('#300000'), Color4.fromHex('#003000'), 1.26],
|
81
|
+
[Color4.fromHex('#300000'), Color4.fromHex('#003000'), 1.26],
|
82
|
+
[Color4.fromHex('#D60000'), Color4.fromHex('#003000'), 2.71],
|
88
83
|
];
|
89
84
|
|
90
|
-
for (const [
|
85
|
+
for (const [colorA, colorB, expectedContrast] of testCases) {
|
91
86
|
expect(Color4.contrastRatio(colorA, colorB)).toBeCloseTo(expectedContrast, 1);
|
92
87
|
}
|
93
88
|
});
|
94
|
-
|
89
|
+
|
90
|
+
it('should support creating colors from an RGBA array', () => {
|
91
|
+
expect(Color4.fromRGBArray([255, 0, 0])).objEq(Color4.ofRGB(1, 0, 0));
|
92
|
+
expect(Color4.fromRGBArray([255, 0, 0, 128])).objEq(Color4.ofRGBA(1, 0, 0, 0.5));
|
93
|
+
});
|
94
|
+
});
|