1/** 2 * @author mrdoob / http://mrdoob.com/ 3 */ 4 5THREEW.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 : THREEW.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 THREEW.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 //fuzzoid mod 411 this.byteLength = text.length; 412 413 var state = new ParserState(); 414 415 if ( text.indexOf( '\r\n' ) !== - 1 ) { 416 417 // This is faster than String.split with regex that splits on both 418 text = text.replace( /\r\n/g, '\n' ); 419 420 } 421 422 if ( text.indexOf( '\\\n' ) !== - 1 ) { 423 424 // join lines separated by a line continuation character (\) 425 text = text.replace( /\\\n/g, '' ); 426 427 } 428 429 var lines = text.split( '\n' ); 430 var line = '', lineFirstChar = ''; 431 var lineLength = 0; 432 var result = []; 433 434 // Faster to just trim left side of the line. Use if available. 435 var trimLeft = ( typeof ''.trimLeft === 'function' ); 436 437 for ( var i = 0, l = lines.length; i < l; i ++ ) { 438 439 line = lines[ i ]; 440 441 line = trimLeft ? line.trimLeft() : line.trim(); 442 443 lineLength = line.length; 444 445 if ( lineLength === 0 ) continue; 446 447 lineFirstChar = line.charAt( 0 ); 448 449 // @todo invoke passed in handler if any 450 if ( lineFirstChar === '#' ) continue; 451 452 if ( lineFirstChar === 'v' ) { 453 454 var data = line.split( /\s+/ ); 455 456 switch ( data[ 0 ] ) { 457 458 case 'v': 459 state.vertices.push( 460 parseFloat( data[ 1 ] ), 461 parseFloat( data[ 2 ] ), 462 parseFloat( data[ 3 ] ) 463 ); 464 if ( data.length === 8 ) { 465 466 state.colors.push( 467 parseFloat( data[ 4 ] ), 468 parseFloat( data[ 5 ] ), 469 parseFloat( data[ 6 ] ) 470 471 ); 472 473 } 474 break; 475 case 'vn': 476 state.normals.push( 477 parseFloat( data[ 1 ] ), 478 parseFloat( data[ 2 ] ), 479 parseFloat( data[ 3 ] ) 480 ); 481 break; 482 case 'vt': 483 state.uvs.push( 484 parseFloat( data[ 1 ] ), 485 parseFloat( data[ 2 ] ) 486 ); 487 break; 488 489 } 490 491 } else if ( lineFirstChar === 'f' ) { 492 493 var lineData = line.substr( 1 ).trim(); 494 var vertexData = lineData.split( /\s+/ ); 495 var faceVertices = []; 496 497 // Parse the face vertex data into an easy to work with format 498 499 for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { 500 501 var vertex = vertexData[ j ]; 502 503 if ( vertex.length > 0 ) { 504 505 var vertexParts = vertex.split( '/' ); 506 faceVertices.push( vertexParts ); 507 508 } 509 510 } 511 512 // Draw an edge between the first vertex and all subsequent vertices to form an n-gon 513 514 var v1 = faceVertices[ 0 ]; 515 516 for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { 517 518 var v2 = faceVertices[ j ]; 519 var v3 = faceVertices[ j + 1 ]; 520 521 state.addFace( 522 v1[ 0 ], v2[ 0 ], v3[ 0 ], 523 v1[ 1 ], v2[ 1 ], v3[ 1 ],
524 v1[ 2 ], v2[ 2 ], v3[ 2 ] 525 ); 526 527 } 528 529 } else if ( lineFirstChar === 'l' ) { 530 531 var lineParts = line.substring( 1 ).trim().split( " " ); 532 var lineVertices = [], lineUVs = []; 533 534 if ( line.indexOf( "/" ) === - 1 ) { 535 536 lineVertices = lineParts; 537 538 } else { 539 540 for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 541 542 var parts = lineParts[ li ].split( "/" ); 543 544 if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 545 if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 546 547 } 548 549 } 550 state.addLineGeometry( lineVertices, lineUVs ); 551 552 } else if ( lineFirstChar === 'p' ) { 553 554 var lineData = line.substr( 1 ).trim(); 555 var pointData = lineData.split( " " ); 556 557 state.addPointGeometry( pointData ); 558 559 } else if ( ( result = object_pattern.exec( line ) ) !== null ) { 560 561 // o object_name 562 // or 563 // g group_name 564 565 // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 566 // var name = result[ 0 ].substr( 1 ).trim(); 567 var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 568 569 state.startObject( name ); 570 571 } else if ( material_use_pattern.test( line ) ) { 572 573 // material 574 575 state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 576 577 } else if ( material_library_pattern.test( line ) ) { 578 579 // mtl file 580 581 state.materialLibraries.push( line.substring( 7 ).trim() ); 582 583 } else if ( lineFirstChar === 's' ) { 584 585 result = line.split( ' ' ); 586 587 // smooth shading 588 589 // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 590 // but does not define a usemtl for each face set. 591 // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 592 // This requires some care to not create extra material on each smooth value for "normal" obj files. 593 // where explicit usemtl defines geometry groups. 594 // Example asset: examples/models/obj/cerberus/Cerberus.obj 595 596 /* 597 * http://paulbourke.net/dataformats/obj/ 598 * or 599 * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf 600 * 601 * From chapter "Grouping" Syntax explanation "s group_number": 602 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. 603 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form 604 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater 605 * than 0." 606 */ 607 if ( result.length > 1 ) { 608 609 var value = result[ 1 ].trim().toLowerCase(); 610 state.object.smooth = ( value !== '0' && value !== 'off' ); 611 612 } else { 613 614 // ZBrush can produce "s" lines #11707 615 state.object.smooth = true; 616 617 } 618 var material = state.object.currentMaterial(); 619 if ( material ) material.smooth = state.object.smooth; 620 621 } else { 622 623 // Handle null terminated files without exception 624 if ( line === '\0' ) continue; 625 626 throw new Error( 'THREEW.OBJLoader: Unexpected line: "' + line + '"' ); 627 628 } 629 630 } 631 632 state.finalize(); 633 634 var container = new THREEW.Group(); 635 container.materialLibraries = [].concat( state.materialLibraries ); 636 637 for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 638 639 var object = state.objects[ i ]; 640 var geometry = object.geometry; 641 var materials = object.materials; 642 var isLine = ( geometry.type === 'Line' ); 643 var isPoints = ( geometry.type === 'Points' ); 644 var hasVertexColors = false; 645 646 // Skip o/g line declarations that did not follow with any faces 647 if ( geometry.vertices.length === 0 ) continue; 648 649 var buffergeometry = new THREEW.BufferGeometry(); 650 651 buffergeometry.addAttribute( 'position', new THREEW.Float32BufferAttribute( geometry.vertices, 3 ) ); 652 653 if ( geometry.normals.length > 0 ) { 654 655 buffergeometry.addAttribute( 'normal', new THREEW.Float32BufferAttribute( geometry.normals, 3 ) ); 656 657 } else { 658 659 buffergeometry.computeVertexNormals(); 660 661 } 662 663 if ( geometry.colors.length > 0 ) { 664 665 hasVertexColors = true; 666 buffergeometry.addAttribute( 'color', new THREEW.Float32BufferAttribute( geometry.colors, 3 ) ); 667 668 } 669 670 if ( geometry.uvs.length > 0 ) { 671 672 buffergeometry.addAttribute( 'uv', new THREEW.Float32BufferAttribute( geometry.uvs, 2 ) ); 673 674 } 675 676 // Create materials 677 678 var createdMaterials = []; 679 680 for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 681 682 var sourceMaterial = materials[ mi ]; 683 var material = undefined; 684 685 if ( this.materials !== null ) { 686 687 material = this.materials.create( sourceMaterial.name ); 688 689 // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 690 if ( isLine && material && ! ( material instanceof THREEW.LineBasicMaterial ) ) { 691 692 var materialLine = new THREEW.LineBasicMaterial(); 693 THREEW.Material.prototype.copy.call( materialLine, material ); 694 materialLine.color.copy( material.color ); 695 materialLine.lights = false;
696 material = materialLine; 697 698 } else if ( isPoints && material && ! ( material instanceof THREEW.PointsMaterial ) ) { 699 700 var materialPoints = new THREEW.PointsMaterial( { size: 10, sizeAttenuation: false } ); 701 THREEW.Material.prototype.copy.call( materialPoints, material ); 702 materialPoints.color.copy( material.color ); 703 materialPoints.map = material.map; 704 materialPoints.lights = false; 705 material = materialPoints; 706 707 } 708 709 } 710 711 if ( ! material ) { 712 713 if ( isLine ) { 714 715 material = new THREEW.LineBasicMaterial(); 716 717 } else if ( isPoints ) { 718 719 material = new THREEW.PointsMaterial( { size: 1, sizeAttenuation: false } ); 720 721 } else { 722 723 material = new THREEW.MeshPhongMaterial(); 724// material = new THREEW.MeshLambertMaterial({precision: 'mediump'}); 725 726 } 727 728 material.name = sourceMaterial.name; 729 730 } 731 732 material.flatShading = sourceMaterial.smooth ? false : true; 733 material.vertexColors = hasVertexColors ? THREEW.VertexColors : THREEW.NoColors; 734 735 createdMaterials.push( material ); 736 737 } 738 739 // Create mesh 740 741 var mesh; 742 743 if ( createdMaterials.length > 1 ) { 744 745 for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 746 747 var sourceMaterial = materials[ mi ]; 748 buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 749 750 } 751 752 if ( isLine ) { 753 754 mesh = new THREEW.LineSegments( buffergeometry, createdMaterials ); 755 756 } else if ( isPoints ) { 757 758 mesh = new THREEW.Points( buffergeometry, createdMaterials ); 759 760 } else { 761 762 mesh = new THREEW.Mesh( buffergeometry, createdMaterials ); 763 764 } 765 766 } else { 767 768 if ( isLine ) { 769 770 mesh = new THREEW.LineSegments( buffergeometry, createdMaterials[ 0 ] ); 771 772 } else if ( isPoints ) { 773 774 mesh = new THREEW.Points( buffergeometry, createdMaterials[ 0 ] ); 775 776 } else { 777 778 mesh = new THREEW.Mesh( buffergeometry, createdMaterials[ 0 ] ); 779 780 } 781 782 } 783 784 mesh.name = object.name; 785 786 container.add( mesh ); 787 788 } 789 790 console.timeEnd( 'OBJLoader' ); 791 792 return container; 793 794 } 795 796 }; 797 798 return OBJLoader; 799 800} )();
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.