Search Unity

Exporting Unity Model to FBX NullReferenceException not set to an instance of an object

Discussion in 'Getting Started' started by Jefflinzer, Mar 16, 2023.

  1. Jefflinzer

    Jefflinzer

    Joined:
    Mar 16, 2023
    Posts:
    4
  2. Jefflinzer

    Jefflinzer

    Joined:
    Mar 16, 2023
    Posts:
    4
    Code (CSharp):
    1. using System.IO;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Animations;
    5. using UnityEngine.Timeline;
    6. using UnityEditor.Timeline;
    7. using System.Linq;
    8. using Autodesk.Fbx;
    9. using System.Runtime.CompilerServices;
    10. using System.Runtime.Serialization;
    11. using UnityEditor.Formats.Fbx.Exporter.Visitors;
    12. using System.Security.Permissions;
    13.  
    14. [assembly: InternalsVisibleTo("Unity.Formats.Fbx.Editor.Tests")]
    15. [assembly: InternalsVisibleTo("Unity.ProBuilder.AddOns.Editor")]
    16.  
    17. namespace UnityEditor.Formats.Fbx.Exporter
    18. {
    19.    /// <summary>
    20.    /// If your MonoBehaviour knows about some custom geometry that
    21.    /// isn't in a MeshFilter or SkinnedMeshRenderer, use
    22.    /// RegisterMeshCallback to get a callback when the exporter tries
    23.    /// to export your component.
    24.    ///
    25.    /// The callback should return true, and output the mesh you want.
    26.    ///
    27.    /// Return false if you don't want to drive this game object.
    28.    ///
    29.    /// Return true and output a null mesh if you don't want the
    30.    /// exporter to output anything.
    31.    /// </summary>
    32.    internal delegate bool GetMeshForComponent<T>(ModelExporter exporter, T component, FbxNode fbxNode) where T : MonoBehaviour;
    33.    internal delegate bool GetMeshForComponent(ModelExporter exporter, MonoBehaviour component, FbxNode fbxNode);
    34.  
    35.    /// <summary>
    36.    /// Delegate used to convert a GameObject into a mesh.
    37.    ///
    38.    /// This is useful if you want to have broader control over
    39.    /// the export process than the GetMeshForComponent callbacks
    40.    /// provide. But it's less efficient because you'll get a callback
    41.    /// on every single GameObject.
    42.    /// </summary>
    43.    internal delegate bool GetMeshForObject(ModelExporter exporter, GameObject gameObject, FbxNode fbxNode);
    44.  
    45.    [System.Serializable]
    46.    internal class ModelExportException : System.Exception
    47.    {
    48.        public ModelExportException(){}
    49.  
    50.        public ModelExportException(string message)
    51.            : base(message){}
    52.  
    53.        public ModelExportException(string message, System.Exception inner)
    54.            : base(message, inner){}
    55.  
    56.        protected ModelExportException(SerializationInfo info, StreamingContext context)
    57.            : base(info, context){}
    58.    }
    59.  
    60.    /// <summary>
    61.    /// Use the ModelExporter class to export Unity GameObjects to an FBX file.
    62.    /// <para>
    63.    /// Use the ExportObject and ExportObjects methods. The default export
    64.    /// options are used when exporting the objects to the FBX file.
    65.    /// </para>
    66.    /// <para>For information on using the ModelExporter class, see <a href="../manual/api_index.html">the Developer's Guide</a>.</para>
    67.    /// </summary>
    68.    public sealed class ModelExporter : System.IDisposable
    69.    {
    70.        const string Title =
    71.            "Created by FBX Exporter from Unity Technologies";
    72.  
    73.        const string Subject =
    74.            "";
    75.  
    76.        const string Keywords =
    77.            "Nodes Meshes Materials Textures Cameras Lights Skins Animation";
    78.  
    79.        const string Comments =
    80.            @"";
    81.  
    82.        /// <summary>
    83.        /// Path to the CHANGELOG file in Unity's virtual file system. Used to get the version number.
    84.        /// </summary>
    85.        const string ChangeLogPath = "Packages/com.unity.formats.fbx/CHANGELOG.md";
    86.  
    87.        // NOTE: The ellipsis at the end of the Menu Item name prevents the context
    88.        //       from being passed to command, thus resulting in OnContextItem()
    89.        //       being called only once regardless of what is selected.
    90.        const string MenuItemName = "GameObject/Export To FBX...";
    91.  
    92.        const string TimelineClipMenuItemName = "GameObject/Export Selected Timeline Clip...";
    93.  
    94.        const string ProgressBarTitle = "FBX Export";
    95.  
    96.        const char MayaNamespaceSeparator = ':';
    97.  
    98.        // replace invalid chars with this one
    99.        const char InvalidCharReplacement = '_';
    100.  
    101.        const string RegexCharStart = "[";
    102.        const string RegexCharEnd = "]";
    103.  
    104.        internal const float UnitScaleFactor = 100f;
    105.  
    106.        internal const string PACKAGE_UI_NAME = "FBX Exporter";
    107.  
    108.        /// <summary>
    109.        /// name of the scene's default camera
    110.        /// </summary>
    111.        private static string DefaultCamera = "";
    112.  
    113.        private const string SkeletonPrefix = "_Skel";
    114.  
    115.        private const string SkinPrefix = "_Skin";
    116.  
    117.        /// <summary>
    118.        /// name prefix for custom properties
    119.        /// </summary>
    120.        const string NamePrefix = "Unity_";
    121.  
    122.        private static string MakeName (string basename)
    123.        {
    124.            return NamePrefix + basename;
    125.        }
    126.  
    127.        /// <summary>
    128.        /// Create instance of exporter.
    129.        /// </summary>
    130.        static ModelExporter Create ()
    131.        {
    132.            return new ModelExporter ();
    133.        }
    134.  
    135.        /// <summary>
    136.        /// Which components map from Unity Object to Fbx Object
    137.        /// </summary>
    138.        internal enum FbxNodeRelationType
    139.        {
    140.            NodeAttribute,
    141.            Property,
    142.            Material
    143.        }
    144.  
    145.        internal static Dictionary<System.Type, KeyValuePair<System.Type,FbxNodeRelationType>> MapsToFbxObject = new Dictionary<System.Type, KeyValuePair<System.Type,FbxNodeRelationType>> ()
    146.        {
    147.            { typeof(Transform),            new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxProperty), FbxNodeRelationType.Property) },
    148.            { typeof(MeshFilter),           new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxMesh), FbxNodeRelationType.NodeAttribute) },
    149.            { typeof(SkinnedMeshRenderer),  new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxMesh), FbxNodeRelationType.NodeAttribute) },
    150.            { typeof(Light),                new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxLight), FbxNodeRelationType.NodeAttribute) },
    151.            { typeof(Camera),               new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxCamera), FbxNodeRelationType.NodeAttribute) },
    152.            { typeof(Material),             new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxSurfaceMaterial), FbxNodeRelationType.Material) },
    153.        };
    154.  
    155.        /// <summary>
    156.        /// keep a map between GameObject and FbxNode for quick lookup when we export
    157.        /// animation.
    158.        /// </summary>
    159.        Dictionary<GameObject, FbxNode> MapUnityObjectToFbxNode = new Dictionary<GameObject, FbxNode> ();
    160.  
    161.        /// <summary>
    162.        /// keep a map between the constrained FbxNode (in Unity this is the GameObject with constraint component)
    163.        /// and its FbxConstraints for quick lookup when exporting constraint animations.
    164.        /// </summary>
    165.        Dictionary<FbxNode, Dictionary<FbxConstraint, System.Type>> MapConstrainedObjectToConstraints = new Dictionary<FbxNode, Dictionary<FbxConstraint, System.Type>>();
    166.  
    167.        /// <summary>
    168.        /// keep a map between the FbxNode and its blendshape channels for quick lookup when exporting blendshapes.
    169.        /// </summary>
    170.        Dictionary<FbxNode, List<FbxBlendShapeChannel>> MapUnityObjectToBlendShapes = new Dictionary<FbxNode, List<FbxBlendShapeChannel>>();
    171.  
    172.        /// <summary>
    173.        /// Map Unity material ID to FBX material object
    174.        /// </summary>
    175.        Dictionary<int, FbxSurfaceMaterial> MaterialMap = new Dictionary<int, FbxSurfaceMaterial> ();
    176.  
    177.        /// <summary>
    178.        /// Map texture filename name to FBX texture object
    179.        /// </summary>
    180.        Dictionary<string, FbxTexture> TextureMap = new Dictionary<string, FbxTexture> ();
    181.  
    182.        /// <summary>
    183.        /// Map a Unity mesh to an fbx node (for preserving instances)
    184.        /// </summary>
    185.        Dictionary<Mesh, FbxNode> SharedMeshes = new Dictionary<Mesh, FbxNode>();
    186.  
    187.        /// <summary>
    188.        /// Map for the Name of an Object to number of objects with this name.
    189.        /// Used for enforcing unique names on export.
    190.        /// </summary>
    191.        Dictionary<string, int> NameToIndexMap = new Dictionary<string, int> ();
    192.  
    193.        /// <summary>
    194.        /// Map for the Material Name to number of materials with this name.
    195.        /// Used for enforcing unique names on export.
    196.        /// </summary>
    197.        Dictionary<string, int> MaterialNameToIndexMap = new Dictionary<string, int>();
    198.  
    199.        /// <summary>
    200.        /// Map for the Texture Name to number of textures with this name.
    201.        /// Used for enforcing unique names on export.
    202.        /// </summary>
    203.        Dictionary<string, int> TextureNameToIndexMap = new Dictionary<string, int>();
    204.      
    205.        /// <summary>
    206.        /// Format for creating unique names
    207.        /// </summary>
    208.        const string UniqueNameFormat = "{0}_{1}";
    209.  
    210.        /// <summary>
    211.        /// The animation fbx file format.
    212.        /// </summary>
    213.        const string AnimFbxFileFormat = "{0}/{1}@{2}.fbx";
    214.  
    215.        /// <summary>
    216.        /// Gets the export settings.
    217.        /// </summary>
    218.        internal static ExportSettings ExportSettings {
    219.            get { return ExportSettings.instance; }
    220.        }
    221.  
    222.        internal static IExportOptions DefaultOptions {
    223.            get { return new ExportModelSettingsSerialize(); }
    224.        }
    225.  
    226.        private IExportOptions m_exportOptions;
    227.        private IExportOptions ExportOptions {
    228.            get {
    229.                if (m_exportOptions == null) {
    230.                    // get default settings;
    231.                    m_exportOptions = DefaultOptions;
    232.                }
    233.                return m_exportOptions;
    234.            }
    235.            set { m_exportOptions = value; }
    236.        }
    237.  
    238.        /// <summary>
    239.        /// Gets the Unity default material.
    240.        /// </summary>
    241.        internal static Material DefaultMaterial {
    242.            get {
    243.                if (!s_defaultMaterial) {
    244.                    var obj = GameObject.CreatePrimitive (PrimitiveType.Quad);
    245.                    s_defaultMaterial = obj.GetComponent<Renderer> ().sharedMaterial;
    246.                    Object.DestroyImmediate (obj);
    247.                }
    248.                return s_defaultMaterial;
    249.            }
    250.        }
    251.  
    252.        static Material s_defaultMaterial = null;
    253.  
    254.        static Dictionary<UnityEngine.LightType, FbxLight.EType> MapLightType = new Dictionary<UnityEngine.LightType, FbxLight.EType> () {
    255.            { UnityEngine.LightType.Directional,    FbxLight.EType.eDirectional },
    256.            { UnityEngine.LightType.Spot,           FbxLight.EType.eSpot },
    257.            { UnityEngine.LightType.Point,          FbxLight.EType.ePoint },
    258.            { UnityEngine.LightType.Area,           FbxLight.EType.eArea },
    259.        };
    260.  
    261.        /// <summary>
    262.        /// Gets the version number of the FbxExporters plugin from the readme.
    263.        /// </summary>
    264.        internal static string GetVersionFromReadme()
    265.        {
    266.            if (!File.Exists (ChangeLogPath)) {
    267.                Debug.LogWarning (string.Format("Could not find version number, the ChangeLog file is missing from: {0}", ChangeLogPath));
    268.                return null;
    269.            }
    270.  
    271.            try {
    272.                // The standard format is:
    273.                //   ## [a.b.c-whatever] - yyyy-mm-dd
    274.                // Another format is:
    275.                //   **Version**: a.b.c-whatever
    276.                // we handle either one and read out the version
    277.                var lines = File.ReadAllLines (ChangeLogPath);
    278.                var regexes = new string [] {
    279.                    @"^\s*##\s*\[(.*)\]",
    280.                    @"^\s*\*\*Version\*\*:\s*(.*)\s*"
    281.                };
    282.                foreach (var line in lines) {
    283.                    foreach (var regex in regexes) {
    284.                        var match = System.Text.RegularExpressions.Regex.Match(line, regex);
    285.                        if (match.Success) {
    286.                            var version = match.Groups[1].Value;
    287.                            return version.Trim ();
    288.                        }
    289.                    }
    290.                }
    291.  
    292.                // If we're here, we didn't find any match.
    293.                Debug.LogWarning (string.Format("Could not find most recent version number in {0}", ChangeLogPath));
    294.                return null;
    295.            }
    296.            catch(IOException e){
    297.                Debug.LogException (e);
    298.                Debug.LogWarning (string.Format("Error reading file {0} ({1})", ChangeLogPath, e));
    299.                return null;
    300.            }
    301.  
    302.        }
    303.  
    304.        /// <summary>
    305.        /// Get a layer (to store UVs, normals, etc) on the mesh.
    306.        /// If it doesn't exist yet, create it.
    307.        /// </summary>
    308.        internal static FbxLayer GetOrCreateLayer(FbxMesh fbxMesh, int layer = 0 /* default layer */)
    309.        {
    310.            int maxLayerIndex = fbxMesh.GetLayerCount() - 1;
    311.            while (layer > maxLayerIndex) {
    312.                // We'll have to create the layer (potentially several).
    313.                // Make sure to avoid infinite loops even if there's an
    314.                // FbxSdk bug.
    315.                int newLayerIndex = fbxMesh.CreateLayer();
    316.                if (newLayerIndex <= maxLayerIndex) {
    317.                    // Error!
    318.                    throw new ModelExportException(
    319.                        "Internal error: Unable to create mesh layer "
    320.                        + (maxLayerIndex + 1)
    321.                        + " on mesh " + fbxMesh.GetName ());
    322.                }
    323.                maxLayerIndex = newLayerIndex;
    324.            }
    325.            return fbxMesh.GetLayer (layer);
    326.        }
    327.  
    328.        /// <summary>
    329.        /// Export the mesh's attributes using layer 0.
    330.        /// </summary>
    331.        private bool ExportComponentAttributes (MeshInfo mesh, FbxMesh fbxMesh, int[] unmergedTriangles)
    332.        {
    333.            // return true if any attribute was exported
    334.            bool exportedAttribute = false;
    335.  
    336.            // Set the normals on Layer 0.
    337.            FbxLayer fbxLayer = GetOrCreateLayer(fbxMesh);
    338.  
    339.            if (mesh.HasValidNormals()) {
    340.                using (var fbxLayerElement = FbxLayerElementNormal.Create (fbxMesh, "Normals")) {
    341.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
    342.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
    343.  
    344.                    // Add one normal per each vertex face index (3 per triangle)
    345.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
    346.  
    347.                    for (int n = 0; n < unmergedTriangles.Length; n++) {
    348.                        int unityTriangle = unmergedTriangles [n];
    349.                        fbxElementArray.Add (ConvertToFbxVector4 (mesh.Normals [unityTriangle]));
    350.                    }
    351.  
    352.                    fbxLayer.SetNormals (fbxLayerElement);
    353.                }
    354.                exportedAttribute = true;
    355.            }
    356.  
    357.            /// Set the binormals on Layer 0.
    358.            if (mesh.HasValidBinormals()) {
    359.                using (var fbxLayerElement = FbxLayerElementBinormal.Create (fbxMesh, "Binormals")) {
    360.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
    361.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
    362.  
    363.                    // Add one normal per each vertex face index (3 per triangle)
    364.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
    365.  
    366.                    for (int n = 0; n < unmergedTriangles.Length; n++) {
    367.                        int unityTriangle = unmergedTriangles [n];
    368.                        fbxElementArray.Add (ConvertToFbxVector4 (mesh.Binormals [unityTriangle]));
    369.                    }
    370.                    fbxLayer.SetBinormals (fbxLayerElement);
    371.                }
    372.                exportedAttribute = true;
    373.            }
    374.  
    375.            /// Set the tangents on Layer 0.
    376.            if (mesh.HasValidTangents()) {
    377.                using (var fbxLayerElement = FbxLayerElementTangent.Create (fbxMesh, "Tangents")) {
    378.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
    379.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
    380.  
    381.                    // Add one normal per each vertex face index (3 per triangle)
    382.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
    383.  
    384.                    for (int n = 0; n < unmergedTriangles.Length; n++) {
    385.                        int unityTriangle = unmergedTriangles [n];
    386.                        fbxElementArray.Add (ConvertToFbxVector4 (
    387.                            new Vector3 (
    388.                                mesh.Tangents [unityTriangle] [0],
    389.                                mesh.Tangents [unityTriangle] [1],
    390.                                mesh.Tangents [unityTriangle] [2]
    391.                            )));
    392.                    }
    393.                    fbxLayer.SetTangents (fbxLayerElement);
    394.                }
    395.                exportedAttribute = true;
    396.            }
    397.  
    398.            exportedAttribute |= ExportUVs (fbxMesh, mesh, unmergedTriangles);
    399.  
    400.            if (mesh.HasValidVertexColors()) {
    401.                using (var fbxLayerElement = FbxLayerElementVertexColor.Create (fbxMesh, "VertexColors")) {
    402.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
    403.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
    404.  
    405.                    // set texture coordinates per vertex
    406.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
    407.  
    408.                    // (Uni-31596) only copy unique UVs into this array, and index appropriately
    409.                    for (int n = 0; n < mesh.VertexColors.Length; n++) {
    410.                        // Converting to Color from Color32, as Color32 stores the colors
    411.                        // as ints between 0-255, while FbxColor and Color
    412.                        // use doubles between 0-1
    413.                        Color color = mesh.VertexColors [n];
    414.                        fbxElementArray.Add (new FbxColor (color.r,
    415.                            color.g,
    416.                            color.b,
    417.                            color.a));
    418.                    }
    419.  
    420.                    // For each face index, point to a texture uv
    421.                    FbxLayerElementArray fbxIndexArray = fbxLayerElement.GetIndexArray ();
    422.                    fbxIndexArray.SetCount (unmergedTriangles.Length);
    423.  
    424.                    for (int i = 0; i < unmergedTriangles.Length; i++) {
    425.                        fbxIndexArray.SetAt (i, unmergedTriangles );
    426.                    }
    427.                    fbxLayer.SetVertexColors (fbxLayerElement);
    428.                }
    429.                exportedAttribute = true;
    430.            }
    431.            return exportedAttribute;
    432.        }
    433.  
    434.        /// <summary>
    435.        /// Unity has up to 4 uv sets per mesh. Export all the ones that exist.
    436.        /// </summary>
    437.        /// <param name="fbxMesh">Fbx mesh.</param>
    438.        /// <param name="mesh">Mesh.</param>
    439.        /// <param name="unmergedTriangles">Unmerged triangles.</param>
    440.        private static bool ExportUVs(FbxMesh fbxMesh, MeshInfo mesh, int[] unmergedTriangles)
    441.        {
    442.            Vector2[][] uvs = new Vector2[][] {
    443.                mesh.UV,
    444.                mesh.mesh.uv2,
    445.                mesh.mesh.uv3,
    446.                mesh.mesh.uv4
    447.            };
    448.  
    449.            int k = 0;
    450.            for (int i = 0; i < uvs.Length; i++) {
    451.                if (uvs == null || uvs .Length == 0) {
    452.                    continue; // don't have these UV's, so skip
    453.                }
    454.  
    455.                FbxLayer fbxLayer = GetOrCreateLayer (fbxMesh, k);
    456.                using (var fbxLayerElement = FbxLayerElementUV.Create (fbxMesh, "UVSet" + i))
    457.                {
    458.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
    459.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
    460.  
    461.                    // set texture coordinates per vertex
    462.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
    463.  
    464.                    // (Uni-31596) only copy unique UVs into this array, and index appropriately
    465.                    for (int n = 0; n < uvs.Length; n++) {
    466.                        fbxElementArray.Add (new FbxVector2 (uvs [n] [0],
    467.                            uvs [n] [1]));
    468.                    }
    469.  
    470.                    // For each face index, point to a texture uv
    471.                    FbxLayerElementArray fbxIndexArray = fbxLayerElement.GetIndexArray ();
    472.                    fbxIndexArray.SetCount (unmergedTriangles.Length);
    473.  
    474.                    for(int j = 0; j < unmergedTriangles.Length; j++){
    475.                        fbxIndexArray.SetAt (j, unmergedTriangles [j]);
    476.                    }
    477.                    fbxLayer.SetUVs (fbxLayerElement, FbxLayerElement.EType.eTextureDiffuse);
    478.                }
    479.                k++;
    480.            }
    481.  
    482.            // if we incremented k, then at least on set of UV's were exported
    483.            return k > 0;
    484.        }
    485.  
    486.        /// <summary>
    487.        /// Export the mesh's blend shapes.
    488.        /// </summary>
    489.        private FbxBlendShape ExportBlendShapes(MeshInfo mesh, FbxMesh fbxMesh, FbxScene fbxScene, int[] unmergedTriangles)
    490.        {
    491.            var umesh = mesh.mesh;
    492.            if (umesh.blendShapeCount == 0)
    493.                return null;
    494.  
    495.            var fbxBlendShape = FbxBlendShape.Create(fbxScene, umesh.name + "_BlendShape");
    496.            fbxMesh.AddDeformer(fbxBlendShape);
    497.  
    498.            var numVertices = umesh.vertexCount;
    499.            var basePoints = umesh.vertices;
    500.            var baseNormals = umesh.normals;
    501.            var baseTangents = umesh.tangents;
    502.            var deltaPoints = new Vector3[numVertices];
    503.            var deltaNormals = new Vector3[numVertices];
    504.            var deltaTangents = new Vector3[numVertices];
    505.  
    506.            for (int bi = 0; bi < umesh.blendShapeCount; ++bi)
    507.            {
    508.                var bsName = umesh.GetBlendShapeName(bi);
    509.                var numFrames = umesh.GetBlendShapeFrameCount(bi);
    510.                var fbxChannel = FbxBlendShapeChannel.Create(fbxScene, bsName);
    511.                fbxBlendShape.AddBlendShapeChannel(fbxChannel);
    512.  
    513.                for (int fi = 0; fi < numFrames; ++fi)
    514.                {
    515.                    var weight = umesh.GetBlendShapeFrameWeight(bi, fi);
    516.                    umesh.GetBlendShapeFrameVertices(bi, fi, deltaPoints, deltaNormals, deltaTangents);
    517.  
    518.                    var fbxShapeName = bsName;
    519.  
    520.                    if (numFrames > 1)
    521.                    {
    522.                        fbxShapeName += "_" + fi;
    523.                    }
    524.  
    525.                    var fbxShape = FbxShape.Create(fbxScene, fbxShapeName);
    526.                    fbxChannel.AddTargetShape(fbxShape, weight);
    527.  
    528.                    // control points
    529.                    fbxShape.InitControlPoints(ControlPointToIndex.Count());
    530.                    for (int vi = 0; vi < numVertices; ++vi)
    531.                    {
    532.                        int ni = ControlPointToIndex[basePoints[vi]];
    533.                        var v = basePoints[vi] + deltaPoints[vi];
    534.                        fbxShape.SetControlPointAt(ConvertToFbxVector4(v, UnitScaleFactor), ni);
    535.                    }
    536.  
    537.                    // normals
    538.                    if (mesh.HasValidNormals())
    539.                    {
    540.                        var elemNormals = fbxShape.CreateElementNormal();
    541.                        elemNormals.SetMappingMode(FbxLayerElement.EMappingMode.eByPolygonVertex);
    542.                        elemNormals.SetReferenceMode(FbxLayerElement.EReferenceMode.eDirect);
    543.                        var dstNormals = elemNormals.GetDirectArray();
    544.                        dstNormals.SetCount(unmergedTriangles.Length);
    545.                        for (int ii = 0; ii < unmergedTriangles.Length; ++ii)
    546.                        {
    547.                            int vi = unmergedTriangles[ii];
    548.                            var n = baseNormals[vi] + deltaNormals[vi];
    549.                            dstNormals.SetAt(ii, ConvertToFbxVector4(n));
    550.                        }
    551.                    }
    552.  
    553.                    // tangents
    554.                    if (mesh.HasValidTangents())
    555.                    {
    556.                        var elemTangents = fbxShape.CreateElementTangent();
    557.                        elemTangents.SetMappingMode(FbxLayerElement.EMappingMode.eByPolygonVertex);
    558.                        elemTangents.SetReferenceMode(FbxLayerElement.EReferenceMode.eDirect);
    559.                        var dstTangents = elemTangents.GetDirectArray();
    560.                        dstTangents.SetCount(unmergedTriangles.Length);
    561.                        for (int ii = 0; ii < unmergedTriangles.Length; ++ii)
    562.                        {
    563.                            int vi = unmergedTriangles[ii];
    564.                            var t = (Vector3)baseTangents[vi] + deltaTangents[vi];
    565.                            dstTangents.SetAt(ii, ConvertToFbxVector4(t));
    566.                        }
    567.                    }
    568.                }
    569.            }
    570.            return fbxBlendShape;
    571.        }
    572.  
    573.        /// <summary>
    574.        /// Takes in a left-handed UnityEngine.Vector3 denoting a normal,
    575.        /// returns a right-handed FbxVector4.
    576.        ///
    577.        /// Unity is left-handed, Maya and Max are right-handed.
    578.        /// The FbxSdk conversion routines can't handle changing handedness.
    579.        ///
    580.        /// Remember you also need to flip the winding order on your polygons.
    581.        /// </summary>
    582.        internal static FbxVector4 ConvertToFbxVector4(Vector3 leftHandedVector, float unitScale = 1f)
    583.        {
    584.            // negating the x component of the vector converts it from left to right handed coordinates
    585.            return unitScale * new FbxVector4 (
    586.                leftHandedVector[0],
    587.                leftHandedVector[1],
    588.                leftHandedVector[2]);
    589.        }
    590.  
    591.        /// <summary>
    592.        /// Exports a texture from Unity to FBX.
    593.        /// The texture must be a property on the unityMaterial; it gets
    594.        /// linked to the FBX via a property on the fbxMaterial.
    595.        ///
    596.        /// The texture file must be a file on disk; it is not embedded within the FBX.
    597.        /// </summary>
    598.        /// <param name="unityMaterial">Unity material.</param>
    599.        /// <param name="unityPropName">Unity property name, e.g. "_MainTex".</param>
    600.        /// <param name="fbxMaterial">Fbx material.</param>
    601.        /// <param name="fbxPropName">Fbx property name, e.g. <c>FbxSurfaceMaterial.sDiffuse</c>.</param>
    602.        internal bool ExportTexture (Material unityMaterial, string unityPropName,
    603.                                    FbxSurfaceMaterial fbxMaterial, string fbxPropName)
    604.        {
    605.            if (!unityMaterial) {
    606.                return false;
    607.            }
    608.  
    609.            // Get the texture on this property, if any.
    610.            if (!unityMaterial.HasProperty (unityPropName)) {
    611.                return false;
    612.            }
    613.            var unityTexture = unityMaterial.GetTexture (unityPropName);
    614.            if (!unityTexture) {
    615.                return false;
    616.            }
    617.  
    618.            // Find its filename
    619.            var textureSourceFullPath = AssetDatabase.GetAssetPath(unityTexture);
    620.            if (string.IsNullOrEmpty(textureSourceFullPath)) {
    621.                return false;
    622.            }
    623.  
    624.            // get absolute filepath to texture
    625.            textureSourceFullPath = Path.GetFullPath (textureSourceFullPath);
    626.  
    627.            if (Verbose) {
    628.                Debug.Log (string.Format ("{2}.{1} setting texture path {0}", textureSourceFullPath, fbxPropName, fbxMaterial.GetName ()));
    629.            }
    630.  
    631.            // Find the corresponding property on the fbx material.
    632.            var fbxMaterialProperty = fbxMaterial.FindProperty (fbxPropName);
    633.            if (fbxMaterialProperty == null || !fbxMaterialProperty.IsValid ()) {
    634.                Debug.Log ("property not found");
    635.                return false;
    636.            }
    637.  
    638.            // Find or create an fbx texture and link it up to the fbx material.
    639.            if (!TextureMap.ContainsKey (textureSourceFullPath)) {
    640.                var textureName = GetUniqueTextureName(fbxPropName + "_Texture");
    641.                var fbxTexture = FbxFileTexture.Create (fbxMaterial, textureName);
    642.                fbxTexture.SetFileName (textureSourceFullPath);
    643.                fbxTexture.SetTextureUse (FbxTexture.ETextureUse.eStandard);
    644.                fbxTexture.SetMappingType (FbxTexture.EMappingType.eUV);
    645.                TextureMap.Add (textureSourceFullPath, fbxTexture);
    646.            }
    647.            TextureMap [textureSourceFullPath].ConnectDstProperty (fbxMaterialProperty);
    648.  
    649.            return true;
    650.        }
    651.  
    652.        /// <summary>
    653.        /// Get the color of a material, or grey if we can't find it.
    654.        /// </summary>
    655.        internal FbxDouble3 GetMaterialColor (Material unityMaterial, string unityPropName, float defaultValue = 1)
    656.        {
    657.            if (!unityMaterial) {
    658.                return new FbxDouble3(defaultValue);
    659.            }
    660.            if (!unityMaterial.HasProperty (unityPropName)) {
    661.                return new FbxDouble3(defaultValue);
    662.            }
    663.            var unityColor = unityMaterial.GetColor (unityPropName);
    664.            return new FbxDouble3 (unityColor.r, unityColor.g, unityColor.b);
    665.        }
    666.  
    667.        /// <summary>
    668.        /// Export (and map) a Unity PBS material to FBX classic material
    669.        /// </summary>
    670.        internal bool ExportMaterial (Material unityMaterial, FbxScene fbxScene, FbxNode fbxNode)
    671.        {
    672.            if (!unityMaterial) {
    673.                unityMaterial = DefaultMaterial;
    674.            }
    675.  
    676.            var unityID = unityMaterial.GetInstanceID();
    677.            FbxSurfaceMaterial mappedMaterial;
    678.            if (MaterialMap.TryGetValue (unityID, out mappedMaterial)) {
    679.                fbxNode.AddMaterial (mappedMaterial);
    680.                return true;
    681.            }
    682.  
    683.            var unityName = unityMaterial.name;
    684.            var fbxName = ExportOptions.UseMayaCompatibleNames
    685.                ? ConvertToMayaCompatibleName(unityName) : unityName;
    686.  
    687.            fbxName = GetUniqueMaterialName(fbxName);
    688.  
    689.            if (Verbose) {
    690.                if (unityName != fbxName) {
    691.                    Debug.Log (string.Format ("exporting material {0} as {1}", unityName, fbxName));
    692.                } else {
    693.                    Debug.Log(string.Format("exporting material {0}", unityName));
    694.                }
    695.            }
    696.  
    697.            // We'll export either Phong or Lambert. Phong if it calls
    698.            // itself specular, Lambert otherwise.
    699.            var shader = unityMaterial.shader;
    700.            bool specular = shader.name.ToLower ().Contains ("specular");
    701.            bool hdrp = shader.name.ToLower().Contains("hdrp");
    702.  
    703.            var fbxMaterial = specular
    704.                ? FbxSurfacePhong.Create (fbxScene, fbxName)
    705.                : FbxSurfaceLambert.Create (fbxScene, fbxName);
    706.  
    707.            // Copy the flat colours over from Unity standard materials to FBX.
    708.            fbxMaterial.Diffuse.Set (GetMaterialColor (unityMaterial, "_Color"));
    709.            fbxMaterial.Emissive.Set (GetMaterialColor (unityMaterial, "_EmissionColor", 0));
    710.            // hdrp materials dont export emission properly, so default to 0
    711.            if (hdrp) {
    712.                fbxMaterial.Emissive.Set(new FbxDouble3(0, 0, 0));
    713.            }
    714.            fbxMaterial.Ambient.Set (new FbxDouble3 ());
    715.  
    716.            fbxMaterial.BumpFactor.Set (unityMaterial.HasProperty ("_BumpScale") ? unityMaterial.GetFloat ("_BumpScale") : 0);
    717.  
    718.            if (specular) {
    719.                (fbxMaterial as FbxSurfacePhong).Specular.Set (GetMaterialColor (unityMaterial, "_SpecColor"));
    720.            }
    721.  
    722.            // Export the textures from Unity standard materials to FBX.
    723.            ExportTexture (unityMaterial, "_MainTex", fbxMaterial, FbxSurfaceMaterial.sDiffuse);
    724.            ExportTexture (unityMaterial, "_EmissionMap", fbxMaterial, FbxSurfaceMaterial.sEmissive);
    725.            ExportTexture (unityMaterial, "_BumpMap", fbxMaterial, FbxSurfaceMaterial.sNormalMap);
    726.            if (specular) {
    727.                ExportTexture (unityMaterial, "_SpecGlossMap", fbxMaterial, FbxSurfaceMaterial.sSpecular);
    728.            }
    729.  
    730.            MaterialMap.Add (unityID, fbxMaterial);
    731.            fbxNode.AddMaterial (fbxMaterial);
    732.            return true;
    733.        }
    734.  
    735.        /// <summary>
    736.        /// Sets up the material to polygon mapping for fbxMesh.
    737.        /// To determine which part of the mesh uses which material, look at the submeshes
    738.        /// and which polygons they represent.
    739.        /// Assuming equal number of materials as submeshes, and that they are in the same order.
    740.        /// (i.e. submesh 1 uses material 1)
    741.        /// </summary>
    742.        /// <param name="fbxMesh">Fbx mesh.</param>
    743.        /// <param name="mesh">Mesh.</param>
    744.        /// <param name="materials">Materials.</param>
    745.        private void AssignLayerElementMaterial(FbxMesh fbxMesh, Mesh mesh, int materialCount)
    746.        {
    747.            // Add FbxLayerElementMaterial to layer 0 of the node
    748.            FbxLayer fbxLayer = fbxMesh.GetLayer (0 /* default layer */);
    749.            if (fbxLayer == null) {
    750.                fbxMesh.CreateLayer ();
    751.                fbxLayer = fbxMesh.GetLayer (0 /* default layer */);
    752.            }
    753.  
    754.            using (var fbxLayerElement = FbxLayerElementMaterial.Create (fbxMesh, "Material")) {
    755.                // if there is only one material then set everything to that material
    756.                if (materialCount == 1) {
    757.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eAllSame);
    758.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
    759.  
    760.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetIndexArray ();
    761.                    fbxElementArray.Add (0);
    762.                } else {
    763.                    fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygon);
    764.                    fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
    765.  
    766.                    FbxLayerElementArray fbxElementArray = fbxLayerElement.GetIndexArray ();
    767.  
    768.                    for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) {
    769.                        var topology = mesh.GetTopology (subMeshIndex);
    770.                        int polySize;
    771.  
    772.                        switch (topology) {
    773.                            case MeshTopology.Triangles:
    774.                                polySize = 3;
    775.                                break;
    776.                            case MeshTopology.Quads:
    777.                                polySize = 4;
    778.                                break;
    779.                            case MeshTopology.Lines:
    780.                                throw new System.NotImplementedException();
    781.                            case MeshTopology.Points:
    782.                                throw new System.NotImplementedException();
    783.                            case MeshTopology.LineStrip:
    784.                                throw new System.NotImplementedException();
    785.                            default:
    786.                                throw new System.NotImplementedException();
    787.                        }
    788.  
    789.                        // Specify the material index for each polygon.
    790.                        // Material index should match subMeshIndex.
    791.                        var indices = mesh.GetIndices(subMeshIndex);
    792.                        for (int j = 0, n = indices.Length / polySize; j < n; j++) {
    793.                            fbxElementArray.Add(subMeshIndex);
    794.                        }
    795.                    }
    796.                }
    797.                fbxLayer.SetMaterials (fbxLayerElement);
    798.            }
    799.        }
    800.  
    801.        /// <summary>
    802.        /// Exports a unity mesh and attaches it to the node as an FbxMesh.
    803.        ///
    804.        /// Able to export materials per sub-mesh as well (by default, exports with the default material).
    805.        ///
    806.        /// Use fbxNode.GetMesh() to access the exported mesh.
    807.        /// </summary>
    808.        internal bool ExportMesh (Mesh mesh, FbxNode fbxNode, Material[] materials = null)
    809.        {
    810.            var meshInfo = new MeshInfo(mesh, materials);
    811.            return ExportMesh(meshInfo, fbxNode);
    812.        }
    813.  
    814.        /// <summary>
    815.        /// Keeps track of the index of each point in the exported vertex array.
    816.        /// </summary>
    817.        private Dictionary<Vector3, int> ControlPointToIndex = new Dictionary<Vector3, int> ();
    818.  
    819.        /// <summary>
    820.        /// Exports a unity mesh and attaches it to the node as an FbxMesh.
    821.        /// </summary>
    822.        bool ExportMesh (MeshInfo meshInfo, FbxNode fbxNode)
    823.        {
    824.            if (!meshInfo.IsValid) {
    825.                return false;
    826.            }
    827.  
    828.            NumMeshes++;
    829.            NumTriangles += meshInfo.Triangles.Length / 3;
    830.  
    831.            // create the mesh structure.
    832.            var fbxScene = fbxNode.GetScene();
    833.            FbxMesh fbxMesh = FbxMesh.Create (fbxScene, "Scene");
    834.  
    835.            // Create control points.
    836.            ControlPointToIndex.Clear();
    837.            {
    838.                var vertices = meshInfo.Vertices;
    839.                for (int v = 0, n = meshInfo.VertexCount; v < n; v++) {
    840.                    if (ControlPointToIndex.ContainsKey (vertices [v])) {
    841.                        continue;
    842.                    }
    843.                    ControlPointToIndex [vertices [v]] = ControlPointToIndex.Count();
    844.                }
    845.                fbxMesh.InitControlPoints (ControlPointToIndex.Count());
    846.  
    847.                foreach (var kvp in ControlPointToIndex) {
    848.                    var controlPoint = kvp.Key;
    849.                    var index = kvp.Value;
    850.                    fbxMesh.SetControlPointAt (ConvertToFbxVector4(controlPoint, UnitScaleFactor), index);
    851.                }
    852.            }
    853.  
    854.            var unmergedPolygons = new List<int> ();
    855.            var mesh = meshInfo.mesh;
    856.            for (int s = 0; s < mesh.subMeshCount; s++) {
    857.                var topology = mesh.GetTopology (s);
    858.                var indices = mesh.GetIndices (s);
    859.  
    860.                int polySize;
    861.                int[] vertOrder;
    862.  
    863.                switch (topology) {
    864.                    case MeshTopology.Triangles:
    865.                        polySize = 3;
    866.                        vertOrder = new int[] { 0, 1, 2 };
    867.                        break;
    868.                    case MeshTopology.Quads:
    869.                        polySize = 4;
    870.                        vertOrder = new int[] { 0, 1, 2, 3 };
    871.                        break;
    872.                    case MeshTopology.Lines:
    873.                        throw new System.NotImplementedException();
    874.                    case MeshTopology.Points:
    875.                        throw new System.NotImplementedException();
    876.                    case MeshTopology.LineStrip:
    877.                        throw new System.NotImplementedException();
    878.                    default:
    879.                        throw new System.NotImplementedException();
    880.                }
    881.  
    882.                for (int f = 0; f < indices.Length / polySize; f++) {
    883.                    fbxMesh.BeginPolygon ();
    884.  
    885.                    foreach (int val in vertOrder) {
    886.                        int polyVert = indices [polySize * f + val];
    887.  
    888.                        // Save the polygon order (without merging vertices) so we
    889.                        // properly export UVs, normals, binormals, etc.
    890.                        unmergedPolygons.Add(polyVert);
    891.  
    892.                        polyVert = ControlPointToIndex [meshInfo.Vertices [polyVert]];
    893.                        fbxMesh.AddPolygon (polyVert);
    894.  
    895.                    }
    896.                    fbxMesh.EndPolygon ();
    897.                }
    898.            }
    899.  
    900.            // Set up materials per submesh.
    901.            foreach (var mat in meshInfo.Materials) {
    902.                ExportMaterial (mat, fbxScene, fbxNode);
    903.            }
    904.            AssignLayerElementMaterial (fbxMesh, meshInfo.mesh, meshInfo.Materials.Length);
    905.  
    906.            // Set up normals, etc.
    907.            ExportComponentAttributes (meshInfo, fbxMesh, unmergedPolygons.ToArray());
    908.  
    909.            // Set up blend shapes.
    910.            FbxBlendShape fbxBlendShape = ExportBlendShapes(meshInfo, fbxMesh, fbxScene, unmergedPolygons.ToArray());
    911.          
    912.            if(fbxBlendShape != null && fbxBlendShape.GetBlendShapeChannelCount() > 0)
    913.            {
    914.                // Populate mapping for faster lookup when exporting blendshape animations
    915.                List<FbxBlendShapeChannel> blendshapeChannels;
    916.                if (!MapUnityObjectToBlendShapes.TryGetValue(fbxNode, out blendshapeChannels))
    917.                {
    918.                    blendshapeChannels = new List<FbxBlendShapeChannel>();
    919.                    MapUnityObjectToBlendShapes.Add(fbxNode, blendshapeChannels);
    920.                }
    921.              
    922.                for(int i = 0; i < fbxBlendShape.GetBlendShapeChannelCount(); i++)
    923.                {
    924.                    var bsChannel = fbxBlendShape.GetBlendShapeChannel(i);
    925.                    blendshapeChannels.Add(bsChannel);
    926.                }
    927.            }
    928.  
    929.            // set the fbxNode containing the mesh
    930.            fbxNode.SetNodeAttribute (fbxMesh);
    931.            fbxNode.SetShadingMode (FbxNode.EShadingMode.eWireFrame);
    932.            return true;
    933.        }
    934.  
    935.        /// <summary>
    936.        /// Export GameObject as a skinned mesh with material, bones, a skin and, a bind pose.
    937.        /// </summary>
    938.        [SecurityPermission(SecurityAction.LinkDemand)]
    939.        private bool ExportSkinnedMesh (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
    940.        {
    941.            if(!unityGo || fbxNode == null)
    942.            {
    943.                return false;
    944.            }
    945.  
    946.            SkinnedMeshRenderer unitySkin
    947.            = unityGo.GetComponent<SkinnedMeshRenderer> ();
    948.  
    949.            if (unitySkin == null) {
    950.                return false;
    951.            }
    952.  
    953.            var mesh = unitySkin.sharedMesh;
    954.            if (!mesh) {
    955.                return false;
    956.            }
    957.  
    958.            if (Verbose)
    959.                Debug.Log (string.Format ("exporting {0} {1}", "Skin", fbxNode.GetName ()));
    960.  
    961.  
    962.            var meshInfo = new MeshInfo(unitySkin.sharedMesh, unitySkin.sharedMaterials);
    963.  
    964.            FbxMesh fbxMesh = null;
    965.            if (ExportMesh(meshInfo, fbxNode))
    966.            {
    967.                fbxMesh = fbxNode.GetMesh();
    968.            }
    969.            if (fbxMesh == null)
    970.            {
    971.                Debug.LogError("Could not find mesh");
    972.                return false;
    973.            }
    974.  
    975.            Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap;
    976.            // export skeleton
    977.            if (ExportSkeleton (unitySkin, fbxScene, out skinnedMeshToBonesMap)) {
    978.                // bind mesh to skeleton
    979.                ExportSkin (unitySkin, meshInfo, fbxScene, fbxMesh, fbxNode);
    980.  
    981.                // add bind pose
    982.                ExportBindPose (unitySkin, fbxNode, fbxScene, skinnedMeshToBonesMap);
    983.  
    984.                // now that the skin and bindpose are set, make sure that each of the bones
    985.                // is set to its original position
    986.                var bones = unitySkin.bones;
    987.                foreach (var bone in bones)
    988.                {
    989.                    // ignore null bones
    990.                    if (bone != null)
    991.                    {
    992.                        var fbxBone = MapUnityObjectToFbxNode[bone.gameObject];
    993.                        ExportTransform(bone, fbxBone, newCenter: Vector3.zero, TransformExportType.Local);
    994.  
    995.                        // Cancel out the pre-rotation from the exported rotation
    996.  
    997.                        // Get prerotation
    998.                        var fbxPreRotationEuler = fbxBone.GetPreRotation(FbxNode.EPivotSet.eSourcePivot);
    999.                        // Convert the prerotation to a Quaternion
    1000.                        var fbxPreRotationQuaternion = EulerToQuaternionXYZ(fbxPreRotationEuler);
    1001.                        // Inverse of the prerotation
    1002.                        fbxPreRotationQuaternion.Inverse();
    1003.  
    1004.                        // Multiply LclRotation by pre-rotation inverse to get the LclRotation without pre-rotation applied
    1005.                        var finalLclRotationQuat = fbxPreRotationQuaternion * EulerToQuaternionZXY(bone.localEulerAngles);
    1006.  
    1007.                        // Convert to Euler with Unity axis system and update LclRotation
    1008.                        var finalUnityQuat = new Quaternion((float)finalLclRotationQuat.X, (float)finalLclRotationQuat.Y, (float)finalLclRotationQuat.Z, (float)finalLclRotationQuat.W);
    1009.                        fbxBone.LclRotation.Set(ToFbxDouble3(finalUnityQuat.eulerAngles));
    1010.                    }
    1011.                    else
    1012.                    {
    1013.                        Debug.Log("Warning: One or more bones are null. Skeleton may not export correctly.");
    1014.                    }
    1015.                }
    1016.            }
    1017.  
    1018.            return true;
    1019.        }
    1020.  
    1021.        /// <summary>
    1022.        /// Gets the bind pose for the Unity bone.
    1023.        /// </summary>
    1024.        /// <returns>The bind pose.</returns>
    1025.        /// <param name="unityBone">Unity bone.</param>
    1026.        /// <param name="bindPoses">Bind poses.</param>
    1027.        /// <param name="boneInfo">Contains information about bones and skinned mesh.</param>
    1028.        private Matrix4x4 GetBindPose(
    1029.            Transform unityBone, Matrix4x4[] bindPoses,
    1030.            ref SkinnedMeshBoneInfo boneInfo
    1031.        )
    1032.        {
    1033.            var boneDict = boneInfo.boneDict;
    1034.            var skinnedMesh = boneInfo.skinnedMesh;
    1035.            var boneToBindPose = boneInfo.boneToBindPose;
    1036.  
    1037.            // If we have already retrieved the bindpose for this bone before
    1038.            // it will be present in the boneToBindPose dictionary,
    1039.            // simply return this bindpose.
    1040.            Matrix4x4 bindPose;
    1041.            if(boneToBindPose.TryGetValue(unityBone, out bindPose))
    1042.            {
    1043.                return bindPose;
    1044.            }
    1045.  
    1046.            // Check if unityBone is a bone registered in the bone list of the skinned mesh.
    1047.            // If it is, then simply retrieve the bindpose from the boneDict (maps bone to index in bone/bindpose list).
    1048.            // Make sure to update the boneToBindPose list in case the bindpose for this bone needs to be retrieved again.
    1049.            int index;
    1050.            if (boneDict.TryGetValue(unityBone, out index))
    1051.            {
    1052.                bindPose = bindPoses[index];
    1053.                boneToBindPose.Add(unityBone, bindPose);
    1054.                return bindPose;
    1055.            }
    1056.  
    1057.            // unityBone is not registered as a bone in the skinned mesh, therefore there is no bindpose
    1058.            // associated with it, we need to calculate one.
    1059.  
    1060.            // If this is the rootbone of the mesh or an object without a parent, use the global matrix relative to the skinned mesh
    1061.            // as the bindpose.
    1062.            if(unityBone == skinnedMesh.rootBone || unityBone.parent == null)
    1063.            {
    1064.                // there is no bone above this object with a bindpose, calculate bindpose relative to skinned mesh
    1065.                bindPose = (unityBone.worldToLocalMatrix * skinnedMesh.transform.localToWorldMatrix);
    1066.                boneToBindPose.Add(unityBone, bindPose);
    1067.                return bindPose;
    1068.            }
    1069.  
    1070.            // If this object has a parent that could be a bone, then it is not enough to use the worldToLocalMatrix,
    1071.            // as this will give an incorrect global transform if the parents are not already in the bindpose in the scene.
    1072.            // Instead calculate what the bindpose would be based on the bindpose of the parent object.
    1073.  
    1074.            // get the bindpose of the parent
    1075.            var parentBindPose = GetBindPose(unityBone.parent, bindPoses, ref boneInfo);
    1076.            // Get the local transformation matrix of the bone, then transform it into
    1077.            // the global transformation matrix with the parent in the bind pose.
    1078.            // Formula to get the global transformation matrix:
    1079.            //   (parentBindPose.inverse * boneLocalTRSMatrix)
    1080.            // The bindpose is then the inverse of this matrix:
    1081.            //   (parentBindPose.inverse * boneLocalTRSMatrix).inverse
    1082.            // This can be simplified with (AB)^{-1} = B^{-1}A^{-1} rule as follows:
    1083.            //   (parentBindPose.inverse * boneLocalTRSMatrix).inverse
    1084.            // = boneLocalTRSMatrix.inverse * parentBindPose.inverse.inverse
    1085.            // = boneLocalTRSMatrix.inverse * parentBindPose
    1086.            var boneLocalTRSMatrix = Matrix4x4.TRS(unityBone.localPosition, unityBone.localRotation, unityBone.localScale);
    1087.            bindPose = boneLocalTRSMatrix.inverse * parentBindPose;
    1088.            boneToBindPose.Add(unityBone, bindPose);
    1089.            return bindPose;
    1090.        }
    1091.  
    1092.        /// <summary>
    1093.        /// Export bones of skinned mesh, if this is a skinned mesh with
    1094.        /// bones and bind poses.
    1095.        /// </summary>
    1096.        [SecurityPermission(SecurityAction.LinkDemand)]
    1097.        private bool ExportSkeleton (SkinnedMeshRenderer skinnedMesh, FbxScene fbxScene, out Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap)
    1098.        {
    1099.            skinnedMeshToBonesMap = new Dictionary<SkinnedMeshRenderer, Transform[]> ();
    1100.  
    1101.            if (!skinnedMesh) {
    1102.                return false;
    1103.            }
    1104.            var bones = skinnedMesh.bones;
    1105.            if (bones == null || bones.Length == 0) {
    1106.                return false;
    1107.            }
    1108.            var mesh = skinnedMesh.sharedMesh;
    1109.            if (!mesh) {
    1110.                return false;
    1111.            }
    1112.  
    1113.            var bindPoses = mesh.bindposes;
    1114.            if (bindPoses == null || bindPoses.Length != bones.Length) {
    1115.                return false;
    1116.            }
    1117.  
    1118.            // Two steps:
    1119.            // 0. Set up the map from bone to index.
    1120.            // 1. Set the transforms.
    1121.  
    1122.            // Step 0: map transform to index so we can look up index by bone.
    1123.            Dictionary<Transform, int> index = new Dictionary<Transform, int>();
    1124.            for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++) {
    1125.                Transform unityBoneTransform = bones [boneIndex];
    1126.  
    1127.                // ignore null bones
    1128.                if (unityBoneTransform != null)
    1129.                {
    1130.                    index[unityBoneTransform] = boneIndex;
    1131.                }
    1132.            }
    1133.  
    1134.            skinnedMeshToBonesMap.Add (skinnedMesh, bones);
    1135.  
    1136.            // Step 1: Set transforms
    1137.            var boneInfo = new SkinnedMeshBoneInfo (skinnedMesh, index);
    1138.            foreach (var bone in bones) {
    1139.                // ignore null bones
    1140.                if (bone != null)
    1141.                {
    1142.                    var fbxBone = MapUnityObjectToFbxNode[bone.gameObject];
    1143.                    ExportBoneTransform(fbxBone, fbxScene, bone, boneInfo);
    1144.                }
    1145.            }
    1146.            return true;
    1147.        }
    1148.  
    1149.        /// <summary>
    1150.        /// Export binding of mesh to skeleton
    1151.        /// </summary>
    1152.        private bool ExportSkin (SkinnedMeshRenderer skinnedMesh,
    1153.                                    MeshInfo meshInfo, FbxScene fbxScene, FbxMesh fbxMesh,
    1154.                                    FbxNode fbxRootNode)
    1155.        {
    1156.            FbxSkin fbxSkin = FbxSkin.Create (fbxScene, (skinnedMesh.name + SkinPrefix));
    1157.  
    1158.            FbxAMatrix fbxMeshMatrix = fbxRootNode.EvaluateGlobalTransform ();
    1159.  
    1160.            // keep track of the bone index -> fbx cluster mapping, so that we can add the bone weights afterwards
    1161.            Dictionary<int, FbxCluster> boneCluster = new Dictionary<int, FbxCluster> ();
    1162.  
    1163.            for(int i = 0; i < skinnedMesh.bones.Length; i++) {
    1164.                // ignore null bones
    1165.                if (skinnedMesh.bones != null)
    1166.                {
    1167.                    FbxNode fbxBoneNode = MapUnityObjectToFbxNode[skinnedMesh.bones.gameObject];
    1168.  
    1169.                    // Create the deforming cluster
    1170.                    FbxCluster fbxCluster = FbxCluster.Create(fbxScene, "BoneWeightCluster");
    1171.  
    1172.                    fbxCluster.SetLink(fbxBoneNode);
    1173.                    fbxCluster.SetLinkMode(FbxCluster.ELinkMode.eNormalize);
    1174.  
    1175.                    boneCluster.Add(i, fbxCluster);
    1176.  
    1177.                    // set the Transform and TransformLink matrix
    1178.                    fbxCluster.SetTransformMatrix(fbxMeshMatrix);
    1179.  
    1180.                    FbxAMatrix fbxLinkMatrix = fbxBoneNode.EvaluateGlobalTransform();
    1181.                    fbxCluster.SetTransformLinkMatrix(fbxLinkMatrix);
    1182.  
    1183.                    // add the cluster to the skin
    1184.                    fbxSkin.AddCluster(fbxCluster);
    1185.                }
    1186.            }
    1187.  
    1188.            // set the vertex weights for each bone
    1189.            SetVertexWeights(meshInfo, boneCluster);
    1190.  
    1191.            // Add the skin to the mesh after the clusters have been added
    1192.            fbxMesh.AddDeformer (fbxSkin);
    1193.  
    1194.            return true;
    1195.        }
    1196.  
    1197.        /// <summary>
    1198.        /// set vertex weights in cluster
    1199.        /// </summary>
    1200.        private void SetVertexWeights (MeshInfo meshInfo, Dictionary<int, FbxCluster> boneIndexToCluster)
    1201.        {
    1202.            var mesh = meshInfo.mesh;
    1203.            // Get the number of bone weights per vertex
    1204.            var bonesPerVertex = mesh.GetBonesPerVertex();
    1205.            if (bonesPerVertex.Length == 0)
    1206.            {
    1207.                // no bone weights to set
    1208.                return;
    1209.            }
    1210.  
    1211.            HashSet<int> visitedVertices = new HashSet<int>();
    1212.  
    1213.            // Get all the bone weights, in vertex index order
    1214.            // Note: this contains all the bone weights for all vertices.
    1215.            //       Use number of bonesPerVertex to determine where weights end
    1216.            //       for one vertex and begin for another.
    1217.            var boneWeights1 = mesh.GetAllBoneWeights();
    1218.  
    1219.            // Keep track of where we are in the array of BoneWeights, as we iterate over the vertices
    1220.            var boneWeightIndex = 0;
    1221.  
    1222.            for (var vertIndex = 0; vertIndex < meshInfo.VertexCount; vertIndex++)
    1223.            {
    1224.                // Get the index into the list of vertices without duplicates
    1225.                var actualIndex = ControlPointToIndex[meshInfo.Vertices[vertIndex]];
    1226.  
    1227.                var numberOfBonesForThisVertex = bonesPerVertex[vertIndex];
    1228.  
    1229.                if (visitedVertices.Contains(actualIndex))
    1230.                {
    1231.                    // skip duplicate vertex
    1232.                    boneWeightIndex += numberOfBonesForThisVertex;
    1233.                    continue;
    1234.                }
    1235.                visitedVertices.Add(actualIndex);
    1236.  
    1237.                // For each vertex, iterate over its BoneWeights
    1238.                for (var i = 0; i < numberOfBonesForThisVertex; i++)
    1239.                {
    1240.                    var currentBoneWeight = boneWeights1[boneWeightIndex];
    1241.  
    1242.                    // bone index is index into skinnedmesh.bones[]
    1243.                    var boneIndex = currentBoneWeight.boneIndex;
    1244.                    // how much influence does this bone have on vertex at vertIndex
    1245.                    var weight = currentBoneWeight.weight;
    1246.  
    1247.                    if (weight <= 0)
    1248.                    {
    1249.                        continue;
    1250.                    }
    1251.  
    1252.                    // get the right cluster
    1253.                    FbxCluster boneCluster;
    1254.                    if (!boneIndexToCluster.TryGetValue(boneIndex, out boneCluster))
    1255.                    {
    1256.                        continue;
    1257.                    }
    1258.                    // add vertex and weighting on vertex to this bone's cluster
    1259.                    boneCluster.AddControlPointIndex(actualIndex, weight);
    1260.  
    1261.                    boneWeightIndex++;
    1262.                }
    1263.            }
    1264.        }
    1265.  
    1266.        /// <summary>
    1267.        /// Export bind pose of mesh to skeleton
    1268.        /// </summary>
    1269.        private bool ExportBindPose (SkinnedMeshRenderer skinnedMesh, FbxNode fbxMeshNode,
    1270.                                FbxScene fbxScene, Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap)
    1271.        {
    1272.            if (fbxMeshNode == null || skinnedMeshToBonesMap == null || fbxScene == null)
    1273.            {
    1274.                return false;
    1275.            }
    1276.  
    1277.            FbxPose fbxPose = FbxPose.Create(fbxScene, fbxMeshNode.GetName());
    1278.  
    1279.            // set as bind pose
    1280.            fbxPose.SetIsBindPose (true);
    1281.  
    1282.            // assume each bone node has one weighted vertex cluster
    1283.            Transform[] bones;
    1284.            if (!skinnedMeshToBonesMap.TryGetValue (skinnedMesh, out bones)) {
    1285.                return false;
    1286.            }
    1287.            for (int i = 0; i < bones.Length; i++) {
    1288.                // ignore null bones
    1289.                if (bones != null)
    1290.                {
    1291.                    FbxNode fbxBoneNode = MapUnityObjectToFbxNode[bones.gameObject];
    1292.  
    1293.                    // EvaluateGlobalTransform returns an FbxAMatrix (affine matrix)
    1294.                    // which has to be converted to an FbxMatrix so that it can be passed to fbxPose.Add().
    1295.                    // The hierarchy for FbxMatrix and FbxAMatrix is as follows:
    1296.                    //
    1297.                    //      FbxDouble4x4
    1298.                    //      /           \
    1299.                    // FbxMatrix     FbxAMatrix
    1300.                    //
    1301.                    // Therefore we can't convert directly from FbxAMatrix to FbxMatrix,
    1302.                    // however FbxMatrix has a constructor that takes an FbxAMatrix.
    1303.                    FbxMatrix fbxBindMatrix = new FbxMatrix(fbxBoneNode.EvaluateGlobalTransform());
    1304.  
    1305.                    fbxPose.Add(fbxBoneNode, fbxBindMatrix);
    1306.                }
    1307.            }
    1308.  
    1309.            fbxPose.Add (fbxMeshNode, new FbxMatrix (fbxMeshNode.EvaluateGlobalTransform ()));
    1310.  
    1311.            // add the pose to the scene
    1312.            fbxScene.AddPose (fbxPose);
    1313.  
    1314.            return true;
    1315.        }
    1316.      
    1317.        internal static FbxDouble3 ToFbxDouble3(Vector3 v)
    1318.        {
    1319.            return new FbxDouble3(v.x, v.y, v.z);
    1320.        }
    1321.  
    1322.        internal static FbxDouble3 ToFbxDouble3(FbxVector4 v)
    1323.        {
    1324.            return new FbxDouble3(v.X, v.Y, v.Z);
    1325.        }
    1326.  
    1327.        /// <summary>
    1328.        /// Euler (roll/pitch/yaw (ZXY rotation order) to quaternion.
    1329.        /// </summary>
    1330.        /// <returns>a quaternion.</returns>
    1331.        /// <param name="euler">ZXY Euler.</param>
    1332.        internal static FbxQuaternion EulerToQuaternionZXY(Vector3 euler)
    1333.        {
    1334.            var unityQuat = Quaternion.Euler(euler);
    1335.            return new FbxQuaternion(unityQuat.x, unityQuat.y, unityQuat.z, unityQuat.w);
    1336.        }
    1337.  
    1338.        /// <summary>
    1339.        /// Euler X/Y/Z rotation order to quaternion.
    1340.        /// </summary>
    1341.        /// <param name="euler">XYZ Euler.</param>
    1342.        /// <returns>a quaternion</returns>
    1343.        internal static FbxQuaternion EulerToQuaternionXYZ(FbxVector4 euler)
    1344.        {
    1345.            FbxAMatrix m = new FbxAMatrix ();
    1346.            m.SetR (euler);
    1347.            return m.GetQ ();
    1348.        }
    1349.  
    1350.        // get a fbxNode's global default position.
    1351.        internal bool ExportTransform (UnityEngine.Transform unityTransform, FbxNode fbxNode, Vector3 newCenter, TransformExportType exportType)
    1352.        {
    1353.            UnityEngine.Vector3 unityTranslate;
    1354.            FbxDouble3 fbxRotate;
    1355.            UnityEngine.Vector3 unityScale;
    1356.  
    1357.            switch (exportType) {
    1358.                case TransformExportType.Reset:
    1359.                    unityTranslate = Vector3.zero;
    1360.                    fbxRotate = new FbxDouble3(0);
    1361.                    unityScale = Vector3.one;
    1362.                    break;
    1363.                case TransformExportType.Global:
    1364.                    unityTranslate = GetRecenteredTranslation(unityTransform, newCenter);
    1365.                    fbxRotate = ToFbxDouble3(unityTransform.eulerAngles);
    1366.                    unityScale = unityTransform.lossyScale;
    1367.                    break;
    1368.                default: /*case TransformExportType.Local*/
    1369.                    unityTranslate = unityTransform.localPosition;
    1370.                    fbxRotate = ToFbxDouble3(unityTransform.localEulerAngles);
    1371.                    unityScale = unityTransform.localScale;
    1372.                    break;
    1373.            }
    1374.  
    1375.            // Transfer transform data from Unity to Fbx
    1376.            var fbxTranslate = ConvertToFbxVector4(unityTranslate, UnitScaleFactor);
    1377.            var fbxScale = new FbxDouble3 (unityScale.x, unityScale.y, unityScale.z);
    1378.  
    1379.            // Zero scale causes issues in 3ds Max (child of object with zero scale will end up with a much larger scale, e.g. >9000).
    1380.            // When exporting 0 scale from Maya, the FBX contains 1e-12 instead of 0,
    1381.            // which doesn't cause issues in Max. Do the same here.
    1382.            if(fbxScale.X == 0)
    1383.            {
    1384.                fbxScale.X = 1e-12;
    1385.            }
    1386.            if(fbxScale.Y == 0)
    1387.            {
    1388.                fbxScale.Y = 1e-12;
    1389.            }
    1390.            if(fbxScale.Z == 0)
    1391.            {
    1392.                fbxScale.Z = 1e-12;
    1393.            }
    1394.  
    1395.            // set the local position of fbxNode
    1396.            fbxNode.LclTranslation.Set (new FbxDouble3(fbxTranslate.X, fbxTranslate.Y, fbxTranslate.Z));
    1397.            fbxNode.LclRotation.Set (fbxRotate);
    1398.            fbxNode.LclScaling.Set (fbxScale);
    1399.  
    1400.            return true;
    1401.        }
    1402.  
    1403.        /// <summary>
    1404.        /// if this game object is a model prefab or the model has already been exported, then export with shared components
    1405.        /// </summary>
    1406.        [SecurityPermission(SecurityAction.LinkDemand)]
    1407.        private bool ExportInstance(GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
    1408.        {
    1409.            if (!unityGo || fbxNode == null)
    1410.            {
    1411.                return false;
    1412.            }
    1413.  
    1414.            // where the fbx mesh is stored on a successful export
    1415.            FbxMesh fbxMesh = null;
    1416.            // store the shared mesh of the game object
    1417.            Mesh unityGoMesh = null;
    1418.          
    1419.            // get the mesh of the game object
    1420.            if (unityGo.TryGetComponent<MeshFilter>(out MeshFilter meshFilter))
    1421.            {
    1422.                unityGoMesh = meshFilter.sharedMesh;
    1423.            }
    1424.  
    1425.            if (!unityGoMesh)
    1426.            {
    1427.                return false;
    1428.            }
    1429.            // export mesh as an instance if it is a duplicate mesh or a prefab
    1430.            else if (SharedMeshes.TryGetValue(unityGoMesh, out FbxNode node))
    1431.            {
    1432.                if (Verbose)
    1433.                {
    1434.                    Debug.Log (string.Format ("exporting instance {0}", unityGo.name));
    1435.                }
    1436.              
    1437.                fbxMesh = node.GetMesh();
    1438.            }
    1439.            // unique mesh, so save it to find future duplicates
    1440.            else
    1441.            {
    1442.                SharedMeshes.Add(unityGoMesh, fbxNode);
    1443.                return false;
    1444.            }
    1445.  
    1446.            // mesh doesn't exist or wasn't exported successfully
    1447.            if (fbxMesh == null)
    1448.            {
    1449.                return false;
    1450.            }
    1451.  
    1452.            // We don't export the mesh because we already have it from the parent, but we still need to assign the material
    1453.            var renderer = unityGo.GetComponent<Renderer>();
    1454.            var materials = renderer ? renderer.sharedMaterials : null;
    1455.  
    1456.            Autodesk.Fbx.FbxSurfaceMaterial newMaterial = null;
    1457.            if (materials != null)
    1458.            {
    1459.                foreach (var mat in materials) {
    1460.                    if (mat != null && MaterialMap.TryGetValue(mat.GetInstanceID(), out newMaterial))
    1461.                    {
    1462.                        fbxNode.AddMaterial(newMaterial);
    1463.                    }
    1464.                    else
    1465.                    {
    1466.                        // create new material
    1467.                        ExportMaterial(mat, fbxScene, fbxNode);
    1468.                    }
    1469.                }
    1470.            }
    1471.  
    1472.            // set the fbxNode containing the mesh
    1473.            fbxNode.SetNodeAttribute (fbxMesh);
    1474.            fbxNode.SetShadingMode (FbxNode.EShadingMode.eWireFrame);
    1475.  
    1476.            return true;
    1477.        }
    1478.  
    1479.        /// <summary>
    1480.        /// Exports camera component
    1481.        /// </summary>
    1482.        private bool ExportCamera (GameObject unityGO, FbxScene fbxScene, FbxNode fbxNode)
    1483.        {
    1484.            if (!unityGO || fbxScene == null || fbxNode == null)
    1485.            {
    1486.                return false;
    1487.            }
    1488.  
    1489.            Camera unityCamera = unityGO.GetComponent<Camera> ();
    1490.            if (unityCamera == null) {
    1491.                return false;
    1492.            }
    1493.  
    1494.            FbxCamera fbxCamera = FbxCamera.Create (fbxScene.GetFbxManager(), unityCamera.name);
    1495.            if (fbxCamera == null) {
    1496.                return false;
    1497.            }
    1498.  
    1499.            CameraVisitor.ConfigureCamera(unityCamera, fbxCamera);
    1500.              
    1501.            fbxNode.SetNodeAttribute (fbxCamera);
    1502.  
    1503.            // set +90 post rotation to counteract for FBX camera's facing +X direction by default
    1504.            fbxNode.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, new FbxVector4(0,90,0));
    1505.            // have to set rotation active to true in order for post rotation to be applied
    1506.            fbxNode.SetRotationActive (true);
    1507.  
    1508.            // make the last camera exported the default camera
    1509.            DefaultCamera = fbxNode.GetName ();
    1510.  
    1511.            return true;
    1512.        }
    1513.  
    1514.        /// <summary>
    1515.        /// Exports light component.
    1516.        /// Supported types: point, spot and directional
    1517.        /// Cookie => Gobo
    1518.        /// </summary>
    1519.        private bool ExportLight (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
    1520.        {
    1521.            if(!unityGo || fbxScene == null || fbxNode == null)
    1522.            {
    1523.                return false;
    1524.            }
    1525.  
    1526.            Light unityLight = unityGo.GetComponent<Light> ();
    1527.  
    1528.            if (unityLight == null)
    1529.                return false;
    1530.  
    1531.            FbxLight.EType fbxLightType;
    1532.  
    1533.            // Is light type supported?
    1534.            if (!MapLightType.TryGetValue (unityLight.type, out fbxLightType))
    1535.                return false;
    1536.              
    1537.            FbxLight fbxLight = FbxLight.Create (fbxScene.GetFbxManager (), unityLight.name);
    1538.  
    1539.            // Set the type of the light.  
    1540.            fbxLight.LightType.Set(fbxLightType);
    1541.  
    1542.            switch (unityLight.type)
    1543.            {
    1544.            case LightType.Directional : {
    1545.                    break;
    1546.                }
    1547.            case LightType.Spot : {
    1548.                    // Set the angle of the light's spotlight cone in degrees.
    1549.                    fbxLight.InnerAngle.Set(unityLight.spotAngle);
    1550.                    fbxLight.OuterAngle.Set(unityLight.spotAngle);
    1551.                    break;
    1552.                }
    1553.            case LightType.Point : {
    1554.                    break;
    1555.                }
    1556.            case LightType.Area : {
    1557.                    // TODO: areaSize: The size of the area light by scaling the node XY
    1558.                    break;
    1559.                }
    1560.            }
    1561.            // The color of the light.
    1562.            var unityLightColor = unityLight.color;
    1563.            fbxLight.Color.Set (new FbxDouble3(unityLightColor.r, unityLightColor.g, unityLightColor.b));
    1564.  
    1565.            // Set the Intensity of a light is multiplied with the Light color.
    1566.            fbxLight.Intensity.Set (unityLight.intensity * UnitScaleFactor /*compensate for Maya scaling by system units*/ );
    1567.  
    1568.            // Set the range of the light.
    1569.            // applies-to: Point & Spot
    1570.            // => FarAttenuationStart, FarAttenuationEnd
    1571.            fbxLight.FarAttenuationStart.Set (0.01f /* none zero start */);
    1572.            fbxLight.FarAttenuationEnd.Set(unityLight.range*UnitScaleFactor);
    1573.  
    1574.            // shadows           Set how this light casts shadows
    1575.            // applies-to: Point & Spot
    1576.            bool unityLightCastShadows = unityLight.shadows != LightShadows.None;
    1577.            fbxLight.CastShadows.Set (unityLightCastShadows);
    1578.  
    1579.            fbxNode.SetNodeAttribute (fbxLight);
    1580.  
    1581.            // set +90 post rotation on x to counteract for FBX light's facing -Y direction by default
    1582.            fbxNode.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, new FbxVector4(90,0,0));
    1583.            // have to set rotation active to true in order for post rotation to be applied
    1584.            fbxNode.SetRotationActive (true);
    1585.  
    1586.            return true;
    1587.        }
    1588.  
    1589.        private bool ExportCommonConstraintProperties<TUnityConstraint,TFbxConstraint>(TUnityConstraint uniConstraint, TFbxConstraint fbxConstraint, FbxNode fbxNode)
    1590.            where TUnityConstraint : IConstraint where TFbxConstraint : FbxConstraint
    1591.        {
    1592.            fbxConstraint.Active.Set(uniConstraint.constraintActive);
    1593.            fbxConstraint.Lock.Set(uniConstraint.locked);
    1594.            fbxConstraint.Weight.Set(uniConstraint.weight * UnitScaleFactor);
    1595.  
    1596.            AddFbxNodeToConstraintsMapping(fbxNode, fbxConstraint, typeof(TUnityConstraint));
    1597.            return true;
    1598.        }
    1599.  
    1600.        private struct ExpConstraintSource
    1601.        {
    1602.            private FbxNode m_node;
    1603.            public FbxNode node
    1604.            {
    1605.                get { return m_node; }
    1606.                set { m_node = value; }
    1607.            }
    1608.  
    1609.            private float m_weight;
    1610.            public float weight
    1611.            {
    1612.                get { return m_weight; }
    1613.                set { m_weight = value; }
    1614.            }
    1615.  
    1616.            public ExpConstraintSource(FbxNode node, float weight)
    1617.            {
    1618.                this.m_node = node;
    1619.                this.m_weight = weight;
    1620.            }
    1621.        }
    1622.  
    1623.        private List<ExpConstraintSource> GetConstraintSources(IConstraint unityConstraint)
    1624.        {
    1625.            if(unityConstraint == null)
    1626.            {
    1627.                return null;
    1628.            }
    1629.  
    1630.            var fbxSources = new List<ExpConstraintSource>();
    1631.            var sources = new List<ConstraintSource>();
    1632.            unityConstraint.GetSources(sources);
    1633.            foreach (var source in sources)
    1634.            {
    1635.                // ignore any sources that are not getting exported
    1636.                FbxNode sourceNode;
    1637.                if (!MapUnityObjectToFbxNode.TryGetValue(source.sourceTransform.gameObject, out sourceNode))
    1638.                {
    1639.                    continue;
    1640.                }
    1641.                fbxSources.Add(new ExpConstraintSource(sourceNode, source.weight * UnitScaleFactor));
    1642.            }
    1643.            return fbxSources;
    1644.        }
    1645.  
    1646.        private void AddFbxNodeToConstraintsMapping<T>(FbxNode fbxNode, T fbxConstraint, System.Type uniConstraintType) where T : FbxConstraint
    1647.        {
    1648.            Dictionary<FbxConstraint, System.Type> constraintMapping;
    1649.            if (!MapConstrainedObjectToConstraints.TryGetValue(fbxNode, out constraintMapping))
    1650.            {
    1651.                constraintMapping = new Dictionary<FbxConstraint, System.Type>();
    1652.                MapConstrainedObjectToConstraints.Add(fbxNode, constraintMapping);
    1653.            }
    1654.            constraintMapping.Add(fbxConstraint, uniConstraintType);
    1655.        }
    1656.  
    1657.        private bool ExportPositionConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
    1658.        {
    1659.            if(fbxNode == null)
    1660.            {
    1661.                return false;
    1662.            }
    1663.  
    1664.            var uniPosConstraint = uniConstraint as PositionConstraint;
    1665.            Debug.Assert (uniPosConstraint != null);
    1666.  
    1667.            FbxConstraintPosition fbxPosConstraint = FbxConstraintPosition.Create(fbxScene, fbxNode.GetName() + "_positionConstraint");
    1668.            fbxPosConstraint.SetConstrainedObject(fbxNode);
    1669.            var uniSources = GetConstraintSources(uniPosConstraint);
    1670.            uniSources.ForEach(uniSource => fbxPosConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
    1671.            ExportCommonConstraintProperties(uniPosConstraint, fbxPosConstraint, fbxNode);
    1672.  
    1673.            var uniAffectedAxes = uniPosConstraint.translationAxis;
    1674.            fbxPosConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
    1675.            fbxPosConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
    1676.            fbxPosConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
    1677.  
    1678.            var fbxTranslationOffset = ConvertToFbxVector4(uniPosConstraint.translationOffset, UnitScaleFactor);
    1679.            fbxPosConstraint.Translation.Set(ToFbxDouble3(fbxTranslationOffset));
    1680.  
    1681.            // rest position is the position of the fbx node
    1682.            var fbxRestTranslation = ConvertToFbxVector4(uniPosConstraint.translationAtRest, UnitScaleFactor);
    1683.            // set the local position of fbxNode
    1684.            fbxNode.LclTranslation.Set(ToFbxDouble3(fbxRestTranslation));
    1685.            return true;
    1686.        }
    1687.  
    1688.        private bool ExportRotationConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
    1689.        {
    1690.            if(fbxNode == null)
    1691.            {
    1692.                return false;
    1693.            }
    1694.  
    1695.            var uniRotConstraint = uniConstraint as RotationConstraint;
    1696.            Debug.Assert(uniRotConstraint != null);
    1697.  
    1698.            FbxConstraintRotation fbxRotConstraint = FbxConstraintRotation.Create(fbxScene, fbxNode.GetName() + "_rotationConstraint");
    1699.            fbxRotConstraint.SetConstrainedObject(fbxNode);
    1700.            var uniSources = GetConstraintSources(uniRotConstraint);
    1701.            uniSources.ForEach(uniSource => fbxRotConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
    1702.            ExportCommonConstraintProperties(uniRotConstraint, fbxRotConstraint, fbxNode);
    1703.  
    1704.            var uniAffectedAxes = uniRotConstraint.rotationAxis;
    1705.            fbxRotConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
    1706.            fbxRotConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
    1707.            fbxRotConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
    1708.  
    1709.            // Not converting rotation offset to XYZ euler as it gives the incorrect result in both Maya and Unity.
    1710.            var uniRotationOffset = uniRotConstraint.rotationOffset;
    1711.            var fbxRotationOffset = ToFbxDouble3(uniRotationOffset);
    1712.  
    1713.            fbxRotConstraint.Rotation.Set(fbxRotationOffset);
    1714.  
    1715.            // rest rotation is the rotation of the fbx node
    1716.            var fbxRestRotation = ToFbxDouble3(uniRotConstraint.rotationAtRest);
    1717.            // set the local rotation of fbxNode
    1718.            fbxNode.LclRotation.Set(fbxRestRotation);
    1719.            return true;
    1720.        }
    1721.  
    1722.        private bool ExportScaleConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
    1723.        {
    1724.            if(fbxNode == null)
    1725.            {
    1726.                return false;
    1727.            }
    1728.  
    1729.            var uniScaleConstraint = uniConstraint as ScaleConstraint;
    1730.            Debug.Assert(uniScaleConstraint != null);
    1731.  
    1732.            FbxConstraintScale fbxScaleConstraint = FbxConstraintScale.Create(fbxScene, fbxNode.GetName() + "_scaleConstraint");
    1733.            fbxScaleConstraint.SetConstrainedObject(fbxNode);
    1734.            var uniSources = GetConstraintSources(uniScaleConstraint);
    1735.            uniSources.ForEach(uniSource => fbxScaleConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
    1736.            ExportCommonConstraintProperties(uniScaleConstraint, fbxScaleConstraint, fbxNode);
    1737.  
    1738.            var uniAffectedAxes = uniScaleConstraint.scalingAxis;
    1739.            fbxScaleConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
    1740.            fbxScaleConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
    1741.            fbxScaleConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
    1742.  
    1743.            var uniScaleOffset = uniScaleConstraint.scaleOffset;
    1744.            var fbxScalingOffset = ToFbxDouble3(uniScaleOffset);
    1745.            fbxScaleConstraint.Scaling.Set(fbxScalingOffset);
    1746.  
    1747.            // rest rotation is the rotation of the fbx node
    1748.            var uniRestScale = uniScaleConstraint.scaleAtRest;
    1749.            var fbxRestScale = ToFbxDouble3(uniRestScale);
    1750.            // set the local rotation of fbxNode
    1751.            fbxNode.LclScaling.Set(fbxRestScale);
    1752.            return true;
    1753.        }
    1754.  
    1755.        private bool ExportAimConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
    1756.        {
    1757.            if(fbxNode == null)
    1758.            {
    1759.                return false;
    1760.            }
    1761.  
    1762.            var uniAimConstraint = uniConstraint as AimConstraint;
    1763.            Debug.Assert(uniAimConstraint != null);
    1764.  
    1765.            FbxConstraintAim fbxAimConstraint = FbxConstraintAim.Create(fbxScene, fbxNode.GetName() + "_aimConstraint");
    1766.            fbxAimConstraint.SetConstrainedObject(fbxNode);
    1767.            var uniSources = GetConstraintSources(uniAimConstraint);
    1768.            uniSources.ForEach(uniSource => fbxAimConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
    1769.            ExportCommonConstraintProperties(uniAimConstraint, fbxAimConstraint, fbxNode);
    1770.  
    1771.            var uniAffectedAxes = uniAimConstraint.rotationAxis;
    1772.            fbxAimConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
    1773.            fbxAimConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
    1774.            fbxAimConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
    1775.  
    1776.            var uniRotationOffset = uniAimConstraint.rotationOffset;
    1777.            var fbxRotationOffset = ToFbxDouble3(uniRotationOffset);
    1778.            fbxAimConstraint.RotationOffset.Set(fbxRotationOffset);
    1779.  
    1780.            // rest rotation is the rotation of the fbx node
    1781.            var fbxRestRotation = ToFbxDouble3(uniAimConstraint.rotationAtRest);
    1782.            // set the local rotation of fbxNode
    1783.            fbxNode.LclRotation.Set(fbxRestRotation);
    1784.  
    1785.            FbxConstraintAim.EWorldUp fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtNone;
    1786.            switch (uniAimConstraint.worldUpType)
    1787.            {
    1788.                case AimConstraint.WorldUpType.None:
    1789.                    fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtNone;
    1790.                    break;
    1791.                case AimConstraint.WorldUpType.ObjectRotationUp:
    1792.                    fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtObjectRotationUp;
    1793.                    break;
    1794.                case AimConstraint.WorldUpType.ObjectUp:
    1795.                    fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtObjectUp;
    1796.                    break;
    1797.                case AimConstraint.WorldUpType.SceneUp:
    1798.                    fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtSceneUp;
    1799.                    break;
    1800.                case AimConstraint.WorldUpType.Vector:
    1801.                    fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtVector;
    1802.                    break;
    1803.                default:
    1804.                    throw new System.NotImplementedException();
    1805.            }
    1806.            fbxAimConstraint.WorldUpType.Set((int)fbxWorldUpType);
    1807.              
    1808.            var uniAimVector = ConvertToFbxVector4(uniAimConstraint.aimVector);
    1809.            fbxAimConstraint.AimVector.Set(ToFbxDouble3(uniAimVector));
    1810.            fbxAimConstraint.UpVector.Set(ToFbxDouble3(uniAimConstraint.upVector));
    1811.            fbxAimConstraint.WorldUpVector.Set(ToFbxDouble3(uniAimConstraint.worldUpVector));
    1812.  
    1813.            if (uniAimConstraint.worldUpObject && MapUnityObjectToFbxNode.ContainsKey(uniAimConstraint.worldUpObject.gameObject))
    1814.            {
    1815.                fbxAimConstraint.SetWorldUpObject(MapUnityObjectToFbxNode[uniAimConstraint.worldUpObject.gameObject]);
    1816.            }
    1817.            return true;
    1818.        }
    1819.  
    1820.        private bool ExportParentConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
    1821.        {
    1822.            if(fbxNode == null)
    1823.            {
    1824.                return false;
    1825.            }
    1826.  
    1827.            var uniParentConstraint = uniConstraint as ParentConstraint;
    1828.            Debug.Assert(uniParentConstraint != null);
    1829.  
    1830.            FbxConstraintParent fbxParentConstraint = FbxConstraintParent.Create(fbxScene, fbxNode.GetName() + "_parentConstraint");
    1831.            fbxParentConstraint.SetConstrainedObject(fbxNode);
    1832.            var uniSources = GetConstraintSources(uniParentConstraint);
    1833.            var uniTranslationOffsets = uniParentConstraint.translationOffsets;
    1834.            var uniRotationOffsets = uniParentConstraint.rotationOffsets;
    1835.            for(int i = 0; i < uniSources.Count; i++)
    1836.            {
    1837.                var uniSource = uniSources;
    1838.                var uniTranslationOffset = uniTranslationOffsets;
    1839.                var uniRotationOffset = uniRotationOffsets;
    1840.  
    1841.                fbxParentConstraint.AddConstraintSource(uniSource.node, uniSource.weight);
    1842.                  
    1843.                var fbxTranslationOffset = ConvertToFbxVector4(uniTranslationOffset, UnitScaleFactor);
    1844.                fbxParentConstraint.SetTranslationOffset(uniSource.node, fbxTranslationOffset);
    1845.                  
    1846.                var fbxRotationOffset = ConvertToFbxVector4(uniRotationOffset);
    1847.                fbxParentConstraint.SetRotationOffset(uniSource.node, fbxRotationOffset);
    1848.            }
    1849.            ExportCommonConstraintProperties(uniParentConstraint, fbxParentConstraint, fbxNode);
    1850.  
    1851.            var uniTranslationAxes = uniParentConstraint.translationAxis;
    1852.            fbxParentConstraint.AffectTranslationX.Set((uniTranslationAxes & Axis.X) == Axis.X);
    1853.            fbxParentConstraint.AffectTranslationY.Set((uniTranslationAxes & Axis.Y) == Axis.Y);
    1854.            fbxParentConstraint.AffectTranslationZ.Set((uniTranslationAxes & Axis.Z) == Axis.Z);
    1855.  
    1856.            var uniRotationAxes = uniParentConstraint.rotationAxis;
    1857.            fbxParentConstraint.AffectRotationX.Set((uniRotationAxes & Axis.X) == Axis.X);
    1858.            fbxParentConstraint.AffectRotationY.Set((uniRotationAxes & Axis.Y) == Axis.Y);
    1859.            fbxParentConstraint.AffectRotationZ.Set((uniRotationAxes & Axis.Z) == Axis.Z);
    1860.  
    1861.            // rest position is the position of the fbx node
    1862.            var fbxRestTranslation = ConvertToFbxVector4(uniParentConstraint.translationAtRest, UnitScaleFactor);
    1863.            // set the local position of fbxNode
    1864.            fbxNode.LclTranslation.Set(ToFbxDouble3(fbxRestTranslation));
    1865.  
    1866.            // rest rotation is the rotation of the fbx node
    1867.            var fbxRestRotation = ToFbxDouble3(uniParentConstraint.rotationAtRest);
    1868.            // set the local rotation of fbxNode
    1869.            fbxNode.LclRotation.Set(fbxRestRotation);
    1870.            return true;
    1871.        }
    1872.  
    1873.        private delegate bool ExportConstraintDelegate(IConstraint c , FbxScene fs, FbxNode fn);
    1874.  
    1875.        private bool ExportConstraints (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
    1876.        {
    1877.            if (!unityGo)
    1878.            {
    1879.                return false;
    1880.            }
    1881.  
    1882.            var mapConstraintTypeToExportFunction = new Dictionary<System.Type, ExportConstraintDelegate>()
    1883.            {
    1884.                { typeof(PositionConstraint), ExportPositionConstraint },
    1885.                { typeof(RotationConstraint), ExportRotationConstraint },
    1886.                { typeof(ScaleConstraint), ExportScaleConstraint },
    1887.                { typeof(AimConstraint), ExportAimConstraint },
    1888.                { typeof(ParentConstraint), ExportParentConstraint }
    1889.            };
    1890.  
    1891.            // check if GameObject has one of the 5 supported constraints: aim, parent, position, rotation, scale
    1892.            var uniConstraints = unityGo.GetComponents<IConstraint>();
    1893.  
    1894.            foreach(var uniConstraint in uniConstraints)
    1895.            {
    1896.                var uniConstraintType = uniConstraint.GetType();
    1897.                ExportConstraintDelegate constraintDelegate;
    1898.                if (!mapConstraintTypeToExportFunction.TryGetValue(uniConstraintType, out constraintDelegate))
    1899.                {
    1900.                    Debug.LogWarningFormat("FbxExporter: Missing function to export constraint of type {0}", uniConstraintType.Name);
    1901.                    continue;
    1902.                }
    1903.                constraintDelegate(uniConstraint, fbxScene, fbxNode);
    1904.            }
    1905.  
    1906.            return true;
    1907.        }
    1908.  
    1909.        /// <summary>
    1910.        /// Return set of sample times to cover all keys on animation curves
    1911.        /// </summary>
    1912.        [SecurityPermission(SecurityAction.LinkDemand)]
    1913.        internal static HashSet<float> GetSampleTimes(AnimationCurve[] animCurves, double sampleRate)
    1914.        {
    1915.            var keyTimes = new HashSet<float>();
    1916.            double fs = 1.0/sampleRate;
    1917.  
    1918.            double firstTime = double.MaxValue, lastTime = double.MinValue;
    1919.  
    1920.            foreach (var ac in animCurves)
    1921.            {
    1922.                if (ac==null || ac.length<=0) continue;
    1923.  
    1924.                firstTime = System.Math.Min(firstTime, ac[0].time);
    1925.                lastTime = System.Math.Max(lastTime, ac[ac.length-1].time);
    1926.            }
    1927.  
    1928.            // if these values didn't get set there were no valid anim curves,
    1929.            // so don't return any keys
    1930.            if(firstTime == double.MaxValue || lastTime == double.MinValue)
    1931.            {
    1932.                return keyTimes;
    1933.            }
    1934.  
    1935.            int firstframe = (int)System.Math.Floor(firstTime * sampleRate);
    1936.            int lastframe = (int)System.Math.Ceiling(lastTime * sampleRate);
    1937.            for (int i = firstframe; i <= lastframe; i++) {
    1938.                keyTimes.Add ((float)(i * fs));
    1939.            }
    1940.  
    1941.            return keyTimes;
    1942.        }
    1943.  
    1944.        /// <summary>
    1945.        /// Return set of all keys times on animation curves
    1946.        /// </summary>
    1947.        [SecurityPermission(SecurityAction.LinkDemand)]
    1948.        internal static HashSet<float> GetKeyTimes(AnimationCurve[] animCurves)
    1949.        {
    1950.            var keyTimes = new HashSet<float>();
    1951.  
    1952.            foreach (var ac in animCurves)
    1953.            {
    1954.                if (ac!=null) foreach(var key in ac.keys) { keyTimes.Add(key.time); }
    1955.            }
    1956.  
    1957.            return keyTimes;
    1958.        }
    1959.  
    1960.        /// <summary>
    1961.        /// Export animation curve key frames with key tangents
    1962.        /// NOTE : This is a work in progress (WIP). We only export the key time and value on
    1963.        /// a Cubic curve using the default tangents.
    1964.        /// </summary>
    1965.        internal static void ExportAnimationKeys (AnimationCurve uniAnimCurve, FbxAnimCurve fbxAnimCurve,
    1966.            UnityToMayaConvertSceneHelper convertSceneHelper)
    1967.        {
    1968.            // Copy Unity AnimCurve to FBX AnimCurve.
    1969.            // NOTE: only cubic keys are supported by the FbxImporter
    1970.            using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve>{fbxAnimCurve}))
    1971.            {
    1972.                for (int keyIndex = 0; keyIndex < uniAnimCurve.length; ++keyIndex)
    1973.                {
    1974.                    var uniKeyFrame = uniAnimCurve [keyIndex];
    1975.                    var fbxTime = FbxTime.FromSecondDouble (uniKeyFrame.time);
    1976.  
    1977.                    int fbxKeyIndex = fbxAnimCurve.KeyAdd (fbxTime);
    1978.  
    1979.  
    1980.                    // configure tangents
    1981.                    var lTangent = AnimationUtility.GetKeyLeftTangentMode(uniAnimCurve, keyIndex);
    1982.                    var rTangent = AnimationUtility.GetKeyRightTangentMode(uniAnimCurve, keyIndex);
    1983.  
    1984.                    // Always set tangent mode to eTangentBreak, as other modes are not handled the same in FBX as in
    1985.                    // Unity, thus leading to discrepancies in animation curves.
    1986.                    FbxAnimCurveDef.ETangentMode tanMode = FbxAnimCurveDef.ETangentMode.eTangentBreak;
    1987.  
    1988.                    // Default to cubic interpolation, which is the default for KeySet
    1989.                    FbxAnimCurveDef.EInterpolationType interpMode = FbxAnimCurveDef.EInterpolationType.eInterpolationCubic;
    1990.                    switch (rTangent)
    1991.                    {
    1992.                        case AnimationUtility.TangentMode.Linear:
    1993.                            interpMode = FbxAnimCurveDef.EInterpolationType.eInterpolationLinear;
    1994.                            break;
    1995.                        case AnimationUtility.TangentMode.Constant:
    1996.                            interpMode = FbxAnimCurveDef.EInterpolationType.eInterpolationConstant;
    1997.                            break;
    1998.                        default:
    1999.                            break;
    2000.                    }
    2001.  
    2002.                    fbxAnimCurve.KeySet (fbxKeyIndex,
    2003.                        fbxTime,
    2004.                        convertSceneHelper.Convert(uniKeyFrame.value),
    2005.                        interpMode,
    2006.                        tanMode,
    2007.                        // value of right slope
    2008.                        convertSceneHelper.Convert(uniKeyFrame.outTangent),
    2009.                        // value of next left slope
    2010.                        keyIndex < uniAnimCurve.length -1 ? convertSceneHelper.Convert(uniAnimCurve[keyIndex+1].inTangent) : 0,
    2011.                        FbxAnimCurveDef.EWeightedMode.eWeightedAll,
    2012.                        // weight for right slope
    2013.                        uniKeyFrame.outWeight,
    2014.                        // weight for next left slope
    2015.                        keyIndex < uniAnimCurve.length - 1 ? uniAnimCurve[keyIndex + 1].inWeight : 0
    2016.                    );
    2017.                }
    2018.            }
    2019.        }
    2020.  
    2021.        /// <summary>
    2022.        /// Export animation curve key samples
    2023.        /// </summary>
    2024.        [SecurityPermission(SecurityAction.LinkDemand)]
    2025.        internal void ExportAnimationSamples (AnimationCurve uniAnimCurve, FbxAnimCurve fbxAnimCurve,
    2026.            double sampleRate,
    2027.            UnityToMayaConvertSceneHelper convertSceneHelper)
    2028.        {
    2029.              
    2030.            using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve>{fbxAnimCurve}))
    2031.            {
    2032.                foreach (var currSampleTime in GetSampleTimes(new AnimationCurve[]{uniAnimCurve}, sampleRate))
    2033.                {
    2034.                    float currSampleValue = uniAnimCurve.Evaluate((float)currSampleTime);
    2035.  
    2036.                    var fbxTime = FbxTime.FromSecondDouble (currSampleTime);
    2037.  
    2038.                    int fbxKeyIndex = fbxAnimCurve.KeyAdd (fbxTime);
    2039.  
    2040.                    fbxAnimCurve.KeySet (fbxKeyIndex,
    2041.                        fbxTime,
    2042.                        convertSceneHelper.Convert(currSampleValue)
    2043.                    );
    2044.                }
    2045.            }
    2046.        }
    2047.  
    2048.        /// <summary>
    2049.        /// Get the FbxConstraint associated with the constrained node.
    2050.        /// </summary>
    2051.        /// <param name="constrainedNode"></param>
    2052.        /// <param name="uniConstraintType"></param>
    2053.        /// <returns></returns>
    2054.        private FbxConstraint GetFbxConstraint(FbxNode constrainedNode, System.Type uniConstraintType)
    2055.        {
    2056.            if (uniConstraintType == null || !uniConstraintType.GetInterfaces().Contains(typeof(IConstraint)))
    2057.            {
    2058.                // not actually a constraint
    2059.                return null;
    2060.            }
    2061.  
    2062.            Dictionary<FbxConstraint, System.Type> constraints;
    2063.            if (MapConstrainedObjectToConstraints.TryGetValue(constrainedNode, out constraints))
    2064.            {
    2065.                var targetConstraint = constraints.FirstOrDefault(constraint => (constraint.Value == uniConstraintType));
    2066.                if (!targetConstraint.Equals(default(KeyValuePair<FbxConstraint, System.Type>)))
    2067.                {
    2068.                    return targetConstraint.Key;
    2069.                }
    2070.            }
    2071.  
    2072.            return null;
    2073.        }
    2074.  
    2075.        /// <summary>
    2076.        /// Get the FbxBlendshape with the given name associated with the FbxNode.
    2077.        /// </summary>
    2078.        /// <param name="blendshapeNode"></param>
    2079.        /// <param name="uniPropertyName"></param>
    2080.        /// <returns></returns>
    2081.        private FbxBlendShapeChannel GetFbxBlendShape(FbxNode blendshapeNode, string uniPropertyName)
    2082.        {
    2083.            List<FbxBlendShapeChannel> blendshapeChannels;
    2084.            if (MapUnityObjectToBlendShapes.TryGetValue(blendshapeNode, out blendshapeChannels))
    2085.            {
    2086.                var match = System.Text.RegularExpressions.Regex.Match(uniPropertyName, @"blendShape\.(\S+)");
    2087.                if (match.Success && match.Groups.Count > 0)
    2088.                {
    2089.                    string blendshapeName = match.Groups[1].Value;
    2090.  
    2091.                    var targetChannel = blendshapeChannels.FirstOrDefault(channel => (channel.GetName() == blendshapeName));
    2092.                    if (targetChannel != null)
    2093.                    {
    2094.                        return targetChannel;
    2095.                    }
    2096.                }
    2097.            }
    2098.            return null;
    2099.        }
    2100.  
    2101.        private FbxProperty GetFbxProperty(FbxNode fbxNode, string fbxPropertyName, System.Type uniPropertyType, string uniPropertyName)
    2102.        {
    2103.            if(fbxNode == null)
    2104.            {
    2105.                return null;
    2106.            }
    2107.  
    2108.            // check if property maps to a constraint
    2109.            // check this first because both constraints and FbxNodes can contain a RotationOffset property,
    2110.            // but only the constraint one is animatable.
    2111.            var fbxConstraint = GetFbxConstraint(fbxNode, uniPropertyType);
    2112.            if(fbxConstraint != null)
    2113.            {
    2114.                var prop = fbxConstraint.FindProperty(fbxPropertyName, false);
    2115.                if (prop.IsValid())
    2116.                {
    2117.                    return prop;
    2118.                }
    2119.            }
    2120.  
    2121.            // check if the property maps to a blendshape
    2122.            var fbxBlendShape = GetFbxBlendShape(fbxNode, uniPropertyName);
    2123.            if (fbxBlendShape != null)
    2124.            {
    2125.                var prop = fbxBlendShape.FindProperty(fbxPropertyName, false);
    2126.                if (prop.IsValid())
    2127.                {
    2128.                    return prop;
    2129.                }
    2130.            }
    2131.  
    2132.            // map unity property name to fbx property
    2133.            var fbxProperty = fbxNode.FindProperty(fbxPropertyName, false);
    2134.            if (fbxProperty.IsValid())
    2135.            {
    2136.                return fbxProperty;
    2137.            }
    2138.  
    2139.            var fbxNodeAttribute = fbxNode.GetNodeAttribute();
    2140.            if (fbxNodeAttribute != null)
    2141.            {
    2142.                fbxProperty = fbxNodeAttribute.FindProperty(fbxPropertyName, false);
    2143.            }
    2144.            return fbxProperty;
    2145.        }
    2146.  
    2147.        /// <summary>
    2148.        /// Export an AnimationCurve.
    2149.        /// NOTE: This is not used for rotations, because we need to convert from
    2150.        /// quaternion to euler and various other stuff.
    2151.        /// </summary>
    2152.        [SecurityPermission(SecurityAction.LinkDemand)]
    2153.        private void ExportAnimationCurve (FbxNode fbxNode,
    2154.                                                AnimationCurve uniAnimCurve,
    2155.                                                float frameRate,
    2156.                                                string uniPropertyName,
    2157.                                                System.Type uniPropertyType,
    2158.                                                FbxAnimLayer fbxAnimLayer)
    2159.        {
    2160.            if(fbxNode == null)
    2161.            {
    2162.                return;
    2163.            }
    2164.  
    2165.            if (Verbose) {
    2166.                Debug.Log ("Exporting animation for " + fbxNode.GetName() + " (" + uniPropertyName + ")");
    2167.            }
    2168.  
    2169.            var fbxConstraint = GetFbxConstraint(fbxNode, uniPropertyType);
    2170.            FbxPropertyChannelPair[] fbxPropertyChannelPairs;
    2171.            if (!FbxPropertyChannelPair.TryGetValue (uniPropertyName, out fbxPropertyChannelPairs, fbxConstraint)) {
    2172.                Debug.LogWarning(string.Format("no mapping from Unity '{0}' to fbx property", uniPropertyName));
    2173.                return;
    2174.            }
    2175.  
    2176.            foreach (var fbxPropertyChannelPair in fbxPropertyChannelPairs) {
    2177.                // map unity property name to fbx property
    2178.                var fbxProperty = GetFbxProperty(fbxNode, fbxPropertyChannelPair.Property, uniPropertyType, uniPropertyName);
    2179.                if (!fbxProperty.IsValid ())
    2180.                {
    2181.                    Debug.LogError (string.Format ("no fbx property {0} found on {1} node or nodeAttribute ", fbxPropertyChannelPair.Property, fbxNode.GetName ()));
    2182.                    return;
    2183.                }
    2184.                if (!fbxProperty.GetFlag(FbxPropertyFlags.EFlags.eAnimatable))
    2185.                {
    2186.                    Debug.LogErrorFormat("fbx property {0} found on node {1} is not animatable", fbxPropertyChannelPair.Property, fbxNode.GetName());
    2187.                }
    2188.  
    2189.                // Create the AnimCurve on the channel
    2190.                FbxAnimCurve fbxAnimCurve = fbxProperty.GetCurve (fbxAnimLayer, fbxPropertyChannelPair.Channel, true);
    2191.                if(fbxAnimCurve == null)
    2192.                {
    2193.                    return;
    2194.                }
    2195.  
    2196.                // create a convert scene helper so that we can convert from Unity to Maya
    2197.                // AxisSystem (LeftHanded to RightHanded) and FBX's default units
    2198.                // (Meters to Centimetres)
    2199.                var convertSceneHelper = new UnityToMayaConvertSceneHelper (uniPropertyName, fbxNode);
    2200.              
    2201.                if (ModelExporter.ExportSettings.BakeAnimationProperty) {
    2202.                    ExportAnimationSamples (uniAnimCurve, fbxAnimCurve, frameRate, convertSceneHelper);
    2203.                } else {
    2204.                    ExportAnimationKeys (uniAnimCurve, fbxAnimCurve, convertSceneHelper);
    2205.                }
    2206.            }
    2207.        }
    2208.  
    2209.        internal class UnityToMayaConvertSceneHelper
    2210.        {
    2211.            bool convertDistance = false;
    2212.            bool convertToRadian = false;
    2213.            bool convertLensShiftX = false;
    2214.            bool convertLensShiftY = false;
    2215.  
    2216.            FbxCamera camera = null;
    2217.  
    2218.            float unitScaleFactor = 1f;
    2219.  
    2220.            public UnityToMayaConvertSceneHelper(string uniPropertyName, FbxNode fbxNode)
    2221.            {
    2222.                System.StringComparison cc = System.StringComparison.CurrentCulture;
    2223.  
    2224.                bool partT = uniPropertyName.StartsWith("m_LocalPosition.", cc) || uniPropertyName.StartsWith("m_TranslationOffset", cc);
    2225.  
    2226.                convertDistance |= partT;
    2227.                convertDistance |= uniPropertyName.StartsWith ("m_Intensity", cc);
    2228.                convertDistance |= uniPropertyName.ToLower().EndsWith("weight", cc);
    2229.                convertLensShiftX |= uniPropertyName.StartsWith("m_LensShift.x", cc);
    2230.                convertLensShiftY |= uniPropertyName.StartsWith("m_LensShift.y", cc);
    2231.                if (convertLensShiftX || convertLensShiftY)
    2232.                {
    2233.                    camera = fbxNode.GetCamera();
    2234.                }
    2235.  
    2236.                // The ParentConstraint's source Rotation Offsets are read in as radians, so make sure they are exported as radians
    2237.                convertToRadian = uniPropertyName.StartsWith("m_RotationOffsets.Array.data", cc);
    2238.  
    2239.                if (convertDistance)
    2240.                    unitScaleFactor = ModelExporter.UnitScaleFactor;
    2241.  
    2242.                if (convertToRadian)
    2243.                {
    2244.                    unitScaleFactor *= (Mathf.PI / 180);
    2245.                }
    2246.            }
    2247.  
    2248.            public float Convert(float value)
    2249.            {
    2250.                float convertedValue = value;
    2251.                if (convertLensShiftX || convertLensShiftY)
    2252.                {
    2253.                    convertedValue = Mathf.Clamp(Mathf.Abs(value), 0f, 1f)*Mathf.Sign(value);
    2254.                }
    2255.                if (camera != null)
    2256.                {
    2257.                    if (convertLensShiftX)
    2258.                    {
    2259.                        convertedValue *= (float)camera.GetApertureWidth();
    2260.                    }
    2261.                    else if (convertLensShiftY)
    2262.                    {
    2263.                        convertedValue *= (float)camera.GetApertureHeight();
    2264.                    }
    2265.                }
    2266.  
    2267.                // left handed to right handed conversion
    2268.                // meters to centimetres conversion
    2269.                return unitScaleFactor * convertedValue;
    2270.            }
    2271.  
    2272.        }
    2273.  
    2274.        /// <summary>
    2275.        /// Export an AnimationClip as a single take
    2276.        /// </summary>
    2277.        [SecurityPermission(SecurityAction.LinkDemand)]
    2278.        private void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoot, FbxScene fbxScene)
    2279.        {
    2280.            if (!uniAnimClip || !uniRoot || fbxScene == null) return;
    2281.  
    2282.            if (Verbose)
    2283.                Debug.Log (string.Format ("Exporting animation clip ({1}) for {0}", uniRoot.name, uniAnimClip.name));
    2284.  
    2285.            // setup anim stack
    2286.            FbxAnimStack fbxAnimStack = FbxAnimStack.Create (fbxScene, uniAnimClip.name);
    2287.            fbxAnimStack.Description.Set ("Animation Take: " + uniAnimClip.name);
    2288.  
    2289.            // add one mandatory animation layer
    2290.            FbxAnimLayer fbxAnimLayer = FbxAnimLayer.Create (fbxScene, "Animation Base Layer");
    2291.            fbxAnimStack.AddMember (fbxAnimLayer);
    2292.  
    2293.            // Set up the FPS so our frame-relative math later works out
    2294.            // Custom frame rate isn't really supported in FBX SDK (there's
    2295.            // a bug), so try hard to find the nearest time mode.
    2296.            FbxTime.EMode timeMode = FbxTime.EMode.eCustom;
    2297.            double precision = 1e-6;
    2298.            while (timeMode == FbxTime.EMode.eCustom && precision < 1000) {
    2299.                timeMode = FbxTime.ConvertFrameRateToTimeMode (uniAnimClip.frameRate, precision);
    2300.                precision *= 10;
    2301.            }
    2302.            if (timeMode == FbxTime.EMode.eCustom) {
    2303.                timeMode = FbxTime.EMode.eFrames30;
    2304.            }
    2305.  
    2306.            fbxScene.GetGlobalSettings ().SetTimeMode (timeMode);
    2307.  
    2308.            // set time correctly
    2309.            var fbxStartTime = FbxTime.FromSecondDouble (0);
    2310.            var fbxStopTime = FbxTime.FromSecondDouble (uniAnimClip.length);
    2311.  
    2312.            fbxAnimStack.SetLocalTimeSpan (new FbxTimeSpan (fbxStartTime, fbxStopTime));
    2313.  
    2314.            var unityCurves = new Dictionary<GameObject, List<UnityCurve>> ();
    2315.  
    2316.            // extract and store all necessary information from the curve bindings, namely the animation curves
    2317.            // and their corresponding property names for each GameObject.
    2318.            foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings (uniAnimClip)) {
    2319.                Object uniObj = AnimationUtility.GetAnimatedObject (uniRoot, uniCurveBinding);
    2320.                if (!uniObj) {
    2321.                    continue;
    2322.                }
    2323.  
    2324.                AnimationCurve uniAnimCurve = AnimationUtility.GetEditorCurve (uniAnimClip, uniCurveBinding);
    2325.                if (uniAnimCurve == null) {
    2326.                    continue;
    2327.                }
    2328.  
    2329.                var uniGO = GetGameObject (uniObj);
    2330.                // Check if the GameObject has an FBX node to the animation. It might be null because the LOD selected doesn't match the one on the gameobject.
    2331.                if (!uniGO || MapUnityObjectToFbxNode.ContainsKey(uniGO) == false) {
    2332.                    continue;
    2333.                }
    2334.  
    2335.                if (unityCurves.ContainsKey (uniGO)) {
    2336.                    unityCurves [uniGO].Add (new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve, uniCurveBinding.type));
    2337.                    continue;
    2338.                }
    2339.                unityCurves.Add (uniGO, new List<UnityCurve> (){ new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve, uniCurveBinding.type) });
    2340.            }
    2341.  
    2342.            // transfer root motion
    2343.            var animSource = ExportOptions.AnimationSource;
    2344.            var animDest = ExportOptions.AnimationDest;
    2345.            if (animSource && animDest && animSource != animDest) {
    2346.                // list of all transforms between source and dest, including source and dest
    2347.                var transformsFromSourceToDest = new List<Transform> ();
    2348.                var curr = animDest;
    2349.                while (curr != animSource) {
    2350.                    transformsFromSourceToDest.Add (curr);
    2351.                    curr = curr.parent;
    2352.                }
    2353.                transformsFromSourceToDest.Add (animSource);
    2354.                transformsFromSourceToDest.Reverse ();
    2355.  
    2356.                // while there are 2 transforms in the list, transfer the animation from the
    2357.                // first to the next transform.
    2358.                // Then remove the first transform from the list.
    2359.                while (transformsFromSourceToDest.Count >= 2) {
    2360.                    var source = transformsFromSourceToDest [0];
    2361.                    transformsFromSourceToDest.RemoveAt (0);
    2362.                    var dest = transformsFromSourceToDest [0];
    2363.  
    2364.                    TransferMotion (source, dest, uniAnimClip.frameRate, ref unityCurves);
    2365.                }
    2366.            }
    2367.  
    2368.            /* The major difficulty: Unity uses quaternions for rotation
    2369.                * (which is how it should be) but FBX uses Euler angles. So we
    2370.                * need to gather up the list of transform curves per object.
    2371.                *
    2372.                * For euler angles, Unity uses ZXY rotation order while Maya uses XYZ.
    2373.                * Maya doesn't import files with ZXY rotation correctly, so have to convert to XYZ.
    2374.                * Need all 3 curves in order to convert.
    2375.                *
    2376.                * Also, in both cases, prerotation has to be removed from the animated rotation if
    2377.                * there are bones being exported.
    2378.                */
    2379.            var rotations = new Dictionary<GameObject, RotationCurve>();
    2380.  
    2381.            // export the animation curves for each GameObject that has animation
    2382.            foreach (var kvp in unityCurves) {
    2383.                var uniGO = kvp.Key;
    2384.                foreach (var uniCurve in kvp.Value) {
    2385.                    var propertyName = uniCurve.propertyName;
    2386.                    var uniAnimCurve = uniCurve.uniAnimCurve;
    2387.  
    2388.                    // Do not create the curves if the component is a SkinnedMeshRenderer and if the option in FBX Export settings is toggled on.
    2389.                    if (!ExportOptions.AnimateSkinnedMesh && (uniGO.GetComponent<SkinnedMeshRenderer> () != null)) {
    2390.                        continue;
    2391.                    }
    2392.  
    2393.                    FbxNode fbxNode;
    2394.                    if (!MapUnityObjectToFbxNode.TryGetValue(uniGO, out fbxNode))
    2395.                    {
    2396.                        Debug.LogError(string.Format("no FbxNode found for {0}", uniGO.name));
    2397.                        continue;
    2398.                    }
    2399.  
    2400.                    int index = QuaternionCurve.GetQuaternionIndex (propertyName);
    2401.                    if (index >= 0) {
    2402.                        // Rotation property; save it to convert quaternion -> euler later.
    2403.                        RotationCurve rotCurve = GetRotationCurve<QuaternionCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
    2404.                        rotCurve.SetCurve (index, uniAnimCurve);
    2405.                        continue;
    2406.                    }
    2407.  
    2408.                    // If this is an euler curve with a prerotation, then need to sample animations to remove the prerotation.
    2409.                    // Otherwise can export normally with tangents.
    2410.                    index = EulerCurve.GetEulerIndex (propertyName);
    2411.                    if (index >= 0 &&
    2412.                        // still need to sample euler curves if baking is specified
    2413.                        (ModelExporter.ExportSettings.BakeAnimationProperty ||
    2414.                        // also need to make sure to sample if there is a prerotation, as this is baked into the Unity curves
    2415.                        fbxNode.GetPreRotation(FbxNode.EPivotSet.eSourcePivot).Distance(new FbxVector4()) > 0)) {
    2416.  
    2417.                        RotationCurve rotCurve = GetRotationCurve<EulerCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
    2418.                        rotCurve.SetCurve (index, uniAnimCurve);
    2419.                        continue;
    2420.                    }
    2421.  
    2422.                    // simple property (e.g. intensity), export right away
    2423.                    ExportAnimationCurve (fbxNode, uniAnimCurve, uniAnimClip.frameRate,
    2424.                        propertyName, uniCurve.propertyType,
    2425.                        fbxAnimLayer);
    2426.                }
    2427.            }
    2428.  
    2429.            // now export all the quaternion curves
    2430.            foreach (var kvp in rotations) {
    2431.                var unityGo = kvp.Key;
    2432.                var rot = kvp.Value;
    2433.  
    2434.                FbxNode fbxNode;
    2435.                if (!MapUnityObjectToFbxNode.TryGetValue (unityGo, out fbxNode)) {
    2436.                    Debug.LogError (string.Format ("no FbxNode found for {0}", unityGo.name));
    2437.                    continue;
    2438.                }
    2439.                rot.Animate (unityGo.transform, fbxNode, fbxAnimLayer, Verbose);
    2440.            }
    2441.        }
    2442.  
    2443.        /// <summary>
    2444.        /// Transfers transform animation from source to dest. Replaces dest's Unity Animation Curves with updated animations.
    2445.        /// NOTE: Source must be the parent of dest.
    2446.        /// </summary>
    2447.        /// <param name="source">Source animated object.</param>
    2448.        /// <param name="dest">Destination, child of the source.</param>
    2449.        /// <param name="sampleRate">Sample rate.</param>
    2450.        /// <param name="unityCurves">Unity curves.</param>
    2451.        [SecurityPermission(SecurityAction.LinkDemand)]
    2452.        private void TransferMotion(Transform source, Transform dest, float sampleRate, ref Dictionary<GameObject, List<UnityCurve>> unityCurves){
    2453.            // get sample times for curves in dest + source
    2454.            // at each sample time, evaluate all 18 transfom anim curves, creating 2 transform matrices
    2455.            // combine the matrices, get the new values, apply to the 9 new anim curves for dest
    2456.            if (dest.parent != source) {
    2457.                Debug.LogError ("dest must be a child of source");
    2458.                return;
    2459.            }
    2460.  
    2461.            List<UnityCurve> sourceUnityCurves;
    2462.            if (!unityCurves.TryGetValue (source.gameObject, out sourceUnityCurves)) {
    2463.                return; // nothing to do, source has no animation
    2464.            }
    2465.  
    2466.            List<UnityCurve> destUnityCurves;
    2467.            if (!unityCurves.TryGetValue (dest.gameObject, out destUnityCurves)) {
    2468.                destUnityCurves = new List<UnityCurve> ();
    2469.            }
    2470.  
    2471.            List<AnimationCurve> animCurves = new List<AnimationCurve> ();
    2472.            foreach (var curve in sourceUnityCurves) {
    2473.                // TODO: check if curve is anim related
    2474.                animCurves.Add(curve.uniAnimCurve);
    2475.            }
    2476.            foreach (var curve in destUnityCurves) {
    2477.                animCurves.Add (curve.uniAnimCurve);
    2478.            }
    2479.  
    2480.            var sampleTimes = GetSampleTimes (animCurves.ToArray (), sampleRate);
    2481.            // need to create 9 new UnityCurves, one for each property
    2482.            var posKeyFrames = new Keyframe[3][];
    2483.            var rotKeyFrames = new Keyframe[3][];
    2484.            var scaleKeyFrames = new Keyframe[3][];
    2485.  
    2486.            for (int k = 0; k < posKeyFrames.Length; k++) {
    2487.                posKeyFrames [k] = new Keyframe[sampleTimes.Count];
    2488.                rotKeyFrames[k] = new Keyframe[sampleTimes.Count];
    2489.                scaleKeyFrames[k] = new Keyframe[sampleTimes.Count];
    2490.            }
    2491.  
    2492.            // If we have a point in local coords represented as a column-vector x, the equation of x in coordinates relative to source's parent is:
    2493.            //   x_grandparent = source * dest * x
    2494.            // Now we're going to change dest to dest' which has the animation from source. And we're going to change
    2495.            // source to source' which has no animation. The equation of x will become:
    2496.            //   x_grandparent = source' * dest' * x
    2497.            // We're not changing x_grandparent and x, so we need that:
    2498.            //   source * dest = source' * dest'
    2499.            // We know dest and source (both animated) and source' (static). Solve for dest':
    2500.            //   dest' = (source')^-1 * source * dest
    2501.            int keyIndex = 0;
    2502.            var sourceStaticMatrixInverse = Matrix4x4.TRS(source.localPosition, source.localRotation, source.localScale).inverse;
    2503.            foreach (var currSampleTime in sampleTimes)
    2504.            {
    2505.                var sourceLocalMatrix = GetTransformMatrix (currSampleTime, source, sourceUnityCurves);
    2506.                var destLocalMatrix = GetTransformMatrix (currSampleTime, dest, destUnityCurves);
    2507.  
    2508.                var newLocalMatrix = sourceStaticMatrixInverse * sourceLocalMatrix * destLocalMatrix;
    2509.  
    2510.                FbxVector4 translation, rotation, scale;
    2511.                GetTRSFromMatrix (newLocalMatrix, out translation, out rotation, out scale);
    2512.  
    2513.                // get rotation directly from matrix, as otherwise causes issues
    2514.                // with negative rotations.
    2515.                var rot = newLocalMatrix.rotation.eulerAngles;
    2516.  
    2517.                for (int k = 0; k < 3; k++) {
    2518.                    posKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, (float)translation [k]);
    2519.                    rotKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, rot [k]);
    2520.                    scaleKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, (float)scale [k]);
    2521.                }
    2522.                keyIndex++;
    2523.            }
    2524.  
    2525.            // create the new list of unity curves, and add it to dest's curves
    2526.            var newUnityCurves = new List<UnityCurve>();
    2527.            string posPropName = "m_LocalPosition.";
    2528.            string rotPropName = "localEulerAnglesRaw.";
    2529.            string scalePropName = "m_LocalScale.";
    2530.            var xyz = "xyz";
    2531.            for (int k = 0; k < 3; k++) {
    2532.                var posUniCurve = new UnityCurve ( posPropName + xyz[k], new AnimationCurve(posKeyFrames[k]), typeof(Transform));
    2533.                newUnityCurves.Add (posUniCurve);
    2534.  
    2535.                var rotUniCurve = new UnityCurve ( rotPropName + xyz[k], new AnimationCurve(rotKeyFrames[k]), typeof(Transform));
    2536.                newUnityCurves.Add (rotUniCurve);
    2537.  
    2538.                var scaleUniCurve = new UnityCurve ( scalePropName + xyz[k], new AnimationCurve(scaleKeyFrames[k]), typeof(Transform));
    2539.                newUnityCurves.Add (scaleUniCurve);
    2540.            }
    2541.  
    2542.            // remove old transform curves
    2543.            RemoveTransformCurves (ref sourceUnityCurves);
    2544.            RemoveTransformCurves (ref destUnityCurves);
    2545.  
    2546.            unityCurves [source.gameObject] = sourceUnityCurves;
    2547.            if (!unityCurves.ContainsKey(dest.gameObject)) {
    2548.                unityCurves.Add (dest.gameObject, newUnityCurves);
    2549.                return;
    2550.            }
    2551.            unityCurves [dest.gameObject].AddRange(newUnityCurves);
    2552.  
    2553.        }
    2554.  
    2555.  
    2556.        private void RemoveTransformCurves(ref List<UnityCurve> curves){
    2557.            var transformCurves = new List<UnityCurve> ();
    2558.            var transformPropNames = new string[]{"m_LocalPosition.", "m_LocalRotation", "localEulerAnglesRaw.", "m_LocalScale."};
    2559.            foreach (var curve in curves) {
    2560.                foreach (var prop in transformPropNames) {
    2561.                    if (curve.propertyName.StartsWith (prop)) {
    2562.                        transformCurves.Add (curve);
    2563.                        break;
    2564.                    }
    2565.                }
    2566.            }
    2567.            foreach (var curve in transformCurves) {
    2568.                curves.Remove (curve);
    2569.            }
    2570.        }
    2571.  
    2572.        private Matrix4x4 GetTransformMatrix(float currSampleTime, Transform orig, List<UnityCurve> unityCurves){
    2573.            var sourcePos = orig.localPosition;
    2574.            var sourceRot = orig.localRotation;
    2575.            var sourceScale = orig.localScale;
    2576.  
    2577.            foreach (var uniCurve in unityCurves) {
    2578.                float currSampleValue = uniCurve.uniAnimCurve.Evaluate(currSampleTime);
    2579.                string propName = uniCurve.propertyName;
    2580.                // try position, scale, quat then euler
    2581.                int temp = QuaternionCurve.GetQuaternionIndex(propName);
    2582.                if (temp >= 0) {
    2583.                    sourceRot [temp] = currSampleValue;
    2584.                    continue;
    2585.                }
    2586.                temp = EulerCurve.GetEulerIndex (propName);
    2587.                if (temp >= 0) {
    2588.                    var euler = sourceRot.eulerAngles;
    2589.                    euler [temp] = currSampleValue;
    2590.                    sourceRot.eulerAngles = euler;
    2591.                    continue;
    2592.                }
    2593.                temp = GetPositionIndex (propName);
    2594.                if (temp >= 0) {
    2595.                    sourcePos [temp] = currSampleValue;
    2596.                    continue;
    2597.                }
    2598.                temp = GetScaleIndex (propName);
    2599.                if (temp >= 0) {
    2600.                    sourceScale [temp] = currSampleValue;
    2601.                }
    2602.            }
    2603.  
    2604.            sourceRot = Quaternion.Euler(sourceRot.eulerAngles.x, sourceRot.eulerAngles.y, sourceRot.eulerAngles.z);
    2605.            return Matrix4x4.TRS(sourcePos, sourceRot, sourceScale);
    2606.        }
    2607.  
    2608.        internal struct UnityCurve {
    2609.            public string propertyName;
    2610.            public AnimationCurve uniAnimCurve;
    2611.            public System.Type propertyType;
    2612.  
    2613.            public UnityCurve(string propertyName, AnimationCurve uniAnimCurve, System.Type propertyType){
    2614.                this.propertyName = propertyName;
    2615.                this.uniAnimCurve = uniAnimCurve;
    2616.                this.propertyType = propertyType;
    2617.            }
    2618.        }
    2619.  
    2620.        private int GetPositionIndex(string uniPropertyName){
    2621.            System.StringComparison ct = System.StringComparison.CurrentCulture;
    2622.            bool isPositionComponent = uniPropertyName.StartsWith ("m_LocalPosition.", ct);
    2623.  
    2624.            if (!isPositionComponent) { return -1; }
    2625.  
    2626.            switch (uniPropertyName [uniPropertyName.Length - 1]) {
    2627.            case 'x':
    2628.                return 0;
    2629.            case 'y':
    2630.                return 1;
    2631.            case 'z':
    2632.                return 2;
    2633.            default:
    2634.                return -1;
    2635.            }
    2636.        }
    2637.  
    2638.        private int GetScaleIndex(string uniPropertyName){
    2639.            System.StringComparison ct = System.StringComparison.CurrentCulture;
    2640.            bool isScaleComponent = uniPropertyName.StartsWith ("m_LocalScale.", ct);
    2641.  
    2642.            if (!isScaleComponent) { return -1; }
    2643.  
    2644.            switch (uniPropertyName [uniPropertyName.Length - 1]) {
    2645.            case 'x':
    2646.                return 0;
    2647.            case 'y':
    2648.                return 1;
    2649.            case 'z':
    2650.                return 2;
    2651.            default:
    2652.                return -1;
    2653.            }
    2654.        }
    2655.  
    2656.        /// <summary>
    2657.        /// Gets or creates the rotation curve for GameObject uniGO.
    2658.        /// </summary>
    2659.        /// <returns>The rotation curve.</returns>
    2660.        /// <param name="uniGO">Unity GameObject.</param>
    2661.        /// <param name="frameRate">Frame rate.</param>
    2662.        /// <param name="rotations">Rotations.</param>
    2663.        /// <typeparam name="T"> RotationCurve is abstract so specify type of RotationCurve to create.</typeparam>
    2664.        private RotationCurve GetRotationCurve<T>(
    2665.            GameObject uniGO, float frameRate,
    2666.            ref Dictionary<GameObject, RotationCurve> rotations
    2667.            ) where T : RotationCurve, new()
    2668.        {
    2669.            RotationCurve rotCurve;
    2670.            if (!rotations.TryGetValue (uniGO, out rotCurve)) {
    2671.                rotCurve = new T { SampleRate = frameRate };
    2672.                rotations.Add (uniGO, rotCurve);
    2673.            }
    2674.            return rotCurve;
    2675.        }
    2676.  
    2677.        /// <summary>
    2678.        /// Export the Animator component on this game object
    2679.        /// </summary>
    2680.        [SecurityPermission(SecurityAction.LinkDemand)]
    2681.        private void ExportAnimation (GameObject uniRoot, FbxScene fbxScene)
    2682.        {
    2683.            if (!uniRoot)
    2684.            {
    2685.                return;
    2686.            }
    2687.  
    2688.            var exportedClips = new HashSet<AnimationClip> ();
    2689.  
    2690.            var uniAnimator = uniRoot.GetComponent<Animator> ();
    2691.            if (uniAnimator)
    2692.            {
    2693.                // Try the animator controller (mecanim)
    2694.                var controller = uniAnimator.runtimeAnimatorController;
    2695.  
    2696.                if (controller)
    2697.                {
    2698.                    // Only export each clip once per game object.
    2699.                    foreach (var clip in controller.animationClips) {
    2700.                        if (exportedClips.Add (clip)) {
    2701.                            ExportAnimationClip (clip, uniRoot, fbxScene);
    2702.                        }
    2703.                    }
    2704.                }
    2705.            }
    2706.  
    2707.            // Try the playable director
    2708.            var director = uniRoot.GetComponent<UnityEngine.Playables.PlayableDirector> ();
    2709.            if (director)
    2710.            {
    2711.                Debug.LogWarning(string.Format("Exporting animation from PlayableDirector on {0} not supported", uniRoot.name));
    2712.                // TODO: export animationclips from playabledirector
    2713.            }
    2714.  
    2715.            // Try the animation (legacy)
    2716.            var uniAnimation = uniRoot.GetComponent<Animation> ();
    2717.            if (uniAnimation)
    2718.            {
    2719.                // Only export each clip once per game object.
    2720.                foreach (var uniAnimObj in uniAnimation) {
    2721.                    AnimationState uniAnimState = uniAnimObj as AnimationState;
    2722.                    if (uniAnimState)
    2723.                    {
    2724.                        AnimationClip uniAnimClip = uniAnimState.clip;
    2725.                        if (exportedClips.Add (uniAnimClip)) {
    2726.                            ExportAnimationClip (uniAnimClip, uniRoot, fbxScene);
    2727.                        }
    2728.                    }
    2729.                }
    2730.            }
    2731.        }
    2732.  
    2733.        /// <summary>
    2734.        /// configures default camera for the scene
    2735.        /// </summary>
    2736.        private void SetDefaultCamera (FbxScene fbxScene)
    2737.        {
    2738.            if(fbxScene == null) { return; }
    2739.  
    2740.            if (string.IsNullOrEmpty(DefaultCamera))
    2741.                DefaultCamera = Globals.FBXSDK_CAMERA_PERSPECTIVE;
    2742.  
    2743.            fbxScene.GetGlobalSettings ().SetDefaultCamera (DefaultCamera);
    2744.        }
    2745.  
    2746.        /// <summary>
    2747.        /// Ensures that the inputted name is unique.
    2748.        /// If a duplicate name is found, then it is incremented.
    2749.        /// e.g. Sphere becomes Sphere_1
    2750.        /// </summary>
    2751.        /// <returns>Unique name</returns>
    2752.        /// <param name="name">Name</param>
    2753.        /// <param name="nameToCountMap">The dictionary to use to map name to # of occurences</param>
    2754.        private string GetUniqueName(string name, Dictionary<string, int> nameToCountMap)
    2755.        {
    2756.            var uniqueName = name;
    2757.            int count;
    2758.            if (nameToCountMap.TryGetValue(name, out count))
    2759.            {
    2760.                uniqueName = string.Format(UniqueNameFormat, name, count);
    2761.            }
    2762.            else
    2763.            {
    2764.                count = 0;
    2765.            }
    2766.            nameToCountMap[name] = count + 1;
    2767.            return uniqueName;
    2768.        }
    2769.  
    2770.        /// <summary>
    2771.        /// Ensures that the inputted name is unique.
    2772.        /// If a duplicate name is found, then it is incremented.
    2773.        /// e.g. Sphere becomes Sphere_1
    2774.        /// </summary>
    2775.        /// <returns>Unique name</returns>
    2776.        /// <param name="name">Name</param>
    2777.        private string GetUniqueFbxNodeName(string name)
    2778.        {
    2779.            return GetUniqueName(name, NameToIndexMap);
    2780.        }
    2781.  
    2782.        /// <summary>
    2783.        /// Ensures that the inputted material name is unique.
    2784.        /// If a duplicate name is found, then it is incremented.
    2785.        /// e.g. mat becomes mat_1
    2786.        /// </summary>
    2787.        /// <param name="name">Name</param>
    2788.        /// <returns>Unique material name</returns>
    2789.        private string GetUniqueMaterialName(string name)
    2790.        {
    2791.            return GetUniqueName(name, MaterialNameToIndexMap);
    2792.        }
    2793.  
    2794.        /// <summary>
    2795.        /// Ensures that the inputted texture name is unique.
    2796.        /// If a duplicate name is found, then it is incremented.
    2797.        /// e.g. tex becomes tex_1
    2798.        /// </summary>
    2799.        /// <param name="name">Name</param>
    2800.        /// <returns>Unique texture name</returns>
    2801.        private string GetUniqueTextureName(string name)
    2802.        {
    2803.            return GetUniqueName(name, TextureNameToIndexMap);
    2804.        }
    2805.  
    2806.        /// <summary>
    2807.        /// Create a fbxNode from unityGo.
    2808.        /// </summary>
    2809.        /// <param name="unityGo"></param>
    2810.        /// <param name="fbxScene"></param>
    2811.        /// <returns>the created FbxNode</returns>
    2812.        private FbxNode CreateFbxNode(GameObject unityGo, FbxScene fbxScene)
    2813.        {
    2814.  
    2815.            string fbxName = unityGo.name;
    2816.            if (ExportOptions.UseMayaCompatibleNames)
    2817.            {
    2818.                fbxName = ConvertToMayaCompatibleName(unityGo.name);
    2819.                if (ExportOptions.AllowSceneModification)
    2820.                {
    2821.                    Undo.RecordObject(unityGo, "rename " + fbxName);
    2822.                    unityGo.name = fbxName;
    2823.                }
    2824.            }
    2825.          
    2826.            FbxNode fbxNode = FbxNode.Create(fbxScene, GetUniqueFbxNodeName(fbxName));
    2827.  
    2828.            // Default inheritance type in FBX is RrSs, which causes scaling issues in Maya as
    2829.            // both Maya and Unity use RSrs inheritance by default.
    2830.            // Note: MotionBuilder uses RrSs inheritance by default as well, though it is possible
    2831.            //       to select a different inheritance type in the UI.
    2832.            // Use RSrs as the scaling inheritance instead.
    2833.            fbxNode.SetTransformationInheritType(FbxTransform.EInheritType.eInheritRSrs);
    2834.  
    2835.            // Fbx rotation order is XYZ, but Unity rotation order is ZXY.
    2836.            // Also, DeepConvert does not convert the rotation order (assumes XYZ), unless RotationActive is true.
    2837.            fbxNode.SetRotationOrder(FbxNode.EPivotSet.eSourcePivot, FbxEuler.EOrder.eOrderZXY);
    2838.            fbxNode.SetRotationActive(true);
    2839.  
    2840.            MapUnityObjectToFbxNode[unityGo] = fbxNode;
    2841.  
    2842.            return fbxNode;
    2843.        }
    2844.  
    2845.        /// <summary>
    2846.        /// Creates an FbxNode for each GameObject.
    2847.        /// </summary>
    2848.        /// <returns>The number of nodes exported.</returns>
    2849.        internal int ExportTransformHierarchy(
    2850.            GameObject  unityGo, FbxScene fbxScene, FbxNode fbxNodeParent,
    2851.            int exportProgress, int objectCount, Vector3 newCenter,
    2852.            TransformExportType exportType = TransformExportType.Local,
    2853.            ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All
    2854.        )
    2855.        {
    2856.            int numObjectsExported = exportProgress;
    2857.  
    2858.            FbxNode fbxNode = CreateFbxNode(unityGo, fbxScene);
    2859.  
    2860.            if (Verbose)
    2861.                Debug.Log (string.Format ("exporting {0}", fbxNode.GetName ()));
    2862.  
    2863.            numObjectsExported++;
    2864.            if (EditorUtility.DisplayCancelableProgressBar (
    2865.                    ProgressBarTitle,
    2866.                    string.Format ("Creating FbxNode {0}/{1}", numObjectsExported, objectCount),
    2867.                    (numObjectsExported / (float)objectCount) * 0.25f)) {
    2868.                // cancel silently
    2869.                return -1;
    2870.            }
    2871.  
    2872.            ExportTransform (unityGo.transform, fbxNode, newCenter, exportType);
    2873.  
    2874.            fbxNodeParent.AddChild (fbxNode);
    2875.  
    2876.            // if this object has an LOD group, then export according to the LOD preference setting
    2877.            var lodGroup = unityGo.GetComponent<LODGroup>();
    2878.            if (lodGroup && lodExportType != ExportSettings.LODExportType.All) {
    2879.                LOD[] lods = lodGroup.GetLODs ();
    2880.  
    2881.                // LODs are ordered from highest to lowest.
    2882.                // If exporting lowest LOD, reverse the array
    2883.                if (lodExportType == ExportSettings.LODExportType.Lowest) {
    2884.                    // reverse the array
    2885.                    LOD[] tempLods = new LOD[lods.Length];
    2886.                    System.Array.Copy (lods, tempLods, lods.Length);
    2887.                    System.Array.Reverse (tempLods);
    2888.                    lods = tempLods;
    2889.                }
    2890.  
    2891.                for(int i = 0; i < lods.Length; i++){
    2892.                    var lod = lods ;
    2893.                    bool exportedRenderer = false;
    2894.                    foreach (var renderer in lod.renderers) {
    2895.                        // only export if parented under LOD group
    2896.                        if (renderer.transform.parent == unityGo.transform) {
    2897.                            numObjectsExported = ExportTransformHierarchy (renderer.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType);
    2898.                            exportedRenderer = true;
    2899.                        } else if(Verbose) {
    2900.                            Debug.LogFormat ("FbxExporter: Not exporting LOD {0}: {1}", i, renderer.name);
    2901.                        }
    2902.                    }
    2903.  
    2904.                    // if at least one renderer for this LOD was exported, then we succeeded
    2905.                    // so stop exporting.
    2906.                    if (exportedRenderer) {
    2907.                        return numObjectsExported;
    2908.                    }
    2909.                }
    2910.            }
    2911.  
    2912.            // now  unityGo  through our children and recurse
    2913.            foreach (Transform childT in  unityGo.transform) {
    2914.                numObjectsExported = ExportTransformHierarchy (childT.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType);
    2915.            }
    2916.  
    2917.            return numObjectsExported;
    2918.        }
    2919.  
    2920.        /// <summary>
    2921.        /// Exports all animation clips in the hierarchy along with
    2922.        /// the minimum required GameObject information.
    2923.        /// i.e. Animated GameObjects, their ancestors, and their transforms are exported,
    2924.        ///     but components are only exported if explicitly animated. Meshes are not exported.
    2925.        /// </summary>
    2926.        /// <returns>The number of nodes exported.</returns>
    2927.        [SecurityPermission(SecurityAction.LinkDemand)]
    2928.        internal int ExportAnimationOnly(
    2929.            GameObject unityGO,
    2930.            FbxScene fbxScene,
    2931.            int exportProgress,
    2932.            int objectCount,
    2933.            Vector3 newCenter,
    2934.            IExportData data,
    2935.            TransformExportType exportType = TransformExportType.Local
    2936.        ){
    2937.            AnimationOnlyExportData exportData = (AnimationOnlyExportData)data;
    2938.            int numObjectsExported = exportProgress;
    2939.  
    2940.            // make sure anim destination node is exported as well
    2941.            var exportSet = exportData.Objects;
    2942.            if (ExportOptions.AnimationDest && ExportOptions.AnimationSource)
    2943.            {
    2944.                exportSet.Add(ExportOptions.AnimationDest.gameObject);
    2945.            }
    2946.  
    2947.            // first export all the animated bones that are in the export set
    2948.            // as only a subset of bones are exported, but we still need to make sure the bone transforms are correct
    2949.            if(!ExportAnimatedBones(unityGO, fbxScene, ref numObjectsExported, objectCount, exportData))
    2950.            {
    2951.                // export cancelled
    2952.                return -1;
    2953.            }
    2954.  
    2955.            // export everything else and make sure all nodes are connected
    2956.            foreach (var go in exportSet) {
    2957.                FbxNode node;
    2958.                if (!ExportGameObjectAndParents (
    2959.                    go, unityGO, fbxScene, out node, newCenter, exportType, ref numObjectsExported, objectCount
    2960.                    )) {
    2961.                    // export cancelled
    2962.                    return -1;
    2963.                }
    2964.  
    2965.                ExportConstraints(go, fbxScene, node);
    2966.  
    2967.                System.Type compType;
    2968.                if (exportData.exportComponent.TryGetValue (go, out compType)) {
    2969.                    if (compType == typeof(Light)) {
    2970.                        ExportLight (go, fbxScene, node);
    2971.                    } else if (compType == typeof(Camera)) {
    2972.                        ExportCamera (go, fbxScene, node);
    2973.                    } else if (compType == typeof(SkinnedMeshRenderer)) {
    2974.                        // export only what is necessary for exporting blendshape animation
    2975.                        var unitySkin = go.GetComponent<SkinnedMeshRenderer>();
    2976.                        var meshInfo = new MeshInfo(unitySkin.sharedMesh, unitySkin.sharedMaterials);
    2977.                        ExportMesh(meshInfo, node);
    2978.                    }
    2979.                }
    2980.            }
    2981.  
    2982.            return numObjectsExported;
    2983.        }
    2984.  
    2985.        internal class SkinnedMeshBoneInfo {
    2986.            public SkinnedMeshRenderer skinnedMesh;
    2987.            public Dictionary<Transform, int> boneDict;
    2988.            public Dictionary<Transform, Matrix4x4> boneToBindPose;
    2989.  
    2990.            public SkinnedMeshBoneInfo(SkinnedMeshRenderer skinnedMesh, Dictionary<Transform, int> boneDict){
    2991.                this.skinnedMesh = skinnedMesh;
    2992.                this.boneDict = boneDict;
    2993.                this.boneToBindPose = new Dictionary<Transform, Matrix4x4>();
    2994.            }
    2995.        }
    2996.  
    2997.        private bool ExportAnimatedBones (
    2998.            GameObject unityGo,
    2999.            FbxScene fbxScene,
    3000.            ref int exportProgress,
    3001.            int objectCount,
    3002.            AnimationOnlyExportData exportData
    3003.            )
    3004.        {
    3005.            var skinnedMeshRenderers = unityGo.GetComponentsInChildren<SkinnedMeshRenderer>();
    3006.            foreach (var skinnedMesh in skinnedMeshRenderers)
    3007.            {
    3008.                var boneArray = skinnedMesh.bones;
    3009.                var bones = new HashSet<GameObject>();
    3010.                var boneDict = new Dictionary<Transform, int>();
    3011.  
    3012.                for (int i = 0; i < boneArray.Length; i++)
    3013.                {
    3014.                    bones.Add(boneArray.gameObject);
    3015.                    boneDict.Add(boneArray, i);
    3016.                }
    3017.  
    3018.                // get the bones that are also in the export set
    3019.                bones.IntersectWith(exportData.Objects);
    3020.  
    3021.                var boneInfo = new SkinnedMeshBoneInfo(skinnedMesh, boneDict);
    3022.                foreach (var bone in bones)
    3023.                {
    3024.                    FbxNode fbxNode;
    3025.                    // bone already exported
    3026.                    if (MapUnityObjectToFbxNode.TryGetValue(bone, out fbxNode))
    3027.                    {
    3028.                        continue;
    3029.                    }
    3030.                    fbxNode = CreateFbxNode(bone, fbxScene);
    3031.  
    3032.                    exportProgress++;
    3033.                    if (EditorUtility.DisplayCancelableProgressBar(
    3034.                            ProgressBarTitle,
    3035.                        string.Format("Creating FbxNode {0}/{1}", exportProgress, objectCount),
    3036.                        (exportProgress / (float)objectCount) * 0.5f))
    3037.                    {
    3038.                        // cancel silently
    3039.                        return false;
    3040.                    }
    3041.                    ExportBoneTransform(fbxNode, fbxScene, bone.transform, boneInfo);
    3042.                }
    3043.            }
    3044.            return true;
    3045.        }
    3046.  
    3047.        /// <summary>
    3048.        /// Exports the Gameobject and its ancestors.
    3049.        /// </summary>
    3050.        /// <returns><c>true</c>, if game object and parents were exported,
    3051.        ///  <c>false</c> if export cancelled.</returns>
    3052.        private bool ExportGameObjectAndParents(
    3053.            GameObject unityGo,
    3054.            GameObject rootObject,
    3055.            FbxScene fbxScene,
    3056.            out FbxNode fbxNode,
    3057.            Vector3 newCenter,
    3058.            TransformExportType exportType,
    3059.            ref int exportProgress,
    3060.            int objectCount
    3061.            )
    3062.        {
    3063.            // node doesn't exist so create it
    3064.            if (!MapUnityObjectToFbxNode.TryGetValue(unityGo, out fbxNode))
    3065.            {
    3066.                fbxNode = CreateFbxNode(unityGo, fbxScene);
    3067.  
    3068.                exportProgress++;
    3069.                if (EditorUtility.DisplayCancelableProgressBar(
    3070.                        ProgressBarTitle,
    3071.                    string.Format("Creating FbxNode {0}/{1}", exportProgress, objectCount),
    3072.                    (exportProgress / (float)objectCount) * 0.5f))
    3073.                {
    3074.                    // cancel silently
    3075.                    return false;
    3076.                }
    3077.  
    3078.                ExportTransform(unityGo.transform, fbxNode, newCenter, exportType);
    3079.            }
    3080.  
    3081.            if (unityGo == rootObject || unityGo.transform.parent == null)
    3082.            {
    3083.                fbxScene.GetRootNode().AddChild(fbxNode);
    3084.                return true;
    3085.            }
    3086.  
    3087.            // make sure all the nodes are connected and exported
    3088.            FbxNode fbxNodeParent;
    3089.            if (!ExportGameObjectAndParents (
    3090.                unityGo.transform.parent.gameObject,
    3091.                rootObject,
    3092.                fbxScene,
    3093.                out fbxNodeParent,
    3094.                newCenter,
    3095.                TransformExportType.Local,
    3096.                ref exportProgress,
    3097.                objectCount
    3098.            )) {
    3099.                // export cancelled
    3100.                return false;
    3101.            }
    3102.            fbxNodeParent.AddChild (fbxNode);
    3103.  
    3104.            return true;
    3105.        }
    3106.  
    3107.        /// <summary>
    3108.        /// Exports the bone transform.
    3109.        /// </summary>
    3110.        /// <returns><c>true</c>, if bone transform was exported, <c>false</c> otherwise.</returns>
    3111.        /// <param name="fbxNode">Fbx node.</param>
    3112.        /// <param name="fbxScene">Fbx scene.</param>
    3113.        /// <param name="unityBone">Unity bone.</param>
    3114.        /// <param name="boneInfo">Bone info.</param>
    3115.        private bool ExportBoneTransform(
    3116.            FbxNode fbxNode, FbxScene fbxScene, Transform unityBone, SkinnedMeshBoneInfo boneInfo
    3117.        ){
    3118.            if (boneInfo == null || boneInfo.skinnedMesh == null || boneInfo.boneDict == null || unityBone == null) {
    3119.                return false;
    3120.            }
    3121.  
    3122.            var skinnedMesh = boneInfo.skinnedMesh;
    3123.            var boneDict = boneInfo.boneDict;
    3124.            var rootBone = skinnedMesh.rootBone;
    3125.  
    3126.            // setup the skeleton
    3127.            var fbxSkeleton = fbxNode.GetSkeleton ();
    3128.            if (fbxSkeleton == null) {
    3129.                fbxSkeleton = FbxSkeleton.Create (fbxScene, unityBone.name + SkeletonPrefix);
    3130.  
    3131.                fbxSkeleton.Size.Set (1.0f * UnitScaleFactor);
    3132.                fbxNode.SetNodeAttribute (fbxSkeleton);
    3133.            }
    3134.            var fbxSkeletonType = FbxSkeleton.EType.eLimbNode;
    3135.  
    3136.            // Only set the rootbone's skeleton type to FbxSkeleton.EType.eRoot
    3137.            // if it has at least one child that is also a bone.
    3138.            // Otherwise if it is marked as Root but has no bones underneath,
    3139.            // Maya will import it as a Null object instead of a bone.
    3140.            if (rootBone == unityBone && rootBone.childCount > 0)
    3141.            {
    3142.                var hasChildBone = false;
    3143.                foreach (Transform child in unityBone)
    3144.                {
    3145.                    if (boneDict.ContainsKey(child))
    3146.                    {
    3147.                        hasChildBone = true;
    3148.                        break;
    3149.                    }
    3150.                }
    3151.                if (hasChildBone)
    3152.                {
    3153.                    fbxSkeletonType = FbxSkeleton.EType.eRoot;
    3154.                }
    3155.            }
    3156.            fbxSkeleton.SetSkeletonType (fbxSkeletonType);
    3157.  
    3158.            var bindPoses = skinnedMesh.sharedMesh.bindposes;
    3159.  
    3160.            // get bind pose
    3161.            Matrix4x4 bindPose = GetBindPose(unityBone, bindPoses, ref boneInfo);
    3162.  
    3163.            Matrix4x4 pose;
    3164.            // get parent's bind pose
    3165.            Matrix4x4 parentBindPose = GetBindPose(unityBone.parent, bindPoses, ref boneInfo);
    3166.            pose = parentBindPose * bindPose.inverse;
    3167.  
    3168.            FbxVector4 translation, rotation, scale;
    3169.            GetTRSFromMatrix (pose, out translation, out rotation, out scale);
    3170.  
    3171.            // Export bones with zero rotation, using a pivot instead to set the rotation
    3172.            // so that the bones are easier to animate and the rotation shows up as the "joint orientation" in Maya.
    3173.            fbxNode.LclTranslation.Set (new FbxDouble3(translation.X*UnitScaleFactor, translation.Y*UnitScaleFactor, translation.Z*UnitScaleFactor));
    3174.            fbxNode.LclRotation.Set (new FbxDouble3(0,0,0));
    3175.            fbxNode.LclScaling.Set (new FbxDouble3 (scale.X, scale.Y, scale.Z));
    3176.  
    3177.            // TODO (UNI-34294): add detailed comment about why we export rotation as pre-rotation
    3178.            fbxNode.SetRotationActive (true);
    3179.            fbxNode.SetPivotState (FbxNode.EPivotSet.eSourcePivot, FbxNode.EPivotState.ePivotReference);
    3180.            fbxNode.SetPreRotation (FbxNode.EPivotSet.eSourcePivot, new FbxVector4 (rotation.X, rotation.Y, rotation.Z));
    3181.  
    3182.            return true;
    3183.        }
    3184.  
    3185.        private void GetTRSFromMatrix(Matrix4x4 unityMatrix, out FbxVector4 translation, out FbxVector4 rotation, out FbxVector4 scale){
    3186.            // FBX is transposed relative to Unity: transpose as we convert.
    3187.            FbxMatrix matrix = new FbxMatrix ();
    3188.            matrix.SetColumn (0, new FbxVector4 (unityMatrix.GetRow (0).x, unityMatrix.GetRow (0).y, unityMatrix.GetRow (0).z, unityMatrix.GetRow (0).w));
    3189.            matrix.SetColumn (1, new FbxVector4 (unityMatrix.GetRow (1).x, unityMatrix.GetRow (1).y, unityMatrix.GetRow (1).z, unityMatrix.GetRow (1).w));
    3190.            matrix.SetColumn (2, new FbxVector4 (unityMatrix.GetRow (2).x, unityMatrix.GetRow (2).y, unityMatrix.GetRow (2).z, unityMatrix.GetRow (2).w));
    3191.            matrix.SetColumn (3, new FbxVector4 (unityMatrix.GetRow (3).x, unityMatrix.GetRow (3).y, unityMatrix.GetRow (3).z, unityMatrix.GetRow (3).w));
    3192.  
    3193.            // FBX wants translation, rotation (in euler angles) and scale.
    3194.            // We assume there's no real shear, just rounding error.
    3195.            FbxVector4 shear;
    3196.            double sign;
    3197.            matrix.GetElements (out translation, out rotation, out shear, out scale, out sign);
    3198.        }
    3199.  
    3200.        /// <summary>
    3201.        /// Counts how many objects are between this object and the root (exclusive).
    3202.        /// </summary>
    3203.        /// <returns>The object to root count.</returns>
    3204.        /// <param name="startObject">Start object.</param>
    3205.        /// <param name="root">Root object.</param>
    3206.        private static int GetObjectToRootDepth(Transform startObject, Transform root){
    3207.            if (startObject == null) {
    3208.                return 0;
    3209.            }
    3210.  
    3211.            int count = 0;
    3212.            var parent = startObject.parent;
    3213.            while (parent != null && parent != root) {
    3214.                count++;
    3215.                parent = parent.parent;
    3216.            }
    3217.            return count;
    3218.        }
    3219.  
    3220.  
    3221.        /// <summary>
    3222.        /// Gets the count of animated objects to be exported.
    3223.        ///
    3224.        /// In addition, collects the minimum set of what needs to be exported for each GameObject hierarchy.
    3225.        /// This contains all the animated GameObjects, their ancestors, their transforms, as well as any animated
    3226.        /// components and the animation clips. Also, the first animation to export, if any.
    3227.        /// </summary>
    3228.        /// <returns>The animation only hierarchy count.</returns>
    3229.        /// <param name="hierarchyToExportData">Map from GameObject hierarchy to animation export data.</param>
    3230.        [SecurityPermission(SecurityAction.LinkDemand)]
    3231.        internal int GetAnimOnlyHierarchyCount(Dictionary<GameObject, IExportData> hierarchyToExportData)
    3232.        {
    3233.            // including any parents of animated objects that are exported
    3234.            var completeExpSet = new HashSet<GameObject>();
    3235.            foreach (var data in hierarchyToExportData.Values) {
    3236.                if(data == null || data.Objects == null || data.Objects.Count <= 0)
    3237.                {
    3238.                    continue;
    3239.                }
    3240.                foreach (var go in data.Objects) {
    3241.                    completeExpSet.Add(go);
    3242.  
    3243.                    var parent = go.transform.parent;
    3244.                    while (parent != null && completeExpSet.Add(parent.gameObject)) {
    3245.                        parent = parent.parent;
    3246.                    }
    3247.                }
    3248.            }
    3249.  
    3250.            return completeExpSet.Count;
    3251.        }
    3252.  
    3253.        [SecurityPermission(SecurityAction.LinkDemand)]
    3254.        internal static Dictionary<GameObject, IExportData> GetExportData(Object[] objects, IExportOptions exportOptions = null)
    3255.        {
    3256.            if (exportOptions==null)
    3257.                exportOptions = DefaultOptions;
    3258.            Debug.Assert(exportOptions!=null);
    3259.  
    3260.            Dictionary<GameObject, IExportData>  exportData = new Dictionary<GameObject, IExportData>();
    3261.  
    3262.            if (exportOptions.ModelAnimIncludeOption == ExportSettings.Include.Model)
    3263.            {
    3264.                return null;
    3265.            }
    3266.  
    3267.            foreach (var obj in objects)
    3268.            {
    3269.                GameObject go = ModelExporter.GetGameObject (obj);
    3270.                if (go)
    3271.                {
    3272.                    exportData[go] = GetExportData(go, exportOptions);
    3273.                }
    3274.                else if (IsEditorClip(obj))
    3275.                {
    3276.                    KeyValuePair<GameObject, AnimationClip> pair = AnimationOnlyExportData.GetGameObjectAndAnimationClip(obj);
    3277.                    exportData[pair.Key] = GetExportData (pair.Key, pair.Value, exportOptions);
    3278.                }
    3279.            }
    3280.  
    3281.            return exportData.Count == 0 ? null : exportData;
    3282.        }
    3283.  
    3284.        [SecurityPermission(SecurityAction.LinkDemand)]
    3285.        internal static IExportData GetExportData(GameObject rootObject, AnimationClip animationClip, IExportOptions exportOptions = null)
    3286.        {
    3287.            if (exportOptions==null)
    3288.                exportOptions = DefaultOptions;
    3289.            Debug.Assert(exportOptions!=null);
    3290.              
    3291.            var exportData = new AnimationOnlyExportData();
    3292.            exportData.CollectDependencies(animationClip, rootObject, exportOptions);
    3293.              
    3294.            // could not find any dependencies, return null
    3295.            if(exportData.Objects.Count <= 0)
    3296.            {
    3297.                return null;
    3298.            }
    3299.            return exportData;
    3300.        }
    3301.  
    3302.        internal static IExportData GetExportData(GameObject go, IExportOptions exportOptions = null)
    3303.        {
    3304.            if (exportOptions==null)
    3305.                exportOptions = DefaultOptions;
    3306.            Debug.Assert(exportOptions!=null);
    3307.  
    3308.            // gather all animation clips
    3309.            var legacyAnim = go.GetComponentsInChildren<Animation>();
    3310.            var genericAnim = go.GetComponentsInChildren<Animator>();
    3311.  
    3312.            var exportData = new AnimationOnlyExportData();
    3313.  
    3314.            int depthFromRootAnimation = int.MaxValue;
    3315.            Animation rootAnimation = null;
    3316.            foreach (var anim in legacyAnim)
    3317.            {
    3318.                int count = GetObjectToRootDepth(anim.transform, go.transform);
    3319.  
    3320.                if (count < depthFromRootAnimation)
    3321.                {
    3322.                    depthFromRootAnimation = count;
    3323.                    rootAnimation = anim;
    3324.                }
    3325.  
    3326.                var animClips = AnimationUtility.GetAnimationClips(anim.gameObject);
    3327.                exportData.CollectDependencies(animClips, anim.gameObject, exportOptions);
    3328.            }
    3329.  
    3330.            int depthFromRootAnimator = int.MaxValue;
    3331.            Animator rootAnimator = null;
    3332.            foreach (var anim in genericAnim)
    3333.            {
    3334.                int count = GetObjectToRootDepth(anim.transform, go.transform);
    3335.  
    3336.                if (count < depthFromRootAnimator)
    3337.                {
    3338.                    depthFromRootAnimator = count;
    3339.                    rootAnimator = anim;
    3340.                }
    3341.  
    3342.                // Try the animator controller (mecanim)
    3343.                var controller = anim.runtimeAnimatorController;
    3344.                if (controller)
    3345.                {
    3346.                    exportData.CollectDependencies(controller.animationClips, anim.gameObject, exportOptions);
    3347.                }
    3348.            }
    3349.  
    3350.            // set the first clip to export
    3351.            if (depthFromRootAnimation < depthFromRootAnimator)
    3352.            {
    3353.                exportData.defaultClip = rootAnimation.clip;
    3354.            }
    3355.            else if(rootAnimator)
    3356.            {
    3357.                // Try the animator controller (mecanim)
    3358.                var controller = rootAnimator.runtimeAnimatorController;
    3359.                if (controller)
    3360.                {
    3361.                    var dController = controller as UnityEditor.Animations.AnimatorController;
    3362.                    if (dController && dController.layers.Count() > 0)
    3363.                    {
    3364.                        var motion = dController.layers[0].stateMachine.defaultState.motion;
    3365.                        var defaultClip = motion as AnimationClip;
    3366.                        if (defaultClip)
    3367.                        {
    3368.                            exportData.defaultClip = defaultClip;
    3369.                        }
    3370.                        else
    3371.                        {
    3372.                            if (motion != null) {
    3373.                                Debug.LogWarningFormat("Couldn't export motion {0}", motion.name);
    3374.                            }
    3375.                            // missing animation
    3376.                            else {
    3377.                                Debug.LogWarningFormat("Couldn't export motion. Motion is missing.");
    3378.                            }
    3379.                        }
    3380.                    }
    3381.                }
    3382.            }
    3383.            return exportData;
    3384.        }
    3385.  
    3386.        /// <summary>
    3387.        /// Export components on this game object.
    3388.        /// Transform components have already been exported.
    3389.        /// This function exports the other components and animation.
    3390.        /// </summary>
    3391.        [SecurityPermission(SecurityAction.LinkDemand)]
    3392.        private bool ExportComponents(FbxScene fbxScene)
    3393.        {
    3394.            var animationNodes = new HashSet<GameObject> ();
    3395.  
    3396.            int numObjectsExported = 0;
    3397.            int objectCount = MapUnityObjectToFbxNode.Count;
    3398.            foreach (KeyValuePair<GameObject, FbxNode> entry in MapUnityObjectToFbxNode) {
    3399.                numObjectsExported++;
    3400.                if (EditorUtility.DisplayCancelableProgressBar (
    3401.                        ProgressBarTitle,
    3402.                        string.Format ("Exporting Components for GameObject {0}/{1}", numObjectsExported, objectCount),
    3403.                        ((numObjectsExported / (float)objectCount) * 0.25f) + 0.25f)) {
    3404.                    // cancel silently
    3405.                    return false;
    3406.                }
    3407.  
    3408.                var unityGo = entry.Key;
    3409.                var fbxNode = entry.Value;
    3410.  
    3411.                // try export mesh
    3412.                bool exportedMesh = ExportInstance (unityGo, fbxScene, fbxNode);
    3413.  
    3414.                if (!exportedMesh) {
    3415.                    exportedMesh = ExportMesh (unityGo, fbxNode);
    3416.                }
    3417.  
    3418.                // export camera, but only if no mesh was exported
    3419.                bool exportedCamera = false;
    3420.                if (!exportedMesh) {
    3421.                    exportedCamera = ExportCamera (unityGo, fbxScene, fbxNode);
    3422.                }
    3423.  
    3424.                // export light, but only if no mesh or camera was exported
    3425.                if (!exportedMesh && !exportedCamera) {
    3426.                    ExportLight (unityGo, fbxScene, fbxNode);
    3427.                }
    3428.  
    3429.                ExportConstraints(unityGo, fbxScene, fbxNode);
    3430.            }
    3431.            return true;
    3432.        }
    3433.  
    3434.        /// <summary>
    3435.        /// Checks if the GameObject has animation.
    3436.        /// </summary>
    3437.        /// <returns><c>true</c>, if object has animation, <c>false</c> otherwise.</returns>
    3438.        /// <param name="go">Go.</param>
    3439.        private bool GameObjectHasAnimation(GameObject go){
    3440.            return go != null &&
    3441.                (go.GetComponent<Animator> () ||
    3442.                go.GetComponent<Animation> () ||
    3443.                go.GetComponent<UnityEngine.Playables.PlayableDirector> ());
    3444.        }
    3445.  
    3446.        /// <summary>
    3447.        /// A count of how many GameObjects we are exporting, to have a rough
    3448.        /// idea of how long creating the scene will take.
    3449.        /// </summary>
    3450.        /// <returns>The hierarchy count.</returns>
    3451.        /// <param name="exportSet">Export set.</param>
    3452.        internal int GetHierarchyCount (HashSet<GameObject> exportSet)
    3453.        {
    3454.            int count = 0;
    3455.            Queue<GameObject> queue = new Queue<GameObject> (exportSet);
    3456.            while (queue.Count > 0) {
    3457.                var obj = queue.Dequeue ();
    3458.                var objTransform = obj.transform;
    3459.                foreach (Transform child in objTransform) {
    3460.                    queue.Enqueue (child.gameObject);
    3461.                }
    3462.                count++;
    3463.            }
    3464.            return count;
    3465.        }
    3466.  
    3467.        /// <summary>
    3468.        /// Removes objects that will already be exported anyway.
    3469.        /// E.g. if a parent and its child are both selected, then the child
    3470.        ///      will be removed from the export set.
    3471.        /// </summary>
    3472.        /// <returns>The revised export set</returns>
    3473.        /// <param name="unityExportSet">Unity export set.</param>
    3474.        [SecurityPermission(SecurityAction.LinkDemand)]
    3475.        internal static HashSet<GameObject> RemoveRedundantObjects(IEnumerable<UnityEngine.Object> unityExportSet)
    3476.        {
    3477.            // basically just remove the descendents from the unity export set
    3478.            HashSet<GameObject> toExport = new HashSet<GameObject> ();
    3479.            HashSet<UnityEngine.Object> hashedExportSet = new HashSet<Object> (unityExportSet);
    3480.  
    3481.            foreach(var obj in unityExportSet){
    3482.                var unityGo = GetGameObject (obj);
    3483.  
    3484.                if (unityGo) {
    3485.                    // if any of this nodes ancestors is already in the export set,
    3486.                    // then ignore it, it will get exported already
    3487.                    bool parentInSet = false;
    3488.                    var parent = unityGo.transform.parent;
    3489.                    while (parent != null) {
    3490.                        if (hashedExportSet.Contains (parent.gameObject)) {
    3491.                            parentInSet = true;
    3492.                            break;
    3493.                        }
    3494.                        parent = parent.parent;
    3495.                    }
    3496.  
    3497.                    if (!parentInSet) {
    3498.                        toExport.Add (unityGo);
    3499.                    }
    3500.                }
    3501.            }
    3502.            return toExport;
    3503.        }
    3504.  
    3505.        /// <summary>
    3506.        /// Recursively go through the hierarchy, unioning the bounding box centers
    3507.        /// of all the children, to find the combined bounds.
    3508.        /// </summary>
    3509.        /// <param name="t">Transform.</param>
    3510.        /// <param name="boundsUnion">The Bounds that is the Union of all the bounds on this transform's hierarchy.</param>
    3511.        private static void EncapsulateBounds(Transform t, ref Bounds boundsUnion)
    3512.        {
    3513.            var bounds = GetBounds (t);
    3514.            boundsUnion.Encapsulate (bounds);
    3515.  
    3516.            foreach (Transform child in t) {
    3517.                EncapsulateBounds (child, ref boundsUnion);
    3518.            }
    3519.        }
    3520.  
    3521.        /// <summary>
    3522.        /// Gets the bounds of a transform.
    3523.        /// Looks first at the Renderer, then Mesh, then Collider.
    3524.        /// Default to a bounds with center transform.position and size zero.
    3525.        /// </summary>
    3526.        /// <returns>The bounds.</returns>
    3527.        /// <param name="t">Transform.</param>
    3528.        private static Bounds GetBounds(Transform t)
    3529.        {
    3530.            var renderer = t.GetComponent<Renderer> ();
    3531.            if (renderer) {
    3532.                return renderer.bounds;
    3533.            }
    3534.            var mesh = t.GetComponent<Mesh> ();
    3535.            if (mesh) {
    3536.                return mesh.bounds;
    3537.            }
    3538.            var collider = t.GetComponent<Collider> ();
    3539.            if (collider) {
    3540.                return collider.bounds;
    3541.            }
    3542.            return new Bounds(t.position, Vector3.zero);
    3543.        }
    3544.  
    3545.        /// <summary>
    3546.        /// Finds the center of a group of GameObjects.
    3547.        /// </summary>
    3548.        /// <returns>Center of gameObjects.</returns>
    3549.        /// <param name="gameObjects">Game objects.</param>
    3550.        internal static Vector3 FindCenter(IEnumerable<GameObject> gameObjects)
    3551.        {
    3552.            Bounds bounds = new Bounds();
    3553.            // Assign the initial bounds to first GameObject's bounds
    3554.            // (if we initialize the bounds to 0, then 0 will be part of the bounds)
    3555.            foreach (var go in gameObjects) {
    3556.                var tempBounds = GetBounds (go.transform);
    3557.                bounds = new Bounds (tempBounds.center, tempBounds.size);
    3558.                break;
    3559.            }
    3560.            foreach (var go in gameObjects) {
    3561.                EncapsulateBounds (go.transform, ref bounds);
    3562.            }
    3563.            return bounds.center;
    3564.        }
    3565.  
    3566.        /// <summary>
    3567.        /// Gets the recentered translation.
    3568.        /// </summary>
    3569.        /// <returns>The recentered translation.</returns>
    3570.        /// <param name="t">Transform.</param>
    3571.        /// <param name="center">Center point.</param>
    3572.        internal static Vector3 GetRecenteredTranslation(Transform t, Vector3 center)
    3573.        {
    3574.            return t.position - center;
    3575.        }
    3576.  
    3577.        internal enum TransformExportType { Local, Global, Reset };
    3578.  
    3579.        /// <summary>
    3580.        /// Export all the objects in the set.
    3581.        /// Return the number of objects in the set that we exported.
    3582.        ///
    3583.        /// This refreshes the asset database.
    3584.        /// </summary>
    3585.        [SecurityPermission(SecurityAction.LinkDemand)]
    3586.        internal int ExportAll (
    3587.            IEnumerable<UnityEngine.Object> unityExportSet,
    3588.            Dictionary<GameObject, IExportData> exportData)
    3589.        {
    3590.            exportCancelled = false;
    3591.          
    3592.            m_lastFilePath = LastFilePath;
    3593.  
    3594.            // Export first to a temporary file
    3595.            // in case the export is cancelled.
    3596.            // This way we won't overwrite existing files.
    3597.            try
    3598.            {
    3599.                // create a temp file in the same directory where the fbx will be exported
    3600.                var exportDir = Path.GetDirectoryName(m_lastFilePath);
    3601.                var lastFileName = Path.GetFileName(m_lastFilePath);
    3602.                var tempFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + "_" + lastFileName;
    3603.                m_tempFilePath = Path.Combine(new string[] { exportDir, tempFileName });
    3604.            }
    3605.            catch(IOException){
    3606.                return 0;
    3607.            }
    3608.  
    3609.            if (string.IsNullOrEmpty (m_tempFilePath)) {
    3610.                return 0;
    3611.            }
    3612.  
    3613.            try {
    3614.                bool animOnly = exportData != null && ExportOptions.ModelAnimIncludeOption == ExportSettings.Include.Anim;
    3615.                bool status = false;
    3616.                // Create the FBX manager
    3617.                using (var fbxManager = FbxManager.Create ()) {
    3618.                    // Configure fbx IO settings.
    3619.                    fbxManager.SetIOSettings (FbxIOSettings.Create (fbxManager, Globals.IOSROOT));
    3620.  
    3621.                    // Create the exporter
    3622.                    var fbxExporter = FbxExporter.Create (fbxManager, "Exporter");
    3623.  
    3624.                    // Initialize the exporter.
    3625.                    // fileFormat must be binary if we are embedding textures
    3626.                    int fileFormat = -1;
    3627.                    if (ExportOptions.ExportFormat == ExportSettings.ExportFormat.ASCII)
    3628.                    {
    3629.                        fileFormat = fbxManager.GetIOPluginRegistry().FindWriterIDByDescription("FBX ascii (*.fbx)");
    3630.                    }                    
    3631.                      
    3632.                    status = fbxExporter.Initialize (m_tempFilePath, fileFormat, fbxManager.GetIOSettings ());
    3633.                    // Check that initialization of the fbxExporter was successful
    3634.                    if (!status)
    3635.                        return 0;
    3636.  
    3637.                    // Set compatibility to 2014
    3638.                    fbxExporter.SetFileExportVersion ("FBX201400");
    3639.  
    3640.                    // Set the progress callback.
    3641.                    fbxExporter.SetProgressCallback (ExportProgressCallback);
    3642.  
    3643.                    // Create a scene
    3644.                    var fbxScene = FbxScene.Create (fbxManager, "Scene");
    3645.  
    3646.                    // set up the scene info
    3647.                    FbxDocumentInfo fbxSceneInfo = FbxDocumentInfo.Create (fbxManager, "SceneInfo");
    3648.                    fbxSceneInfo.mTitle = Title;
    3649.                    fbxSceneInfo.mSubject = Subject;
    3650.                    fbxSceneInfo.mAuthor = "Unity Technologies";
    3651.                    fbxSceneInfo.mRevision = "1.0";
    3652.                    fbxSceneInfo.mKeywords = Keywords;
    3653.                    fbxSceneInfo.mComment = Comments;
    3654.                    fbxSceneInfo_Original_ApplicationName.Set(string.Format("Unity {0}", PACKAGE_UI_NAME));
    3655.                    // set last saved to be the same as original, as this is a new file.
    3656.                    fbxSceneInfo.LastSaved_ApplicationName.Set(fbxSceneInfo_Original_ApplicationName.Get());
    3657.  
    3658.                    var version = GetVersionFromReadme();
    3659.                    if(version != null){
    3660.                        fbxSceneInfo_Original_ApplicationVersion.Set(version);
    3661.                        fbxSceneInfo.LastSaved_ApplicationVersion.Set(fbxSceneInfo_Original_ApplicationVersion.Get());
    3662.                    }
    3663.                    fbxScene.SetSceneInfo (fbxSceneInfo);
    3664.  
    3665.                    // Set up the axes (Y up, Z forward, X to the right) and units (centimeters)
    3666.                    // Exporting in centimeters as this is the default unit for FBX files, and easiest
    3667.                    // to work with when importing into Maya or Max
    3668.                    var fbxSettings = fbxScene.GetGlobalSettings ();
    3669.                    fbxSettings.SetSystemUnit (FbxSystemUnit.cm);
    3670.  
    3671.                    // The Unity axis system has Y up, Z forward, X to the right (left handed system with odd parity).
    3672.                    // DirectX has the same axis system, so use this constant.
    3673.                    var unityAxisSystem = FbxAxisSystem.DirectX;
    3674.                    fbxSettings.SetAxisSystem (unityAxisSystem);
    3675.  
    3676.                    // export set of object
    3677.                    FbxNode fbxRootNode = fbxScene.GetRootNode ();
    3678.                    // stores how many objects we have exported, -1 if export was cancelled
    3679.                    int exportProgress = 0;
    3680.                    IEnumerable<GameObject> revisedExportSet = null;
    3681.  
    3682.                    // Total # of objects to be exported
    3683.                    // Used by progress bar to show how many objects will be exported in total
    3684.                    // i.e. exporting x/count...
    3685.                    int count = 0;
    3686.  
    3687.                    // number of object hierarchies being exported.
    3688.                    // Used to figure out exported transforms for root objects.
    3689.                    // i.e. if we are exporting a single hierarchy at local position, then it's root is set to zero,
    3690.                    // but if we are exporting multiple hierarchies at local position, then each hierarchy will be recentered according
    3691.                    // to the center of the bounding box.
    3692.                    int rootObjCount = 0;
    3693.  
    3694.                    if(animOnly){
    3695.                        count = GetAnimOnlyHierarchyCount(exportData);
    3696.                        revisedExportSet = from entry in exportData select entry.Key;
    3697.                        rootObjCount = exportData.Keys.Count;
    3698.                    } else {
    3699.                        var revisedGOSet = RemoveRedundantObjects(unityExportSet);
    3700.                        count = GetHierarchyCount (revisedGOSet);
    3701.                        rootObjCount = revisedGOSet.Count;
    3702.                        revisedExportSet = revisedGOSet;
    3703.                    }
    3704.  
    3705.                    if(count <= 0){
    3706.                        // nothing to export
    3707.                        Debug.LogWarning("Nothing to Export");
    3708.                        return 0;
    3709.                    }
    3710.  
    3711.                    Vector3 center = Vector3.zero;
    3712.                    TransformExportType transformExportType = TransformExportType.Global;
    3713.                    switch(ExportOptions.ObjectPosition){
    3714.                    case ExportSettings.ObjectPosition.LocalCentered:
    3715.                        // one object to export -> move to (0,0,0)
    3716.                        if(rootObjCount == 1){
    3717.                            var tempList = new List<GameObject>(revisedExportSet);
    3718.                            center = tempList[0].transform.position;
    3719.                            break;
    3720.                        }
    3721.                        // more than one object to export -> get bounding center
    3722.                        center = FindCenter(revisedExportSet);
    3723.                        break;
    3724.                    case ExportSettings.ObjectPosition.Reset:
    3725.                        transformExportType = TransformExportType.Reset;
    3726.                        break;
    3727.                    // absolute center -> don't do anything
    3728.                    default:
    3729.                        center = Vector3.zero;
    3730.                        break;
    3731.                    }
    3732.  
    3733.                    foreach (var unityGo in revisedExportSet) {
    3734.                        IExportData data;
    3735.                        if(animOnly && exportData.TryGetValue(unityGo, out data)){
    3736.                            exportProgress = this.ExportAnimationOnly(unityGo, fbxScene, exportProgress, count, center, data, transformExportType);
    3737.                        }
    3738.                        else {
    3739.                            exportProgress = this.ExportTransformHierarchy (unityGo, fbxScene, fbxRootNode,
    3740.                                exportProgress, count, center, transformExportType, ExportOptions.LODExportType);
    3741.                        }
    3742.                        if (exportCancelled || exportProgress < 0) {
    3743.                            Debug.LogWarning ("Export Cancelled");
    3744.                            return 0;
    3745.                        }
    3746.                    }
    3747.  
    3748.                    if(!animOnly){
    3749.                        if(!ExportComponents(fbxScene)){
    3750.                            Debug.LogWarning ("Export Cancelled");
    3751.                            return 0;
    3752.                        }
    3753.                    }
    3754.  
    3755.                    // Export animation if any
    3756.                    if (exportData != null)
    3757.                    {
    3758.                        foreach (var unityGo in revisedExportSet)
    3759.                        {
    3760.                            IExportData iData;
    3761.                            if (!exportData.TryGetValue(unityGo, out iData))
    3762.                            {
    3763.                                continue;
    3764.                            }
    3765.                            var data = iData as AnimationOnlyExportData;
    3766.                            if (data == null)
    3767.                            {
    3768.                                Debug.LogWarningFormat("FBX Exporter: no animation export data found for {0}", unityGo.name);
    3769.                                continue;
    3770.                            }
    3771.                            // export animation
    3772.                            // export default clip first
    3773.                            if (data.defaultClip != null)
    3774.                            {
    3775.                                var defaultClip = data.defaultClip;
    3776.                                ExportAnimationClip(defaultClip, data.animationClips[defaultClip], fbxScene);
    3777.                                data.animationClips.Remove(defaultClip);
    3778.                            }
    3779.  
    3780.                            foreach (var animClip in data.animationClips)
    3781.                            {
    3782.                                ExportAnimationClip(animClip.Key, animClip.Value, fbxScene);
    3783.                            }
    3784.                        }
    3785.                    }
    3786.                    // Set the scene's default camera.
    3787.                    SetDefaultCamera (fbxScene);
    3788.  
    3789.                    // The Maya axis system has Y up, Z forward, X to the left (right handed system with odd parity).
    3790.                    // We need to export right-handed for Maya because ConvertScene (used by Maya and Max importers) can't switch handedness:
    3791.                    // https://forums.autodesk.com/t5/fbx-forum/get-confused-with-fbxaxissystem-convertscene/td-p/4265472
    3792.                    // This needs to be done last so that everything is converted properly.
    3793.                    FbxAxisSystem.MayaYUp.DeepConvertScene(fbxScene);
    3794.  
    3795.                    // Export the scene to the file.
    3796.                    status = fbxExporter.Export (fbxScene);
    3797.  
    3798.                    // cleanup
    3799.                    fbxScene.Destroy ();
    3800.                    fbxExporter.Destroy ();
    3801.                }
    3802.  
    3803.                if (exportCancelled) {
    3804.                    Debug.LogWarning ("Export Cancelled");
    3805.                    return 0;
    3806.                }
    3807.  
    3808.                // make a temporary copy of the original metafile
    3809.                string originalMetafilePath = "";
    3810.                if (ExportOptions.PreserveImportSettings && File.Exists(m_lastFilePath))
    3811.                {
    3812.                    originalMetafilePath = SaveMetafile();
    3813.                }
    3814.  
    3815.                // delete old file, move temp file
    3816.                ReplaceFile();
    3817.  
    3818.                // refresh the database so Unity knows the file's been deleted
    3819.                AssetDatabase.Refresh();
    3820.              
    3821.                // replace with original metafile if specified to
    3822.                if (ExportOptions.PreserveImportSettings && !string.IsNullOrEmpty(originalMetafilePath))
    3823.                {
    3824.                    ReplaceMetafile(originalMetafilePath);
    3825.                }
    3826.  
    3827.                return status == true ? NumNodes : 0;
    3828.            }
    3829.            finally {
    3830.                // You must clear the progress bar when you're done,
    3831.                // otherwise it never goes away and many actions in Unity
    3832.                // are blocked (e.g. you can't quit).
    3833.                EditorUtility.ClearProgressBar ();
    3834.  
    3835.                // make sure the temp file is deleted, no matter
    3836.                // when we return
    3837.                DeleteTempFile();
    3838.            }
    3839.        }
    3840.  
    3841.        static bool exportCancelled = false;
    3842.  
    3843.        static bool ExportProgressCallback (float percentage, string status)
    3844.        {
    3845.            // Convert from percentage to [0,1].
    3846.            // Then convert from that to [0.5,1] because the first half of
    3847.            // the progress bar was for creating the scene.
    3848.            var progress01 = 0.5f * (1f + (percentage / 100.0f));
    3849.  
    3850.            bool cancel = EditorUtility.DisplayCancelableProgressBar (ProgressBarTitle, "Exporting Scene...", progress01);
    3851.  
    3852.            if (cancel) {
    3853.                exportCancelled = true;
    3854.            }
    3855.  
    3856.            // Unity says "true" for "cancel"; FBX wants "true" for "continue"
    3857.            return !cancel;
    3858.        }
    3859.  
    3860.        /// <summary>
    3861.        /// Deletes the file that got created while exporting.
    3862.        /// </summary>
    3863.        private void DeleteTempFile ()
    3864.        {
    3865.            if (!File.Exists (m_tempFilePath)) {
    3866.                return;
    3867.            }
    3868.  
    3869.            try {
    3870.                File.Delete (m_tempFilePath);
    3871.            } catch (IOException) {
    3872.            }
    3873.  
    3874.            if (File.Exists (m_tempFilePath)) {
    3875.                Debug.LogWarning ("Failed to delete file: " + m_tempFilePath);
    3876.            }
    3877.        }
    3878.  
    3879.        /// <summary>
    3880.        /// Replaces the file we are overwriting with
    3881.        /// the temp file that was exported to.
    3882.        /// </summary>
    3883.        private void ReplaceFile ()
    3884.        {
    3885.            if (m_tempFilePath.Equals (m_lastFilePath) || !File.Exists (m_tempFilePath)) {
    3886.                return;
    3887.            }
    3888.            // delete old file
    3889.            try {
    3890.                File.Delete (m_lastFilePath);
    3891.                // delete meta file also
    3892.                File.Delete(m_lastFilePath + ".meta");
    3893.            } catch (IOException) {
    3894.            }
    3895.  
    3896.            if (File.Exists (m_lastFilePath)) {
    3897.                Debug.LogWarning ("Failed to delete file: " + m_lastFilePath);
    3898.            }
    3899.  
    3900.            // rename the new file
    3901.            try{
    3902.                File.Move(m_tempFilePath, m_lastFilePath);
    3903.            } catch(IOException){
    3904.                Debug.LogWarning (string.Format("Failed to move file {0} to {1}", m_tempFilePath, m_lastFilePath));
    3905.            }
    3906.        }
    3907.  
    3908.        private string SaveMetafile()
    3909.        {
    3910.            var tempMetafilePath = Path.GetTempFileName();
    3911.          
    3912.            // get relative path
    3913.            var fbxPath = "Assets/" + ExportSettings.ConvertToAssetRelativePath(m_lastFilePath);
    3914.            if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
    3915.            {
    3916.                Debug.LogWarning(string.Format("Failed to find a valid asset at {0}. Import settings will be reset to default values.", m_lastFilePath));
    3917.                return "";
    3918.            }
    3919.          
    3920.            // get metafile for original fbx file
    3921.            var metafile = fbxPath + ".meta";
    3922.  
    3923. #if UNITY_2019_1_OR_NEWER
    3924.            metafile = VersionControl.Provider.GetAssetByPath(fbxPath).metaPath;
    3925. #endif
    3926.  
    3927.            // save it to a temp file
    3928.            try {
    3929.                File.Copy(metafile, tempMetafilePath, true);
    3930.            } catch(IOException) {
    3931.                Debug.LogWarning (string.Format("Failed to copy file {0} to {1}. Import settings will be reset to default values.", metafile, tempMetafilePath));
    3932.                return "";
    3933.            }
    3934.  
    3935.            return tempMetafilePath;
    3936.        }
    3937.  
    3938.        private void ReplaceMetafile(string metafilePath)
    3939.        {
    3940.            // get relative path
    3941.            var fbxPath = "Assets/" + ExportSettings.ConvertToAssetRelativePath(m_lastFilePath);
    3942.            if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
    3943.            {
    3944.                Debug.LogWarning(string.Format("Failed to find a valid asset at {0}. Import settings will be reset to default values.", m_lastFilePath));
    3945.                return;
    3946.            }
    3947.          
    3948.            // get metafile for new fbx file
    3949.            var metafile = fbxPath + ".meta";
    3950.  
    3951. #if UNITY_2019_1_OR_NEWER
    3952.            metafile = VersionControl.Provider.GetAssetByPath(fbxPath).metaPath;
    3953. #endif
    3954.  
    3955.            // replace metafile with original one in temp file
    3956.            try {
    3957.                File.Copy(metafilePath, metafile, true);
    3958.            } catch(IOException) {
    3959.                Debug.LogWarning (string.Format("Failed to copy file {0} to {1}. Import settings will be reset to default values.", metafilePath, m_lastFilePath));
    3960.            }
    3961.        }
    3962.  
    3963.        /// <summary>
    3964.        /// GameObject/Export Selected Timeline Clip...
    3965.        /// </summary>
    3966.        /// <param name="command"></param>
    3967.        [MenuItem(TimelineClipMenuItemName, false, 31)]
    3968.        static void OnClipContextClick(MenuCommand command)
    3969.        {
    3970.            Object[] selectedObjects = Selection.objects;
    3971.  
    3972.            foreach (Object editorClipSelected in selectedObjects)
    3973.            {
    3974.                // export first selected editor clip.
    3975.                if (IsEditorClip(editorClipSelected)) {
    3976.                    ExportSingleTimelineClip(editorClipSelected);
    3977.                    return;
    3978.                }
    3979.            }
    3980.        }
    3981.  
    3982.        /// <summary>
    3983.        /// Validate the menu item defined by the function OnClipContextClick.
    3984.        /// </summary>
    3985.        [MenuItem(TimelineClipMenuItemName, true, 31)]
    3986.        static bool ValidateOnClipContextClick()
    3987.        {
    3988.            TimelineClip[] selectedClips = TimelineEditor.selectedClips;
    3989.          
    3990.            if(selectedClips != null && selectedClips.Length > 0)
    3991.            {
    3992.                return true;
    3993.            }
    3994.            return false;
    3995.        }
    3996.  
    3997.        internal static bool IsEditorClip(object obj)
    3998.        {
    3999.            if (obj == null)
    4000.                return false;
    4001.  
    4002.            return obj.GetType().Name.Contains("EditorClip");
    4003.        }
    4004.  
    4005.        internal static void ExportSingleTimelineClip(Object editorClipSelected)
    4006.        {
    4007.            UnityEngine.Object[] exportArray = new UnityEngine.Object[] { editorClipSelected };
    4008.            string filename = AnimationOnlyExportData.GetFileName(editorClipSelected);
    4009.            if (ExportSettings.DisplayOptionsWindow)
    4010.            {
    4011.                ExportModelEditorWindow.Init(exportArray, filename, isTimelineAnim: true);
    4012.                return;
    4013.            }
    4014.  
    4015.            var folderPath = ExportSettings.FbxAbsoluteSavePath;
    4016.            var filePath = System.IO.Path.Combine(folderPath, filename + ".fbx");
    4017.  
    4018.            if (System.IO.File.Exists(filePath))
    4019.            {
    4020.                Debug.LogErrorFormat("{0}: Failed to export to {1}, file already exists", PACKAGE_UI_NAME, filePath);
    4021.                return;
    4022.            }
    4023.  
    4024.            var previousInclude = ExportSettings.instance.ExportModelSettings.info.ModelAnimIncludeOption;
    4025.            ExportSettings.instance.ExportModelSettings.info.SetModelAnimIncludeOption(ExportSettings.Include.Anim);
    4026.  
    4027.            if (ExportObjects(filePath, exportArray, ExportSettings.instance.ExportModelSettings.info) != null)
    4028.            {
    4029.                // refresh the asset database so that the file appears in the
    4030.                // asset folder view.
    4031.                AssetDatabase.Refresh();
    4032.            }
    4033.  
    4034.            ExportSettings.instance.ExportModelSettings.info.SetModelAnimIncludeOption(previousInclude);
    4035.        }
    4036.  
    4037.        /// <summary>
    4038.        /// Add a menu item "Export Model..." to a GameObject's context menu.
    4039.        /// </summary>
    4040.        /// <param name="command">Command.</param>
    4041.        [MenuItem (MenuItemName, false, 30)]
    4042.        static void OnContextItem (MenuCommand command)
    4043.        {
    4044.            if (Selection.objects.Length <= 0) {
    4045.                DisplayNoSelectionDialog ();
    4046.                return;
    4047.            }
    4048.            OnExport ();
    4049.        }
    4050.  
    4051.        /// <summary>
    4052.        /// Validate the menu item defined by the function OnContextItem.
    4053.        /// </summary>
    4054.        [MenuItem (MenuItemName, true, 30)]
    4055.        internal static bool OnValidateMenuItem ()
    4056.        {
    4057.            return true;
    4058.        }
    4059.  
    4060.        internal static void DisplayNoSelectionDialog()
    4061.        {
    4062.            UnityEditor.EditorUtility.DisplayDialog (
    4063.                string.Format("{0} Warning", PACKAGE_UI_NAME),
    4064.                "No GameObjects selected for export.",
    4065.                "Ok");
    4066.        }
    4067.  
    4068.        //
    4069.        // export mesh info from Unity
    4070.        //
    4071.        ///<summary>
    4072.        ///Information about the mesh that is important for exporting.
    4073.        ///</summary>
    4074.        internal class MeshInfo
    4075.        {
    4076.            public Mesh mesh;
    4077.  
    4078.            /// <summary>
    4079.            /// Return true if there's a valid mesh information
    4080.            /// </summary>
    4081.            public bool IsValid { get { return mesh; } }
    4082.  
    4083.            /// <summary>
    4084.            /// Gets the vertex count.
    4085.            /// </summary>
    4086.            /// <value>The vertex count.</value>
    4087.            public int VertexCount { get { return mesh.vertexCount; } }
    4088.  
    4089.            /// <summary>
    4090.            /// Gets the triangles. Each triangle is represented as 3 indices from the vertices array.
    4091.            /// Ex: if triangles = [3,4,2], then we have one triangle with vertices vertices[3], vertices[4], and vertices[2]
    4092.            /// </summary>
    4093.            /// <value>The triangles.</value>
    4094.            private int[] m_triangles;
    4095.            public int [] Triangles { get {
    4096.                    if(m_triangles == null) { m_triangles = mesh.triangles; }
    4097.                    return m_triangles;
    4098.                } }
    4099.  
    4100.            /// <summary>
    4101.            /// Gets the vertices, represented in local coordinates.
    4102.            /// </summary>
    4103.            /// <value>The vertices.</value>
    4104.            private Vector3[] m_vertices;
    4105.            public Vector3 [] Vertices { get {
    4106.                    if(m_vertices == null) { m_vertices = mesh.vertices; }
    4107.                    return m_vertices;
    4108.                } }
    4109.  
    4110.            /// <summary>
    4111.            /// Gets the normals for the vertices.
    4112.            /// </summary>
    4113.            /// <value>The normals.</value>
    4114.            private Vector3[] m_normals;
    4115.            public Vector3 [] Normals { get {
    4116.                    if (m_normals == null) {
    4117.                        m_normals = mesh.normals;
    4118.                    }
    4119.                    return m_normals;
    4120.                }
    4121.            }
    4122.  
    4123.            /// <summary>
    4124.            /// Gets the binormals for the vertices.
    4125.            /// </summary>
    4126.            /// <value>The normals.</value>
    4127.            private Vector3[] m_Binormals;
    4128.  
    4129.            public Vector3 [] Binormals {
    4130.                get {
    4131.                    /// NOTE: LINQ
    4132.                    ///    return mesh.normals.Zip (mesh.tangents, (first, second)
    4133.                    ///    => Math.cross (normal, tangent.xyz) * tangent.w
    4134.                    if (m_Binormals == null || m_Binormals.Length == 0)
    4135.                    {
    4136.                        var normals = Normals;
    4137.                        var tangents = Tangents;
    4138.  
    4139.                        if (HasValidNormals() && HasValidTangents()) {
    4140.                            m_Binormals = new Vector3 [normals.Length];
    4141.  
    4142.                            for (int i = 0; i < normals.Length; i++)
    4143.                                m_Binormals = Vector3.Cross (normals ,
    4144.                                    tangents )
    4145.                                * tangents .w;
    4146.                        }
    4147.                    }
    4148.                    return m_Binormals;
    4149.                }
    4150.            }
    4151.  
    4152.            /// <summary>
    4153.            /// Gets the tangents for the vertices.
    4154.            /// </summary>
    4155.            /// <value>The tangents.</value>
    4156.            private Vector4[] m_tangents;
    4157.            public Vector4 [] Tangents { get {
    4158.                    if (m_tangents == null) {
    4159.                        m_tangents = mesh.tangents;
    4160.                    }
    4161.                    return m_tangents;
    4162.                }
    4163.            }
    4164.  
    4165.            /// <summary>
    4166.            /// Gets the vertex colors for the vertices.
    4167.            /// </summary>
    4168.            /// <value>The vertex colors.</value>
    4169.            private Color32 [] m_vertexColors;
    4170.            public Color32 [] VertexColors { get {
    4171.                    if (m_vertexColors == null) {
    4172.                        m_vertexColors = mesh.colors32;
    4173.                    }
    4174.                    return m_vertexColors;
    4175.                }
    4176.            }
    4177.  
    4178.            /// <summary>
    4179.            /// Gets the uvs.
    4180.            /// </summary>
    4181.            /// <value>The uv.</value>
    4182.            private Vector2[] m_UVs;
    4183.            public Vector2 [] UV { get {
    4184.                    if (m_UVs == null) {
    4185.                        m_UVs = mesh.uv;
    4186.                    }
    4187.                    return m_UVs;
    4188.                }
    4189.            }
    4190.  
    4191.            /// <summary>
    4192.            /// The material(s) used.
    4193.            /// Always at least one.
    4194.            /// None are missing materials (we replace missing materials with the default material).
    4195.            /// </summary>
    4196.            public Material[] Materials { get ; private set; }
    4197.  
    4198.            /// <summary>
    4199.            /// Set up the MeshInfo with the given mesh and materials.
    4200.            /// </summary>
    4201.            public MeshInfo (Mesh mesh, Material[] materials)
    4202.            {
    4203.                this.mesh = mesh;
    4204.  
    4205.                this.m_Binormals = null;
    4206.                this.m_vertices = null;
    4207.                this.m_triangles = null;
    4208.                this.m_normals = null;
    4209.                this.m_UVs = null;
    4210.                this.m_vertexColors = null;
    4211.                this.m_tangents = null;
    4212.  
    4213.                if (materials == null) {
    4214.                    this.Materials = new Material[] { DefaultMaterial };
    4215.                } else {
    4216.                    this.Materials = materials.Select (mat => mat ? mat : DefaultMaterial).ToArray ();
    4217.                    if (this.Materials.Length == 0) {
    4218.                        this.Materials = new Material[] { DefaultMaterial };
    4219.                    }
    4220.                }
    4221.            }
    4222.  
    4223.            public bool HasValidNormals(){
    4224.                return Normals != null && Normals.Length > 0;
    4225.            }
    4226.  
    4227.            public bool HasValidBinormals(){
    4228.                return HasValidNormals () &&
    4229.                    HasValidTangents () &&
    4230.                    Binormals != null;
    4231.            }
    4232.  
    4233.            public bool HasValidTangents(){
    4234.                return Tangents != null && Tangents.Length > 0;
    4235.            }
    4236.  
    4237.            public bool HasValidVertexColors(){
    4238.                return VertexColors != null && VertexColors.Length > 0;
    4239.            }
    4240.        }
    4241.  
    4242.        /// <summary>
    4243.        /// Get the GameObject
    4244.        /// </summary>
    4245.        internal static GameObject GetGameObject (Object obj)
    4246.        {
    4247.            if (obj is UnityEngine.Transform) {
    4248.                var xform = obj as UnityEngine.Transform;
    4249.                return xform.gameObject;
    4250.            }
    4251.            else if (obj is UnityEngine.SkinnedMeshRenderer)
    4252.            {
    4253.                var skinnedMeshRenderer = obj as UnityEngine.SkinnedMeshRenderer;
    4254.                return skinnedMeshRenderer.gameObject;
    4255.            }
    4256.            else if (obj is UnityEngine.GameObject)
    4257.            {
    4258.                return obj as UnityEngine.GameObject;
    4259.            }
    4260.            else if (obj is Behaviour)
    4261.            {
    4262.                var behaviour = obj as Behaviour;
    4263.                return behaviour.gameObject;
    4264.            }
    4265.  
    4266.            return null;
    4267.        }
    4268.  
    4269.        /// <summary>
    4270.        /// Map from type (must be a MonoBehaviour) to callback.
    4271.        /// The type safety is lost; the caller must ensure it at run-time.
    4272.        /// </summary>
    4273.        static Dictionary<System.Type, GetMeshForComponent> MeshForComponentCallbacks
    4274.            = new Dictionary<System.Type, GetMeshForComponent>();
    4275.  
    4276.        /// <summary>
    4277.        /// Register a callback to invoke if the object has a component of type T.
    4278.        ///
    4279.        /// This function is prefered over the other mesh callback
    4280.        /// registration methods because it's type-safe, efficient, and
    4281.        /// invocation order between types can be controlled in the UI by
    4282.        /// reordering the components.
    4283.        ///
    4284.        /// It's an error to register a callback for a component that
    4285.        /// already has one, unless 'replace' is set to true.
    4286.        /// </summary>
    4287.        internal static void RegisterMeshCallback<T>(GetMeshForComponent<T> callback, bool replace = false)
    4288.            where T: UnityEngine.MonoBehaviour
    4289.        {
    4290.            // Under the hood we lose type safety, but don't let the user notice!
    4291.            RegisterMeshCallback (typeof(T),
    4292.                (ModelExporter exporter, MonoBehaviour component, FbxNode fbxNode) =>
    4293.                        callback (exporter, (T)component, fbxNode),
    4294.                replace);
    4295.        }
    4296.  
    4297.        /// <summary>
    4298.        /// Register a callback to invoke if the object has a component of type T.
    4299.        ///
    4300.        /// The callback will be invoked with an argument of type T, it's
    4301.        /// safe to downcast.
    4302.        ///
    4303.        /// Normally you'll want to use the generic form, but this one is
    4304.        /// easier to use with reflection.
    4305.        /// </summary>
    4306.        internal static void RegisterMeshCallback(System.Type t,
    4307.                GetMeshForComponent callback,
    4308.                bool replace = false)
    4309.        {
    4310.            if (!t.IsSubclassOf(typeof(MonoBehaviour))) {
    4311.                throw new ModelExportException("Registering a callback for a type that isn't derived from MonoBehaviour: " + t);
    4312.            }
    4313.            if (!replace && MeshForComponentCallbacks.ContainsKey(t)) {
    4314.                throw new ModelExportException("Replacing a callback for type " + t);
    4315.            }
    4316.            MeshForComponentCallbacks[t] = callback;
    4317.        }
    4318.  
    4319.        /// <summary>
    4320.        /// Forget the callback linked to a component of type T.
    4321.        /// </summary>
    4322.        internal static void UnRegisterMeshCallback<T>()
    4323.        {
    4324.            MeshForComponentCallbacks.Remove(typeof(T));
    4325.        }
    4326.  
    4327.        /// <summary>
    4328.        /// Forget the callback linked to a component of type T.
    4329.        /// </summary>
    4330.        internal static void UnRegisterMeshCallback(System.Type t)
    4331.        {
    4332.            MeshForComponentCallbacks.Remove(t);
    4333.        }
    4334.  
    4335.        /// <summary>
    4336.        /// Forget the callbacks linked to components.
    4337.        /// </summary>
    4338.        internal static void UnRegisterAllMeshCallbacks()
    4339.        {
    4340.            MeshForComponentCallbacks.Clear();
    4341.        }
    4342.      
    4343.        static List<GetMeshForObject> MeshForObjectCallbacks = new List<GetMeshForObject>();
    4344.  
    4345.        /// <summary>
    4346.        /// Register a callback to invoke on every GameObject we export.
    4347.        ///
    4348.        /// Avoid doing this if you can use a callback that depends on type.
    4349.        ///
    4350.        /// The GameObject-based callbacks are checked before the
    4351.        /// component-based ones.
    4352.        ///
    4353.        /// Multiple GameObject-based callbacks can be registered; they are
    4354.        /// checked in order of registration.
    4355.        /// </summary>
    4356.        internal static void RegisterMeshObjectCallback(GetMeshForObject callback)
    4357.        {
    4358.            MeshForObjectCallbacks.Add(callback);
    4359.        }
    4360.  
    4361.        /// <summary>
    4362.        /// Forget a GameObject-based callback.
    4363.        /// </summary>
    4364.        internal static void UnRegisterMeshObjectCallback(GetMeshForObject callback)
    4365.        {
    4366.            MeshForObjectCallbacks.Remove(callback);
    4367.        }
    4368.  
    4369.        /// <summary>
    4370.        /// Forget all GameObject-based callbacks.
    4371.        /// </summary>
    4372.        internal static void UnRegisterAllMeshObjectCallbacks()
    4373.        {
    4374.            MeshForObjectCallbacks.Clear();
    4375.        }
    4376.  
    4377.        /// <summary>
    4378.        /// Exports a mesh for a unity gameObject.
    4379.        ///
    4380.        /// This goes through the callback system to find the right mesh and
    4381.        /// allow plugins to substitute their own meshes.
    4382.        /// </summary>
    4383.        [SecurityPermission(SecurityAction.LinkDemand)]
    4384.        bool ExportMesh (GameObject gameObject, FbxNode fbxNode)
    4385.        {
    4386.            // First allow the object-based callbacks to have a hack at it.
    4387.            foreach(var callback in MeshForObjectCallbacks) {
    4388.                if (callback(this, gameObject, fbxNode)) {
    4389.                    return true;
    4390.                }
    4391.            }
    4392.  
    4393.            // Next iterate over components and allow the component-based
    4394.            // callbacks to have a hack at it. This is complicated by the
    4395.            // potential of subclassing. While we're iterating we keep the
    4396.            // first MeshFilter or SkinnedMeshRenderer we find.
    4397.            Component defaultComponent = null;
    4398.            foreach(var component in gameObject.GetComponents<Component>()) {
    4399.                if (!component) {
    4400.                    continue;
    4401.                }
    4402.                var monoBehaviour = component as MonoBehaviour;
    4403.                if (!monoBehaviour) {
    4404.                    // Check for default handling. But don't commit yet.
    4405.                    if (defaultComponent) {
    4406.                        continue;
    4407.                    } else if (component is MeshFilter) {
    4408.                        defaultComponent = component;
    4409.                    } else if (component is SkinnedMeshRenderer) {
    4410.                        defaultComponent = component;
    4411.                    }
    4412.                } else {
    4413.                    // Check if we have custom behaviour for this component type, or
    4414.                    // one of its base classes.
    4415.                    if (!monoBehaviour.enabled) {
    4416.                        continue;
    4417.                    }
    4418.                    var componentType = monoBehaviour.GetType ();
    4419.                    do {
    4420.                        GetMeshForComponent callback;
    4421.                        if (MeshForComponentCallbacks.TryGetValue (componentType, out callback)) {
    4422.                            if (callback (this, monoBehaviour, fbxNode)) {
    4423.                                return true;
    4424.                            }
    4425.                        }
    4426.                        componentType = componentType.BaseType;
    4427.                    } while(componentType.IsSubclassOf (typeof(MonoBehaviour)));
    4428.                }
    4429.            }
    4430.  
    4431.            // If we're here, custom handling didn't work.
    4432.            // Revert to default handling.
    4433.  
    4434.            // if user doesn't want to export mesh colliders, and this gameobject doesn't have a renderer
    4435.            // then don't export it.
    4436.            if (!ExportOptions.ExportUnrendered && (!gameObject.GetComponent<Renderer>() || !gameObject.GetComponent<Renderer>().enabled)) {
    4437.                return false;
    4438.            }
    4439.  
    4440.            var meshFilter = defaultComponent as MeshFilter;
    4441.            if (meshFilter) {
    4442.                var renderer = gameObject.GetComponent<Renderer>();
    4443.                var materials = renderer ? renderer.sharedMaterials : null;
    4444.                return ExportMesh(new MeshInfo(meshFilter.sharedMesh, materials), fbxNode);
    4445.            } else {
    4446.                var smr = defaultComponent as SkinnedMeshRenderer;
    4447.                if (smr) {
    4448.                    var result = ExportSkinnedMesh (gameObject, fbxNode.GetScene (), fbxNode);
    4449.                    if(!result){
    4450.                        // fall back to exporting as a static mesh
    4451.                        var mesh = new Mesh();
    4452.                        smr.BakeMesh(mesh);
    4453.                        var materials = smr.sharedMaterials;
    4454.                        result = ExportMesh(new MeshInfo(mesh, materials), fbxNode);
    4455.                        Object.DestroyImmediate(mesh);
    4456.                    }
    4457.                    return result;
    4458.                }
    4459.            }
    4460.  
    4461.            return false;
    4462.        }
    4463.  
    4464.        /// <summary>
    4465.        /// Number of nodes exported including siblings and decendents
    4466.        /// </summary>
    4467.        internal int NumNodes { get { return MapUnityObjectToFbxNode.Count; } }
    4468.  
    4469.        /// <summary>
    4470.        /// Number of meshes exported
    4471.        /// </summary>
    4472.        internal int NumMeshes { set; get; }
    4473.  
    4474.        /// <summary>
    4475.        /// Number of triangles exported
    4476.        /// </summary>
    4477.        internal int NumTriangles { set; get; }
    4478.  
    4479.        /// <summary>
    4480.        /// Cleans up this class on garbage collection
    4481.        /// </summary>
    4482.        public void Dispose ()
    4483.        {
    4484.            System.GC.SuppressFinalize(this);
    4485.        }
    4486.  
    4487.        internal bool Verbose { get { return ExportSettings.instance.VerboseProperty; } }
    4488.  
    4489.        /// <summary>
    4490.        /// manage the selection of a filename
    4491.        /// </summary>
    4492.        static string LastFilePath { get; set; }
    4493.        private string m_tempFilePath { get; set; }
    4494.        private string m_lastFilePath { get; set; }
    4495.  
    4496.        const string kFBXFileExtension = "fbx";
    4497.          
    4498.        private static string MakeFileName (string basename = "test", string extension = kFBXFileExtension)
    4499.        {
    4500.            return basename + "." + extension;
    4501.        }
    4502.              
    4503.        private static void OnExport ()
    4504.        {
    4505.            GameObject [] selectedGOs = Selection.GetFiltered<GameObject> (SelectionMode.TopLevel);
    4506.  
    4507.            var toExport = ModelExporter.RemoveRedundantObjects(selectedGOs);
    4508.            if (ExportSettings.instance.DisplayOptionsWindow)
    4509.            {
    4510.                ExportModelEditorWindow.Init(System.Linq.Enumerable.Cast<UnityEngine.Object>(toExport), isTimelineAnim: false);
    4511.                return;
    4512.            }
    4513.  
    4514.            var filename = "";
    4515.            if (toExport.Count == 1)
    4516.            {
    4517.                filename = toExport.ToArray()[0].name;
    4518.            }
    4519.            else
    4520.            {
    4521.                filename = "Untitled";
    4522.            }
    4523.  
    4524.            var folderPath = ExportSettings.FbxAbsoluteSavePath;
    4525.            var filePath = System.IO.Path.Combine(folderPath, filename + ".fbx");
    4526.  
    4527.            if (System.IO.File.Exists(filePath))
    4528.            {
    4529.                Debug.LogErrorFormat("{0}: Failed to export to {1}, file already exists", PACKAGE_UI_NAME, filePath);
    4530.                return;
    4531.            }
    4532.  
    4533.            if (ExportObjects(filePath, toExport.ToArray(), ExportSettings.instance.ExportModelSettings.info) != null)
    4534.            {
    4535.                // refresh the asset database so that the file appears in the
    4536.                // asset folder view.
    4537.                AssetDatabase.Refresh();
    4538.            }
    4539.        }
    4540.  
    4541.        [SecurityPermission(SecurityAction.LinkDemand)]
    4542.        internal static string ExportObject (
    4543.            string filePath,
    4544.            UnityEngine.Object root,
    4545.            IExportOptions exportOptions = null
    4546.        )
    4547.        {
    4548.            return ExportObjects(filePath, new Object[] { root }, exportOptions);
    4549.        }
    4550.  
    4551.        /// <summary>
    4552.        /// Exports an array of Unity GameObjects to an FBX file.
    4553.        /// </summary>
    4554.        /// <returns>
    4555.        /// The FBX file path if successful; otherwise returns null.
    4556.        /// </returns>
    4557.        /// <param name="filePath">Absolute file path to use for the FBX file.</param>
    4558.        /// <param name="objects">Array of Unity GameObjects to export.</param>
    4559.        [SecurityPermission(SecurityAction.LinkDemand)]
    4560.        public static string ExportObjects(string filePath, UnityEngine.Object[] objects = null)
    4561.        {
    4562.            return ExportObjects(filePath, objects, exportOptions: null, exportData: null);
    4563.        }
    4564.  
    4565.        /// <summary>
    4566.        /// Exports a single Unity GameObject to an FBX file.
    4567.        /// </summary>
    4568.        /// <returns>
    4569.        /// The FBX file path if successful; otherwise null.
    4570.        /// </returns>
    4571.        /// <param name="filePath">Absolute file path to use for the FBX file.</param>
    4572.        /// <param name="singleObject">The Unity GameObject to export.</param>
    4573.        [SecurityPermission(SecurityAction.LinkDemand)]
    4574.        public static string ExportObject (string filePath, UnityEngine.Object singleObject)
    4575.        {
    4576.            return ExportObjects(filePath, new Object[] {singleObject}, exportOptions: null);
    4577.        }
    4578.  
    4579.        /// <summary>
    4580.        /// Exports a list of GameObjects to an FBX file.
    4581.        /// <para>
    4582.        /// Use the SaveFile panel to allow the user to enter a file name.
    4583.        /// </para>
    4584.        /// </summary>
    4585.        [SecurityPermission(SecurityAction.LinkDemand)]
    4586.        internal static string ExportObjects (
    4587.            string filePath,
    4588.            UnityEngine.Object[] objects = null,
    4589.            IExportOptions exportOptions = null,
    4590.            Dictionary<GameObject, IExportData> exportData = null
    4591.        )
    4592.        {
    4593.            LastFilePath = filePath;
    4594.  
    4595.            using (var fbxExporter = Create ()) {
    4596.                // ensure output directory exists
    4597.                EnsureDirectory (filePath);
    4598.                fbxExporter.ExportOptions = exportOptions;
    4599.  
    4600.                if (objects == null) {
    4601.                    objects = Selection.objects;
    4602.                }
    4603.  
    4604.                if (exportData==null)
    4605.                    exportData = ModelExporter.GetExportData (objects, exportOptions);
    4606.  
    4607.                if (fbxExporter.ExportAll (objects, exportData) > 0) {
    4608.                    string message = string.Format ("Successfully exported: {0}", filePath);
    4609.                    UnityEngine.Debug.Log (message);
    4610.  
    4611.                    return filePath;
    4612.                }
    4613.            }
    4614.            return null;
    4615.        }
    4616.  
    4617.        private static void EnsureDirectory (string path)
    4618.        {
    4619.            //check to make sure the path exists, and if it doesn't then
    4620.            //create all the missing directories.
    4621.            FileInfo fileInfo = new FileInfo (path);
    4622.  
    4623.            if (!fileInfo.Exists) {
    4624.                Directory.CreateDirectory (fileInfo.Directory.FullName);
    4625.            }
    4626.        }
    4627.  
    4628.        /// <summary>
    4629.        /// Removes the diacritics (i.e. accents) from letters.
    4630.        /// e.g. é becomes e
    4631.        /// </summary>
    4632.        /// <returns>Text with accents removed.</returns>
    4633.        /// <param name="text">Text.</param>
    4634.        private static string RemoveDiacritics(string text)
    4635.        {
    4636.            var normalizedString = text.Normalize(System.Text.NormalizationForm.FormD);
    4637.            var stringBuilder = new System.Text.StringBuilder();
    4638.  
    4639.            foreach (var c in normalizedString)
    4640.            {
    4641.                var unicodeCategory = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(c);
    4642.                if (unicodeCategory != System.Globalization.UnicodeCategory.NonSpacingMark)
    4643.                {
    4644.                    stringBuilder.Append(c);
    4645.                }
    4646.            }
    4647.  
    4648.            return stringBuilder.ToString().Normalize(System.Text.NormalizationForm.FormC);
    4649.        }
    4650.  
    4651.        private static string ConvertToMayaCompatibleName(string name)
    4652.        {
    4653.            if (string.IsNullOrEmpty(name)) {
    4654.                return InvalidCharReplacement.ToString();
    4655.            }
    4656.            string newName = RemoveDiacritics (name);
    4657.  
    4658.            if (char.IsDigit (newName [0])) {
    4659.                newName = newName.Insert (0, InvalidCharReplacement.ToString());
    4660.            }
    4661.  
    4662.            for (int i = 0; i < newName.Length; i++) {
    4663.                if (!char.IsLetterOrDigit (newName, i)) {
    4664.                    if (i < newName.Length-1 && newName [i] == MayaNamespaceSeparator) {
    4665.                        continue;
    4666.                    }
    4667.                    newName = newName.Replace (newName [i], InvalidCharReplacement);
    4668.                }
    4669.            }
    4670.            return newName;
    4671.        }
    4672.  
    4673.        internal static string ConvertToValidFilename(string filename)
    4674.        {
    4675.            return System.Text.RegularExpressions.Regex.Replace (filename,
    4676.                RegexCharStart + new string(Path.GetInvalidFileNameChars()) + RegexCharEnd,
    4677.                InvalidCharReplacement.ToString()
    4678.            );
    4679.        }
    4680.    }
    4681. }[/i][/i]
     
    Last edited: Mar 20, 2023
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,491
    Please don't post code as a wall of plain text, nobody can really do anything with it.

    If you have to post code then please use code-tags. You can edit your post for this btw.

    Thanks.
     
  4. Jefflinzer

    Jefflinzer

    Joined:
    Mar 16, 2023
    Posts:
    4
    Updated to code thanks. Anyways how do I fix the fbx exporter NullReferenceException?

    do I have to upload the model link for someone to download so that they could screenshot and share their experiences?