/** * 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 ); }; 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.animationEntities = GameLib.EntityManager.Instance.findEntities([GameLib.D3.Animation]); this.animationEntities.map( function(animationEntity) { /** * Get all meshes and all animation components */ var meshComponents = animationEntity.getComponents(GameLib.D3.Mesh); var animationComponents = animationEntity.getComponents(GameLib.D3.Animation); animationComponents.map(function(animation){ meshComponents.map(function(mesh){ if (animation.rotationSpeed) { /** * 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 = []; } }; var setTargetAngle = function(value) { /** * 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); } ); }; /** * Override the property descriptor - to slowly increment the angle, but report back its * target angle as its actual angle. */ Object.defineProperty( mesh.quaternion, 'angle', { 'get' : getTargetAngle, 'set' : setTargetAngle, 'configurable' : true } ); } }); }); } ); }; /** * Stop Animation System */ GameLib.System.Animation.prototype.stop = function() { GameLib.System.prototype.stop.call(this); this.animationEntities = GameLib.EntityManager.Instance.findEntities([GameLib.D3.Animation]); this.animationEntities.map( function(animationEntity) { /** * Get all meshes and all animation components */ var meshComponents = animationEntity.getComponents(GameLib.D3.Mesh); var animationComponents = animationEntity.getComponents(GameLib.D3.Animation); animationComponents.map( function (animation) { meshComponents.map( function (mesh) { if (animation.rotationSpeed) { if (mesh.animationObject) { if (mesh.animationObject.backupAngleDescriptor) { Object.defineProperty( mesh.quaternion, 'angle', mesh.animationObject.backupAngleDescriptor ) } if (mesh.animationObject.targetAngle) { mesh.quaternion.angle = mesh.animationObject.targetAngle; } if (mesh.animationObject.subscription) { mesh.animationObject.subscription.remove(); } delete mesh.animationObject; } } } ) } ) } ); };