js/OBJLoader.js

changeset 16
800e8da193a7
equal deleted inserted replaced
15:0760f2016167 16:800e8da193a7
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 };

mercurial