forked from scratchfoundation/scratch-render
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRectangle.js
204 lines (185 loc) · 7.04 KB
/
Rectangle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class Rectangle {
/**
* A utility for creating and comparing axis-aligned rectangles.
* Rectangles are always initialized to the "largest possible rectangle";
* use one of the init* methods below to set up a particular rectangle.
* @constructor
*/
constructor () {
this.left = -Infinity;
this.right = Infinity;
this.bottom = -Infinity;
this.top = Infinity;
}
/**
* Initialize a Rectangle from given Scratch-coordinate bounds.
* @param {number} left Left bound of the rectangle.
* @param {number} right Right bound of the rectangle.
* @param {number} bottom Bottom bound of the rectangle.
* @param {number} top Top bound of the rectangle.
*/
initFromBounds (left, right, bottom, top) {
this.left = left;
this.right = right;
this.bottom = bottom;
this.top = top;
}
/**
* Initialize a Rectangle to the minimum AABB around a set of points.
* @param {Array<Array<number>>} points Array of [x, y] points.
* @param {number} scale The "Scratch-space" area under each point sample.
*/
initFromPointsAABB (points, scale = 0) {
this.left = Infinity;
this.right = -Infinity;
this.top = -Infinity;
this.bottom = Infinity;
// Each point is the center of a pixel. However, said pixels each have area.
// We can think of the `scale` parameter as the 'diameter' of each area sample centered around each point.
// The bounds must be extended by the 'radius' of each sample on every side.
// Since nearest-neighbor sampling gives pixels a square area, the radius is sqrt(2) / 2 = 0.707...
// at its longest (from the center of the pixel to one of its corners).
const halfPixel = 0.7071067811865476 * scale;
for (let i = 0; i < points.length; i++) {
const x = points[i][0];
const y = points[i][1];
if ((x - halfPixel) < this.left) {
this.left = x - halfPixel;
}
if ((x + halfPixel) > this.right) {
this.right = x + halfPixel;
}
if ((y + halfPixel) > this.top) {
this.top = y + halfPixel;
}
if ((y - halfPixel) < this.bottom) {
this.bottom = y - halfPixel;
}
}
}
/**
* Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed
* by a model matrix.
* @param {Array.<number>} m A 4x4 matrix to transform the rectangle by.
* @tutorial Rectangle-AABB-Matrix
*/
initFromModelMatrix (m) {
// In 2D space, we will soon use the 2x2 "top left" scale and rotation
// submatrix, while we store and the 1x2 "top right" that position
// vector.
const m30 = m[(3 * 4) + 0];
const m31 = m[(3 * 4) + 1];
// "Transform" a (0.5, 0.5) vector by the scale and rotation matrix but
// sum the absolute of each component instead of use the signed values.
const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]);
const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]);
// And adding them to the position components initializes our Rectangle.
this.left = -x + m30;
this.right = x + m30;
this.top = y + m31;
this.bottom = -y + m31;
}
/**
* Determine if this Rectangle intersects some other.
* Note that this is a comparison assuming the Rectangle was
* initialized with Scratch-space bounds or points.
* @param {!Rectangle} other Rectangle to check if intersecting.
* @return {boolean} True if this Rectangle intersects other.
*/
intersects (other) {
return (
this.left <= other.right &&
other.left <= this.right &&
this.top >= other.bottom &&
other.top >= this.bottom
);
}
/**
* Determine if this Rectangle fully contains some other.
* Note that this is a comparison assuming the Rectangle was
* initialized with Scratch-space bounds or points.
* @param {!Rectangle} other Rectangle to check if fully contained.
* @return {boolean} True if this Rectangle fully contains other.
*/
contains (other) {
return (
other.left > this.left &&
other.right < this.right &&
other.top < this.top &&
other.bottom > this.bottom
);
}
/**
* Clamp a Rectangle to bounds.
* @param {number} left Left clamp.
* @param {number} right Right clamp.
* @param {number} bottom Bottom clamp.
* @param {number} top Top clamp.
*/
clamp (left, right, bottom, top) {
this.left = Math.max(this.left, left);
this.right = Math.min(this.right, right);
this.bottom = Math.max(this.bottom, bottom);
this.top = Math.min(this.top, top);
this.left = Math.min(this.left, right);
this.right = Math.max(this.right, left);
this.bottom = Math.min(this.bottom, top);
this.top = Math.max(this.top, bottom);
}
/**
* Push out the Rectangle to integer bounds.
*/
snapToInt () {
this.left = Math.floor(this.left);
this.right = Math.ceil(this.right);
this.bottom = Math.floor(this.bottom);
this.top = Math.ceil(this.top);
}
/**
* Compute the intersection of two bounding Rectangles.
* Could be an impossible box if they don't intersect.
* @param {Rectangle} a One rectangle
* @param {Rectangle} b Other rectangle
* @param {?Rectangle} result A resulting storage rectangle (safe to pass
* a or b if you want to overwrite one)
* @returns {Rectangle} resulting rectangle
*/
static intersect (a, b, result = new Rectangle()) {
result.left = Math.max(a.left, b.left);
result.right = Math.min(a.right, b.right);
result.top = Math.min(a.top, b.top);
result.bottom = Math.max(a.bottom, b.bottom);
return result;
}
/**
* Compute the union of two bounding Rectangles.
* @param {Rectangle} a One rectangle
* @param {Rectangle} b Other rectangle
* @param {?Rectangle} result A resulting storage rectangle (safe to pass
* a or b if you want to overwrite one)
* @returns {Rectangle} resulting rectangle
*/
static union (a, b, result = new Rectangle()) {
result.left = Math.min(a.left, b.left);
result.right = Math.max(a.right, b.right);
// Scratch Space - +y is up
result.top = Math.max(a.top, b.top);
result.bottom = Math.min(a.bottom, b.bottom);
return result;
}
/**
* Width of the Rectangle.
* @return {number} Width of rectangle.
*/
get width () {
return Math.abs(this.left - this.right);
}
/**
* Height of the Rectangle.
* @return {number} Height of rectangle.
*/
get height () {
return Math.abs(this.top - this.bottom);
}
}
module.exports = Rectangle;