Path: blob/trunk/third_party/closure/goog/math/rect.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A utility class for representing rectangles. Some of these16* functions should be migrated over to non-nullable params.17*/1819goog.provide('goog.math.Rect');2021goog.require('goog.asserts');22goog.require('goog.math.Box');23goog.require('goog.math.Coordinate');24goog.require('goog.math.IRect');25goog.require('goog.math.Size');26272829/**30* Class for representing rectangular regions.31* @param {number} x Left.32* @param {number} y Top.33* @param {number} w Width.34* @param {number} h Height.35* @struct36* @constructor37* @implements {goog.math.IRect}38*/39goog.math.Rect = function(x, y, w, h) {40/** @type {number} */41this.left = x;4243/** @type {number} */44this.top = y;4546/** @type {number} */47this.width = w;4849/** @type {number} */50this.height = h;51};525354/**55* @return {!goog.math.Rect} A new copy of this Rectangle.56*/57goog.math.Rect.prototype.clone = function() {58return new goog.math.Rect(this.left, this.top, this.width, this.height);59};606162/**63* Returns a new Box object with the same position and dimensions as this64* rectangle.65* @return {!goog.math.Box} A new Box representation of this Rectangle.66*/67goog.math.Rect.prototype.toBox = function() {68var right = this.left + this.width;69var bottom = this.top + this.height;70return new goog.math.Box(this.top, right, bottom, this.left);71};727374/**75* Creates a new Rect object with the position and size given.76* @param {!goog.math.Coordinate} position The top-left coordinate of the Rect77* @param {!goog.math.Size} size The size of the Rect78* @return {!goog.math.Rect} A new Rect initialized with the given position and79* size.80*/81goog.math.Rect.createFromPositionAndSize = function(position, size) {82return new goog.math.Rect(position.x, position.y, size.width, size.height);83};848586/**87* Creates a new Rect object with the same position and dimensions as a given88* Box. Note that this is only the inverse of toBox if left/top are defined.89* @param {goog.math.Box} box A box.90* @return {!goog.math.Rect} A new Rect initialized with the box's position91* and size.92*/93goog.math.Rect.createFromBox = function(box) {94return new goog.math.Rect(95box.left, box.top, box.right - box.left, box.bottom - box.top);96};979899if (goog.DEBUG) {100/**101* Returns a nice string representing size and dimensions of rectangle.102* @return {string} In the form (50, 73 - 75w x 25h).103* @override104*/105goog.math.Rect.prototype.toString = function() {106return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +107this.height + 'h)';108};109}110111112/**113* Compares rectangles for equality.114* @param {goog.math.IRect} a A Rectangle.115* @param {goog.math.IRect} b A Rectangle.116* @return {boolean} True iff the rectangles have the same left, top, width,117* and height, or if both are null.118*/119goog.math.Rect.equals = function(a, b) {120if (a == b) {121return true;122}123if (!a || !b) {124return false;125}126return a.left == b.left && a.width == b.width && a.top == b.top &&127a.height == b.height;128};129130131/**132* Computes the intersection of this rectangle and the rectangle parameter. If133* there is no intersection, returns false and leaves this rectangle as is.134* @param {goog.math.IRect} rect A Rectangle.135* @return {boolean} True iff this rectangle intersects with the parameter.136*/137goog.math.Rect.prototype.intersection = function(rect) {138var x0 = Math.max(this.left, rect.left);139var x1 = Math.min(this.left + this.width, rect.left + rect.width);140141if (x0 <= x1) {142var y0 = Math.max(this.top, rect.top);143var y1 = Math.min(this.top + this.height, rect.top + rect.height);144145if (y0 <= y1) {146this.left = x0;147this.top = y0;148this.width = x1 - x0;149this.height = y1 - y0;150151return true;152}153}154return false;155};156157158/**159* Returns the intersection of two rectangles. Two rectangles intersect if they160* touch at all, for example, two zero width and height rectangles would161* intersect if they had the same top and left.162* @param {goog.math.IRect} a A Rectangle.163* @param {goog.math.IRect} b A Rectangle.164* @return {goog.math.Rect} A new intersection rect (even if width and height165* are 0), or null if there is no intersection.166*/167goog.math.Rect.intersection = function(a, b) {168// There is no nice way to do intersection via a clone, because any such169// clone might be unnecessary if this function returns null. So, we duplicate170// code from above.171172var x0 = Math.max(a.left, b.left);173var x1 = Math.min(a.left + a.width, b.left + b.width);174175if (x0 <= x1) {176var y0 = Math.max(a.top, b.top);177var y1 = Math.min(a.top + a.height, b.top + b.height);178179if (y0 <= y1) {180return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);181}182}183return null;184};185186187/**188* Returns whether two rectangles intersect. Two rectangles intersect if they189* touch at all, for example, two zero width and height rectangles would190* intersect if they had the same top and left.191* @param {goog.math.IRect} a A Rectangle.192* @param {goog.math.IRect} b A Rectangle.193* @return {boolean} Whether a and b intersect.194*/195goog.math.Rect.intersects = function(a, b) {196return (197a.left <= b.left + b.width && b.left <= a.left + a.width &&198a.top <= b.top + b.height && b.top <= a.top + a.height);199};200201202/**203* Returns whether a rectangle intersects this rectangle.204* @param {goog.math.IRect} rect A rectangle.205* @return {boolean} Whether rect intersects this rectangle.206*/207goog.math.Rect.prototype.intersects = function(rect) {208return goog.math.Rect.intersects(this, rect);209};210211212/**213* Computes the difference regions between two rectangles. The return value is214* an array of 0 to 4 rectangles defining the remaining regions of the first215* rectangle after the second has been subtracted.216* @param {goog.math.Rect} a A Rectangle.217* @param {goog.math.IRect} b A Rectangle.218* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which219* together define the difference area of rectangle a minus rectangle b.220*/221goog.math.Rect.difference = function(a, b) {222var intersection = goog.math.Rect.intersection(a, b);223if (!intersection || !intersection.height || !intersection.width) {224return [a.clone()];225}226227var result = [];228229var top = a.top;230var height = a.height;231232var ar = a.left + a.width;233var ab = a.top + a.height;234235var br = b.left + b.width;236var bb = b.top + b.height;237238// Subtract off any area on top where A extends past B239if (b.top > a.top) {240result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));241top = b.top;242// If we're moving the top down, we also need to subtract the height diff.243height -= b.top - a.top;244}245// Subtract off any area on bottom where A extends past B246if (bb < ab) {247result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));248height = bb - top;249}250// Subtract any area on left where A extends past B251if (b.left > a.left) {252result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));253}254// Subtract any area on right where A extends past B255if (br < ar) {256result.push(new goog.math.Rect(br, top, ar - br, height));257}258259return result;260};261262263/**264* Computes the difference regions between this rectangle and {@code rect}. The265* return value is an array of 0 to 4 rectangles defining the remaining regions266* of this rectangle after the other has been subtracted.267* @param {goog.math.IRect} rect A Rectangle.268* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which269* together define the difference area of rectangle a minus rectangle b.270*/271goog.math.Rect.prototype.difference = function(rect) {272return goog.math.Rect.difference(this, rect);273};274275276/**277* Expand this rectangle to also include the area of the given rectangle.278* @param {goog.math.IRect} rect The other rectangle.279*/280goog.math.Rect.prototype.boundingRect = function(rect) {281// We compute right and bottom before we change left and top below.282var right = Math.max(this.left + this.width, rect.left + rect.width);283var bottom = Math.max(this.top + this.height, rect.top + rect.height);284285this.left = Math.min(this.left, rect.left);286this.top = Math.min(this.top, rect.top);287288this.width = right - this.left;289this.height = bottom - this.top;290};291292293/**294* Returns a new rectangle which completely contains both input rectangles.295* @param {goog.math.IRect} a A rectangle.296* @param {goog.math.IRect} b A rectangle.297* @return {goog.math.Rect} A new bounding rect, or null if either rect is298* null.299*/300goog.math.Rect.boundingRect = function(a, b) {301if (!a || !b) {302return null;303}304305var newRect = new goog.math.Rect(a.left, a.top, a.width, a.height);306newRect.boundingRect(b);307308return newRect;309};310311312/**313* Tests whether this rectangle entirely contains another rectangle or314* coordinate.315*316* @param {goog.math.IRect|goog.math.Coordinate} another The rectangle or317* coordinate to test for containment.318* @return {boolean} Whether this rectangle contains given rectangle or319* coordinate.320*/321goog.math.Rect.prototype.contains = function(another) {322if (another instanceof goog.math.Coordinate) {323return another.x >= this.left && another.x <= this.left + this.width &&324another.y >= this.top && another.y <= this.top + this.height;325} else { // (another instanceof goog.math.IRect)326return this.left <= another.left &&327this.left + this.width >= another.left + another.width &&328this.top <= another.top &&329this.top + this.height >= another.top + another.height;330}331};332333334/**335* @param {!goog.math.Coordinate} point A coordinate.336* @return {number} The squared distance between the point and the closest337* point inside the rectangle. Returns 0 if the point is inside the338* rectangle.339*/340goog.math.Rect.prototype.squaredDistance = function(point) {341var dx = point.x < this.left ?342this.left - point.x :343Math.max(point.x - (this.left + this.width), 0);344var dy = point.y < this.top ? this.top - point.y :345Math.max(point.y - (this.top + this.height), 0);346return dx * dx + dy * dy;347};348349350/**351* @param {!goog.math.Coordinate} point A coordinate.352* @return {number} The distance between the point and the closest point353* inside the rectangle. Returns 0 if the point is inside the rectangle.354*/355goog.math.Rect.prototype.distance = function(point) {356return Math.sqrt(this.squaredDistance(point));357};358359360/**361* @return {!goog.math.Size} The size of this rectangle.362*/363goog.math.Rect.prototype.getSize = function() {364return new goog.math.Size(this.width, this.height);365};366367368/**369* @return {!goog.math.Coordinate} A new coordinate for the top-left corner of370* the rectangle.371*/372goog.math.Rect.prototype.getTopLeft = function() {373return new goog.math.Coordinate(this.left, this.top);374};375376377/**378* @return {!goog.math.Coordinate} A new coordinate for the center of the379* rectangle.380*/381goog.math.Rect.prototype.getCenter = function() {382return new goog.math.Coordinate(383this.left + this.width / 2, this.top + this.height / 2);384};385386387/**388* @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner389* of the rectangle.390*/391goog.math.Rect.prototype.getBottomRight = function() {392return new goog.math.Coordinate(393this.left + this.width, this.top + this.height);394};395396397/**398* Rounds the fields to the next larger integer values.399* @return {!goog.math.Rect} This rectangle with ceil'd fields.400*/401goog.math.Rect.prototype.ceil = function() {402this.left = Math.ceil(this.left);403this.top = Math.ceil(this.top);404this.width = Math.ceil(this.width);405this.height = Math.ceil(this.height);406return this;407};408409410/**411* Rounds the fields to the next smaller integer values.412* @return {!goog.math.Rect} This rectangle with floored fields.413*/414goog.math.Rect.prototype.floor = function() {415this.left = Math.floor(this.left);416this.top = Math.floor(this.top);417this.width = Math.floor(this.width);418this.height = Math.floor(this.height);419return this;420};421422423/**424* Rounds the fields to nearest integer values.425* @return {!goog.math.Rect} This rectangle with rounded fields.426*/427goog.math.Rect.prototype.round = function() {428this.left = Math.round(this.left);429this.top = Math.round(this.top);430this.width = Math.round(this.width);431this.height = Math.round(this.height);432return this;433};434435436/**437* Translates this rectangle by the given offsets. If a438* {@code goog.math.Coordinate} is given, then the left and top values are439* translated by the coordinate's x and y values. Otherwise, top and left are440* translated by {@code tx} and {@code opt_ty} respectively.441* @param {number|goog.math.Coordinate} tx The value to translate left by or the442* the coordinate to translate this rect by.443* @param {number=} opt_ty The value to translate top by.444* @return {!goog.math.Rect} This rectangle after translating.445*/446goog.math.Rect.prototype.translate = function(tx, opt_ty) {447if (tx instanceof goog.math.Coordinate) {448this.left += tx.x;449this.top += tx.y;450} else {451this.left += goog.asserts.assertNumber(tx);452if (goog.isNumber(opt_ty)) {453this.top += opt_ty;454}455}456return this;457};458459460/**461* Scales this rectangle by the given scale factors. The left and width values462* are scaled by {@code sx} and the top and height values are scaled by463* {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled464* by {@code sx}.465* @param {number} sx The scale factor to use for the x dimension.466* @param {number=} opt_sy The scale factor to use for the y dimension.467* @return {!goog.math.Rect} This rectangle after scaling.468*/469goog.math.Rect.prototype.scale = function(sx, opt_sy) {470var sy = goog.isNumber(opt_sy) ? opt_sy : sx;471this.left *= sx;472this.width *= sx;473this.top *= sy;474this.height *= sy;475return this;476};477478479