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