var Item = require('burner').Item,
System = require('burner').System,
Utils = require('burner').Utils,
Vector = require('burner').Vector;
/**
* Creates a new Mover.
*
* Movers are the root object for any item that moves. They are not
* aware of other Movers or stimuli. They have no means of locomotion
* and change only due to external forces. You will never directly
* implement Mover.
*
* @constructor
* @extends Item
*/
function Mover(opt_options) {
Item.call(this);
}
Utils.extend(Mover, Item);
/**
* Initializes an instance of Mover.
* @param {Object} world An instance of World.
* @param {Object} opt_options A map of initial properties.
* @param {string|Array} [opt_options.color = 255, 255, 255] Color.
* @param {number} [opt_options.borderRadius = 100] Border radius.
* @param {number} [opt_options.borderWidth = 2] Border width.
* @param {string} [opt_options.borderStyle = 'solid'] Border style.
* @param {Array} [opt_options.borderColor = 60, 60, 60] Border color.
* @param {boolean} [opt_options.pointToDirection = true] If true, object will point in the direction it's moving.
* @param {boolean} [opt_options.draggable = false] If true, object can move via drag and drop.
* @param {Object} [opt_options.parent = null] A parent object. If set, object will be fixed to the parent relative to an offset distance.
* @param {boolean} [opt_options.pointToParentDirection = true] If true, object points in the direction of the parent's velocity.
* @param {number} [opt_options.offsetDistance = 30] The distance from the center of the object's parent.
* @param {number} [opt_options.offsetAngle = 0] The rotation around the center of the object's parent.
* @param {function} [opt_options.afterStep = null] A function to run after the step() function.
* @param {function} [opt_options.isStatic = false] Set to true to prevent object from moving.
* @param {Object} [opt_options.parent = null] Attach to another Flora object.
*/
Mover.prototype.init = function(world, opt_options) {
Mover._superClass.init.call(this, world, opt_options);
var options = opt_options || {};
this.color = options.color || [255, 255, 255];
this.borderRadius = options.borderRadius || 0;
this.borderWidth = options.borderWidth || 0;
this.borderStyle = options.borderStyle || 'none';
this.borderColor = options.borderColor || [0, 0, 0];
this.pointToDirection = typeof options.pointToDirection === 'undefined' ? true : options.pointToDirection;
this.draggable = !!options.draggable;
this.parent = options.parent || null;
this.pointToParentDirection = typeof options.pointToParentDirection === 'undefined' ? true : options.pointToParentDirection;
this.offsetDistance = typeof options.offsetDistance === 'undefined' ? 0 : options.offsetDistance;
this.offsetAngle = options.offsetAngle || 0;
this.isStatic = !!options.isStatic;
var me = this;
this.isMouseOut = false;
this.isPressed = false;
this.mouseOutInterval = false;
this._friction = new Vector();
if (this.draggable) {
Utils.addEvent(this.el, 'mouseover', (function() {
return function(e) {
Mover.mouseover.call(me, e);
};
}()));
Utils.addEvent(this.el, 'mousedown', (function() {
return function(e) {
Mover.mousedown.call(me, e);
};
}()));
Utils.addEvent(this.el, 'mousemove', (function() {
return function(e) {
Mover.mousemove.call(me, e);
};
}()));
Utils.addEvent(this.el, 'mouseup', (function() {
return function(e) {
Mover.mouseup.call(me, e);
};
}()));
Utils.addEvent(this.el, 'mouseout', (function() {
return function(e) {
Mover.mouseout.call(me, e);
};
}()));
}
};
/**
* Handles mouseup events.
*/
Mover.mouseover = function() {
this.isMouseOut = false;
clearInterval(this.mouseOutInterval);
};
/**
* Handles mousedown events.
*/
Mover.mousedown = function() {
this.isPressed = true;
this.isMouseOut = false;
};
/**
* Handles mousemove events.
* @param {Object} e An event object.
*/
Mover.mousemove = function(e) {
var x, y;
if (this.isPressed) {
this.isMouseOut = false;
if (e.pageX && e.pageY) {
x = e.pageX - this.world.el.offsetLeft;
y = e.pageY - this.world.el.offsetTop;
} else if (e.clientX && e.clientY) {
x = e.clientX - this.world.el.offsetLeft;
y = e.clientY - this.world.el.offsetTop;
}
if (x & y) {
this.location = new Vector(x, y);
}
this._checkWorldEdges();
}
};
/**
* Handles mouseup events.
*/
Mover.mouseup = function() {
this.isPressed = false;
// TODO: add mouse to obj acceleration
};
/**
* Handles mouse out events.
*/
Mover.mouseout = function() {
var x, y, me = this, mouse = System.mouse;
if (this.isPressed) {
this.isMouseOut = true;
this.mouseOutInterval = setInterval(function () { // if mouse is too fast for block update, update via an interval until it catches up
if (me.isPressed && me.isMouseOut) {
x = mouse.location.x - me.world.el.offsetLeft;
y = mouse.location.y - me.world.el.offsetTop;
me.location = new Vector(x, y);
}
}, 16);
}
};
Mover.prototype.step = function() {
var i, max, x = this.location.x,
y = this.location.y;
this.beforeStep.call(this);
if (this.isStatic || this.isPressed) {
return;
}
// start apply forces
if (this.world.c) { // friction
this._friction.x = this.velocity.x;
this._friction.y = this.velocity.y;
this._friction.mult(-1);
this._friction.normalize();
this._friction.mult(this.world.c);
this.applyForce(this._friction);
}
this.applyForce(this.world.gravity); // gravity
// attractors
var attractors = System.getAllItemsByName('Attractor');
for (i = 0, max = attractors.length; i < max; i += 1) {
if (this.id !== attractors[i].id) {
this.applyForce(attractors[i].attract(this));
}
}
// repellers
var repellers = System.getAllItemsByName('Repeller');
for (i = 0, max = repellers.length; i < max; i += 1) {
if (this.id !== repellers[i].id) {
this.applyForce(repellers[i].attract(this));
}
}
// draggers
var draggers = System.getAllItemsByName('Dragger');
for (i = 0, max = draggers.length; i < max; i += 1) {
if (this.id !== draggers[i].id && Utils.isInside(this, draggers[i])) {
this.applyForce(draggers[i].drag(this));
}
}
if (this.applyAdditionalForces) {
this.applyAdditionalForces.call(this);
}
this.velocity.add(this.acceleration); // add acceleration
this.velocity.limit(this.maxSpeed, this.minSpeed);
this.location.add(this.velocity); // add velocity
if (this.pointToDirection) { // object rotates toward direction
if (this.velocity.mag() > 0.1) {
this.angle = Utils.radiansToDegrees(Math.atan2(this.velocity.y, this.velocity.x));
}
}
if (this.wrapWorldEdges) {
this._wrapWorldEdges();
} else if (this.checkWorldEdges) {
this._checkWorldEdges();
}
if (this.controlCamera) {
this._checkCameraEdges(x, y, this.location.x, this.location.y);
}
if (this.parent) { // parenting
if (this.offsetDistance) {
r = this.offsetDistance; // use angle to calculate x, y
theta = Utils.degreesToRadians(this.parent.angle + this.offsetAngle);
x = r * Math.cos(theta);
y = r * Math.sin(theta);
this.location.x = this.parent.location.x;
this.location.y = this.parent.location.y;
this.location.add(new Vector(x, y)); // position the child
if (this.pointToParentDirection) {
this.angle = Utils.radiansToDegrees(Math.atan2(this.parent.velocity.y, this.parent.velocity.x));
}
} else {
this.location.x = this.parent.location.x;
this.location.y = this.parent.location.y;
}
}
this.acceleration.mult(0);
if (this.life < this.lifespan) {
this.life += 1;
} else if (this.lifespan !== -1) {
System.remove(this);
return;
}
this.afterStep.call(this);
};
/**
* Updates the corresponding DOM element's style property.
* @function draw
* @memberof Mover
*/
Mover.prototype.draw = function() {
var cssText = this.getCSSText({
x: this.location.x - (this.width / 2),
y: this.location.y - (this.height / 2),
angle: this.angle,
scale: this.scale || 1,
width: this.width,
height: this.height,
colorMode: this.colorMode,
color0: this.color[0],
color1: this.color[1],
color2: this.color[2],
opacity: this.opacity,
zIndex: this.zIndex,
visibility: this.visibility,
borderRadius: this.borderRadius,
borderWidth: this.borderWidth,
borderStyle: this.borderStyle,
borderColor0: this.borderColor[0],
borderColor1: this.borderColor[1],
borderColor2: this.borderColor[2]
});
this.el.style.cssText = cssText;
};
/**
* Concatenates a new cssText string.
*
* @function getCSSText
* @memberof Mover
* @param {Object} props A map of object properties.
* @returns {string} A string representing cssText.
*/
Mover.prototype.getCSSText = function(props) {
return Item._stylePosition.replace(/<x>/g, props.x).replace(/<y>/g, props.y).replace(/<angle>/g, props.angle).replace(/<scale>/g, props.scale) + 'width: ' +
props.width + 'px; height: ' + props.height + 'px; background-color: ' +
props.colorMode + '(' + props.color0 + ', ' + props.color1 + (props.colorMode === 'hsl' ? '%' : '') + ', ' + props.color2 + (props.colorMode === 'hsl' ? '%' : '') +'); opacity: ' + props.opacity + '; z-index: ' + props.zIndex + '; visibility: ' + props.visibility + '; border: ' +
props.borderWidth + 'px ' + props.borderStyle + ' ' + props.colorMode + '(' + props.borderColor0 + ', ' + props.borderColor1 + (props.colorMode === 'hsl' ? '%' : '') + ', ' + props.borderColor2 + (props.colorMode === 'hsl' ? '%' : '') + '); border-radius: ' +
props.borderRadius + '%;';
};
module.exports = Mover;