553 lines
18 KiB
JavaScript
553 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
/* eslint-env node, browser */
|
|
|
|
/**
|
|
* Creates a new read-only property and attaches it to the provided context.
|
|
* @private
|
|
* @param {string} name - Name for new property.
|
|
* @param {*} [value] - Value of new property.
|
|
*/
|
|
function addReadOnlyProperty(name, value) {
|
|
Object.defineProperty(this, name, {
|
|
value: value,
|
|
writable: false,
|
|
enumerable: true,
|
|
configurable: false
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @constructor Point
|
|
*
|
|
* @desc This object represents a single point in an abstract 2-dimensional matrix.
|
|
*
|
|
* The unit of measure is typically pixels.
|
|
* (If used to model computer graphics, vertical coordinates are typically measured downwards
|
|
* from the top of the window. This convention however is not inherent in this object.)
|
|
*
|
|
* Note: This object should be instantiated with the `new` keyword.
|
|
*
|
|
* @param {number} x - the new point's `x` property
|
|
* @param {number} y - the new point's `y` property
|
|
*/
|
|
function Point(x, y) {
|
|
|
|
/**
|
|
* @name x
|
|
* @type {number}
|
|
* @summary This point's horizontal coordinate.
|
|
* @desc Created upon instantiation by the {@link Point|constructor}.
|
|
* @memberOf Point.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'x', Number(x) || 0);
|
|
|
|
/**
|
|
* @name y
|
|
* @type {number}
|
|
* @summary This point's vertical coordinate.
|
|
* @desc Created upon instantiation by the {@link Point|constructor}.
|
|
* @memberOf Point.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'y', Number(y) || 0);
|
|
|
|
}
|
|
|
|
Point.prototype = {
|
|
|
|
/**
|
|
* @returns {Point} A new point which is this point's position increased by coordinates of given `offset`.
|
|
* @param {Point} offset - Horizontal and vertical values to add to this point's coordinates.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
plus: function(offset) {
|
|
return new Point(
|
|
this.x + offset.x,
|
|
this.y + offset.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {Point} A new point which is this point's position increased by given offsets.
|
|
* @param {number} [offsetX=0] - Value to add to this point's horizontal coordinate.
|
|
* @param {number} [offsetY=0] - Value to add to this point's horizontal coordinate.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
plusXY: function(offsetX, offsetY) {
|
|
return new Point(
|
|
this.x + (offsetX || 0),
|
|
this.y + (offsetY || 0)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {Point} A new point which is this point's position decreased by coordinates of given `offset`.
|
|
* @param {Point} offset - Horizontal and vertical values to subtract from this point's coordinates.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
minus: function(offset) {
|
|
return new Point(
|
|
this.x - offset.x,
|
|
this.y - offset.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {Point} A new `Point` positioned to least x and least y of this point and given `offset`.
|
|
* @param {Point} point - A point to compare to this point.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
min: function(point) {
|
|
return new Point(
|
|
Math.min(this.x, point.x),
|
|
Math.min(this.y, point.y)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {Point} A new `Point` positioned to greatest x and greatest y of this point and given `point`.
|
|
* @param {Point} point - A point to compare to this point.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
max: function(point) {
|
|
return new Point(
|
|
Math.max(this.x, point.x),
|
|
Math.max(this.y, point.y)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {number} Distance between given `point` and this point using Pythagorean Theorem formula.
|
|
* @param {Point} point - A point from which to compute the distance to this point.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
distance: function(point) {
|
|
var deltaX = point.x - this.x,
|
|
deltaY = point.y - this.y;
|
|
|
|
return Math.sqrt(
|
|
deltaX * deltaX +
|
|
deltaY * deltaY
|
|
);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly: `equal`.)_
|
|
* @returns {boolean} `true` iff _both_ coordinates of this point are exactly equal to those of given `point`.
|
|
* @param {Point} point - A point to compare to this point.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
equals: function(point) {
|
|
var result = false;
|
|
|
|
if (point) {
|
|
result =
|
|
this.x === point.x &&
|
|
this.y === point.y;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @returns {boolean} `true` iff _both_ coordinates of this point are greater than those of given `point`.
|
|
* @param {Point} point - A point to compare to this point
|
|
* @memberOf Point.prototype
|
|
*/
|
|
greaterThan: function(point) {
|
|
return (
|
|
this.x > point.x &&
|
|
this.y > point.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @returns {boolean} `true` iff _both_ coordinates of this point are less than those of given `point`.
|
|
* @param {Point} point - A point to compare to this point
|
|
* @memberOf Point.prototype
|
|
*/
|
|
lessThan: function(point) {
|
|
return (
|
|
this.x < point.x &&
|
|
this.y < point.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly `greaterThanEqualTo`.)_
|
|
* @returns {boolean} `true` iff _both_ coordinates of this point are greater than or equal to those of given `point`.
|
|
* @param {Point} point - A point to compare to this point
|
|
* @memberOf Point.prototype
|
|
*/
|
|
greaterThanOrEqualTo: function(point) {
|
|
return (
|
|
this.x >= point.x &&
|
|
this.y >= point.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly `lessThanEqualTo`.)_
|
|
* @returns {boolean} `true` iff _both_ coordinates of this point are less than or equal to those of given `point`.
|
|
* @param {Point} point - A point to compare to this point.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
lessThanOrEqualTo: function(point) {
|
|
return (
|
|
this.x <= point.x &&
|
|
this.y <= point.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly `isContainedWithinRectangle`.)_
|
|
* @param rect {Rectangle} - Rectangle to test this point against.
|
|
* @returns {boolean} `true` iff this point is within given `rect`.
|
|
* @memberOf Point.prototype
|
|
*/
|
|
within: function(rect) {
|
|
var minX = rect.origin.x,
|
|
maxX = minX + rect.extent.x;
|
|
var minY = rect.origin.y,
|
|
maxY = minY + rect.extent.y;
|
|
|
|
if (rect.extent.x < 0) {
|
|
minX = maxX;
|
|
maxX = rect.origin.x;
|
|
}
|
|
|
|
if (rect.extent.y < 0) {
|
|
minY = maxY;
|
|
maxY = rect.origin.y;
|
|
}
|
|
|
|
return (
|
|
minX <= this.x && this.x < maxX &&
|
|
minY <= this.y && this.y < maxY
|
|
);
|
|
}
|
|
};
|
|
|
|
Point.prototype.EQ = Point.prototype.equals;
|
|
Point.prototype.GT = Point.prototype.greaterThan;
|
|
Point.prototype.LT = Point.prototype.lessThan;
|
|
Point.prototype.GE = Point.prototype.greaterThanOrEqualTo;
|
|
Point.prototype.LE = Point.prototype.lessThanOrEqualTo;
|
|
|
|
|
|
/**
|
|
* @constructor Rectangle
|
|
*
|
|
* @desc This object represents a rectangular area within an abstract 2-dimensional matrix.
|
|
*
|
|
* The unit of measure is typically pixels.
|
|
* (If used to model computer graphics, vertical coordinates are typically measured downwards
|
|
* from the top of the window. This convention however is not inherent in this object.)
|
|
*
|
|
* Normally, the `x` and `y` parameters to the constructor describe the upper left corner of the rect.
|
|
* However, negative values of `width` and `height` will be added to the given `x` and `y`. That is,
|
|
* a negative value of the `width` parameter will extend the rect to the left of the given `x` and
|
|
* a negative value of the `height` parameter will extend the rect above the given `y`.
|
|
* In any case, after instantiation the following are guaranteed to always be true:
|
|
* * The `extent`, `width`, and `height` properties _always_ give positive values.
|
|
* * The `origin`, `top`, and `left` properties _always_ reflect the upper left corner.
|
|
* * The `corner`, `bottom`, and `right` properties _always_ reflect the lower right corner.
|
|
*
|
|
* Note: This object should be instantiated with the `new` keyword.
|
|
*
|
|
* @param {number} [x=0] - Horizontal coordinate of some corner of the rect.
|
|
* @param {number} [y=0] - Vertical coordinate of some corner of the rect.
|
|
* @param {number} [width=0] - Width of the new rect. May be negative (see above).
|
|
* @param {number} [height=0] - Height of the new rect. May be negative (see above).
|
|
*/
|
|
function Rectangle(x, y, width, height) {
|
|
|
|
x = Number(x) || 0;
|
|
y = Number(y) || 0;
|
|
width = Number(width) || 0;
|
|
height = Number(height) || 0;
|
|
|
|
if (width < 0) {
|
|
x += width;
|
|
width = -width;
|
|
}
|
|
|
|
if (height < 0) {
|
|
y += height;
|
|
height = -height;
|
|
}
|
|
|
|
/**
|
|
* @name origin
|
|
* @type {Point}
|
|
* @summary Upper left corner of this rect.
|
|
* @desc Created upon instantiation by the {@linkplain Rectangle|constructor}.
|
|
* @memberOf Rectangle.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'origin', new Point(x, y));
|
|
|
|
/**
|
|
* @name extent
|
|
* @type {Point}
|
|
* @summary this rect's width and height.
|
|
* @desc Unlike the other `Point` properties, `extent` is not a global coordinate pair; rather it consists of a _width_ (`x`, always positive) and a _height_ (`y`, always positive).
|
|
*
|
|
* This object might be more legitimately typed as something like `Area` with properties `width` and `height`; however we wanted it to be able to use it efficiently with a point's `plus` and `minus` methods (that is, without those methods having to check and branch on the type of its parameter).
|
|
*
|
|
* Created upon instantiation by the {@linkplain Rectangle|constructor}.
|
|
* @see The {@link Rectangle#corner|corner} method.
|
|
* @memberOf Rectangle.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'extent', new Point(width, height));
|
|
|
|
/**
|
|
* @name corner
|
|
* @type {Point}
|
|
* @summary Lower right corner of this rect.
|
|
* @desc This is a calculated value created upon instantiation by the {@linkplain Rectangle|constructor}. It is `origin` offset by `extent`.
|
|
*
|
|
* **Note:** These coordinates actually point to the pixel one below and one to the right of the rect's actual lower right pixel.
|
|
* @memberOf Rectangle.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'corner', new Point(x + width, y + height));
|
|
|
|
/**
|
|
* @name center
|
|
* @type {Point}
|
|
* @summary Center of this rect.
|
|
* @desc Created upon instantiation by the {@linkplain Rectangle|constructor}.
|
|
* @memberOf Rectangle.prototype
|
|
* @abstract
|
|
*/
|
|
addReadOnlyProperty.call(this, 'center', new Point(x + (width / 2), y + (height / 2)));
|
|
|
|
}
|
|
|
|
Rectangle.prototype = {
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Minimum vertical coordinate of this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get top() {
|
|
return this.origin.y;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Minimum horizontal coordinate of this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get left() {
|
|
return this.origin.x;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Maximum vertical coordinate of this rect + 1.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get bottom() {
|
|
return this.corner.y;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Maximum horizontal coordinate of this rect + 1.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get right() {
|
|
return this.corner.x;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Width of this rect (always positive).
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get width() {
|
|
return this.extent.x;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Height of this rect (always positive).
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get height() {
|
|
return this.extent.y;
|
|
},
|
|
|
|
/**
|
|
* @type {number}
|
|
* @desc _(Formerly a function; now a getter.)_
|
|
* @summary Area of this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
get area() {
|
|
return this.width * this.height;
|
|
},
|
|
|
|
/**
|
|
* @returns {Rectangle} A copy of this rect but with horizontal position reset to given `x` and no width.
|
|
* @param {number} x - Horizontal coordinate of the new rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
flattenXAt: function(x) {
|
|
return new Rectangle(x, this.origin.y, 0, this.extent.y);
|
|
},
|
|
|
|
/**
|
|
* @returns {Rectangle} A copy of this rect but with vertical position reset to given `y` and no height.
|
|
* @param {number} y - Vertical coordinate of the new rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
flattenYAt: function(y) {
|
|
return new Rectangle(this.origin.x, y, this.extent.x, 0);
|
|
},
|
|
|
|
/**
|
|
* @returns {boolean} `true` iff given `point` entirely contained within this rect.
|
|
* @param {Point} pointOrRect - The point or rect to test for containment.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
contains: function(pointOrRect) {
|
|
return pointOrRect.within(this);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly `isContainedWithinRectangle`.)_
|
|
* @returns {boolean} `true` iff `this` rect is entirely contained within given `rect`.
|
|
* @param {Rectangle} rect - Rectangle to test against this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
within: function(rect) {
|
|
return (
|
|
rect.origin.lessThanOrEqualTo(this.origin) &&
|
|
rect.corner.greaterThanOrEqualTo(this.corner)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* _(Formerly: `insetBy`.)_
|
|
* @returns {Rectangle} That is enlarged/shrunk by given `padding`.
|
|
* @param {number} padding - Amount by which to increase (+) or decrease (-) this rect
|
|
* @see The {@link Rectangle#shrinkBy|shrinkBy} method.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
growBy: function(padding) {
|
|
return new Rectangle(
|
|
this.origin.x + padding,
|
|
this.origin.y + padding,
|
|
this.extent.x - padding - padding,
|
|
this.extent.y - padding - padding);
|
|
},
|
|
|
|
/**
|
|
* @returns {Rectangle} That is enlarged/shrunk by given `padding`.
|
|
* @param {number} padding - Amount by which to decrease (+) or increase (-) this rect.
|
|
* @see The {@link Rectangle#growBy|growBy} method.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
shrinkBy: function(padding) {
|
|
return this.growBy(-padding);
|
|
},
|
|
|
|
/**
|
|
* @returns {Rectangle} Bounding rect that contains both this rect and the given `rect`.
|
|
* @param {Rectangle} rect - The rectangle to union with this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
union: function(rect) {
|
|
var origin = this.origin.min(rect.origin),
|
|
corner = this.corner.max(rect.corner),
|
|
extent = corner.minus(origin);
|
|
|
|
return new Rectangle(
|
|
origin.x, origin.y,
|
|
extent.x, extent.y
|
|
);
|
|
},
|
|
|
|
/**
|
|
* iterate over all points within this rect, invoking `iteratee` for each.
|
|
* @param {function(number,number)} iteratee - Function to call for each point.
|
|
* Bound to `context` when given; otherwise it is bound to this rect.
|
|
* Each invocation of `iteratee` is called with two arguments:
|
|
* the horizontal and vertical coordinates of the point.
|
|
* @param {object} [context=this] - Context to bind to `iteratee` (when not `this`).
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
forEach: function(iteratee, context) {
|
|
context = context || this;
|
|
for (var x = this.origin.x, x2 = this.corner.x; x < x2; x++) {
|
|
for (var y = this.origin.y, y2 = this.corner.y; y < y2; y++) {
|
|
iteratee.call(context, x, y);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @returns {Rectangle} One of:
|
|
* * _If this rect intersects with the given `rect`:_
|
|
* a new rect representing that intersection.
|
|
* * _If it doesn't intersect and `ifNoneAction` defined:_
|
|
* result of calling `ifNoneAction`.
|
|
* * _If it doesn't intersect and `ifNoneAction` undefined:_
|
|
* `null`.
|
|
* @param {Rectangle} rect - The rectangle to intersect with this rect.
|
|
* @param {function(Rectangle)} [ifNoneAction] - When no intersection, invoke and return result.
|
|
* Bound to `context` when given; otherwise bound to this rect.
|
|
* Invoked with `rect` as sole parameter.
|
|
* @param {object} [context=this] - Context to bind to `ifNoneAction` (when not `this`).
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
intersect: function(rect, ifNoneAction, context) {
|
|
var result = null,
|
|
origin = this.origin.max(rect.origin),
|
|
corner = this.corner.min(rect.corner),
|
|
extent = corner.minus(origin);
|
|
|
|
if (extent.x > 0 && extent.y > 0) {
|
|
result = new Rectangle(
|
|
origin.x, origin.y,
|
|
extent.x, extent.y
|
|
);
|
|
} else if (typeof ifNoneAction === 'function') {
|
|
result = ifNoneAction.call(context || this, rect);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @returns {boolean} `true` iff this rect overlaps with given `rect`.
|
|
* @param {Rectangle} rect - The rectangle to intersect with this rect.
|
|
* @memberOf Rectangle.prototype
|
|
*/
|
|
intersects: function(rect) {
|
|
return (
|
|
rect.corner.x > this.origin.x &&
|
|
rect.corner.y > this.origin.y &&
|
|
rect.origin.x < this.corner.x &&
|
|
rect.origin.y < this.corner.y
|
|
);
|
|
}
|
|
};
|
|
|
|
// Interface
|
|
exports.Point = Point;
|
|
exports.Rectangle = Rectangle;
|