1 tQuery.registerStatic('createAnimation', function(opts){ 2 return new tQuery.Animation(opts); 3 }); 4 5 6 7 ////////////////////////////////////////////////////////////////////////////////// 8 // Constructor // 9 ////////////////////////////////////////////////////////////////////////////////// 10 11 /** 12 * handle an animation 13 * 14 * @name tQuery.Animation 15 * @class 16 */ 17 tQuery.registerStatic('Animation', function(opts){ 18 opts = this._opts = tQuery.extend(opts, { 19 world : tQuery.world 20 }) 21 this._keyframes = new Array; 22 this._totalTime = null; 23 this._onUpdate = null; 24 this._onCapture = function(position){}; 25 this._initialPos = {}; 26 this._propertyTweens = {}; 27 }); 28 29 30 /** 31 * Destructor 32 */ 33 tQuery.Animation.prototype.destroy = function(){ 34 this.stop(); 35 }; 36 37 38 ////////////////////////////////////////////////////////////////////////////////// 39 // setup // 40 ////////////////////////////////////////////////////////////////////////////////// 41 42 /** 43 * @param {Number} duration the duration of this keyframe validity in seconds 44 * @param {Object} position list of properties involved in the animations 45 */ 46 tQuery.Animation.prototype.pushKeyframe = function(duration, position){ 47 this._keyframes.push({ 48 duration : duration, 49 position : position 50 }); 51 return this; // for chained API 52 }; 53 54 /** 55 * Set the Update callback 56 * 57 * @param {function} fn the update callback 58 */ 59 tQuery.Animation.prototype.onUpdate = function(fn){ 60 this._onUpdate = fn 61 return this; // for chained API 62 } 63 64 /** 65 * Set the Capture callback 66 * 67 * @param {function} fn the update callback 68 */ 69 tQuery.Animation.prototype.onCapture = function(fn){ 70 this._onCapture = fn 71 return this; // for chained API 72 } 73 74 /** 75 * Set propertyTweens 76 * 77 * @param {function} fn the update callback 78 */ 79 tQuery.Animation.prototype.propertyTweens = function(propertyTweens){ 80 this._propertyTweens = propertyTweens; 81 return this; // for chained API 82 } 83 84 /** 85 * get the total animation duration 86 * 87 * @returns {Number} the duration of the whole animation 88 */ 89 tQuery.Animation.prototype.duration = function(){ 90 if( this._keyframes.length === 0 ) return 0; 91 var lastKeyframe = this._keyframes[this._keyframes.length - 1]; 92 return lastKeyframe._endTime; 93 }; 94 95 96 ////////////////////////////////////////////////////////////////////////////////// 97 // interpolation // 98 ////////////////////////////////////////////////////////////////////////////////// 99 100 /** 101 * build a interpolated position 102 * 103 * @param {Number} age amount of seconds since the animation started 104 */ 105 tQuery.Animation.prototype._buildPosition = function(age){ 106 // compute the deltatime 107 var delta = age % this.duration(); 108 // find baseFrame based on delta 109 for(var frameIdx = 0; frameIdx < this._keyframes.length; frameIdx++){ 110 var baseFrame = this._keyframes[frameIdx]; 111 if( delta < baseFrame._startTime ) continue; 112 if( delta >= baseFrame._endTime ) continue; 113 break; 114 } 115 // sanity check - the baseFrame has to be known 116 console.assert( frameIdx !== this._keyframes.length ); 117 // compute some variables 118 var timeOffset = delta - baseFrame._startTime; 119 var timePercent = timeOffset / baseFrame.duration; 120 var nextFrame = this._keyframes[ (frameIdx+1) % this._keyframes.length ]; 121 122 //console.log("delta", delta) 123 //console.log("frameIdx", frameIdx) 124 //console.log("timeOffset", timeOffset) 125 //console.log("timePercent", timePercent) 126 127 var basePosition= baseFrame.position; 128 var nextPosition= nextFrame.position; 129 130 // zero this._initialPos if age > baseFrame.duration - it wont be usefull anymore 131 if( age > baseFrame.duration && this._initialPos ) this._initialPos= null; 132 // if frameIdx === 0 and there is a this._initialPos, use it as basePosition 133 if( frameIdx === 0 && this._initialPos ) basePosition = this._initialPos; 134 135 // compute the result based on the linear interpolation between the two frames based on time offset within the frame 136 var result = {}; 137 for( var property in baseFrame.position ){ 138 // check the property exists 139 console.assert( nextPosition[property] !== undefined ); 140 console.assert( basePosition[property] !== undefined ); 141 // linear interpolation between the values 142 var baseValue = basePosition[property]; 143 var nextValue = nextPosition[property]; 144 // define propertyTween for this property - default to linear interpolation 145 var propertyTween = this._propertyTweens[property] || function(baseValue, nextValue, timePercent){ 146 return (1-timePercent) * baseValue + timePercent * nextValue; 147 } 148 // compute the actual result 149 result[property]= propertyTween(baseValue, nextValue, timePercent); 150 } 151 // return the result 152 return result; 153 }; 154 155 ////////////////////////////////////////////////////////////////////////////////// 156 // // 157 ////////////////////////////////////////////////////////////////////////////////// 158 159 /** 160 * Start the animation 161 */ 162 tQuery.Animation.prototype.start = function(){ 163 // update _startTime and _endTime 164 this._totalTime = 0; 165 this._keyframes.forEach(function(keyframe){ 166 keyframe._startTime = this._totalTime; 167 this._totalTime += keyframe.duration; 168 keyframe._endTime = this._totalTime; 169 }.bind(this)); 170 171 // get this._initialPos from this._onCapture() 172 // - the initial position is the position when the animation started. 173 // - it will be used as basePosition during the first keyframe of the animation 174 // - it is optional. the user may not define it 175 this._initialPos= tQuery.extend({}, this._keyframes[0].position) 176 this._onCapture(this._initialPos); 177 178 // init the loop callback 179 var startDate = Date.now()/1000; 180 var duration = this.duration(); 181 this._$loopCb = this._opts.world.loop().hook(function(){ 182 var age = Date.now()/1000 - startDate; 183 var position = this._buildPosition(age) 184 this._onUpdate(position) 185 }.bind(this)); 186 } 187 188 /** 189 * test if the animation is running or not 190 * 191 * @returns {boolean} return true if the animation is running, false otherwise 192 */ 193 tQuery.Animation.prototype.isRunning = function(){ 194 return this._$loopCb ? true : false; 195 }; 196 197 /** 198 * Stop the animation 199 */ 200 tQuery.Animation.prototype.stop = function(){ 201 this._$loopCb && this._opts.world.loop().unhook(this._$loopCb); 202 this._$loopCb = null; 203 } 204