@js-draw/math 1.16.0 → 1.18.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/Mat33.js +6 -1
- package/dist/cjs/Vec3.d.ts +23 -1
- package/dist/cjs/Vec3.js +33 -7
- package/dist/cjs/lib.d.ts +2 -1
- package/dist/cjs/lib.js +5 -1
- package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +19 -5
- package/dist/cjs/shapes/BezierJSWrapper.js +170 -18
- package/dist/cjs/shapes/LineSegment2.d.ts +45 -5
- package/dist/cjs/shapes/LineSegment2.js +89 -11
- package/dist/cjs/shapes/Parameterized2DShape.d.ts +36 -0
- package/dist/cjs/shapes/Parameterized2DShape.js +20 -0
- package/dist/cjs/shapes/Path.d.ts +131 -13
- package/dist/cjs/shapes/Path.js +507 -26
- package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/cjs/shapes/PointShape2D.js +28 -5
- package/dist/cjs/shapes/QuadraticBezier.d.ts +6 -3
- package/dist/cjs/shapes/QuadraticBezier.js +21 -7
- package/dist/cjs/shapes/Rect2.d.ts +9 -1
- package/dist/cjs/shapes/Rect2.js +9 -2
- package/dist/cjs/utils/convexHull2Of.d.ts +9 -0
- package/dist/cjs/utils/convexHull2Of.js +61 -0
- package/dist/cjs/utils/convexHull2Of.test.d.ts +1 -0
- package/dist/mjs/Mat33.mjs +6 -1
- package/dist/mjs/Vec3.d.ts +23 -1
- package/dist/mjs/Vec3.mjs +33 -7
- package/dist/mjs/lib.d.ts +2 -1
- package/dist/mjs/lib.mjs +2 -1
- package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +19 -5
- package/dist/mjs/shapes/BezierJSWrapper.mjs +168 -18
- package/dist/mjs/shapes/LineSegment2.d.ts +45 -5
- package/dist/mjs/shapes/LineSegment2.mjs +89 -11
- package/dist/mjs/shapes/Parameterized2DShape.d.ts +36 -0
- package/dist/mjs/shapes/Parameterized2DShape.mjs +13 -0
- package/dist/mjs/shapes/Path.d.ts +131 -13
- package/dist/mjs/shapes/Path.mjs +504 -25
- package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/mjs/shapes/PointShape2D.mjs +28 -5
- package/dist/mjs/shapes/QuadraticBezier.d.ts +6 -3
- package/dist/mjs/shapes/QuadraticBezier.mjs +21 -7
- package/dist/mjs/shapes/Rect2.d.ts +9 -1
- package/dist/mjs/shapes/Rect2.mjs +9 -2
- package/dist/mjs/utils/convexHull2Of.d.ts +9 -0
- package/dist/mjs/utils/convexHull2Of.mjs +59 -0
- package/dist/mjs/utils/convexHull2Of.test.d.ts +1 -0
- package/package.json +5 -5
- package/src/Mat33.ts +8 -2
- package/src/Vec3.test.ts +42 -7
- package/src/Vec3.ts +37 -8
- package/src/lib.ts +5 -0
- package/src/shapes/Abstract2DShape.ts +3 -0
- package/src/shapes/BezierJSWrapper.ts +195 -14
- package/src/shapes/LineSegment2.test.ts +61 -1
- package/src/shapes/LineSegment2.ts +110 -12
- package/src/shapes/Parameterized2DShape.ts +44 -0
- package/src/shapes/Path.test.ts +233 -5
- package/src/shapes/Path.ts +593 -37
- package/src/shapes/PointShape2D.ts +33 -6
- package/src/shapes/QuadraticBezier.test.ts +69 -12
- package/src/shapes/QuadraticBezier.ts +25 -8
- package/src/shapes/Rect2.ts +10 -3
- package/src/utils/convexHull2Of.test.ts +43 -0
- package/src/utils/convexHull2Of.ts +71 -0
@@ -1,26 +1,49 @@
|
|
1
|
-
import
|
1
|
+
import { Vec2 } from '../Vec2.mjs';
|
2
|
+
import Parameterized2DShape from './Parameterized2DShape.mjs';
|
2
3
|
import Rect2 from './Rect2.mjs';
|
3
4
|
/**
|
4
5
|
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
5
6
|
*
|
6
7
|
* Access the internal `Point2` using the `p` property.
|
7
8
|
*/
|
8
|
-
class PointShape2D extends
|
9
|
+
class PointShape2D extends Parameterized2DShape {
|
9
10
|
constructor(p) {
|
10
11
|
super();
|
11
12
|
this.p = p;
|
12
13
|
}
|
13
14
|
signedDistance(point) {
|
14
|
-
return this.p.
|
15
|
+
return this.p.distanceTo(point);
|
15
16
|
}
|
16
|
-
|
17
|
+
argIntersectsLineSegment(lineSegment, epsilon) {
|
17
18
|
if (lineSegment.containsPoint(this.p, epsilon)) {
|
18
|
-
return [
|
19
|
+
return [0];
|
19
20
|
}
|
20
21
|
return [];
|
21
22
|
}
|
22
23
|
getTightBoundingBox() {
|
23
24
|
return new Rect2(this.p.x, this.p.y, 0, 0);
|
24
25
|
}
|
26
|
+
at(_t) {
|
27
|
+
return this.p;
|
28
|
+
}
|
29
|
+
/**
|
30
|
+
* Returns an arbitrary unit-length vector.
|
31
|
+
*/
|
32
|
+
normalAt(_t) {
|
33
|
+
// Return a vector that makes sense.
|
34
|
+
return Vec2.unitY;
|
35
|
+
}
|
36
|
+
tangentAt(_t) {
|
37
|
+
return Vec2.unitX;
|
38
|
+
}
|
39
|
+
splitAt(_t) {
|
40
|
+
return [this];
|
41
|
+
}
|
42
|
+
nearestPointTo(_point) {
|
43
|
+
return {
|
44
|
+
point: this.p,
|
45
|
+
parameterValue: 0,
|
46
|
+
};
|
47
|
+
}
|
25
48
|
}
|
26
49
|
export default PointShape2D;
|
@@ -2,10 +2,9 @@ import { Point2, Vec2 } from '../Vec2';
|
|
2
2
|
import BezierJSWrapper from './BezierJSWrapper';
|
3
3
|
import Rect2 from './Rect2';
|
4
4
|
/**
|
5
|
-
*
|
5
|
+
* Represents a 2D Bézier curve.
|
6
6
|
*
|
7
|
-
*
|
8
|
-
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
7
|
+
* **Note**: Many Bézier operations use `bezier-js`'s.
|
9
8
|
*/
|
10
9
|
export declare class QuadraticBezier extends BezierJSWrapper {
|
11
10
|
readonly p0: Point2;
|
@@ -18,11 +17,15 @@ export declare class QuadraticBezier extends BezierJSWrapper {
|
|
18
17
|
*/
|
19
18
|
private static componentAt;
|
20
19
|
private static derivativeComponentAt;
|
20
|
+
private static secondDerivativeComponentAt;
|
21
21
|
/**
|
22
22
|
* @returns the curve evaluated at `t`.
|
23
|
+
*
|
24
|
+
* `t` should be a number in `[0, 1]`.
|
23
25
|
*/
|
24
26
|
at(t: number): Point2;
|
25
27
|
derivativeAt(t: number): Point2;
|
28
|
+
secondDerivativeAt(t: number): Point2;
|
26
29
|
normal(t: number): Vec2;
|
27
30
|
/** @returns an overestimate of this shape's bounding box. */
|
28
31
|
getLooseBoundingBox(): Rect2;
|
@@ -3,10 +3,9 @@ import solveQuadratic from '../polynomial/solveQuadratic.mjs';
|
|
3
3
|
import BezierJSWrapper from './BezierJSWrapper.mjs';
|
4
4
|
import Rect2 from './Rect2.mjs';
|
5
5
|
/**
|
6
|
-
*
|
6
|
+
* Represents a 2D Bézier curve.
|
7
7
|
*
|
8
|
-
*
|
9
|
-
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
8
|
+
* **Note**: Many Bézier operations use `bezier-js`'s.
|
10
9
|
*/
|
11
10
|
export class QuadraticBezier extends BezierJSWrapper {
|
12
11
|
constructor(p0, p1, p2) {
|
@@ -25,10 +24,19 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
25
24
|
static derivativeComponentAt(t, p0, p1, p2) {
|
26
25
|
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
27
26
|
}
|
27
|
+
static secondDerivativeComponentAt(t, p0, p1, p2) {
|
28
|
+
return 2 * (p0 - 2 * p1 + p2);
|
29
|
+
}
|
28
30
|
/**
|
29
31
|
* @returns the curve evaluated at `t`.
|
32
|
+
*
|
33
|
+
* `t` should be a number in `[0, 1]`.
|
30
34
|
*/
|
31
35
|
at(t) {
|
36
|
+
if (t === 0)
|
37
|
+
return this.p0;
|
38
|
+
if (t === 1)
|
39
|
+
return this.p2;
|
32
40
|
const p0 = this.p0;
|
33
41
|
const p1 = this.p1;
|
34
42
|
const p2 = this.p2;
|
@@ -40,6 +48,12 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
40
48
|
const p2 = this.p2;
|
41
49
|
return Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
42
50
|
}
|
51
|
+
secondDerivativeAt(t) {
|
52
|
+
const p0 = this.p0;
|
53
|
+
const p1 = this.p1;
|
54
|
+
const p2 = this.p2;
|
55
|
+
return Vec2.of(QuadraticBezier.secondDerivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.secondDerivativeComponentAt(t, p0.y, p1.y, p2.y));
|
56
|
+
}
|
43
57
|
normal(t) {
|
44
58
|
const tangent = this.derivativeAt(t);
|
45
59
|
return tangent.orthog().normalized();
|
@@ -100,10 +114,10 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
100
114
|
}
|
101
115
|
const at1 = this.at(min1);
|
102
116
|
const at2 = this.at(min2);
|
103
|
-
const sqrDist1 = at1.
|
104
|
-
const sqrDist2 = at2.
|
105
|
-
const sqrDist3 = this.at(0).
|
106
|
-
const sqrDist4 = this.at(1).
|
117
|
+
const sqrDist1 = at1.squareDistanceTo(point);
|
118
|
+
const sqrDist2 = at2.squareDistanceTo(point);
|
119
|
+
const sqrDist3 = this.at(0).squareDistanceTo(point);
|
120
|
+
const sqrDist4 = this.at(1).squareDistanceTo(point);
|
107
121
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
108
122
|
}
|
109
123
|
getPoints() {
|
@@ -3,7 +3,7 @@ import Mat33 from '../Mat33';
|
|
3
3
|
import { Point2, Vec2 } from '../Vec2';
|
4
4
|
import Abstract2DShape from './Abstract2DShape';
|
5
5
|
import Vec3 from '../Vec3';
|
6
|
-
/** An object that can be converted to a Rect2. */
|
6
|
+
/** An object that can be converted to a {@link Rect2}. */
|
7
7
|
export interface RectTemplate {
|
8
8
|
x: number;
|
9
9
|
y: number;
|
@@ -12,6 +12,11 @@ export interface RectTemplate {
|
|
12
12
|
width?: number;
|
13
13
|
height?: number;
|
14
14
|
}
|
15
|
+
/**
|
16
|
+
* Represents a rectangle in 2D space, parallel to the XY axes.
|
17
|
+
*
|
18
|
+
* `invariant: w ≥ 0, h ≥ 0, immutable`
|
19
|
+
*/
|
15
20
|
export declare class Rect2 extends Abstract2DShape {
|
16
21
|
readonly x: number;
|
17
22
|
readonly y: number;
|
@@ -25,6 +30,9 @@ export declare class Rect2 extends Abstract2DShape {
|
|
25
30
|
resizedTo(size: Vec2): Rect2;
|
26
31
|
containsPoint(other: Point2): boolean;
|
27
32
|
containsRect(other: Rect2): boolean;
|
33
|
+
/**
|
34
|
+
* @returns true iff this and `other` overlap
|
35
|
+
*/
|
28
36
|
intersects(other: Rect2): boolean;
|
29
37
|
intersection(other: Rect2): Rect2 | null;
|
30
38
|
union(other: Rect2): Rect2;
|
@@ -1,7 +1,11 @@
|
|
1
1
|
import LineSegment2 from './LineSegment2.mjs';
|
2
2
|
import { Vec2 } from '../Vec2.mjs';
|
3
3
|
import Abstract2DShape from './Abstract2DShape.mjs';
|
4
|
-
|
4
|
+
/**
|
5
|
+
* Represents a rectangle in 2D space, parallel to the XY axes.
|
6
|
+
*
|
7
|
+
* `invariant: w ≥ 0, h ≥ 0, immutable`
|
8
|
+
*/
|
5
9
|
export class Rect2 extends Abstract2DShape {
|
6
10
|
constructor(x, y, w, h) {
|
7
11
|
super();
|
@@ -38,6 +42,9 @@ export class Rect2 extends Abstract2DShape {
|
|
38
42
|
&& this.x + this.w >= other.x + other.w
|
39
43
|
&& this.y + this.h >= other.y + other.h;
|
40
44
|
}
|
45
|
+
/**
|
46
|
+
* @returns true iff this and `other` overlap
|
47
|
+
*/
|
41
48
|
intersects(other) {
|
42
49
|
// Project along x/y axes.
|
43
50
|
const thisMinX = this.x;
|
@@ -124,7 +131,7 @@ export class Rect2 extends Abstract2DShape {
|
|
124
131
|
let closest = null;
|
125
132
|
let closestDist = null;
|
126
133
|
for (const point of closestEdgePoints) {
|
127
|
-
const dist = point.
|
134
|
+
const dist = point.distanceTo(target);
|
128
135
|
if (closestDist === null || dist < closestDist) {
|
129
136
|
closest = point;
|
130
137
|
closestDist = dist;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Point2 } from '../Vec2';
|
2
|
+
/**
|
3
|
+
* Implements Gift Wrapping, in $O(nh)$. This algorithm is not the most efficient in the worst case.
|
4
|
+
*
|
5
|
+
* See https://en.wikipedia.org/wiki/Gift_wrapping_algorithm
|
6
|
+
* and https://www.cs.jhu.edu/~misha/Spring16/06.pdf
|
7
|
+
*/
|
8
|
+
declare const convexHull2Of: (points: Point2[]) => import("../Vec3").Vec3[];
|
9
|
+
export default convexHull2Of;
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { Vec2 } from '../Vec2.mjs';
|
2
|
+
/**
|
3
|
+
* Implements Gift Wrapping, in $O(nh)$. This algorithm is not the most efficient in the worst case.
|
4
|
+
*
|
5
|
+
* See https://en.wikipedia.org/wiki/Gift_wrapping_algorithm
|
6
|
+
* and https://www.cs.jhu.edu/~misha/Spring16/06.pdf
|
7
|
+
*/
|
8
|
+
const convexHull2Of = (points) => {
|
9
|
+
if (points.length === 0) {
|
10
|
+
return [];
|
11
|
+
}
|
12
|
+
// 1. Start with a vertex on the hull
|
13
|
+
const lowestPoint = points.reduce((lowest, current) => current.y < lowest.y ? current : lowest, points[0]);
|
14
|
+
const vertices = [lowestPoint];
|
15
|
+
let toProcess = [...points.filter(p => !p.eq(lowestPoint))];
|
16
|
+
let lastBaseDirection = Vec2.of(-1, 0);
|
17
|
+
// 2. Find the point with greatest angle from the vertex:
|
18
|
+
//
|
19
|
+
// . . .
|
20
|
+
// . . / <- Notice that **all** other points are to the
|
21
|
+
// / **left** of the vector from the current
|
22
|
+
// ./ vertex to the new point.
|
23
|
+
while (toProcess.length > 0) {
|
24
|
+
const lastVertex = vertices[vertices.length - 1];
|
25
|
+
let smallestDotProductSoFar = lastBaseDirection.dot(lowestPoint.minus(lastVertex).normalizedOrZero());
|
26
|
+
let furthestPointSoFar = lowestPoint;
|
27
|
+
for (const point of toProcess) {
|
28
|
+
// Maximizing the angle is the same as minimizing the dot product:
|
29
|
+
// point.minus(lastVertex)
|
30
|
+
// ^
|
31
|
+
// /
|
32
|
+
// /
|
33
|
+
// ϑ /
|
34
|
+
// <-----. lastBaseDirection
|
35
|
+
const currentDotProduct = lastBaseDirection.dot(point.minus(lastVertex).normalizedOrZero());
|
36
|
+
if (currentDotProduct <= smallestDotProductSoFar) {
|
37
|
+
furthestPointSoFar = point;
|
38
|
+
smallestDotProductSoFar = currentDotProduct;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
toProcess = toProcess.filter(p => !p.eq(furthestPointSoFar));
|
42
|
+
const newBaseDirection = furthestPointSoFar.minus(lastVertex).normalized();
|
43
|
+
// If the last vertex is on the same edge as the current, there's no need to include
|
44
|
+
// the previous one.
|
45
|
+
if (Math.abs(newBaseDirection.dot(lastBaseDirection)) === 1 && vertices.length > 1) {
|
46
|
+
vertices.pop();
|
47
|
+
}
|
48
|
+
// Stoping condition: We've gone in a full circle.
|
49
|
+
if (furthestPointSoFar.eq(lowestPoint)) {
|
50
|
+
break;
|
51
|
+
}
|
52
|
+
else {
|
53
|
+
vertices.push(furthestPointSoFar);
|
54
|
+
lastBaseDirection = lastVertex.minus(furthestPointSoFar).normalized();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
return vertices;
|
58
|
+
};
|
59
|
+
export default convexHull2Of;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@js-draw/math",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.18.0",
|
4
4
|
"description": "A math library for js-draw. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -21,14 +21,14 @@
|
|
21
21
|
"scripts": {
|
22
22
|
"dist-test": "cd dist-test/test_imports && npm install && npm run test",
|
23
23
|
"dist": "npm run build && npm run dist-test",
|
24
|
-
"build": "rm -rf ./dist &&
|
25
|
-
"watch": "
|
24
|
+
"build": "rm -rf ./dist && build-tool build",
|
25
|
+
"watch": "build-tool watch"
|
26
26
|
},
|
27
27
|
"dependencies": {
|
28
28
|
"bezier-js": "6.1.3"
|
29
29
|
},
|
30
30
|
"devDependencies": {
|
31
|
-
"@js-draw/build-tool": "^1.
|
31
|
+
"@js-draw/build-tool": "^1.17.0",
|
32
32
|
"@types/bezier-js": "4.1.0",
|
33
33
|
"@types/jest": "29.5.5",
|
34
34
|
"@types/jsdom": "21.1.3"
|
@@ -45,5 +45,5 @@
|
|
45
45
|
"svg",
|
46
46
|
"math"
|
47
47
|
],
|
48
|
-
"gitHead": "
|
48
|
+
"gitHead": "73c0d802a8439b5d408ba1e60f91be029db7e402"
|
49
49
|
}
|
package/src/Mat33.ts
CHANGED
@@ -444,8 +444,13 @@ export class Mat33 {
|
|
444
444
|
return Mat33.identity;
|
445
445
|
}
|
446
446
|
|
447
|
-
const parseArguments = (argumentString: string) => {
|
448
|
-
|
447
|
+
const parseArguments = (argumentString: string): number[] => {
|
448
|
+
const parsed = argumentString.split(/[, \t\n]+/g).map(argString => {
|
449
|
+
// Handle trailing spaces/commands
|
450
|
+
if (argString.trim() === '') {
|
451
|
+
return null;
|
452
|
+
}
|
453
|
+
|
449
454
|
let isPercentage = false;
|
450
455
|
if (argString.endsWith('%')) {
|
451
456
|
isPercentage = true;
|
@@ -476,6 +481,7 @@ export class Mat33 {
|
|
476
481
|
|
477
482
|
return argNumber;
|
478
483
|
});
|
484
|
+
return parsed.filter(n => n !== null) as number[];
|
479
485
|
};
|
480
486
|
|
481
487
|
|
package/src/Vec3.test.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
import Vec3 from './Vec3';
|
3
3
|
|
4
4
|
describe('Vec3', () => {
|
5
|
-
|
5
|
+
test('.xy should contain the x and y components', () => {
|
6
6
|
const vec = Vec3.of(1, 2, 3);
|
7
7
|
expect(vec.xy).toMatchObject({
|
8
8
|
x: 1,
|
@@ -10,42 +10,77 @@ describe('Vec3', () => {
|
|
10
10
|
});
|
11
11
|
});
|
12
12
|
|
13
|
-
|
13
|
+
test('should be combinable with other vectors via .zip', () => {
|
14
14
|
const vec1 = Vec3.unitX;
|
15
15
|
const vec2 = Vec3.unitY;
|
16
16
|
expect(vec1.zip(vec2, Math.min)).objEq(Vec3.zero);
|
17
17
|
expect(vec1.zip(vec2, Math.max)).objEq(Vec3.of(1, 1, 0));
|
18
18
|
});
|
19
19
|
|
20
|
-
|
20
|
+
test('.cross should obey the right hand rule', () => {
|
21
21
|
const vec1 = Vec3.unitX;
|
22
22
|
const vec2 = Vec3.unitY;
|
23
23
|
expect(vec1.cross(vec2)).objEq(Vec3.unitZ);
|
24
24
|
expect(vec2.cross(vec1)).objEq(Vec3.unitZ.times(-1));
|
25
25
|
});
|
26
26
|
|
27
|
-
|
27
|
+
test('.orthog should return an orthogonal vector', () => {
|
28
28
|
expect(Vec3.unitZ.orthog().dot(Vec3.unitZ)).toBe(0);
|
29
29
|
|
30
30
|
// Should return some orthogonal vector, even if given the zero vector
|
31
31
|
expect(Vec3.zero.orthog().dot(Vec3.zero)).toBe(0);
|
32
32
|
});
|
33
33
|
|
34
|
-
|
34
|
+
test('.minus should return the difference between two vectors', () => {
|
35
35
|
expect(Vec3.of(1, 2, 3).minus(Vec3.of(4, 5, 6))).objEq(Vec3.of(1 - 4, 2 - 5, 3 - 6));
|
36
36
|
});
|
37
37
|
|
38
|
-
|
38
|
+
test('.orthog should return a unit vector', () => {
|
39
39
|
expect(Vec3.zero.orthog().magnitude()).toBe(1);
|
40
40
|
expect(Vec3.unitZ.orthog().magnitude()).toBe(1);
|
41
41
|
expect(Vec3.unitX.orthog().magnitude()).toBe(1);
|
42
42
|
expect(Vec3.unitY.orthog().magnitude()).toBe(1);
|
43
43
|
});
|
44
44
|
|
45
|
-
|
45
|
+
test('.normalizedOrZero should normalize the given vector or return zero', () => {
|
46
46
|
expect(Vec3.zero.normalizedOrZero()).objEq(Vec3.zero);
|
47
47
|
expect(Vec3.unitX.normalizedOrZero()).objEq(Vec3.unitX);
|
48
48
|
expect(Vec3.unitX.times(22).normalizedOrZero()).objEq(Vec3.unitX);
|
49
49
|
expect(Vec3.of(1, 1, 1).times(22).normalizedOrZero().length()).toBeCloseTo(1);
|
50
50
|
});
|
51
|
+
|
52
|
+
test.each([
|
53
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(1, 2, 1), expected: 1 },
|
54
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(1, 2, 2), expected: 2 },
|
55
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(2, 2, 2), expected: 3 },
|
56
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 1, 1), expected: 1 },
|
57
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 1, 0), expected: 2 },
|
58
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 0, 0), expected: 3 },
|
59
|
+
{ from: Vec3.of(-1, -10, 0), to: Vec3.of(1, 2, 0), expected: 148 },
|
60
|
+
{ from: Vec3.of(-1, -10, 0), to: Vec3.of(1, 2, 0), expected: 148 },
|
61
|
+
])(
|
62
|
+
'.squareDistanceTo and .distanceTo should return correct square and euclidean distances (%j)',
|
63
|
+
({ from , to, expected }) => {
|
64
|
+
expect(from.squareDistanceTo(to)).toBe(expected);
|
65
|
+
expect(to.squareDistanceTo(from)).toBe(expected);
|
66
|
+
expect(to.distanceTo(from)).toBeCloseTo(Math.sqrt(expected));
|
67
|
+
expect(to.minus(from).magnitudeSquared()).toBe(expected);
|
68
|
+
expect(from.minus(to).magnitudeSquared()).toBe(expected);
|
69
|
+
},
|
70
|
+
);
|
71
|
+
|
72
|
+
test.each([
|
73
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(4, 5, 6), tolerance: 0.1, eq: false },
|
74
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(4, 5, 6), tolerance: 10, eq: true },
|
75
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(1, 2, 3), tolerance: 0, eq: true },
|
76
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(1, 2, 4), tolerance: 0, eq: false },
|
77
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(1, 4, 3), tolerance: 0, eq: false },
|
78
|
+
{ a: Vec3.of(1, 2, 3), b: Vec3.of(4, 2, 3), tolerance: 0, eq: false },
|
79
|
+
{ a: Vec3.of(1, 2, 3.0001), b: Vec3.of(1, 2, 3), tolerance: 1e-12, eq: false },
|
80
|
+
{ a: Vec3.of(1, 2, 3.0001), b: Vec3.of(1, 2, 3), tolerance: 1e-3, eq: true },
|
81
|
+
{ a: Vec3.of(1, 2.00001, 3.0001), b: Vec3.of(1.00001, 2, 3), tolerance: 1e-3, eq: true },
|
82
|
+
])('.eq should support tolerance (case %#)', ({ a, b, tolerance, eq }) => {
|
83
|
+
expect(a.eq(b, tolerance)).toBe(eq);
|
84
|
+
expect(b.eq(a, tolerance)).toBe(eq);
|
85
|
+
});
|
51
86
|
});
|
package/src/Vec3.ts
CHANGED
@@ -63,11 +63,40 @@ export class Vec3 {
|
|
63
63
|
return this.dot(this);
|
64
64
|
}
|
65
65
|
|
66
|
+
/**
|
67
|
+
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
68
|
+
* to another point, `p`.
|
69
|
+
*
|
70
|
+
* Equivalent to `.minus(p).magnitudeSquared()`.
|
71
|
+
*/
|
72
|
+
public squareDistanceTo(p: Vec3) {
|
73
|
+
const dx = this.x - p.x;
|
74
|
+
const dy = this.y - p.y;
|
75
|
+
const dz = this.z - p.z;
|
76
|
+
return dx * dx + dy * dy + dz * dz;
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
81
|
+
* `p`.
|
82
|
+
*
|
83
|
+
* Equivalent to `.minus(p).magnitude()`.
|
84
|
+
*/
|
85
|
+
public distanceTo(p: Vec3) {
|
86
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
87
|
+
}
|
88
|
+
|
66
89
|
/**
|
67
90
|
* Returns the entry of this with the greatest magnitude.
|
68
91
|
*
|
69
92
|
* In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
|
70
93
|
* all entries of this vector.
|
94
|
+
*
|
95
|
+
* **Example**:
|
96
|
+
* ```ts,runnable,console
|
97
|
+
* import { Vec3 } from '@js-draw/math';
|
98
|
+
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
99
|
+
* ```
|
71
100
|
*/
|
72
101
|
public maximumEntryMagnitude(): number {
|
73
102
|
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
@@ -81,6 +110,7 @@ export class Vec3 {
|
|
81
110
|
* As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
|
82
111
|
* the resultant angle is in the range $[-\pi, pi]$.
|
83
112
|
*
|
113
|
+
* **Example**:
|
84
114
|
* ```ts,runnable,console
|
85
115
|
* import { Vec2 } from '@js-draw/math';
|
86
116
|
* console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
|
@@ -214,7 +244,8 @@ export class Vec3 {
|
|
214
244
|
* Returns a vector with each component acted on by `fn`.
|
215
245
|
*
|
216
246
|
* @example
|
217
|
-
* ```
|
247
|
+
* ```ts,runnable,console
|
248
|
+
* import { Vec3 } from '@js-draw/math';
|
218
249
|
* console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
|
219
250
|
* ```
|
220
251
|
*/
|
@@ -242,13 +273,11 @@ export class Vec3 {
|
|
242
273
|
* ```
|
243
274
|
*/
|
244
275
|
public eq(other: Vec3, fuzz: number = 1e-10): boolean {
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
return true;
|
276
|
+
return (
|
277
|
+
Math.abs(other.x - this.x) <= fuzz
|
278
|
+
&& Math.abs(other.y - this.y) <= fuzz
|
279
|
+
&& Math.abs(other.z - this.z) <= fuzz
|
280
|
+
);
|
252
281
|
}
|
253
282
|
|
254
283
|
public toString(): string {
|
package/src/lib.ts
CHANGED
@@ -21,6 +21,10 @@ export { LineSegment2 } from './shapes/LineSegment2';
|
|
21
21
|
export {
|
22
22
|
Path,
|
23
23
|
|
24
|
+
IntersectionResult as PathIntersectionResult,
|
25
|
+
CurveIndexRecord as PathCurveIndex,
|
26
|
+
stepCurveIndexBy as stepPathIndexBy,
|
27
|
+
compareCurveIndices as comparePathIndices,
|
24
28
|
PathCommandType,
|
25
29
|
PathCommand,
|
26
30
|
LinePathCommand,
|
@@ -29,6 +33,7 @@ export {
|
|
29
33
|
CubicBezierPathCommand,
|
30
34
|
} from './shapes/Path';
|
31
35
|
export { Rect2 } from './shapes/Rect2';
|
36
|
+
export { Parameterized2DShape } from './shapes/Parameterized2DShape';
|
32
37
|
export { QuadraticBezier } from './shapes/QuadraticBezier';
|
33
38
|
export { Abstract2DShape } from './shapes/Abstract2DShape';
|
34
39
|
|
@@ -49,6 +49,9 @@ export abstract class Abstract2DShape {
|
|
49
49
|
|
50
50
|
/**
|
51
51
|
* Returns a bounding box that precisely fits the content of this shape.
|
52
|
+
*
|
53
|
+
* **Note**: This bounding box should aligned with the x/y axes. (Thus, it may be
|
54
|
+
* possible to find a tighter bounding box not axes-aligned).
|
52
55
|
*/
|
53
56
|
public abstract getTightBoundingBox(): Rect2;
|
54
57
|
|