1 | class 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 |
|
196 | module.exports = Rectangle;
|