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