|
1 /** |
|
2 * @author mrdoob / http://mrdoob.com/ |
|
3 */ |
|
4 |
|
5 THREE.OBJLoader = function ( manager ) { |
|
6 |
|
7 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; |
|
8 |
|
9 this.materials = null; |
|
10 |
|
11 this.regexp = { |
|
12 // v float float float |
|
13 vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, |
|
14 // vn float float float |
|
15 normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, |
|
16 // vt float float |
|
17 uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, |
|
18 // f vertex vertex vertex |
|
19 face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, |
|
20 // f vertex/uv vertex/uv vertex/uv |
|
21 face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, |
|
22 // f vertex/uv/normal vertex/uv/normal vertex/uv/normal |
|
23 face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, |
|
24 // f vertex//normal vertex//normal vertex//normal |
|
25 face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, |
|
26 // o object_name | g group_name |
|
27 object_pattern : /^[og]\s*(.+)?/, |
|
28 // s boolean |
|
29 smoothing_pattern : /^s\s+(\d+|on|off)/, |
|
30 // mtllib file_reference |
|
31 material_library_pattern : /^mtllib /, |
|
32 // usemtl material_name |
|
33 material_use_pattern : /^usemtl / |
|
34 }; |
|
35 |
|
36 }; |
|
37 |
|
38 THREE.OBJLoader.prototype = { |
|
39 |
|
40 constructor: THREE.OBJLoader, |
|
41 |
|
42 load: function ( url, onLoad, onProgress, onError ) { |
|
43 |
|
44 var scope = this; |
|
45 |
|
46 var loader = new THREE.FileLoader( scope.manager ); |
|
47 loader.setPath( this.path ); |
|
48 loader.load( url, function ( text ) { |
|
49 |
|
50 onLoad( scope.parse( text ) ); |
|
51 |
|
52 }, onProgress, onError ); |
|
53 |
|
54 }, |
|
55 |
|
56 setPath: function ( value ) { |
|
57 |
|
58 this.path = value; |
|
59 |
|
60 }, |
|
61 |
|
62 setMaterials: function ( materials ) { |
|
63 |
|
64 this.materials = materials; |
|
65 |
|
66 }, |
|
67 |
|
68 _createParserState : function () { |
|
69 |
|
70 var state = { |
|
71 objects : [], |
|
72 object : {}, |
|
73 |
|
74 vertices : [], |
|
75 normals : [], |
|
76 uvs : [], |
|
77 |
|
78 materialLibraries : [], |
|
79 |
|
80 startObject: function ( name, fromDeclaration ) { |
|
81 |
|
82 // If the current object (initial from reset) is not from a g/o declaration in the parsed |
|
83 // file. We need to use it for the first parsed g/o to keep things in sync. |
|
84 if ( this.object && this.object.fromDeclaration === false ) { |
|
85 |
|
86 this.object.name = name; |
|
87 this.object.fromDeclaration = ( fromDeclaration !== false ); |
|
88 return; |
|
89 |
|
90 } |
|
91 |
|
92 var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); |
|
93 |
|
94 if ( this.object && typeof this.object._finalize === 'function' ) { |
|
95 |
|
96 this.object._finalize( true ); |
|
97 |
|
98 } |
|
99 |
|
100 this.object = { |
|
101 name : name || '', |
|
102 fromDeclaration : ( fromDeclaration !== false ), |
|
103 |
|
104 geometry : { |
|
105 vertices : [], |
|
106 normals : [], |
|
107 uvs : [] |
|
108 }, |
|
109 materials : [], |
|
110 smooth : true, |
|
111 |
|
112 startMaterial : function( name, libraries ) { |
|
113 |
|
114 var previous = this._finalize( false ); |
|
115 |
|
116 // New usemtl declaration overwrites an inherited material, except if faces were declared |
|
117 // after the material, then it must be preserved for proper MultiMaterial continuation. |
|
118 if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { |
|
119 |
|
120 this.materials.splice( previous.index, 1 ); |
|
121 |
|
122 } |
|
123 |
|
124 var material = { |
|
125 index : this.materials.length, |
|
126 name : name || '', |
|
127 mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), |
|
128 smooth : ( previous !== undefined ? previous.smooth : this.smooth ), |
|
129 groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), |
|
130 groupEnd : -1, |
|
131 groupCount : -1, |
|
132 inherited : false, |
|
133 |
|
134 clone : function( index ) { |
|
135 var cloned = { |
|
136 index : ( typeof index === 'number' ? index : this.index ), |
|
137 name : this.name, |
|
138 mtllib : this.mtllib, |
|
139 smooth : this.smooth, |
|
140 groupStart : 0, |
|
141 groupEnd : -1, |
|
142 groupCount : -1, |
|
143 inherited : false |
|
144 }; |
|
145 cloned.clone = this.clone.bind(cloned); |
|
146 return cloned; |
|
147 } |
|
148 }; |
|
149 |
|
150 this.materials.push( material ); |
|
151 |
|
152 return material; |
|
153 |
|
154 }, |
|
155 |
|
156 currentMaterial : function() { |
|
157 |
|
158 if ( this.materials.length > 0 ) { |
|
159 return this.materials[ this.materials.length - 1 ]; |
|
160 } |
|
161 |
|
162 return undefined; |
|
163 |
|
164 }, |
|
165 |
|
166 _finalize : function( end ) { |
|
167 |
|
168 var lastMultiMaterial = this.currentMaterial(); |
|
169 if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { |
|
170 |
|
171 lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; |
|
172 lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; |
|
173 lastMultiMaterial.inherited = false; |
|
174 |
|
175 } |
|
176 |
|
177 // Ignore objects tail materials if no face declarations followed them before a new o/g started. |
|
178 if ( end && this.materials.length > 1 ) { |
|
179 |
|
180 for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) { |
|
181 if ( this.materials[mi].groupCount <= 0 ) { |
|
182 this.materials.splice( mi, 1 ); |
|
183 } |
|
184 } |
|
185 |
|
186 } |
|
187 |
|
188 // Guarantee at least one empty material, this makes the creation later more straight forward. |
|
189 if ( end && this.materials.length === 0 ) { |
|
190 |
|
191 this.materials.push({ |
|
192 name : '', |
|
193 smooth : this.smooth |
|
194 }); |
|
195 |
|
196 } |
|
197 |
|
198 return lastMultiMaterial; |
|
199 |
|
200 } |
|
201 }; |
|
202 |
|
203 // Inherit previous objects material. |
|
204 // Spec tells us that a declared material must be set to all objects until a new material is declared. |
|
205 // If a usemtl declaration is encountered while this new object is being parsed, it will |
|
206 // overwrite the inherited material. Exception being that there was already face declarations |
|
207 // to the inherited material, then it will be preserved for proper MultiMaterial continuation. |
|
208 |
|
209 if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { |
|
210 |
|
211 var declared = previousMaterial.clone( 0 ); |
|
212 declared.inherited = true; |
|
213 this.object.materials.push( declared ); |
|
214 |
|
215 } |
|
216 |
|
217 this.objects.push( this.object ); |
|
218 |
|
219 }, |
|
220 |
|
221 finalize : function() { |
|
222 |
|
223 if ( this.object && typeof this.object._finalize === 'function' ) { |
|
224 |
|
225 this.object._finalize( true ); |
|
226 |
|
227 } |
|
228 |
|
229 }, |
|
230 |
|
231 parseVertexIndex: function ( value, len ) { |
|
232 |
|
233 var index = parseInt( value, 10 ); |
|
234 return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; |
|
235 |
|
236 }, |
|
237 |
|
238 parseNormalIndex: function ( value, len ) { |
|
239 |
|
240 var index = parseInt( value, 10 ); |
|
241 return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; |
|
242 |
|
243 }, |
|
244 |
|
245 parseUVIndex: function ( value, len ) { |
|
246 |
|
247 var index = parseInt( value, 10 ); |
|
248 return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; |
|
249 |
|
250 }, |
|
251 |
|
252 addVertex: function ( a, b, c ) { |
|
253 |
|
254 var src = this.vertices; |
|
255 var dst = this.object.geometry.vertices; |
|
256 |
|
257 dst.push( src[ a + 0 ] ); |
|
258 dst.push( src[ a + 1 ] ); |
|
259 dst.push( src[ a + 2 ] ); |
|
260 dst.push( src[ b + 0 ] ); |
|
261 dst.push( src[ b + 1 ] ); |
|
262 dst.push( src[ b + 2 ] ); |
|
263 dst.push( src[ c + 0 ] ); |
|
264 dst.push( src[ c + 1 ] ); |
|
265 dst.push( src[ c + 2 ] ); |
|
266 |
|
267 }, |
|
268 |
|
269 addVertexLine: function ( a ) { |
|
270 |
|
271 var src = this.vertices; |
|
272 var dst = this.object.geometry.vertices; |
|
273 |
|
274 dst.push( src[ a + 0 ] ); |
|
275 dst.push( src[ a + 1 ] ); |
|
276 dst.push( src[ a + 2 ] ); |
|
277 |
|
278 }, |
|
279 |
|
280 addNormal : function ( a, b, c ) { |
|
281 |
|
282 var src = this.normals; |
|
283 var dst = this.object.geometry.normals; |
|
284 |
|
285 dst.push( src[ a + 0 ] ); |
|
286 dst.push( src[ a + 1 ] ); |
|
287 dst.push( src[ a + 2 ] ); |
|
288 dst.push( src[ b + 0 ] ); |
|
289 dst.push( src[ b + 1 ] ); |
|
290 dst.push( src[ b + 2 ] ); |
|
291 dst.push( src[ c + 0 ] ); |
|
292 dst.push( src[ c + 1 ] ); |
|
293 dst.push( src[ c + 2 ] ); |
|
294 |
|
295 }, |
|
296 |
|
297 addUV: function ( a, b, c ) { |
|
298 |
|
299 var src = this.uvs; |
|
300 var dst = this.object.geometry.uvs; |
|
301 |
|
302 dst.push( src[ a + 0 ] ); |
|
303 dst.push( src[ a + 1 ] ); |
|
304 dst.push( src[ b + 0 ] ); |
|
305 dst.push( src[ b + 1 ] ); |
|
306 dst.push( src[ c + 0 ] ); |
|
307 dst.push( src[ c + 1 ] ); |
|
308 |
|
309 }, |
|
310 |
|
311 addUVLine: function ( a ) { |
|
312 |
|
313 var src = this.uvs; |
|
314 var dst = this.object.geometry.uvs; |
|
315 |
|
316 dst.push( src[ a + 0 ] ); |
|
317 dst.push( src[ a + 1 ] ); |
|
318 |
|
319 }, |
|
320 |
|
321 addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { |
|
322 |
|
323 var vLen = this.vertices.length; |
|
324 |
|
325 var ia = this.parseVertexIndex( a, vLen ); |
|
326 var ib = this.parseVertexIndex( b, vLen ); |
|
327 var ic = this.parseVertexIndex( c, vLen ); |
|
328 var id; |
|
329 |
|
330 if ( d === undefined ) { |
|
331 |
|
332 this.addVertex( ia, ib, ic ); |
|
333 |
|
334 } else { |
|
335 |
|
336 id = this.parseVertexIndex( d, vLen ); |
|
337 |
|
338 this.addVertex( ia, ib, id ); |
|
339 this.addVertex( ib, ic, id ); |
|
340 |
|
341 } |
|
342 |
|
343 if ( ua !== undefined ) { |
|
344 |
|
345 var uvLen = this.uvs.length; |
|
346 |
|
347 ia = this.parseUVIndex( ua, uvLen ); |
|
348 ib = this.parseUVIndex( ub, uvLen ); |
|
349 ic = this.parseUVIndex( uc, uvLen ); |
|
350 |
|
351 if ( d === undefined ) { |
|
352 |
|
353 this.addUV( ia, ib, ic ); |
|
354 |
|
355 } else { |
|
356 |
|
357 id = this.parseUVIndex( ud, uvLen ); |
|
358 |
|
359 this.addUV( ia, ib, id ); |
|
360 this.addUV( ib, ic, id ); |
|
361 |
|
362 } |
|
363 |
|
364 } |
|
365 |
|
366 if ( na !== undefined ) { |
|
367 |
|
368 // Normals are many times the same. If so, skip function call and parseInt. |
|
369 var nLen = this.normals.length; |
|
370 ia = this.parseNormalIndex( na, nLen ); |
|
371 |
|
372 ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); |
|
373 ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); |
|
374 |
|
375 if ( d === undefined ) { |
|
376 |
|
377 this.addNormal( ia, ib, ic ); |
|
378 |
|
379 } else { |
|
380 |
|
381 id = this.parseNormalIndex( nd, nLen ); |
|
382 |
|
383 this.addNormal( ia, ib, id ); |
|
384 this.addNormal( ib, ic, id ); |
|
385 |
|
386 } |
|
387 |
|
388 } |
|
389 |
|
390 }, |
|
391 |
|
392 addLineGeometry: function ( vertices, uvs ) { |
|
393 |
|
394 this.object.geometry.type = 'Line'; |
|
395 |
|
396 var vLen = this.vertices.length; |
|
397 var uvLen = this.uvs.length; |
|
398 |
|
399 for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { |
|
400 |
|
401 this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); |
|
402 |
|
403 } |
|
404 |
|
405 for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { |
|
406 |
|
407 this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); |
|
408 |
|
409 } |
|
410 |
|
411 } |
|
412 |
|
413 }; |
|
414 |
|
415 state.startObject( '', false ); |
|
416 |
|
417 return state; |
|
418 |
|
419 }, |
|
420 |
|
421 parse: function ( text ) { |
|
422 |
|
423 console.time( 'OBJLoader' ); |
|
424 |
|
425 var state = this._createParserState(); |
|
426 |
|
427 if ( text.indexOf( '\r\n' ) !== - 1 ) { |
|
428 |
|
429 // This is faster than String.split with regex that splits on both |
|
430 text = text.replace( /\r\n/g, '\n' ); |
|
431 |
|
432 } |
|
433 |
|
434 if ( text.indexOf( '\\\n' ) !== - 1) { |
|
435 |
|
436 // join lines separated by a line continuation character (\) |
|
437 text = text.replace( /\\\n/g, '' ); |
|
438 |
|
439 } |
|
440 |
|
441 var lines = text.split( '\n' ); |
|
442 var line = '', lineFirstChar = '', lineSecondChar = ''; |
|
443 var lineLength = 0; |
|
444 var result = []; |
|
445 |
|
446 // Faster to just trim left side of the line. Use if available. |
|
447 var trimLeft = ( typeof ''.trimLeft === 'function' ); |
|
448 |
|
449 for ( var i = 0, l = lines.length; i < l; i ++ ) { |
|
450 |
|
451 line = lines[ i ]; |
|
452 |
|
453 line = trimLeft ? line.trimLeft() : line.trim(); |
|
454 |
|
455 lineLength = line.length; |
|
456 |
|
457 if ( lineLength === 0 ) continue; |
|
458 |
|
459 lineFirstChar = line.charAt( 0 ); |
|
460 |
|
461 // @todo invoke passed in handler if any |
|
462 if ( lineFirstChar === '#' ) continue; |
|
463 |
|
464 if ( lineFirstChar === 'v' ) { |
|
465 |
|
466 lineSecondChar = line.charAt( 1 ); |
|
467 |
|
468 if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { |
|
469 |
|
470 // 0 1 2 3 |
|
471 // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] |
|
472 |
|
473 state.vertices.push( |
|
474 parseFloat( result[ 1 ] ), |
|
475 parseFloat( result[ 2 ] ), |
|
476 parseFloat( result[ 3 ] ) |
|
477 ); |
|
478 |
|
479 } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { |
|
480 |
|
481 // 0 1 2 3 |
|
482 // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] |
|
483 |
|
484 state.normals.push( |
|
485 parseFloat( result[ 1 ] ), |
|
486 parseFloat( result[ 2 ] ), |
|
487 parseFloat( result[ 3 ] ) |
|
488 ); |
|
489 |
|
490 } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { |
|
491 |
|
492 // 0 1 2 |
|
493 // ["vt 0.1 0.2", "0.1", "0.2"] |
|
494 |
|
495 state.uvs.push( |
|
496 parseFloat( result[ 1 ] ), |
|
497 parseFloat( result[ 2 ] ) |
|
498 ); |
|
499 |
|
500 } else { |
|
501 |
|
502 throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); |
|
503 |
|
504 } |
|
505 |
|
506 } else if ( lineFirstChar === "f" ) { |
|
507 |
|
508 if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { |
|
509 |
|
510 // f vertex/uv/normal vertex/uv/normal vertex/uv/normal |
|
511 // 0 1 2 3 4 5 6 7 8 9 10 11 12 |
|
512 // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] |
|
513 |
|
514 state.addFace( |
|
515 result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], |
|
516 result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], |
|
517 result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] |
|
518 ); |
|
519 |
|
520 } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { |
|
521 |
|
522 // f vertex/uv vertex/uv vertex/uv |
|
523 // 0 1 2 3 4 5 6 7 8 |
|
524 // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] |
|
525 |
|
526 state.addFace( |
|
527 result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], |
|
528 result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] |
|
529 ); |
|
530 |
|
531 } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { |
|
532 |
|
533 // f vertex//normal vertex//normal vertex//normal |
|
534 // 0 1 2 3 4 5 6 7 8 |
|
535 // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] |
|
536 |
|
537 state.addFace( |
|
538 result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], |
|
539 undefined, undefined, undefined, undefined, |
|
540 result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] |
|
541 ); |
|
542 |
|
543 } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { |
|
544 |
|
545 // f vertex vertex vertex |
|
546 // 0 1 2 3 4 |
|
547 // ["f 1 2 3", "1", "2", "3", undefined] |
|
548 |
|
549 state.addFace( |
|
550 result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] |
|
551 ); |
|
552 |
|
553 } else { |
|
554 |
|
555 throw new Error( "Unexpected face line: '" + line + "'" ); |
|
556 |
|
557 } |
|
558 |
|
559 } else if ( lineFirstChar === "l" ) { |
|
560 |
|
561 var lineParts = line.substring( 1 ).trim().split( " " ); |
|
562 var lineVertices = [], lineUVs = []; |
|
563 |
|
564 if ( line.indexOf( "/" ) === - 1 ) { |
|
565 |
|
566 lineVertices = lineParts; |
|
567 |
|
568 } else { |
|
569 |
|
570 for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { |
|
571 |
|
572 var parts = lineParts[ li ].split( "/" ); |
|
573 |
|
574 if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); |
|
575 if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); |
|
576 |
|
577 } |
|
578 |
|
579 } |
|
580 state.addLineGeometry( lineVertices, lineUVs ); |
|
581 |
|
582 } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { |
|
583 |
|
584 // o object_name |
|
585 // or |
|
586 // g group_name |
|
587 |
|
588 // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 |
|
589 // var name = result[ 0 ].substr( 1 ).trim(); |
|
590 var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); |
|
591 |
|
592 state.startObject( name ); |
|
593 |
|
594 } else if ( this.regexp.material_use_pattern.test( line ) ) { |
|
595 |
|
596 // material |
|
597 |
|
598 state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); |
|
599 |
|
600 } else if ( this.regexp.material_library_pattern.test( line ) ) { |
|
601 |
|
602 // mtl file |
|
603 |
|
604 state.materialLibraries.push( line.substring( 7 ).trim() ); |
|
605 |
|
606 } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { |
|
607 |
|
608 // smooth shading |
|
609 |
|
610 // @todo Handle files that have varying smooth values for a set of faces inside one geometry, |
|
611 // but does not define a usemtl for each face set. |
|
612 // This should be detected and a dummy material created (later MultiMaterial and geometry groups). |
|
613 // This requires some care to not create extra material on each smooth value for "normal" obj files. |
|
614 // where explicit usemtl defines geometry groups. |
|
615 // Example asset: examples/models/obj/cerberus/Cerberus.obj |
|
616 |
|
617 var value = result[ 1 ].trim().toLowerCase(); |
|
618 state.object.smooth = ( value === '1' || value === 'on' ); |
|
619 |
|
620 var material = state.object.currentMaterial(); |
|
621 if ( material ) { |
|
622 |
|
623 material.smooth = state.object.smooth; |
|
624 |
|
625 } |
|
626 |
|
627 } else { |
|
628 |
|
629 // Handle null terminated files without exception |
|
630 if ( line === '\0' ) continue; |
|
631 |
|
632 throw new Error( "Unexpected line: '" + line + "'" ); |
|
633 |
|
634 } |
|
635 |
|
636 } |
|
637 |
|
638 state.finalize(); |
|
639 |
|
640 var container = new THREE.Group(); |
|
641 container.materialLibraries = [].concat( state.materialLibraries ); |
|
642 |
|
643 for ( var i = 0, l = state.objects.length; i < l; i ++ ) { |
|
644 |
|
645 var object = state.objects[ i ]; |
|
646 var geometry = object.geometry; |
|
647 var materials = object.materials; |
|
648 var isLine = ( geometry.type === 'Line' ); |
|
649 |
|
650 // Skip o/g line declarations that did not follow with any faces |
|
651 if ( geometry.vertices.length === 0 ) continue; |
|
652 |
|
653 var buffergeometry = new THREE.BufferGeometry(); |
|
654 |
|
655 buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); |
|
656 |
|
657 if ( geometry.normals.length > 0 ) { |
|
658 |
|
659 buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); |
|
660 |
|
661 } else { |
|
662 |
|
663 buffergeometry.computeVertexNormals(); |
|
664 |
|
665 } |
|
666 |
|
667 if ( geometry.uvs.length > 0 ) { |
|
668 |
|
669 buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); |
|
670 |
|
671 } |
|
672 |
|
673 // Create materials |
|
674 |
|
675 var createdMaterials = []; |
|
676 |
|
677 for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { |
|
678 |
|
679 var sourceMaterial = materials[mi]; |
|
680 var material = undefined; |
|
681 |
|
682 if ( this.materials !== null ) { |
|
683 |
|
684 material = this.materials.create( sourceMaterial.name ); |
|
685 |
|
686 // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. |
|
687 if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { |
|
688 |
|
689 var materialLine = new THREE.LineBasicMaterial(); |
|
690 materialLine.copy( material ); |
|
691 material = materialLine; |
|
692 |
|
693 } |
|
694 |
|
695 } |
|
696 |
|
697 if ( ! material ) { |
|
698 |
|
699 material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); |
|
700 material.name = sourceMaterial.name; |
|
701 |
|
702 } |
|
703 |
|
704 material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; |
|
705 |
|
706 createdMaterials.push(material); |
|
707 |
|
708 } |
|
709 |
|
710 // Create mesh |
|
711 |
|
712 var mesh; |
|
713 |
|
714 if ( createdMaterials.length > 1 ) { |
|
715 |
|
716 for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { |
|
717 |
|
718 var sourceMaterial = materials[mi]; |
|
719 buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); |
|
720 |
|
721 } |
|
722 |
|
723 var multiMaterial = new THREE.MultiMaterial( createdMaterials ); |
|
724 mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) ); |
|
725 |
|
726 } else { |
|
727 |
|
728 mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); |
|
729 } |
|
730 |
|
731 mesh.name = object.name; |
|
732 |
|
733 container.add( mesh ); |
|
734 |
|
735 } |
|
736 |
|
737 console.timeEnd( 'OBJLoader' ); |
|
738 |
|
739 return container; |
|
740 |
|
741 } |
|
742 |
|
743 }; |