示例#1
0
            public int Main(KeyData keyData, string testName, string path)
            {
                if (!keyData.targetObject)
                {
                    keyData.targetObject = CreateTargetObject(testName, keyData.componentType);
                }

                Animation animOrig = keyData.targetObject.AddComponent(typeof(Animation)) as Animation;

                AnimationClip animClipOriginal   = new AnimationClip();
                var           animCurvesOriginal = new AnimationCurve[keyData.NumProperties];

                animClipOriginal.legacy = true;
                animClipOriginal.name   = "anim_" + testName;

                for (int id = 0; id < keyData.NumProperties; id++)
                {
                    // initialize keys
                    Keyframe [] keys = new Keyframe [keyData.NumKeys];

                    for (int idx = 0; idx < keyData.NumKeys; idx++)
                    {
                        keys [idx].time  = keyData.keyTimes [idx];
                        keys [idx].value = keyData.GetKeyValues(id) [idx];
                    }
                    animCurvesOriginal[id] = new AnimationCurve(keys);

                    animClipOriginal.SetCurve("", keyData.componentType, keyData.GetPropertyName(id), animCurvesOriginal[id]);
                }

                animOrig.AddClip(animClipOriginal, animClipOriginal.name);
                animOrig.clip = animClipOriginal;

                // NOTE: when we first cached the curves the tangents wheren't set.
                foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animOrig.clip))
                {
                    int id = keyData.GetIndexOf(curveBinding.propertyName);
                    if (id == -1)
                    {
                        continue;
                    }

                    animCurvesOriginal[id] = AnimationUtility.GetEditorCurve(animOrig.clip, curveBinding);
                }

                // TODO: add extra parent so that we can test export/import of transforms
                var goRoot = new GameObject();

                goRoot.name = "Root_" + testName;
                keyData.targetObject.transform.parent = goRoot.transform;

                //export the object
                var exportedFilePath = ModelExporter.ExportObject(path, goRoot, exportOptions);

                Assert.That(exportedFilePath, Is.EqualTo(path));

                // TODO: Uni-34492 change importer settings of (newly exported model)
                // so that it's not resampled and it is legacy animation
                AnimTester.ConfigureImportSettings(path, keyData.importSettings);

                // create a scene GO so we can compare.
                #if DEBUG_UNITTEST
                GameObject prefabGO = AssetDatabase.LoadMainAssetAtPath(path) as GameObject;
                GameObject sceneGO  = Object.Instantiate(prefabGO, keyData.targetObject.transform.localPosition, keyData.targetObject.transform.localRotation);
                sceneGO.name = "Imported_" + testName;
                #endif

                //acquire imported object from exported file
                AnimationClip animClipImported = GetClipFromFbx(path);

                ClipPropertyTest(animClipOriginal, animClipImported);

                int result = 0;

                foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animClipImported))
                {
                    AnimationCurve animCurveImported = AnimationUtility.GetEditorCurve(animClipImported, curveBinding);
                    Assert.That(animCurveImported, Is.Not.Null);

                    string propertyBinding = curveBinding.propertyName;
                    int    id = keyData.GetIndexOf(propertyBinding);

                    bool hasQuatBinding =
                        MapEulerToQuaternionPropertyName.TryGetValue(propertyBinding, out propertyBinding);

                    bool isRotation = AnimationTestDataClass.m_rotationEulerNames.Contains(curveBinding.propertyName) ||
                                      AnimationTestDataClass.m_rotationQuaternionNames.Contains(curveBinding.propertyName);

                    if (id == -1)
                    {
                        id = keyData.GetIndexOf(propertyBinding);
                    }

                    #if DEBUG_UNITTEST
                    Debug.Log(string.Format("propertyBinding={0} mappedBinding={1} id={2}", curveBinding.propertyName, propertyBinding, id));
                    #endif

                    if (id != -1)
                    {
                        if (keyData.compareOriginalKeys)
                        {
                            // NOTE: we cannot compare the keys that exported quaternion but are imported as euler.
                            if (!hasQuatBinding)
                            {
                                // compare against original keydata
                                KeysTest(keyData.keyTimes, keyData.GetKeyValues(id), animCurveImported, curveBinding.propertyName);

                                // compare against original animCurve
                                KeysTest(animCurvesOriginal[id], animCurveImported, curveBinding.propertyName, keyComparer);
                            }
                            else
                            {
                                // compare by sampled keyvalues against original keydata
                                KeyValuesTest(keyData.keyTimes, keyData.GetAltKeyValues(id), animCurveImported, curveBinding.propertyName, isRotation);
                            }
                        }
                        else
                        {
                            // compare by sampled keyvalues against original animCurve
                            KeyValuesTest(animCurvesOriginal[id], animCurveImported, curveBinding.propertyName, isRotation);
                        }
                        result++;
                    }
                }

                return(result);
            }
