looeee

Untitled

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