looeee

fbx loader track offset to 0

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