示例#2
0
        /// <summary>
        /// Return an FBX asset that corresponds to 'toConvert'.
        ///
        /// If 'toConvert' is the root of an FBX asset, return it.
        ///
        /// If it's an instance in a scene the points to the root of an FBX
        /// asset, return that asset.
        ///
        /// Otherwise, export according to the paths and options, and
        /// return the new asset.
        /// </summary>
        /// <param name="toConvert">GameObject for which we want an fbx asset</param>
        /// <param name="fbxDirectoryFullPath">Export will choose an
        /// appropriate filename in this directory. Ignored if fbxFullPath is
        /// set. Ignored if toConvert is an fbx asset or an instance of an
        /// fbx.</param>
        /// <param name="fbxDirectoryFullPath">Export will create this
        /// file. Overrides fbxDirectoryFullPath. Ignored if toConvert is an
        /// fbx asset or an instance of an fbx.</param>
        /// <returns>The root of a model prefab asset.</returns>
        internal static GameObject GetOrCreateFbxAsset(GameObject toConvert,
                                                       string fbxDirectoryFullPath = null,
                                                       string fbxFullPath          = null,
                                                       ConvertToPrefabSettingsSerialize exportOptions = null)
        {
            if (toConvert == null)
            {
                throw new System.ArgumentNullException("toConvert");
            }

            var mainAsset = GetFbxAssetOrNull(toConvert);

            if (mainAsset)
            {
                return(mainAsset);
            }

            if (string.IsNullOrEmpty(fbxFullPath))
            {
                // Generate a unique filename.
                if (string.IsNullOrEmpty(fbxDirectoryFullPath))
                {
                    fbxDirectoryFullPath = UnityEditor.Formats.Fbx.Exporter.ExportSettings.FbxAbsoluteSavePath;
                }
                else
                {
                    fbxDirectoryFullPath = Path.GetFullPath(fbxDirectoryFullPath);
                }
                var fbxBasename = ModelExporter.ConvertToValidFilename(toConvert.name + ".fbx");

                fbxFullPath = Path.Combine(fbxDirectoryFullPath, fbxBasename);
                if (File.Exists(fbxFullPath))
                {
                    fbxFullPath = IncrementFileName(fbxDirectoryFullPath, fbxFullPath);
                }
            }
            var projectRelativePath = ExportSettings.GetProjectRelativePath(fbxFullPath);

            // Make sure that the object names in the hierarchy are unique.
            // The import back in to Unity would do this automatically but
            // we prefer to control it so that the Maya artist can see the
            // same names as exist in Unity.
            EnforceUniqueNames(new GameObject[] { toConvert });

            // Export to FBX. It refreshes the database.
            {
                var fbxActualPath = ModelExporter.ExportObject(
                    fbxFullPath, toConvert,
                    exportOptions != null ? exportOptions : new ConvertToPrefabSettingsSerialize()
                    );
                if (fbxActualPath != fbxFullPath)
                {
                    throw new ConvertToLinkedPrefabException("Failed to convert " + toConvert.name);
                }
            }

            // Replace w Model asset. LoadMainAssetAtPath wants a path
            // relative to the project, not relative to the assets folder.
            var unityMainAsset = AssetDatabase.LoadMainAssetAtPath(projectRelativePath) as GameObject;

            if (!unityMainAsset)
            {
                throw new ConvertToLinkedPrefabException("Failed to convert " + toConvert.name);
            }

            // Copy the mesh/materials from the FBX
            UpdateFromSourceRecursive(toConvert, unityMainAsset);

            return(unityMainAsset);
        }
        public static void PackAnimations(string path, GameObject meshObject)
        {
            List <AnimationClip> clips = new List <AnimationClip>();
            List <ModelImporterClipAnimation> clipDatas = new List <ModelImporterClipAnimation>();

            var info     = new DirectoryInfo(path);
            var fileInfo = info.GetFiles();

            foreach (var file in fileInfo)
            {
                if (file.Extension == ".fbx" || file.Extension == ".FBX")
                {
                    string        relativePath = path + "/" + file.Name;
                    AnimationClip clip         = AssetDatabase.LoadAssetAtPath <AnimationClip>(relativePath);
                    //Debug.Log(relativePath);
                    if (clip != null)
                    {
                        clips.Add(clip);
                    }
                    if (alwaysFalse)
                    {
                        ModelImporter importer = AssetDatabase.LoadAssetAtPath <ModelImporter>(relativePath);
                        if (importer != null)
                        {
                            clipDatas.AddRange(importer.clipAnimations);
                        }
                    }
                }
            }
            Debug.Log(clips.Count);

            //---getting all curves/keyframes---
            if (alwaysFalse)
            {
                AnimationClip collectiveClip = new AnimationClip();
                Dictionary <string, Container> collectiveCurves = new Dictionary <string, Container>();
                float timeOffset = 0f;
                float maxTime    = 0f;
                for (int i = 0; i < clips.Count; i++)
                {
                    var bindings = AnimationUtility.GetCurveBindings(clips[i]);
                    Debug.Log("Clip[i] Count of bindings: " + bindings.Length);
                    foreach (var binding in bindings)
                    {
                        Debug.Log(binding.propertyName + ": Path: " + binding.path);
                        if (!collectiveCurves.ContainsKey(binding.propertyName))
                        {
                            collectiveCurves.Add(binding.propertyName, new Container(binding.type, binding.path));
                        }
                        var curveToWriteTo  = collectiveCurves[binding.propertyName];
                        var curveToReadFrom = AnimationUtility.GetEditorCurve(clips[i], binding);
                        //Debug.Log("Keys: " + curve.keys.Length);
                        maxTime = Mathf.Max(maxTime, curveToReadFrom.GetLastKey().time);
                        for (int frame = 0; frame < curveToReadFrom.length; frame++)
                        {
                            var keyFrame = curveToReadFrom.keys[frame];
                            keyFrame.time += timeOffset;
                            curveToWriteTo.curve.AddKey(keyFrame);
                        }
                        //collectiveClip.SetCurve()
                    }
                    timeOffset += maxTime;
                }

                foreach (var kvp in collectiveCurves)
                {
                    collectiveClip.SetCurve(kvp.Value.path, kvp.Value.typeOf, kvp.Key, kvp.Value.curve);
                }
            }

            //---instantiating go and adding animation component
            GameObject clone = Instantiate(meshObject);

            clone.name = meshObject.name;
            if (alwaysFalse)
            {
                var anim = clone.AddComponent <Animation>();
                AnimationUtility.SetAnimationClips(anim, clips.ToArray());
            }
            if (true)
            {
                var anim = clone.GetComponent <Animator>();
                if (!anim)
                {
                    anim = clone.AddComponent <Animator>();
                }
                var controller = AnimatorController.CreateAnimatorControllerAtPath(path + "/temp.controller");
                foreach (var clip in clips)
                {
                    controller.AddMotion(clip);
                }

                anim.runtimeAnimatorController = controller;
            }

            if (alwaysFalse)
            {
                var fbxManager = FbxManager.Create();
                var settings   = fbxManager.GetIOSettings();
                var exporter   = FbxExporter.Create(fbxManager, "name");
                //exporter.
            }
            if (true)
            {
                ModelExporter.ExportObject(path + "/combined.fbx", clone);
            }

            if (alwaysFalse)
            {
                using (FbxManager fbxManager = FbxManager.Create())
                {
                    // configure IO settings.
                    var settings = FbxIOSettings.Create(fbxManager, Globals.IOSROOT);
                    fbxManager.SetIOSettings(settings);
                    // Export the scene
                    using (FbxExporter exporter = FbxExporter.Create(fbxManager, "myExporter"))
                    {
                        // Initialize the exporter.
                        bool status = exporter.Initialize("combindObjects", -1, fbxManager.GetIOSettings());

                        // Create a new scene to export
                        FbxScene scene = FbxScene.Create(fbxManager, "myScene");
                        //FbxObject obj = FbxObject.Create(fbxManager, "combinedThings");
                        // Export the scene to the file.
                        exporter.Export(scene);
                    }
                }
            }


            //AssetDatabase.CreateAsset(collectiveClip, path + "/combined.anim");
            //AssetDatabase.SaveAssets();

            //Animation newAnimation = GetComponent<Animation>();
            //Debug.Log(newAnimation.name);
            //AnimationUtility.SetAnimationClips(newAnimation, clips.ToArray());
            //AssetDatabase.CreateAsset(newAnimation, path + "/combined.fbx");
            //AssetDatabase.CreateAsset(clips, path + "/testasset.fbx");
        }
