1/** 2 * @author Kyle-Larson https://github.com/Kyle-Larson 3 * @author Takahiro https://github.com/takahirox 4 * @author Lewy Blue https://github.com/looeee 5 * 6 * Loader loads FBX file and generates Group representing FBX scene. 7 * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format 8 * Versions lower than this may load but will probably have errors 9 * 10 * Needs Support: 11 * Morph normals / blend shape normals 12 * 13 * FBX format references: 14 * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure 15 * http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) 16 * 17 * Binary format specification: 18 * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ 19 */ 20 21 22THREE.FBXLoader = ( function () { 23 24 var fbxTree; 25 var connections; 26 var sceneGraph; 27 28 function FBXLoader( manager ) { 29 30 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 31 32 } 33 34 FBXLoader.prototype = { 35 36 constructor: FBXLoader, 37 38 crossOrigin: 'anonymous', 39 40 load: function ( url, onLoad, onProgress, onError ) { 41 42 var self = this; 43 44 var path = ( self.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : self.path; 45 46 var loader = new THREE.FileLoader( this.manager ); 47 loader.setPath( self.path ); 48 loader.setResponseType( 'arraybuffer' ); 49 50 loader.load( url, function ( buffer ) { 51 52 try { 53 54 onLoad( self.parse( buffer, path ) ); 55 56 } catch ( error ) { 57 58 setTimeout( function () { 59 60 if ( onError ) onError( error ); 61 62 self.manager.itemError( url ); 63 64 }, 0 ); 65 66 } 67 68 }, onProgress, onError ); 69 70 }, 71 72 setPath: function ( value ) { 73 74 this.path = value; 75 return this; 76 77 }, 78 79 setResourcePath: function ( value ) { 80 81 this.resourcePath = value; 82 return this; 83 84 }, 85 86 setCrossOrigin: function ( value ) { 87 88 this.crossOrigin = value; 89 return this; 90 91 }, 92 93 parse: function ( FBXBuffer, path ) { 94 95 if ( isFbxFormatBinary( FBXBuffer ) ) { 96 97 fbxTree = new BinaryParser().parse( FBXBuffer ); 98 99 } else { 100 101 var FBXText = convertArrayBufferToString( FBXBuffer ); 102 103 if ( ! isFbxFormatASCII( FBXText ) ) { 104 105 throw new Error( 'THREE.FBXLoader: Unknown format.' ); 106 107 } 108 109 if ( getFbxVersion( FBXText ) < 7000 ) { 110 111 throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) ); 112 113 } 114 115 fbxTree = new TextParser().parse( FBXText ); 116 117 } 118 119 // console.log( fbxTree ); 120 121 var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); 122 123 return new FBXTreeParser( textureLoader ).parse( fbxTree ); 124 125 } 126 127 }; 128 129 // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group 130 function FBXTreeParser( textureLoader ) { 131 132 this.textureLoader = textureLoader; 133 134 } 135 136 FBXTreeParser.prototype = { 137 138 constructor: FBXTreeParser, 139 140 parse: function () { 141 142 connections = this.parseConnections(); 143 144 var images = this.parseImages(); 145 var textures = this.parseTextures( images ); 146 var materials = this.parseMaterials( textures ); 147 var deformers = this.parseDeformers(); 148 var geometryMap = new GeometryParser().parse( deformers ); 149 150 this.parseScene( deformers, geometryMap, materials ); 151 152 return sceneGraph; 153 154 }, 155 156 // Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry ) 157 // and details the connection type 158 parseConnections: function () { 159 160 var connectionMap = new Map(); 161 162 if ( 'Connections' in fbxTree ) { 163 164 var rawConnections = fbxTree.Connections.connections; 165 166 rawConnections.forEach( function ( rawConnection ) { 167 168 var fromID = rawConnection[ 0 ]; 169 var toID = rawConnection[ 1 ]; 170 var relationship = rawConnection[ 2 ]; 171 172 if ( ! connectionMap.has( fromID ) ) { 173 174 connectionMap.set( fromID, { 175 parents: [], 176 children: [] 177 } ); 178 179 } 180 181 var parentRelationship = { ID: toID, relationship: relationship }; 182 connectionMap.get( fromID ).parents.push( parentRelationship ); 183 184 if ( ! connectionMap.has( toID ) ) { 185 186 connectionMap.set( toID, { 187 parents: [], 188 children: [] 189 } ); 190 191 } 192 193 var childRelationship = { ID: fromID, relationship: relationship }; 194 connectionMap.get( toID ).children.push( childRelationship ); 195 196 } ); 197 198 } 199 200 return connectionMap; 201 202 }, 203 204 // Parse FBXTree.Objects.Video for embedded image data 205 // These images are connected to textures in FBXTree.Objects.Textures 206 // via FBXTree.Connections. 207 parseImages: function () { 208 209 var images = {}; 210 var blobs = {}; 211 212 if ( 'Video' in fbxTree.Objects ) { 213 214 var videoNodes = fbxTree.Objects.Video; 215 216 for ( var nodeID in videoNodes ) { 217 218 var videoNode = videoNodes[ nodeID ]; 219 220 var id = parseInt( nodeID ); 221 222 images[ id ] = videoNode.RelativeFilename || videoNode.Filename; 223 224 // raw image data is in videoNode.Content 225 if ( 'Content' in videoNode ) { 226 227 var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 ); 228 var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' ); 229 230 if ( arrayBufferContent || base64Content ) { 231 232 var image = this.parseImage( videoNodes[ nodeID ] ); 233 234 blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image; 235 236 } 237 238 } 239 240 } 241 242 } 243 244 for ( var id in images ) { 245 246 var filename = images[ id ]; 247 248 if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ]; 249 else images[ id ] = images[ id ].split( '\\' ).pop(); 250 251 } 252 253 return images; 254 255 }, 256 257 // Parse embedded image data in FBXTree.Video.Content 258 parseImage: function ( videoNode ) { 259 260 var content = videoNode.Content; 261 var fileName = videoNode.RelativeFilename || videoNode.Filename; 262 var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase(); 263 264 var type; 265 266 switch ( extension ) { 267 268 case 'bmp': 269 270 type = 'image/bmp'; 271 break; 272
273 case 'jpg': 274 case 'jpeg': 275 276 type = 'image/jpeg'; 277 break; 278 279 case 'png': 280 281 type = 'image/png'; 282 break; 283 284 case 'tif': 285 286 type = 'image/tiff'; 287 break; 288 289 case 'tga': 290 291 if ( typeof THREE.TGALoader !== 'function' ) { 292 293 console.warn( 'FBXLoader: THREE.TGALoader is required to load TGA textures' ); 294 return; 295 296 } else { 297 298 if ( THREE.Loader.Handlers.get( '.tga' ) === null ) { 299 300 var tgaLoader = new THREE.TGALoader(); 301 tgaLoader.setPath( this.textureLoader.path ); 302 303 THREE.Loader.Handlers.add( /\.tga$/i, tgaLoader ); 304 305 } 306 307 type = 'image/tga'; 308 break; 309 310 } 311 312 default: 313 314 console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' ); 315 return; 316 317 } 318 319 if ( typeof content === 'string' ) { // ASCII format 320 321 return 'data:' + type + ';base64,' + content; 322 323 } else { // Binary Format 324 325 var array = new Uint8Array( content ); 326 return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) ); 327 328 } 329 330 }, 331 332 // Parse nodes in FBXTree.Objects.Texture 333 // These contain details such as UV scaling, cropping, rotation etc and are connected 334 // to images in FBXTree.Objects.Video 335 parseTextures: function ( images ) { 336 337 var textureMap = new Map(); 338 339 if ( 'Texture' in fbxTree.Objects ) { 340 341 var textureNodes = fbxTree.Objects.Texture; 342 for ( var nodeID in textureNodes ) { 343 344 var texture = this.parseTexture( textureNodes[ nodeID ], images ); 345 textureMap.set( parseInt( nodeID ), texture ); 346 347 } 348 349 } 350 351 return textureMap; 352 353 }, 354 355 // Parse individual node in FBXTree.Objects.Texture 356 parseTexture: function ( textureNode, images ) { 357 358 var texture = this.loadTexture( textureNode, images ); 359 360 texture.ID = textureNode.id; 361 362 texture.name = textureNode.attrName; 363 364 var wrapModeU = textureNode.WrapModeU; 365 var wrapModeV = textureNode.WrapModeV; 366 367 var valueU = wrapModeU !== undefined ? wrapModeU.value : 0; 368 var valueV = wrapModeV !== undefined ? wrapModeV.value : 0; 369 370 // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a 371 // 0: repeat(default), 1: clamp 372 373 texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 374 texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 375 376 if ( 'Scaling' in textureNode ) { 377 378 var values = textureNode.Scaling.value; 379 380 texture.repeat.x = values[ 0 ]; 381 texture.repeat.y = values[ 1 ]; 382 383 } 384 385 return texture; 386 387 }, 388 389 // load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader 390 loadTexture: function ( textureNode, images ) { 391 392 var fileName; 393 394 var currentPath = this.textureLoader.path; 395 396 var children = connections.get( textureNode.id ).children; 397 398 if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) { 399 400 fileName = images[ children[ 0 ].ID ]; 401 402 if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) { 403 404 this.textureLoader.setPath( undefined ); 405 406 } 407 408 } 409 410 var texture; 411 412 var extension = textureNode.FileName.slice( - 3 ).toLowerCase(); 413 414 if ( extension === 'tga' ) { 415 416 var loader = THREE.Loader.Handlers.get( '.tga' ); 417 418 if ( loader === null ) { 419 420 console.warn( 'FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName ); 421 texture = new THREE.Texture(); 422 423 } else { 424 425 texture = loader.load( fileName ); 426 427 } 428 429 } else if ( extension === 'psd' ) { 430 431 console.warn( 'FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName ); 432 texture = new THREE.Texture(); 433 434 } else { 435 436 texture = this.textureLoader.load( fileName ); 437 438 } 439 440 this.textureLoader.setPath( currentPath ); 441 442 return texture; 443 444 }, 445 446 // Parse nodes in FBXTree.Objects.Material 447 parseMaterials: function ( textureMap ) { 448 449 var materialMap = new Map(); 450 451 if ( 'Material' in fbxTree.Objects ) { 452 453 var materialNodes = fbxTree.Objects.Material; 454 455 for ( var nodeID in materialNodes ) { 456 457 var material = this.parseMaterial( materialNodes[ nodeID ], textureMap ); 458 459 if ( material !== null ) materialMap.set( parseInt( nodeID ), material ); 460 461 } 462 463 } 464 465 return materialMap; 466 467 }, 468 469 // Parse single node in FBXTree.Objects.Material 470 // Materials are connected to texture maps in FBXTree.Objects.Textures 471 // FBX format currently only supports Lambert and Phong shading models 472 parseMaterial: function ( materialNode, textureMap ) { 473 474 var ID = materialNode.id; 475 var name = materialNode.attrName; 476 var type = materialNode.ShadingModel; 477 478 // Case where FBX wraps shading model in property object. 479 if ( typeof type === 'object' ) { 480 481 type = type.value; 482 483 } 484 485 // Ignore unused materials which don't have any connections. 486 if ( ! connections.has( ID ) ) return null; 487 488 var parameters = this.parseParameters( materialNode, textureMap, ID ); 489 490 var material; 491 492 switch ( type.toLowerCase() ) { 493 494 case 'phong': 495 material = new THREE.MeshPhongMaterial(); 496 break; 497 case 'lambert': 498 material = new THREE.MeshLambertMaterial(); 499 break; 500 default: 501 console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type ); 502 material = new THREE.MeshPhongMaterial(); 503 break; 504 505 } 506 507 material.setValues( parameters ); 508 material.name = name; 509 510 return material; 511 512 }, 513 514 // Parse FBX material and return parameters suitable for a three.js material 515 // Also parse the texture map and return any textures associated with the material 516 parseParameters: function ( materialNode, textureMap, ID ) { 517 518 var parameters = {}; 519 520 if ( materialNode.BumpFactor ) { 521 522 parameters.bumpScale = materialNode.BumpFactor.value; 523 524 } 525 if ( materialNode.Diffuse ) { 526 527 parameters.color = new THREE.Color().fromArray( materialNode.Diffuse.value ); 528 529 } else if ( materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color' ) { 530 531 // The blender exporter exports diffuse here instead of in materialNode.Diffuse 532 parameters.color = new THREE.Color().fromArray( materialNode.DiffuseColor.value ); 533 534 } 535 536 if ( materialNode.DisplacementFactor ) { 537 538 parameters.displacementScale = materialNode.DisplacementFactor.value; 539 540 } 541 542 if ( materialNode.Emissive ) { 543 544 parameters.emissive = new THREE.Color().fromArray( materialNode.Emissive.value ); 545 546 } else if ( materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color' ) { 547 548 // The blender exporter exports emissive color here instead of in materialNode.Emissive 549 parameters.emissive = new THREE.Color().fromArray( materialNode.EmissiveColor.value ); 550 551 } 552 553 if ( materialNode.EmissiveFactor ) { 554 555 parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value ); 556 557 } 558 559 if ( materialNode.Opacity ) { 560 561 parameters.opacity = parseFloat( materialNode.Opacity.value ); 562 563 } 564 565 if ( parameters.opacity < 1.0 ) { 566 567 parameters.transparent = true; 568 569 } 570 571 if ( materialNode.ReflectionFactor ) { 572 573 parameters.reflectivity = materialNode.ReflectionFactor.value; 574 575 } 576 577 if ( materialNode.Shininess ) { 578 579 parameters.shininess = materialNode.Shininess.value; 580 581 } 582 583 if ( materialNode.Specular ) { 584 585 parameters.specular = new THREE.Color().fromArray( materialNode.Specular.value ); 586 587 } else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) { 588 589 // The blender exporter exports specular color here instead of in materialNode.Specular 590 parameters.specular = new THREE.Color().fromArray( materialNode.SpecularColor.value ); 591 592 } 593 594 var self = this;
595 connections.get( ID ).children.forEach( function ( child ) { 596 597 var type = child.relationship; 598 599 switch ( type ) { 600 601 case 'Bump': 602 parameters.bumpMap = self.getTexture( textureMap, child.ID ); 603 break; 604 605 case 'Maya|TEX_ao_map': 606 parameters.aoMap = self.getTexture( textureMap, child.ID ); 607 break; 608 609 case 'DiffuseColor': 610 case 'Maya|TEX_color_map': 611 parameters.map = self.getTexture( textureMap, child.ID ); 612 break; 613 614 case 'DisplacementColor': 615 parameters.displacementMap = self.getTexture( textureMap, child.ID ); 616 break; 617 618 case 'EmissiveColor': 619 parameters.emissiveMap = self.getTexture( textureMap, child.ID ); 620 break; 621 622 case 'NormalMap': 623 case 'Maya|TEX_normal_map': 624 parameters.normalMap = self.getTexture( textureMap, child.ID ); 625 break; 626 627 case 'ReflectionColor': 628 parameters.envMap = self.getTexture( textureMap, child.ID ); 629 parameters.envMap.mapping = THREE.EquirectangularReflectionMapping; 630 break; 631 632 case 'SpecularColor': 633 parameters.specularMap = self.getTexture( textureMap, child.ID ); 634 break; 635 636 case 'TransparentColor': 637 parameters.alphaMap = self.getTexture( textureMap, child.ID ); 638 parameters.transparent = true; 639 break; 640 641 case 'AmbientColor': 642 case 'ShininessExponent': // AKA glossiness map 643 case 'SpecularFactor': // AKA specularLevel 644 case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor 645 default: 646 console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type ); 647 break; 648 649 } 650 651 } ); 652 653 return parameters; 654 655 }, 656 657 // get a texture from the textureMap for use by a material. 658 getTexture: function ( textureMap, id ) { 659 660 // if the texture is a layered texture, just use the first layer and issue a warning 661 if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) { 662 663 console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' ); 664 id = connections.get( id ).children[ 0 ].ID; 665 666 } 667 668 return textureMap.get( id ); 669 670 }, 671 672 // Parse nodes in FBXTree.Objects.Deformer 673 // Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here 674 // Generates map of Skeleton-like objects for use later when generating and binding skeletons. 675 parseDeformers: function () { 676 677 var skeletons = {}; 678 var morphTargets = {}; 679 680 if ( 'Deformer' in fbxTree.Objects ) { 681 682 var DeformerNodes = fbxTree.Objects.Deformer; 683 684 for ( var nodeID in DeformerNodes ) { 685 686 var deformerNode = DeformerNodes[ nodeID ]; 687 688 var relationships = connections.get( parseInt( nodeID ) ); 689 690 if ( deformerNode.attrType === 'Skin' ) { 691 692 var skeleton = this.parseSkeleton( relationships, DeformerNodes ); 693 skeleton.ID = nodeID; 694 695 if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' ); 696 skeleton.geometryID = relationships.parents[ 0 ].ID; 697 698 skeletons[ nodeID ] = skeleton; 699 700 } else if ( deformerNode.attrType === 'BlendShape' ) { 701 702 var morphTarget = { 703 id: nodeID, 704 }; 705 706 morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes ); 707 morphTarget.id = nodeID; 708 709 if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' ); 710 711 morphTargets[ nodeID ] = morphTarget; 712 713 } 714 715 } 716 717 } 718 719 return { 720 721 skeletons: skeletons, 722 morphTargets: morphTargets, 723 724 }; 725 726 }, 727 728 // Parse single nodes in FBXTree.Objects.Deformer 729 // The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster' 730 // Each skin node represents a skeleton and each cluster node represents a bone 731 parseSkeleton: function ( relationships, deformerNodes ) { 732 733 var rawBones = []; 734 735 relationships.children.forEach( function ( child ) { 736 737 var boneNode = deformerNodes[ child.ID ]; 738 739 if ( boneNode.attrType !== 'Cluster' ) return; 740 741 var rawBone = { 742 743 ID: child.ID, 744 indices: [], 745 weights: [], 746 transformLink: new THREE.Matrix4().fromArray( boneNode.TransformLink.a ), 747 // transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ), 748 // linkMode: boneNode.Mode, 749 750 }; 751
752 if ( 'Indexes' in boneNode ) { 753 754 rawBone.indices = boneNode.Indexes.a; 755 rawBone.weights = boneNode.Weights.a; 756 757 } 758 759 rawBones.push( rawBone ); 760 761 } ); 762 763 return { 764 765 rawBones: rawBones, 766 bones: [] 767 768 }; 769 770 }, 771 772 // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" 773 parseMorphTargets: function ( relationships, deformerNodes ) { 774 775 var rawMorphTargets = []; 776 777 for ( var i = 0; i < relationships.children.length; i ++ ) { 778 779 var child = relationships.children[ i ]; 780 781 var morphTargetNode = deformerNodes[ child.ID ]; 782 783 var rawMorphTarget = { 784 785 name: morphTargetNode.attrName, 786 initialWeight: morphTargetNode.DeformPercent, 787 id: morphTargetNode.id, 788 fullWeights: morphTargetNode.FullWeights.a 789 790 }; 791 792 if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return; 793 794 rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) { 795 796 return child.relationship === undefined; 797 798 } )[ 0 ].ID; 799 800 rawMorphTargets.push( rawMorphTarget ); 801 802 } 803 804 return rawMorphTargets; 805 806 }, 807 808 // create the main THREE.Group() to be returned by the loader 809 parseScene: function ( deformers, geometryMap, materialMap ) { 810 811 sceneGraph = new THREE.Group(); 812 813 var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap ); 814 815 var modelNodes = fbxTree.Objects.Model; 816 817 var self = this; 818 modelMap.forEach( function ( model ) { 819 820 var modelNode = modelNodes[ model.ID ]; 821 self.setLookAtProperties( model, modelNode ); 822 823 var parentConnections = connections.get( model.ID ).parents; 824 825 parentConnections.forEach( function ( connection ) { 826 827 var parent = modelMap.get( connection.ID ); 828 if ( parent !== undefined ) parent.add( model ); 829 830 } ); 831 832 if ( model.parent === null ) { 833 834 sceneGraph.add( model ); 835 836 } 837 838 839 } ); 840 841 this.bindSkeleton( deformers.skeletons, geometryMap, modelMap ); 842 843 this.createAmbientLight(); 844 845 this.setupMorphMaterials(); 846 847 sceneGraph.traverse( function ( node ) { 848 849 if ( node.userData.transformData ) { 850 851 if ( node.parent ) node.userData.transformData.parentMatrixWorld = node.parent.matrix; 852 853 var transform = generateTransform( node.userData.transformData ); 854 855 node.applyMatrix( transform ); 856 857 } 858 859 } ); 860 861 var animations = new AnimationParser().parse(); 862 863 // if all the models where already combined in a single group, just return that 864 if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) { 865 866 sceneGraph.children[ 0 ].animations = animations; 867 sceneGraph = sceneGraph.children[ 0 ]; 868 869 } 870 871 sceneGraph.animations = animations; 872 873 }, 874 875 // parse nodes in FBXTree.Objects.Model 876 parseModels: function ( skeletons, geometryMap, materialMap ) { 877 878 var modelMap = new Map(); 879 var modelNodes = fbxTree.Objects.Model; 880 881 for ( var nodeID in modelNodes ) { 882 883 var id = parseInt( nodeID ); 884 var node = modelNodes[ nodeID ]; 885 var relationships = connections.get( id ); 886 887 var model = this.buildSkeleton( relationships, skeletons, id, node.attrName ); 888 889 if ( ! model ) { 890 891 switch ( node.attrType ) { 892 893 case 'Camera': 894 model = this.createCamera( relationships ); 895 break; 896 case 'Light': 897 model = this.createLight( relationships ); 898 break; 899 case 'Mesh': 900 model = this.createMesh( relationships, geometryMap, materialMap ); 901 break; 902 case 'NurbsCurve': 903 model = this.createCurve( relationships, geometryMap ); 904 break; 905 case 'LimbNode': 906 case 'Root': 907 model = new THREE.Bone(); 908 break; 909 case 'Null': 910 default: 911 model = new THREE.Group(); 912 break; 913 914 } 915 916 model.name = THREE.PropertyBinding.sanitizeNodeName( node.attrName ); 917 model.ID = id; 918 919 } 920 921 this.getTransformData( model, node ); 922 modelMap.set( id, model ); 923 924 } 925 926 return modelMap; 927 928 }, 929 930 buildSkeleton: function ( relationships, skeletons, id, name ) { 931 932 var bone = null; 933 934 relationships.parents.forEach( function ( parent ) { 935 936 for ( var ID in skeletons ) { 937 938 var skeleton = skeletons[ ID ]; 939
940 skeleton.rawBones.forEach( function ( rawBone, i ) { 941 942 if ( rawBone.ID === parent.ID ) { 943 944 var subBone = bone; 945 bone = new THREE.Bone(); 946 947 bone.matrixWorld.copy( rawBone.transformLink ); 948 949 // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id 950 bone.name = THREE.PropertyBinding.sanitizeNodeName( name ); 951 bone.ID = id; 952 953 skeleton.bones[ i ] = bone; 954 955 // In cases where a bone is shared between multiple meshes 956 // duplicate the bone here and and it as a child of the first bone 957 if ( subBone !== null ) { 958 959 bone.add( subBone ); 960 961 } 962 963 } 964 965 } ); 966 967 } 968 969 } ); 970 971 return bone; 972 973 }, 974 975 // create a THREE.PerspectiveCamera or THREE.OrthographicCamera 976 createCamera: function ( relationships ) { 977 978 var model; 979 var cameraAttribute; 980 981 relationships.children.forEach( function ( child ) { 982 983 var attr = fbxTree.Objects.NodeAttribute[ child.ID ]; 984 985 if ( attr !== undefined ) { 986 987 cameraAttribute = attr; 988 989 } 990 991 } ); 992 993 if ( cameraAttribute === undefined ) { 994 995 model = new THREE.Object3D(); 996 997 } else { 998 999 var type = 0; 1000 if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) { 1001 1002 type = 1; 1003 1004 } 1005 1006 var nearClippingPlane = 1; 1007 if ( cameraAttribute.NearPlane !== undefined ) { 1008 1009 nearClippingPlane = cameraAttribute.NearPlane.value / 1000; 1010 1011 } 1012 1013 var farClippingPlane = 1000; 1014 if ( cameraAttribute.FarPlane !== undefined ) { 1015 1016 farClippingPlane = cameraAttribute.FarPlane.value / 1000; 1017 1018 } 1019 1020 1021 var width = window.innerWidth; 1022 var height = window.innerHeight; 1023 1024 if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) { 1025 1026 width = cameraAttribute.AspectWidth.value; 1027 height = cameraAttribute.AspectHeight.value; 1028 1029 } 1030 1031 var aspect = width / height; 1032 1033 var fov = 45; 1034 if ( cameraAttribute.FieldOfView !== undefined ) { 1035 1036 fov = cameraAttribute.FieldOfView.value; 1037 1038 } 1039 1040 var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; 1041 1042 switch ( type ) { 1043 1044 case 0: // Perspective 1045 model = new THREE.PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane ); 1046 if ( focalLength !== null ) model.setFocalLength( focalLength ); 1047 break; 1048 1049 case 1: // Orthographic 1050 model = new THREE.OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane ); 1051 break; 1052 1053 default: 1054 console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' ); 1055 model = new THREE.Object3D(); 1056 break; 1057 1058 } 1059 1060 } 1061 1062 return model; 1063 1064 }, 1065 1066 // Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight 1067 createLight: function ( relationships ) { 1068 1069 var model; 1070 var lightAttribute; 1071 1072 relationships.children.forEach( function ( child ) { 1073 1074 var attr = fbxTree.Objects.NodeAttribute[ child.ID ]; 1075 1076 if ( attr !== undefined ) { 1077 1078 lightAttribute = attr; 1079 1080 } 1081 1082 } ); 1083 1084 if ( lightAttribute === undefined ) { 1085 1086 model = new THREE.Object3D(); 1087 1088 } else { 1089 1090 var type; 1091 1092 // LightType can be undefined for Point lights 1093 if ( lightAttribute.LightType === undefined ) { 1094 1095 type = 0; 1096 1097 } else { 1098 1099 type = lightAttribute.LightType.value; 1100 1101 } 1102 1103 var color = 0xffffff; 1104 1105 if ( lightAttribute.Color !== undefined ) { 1106 1107 color = new THREE.Color().fromArray( lightAttribute.Color.value ); 1108 1109 } 1110 1111 var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100; 1112 1113 // light disabled 1114 if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) { 1115 1116 intensity = 0; 1117 1118 } 1119 1120 var distance = 0; 1121 if ( lightAttribute.FarAttenuationEnd !== undefined ) { 1122 1123 if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) { 1124 1125 distance = 0; 1126 1127 } else { 1128 1129 distance = lightAttribute.FarAttenuationEnd.value; 1130 1131 } 1132 1133 } 1134 1135 // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? 1136 var decay = 1; 1137 1138 switch ( type ) { 1139 1140 case 0: // Point 1141 model = new THREE.PointLight( color, intensity, distance, decay ); 1142 break; 1143 1144 case 1: // Directional 1145 model = new THREE.DirectionalLight( color, intensity ); 1146 break; 1147 1148 case 2: // Spot 1149 var angle = Math.PI / 3; 1150 1151 if ( lightAttribute.InnerAngle !== undefined ) { 1152 1153 angle = THREE.Math.degToRad( lightAttribute.InnerAngle.value ); 1154 1155 } 1156 1157 var penumbra = 0; 1158 if ( lightAttribute.OuterAngle !== undefined ) { 1159 1160 // TODO: this is not correct - FBX calculates outer and inner angle in degrees 1161 // with OuterAngle > InnerAngle && OuterAngle <= Math.PI 1162 // while three.js uses a penumbra between (0, 1) to attenuate the inner angle 1163 penumbra = THREE.Math.degToRad( lightAttribute.OuterAngle.value ); 1164 penumbra = Math.max( penumbra, 1 ); 1165 1166 } 1167 1168 model = new THREE.SpotLight( color, intensity, distance, angle, penumbra, decay ); 1169 break; 1170 1171 default: 1172 console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.' ); 1173 model = new THREE.PointLight( color, intensity ); 1174 break; 1175 1176 } 1177 1178 if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) { 1179 1180 model.castShadow = true; 1181 1182 } 1183 1184 } 1185 1186 return model; 1187 1188 }, 1189 1190 createMesh: function ( relationships, geometryMap, materialMap ) { 1191 1192 var model; 1193 var geometry = null; 1194 var material = null; 1195 var materials = []; 1196 1197 // get geometry and materials(s) from connections
1198 relationships.children.forEach( function ( child ) { 1199 1200 if ( geometryMap.has( child.ID ) ) { 1201 1202 geometry = geometryMap.get( child.ID ); 1203 1204 } 1205 1206 if ( materialMap.has( child.ID ) ) { 1207 1208 materials.push( materialMap.get( child.ID ) ); 1209 1210 } 1211 1212 } ); 1213 1214 if ( materials.length > 1 ) { 1215 1216 material = materials; 1217 1218 } else if ( materials.length > 0 ) { 1219 1220 material = materials[ 0 ]; 1221 1222 } else { 1223 1224 material = new THREE.MeshPhongMaterial( { color: 0xcccccc } ); 1225 materials.push( material ); 1226 1227 } 1228 1229 if ( 'color' in geometry.attributes ) { 1230 1231 materials.forEach( function ( material ) { 1232 1233 material.vertexColors = THREE.VertexColors; 1234 1235 } ); 1236 1237 } 1238 1239 if ( geometry.FBX_Deformer ) { 1240 1241 materials.forEach( function ( material ) { 1242 1243 material.skinning = true; 1244 1245 } ); 1246 1247 model = new THREE.SkinnedMesh( geometry, material ); 1248 model.normalizeSkinWeights(); 1249 1250 } else { 1251 1252 model = new THREE.Mesh( geometry, material ); 1253 1254 } 1255 1256 return model; 1257 1258 }, 1259 1260 createCurve: function ( relationships, geometryMap ) { 1261 1262 var geometry = relationships.children.reduce( function ( geo, child ) { 1263 1264 if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID ); 1265 1266 return geo; 1267 1268 }, null ); 1269 1270 // FBX does not list materials for Nurbs lines, so we'll just put our own in here. 1271 var material = new THREE.LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } ); 1272 return new THREE.Line( geometry, material ); 1273 1274 }, 1275 1276 // parse the model node for transform data 1277 getTransformData: function ( model, modelNode ) { 1278 1279 var transformData = {}; 1280 1281 if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); 1282 1283 if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); 1284 else transformData.eulerOrder = 'ZYX'; 1285 1286 if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value; 1287 1288 if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value; 1289 if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value; 1290 if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value; 1291 1292 if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value; 1293 1294 if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value; 1295 if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value; 1296 1297 if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value; 1298 if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value; 1299 1300 model.userData.transformData = transformData; 1301 1302 }, 1303 1304 setLookAtProperties: function ( model, modelNode ) { 1305 1306 if ( 'LookAtProperty' in modelNode ) { 1307 1308 var children = connections.get( model.ID ).children; 1309 1310 children.forEach( function ( child ) { 1311 1312 if ( child.relationship === 'LookAtProperty' ) { 1313 1314 var lookAtTarget = fbxTree.Objects.Model[ child.ID ]; 1315 1316 if ( 'Lcl_Translation' in lookAtTarget ) { 1317 1318 var pos = lookAtTarget.Lcl_Translation.value; 1319 1320 // DirectionalLight, SpotLight 1321 if ( model.target !== undefined ) { 1322 1323 model.target.position.fromArray( pos ); 1324 sceneGraph.add( model.target ); 1325 1326 } else { // Cameras and other Object3Ds 1327 1328 model.lookAt( new THREE.Vector3().fromArray( pos ) ); 1329 1330 } 1331 1332 } 1333 1334 } 1335 1336 } ); 1337 1338 } 1339 1340 }, 1341 1342 bindSkeleton: function ( skeletons, geometryMap, modelMap ) { 1343 1344 var bindMatrices = this.parsePoseNodes(); 1345 1346 for ( var ID in skeletons ) { 1347 1348 var skeleton = skeletons[ ID ]; 1349 1350 var parents = connections.get( parseInt( skeleton.ID ) ).parents; 1351 1352 parents.forEach( function ( parent ) { 1353 1354 if ( geometryMap.has( parent.ID ) ) { 1355 1356 var geoID = parent.ID; 1357 var geoRelationships = connections.get( geoID ); 1358 1359 geoRelationships.parents.forEach( function ( geoConnParent ) { 1360 1361 if ( modelMap.has( geoConnParent.ID ) ) { 1362 1363 var model = modelMap.get( geoConnParent.ID ); 1364 1365 model.bind( new THREE.Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] ); 1366 1367 } 1368 1369 } ); 1370 1371 } 1372 1373 } ); 1374 1375 } 1376 1377 }, 1378 1379 parsePoseNodes: function () { 1380 1381 var bindMatrices = {}; 1382 1383 if ( 'Pose' in fbxTree.Objects ) { 1384 1385 var BindPoseNode = fbxTree.Objects.Pose; 1386 1387 for ( var nodeID in BindPoseNode ) { 1388 1389 if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) { 1390 1391 var poseNodes = BindPoseNode[ nodeID ].PoseNode; 1392 1393 if ( Array.isArray( poseNodes ) ) { 1394
1395 poseNodes.forEach( function ( poseNode ) { 1396 1397 bindMatrices[ poseNode.Node ] = new THREE.Matrix4().fromArray( poseNode.Matrix.a ); 1398 1399 } ); 1400 1401 } else { 1402 1403 bindMatrices[ poseNodes.Node ] = new THREE.Matrix4().fromArray( poseNodes.Matrix.a ); 1404 1405 } 1406 1407 } 1408 1409 } 1410 1411 } 1412 1413 return bindMatrices; 1414 1415 }, 1416 1417 // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light 1418 createAmbientLight: function () { 1419 1420 if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) { 1421 1422 var ambientColor = fbxTree.GlobalSettings.AmbientColor.value; 1423 var r = ambientColor[ 0 ]; 1424 var g = ambientColor[ 1 ]; 1425 var b = ambientColor[ 2 ]; 1426 1427 if ( r !== 0 || g !== 0 || b !== 0 ) { 1428 1429 var color = new THREE.Color( r, g, b ); 1430 sceneGraph.add( new THREE.AmbientLight( color, 1 ) ); 1431 1432 } 1433 1434 } 1435 1436 }, 1437 1438 setupMorphMaterials: function () { 1439 1440 var self = this; 1441 sceneGraph.traverse( function ( child ) { 1442 1443 if ( child.isMesh ) { 1444 1445 if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) { 1446 1447 if ( Array.isArray( child.material ) ) { 1448 1449 child.material.forEach( function ( material, i ) { 1450 1451 self.setupMorphMaterial( child, material, i ); 1452 1453 } ); 1454 1455 } else { 1456 1457 self.setupMorphMaterial( child, child.material ); 1458 1459 } 1460 1461 } 1462 1463 } 1464 1465 } ); 1466 1467 }, 1468 1469 setupMorphMaterial: function ( child, material, index ) { 1470 1471 var uuid = child.uuid; 1472 var matUuid = material.uuid; 1473 1474 // if a geometry has morph targets, it cannot share the material with other geometries 1475 var sharedMat = false; 1476 1477 sceneGraph.traverse( function ( node ) { 1478 1479 if ( node.isMesh ) { 1480 1481 if ( Array.isArray( node.material ) ) { 1482 1483 node.material.forEach( function ( mat ) { 1484 1485 if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true; 1486 1487 } ); 1488 1489 } else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true; 1490 1491 } 1492 1493 } ); 1494 1495 if ( sharedMat === true ) { 1496 1497 var clonedMat = material.clone(); 1498 clonedMat.morphTargets = true; 1499 1500 if ( index === undefined ) child.material = clonedMat; 1501 else child.material[ index ] = clonedMat; 1502 1503 } else material.morphTargets = true; 1504 1505 } 1506 1507 }; 1508 1509 // parse Geometry data from FBXTree and return map of BufferGeometries 1510 function GeometryParser() {} 1511 1512 GeometryParser.prototype = { 1513 1514 constructor: GeometryParser, 1515 1516 // Parse nodes in FBXTree.Objects.Geometry 1517 parse: function ( deformers ) { 1518 1519 var geometryMap = new Map(); 1520 1521 if ( 'Geometry' in fbxTree.Objects ) { 1522 1523 var geoNodes = fbxTree.Objects.Geometry; 1524 1525 for ( var nodeID in geoNodes ) { 1526 1527 var relationships = connections.get( parseInt( nodeID ) ); 1528 var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers ); 1529 1530 geometryMap.set( parseInt( nodeID ), geo ); 1531 1532 } 1533 1534 } 1535 1536 return geometryMap; 1537 1538 }, 1539 1540 // Parse single node in FBXTree.Objects.Geometry 1541 parseGeometry: function ( relationships, geoNode, deformers ) { 1542 1543 switch ( geoNode.attrType ) { 1544 1545 case 'Mesh': 1546 return this.parseMeshGeometry( relationships, geoNode, deformers ); 1547 break; 1548 1549 case 'NurbsCurve': 1550 return this.parseNurbsGeometry( geoNode ); 1551 break; 1552 1553 } 1554 1555 }, 1556 1557 // Parse single node mesh geometry in FBXTree.Objects.Geometry 1558 parseMeshGeometry: function ( relationships, geoNode, deformers ) { 1559 1560 var skeletons = deformers.skeletons; 1561 var morphTargets = deformers.morphTargets; 1562 1563 var modelNodes = relationships.parents.map( function ( parent ) { 1564 1565 return fbxTree.Objects.Model[ parent.ID ]; 1566 1567 } ); 1568 1569 // don't create geometry if it is not associated with any models 1570 if ( modelNodes.length === 0 ) return; 1571 1572 var skeleton = relationships.children.reduce( function ( skeleton, child ) { 1573 1574 if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ]; 1575 1576 return skeleton; 1577 1578 }, null ); 1579 1580 var morphTarget = relationships.children.reduce( function ( morphTarget, child ) { 1581 1582 if ( morphTargets[ child.ID ] !== undefined ) morphTarget = morphTargets[ child.ID ]; 1583 1584 return morphTarget; 1585 1586 }, null ); 1587 1588 // Assume one model and get the preRotation from that 1589 // if there is more than one model associated with the geometry this may cause problems 1590 var modelNode = modelNodes[ 0 ]; 1591 1592 var transformData = {}; 1593 1594 if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); 1595 if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); 1596 1597 if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value; 1598 if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value; 1599 if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value; 1600 1601 var transform = generateTransform( transformData ); 1602 1603 return this.genGeometry( geoNode, skeleton, morphTarget, transform ); 1604 1605 }, 1606 1607 // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry 1608 genGeometry: function ( geoNode, skeleton, morphTarget, preTransform ) { 1609 1610 var geo = new THREE.BufferGeometry(); 1611 if ( geoNode.attrName ) geo.name = geoNode.attrName; 1612 1613 var geoInfo = this.parseGeoNode( geoNode, skeleton ); 1614 var buffers = this.genBuffers( geoInfo ); 1615 1616 var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 ); 1617 1618 preTransform.applyToBufferAttribute( positionAttribute ); 1619 1620 geo.addAttribute( 'position', positionAttribute ); 1621 1622 if ( buffers.colors.length > 0 ) { 1623 1624 geo.addAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) ); 1625 1626 } 1627 1628 if ( skeleton ) { 1629 1630 geo.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) ); 1631 1632 geo.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffers.vertexWeights, 4 ) ); 1633 1634 // used later to bind the skeleton to the model 1635 geo.FBX_Deformer = skeleton; 1636 1637 } 1638 1639 if ( buffers.normal.length > 0 ) { 1640 1641 var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 ); 1642 1643 var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform ); 1644 normalMatrix.applyToBufferAttribute( normalAttribute ); 1645
1646 geo.addAttribute( 'normal', normalAttribute ); 1647 1648 } 1649 1650 buffers.uvs.forEach( function ( uvBuffer, i ) { 1651 1652 // subsequent uv buffers are called 'uv1', 'uv2', ... 1653 var name = 'uv' + ( i + 1 ).toString(); 1654 1655 // the first uv buffer is just called 'uv' 1656 if ( i === 0 ) { 1657 1658 name = 'uv'; 1659 1660 } 1661 1662 geo.addAttribute( name, new THREE.Float32BufferAttribute( buffers.uvs[ i ], 2 ) ); 1663 1664 } ); 1665 1666 if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { 1667 1668 // Convert the material indices of each vertex into rendering groups on the geometry. 1669 var prevMaterialIndex = buffers.materialIndex[ 0 ]; 1670 var startIndex = 0; 1671 1672 buffers.materialIndex.forEach( function ( currentIndex, i ) { 1673 1674 if ( currentIndex !== prevMaterialIndex ) { 1675 1676 geo.addGroup( startIndex, i - startIndex, prevMaterialIndex ); 1677 1678 prevMaterialIndex = currentIndex; 1679 startIndex = i; 1680 1681 } 1682 1683 } ); 1684 1685 // the loop above doesn't add the last group, do that here. 1686 if ( geo.groups.length > 0 ) { 1687 1688 var lastGroup = geo.groups[ geo.groups.length - 1 ]; 1689 var lastIndex = lastGroup.start + lastGroup.count; 1690 1691 if ( lastIndex !== buffers.materialIndex.length ) { 1692 1693 geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex ); 1694 1695 } 1696 1697 } 1698 1699 // case where there are multiple materials but the whole geometry is only 1700 // using one of them 1701 if ( geo.groups.length === 0 ) { 1702 1703 geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] ); 1704 1705 } 1706 1707 } 1708 1709 this.addMorphTargets( geo, geoNode, morphTarget, preTransform ); 1710 1711 return geo; 1712 1713 }, 1714 1715 parseGeoNode: function ( geoNode, skeleton ) { 1716 1717 var geoInfo = {}; 1718 1719 geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : []; 1720 geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : []; 1721 1722 if ( geoNode.LayerElementColor ) { 1723 1724 geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] ); 1725 1726 } 1727 1728 if ( geoNode.LayerElementMaterial ) { 1729 1730 geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] ); 1731 1732 } 1733 1734 if ( geoNode.LayerElementNormal ) { 1735 1736 geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] ); 1737 1738 } 1739 1740 if ( geoNode.LayerElementUV ) { 1741 1742 geoInfo.uv = []; 1743 1744 var i = 0; 1745 while ( geoNode.LayerElementUV[ i ] ) { 1746 1747 geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) ); 1748 i ++; 1749 1750 } 1751 1752 } 1753 1754 geoInfo.weightTable = {}; 1755 1756 if ( skeleton !== null ) { 1757 1758 geoInfo.skeleton = skeleton; 1759 1760 skeleton.rawBones.forEach( function ( rawBone, i ) { 1761 1762 // loop over the bone's vertex indices and weights 1763 rawBone.indices.forEach( function ( index, j ) { 1764 1765 if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = []; 1766 1767 geoInfo.weightTable[ index ].push( { 1768 1769 id: i, 1770 weight: rawBone.weights[ j ], 1771 1772 } ); 1773 1774 } ); 1775 1776 } ); 1777 1778 } 1779 1780 return geoInfo; 1781 1782 }, 1783 1784 genBuffers: function ( geoInfo ) { 1785 1786 var buffers = { 1787 vertex: [], 1788 normal: [], 1789 colors: [], 1790 uvs: [], 1791 materialIndex: [], 1792 vertexWeights: [], 1793 weightsIndices: [], 1794 }; 1795 1796 var polygonIndex = 0; 1797 var faceLength = 0; 1798 var displayedWeightsWarning = false; 1799 1800 // these will hold data for a single face 1801 var facePositionIndexes = []; 1802 var faceNormals = []; 1803 var faceColors = []; 1804 var faceUVs = []; 1805 var faceWeights = []; 1806 var faceWeightIndices = []; 1807 1808 var self = this; 1809 geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) { 1810 1811 var endOfFace = false; 1812 1813 // Face index and vertex index arrays are combined in a single array 1814 // A cube with quad faces looks like this: 1815 // PolygonVertexIndex: *24 { 1816 // a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5 1817 // } 1818 // Negative numbers mark the end of a face - first face here is 0, 1, 3, -3 1819 // to find index of last vertex bit shift the index: ^ - 1 1820 if ( vertexIndex < 0 ) { 1821 1822 vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1 1823 endOfFace = true; 1824 1825 } 1826 1827 var weightIndices = []; 1828 var weights = []; 1829 1830 facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 ); 1831 1832 if ( geoInfo.color ) { 1833 1834 var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color ); 1835 1836 faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] ); 1837 1838 } 1839 1840 if ( geoInfo.skeleton ) { 1841 1842 if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) { 1843 1844 geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) { 1845 1846 weights.push( wt.weight ); 1847 weightIndices.push( wt.id ); 1848 1849 } ); 1850 1851 1852 } 1853 1854 if ( weights.length > 4 ) { 1855 1856 if ( ! displayedWeightsWarning ) { 1857 1858 console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' ); 1859 displayedWeightsWarning = true; 1860 1861 } 1862 1863 var wIndex = [ 0, 0, 0, 0 ]; 1864 var Weight = [ 0, 0, 0, 0 ]; 1865
1866 weights.forEach( function ( weight, weightIndex ) { 1867 1868 var currentWeight = weight; 1869 var currentIndex = weightIndices[ weightIndex ]; 1870 1871 Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) { 1872 1873 if ( currentWeight > comparedWeight ) { 1874 1875 comparedWeightArray[ comparedWeightIndex ] = currentWeight; 1876 currentWeight = comparedWeight; 1877 1878 var tmp = wIndex[ comparedWeightIndex ]; 1879 wIndex[ comparedWeightIndex ] = currentIndex; 1880 currentIndex = tmp; 1881 1882 } 1883 1884 } ); 1885 1886 } ); 1887 1888 weightIndices = wIndex; 1889 weights = Weight; 1890 1891 } 1892 1893 // if the weight array is shorter than 4 pad with 0s 1894 while ( weights.length < 4 ) { 1895 1896 weights.push( 0 ); 1897 weightIndices.push( 0 ); 1898 1899 } 1900 1901 for ( var i = 0; i < 4; ++ i ) { 1902 1903 faceWeights.push( weights[ i ] ); 1904 faceWeightIndices.push( weightIndices[ i ] ); 1905 1906 } 1907 1908 } 1909 1910 if ( geoInfo.normal ) { 1911 1912 var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal ); 1913 1914 faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] ); 1915 1916 } 1917 1918 if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { 1919 1920 var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ]; 1921 1922 } 1923 1924 if ( geoInfo.uv ) { 1925 1926 geoInfo.uv.forEach( function ( uv, i ) { 1927 1928 var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv ); 1929 1930 if ( faceUVs[ i ] === undefined ) { 1931 1932 faceUVs[ i ] = []; 1933 1934 } 1935 1936 faceUVs[ i ].push( data[ 0 ] ); 1937 faceUVs[ i ].push( data[ 1 ] ); 1938 1939 } ); 1940 1941 } 1942 1943 faceLength ++; 1944 1945 if ( endOfFace ) { 1946 1947 self.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ); 1948 1949 polygonIndex ++; 1950 faceLength = 0; 1951 1952 // reset arrays for the next face 1953 facePositionIndexes = []; 1954 faceNormals = []; 1955 faceColors = []; 1956 faceUVs = []; 1957 faceWeights = []; 1958 faceWeightIndices = []; 1959 1960 } 1961 1962 } ); 1963 1964 return buffers; 1965 1966 }, 1967 1968 // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris 1969 genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) { 1970 1971 for ( var i = 2; i < faceLength; i ++ ) { 1972 1973 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] ); 1974 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] ); 1975 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] ); 1976 1977 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] ); 1978 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] ); 1979 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] ); 1980 1981 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] ); 1982 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] ); 1983 buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] ); 1984 1985 if ( geoInfo.skeleton ) { 1986 1987 buffers.vertexWeights.push( faceWeights[ 0 ] ); 1988 buffers.vertexWeights.push( faceWeights[ 1 ] ); 1989 buffers.vertexWeights.push( faceWeights[ 2 ] ); 1990 buffers.vertexWeights.push( faceWeights[ 3 ] ); 1991 1992 buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] ); 1993 buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] ); 1994 buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] ); 1995 buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] ); 1996 1997 buffers.vertexWeights.push( faceWeights[ i * 4 ] ); 1998 buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] ); 1999 buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] ); 2000 buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] ); 2001 2002 buffers.weightsIndices.push( faceWeightIndices[ 0 ] ); 2003 buffers.weightsIndices.push( faceWeightIndices[ 1 ] ); 2004 buffers.weightsIndices.push( faceWeightIndices[ 2 ] ); 2005 buffers.weightsIndices.push( faceWeightIndices[ 3 ] ); 2006 2007 buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] ); 2008 buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] ); 2009 buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] ); 2010 buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] ); 2011 2012 buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] ); 2013 buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] ); 2014 buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] ); 2015 buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] ); 2016 2017 } 2018 2019 if ( geoInfo.color ) { 2020 2021 buffers.colors.push( faceColors[ 0 ] ); 2022 buffers.colors.push( faceColors[ 1 ] ); 2023 buffers.colors.push( faceColors[ 2 ] ); 2024 2025 buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] ); 2026 buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] ); 2027 buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] ); 2028 2029 buffers.colors.push( faceColors[ i * 3 ] ); 2030 buffers.colors.push( faceColors[ i * 3 + 1 ] ); 2031 buffers.colors.push( faceColors[ i * 3 + 2 ] ); 2032 2033 } 2034 2035 if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { 2036 2037 buffers.materialIndex.push( materialIndex ); 2038 buffers.materialIndex.push( materialIndex ); 2039 buffers.materialIndex.push( materialIndex ); 2040 2041 } 2042 2043 if ( geoInfo.normal ) { 2044 2045 buffers.normal.push( faceNormals[ 0 ] ); 2046 buffers.normal.push( faceNormals[ 1 ] ); 2047 buffers.normal.push( faceNormals[ 2 ] ); 2048 2049 buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] ); 2050 buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] ); 2051 buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] ); 2052 2053 buffers.normal.push( faceNormals[ i * 3 ] ); 2054 buffers.normal.push( faceNormals[ i * 3 + 1 ] ); 2055 buffers.normal.push( faceNormals[ i * 3 + 2 ] ); 2056 2057 } 2058 2059 if ( geoInfo.uv ) { 2060 2061 geoInfo.uv.forEach( function ( uv, j ) { 2062 2063 if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = []; 2064 2065 buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] ); 2066 buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] ); 2067
2068 buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] ); 2069 buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] ); 2070 2071 buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] ); 2072 buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] ); 2073 2074 } ); 2075 2076 } 2077 2078 } 2079 2080 }, 2081 2082 addMorphTargets: function ( parentGeo, parentGeoNode, morphTarget, preTransform ) { 2083 2084 if ( morphTarget === null ) return; 2085 2086 parentGeo.morphAttributes.position = []; 2087 // parentGeo.morphAttributes.normal = []; // not implemented 2088 2089 var self = this; 2090 morphTarget.rawTargets.forEach( function ( rawTarget ) { 2091 2092 var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ]; 2093 2094 if ( morphGeoNode !== undefined ) { 2095 2096 self.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name ); 2097 2098 } 2099 2100 } ); 2101 2102 }, 2103 2104 // a morph geometry node is similar to a standard node, and the node is also contained 2105 // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal 2106 // and a special attribute Index defining which vertices of the original geometry are affected 2107 // Normal and position attributes only have data for the vertices that are affected by the morph 2108 genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) { 2109 2110 var morphGeo = new THREE.BufferGeometry(); 2111 if ( morphGeoNode.attrName ) morphGeo.name = morphGeoNode.attrName; 2112 2113 var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : []; 2114 2115 // make a copy of the parent's vertex positions 2116 var vertexPositions = ( parentGeoNode.Vertices !== undefined ) ? parentGeoNode.Vertices.a.slice() : []; 2117 2118 var morphPositions = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : []; 2119 var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : []; 2120 2121 for ( var i = 0; i < indices.length; i ++ ) { 2122 2123 var morphIndex = indices[ i ] * 3; 2124 2125 // FBX format uses blend shapes rather than morph targets. This can be converted 2126 // by additively combining the blend shape positions with the original geometry's positions 2127 vertexPositions[ morphIndex ] += morphPositions[ i * 3 ]; 2128 vertexPositions[ morphIndex + 1 ] += morphPositions[ i * 3 + 1 ]; 2129 vertexPositions[ morphIndex + 2 ] += morphPositions[ i * 3 + 2 ]; 2130 2131 } 2132 2133 // TODO: add morph normal support 2134 var morphGeoInfo = { 2135 vertexIndices: vertexIndices, 2136 vertexPositions: vertexPositions, 2137 }; 2138 2139 var morphBuffers = this.genBuffers( morphGeoInfo ); 2140 2141 var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 ); 2142 positionAttribute.name = name || morphGeoNode.attrName; 2143 2144 preTransform.applyToBufferAttribute( positionAttribute ); 2145 2146 parentGeo.morphAttributes.position.push( positionAttribute ); 2147 2148 }, 2149 2150 // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists 2151 parseNormals: function ( NormalNode ) { 2152 2153 var mappingType = NormalNode.MappingInformationType; 2154 var referenceType = NormalNode.ReferenceInformationType; 2155 var buffer = NormalNode.Normals.a; 2156 var indexBuffer = []; 2157 if ( referenceType === 'IndexToDirect' ) { 2158 2159 if ( 'NormalIndex' in NormalNode ) { 2160 2161 indexBuffer = NormalNode.NormalIndex.a; 2162 2163 } else if ( 'NormalsIndex' in NormalNode ) { 2164 2165 indexBuffer = NormalNode.NormalsIndex.a; 2166 2167 } 2168 2169 } 2170 2171 return { 2172 dataSize: 3, 2173 buffer: buffer, 2174 indices: indexBuffer, 2175 mappingType: mappingType, 2176 referenceType: referenceType 2177 }; 2178 2179 }, 2180 2181 // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists 2182 parseUVs: function ( UVNode ) { 2183 2184 var mappingType = UVNode.MappingInformationType; 2185 var referenceType = UVNode.ReferenceInformationType; 2186 var buffer = UVNode.UV.a; 2187 var indexBuffer = []; 2188 if ( referenceType === 'IndexToDirect' ) { 2189 2190 indexBuffer = UVNode.UVIndex.a; 2191 2192 } 2193 2194 return { 2195 dataSize: 2, 2196 buffer: buffer, 2197 indices: indexBuffer, 2198 mappingType: mappingType, 2199 referenceType: referenceType 2200 }; 2201 2202 }, 2203 2204 // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists 2205 parseVertexColors: function ( ColorNode ) { 2206 2207 var mappingType = ColorNode.MappingInformationType; 2208 var referenceType = ColorNode.ReferenceInformationType; 2209 var buffer = ColorNode.Colors.a; 2210 var indexBuffer = []; 2211 if ( referenceType === 'IndexToDirect' ) { 2212 2213 indexBuffer = ColorNode.ColorIndex.a; 2214 2215 } 2216 2217 return { 2218 dataSize: 4, 2219 buffer: buffer, 2220 indices: indexBuffer, 2221 mappingType: mappingType, 2222 referenceType: referenceType 2223 }; 2224 2225 }, 2226 2227 // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists 2228 parseMaterialIndices: function ( MaterialNode ) { 2229 2230 var mappingType = MaterialNode.MappingInformationType; 2231 var referenceType = MaterialNode.ReferenceInformationType; 2232 2233 if ( mappingType === 'NoMappingInformation' ) { 2234 2235 return { 2236 dataSize: 1, 2237 buffer: [ 0 ], 2238 indices: [ 0 ], 2239 mappingType: 'AllSame', 2240 referenceType: referenceType 2241 }; 2242 2243 } 2244 2245 var materialIndexBuffer = MaterialNode.Materials.a; 2246 2247 // Since materials are stored as indices, there's a bit of a mismatch between FBX and what 2248 // we expect.So we create an intermediate buffer that points to the index in the buffer, 2249 // for conforming with the other functions we've written for other data. 2250 var materialIndices = []; 2251 2252 for ( var i = 0; i < materialIndexBuffer.length; ++ i ) { 2253 2254 materialIndices.push( i ); 2255 2256 } 2257 2258 return { 2259 dataSize: 1, 2260 buffer: materialIndexBuffer, 2261 indices: materialIndices, 2262 mappingType: mappingType, 2263 referenceType: referenceType 2264 }; 2265 2266 }, 2267 2268 // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry 2269 parseNurbsGeometry: function ( geoNode ) { 2270 2271 if ( THREE.NURBSCurve === undefined ) { 2272 2273 console.error( 'THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' ); 2274 return new THREE.BufferGeometry(); 2275 2276 } 2277 2278 var order = parseInt( geoNode.Order ); 2279 2280 if ( isNaN( order ) ) { 2281 2282 console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id ); 2283 return new THREE.BufferGeometry(); 2284 2285 } 2286 2287 var degree = order - 1; 2288 2289 var knots = geoNode.KnotVector.a; 2290 var controlPoints = []; 2291 var pointsValues = geoNode.Points.a; 2292 2293 for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) { 2294 2295 controlPoints.push( new THREE.Vector4().fromArray( pointsValues, i ) ); 2296 2297 } 2298 2299 var startKnot, endKnot; 2300 2301 if ( geoNode.Form === 'Closed' ) { 2302 2303 controlPoints.push( controlPoints[ 0 ] ); 2304 2305 } else if ( geoNode.Form === 'Periodic' ) { 2306 2307 startKnot = degree; 2308 endKnot = knots.length - 1 - startKnot; 2309 2310 for ( var i = 0; i < degree; ++ i ) { 2311 2312 controlPoints.push( controlPoints[ i ] ); 2313 2314 } 2315 2316 } 2317 2318 var curve = new THREE.NURBSCurve( degree, knots, controlPoints, startKnot, endKnot ); 2319 var vertices = curve.getPoints( controlPoints.length * 7 ); 2320 2321 var positions = new Float32Array( vertices.length * 3 ); 2322
2323 vertices.forEach( function ( vertex, i ) { 2324 2325 vertex.toArray( positions, i * 3 ); 2326 2327 } ); 2328 2329 var geometry = new THREE.BufferGeometry(); 2330 geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); 2331 2332 return geometry; 2333 2334 }, 2335 2336 }; 2337 2338 // parse animation data from FBXTree 2339 function AnimationParser() {} 2340 2341 AnimationParser.prototype = { 2342 2343 constructor: AnimationParser, 2344 2345 // take raw animation clips and turn them into three.js animation clips 2346 parse: function () { 2347 2348 var animationClips = []; 2349 2350 var rawClips = this.parseClips(); 2351 2352 if ( rawClips !== undefined ) { 2353 2354 for ( var key in rawClips ) { 2355 2356 var rawClip = rawClips[ key ]; 2357 2358 var clip = this.addClip( rawClip ); 2359 2360 animationClips.push( clip ); 2361 2362 } 2363 2364 } 2365 2366 return animationClips; 2367 2368 }, 2369 2370 parseClips: function () { 2371 2372 // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, 2373 // if this is undefined we can safely assume there are no animations 2374 if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined; 2375 2376 var curveNodesMap = this.parseAnimationCurveNodes(); 2377 2378 this.parseAnimationCurves( curveNodesMap ); 2379 2380 var layersMap = this.parseAnimationLayers( curveNodesMap ); 2381 var rawClips = this.parseAnimStacks( layersMap ); 2382 2383 return rawClips; 2384 2385 }, 2386 2387 // parse nodes in FBXTree.Objects.AnimationCurveNode 2388 // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) 2389 // and is referenced by an AnimationLayer 2390 parseAnimationCurveNodes: function () { 2391 2392 var rawCurveNodes = fbxTree.Objects.AnimationCurveNode; 2393 2394 var curveNodesMap = new Map(); 2395 2396 for ( var nodeID in rawCurveNodes ) { 2397 2398 var rawCurveNode = rawCurveNodes[ nodeID ]; 2399 2400 if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) { 2401 2402 var curveNode = { 2403 2404 id: rawCurveNode.id, 2405 attr: rawCurveNode.attrName, 2406 curves: {}, 2407 2408 }; 2409 2410 curveNodesMap.set( curveNode.id, curveNode ); 2411 2412 } 2413 2414 } 2415 2416 return curveNodesMap; 2417 2418 }, 2419 2420 // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to 2421 // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated 2422 // axis ( e.g. times and values of x rotation) 2423 parseAnimationCurves: function ( curveNodesMap ) { 2424 2425 var rawCurves = fbxTree.Objects.AnimationCurve; 2426 2427 // TODO: Many values are identical up to roundoff error, but won't be optimised 2428 // e.g. position times: [0, 0.4, 0. 8] 2429 // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] 2430 // clearly, this should be optimised to 2431 // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] 2432 // this shows up in nearly every FBX file, and generally time array is length > 100 2433 2434 for ( var nodeID in rawCurves ) { 2435 2436 var animationCurve = { 2437 2438 id: rawCurves[ nodeID ].id, 2439 times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ), 2440 values: rawCurves[ nodeID ].KeyValueFloat.a, 2441 2442 }; 2443 2444 var relationships = connections.get( animationCurve.id ); 2445 2446 if ( relationships !== undefined ) { 2447 2448 var animationCurveID = relationships.parents[ 0 ].ID; 2449 var animationCurveRelationship = relationships.parents[ 0 ].relationship; 2450 2451 if ( animationCurveRelationship.match( /X/ ) ) { 2452 2453 curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve; 2454 2455 } else if ( animationCurveRelationship.match( /Y/ ) ) { 2456 2457 curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve; 2458 2459 } else if ( animationCurveRelationship.match( /Z/ ) ) { 2460 2461 curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve; 2462 2463 } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) { 2464 2465 curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve; 2466 2467 } 2468 2469 } 2470 2471 } 2472 2473 }, 2474 2475 // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references 2476 // to various AnimationCurveNodes and is referenced by an AnimationStack node 2477 // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack 2478 parseAnimationLayers: function ( curveNodesMap ) { 2479 2480 var rawLayers = fbxTree.Objects.AnimationLayer; 2481 2482 var layersMap = new Map(); 2483 2484 for ( var nodeID in rawLayers ) { 2485 2486 var layerCurveNodes = []; 2487 2488 var connection = connections.get( parseInt( nodeID ) ); 2489 2490 if ( connection !== undefined ) { 2491 2492 // all the animationCurveNodes used in the layer 2493 var children = connection.children; 2494
2495 children.forEach( function ( child, i ) { 2496 2497 if ( curveNodesMap.has( child.ID ) ) { 2498 2499 var curveNode = curveNodesMap.get( child.ID ); 2500 2501 // check that the curves are defined for at least one axis, otherwise ignore the curveNode 2502 if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) { 2503 2504 if ( layerCurveNodes[ i ] === undefined ) { 2505 2506 var modelID = connections.get( child.ID ).parents.filter( function ( parent ) { 2507 2508 return parent.relationship !== undefined; 2509 2510 } )[ 0 ].ID; 2511 2512 if ( modelID !== undefined ) { 2513 2514 var rawModel = fbxTree.Objects.Model[ modelID.toString() ]; 2515 2516 var node = { 2517 2518 modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ), 2519 ID: rawModel.id, 2520 initialPosition: [ 0, 0, 0 ], 2521 initialRotation: [ 0, 0, 0 ], 2522 initialScale: [ 1, 1, 1 ], 2523 2524 }; 2525 2526 sceneGraph.traverse( function ( child ) { 2527 2528 if ( child.ID === rawModel.id ) { 2529 2530 node.transform = child.matrix; 2531 2532 if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder; 2533 2534 } 2535 2536 } ); 2537 2538 if ( ! node.transform ) node.transform = new THREE.Matrix4(); 2539 2540 // if the animated model is pre rotated, we'll have to apply the pre rotations to every 2541 // animation value as well 2542 if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value; 2543 if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value; 2544 2545 layerCurveNodes[ i ] = node; 2546 2547 } 2548 2549 } 2550 2551 if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode; 2552 2553 } else if ( curveNode.curves.morph !== undefined ) { 2554 2555 if ( layerCurveNodes[ i ] === undefined ) { 2556 2557 var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) { 2558 2559 return parent.relationship !== undefined; 2560 2561 } )[ 0 ].ID; 2562 2563 var morpherID = connections.get( deformerID ).parents[ 0 ].ID; 2564 var geoID = connections.get( morpherID ).parents[ 0 ].ID; 2565 2566 // assuming geometry is not used in more than one model 2567 var modelID = connections.get( geoID ).parents[ 0 ].ID; 2568 2569 var rawModel = fbxTree.Objects.Model[ modelID ]; 2570 2571 var node = { 2572 2573 modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ), 2574 morphName: fbxTree.Objects.Deformer[ deformerID ].attrName, 2575 2576 }; 2577 2578 layerCurveNodes[ i ] = node; 2579 2580 } 2581 2582 layerCurveNodes[ i ][ curveNode.attr ] = curveNode; 2583 2584 } 2585 2586 } 2587 2588 } ); 2589 2590 layersMap.set( parseInt( nodeID ), layerCurveNodes ); 2591 2592 } 2593 2594 } 2595 2596 return layersMap; 2597 2598 }, 2599 2600 // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation 2601 // hierarchy. Each Stack node will be used to create a THREE.AnimationClip 2602 parseAnimStacks: function ( layersMap ) { 2603 2604 var rawStacks = fbxTree.Objects.AnimationStack; 2605 2606 // connect the stacks (clips) up to the layers 2607 var rawClips = {}; 2608 2609 for ( var nodeID in rawStacks ) { 2610 2611 var children = connections.get( parseInt( nodeID ) ).children; 2612 2613 if ( children.length > 1 ) { 2614 2615 // it seems like stacks will always be associated with a single layer. But just in case there are files 2616 // where there are multiple layers per stack, we'll display a warning 2617 console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' ); 2618 2619 } 2620 2621 var layer = layersMap.get( children[ 0 ].ID ); 2622 2623 rawClips[ nodeID ] = { 2624 2625 name: rawStacks[ nodeID ].attrName, 2626 layer: layer, 2627 2628 }; 2629 2630 } 2631 2632 return rawClips; 2633 2634 }, 2635 2636 addClip: function ( rawClip ) { 2637 2638 var tracks = []; 2639 2640 var self = this; 2641 rawClip.layer.forEach( function ( rawTracks ) { 2642 2643 tracks = tracks.concat( self.generateTracks( rawTracks ) ); 2644 2645 } ); 2646 2647 return new THREE.AnimationClip( rawClip.name, - 1, tracks ); 2648 2649 }, 2650 2651 generateTracks: function ( rawTracks ) { 2652 2653 var tracks = []; 2654 2655 var initialPosition = new THREE.Vector3(); 2656 var initialRotation = new THREE.Quaternion(); 2657 var initialScale = new THREE.Vector3(); 2658 2659 if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale ); 2660 2661 initialPosition = initialPosition.toArray(); 2662 initialRotation = new THREE.Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray(); 2663 initialScale = initialScale.toArray(); 2664 2665 if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) { 2666 2667 var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' ); 2668 if ( positionTrack !== undefined ) tracks.push( positionTrack ); 2669 2670 } 2671 2672 if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) { 2673 2674 var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder ); 2675 if ( rotationTrack !== undefined ) tracks.push( rotationTrack ); 2676 2677 } 2678 2679 if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) { 2680 2681 var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' ); 2682 if ( scaleTrack !== undefined ) tracks.push( scaleTrack ); 2683 2684 } 2685 2686 if ( rawTracks.DeformPercent !== undefined ) { 2687 2688 var morphTrack = this.generateMorphTrack( rawTracks ); 2689 if ( morphTrack !== undefined ) tracks.push( morphTrack ); 2690 2691 } 2692 2693 return tracks; 2694 2695 }, 2696 2697 generateVectorTrack: function ( modelName, curves, initialValue, type ) { 2698 2699 var times = this.getTimesForAllAxes( curves ); 2700 var values = this.getKeyframeTrackValues( times, curves, initialValue ); 2701 2702 return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values ); 2703 2704 }, 2705 2706 generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) { 2707 2708 if ( curves.x !== undefined ) { 2709 2710 this.interpolateRotations( curves.x ); 2711 curves.x.values = curves.x.values.map( THREE.Math.degToRad ); 2712 2713 } 2714 if ( curves.y !== undefined ) { 2715 2716 this.interpolateRotations( curves.y ); 2717 curves.y.values = curves.y.values.map( THREE.Math.degToRad ); 2718 2719 } 2720 if ( curves.z !== undefined ) { 2721 2722 this.interpolateRotations( curves.z ); 2723 curves.z.values = curves.z.values.map( THREE.Math.degToRad ); 2724 2725 } 2726 2727 var times = this.getTimesForAllAxes( curves ); 2728 var values = this.getKeyframeTrackValues( times, curves, initialValue ); 2729 2730 if ( preRotation !== undefined ) { 2731 2732 preRotation = preRotation.map( THREE.Math.degToRad ); 2733 preRotation.push( eulerOrder ); 2734 2735 preRotation = new THREE.Euler().fromArray( preRotation ); 2736 preRotation = new THREE.Quaternion().setFromEuler( preRotation ); 2737 2738 } 2739 2740 if ( postRotation !== undefined ) { 2741 2742 postRotation = postRotation.map( THREE.Math.degToRad ); 2743 postRotation.push( eulerOrder ); 2744 2745 postRotation = new THREE.Euler().fromArray( postRotation ); 2746 postRotation = new THREE.Quaternion().setFromEuler( postRotation ).inverse(); 2747 2748 } 2749 2750 var quaternion = new THREE.Quaternion(); 2751 var euler = new THREE.Euler(); 2752 2753 var quaternionValues = []; 2754 2755 for ( var i = 0; i < values.length; i += 3 ) { 2756 2757 euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder ); 2758 2759 quaternion.setFromEuler( euler ); 2760 2761 if ( preRotation !== undefined ) quaternion.premultiply( preRotation ); 2762 if ( postRotation !== undefined ) quaternion.multiply( postRotation ); 2763 2764 quaternion.toArray( quaternionValues, ( i / 3 ) * 4 ); 2765 2766 } 2767 2768 return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues ); 2769 2770 }, 2771 2772 generateMorphTrack: function ( rawTracks ) { 2773 2774 var curves = rawTracks.DeformPercent.curves.morph; 2775 var values = curves.values.map( function ( val ) { 2776 2777 return val / 100; 2778 2779 } ); 2780 2781 var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ]; 2782 2783 return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values ); 2784 2785 }, 2786 2787 // For all animated objects, times are defined separately for each axis 2788 // Here we'll combine the times into one sorted array without duplicates 2789 getTimesForAllAxes: function ( curves ) { 2790 2791 var times = []; 2792 2793 // first join together the times for each axis, if defined 2794 if ( curves.x !== undefined ) times = times.concat( curves.x.times ); 2795 if ( curves.y !== undefined ) times = times.concat( curves.y.times ); 2796 if ( curves.z !== undefined ) times = times.concat( curves.z.times ); 2797 2798 // then sort them and remove duplicates 2799 times = times.sort( function ( a, b ) { 2800 2801 return a - b; 2802 2803 } ).filter( function ( elem, index, array ) { 2804 2805 return array.indexOf( elem ) == index; 2806 2807 } ); 2808 2809 return times; 2810 2811 }, 2812 2813 getKeyframeTrackValues: function ( times, curves, initialValue ) { 2814 2815 var prevValue = initialValue; 2816 2817 var values = []; 2818 2819 var xIndex = - 1; 2820 var yIndex = - 1; 2821 var zIndex = - 1; 2822
2823 times.forEach( function ( time ) { 2824 2825 if ( curves.x ) xIndex = curves.x.times.indexOf( time ); 2826 if ( curves.y ) yIndex = curves.y.times.indexOf( time ); 2827 if ( curves.z ) zIndex = curves.z.times.indexOf( time ); 2828 2829 // if there is an x value defined for this frame, use that 2830 if ( xIndex !== - 1 ) { 2831 2832 var xValue = curves.x.values[ xIndex ]; 2833 values.push( xValue ); 2834 prevValue[ 0 ] = xValue; 2835 2836 } else { 2837 2838 // otherwise use the x value from the previous frame 2839 values.push( prevValue[ 0 ] ); 2840 2841 } 2842 2843 if ( yIndex !== - 1 ) { 2844 2845 var yValue = curves.y.values[ yIndex ]; 2846 values.push( yValue ); 2847 prevValue[ 1 ] = yValue; 2848 2849 } else { 2850 2851 values.push( prevValue[ 1 ] ); 2852 2853 } 2854 2855 if ( zIndex !== - 1 ) { 2856 2857 var zValue = curves.z.values[ zIndex ]; 2858 values.push( zValue ); 2859 prevValue[ 2 ] = zValue; 2860 2861 } else { 2862 2863 values.push( prevValue[ 2 ] ); 2864 2865 } 2866 2867 } ); 2868 2869 return values; 2870 2871 }, 2872 2873 // Rotations are defined as Euler angles which can have values of any size 2874 // These will be converted to quaternions which don't support values greater than 2875 // PI, so we'll interpolate large rotations 2876 interpolateRotations: function ( curve ) { 2877 2878 for ( var i = 1; i < curve.values.length; i ++ ) { 2879 2880 var initialValue = curve.values[ i - 1 ]; 2881 var valuesSpan = curve.values[ i ] - initialValue; 2882 2883 var absoluteSpan = Math.abs( valuesSpan ); 2884 2885 if ( absoluteSpan >= 180 ) { 2886 2887 var numSubIntervals = absoluteSpan / 180; 2888 2889 var step = valuesSpan / numSubIntervals; 2890 var nextValue = initialValue + step; 2891 2892 var initialTime = curve.times[ i - 1 ]; 2893 var timeSpan = curve.times[ i ] - initialTime; 2894 var interval = timeSpan / numSubIntervals; 2895 var nextTime = initialTime + interval; 2896 2897 var interpolatedTimes = []; 2898 var interpolatedValues = []; 2899 2900 while ( nextTime < curve.times[ i ] ) { 2901 2902 interpolatedTimes.push( nextTime ); 2903 nextTime += interval; 2904 2905 interpolatedValues.push( nextValue ); 2906 nextValue += step; 2907 2908 } 2909 2910 curve.times = inject( curve.times, i, interpolatedTimes ); 2911 curve.values = inject( curve.values, i, interpolatedValues ); 2912 2913 } 2914 2915 } 2916 2917 }, 2918 2919 }; 2920 2921 // parse an FBX file in ASCII format 2922 function TextParser() {} 2923 2924 TextParser.prototype = { 2925 2926 constructor: TextParser, 2927 2928 getPrevNode: function () { 2929 2930 return this.nodeStack[ this.currentIndent - 2 ]; 2931 2932 }, 2933 2934 getCurrentNode: function () { 2935 2936 return this.nodeStack[ this.currentIndent - 1 ]; 2937 2938 }, 2939 2940 getCurrentProp: function () { 2941 2942 return this.currentProp; 2943 2944 }, 2945 2946 pushStack: function ( node ) { 2947 2948 this.nodeStack.push( node ); 2949 this.currentIndent += 1; 2950 2951 }, 2952 2953 popStack: function () { 2954 2955 this.nodeStack.pop(); 2956 this.currentIndent -= 1; 2957 2958 }, 2959 2960 setCurrentProp: function ( val, name ) { 2961 2962 this.currentProp = val; 2963 this.currentPropName = name; 2964 2965 }, 2966 2967 parse: function ( text ) { 2968 2969 this.currentIndent = 0; 2970 2971 this.allNodes = new FBXTree(); 2972 this.nodeStack = []; 2973 this.currentProp = []; 2974 this.currentPropName = ''; 2975 2976 var self = this; 2977 2978 var split = text.split( /[\r\n]+/ ); 2979 2980 split.forEach( function ( line, i ) { 2981 2982 var matchComment = line.match( /^[\s\t]*;/ ); 2983 var matchEmpty = line.match( /^[\s\t]*$/ ); 2984 2985 if ( matchComment || matchEmpty ) return; 2986 2987 var matchBeginning = line.match( '^\\t{' + self.currentIndent + '}(\\w+):(.*){', '' ); 2988 var matchProperty = line.match( '^\\t{' + ( self.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' ); 2989 var matchEnd = line.match( '^\\t{' + ( self.currentIndent - 1 ) + '}}' ); 2990 2991 if ( matchBeginning ) { 2992 2993 self.parseNodeBegin( line, matchBeginning ); 2994 2995 } else if ( matchProperty ) { 2996 2997 self.parseNodeProperty( line, matchProperty, split[ ++ i ] ); 2998 2999 } else if ( matchEnd ) { 3000 3001 self.popStack(); 3002 3003 } else if ( line.match( /^[^\s\t}]/ ) ) { 3004 3005 // large arrays are split over multiple lines terminated with a ',' character 3006 // if this is encountered the line needs to be joined to the previous line 3007 self.parseNodePropertyContinued( line ); 3008 3009 } 3010 3011 } ); 3012 3013 return this.allNodes; 3014 3015 }, 3016 3017 parseNodeBegin: function ( line, property ) { 3018 3019 var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' ); 3020 3021 var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) { 3022 3023 return attr.trim().replace( /^"/, '' ).replace( /"$/, '' ); 3024 3025 } ); 3026 3027 var node = { name: nodeName }; 3028 var attrs = this.parseNodeAttr( nodeAttrs ); 3029 3030 var currentNode = this.getCurrentNode(); 3031 3032 // a top node 3033 if ( this.currentIndent === 0 ) { 3034 3035 this.allNodes.add( nodeName, node ); 3036 3037 } else { // a subnode 3038 3039 // if the subnode already exists, append it 3040 if ( nodeName in currentNode ) { 3041 3042 // special case Pose needs PoseNodes as an array 3043 if ( nodeName === 'PoseNode' ) { 3044
3045 currentNode.PoseNode.push( node ); 3046 3047 } else if ( currentNode[ nodeName ].id !== undefined ) { 3048 3049 currentNode[ nodeName ] = {}; 3050 currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ]; 3051 3052 } 3053 3054 if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node; 3055 3056 } else if ( typeof attrs.id === 'number' ) { 3057 3058 currentNode[ nodeName ] = {}; 3059 currentNode[ nodeName ][ attrs.id ] = node; 3060 3061 } else if ( nodeName !== 'Properties70' ) { 3062 3063 if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ]; 3064 else currentNode[ nodeName ] = node; 3065 3066 } 3067 3068 } 3069 3070 if ( typeof attrs.id === 'number' ) node.id = attrs.id; 3071 if ( attrs.name !== '' ) node.attrName = attrs.name; 3072 if ( attrs.type !== '' ) node.attrType = attrs.type; 3073 3074 this.pushStack( node ); 3075 3076 }, 3077 3078 parseNodeAttr: function ( attrs ) { 3079 3080 var id = attrs[ 0 ]; 3081 3082 if ( attrs[ 0 ] !== '' ) { 3083 3084 id = parseInt( attrs[ 0 ] ); 3085 3086 if ( isNaN( id ) ) { 3087 3088 id = attrs[ 0 ]; 3089 3090 } 3091 3092 } 3093 3094 var name = '', type = ''; 3095 3096 if ( attrs.length > 1 ) { 3097 3098 name = attrs[ 1 ].replace( /^(\w+)::/, '' ); 3099 type = attrs[ 2 ]; 3100 3101 } 3102 3103 return { id: id, name: name, type: type }; 3104 3105 }, 3106 3107 parseNodeProperty: function ( line, property, contentLine ) { 3108 3109 var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); 3110 var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); 3111 3112 // for special case: base64 image data follows "Content: ," line 3113 // Content: , 3114 // "/9j/4RDaRXhpZgAATU0A..." 3115 if ( propName === 'Content' && propValue === ',' ) { 3116 3117 propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim(); 3118 3119 } 3120 3121 var currentNode = this.getCurrentNode(); 3122 var parentName = currentNode.name; 3123 3124 if ( parentName === 'Properties70' ) { 3125 3126 this.parseNodeSpecialProperty( line, propName, propValue ); 3127 return; 3128 3129 } 3130 3131 // Connections 3132 if ( propName === 'C' ) { 3133 3134 var connProps = propValue.split( ',' ).slice( 1 ); 3135 var from = parseInt( connProps[ 0 ] ); 3136 var to = parseInt( connProps[ 1 ] ); 3137 3138 var rest = propValue.split( ',' ).slice( 3 ); 3139 3140 rest = rest.map( function ( elem ) { 3141 3142 return elem.trim().replace( /^"/, '' ); 3143 3144 } ); 3145 3146 propName = 'connections'; 3147 propValue = [ from, to ]; 3148 append( propValue, rest ); 3149 3150 if ( currentNode[ propName ] === undefined ) { 3151 3152 currentNode[ propName ] = []; 3153 3154 } 3155 3156 } 3157 3158 // Node 3159 if ( propName === 'Node' ) currentNode.id = propValue; 3160 3161 // connections 3162 if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) { 3163 3164 currentNode[ propName ].push( propValue ); 3165 3166 } else { 3167 3168 if ( propName !== 'a' ) currentNode[ propName ] = propValue; 3169 else currentNode.a = propValue; 3170 3171 } 3172 3173 this.setCurrentProp( currentNode, propName ); 3174 3175 // convert string to array, unless it ends in ',' in which case more will be added to it 3176 if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) { 3177 3178 currentNode.a = parseNumberArray( propValue ); 3179 3180 } 3181 3182 }, 3183 3184 parseNodePropertyContinued: function ( line ) { 3185 3186 var currentNode = this.getCurrentNode(); 3187 3188 currentNode.a += line; 3189 3190 // if the line doesn't end in ',' we have reached the end of the property value 3191 // so convert the string to an array 3192 if ( line.slice( - 1 ) !== ',' ) { 3193 3194 currentNode.a = parseNumberArray( currentNode.a ); 3195 3196 } 3197 3198 }, 3199 3200 // parse "Property70" 3201 parseNodeSpecialProperty: function ( line, propName, propValue ) { 3202 3203 // split this 3204 // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 3205 // into array like below 3206 // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] 3207 var props = propValue.split( '",' ).map( function ( prop ) { 3208 3209 return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' ); 3210 3211 } ); 3212 3213 var innerPropName = props[ 0 ]; 3214 var innerPropType1 = props[ 1 ]; 3215 var innerPropType2 = props[ 2 ]; 3216 var innerPropFlag = props[ 3 ]; 3217 var innerPropValue = props[ 4 ]; 3218 3219 // cast values where needed, otherwise leave as strings 3220 switch ( innerPropType1 ) { 3221 3222 case 'int': 3223 case 'enum': 3224 case 'bool': 3225 case 'ULongLong': 3226 case 'double': 3227 case 'Number': 3228 case 'FieldOfView': 3229 innerPropValue = parseFloat( innerPropValue ); 3230 break; 3231
3232 case 'Color': 3233 case 'ColorRGB': 3234 case 'Vector3D': 3235 case 'Lcl_Translation': 3236 case 'Lcl_Rotation': 3237 case 'Lcl_Scaling': 3238 innerPropValue = parseNumberArray( innerPropValue ); 3239 break; 3240 3241 } 3242 3243 // CAUTION: these props must append to parent's parent 3244 this.getPrevNode()[ innerPropName ] = { 3245 3246 'type': innerPropType1, 3247 'type2': innerPropType2, 3248 'flag': innerPropFlag, 3249 'value': innerPropValue 3250 3251 }; 3252 3253 this.setCurrentProp( this.getPrevNode(), innerPropName ); 3254 3255 }, 3256 3257 }; 3258 3259 // Parse an FBX file in Binary format 3260 function BinaryParser() {} 3261 3262 BinaryParser.prototype = { 3263 3264 constructor: BinaryParser, 3265 3266 parse: function ( buffer ) { 3267 3268 var reader = new BinaryReader( buffer ); 3269 reader.skip( 23 ); // skip magic 23 bytes 3270 3271 var version = reader.getUint32(); 3272 3273 console.log( 'THREE.FBXLoader: FBX binary version: ' + version ); 3274 3275 var allNodes = new FBXTree(); 3276 3277 while ( ! this.endOfContent( reader ) ) { 3278 3279 var node = this.parseNode( reader, version ); 3280 if ( node !== null ) allNodes.add( node.name, node ); 3281 3282 } 3283 3284 return allNodes; 3285 3286 }, 3287 3288 // Check if reader has reached the end of content. 3289 endOfContent: function ( reader ) { 3290 3291 // footer size: 160bytes + 16-byte alignment padding 3292 // - 16bytes: magic 3293 // - padding til 16-byte alignment (at least 1byte?) 3294 // (seems like some exporters embed fixed 15 or 16bytes?) 3295 // - 4bytes: magic 3296 // - 4bytes: version 3297 // - 120bytes: zero 3298 // - 16bytes: magic 3299 if ( reader.size() % 16 === 0 ) { 3300 3301 return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size(); 3302 3303 } else { 3304 3305 return reader.getOffset() + 160 + 16 >= reader.size(); 3306 3307 } 3308 3309 }, 3310 3311 // recursively parse nodes until the end of the file is reached 3312 parseNode: function ( reader, version ) { 3313 3314 var node = {}; 3315 3316 // The first three data sizes depends on version. 3317 var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); 3318 var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); 3319 3320 // note: do not remove this even if you get a linter warning as it moves the buffer forward 3321 var propertyListLen = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); 3322 3323 var nameLen = reader.getUint8(); 3324 var name = reader.getString( nameLen ); 3325 3326 // Regards this node as NULL-record if endOffset is zero 3327 if ( endOffset === 0 ) return null; 3328 3329 var propertyList = []; 3330 3331 for ( var i = 0; i < numProperties; i ++ ) { 3332 3333 propertyList.push( this.parseProperty( reader ) ); 3334 3335 } 3336 3337 // Regards the first three elements in propertyList as id, attrName, and attrType 3338 var id = propertyList.length > 0 ? propertyList[ 0 ] : ''; 3339 var attrName = propertyList.length > 1 ? propertyList[ 1 ] : ''; 3340 var attrType = propertyList.length > 2 ? propertyList[ 2 ] : ''; 3341 3342 // check if this node represents just a single property 3343 // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} 3344 node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false; 3345 3346 while ( endOffset > reader.getOffset() ) { 3347 3348 var subNode = this.parseNode( reader, version ); 3349 3350 if ( subNode !== null ) this.parseSubNode( name, node, subNode ); 3351 3352 } 3353 3354 node.propertyList = propertyList; // raw property list used by parent 3355 3356 if ( typeof id === 'number' ) node.id = id; 3357 if ( attrName !== '' ) node.attrName = attrName; 3358 if ( attrType !== '' ) node.attrType = attrType; 3359 if ( name !== '' ) node.name = name; 3360 3361 return node; 3362 3363 }, 3364 3365 parseSubNode: function ( name, node, subNode ) { 3366 3367 // special case: child node is single property 3368 if ( subNode.singleProperty === true ) { 3369 3370 var value = subNode.propertyList[ 0 ]; 3371 3372 if ( Array.isArray( value ) ) { 3373 3374 node[ subNode.name ] = subNode; 3375 3376 subNode.a = value; 3377 3378 } else { 3379 3380 node[ subNode.name ] = value; 3381 3382 } 3383 3384 } else if ( name === 'Connections' && subNode.name === 'C' ) { 3385 3386 var array = []; 3387 3388 subNode.propertyList.forEach( function ( property, i ) { 3389 3390 // first Connection is FBX type (OO, OP, etc.). We'll discard these 3391 if ( i !== 0 ) array.push( property ); 3392 3393 } ); 3394 3395 if ( node.connections === undefined ) { 3396 3397 node.connections = []; 3398 3399 } 3400 3401 node.connections.push( array ); 3402 3403 } else if ( subNode.name === 'Properties70' ) { 3404 3405 var keys = Object.keys( subNode ); 3406 3407 keys.forEach( function ( key ) { 3408 3409 node[ key ] = subNode[ key ]; 3410 3411 } ); 3412 3413 } else if ( name === 'Properties70' && subNode.name === 'P' ) { 3414 3415 var innerPropName = subNode.propertyList[ 0 ]; 3416 var innerPropType1 = subNode.propertyList[ 1 ]; 3417 var innerPropType2 = subNode.propertyList[ 2 ]; 3418 var innerPropFlag = subNode.propertyList[ 3 ]; 3419 var innerPropValue; 3420 3421 if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' ); 3422 if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' ); 3423 3424 if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) { 3425 3426 innerPropValue = [ 3427 subNode.propertyList[ 4 ], 3428 subNode.propertyList[ 5 ], 3429 subNode.propertyList[ 6 ] 3430 ]; 3431 3432 } else { 3433 3434 innerPropValue = subNode.propertyList[ 4 ]; 3435 3436 } 3437 3438 // this will be copied to parent, see above 3439 node[ innerPropName ] = { 3440 3441 'type': innerPropType1, 3442 'type2': innerPropType2, 3443 'flag': innerPropFlag, 3444 'value': innerPropValue 3445 3446 }; 3447 3448 } else if ( node[ subNode.name ] === undefined ) { 3449 3450 if ( typeof subNode.id === 'number' ) { 3451 3452 node[ subNode.name ] = {}; 3453 node[ subNode.name ][ subNode.id ] = subNode; 3454 3455 } else { 3456 3457 node[ subNode.name ] = subNode; 3458 3459 } 3460 3461 } else { 3462 3463 if ( subNode.name === 'PoseNode' ) { 3464 3465 if ( ! Array.isArray( node[ subNode.name ] ) ) { 3466 3467 node[ subNode.name ] = [ node[ subNode.name ] ]; 3468 3469 } 3470 3471 node[ subNode.name ].push( subNode ); 3472 3473 } else if ( node[ subNode.name ][ subNode.id ] === undefined ) { 3474 3475 node[ subNode.name ][ subNode.id ] = subNode; 3476 3477 } 3478 3479 } 3480 3481 }, 3482 3483 parseProperty: function ( reader ) { 3484 3485 var type = reader.getString( 1 ); 3486 3487 switch ( type ) { 3488 3489 case 'C': 3490 return reader.getBoolean(); 3491
3492 case 'D': 3493 return reader.getFloat64(); 3494 3495 case 'F': 3496 return reader.getFloat32(); 3497 3498 case 'I': 3499 return reader.getInt32(); 3500 3501 case 'L': 3502 return reader.getInt64(); 3503 3504 case 'R': 3505 var length = reader.getUint32(); 3506 return reader.getArrayBuffer( length ); 3507 3508 case 'S': 3509 var length = reader.getUint32(); 3510 return reader.getString( length ); 3511 3512 case 'Y': 3513 return reader.getInt16(); 3514 3515 case 'b': 3516 case 'c': 3517 case 'd': 3518 case 'f': 3519 case 'i': 3520 case 'l': 3521 3522 var arrayLength = reader.getUint32(); 3523 var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed 3524 var compressedLength = reader.getUint32(); 3525 3526 if ( encoding === 0 ) { 3527 3528 switch ( type ) { 3529 3530 case 'b': 3531 case 'c': 3532 return reader.getBooleanArray( arrayLength ); 3533 3534 case 'd': 3535 return reader.getFloat64Array( arrayLength ); 3536 3537 case 'f': 3538 return reader.getFloat32Array( arrayLength ); 3539 3540 case 'i': 3541 return reader.getInt32Array( arrayLength ); 3542 3543 case 'l': 3544 return reader.getInt64Array( arrayLength ); 3545 3546 } 3547 3548 } 3549 3550 if ( typeof Zlib === 'undefined' ) { 3551 3552 console.error( 'THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js' ); 3553 3554 } 3555 3556 var inflate = new Zlib.Inflate( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef 3557 var reader2 = new BinaryReader( inflate.decompress().buffer ); 3558 3559 switch ( type ) { 3560 3561 case 'b': 3562 case 'c': 3563 return reader2.getBooleanArray( arrayLength ); 3564 3565 case 'd': 3566 return reader2.getFloat64Array( arrayLength ); 3567 3568 case 'f': 3569 return reader2.getFloat32Array( arrayLength ); 3570 3571 case 'i': 3572 return reader2.getInt32Array( arrayLength ); 3573 3574 case 'l': 3575 return reader2.getInt64Array( arrayLength ); 3576 3577 } 3578 3579 default: 3580 throw new Error( 'THREE.FBXLoader: Unknown property type ' + type ); 3581 3582 } 3583 3584 } 3585 3586 }; 3587 3588 function BinaryReader( buffer, littleEndian ) { 3589 3590 this.dv = new DataView( buffer ); 3591 this.offset = 0; 3592 this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; 3593 3594 } 3595 3596 BinaryReader.prototype = { 3597 3598 constructor: BinaryReader, 3599 3600 getOffset: function () { 3601 3602 return this.offset; 3603 3604 }, 3605 3606 size: function () { 3607 3608 return this.dv.buffer.byteLength; 3609 3610 }, 3611 3612 skip: function ( length ) { 3613 3614 this.offset += length; 3615 3616 }, 3617 3618 // seems like true/false representation depends on exporter. 3619 // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) 3620 // then sees LSB. 3621 getBoolean: function () { 3622 3623 return ( this.getUint8() & 1 ) === 1; 3624 3625 }, 3626 3627 getBooleanArray: function ( size ) { 3628 3629 var a = []; 3630 3631 for ( var i = 0; i < size; i ++ ) { 3632 3633 a.push( this.getBoolean() ); 3634 3635 } 3636 3637 return a; 3638 3639 }, 3640 3641 getUint8: function () { 3642 3643 var value = this.dv.getUint8( this.offset ); 3644 this.offset += 1; 3645 return value; 3646 3647 }, 3648 3649 getInt16: function () { 3650 3651 var value = this.dv.getInt16( this.offset, this.littleEndian ); 3652 this.offset += 2; 3653 return value; 3654 3655 }, 3656 3657 getInt32: function () { 3658 3659 var value = this.dv.getInt32( this.offset, this.littleEndian ); 3660 this.offset += 4; 3661 return value; 3662 3663 }, 3664 3665 getInt32Array: function ( size ) { 3666 3667 var a = []; 3668 3669 for ( var i = 0; i < size; i ++ ) { 3670 3671 a.push( this.getInt32() ); 3672 3673 } 3674 3675 return a; 3676 3677 }, 3678 3679 getUint32: function () { 3680 3681 var value = this.dv.getUint32( this.offset, this.littleEndian ); 3682 this.offset += 4; 3683 return value; 3684 3685 }, 3686 3687 // JavaScript doesn't support 64-bit integer so calculate this here 3688 // 1 << 32 will return 1 so using multiply operation instead here. 3689 // There's a possibility that this method returns wrong value if the value 3690 // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. 3691 // TODO: safely handle 64-bit integer 3692 getInt64: function () { 3693 3694 var low, high; 3695 3696 if ( this.littleEndian ) { 3697 3698 low = this.getUint32(); 3699 high = this.getUint32(); 3700 3701 } else { 3702 3703 high = this.getUint32(); 3704 low = this.getUint32(); 3705 3706 } 3707 3708 // calculate negative value 3709 if ( high & 0x80000000 ) { 3710 3711 high = ~ high & 0xFFFFFFFF; 3712 low = ~ low & 0xFFFFFFFF; 3713 3714 if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF; 3715 3716 low = ( low + 1 ) & 0xFFFFFFFF; 3717 3718 return - ( high * 0x100000000 + low ); 3719 3720 } 3721 3722 return high * 0x100000000 + low; 3723 3724 }, 3725 3726 getInt64Array: function ( size ) { 3727 3728 var a = []; 3729 3730 for ( var i = 0; i < size; i ++ ) { 3731 3732 a.push( this.getInt64() ); 3733 3734 } 3735 3736 return a; 3737 3738 }, 3739 3740 // Note: see getInt64() comment 3741 getUint64: function () { 3742 3743 var low, high; 3744 3745 if ( this.littleEndian ) { 3746 3747 low = this.getUint32(); 3748 high = this.getUint32(); 3749 3750 } else { 3751 3752 high = this.getUint32(); 3753 low = this.getUint32(); 3754 3755 } 3756 3757 return high * 0x100000000 + low; 3758 3759 }, 3760 3761 getFloat32: function () { 3762 3763 var value = this.dv.getFloat32( this.offset, this.littleEndian ); 3764 this.offset += 4; 3765 return value; 3766 3767 }, 3768 3769 getFloat32Array: function ( size ) { 3770 3771 var a = []; 3772 3773 for ( var i = 0; i < size; i ++ ) { 3774 3775 a.push( this.getFloat32() ); 3776 3777 } 3778 3779 return a; 3780 3781 }, 3782 3783 getFloat64: function () { 3784 3785 var value = this.dv.getFloat64( this.offset, this.littleEndian ); 3786 this.offset += 8; 3787 return value; 3788 3789 }, 3790 3791 getFloat64Array: function ( size ) { 3792 3793 var a = []; 3794 3795 for ( var i = 0; i < size; i ++ ) { 3796 3797 a.push( this.getFloat64() ); 3798 3799 } 3800 3801 return a; 3802 3803 }, 3804 3805 getArrayBuffer: function ( size ) { 3806 3807 var value = this.dv.buffer.slice( this.offset, this.offset + size ); 3808 this.offset += size; 3809 return value; 3810 3811 }, 3812 3813 getString: function ( size ) { 3814 3815 // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead 3816 var a = []; 3817 3818 for ( var i = 0; i < size; i ++ ) { 3819 3820 a[ i ] = this.getUint8(); 3821 3822 } 3823 3824 var nullByte = a.indexOf( 0 ); 3825 if ( nullByte >= 0 ) a = a.slice( 0, nullByte ); 3826 3827 return THREE.LoaderUtils.decodeText( new Uint8Array( a ) ); 3828 3829 } 3830 3831 }; 3832 3833 // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) 3834 // and BinaryParser( FBX Binary format) 3835 function FBXTree() {} 3836 3837 FBXTree.prototype = { 3838 3839 constructor: FBXTree, 3840 3841 add: function ( key, val ) { 3842 3843 this[ key ] = val; 3844 3845 }, 3846 3847 }; 3848 3849 // ************** UTILITY FUNCTIONS ************** 3850 3851 function isFbxFormatBinary( buffer ) { 3852 3853 var CORRECT = 'Kaydara FBX Binary \0'; 3854 3855 return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length ); 3856 3857 } 3858 3859 function isFbxFormatASCII( text ) { 3860 3861 var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ]; 3862 3863 var cursor = 0; 3864 3865 function read( offset ) { 3866 3867 var result = text[ offset - 1 ];
3868 text = text.slice( cursor + offset ); 3869 cursor ++; 3870 return result; 3871 3872 } 3873 3874 for ( var i = 0; i < CORRECT.length; ++ i ) { 3875 3876 var num = read( 1 ); 3877 if ( num === CORRECT[ i ] ) { 3878 3879 return false; 3880 3881 } 3882 3883 } 3884 3885 return true; 3886 3887 } 3888 3889 function getFbxVersion( text ) { 3890 3891 var versionRegExp = /FBXVersion: (\d+)/; 3892 var match = text.match( versionRegExp ); 3893 if ( match ) { 3894 3895 var version = parseInt( match[ 1 ] ); 3896 return version; 3897 3898 } 3899 throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' ); 3900 3901 } 3902 3903 // Converts FBX ticks into real time seconds. 3904 function convertFBXTimeToSeconds( time ) { 3905 3906 return time / 46186158000; 3907 3908 } 3909 3910 var dataArray = []; 3911 3912 // extracts the data from the correct position in the FBX array based on indexing type 3913 function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) { 3914 3915 var index; 3916 3917 switch ( infoObject.mappingType ) { 3918 3919 case 'ByPolygonVertex' : 3920 index = polygonVertexIndex; 3921 break; 3922 case 'ByPolygon' : 3923 index = polygonIndex; 3924 break; 3925 case 'ByVertice' : 3926 index = vertexIndex; 3927 break; 3928 case 'AllSame' : 3929 index = infoObject.indices[ 0 ]; 3930 break; 3931 default : 3932 console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType ); 3933 3934 } 3935 3936 if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ]; 3937 3938 var from = index * infoObject.dataSize; 3939 var to = from + infoObject.dataSize; 3940 3941 return slice( dataArray, infoObject.buffer, from, to ); 3942 3943 } 3944 3945 var tempEuler = new THREE.Euler(); 3946 var tempVec = new THREE.Vector3(); 3947 3948 // generate transformation from FBX transform data 3949 // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm 3950 // ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e 3951 function generateTransform( transformData ) { 3952 3953 var lTranslationM = new THREE.Matrix4(); 3954 var lPreRotationM = new THREE.Matrix4(); 3955 var lRotationM = new THREE.Matrix4(); 3956 var lPostRotationM = new THREE.Matrix4(); 3957 3958 var lScalingM = new THREE.Matrix4(); 3959 var lScalingPivotM = new THREE.Matrix4(); 3960 var lScalingOffsetM = new THREE.Matrix4(); 3961 var lRotationOffsetM = new THREE.Matrix4(); 3962 var lRotationPivotM = new THREE.Matrix4(); 3963 3964 var lParentGX = new THREE.Matrix4(); 3965 var lGlobalT = new THREE.Matrix4(); 3966 3967 var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0; 3968 3969 if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) ); 3970 3971 if ( transformData.preRotation ) { 3972 3973 var array = transformData.preRotation.map( THREE.Math.degToRad ); 3974 array.push( transformData.eulerOrder ); 3975 lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); 3976 3977 } 3978 3979 if ( transformData.rotation ) { 3980 3981 var array = transformData.rotation.map( THREE.Math.degToRad ); 3982 array.push( transformData.eulerOrder ); 3983 lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); 3984 3985 } 3986 3987 if ( transformData.postRotation ) { 3988 3989 var array = transformData.postRotation.map( THREE.Math.degToRad ); 3990 array.push( transformData.eulerOrder ); 3991 lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); 3992 3993 } 3994 3995 if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) ); 3996 3997 // Pivots and offsets 3998 if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) ); 3999 if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) ); 4000 if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) ); 4001 if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) ); 4002 4003 // parent transform 4004 if ( transformData.parentMatrixWorld ) lParentGX = transformData.parentMatrixWorld; 4005 4006 // Global Rotation 4007 var lLRM = lPreRotationM.multiply( lRotationM ).multiply( lPostRotationM ); 4008 var lParentGRM = new THREE.Matrix4(); 4009 lParentGX.extractRotation( lParentGRM ); 4010 4011 // Global Shear*Scaling 4012 var lParentTM = new THREE.Matrix4(); 4013 var lLSM; 4014 var lParentGSM; 4015 var lParentGRSM; 4016 4017 lParentTM.copyPosition( lParentGX ); 4018 lParentGRSM = lParentTM.getInverse( lParentTM ).multiply( lParentGX ); 4019 lParentGSM = lParentGRM.getInverse( lParentGRM ).multiply( lParentGRSM ); 4020 lLSM = lScalingM; 4021 4022 var lGlobalRS; 4023 if ( inheritType === 0 ) { 4024 4025 lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM ); 4026 4027 } else if ( inheritType === 1 ) { 4028 4029 lGlobalRS = lParentGRM.multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM ); 4030 4031 } else { 4032 4033 var lParentLSM = new THREE.Matrix4().copy( lScalingM ); 4034 4035 var lParentGSM_noLocal = lParentGSM.multiply( lParentLSM.getInverse( lParentLSM ) ); 4036 4037 lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM ); 4038 4039 } 4040 4041 // Calculate the local transform matrix 4042 var lTransform = lTranslationM.multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM.getInverse( lRotationPivotM ) ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM.getInverse( lScalingPivotM ) ); 4043 4044 var lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition( lTransform ); 4045 4046 var lGlobalTranslation = lParentGX.multiply( lLocalTWithAllPivotAndOffsetInfo ); 4047 lGlobalT.copyPosition( lGlobalTranslation ); 4048 4049 lTransform = lGlobalT.multiply( lGlobalRS ); 4050 4051 return lTransform; 4052 4053 } 4054
4055 // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order 4056 // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html 4057 function getEulerOrder( order ) { 4058 4059 order = order || 0; 4060 4061 var enums = [ 4062 'ZYX', // -> XYZ extrinsic 4063 'YZX', // -> XZY extrinsic 4064 'XZY', // -> YZX extrinsic 4065 'ZXY', // -> YXZ extrinsic 4066 'YXZ', // -> ZXY extrinsic 4067 'XYZ', // -> ZYX extrinsic 4068 //'SphericXYZ', // not possible to support 4069 ]; 4070 4071 if ( order === 6 ) { 4072 4073 console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' ); 4074 return enums[ 0 ]; 4075 4076 } 4077 4078 return enums[ order ]; 4079 4080 } 4081 4082 // Parses comma separated list of numbers and returns them an array. 4083 // Used internally by the TextParser 4084 function parseNumberArray( value ) { 4085 4086 var array = value.split( ',' ).map( function ( val ) { 4087 4088 return parseFloat( val ); 4089 4090 } ); 4091 4092 return array; 4093 4094 } 4095 4096 function convertArrayBufferToString( buffer, from, to ) { 4097 4098 if ( from === undefined ) from = 0; 4099 if ( to === undefined ) to = buffer.byteLength; 4100 4101 return THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ); 4102 4103 } 4104 4105 function append( a, b ) { 4106 4107 for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) { 4108 4109 a[ j ] = b[ i ]; 4110 4111 } 4112 4113 } 4114 4115 function slice( a, b, from, to ) { 4116 4117 for ( var i = from, j = 0; i < to; i ++, j ++ ) { 4118 4119 a[ j ] = b[ i ]; 4120 4121 } 4122 4123 return a; 4124 4125 } 4126 4127 // inject array a2 into array a1 at index 4128 function inject( a1, index, a2 ) { 4129 4130 return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) ); 4131 4132 } 4133 4134 return FBXLoader; 4135 4136} )();
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.