@js-draw/math 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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(
|