@js-draw/math 1.0.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/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,31 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
|
7
|
+
const Rect2_1 = __importDefault(require("./Rect2"));
|
8
|
+
/**
|
9
|
+
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
10
|
+
*
|
11
|
+
* Access the internal `Point2` using the `p` property.
|
12
|
+
*/
|
13
|
+
class PointShape2D extends Abstract2DShape_1.default {
|
14
|
+
constructor(p) {
|
15
|
+
super();
|
16
|
+
this.p = p;
|
17
|
+
}
|
18
|
+
signedDistance(point) {
|
19
|
+
return this.p.minus(point).magnitude();
|
20
|
+
}
|
21
|
+
intersectsLineSegment(lineSegment, epsilon) {
|
22
|
+
if (lineSegment.containsPoint(this.p, epsilon)) {
|
23
|
+
return [this.p];
|
24
|
+
}
|
25
|
+
return [];
|
26
|
+
}
|
27
|
+
getTightBoundingBox() {
|
28
|
+
return new Rect2_1.default(this.p.x, this.p.y, 0, 0);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
exports.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,120 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.QuadraticBezier = void 0;
|
7
|
+
const Vec2_1 = require("../Vec2");
|
8
|
+
const solveQuadratic_1 = __importDefault(require("../polynomial/solveQuadratic"));
|
9
|
+
const BezierJSWrapper_1 = __importDefault(require("./BezierJSWrapper"));
|
10
|
+
const Rect2_1 = __importDefault(require("./Rect2"));
|
11
|
+
/**
|
12
|
+
* A wrapper around `bezier-js`'s quadratic Bézier.
|
13
|
+
*
|
14
|
+
* This wrappper lazy-loads `bezier-js`'s Bézier and can perform some operations
|
15
|
+
* without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
|
16
|
+
*/
|
17
|
+
class QuadraticBezier extends BezierJSWrapper_1.default {
|
18
|
+
constructor(p0, p1, p2) {
|
19
|
+
super();
|
20
|
+
this.p0 = p0;
|
21
|
+
this.p1 = p1;
|
22
|
+
this.p2 = p2;
|
23
|
+
}
|
24
|
+
/**
|
25
|
+
* Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
|
26
|
+
* all y components of the target curve.
|
27
|
+
*/
|
28
|
+
static componentAt(t, p0, p1, p2) {
|
29
|
+
return p0 + t * (-2 * p0 + 2 * p1) + t * t * (p0 - 2 * p1 + p2);
|
30
|
+
}
|
31
|
+
static derivativeComponentAt(t, p0, p1, p2) {
|
32
|
+
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* @returns the curve evaluated at `t`.
|
36
|
+
*/
|
37
|
+
at(t) {
|
38
|
+
const p0 = this.p0;
|
39
|
+
const p1 = this.p1;
|
40
|
+
const p2 = this.p2;
|
41
|
+
return Vec2_1.Vec2.of(QuadraticBezier.componentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.componentAt(t, p0.y, p1.y, p2.y));
|
42
|
+
}
|
43
|
+
derivativeAt(t) {
|
44
|
+
const p0 = this.p0;
|
45
|
+
const p1 = this.p1;
|
46
|
+
const p2 = this.p2;
|
47
|
+
return Vec2_1.Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
48
|
+
}
|
49
|
+
normal(t) {
|
50
|
+
const tangent = this.derivativeAt(t);
|
51
|
+
return tangent.orthog().normalized();
|
52
|
+
}
|
53
|
+
/** @returns an overestimate of this shape's bounding box. */
|
54
|
+
getLooseBoundingBox() {
|
55
|
+
return Rect2_1.default.bboxOf([this.p0, this.p1, this.p2]);
|
56
|
+
}
|
57
|
+
/**
|
58
|
+
* @returns the *approximate* distance from `point` to this curve.
|
59
|
+
*/
|
60
|
+
approximateDistance(point) {
|
61
|
+
// We want to minimize f(t) = |B(t) - p|².
|
62
|
+
// Expanding,
|
63
|
+
// f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
|
64
|
+
// ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
|
65
|
+
//
|
66
|
+
// Considering just one component,
|
67
|
+
// Dₜ(Bₓ(t) - pₓ)² = 2(Bₓ(t) - pₓ)(DₜBₓ(t))
|
68
|
+
// = 2(Bₓ(t)DₜBₓ(t) - pₓBₓ(t))
|
69
|
+
// = 2(p0ₓ + (t)(-2p0ₓ + 2p1ₓ) + (t²)(p0ₓ - 2p1ₓ + p2ₓ) - pₓ)((-2p0ₓ + 2p1ₓ) + 2(t)(p0ₓ - 2p1ₓ + p2ₓ))
|
70
|
+
// - (pₓ)((-2p0ₓ + 2p1ₓ) + (t)(p0ₓ - 2p1ₓ + p2ₓ))
|
71
|
+
const A = this.p0.x - point.x;
|
72
|
+
const B = -2 * this.p0.x + 2 * this.p1.x;
|
73
|
+
const C = this.p0.x - 2 * this.p1.x + this.p2.x;
|
74
|
+
// Let A = p0ₓ - pₓ, B = -2p0ₓ + 2p1ₓ, C = p0ₓ - 2p1ₓ + p2ₓ. We then have,
|
75
|
+
// Dₜ(Bₓ(t) - pₓ)²
|
76
|
+
// = 2(A + tB + t²C)(B + 2tC) - (pₓ)(B + 2tC)
|
77
|
+
// = 2(AB + tB² + t²BC + 2tCA + 2tCtB + 2tCt²C) - pₓB - pₓ2tC
|
78
|
+
// = 2(AB + tB² + 2tCA + t²BC + 2t²CB + 2C²t³) - pₓB - pₓ2tC
|
79
|
+
// = 2AB + 2t(B² + 2CA) + 2t²(BC + 2CB) + 4C²t³ - pₓB - pₓ2tC
|
80
|
+
// = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
81
|
+
//
|
82
|
+
const D = this.p0.y - point.y;
|
83
|
+
const E = -2 * this.p0.y + 2 * this.p1.y;
|
84
|
+
const F = this.p0.y - 2 * this.p1.y + this.p2.y;
|
85
|
+
// Using D = p0ᵧ - pᵧ, E = -2p0ᵧ + 2p1ᵧ, F = p0ᵧ - 2p1ᵧ + p2ᵧ, we thus have,
|
86
|
+
// f'(t) = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
87
|
+
// + 2DE + 2t(E² + 2FD - pᵧF) + 2t²(EF + 2FE) + 4F²t³ - pᵧE
|
88
|
+
const a = 2 * A * B + 2 * D * E - point.x * B - point.y * E;
|
89
|
+
const b = 2 * B * B + 2 * E * E + 2 * C * A + 2 * F * D - point.x * C - point.y * F;
|
90
|
+
const c = 2 * E * F + 2 * B * C + 2 * C * B + 2 * F * E;
|
91
|
+
//const d = 4 * C * C + 4 * F * F;
|
92
|
+
// Thus,
|
93
|
+
// f'(t) = a + bt + ct² + dt³
|
94
|
+
const fDerivAtZero = a;
|
95
|
+
const f2ndDerivAtZero = b;
|
96
|
+
const f3rdDerivAtZero = 2 * c;
|
97
|
+
// Using the first few terms of a Maclaurin series to approximate f'(t),
|
98
|
+
// f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
|
99
|
+
let [min1, min2] = (0, solveQuadratic_1.default)(f3rdDerivAtZero / 2, f2ndDerivAtZero, fDerivAtZero);
|
100
|
+
// If the quadratic has no solutions, approximate.
|
101
|
+
if (isNaN(min1)) {
|
102
|
+
min1 = 0.25;
|
103
|
+
}
|
104
|
+
if (isNaN(min2)) {
|
105
|
+
min2 = 0.75;
|
106
|
+
}
|
107
|
+
const at1 = this.at(min1);
|
108
|
+
const at2 = this.at(min2);
|
109
|
+
const sqrDist1 = at1.minus(point).magnitudeSquared();
|
110
|
+
const sqrDist2 = at2.minus(point).magnitudeSquared();
|
111
|
+
const sqrDist3 = this.at(0).minus(point).magnitudeSquared();
|
112
|
+
const sqrDist4 = this.at(1).minus(point).magnitudeSquared();
|
113
|
+
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
114
|
+
}
|
115
|
+
getPoints() {
|
116
|
+
return [this.p0, this.p1, this.p2];
|
117
|
+
}
|
118
|
+
}
|
119
|
+
exports.QuadraticBezier = QuadraticBezier;
|
120
|
+
exports.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,259 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.Rect2 = void 0;
|
7
|
+
const LineSegment2_1 = __importDefault(require("./LineSegment2"));
|
8
|
+
const Vec2_1 = require("../Vec2");
|
9
|
+
const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
|
10
|
+
// invariant: w ≥ 0, h ≥ 0, immutable
|
11
|
+
class Rect2 extends Abstract2DShape_1.default {
|
12
|
+
constructor(x, y, w, h) {
|
13
|
+
super();
|
14
|
+
this.x = x;
|
15
|
+
this.y = y;
|
16
|
+
this.w = w;
|
17
|
+
this.h = h;
|
18
|
+
if (w < 0) {
|
19
|
+
this.x += w;
|
20
|
+
this.w = Math.abs(w);
|
21
|
+
}
|
22
|
+
if (h < 0) {
|
23
|
+
this.y += h;
|
24
|
+
this.h = Math.abs(h);
|
25
|
+
}
|
26
|
+
// Precompute/store vector forms.
|
27
|
+
this.topLeft = Vec2_1.Vec2.of(this.x, this.y);
|
28
|
+
this.size = Vec2_1.Vec2.of(this.w, this.h);
|
29
|
+
this.bottomRight = this.topLeft.plus(this.size);
|
30
|
+
this.area = this.w * this.h;
|
31
|
+
}
|
32
|
+
translatedBy(vec) {
|
33
|
+
return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
|
34
|
+
}
|
35
|
+
// Returns a copy of this with the given size (but same top-left).
|
36
|
+
resizedTo(size) {
|
37
|
+
return new Rect2(this.x, this.y, size.x, size.y);
|
38
|
+
}
|
39
|
+
containsPoint(other) {
|
40
|
+
return this.x <= other.x && this.y <= other.y
|
41
|
+
&& this.x + this.w >= other.x && this.y + this.h >= other.y;
|
42
|
+
}
|
43
|
+
containsRect(other) {
|
44
|
+
return this.x <= other.x && this.y <= other.y
|
45
|
+
&& this.bottomRight.x >= other.bottomRight.x
|
46
|
+
&& this.bottomRight.y >= other.bottomRight.y;
|
47
|
+
}
|
48
|
+
intersects(other) {
|
49
|
+
// Project along x/y axes.
|
50
|
+
const thisMinX = this.x;
|
51
|
+
const thisMaxX = thisMinX + this.w;
|
52
|
+
const otherMinX = other.x;
|
53
|
+
const otherMaxX = other.x + other.w;
|
54
|
+
if (thisMaxX < otherMinX || thisMinX > otherMaxX) {
|
55
|
+
return false;
|
56
|
+
}
|
57
|
+
const thisMinY = this.y;
|
58
|
+
const thisMaxY = thisMinY + this.h;
|
59
|
+
const otherMinY = other.y;
|
60
|
+
const otherMaxY = other.y + other.h;
|
61
|
+
if (thisMaxY < otherMinY || thisMinY > otherMaxY) {
|
62
|
+
return false;
|
63
|
+
}
|
64
|
+
return true;
|
65
|
+
}
|
66
|
+
// Returns the overlap of this and [other], or null, if no such
|
67
|
+
// overlap exists
|
68
|
+
intersection(other) {
|
69
|
+
if (!this.intersects(other)) {
|
70
|
+
return null;
|
71
|
+
}
|
72
|
+
const topLeft = this.topLeft.zip(other.topLeft, Math.max);
|
73
|
+
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min);
|
74
|
+
return Rect2.fromCorners(topLeft, bottomRight);
|
75
|
+
}
|
76
|
+
// Returns a new rectangle containing both [this] and [other].
|
77
|
+
union(other) {
|
78
|
+
return Rect2.union(this, other);
|
79
|
+
}
|
80
|
+
// Returns a the subdivision of this into [columns] columns
|
81
|
+
// and [rows] rows. For example,
|
82
|
+
// Rect2.unitSquare.divideIntoGrid(2, 2)
|
83
|
+
// -> [ 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) ]
|
84
|
+
// The rectangles are ordered in row-major order.
|
85
|
+
divideIntoGrid(columns, rows) {
|
86
|
+
const result = [];
|
87
|
+
if (columns <= 0 || rows <= 0) {
|
88
|
+
return result;
|
89
|
+
}
|
90
|
+
const eachRectWidth = this.w / columns;
|
91
|
+
const eachRectHeight = this.h / rows;
|
92
|
+
if (eachRectWidth === 0) {
|
93
|
+
columns = 1;
|
94
|
+
}
|
95
|
+
if (eachRectHeight === 0) {
|
96
|
+
rows = 1;
|
97
|
+
}
|
98
|
+
for (let j = 0; j < rows; j++) {
|
99
|
+
for (let i = 0; i < columns; i++) {
|
100
|
+
const x = eachRectWidth * i + this.x;
|
101
|
+
const y = eachRectHeight * j + this.y;
|
102
|
+
result.push(new Rect2(x, y, eachRectWidth, eachRectHeight));
|
103
|
+
}
|
104
|
+
}
|
105
|
+
return result;
|
106
|
+
}
|
107
|
+
// Returns a rectangle containing this and [point].
|
108
|
+
// [margin] is the minimum distance between the new point and the edge
|
109
|
+
// of the resultant rectangle.
|
110
|
+
grownToPoint(point, margin = 0) {
|
111
|
+
const otherRect = new Rect2(point.x - margin, point.y - margin, margin * 2, margin * 2);
|
112
|
+
return this.union(otherRect);
|
113
|
+
}
|
114
|
+
// Returns this grown by [margin] in both the x and y directions.
|
115
|
+
grownBy(margin) {
|
116
|
+
if (margin === 0) {
|
117
|
+
return this;
|
118
|
+
}
|
119
|
+
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
120
|
+
}
|
121
|
+
getClosestPointOnBoundaryTo(target) {
|
122
|
+
const closestEdgePoints = this.getEdges().map(edge => {
|
123
|
+
return edge.closestPointTo(target);
|
124
|
+
});
|
125
|
+
let closest = null;
|
126
|
+
let closestDist = null;
|
127
|
+
for (const point of closestEdgePoints) {
|
128
|
+
const dist = point.minus(target).length();
|
129
|
+
if (closestDist === null || dist < closestDist) {
|
130
|
+
closest = point;
|
131
|
+
closestDist = dist;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
return closest;
|
135
|
+
}
|
136
|
+
get corners() {
|
137
|
+
return [
|
138
|
+
this.bottomRight,
|
139
|
+
this.topRight,
|
140
|
+
this.topLeft,
|
141
|
+
this.bottomLeft,
|
142
|
+
];
|
143
|
+
}
|
144
|
+
get maxDimension() {
|
145
|
+
return Math.max(this.w, this.h);
|
146
|
+
}
|
147
|
+
get topRight() {
|
148
|
+
return this.bottomRight.plus(Vec2_1.Vec2.of(0, -this.h));
|
149
|
+
}
|
150
|
+
get bottomLeft() {
|
151
|
+
return this.topLeft.plus(Vec2_1.Vec2.of(0, this.h));
|
152
|
+
}
|
153
|
+
get width() {
|
154
|
+
return this.w;
|
155
|
+
}
|
156
|
+
get height() {
|
157
|
+
return this.h;
|
158
|
+
}
|
159
|
+
get center() {
|
160
|
+
return this.topLeft.plus(this.size.times(0.5));
|
161
|
+
}
|
162
|
+
// Returns edges in the order
|
163
|
+
// [ rightEdge, topEdge, leftEdge, bottomEdge ]
|
164
|
+
getEdges() {
|
165
|
+
const corners = this.corners;
|
166
|
+
return [
|
167
|
+
new LineSegment2_1.default(corners[0], corners[1]),
|
168
|
+
new LineSegment2_1.default(corners[1], corners[2]),
|
169
|
+
new LineSegment2_1.default(corners[2], corners[3]),
|
170
|
+
new LineSegment2_1.default(corners[3], corners[0]),
|
171
|
+
];
|
172
|
+
}
|
173
|
+
intersectsLineSegment(lineSegment) {
|
174
|
+
const result = [];
|
175
|
+
for (const edge of this.getEdges()) {
|
176
|
+
const intersection = edge.intersectsLineSegment(lineSegment);
|
177
|
+
intersection.forEach(point => result.push(point));
|
178
|
+
}
|
179
|
+
return result;
|
180
|
+
}
|
181
|
+
signedDistance(point) {
|
182
|
+
const closestBoundaryPoint = this.getClosestPointOnBoundaryTo(point);
|
183
|
+
const dist = point.minus(closestBoundaryPoint).magnitude();
|
184
|
+
if (this.containsPoint(point)) {
|
185
|
+
return -dist;
|
186
|
+
}
|
187
|
+
return dist;
|
188
|
+
}
|
189
|
+
getTightBoundingBox() {
|
190
|
+
return this;
|
191
|
+
}
|
192
|
+
// [affineTransform] is a transformation matrix that both scales and **translates**.
|
193
|
+
// the bounding box of this' four corners after transformed by the given affine transformation.
|
194
|
+
transformedBoundingBox(affineTransform) {
|
195
|
+
return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
|
196
|
+
}
|
197
|
+
/** @return true iff this is equal to [other] ± fuzz */
|
198
|
+
eq(other, fuzz = 0) {
|
199
|
+
return this.topLeft.eq(other.topLeft, fuzz) && this.size.eq(other.size, fuzz);
|
200
|
+
}
|
201
|
+
toString() {
|
202
|
+
return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
|
203
|
+
}
|
204
|
+
static fromCorners(corner1, corner2) {
|
205
|
+
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));
|
206
|
+
}
|
207
|
+
// Returns a box that contains all points in [points] with at least [margin]
|
208
|
+
// between each point and the edge of the box.
|
209
|
+
static bboxOf(points, margin = 0) {
|
210
|
+
let minX = 0;
|
211
|
+
let minY = 0;
|
212
|
+
let maxX = 0;
|
213
|
+
let maxY = 0;
|
214
|
+
let isFirst = true;
|
215
|
+
for (const point of points) {
|
216
|
+
if (isFirst) {
|
217
|
+
minX = point.x;
|
218
|
+
minY = point.y;
|
219
|
+
maxX = point.x;
|
220
|
+
maxY = point.y;
|
221
|
+
isFirst = false;
|
222
|
+
}
|
223
|
+
minX = Math.min(minX, point.x);
|
224
|
+
minY = Math.min(minY, point.y);
|
225
|
+
maxX = Math.max(maxX, point.x);
|
226
|
+
maxY = Math.max(maxY, point.y);
|
227
|
+
}
|
228
|
+
return Rect2.fromCorners(Vec2_1.Vec2.of(minX - margin, minY - margin), Vec2_1.Vec2.of(maxX + margin, maxY + margin));
|
229
|
+
}
|
230
|
+
// @returns a rectangle that contains all of the given rectangles, the bounding box
|
231
|
+
// of the given rectangles.
|
232
|
+
static union(...rects) {
|
233
|
+
if (rects.length === 0) {
|
234
|
+
return Rect2.empty;
|
235
|
+
}
|
236
|
+
const firstRect = rects[0];
|
237
|
+
let minX = firstRect.topLeft.x;
|
238
|
+
let minY = firstRect.topLeft.y;
|
239
|
+
let maxX = firstRect.bottomRight.x;
|
240
|
+
let maxY = firstRect.bottomRight.y;
|
241
|
+
for (let i = 1; i < rects.length; i++) {
|
242
|
+
const rect = rects[i];
|
243
|
+
minX = Math.min(minX, rect.topLeft.x);
|
244
|
+
minY = Math.min(minY, rect.topLeft.y);
|
245
|
+
maxX = Math.max(maxX, rect.bottomRight.x);
|
246
|
+
maxY = Math.max(maxY, rect.bottomRight.y);
|
247
|
+
}
|
248
|
+
return new Rect2(minX, minY, maxX - minX, maxY - minY);
|
249
|
+
}
|
250
|
+
static of(template) {
|
251
|
+
const width = template.width ?? template.w ?? 0;
|
252
|
+
const height = template.height ?? template.h ?? 0;
|
253
|
+
return new Rect2(template.x, template.y, width, height);
|
254
|
+
}
|
255
|
+
}
|
256
|
+
exports.Rect2 = Rect2;
|
257
|
+
Rect2.empty = new Rect2(0, 0, 0, 0);
|
258
|
+
Rect2.unitSquare = new Rect2(0, 0, 1, 1);
|
259
|
+
exports.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 {};
|