rect.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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. * @fileoverview A utility class for representing rectangles. Some of these
  16. * functions should be migrated over to non-nullable params.
  17. */
  18. goog.provide('goog.math.Rect');
  19. goog.require('goog.asserts');
  20. goog.require('goog.math.Box');
  21. goog.require('goog.math.Coordinate');
  22. goog.require('goog.math.IRect');
  23. goog.require('goog.math.Size');
  24. /**
  25. * Class for representing rectangular regions.
  26. * @param {number} x Left.
  27. * @param {number} y Top.
  28. * @param {number} w Width.
  29. * @param {number} h Height.
  30. * @struct
  31. * @constructor
  32. * @implements {goog.math.IRect}
  33. */
  34. goog.math.Rect = function(x, y, w, h) {
  35. /** @type {number} */
  36. this.left = x;
  37. /** @type {number} */
  38. this.top = y;
  39. /** @type {number} */
  40. this.width = w;
  41. /** @type {number} */
  42. this.height = h;
  43. };
  44. /**
  45. * @return {!goog.math.Rect} A new copy of this Rectangle.
  46. */
  47. goog.math.Rect.prototype.clone = function() {
  48. return new goog.math.Rect(this.left, this.top, this.width, this.height);
  49. };
  50. /**
  51. * Returns a new Box object with the same position and dimensions as this
  52. * rectangle.
  53. * @return {!goog.math.Box} A new Box representation of this Rectangle.
  54. */
  55. goog.math.Rect.prototype.toBox = function() {
  56. var right = this.left + this.width;
  57. var bottom = this.top + this.height;
  58. return new goog.math.Box(this.top, right, bottom, this.left);
  59. };
  60. /**
  61. * Creates a new Rect object with the position and size given.
  62. * @param {!goog.math.Coordinate} position The top-left coordinate of the Rect
  63. * @param {!goog.math.Size} size The size of the Rect
  64. * @return {!goog.math.Rect} A new Rect initialized with the given position and
  65. * size.
  66. */
  67. goog.math.Rect.createFromPositionAndSize = function(position, size) {
  68. return new goog.math.Rect(position.x, position.y, size.width, size.height);
  69. };
  70. /**
  71. * Creates a new Rect object with the same position and dimensions as a given
  72. * Box. Note that this is only the inverse of toBox if left/top are defined.
  73. * @param {goog.math.Box} box A box.
  74. * @return {!goog.math.Rect} A new Rect initialized with the box's position
  75. * and size.
  76. */
  77. goog.math.Rect.createFromBox = function(box) {
  78. return new goog.math.Rect(
  79. box.left, box.top, box.right - box.left, box.bottom - box.top);
  80. };
  81. if (goog.DEBUG) {
  82. /**
  83. * Returns a nice string representing size and dimensions of rectangle.
  84. * @return {string} In the form (50, 73 - 75w x 25h).
  85. * @override
  86. */
  87. goog.math.Rect.prototype.toString = function() {
  88. return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
  89. this.height + 'h)';
  90. };
  91. }
  92. /**
  93. * Compares rectangles for equality.
  94. * @param {goog.math.IRect} a A Rectangle.
  95. * @param {goog.math.IRect} b A Rectangle.
  96. * @return {boolean} True iff the rectangles have the same left, top, width,
  97. * and height, or if both are null.
  98. */
  99. goog.math.Rect.equals = function(a, b) {
  100. if (a == b) {
  101. return true;
  102. }
  103. if (!a || !b) {
  104. return false;
  105. }
  106. return a.left == b.left && a.width == b.width && a.top == b.top &&
  107. a.height == b.height;
  108. };
  109. /**
  110. * Computes the intersection of this rectangle and the rectangle parameter. If
  111. * there is no intersection, returns false and leaves this rectangle as is.
  112. * @param {goog.math.IRect} rect A Rectangle.
  113. * @return {boolean} True iff this rectangle intersects with the parameter.
  114. */
  115. goog.math.Rect.prototype.intersection = function(rect) {
  116. var x0 = Math.max(this.left, rect.left);
  117. var x1 = Math.min(this.left + this.width, rect.left + rect.width);
  118. if (x0 <= x1) {
  119. var y0 = Math.max(this.top, rect.top);
  120. var y1 = Math.min(this.top + this.height, rect.top + rect.height);
  121. if (y0 <= y1) {
  122. this.left = x0;
  123. this.top = y0;
  124. this.width = x1 - x0;
  125. this.height = y1 - y0;
  126. return true;
  127. }
  128. }
  129. return false;
  130. };
  131. /**
  132. * Returns the intersection of two rectangles. Two rectangles intersect if they
  133. * touch at all, for example, two zero width and height rectangles would
  134. * intersect if they had the same top and left.
  135. * @param {goog.math.IRect} a A Rectangle.
  136. * @param {goog.math.IRect} b A Rectangle.
  137. * @return {goog.math.Rect} A new intersection rect (even if width and height
  138. * are 0), or null if there is no intersection.
  139. */
  140. goog.math.Rect.intersection = function(a, b) {
  141. // There is no nice way to do intersection via a clone, because any such
  142. // clone might be unnecessary if this function returns null. So, we duplicate
  143. // code from above.
  144. var x0 = Math.max(a.left, b.left);
  145. var x1 = Math.min(a.left + a.width, b.left + b.width);
  146. if (x0 <= x1) {
  147. var y0 = Math.max(a.top, b.top);
  148. var y1 = Math.min(a.top + a.height, b.top + b.height);
  149. if (y0 <= y1) {
  150. return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
  151. }
  152. }
  153. return null;
  154. };
  155. /**
  156. * Returns whether two rectangles intersect. Two rectangles intersect if they
  157. * touch at all, for example, two zero width and height rectangles would
  158. * intersect if they had the same top and left.
  159. * @param {goog.math.IRect} a A Rectangle.
  160. * @param {goog.math.IRect} b A Rectangle.
  161. * @return {boolean} Whether a and b intersect.
  162. */
  163. goog.math.Rect.intersects = function(a, b) {
  164. return (
  165. a.left <= b.left + b.width && b.left <= a.left + a.width &&
  166. a.top <= b.top + b.height && b.top <= a.top + a.height);
  167. };
  168. /**
  169. * Returns whether a rectangle intersects this rectangle.
  170. * @param {goog.math.IRect} rect A rectangle.
  171. * @return {boolean} Whether rect intersects this rectangle.
  172. */
  173. goog.math.Rect.prototype.intersects = function(rect) {
  174. return goog.math.Rect.intersects(this, rect);
  175. };
  176. /**
  177. * Computes the difference regions between two rectangles. The return value is
  178. * an array of 0 to 4 rectangles defining the remaining regions of the first
  179. * rectangle after the second has been subtracted.
  180. * @param {goog.math.Rect} a A Rectangle.
  181. * @param {goog.math.IRect} b A Rectangle.
  182. * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
  183. * together define the difference area of rectangle a minus rectangle b.
  184. */
  185. goog.math.Rect.difference = function(a, b) {
  186. var intersection = goog.math.Rect.intersection(a, b);
  187. if (!intersection || !intersection.height || !intersection.width) {
  188. return [a.clone()];
  189. }
  190. var result = [];
  191. var top = a.top;
  192. var height = a.height;
  193. var ar = a.left + a.width;
  194. var ab = a.top + a.height;
  195. var br = b.left + b.width;
  196. var bb = b.top + b.height;
  197. // Subtract off any area on top where A extends past B
  198. if (b.top > a.top) {
  199. result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
  200. top = b.top;
  201. // If we're moving the top down, we also need to subtract the height diff.
  202. height -= b.top - a.top;
  203. }
  204. // Subtract off any area on bottom where A extends past B
  205. if (bb < ab) {
  206. result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
  207. height = bb - top;
  208. }
  209. // Subtract any area on left where A extends past B
  210. if (b.left > a.left) {
  211. result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
  212. }
  213. // Subtract any area on right where A extends past B
  214. if (br < ar) {
  215. result.push(new goog.math.Rect(br, top, ar - br, height));
  216. }
  217. return result;
  218. };
  219. /**
  220. * Computes the difference regions between this rectangle and {@code rect}. The
  221. * return value is an array of 0 to 4 rectangles defining the remaining regions
  222. * of this rectangle after the other has been subtracted.
  223. * @param {goog.math.IRect} rect A Rectangle.
  224. * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
  225. * together define the difference area of rectangle a minus rectangle b.
  226. */
  227. goog.math.Rect.prototype.difference = function(rect) {
  228. return goog.math.Rect.difference(this, rect);
  229. };
  230. /**
  231. * Expand this rectangle to also include the area of the given rectangle.
  232. * @param {goog.math.IRect} rect The other rectangle.
  233. */
  234. goog.math.Rect.prototype.boundingRect = function(rect) {
  235. // We compute right and bottom before we change left and top below.
  236. var right = Math.max(this.left + this.width, rect.left + rect.width);
  237. var bottom = Math.max(this.top + this.height, rect.top + rect.height);
  238. this.left = Math.min(this.left, rect.left);
  239. this.top = Math.min(this.top, rect.top);
  240. this.width = right - this.left;
  241. this.height = bottom - this.top;
  242. };
  243. /**
  244. * Returns a new rectangle which completely contains both input rectangles.
  245. * @param {goog.math.IRect} a A rectangle.
  246. * @param {goog.math.IRect} b A rectangle.
  247. * @return {goog.math.Rect} A new bounding rect, or null if either rect is
  248. * null.
  249. */
  250. goog.math.Rect.boundingRect = function(a, b) {
  251. if (!a || !b) {
  252. return null;
  253. }
  254. var newRect = new goog.math.Rect(a.left, a.top, a.width, a.height);
  255. newRect.boundingRect(b);
  256. return newRect;
  257. };
  258. /**
  259. * Tests whether this rectangle entirely contains another rectangle or
  260. * coordinate.
  261. *
  262. * @param {goog.math.IRect|goog.math.Coordinate} another The rectangle or
  263. * coordinate to test for containment.
  264. * @return {boolean} Whether this rectangle contains given rectangle or
  265. * coordinate.
  266. */
  267. goog.math.Rect.prototype.contains = function(another) {
  268. if (another instanceof goog.math.Coordinate) {
  269. return another.x >= this.left && another.x <= this.left + this.width &&
  270. another.y >= this.top && another.y <= this.top + this.height;
  271. } else { // (another instanceof goog.math.IRect)
  272. return this.left <= another.left &&
  273. this.left + this.width >= another.left + another.width &&
  274. this.top <= another.top &&
  275. this.top + this.height >= another.top + another.height;
  276. }
  277. };
  278. /**
  279. * @param {!goog.math.Coordinate} point A coordinate.
  280. * @return {number} The squared distance between the point and the closest
  281. * point inside the rectangle. Returns 0 if the point is inside the
  282. * rectangle.
  283. */
  284. goog.math.Rect.prototype.squaredDistance = function(point) {
  285. var dx = point.x < this.left ?
  286. this.left - point.x :
  287. Math.max(point.x - (this.left + this.width), 0);
  288. var dy = point.y < this.top ? this.top - point.y :
  289. Math.max(point.y - (this.top + this.height), 0);
  290. return dx * dx + dy * dy;
  291. };
  292. /**
  293. * @param {!goog.math.Coordinate} point A coordinate.
  294. * @return {number} The distance between the point and the closest point
  295. * inside the rectangle. Returns 0 if the point is inside the rectangle.
  296. */
  297. goog.math.Rect.prototype.distance = function(point) {
  298. return Math.sqrt(this.squaredDistance(point));
  299. };
  300. /**
  301. * @return {!goog.math.Size} The size of this rectangle.
  302. */
  303. goog.math.Rect.prototype.getSize = function() {
  304. return new goog.math.Size(this.width, this.height);
  305. };
  306. /**
  307. * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
  308. * the rectangle.
  309. */
  310. goog.math.Rect.prototype.getTopLeft = function() {
  311. return new goog.math.Coordinate(this.left, this.top);
  312. };
  313. /**
  314. * @return {!goog.math.Coordinate} A new coordinate for the center of the
  315. * rectangle.
  316. */
  317. goog.math.Rect.prototype.getCenter = function() {
  318. return new goog.math.Coordinate(
  319. this.left + this.width / 2, this.top + this.height / 2);
  320. };
  321. /**
  322. * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
  323. * of the rectangle.
  324. */
  325. goog.math.Rect.prototype.getBottomRight = function() {
  326. return new goog.math.Coordinate(
  327. this.left + this.width, this.top + this.height);
  328. };
  329. /**
  330. * Rounds the fields to the next larger integer values.
  331. * @return {!goog.math.Rect} This rectangle with ceil'd fields.
  332. */
  333. goog.math.Rect.prototype.ceil = function() {
  334. this.left = Math.ceil(this.left);
  335. this.top = Math.ceil(this.top);
  336. this.width = Math.ceil(this.width);
  337. this.height = Math.ceil(this.height);
  338. return this;
  339. };
  340. /**
  341. * Rounds the fields to the next smaller integer values.
  342. * @return {!goog.math.Rect} This rectangle with floored fields.
  343. */
  344. goog.math.Rect.prototype.floor = function() {
  345. this.left = Math.floor(this.left);
  346. this.top = Math.floor(this.top);
  347. this.width = Math.floor(this.width);
  348. this.height = Math.floor(this.height);
  349. return this;
  350. };
  351. /**
  352. * Rounds the fields to nearest integer values.
  353. * @return {!goog.math.Rect} This rectangle with rounded fields.
  354. */
  355. goog.math.Rect.prototype.round = function() {
  356. this.left = Math.round(this.left);
  357. this.top = Math.round(this.top);
  358. this.width = Math.round(this.width);
  359. this.height = Math.round(this.height);
  360. return this;
  361. };
  362. /**
  363. * Translates this rectangle by the given offsets. If a
  364. * {@code goog.math.Coordinate} is given, then the left and top values are
  365. * translated by the coordinate's x and y values. Otherwise, top and left are
  366. * translated by {@code tx} and {@code opt_ty} respectively.
  367. * @param {number|goog.math.Coordinate} tx The value to translate left by or the
  368. * the coordinate to translate this rect by.
  369. * @param {number=} opt_ty The value to translate top by.
  370. * @return {!goog.math.Rect} This rectangle after translating.
  371. */
  372. goog.math.Rect.prototype.translate = function(tx, opt_ty) {
  373. if (tx instanceof goog.math.Coordinate) {
  374. this.left += tx.x;
  375. this.top += tx.y;
  376. } else {
  377. this.left += goog.asserts.assertNumber(tx);
  378. if (goog.isNumber(opt_ty)) {
  379. this.top += opt_ty;
  380. }
  381. }
  382. return this;
  383. };
  384. /**
  385. * Scales this rectangle by the given scale factors. The left and width values
  386. * are scaled by {@code sx} and the top and height values are scaled by
  387. * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled
  388. * by {@code sx}.
  389. * @param {number} sx The scale factor to use for the x dimension.
  390. * @param {number=} opt_sy The scale factor to use for the y dimension.
  391. * @return {!goog.math.Rect} This rectangle after scaling.
  392. */
  393. goog.math.Rect.prototype.scale = function(sx, opt_sy) {
  394. var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
  395. this.left *= sx;
  396. this.width *= sx;
  397. this.top *= sy;
  398. this.height *= sy;
  399. return this;
  400. };