# lib/goog/math/box.js

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 | };