@js-draw/math 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +3 -0
- package/build-config.json +4 -0
- package/dist/cjs/Color4.d.ts +83 -0
- package/dist/cjs/Color4.js +277 -0
- package/dist/cjs/Mat33.d.ts +131 -0
- package/dist/cjs/Mat33.js +345 -0
- package/dist/cjs/Vec2.d.ts +42 -0
- package/dist/cjs/Vec2.js +48 -0
- package/dist/cjs/Vec3.d.ts +126 -0
- package/dist/cjs/Vec3.js +203 -0
- package/dist/cjs/lib.d.ts +27 -0
- package/dist/cjs/lib.js +42 -0
- package/dist/cjs/polynomial/solveQuadratic.d.ts +9 -0
- package/dist/cjs/polynomial/solveQuadratic.js +39 -0
- package/dist/cjs/rounding.d.ts +15 -0
- package/dist/cjs/rounding.js +146 -0
- package/dist/cjs/shapes/Abstract2DShape.d.ts +49 -0
- package/dist/cjs/shapes/Abstract2DShape.js +38 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +36 -0
- package/dist/cjs/shapes/BezierJSWrapper.js +94 -0
- package/dist/cjs/shapes/CubicBezier.d.ts +17 -0
- package/dist/cjs/shapes/CubicBezier.js +35 -0
- package/dist/cjs/shapes/LineSegment2.d.ts +70 -0
- package/dist/cjs/shapes/LineSegment2.js +183 -0
- package/dist/cjs/shapes/Path.d.ts +96 -0
- package/dist/cjs/shapes/Path.js +766 -0
- package/dist/cjs/shapes/PointShape2D.d.ts +18 -0
- package/dist/cjs/shapes/PointShape2D.js +31 -0
- package/dist/cjs/shapes/QuadraticBezier.d.ts +35 -0
- package/dist/cjs/shapes/QuadraticBezier.js +120 -0
- package/dist/cjs/shapes/Rect2.d.ts +58 -0
- package/dist/cjs/shapes/Rect2.js +259 -0
- package/dist/cjs/shapes/Triangle.d.ts +46 -0
- package/dist/cjs/shapes/Triangle.js +126 -0
- package/dist/mjs/Color4.d.ts +83 -0
- package/dist/mjs/Color4.mjs +271 -0
- package/dist/mjs/Mat33.d.ts +131 -0
- package/dist/mjs/Mat33.mjs +338 -0
- package/dist/mjs/Vec2.d.ts +42 -0
- package/dist/mjs/Vec2.mjs +42 -0
- package/dist/mjs/Vec3.d.ts +126 -0
- package/dist/mjs/Vec3.mjs +199 -0
- package/dist/mjs/lib.d.ts +27 -0
- package/dist/mjs/lib.mjs +29 -0
- package/dist/mjs/polynomial/solveQuadratic.d.ts +9 -0
- package/dist/mjs/polynomial/solveQuadratic.mjs +37 -0
- package/dist/mjs/rounding.d.ts +15 -0
- package/dist/mjs/rounding.mjs +139 -0
- package/dist/mjs/shapes/Abstract2DShape.d.ts +49 -0
- package/dist/mjs/shapes/Abstract2DShape.mjs +36 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +36 -0
- package/dist/mjs/shapes/BezierJSWrapper.mjs +89 -0
- package/dist/mjs/shapes/CubicBezier.d.ts +17 -0
- package/dist/mjs/shapes/CubicBezier.mjs +30 -0
- package/dist/mjs/shapes/LineSegment2.d.ts +70 -0
- package/dist/mjs/shapes/LineSegment2.mjs +176 -0
- package/dist/mjs/shapes/Path.d.ts +96 -0
- package/dist/mjs/shapes/Path.mjs +759 -0
- package/dist/mjs/shapes/PointShape2D.d.ts +18 -0
- package/dist/mjs/shapes/PointShape2D.mjs +26 -0
- package/dist/mjs/shapes/QuadraticBezier.d.ts +35 -0
- package/dist/mjs/shapes/QuadraticBezier.mjs +113 -0
- package/dist/mjs/shapes/Rect2.d.ts +58 -0
- package/dist/mjs/shapes/Rect2.mjs +252 -0
- package/dist/mjs/shapes/Triangle.d.ts +46 -0
- package/dist/mjs/shapes/Triangle.mjs +121 -0
- package/package.json +48 -0
- package/tsconfig.json +7 -0
- package/typedoc.json +5 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Point2 } from '../Vec2';
|
2
|
+
import Vec3 from '../Vec3';
|
3
|
+
import Abstract2DShape from './Abstract2DShape';
|
4
|
+
import LineSegment2 from './LineSegment2';
|
5
|
+
import Rect2 from './Rect2';
|
6
|
+
/**
|
7
|
+
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
8
|
+
*
|
9
|
+
* Access the internal `Point2` using the `p` property.
|
10
|
+
*/
|
11
|
+
declare class PointShape2D extends Abstract2DShape {
|
12
|
+
readonly p: Point2;
|
13
|
+
constructor(p: Point2);
|
14
|
+
signedDistance(point: Vec3): number;
|
15
|
+
intersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): Vec3[];
|
16
|
+
getTightBoundingBox(): Rect2;
|
17
|
+
}
|
18
|
+
export default PointShape2D;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import Abstract2DShape from './Abstract2DShape.mjs';
|
2
|
+
import Rect2 from './Rect2.mjs';
|
3
|
+
/**
|
4
|
+
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
5
|
+
*
|
6
|
+
* Access the internal `Point2` using the `p` property.
|
7
|
+
*/
|
8
|
+
class PointShape2D extends Abstract2DShape {
|
9
|
+
constructor(p) {
|
10
|
+
super();
|
11
|
+
this.p = p;
|
12
|
+
}
|
13
|
+
signedDistance(point) {
|
14
|
+
return this.p.minus(point).magnitude();
|
15
|
+
}
|
16
|
+
intersectsLineSegment(lineSegment, epsilon) {
|
17
|
+
if (lineSegment.containsPoint(this.p, epsilon)) {
|
18
|
+
return [this.p];
|
19
|
+
}
|
20
|
+
return [];
|
21
|
+
}
|
22
|
+
getTightBoundingBox() {
|
23
|
+
return new Rect2(this.p.x, this.p.y, 0, 0);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
export default PointShape2D;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { Point2, Vec2 } from '../Vec2';
|
2
|
+
import BezierJSWrapper from './BezierJSWrapper';
|
3
|
+
import Rect2 from './Rect2';
|
4
|
+
/**
|
5
|
+
* A wrapper around `bezier-js`'s quadratic Bézier.
|
6
|
+
*
|
7
|
+
* This wrappper lazy-loads `bezier-js`'s Bézier and can perform some operations
|
8
|
+
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
9
|
+
*/
|
10
|
+
export declare class QuadraticBezier extends BezierJSWrapper {
|
11
|
+
readonly p0: Point2;
|
12
|
+
readonly p1: Point2;
|
13
|
+
readonly p2: Point2;
|
14
|
+
constructor(p0: Point2, p1: Point2, p2: Point2);
|
15
|
+
/**
|
16
|
+
* Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
|
17
|
+
* all y components of the target curve.
|
18
|
+
*/
|
19
|
+
private static componentAt;
|
20
|
+
private static derivativeComponentAt;
|
21
|
+
/**
|
22
|
+
* @returns the curve evaluated at `t`.
|
23
|
+
*/
|
24
|
+
at(t: number): Point2;
|
25
|
+
derivativeAt(t: number): Point2;
|
26
|
+
normal(t: number): Vec2;
|
27
|
+
/** @returns an overestimate of this shape's bounding box. */
|
28
|
+
getLooseBoundingBox(): Rect2;
|
29
|
+
/**
|
30
|
+
* @returns the *approximate* distance from `point` to this curve.
|
31
|
+
*/
|
32
|
+
approximateDistance(point: Point2): number;
|
33
|
+
getPoints(): import("../Vec3").Vec3[];
|
34
|
+
}
|
35
|
+
export default QuadraticBezier;
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { Vec2 } from '../Vec2.mjs';
|
2
|
+
import solveQuadratic from '../polynomial/solveQuadratic.mjs';
|
3
|
+
import BezierJSWrapper from './BezierJSWrapper.mjs';
|
4
|
+
import Rect2 from './Rect2.mjs';
|
5
|
+
/**
|
6
|
+
* A wrapper around `bezier-js`'s quadratic Bézier.
|
7
|
+
*
|
8
|
+
* This wrappper lazy-loads `bezier-js`'s Bézier and can perform some operations
|
9
|
+
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
10
|
+
*/
|
11
|
+
export class QuadraticBezier extends BezierJSWrapper {
|
12
|
+
constructor(p0, p1, p2) {
|
13
|
+
super();
|
14
|
+
this.p0 = p0;
|
15
|
+
this.p1 = p1;
|
16
|
+
this.p2 = p2;
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
|
20
|
+
* all y components of the target curve.
|
21
|
+
*/
|
22
|
+
static componentAt(t, p0, p1, p2) {
|
23
|
+
return p0 + t * (-2 * p0 + 2 * p1) + t * t * (p0 - 2 * p1 + p2);
|
24
|
+
}
|
25
|
+
static derivativeComponentAt(t, p0, p1, p2) {
|
26
|
+
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* @returns the curve evaluated at `t`.
|
30
|
+
*/
|
31
|
+
at(t) {
|
32
|
+
const p0 = this.p0;
|
33
|
+
const p1 = this.p1;
|
34
|
+
const p2 = this.p2;
|
35
|
+
return Vec2.of(QuadraticBezier.componentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.componentAt(t, p0.y, p1.y, p2.y));
|
36
|
+
}
|
37
|
+
derivativeAt(t) {
|
38
|
+
const p0 = this.p0;
|
39
|
+
const p1 = this.p1;
|
40
|
+
const p2 = this.p2;
|
41
|
+
return Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
42
|
+
}
|
43
|
+
normal(t) {
|
44
|
+
const tangent = this.derivativeAt(t);
|
45
|
+
return tangent.orthog().normalized();
|
46
|
+
}
|
47
|
+
/** @returns an overestimate of this shape's bounding box. */
|
48
|
+
getLooseBoundingBox() {
|
49
|
+
return Rect2.bboxOf([this.p0, this.p1, this.p2]);
|
50
|
+
}
|
51
|
+
/**
|
52
|
+
* @returns the *approximate* distance from `point` to this curve.
|
53
|
+
*/
|
54
|
+
approximateDistance(point) {
|
55
|
+
// We want to minimize f(t) = |B(t) - p|².
|
56
|
+
// Expanding,
|
57
|
+
// f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
|
58
|
+
// ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
|
59
|
+
//
|
60
|
+
// Considering just one component,
|
61
|
+
// Dₜ(Bₓ(t) - pₓ)² = 2(Bₓ(t) - pₓ)(DₜBₓ(t))
|
62
|
+
// = 2(Bₓ(t)DₜBₓ(t) - pₓBₓ(t))
|
63
|
+
// = 2(p0ₓ + (t)(-2p0ₓ + 2p1ₓ) + (t²)(p0ₓ - 2p1ₓ + p2ₓ) - pₓ)((-2p0ₓ + 2p1ₓ) + 2(t)(p0ₓ - 2p1ₓ + p2ₓ))
|
64
|
+
// - (pₓ)((-2p0ₓ + 2p1ₓ) + (t)(p0ₓ - 2p1ₓ + p2ₓ))
|
65
|
+
const A = this.p0.x - point.x;
|
66
|
+
const B = -2 * this.p0.x + 2 * this.p1.x;
|
67
|
+
const C = this.p0.x - 2 * this.p1.x + this.p2.x;
|
68
|
+
// Let A = p0ₓ - pₓ, B = -2p0ₓ + 2p1ₓ, C = p0ₓ - 2p1ₓ + p2ₓ. We then have,
|
69
|
+
// Dₜ(Bₓ(t) - pₓ)²
|
70
|
+
// = 2(A + tB + t²C)(B + 2tC) - (pₓ)(B + 2tC)
|
71
|
+
// = 2(AB + tB² + t²BC + 2tCA + 2tCtB + 2tCt²C) - pₓB - pₓ2tC
|
72
|
+
// = 2(AB + tB² + 2tCA + t²BC + 2t²CB + 2C²t³) - pₓB - pₓ2tC
|
73
|
+
// = 2AB + 2t(B² + 2CA) + 2t²(BC + 2CB) + 4C²t³ - pₓB - pₓ2tC
|
74
|
+
// = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
75
|
+
//
|
76
|
+
const D = this.p0.y - point.y;
|
77
|
+
const E = -2 * this.p0.y + 2 * this.p1.y;
|
78
|
+
const F = this.p0.y - 2 * this.p1.y + this.p2.y;
|
79
|
+
// Using D = p0ᵧ - pᵧ, E = -2p0ᵧ + 2p1ᵧ, F = p0ᵧ - 2p1ᵧ + p2ᵧ, we thus have,
|
80
|
+
// f'(t) = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
81
|
+
// + 2DE + 2t(E² + 2FD - pᵧF) + 2t²(EF + 2FE) + 4F²t³ - pᵧE
|
82
|
+
const a = 2 * A * B + 2 * D * E - point.x * B - point.y * E;
|
83
|
+
const b = 2 * B * B + 2 * E * E + 2 * C * A + 2 * F * D - point.x * C - point.y * F;
|
84
|
+
const c = 2 * E * F + 2 * B * C + 2 * C * B + 2 * F * E;
|
85
|
+
//const d = 4 * C * C + 4 * F * F;
|
86
|
+
// Thus,
|
87
|
+
// f'(t) = a + bt + ct² + dt³
|
88
|
+
const fDerivAtZero = a;
|
89
|
+
const f2ndDerivAtZero = b;
|
90
|
+
const f3rdDerivAtZero = 2 * c;
|
91
|
+
// Using the first few terms of a Maclaurin series to approximate f'(t),
|
92
|
+
// f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
|
93
|
+
let [min1, min2] = solveQuadratic(f3rdDerivAtZero / 2, f2ndDerivAtZero, fDerivAtZero);
|
94
|
+
// If the quadratic has no solutions, approximate.
|
95
|
+
if (isNaN(min1)) {
|
96
|
+
min1 = 0.25;
|
97
|
+
}
|
98
|
+
if (isNaN(min2)) {
|
99
|
+
min2 = 0.75;
|
100
|
+
}
|
101
|
+
const at1 = this.at(min1);
|
102
|
+
const at2 = this.at(min2);
|
103
|
+
const sqrDist1 = at1.minus(point).magnitudeSquared();
|
104
|
+
const sqrDist2 = at2.minus(point).magnitudeSquared();
|
105
|
+
const sqrDist3 = this.at(0).minus(point).magnitudeSquared();
|
106
|
+
const sqrDist4 = this.at(1).minus(point).magnitudeSquared();
|
107
|
+
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
108
|
+
}
|
109
|
+
getPoints() {
|
110
|
+
return [this.p0, this.p1, this.p2];
|
111
|
+
}
|
112
|
+
}
|
113
|
+
export default QuadraticBezier;
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import LineSegment2 from './LineSegment2';
|
2
|
+
import Mat33 from '../Mat33';
|
3
|
+
import { Point2, Vec2 } from '../Vec2';
|
4
|
+
import Abstract2DShape from './Abstract2DShape';
|
5
|
+
import Vec3 from '../Vec3';
|
6
|
+
/** An object that can be converted to a Rect2. */
|
7
|
+
export interface RectTemplate {
|
8
|
+
x: number;
|
9
|
+
y: number;
|
10
|
+
w?: number;
|
11
|
+
h?: number;
|
12
|
+
width?: number;
|
13
|
+
height?: number;
|
14
|
+
}
|
15
|
+
export declare class Rect2 extends Abstract2DShape {
|
16
|
+
readonly x: number;
|
17
|
+
readonly y: number;
|
18
|
+
readonly w: number;
|
19
|
+
readonly h: number;
|
20
|
+
readonly topLeft: Point2;
|
21
|
+
readonly size: Vec2;
|
22
|
+
readonly bottomRight: Point2;
|
23
|
+
readonly area: number;
|
24
|
+
constructor(x: number, y: number, w: number, h: number);
|
25
|
+
translatedBy(vec: Vec2): Rect2;
|
26
|
+
resizedTo(size: Vec2): Rect2;
|
27
|
+
containsPoint(other: Point2): boolean;
|
28
|
+
containsRect(other: Rect2): boolean;
|
29
|
+
intersects(other: Rect2): boolean;
|
30
|
+
intersection(other: Rect2): Rect2 | null;
|
31
|
+
union(other: Rect2): Rect2;
|
32
|
+
divideIntoGrid(columns: number, rows: number): Rect2[];
|
33
|
+
grownToPoint(point: Point2, margin?: number): Rect2;
|
34
|
+
grownBy(margin: number): Rect2;
|
35
|
+
getClosestPointOnBoundaryTo(target: Point2): Vec3;
|
36
|
+
get corners(): Point2[];
|
37
|
+
get maxDimension(): number;
|
38
|
+
get topRight(): Vec3;
|
39
|
+
get bottomLeft(): Vec3;
|
40
|
+
get width(): number;
|
41
|
+
get height(): number;
|
42
|
+
get center(): Vec3;
|
43
|
+
getEdges(): LineSegment2[];
|
44
|
+
intersectsLineSegment(lineSegment: LineSegment2): Point2[];
|
45
|
+
signedDistance(point: Vec3): number;
|
46
|
+
getTightBoundingBox(): Rect2;
|
47
|
+
transformedBoundingBox(affineTransform: Mat33): Rect2;
|
48
|
+
/** @return true iff this is equal to [other] ± fuzz */
|
49
|
+
eq(other: Rect2, fuzz?: number): boolean;
|
50
|
+
toString(): string;
|
51
|
+
static fromCorners(corner1: Point2, corner2: Point2): Rect2;
|
52
|
+
static bboxOf(points: Point2[], margin?: number): Rect2;
|
53
|
+
static union(...rects: Rect2[]): Rect2;
|
54
|
+
static of(template: RectTemplate): Rect2;
|
55
|
+
static empty: Rect2;
|
56
|
+
static unitSquare: Rect2;
|
57
|
+
}
|
58
|
+
export default Rect2;
|
@@ -0,0 +1,252 @@
|
|
1
|
+
import LineSegment2 from './LineSegment2.mjs';
|
2
|
+
import { Vec2 } from '../Vec2.mjs';
|
3
|
+
import Abstract2DShape from './Abstract2DShape.mjs';
|
4
|
+
// invariant: w ≥ 0, h ≥ 0, immutable
|
5
|
+
export class Rect2 extends Abstract2DShape {
|
6
|
+
constructor(x, y, w, h) {
|
7
|
+
super();
|
8
|
+
this.x = x;
|
9
|
+
this.y = y;
|
10
|
+
this.w = w;
|
11
|
+
this.h = h;
|
12
|
+
if (w < 0) {
|
13
|
+
this.x += w;
|
14
|
+
this.w = Math.abs(w);
|
15
|
+
}
|
16
|
+
if (h < 0) {
|
17
|
+
this.y += h;
|
18
|
+
this.h = Math.abs(h);
|
19
|
+
}
|
20
|
+
// Precompute/store vector forms.
|
21
|
+
this.topLeft = Vec2.of(this.x, this.y);
|
22
|
+
this.size = Vec2.of(this.w, this.h);
|
23
|
+
this.bottomRight = this.topLeft.plus(this.size);
|
24
|
+
this.area = this.w * this.h;
|
25
|
+
}
|
26
|
+
translatedBy(vec) {
|
27
|
+
return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
|
28
|
+
}
|
29
|
+
// Returns a copy of this with the given size (but same top-left).
|
30
|
+
resizedTo(size) {
|
31
|
+
return new Rect2(this.x, this.y, size.x, size.y);
|
32
|
+
}
|
33
|
+
containsPoint(other) {
|
34
|
+
return this.x <= other.x && this.y <= other.y
|
35
|
+
&& this.x + this.w >= other.x && this.y + this.h >= other.y;
|
36
|
+
}
|
37
|
+
containsRect(other) {
|
38
|
+
return this.x <= other.x && this.y <= other.y
|
39
|
+
&& this.bottomRight.x >= other.bottomRight.x
|
40
|
+
&& this.bottomRight.y >= other.bottomRight.y;
|
41
|
+
}
|
42
|
+
intersects(other) {
|
43
|
+
// Project along x/y axes.
|
44
|
+
const thisMinX = this.x;
|
45
|
+
const thisMaxX = thisMinX + this.w;
|
46
|
+
const otherMinX = other.x;
|
47
|
+
const otherMaxX = other.x + other.w;
|
48
|
+
if (thisMaxX < otherMinX || thisMinX > otherMaxX) {
|
49
|
+
return false;
|
50
|
+
}
|
51
|
+
const thisMinY = this.y;
|
52
|
+
const thisMaxY = thisMinY + this.h;
|
53
|
+
const otherMinY = other.y;
|
54
|
+
const otherMaxY = other.y + other.h;
|
55
|
+
if (thisMaxY < otherMinY || thisMinY > otherMaxY) {
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
return true;
|
59
|
+
}
|
60
|
+
// Returns the overlap of this and [other], or null, if no such
|
61
|
+
// overlap exists
|
62
|
+
intersection(other) {
|
63
|
+
if (!this.intersects(other)) {
|
64
|
+
return null;
|
65
|
+
}
|
66
|
+
const topLeft = this.topLeft.zip(other.topLeft, Math.max);
|
67
|
+
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min);
|
68
|
+
return Rect2.fromCorners(topLeft, bottomRight);
|
69
|
+
}
|
70
|
+
// Returns a new rectangle containing both [this] and [other].
|
71
|
+
union(other) {
|
72
|
+
return Rect2.union(this, other);
|
73
|
+
}
|
74
|
+
// Returns a the subdivision of this into [columns] columns
|
75
|
+
// and [rows] rows. For example,
|
76
|
+
// Rect2.unitSquare.divideIntoGrid(2, 2)
|
77
|
+
// -> [ Rect2(0, 0, 0.5, 0.5), Rect2(0.5, 0, 0.5, 0.5), Rect2(0, 0.5, 0.5, 0.5), Rect2(0.5, 0.5, 0.5, 0.5) ]
|
78
|
+
// The rectangles are ordered in row-major order.
|
79
|
+
divideIntoGrid(columns, rows) {
|
80
|
+
const result = [];
|
81
|
+
if (columns <= 0 || rows <= 0) {
|
82
|
+
return result;
|
83
|
+
}
|
84
|
+
const eachRectWidth = this.w / columns;
|
85
|
+
const eachRectHeight = this.h / rows;
|
86
|
+
if (eachRectWidth === 0) {
|
87
|
+
columns = 1;
|
88
|
+
}
|
89
|
+
if (eachRectHeight === 0) {
|
90
|
+
rows = 1;
|
91
|
+
}
|
92
|
+
for (let j = 0; j < rows; j++) {
|
93
|
+
for (let i = 0; i < columns; i++) {
|
94
|
+
const x = eachRectWidth * i + this.x;
|
95
|
+
const y = eachRectHeight * j + this.y;
|
96
|
+
result.push(new Rect2(x, y, eachRectWidth, eachRectHeight));
|
97
|
+
}
|
98
|
+
}
|
99
|
+
return result;
|
100
|
+
}
|
101
|
+
// Returns a rectangle containing this and [point].
|
102
|
+
// [margin] is the minimum distance between the new point and the edge
|
103
|
+
// of the resultant rectangle.
|
104
|
+
grownToPoint(point, margin = 0) {
|
105
|
+
const otherRect = new Rect2(point.x - margin, point.y - margin, margin * 2, margin * 2);
|
106
|
+
return this.union(otherRect);
|
107
|
+
}
|
108
|
+
// Returns this grown by [margin] in both the x and y directions.
|
109
|
+
grownBy(margin) {
|
110
|
+
if (margin === 0) {
|
111
|
+
return this;
|
112
|
+
}
|
113
|
+
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
114
|
+
}
|
115
|
+
getClosestPointOnBoundaryTo(target) {
|
116
|
+
const closestEdgePoints = this.getEdges().map(edge => {
|
117
|
+
return edge.closestPointTo(target);
|
118
|
+
});
|
119
|
+
let closest = null;
|
120
|
+
let closestDist = null;
|
121
|
+
for (const point of closestEdgePoints) {
|
122
|
+
const dist = point.minus(target).length();
|
123
|
+
if (closestDist === null || dist < closestDist) {
|
124
|
+
closest = point;
|
125
|
+
closestDist = dist;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
return closest;
|
129
|
+
}
|
130
|
+
get corners() {
|
131
|
+
return [
|
132
|
+
this.bottomRight,
|
133
|
+
this.topRight,
|
134
|
+
this.topLeft,
|
135
|
+
this.bottomLeft,
|
136
|
+
];
|
137
|
+
}
|
138
|
+
get maxDimension() {
|
139
|
+
return Math.max(this.w, this.h);
|
140
|
+
}
|
141
|
+
get topRight() {
|
142
|
+
return this.bottomRight.plus(Vec2.of(0, -this.h));
|
143
|
+
}
|
144
|
+
get bottomLeft() {
|
145
|
+
return this.topLeft.plus(Vec2.of(0, this.h));
|
146
|
+
}
|
147
|
+
get width() {
|
148
|
+
return this.w;
|
149
|
+
}
|
150
|
+
get height() {
|
151
|
+
return this.h;
|
152
|
+
}
|
153
|
+
get center() {
|
154
|
+
return this.topLeft.plus(this.size.times(0.5));
|
155
|
+
}
|
156
|
+
// Returns edges in the order
|
157
|
+
// [ rightEdge, topEdge, leftEdge, bottomEdge ]
|
158
|
+
getEdges() {
|
159
|
+
const corners = this.corners;
|
160
|
+
return [
|
161
|
+
new LineSegment2(corners[0], corners[1]),
|
162
|
+
new LineSegment2(corners[1], corners[2]),
|
163
|
+
new LineSegment2(corners[2], corners[3]),
|
164
|
+
new LineSegment2(corners[3], corners[0]),
|
165
|
+
];
|
166
|
+
}
|
167
|
+
intersectsLineSegment(lineSegment) {
|
168
|
+
const result = [];
|
169
|
+
for (const edge of this.getEdges()) {
|
170
|
+
const intersection = edge.intersectsLineSegment(lineSegment);
|
171
|
+
intersection.forEach(point => result.push(point));
|
172
|
+
}
|
173
|
+
return result;
|
174
|
+
}
|
175
|
+
signedDistance(point) {
|
176
|
+
const closestBoundaryPoint = this.getClosestPointOnBoundaryTo(point);
|
177
|
+
const dist = point.minus(closestBoundaryPoint).magnitude();
|
178
|
+
if (this.containsPoint(point)) {
|
179
|
+
return -dist;
|
180
|
+
}
|
181
|
+
return dist;
|
182
|
+
}
|
183
|
+
getTightBoundingBox() {
|
184
|
+
return this;
|
185
|
+
}
|
186
|
+
// [affineTransform] is a transformation matrix that both scales and **translates**.
|
187
|
+
// the bounding box of this' four corners after transformed by the given affine transformation.
|
188
|
+
transformedBoundingBox(affineTransform) {
|
189
|
+
return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
|
190
|
+
}
|
191
|
+
/** @return true iff this is equal to [other] ± fuzz */
|
192
|
+
eq(other, fuzz = 0) {
|
193
|
+
return this.topLeft.eq(other.topLeft, fuzz) && this.size.eq(other.size, fuzz);
|
194
|
+
}
|
195
|
+
toString() {
|
196
|
+
return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
|
197
|
+
}
|
198
|
+
static fromCorners(corner1, corner2) {
|
199
|
+
return new Rect2(Math.min(corner1.x, corner2.x), Math.min(corner1.y, corner2.y), Math.abs(corner1.x - corner2.x), Math.abs(corner1.y - corner2.y));
|
200
|
+
}
|
201
|
+
// Returns a box that contains all points in [points] with at least [margin]
|
202
|
+
// between each point and the edge of the box.
|
203
|
+
static bboxOf(points, margin = 0) {
|
204
|
+
let minX = 0;
|
205
|
+
let minY = 0;
|
206
|
+
let maxX = 0;
|
207
|
+
let maxY = 0;
|
208
|
+
let isFirst = true;
|
209
|
+
for (const point of points) {
|
210
|
+
if (isFirst) {
|
211
|
+
minX = point.x;
|
212
|
+
minY = point.y;
|
213
|
+
maxX = point.x;
|
214
|
+
maxY = point.y;
|
215
|
+
isFirst = false;
|
216
|
+
}
|
217
|
+
minX = Math.min(minX, point.x);
|
218
|
+
minY = Math.min(minY, point.y);
|
219
|
+
maxX = Math.max(maxX, point.x);
|
220
|
+
maxY = Math.max(maxY, point.y);
|
221
|
+
}
|
222
|
+
return Rect2.fromCorners(Vec2.of(minX - margin, minY - margin), Vec2.of(maxX + margin, maxY + margin));
|
223
|
+
}
|
224
|
+
// @returns a rectangle that contains all of the given rectangles, the bounding box
|
225
|
+
// of the given rectangles.
|
226
|
+
static union(...rects) {
|
227
|
+
if (rects.length === 0) {
|
228
|
+
return Rect2.empty;
|
229
|
+
}
|
230
|
+
const firstRect = rects[0];
|
231
|
+
let minX = firstRect.topLeft.x;
|
232
|
+
let minY = firstRect.topLeft.y;
|
233
|
+
let maxX = firstRect.bottomRight.x;
|
234
|
+
let maxY = firstRect.bottomRight.y;
|
235
|
+
for (let i = 1; i < rects.length; i++) {
|
236
|
+
const rect = rects[i];
|
237
|
+
minX = Math.min(minX, rect.topLeft.x);
|
238
|
+
minY = Math.min(minY, rect.topLeft.y);
|
239
|
+
maxX = Math.max(maxX, rect.bottomRight.x);
|
240
|
+
maxY = Math.max(maxY, rect.bottomRight.y);
|
241
|
+
}
|
242
|
+
return new Rect2(minX, minY, maxX - minX, maxY - minY);
|
243
|
+
}
|
244
|
+
static of(template) {
|
245
|
+
const width = template.width ?? template.w ?? 0;
|
246
|
+
const height = template.height ?? template.h ?? 0;
|
247
|
+
return new Rect2(template.x, template.y, width, height);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
Rect2.empty = new Rect2(0, 0, 0, 0);
|
251
|
+
Rect2.unitSquare = new Rect2(0, 0, 1, 1);
|
252
|
+
export default Rect2;
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import Mat33 from '../Mat33';
|
2
|
+
import { Point2 } from '../Vec2';
|
3
|
+
import Vec3 from '../Vec3';
|
4
|
+
import Abstract2DShape from './Abstract2DShape';
|
5
|
+
import LineSegment2 from './LineSegment2';
|
6
|
+
import Rect2 from './Rect2';
|
7
|
+
type TriangleBoundary = [LineSegment2, LineSegment2, LineSegment2];
|
8
|
+
export default class Triangle extends Abstract2DShape {
|
9
|
+
#private;
|
10
|
+
readonly vertex1: Vec3;
|
11
|
+
readonly vertex2: Vec3;
|
12
|
+
readonly vertex3: Vec3;
|
13
|
+
/**
|
14
|
+
* @see {@link fromVertices}
|
15
|
+
*/
|
16
|
+
protected constructor(vertex1: Vec3, vertex2: Vec3, vertex3: Vec3);
|
17
|
+
/**
|
18
|
+
* Creates a triangle from its three corners. Corners may be stored in a different
|
19
|
+
* order than given.
|
20
|
+
*/
|
21
|
+
static fromVertices(vertex1: Vec3, vertex2: Vec3, vertex3: Vec3): Triangle;
|
22
|
+
get vertices(): [Point2, Point2, Point2];
|
23
|
+
map(mapping: (vertex: Vec3) => Vec3): Triangle;
|
24
|
+
transformed2DBy(affineTransform: Mat33): Triangle;
|
25
|
+
transformedBy(linearTransform: Mat33): Triangle;
|
26
|
+
/**
|
27
|
+
* Returns the sides of this triangle, as an array of `LineSegment2`s.
|
28
|
+
*
|
29
|
+
* The first side is from `vertex1` to `vertex2`, the next from `vertex2` to `vertex3`,
|
30
|
+
* and the last from `vertex3` to `vertex1`.
|
31
|
+
*/
|
32
|
+
getEdges(): TriangleBoundary;
|
33
|
+
intersectsLineSegment(lineSegment: LineSegment2): Vec3[];
|
34
|
+
/** @inheritdoc */
|
35
|
+
containsPoint(point: Vec3, epsilon?: number): boolean;
|
36
|
+
/**
|
37
|
+
* @returns the signed distance from `point` to the closest edge of this triangle.
|
38
|
+
*
|
39
|
+
* If `point` is inside `this`, the result is negative, otherwise, the result is
|
40
|
+
* positive.
|
41
|
+
*/
|
42
|
+
signedDistance(point: Vec3): number;
|
43
|
+
/** @inheritdoc */
|
44
|
+
getTightBoundingBox(): Rect2;
|
45
|
+
}
|
46
|
+
export {};
|
@@ -0,0 +1,121 @@
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
5
|
+
};
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
11
|
+
};
|
12
|
+
var _Triangle_sides;
|
13
|
+
import Abstract2DShape from './Abstract2DShape.mjs';
|
14
|
+
import LineSegment2 from './LineSegment2.mjs';
|
15
|
+
import Rect2 from './Rect2.mjs';
|
16
|
+
class Triangle extends Abstract2DShape {
|
17
|
+
/**
|
18
|
+
* @see {@link fromVertices}
|
19
|
+
*/
|
20
|
+
constructor(vertex1, vertex2, vertex3) {
|
21
|
+
super();
|
22
|
+
this.vertex1 = vertex1;
|
23
|
+
this.vertex2 = vertex2;
|
24
|
+
this.vertex3 = vertex3;
|
25
|
+
_Triangle_sides.set(this, undefined);
|
26
|
+
}
|
27
|
+
/**
|
28
|
+
* Creates a triangle from its three corners. Corners may be stored in a different
|
29
|
+
* order than given.
|
30
|
+
*/
|
31
|
+
static fromVertices(vertex1, vertex2, vertex3) {
|
32
|
+
return new Triangle(vertex1, vertex2, vertex3);
|
33
|
+
}
|
34
|
+
get vertices() {
|
35
|
+
return [this.vertex1, this.vertex2, this.vertex3];
|
36
|
+
}
|
37
|
+
map(mapping) {
|
38
|
+
return new Triangle(mapping(this.vertex1), mapping(this.vertex2), mapping(this.vertex3));
|
39
|
+
}
|
40
|
+
// Transform, treating this as composed of 2D points.
|
41
|
+
transformed2DBy(affineTransform) {
|
42
|
+
return this.map(affineTransform.transformVec2);
|
43
|
+
}
|
44
|
+
// Transforms this by a linear transform --- verticies are treated as
|
45
|
+
// 3D points.
|
46
|
+
transformedBy(linearTransform) {
|
47
|
+
return this.map(linearTransform.transformVec3);
|
48
|
+
}
|
49
|
+
/**
|
50
|
+
* Returns the sides of this triangle, as an array of `LineSegment2`s.
|
51
|
+
*
|
52
|
+
* The first side is from `vertex1` to `vertex2`, the next from `vertex2` to `vertex3`,
|
53
|
+
* and the last from `vertex3` to `vertex1`.
|
54
|
+
*/
|
55
|
+
getEdges() {
|
56
|
+
if (__classPrivateFieldGet(this, _Triangle_sides, "f")) {
|
57
|
+
return __classPrivateFieldGet(this, _Triangle_sides, "f");
|
58
|
+
}
|
59
|
+
const side1 = new LineSegment2(this.vertex1, this.vertex2);
|
60
|
+
const side2 = new LineSegment2(this.vertex2, this.vertex3);
|
61
|
+
const side3 = new LineSegment2(this.vertex3, this.vertex1);
|
62
|
+
const sides = [side1, side2, side3];
|
63
|
+
__classPrivateFieldSet(this, _Triangle_sides, sides, "f");
|
64
|
+
return sides;
|
65
|
+
}
|
66
|
+
intersectsLineSegment(lineSegment) {
|
67
|
+
const result = [];
|
68
|
+
for (const edge of this.getEdges()) {
|
69
|
+
edge.intersectsLineSegment(lineSegment)
|
70
|
+
.forEach(point => result.push(point));
|
71
|
+
}
|
72
|
+
return result;
|
73
|
+
}
|
74
|
+
/** @inheritdoc */
|
75
|
+
containsPoint(point, epsilon = Abstract2DShape.smallValue) {
|
76
|
+
// Project `point` onto normals to each of this' sides.
|
77
|
+
// Uses the Separating Axis Theorem (https://en.wikipedia.org/wiki/Hyperplane_separation_theorem#Use_in_collision_detection)
|
78
|
+
const sides = this.getEdges();
|
79
|
+
for (const side of sides) {
|
80
|
+
const orthog = side.direction.orthog();
|
81
|
+
// Project all three vertices
|
82
|
+
// TODO: Performance can be improved here (two vertices will always have the same projection)
|
83
|
+
const projv1 = orthog.dot(this.vertex1);
|
84
|
+
const projv2 = orthog.dot(this.vertex2);
|
85
|
+
const projv3 = orthog.dot(this.vertex3);
|
86
|
+
const minProjVertex = Math.min(projv1, projv2, projv3);
|
87
|
+
const maxProjVertex = Math.max(projv1, projv2, projv3);
|
88
|
+
const projPoint = orthog.dot(point);
|
89
|
+
const inProjection = projPoint >= minProjVertex - epsilon && projPoint <= maxProjVertex + epsilon;
|
90
|
+
if (!inProjection) {
|
91
|
+
return false;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
return true;
|
95
|
+
}
|
96
|
+
/**
|
97
|
+
* @returns the signed distance from `point` to the closest edge of this triangle.
|
98
|
+
*
|
99
|
+
* If `point` is inside `this`, the result is negative, otherwise, the result is
|
100
|
+
* positive.
|
101
|
+
*/
|
102
|
+
signedDistance(point) {
|
103
|
+
const sides = this.getEdges();
|
104
|
+
const distances = sides.map(side => side.distance(point));
|
105
|
+
const distance = Math.min(...distances);
|
106
|
+
// If the point is in this' interior, signedDistance must return a negative
|
107
|
+
// number.
|
108
|
+
if (this.containsPoint(point, 0)) {
|
109
|
+
return -distance;
|
110
|
+
}
|
111
|
+
else {
|
112
|
+
return distance;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
/** @inheritdoc */
|
116
|
+
getTightBoundingBox() {
|
117
|
+
return Rect2.bboxOf(this.vertices);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
_Triangle_sides = new WeakMap();
|
121
|
+
export default Triangle;
|