@js-draw/math 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Color4.d.ts +2 -2
- package/dist/cjs/shapes/Path.d.ts +7 -0
- package/dist/cjs/shapes/Path.js +33 -0
- package/dist/cjs/shapes/Rect2.d.ts +1 -1
- package/dist/cjs/shapes/Rect2.js +19 -11
- package/dist/mjs/Color4.d.ts +2 -2
- package/dist/mjs/Color4.mjs +1 -2
- package/dist/mjs/shapes/Path.d.ts +7 -0
- package/dist/mjs/shapes/Path.mjs +33 -0
- package/dist/mjs/shapes/Rect2.d.ts +1 -1
- package/dist/mjs/shapes/Rect2.mjs +19 -11
- package/package.json +2 -2
- package/src/Color4.ts +2 -2
- package/src/shapes/Path.ts +37 -0
- package/src/shapes/Rect2.test.ts +34 -0
- package/src/shapes/Rect2.ts +25 -12
package/dist/cjs/Color4.d.ts
CHANGED
@@ -12,7 +12,7 @@ import Vec3 from './Vec3';
|
|
12
12
|
* console.log('To string:', Color4.orange.toHexString());
|
13
13
|
* ```
|
14
14
|
*/
|
15
|
-
export
|
15
|
+
export declare class Color4 {
|
16
16
|
/** Red component. Should be in the range [0, 1]. */
|
17
17
|
readonly r: number;
|
18
18
|
/** Green component. ${\tt g} \in [0, 1]$ */
|
@@ -120,4 +120,4 @@ export default class Color4 {
|
|
120
120
|
static gray: Color4;
|
121
121
|
static white: Color4;
|
122
122
|
}
|
123
|
-
export
|
123
|
+
export default Color4;
|
@@ -53,6 +53,13 @@ export declare class Path {
|
|
53
53
|
getExactBBox(): Rect2;
|
54
54
|
private cachedGeometry;
|
55
55
|
get geometry(): GeometryArrayType;
|
56
|
+
/**
|
57
|
+
* Iterates through the start/end points of each component in this path.
|
58
|
+
*
|
59
|
+
* If a start point is equivalent to the end point of the previous segment,
|
60
|
+
* the point is **not** emitted twice.
|
61
|
+
*/
|
62
|
+
startEndPoints(): Generator<import("../Vec3").Vec3, undefined, unknown>;
|
56
63
|
private cachedPolylineApproximation;
|
57
64
|
polylineApproximation(): LineSegment2[];
|
58
65
|
static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
|
package/dist/cjs/shapes/Path.js
CHANGED
@@ -51,6 +51,7 @@ class Path {
|
|
51
51
|
let startPoint = this.startPoint;
|
52
52
|
const geometry = [];
|
53
53
|
for (const part of this.parts) {
|
54
|
+
let exhaustivenessCheck;
|
54
55
|
switch (part.kind) {
|
55
56
|
case PathCommandType.CubicBezierTo:
|
56
57
|
geometry.push(new CubicBezier_1.default(startPoint, part.controlPoint1, part.controlPoint2, part.endPoint));
|
@@ -68,11 +69,43 @@ class Path {
|
|
68
69
|
geometry.push(new PointShape2D_1.default(part.point));
|
69
70
|
startPoint = part.point;
|
70
71
|
break;
|
72
|
+
default:
|
73
|
+
exhaustivenessCheck = part;
|
74
|
+
return exhaustivenessCheck;
|
71
75
|
}
|
72
76
|
}
|
73
77
|
this.cachedGeometry = geometry;
|
74
78
|
return this.cachedGeometry;
|
75
79
|
}
|
80
|
+
/**
|
81
|
+
* Iterates through the start/end points of each component in this path.
|
82
|
+
*
|
83
|
+
* If a start point is equivalent to the end point of the previous segment,
|
84
|
+
* the point is **not** emitted twice.
|
85
|
+
*/
|
86
|
+
*startEndPoints() {
|
87
|
+
yield this.startPoint;
|
88
|
+
for (const part of this.parts) {
|
89
|
+
let exhaustivenessCheck;
|
90
|
+
switch (part.kind) {
|
91
|
+
case PathCommandType.CubicBezierTo:
|
92
|
+
yield part.endPoint;
|
93
|
+
break;
|
94
|
+
case PathCommandType.QuadraticBezierTo:
|
95
|
+
yield part.endPoint;
|
96
|
+
break;
|
97
|
+
case PathCommandType.LineTo:
|
98
|
+
yield part.point;
|
99
|
+
break;
|
100
|
+
case PathCommandType.MoveTo:
|
101
|
+
yield part.point;
|
102
|
+
break;
|
103
|
+
default:
|
104
|
+
exhaustivenessCheck = part;
|
105
|
+
return exhaustivenessCheck;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
76
109
|
// Approximates this path with a group of line segments.
|
77
110
|
polylineApproximation() {
|
78
111
|
if (this.cachedPolylineApproximation) {
|
@@ -19,7 +19,6 @@ export declare class Rect2 extends Abstract2DShape {
|
|
19
19
|
readonly h: number;
|
20
20
|
readonly topLeft: Point2;
|
21
21
|
readonly size: Vec2;
|
22
|
-
readonly bottomRight: Point2;
|
23
22
|
readonly area: number;
|
24
23
|
constructor(x: number, y: number, w: number, h: number);
|
25
24
|
translatedBy(vec: Vec2): Rect2;
|
@@ -35,6 +34,7 @@ export declare class Rect2 extends Abstract2DShape {
|
|
35
34
|
getClosestPointOnBoundaryTo(target: Point2): Vec3;
|
36
35
|
get corners(): Point2[];
|
37
36
|
get maxDimension(): number;
|
37
|
+
get bottomRight(): Vec3;
|
38
38
|
get topRight(): Vec3;
|
39
39
|
get bottomLeft(): Vec3;
|
40
40
|
get width(): number;
|
package/dist/cjs/shapes/Rect2.js
CHANGED
@@ -26,7 +26,6 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
26
26
|
// Precompute/store vector forms.
|
27
27
|
this.topLeft = Vec2_1.Vec2.of(this.x, this.y);
|
28
28
|
this.size = Vec2_1.Vec2.of(this.w, this.h);
|
29
|
-
this.bottomRight = this.topLeft.plus(this.size);
|
30
29
|
this.area = this.w * this.h;
|
31
30
|
}
|
32
31
|
translatedBy(vec) {
|
@@ -42,8 +41,8 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
42
41
|
}
|
43
42
|
containsRect(other) {
|
44
43
|
return this.x <= other.x && this.y <= other.y
|
45
|
-
&& this.
|
46
|
-
&& this.
|
44
|
+
&& this.x + this.w >= other.x + other.w
|
45
|
+
&& this.y + this.h >= other.y + other.h;
|
47
46
|
}
|
48
47
|
intersects(other) {
|
49
48
|
// Project along x/y axes.
|
@@ -116,6 +115,12 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
116
115
|
if (margin === 0) {
|
117
116
|
return this;
|
118
117
|
}
|
118
|
+
// Prevent width/height from being negative
|
119
|
+
if (margin < 0) {
|
120
|
+
const xMargin = -Math.min(-margin, this.w / 2);
|
121
|
+
const yMargin = -Math.min(-margin, this.h / 2);
|
122
|
+
return new Rect2(this.x - xMargin, this.y - yMargin, this.w + xMargin * 2, this.h + yMargin * 2);
|
123
|
+
}
|
119
124
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
120
125
|
}
|
121
126
|
getClosestPointOnBoundaryTo(target) {
|
@@ -144,6 +149,9 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
144
149
|
get maxDimension() {
|
145
150
|
return Math.max(this.w, this.h);
|
146
151
|
}
|
152
|
+
get bottomRight() {
|
153
|
+
return this.topLeft.plus(this.size);
|
154
|
+
}
|
147
155
|
get topRight() {
|
148
156
|
return this.bottomRight.plus(Vec2_1.Vec2.of(0, -this.h));
|
149
157
|
}
|
@@ -234,16 +242,16 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
234
242
|
return Rect2.empty;
|
235
243
|
}
|
236
244
|
const firstRect = rects[0];
|
237
|
-
let minX = firstRect.
|
238
|
-
let minY = firstRect.
|
239
|
-
let maxX = firstRect.
|
240
|
-
let maxY = firstRect.
|
245
|
+
let minX = firstRect.x;
|
246
|
+
let minY = firstRect.y;
|
247
|
+
let maxX = firstRect.x + firstRect.w;
|
248
|
+
let maxY = firstRect.y + firstRect.h;
|
241
249
|
for (let i = 1; i < rects.length; i++) {
|
242
250
|
const rect = rects[i];
|
243
|
-
minX = Math.min(minX, rect.
|
244
|
-
minY = Math.min(minY, rect.
|
245
|
-
maxX = Math.max(maxX, rect.
|
246
|
-
maxY = Math.max(maxY, rect.
|
251
|
+
minX = Math.min(minX, rect.x);
|
252
|
+
minY = Math.min(minY, rect.y);
|
253
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
254
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
247
255
|
}
|
248
256
|
return new Rect2(minX, minY, maxX - minX, maxY - minY);
|
249
257
|
}
|
package/dist/mjs/Color4.d.ts
CHANGED
@@ -12,7 +12,7 @@ import Vec3 from './Vec3';
|
|
12
12
|
* console.log('To string:', Color4.orange.toHexString());
|
13
13
|
* ```
|
14
14
|
*/
|
15
|
-
export
|
15
|
+
export declare class Color4 {
|
16
16
|
/** Red component. Should be in the range [0, 1]. */
|
17
17
|
readonly r: number;
|
18
18
|
/** Green component. ${\tt g} \in [0, 1]$ */
|
@@ -120,4 +120,4 @@ export default class Color4 {
|
|
120
120
|
static gray: Color4;
|
121
121
|
static white: Color4;
|
122
122
|
}
|
123
|
-
export
|
123
|
+
export default Color4;
|
package/dist/mjs/Color4.mjs
CHANGED
@@ -12,7 +12,7 @@ import Vec3 from './Vec3.mjs';
|
|
12
12
|
* console.log('To string:', Color4.orange.toHexString());
|
13
13
|
* ```
|
14
14
|
*/
|
15
|
-
class Color4 {
|
15
|
+
export class Color4 {
|
16
16
|
constructor(
|
17
17
|
/** Red component. Should be in the range [0, 1]. */
|
18
18
|
r,
|
@@ -377,4 +377,3 @@ Color4.black = Color4.ofRGB(0, 0, 0);
|
|
377
377
|
Color4.gray = Color4.ofRGB(0.5, 0.5, 0.5);
|
378
378
|
Color4.white = Color4.ofRGB(1, 1, 1);
|
379
379
|
export default Color4;
|
380
|
-
export { Color4 };
|
@@ -53,6 +53,13 @@ export declare class Path {
|
|
53
53
|
getExactBBox(): Rect2;
|
54
54
|
private cachedGeometry;
|
55
55
|
get geometry(): GeometryArrayType;
|
56
|
+
/**
|
57
|
+
* Iterates through the start/end points of each component in this path.
|
58
|
+
*
|
59
|
+
* If a start point is equivalent to the end point of the previous segment,
|
60
|
+
* the point is **not** emitted twice.
|
61
|
+
*/
|
62
|
+
startEndPoints(): Generator<import("../Vec3").Vec3, undefined, unknown>;
|
56
63
|
private cachedPolylineApproximation;
|
57
64
|
polylineApproximation(): LineSegment2[];
|
58
65
|
static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
|
package/dist/mjs/shapes/Path.mjs
CHANGED
@@ -45,6 +45,7 @@ export class Path {
|
|
45
45
|
let startPoint = this.startPoint;
|
46
46
|
const geometry = [];
|
47
47
|
for (const part of this.parts) {
|
48
|
+
let exhaustivenessCheck;
|
48
49
|
switch (part.kind) {
|
49
50
|
case PathCommandType.CubicBezierTo:
|
50
51
|
geometry.push(new CubicBezier(startPoint, part.controlPoint1, part.controlPoint2, part.endPoint));
|
@@ -62,11 +63,43 @@ export class Path {
|
|
62
63
|
geometry.push(new PointShape2D(part.point));
|
63
64
|
startPoint = part.point;
|
64
65
|
break;
|
66
|
+
default:
|
67
|
+
exhaustivenessCheck = part;
|
68
|
+
return exhaustivenessCheck;
|
65
69
|
}
|
66
70
|
}
|
67
71
|
this.cachedGeometry = geometry;
|
68
72
|
return this.cachedGeometry;
|
69
73
|
}
|
74
|
+
/**
|
75
|
+
* Iterates through the start/end points of each component in this path.
|
76
|
+
*
|
77
|
+
* If a start point is equivalent to the end point of the previous segment,
|
78
|
+
* the point is **not** emitted twice.
|
79
|
+
*/
|
80
|
+
*startEndPoints() {
|
81
|
+
yield this.startPoint;
|
82
|
+
for (const part of this.parts) {
|
83
|
+
let exhaustivenessCheck;
|
84
|
+
switch (part.kind) {
|
85
|
+
case PathCommandType.CubicBezierTo:
|
86
|
+
yield part.endPoint;
|
87
|
+
break;
|
88
|
+
case PathCommandType.QuadraticBezierTo:
|
89
|
+
yield part.endPoint;
|
90
|
+
break;
|
91
|
+
case PathCommandType.LineTo:
|
92
|
+
yield part.point;
|
93
|
+
break;
|
94
|
+
case PathCommandType.MoveTo:
|
95
|
+
yield part.point;
|
96
|
+
break;
|
97
|
+
default:
|
98
|
+
exhaustivenessCheck = part;
|
99
|
+
return exhaustivenessCheck;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
70
103
|
// Approximates this path with a group of line segments.
|
71
104
|
polylineApproximation() {
|
72
105
|
if (this.cachedPolylineApproximation) {
|
@@ -19,7 +19,6 @@ export declare class Rect2 extends Abstract2DShape {
|
|
19
19
|
readonly h: number;
|
20
20
|
readonly topLeft: Point2;
|
21
21
|
readonly size: Vec2;
|
22
|
-
readonly bottomRight: Point2;
|
23
22
|
readonly area: number;
|
24
23
|
constructor(x: number, y: number, w: number, h: number);
|
25
24
|
translatedBy(vec: Vec2): Rect2;
|
@@ -35,6 +34,7 @@ export declare class Rect2 extends Abstract2DShape {
|
|
35
34
|
getClosestPointOnBoundaryTo(target: Point2): Vec3;
|
36
35
|
get corners(): Point2[];
|
37
36
|
get maxDimension(): number;
|
37
|
+
get bottomRight(): Vec3;
|
38
38
|
get topRight(): Vec3;
|
39
39
|
get bottomLeft(): Vec3;
|
40
40
|
get width(): number;
|
@@ -20,7 +20,6 @@ export class Rect2 extends Abstract2DShape {
|
|
20
20
|
// Precompute/store vector forms.
|
21
21
|
this.topLeft = Vec2.of(this.x, this.y);
|
22
22
|
this.size = Vec2.of(this.w, this.h);
|
23
|
-
this.bottomRight = this.topLeft.plus(this.size);
|
24
23
|
this.area = this.w * this.h;
|
25
24
|
}
|
26
25
|
translatedBy(vec) {
|
@@ -36,8 +35,8 @@ export class Rect2 extends Abstract2DShape {
|
|
36
35
|
}
|
37
36
|
containsRect(other) {
|
38
37
|
return this.x <= other.x && this.y <= other.y
|
39
|
-
&& this.
|
40
|
-
&& this.
|
38
|
+
&& this.x + this.w >= other.x + other.w
|
39
|
+
&& this.y + this.h >= other.y + other.h;
|
41
40
|
}
|
42
41
|
intersects(other) {
|
43
42
|
// Project along x/y axes.
|
@@ -110,6 +109,12 @@ export class Rect2 extends Abstract2DShape {
|
|
110
109
|
if (margin === 0) {
|
111
110
|
return this;
|
112
111
|
}
|
112
|
+
// Prevent width/height from being negative
|
113
|
+
if (margin < 0) {
|
114
|
+
const xMargin = -Math.min(-margin, this.w / 2);
|
115
|
+
const yMargin = -Math.min(-margin, this.h / 2);
|
116
|
+
return new Rect2(this.x - xMargin, this.y - yMargin, this.w + xMargin * 2, this.h + yMargin * 2);
|
117
|
+
}
|
113
118
|
return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
|
114
119
|
}
|
115
120
|
getClosestPointOnBoundaryTo(target) {
|
@@ -138,6 +143,9 @@ export class Rect2 extends Abstract2DShape {
|
|
138
143
|
get maxDimension() {
|
139
144
|
return Math.max(this.w, this.h);
|
140
145
|
}
|
146
|
+
get bottomRight() {
|
147
|
+
return this.topLeft.plus(this.size);
|
148
|
+
}
|
141
149
|
get topRight() {
|
142
150
|
return this.bottomRight.plus(Vec2.of(0, -this.h));
|
143
151
|
}
|
@@ -228,16 +236,16 @@ export class Rect2 extends Abstract2DShape {
|
|
228
236
|
return Rect2.empty;
|
229
237
|
}
|
230
238
|
const firstRect = rects[0];
|
231
|
-
let minX = firstRect.
|
232
|
-
let minY = firstRect.
|
233
|
-
let maxX = firstRect.
|
234
|
-
let maxY = firstRect.
|
239
|
+
let minX = firstRect.x;
|
240
|
+
let minY = firstRect.y;
|
241
|
+
let maxX = firstRect.x + firstRect.w;
|
242
|
+
let maxY = firstRect.y + firstRect.h;
|
235
243
|
for (let i = 1; i < rects.length; i++) {
|
236
244
|
const rect = rects[i];
|
237
|
-
minX = Math.min(minX, rect.
|
238
|
-
minY = Math.min(minY, rect.
|
239
|
-
maxX = Math.max(maxX, rect.
|
240
|
-
maxY = Math.max(maxY, rect.
|
245
|
+
minX = Math.min(minX, rect.x);
|
246
|
+
minY = Math.min(minY, rect.y);
|
247
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
248
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
241
249
|
}
|
242
250
|
return new Rect2(minX, minY, maxX - minX, maxY - minY);
|
243
251
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@js-draw/math",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.3.0",
|
4
4
|
"description": "A math library for js-draw. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -45,5 +45,5 @@
|
|
45
45
|
"svg",
|
46
46
|
"math"
|
47
47
|
],
|
48
|
-
"gitHead": "
|
48
|
+
"gitHead": "46b3d8f819f8e083f6e3e1d01e027e4311355456"
|
49
49
|
}
|
package/src/Color4.ts
CHANGED
@@ -13,7 +13,7 @@ import Vec3 from './Vec3';
|
|
13
13
|
* console.log('To string:', Color4.orange.toHexString());
|
14
14
|
* ```
|
15
15
|
*/
|
16
|
-
export
|
16
|
+
export class Color4 {
|
17
17
|
private constructor(
|
18
18
|
/** Red component. Should be in the range [0, 1]. */
|
19
19
|
public readonly r: number,
|
@@ -437,4 +437,4 @@ export default class Color4 {
|
|
437
437
|
public static white = Color4.ofRGB(1, 1, 1);
|
438
438
|
}
|
439
439
|
|
440
|
-
export
|
440
|
+
export default Color4;
|
package/src/shapes/Path.ts
CHANGED
@@ -97,6 +97,8 @@ export class Path {
|
|
97
97
|
const geometry: GeometryArrayType = [];
|
98
98
|
|
99
99
|
for (const part of this.parts) {
|
100
|
+
let exhaustivenessCheck: never;
|
101
|
+
|
100
102
|
switch (part.kind) {
|
101
103
|
case PathCommandType.CubicBezierTo:
|
102
104
|
geometry.push(
|
@@ -124,6 +126,9 @@ export class Path {
|
|
124
126
|
geometry.push(new PointShape2D(part.point));
|
125
127
|
startPoint = part.point;
|
126
128
|
break;
|
129
|
+
default:
|
130
|
+
exhaustivenessCheck = part;
|
131
|
+
return exhaustivenessCheck;
|
127
132
|
}
|
128
133
|
}
|
129
134
|
|
@@ -131,6 +136,38 @@ export class Path {
|
|
131
136
|
return this.cachedGeometry;
|
132
137
|
}
|
133
138
|
|
139
|
+
/**
|
140
|
+
* Iterates through the start/end points of each component in this path.
|
141
|
+
*
|
142
|
+
* If a start point is equivalent to the end point of the previous segment,
|
143
|
+
* the point is **not** emitted twice.
|
144
|
+
*/
|
145
|
+
public *startEndPoints() {
|
146
|
+
yield this.startPoint;
|
147
|
+
|
148
|
+
for (const part of this.parts) {
|
149
|
+
let exhaustivenessCheck: never;
|
150
|
+
|
151
|
+
switch (part.kind) {
|
152
|
+
case PathCommandType.CubicBezierTo:
|
153
|
+
yield part.endPoint;
|
154
|
+
break;
|
155
|
+
case PathCommandType.QuadraticBezierTo:
|
156
|
+
yield part.endPoint;
|
157
|
+
break;
|
158
|
+
case PathCommandType.LineTo:
|
159
|
+
yield part.point;
|
160
|
+
break;
|
161
|
+
case PathCommandType.MoveTo:
|
162
|
+
yield part.point;
|
163
|
+
break;
|
164
|
+
default:
|
165
|
+
exhaustivenessCheck = part;
|
166
|
+
return exhaustivenessCheck;
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
134
171
|
private cachedPolylineApproximation: LineSegment2[]|null = null;
|
135
172
|
|
136
173
|
// Approximates this path with a group of line segments.
|
package/src/shapes/Rect2.test.ts
CHANGED
@@ -111,6 +111,27 @@ describe('Rect2', () => {
|
|
111
111
|
expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, 50, 10, 30))).toBe(false);
|
112
112
|
});
|
113
113
|
|
114
|
+
it('should correctly compute the intersection of one rectangle and several others', () => {
|
115
|
+
const mainRect = new Rect2(334,156,333,179);
|
116
|
+
const shouldIntersect = [
|
117
|
+
new Rect2(400.8, 134.8, 8.4, 161.4),
|
118
|
+
new Rect2(324.8,93,164.4,75.2),
|
119
|
+
new Rect2(435.8,146.8,213.2,192.6),
|
120
|
+
new Rect2(550.8,211.8,3.4,3.4),
|
121
|
+
new Rect2(478.8,93.8,212.4,95.4),
|
122
|
+
];
|
123
|
+
const shouldNotIntersect = [
|
124
|
+
new Rect2(200, 200, 1, 1),
|
125
|
+
];
|
126
|
+
|
127
|
+
for (const rect of shouldIntersect) {
|
128
|
+
expect(mainRect.intersects(rect)).toBe(true);
|
129
|
+
}
|
130
|
+
for (const rect of shouldNotIntersect) {
|
131
|
+
expect(mainRect.intersects(rect)).toBe(false);
|
132
|
+
}
|
133
|
+
});
|
134
|
+
|
114
135
|
it('intersecting rectangles should have their intersections correctly computed', () => {
|
115
136
|
expect(new Rect2(-1, -1, 2, 2).intersection(Rect2.empty)).objEq(Rect2.empty);
|
116
137
|
expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(0, 0, 3, 3))).objEq(
|
@@ -130,6 +151,19 @@ describe('Rect2', () => {
|
|
130
151
|
expect(transformedBBox.containsRect(rect)).toBe(true);
|
131
152
|
});
|
132
153
|
|
154
|
+
it('.grownBy should expand a rectangle by the given margin', () => {
|
155
|
+
expect(Rect2.empty.grownBy(0)).toBe(Rect2.empty);
|
156
|
+
|
157
|
+
// Should add padding to all sides.
|
158
|
+
expect(new Rect2(1, 2, 3, 4).grownBy(1)).objEq(new Rect2(0, 1, 5, 6));
|
159
|
+
|
160
|
+
// Shrinking should not result in negative widths/heights and
|
161
|
+
// should adjust x/y appropriately
|
162
|
+
expect(new Rect2(1, 2, 1, 2).grownBy(-1)).objEq(new Rect2(1.5, 3, 0, 0));
|
163
|
+
expect(new Rect2(1, 2, 4, 4).grownBy(-1)).objEq(new Rect2(2, 3, 2, 2));
|
164
|
+
expect(new Rect2(1, 2, 2, 8).grownBy(-2)).objEq(new Rect2(2, 4, 0, 4));
|
165
|
+
});
|
166
|
+
|
133
167
|
describe('should correctly expand to include a given point', () => {
|
134
168
|
it('Growing an empty rectange to include (1, 0)', () => {
|
135
169
|
const originalRect = Rect2.empty;
|
package/src/shapes/Rect2.ts
CHANGED
@@ -21,7 +21,6 @@ export class Rect2 extends Abstract2DShape {
|
|
21
21
|
// topLeft assumes up is -y
|
22
22
|
public readonly topLeft: Point2;
|
23
23
|
public readonly size: Vec2;
|
24
|
-
public readonly bottomRight: Point2;
|
25
24
|
public readonly area: number;
|
26
25
|
|
27
26
|
public constructor(
|
@@ -45,7 +44,6 @@ export class Rect2 extends Abstract2DShape {
|
|
45
44
|
// Precompute/store vector forms.
|
46
45
|
this.topLeft = Vec2.of(this.x, this.y);
|
47
46
|
this.size = Vec2.of(this.w, this.h);
|
48
|
-
this.bottomRight = this.topLeft.plus(this.size);
|
49
47
|
this.area = this.w * this.h;
|
50
48
|
}
|
51
49
|
|
@@ -65,8 +63,8 @@ export class Rect2 extends Abstract2DShape {
|
|
65
63
|
|
66
64
|
public containsRect(other: Rect2): boolean {
|
67
65
|
return this.x <= other.x && this.y <= other.y
|
68
|
-
&& this.
|
69
|
-
&& this.
|
66
|
+
&& this.x + this.w >= other.x + other.w
|
67
|
+
&& this.y + this.h >= other.y + other.h;
|
70
68
|
}
|
71
69
|
|
72
70
|
public intersects(other: Rect2): boolean {
|
@@ -159,6 +157,17 @@ export class Rect2 extends Abstract2DShape {
|
|
159
157
|
return this;
|
160
158
|
}
|
161
159
|
|
160
|
+
// Prevent width/height from being negative
|
161
|
+
if (margin < 0) {
|
162
|
+
const xMargin = -Math.min(-margin, this.w / 2);
|
163
|
+
const yMargin = -Math.min(-margin, this.h / 2);
|
164
|
+
|
165
|
+
return new Rect2(
|
166
|
+
this.x - xMargin, this.y - yMargin,
|
167
|
+
this.w + xMargin * 2, this.h + yMargin * 2,
|
168
|
+
);
|
169
|
+
}
|
170
|
+
|
162
171
|
return new Rect2(
|
163
172
|
this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2
|
164
173
|
);
|
@@ -194,6 +203,10 @@ export class Rect2 extends Abstract2DShape {
|
|
194
203
|
return Math.max(this.w, this.h);
|
195
204
|
}
|
196
205
|
|
206
|
+
public get bottomRight() {
|
207
|
+
return this.topLeft.plus(this.size);
|
208
|
+
}
|
209
|
+
|
197
210
|
public get topRight() {
|
198
211
|
return this.bottomRight.plus(Vec2.of(0, -this.h));
|
199
212
|
}
|
@@ -315,17 +328,17 @@ export class Rect2 extends Abstract2DShape {
|
|
315
328
|
}
|
316
329
|
|
317
330
|
const firstRect = rects[0];
|
318
|
-
let minX: number = firstRect.
|
319
|
-
let minY: number = firstRect.
|
320
|
-
let maxX: number = firstRect.
|
321
|
-
let maxY: number = firstRect.
|
331
|
+
let minX: number = firstRect.x;
|
332
|
+
let minY: number = firstRect.y;
|
333
|
+
let maxX: number = firstRect.x + firstRect.w;
|
334
|
+
let maxY: number = firstRect.y + firstRect.h;
|
322
335
|
|
323
336
|
for (let i = 1; i < rects.length; i++) {
|
324
337
|
const rect = rects[i];
|
325
|
-
minX = Math.min(minX, rect.
|
326
|
-
minY = Math.min(minY, rect.
|
327
|
-
maxX = Math.max(maxX, rect.
|
328
|
-
maxY = Math.max(maxY, rect.
|
338
|
+
minX = Math.min(minX, rect.x);
|
339
|
+
minY = Math.min(minY, rect.y);
|
340
|
+
maxX = Math.max(maxX, rect.x + rect.w);
|
341
|
+
maxY = Math.max(maxY, rect.y + rect.h);
|
329
342
|
}
|
330
343
|
|
331
344
|
return new Rect2(
|