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
20goog.provide('goog.math.Box');
21
22goog.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 */
39goog.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 */
72goog.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 */
89goog.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 */
97goog.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 */
106goog.math.Box.prototype.clone = function() {
107 return new goog.math.Box(this.top, this.right, this.bottom, this.left);
108};
109
110
111if (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 */
130goog.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 */
144goog.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 */
169goog.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 */
183goog.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 */
202goog.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 */
227goog.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 */
246goog.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 */
266goog.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 */
280goog.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 */
294goog.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 */
305goog.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 */
319goog.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 */
333goog.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 */
354goog.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 */
381goog.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};