'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;