@js-draw/math 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. package/README.md +3 -0
  2. package/build-config.json +4 -0
  3. package/dist/cjs/Color4.d.ts +83 -0
  4. package/dist/cjs/Color4.js +277 -0
  5. package/dist/cjs/Mat33.d.ts +131 -0
  6. package/dist/cjs/Mat33.js +345 -0
  7. package/dist/cjs/Vec2.d.ts +42 -0
  8. package/dist/cjs/Vec2.js +48 -0
  9. package/dist/cjs/Vec3.d.ts +126 -0
  10. package/dist/cjs/Vec3.js +203 -0
  11. package/dist/cjs/lib.d.ts +27 -0
  12. package/dist/cjs/lib.js +42 -0
  13. package/dist/cjs/polynomial/solveQuadratic.d.ts +9 -0
  14. package/dist/cjs/polynomial/solveQuadratic.js +39 -0
  15. package/dist/cjs/rounding.d.ts +15 -0
  16. package/dist/cjs/rounding.js +146 -0
  17. package/dist/cjs/shapes/Abstract2DShape.d.ts +49 -0
  18. package/dist/cjs/shapes/Abstract2DShape.js +38 -0
  19. package/dist/cjs/shapes/BezierJSWrapper.d.ts +36 -0
  20. package/dist/cjs/shapes/BezierJSWrapper.js +94 -0
  21. package/dist/cjs/shapes/CubicBezier.d.ts +17 -0
  22. package/dist/cjs/shapes/CubicBezier.js +35 -0
  23. package/dist/cjs/shapes/LineSegment2.d.ts +70 -0
  24. package/dist/cjs/shapes/LineSegment2.js +183 -0
  25. package/dist/cjs/shapes/Path.d.ts +96 -0
  26. package/dist/cjs/shapes/Path.js +766 -0
  27. package/dist/cjs/shapes/PointShape2D.d.ts +18 -0
  28. package/dist/cjs/shapes/PointShape2D.js +31 -0
  29. package/dist/cjs/shapes/QuadraticBezier.d.ts +35 -0
  30. package/dist/cjs/shapes/QuadraticBezier.js +120 -0
  31. package/dist/cjs/shapes/Rect2.d.ts +58 -0
  32. package/dist/cjs/shapes/Rect2.js +259 -0
  33. package/dist/cjs/shapes/Triangle.d.ts +46 -0
  34. package/dist/cjs/shapes/Triangle.js +126 -0
  35. package/dist/mjs/Color4.d.ts +83 -0
  36. package/dist/mjs/Color4.mjs +271 -0
  37. package/dist/mjs/Mat33.d.ts +131 -0
  38. package/dist/mjs/Mat33.mjs +338 -0
  39. package/dist/mjs/Vec2.d.ts +42 -0
  40. package/dist/mjs/Vec2.mjs +42 -0
  41. package/dist/mjs/Vec3.d.ts +126 -0
  42. package/dist/mjs/Vec3.mjs +199 -0
  43. package/dist/mjs/lib.d.ts +27 -0
  44. package/dist/mjs/lib.mjs +29 -0
  45. package/dist/mjs/polynomial/solveQuadratic.d.ts +9 -0
  46. package/dist/mjs/polynomial/solveQuadratic.mjs +37 -0
  47. package/dist/mjs/rounding.d.ts +15 -0
  48. package/dist/mjs/rounding.mjs +139 -0
  49. package/dist/mjs/shapes/Abstract2DShape.d.ts +49 -0
  50. package/dist/mjs/shapes/Abstract2DShape.mjs +36 -0
  51. package/dist/mjs/shapes/BezierJSWrapper.d.ts +36 -0
  52. package/dist/mjs/shapes/BezierJSWrapper.mjs +89 -0
  53. package/dist/mjs/shapes/CubicBezier.d.ts +17 -0
  54. package/dist/mjs/shapes/CubicBezier.mjs +30 -0
  55. package/dist/mjs/shapes/LineSegment2.d.ts +70 -0
  56. package/dist/mjs/shapes/LineSegment2.mjs +176 -0
  57. package/dist/mjs/shapes/Path.d.ts +96 -0
  58. package/dist/mjs/shapes/Path.mjs +759 -0
  59. package/dist/mjs/shapes/PointShape2D.d.ts +18 -0
  60. package/dist/mjs/shapes/PointShape2D.mjs +26 -0
  61. package/dist/mjs/shapes/QuadraticBezier.d.ts +35 -0
  62. package/dist/mjs/shapes/QuadraticBezier.mjs +113 -0
  63. package/dist/mjs/shapes/Rect2.d.ts +58 -0
  64. package/dist/mjs/shapes/Rect2.mjs +252 -0
  65. package/dist/mjs/shapes/Triangle.d.ts +46 -0
  66. package/dist/mjs/shapes/Triangle.mjs +121 -0
  67. package/package.json +48 -0
  68. package/tsconfig.json +7 -0
  69. 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;