UNPKG

threex

Version:

Game Extensions for three.js http://www.threejsgames.com/extensions/

204 lines (177 loc) 6.09 kB
tQuery.registerStatic('createAnimation', function(opts){ return new tQuery.Animation(opts); }); ////////////////////////////////////////////////////////////////////////////////// // Constructor // ////////////////////////////////////////////////////////////////////////////////// /** * handle an animation * * @name tQuery.Animation * @class */ tQuery.registerStatic('Animation', function(opts){ opts = this._opts = tQuery.extend(opts, { world : tQuery.world }) this._keyframes = new Array; this._totalTime = null; this._onUpdate = null; this._onCapture = function(position){}; this._initialPos = {}; this._propertyTweens = {}; }); /** * Destructor */ tQuery.Animation.prototype.destroy = function(){ this.stop(); }; ////////////////////////////////////////////////////////////////////////////////// // setup // ////////////////////////////////////////////////////////////////////////////////// /** * @param {Number} duration the duration of this keyframe validity in seconds * @param {Object} position list of properties involved in the animations */ tQuery.Animation.prototype.pushKeyframe = function(duration, position){ this._keyframes.push({ duration : duration, position : position }); return this; // for chained API }; /** * Set the Update callback * * @param {function} fn the update callback */ tQuery.Animation.prototype.onUpdate = function(fn){ this._onUpdate = fn return this; // for chained API } /** * Set the Capture callback * * @param {function} fn the update callback */ tQuery.Animation.prototype.onCapture = function(fn){ this._onCapture = fn return this; // for chained API } /** * Set propertyTweens * * @param {function} fn the update callback */ tQuery.Animation.prototype.propertyTweens = function(propertyTweens){ this._propertyTweens = propertyTweens; return this; // for chained API } /** * get the total animation duration * * @returns {Number} the duration of the whole animation */ tQuery.Animation.prototype.duration = function(){ if( this._keyframes.length === 0 ) return 0; var lastKeyframe = this._keyframes[this._keyframes.length - 1]; return lastKeyframe._endTime; }; ////////////////////////////////////////////////////////////////////////////////// // interpolation // ////////////////////////////////////////////////////////////////////////////////// /** * build a interpolated position * * @param {Number} age amount of seconds since the animation started */ tQuery.Animation.prototype._buildPosition = function(age){ // compute the deltatime var delta = age % this.duration(); // find baseFrame based on delta for(var frameIdx = 0; frameIdx < this._keyframes.length; frameIdx++){ var baseFrame = this._keyframes[frameIdx]; if( delta < baseFrame._startTime ) continue; if( delta >= baseFrame._endTime ) continue; break; } // sanity check - the baseFrame has to be known console.assert( frameIdx !== this._keyframes.length ); // compute some variables var timeOffset = delta - baseFrame._startTime; var timePercent = timeOffset / baseFrame.duration; var nextFrame = this._keyframes[ (frameIdx+1) % this._keyframes.length ]; //console.log("delta", delta) //console.log("frameIdx", frameIdx) //console.log("timeOffset", timeOffset) //console.log("timePercent", timePercent) var basePosition= baseFrame.position; var nextPosition= nextFrame.position; // zero this._initialPos if age > baseFrame.duration - it wont be usefull anymore if( age > baseFrame.duration && this._initialPos ) this._initialPos= null; // if frameIdx === 0 and there is a this._initialPos, use it as basePosition if( frameIdx === 0 && this._initialPos ) basePosition = this._initialPos; // compute the result based on the linear interpolation between the two frames based on time offset within the frame var result = {}; for( var property in baseFrame.position ){ // check the property exists console.assert( nextPosition[property] !== undefined ); console.assert( basePosition[property] !== undefined ); // linear interpolation between the values var baseValue = basePosition[property]; var nextValue = nextPosition[property]; // define propertyTween for this property - default to linear interpolation var propertyTween = this._propertyTweens[property] || function(baseValue, nextValue, timePercent){ return (1-timePercent) * baseValue + timePercent * nextValue; } // compute the actual result result[property]= propertyTween(baseValue, nextValue, timePercent); } // return the result return result; }; ////////////////////////////////////////////////////////////////////////////////// // // ////////////////////////////////////////////////////////////////////////////////// /** * Start the animation */ tQuery.Animation.prototype.start = function(){ // update _startTime and _endTime this._totalTime = 0; this._keyframes.forEach(function(keyframe){ keyframe._startTime = this._totalTime; this._totalTime += keyframe.duration; keyframe._endTime = this._totalTime; }.bind(this)); // get this._initialPos from this._onCapture() // - the initial position is the position when the animation started. // - it will be used as basePosition during the first keyframe of the animation // - it is optional. the user may not define it this._initialPos= tQuery.extend({}, this._keyframes[0].position) this._onCapture(this._initialPos); // init the loop callback var startDate = Date.now()/1000; var duration = this.duration(); this._$loopCb = this._opts.world.hook(function(){ var age = Date.now()/1000 - startDate; var position = this._buildPosition(age) this._onUpdate(position) }.bind(this)); } /** * test if the animation is running or not * * @returns {boolean} return true if the animation is running, false otherwise */ tQuery.Animation.prototype.isRunning = function(){ return this._$loopCb ? true : false; }; /** * Stop the animation */ tQuery.Animation.prototype.stop = function(){ this._$loopCb && this._opts.world.unhook(this._$loopCb); this._$loopCb = null; }