UNPKG

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