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