diff --git a/packages/math/index.js b/packages/math/index.js index 9d74d14..4818a27 100644 --- a/packages/math/index.js +++ b/packages/math/index.js @@ -1,4 +1,7 @@ // export * as Matrix from './matrix'; -// export * as Rectangle from './rectangle'; + +import * as Rectangle from './rectangle'; +export {Rectangle}; + import * as Vector from './vector'; export {Vector}; diff --git a/packages/math/rectangle/index.coffee b/packages/math/rectangle/index.coffee deleted file mode 100644 index 936139f..0000000 --- a/packages/math/rectangle/index.coffee +++ /dev/null @@ -1,167 +0,0 @@ -# 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 intersects = (l, r) -> - - return false 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 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 isTouching = (r, v) -> - - return false 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 true - -# Compose a rectangle from a position vector and a size vector. -# -# avocado> Rectangle.compose [0, 0], [16, 16] -# [0, 0, 16, 16] -export compose = (l, r) -> [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 copy = (r) -> [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 toObject = (r, useShortKeys = false) -> - - whKeys = if useShortKeys then ['w', 'h'] else ['width', 'height'] - - O = x: r[0], y: r[1] - O[whKeys[0]] = r[2] - O[whKeys[1]] = r[3] - return O - -export fromObject = (O) -> [O.x, O.y, O.width, O.height] - -# Returns the position of a rectangle. -# -# avocado> Rectangle.position [8, 8, 16, 16] -# [8, 8] -export position = (r) -> [r[0], r[1]] - -# Returns the size of a rectangle. -# -# avocado> Rectangle.size [8, 8, 16, 16] -# [16, 16] -export size = (r) -> [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 intersection = (l, r) -> - - return [0, 0, 0, 0] unless intersects l, r - - x = Math.max l[0], r[0] - y = Math.max l[1], r[1] - - lx2 = l[0] + l[2] - rx2 = r[0] + r[2] - ly2 = l[1] + l[3] - ry2 = r[1] + r[3] - - w = (if lx2 <= rx2 then lx2 else rx2) - x - h = (if ly2 <= ry2 then ly2 else 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 translated = (r, v) -> 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 isNull = (r) -> - - return true unless r? - return true unless r.length is 4 - - 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 equals = (l, r) -> - - return l[0] is r[0] and l[1] is r[1] and l[2] is r[2] and l[3] is 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 united = (l, r) -> - - return r if isNull l - return l if isNull r - - x = Math.min l[0], r[0] - y = Math.min l[1], r[1] - x2 = Math.max l[0] + l[2], r[0] + r[2] - 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 round = (r) -> r.map Math.round - -# Floor the position and size of a rectangle. -# -# avocado> Rectangle.floor [3.14, 4.70, 5.32, 1.8] -# [3, 4, 5, 1] -export floor = (r) -> r.map Math.floor diff --git a/packages/math/rectangle/index.js b/packages/math/rectangle/index.js new file mode 100644 index 0000000..03f0080 --- /dev/null +++ b/packages/math/rectangle/index.js @@ -0,0 +1,206 @@ +// 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]), + ]; +}