示例#4
0
        public void TestExporterCallbacks()
        {
            var tree   = CreateHierarchy();
            var tester = new CallbackTester(tree.transform, GetRandomFbxFilePath());
            var n      = tree.GetComponentsInChildren <Transform>().Length;

            // No callbacks registered => no calls.
            tester.Verify(0, 0);

            ModelExporter.RegisterMeshObjectCallback(tester.CallbackForObject);
            ModelExporter.RegisterMeshCallback <FbxPrefab>(tester.CallbackForFbxPrefab);

            // No fbprefab => no component calls, but every object called.
            tester.Verify(0, n);

            // Add a fbxprefab, check every object called and the prefab called.
            tree.transform.Find("Parent1").gameObject.AddComponent <FbxPrefab>();
            tester.Verify(1, n);

            // Make the object report it's replacing everything => no component calls.
            tester.Verify(0, n, objectResult: true);

            // Make sure we can't register for a component twice, but we can
            // for an object.  Register twice for an object means two calls per
            // object.
            Assert.That(() => ModelExporter.RegisterMeshCallback <FbxPrefab>(tester.CallbackForFbxPrefab),
                        Throws.Exception);
            ModelExporter.RegisterMeshObjectCallback(tester.CallbackForObject);
            tester.Verify(1, 2 * n);

            // Register twice but return true => only one call per object.
            tester.Verify(0, n, objectResult: true);

            // Unregister once => only one call per object, and no more for the prefab.
            ModelExporter.UnRegisterMeshCallback <FbxPrefab>();
            ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject);
            tester.Verify(0, n);

            // Legal to unregister if already unregistered.
            ModelExporter.UnRegisterMeshCallback <FbxPrefab>();
            tester.Verify(0, n);

            // Register same callback twice gets back to original state.
            ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject);
            tester.Verify(0, 0);

            // Legal to unregister if already unregistered.
            ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject);
            tester.Verify(0, 0);

            ///////////////////////
            // Test that the callbacks not only get called, but can replace
            // meshes.
            var sphere     = GameObject.CreatePrimitive(PrimitiveType.Capsule);
            var sphereMesh = sphere.GetComponent <MeshFilter>().sharedMesh;
            var cube       = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var cubeMesh   = cube.GetComponent <MeshFilter>().sharedMesh;

            // Pick on the fbxprefab to output a sphere in place of the cube.
            // the fbxprefab is on parent1.
            string     filename;
            GameObject asset;
            Mesh       assetMesh;

            GetMeshForComponent <FbxPrefab> prefabCallback =
                (ModelExporter exporter, FbxPrefab component, FbxNode node) => {
                exporter.ExportMesh(sphereMesh, node);
                return(true);
            };

            ModelExporter.RegisterMeshCallback(prefabCallback);
            filename = GetRandomFbxFilePath();
            ModelExporter.ExportObject(filename, tree);
            ModelExporter.UnRegisterMeshCallback <FbxPrefab>();

            asset     = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
            assetMesh = asset.transform.Find("Parent1").GetComponent <MeshFilter>().sharedMesh;
            Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length);
            assetMesh = asset.transform.Find("Parent2").GetComponent <MeshFilter>().sharedMesh;
            Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length);

            // Try again, but this time pick on Parent2 by name (different just
            // to make sure we don't pass if the previous pass didn't
            // actually unregister).
            filename = GetRandomFbxFilePath();
            GetMeshForObject callback =
                (ModelExporter exporter, GameObject gameObject, FbxNode node) => {
                if (gameObject.name == "Parent2")
                {
                    exporter.ExportMesh(sphereMesh, node);
                    return(true);
                }
                else
                {
                    return(false);
                }
            };

            ModelExporter.RegisterMeshObjectCallback(callback);
            ModelExporter.ExportObject(filename, tree);
            ModelExporter.UnRegisterMeshObjectCallback(callback);

            asset     = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
            assetMesh = asset.transform.Find("Parent1").GetComponent <MeshFilter>().sharedMesh;
            Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length);
            assetMesh = asset.transform.Find("Parent2").GetComponent <MeshFilter>().sharedMesh;
            Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length);
        }
