/** * 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, this.beforeRender.bind(this) ); var animations = GameLib.EntityManager.Instance.queryComponents(GameLib.D3.Animation); animations.map(function(animation){ animation.meshes.map( function(mesh) { this.attachAnimation(animation, mesh); }.bind(this) ) }.bind(this)); this.animationMeshAddedSubscription = GameLib.Event.Subscribe( GameLib.Event.ANIMATION_MESH_ADDED, function(data) { this.attachAnimation(data.animation, data.mesh); }.bind(this) ); this.animationMeshRemovedSubscription = GameLib.Event.Subscribe( GameLib.Event.ANIMATION_MESH_REMOVED, function(data) { this.detachAnimation(data.mesh); }.bind(this) ) }; GameLib.System.Animation.prototype.beforeRender = function(data) { if (this.paused) { return; } var delta = data.delta; for (var property in this.animations) { if (this.animations.hasOwnProperty(property)) { var processing = []; if (this.animations[property].length > 0) { var rotationDone = false; var positionDone = false; var scaleDone = false; for (var i = 0; i < this.animations[property].length; i++) { if (this.animations[property][i].type === 'rotation' && !rotationDone) { rotationDone = true; processing.push(this.animations[property][i]); } if (this.animations[property][i].type === 'position' && !positionDone) { // if (this.animations[property][i].animation.blocking.position) { // positionDone = true; // } processing.push(this.animations[property][i]); } if (this.animations[property][i].type === 'scale' && !scaleDone) { //scaleDone = true; processing.push(this.animations[property][i]); } if (scaleDone && positionDone && rotationDone) { break; } } } processing.map( function(__property) { return function(animationObject) { var done = false; var increment = 0; if (animationObject.type === 'rotation') { increment = animationObject.animation.rotationSpeed * delta; } if (animationObject.type === 'position') { increment = animationObject.animation.translationSpeed * delta; } if (animationObject.type === 'scale') { increment = animationObject.animation.scaleSpeed * delta; } if (animationObject.from < animationObject.to) { animationObject.from += increment; } else { animationObject.from -= increment; } if (Math.abs(animationObject.from - animationObject.to) < increment) { animationObject.mesh.instance[animationObject.type][animationObject.axis] = animationObject.to; done = true; } else { animationObject.mesh.instance[animationObject.type][animationObject.axis] = animationObject.from; } if (done) { var index = this.animations[__property].indexOf(animationObject); this.animations[__property].splice(index, 1); } }.bind(this); }.bind(this)(property) ); } } }; GameLib.System.Animation.prototype.detachAnimation = function(mesh) { var detached = false; if (mesh.backupQuaternionAngleDescriptor) { Object.defineProperty( mesh.quaternion, 'angle', mesh.backupQuaternionAngleDescriptor ); delete mesh.backupQuaternionAngleDescriptor; detached = true; } if (mesh.backupQuaternionAxisXDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'x', mesh.backupQuaternionAxisXDescriptor ); delete mesh.backupQuaternionAxisXDescriptor; detached = true; } if (mesh.backupQuaternionAxisYDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'y', mesh.backupQuaternionAxisYDescriptor ); delete mesh.backupQuaternionAxisYDescriptor; detached = true; } if (mesh.backupQuaternionAxisZDescriptor) { Object.defineProperty( mesh.quaternion.axis, 'z', mesh.backupQuaternionAxisZDescriptor ); delete mesh.backupQuaternionAxisXDescriptor; detached = true; } if (mesh.backupRotationXDescriptor) { Object.defineProperty( mesh.rotation, 'x', mesh.backupRotationXDescriptor ); delete mesh.backupRotationXDescriptor; detached = true; } if (mesh.backupRotationYDescriptor) { Object.defineProperty( mesh.rotation, 'y', mesh.backupRotationYDescriptor ); delete mesh.backupRotationYDescriptor; detached = true; } if (mesh.backupRotationZDescriptor) { Object.defineProperty( mesh.rotation, 'z', mesh.backupRotationZDescriptor ); delete mesh.backupRotationZDescriptor; detached = true; } if (mesh.backupPositionXDescriptor) { Object.defineProperty( mesh.position, 'x', mesh.backupPositionXDescriptor ); delete mesh.backupPositionXDescriptor; detached = true; } if (mesh.backupPositionYDescriptor) { Object.defineProperty( mesh.position, 'y', mesh.backupPositionYDescriptor ); delete mesh.backupPositionYDescriptor; detached = true; } if (mesh.backupPositionZDescriptor) { Object.defineProperty( mesh.position, 'z', mesh.backupPositionZDescriptor ); delete mesh.backupPositionZDescriptor; detached = true; } if (mesh.backupScaleXDescriptor) { Object.defineProperty( mesh.scale, 'x', mesh.backupScaleXDescriptor ); delete mesh.backupScaleXDescriptor; detached = true; } if (mesh.backupScaleYDescriptor) { Object.defineProperty( mesh.scale, 'y', mesh.backupScaleYDescriptor ); delete mesh.backupScaleYDescriptor; detached = true; } if (mesh.backupScaleZDescriptor) { Object.defineProperty( mesh.scale, 'z', mesh.backupScaleZDescriptor ); delete mesh.backupScaleZDescriptor; detached = true; } if (this.latest[mesh.id]) { mesh.rotation.x = this.latest[mesh.id].rotation.x; mesh.rotation.y = this.latest[mesh.id].rotation.y; mesh.rotation.z = this.latest[mesh.id].rotation.z; mesh.position.x = this.latest[mesh.id].position.x; mesh.position.y = this.latest[mesh.id].position.y; mesh.position.z = this.latest[mesh.id].position.z; mesh.scale.x = this.latest[mesh.id].scale.x; mesh.scale.y = this.latest[mesh.id].scale.y; mesh.scale.z = this.latest[mesh.id].scale.z; mesh.quaternion.axis.x = this.latest[mesh.id].quaternion.axis.x; mesh.quaternion.axis.y = this.latest[mesh.id].quaternion.axis.y; mesh.quaternion.axis.z = this.latest[mesh.id].quaternion.axis.z; mesh.quaternion.angle = this.latest[mesh.id].quaternion.angle; delete this.latest[mesh.id]; detached = true; } if (this.animations[mesh.id]) { delete this.animations[mesh.id]; detached = true; } if (detached) { mesh.updateInstance(); } }; GameLib.System.Animation.prototype.attachAnimation = function(animation, 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] = []; if (mesh.backupRotationXDescriptor) { throw new Error('already a backed up x descriptor'); } 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.getProperty(mesh, 'angle', 'quaternion'), 'set': this.setProperty(mesh, animation, 'angle', 'quaternion'), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'x', { 'get': this.getSubProperty(mesh, 'x', 'quaternion', 'axis'), 'set': this.setSubProperty(mesh, animation, 'x', 'quaternion', 'axis'), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'y', { 'get': this.getSubProperty(mesh, 'y', 'quaternion', 'axis'), 'set': this.setSubProperty(mesh, animation, 'y', 'quaternion', 'axis'), 'configurable': true } ); Object.defineProperty( mesh.quaternion.axis, 'z', { 'get': this.getSubProperty(mesh, 'z', 'quaternion', 'axis'), 'set': this.setSubProperty(mesh, animation, 'z', 'quaternion', 'axis'), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'x', { 'get': this.getProperty(mesh, 'x', 'rotation'), 'set': this.setProperty(mesh, animation, 'x', 'rotation'), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'y', { 'get': this.getProperty(mesh, 'y', 'rotation'), 'set': this.setProperty(mesh, animation, 'y', 'rotation'), 'configurable': true } ); Object.defineProperty( mesh.rotation, 'z', { 'get': this.getProperty(mesh, 'z', 'rotation'), 'set': this.setProperty(mesh, animation, 'z', 'rotation'), 'configurable': true } ); Object.defineProperty( mesh.scale, 'x', { 'get': this.getProperty(mesh, 'x', 'scale'), 'set': this.setProperty(mesh, animation, 'x', 'scale'), 'configurable': true } ); Object.defineProperty( mesh.scale, 'y', { 'get': this.getProperty(mesh, 'y', 'scale'), 'set': this.setProperty(mesh, animation, 'y', 'scale'), 'configurable': true } ); Object.defineProperty( mesh.scale, 'z', { 'get': this.getProperty(mesh, 'z', 'scale'), 'set': this.setProperty(mesh, animation, 'z', 'scale'), 'configurable': true } ); Object.defineProperty( mesh.position, 'x', { 'get': this.getProperty(mesh, 'x', 'position'), 'set': this.setProperty(mesh, animation, 'x', 'position'), 'configurable': true } ); Object.defineProperty( mesh.position, 'y', { 'get': this.getProperty(mesh, 'y', 'position'), 'set': this.setProperty(mesh, animation, 'y', 'position'), 'configurable': true } ); Object.defineProperty( mesh.position, 'z', { 'get': this.getProperty(mesh, 'z', 'position'), 'set': this.setProperty(mesh, animation, 'z', 'position'), 'configurable': true } ); }; // 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.getSubProperty = function(mesh, axis, property, subProperty) { return function() { return this.latest[mesh.id][property][subProperty][axis]; }.bind(this); }; GameLib.System.Animation.prototype.setSubProperty = function(mesh, animation, axis, property, subProperty) { return function(value) { var from = Number(this.latest[mesh.id][property][subProperty][axis]); this.latest[mesh.id][property][subProperty][axis] = value; if (property === 'position') { /** * Look for other position animations * TODO:check when not super exausted */ // var positionAnimationObject = this.animations[mesh.id].reduce( // function(result, animationObject) { // // if (animationObject.type === 'position') { // result = animationObject; // } // // return result; // }, // null // ); /** * We found another position animation - just update the 'to' property */ // if (positionAnimationObject) { // positionAnimationObject.to = value; // return; // } } this.animations[mesh.id].push( { type : property, axis : axis, from : from, to : value, animation : animation, mesh : mesh } ); }.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; if (property === 'position') { /** * Look for other position animations * TODO:check when not super exausted */ // var positionAnimationObject = this.animations[mesh.id].reduce( // function(result, animationObject) { // // if (animationObject.type === 'position') { // result = animationObject; // } // // return result; // }, // null // ); /** * We found another position animation - just update the 'to' property */ // if (positionAnimationObject) { // positionAnimationObject.to = value; // return; // } } this.animations[mesh.id].push( { type : property, axis : axis, from : from, to : value, animation : animation, mesh : mesh } ); }.bind(this) }; /** * Stop Animation System */ GameLib.System.Animation.prototype.stop = function() { GameLib.System.prototype.stop.call(this); this.beforeRenderSubscription.remove(); this.animationMeshAddedSubscription.remove(); this.animationMeshRemovedSubscription.remove(); this.animations = {}; var meshes = GameLib.EntityManager.Instance.queryComponents(GameLib.D3.Mesh); meshes.map( function(mesh) { for (var property in this.latest) { if ( this.latest.hasOwnProperty(property) && property === mesh.id ) { this.detachAnimation(mesh); } } }.bind(this) ); };