1/** 2 * @author mrdoob / http://mrdoob.com/ 3 */ 4 5THREE.VRMLLoader = function ( manager ) { 6 7 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 9}; 10 11THREE.VRMLLoader.prototype = { 12 13 constructor: THREE.VRMLLoader, 14 15 // for IndexedFaceSet support 16 isRecordingPoints: false, 17 isRecordingFaces: false, 18 points: [], 19 indexes: [], 20 21 // for Background support 22 isRecordingAngles: false, 23 isRecordingColors: false, 24 angles: [], 25 colors: [], 26 27 recordingFieldname: null, 28 29 crossOrigin: 'anonymous', 30 31 load: function ( url, onLoad, onProgress, onError ) { 32 33 var scope = this; 34 35 var path = ( scope.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path; 36 37 var loader = new THREE.FileLoader( this.manager ); 38 loader.setPath( scope.path ); 39 loader.load( url, function ( text ) { 40 41 onLoad( scope.parse( text, path ) ); 42 43 }, onProgress, onError ); 44 45 }, 46 47 setPath: function ( value ) { 48 49 this.path = value; 50 return this; 51 52 }, 53 54 setResourcePath: function ( value ) { 55 56 this.resourcePath = value; 57 return this; 58 59 }, 60 61 setCrossOrigin: function ( value ) { 62 63 this.crossOrigin = value; 64 return this; 65 66 }, 67 68 parse: function ( data, path ) { 69 70 var scope = this; 71 72 var textureLoader = new THREE.TextureLoader( this.manager ); 73 textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); 74 75 function parseV2( lines, scene ) { 76 77 var defines = {}; 78 var float_pattern = /(\b|\-|\+)([\d\.e]+)/; 79 var float2_pattern = /([\d\.\+\-e]+)\s+([\d\.\+\-e]+)/g; 80 var float3_pattern = /([\d\.\+\-e]+)\s+([\d\.\+\-e]+)\s+([\d\.\+\-e]+)/g; 81 82 /** 83 * Vertically paints the faces interpolating between the 84 * specified colors at the specified angels. This is used for the Background 85 * node, but could be applied to other nodes with multiple faces as well. 86 * 87 * When used with the Background node, default is directionIsDown is true if 88 * interpolating the skyColor down from the Zenith. When interpolationg up from 89 * the Nadir i.e. interpolating the groundColor, the directionIsDown is false. 90 * 91 * The first angle is never specified, it is the Zenith (0 rad). Angles are specified 92 * in radians. The geometry is thought a sphere, but could be anything. The color interpolation 93 * is linear along the Y axis in any case. 94 * 95 * You must specify one more color than you have angles at the beginning of the colors array. 96 * This is the color of the Zenith (the top of the shape). 97 * 98 * @param geometry 99 * @param radius 100 * @param angles 101 * @param colors 102 * @param boolean topDown Whether to work top down or bottom up. 103 */ 104 function paintFaces( geometry, radius, angles, colors, topDown ) { 105 106 var direction = ( topDown === true ) ? 1 : - 1; 107 108 var coord = [], A = {}, B = {}, applyColor = false; 109 110 for ( var k = 0; k < angles.length; k ++ ) { 111 112 // push the vector at which the color changes 113 114 var vec = { 115 x: direction * ( Math.cos( angles[ k ] ) * radius ), 116 y: direction * ( Math.sin( angles[ k ] ) * radius ) 117 }; 118 119 coord.push( vec ); 120 121 } 122 123 var index = geometry.index; 124 var positionAttribute = geometry.attributes.position; 125 var colorAttribute = new THREE.BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 ); 126 127 var position = new THREE.Vector3(); 128 var color = new THREE.Color(); 129 130 for ( var i = 0; i < index.count; i ++ ) { 131 132 var vertexIndex = index.getX( i ); 133 134 position.fromBufferAttribute( positionAttribute, vertexIndex ); 135 136 for ( var j = 0; j < colors.length; j ++ ) { 137 138 // linear interpolation between aColor and bColor, calculate proportion 139 // A is previous point (angle) 140 141 if ( j === 0 ) { 142 143 A.x = 0; 144 A.y = ( topDown === true ) ? radius : - 1 * radius; 145 146 } else { 147 148 A.x = coord[ j - 1 ].x; 149 A.y = coord[ j - 1 ].y; 150 151 } 152 153 // B is current point (angle) 154 155 B = coord[ j ]; 156 157 if ( B !== undefined ) { 158 159 // p has to be between the points A and B which we interpolate 160 161 applyColor = ( topDown === true ) ? ( position.y <= A.y && position.y > B.y ) : ( position.y >= A.y && position.y < B.y ); 162 163 if ( applyColor === true ) { 164 165 var aColor = colors[ j ]; 166 var bColor = colors[ j + 1 ]; 167 168 // below is simple linear interpolation 169 170 var t = Math.abs( position.y - A.y ) / ( A.y - B.y ); 171 172 // to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y 173 174 color.copy( aColor ).lerp( bColor, t ); 175 176 colorAttribute.setXYZ( vertexIndex, color.r, color.g, color.b ); 177 178 } else { 179 180 var colorIndex = ( topDown === true ) ? colors.length - 1 : 0; 181 var c = colors[ colorIndex ]; 182 colorAttribute.setXYZ( vertexIndex, c.r, c.g, c.b ); 183 184 } 185 186 } 187 188 } 189 190 } 191 192 geometry.addAttribute( 'color', colorAttribute ); 193 194 } 195 196 var index = []; 197 198 function parseProperty( node, line ) { 199 200 var parts = [], part, property = {}, fieldName; 201 202 /** 203 * Expression for matching relevant information, such as a name or value, but not the separators 204 * @type {RegExp} 205 */ 206 var regex = /[^\s,\[\]]+/g; 207 208 var point; 209 210 while ( null !== ( part = regex.exec( line ) ) ) { 211 212 parts.push( part[ 0 ] ); 213 214 } 215 216 fieldName = parts[ 0 ]; 217 218 219 // trigger several recorders 220 switch ( fieldName ) { 221 222 case 'skyAngle': 223 case 'groundAngle': 224 scope.recordingFieldname = fieldName; 225 scope.isRecordingAngles = true; 226 scope.angles = []; 227 break; 228 229 case 'color': 230 case 'skyColor': 231 case 'groundColor': 232 scope.recordingFieldname = fieldName; 233 scope.isRecordingColors = true; 234 scope.colors = []; 235 break; 236 237 case 'point': 238 case 'vector': 239 scope.recordingFieldname = fieldName; 240 scope.isRecordingPoints = true; 241 scope.points = []; 242 break; 243 244 case 'colorIndex': 245 case 'coordIndex': 246 case 'normalIndex': 247 case 'texCoordIndex': 248 scope.recordingFieldname = fieldName; 249 scope.isRecordingFaces = true; 250 scope.indexes = []; 251 break; 252 253 } 254 255 if ( scope.isRecordingFaces ) { 256 257 // the parts hold the indexes as strings 258 if ( parts.length > 0 ) { 259 260 for ( var ind = 0; ind < parts.length; ind ++ ) { 261 262 // the part should either be positive integer or -1 263 if ( ! /(-?\d+)/.test( parts[ ind ] ) ) { 264 265 continue; 266 267 } 268 269 // end of current face 270 if ( parts[ ind ] === '-1' ) { 271 272 if ( index.length > 0 ) { 273 274 scope.indexes.push( index ); 275 276 } 277 278 // start new one 279 index = []; 280 281 } else { 282 283 index.push( parseInt( parts[ ind ] ) ); 284 285 } 286 287 } 288 289 } 290 291 // end 292 if ( /]/.exec( line ) ) { 293 294 if ( index.length > 0 ) { 295 296 scope.indexes.push( index ); 297 298 } 299 300 // start new one 301 index = []; 302 303 scope.isRecordingFaces = false;
304 node[ scope.recordingFieldname ] = scope.indexes; 305 306 } 307 308 } else if ( scope.isRecordingPoints ) { 309 310 if ( node.nodeType == 'Coordinate' ) { 311 312 while ( null !== ( parts = float3_pattern.exec( line ) ) ) { 313 314 point = { 315 x: parseFloat( parts[ 1 ] ), 316 y: parseFloat( parts[ 2 ] ), 317 z: parseFloat( parts[ 3 ] ) 318 }; 319 320 scope.points.push( point ); 321 322 } 323 324 } 325 326 if ( node.nodeType == 'Normal' ) { 327 328 while ( null !== ( parts = float3_pattern.exec( line ) ) ) { 329 330 point = { 331 x: parseFloat( parts[ 1 ] ), 332 y: parseFloat( parts[ 2 ] ), 333 z: parseFloat( parts[ 3 ] ) 334 }; 335 336 scope.points.push( point ); 337 338 } 339 340 } 341 342 if ( node.nodeType == 'TextureCoordinate' ) { 343 344 while ( null !== ( parts = float2_pattern.exec( line ) ) ) { 345 346 point = { 347 x: parseFloat( parts[ 1 ] ), 348 y: parseFloat( parts[ 2 ] ) 349 }; 350 351 scope.points.push( point ); 352 353 } 354 355 } 356 357 // end 358 if ( /]/.exec( line ) ) { 359 360 scope.isRecordingPoints = false; 361 node.points = scope.points; 362 363 } 364 365 } else if ( scope.isRecordingAngles ) { 366 367 // the parts hold the angles as strings 368 if ( parts.length > 0 ) { 369 370 for ( var ind = 0; ind < parts.length; ind ++ ) { 371 372 // the part should be a float 373 if ( ! float_pattern.test( parts[ ind ] ) ) { 374 375 continue; 376 377 } 378 379 scope.angles.push( parseFloat( parts[ ind ] ) ); 380 381 } 382 383 } 384 385 // end 386 if ( /]/.exec( line ) ) { 387 388 scope.isRecordingAngles = false; 389 node[ scope.recordingFieldname ] = scope.angles; 390 391 } 392 393 } else if ( scope.isRecordingColors ) { 394 395 while ( null !== ( parts = float3_pattern.exec( line ) ) ) { 396 397 var color = { 398 r: parseFloat( parts[ 1 ] ), 399 g: parseFloat( parts[ 2 ] ), 400 b: parseFloat( parts[ 3 ] ) 401 }; 402 403 scope.colors.push( color ); 404 405 } 406 407 // end 408 if ( /]/.exec( line ) ) { 409 410 scope.isRecordingColors = false; 411 node[ scope.recordingFieldname ] = scope.colors; 412 413 } 414 415 } else if ( parts[ parts.length - 1 ] !== 'NULL' && fieldName !== 'children' ) { 416 417 switch ( fieldName ) { 418 419 case 'diffuseColor': 420 case 'emissiveColor': 421 case 'specularColor': 422 case 'color': 423 424 if ( parts.length !== 4 ) { 425 426 console.warn( 'THREE.VRMLLoader: Invalid color format detected for %s.', fieldName ); 427 break; 428 429 } 430 431 property = { 432 r: parseFloat( parts[ 1 ] ), 433 g: parseFloat( parts[ 2 ] ), 434 b: parseFloat( parts[ 3 ] ) 435 }; 436 437 break; 438 439 case 'location': 440 case 'direction': 441 case 'translation': 442 case 'scale': 443 case 'size': 444 if ( parts.length !== 4 ) { 445 446 console.warn( 'THREE.VRMLLoader: Invalid vector format detected for %s.', fieldName ); 447 break; 448 449 } 450 451 property = { 452 x: parseFloat( parts[ 1 ] ), 453 y: parseFloat( parts[ 2 ] ), 454 z: parseFloat( parts[ 3 ] ) 455 }; 456 457 break; 458 459 case 'intensity': 460 case 'cutOffAngle': 461 case 'radius': 462 case 'topRadius': 463 case 'bottomRadius': 464 case 'height': 465 case 'transparency': 466 case 'shininess': 467 case 'ambientIntensity': 468 case 'creaseAngle': 469 if ( parts.length !== 2 ) { 470 471 console.warn( 'THREE.VRMLLoader: Invalid single float value specification detected for %s.', fieldName ); 472 break; 473 474 } 475 476 property = parseFloat( parts[ 1 ] ); 477 478 break; 479 480 case 'rotation': 481 if ( parts.length !== 5 ) { 482 483 console.warn( 'THREE.VRMLLoader: Invalid quaternion format detected for %s.', fieldName ); 484 break; 485 486 } 487 488 property = { 489 x: parseFloat( parts[ 1 ] ), 490 y: parseFloat( parts[ 2 ] ), 491 z: parseFloat( parts[ 3 ] ), 492 w: parseFloat( parts[ 4 ] ) 493 }; 494 495 break; 496 497 case 'on': 498 case 'ccw': 499 case 'solid': 500 case 'colorPerVertex': 501 case 'convex': 502 if ( parts.length !== 2 ) { 503 504 console.warn( 'THREE.VRMLLoader: Invalid format detected for %s.', fieldName ); 505 break; 506 507 } 508 509 property = parts[ 1 ] === 'TRUE' ? true : false;
510 511 break; 512 513 } 514 515 // VRMLLoader does not support text so it can't process the "string" property yet 516 517 if ( fieldName !== 'string' ) node[ fieldName ] = property; 518 519 } 520 521 return property; 522 523 } 524 525 function getTree( lines ) { 526 527 var tree = { 'string': 'Scene', children: [] }; 528 var current = tree; 529 var matches; 530 var specification; 531 532 for ( var i = 0; i < lines.length; i ++ ) { 533 534 var comment = ''; 535 536 var line = lines[ i ]; 537 538 // omit whitespace only lines 539 if ( null !== ( /^\s+?$/g.exec( line ) ) ) { 540 541 continue; 542 543 } 544 545 line = line.trim(); 546 547 // skip empty lines 548 if ( line === '' ) { 549 550 continue; 551 552 } 553 554 if ( /#/.exec( line ) ) { 555 556 var parts = line.split( '#' ); 557 558 // discard everything after the #, it is a comment 559 line = parts[ 0 ]; 560 561 // well, let's also keep the comment 562 comment = parts[ 1 ]; 563 564 } 565 566 if ( matches = /([^\s]*){1}(?:\s+)?{/.exec( line ) ) { 567 568 // first subpattern should match the Node name 569 570 var block = { 'nodeType': matches[ 1 ], 'string': line, 'parent': current, 'children': [], 'comment': comment }; 571 current.children.push( block ); 572 current = block; 573 574 if ( /}/.exec( line ) ) { 575 576 // example: geometry Box { size 1 1 1 } # all on the same line 577 specification = /{(.*)}/.exec( line )[ 1 ]; 578 579 // todo: remove once new parsing is complete? 580 block.children.push( specification ); 581 582 parseProperty( current, specification ); 583 584 current = current.parent; 585 586 } 587 588 } else if ( /}/.exec( line ) ) { 589 590 current = current.parent; 591 592 } else if ( line !== '' ) { 593 594 parseProperty( current, line ); 595 // todo: remove once new parsing is complete? we still do not parse geometry and appearance the new way 596 current.children.push( line ); 597 598 } 599 600 } 601 602 return tree; 603 604 } 605 606 function parseNode( data, parent ) { 607 608 var object; 609 610 if ( typeof data === 'string' ) { 611 612 if ( /USE/.exec( data ) ) { 613 614 var defineKey = /USE\s+?([^\s]+)/.exec( data )[ 1 ]; 615 616 if ( undefined == defines[ defineKey ] ) { 617 618 console.warn( 'THREE.VRMLLoader: %s is not defined.', defineKey ); 619 620 } else { 621 622 if ( /appearance/.exec( data ) && defineKey ) { 623 624 parent.material = defines[ defineKey ].clone(); 625 626 } else if ( /geometry/.exec( data ) && defineKey ) { 627 628 parent.geometry = defines[ defineKey ].clone(); 629 630 // the solid property is not cloned with clone(), is only needed for VRML loading, so we need to transfer it 631 if ( defines[ defineKey ].solid !== undefined && defines[ defineKey ].solid === false ) { 632 633 parent.geometry.solid = false; 634 parent.material.side = THREE.DoubleSide; 635 636 } 637 638 } else if ( defineKey ) { 639 640 object = defines[ defineKey ].clone(); 641 parent.add( object ); 642 643 } 644 645 } 646 647 } 648 649 return; 650 651 } 652 653 object = parent; 654 655 if ( data.string.indexOf( 'AmbientLight' ) > - 1 && data.nodeType === 'PointLight' ) { 656 657 data.nodeType = 'AmbientLight'; 658 659 } 660 661 var l_visible = data.on !== undefined ? data.on : true; 662 var l_intensity = data.intensity !== undefined ? data.intensity : 1; 663 var l_color = new THREE.Color(); 664 665 if ( data.color ) { 666 667 l_color.copy( data.color ); 668 669 } 670 671 if ( data.nodeType === 'AmbientLight' ) { 672 673 object = new THREE.AmbientLight( l_color, l_intensity ); 674 object.visible = l_visible; 675 676 parent.add( object ); 677 678 } else if ( data.nodeType === 'PointLight' ) { 679 680 var l_distance = 0; 681 682 if ( data.radius !== undefined && data.radius < 1000 ) { 683 684 l_distance = data.radius; 685 686 } 687 688 object = new THREE.PointLight( l_color, l_intensity, l_distance ); 689 object.visible = l_visible; 690 691 parent.add( object ); 692 693 } else if ( data.nodeType === 'SpotLight' ) { 694 695 var l_intensity = 1; 696 var l_distance = 0; 697 var l_angle = Math.PI / 3; 698 var l_penumbra = 0; 699 var l_visible = true; 700 701 if ( data.radius !== undefined && data.radius < 1000 ) { 702 703 l_distance = data.radius; 704 705 } 706 707 if ( data.cutOffAngle !== undefined ) { 708 709 l_angle = data.cutOffAngle; 710 711 } 712 713 object = new THREE.SpotLight( l_color, l_intensity, l_distance, l_angle, l_penumbra ); 714 object.visible = l_visible; 715 716 parent.add( object ); 717 718 } else if ( data.nodeType === 'Transform' || data.nodeType === 'Group' ) { 719 720 object = new THREE.Object3D(); 721 722 if ( /DEF/.exec( data.string ) ) { 723 724 object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ]; 725 defines[ object.name ] = object; 726 727 } 728 729 if ( data.translation !== undefined ) { 730 731 var t = data.translation; 732 733 object.position.set( t.x, t.y, t.z ); 734 735 } 736 737 if ( data.rotation !== undefined ) { 738 739 var r = data.rotation; 740 741 object.quaternion.setFromAxisAngle( new THREE.Vector3( r.x, r.y, r.z ), r.w ); 742 743 } 744 745 if ( data.scale !== undefined ) { 746 747 var s = data.scale; 748 749 object.scale.set( s.x, s.y, s.z ); 750 751 } 752 753 parent.add( object ); 754 755 } else if ( data.nodeType === 'Shape' ) { 756 757 object = new THREE.Mesh(); 758 759 if ( /DEF/.exec( data.string ) ) { 760 761 object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ]; 762 763 defines[ object.name ] = object; 764 765 } 766 767 parent.add( object ); 768 769 }
769 else if ( data.nodeType === 'Background' ) { 770 771 var segments = 20; 772 773 // sky (full sphere): 774 775 var radius = 2e4; 776 777 var skyGeometry = new THREE.SphereBufferGeometry( radius, segments, segments ); 778 var skyMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide } ); 779 780 if ( data.skyColor.length > 1 ) { 781 782 paintFaces( skyGeometry, radius, data.skyAngle, data.skyColor, true ); 783 784 skyMaterial.vertexColors = THREE.VertexColors; 785 786 } else { 787 788 var color = data.skyColor[ 0 ]; 789 skyMaterial.color.setRGB( color.r, color.b, color.g ); 790 791 } 792 793 scene.add( new THREE.Mesh( skyGeometry, skyMaterial ) ); 794 795 // ground (half sphere): 796 797 if ( data.groundColor !== undefined ) { 798 799 radius = 1.2e4; 800 801 var groundGeometry = new THREE.SphereBufferGeometry( radius, segments, segments, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI ); 802 var groundMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide, vertexColors: THREE.VertexColors } ); 803 804 paintFaces( groundGeometry, radius, data.groundAngle, data.groundColor, false ); 805 806 scene.add( new THREE.Mesh( groundGeometry, groundMaterial ) ); 807 808 } 809 810 } else if ( /geometry/.exec( data.string ) ) { 811 812 if ( data.nodeType === 'Box' ) { 813 814 var s = data.size; 815 816 parent.geometry = new THREE.BoxBufferGeometry( s.x, s.y, s.z ); 817 818 } else if ( data.nodeType === 'Cylinder' ) { 819 820 parent.geometry = new THREE.CylinderBufferGeometry( data.radius, data.radius, data.height ); 821 822 } else if ( data.nodeType === 'Cone' ) { 823 824 parent.geometry = new THREE.CylinderBufferGeometry( data.topRadius, data.bottomRadius, data.height ); 825 826 } else if ( data.nodeType === 'Sphere' ) { 827 828 parent.geometry = new THREE.SphereBufferGeometry( data.radius ); 829 830 } else if ( data.nodeType === 'IndexedLineSet' ) { 831 832 console.warn( 'THREE.VRMLLoader: IndexedLineSet not supported yet.' ); 833 parent.parent.remove( parent ); // since the loader is not able to parse the geometry, remove the respective 3D object 834 835 } else if ( data.nodeType === 'Text' ) { 836 837 console.warn( 'THREE.VRMLLoader: Text not supported yet.' ); 838 parent.parent.remove( parent ); 839 840 } else if ( data.nodeType === 'IndexedFaceSet' ) { 841 842 var geometry = new THREE.BufferGeometry(); 843 844 var positions = []; 845 var colors = []; 846 var normals = []; 847 var uvs = []; 848 849 var position, color, normal, uv; 850 851 var i, il, j, jl; 852 853 for ( i = 0, il = data.children.length; i < il; i ++ ) { 854 855 var child = data.children[ i ]; 856 857 // uvs 858 859 if ( child.nodeType === 'TextureCoordinate' ) { 860 861 if ( child.points ) { 862 863 for ( j = 0, jl = child.points.length; j < jl; j ++ ) { 864 865 uv = child.points[ j ]; 866 uvs.push( uv.x, uv.y ); 867 868 } 869 870 } 871 872 } 873 874 // normals 875 876 if ( child.nodeType === 'Normal' ) { 877 878 if ( child.points ) { 879 880 for ( j = 0, jl = child.points.length; j < jl; j ++ ) { 881 882 normal = child.points[ j ]; 883 normals.push( normal.x, normal.y, normal.z ); 884 885 } 886 887 } 888 889 } 890 891 // colors 892 893 if ( child.nodeType === 'Color' ) { 894 895 if ( child.color ) { 896 897 for ( j = 0, jl = child.color.length; j < jl; j ++ ) { 898 899 color = child.color[ j ]; 900 colors.push( color.r, color.g, color.b ); 901 902 } 903 904 } 905 906 } 907 908 // positions 909 910 if ( child.nodeType === 'Coordinate' ) { 911 912 if ( child.points ) { 913 914 for ( j = 0, jl = child.points.length; j < jl; j ++ ) { 915 916 position = child.points[ j ]; 917 positions.push( position.x, position.y, position.z ); 918 919 } 920 921 } 922 923 if ( child.string.indexOf( 'DEF' ) > - 1 ) { 924 925 var name = /DEF\s+([^\s]+)/.exec( child.string )[ 1 ]; 926 927 defines[ name ] = positions.slice( 0 ); 928 929 } 930 931 if ( child.string.indexOf( 'USE' ) > - 1 ) { 932 933 var defineKey = /USE\s+([^\s]+)/.exec( child.string )[ 1 ]; 934 935 positions = defines[ defineKey ]; 936 937 } 938 939 } 940 941 } 942 943 // some shapes only have vertices for use in other shapes 944 945 if ( data.coordIndex ) { 946 947 function triangulateIndexArray( indexArray, ccw, colorPerVertex ) { 948 949 if ( ccw === undefined ) { 950 951 // ccw is true by default 952 ccw = true; 953 954 } 955 956 var triangulatedIndexArray = []; 957 var skip = 0; 958 959 for ( i = 0, il = indexArray.length; i < il; i ++ ) { 960 961 if ( colorPerVertex === false ) { 962 963 var colorIndices = indexArray[ i ]; 964 965 for ( j = 0, jl = colorIndices.length; j < jl; j ++ ) { 966 967 var index = colorIndices[ j ]; 968 969 triangulatedIndexArray.push( index, index, index ); 970 971 } 972 973 } else { 974 975 var indexedFace = indexArray[ i ]; 976 977 // VRML support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here 978 979 skip = 0; 980 981 while ( indexedFace.length >= 3 && skip < ( indexedFace.length - 2 ) ) { 982 983 var i1 = indexedFace[ 0 ]; 984 var i2 = indexedFace[ skip + ( ccw ? 1 : 2 ) ]; 985 var i3 = indexedFace[ skip + ( ccw ? 2 : 1 ) ]; 986 987 triangulatedIndexArray.push( i1, i2, i3 ); 988 989 skip ++; 990 991 } 992 993 } 994 995 } 996 997 return triangulatedIndexArray; 998 999 } 1000 1001 var positionIndexes = data.coordIndex ? triangulateIndexArray( data.coordIndex, data.ccw ) : [];
1002 var normalIndexes = data.normalIndex ? triangulateIndexArray( data.normalIndex, data.ccw ) : positionIndexes; 1003 var colorIndexes = data.colorIndex ? triangulateIndexArray( data.colorIndex, data.ccw, data.colorPerVertex ) : []; 1004 var uvIndexes = data.texCoordIndex ? triangulateIndexArray( data.texCoordIndex, data.ccw ) : positionIndexes; 1005 1006 var newIndexes = []; 1007 var newPositions = []; 1008 var newNormals = []; 1009 var newColors = []; 1010 var newUvs = []; 1011 1012 // if any other index array does not match the coordinate indexes, split any points that differ 1013 1014 var pointMap = Object.create( null ); 1015 1016 for ( i = 0; i < positionIndexes.length; i ++ ) { 1017 1018 var pointAttributes = []; 1019 1020 var positionIndex = positionIndexes[ i ]; 1021 var normalIndex = normalIndexes[ i ]; 1022 var colorIndex = colorIndexes[ i ]; 1023 var uvIndex = uvIndexes[ i ]; 1024 1025 var base = 10; // which base to use to represent each value 1026 1027 pointAttributes.push( positionIndex.toString( base ) ); 1028 1029 if ( normalIndex !== undefined ) { 1030 1031 pointAttributes.push( normalIndex.toString( base ) ); 1032 1033 } 1034 1035 if ( colorIndex !== undefined ) { 1036 1037 pointAttributes.push( colorIndex.toString( base ) ); 1038 1039 } 1040 1041 if ( uvIndex !== undefined ) { 1042 1043 pointAttributes.push( uvIndex.toString( base ) ); 1044 1045 } 1046 1047 var pointId = pointAttributes.join( ',' ); 1048 var newIndex = pointMap[ pointId ]; 1049 1050 if ( newIndex === undefined ) { 1051 1052 newIndex = newPositions.length / 3; 1053 pointMap[ pointId ] = newIndex; 1054 1055 newPositions.push( 1056 positions[ positionIndex * 3 ], 1057 positions[ positionIndex * 3 + 1 ], 1058 positions[ positionIndex * 3 + 2 ] 1059 ); 1060 1061 if ( normalIndex !== undefined && normals.length > 0 ) { 1062 1063 newNormals.push( 1064 normals[ normalIndex * 3 ], 1065 normals[ normalIndex * 3 + 1 ], 1066 normals[ normalIndex * 3 + 2 ] 1067 ); 1068 1069 } 1070 1071 if ( colorIndex !== undefined && colors.length > 0 ) { 1072 1073 newColors.push( 1074 colors[ colorIndex * 3 ], 1075 colors[ colorIndex * 3 + 1 ], 1076 colors[ colorIndex * 3 + 2 ] 1077 ); 1078 1079 } 1080 1081 if ( uvIndex !== undefined && uvs.length > 0 ) { 1082 1083 newUvs.push( 1084 uvs[ uvIndex * 2 ], 1085 uvs[ uvIndex * 2 + 1 ] 1086 ); 1087 1088 } 1089 1090 } 1091 1092 newIndexes.push( newIndex ); 1093 1094 } 1095 1096 positions = newPositions; 1097 normals = newNormals; 1098 colors = newColors; 1099 uvs = newUvs; 1100 1101 geometry.setIndex( newIndexes ); 1102 1103 } else { 1104 1105 // do not add dummy mesh to the scene 1106 1107 parent.parent.remove( parent ); 1108 1109 } 1110 1111 if ( false === data.solid ) { 1112 1113 parent.material.side = THREE.DoubleSide; 1114 1115 } 1116 1117 // we need to store it on the geometry for use with defines 1118 geometry.solid = data.solid; 1119 1120 geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) ); 1121 1122 if ( colors.length > 0 ) { 1123 1124 geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) ); 1125 1126 parent.material.vertexColors = THREE.VertexColors; 1127 1128 } 1129 1130 if ( uvs.length > 0 ) { 1131 1132 geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); 1133 1134 } 1135 1136 if ( normals.length > 0 ) { 1137 1138 geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); 1139 1140 } else { 1141 1142 // convert geometry to non-indexed to get sharp normals 1143 geometry = geometry.toNonIndexed(); 1144 geometry.computeVertexNormals(); 1145 1146 } 1147 1148 geometry.computeBoundingSphere(); 1149 1150 // see if it's a define 1151 if ( /DEF/.exec( data.string ) ) { 1152 1153 geometry.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ]; 1154 defines[ geometry.name ] = geometry; 1155 1156 } 1157 1158 parent.geometry = geometry; 1159 1160 } 1161 1162 return; 1163 1164 } else if ( /appearance/.exec( data.string ) ) { 1165 1166 for ( var i = 0; i < data.children.length; i ++ ) { 1167 1168 var child = data.children[ i ]; 1169 1170 if ( child.nodeType === 'Material' ) { 1171 1172 var material = new THREE.MeshPhongMaterial(); 1173 1174 if ( child.diffuseColor !== undefined ) { 1175 1176 var d = child.diffuseColor; 1177 1178 material.color.setRGB( d.r, d.g, d.b ); 1179 1180 } 1181 1182 if ( child.emissiveColor !== undefined ) { 1183 1184 var e = child.emissiveColor; 1185 1186 material.emissive.setRGB( e.r, e.g, e.b ); 1187 1188 } 1189 1190 if ( child.specularColor !== undefined ) { 1191 1192 var s = child.specularColor; 1193 1194 material.specular.setRGB( s.r, s.g, s.b ); 1195 1196 } 1197 1198 if ( child.transparency !== undefined ) { 1199 1200 var t = child.transparency; 1201 1202 // transparency is opposite of opacity 1203 material.opacity = Math.abs( 1 - t ); 1204 1205 material.transparent = true; 1206 1207 } 1208 1209 if ( /DEF/.exec( data.string ) ) { 1210 1211 material.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ]; 1212 1213 defines[ material.name ] = material; 1214 1215 } 1216 1217 parent.material = material; 1218 1219 } 1220 1221 if ( child.nodeType === 'ImageTexture' ) { 1222 1223 var textureName = /"([^"]+)"/.exec( child.children[ 0 ] ); 1224 1225 if ( textureName ) { 1226 1227 parent.material.name = textureName[ 1 ]; 1228 1229 parent.material.map = textureLoader.load( textureName[ 1 ] ); 1230 1231 } 1232 1233 } 1234 1235 } 1236 1237 return; 1238 1239 } 1240 1241 for ( var i = 0, l = data.children.length; i < l; i ++ ) { 1242 1243 parseNode( data.children[ i ], object ); 1244 1245 } 1246 1247 } 1248 1249 parseNode( getTree( lines ), scene ); 1250 1251 } 1252 1253 var scene = new THREE.Scene(); 1254 1255 var lines = data.split( '\n' ); 1256 1257 // some lines do not have breaks 1258 1259 for ( var i = lines.length - 1; i > 0; i -- ) { 1260 1261 // The # symbol indicates that all subsequent text, until the end of the line is a comment, 1262 // and should be ignored. (see http://gun.teipir.gr/VRML-amgem/spec/part1/grammar.html) 1263 lines[ i ] = lines[ i ].replace( /(#.*)/, '' ); 1264 1265 var line = lines[ i ]; 1266 1267 // split lines with {..{ or {..[ - some have both 1268 if ( /{.*[{\[]/.test( line ) ) { 1269 1270 var parts = line.split( '{' ).join( '{\n' ).split( '\n' ); 1271 parts.unshift( 1 ); 1272 parts.unshift( i ); 1273 lines.splice.apply( lines, parts ); 1274 1275 } else if ( /\].*}/.test( line ) ) { 1276 1277 // split lines with ]..} 1278 var parts = line.split( ']' ).join( ']\n' ).split( '\n' ); 1279 parts.unshift( 1 ); 1280 parts.unshift( i ); 1281 lines.splice.apply( lines, parts ); 1282 1283 } 1284 1285 line = lines[ i ]; 1286 1287 if ( /}.*}/.test( line ) ) { 1288 1289 // split lines with }..} 1290 var parts = line.split( '}' ).join( '}\n' ).split( '\n' ); 1291 parts.unshift( 1 ); 1292 parts.unshift( i ); 1293 lines.splice.apply( lines, parts ); 1294 1295 } 1296 1297 line = lines[ i ]; 1298 1299 if ( /^\b[^\s]+\b$/.test( line.trim() ) ) { 1300 1301 // prevent lines with single words like "coord" or "geometry", see #12209 1302 lines[ i + 1 ] = line + ' ' + lines[ i + 1 ].trim(); 1303 lines.splice( i, 1 ); 1304 1305 } else if ( ( line.indexOf( 'coord' ) > - 1 ) && ( line.indexOf( '[' ) < 0 ) && ( line.indexOf( '{' ) < 0 ) ) { 1306 1307 // force the parser to create Coordinate node for empty coords 1308 // coord USE something -> coord USE something Coordinate {} 1309
1310 lines[ i ] += ' Coordinate {}'; 1311 1312 } 1313 1314 } 1315 1316 var header = lines.shift(); 1317 1318 if ( /V1.0/.exec( header ) ) { 1319 1320 console.warn( 'THREE.VRMLLoader: V1.0 not supported yet.' ); 1321 1322 } else if ( /V2.0/.exec( header ) ) { 1323 1324 parseV2( lines, scene ); 1325 1326 } 1327 1328 return scene; 1329 1330 } 1331 1332};
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.