1 /**
  2  * @fileoverview
  3  * 
  4  * TODO add _ prefix to private properties
  5  * TODO much cleanup needed
  6  * TODO no change of property from outside. use getter/setter
  7  * TODO only chained API
  8 */
  9 
 10 /**
 11  * widely inspired from MD2Character.js from alteredq / http://alteredqualia.com/
 12  *
 13  * @name tQuery.MD2Character
 14  * @class
 15 */
 16 tQuery.registerStatic('MD2Character', function(){
 17 	this._scale		= 1;
 18 	this.animationFPS	= 6;
 19 
 20 	this._root		= new THREE.Object3D();
 21 	this._meshBody		= null;
 22 	this._meshWeapon	= null;
 23 
 24 	this._skinsBody		= [];
 25 	this._skinsWeapon	= [];
 26 
 27 	this._weapons		= [];
 28 
 29 	this._curAnimation	= null;
 30 	this._nLoadInProgress	= 0;
 31 });
 32 //tQuery.MD2Character	= function(){
 33 
 34 // make it eventable
 35 tQuery.MicroeventMixin(tQuery.MD2Character.prototype);
 36 
 37 // Make the class pluginable
 38 tQuery.pluginsStaticOn(tQuery.MD2Character);
 39 
 40 /**
 41  * Destructor
 42 */
 43 tQuery.MD2Character.prototype.destroy	= function()
 44 {
 45 	console.log("tQuery.MD2Character destoy")
 46 }
 47 
 48 //////////////////////////////////////////////////////////////////////////////////
 49 //										//
 50 //////////////////////////////////////////////////////////////////////////////////
 51 
 52 /**
 53  * Update the animation
 54  * 
 55  * @param {Number} deltaSeconds nb seconds since the last update
 56 */
 57 tQuery.MD2Character.prototype.update	= function( deltaSeconds )
 58 {
 59 	if ( this._meshBody ) {
 60 		var direction	= this._meshBody.direction;
 61 		var timeBefore	= this._meshBody.time;
 62 		// update the animation
 63 		this._meshBody.updateAnimation( 1000 * deltaSeconds );
 64 		// ugly kludge to get an event 'animationCompleted'
 65 		var timeAfter	= this._meshBody.time;
 66 		if( (direction === 1 && timeBefore > timeAfter) || (direction === -1 && timeAfter < timeBefore) ){
 67 			this.trigger("animationCompleted", this, this._curAnimation)
 68 			//console.log("endofanim", this._curAnimation)
 69 		}
 70 	}
 71 	if ( this._meshWeapon ) {
 72 		this._meshWeapon.updateAnimation( 1000 * deltaSeconds );
 73 	}
 74 	return this;	// for chained API
 75 };
 76 
 77 /**
 78  * @returns {THREE.Object3D} the object3D containing the object
 79 */
 80 tQuery.MD2Character.prototype.container	= function(){
 81 	return this._root;
 82 }
 83 
 84 /**
 85  * @return {Boolean} true if the character is loaded, false otherwise
 86 */
 87 tQuery.MD2Character.prototype.isLoaded	= function(){
 88 	return this._nLoadInProgress === 0 ? true : false;
 89 }
 90 
 91 /**
 92  * Getter/setter for the scale of the object
 93 */
 94 tQuery.MD2Character.prototype.scale	= function(value){
 95 	if( value === undefined )	return this._scale;
 96 	this._scale	= value;
 97 	return this;
 98 }
 99 
