1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS-IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | /** |
16 | * @fileoverview A utility class for representing a numeric box. |
17 | */ |
18 | |
19 | |
20 | goog.provide('goog.math.Box'); |
21 | |
22 | goog.require('goog.math.Coordinate'); |
23 | |
24 | |
25 | |
26 | /** |
27 | * Class for representing a box. A box is specified as a top, right, bottom, |
28 | * and left. A box is useful for representing margins and padding. |
29 | * |
30 | * This class assumes 'screen coordinates': larger Y coordinates are further |
31 | * from the top of the screen. |
32 | * |
33 | * @param {number} top Top. |
34 | * @param {number} right Right. |
35 | * @param {number} bottom Bottom. |
36 | * @param {number} left Left. |
37 | * @constructor |
38 | */ |
39 | goog.math.Box = function(top, right, bottom, left) { |
40 | /** |
41 | * Top |
42 | * @type {number} |
43 | */ |
44 | this.top = top; |
45 | |
46 | /** |
47 | * Right |
48 | * @type {number} |
49 | */ |
50 | this.right = right; |
51 | |
52 | /** |
53 | * Bottom |
54 | * @type {number} |
55 | */ |
56 | this.bottom = bottom; |
57 | |
58 | /** |
59 | * Left |
60 | * @type {number} |
61 | */ |
62 | this.left = left; |
63 | }; |
64 | |
65 | |
66 | /** |
67 | * Creates a Box by bounding a collection of goog.math.Coordinate objects |
68 | * @param {...goog.math.Coordinate} var_args Coordinates to be included inside |
69 | * the box. |
70 | * @return {!goog.math.Box} A Box containing all the specified Coordinates. |
71 | */ |
72 | goog.math.Box.boundingBox = function(var_args) { |
73 | var box = new goog.math.Box(arguments[0].y, arguments[0].x, |
74 | arguments[0].y, arguments[0].x); |
75 | for (var i = 1; i < arguments.length; i++) { |
76 | var coord = arguments[i]; |
77 | box.top = Math.min(box.top, coord.y); |
78 | box.right = Math.max(box.right, coord.x); |
79 | box.bottom = Math.max(box.bottom, coord.y); |
80 | box.left = Math.min(box.left, coord.x); |
81 | } |
82 | return box; |
83 | }; |
84 | |
85 | |
86 | /** |
87 | * @return {number} width The width of this Box. |
88 | */ |
89 | goog.math.Box.prototype.getWidth = function() { |
90 | return this.right - this.left; |
91 | }; |
92 | |
93 | |
94 | /** |
95 | * @return {number} height The height of this Box. |
96 | */ |
97 | goog.math.Box.prototype.getHeight = function() { |
98 | return this.bottom - this.top; |
99 | }; |
100 | |
101 | |
102 | /** |
103 | * Creates a copy of the box with the same dimensions. |
104 | * @return {!goog.math.Box} A clone of this Box. |
105 | */ |
106 | goog.math.Box.prototype.clone = function() { |
107 | return new goog.math.Box(this.top, this.right, this.bottom, this.left); |
108 | }; |
109 | |
110 | |
111 | if (goog.DEBUG) { |
112 | /** |
113 | * Returns a nice string representing the box. |
114 | * @return {string} In the form (50t, 73r, 24b, 13l). |
115 | * @override |
116 | */ |
117 | goog.math.Box.prototype.toString = function() { |
118 | return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + |
119 | this.left + 'l)'; |
120 | }; |
121 | } |
122 | |
123 | |
124 | /** |
125 | * Returns whether the box contains a coordinate or another box. |
126 | * |
127 | * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. |
128 | * @return {boolean} Whether the box contains the coordinate or other box. |
129 | */ |
130 | goog.math.Box.prototype.contains = function(other) { |
131 | return goog.math.Box.contains(this, other); |
132 | }; |
133 | |
134 | |
135 | /** |
136 | * Expands box with the given margins. |
137 | * |
138 | * @param {number|goog.math.Box} top Top margin or box with all margins. |
139 | * @param {number=} opt_right Right margin. |
140 | * @param {number=} opt_bottom Bottom margin. |
141 | * @param {number=} opt_left Left margin. |
142 | * @return {!goog.math.Box} A reference to this Box. |
143 | */ |
144 | goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom, |
145 | opt_left) { |
146 | if (goog.isObject(top)) { |
147 | this.top -= top.top; |
148 | this.right += top.right; |
149 | this.bottom += top.bottom; |
150 | this.left -= top.left; |
151 | } else { |
152 | this.top -= top; |
153 | this.right += opt_right; |
154 | this.bottom += opt_bottom; |
155 | this.left -= opt_left; |
156 | } |
157 | |
158 | return this; |
159 | }; |
160 | |
161 | |
162 | /** |
163 | * Expand this box to include another box. |
164 | * NOTE(user): This is used in code that needs to be very fast, please don't |
165 | * add functionality to this function at the expense of speed (variable |
166 | * arguments, accepting multiple argument types, etc). |
167 | * @param {goog.math.Box} box The box to include in this one. |
168 | */ |
169 | goog.math.Box.prototype.expandToInclude = function(box) { |
170 | this.left = Math.min(this.left, box.left); |
171 | this.top = Math.min(this.top, box.top); |
172 | this.right = Math.max(this.right, box.right); |
173 | this.bottom = Math.max(this.bottom, box.bottom); |
174 | }; |
175 | |
176 | |
177 | /** |
178 | * Compares boxes for equality. |
179 | * @param {goog.math.Box} a A Box. |
180 | * @param {goog.math.Box} b A Box. |
181 | * @return {boolean} True iff the boxes are equal, or if both are null. |
182 | */ |
183 | goog.math.Box.equals = function(a, b) { |
184 | if (a == b) { |
185 | return true; |
186 | } |
187 | if (!a || !b) { |
188 | return false; |
189 | } |
190 | return a.top == b.top && a.right == b.right && |
191 | a.bottom == b.bottom && a.left == b.left; |
192 | }; |
193 | |
194 | |
195 | /** |
196 | * Returns whether a box contains a coordinate or another box. |
197 | * |
198 | * @param {goog.math.Box} box A Box. |
199 | * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. |
200 | * @return {boolean} Whether the box contains the coordinate or other box. |
201 | */ |
202 | goog.math.Box.contains = function(box, other) { |
203 | if (!box || !other) { |
204 | return false; |
205 | } |
206 | |
207 | if (other instanceof goog.math.Box) { |
208 | return other.left >= box.left && other.right <= box.right && |
209 | other.top >= box.top && other.bottom <= box.bottom; |
210 | } |
211 | |
212 | // other is a Coordinate. |
213 | return other.x >= box.left && other.x <= box.right && |
214 | other.y >= box.top && other.y <= box.bottom; |
215 | }; |
216 | |
217 | |
218 | /** |
219 | * Returns the relative x position of a coordinate compared to a box. Returns |
220 | * zero if the coordinate is inside the box. |
221 | * |
222 | * @param {goog.math.Box} box A Box. |
223 | * @param {goog.math.Coordinate} coord A Coordinate. |
224 | * @return {number} The x position of {@code coord} relative to the nearest |
225 | * side of {@code box}, or zero if {@code coord} is inside {@code box}. |
226 | */ |
227 | goog.math.Box.relativePositionX = function(box, coord) { |
228 | if (coord.x < box.left) { |
229 | return coord.x - box.left; |
230 | } else if (coord.x > box.right) { |
231 | return coord.x - box.right; |
232 | } |
233 | return 0; |
234 | }; |
235 | |
236 | |
237 | /** |
238 | * Returns the relative y position of a coordinate compared to a box. Returns |
239 | * zero if the coordinate is inside the box. |
240 | * |
241 | * @param {goog.math.Box} box A Box. |
242 | * @param {goog.math.Coordinate} coord A Coordinate. |
243 | * @return {number} The y position of {@code coord} relative to the nearest |
244 | * side of {@code box}, or zero if {@code coord} is inside {@code box}. |
245 | */ |
246 | goog.math.Box.relativePositionY = function(box, coord) { |
247 | if (coord.y < box.top) { |
248 | return coord.y - box.top; |
249 | } else if (coord.y > box.bottom) { |
250 | return coord.y - box.bottom; |
251 | } |
252 | return 0; |
253 | }; |
254 | |
255 | |
256 | /** |
257 | * Returns the distance between a coordinate and the nearest corner/side of a |
258 | * box. Returns zero if the coordinate is inside the box. |
259 | * |
260 | * @param {goog.math.Box} box A Box. |
261 | * @param {goog.math.Coordinate} coord A Coordinate. |
262 | * @return {number} The distance between {@code coord} and the nearest |
263 | * corner/side of {@code box}, or zero if {@code coord} is inside |
264 | * {@code box}. |
265 | */ |
266 | goog.math.Box.distance = function(box, coord) { |
267 | var x = goog.math.Box.relativePositionX(box, coord); |
268 | var y = goog.math.Box.relativePositionY(box, coord); |
269 | return Math.sqrt(x * x + y * y); |
270 | }; |
271 | |
272 | |
273 | /** |
274 | * Returns whether two boxes intersect. |
275 | * |
276 | * @param {goog.math.Box} a A Box. |
277 | * @param {goog.math.Box} b A second Box. |
278 | * @return {boolean} Whether the boxes intersect. |
279 | */ |
280 | goog.math.Box.intersects = function(a, b) { |
281 | return (a.left <= b.right && b.left <= a.right && |
282 | a.top <= b.bottom && b.top <= a.bottom); |
283 | }; |
284 | |
285 | |
286 | /** |
287 | * Returns whether two boxes would intersect with additional padding. |
288 | * |
289 | * @param {goog.math.Box} a A Box. |
290 | * @param {goog.math.Box} b A second Box. |
291 | * @param {number} padding The additional padding. |
292 | * @return {boolean} Whether the boxes intersect. |
293 | */ |
294 | goog.math.Box.intersectsWithPadding = function(a, b, padding) { |
295 | return (a.left <= b.right + padding && b.left <= a.right + padding && |
296 | a.top <= b.bottom + padding && b.top <= a.bottom + padding); |
297 | }; |
298 | |
299 | |
300 | /** |
301 | * Rounds the fields to the next larger integer values. |
302 | * |
303 | * @return {!goog.math.Box} This box with ceil'd fields. |
304 | */ |
305 | goog.math.Box.prototype.ceil = function() { |
306 | this.top = Math.ceil(this.top); |
307 | this.right = Math.ceil(this.right); |
308 | this.bottom = Math.ceil(this.bottom); |
309 | this.left = Math.ceil(this.left); |
310 | return this; |
311 | }; |
312 | |
313 | |
314 | /** |
315 | * Rounds the fields to the next smaller integer values. |
316 | * |
317 | * @return {!goog.math.Box} This box with floored fields. |
318 | */ |
319 | goog.math.Box.prototype.floor = function() { |
320 | this.top = Math.floor(this.top); |
321 | this.right = Math.floor(this.right); |
322 | this.bottom = Math.floor(this.bottom); |
323 | this.left = Math.floor(this.left); |
324 | return this; |
325 | }; |
326 | |
327 | |
328 | /** |
329 | * Rounds the fields to nearest integer values. |
330 | * |
331 | * @return {!goog.math.Box} This box with rounded fields. |
332 | */ |
333 | goog.math.Box.prototype.round = function() { |
334 | this.top = Math.round(this.top); |
335 | this.right = Math.round(this.right); |
336 | this.bottom = Math.round(this.bottom); |
337 | this.left = Math.round(this.left); |
338 | return this; |
339 | }; |
340 | |
341 | |
342 | /** |
343 | * Translates this box by the given offsets. If a {@code goog.math.Coordinate} |
344 | * is given, then the left and right values are translated by the coordinate's |
345 | * x value and the top and bottom values are translated by the coordinate's y |
346 | * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x |
347 | * and y dimension values. |
348 | * |
349 | * @param {number|goog.math.Coordinate} tx The value to translate the x |
350 | * dimension values by or the the coordinate to translate this box by. |
351 | * @param {number=} opt_ty The value to translate y dimension values by. |
352 | * @return {!goog.math.Box} This box after translating. |
353 | */ |
354 | goog.math.Box.prototype.translate = function(tx, opt_ty) { |
355 | if (tx instanceof goog.math.Coordinate) { |
356 | this.left += tx.x; |
357 | this.right += tx.x; |
358 | this.top += tx.y; |
359 | this.bottom += tx.y; |
360 | } else { |
361 | this.left += tx; |
362 | this.right += tx; |
363 | if (goog.isNumber(opt_ty)) { |
364 | this.top += opt_ty; |
365 | this.bottom += opt_ty; |
366 | } |
367 | } |
368 | return this; |
369 | }; |
370 | |
371 | |
372 | /** |
373 | * Scales this coordinate by the given scale factors. The x and y dimension |
374 | * values are scaled by {@code sx} and {@code opt_sy} respectively. |
375 | * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. |
376 | * |
377 | * @param {number} sx The scale factor to use for the x dimension. |
378 | * @param {number=} opt_sy The scale factor to use for the y dimension. |
379 | * @return {!goog.math.Box} This box after scaling. |
380 | */ |
381 | goog.math.Box.prototype.scale = function(sx, opt_sy) { |
382 | var sy = goog.isNumber(opt_sy) ? opt_sy : sx; |
383 | this.left *= sx; |
384 | this.right *= sx; |
385 | this.top *= sy; |
386 | this.bottom *= sy; |
387 | return this; |
388 | }; |