@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,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;
|