@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,7 +1,7 @@
|
|
1
|
-
import { Point2 } from '../Vec2';
|
1
|
+
import { Point2, Vec2 } from '../Vec2';
|
2
2
|
import Vec3 from '../Vec3';
|
3
|
-
import Abstract2DShape from './Abstract2DShape';
|
4
3
|
import LineSegment2 from './LineSegment2';
|
4
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
5
5
|
import Rect2 from './Rect2';
|
6
6
|
|
7
7
|
/**
|
@@ -9,18 +9,18 @@ import Rect2 from './Rect2';
|
|
9
9
|
*
|
10
10
|
* Access the internal `Point2` using the `p` property.
|
11
11
|
*/
|
12
|
-
class PointShape2D extends
|
12
|
+
class PointShape2D extends Parameterized2DShape {
|
13
13
|
public constructor(public readonly p: Point2) {
|
14
14
|
super();
|
15
15
|
}
|
16
16
|
|
17
17
|
public override signedDistance(point: Vec3): number {
|
18
|
-
return this.p.
|
18
|
+
return this.p.distanceTo(point);
|
19
19
|
}
|
20
20
|
|
21
|
-
public override
|
21
|
+
public override argIntersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): number[] {
|
22
22
|
if (lineSegment.containsPoint(this.p, epsilon)) {
|
23
|
-
return [
|
23
|
+
return [ 0 ];
|
24
24
|
}
|
25
25
|
return [ ];
|
26
26
|
}
|
@@ -28,6 +28,33 @@ class PointShape2D extends Abstract2DShape {
|
|
28
28
|
public override getTightBoundingBox(): Rect2 {
|
29
29
|
return new Rect2(this.p.x, this.p.y, 0, 0);
|
30
30
|
}
|
31
|
+
|
32
|
+
public override at(_t: number) {
|
33
|
+
return this.p;
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Returns an arbitrary unit-length vector.
|
38
|
+
*/
|
39
|
+
public override normalAt(_t: number) {
|
40
|
+
// Return a vector that makes sense.
|
41
|
+
return Vec2.unitY;
|
42
|
+
}
|
43
|
+
|
44
|
+
public override tangentAt(_t: number): Vec3 {
|
45
|
+
return Vec2.unitX;
|
46
|
+
}
|
47
|
+
|
48
|
+
public override splitAt(_t: number): [PointShape2D] {
|
49
|
+
return [this];
|
50
|
+
}
|
51
|
+
|
52
|
+
public override nearestPointTo(_point: Point2) {
|
53
|
+
return {
|
54
|
+
point: this.p,
|
55
|
+
parameterValue: 0,
|
56
|
+
};
|
57
|
+
}
|
31
58
|
}
|
32
59
|
|
33
60
|
export default PointShape2D;
|
@@ -2,13 +2,12 @@ import { Vec2 } from '../Vec2';
|
|
2
2
|
import QuadraticBezier from './QuadraticBezier';
|
3
3
|
|
4
4
|
describe('QuadraticBezier', () => {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
];
|
5
|
+
test.each([
|
6
|
+
new QuadraticBezier(Vec2.zero, Vec2.of(10, 0), Vec2.of(20, 0)),
|
7
|
+
new QuadraticBezier(Vec2.of(-10, 0), Vec2.of(2, 10), Vec2.of(20, 0)),
|
8
|
+
new QuadraticBezier(Vec2.of(0, 0), Vec2.of(4, -10), Vec2.of(20, 60)),
|
9
|
+
new QuadraticBezier(Vec2.of(0, 0), Vec2.of(4, -10), Vec2.of(-20, 60)),
|
10
|
+
])('approxmiateDistance should approximately return the distance to the curve (%s)', (curve) => {
|
12
11
|
const testPoints = [
|
13
12
|
Vec2.of(1, 1),
|
14
13
|
Vec2.of(-1, 1),
|
@@ -18,14 +17,72 @@ describe('QuadraticBezier', () => {
|
|
18
17
|
Vec2.of(5, 0),
|
19
18
|
];
|
20
19
|
|
20
|
+
for (const point of testPoints) {
|
21
|
+
const actualDist = curve.distance(point);
|
22
|
+
const approxDist = curve.approximateDistance(point);
|
23
|
+
|
24
|
+
expect(approxDist).toBeGreaterThan(actualDist * 0.6 - 0.25);
|
25
|
+
expect(approxDist).toBeLessThan(actualDist * 1.5 + 2.6);
|
26
|
+
}
|
27
|
+
});
|
28
|
+
|
29
|
+
test.each([
|
30
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.zero, 0 ],
|
31
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitY, 1 ],
|
32
|
+
|
33
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.of(0.5, 0), Vec2.of(1, 0)), Vec2.of(0.4, 0), 0.4],
|
34
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.of(0, 1)), Vec2.of(0, 0.4), 0.4],
|
35
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitX, 0.42514 ],
|
36
|
+
|
37
|
+
// Should not return an out-of-range parameter
|
38
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, -1000), 0 ],
|
39
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, 1000), 1 ],
|
40
|
+
|
41
|
+
// Edge case -- just a point
|
42
|
+
[ new QuadraticBezier(Vec2.zero, Vec2.zero, Vec2.zero), Vec2.of(0, 1000), 0 ],
|
43
|
+
])('nearestPointTo should return the nearest point and parameter value on %s to %s', (bezier, point, expectedParameter) => {
|
44
|
+
const nearest = bezier.nearestPointTo(point);
|
45
|
+
expect(nearest.parameterValue).toBeCloseTo(expectedParameter, 0.0001);
|
46
|
+
expect(nearest.point).objEq(bezier.at(nearest.parameterValue));
|
47
|
+
});
|
48
|
+
|
49
|
+
test('.normalAt should return a unit normal vector at the given parameter value', () => {
|
50
|
+
const curves = [
|
51
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitY, Vec2.unitY.times(2)),
|
52
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY),
|
53
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY.times(-2)),
|
54
|
+
new QuadraticBezier(Vec2.of(2, 3), Vec2.of(4, 5.1), Vec2.of(6, 7)),
|
55
|
+
new QuadraticBezier(Vec2.of(2, 3), Vec2.of(100, 1000), Vec2.unitY.times(-2)),
|
56
|
+
];
|
57
|
+
|
21
58
|
for (const curve of curves) {
|
22
|
-
for (
|
23
|
-
const
|
24
|
-
|
59
|
+
for (let t = 0; t < 1; t += 0.1) {
|
60
|
+
const normal = curve.normalAt(t);
|
61
|
+
expect(normal.length()).toBe(1);
|
62
|
+
|
63
|
+
const tangentApprox = curve.at(t + 0.001).minus(curve.at(t - 0.001));
|
25
64
|
|
26
|
-
|
27
|
-
expect(
|
65
|
+
// The tangent vector should be perpindicular to the normal
|
66
|
+
expect(tangentApprox.dot(normal)).toBeCloseTo(0);
|
28
67
|
}
|
29
68
|
}
|
30
69
|
});
|
70
|
+
|
71
|
+
test.each([
|
72
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitY, Vec2.unitY.times(2)),
|
73
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY),
|
74
|
+
new QuadraticBezier(Vec2.zero, Vec2.unitY, Vec2.unitX),
|
75
|
+
])('.derivativeAt should return a derivative vector with the correct direction (curve: %s)', (curve) => {
|
76
|
+
for (let t = 0; t < 1; t += 0.1) {
|
77
|
+
const derivative = curve.derivativeAt(t);
|
78
|
+
const derivativeApprox = curve.at(t + 0.001).minus(curve.at(t - 0.001));
|
79
|
+
expect(derivativeApprox.normalized()).objEq(derivative.normalized(), 0.01);
|
80
|
+
}
|
81
|
+
});
|
82
|
+
|
83
|
+
test('should support Bezier-Bezier intersections', () => {
|
84
|
+
const b1 = new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY);
|
85
|
+
const b2 = new QuadraticBezier(Vec2.of(-1, 0.5), Vec2.of(0, 0.6), Vec2.of(1, 0.4));
|
86
|
+
expect(b1.intersectsBezier(b2)).toHaveLength(1);
|
87
|
+
});
|
31
88
|
});
|
@@ -4,10 +4,9 @@ import BezierJSWrapper from './BezierJSWrapper';
|
|
4
4
|
import Rect2 from './Rect2';
|
5
5
|
|
6
6
|
/**
|
7
|
-
*
|
7
|
+
* Represents a 2D Bézier curve.
|
8
8
|
*
|
9
|
-
*
|
10
|
-
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
9
|
+
* **Note**: Many Bézier operations use `bezier-js`'s.
|
11
10
|
*/
|
12
11
|
export class QuadraticBezier extends BezierJSWrapper {
|
13
12
|
public constructor(
|
@@ -30,10 +29,19 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
30
29
|
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
31
30
|
}
|
32
31
|
|
32
|
+
private static secondDerivativeComponentAt(t: number, p0: number, p1: number, p2: number) {
|
33
|
+
return 2 * (p0 - 2 * p1 + p2);
|
34
|
+
}
|
35
|
+
|
33
36
|
/**
|
34
37
|
* @returns the curve evaluated at `t`.
|
38
|
+
*
|
39
|
+
* `t` should be a number in `[0, 1]`.
|
35
40
|
*/
|
36
41
|
public override at(t: number): Point2 {
|
42
|
+
if (t === 0) return this.p0;
|
43
|
+
if (t === 1) return this.p2;
|
44
|
+
|
37
45
|
const p0 = this.p0;
|
38
46
|
const p1 = this.p1;
|
39
47
|
const p2 = this.p2;
|
@@ -53,6 +61,16 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
53
61
|
);
|
54
62
|
}
|
55
63
|
|
64
|
+
public override secondDerivativeAt(t: number): Point2 {
|
65
|
+
const p0 = this.p0;
|
66
|
+
const p1 = this.p1;
|
67
|
+
const p2 = this.p2;
|
68
|
+
return Vec2.of(
|
69
|
+
QuadraticBezier.secondDerivativeComponentAt(t, p0.x, p1.x, p2.x),
|
70
|
+
QuadraticBezier.secondDerivativeComponentAt(t, p0.y, p1.y, p2.y),
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
56
74
|
public override normal(t: number): Vec2 {
|
57
75
|
const tangent = this.derivativeAt(t);
|
58
76
|
return tangent.orthog().normalized();
|
@@ -126,11 +144,10 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
126
144
|
|
127
145
|
const at1 = this.at(min1);
|
128
146
|
const at2 = this.at(min2);
|
129
|
-
const sqrDist1 = at1.
|
130
|
-
const sqrDist2 = at2.
|
131
|
-
const sqrDist3 = this.at(0).
|
132
|
-
const sqrDist4 = this.at(1).
|
133
|
-
|
147
|
+
const sqrDist1 = at1.squareDistanceTo(point);
|
148
|
+
const sqrDist2 = at2.squareDistanceTo(point);
|
149
|
+
const sqrDist3 = this.at(0).squareDistanceTo(point);
|
150
|
+
const sqrDist4 = this.at(1).squareDistanceTo(point);
|
134
151
|
|
135
152
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
136
153
|
}
|
package/src/shapes/Rect2.ts
CHANGED
@@ -4,7 +4,7 @@ import { Point2, Vec2 } from '../Vec2';
|
|
4
4
|
import Abstract2DShape from './Abstract2DShape';
|
5
5
|
import Vec3 from '../Vec3';
|
6
6
|
|
7
|
-
/** An object that can be converted to a Rect2. */
|
7
|
+
/** An object that can be converted to a {@link Rect2}. */
|
8
8
|
export interface RectTemplate {
|
9
9
|
x: number;
|
10
10
|
y: number;
|
@@ -14,7 +14,11 @@ export interface RectTemplate {
|
|
14
14
|
height?: number;
|
15
15
|
}
|
16
16
|
|
17
|
-
|
17
|
+
/**
|
18
|
+
* Represents a rectangle in 2D space, parallel to the XY axes.
|
19
|
+
*
|
20
|
+
* `invariant: w ≥ 0, h ≥ 0, immutable`
|
21
|
+
*/
|
18
22
|
export class Rect2 extends Abstract2DShape {
|
19
23
|
// Derived state:
|
20
24
|
|
@@ -67,6 +71,9 @@ export class Rect2 extends Abstract2DShape {
|
|
67
71
|
&& this.y + this.h >= other.y + other.h;
|
68
72
|
}
|
69
73
|
|
74
|
+
/**
|
75
|
+
* @returns true iff this and `other` overlap
|
76
|
+
*/
|
70
77
|
public intersects(other: Rect2): boolean {
|
71
78
|
// Project along x/y axes.
|
72
79
|
const thisMinX = this.x;
|
@@ -181,7 +188,7 @@ export class Rect2 extends Abstract2DShape {
|
|
181
188
|
let closest: Point2|null = null;
|
182
189
|
let closestDist: number|null = null;
|
183
190
|
for (const point of closestEdgePoints) {
|
184
|
-
const dist = point.
|
191
|
+
const dist = point.distanceTo(target);
|
185
192
|
if (closestDist === null || dist < closestDist) {
|
186
193
|
closest = point;
|
187
194
|
closestDist = dist;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { Vec2 } from '../Vec2';
|
2
|
+
import { Rect2 } from '../shapes/Rect2';
|
3
|
+
import convexHull2Of from './convexHull2Of';
|
4
|
+
|
5
|
+
describe('convexHull2Of', () => {
|
6
|
+
it.each([
|
7
|
+
[ [ Vec2.of(1, 1) ] , [ Vec2.of(1, 1) ] ],
|
8
|
+
|
9
|
+
// Line
|
10
|
+
[ [ Vec2.of(1, 1), Vec2.of(2, 2) ] , [ Vec2.of(1, 1), Vec2.of(2, 2) ] ],
|
11
|
+
|
12
|
+
// Just a triangle
|
13
|
+
[ [ Vec2.of(1, 1), Vec2.of(4, 2), Vec2.of(3, 3) ] , [ Vec2.of(1, 1), Vec2.of(4, 2), Vec2.of(3, 3) ]],
|
14
|
+
|
15
|
+
// Triangle with an extra point
|
16
|
+
[ [ Vec2.of(1, 1), Vec2.of(2, 20), Vec2.of(3, 5), Vec2.of(4, 3) ] , [ Vec2.of(1, 1), Vec2.of(4, 3), Vec2.of(2, 20) ]],
|
17
|
+
|
18
|
+
// Points within a triangle
|
19
|
+
[
|
20
|
+
[ Vec2.of(28, 5), Vec2.of(4, 5), Vec2.of(-100, -100), Vec2.of(7, 120), Vec2.of(1, 8), Vec2.of(100, -100), Vec2.of(2, 4), Vec2.of(3, 4), Vec2.of(4, 5) ],
|
21
|
+
[ Vec2.of(-100, -100), Vec2.of(100, -100), Vec2.of(7, 120) ],
|
22
|
+
],
|
23
|
+
|
24
|
+
// Points within a triangle (repeated vertex)
|
25
|
+
[
|
26
|
+
[ Vec2.of(28, 5), Vec2.of(4, 5), Vec2.of(-100, -100), Vec2.of(-100, -100), Vec2.of(7, 120), Vec2.of(1, 8), Vec2.of(100, -100), Vec2.of(2, 4), Vec2.of(3, 4), Vec2.of(4, 5) ],
|
27
|
+
[ Vec2.of(-100, -100), Vec2.of(100, -100), Vec2.of(7, 120) ],
|
28
|
+
],
|
29
|
+
|
30
|
+
// Points within a square
|
31
|
+
[
|
32
|
+
[ Vec2.of(28, 5), Vec2.of(4, 5), Vec2.of(-100, -100), Vec2.of(100, 100), Vec2.of(7, 100), Vec2.of(1, 8), Vec2.of(-100, 100), Vec2.of(100, -100), Vec2.of(2, 4), Vec2.of(3, 4), Vec2.of(4, 5) ],
|
33
|
+
[ Vec2.of(-100, -100), Vec2.of(100, -100), Vec2.of(100, 100), Vec2.of(-100, 100) ],
|
34
|
+
],
|
35
|
+
|
36
|
+
[
|
37
|
+
Rect2.unitSquare.corners,
|
38
|
+
[ Vec2.of(1, 0), Vec2.of(1, 1), Vec2.of(0, 1), Vec2.of(0, 0) ],
|
39
|
+
]
|
40
|
+
])('should compute the convex hull of a set of points (%j)', (points, expected) => {
|
41
|
+
expect(convexHull2Of(points)).toMatchObject(expected);
|
42
|
+
});
|
43
|
+
});
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { Point2, Vec2 } from '../Vec2';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Implements Gift Wrapping, in $O(nh)$. This algorithm is not the most efficient in the worst case.
|
5
|
+
*
|
6
|
+
* See https://en.wikipedia.org/wiki/Gift_wrapping_algorithm
|
7
|
+
* and https://www.cs.jhu.edu/~misha/Spring16/06.pdf
|
8
|
+
*/
|
9
|
+
const convexHull2Of = (points: Point2[]) => {
|
10
|
+
if (points.length === 0) {
|
11
|
+
return [];
|
12
|
+
}
|
13
|
+
|
14
|
+
// 1. Start with a vertex on the hull
|
15
|
+
const lowestPoint = points.reduce(
|
16
|
+
(lowest, current) => current.y < lowest.y ? current : lowest,
|
17
|
+
points[0]
|
18
|
+
);
|
19
|
+
const vertices = [ lowestPoint ];
|
20
|
+
let toProcess = [...points.filter(p => !p.eq(lowestPoint))];
|
21
|
+
let lastBaseDirection = Vec2.of(-1, 0);
|
22
|
+
|
23
|
+
// 2. Find the point with greatest angle from the vertex:
|
24
|
+
//
|
25
|
+
// . . .
|
26
|
+
// . . / <- Notice that **all** other points are to the
|
27
|
+
// / **left** of the vector from the current
|
28
|
+
// ./ vertex to the new point.
|
29
|
+
while (toProcess.length > 0) {
|
30
|
+
const lastVertex = vertices[vertices.length - 1];
|
31
|
+
|
32
|
+
let smallestDotProductSoFar: number = lastBaseDirection.dot(lowestPoint.minus(lastVertex).normalizedOrZero());
|
33
|
+
let furthestPointSoFar = lowestPoint;
|
34
|
+
for (const point of toProcess) {
|
35
|
+
// Maximizing the angle is the same as minimizing the dot product:
|
36
|
+
// point.minus(lastVertex)
|
37
|
+
// ^
|
38
|
+
// /
|
39
|
+
// /
|
40
|
+
// ϑ /
|
41
|
+
// <-----. lastBaseDirection
|
42
|
+
const currentDotProduct = lastBaseDirection.dot(point.minus(lastVertex).normalizedOrZero());
|
43
|
+
|
44
|
+
if (currentDotProduct <= smallestDotProductSoFar) {
|
45
|
+
furthestPointSoFar = point;
|
46
|
+
smallestDotProductSoFar = currentDotProduct;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
toProcess = toProcess.filter(p => !p.eq(furthestPointSoFar));
|
50
|
+
|
51
|
+
const newBaseDirection = furthestPointSoFar.minus(lastVertex).normalized();
|
52
|
+
|
53
|
+
// If the last vertex is on the same edge as the current, there's no need to include
|
54
|
+
// the previous one.
|
55
|
+
if (Math.abs(newBaseDirection.dot(lastBaseDirection)) === 1 && vertices.length > 1) {
|
56
|
+
vertices.pop();
|
57
|
+
}
|
58
|
+
|
59
|
+
// Stoping condition: We've gone in a full circle.
|
60
|
+
if (furthestPointSoFar.eq(lowestPoint)) {
|
61
|
+
break;
|
62
|
+
} else {
|
63
|
+
vertices.push(furthestPointSoFar);
|
64
|
+
lastBaseDirection = lastVertex.minus(furthestPointSoFar).normalized();
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
return vertices;
|
69
|
+
};
|
70
|
+
|
71
|
+
export default convexHull2Of;
|