示例#5
0
        public void TestBasics()
        {
            Assert.That(!string.IsNullOrEmpty(ModelExporter.GetVersionFromReadme()));

            // Test GetOrCreateLayer
            using (var fbxManager = FbxManager.Create()) {
                var fbxMesh = FbxMesh.Create(fbxManager, "name");
                var layer0  = ModelExporter.GetOrCreateLayer(fbxMesh);
                Assert.That(layer0, Is.Not.Null);
                Assert.That(ModelExporter.GetOrCreateLayer(fbxMesh), Is.EqualTo(layer0));
                var layer5 = ModelExporter.GetOrCreateLayer(fbxMesh, layer: 5);
                Assert.That(layer5, Is.Not.Null);
                Assert.That(layer5, Is.Not.EqualTo(layer0));
            }

            // Test axis conversion: a x b in left-handed is the same as b x a
            // in right-handed (that's why we need to flip the winding order).
            var a         = new Vector3(1, 0, 0);
            var b         = new Vector3(0, 0, 1);
            var crossLeft = Vector3.Cross(a, b);

            Assert.That(ModelExporter.DefaultMaterial);

            // Test non-static functions.
            using (var fbxManager = FbxManager.Create()) {
                var fbxScene = FbxScene.Create(fbxManager, "scene");
                var fbxNode  = FbxNode.Create(fbxScene, "node");
                var exporter = new ModelExporter();

                // Test ExportMaterial: it exports and it re-exports
                bool result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode);
                Assert.IsTrue(result);
                var fbxMaterial = fbxNode.GetMaterial(0);
                Assert.That(fbxMaterial, Is.Not.Null);

                result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode);
                var fbxMaterial2 = fbxNode.GetMaterial(1);
                Assert.AreEqual(fbxMaterial, fbxMaterial2);

                // Test ExportTexture: it finds the same texture for the default-material (it doesn't create a new one)
                var fbxMaterialNew = FbxSurfaceLambert.Create(fbxScene, "lambert");
                exporter.ExportTexture(ModelExporter.DefaultMaterial, "_MainTex",
                                       fbxMaterialNew, FbxSurfaceLambert.sBump);
                Assert.AreEqual(
                    fbxMaterial.FindProperty(FbxSurfaceLambert.sDiffuse).GetSrcObject(),
                    fbxMaterialNew.FindProperty(FbxSurfaceLambert.sBump).GetSrcObject()
                    );

                // Test ExportMesh: make sure we exported a mesh with welded vertices.
                var cube     = GameObject.CreatePrimitive(PrimitiveType.Cube);
                var cubeNode = FbxNode.Create(fbxScene, "cube");
                exporter.ExportMesh(cube.GetComponent <MeshFilter>().sharedMesh, cubeNode);
                Assert.That(cubeNode.GetMesh(), Is.Not.Null);
                Assert.That(cubeNode.GetMesh().GetControlPointsCount(), Is.EqualTo(8));
            }

            // Test exporting a skinned-mesh. Make sure it doesn't leak (it did at one point)
            {
                var cube      = GameObject.CreatePrimitive(PrimitiveType.Cube);
                var character = new GameObject();
                var smr       = character.AddComponent <SkinnedMeshRenderer>();
                smr.sharedMesh = cube.GetComponent <MeshFilter>().sharedMesh;
                var meshCount = Object.FindObjectsOfType <Mesh>().Length;
                ModelExporter.ExportObject(GetRandomFbxFilePath(), character);
                Assert.AreEqual(meshCount, Object.FindObjectsOfType <Mesh>().Length);
            }

            // Test euler to quaternion conversion
            {
                // EulerToQuaternionZXY
                var v         = new Vector3(50, 45, 190);
                var quat      = ModelExporter.EulerToQuaternionZXY(v);
                var unityQuat = Quaternion.Euler(v);
                Assert.That((float)quat.X, Is.EqualTo(unityQuat.x));
                Assert.That((float)quat.Y, Is.EqualTo(unityQuat.y));
                Assert.That((float)quat.Z, Is.EqualTo(unityQuat.z));
                Assert.That((float)quat.W, Is.EqualTo(unityQuat.w));

                // EulerToQuaternionXYZ
                var fbxV    = new FbxVector4(v.x, v.y, v.z);
                var xyzQuat = ModelExporter.EulerToQuaternionXYZ(fbxV);

                // get the vector from the quaternion
                FbxAMatrix m = new FbxAMatrix();
                m.SetR(fbxV);
                var actualQuat = m.GetQ();

                // since this quaternion is XYZ instead of ZXY, it should not match the quaternion
                // created with EulerToQuaternionZXY
                Assert.That(xyzQuat, Is.Not.EqualTo(quat));
                Assert.That(xyzQuat, Is.EqualTo(actualQuat));
            }
        }
