github.com/EngineerKamesh/gofullstack@v0.0.0-20180609171605-d41341d7d4ee/volume3/section2/3dgopher/static/js/OBJLoader.js (about) 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 };