/*!
* Copyright 2014, Christophe Rosset (Topheman)
* http://labs.topheman.com/
* http://twitter.com/topheman
*
* @requires BabylonJS - https://github.com/BabylonJS/Babylon.js
*/
(function(ConeExport) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(ConeExport);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = ConeExport();
} else {
// Browser globals
window.Cone = ConeExport();
}
})(function() {
"use strict";
/**
* @module Cone
*/
//Constants
var CONE_CYLINDER_TOP_DIAMETER = 2;
var CONE_CYLINDER_BOTTOM_DIAMETER = 5;
var CONE_CYLINDER_HEIGHT = 5;
var PARENT_EYES_ORIGINAL_SCALING_Y = 1.5;
var PARENT_EYES_ORIGINAL_POSITION_X = 1;
var PARENT_EYES_ORIGINAL_POSITION_Y = 3.5;
var DEFAULT_FOLLOW_STEP_PRECISION = 0.5;
/**
* Manage 3d Cone with a simple API
*
* Based on BabylonJS
* @class Cone
* @constructor
* @param {BABYLON.Scene} scene
* @param {Object} [options]
* @param {string} [options.name] Name of the cone instance
* @param {number} [options.tessellation=30] Number of faces of the cylinder
* @param {number} [options.moveStep=0.1] How much the cone will move at each move call
* @param {number} [options.turnStep=0.1] How much the cone will turn at each turn call (in rad)
* @param {number} [options.eyeSize=1.5] Eye size
* @param {string|Object} [options.color] Color of the cone's sylinder (default : #900000) - can be in RGB or HEXA
* @param {Boolean} [options.pickable=true] Defines if the cone is pickable
* @param {mixed} [options.$customOption] Pass any custom option with a parameter's name starting by $
* @return {Cone}
*/
var Cone = function(scene, options) {
options = typeof options === 'object' ? options : {};
options.tessellation = typeof options.tessellation === 'undefined' ? 30 : options.tessellation;
/**
* Original sizes
* @private
* @property {Object} _size
* @property {number} _size.topDiameter
* @property {number} _size.bottomDiameter
* @property {number} _size.height
*/
this._size = {
topDiameter : CONE_CYLINDER_TOP_DIAMETER,
bottomDiameter : CONE_CYLINDER_BOTTOM_DIAMETER,
height : CONE_CYLINDER_HEIGHT
};
//single name
this.name = typeof options.name !== 'undefined' ? options.name : "cone"+(new Date()).getTime();
//default settings
this.moveStep = typeof options.moveStep !== 'undefined' ? options.moveStep : 0.1;
this.turnStep = typeof options.turn !== 'undefined' ? options.turnStep : 0.1;
this.eyeSize = typeof options.eyeSize !== 'undefined' ? options.eyeSize : 1.5;
this.color = typeof options.color !== 'undefined' ? ( isRgb(options.color) ? options.color : hexToRgb(options.color) ) : {r: 0.564, g: 0, b: 0};//#900000
options.pickable = typeof options.pickable === 'undefined' ? true : options.pickable;
//parent mesh to group all the others
var parentMesh = BABYLON.Mesh.CreatePlane(this.name + "-group", 1, scene);
parentMesh.isVisible = false;
parentMesh.isPickable = false;
//create + link + reposition the cylinder inside the group
this.cylinder = BABYLON.Mesh.CreateCylinder(this.name + "-group-cylinder", CONE_CYLINDER_HEIGHT, CONE_CYLINDER_BOTTOM_DIAMETER, CONE_CYLINDER_TOP_DIAMETER, options.tessellation, scene);
var pivot = BABYLON.Matrix.Translation(0, CONE_CYLINDER_HEIGHT / 2, 0);
this.cylinder.setPivotMatrix(pivot);
this.cylinder.parent = parentMesh;
this.cylinder.setPositionWithLocalVector(new BABYLON.Vector3(0, 0, 0));
this.cylinder.isPickable = options.pickable;
//create a parent mesh for the eyes + link it to the global parent mesh + reposition and scale
this.parentEyes = BABYLON.Mesh.CreatePlane(this.name + "-group-eyesGroup", 1, scene);
this.parentEyes.parent = parentMesh;
this.parentEyes.isVisible = false;
this.parentEyes.setPositionWithLocalVector(new BABYLON.Vector3(PARENT_EYES_ORIGINAL_POSITION_X, PARENT_EYES_ORIGINAL_POSITION_Y, 0));
this.parentEyes.scaling.y = PARENT_EYES_ORIGINAL_SCALING_Y;
this.parentEyes.isPickable = false;
//create eyes + link them to the parentEyes mesh
this.leftEye = BABYLON.Mesh.CreateSphere(this.name + "-group-eyesGroup-left-eye", 10.0, this.eyeSize, scene);//Parameters are: name, number of segments (highly detailed or not), size, scene to attach the mesh. Beware to adapt the number of segments to the size of your mesh ;)
this.leftEye.parent = this.parentEyes;
this.leftEye.setPositionWithLocalVector(new BABYLON.Vector3(0, 0, 0.7));
this.leftEye.isPickable = options.pickable;
this.rightEye = BABYLON.Mesh.CreateSphere(this.name + "-group-eyesGroup-right-eye", 10.0, this.eyeSize, scene);//Parameters are: name, number of segments (highly detailed or not), size, scene to attach the mesh. Beware to adapt the number of segments to the size of your mesh ;)
this.rightEye.parent = this.parentEyes;
this.rightEye.setPositionWithLocalVector(new BABYLON.Vector3(0, 0, -0.7));
this.rightEye.isPickable = options.pickable;
//add texture to the cylinder
this.cylinder.material = new BABYLON.StandardMaterial(this.name + "-texture-cyclinder", scene);
this.cylinder.material.diffuseColor = new BABYLON.Color3(this.color.r, this.color.g, this.color.b);
//add texture to the eyes
this.leftEye.material = new BABYLON.StandardMaterial(this.name + "-material-leftEye", scene);
this.rightEye.material = new BABYLON.StandardMaterial(this.name + "-material-rightEye", scene);
this.leftEye.material.diffuseTexture = new BABYLON.Texture("./assets/Cone/eye-light.png", scene);
this.rightEye.material.diffuseTexture = new BABYLON.Texture("./assets/Cone/eye-light.png", scene);
this.rightEye.material.diffuseTexture.vOffset = -0.245;
this.rightEye.material.diffuseTexture.uOffset = 0;
this.leftEye.material.diffuseTexture.vOffset = -0.245;
this.leftEye.material.diffuseTexture.uOffset = 0;
//states
this.bumping = false;
this.widenningEyes = false;
this.eyesWiden = false;
this.alphaAnimatingCylinder = false;
this.alphaAnimatingLeftEye = false;
this.alphaAnimatingRightEye = false;
//emulate getter
//emulate getters setters on position babylonjs style
/**
* x postion of the cone in the space
* @property position.x
* @type number
*/
/**
* y postion of the cone in the space
* @property position.y
* @type number
*/
/**
* z postion of the cone in the space
* @property position.z
* @type number
*/
var position = {};
Object.defineProperties(position, {
'x': {
get: function() {
return parentMesh.position.x;
},
set: function(x) {
return parentMesh.position.x = x;
}
},
'y': {
get: function() {
return parentMesh.position.y;
},
set: function(y) {
return parentMesh.position.y = y;
}
},
'z': {
get: function() {
return parentMesh.position.z;
},
set: function(z) {
return parentMesh.position.z = z;
}
}
});
this.position = position;
//emulate getters setters on rotation babylonjs style
/**
* x rotation of the cone in the space
* @property rotation.x
* @type number
*/
/**
* y rotation of the cone in the space
* @property rotation.y
* @type number
*/
/**
* z rotation of the cone in the space
* @property rotation.z
* @type number
*/
var rotation = {};
Object.defineProperties(rotation, {
'x': {
get: function() {
return parentMesh.rotation.x;
},
set: function(x) {
return parentMesh.rotation.x = x;
}
},
'y': {
get: function() {
return parentMesh.rotation.y;
},
set: function(y) {
return parentMesh.rotation.y = y;
}
},
'z': {
get: function() {
return parentMesh.rotation.z;
},
set: function(z) {
return parentMesh.rotation.z = z;
}
}
});
this.rotation = rotation;
//emulate getters setters on rotation babylonjs style
/**
* x scaling of the cone
* @property scaling.x
* @type number
*/
/**
* y scaling of the cone
* @property scaling.y
* @type number
*/
/**
* z scaling of the cone
* @property scaling.z
* @type number
*/
var scaling = {};
Object.defineProperties(scaling, {
'x': {
get: function() {
return parentMesh.scaling.x;
},
set: function(x) {
return parentMesh.scaling.x = x;
}
},
'y': {
get: function() {
return parentMesh.scaling.y;
},
set: function(y) {
return parentMesh.scaling.y = y;
}
},
'z': {
get: function() {
return parentMesh.scaling.z;
},
set: function(z) {
return parentMesh.scaling.z = z;
}
}
});
this.scaling = scaling;
this.getMainMesh = function() {
return parentMesh;
};
//customizable animations are added/removed on the fly
this._coneTailing = false;
this._coneTailedBy = false;
this._queue = {
fx : [],
move : []
};
//used for .then() to know on which queue add the callback
this._lastQueueNameCalled = 'fx';
this._currentMoveBeforeRenderLoopCallback = null;
//allow the user to pass its own attributes starting by $ at the init
for(var optionName in options){
if(/^\$/.test(optionName) === true){
this[optionName] = options[optionName];
}
}
};
//Instance methode shared on the prototype
Cone.fn = Cone.prototype = {
/**
* Launches the next callback in the queue then removes it from the queue
* @method dequeue
* @param {string} queueName
* @return {Cone}
* @chainable
*/
dequeue: function(queueName){
var next = function(){}, that = this;
if(typeof this._queue[queueName] === 'undefined'){
throw new Error('No queue "'+queueName+'" found');
}
setTimeout(function(){
if(that._queue[queueName].length > 0){
if(that._queue[queueName].length > 1){
next = (function(queueName){
return function(){
that.dequeue(queueName);
};
})(queueName);
}
that._queue[queueName][0].call({},next,that);
}
if(that._queue[queueName].length > 0){
that._queue[queueName].shift();
}
},0);
return this;
},
/**
* * Call it only with the queueName : **Returns the queue**.
* * Call it with queueName + callback : registers the callback in the queue. This
* callback has a "next" parameter to launch the next callback in the queue. **Returns the cone to chain**.
* * Call it with queueName + array of callback to replace the queue. **Returns the cone to chain**
*
* @method queue
* @param {string} queueName
* @param {function|Array<function>} [callback] use the next param like : `function(next, currentCone){ myCone.fadeOut().delay(1000).then(next); }`
* @return {Cone|Array<function>}
* @chainable
*/
queue: function(queueName, callback){
var result;
if(typeof this._queue[queueName] === 'undefined'){
if(typeof callback === 'undefined'){
if(this.warnings === true){
console.warn('queue "'+queueName+'" is not registered');
}
}
result = this._queue[queueName] = [];
}
if(typeof callback === 'function'){
this._queue[queueName].push(callback);
if(this._queue[queueName].length === 1){
this.dequeue(queueName);
}
this._lastQueueNameCalled = queueName;
result = this;
}
else if(callback instanceof Array){
this._queue[queueName] = callback;
this._lastQueueNameCalled = queueName;
result = this._queue[queueName];
}
else{
result = this._queue[queueName];
}
return result;
},
/**
* Adds callback to the last used queue
*
* @method then
* @param {function} callback `function(next){}`
* @return {Cone}
* @chainable
* @example ```js
* var myCone = new Cone(scene);
* myCone
* .fadeOut()
* .fadeIn()
* .delay(1000)
* .widenEyes()
* .unWidenEyes()
* .then(function(next){myCone.setColor('#900000'); next()})
* .bump();
* ```
*/
then: function(callback){
if(typeof callback !== 'function'){
throw new Error('callback must be a function');
}
return this.queue(this._lastQueueNameCalled,callback);
},
/**
* Delays the next event in the queue of "delay" ms.
*
* You can force the queue name.
*
* Can also be used without the `queueName` if you're alredy chaining on the right queue like : `myCone.fadeOut().delay(2000).fadeIn()`
*
* @method delay
* @param {string} queueName
* @param {number} delay
* @return {Cone}
* @chainable
*/
delay: function(){
var delay, queueName = null;
//case only a delay was specified
if(arguments.length === 1){
queueName = this._lastQueueNameCalled;
delay = arguments[0];
}
else if(arguments.length === 2){
queueName = arguments[0];
delay = arguments[1];
}
if(typeof delay !== 'number'){
throw new Error('delay must be a number');
}
if(queueName !== null && typeof queueName !== 'string'){
throw new Error('queueName must be a string');
}
this.queue(queueName,function(next){
setTimeout(function(){
next();
},delay);
});
return this;
},
/**
* Clears the queue
*
* @method clearQueue
* @param {string} queueName
* @return {Cone}
* @chainable
*/
clearQueue: function(queueName){
this._queue[queueName] = [];
return this;
},
/**
* Clears all the queues
*
* @method clearQueues
* @return {Cone}
* @chainable
*/
clearQueues: function(){
for(var queueName in this._queue){
this._queue[queueName] = [];
}
return this;
},
/**
* Stops all the animations on the fx queue then clears the queue
* (all other queues continue)
*
* @method flushAnimationQueue
* @return {Cone}
* @chainable
*/
flushAnimationQueue: function(){
this.stopAllAnimationsRunning();
this.clearQueue('fx');
return this;
},
/**
* Returns cone position
*
* @method getPosition
* @return {BABYLON.Vector3}
*/
getPosition:function(){
return this.getMainMesh().position;
},
/**
* Returns true if an fx animation is running
*
* @method isAnimationRunning
* @return {Boolean}
*/
isAnimationRunning: function(){
return this.isBumping() && this.isWidenningEyes() && this.isChangingAlpha();
},
/**
* Returns true if the cone is widenning eyes
*
* @method isWidenningEyes
* @return {Boolean}
*/
isWidenningEyes: function(){
return this.widenningEyes;
},
/**
* Returns true if the cone has its eyes widen
*
* @method isEyesWiden
* @return {Boolean}
*/
isEyesWiden: function(){
return this.eyesWiden;
},
/**
* @method isBumping
* @return {Boolean}
*/
isBumping: function() {
return this.bumping;
},
/**
* Returns true if alpha is animating on the cone
*
* @method isChangingAlpha
* @return {Boolean}
*/
isChangingAlpha: function(){
return this.alphaAnimatingCylinder && this.alphaAnimatingLeftEye && this.alphaAnimatingRightEye;
},
/**
* @method getMoveStep
* @return {number}
*/
getMoveStep: function() {
return this.moveStep;
},
/**
* @method getTurnStep
* @return {number}
*/
getTurnStep: function() {
return this.turnStep;
},
/**
* @method getHeight
* @return {number}
*/
getHeight: function(){
return this._size.height*this.cylinder.scaling.y*this.scaling.y;
},
/**
* @method getTopDiameter
* @return {number}
*/
getTopDiameter: function(){
return this._size.topDiameter*(this.cylinder.scaling.x > this.cylinder.scaling.z ? this.cylinder.scaling.x : this.cylinder.scaling.z)*(this.scaling.x > this.scaling.z ? this.scaling.x : this.scaling.z);
},
/**
* @method getBottomDiameter
* @return {number}
*/
getBottomDiameter: function(){
return this._size.bottomDiameter*(this.cylinder.scaling.x > this.cylinder.scaling.z ? this.cylinder.scaling.x : this.cylinder.scaling.z)*(this.scaling.x > this.scaling.z ? this.scaling.x : this.scaling.z);
},
/**
* @method getDistance
* @param {Cone} cone
* @return {number}
*/
getDistance: function(cone){
return Math.sqrt((this.position.x - cone.position.x)*(this.position.x - cone.position.x)+(this.position.z - cone.position.z)*(this.position.z - cone.position.z));
},
/**
* Checks if two cones intersect (based on the bottom diameter)
*
* If a cone has been rescaled, it's taken account (although, if scaling x and z are different the bigger one is taken in account)
*
* @method intersectsCone
* @param {Cone} cone
* @return {Boolean}
*/
intersectsCone: function(cone){
var distance = this.getDistance(cone);
if(distance < (this.getBottomDiameter() + cone.getBottomDiameter())/2){
return true;
}
return false;
},
/**
*
* @method intersectsGroundLimits
* @param {BABYLON.Mesh} ground (plane)
* @param {Boolean} replace if you wan't not only to check the limit but also keep the cone inside it
* @return {Boolean}
*/
intersectsGroundLimits: function(ground,replace){
var boundingInfos = ground.getBoundingInfo(), result = false;
if((this.position.x + this.getBottomDiameter()/2) > boundingInfos.boundingBox.maximum.x){
if(replace === true){
this.position.x = boundingInfos.boundingBox.maximum.x - this.getBottomDiameter()/2;
}
result = true;
}
if((this.position.x - this.getBottomDiameter()/2) < boundingInfos.boundingBox.minimum.x){
if(replace === true){
this.position.x = boundingInfos.boundingBox.minimum.x + this.getBottomDiameter()/2;
}
result = true;
}
if((this.position.z + this.getBottomDiameter()/2) > boundingInfos.boundingBox.maximum.y){
if(replace === true){
this.position.z = boundingInfos.boundingBox.maximum.y - this.getBottomDiameter()/2;
}
result = true;
}
if((this.position.z - this.getBottomDiameter()/2) < boundingInfos.boundingBox.minimum.y){
if(replace === true){
this.position.z = boundingInfos.boundingBox.minimum.y + this.getBottomDiameter()/2;
}
result = true;
}
return result;
},
/**
* * Attaches this cone to the one passed in parameter
* * If you try to tail a cone already followed by another, your cone will follow the last one in the tail
* * Returns the cone you end up tailing
*
* @method tail
* @param {Cone} cone
* @param {Object} [options]
* @param {number} [options.distance] By default the sum of the radiuses of the cones
* @return {Cone}
*/
tail: function(cone,options){
var fullTail;
options = typeof options === 'undefined' ? {} : options;
options.distance = typeof options.distance === 'undefined' ? (this.getBottomDiameter() + cone.getBottomDiameter())/2 : options.distance;
this._tailingOptions = options;
//if cones are already following, chose the last one in the tail
fullTail = cone.getFullTail();
if(fullTail.length > 0){
cone = fullTail[fullTail.length-1];
}
this._coneTailing = cone;
cone._coneTailedBy = this;
var thisCone = this;
this._tailingBeforeRender = function(){
if(thisCone.getDistance(cone) > options.distance){
thisCone.follow(new BABYLON.Vector3(cone.position.x,0,cone.position.z));
}
};
this.getMainMesh().getScene().registerBeforeRender(this._tailingBeforeRender);
return cone;
},
/**
* Detaches your cone, returns the cone it was tailing
*
* @method unTail
* @return {Cone}
*/
unTail: function(){
var cone = this._coneTailing;
cone._coneTailedBy = false;
this._coneTailing = false;
this.getMainMesh().getScene().unregisterBeforeRender(this._tailingBeforeRender);
return cone;
},
/**
* Returns the cone this cone is tailing (or false if none)
*
* @method tailingCone
* @returns {Boolean|Cone}
*/
tailingCone: function(){
return this._coneTailing;
},
/**
* Returns the cone this cone is tailed by (or false if none)
*
* @method tailedCone
* @returns {Boolean|Cone}
*/
tailedCone: function(){
return this._coneTailedBy;
},
/**
* Returns true if this cone is n a tail in a way or an other
*
* @method isTailRelated
* @return {Boolean}
*/
isInTail: function(){
if(this._coneTailing !== false || this._coneTailedBy !== false){
return true;
}
else{
return false;
}
},
/**
* Returns a Cone.List of the cones tailing this one
*
* @method getFullTail
* @return {Cone.List}
*/
getFullTail: function(){
var fullTail = new Cone.List(), reccursiveTailingConesDiscovery;
reccursiveTailingConesDiscovery = function(cone){
var tailingCone = cone.tailedCone();
if(tailingCone !== false){
fullTail.push(tailingCone);
reccursiveTailingConesDiscovery(tailingCone);
}
};
reccursiveTailingConesDiscovery(this);
return fullTail;
}
};
var stateFullMethods = {
/**
* Registers the cone to a BABYLON.ShadowGenerator to be able to render shadows on the shadow map
*
* @method registerToShadowGenerator
* @param {BABYLON.ShadowGenerator} shadowGenerator
* @return {Cone}
* @chainable
*/
registerToShadowGenerator: function(shadowGenerator) {
var renderList = shadowGenerator.getShadowMap().renderList;
renderList.push(this.cylinder);
renderList.push(this.leftEye);
renderList.push(this.rightEye);
return this;
},
/**
* Squints the eyes of one step
* Returns true if the eyes are not all squinted
* Returns false if they are and stop squint
*
* @method squint
* @return {Boolean}
*/
squint: function() {
if (this.rightEye.material.diffuseTexture.uOffset < 0.08) {
this.leftEye.material.diffuseTexture.vOffset += 0.005;
this.rightEye.material.diffuseTexture.vOffset -= 0.005;
this.leftEye.material.diffuseTexture.uOffset += 0.003;
this.rightEye.material.diffuseTexture.uOffset += 0.003;
return true;
}
return false;
},
/**
* Unsquints the eyes of one step
* Returns true if the eyes are not all unsquinted
* Returns false if they are and stop squint
*
* @method unSquint
* @return {Boolean}
*/
unSquint: function() {
if (this.rightEye.material.diffuseTexture.uOffset > 0) {
this.leftEye.material.diffuseTexture.vOffset -= 0.005;
this.rightEye.material.diffuseTexture.vOffset += 0.005;
this.leftEye.material.diffuseTexture.uOffset -= 0.003;
this.rightEye.material.diffuseTexture.uOffset -= 0.003;
return true;
}
return false;
},
/**
* Sets the color of the cylinder
* Accepts hexa or rgb color
*
* @method setColor
* @param {string|object} color
* @return {Cone}
* @chainable
*/
setColor: function(color){
if(isRgb(color) === false){
color = hexToRgb(color);
}
this.cylinder.material.diffuseColor = new BABYLON.Color3(color.r, color.g, color.b);
return this;
},
/**
* Sets the scale on all the cone
*
* @method setScale
* @param {number} scale
* @returns {Cone}
* @chainable
*/
setScale: function(scale){
this.getMainMesh().scaling.x = scale;
this.getMainMesh().scaling.y = scale;
this.getMainMesh().scaling.z = scale;
return this;
},
/**
* Sets the alpha on all the cone
*
* @method setAlpha
* @param {number} alpha
* @return {Cone}
* @chainable
*/
setAlpha: function(alpha){
this.cylinder.material.alpha = alpha;
this.leftEye.material.alpha = alpha;
this.rightEye.material.alpha = alpha;
return this;
},
/**
* Moves the cone forward of one moveStep
*
* @method moveForward
* @return {Cone}
* @chainable
*/
moveForward: function() {
this.getMainMesh().translate(BABYLON.Axis.X, this.moveStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Moves the cone backwards of one moveStep
*
* @method moveBack
* @return {Cone}
* @chainable
*/
moveBack: function() {
this.getMainMesh().translate(BABYLON.Axis.X, -this.moveStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Moves the cone left of one moveStep
*
* @method moveLeft
* @return {Cone}
* @chainable
*/
moveLeft: function() {
this.getMainMesh().translate(BABYLON.Axis.Z, this.moveStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Moves the cone right of one moveStep
*
* @method moveRight
* @return {Cone}
* @chainable
*/
moveRight: function() {
this.getMainMesh().translate(BABYLON.Axis.Z, -this.moveStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Turns the cone left of one turnStep
*
* @method turnLeft
* @return {Cone}
* @chainable
*/
turnLeft: function() {
this.getMainMesh().rotate(BABYLON.Axis.Y, -this.turnStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Turns the cone right of one turnStep
*
* @method turnRight
* @return {Cone}
* @chainable
*/
turnRight: function() {
this.getMainMesh().rotate(BABYLON.Axis.Y, this.turnStep, BABYLON.Space.LOCAL);
return this;
},
/**
* Stops all the fx animations
*
* @method stopAllAnimationsRunning
* @return {Cone}
* @chainable
*/
stopAllAnimationsRunning: function(){
if(this.isBumping()){
this.stopBump();
}
if(this.isWidenningEyes()){
this.stopWidenEyes();
}
if(this.isChangingAlpha()){
this.stopAnimateAlpha();
}
removeAllAnimations(this);
return this;
},
/**
* Stops eyes fx animation
*
* @method stopWidenEyes
* @return {Cone}
* @chainable
*/
stopWidenEyes: function(){
this.parentEyes.getScene().stopAnimation(this.parentEyes);
removeWidenEyesAnimation(this);
this.resetWidenEyes();
return this;
},
/**
* Reset eyes to orginal scale and position
*
* @method resetWidenEyes
* @return {Cone}
* @chainable
*/
resetWidenEyes: function(){
this.parentEyes.scaling.y = PARENT_EYES_ORIGINAL_SCALING_Y;
this.parentEyes.position.x = PARENT_EYES_ORIGINAL_POSITION_X;
this.parentEyes.position.y = PARENT_EYES_ORIGINAL_POSITION_Y;
this.widenningEyes = false;
return this;
},
/**
* Stops cylinder fx animation
*
* @method stopBump
* @return {Cone}
* @chainable
*/
stopBump: function() {
this.getMainMesh().getScene().stopAnimation(this.getMainMesh());
this.resetBump();
removeBumpAnimation(this);
return this;
},
/**
* Reset cylinder to orginal scale and position
*
* @method resetBump
* @return {Cone}
* @chainable
*/
resetBump: function(){
this.getMainMesh().scaling.y = 1;
this.bumping = false;
return this;
},
/**
* @method toggleBump
* @param {Object} options
* @return {Cone}
* @chainable
*/
toggleBump: function(options) {
if (this.isBumping()) {
this.stopBump();
}
else {
this.bump(options);
}
return this;
},
/**
* @method stopAnimateAlpha
* @return {Cone}
* @chainable
*/
stopAnimateAlpha: function(){
this.cylinder.getScene().stopAnimation(this.cylinder);
this.leftEye.getScene().stopAnimation(this.leftEye);
this.rightEye.getScene().stopAnimation(this.rightEye);
removeAlphaAnimation(this);
return this;
},
/**
* @method setMoveStep
* @param {number} moveStep
* @return {Cone}
* @chainable
*/
setMoveStep: function(moveStep) {
this.moveStep = moveStep;
return this;
},
/**
* @method setTurnStep
* @param {number} turnStep
* @return {Cone}
* @chainable
*/
setTurnStep: function(turnStep) {
this.turnStep = turnStep;
return this;
},
/**
* @method lookAt
* @param {BABYLON.Vector3|Cone} point
* @return {Cone}
* @chainable
*/
lookAt: function(point){
if(point instanceof Cone){
point = new BABYLON.Vector3(point.getPosition().x,point.getPosition().y,point.getPosition().z);
}
point.y = this.getMainMesh().position.y;
this.getMainMesh().lookAt(point,Math.PI/2);
return this;
},
/**
* Moves the cone towards "point" of one moveStep
*
* @method follow
* @param {BABYLON.Vector3|Cone} point
* @param {function}[callback] callback executed when the cone gets to point `function(point){}`
* @return {Cone}
* @chainable
*/
follow: function(point,callback){
if(point instanceof Cone){
point = new BABYLON.Vector3(point.getPosition().x,point.getPosition().y,point.getPosition().z);
}
if(point && point.subtract(this.getPosition()).length() > DEFAULT_FOLLOW_STEP_PRECISION){
this.lookAt(point);
this.moveForward();
}
else{
this.position.x = point.x;
this.position.y = point.y;
this.position.z = point.z;
if(typeof callback === 'function'){
callback.call({},point);
}
}
return this;
}
};
/**
* This method is used internally to expose methods that are used both on Cone and Cone.List
*
* You will use it when you make your own plugins to expose your own methods
*
* @method addMethods
* @param {Object} methodList List of the methods to add to the cone list prototype
* @returns {undefined}
* @static
*
* @example
* ```js
* //at the end of your file
* Cone.addMethods(stateFullMethods);
* Cone.List.addMethods(stateFullMethods);
* ```
*/
Cone.addMethods = function(methodList){
for(var methodName in methodList){
if(!Cone.fn[methodName]){
Cone.fn[methodName] = methodList[methodName];
}
else{
console.warn('method '+methodName+' already registered on Cone');
}
}
};
//Those methods are added to the Cone.prototype below
var animationMethods = {
'fx': {
/**
* Widens the eyes of the cone (and more)
*
* Can also be run via the `.animate()` dispatcher
*
* @method widenEyes
* @param {Object} [options]
* @param {number} [options.speed=5]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @param {boolean} [options.full=false] If true, runs the full animation (openning and closing eyes)
* @param {boolean} [options.close=false] If true, runs only the close eyes part of the animation (by default, only opens eyes)
* @return {Cone}
* @chainable
*/
widenEyes: function(options){
var from, to, endState, eyesWidenState;
options = typeof options === 'undefined' ? {} : options;
options.speed = (typeof options.speed === 'undefined' || options.speed === 0) ? 5 : options.speed;
options.loop = (typeof options.loop === 'undefined') ? false : options.loop;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
if(options.loop === true && options.callback !== null){
console.warn("Can't apply callback on looped animation");
}
if(options.close === true){
from = 50;
to = 100;
endState = false;
eyesWidenState = false;
}
else if(options.full === true){
from = 0;
to = 100;
endState = false;
eyesWidenState = false;
}
else{
from = 0;
to = 50;
endState = true;
eyesWidenState = true;
}
if(options.break === true){
this.flushAnimationQueue();
}
this.queue('fx',(function(that){
return function(){
//to avoid collision between animations @todo animation queue
that.stopAllAnimationsRunning();
addWidenEyesAnimation(that);
that.widenningEyes = true;
that.eyesWiden = false;
that.parentEyes.getScene().beginAnimation(that.parentEyes, from, to, typeof options.loop === 'number' ? false : options.loop, options.speed,function(){
that.widenningEyes = endState;
that.eyesWiden = eyesWidenState;
removeWidenEyesAnimation(that);
setTimeout(function(){
if(options.callback !== null){
options.callback.call({},that);
}
that.dequeue('fx');
},options.delay);
});
};
})(this));
return this;
},
/**
* Unwidens the eyes of the cone (and more)
*
* Can also be run via the `.animate()` dispatcher
*
* @method unWidenEyes
* @param {Object} [options]
* @param {number} [options.speed=5]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @param {boolean} [options.full=false] If true, runs the full animation (openning and closing eyes)
* @param {boolean} [options.close=true] If true, runs only the close eyes part of the animation (by default, does that on unWidenEyes)
* @return {Cone}
* @chainable
*/
unWidenEyes: function(options){
options = typeof options === 'undefined' ? {} : options;
options.close = true;
return this.widenEyes(options);
},
/**
* Bumps the cone
*
* Can also be run via the `.animate()` dispatcher
*
* @method bump
* @param {Object} [options]
* @param {number} [options.scale=1.2]
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @return {Cone}
* @chainable
*/
bump: function(options) {
options = typeof options === 'undefined' ? {} : options;
options.scale = (typeof options.scale === 'undefined' || options.scale === 0) ? 1.2 : options.scale;
options.speed = (typeof options.speed === 'undefined' || options.speed === 0) ? 3 : options.speed;
options.loop = (typeof options.loop === 'undefined') ? false : options.loop;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
if(options.loop === true && options.callback !== null){
console.warn("Can't apply callback on looped animation");
}
if(options.break === true){
this.flushAnimationQueue();
}
this.queue('fx',(function(that){
return function(){
//to avoid collision between animations @todo animation queue
that.stopAllAnimationsRunning();
addBumpAnimation(that,options.scale);
that.getMainMesh().getScene().beginAnimation(that.getMainMesh(), 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function() {
that.resetBump();
removeBumpAnimation(that);
setTimeout(function(){
if(options.callback !== null){
options.callback.call({},that);
}
that.dequeue('fx');
},options.delay);
});
that.bumping = true;
};
})(this));
return this;
},
/**
* Animates the alpha of the cone
*
* Can also be run via the `.animate()` dispatcher
*
* @method animateAlpha
* @param {Object} [options]
* @param {number} [options.alpha=0]
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @param {boolean} [options.cylinder=true] True by default, if false, won't be affected
* @param {boolean} [options.leftEye=true] True by default, if false, won't be affected
* @param {boolean} [options.rightEye=true] True by default, if false, won't be affected
* @return {Cone}
* @chainable
*/
animateAlpha: function(options){
options = typeof options === 'undefined' ? {} : options;
options.alpha = typeof options.alpha === 'undefined' ? 0 : options.alpha;
options.speed = (typeof options.speed === 'undefined' || options.speed === 0) ? 3 : options.speed;
options.loop = typeof options.loop === 'undefined' ? false : options.loop;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
options.cylinder = typeof options.cylinder === 'undefined' ? true : options.cylinder;
options.leftEye = typeof options.leftEye === 'undefined' ? true : options.leftEye;
options.rightEye = typeof options.rightEye === 'undefined' ? true : options.rightEye;
if(options.loop === true && options.callback !== null){
console.warn("Can't apply callback on looped animation");
}
if(options.break === true){
this.flushAnimationQueue();
}
this.queue('fx',(function(that){
return function(){
//to avoid collision between animations @todo animation queue
that.stopAllAnimationsRunning();
addAlphaAnimation(that,options);
var callback = function(cone){
if(cone.isChangingAlpha() === true){
removeAlphaAnimation(cone);
setTimeout(function(){
if(options.callback !== null){
options.callback.call({},that);
}
that.dequeue('fx');
},options.delay);
}
that.alphaAnimatingCylinder = false;
that.alphaAnimatingLeftEye = false;
that.alphaAnimatingRightEye = false;
};
if(options.cylinder === true){
that.alphaAnimatingCylinder = true;
that.cylinder.getScene().beginAnimation(that.cylinder, 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function(){
callback(that);
});
}
if(options.leftEye === true){
that.alphaAnimatingLeftEye = true;
that.leftEye.getScene().beginAnimation(that.leftEye, 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function(){
callback(that);
});
}
if(options.rightEye === true){
that.alphaAnimatingRightEye = true;
that.rightEye.getScene().beginAnimation(that.rightEye, 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function(){
callback(that);
});
}
};
})(this));
return this;
},
/**
* Animates the scale of the cone
*
* Can also be run via the `.animate()` dispatcher
*
* @method animateScale
* @param {Object} [options]
* @param {number} [options.scale=1]
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @return {Cone}
* @chainable
*/
animateScale: function(options){
options = typeof options === 'undefined' ? {} : options;
options.scale = typeof options.scale === 'undefined' ? 1 : options.scale;
options.speed = (typeof options.speed === 'undefined' || options.speed === 0) ? 3 : options.speed;
options.loop = typeof options.loop === 'undefined' ? false : options.loop;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
if(options.loop === true && options.callback !== null){
console.warn("Can't apply callback on looped animation");
}
if(options.break === true){
this.flushAnimationQueue();
}
this.queue('fx',(function(that){
return function (){
//to avoid collision between animations @todo animation queue
that.stopAllAnimationsRunning();
addScaleAnimation(that,options);
that.getMainMesh().getScene().beginAnimation(that.getMainMesh(), 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function() {
setTimeout(function(){
if(options.callback !== null){
options.callback.call({},that);
}
that.dequeue('fx');
},options.delay);
});
};
})(this));
return this;
},
/**
* Animates the color of the cylinder of the cone
*
* Can also be run via the `.animate()` dispatcher
*
* **Does not work for the moment**
*
* @todo Does not work for the moment
* @method animateColor
* @param {Object} options
* @param {number} options.color
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @return {Cone}
* @chainable
* @deprecated
*/
animateColor: function(options){
console.warn('animateColor : this feature has a bug I could fixed : the color changes but is not animated, anyway, your callback is called at the end of the animation');
options = typeof options === 'undefined' ? {} : options;
if(typeof options.color === 'undefined'){
throw new Error('options.color mandatory');
}
if(isRgb(options.color) === false){
options.color = hexToRgb(options.color);
}
options.color = new BABYLON.Color3(options.color.r, options.color.g, options.color.b);
options.speed = (typeof options.speed === 'undefined' || options.speed === 0) ? 3 : options.speed;
options.loop = typeof options.loop === 'undefined' ? false : options.loop;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
if(options.loop === true && options.callback !== null){
console.warn("Can't apply callback on looped animation");
}
if(options.break === true){
this.flushAnimationQueue();
}
this.queue('fx',(function(that){
return function (){
//to avoid collision between animations @todo animation queue
that.stopAllAnimationsRunning();
addColorAnimation(that,options);
that.cylinder.getScene().beginAnimation(that.cylinder, 0, 100, typeof options.loop === 'number' ? false : options.loop, options.speed, function() {
removeColorAnimation(that);
setTimeout(function(){
if(options.callback !== null){
options.callback.call({},that);
}
that.dequeue('fx');
},options.delay);
});
};
})(this));
return this;
},
/**
* Shortcut for animateAlpha(), from the current alpha to alpha=1
*
* Can also be run via the `.animate()` dispatcher
*
* @method fadeIn
* @param {Object} [options]
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @param {boolean} [options.cylinder=true] True by default, if false, won't be affected
* @param {boolean} [options.leftEye=true] True by default, if false, won't be affected
* @param {boolean} [options.rightEye=true] True by default, if false, won't be affected
* @return {Cone}
* @chainable
*/
fadeIn: function(options){
options = typeof options === 'undefined' ? {} : options;
options.alpha = 1;
return this.animateAlpha(options);
},
/**
* Shortcut for animateAlpha(), from the current alpha to alpha=0
*
* Can also be run via the `.animate()` dispatcher
*
* @method fadeOut
* @param {Object} [options]
* @param {number} [options.speed=3]
* @param {boolean|number} [options.loop=false] 3 possibilities :
* * `true` will loop the animation until you stop it
* * `false` will play the animation only once
* * a number will play the animation a number of times
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one
* @param {boolean} [options.cylinder=true] True by default, if false, won't be affected
* @param {boolean} [options.leftEye=true] True by default, if false, won't be affected
* @param {boolean} [options.rightEye=true] True by default, if false, won't be affected
* @return {Cone}
* @chainable
*/
fadeOut: function(options){
options = typeof options === 'undefined' ? {} : options;
options.alpha = 0;
return this.animateAlpha(options);
}
},
'move':{
/**
* Let's your cone move to a position.
*
* Can also be run via the `.animate()` dispatcher
*
* @method moveTo
* @param {Object} [options]
* @param {Cone|BABYLON.Vector3|Object} options.position
* @param {function} [options.callback=null] will run your callback at the end of the animation : `function(cone){}`
* @param {number} [options.delay=0] Delay between the end of the animation and the execution of the callback (and potentionally the next animation in the queue)
* @param {boolean} [options.break=false] If true cancels all animations in the queue before running this one (`.moveTo()` is on the `move` queue, not the `fx` queue as the other animations, so you can run them in parallel of moveTo)
* @return {Cone}
* @chainable
*/
moveTo: function(options){
options = typeof options === 'undefined' ? {} : options;
options.callback = (typeof options.callback !== 'function') ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
options.break = (typeof options.break === 'undefined') ? false : options.break;
if(typeof options.position === 'undefined'){
throw new Error('options.position mandatory. accepts Cone, BABYLON.Vector3, {x,y,z}, {x,z}');
}
else if(options.position instanceof Cone){
options.position = new BABYLON.Vector3(options.position.getPosition().x,options.position.getPosition().y,options.position.getPosition().z);
}
if( (options.position instanceof BABYLON.Vector3 || typeof options.position === 'object') && typeof options.position.x !== 'undefined' && typeof options.position.z !== 'undefined'){
options.position.y = typeof options.position.y === 'undefined' ? this.getPosition().y : options.position.y;
options.position = new BABYLON.Vector3(options.position.x,options.position.y,options.position.z);
}
this.queue('move',(function(that){
var currentMoveBeforeRenderLoopCallback = function(){
that.follow(options.position,function(){
that.getMainMesh().getScene().unregisterBeforeRender(currentMoveBeforeRenderLoopCallback);
that.dequeue('move');
});
};
return function(){
that.getMainMesh().getScene().registerBeforeRender(currentMoveBeforeRenderLoopCallback);
};
})(this));
return this;
}
}
};
/**
* Run any animate methods such as :
*
* * animateAlpha
* * animateScale
* * bump
* * fadeIn
* * fadeOut
* * unWidenEyes
* * widenEyes
* * moveTo
*
* Just specify it in `options.method`. Those methods are also accessible directly via shorcuts on the {{#crossLink "Cone"}}Cone{{/crossLink}} instance.
*
* @method animate
* @param {Object} options
* @param {String} options.method
* @return {Cone}
* @chainable
*
* @example ```js
* //you can use the .animate() dispatcher as well as the shortcuts, directly on a cone instance :
* var myCone = new Cone(scene);
* myCone
* .fadeOut()
* .fadeIn()
* .delay(1000)
* .widenEyes()
* .unWidenEyes()
* .then(function(next){myCone.setColor('#900000'); next()})
* .bump();
* ```
*/
Cone.fn.animate = function(options){
if(typeof options === 'undefined' || typeof options.method === 'undefined'){
throw new Error('options.method mandatory');
}
else if(animationMethodExists(options.method) === false){
throw new Error('"'+options.method+'" : method not allowed');
}
var queueName = getAnimationMethodQueueName(options.method);
return animationMethods[queueName][options.method].call(this,options);
};
//add the animation methods to the Cone.prototype
(function($, methods){
for(var queueName in methods){
for(var methodName in methods[queueName]){
$[methodName] = (function(methodNameToCall){
return function(options){
options = typeof options === 'undefined' ? {} : options;
options.method = methodNameToCall;
if(typeof options.loop === 'number' && options.loop > 1){
for(var i=0;i<options.loop;i++){
(function(cone,timeToAssign,optionsPassed){
var optionsToUse = Cone.helpers.cloneObject(optionsPassed);
if(typeof options.callback === 'function'){
optionsToUse.callback = function(){
return options.callback.call({},cone,timeToAssign);
};
}
cone.animate(optionsToUse);
})(this,i,options);
}
return this;
}
else{
return this.animate(options);
}
};
})(methodName);
}
}
})(Cone.fn, animationMethods);
//you can set this off, not to see the warnings
Cone.fn.warnings = true;
//Private methods
/**
*
* @method animationMethodExists
* @private
* @param {string} methodName
* @return {Boolean}
*/
var animationMethodExists = function(methodName){
return !!getAnimationMethodQueueName(methodName);
};
/**
*
* @method getAnimationMethodQueueName
* @private
* @param {string} methodName
* @return {Boolean|string}
*/
var getAnimationMethodQueueName = function(methodName){
for(var queueName in animationMethods){
if(typeof animationMethods[queueName][methodName] !== 'undefined'){
return queueName;
}
}
return false;
};
/**
* this method is inpired by http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
*
* @method hexToRgb
* @private
* @param {String} hex
* @return {Object}
*/
var hexToRgb = function(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16) / 256,
g: parseInt(result[2], 16) / 256,
b: parseInt(result[3], 16) / 256
} : null;
};
/**
* @method isRgb
* @private
* @param {Object} color
* @return {Boolean}
*/
var isRgb = function(color){
if(typeof color !== 'undefined' && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number'){
return true;
}
return false;
};
/**
* @method removeAllAnimations
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeAllAnimations = function(cone){
removeBumpAnimation(cone);
removeWidenEyesAnimation(cone);
removeAlphaAnimation(cone);
removeColorAnimation(cone);
removeScaleAnimation(cone);
};
/**
*
* @method addBumpAnimation
* @private
* @param {Cone} cone
* @param {Number} scale description
* @return {undefined}
*/
var addBumpAnimation = function(cone,scale) {
var bumpAnimation = new BABYLON.Animation("bumpAnimation", "scaling.y", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var keys = [];
keys.push({
frame: 0,
value: 1
});
keys.push({
frame: 50,
value: scale
});
keys.push({
frame: 100,
value: 1
});
bumpAnimation.setKeys(keys);
cone.getMainMesh().animations.push(bumpAnimation);
};
/**
* @method removeBumpAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeBumpAnimation = function(cone){
helpers.removeAnimationFromMesh(cone.getMainMesh(), "bumpAnimation");
};
/**
* @method addWidenEyesAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var addWidenEyesAnimation = function(cone){
var parentEyesAnimationScalingY = new BABYLON.Animation("parentEyesAnimationScalingY", "scaling.y", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var parentEyesAnimationScalingYKeys = [];
parentEyesAnimationScalingYKeys.push({
frame: 0,
value: PARENT_EYES_ORIGINAL_SCALING_Y
});
parentEyesAnimationScalingYKeys.push({
frame: 50,
value: PARENT_EYES_ORIGINAL_SCALING_Y*1.8
});
parentEyesAnimationScalingYKeys.push({
frame: 100,
value: PARENT_EYES_ORIGINAL_SCALING_Y
});
parentEyesAnimationScalingY.setKeys(parentEyesAnimationScalingYKeys);
cone.parentEyes.animations.push(parentEyesAnimationScalingY);
var parentEyesAnimationPositionX = new BABYLON.Animation("parentEyesAnimationPositionX", "position.x", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var parentEyesAnimationPositionXKeys = [];
parentEyesAnimationPositionXKeys.push({
frame: 0,
value: PARENT_EYES_ORIGINAL_POSITION_X
});
parentEyesAnimationPositionXKeys.push({
frame: 50,
value: PARENT_EYES_ORIGINAL_POSITION_X+1
});
parentEyesAnimationPositionXKeys.push({
frame: 100,
value: PARENT_EYES_ORIGINAL_POSITION_X
});
parentEyesAnimationPositionX.setKeys(parentEyesAnimationPositionXKeys);
cone.parentEyes.animations.push(parentEyesAnimationPositionX);
var parentEyesAnimationPositionY = new BABYLON.Animation("parentEyesAnimationPositionY", "position.y", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var parentEyesAnimationPositionYKeys = [];
parentEyesAnimationPositionYKeys.push({
frame: 0,
value: PARENT_EYES_ORIGINAL_POSITION_Y
});
parentEyesAnimationPositionYKeys.push({
frame: 50,
value: PARENT_EYES_ORIGINAL_POSITION_Y+1
});
parentEyesAnimationPositionYKeys.push({
frame: 100,
value: PARENT_EYES_ORIGINAL_POSITION_Y
});
parentEyesAnimationPositionY.setKeys(parentEyesAnimationPositionYKeys);
cone.parentEyes.animations.push(parentEyesAnimationPositionY);
};
/**
* @method removeWidenEyesAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeWidenEyesAnimation = function(cone){
helpers.removeAnimationFromMesh(cone.parentEyes, "parentEyesAnimationScalingY");
helpers.removeAnimationFromMesh(cone.parentEyes, "parentEyesAnimationPositionX");
helpers.removeAnimationFromMesh(cone.parentEyes, "parentEyesAnimationPositionY");
};
/**
* @method addAlphaAnimation
* @private
* @param {Cone} cone
* @param {Object} options
* @return {undefined}
*/
var addAlphaAnimation = function(cone,options){
if(options.cylinder === true){
var cylinderAlphaAnimation = new BABYLON.Animation("cylinderAlphaAnimation", "material.alpha", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var cylinderAlphaAnimationKeys = [];
cylinderAlphaAnimationKeys.push({
frame: 0,
value: cone.cylinder.material.alpha
});
cylinderAlphaAnimationKeys.push({
frame: 100,
value: options.alpha
});
cylinderAlphaAnimation.setKeys(cylinderAlphaAnimationKeys);
cone.cylinder.animations.push(cylinderAlphaAnimation);
}
if(options.leftEye === true){
var leftEyeAlphaAnimation = new BABYLON.Animation("leftEyeAlphaAnimation", "material.alpha", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var leftEyeAlphaAnimationKeys = [];
leftEyeAlphaAnimationKeys.push({
frame: 0,
value: cone.leftEye.material.alpha
});
leftEyeAlphaAnimationKeys.push({
frame: 100,
value: options.alpha
});
leftEyeAlphaAnimation.setKeys(leftEyeAlphaAnimationKeys);
cone.leftEye.animations.push(leftEyeAlphaAnimation);
}
if(options.rightEye === true){
var rightEyeAlphaAnimation = new BABYLON.Animation("rightEyeAlphaAnimation", "material.alpha", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var rightEyeAlphaAnimationKeys = [];
rightEyeAlphaAnimationKeys.push({
frame: 0,
value: cone.rightEye.material.alpha
});
rightEyeAlphaAnimationKeys.push({
frame: 100,
value: options.alpha
});
rightEyeAlphaAnimation.setKeys(rightEyeAlphaAnimationKeys);
cone.rightEye.animations.push(rightEyeAlphaAnimation);
}
};
/**
* @method removeAlphaAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeAlphaAnimation = function(cone){
helpers.removeAnimationFromMesh(cone.cylinder, "cylinderAlphaAnimation");
helpers.removeAnimationFromMesh(cone.leftEye, "leftEyeAlphaAnimation");
helpers.removeAnimationFromMesh(cone.rightEye, "rightEyeAlphaAnimation");
};
/**
* @method addScaleAnimation
* @private
* @param {Cone} cone
* @param {Object} options
* @return {undefined}
*/
var addScaleAnimation = function(cone, options){
var mainMeshAnimationScalingX = new BABYLON.Animation("mainMeshAnimationScalingX", "scaling.x", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var mainMeshAnimationScalingXKeys = [];
mainMeshAnimationScalingXKeys.push({
frame: 0,
value: cone.scaling.x
});
mainMeshAnimationScalingXKeys.push({
frame: 100,
value: options.scale
});
mainMeshAnimationScalingX.setKeys(mainMeshAnimationScalingXKeys);
cone.getMainMesh().animations.push(mainMeshAnimationScalingX);
var mainMeshAnimationScalingY = new BABYLON.Animation("mainMeshAnimationScalingY", "scaling.y", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var mainMeshAnimationScalingYKeys = [];
mainMeshAnimationScalingYKeys.push({
frame: 0,
value: cone.scaling.x
});
mainMeshAnimationScalingYKeys.push({
frame: 100,
value: options.scale
});
mainMeshAnimationScalingY.setKeys(mainMeshAnimationScalingYKeys);
cone.getMainMesh().animations.push(mainMeshAnimationScalingY);
var mainMeshAnimationScalingZ = new BABYLON.Animation("mainMeshAnimationScalingZ", "scaling.z", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var mainMeshAnimationScalingZKeys = [];
mainMeshAnimationScalingZKeys.push({
frame: 0,
value: cone.scaling.x
});
mainMeshAnimationScalingZKeys.push({
frame: 100,
value: options.scale
});
mainMeshAnimationScalingZ.setKeys(mainMeshAnimationScalingZKeys);
cone.getMainMesh().animations.push(mainMeshAnimationScalingZ);
};
/**
* @method removeScaleAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeScaleAnimation = function(cone){
helpers.removeAnimationFromMesh(cone.getMainMesh(), "mainMeshAnimationScalingX");
helpers.removeAnimationFromMesh(cone.getMainMesh(), "mainMeshAnimationScalingY");
helpers.removeAnimationFromMesh(cone.getMainMesh(), "mainMeshAnimationScalingZ");
};
/**
* @todo still under development
* @method addColorAnimation
* @private
* @param {Cone} cone
* @param {Object} options
* @return {undefined}
*/
var addColorAnimation = function(cone, options){
var cylinderColorAnimation = new BABYLON.Animation("cylinderColorAnimation", "material.diffuseColor", 60, BABYLON.Animation.ANIMATIONTYPE_COLOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
var cylinderColorAnimationKeys = [];
cylinderColorAnimationKeys.push({
frame: 0,
value: new BABYLON.Color3(1,0,0)
});
cylinderColorAnimationKeys.push({
frame: 100,
value: options.color
});
cylinderColorAnimation.setKeys(cylinderColorAnimationKeys);
cone.cylinder.animations.push(cylinderColorAnimation);
};
/**
* @method removeColorAnimation
* @private
* @param {Cone} cone
* @return {undefined}
*/
var removeColorAnimation = function(cone){
helpers.removeAnimationFromMesh(cone.cylinder, "cylinderColorAnimation");
};
/**
*
* Bunch of methods I didn't find inside BabylonJS, that I coded for myself.
* Please tell me if they exist
*
* @class Cone.helpers
*/
var helpers = {
/**
* @method getAnimationNamesFromMesh
* @static
* @param {BABYLON.Mesh} mesh
* @return {Array}
*/
getAnimationNamesFromMesh: function(mesh) {
var result = mesh.animations.map(function(item, index) {
return item.name;
});
return result;
},
/**
* @method isAnimationRegistered
* @static
* @param {BABYLON.Mesh} mesh
* @param {String} animationName
* @return {Boolean}
*/
isAnimationRegistered: function(mesh, animationName) {
return helpers.getAnimationNamesFromMesh(mesh).indexOf(animationName) > -1;
},
/**
* Removes the animation from the mesh
*
* returns true if the animation was removed / false if there was no animation to remove
* @method removeAnimationFromMesh
* @static
* @param {BABYLON.Mesh} mesh
* @param {String} animationName
* @return {Boolean}
*/
removeAnimationFromMesh: function(mesh, animationName) {
if (mesh.animations.length > 0) {
mesh.animations.splice(mesh.animations.indexOf(animationName), 1);
return true;
}
else {
return false;
}
},
/**
* Clone object (simple, not deep reccursive)
* @method cloneObject
* @static
* @param {Object} obj
* @return {Object}
*/
cloneObject: function(obj){
var result = {}, key;
for(key in obj){
result[key] = obj[key];
}
return result;
}
};
Cone.helpers = helpers;
/**
* Management of Cone instances lists made easier
*
* Instance methods made available directly on the list, and lots of other things.
*
* @class Cone.List
* @constructor
* @param {Array<Cone>|Cone} coneList
* @return {Cone.List}
*/
Cone.List = function(coneList){
var MESSAGE_ERROR = 'Cone.List only accepts Cone object or Array of Cone objects';
if(coneList instanceof Cone){
this.push(coneList);
}
else if(coneList instanceof Array){
for(var i=0;i<coneList.length;i++){
if(coneList[i] instanceof Cone){
this.push(coneList[i]);
}
else{
throw new Error(MESSAGE_ERROR);
}
}
}
else if(typeof coneList !== 'undefined'){
throw new Error(MESSAGE_ERROR);
}
};
Cone.List.fn = Cone.List.prototype = [];
/**
* Loops through the cone list providing a callback like `function(cone, index){}`
*
* Return false in the callback to stop the loop
*
* @method each
* @param {function} callback
* @return {Cone.List}
* @chainable
*/
Cone.List.fn.each = function(callback){
if(this.length > 0){
for(var i=0; i<this.length; i++){
if(callback.call({},this[i],i) === false){
break;
}
}
}
return this;
};
/**
* Run any animate methods such as :
*
* * animateAlpha
* * animateScale
* * bump
* * fadeIn
* * fadeOut
* * unWidenEyes
* * widenEyes
* * moveTo
*
* Just specify it in `options.method`. Those methods are also accessible via the same shortcuts like you would use on a {{#crossLink "Cone"}}Cone{{/crossLink}} instance.
*
* @method animate
* @param {Object} options same options as the ones on the cone for each animation method
* @param {String} options.method
* @return {Cone.List}
* @chainable
*
* @example ```js
* //you can use the .animate() dispatcher as well as the shortcuts, directly on a conelist :
* var myConeList = new ConeList([myCone1,myCone2,myCone3]);
* myConeList
* .fadeOut()
* .fadeIn()
* .delay(1000)
* .widenEyes()
* .unWidenEyes()
* .then(function(next){myConeList.setColor('#900000'); next()})
* .bump();
* ```
*/
Cone.List.fn.animate = function(options){
options = typeof options === 'undefined' ? {} : options;
options.loop = (typeof options.loop === 'undefined') ? false : options.loop;
options.callback = typeof options.callback !== 'function' ? null : options.callback;
options.delay = (typeof options.delay === 'undefined') ? 0 : options.delay;
if(typeof options.method === 'undefined'){
throw new Error('method needs to be specified');
}
if(animationMethodExists(options.method) === false){
throw new Error('"'+options.method+'" : method not allowed');
}
this.each(function(cone){
cone[options.method](options);
});
return this;
};
//add the animation methods to the Cone.List.prototype
(function($,animationMethods){
var i, queueName, methodName;
for(queueName in animationMethods){
for(methodName in animationMethods[queueName]){
$[methodName] = (function(curMethodName){
return function(options){
options = typeof options === 'undefined' ? {} : options;
options.method = curMethodName;
return this.animate(options);
};
})(methodName);
}
}
})(Cone.List.fn, animationMethods);
/**
* @method changeStateDispatcher
* @private
* @param {Cone.List} coneList
* @param {string} methodName
* @param {Array} args
* @return {Cone.List}
*/
var changeStateDispatcher = function(coneList, methodName, args){
return coneList.each(function(cone){
stateFullMethods[methodName].apply(cone,args);
});
};
/**
* This method is used internally to expose methods that are used both on Cone and Cone.List
*
* You will use it when you make your own plugins to expose your own methods
*
* @method addMethods
* @param {Object} methodList List of the methods to add to the cone list prototype
* @returns {undefined}
* @static
*
* @example
* ```js
* //at the end of your file
* Cone.addMethods(stateFullMethods);
* Cone.List.addMethods(stateFullMethods);
* ```
*/
Cone.List.addMethods = function(methodList){
for(var methodName in methodList){
if(!Cone.List.fn[methodName]){
Cone.List.fn[methodName] = (function(methodName){
return function(){
return changeStateDispatcher(this, methodName, arguments);
};
})(methodName);
}
else{
console.warn('method '+methodName+' already registered');
}
}
};
//add stateFull methods on both Cone.fn and Cone.List.fn
Cone.addMethods(stateFullMethods);
Cone.List.addMethods(stateFullMethods);
return Cone;
});