1 /**
  2  * Handle object3D
  3  *
  4  * @class include THREE.Object3D
  5  *
  6  * @param {} object
  7  * @param {THREE.Object3D} rootnode
  8  * @returns {tQuery.*} the tQuery object created
  9 */
 10 tQuery.Object3D	= function(object, root)
 11 {
 12 	// handle the case of selector
 13 	if( typeof object === "string" ){
 14 		object	= tQuery.Object3D._select(object, root);
 15 	}
 16 
 17 	// call parent ctor
 18 	tQuery.Object3D.parent.constructor.call(this, object)
 19 
 20 	// sanity check - all items MUST be THREE.Object3D
 21 	this._lists.forEach(function(item){ console.assert(item instanceof THREE.Object3D); });
 22 };
 23 
 24 /**
 25  * inherit from tQuery.Node
 26 */
 27 tQuery.inherit(tQuery.Object3D, tQuery.Node);
 28 
 29 /**
 30  * Make it pluginable
 31 */
 32 tQuery.pluginsInstanceOn(tQuery.Object3D);
 33 
 34 /**
 35  * define all acceptable attributes for this class
 36 */
 37 tQuery.mixinAttributes(tQuery.Object3D, {
 38 	eulerOrder		: tQuery.convert.toString,
 39 	
 40 	doubleSided		: tQuery.convert.toBoolean,
 41 	flipSided		: tQuery.convert.toBoolean,
 42 	
 43 	rotationAutoUpdate	: tQuery.convert.toBoolean,
 44 	matrixAutoUpdate	: tQuery.convert.toBoolean,
 45 	matrixWorldNeedsUpdate	: tQuery.convert.toBoolean,
 46 	useQuaternion		: tQuery.convert.toBoolean,
 47 
 48 	visible			: tQuery.convert.toBoolean,
 49 
 50 	receiveShadow		: tQuery.convert.toBoolean,
 51 	castShadow		: tQuery.convert.toBoolean
 52 });
 53 
 54 /**
 55  * Traverse the hierarchy of Object3D. 
 56  * 
 57  * @returns {tQuery.Object3D} return the tQuery.Object3D itself
 58 */
 59 tQuery.Object3D.prototype.traverseHierarchy	= function(callback){
 60 	this.each(function(object3d){
 61 		THREE.SceneUtils.traverseHierarchy(object3d, function(object3d){
 62 			callback(object3d);
 63 		});
 64 	});
 65 	return this;	// for chained API
 66 };
 67 
 68 
 69 //////////////////////////////////////////////////////////////////////////////////
 70 //		geometry and material						//
 71 //////////////////////////////////////////////////////////////////////////////////
 72 
 73 /**
 74  * get geometry.
 75  *
 76  * TODO this should be move in tQuery.Mesh
 77  * 
 78  * @returns {tQuery.Geometry} return the geometries from the tQuery.Object3D
 79 */
 80 tQuery.Object3D.prototype.geometry	= function(value){
 81 	var geometries	= [];
 82 	this.each(function(object3d){
 83 		geometries.push(object3d.geometry)
 84 	});
 85 	return new tQuery.Geometry(geometries).back(this);
 86 };
 87 
 88 /**
 89  * get material.
 90  * 
 91  * TODO this should be move in tQuery.Mesh
 92  * 
 93  * @returns {tQuery.Material} return the materials from the tQuery.Object3D
 94 */
 95 tQuery.Object3D.prototype.material	= function(){
 96 	var materials	= [];
 97 	this.each(function(object3d){
 98 		materials.push(object3d.material)
 99 	});
100 	return new tQuery.Material(materials);
101 };
102 
103 
104 /**
105  * Clone a Object3D
106 */
107 tQuery.Object3D.prototype.clone	= function(){
108 	var clones	= [];
109 	this._lists.forEach(function(object3d){
110 		var clone	= THREE.SceneUtils.cloneObject(object3d)
111 		clones.push(clone);
112 	})  
113 	return tQuery(clones)
114 }
115 
116 //////////////////////////////////////////////////////////////////////////////////
117 //			addTo/removeFrom tQuery.World/tQuery.Object3d		//
118 //////////////////////////////////////////////////////////////////////////////////
119 
120 /**
121  * add all matched elements to a world
122  * 
123  * @param {tQuery.World or tQuery.Object3D} target object to which add it
124  * @returns {tQuery.Object3D} chained API
125 */
126 tQuery.Object3D.prototype.addTo	= function(target)
127 {
128 	console.assert( target instanceof tQuery.World || target instanceof tQuery.Object3D || target instanceof THREE.Object3D )
129 	this.each(function(object3d){
130 		target.add(object3d)
131 	}.bind(this));
132 	return this;
133 }
134 
135 /**
136  * remove all matched elements from a world
137  * 
138  * @param {tQuery.World or tQuery.Object3D} target object to which add it
139  * @returns {tQuery.Object3D} chained API
140 */
141 tQuery.Object3D.prototype.removeFrom	= function(target)
142 {
143 	console.assert( target instanceof tQuery.World || target instanceof tQuery.Object3D )
144 	this.each(function(tObject3d){
145 		target.remove(tObject3d)
146 	}.bind(this));
147 	return this;
148 }
149 
150 /**
151  * remove an element from the parent to which it is attached
152  * 
153  * @returns {tQuery.Object3D} chained API
154 */
155 tQuery.Object3D.prototype.detach	= function()
156 {
157 	this.each(function(object3D){
158 		if( !object3D.parent )	return;
159 		object3D.parent.remove(object3D)
160 	}.bind(this));
161 	return this;
162 }
163 
164 //////////////////////////////////////////////////////////////////////////////////
165 //			addTo/removeFrom tQuery.World/tQuery.Object3d		//
166 //////////////////////////////////////////////////////////////////////////////////
167 
168 /**
169  * add all matched elements to a world
170  * 
171  * @param {tQuery.Object3D} target object to which add it
172  * @returns {tQuery.Object3D} chained API
173 */
174 tQuery.Object3D.prototype.add	= function(object3D)
175 {
176 	if( object3D instanceof tQuery.Object3D ){
177 		this.each(function(object1){
178 			object3D.each(function(object2){
179 				object1.add(object2);
180 			})
181 		}.bind(this));
182 	}else if( object3D instanceof THREE.Object3D ){
183 		this.each(function(object1){
184 			object1.add(object3D);
185 		});
186 	}else	console.assert(false, "invalid parameter");
187 	return this;
188 }
189 
190 /**
191  * remove all matched elements from a world
192  * 
193  * @param {tQuery.Object3D} object3d the object to add in this object
194  * @returns {tQuery.Object3D} chained API
195 */
196 tQuery.Object3D.prototype.remove	= function(object3D)
197 {
198 	if( object3D instanceof tQuery.Object3D ){
199 		this.each(function(object1){
200 			object3D.each(function(object2){
201 				object1.remove(object2);
202 			})
203 		}.bind(this));
204 	}else if( object3D instanceof THREE.Object3D ){
205 		this.each(function(object1){
206 			object1.remove(object3D);
207 		});
208 	}else	console.assert(false, "invalid parameter");		
209 	return this;
210 }
211 
212 
213 //////////////////////////////////////////////////////////////////////////////////
214 //		Handle dom attribute						//
215 //////////////////////////////////////////////////////////////////////////////////
216 
217 /**
218  * Getter/Setter for the id of the matched elements
219 */
220 tQuery.Object3D.prototype.id	= function(value)
221 {
222 	// sanity check 
223 	console.assert(this.length <= 1, "tQuery.Object3D.id used on multi-elements" );
224 	if( value !== undefined ){
225 		if( this.length > 0 ){
226 			var object3d	= this.get(0);
227 			object3d._tqId	= value;
228 		}
229 		return this;
230 	}else{
231 		if( this.length > 0 ){
232 			var object3d	= this.get(0);
233 			return object3d._tqId;
234 		}
235 		return undefined;
236 	}
237 };
238 
239 /**
240  * add a class to all matched elements
241  * 
242  * @param {string} className the name of the class to add
243  * @returns {tQuery.Object3D} chained API
244 */
245 tQuery.Object3D.prototype.addClass	= function(className){
246 	this.each(function(tObject3d){
247 		// init ._tqClasses if needed
248 		tObject3d._tqClasses	= tObject3d._tqClasses	|| '';
249 
250 		if( tQuery.Object3D._hasClassOne(tObject3d, className) )	return;
251 		
252 		tObject3d._tqClasses	+= ' '+className;
253 	}.bind(this));
254 	return this;
255 };
256 
257 /**
258  * remove a class to all matched elements
259  * 
260  * @param {string} className the name of the class to remove
261  * @returns {tQuery.Object3D} chained API
262 */
263 tQuery.Object3D.prototype.removeClass	= function(className){
264 	this.each(function(tObject3d){
265 		tQuery.Object3D._removeClassOne(tObject3d, className);
266 	}.bind(this));
267 	return this;	// for chained api
268 };
269 
270 /**
271  * return true if any of the matched elements has this class
272  *
273  * @param {string} className the name of the class
274  * @returns {tQuery.Object3D} true if any of the matched elements has this class, false overwise
275 */
276 tQuery.Object3D.prototype.hasClass	= function(className){
277 	var completed	= this.each(function(object3d){
278 		// init ._tqClasses if needed
279 		object3d._tqClasses	= object3d._tqClasses	|| '';
280 
281 		var hasClass	= tQuery.Object3D._hasClassOne(object3d, className);
282 		return hasClass ? false : true;
283 	}.bind(this));
284 	return completed ? false : true;
285 };
286 
287 tQuery.Object3D._hasClassOne	= function(object3d, className){
288 	if( object3d._tqClasses === undefined )	return false;
289 	var classes	= object3d._tqClasses;
290 	var re		= new RegExp('(^| |\t)+('+className+')($| |\t)+');
291 	return classes.match(re) ? true : false;
292 };
293 
294 tQuery.Object3D._removeClassOne	= function(object3d, className){
295 	if( object3d._tqClasses === undefined )	return;
296 	var re		= new RegExp('(^| |\t)('+className+')($| |\t)');
297 	object3d._tqClasses	= object3d._tqClasses.replace(re, ' ');
298 };
299 
300 //////////////////////////////////////////////////////////////////////////////////
301 //			handling selection					//
302 //////////////////////////////////////////////////////////////////////////////////
303 
304 tQuery.Object3D._select	= function(selector, root){
305 	// handle parameter
306 	root		= root	|| tQuery.world.tScene();
307 	if( root instanceof tQuery.Object3D )	root	= root.get(0)
308 	var selectItems	= selector.split(' ').filter(function(v){ return v.length > 0;})
309 	
310 	// sanity check
311 	console.assert(root instanceof THREE.Object3D);
312 
313 	var lists	= [];
314 	root.children.forEach(function(child){
315 		var nodes	= this._crawls(child, selectItems);
316 		// FIXME reallocate the array without need
317 		lists		= lists.concat(nodes);
318 	}.bind(this));	
319 	return lists;
320 }
321 
322 tQuery.Object3D._crawls	= function(root, selectItems)
323 {
324 	var result	= [];
325 //console.log("crawl", root, selectItems)
326 	console.assert( selectItems.length >= 1 );
327 	var match	= this._selectItemMatch(root, selectItems[0]);
328 //console.log("  match", match)
329 	var nextSelect	= match ? selectItems.slice(1) : selectItems;
330 //console.log("  nextSelect", nextSelect)
331 
332 	if( nextSelect.length === 0 )	return [root];
333 
334 	root.children.forEach(function(child){
335 		var nodes	= this._crawls(child, nextSelect);
336 		// FIXME reallocate the array without need
337 		result		= result.concat(nodes);
338 	}.bind(this));
339 
340 	return result;
341 }
342 
343 // all the geometries keywords
344 tQuery.Object3D._selectableGeometries	= Object.keys(THREE).filter(function(value){
345 	return value.match(/.+Geometry$/);}).map(function(value){ return value.replace(/Geometry$/,'').toLowerCase();
346 });
347 
348 // all the light keywords
349 tQuery.Object3D._selectableLights	= Object.keys(THREE).filter(function(value){
350 	return value.match(/.+Light$/);}).map(function(value){ return value.replace(/Light$/,'').toLowerCase();
351 });
352 
353 tQuery.Object3D._selectableClasses	= ['mesh', 'light'];
354 
355 tQuery.Object3D._selectItemMatch	= function(object3d, selectItem)
356 {
357 	// sanity check
358 	console.assert( object3d instanceof THREE.Object3D );
359 	console.assert( typeof selectItem === 'string' );
360 
361 	// parse selectItem into subItems
362 	var subItems	= selectItem.match(new RegExp("([^.#]+|\.[^.#]+|\#[^.#]+)", "g"));;
363 
364 	// go thru each subItem
365 	var completed	= tQuery.each(subItems, function(subItem){
366 		var meta	= subItem.charAt(0);
367 		var suffix	= subItem.slice(1);
368 		//console.log("meta", meta, subItem, suffix, object3d)
369 		if( meta === "." ){
370 			var hasClass	= tQuery.Object3D._hasClassOne(object3d, suffix);
371 			return hasClass ? true : false;
372 		}else if( meta === "#" ){
373 			return object3d._tqId === suffix ? true : false;
374 		}else if( subItem === "*" ){
375 			return true;
376 		}else if( this._selectableGeometries.indexOf(subItem) !== -1 ){	// Handle geometries
377 			var geometry	= object3d.geometry;
378 			var className	= subItem.charAt(0).toUpperCase() + subItem.slice(1) + "Geometry";
379 			return geometry instanceof THREE[className];
380 		}else if( this._selectableLights.indexOf(subItem) !== -1 ){	// Handle light
381 			var className	= subItem.charAt(0).toUpperCase() + subItem.slice(1) + "Light";
382 			return object3d instanceof THREE[className];
383 		}else if( this._selectableClasses.indexOf(subItem) !== -1 ){	// Handle light
384 			var className	= subItem.charAt(0).toUpperCase() + subItem.slice(1);
385 			return object3d instanceof THREE[className];
386 		}
387 		// this point should never be reached
388 		console.assert(false, "invalid selector: "+subItem);
389 		return true;
390 	}.bind(this));
391 
392 	return completed ? true : false;
393 }
394