示例#6
0
        public void TestBasics()
        {
            Assert.That(!string.IsNullOrEmpty(ModelExporter.GetVersionFromReadme()));

            // Test GetOrCreateLayer
            using (var fbxManager = FbxManager.Create()) {
                var fbxMesh = FbxMesh.Create(fbxManager, "name");
                var layer0  = ModelExporter.GetOrCreateLayer(fbxMesh);
                Assert.That(layer0, Is.Not.Null);
                Assert.That(ModelExporter.GetOrCreateLayer(fbxMesh), Is.EqualTo(layer0));
                var layer5 = ModelExporter.GetOrCreateLayer(fbxMesh, layer: 5);
                Assert.That(layer5, Is.Not.Null);
                Assert.That(layer5, Is.Not.EqualTo(layer0));
            }

            // Test axis conversion: a x b in left-handed is the same as b x a
            // in right-handed (that's why we need to flip the winding order).
            var a         = new Vector3(1, 0, 0);
            var b         = new Vector3(0, 0, 1);
            var crossLeft = Vector3.Cross(a, b);

            var afbx = ModelExporter.ConvertToRightHanded(a);
            var bfbx = ModelExporter.ConvertToRightHanded(b);

            Assert.AreEqual(ModelExporter.ConvertToRightHanded(crossLeft), bfbx.CrossProduct(afbx));

            // Test scale conversion. Nothing complicated here...
            var afbxPosition = ModelExporter.ConvertToRightHanded(a, ModelExporter.UnitScaleFactor);

            Assert.AreEqual(100, afbxPosition.Length());

            // Test rotation conversion.
            var q         = Quaternion.Euler(new Vector3(0, 90, 0));
            var fbxAngles = ModelExporter.ConvertQuaternionToXYZEuler(q);

            Assert.AreEqual(fbxAngles.X, 0);
            Assert.That(fbxAngles.Y, Is.InRange(-90.001, -89.999));
            Assert.AreEqual(fbxAngles.Z, 0);

            Assert.That(ModelExporter.DefaultMaterial);

            // Test non-static functions.
            using (var fbxManager = FbxManager.Create()) {
                var fbxScene = FbxScene.Create(fbxManager, "scene");
                var fbxNode  = FbxNode.Create(fbxScene, "node");
                var exporter = new ModelExporter();

                // Test ExportMaterial: it exports and it re-exports
                bool result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode);
                Assert.IsTrue(result);
                var fbxMaterial = fbxNode.GetMaterial(0);
                Assert.That(fbxMaterial, Is.Not.Null);

                result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode);
                var fbxMaterial2 = fbxNode.GetMaterial(1);
                Assert.AreEqual(fbxMaterial, fbxMaterial2);

                // Test ExportTexture: it finds the same texture for the default-material (it doesn't create a new one)
                var fbxMaterialNew = FbxSurfaceLambert.Create(fbxScene, "lambert");
                exporter.ExportTexture(ModelExporter.DefaultMaterial, "_MainTex",
                                       fbxMaterialNew, FbxSurfaceLambert.sBump);
                Assert.AreEqual(
                    fbxMaterial.FindProperty(FbxSurfaceLambert.sDiffuse).GetSrcObject(),
                    fbxMaterialNew.FindProperty(FbxSurfaceLambert.sBump).GetSrcObject()
                    );

                // Test ExportMesh: make sure we exported a mesh with welded vertices.
                var cube     = GameObject.CreatePrimitive(PrimitiveType.Cube);
                var cubeNode = FbxNode.Create(fbxScene, "cube");
                exporter.ExportMesh(cube.GetComponent <MeshFilter>().sharedMesh, cubeNode);
                Assert.That(cubeNode.GetMesh(), Is.Not.Null);
                Assert.That(cubeNode.GetMesh().GetControlPointsCount(), Is.EqualTo(8));
            }

            // Test exporting a skinned-mesh. Make sure it doesn't leak (it did at one point)
            {
                var cube      = GameObject.CreatePrimitive(PrimitiveType.Cube);
                var character = new GameObject();
                var smr       = character.AddComponent <SkinnedMeshRenderer>();
                smr.sharedMesh = cube.GetComponent <MeshFilter>().sharedMesh;
                var meshCount = Object.FindObjectsOfType <Mesh>().Length;
                ModelExporter.ExportObject(GetRandomFbxFilePath(), character);
                Assert.AreEqual(meshCount, Object.FindObjectsOfType <Mesh>().Length);
            }
        }