/** * System takes care of updating all the entities (based on their component data) * @param apiSystem GameLib.API.System * @constructor */ GameLib.System.Animation = function( apiSystem ) { GameLib.System.call( this, apiSystem ); this.animations = {}; this.latest = {}; }; GameLib.System.Animation.prototype = Object.create(GameLib.System.prototype); GameLib.System.Animation.prototype.constructor = GameLib.System.Animation; GameLib.System.Animation.prototype.start = function() { GameLib.System.prototype.start.call(this); this.beforeRenderSubscription = GameLib.Event.Subscribe( GameLib.Event.BEFORE_RENDER, function(data) { var delta = data.delta; for (var property in this.animations) { if (this.animations.hasOwnProperty(property)) { if (this.animations[property].length > 0) { var done = false; var animationObject = this.animations[property][0]; if (animationObject.type === 'rotation') { var increment = animationObject.animation.rotationSpeed * delta; animationObject.from += increment; if (Math.abs(animationObject.from - animationObject.to) < increment) { animationObject.mesh.instance.rotation[animationObject.axis] = animationObject.to; done = true; } else { animationObject.mesh.instance.rotation[animationObject.axis] = animationObject.from; } } if (done) { this.animations[property].splice(0, 1); } } } } }.bind(this) ); this.animationMeshAddedSubscription = GameLib.Event.Subscribe( GameLib.Event.ANIMATION_MESH_ADDED, function(data) { var animation = data.animation; var mesh = data.mesh; /** * Initialize the property with the original mesh z value */ this.latest[mesh.id] = { rotation : { x : mesh.rotation.x, y : mesh.rotation.y, z : mesh.rotation.z }, position : { x : mesh.position.x, y : mesh.position.y, z : mesh.position.z }, scale : { x : mesh.scale.x, y : mesh.scale.y, z : mesh.scale.z }, quaternion : { axis : { x : mesh.quaternion.axis.x, y : mesh.quaternion.axis.y, z : mesh.quaternion.axis.z }, angle : mesh.quaternion.angle } }; this.animations[mesh.id] = []; mesh.backupQuaternionAngleDescriptor = Object.getOwnPropertyDescriptor(mesh.quaternion, 'angle'); mesh.backupQuaternionAxisXDescriptor = Object.getOwnPropertyDescriptor(mesh.quaternion.axis, 'x'); mesh.backupQuaternionAxisYDescriptor = Object.getOwnPropertyDescriptor(mesh.quaternion.axis, 'y'); mesh.backupQuaternionAxisZDescriptor = Object.getOwnPropertyDescriptor(mesh.quaternion.axis, 'z'); mesh.backupRotationXDescriptor = Object.getOwnPropertyDescriptor(mesh.rotation, 'x'); mesh.backupRotationYDescriptor = Object.getOwnPropertyDescriptor(mesh.rotation, 'y'); mesh.backupRotationZDescriptor = Object.getOwnPropertyDescriptor(mesh.rotation, 'z'); mesh.backupPositionXDescriptor = Object.getOwnPropertyDescriptor(mesh.position, 'x'); mesh.backupPositionYDescriptor = Object.getOwnPropertyDescriptor(mesh.position, 'y'); mesh.backupPositionZDescriptor = Object.getOwnPropertyDescriptor(mesh.position, 'z'); mesh.backupScaleXDescriptor = Object.getOwnPropertyDescriptor(mesh.scale, 'x'); mesh.backupScaleYDescriptor = Object.getOwnPropertyDescriptor(mesh.scale, 'y'); mesh.backupScaleZDescriptor = Object.getOwnPropertyDescriptor(mesh.scale, 'z'); Object.defineProperty( mesh.quaternion, 'angle', { 'get': this.getQuaternionAngle(mesh, animation), 'set': this.setQuaternionAngle(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'x', { 'get': this.getQuaternionAxisX(mesh, animation), 'set': this.setQuaternionAxisX(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'y', { 'get': this.getQuaternionAxisY(mesh, animation), 'set': this.setQuaternionAxisY(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'z', { 'get': this.getQuaternionAxisZ(mesh, animation), 'set': this.setQuaternionAxisZ(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'x', { 'get': this.getRotation(mesh, 'x'), 'set': this.setRotation(mesh, animation, 'x'), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'y', { 'get': this.getRotation(mesh, 'y'), 'set': this.setRotation(mesh, animation, 'y'), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'z', { 'get': this.getRotation(mesh, 'z'), 'set': this.setRotation(mesh, animation, 'z'), 'configurable': true } ); Object.defineProperty( mesh.scale, 'x', { 'get': this.getScaleX(mesh, animation), 'set': this.setScaleX(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.scale, 'y', { 'get': this.getScaleY(mesh, animation), 'set': this.setScaleY(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.scale, 'z', { 'get': this.getScaleZ(mesh, animation), 'set': this.setScaleZ(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.position, 'x', { 'get': this.getPositionX(mesh, animation), 'set': this.setPositionX(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.position, 'y', { 'get': this.getPositionY(mesh, animation), 'set': this.setPositionY(mesh, animation), 'configurable': true } ); Object.defineProperty( mesh.position, 'z', { 'get': this.getPositionZ(mesh, animation), 'set': this.setPositionZ(mesh, animation), 'configurable': true } ); }.bind(this) ); }; GameLib.System.Animation.prototype.getQuaternionAngle = function(mesh, animation) { return function() { return; /** * TODO: fix this shit.. * Back up the current property descriptor */ mesh.animationObject = { backupAngleDescriptor: Object.getOwnPropertyDescriptor(mesh.quaternion, 'angle'), targetAngle: mesh.quaternion.angle, angleIncrement: true, intermediateAngle: mesh.quaternion.angle, subscription: null, inProcess: false, blocking: animation.blocking//, // callbacks : [], // storedValues : [] }; var getIntermediateAngle = function () { return mesh.animationObject.intermediateAngle; }; var getTargetAngle = function () { // if (mesh.animationObject.storedValues.length > 0) { // return mesh.animationObject.storedValues[mesh.animationObject.storedValues.length - 1]; // } return mesh.animationObject.targetAngle; }; var animateRotation = function (value) { mesh.animationObject.intermediateAngle += value; var done = false; if (mesh.animationObject.angleIncrement) { /** * We are rotating upwards */ if (mesh.animationObject.intermediateAngle >= mesh.animationObject.targetAngle) { /** * We need to stop */ done = true; } } else { /** * We are rotating downwards */ if (mesh.animationObject.intermediateAngle <= mesh.animationObject.targetAngle) { /** * We need to stop */ done = true; } } if (done) { /** * We clamp to our target angle */ mesh.animationObject.intermediateAngle = mesh.animationObject.targetAngle; /** * We limit our intermediate angle between values of -pi and pi */ while (mesh.animationObject.intermediateAngle > Math.PI) { mesh.animationObject.intermediateAngle -= (Math.PI * 2); } while (mesh.animationObject.intermediateAngle < -(Math.PI)) { mesh.animationObject.intermediateAngle += (Math.PI * 2); } /** * We apply our new intermediate angle to our target */ mesh.animationObject.targetAngle = mesh.animationObject.intermediateAngle; } /** * Apply the actual rotation to the mesh */ mesh.updateInstanceRotationFromAxisAngle(mesh.quaternion.axis, mesh.animationObject.intermediateAngle); /** * Check again if we are done, we need to do some additional work - */ if (done) { if (!mesh.animationObject.subscription) { var message = 'mesh animation object subscription went missing for '; message += mesh.name + ': '; message += animation.name; console.warn(message); throw new Error(message); } /** * Stop subscribing to before render events */ mesh.animationObject.subscription.remove(); /** * @type {null} */ mesh.animationObject.subscription = null; /** * For some meshes, when we are done with the animation, we want to apply * the current state of the mesh to the object data itself (i.e. update * its vertices etc) */ if (animation.applyToMeshWhenDone) { /** * Now we say that our intermediate angle is zero, because we will apply our rotation * and this will prevent us from re-registering a new 'animationRender' event */ mesh.animationObject.targetAngle = 0; mesh.animationObject.intermediateAngle = 0; /** * Apply our position, rotation and scale to the mesh */ mesh.applyPositionRotationScale(); } /** * Tell our animation component that it is no longer in process... * @type {boolean} */ mesh.animationObject.inProcess = false; // if (mesh.animationObject.callbacks.length > 0) { // var callback = mesh.animationObject.callbacks[0]; // mesh.animationObject.callbacks.splice(0,1); // callback(); // } // mesh.animationObject.storedValues = []; } }; } }; GameLib.System.Animation.prototype.setQuaternionAngle = function(mesh, animation) { return function(value) { return; /** * TODO: update this shit */ /** * Check if we have work to do */ if (mesh.animationObject.intermediateAngle === value) { mesh.animationObject.inProcess = false; /** * Nothing to do */ return; } /** * Check if we have another animation in process */ if (mesh.animationObject.inProcess && mesh.animationObject.blocking) { console.log('another animation is already in process'); // setTargetAngle(value); // GameLib.Utils.PushUnique(mesh.animationObject.storedValues, value); // // mesh.animationObject.callbacks.push( // function(__value) { // return function(){ // mesh.quaternion.angle = __value; // } // }(value) // ); /** * Respond that our angle is actually our target angle (it will be that soon) */ return; } /** * We indicate that we now have an animation in process * @type {boolean} */ mesh.animationObject.inProcess = true; /** * Ok - all good - lets start the animation */ if (mesh.animationObject.intermediateAngle > value) { /** * We will rotate towards by decrementing */ mesh.animationObject.angleIncrement = false; } else { /** * We will rotate towards by incrementing */ mesh.animationObject.angleIncrement = true; } /** * We say what our target angle is - when we reach our target angle, we want * to stop our animation */ mesh.animationObject.targetAngle = value; /** * Now we subscribe to 'before render' events, and slowly increment the value * @type {{fn, remove}} */ mesh.animationObject.subscription = GameLib.Event.Subscribe( GameLib.Event.BEFORE_RENDER, function(data) { var increment = Math.abs(animation.rotationSpeed); if (!mesh.animationObject.angleIncrement) { increment *= -1; } animateRotation(data.delta * increment); } ); } }; GameLib.System.Animation.prototype.getQuaternionAxisX = function(mesh, animation) { return function() { return this.latest[mesh.id].quaternion.axis.x; }.bind(this); }; GameLib.System.Animation.prototype.setQuaternionAxisX = function(mesh, animation) { return function(value) { this.latest[mesh.id].quaternion.axis.x = value; }.bind(this); }; GameLib.System.Animation.prototype.getQuaternionAxisY = function(mesh, animation) { return function() { return this.latest[mesh.id].quaternion.axis.y; }.bind(this); }; GameLib.System.Animation.prototype.setQuaternionAxisY = function(mesh, animation) { return function(value) { this.latest[mesh.id].quaternion.axis.y = value; }.bind(this); }; GameLib.System.Animation.prototype.getQuaternionAxisZ = function(mesh, animation) { return function() { return this.latest[mesh.id].quaternion.axis.z; }.bind(this); }; GameLib.System.Animation.prototype.setQuaternionAxisZ = function(mesh, animation) { return function(value) { this.latest[mesh.id].quaternion.axis.z = value; }.bind(this); }; GameLib.System.Animation.prototype.getProperty = function(mesh, axis, property) { return function() { return this.latest[mesh.id][property][axis]; }.bind(this); }; GameLib.System.Animation.prototype.setProperty = function(mesh, animation, axis, property) { return function(value) { var from = Number(this.latest[mesh.id][property][axis]); this.latest[mesh.id][property][axis] = value; this.animations[mesh.id].push( { type : property, axis : axis, from : from, to : value, animation : animation, mesh : mesh } ); }.bind(this) }; GameLib.System.Animation.prototype.getPositionX = function(mesh, axis) { return function() { return this.latest[mesh.id].position[axis]; }.bind(this); }; GameLib.System.Animation.prototype.setPositionX = function(mesh, animation) { return function(value) { this.latest[mesh.id].position.x = value; }.bind(this); }; GameLib.System.Animation.prototype.getPositionY = function(mesh, animation) { return function() { return this.latest[mesh.id].position.y; }.bind(this); }; GameLib.System.Animation.prototype.setPositionY = function(mesh, animation) { return function(value) { this.latest[mesh.id].position.y = value; }.bind(this); }; GameLib.System.Animation.prototype.getPositionZ = function(mesh, animation) { return function() { return this.latest[mesh.id].position.z; }.bind(this); }; GameLib.System.Animation.prototype.setPositionZ = function(mesh, animation) { return function(value) { this.latest[mesh.id].position.z = value; }.bind(this) }; GameLib.System.Animation.prototype.getScaleX = function(mesh, animation) { return function() { return this.latest[mesh.id].scale.x; }.bind(this); }; GameLib.System.Animation.prototype.setScaleX = function(mesh, animation) { return function(value) { this.latest[mesh.id].scale.x = value; }.bind(this); }; GameLib.System.Animation.prototype.getScaleY = function(mesh, animation) { return function() { return this.latest[mesh.id].scale.y; }.bind(this); }; GameLib.System.Animation.prototype.setScaleY = function(mesh, animation) { return function(value) { this.latest[mesh.id].scale.y = value; }.bind(this); }; GameLib.System.Animation.prototype.getScaleZ = function(mesh, animation) { return function() { return this.latest[mesh.id].scale.z; }.bind(this); }; GameLib.System.Animation.prototype.setScaleZ = function(mesh, animation) { return function(value) { this.latest[mesh.id].scale.z = value; }.bind(this) }; /** * Stop Animation System */ GameLib.System.Animation.prototype.stop = function() { GameLib.System.prototype.stop.call(this); this.beforeRenderSubscription.remove(); this.animationMeshAddedSubscription.remove(); var meshes = GameLib.EntityManager.Instance.queryComponents(GameLib.D3.Mesh); meshes.map( function(mesh) { if (mesh.backupQuaternionAngleDescriptor) { Object.defineProperty( mesh.quaternion, 'angle', mesh.backupQuaternionAngleDescriptor ); delete mesh.backupQuaternionAngleDescriptor; } if (mesh.backupQuaternionAxisXDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'x', mesh.backupQuaternionAxisXDescriptor ); delete mesh.backupQuaternionAxisXDescriptor; } if (mesh.backupQuaternionAxisYDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'y', mesh.backupQuaternionAxisYDescriptor ); delete mesh.backupQuaternionAxisYDescriptor; } if (mesh.backupQuaternionAxisZDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'z', mesh.backupQuaternionAxisZDescriptor ); delete mesh.backupQuaternionAxisXDescriptor; } if (mesh.backupRotationXDescriptor) { Object.defineProperty( mesh.rotation, 'x', mesh.backupRotationXDescriptor ); delete mesh.backupRotationXDescriptor; } if (mesh.backupRotationYDescriptor) { Object.defineProperty( mesh.rotation, 'y', mesh.backupRotationYDescriptor ); delete mesh.backupRotationYDescriptor; } if (mesh.backupRotationZDescriptor) { Object.defineProperty( mesh.rotation, 'z', mesh.backupRotationZDescriptor ); delete mesh.backupRotationZDescriptor; } if (mesh.backupPositionXDescriptor) { Object.defineProperty( mesh.position, 'x', mesh.backupPositionXDescriptor ); delete mesh.backupPositionXDescriptor; } if (mesh.backupPositionYDescriptor) { Object.defineProperty( mesh.position, 'y', mesh.backupPositionYDescriptor ); delete mesh.backupPositionYDescriptor; } if (mesh.backupPositionZDescriptor) { Object.defineProperty( mesh.position, 'z', mesh.backupPositionZDescriptor ); delete mesh.backupPositionZDescriptor; } if (mesh.backupScaleXDescriptor) { Object.defineProperty( mesh.scale, 'x', mesh.backupScaleXDescriptor ); delete mesh.backupScaleXDescriptor; } if (mesh.backupScaleYDescriptor) { Object.defineProperty( mesh.scale, 'y', mesh.backupScaleYDescriptor ); delete mesh.backupScaleYDescriptor; } if (mesh.backupScaleZDescriptor) { Object.defineProperty( mesh.scale, 'z', mesh.backupScaleZDescriptor ); delete mesh.backupScaleZDescriptor; } } ); };