// Rectangle operations. // **Rectangle** is a utility class to help with rectangle operations. A // rectangle is implemented as a 4-element array. Element 0 is *x*, element // 1 is *y*, element 2 is *width* and element 3 is *height*. import * as Vector from '../vector'; // Check if a rectangle intersects with another rectangle. // // avocado> Rectangle.intersects [0, 0, 16, 16], [8, 8, 24, 24] // true // // avocado> Rectangle.intersects [0, 0, 16, 16], [16, 16, 32, 32] // false export function intersects (l, r) { if (l[0] >= r[0] + r[2]) { return false; } if (r[0] >= l[0] + l[2]) { return false; } if (l[1] >= r[1] + r[3]) { return false; } if (r[1] >= l[1] + l[3]) { return false; } return true; } // Check if a rectangle is touching a vector. // // avocado> Rectangle.isTouching [0, 0, 16, 16], [0, 0] // true // // avocado> Rectangle.intersects [0, 0, 16, 16], [16, 16] // false export function isTouching (r, v) { if (v[0] < r[0]) { return false; } if (v[1] < r[1]) { return false; } if (v[0] >= r[0] + r[2]) { return false; } if (v[1] >= r[1] + r[3]) { return false; } return true } // Compose a rectangle from a position vector and a size vector. // // avocado> Rectangle.compose [0, 0], [16, 16] // [0, 0, 16, 16] export function compose (l, r) { return [l[0], l[1], r[0], r[1]]; } // Make a deep copy of the rectangle. // // avocado> rectangle = [0, 0, 16, 16] // avocado> rectangle is Rectangle.copy rectangle // false export function copy (r) { return [r[0], r[1], r[2], r[3]]; } // Convert a rectangle to an object. If you *useShortKeys*, The width and // height keys will be named w and h, respectively. // // avocado> Rectangle.toObject [3, 4, 5, 6] // {x: 3, y: 4, width: 5, height: 6} // // avocado> Rectangle.toObject [3, 4, 5, 6], true // {x: 3, y: 4, w: 5, h: 6} export function toObject (r, useShortKeys = false) { const whKeys = useShortKeys ? ['w', 'h'] : ['width', 'height']; const O = {x: r[0], y: r[1]}; O[whKeys[0]] = r[2]; O[whKeys[1]] = r[3]; return O; } export function fromObject(O) { return [O.x, O.y, O.width, O.height]; } // Returns the position of a rectangle. // // avocado> Rectangle.position [8, 8, 16, 16] // [8, 8] export function position(r) { return [r[0], r[1]]; } // Returns the size of a rectangle. // // avocado> Rectangle.size [8, 8, 16, 16] // [16, 16] export function size(r) { return [r[2], r[3]]; } // Compute the intersection rectangle of two rectangles. // // avocado> Rectangle.intersection [0, 0, 16, 16], [8, 8, 24, 24] // [8, 8, 8, 8] export function intersection(l, r) { if (!intersects(l, r)) { return [0, 0, 0, 0]; } const x = Math.max(l[0], r[0]); const y = Math.max(l[1], r[1]); const lx2 = l[0] + l[2]; const rx2 = r[0] + r[2]; const ly2 = l[1] + l[3]; const ry2 = r[1] + r[3]; const w = (lx2 <= rx2 ? lx2 : rx2) - x; const h = (ly2 <= ry2 ? ly2 : ry2) - y; return [x, y, w, h]; } // Returns a rectangle translated along the [*x*, *y*] axis of a vector. // // avocado> Rectangle.translated [0, 0, 16, 16], [8, 8] // [8, 8, 16, 16] export function translated(r, v) { return compose(Vector.add(v, position(r)), size(r)); } // Checks if a rectangle is null. A null rectangle is defined by having any // 0-length axis. // // avocado> Rectangle.isNull [0, 0, 1, 1] // false // // avocado> Rectangle.isNull [0, 0, 1, 0] // true export function isNull(r) { if (!r) { return true; } if (4 !== r.length) { return true; } return Vector.isNull(size(r)); } // Check whether a rectangle equals another rectangle. // // avocado> Rectangle.equals [0, 0, 0, 0], [0, 0, 0, 1] // false // // avocado> Rectangle.equals [0, 0, 0, 0], [0, 0, 0, 0] // true export function equals(l, r) { return l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3]; } // Returns a rectangle that is the united area of two rectangles. // // avocado> Rectangle.united [0, 0, 4, 4], [4, 4, 8, 8] // [0, 0, 12, 12] export function united(l, r) { if (isNull(l)) { return r; } if (isNull(r)) { return l; } const x = Math.min(l[0], r[0]); const y = Math.min(l[1], r[1]); const x2 = Math.max(l[0] + l[2], r[0] + r[2]); const y2 = Math.max(l[1] + l[3], r[1] + r[3]); return [x, y, x2 - x, y2 - y]; } // Round the position and size of a rectangle. // // avocado> Rectangle.round [3.14, 4.70, 5.32, 1.8] // [3, 5, 5, 2] export function round(r) { return [ Math.round(r[0]), Math.round(r[1]), Math.round(r[2]), Math.round(r[3]), ]; } // Floor the position and size of a rectangle. // // avocado> Rectangle.floor [3.14, 4.70, 5.32, 1.8] // [3, 4, 5, 1] export function floor(r) { return [ Math.floor(r[0]), Math.floor(r[1]), Math.floor(r[2]), Math.floor(r[3]), ]; } export function toPoints(r) { // Break into 4 points. const width = r[2] - .0001; const height = r[3] - .0001; const upperLeft = position(r); const upperRight = Vector.add(upperLeft, [width, 0]); const lowerLeft = Vector.add(upperLeft, [0, height]); const lowerRight = Vector.add(upperLeft, [width, height]); return [ upperLeft, upperRight, lowerLeft, lowerRight, ]; }