/*global document */
/**
* Creates a new Item.
*
* @param {Object} options A map of initial properties.
* @constructor
*/
function Item(options) {
if (!options || !options.world || typeof options.world !== 'object') {
throw new Error('Item: A valid DOM object is required for the new Item "world" property.');
}
this.world = options.world;
this.name = options.name || 'Item';
this.id = this.name + exports.System.getNewId();
this._force = new exports.Vector();
this._camera = new exports.Vector();
this.el = document.createElement('div');
this.el.id = this.id;
this.el.className = 'item ' + this.name.toLowerCase();
this.el.style.visibility = 'hidden';
this.world.el.appendChild(this.el);
}
/**
* Resets all properties.
*
* @param {Object} [opt_options=] A map of initial properties.
* @param {number} [opt_options.width = 10] Width.
* @param {number} [opt_options.height = 10] Height.
* @param {Array} [opt_options.color = 0, 0, 0] Color.
* @param {string} [opt_options.colorMode = 'rgb'] Color mode. Accepted values: 'rgb', 'hsl'.
* @param {string} [opt_options.visibility = 'visible'] Visibility. Accepted values: 'visible', 'hidden'.
* @param {number} [opt_options.opacity = 1] Opacity.
* @param {number} [opt_options.zIndex = 1] zIndex.
* @param {number} [opt_options.borderWidth = 0] borderWidth.
* @param {string} [opt_options.borderStyle = 'none'] borderStyle.
* @param {string|Array} [opt_options.borderColor = 'transparent'] borderColor.
* @param {number} [opt_options.borderRadius = 0] borderRadius.
* @param {Object} [opt_options.boxShadowOffset = new Vector()] boxShadowOffset.
* @param {number} [opt_options.boxShadowBlur = 0] boxShadowBlur.
* @param {number} [opt_options.boxShadowSpread = 0] boxShadowSpread.
* @param {string|Array} [opt_options.boxShadowColor = 'transparent'] boxShadowColor.
* @param {number} [opt_options.bounciness = 0.8] bounciness.
* @param {number} [opt_options.mass = 10] mass.
* @param {Function|Object} [opt_options.acceleration = new Vector()] acceleration.
* @param {Function|Object} [opt_options.velocity = new Vector()] velocity.
* @param {Function|Object} [opt_options.location = new Vector()] location.
* @param {number} [opt_options.maxSpeed = 10] maxSpeed.
* @param {number} [opt_options.minSpeed = 10] minSpeed.
* @param {number} [opt_options.angle = 0] Angle.
* @param {string} [opt_options.position = 'absolute'] A css position. Possible values: 'absoulte', 'fixed', 'static', 'relative'.
* @param {number} [opt_options.paddingTop = 0] Padding top.
* @param {number} [opt_options.paddingRight = 0] Padding right.
* @param {number} [opt_options.paddingBottom = 0] Padding bottom.
* @param {number} [opt_options.paddingLeft = 0] Padding left.
* @param {number} [opt_options.lifespan = -1] Lifespan.
* @param {number} [opt_options.life = 0] Life.
* @param {boolean} [opt_options.isStatic = false] If set to true, object will not move.
* @param {boolean} [opt_options.controlCamera = false] If set to true, object controls the camera.
* @param {Array} [opt_options.worldBounds = true, true, true, true] Defines the boundaries checked
* checkWorldEdges is true.
* @param {boolean} [opt_options.checkWorldEdges = false] If set to true, system restricts object
* movement to world boundaries.
* @param {boolean} [opt_options.wrapWorldEdges = false] If set to true, system checks if object
* intersects world boundaries and resets location to the opposite boundary.
* @param {boolean} [opt_options.avoidWorldEdges = false] If set to true, object steers away from
* world boundaries.
* @param {number} [opt_options.avoidWorldEdgesStrength = 0] The distance threshold for object
* start steering away from world boundaries.
*/
Item.prototype.reset = function(opt_options) {
var i, options = opt_options || {};
for (i in options) {
if (options.hasOwnProperty(i)) {
this[i] = options[i];
}
}
this.width = typeof options.width === 'undefined' ? 10 : options.width;
this.height = typeof options.height === 'undefined' ? 10 : options.height;
this.color = options.color || [0, 0, 0];
this.colorMode = options.colorMode || 'rgb';
this.visibility = options.visibility || 'visible';
this.opacity = typeof options.opacity === 'undefined' ? 1 : options.opacity;
this.zIndex = typeof options.zIndex === 'undefined' ? 1 : options.zIndex;
this.borderWidth = options.borderWidth || 0;
this.borderStyle = options.borderStyle || 'none';
this.borderColor = options.borderColor || 'transparent';
this.borderRadius = options.borderRadius || 0;
this.boxShadowOffset = typeof options.boxShadowOffset === 'undefined' ? new exports.Vector() : options.boxShadowOffset;
this.boxShadowBlur = options.boxShadowBlur || 0;
this.boxShadowSpread = options.boxShadowSpread || 0;
this.boxShadowColor = typeof options.boxShadowColor === 'undefined' ? 'transparent' : options.boxShadowColor;
this.bounciness = typeof options.bounciness === 'undefined' ? 0.8 : options.bounciness;
this.mass = typeof options.mass === 'undefined' ? 10 : options.mass;
this.acceleration = typeof options.acceleration === 'function' ? options.acceleration.call(this) :
options.acceleration || new exports.Vector();
this.velocity = typeof options.velocity === 'function' ? options.velocity.call(this) :
options.velocity || new exports.Vector();
this.location = typeof options.location === 'function' ? options.location.call(this) :
options.location || new exports.Vector(this.world.width / 2, this.world.height / 2);
this.initLocation = new exports.Vector();
this.initLocation.x = this.location.x;
this.initLocation.y = this.location.y;
this.maxSpeed = typeof options.maxSpeed === 'undefined' ? 10 : options.maxSpeed;
this.minSpeed = options.minSpeed || 0;
this.angle = options.angle || 0;
this.position = options.position || 'absolute';
this.paddingTop = options.paddingTop || 0;
this.paddingRight = options.paddingRight || 0;
this.paddingBottom = options.paddingBottom || 0;
this.paddingLeft = options.paddingLeft || 0;
this.marginTop = options.marginTop || 0;
this.lifespan = typeof options.lifespan === 'undefined' ? -1 : options.lifespan;
this.life = options.life || 0;
this.isStatic = !!options.isStatic;
this.controlCamera = !!options.controlCamera;
this.worldBounds = options.worldBounds || [true, true, true, true];
this.checkWorldEdges = typeof options.checkWorldEdges === 'undefined' ? true : options.checkWorldEdges;
this.wrapWorldEdges = !!options.wrapWorldEdges;
this.wrapWorldEdgesSoft = !!options.wrapWorldEdgesSoft;
this.avoidWorldEdges = !!options.avoidWorldEdges;
this.avoidWorldEdgesStrength = typeof options.avoidWorldEdgesStrength === 'undefined' ? 50 : options.avoidWorldEdgesStrength;
// if controlCamera is true, world is not static.
this.world.isStatic = false;
};
/**
* Updates properties.
*/
Item.prototype.step = function() {
if (!this.isStatic) {
this.applyForce(this.world.gravity);
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed, this.minSpeed);
this.location.add(this.velocity);
if (this.controlCamera) {
this._checkCameraEdges();
}
if (this.checkWorldEdges) {
this._checkWorldEdges();
}
this.acceleration.mult(0);
if (this.life < this.lifespan) {
this.life++;
} else if (this.lifespan !== -1) {
exports.System.destroyItem(this);
}
}
};
/**
* Adds a force to this object's acceleration.
*
* @param {Object} force A Vector representing a force to apply.
* @returns {Object} A Vector representing a new acceleration.
*/
Item.prototype.applyForce = function(force) {
// calculated via F = m * a
if (force) {
this._force.x = force.x;
this._force.y = force.y;
this._force.div(this.mass);
this.acceleration.add(this._force);
return this.acceleration;
}
};
/**
* Determines if this object is outside the world bounds.
*
* @private
*/
Item.prototype._checkWorldEdges = function() {
var x, y, worldRight = this.world.bounds[1],
worldBottom = this.world.bounds[2],
worldBounds = this.worldBounds,
location = this.location,
velocity = this.velocity,
width = this.width,
height = this.height,
bounciness = this.bounciness;
// transform origin is at the center of the object
if (this.wrapWorldEdgesSoft) {
x = location.x;
y = location.y;
if (location.x > worldRight) {
location.x = -(worldRight - location.x);
if (this.controlCamera) {
this.world.location.x = this.world.location.x + x - location.x;
}
} else if (location.x < 0) {
location.x = worldRight + location.x;
if (this.controlCamera) {
this.world.location.x = this.world.location.x + x - location.x;
}
}
if (location.y > worldBottom) {
location.y = -(worldBottom - location.y);
if (this.controlCamera) {
this.world.location.y = this.world.location.y + y - location.y;
}
} else if (location.y < 0) {
location.y = worldBottom + location.y;
if (this.controlCamera) {
this.world.location.y = this.world.location.y + y - location.y;
}
}
} else if (this.wrapWorldEdges) {
x = location.x;
y = location.y;
if (location.x > worldRight) {
location.x = 0;
if (this.controlCamera) {
this.world.location.x = this.world.location.x + x - location.x;
}
} else if (location.x < 0) {
location.x = worldRight;
if (this.controlCamera) {
this.world.location.x = this.world.location.x + x - location.x;
}
}
if (location.y > worldBottom) {
location.y = 0;
if (this.controlCamera) {
this.world.location.y = this.world.location.y + y - location.y;
}
} else if (location.y < 0) {
location.y = worldBottom;
if (this.controlCamera) {
this.world.location.y = this.world.location.y + y - location.y;
}
}
} else {
if (location.x + width / 2 > worldRight && worldBounds[1]) {
location.x = worldRight - width / 2;
velocity.x *= -1 * bounciness;
} else if (location.x < width / 2 && worldBounds[3]) {
location.x = width / 2;
velocity.x *= -1 * bounciness;
}
if (location.y + height / 2 > worldBottom && worldBounds[2]) {
location.y = worldBottom - height / 2;
velocity.y *= -1 * bounciness;
} else if (location.y < height / 2 && worldBounds[0]) {
location.y = height / 2;
velocity.y *= -1 * bounciness;
}
}
};
/**
* Moves the world in the opposite direction of the Camera's controlObj.
*/
Item.prototype._checkCameraEdges = function() {
this._camera.x = this.velocity.x;
this._camera.y = this.velocity.y;
this.world.location.add(this._camera.mult(-1));
};
/**
* Updates the corresponding DOM element's style property.
*/
Item.prototype.draw = function() {
exports.System._draw(this);
};