100 
101 //////////////////////////////////////////////////////////////////////////////////
102 //		Setter								//
103 //////////////////////////////////////////////////////////////////////////////////
104 
105 /**
106  * @param {Boolean} enable true to enable wireframe, false otherwise
107 */
108 tQuery.MD2Character.prototype.setWireframe = function ( enable )
109 {
110 	// TODO remove the added property on THREE.Mesh
111 	if( enable ){
112 		if ( this._meshBody )	this._meshBody.material	= this._meshBody.materialWireframe;
113 		if ( this._meshWeapon )	this._meshWeapon.material= this._meshWeapon.materialWireframe;
114 	} else {
115 		if ( this._meshBody )	this._meshBody.material	= this._meshBody.materialTexture;
116 		if ( this._meshWeapon )	this._meshWeapon.material= this._meshWeapon.materialTexture;
117 	}
118 	return this;	// for chained API
119 };
120 
121 /**
122  * Set the weapons
123  *
124  * @param {Number} index the index of the animations
125 */
126 tQuery.MD2Character.prototype.setWeapon = function ( index )
127 {
128 	// make all weapons invisible
129 	for ( var i = 0; i < this._weapons.length; i ++ ){
130 		this._weapons[ i ].visible = false;
131 	}
132 	// set the active weapon
133 	var activeWeapon = this._weapons[ index ];
134 
135 	if( activeWeapon ){
136 		activeWeapon.visible	= true;
137 		this._meshWeapon		= activeWeapon;
138 
139 		activeWeapon.playAnimation( this._curAnimation, this.animationFPS );
140 
141 		this._meshWeapon.baseDuration	= this._meshWeapon.duration;
142 
143 		this._meshWeapon.time		= this._meshBody.time;
144 		this._meshWeapon.duration	= this._meshBody.duration;
145 	}
146 	return this;	// for chained API
147 };
148 
149 /**
150  * set the animation. TODO a setter/getter
151  *
152  * @param {string} animationName the animation name to set
153 */
154 tQuery.MD2Character.prototype.animation = function( animationName )
155 {
156 	// for getter
157 	if( animationName === undefined ){
158 		return this._curAnimation;
159 	}
160 	// for setter when the animation is already the same, do nothign
161 	if( animationName === this._curAnimation ){
162 		return this;	// for chained API
163 	}
164 	// sanity check
165 	console.assert( Object.keys(this._meshBody.geometry.animations).indexOf(animationName) !== -1 );
166 	// setter on this._meshBody
167 	if ( this._meshBody ) {
168 		this._meshBody.playAnimation( animationName, this.animationFPS );
169 		this._meshBody.baseDuration	= this._meshBody.duration;
170 	}
171 	// setter on this._meshWeapon
172 	if ( this._meshWeapon ) {
173 		this._meshWeapon.playAnimation( animationName, this.animationFPS );
174 		this._meshWeapon.baseDuration	= this._meshWeapon.duration;
175 		this._meshWeapon.time		= this._meshBody.time;
176 	}
177 	// set the animation itself
178 	this._curAnimation = animationName;
179 	return this;	// for chained API
180 };
181 
182 /**
183  * @param {number} rate the rate to play the object
184 */
185 tQuery.MD2Character.prototype.setPlaybackRate	= function( rate )
186 {
187 	if ( this._meshBody ){
188 		this._meshBody.duration = this._meshBody.baseDuration / rate;
189 	}
190 	if ( this._meshWeapon ){
191 		this._meshWeapon.duration = this._meshWeapon.baseDuration / rate;
192 	}
193 	return this;	// for chained API
194 };
195 
196 /**
197  * @param {Number} index set the index of the skin
198 */
199 tQuery.MD2Character.prototype.setSkin	= function( index )
200 {
201 	if ( this._meshBody && this._meshBody.material.wireframe === false ) {
202 		console.assert( index < this._skinsBody.length );
203 		this._meshBody.material.map	= this._skinsBody[ index ];
204 	}
205 	return this;	// for chained API
206 };
207 
208 //////////////////////////////////////////////////////////////////////////////////
209 //		Loader								//
210 //////////////////////////////////////////////////////////////////////////////////
211 
212 /**
213  * Load the part of your characters
214 */
215 tQuery.MD2Character.prototype.load		= function ( config )
216 {
217 	var _this		= this;
218 	this._nLoadInProgress	= config.weapons.length * 2 + config.skins.length + 1;
219 
220 	var weaponsTextures = []
221 	for ( var i = 0; i < config.weapons.length; i ++ ){
222 		weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
223 	}
224 
225 	// SKINS
226 	this._skinsBody		= this._loadTextures( config.baseUrl + "skins/", config.skins );
227 	this._skinsWeapon	= this._loadTextures( config.baseUrl + "skins/", weaponsTextures );
228 
229 	// BODY
230 	var loader	= new THREE.JSONLoader();
231 
232 	loader.load( config.baseUrl + config.body, function( geometry ) {
233 		geometry.computeBoundingBox();
234 		_this._root.position.y	= - _this._scale * geometry.boundingBox.min.y;
235 
236 		var mesh	= createPart( geometry, _this._skinsBody[ 0 ] );
237 		mesh.scale.set( _this._scale, _this._scale, _this._scale );
238 
239 		_this._root.add( mesh );
240 
241 		_this._meshBody		= mesh;
242 		_this._curAnimation	= geometry.firstAnimation;
243 
244 		_this._checkLoadingComplete();
245 	} );
246 
247 	// WEAPONS
248 	var generateCallback = function( index, name ){
249 		return function( geometry ) {
250 			var mesh	= createPart( geometry, _this._skinsWeapon[ index ] );
251 			mesh.scale.set( _this._scale, _this._scale, _this._scale );
252 			mesh.visible	= false;
253 
254 			mesh.name	= name;
255 
256 			_this._root.add( mesh );
257 
258 			_this._weapons[ index ] = mesh;
259 			_this._meshWeapon = mesh;
260 
261 			_this._checkLoadingComplete();
262 		}.bind(this);
263 	}.bind(this);
264 
265 	for ( var i = 0; i < config.weapons.length; i ++ ) {
266 		var url		= config.baseUrl + config.weapons[ i ][ 0 ];
267 		var callback	= generateCallback( i, config.weapons[ i ][ 0 ] );
268 		loader.load( url, callback );
269 	}
270 
271 	function createPart( geometry, skinMap ) {
272 		geometry.computeMorphNormals();
273 
274 		var whiteMap		= THREE.ImageUtils.generateDataTexture( 1, 1, new THREE.Color( 0xffffff ) );
275 		var materialWireframe	= new THREE.MeshPhongMaterial({
276 			color		: 0xffaa00,
277 			specular	: 0x111111,
278 			shininess	: 50,
279 			wireframe	: true,
280 			shading		: THREE.SmoothShading,
281 			map		: whiteMap,
282 			morphTargets	: true,
283 			morphNormals	: true,
284 			perPixel	: true,
285 			metal		: false
286 		});
287 
288 		var materialTexture	= new THREE.MeshPhongMaterial({
289 			color		: 0xffffff,
290 			specular	: 0x111111,
291 			shininess	: 50,
292 			wireframe	: false,
293 			shading		: THREE.SmoothShading,
294 			map		: skinMap,
295 			morphTargets	: true,
296 			morphNormals	: true,
297 			perPixel	: true,
298 			metal		: false
299 		});
300 		materialTexture.wrapAround = true;
301 
302 		//
303 
304 		var mesh	= new THREE.MorphAnimMesh( geometry, materialTexture );
305 		mesh.rotation.y = -Math.PI/2;
306 
307 		mesh.castShadow		= true;
308 		mesh.receiveShadow	= true;
309 
310 		//
311 
312 		mesh.materialTexture	= materialTexture;
313 		mesh.materialWireframe	= materialWireframe;
314 
315 		//
316 
317 		mesh.parseAnimations();
318 
319 		mesh.playAnimation( geometry.firstAnimation, _this.animationFPS );
320 		mesh.baseDuration	= mesh.duration;
321 
322 		return mesh;
323 	};
324 	return this;	// for chained API
325 };
326 
327 tQuery.MD2Character.prototype._checkLoadingComplete	= function()
328 {
329 	this._nLoadInProgress--;
330 	if( this._nLoadInProgress === 0 ){
331 		this.trigger('loaded');
332 	}
333 }
334 
335 /**
336  * Load a texture and return it
337 */
338 tQuery.MD2Character.prototype._loadTextures	= function( baseUrl, textureUrls )
339 {
340 	var mapping	= new THREE.UVMapping();
341 	var textures	= [];
342 	var callback	= function(){
343 		this._checkLoadingComplete()
344 	}.bind(this);
345 	// load all textureUrls
346 	for( var i = 0; i < textureUrls.length; i ++ ){
347 		var url			= baseUrl + textureUrls[ i ];
348 		var texture		= THREE.ImageUtils.loadTexture( url, mapping, callback);
349 		textures[ i ]		= texture;
350 		textures[ i ].name	= textureUrls[ i ];
351 	}
352 	// return them
353 	return textures;
354 };
355