diff --git a/Assets/FbxExporters/Editor/ConvertToModel.cs b/Assets/FbxExporters/Editor/ConvertToModel.cs index 129f814c4..ba94abfd5 100644 --- a/Assets/FbxExporters/Editor/ConvertToModel.cs +++ b/Assets/FbxExporters/Editor/ConvertToModel.cs @@ -168,7 +168,7 @@ public static GameObject Convert ( // Export to FBX. It refreshes the database. { - var fbxActualPath = ModelExporter.ExportObject (fbxFullPath, toConvert); + var fbxActualPath = ModelExporter.ExportObject (fbxFullPath, toConvert, lodExportType: EditorTools.ExportSettings.LODExportType.All); if (fbxActualPath != fbxFullPath) { throw new System.Exception ("Failed to convert " + toConvert.name); } diff --git a/Assets/FbxExporters/Editor/FbxExportSettings.cs b/Assets/FbxExporters/Editor/FbxExportSettings.cs index 75c575292..b2269ae65 100644 --- a/Assets/FbxExporters/Editor/FbxExportSettings.cs +++ b/Assets/FbxExporters/Editor/FbxExportSettings.cs @@ -62,6 +62,11 @@ public override void OnInspectorGUI() { exportSettings.ExportFormatSelection = EditorGUILayout.Popup(exportSettings.ExportFormatSelection, new string[]{"Binary", "ASCII"}); GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(new GUIContent("LOD Export:", "Select which LOD to export."), GUILayout.Width(LabelWidth - FieldOffset)); + exportSettings.lodExportType = (ExportSettings.LODExportType)EditorGUILayout.Popup((int)exportSettings.lodExportType, new string[]{"All", "Highest", "Lowest"}); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Export Path:", @@ -442,6 +447,15 @@ public static string[] DCCVendorLocations public int selectedDCCApp = 0; + [SerializeField] + public LODExportType lodExportType = LODExportType.All; + + public enum LODExportType { + All = 0, + Highest = 1, + Lowest = 2 + } + /// /// The path where Convert To Model will save the new fbx and prefab. /// diff --git a/Assets/FbxExporters/Editor/FbxExporter.cs b/Assets/FbxExporters/Editor/FbxExporter.cs index e0d921b04..0807f463f 100644 --- a/Assets/FbxExporters/Editor/FbxExporter.cs +++ b/Assets/FbxExporters/Editor/FbxExporter.cs @@ -7,6 +7,7 @@ using System.Linq; using UnityEngine.Playables; using UnityEngine.Timeline; +using FbxExporters.EditorTools; namespace FbxExporters { @@ -1922,7 +1923,9 @@ private string GetUniqueName(string name) protected int ExportTransformHierarchy( GameObject unityGo, FbxScene fbxScene, FbxNode fbxNodeParent, int exportProgress, int objectCount, Vector3 newCenter, - TransformExportType exportType = TransformExportType.Local) + TransformExportType exportType = TransformExportType.Local, + ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All + ) { int numObjectsExported = exportProgress; @@ -1957,9 +1960,45 @@ protected int ExportTransformHierarchy( fbxNodeParent.AddChild (fbxNode); + // if this object has an LOD group, then export according to the LOD preference setting + var lodGroup = unityGo.GetComponent(); + if (lodGroup && lodExportType != ExportSettings.LODExportType.All) { + LOD[] lods = lodGroup.GetLODs (); + + // LODs are ordered from highest to lowest. + // If exporting lowest LOD, reverse the array + if (lodExportType == ExportSettings.LODExportType.Lowest) { + // reverse the array + LOD[] tempLods = new LOD[lods.Length]; + System.Array.Copy (lods, tempLods, lods.Length); + System.Array.Reverse (tempLods); + lods = tempLods; + } + + for(int i = 0; i < lods.Length; i++){ + var lod = lods [i]; + bool exportedRenderer = false; + foreach (var renderer in lod.renderers) { + // only export if parented under LOD group + if (renderer.transform.parent == unityGo.transform) { + numObjectsExported = ExportTransformHierarchy (renderer.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType); + exportedRenderer = true; + } else if(Verbose) { + Debug.LogFormat ("FbxExporter: Not exporting LOD {0}: {1}", i, renderer.name); + } + } + + // if at least one renderer for this LOD was exported, then we succeeded + // so stop exporting. + if (exportedRenderer) { + return numObjectsExported; + } + } + } + // now unityGo through our children and recurse foreach (Transform childT in unityGo.transform) { - numObjectsExported = ExportTransformHierarchy (childT.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter); + numObjectsExported = ExportTransformHierarchy (childT.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType); } return numObjectsExported; @@ -2665,7 +2704,11 @@ public enum TransformExportType { Local, Global, Reset }; /// /// This refreshes the asset database. /// - public int ExportAll (IEnumerable unityExportSet, Dictionary animationExportData, TransformExportType exportType = TransformExportType.Global) + public int ExportAll ( + IEnumerable unityExportSet, + Dictionary animationExportData, + TransformExportType exportType = TransformExportType.Global, + ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All) { exportCancelled = false; @@ -2780,7 +2823,7 @@ public int ExportAll (IEnumerable unityExportSet, Dictionary } else { exportProgress = this.ExportTransformHierarchy (unityGo, fbxScene, fbxRootNode, - exportProgress, count, center, exportType); + exportProgress, count, center, exportType, lodExportType); } if (exportCancelled || exportProgress < 0) { Debug.LogWarning ("Export Cancelled"); @@ -3545,7 +3588,7 @@ private static void OnExport (AnimationExportType exportType = AnimationExportTy return; } - if (ExportObjects (filePath, exportType: exportType) != null) { + if (ExportObjects (filePath, exportType: exportType, lodExportType: ExportSettings.instance.lodExportType) != null) { // refresh the asset database so that the file appears in the // asset folder view. AssetDatabase.Refresh (); @@ -3556,7 +3599,12 @@ private static void OnExport (AnimationExportType exportType = AnimationExportTy /// Export a list of (Game) objects to FBX file. /// Use the SaveFile panel to allow user to enter a file name. /// - public static string ExportObjects (string filePath, UnityEngine.Object[] objects = null, AnimationExportType exportType = AnimationExportType.all, TransformExportType transformExportType = TransformExportType.Global) + public static string ExportObjects ( + string filePath, + UnityEngine.Object[] objects = null, + AnimationExportType exportType = AnimationExportType.all, + TransformExportType transformExportType = TransformExportType.Global, + ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All) { LastFilePath = filePath; @@ -3597,7 +3645,7 @@ public static string ExportObjects (string filePath, UnityEngine.Object[] object break; } - if (fbxExporter.ExportAll (objects, animationExportData, transformExportType) > 0) { + if (fbxExporter.ExportAll (objects, animationExportData, transformExportType, lodExportType) > 0) { string message = string.Format ("Successfully exported: {0}", filePath); UnityEngine.Debug.Log (message); @@ -3607,9 +3655,13 @@ public static string ExportObjects (string filePath, UnityEngine.Object[] object return null; } - public static string ExportObject (string filePath, UnityEngine.Object root, AnimationExportType exportType = AnimationExportType.all, TransformExportType transformExportType = TransformExportType.Reset) + public static string ExportObject ( + string filePath, UnityEngine.Object root, + AnimationExportType exportType = AnimationExportType.all, + TransformExportType transformExportType = TransformExportType.Reset, + ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All) { - return ExportObjects(filePath, new Object[] { root }, exportType, transformExportType); + return ExportObjects(filePath, new Object[] { root }, exportType, transformExportType, lodExportType); } private static void EnsureDirectory (string path) diff --git a/Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs b/Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs index e83ea75ef..40a6521fd 100644 --- a/Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs +++ b/Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs @@ -261,10 +261,12 @@ protected virtual string ExportSelectedObjects(string filename, params Object[] /// The exported fbx file path. /// Hierarchy. /// If set to true export animation only. - protected string ExportToFbx (GameObject hierarchy, bool animOnly = false){ + protected string ExportToFbx (GameObject hierarchy, bool animOnly = false, EditorTools.ExportSettings.LODExportType lodExportType = EditorTools.ExportSettings.LODExportType.All){ string filename = GetRandomFbxFilePath (); var exportedFilePath = FbxExporters.Editor.ModelExporter.ExportObject ( - filename, hierarchy, animOnly? FbxExporters.Editor.ModelExporter.AnimationExportType.componentAnimation : FbxExporters.Editor.ModelExporter.AnimationExportType.all + filename, hierarchy, + animOnly? FbxExporters.Editor.ModelExporter.AnimationExportType.componentAnimation : FbxExporters.Editor.ModelExporter.AnimationExportType.all, + lodExportType: lodExportType ); Assert.That (exportedFilePath, Is.EqualTo (filename)); return filename; diff --git a/Assets/FbxExporters/Editor/UnitTests/ModelExporterTest.cs b/Assets/FbxExporters/Editor/UnitTests/ModelExporterTest.cs index ea55813b0..b4583df7b 100644 --- a/Assets/FbxExporters/Editor/UnitTests/ModelExporterTest.cs +++ b/Assets/FbxExporters/Editor/UnitTests/ModelExporterTest.cs @@ -737,5 +737,105 @@ public void TestBlendShapeExport(string fbxPath) } } } + + [Test] + public void LODExportTest(){ + // Create the following test hierarchy: + // LODGroup + // -- Sphere_LOD0 + // -- Capsule_LOD0 + // -- Cube_LOD2 + // Cylinder_LOD1 + // + // where sphere + capsule renderers are both in LOD0, and cylinder is in LOD1 + // but not parented under the LOD group + + var lodGroup = new GameObject ("LODGroup"); + var sphereLOD0 = GameObject.CreatePrimitive (PrimitiveType.Sphere); + sphereLOD0.name = "Sphere_LOD0"; + var capsuleLOD0 = GameObject.CreatePrimitive (PrimitiveType.Capsule); + capsuleLOD0.name = "Capsule_LOD0"; + var cubeLOD2 = GameObject.CreatePrimitive (PrimitiveType.Cube); + cubeLOD2.name = "Cube_LOD2"; + var cylinderLOD1 = GameObject.CreatePrimitive (PrimitiveType.Cylinder); + cylinderLOD1.name = "Cylinder_LOD1"; + + sphereLOD0.transform.SetParent (lodGroup.transform); + capsuleLOD0.transform.SetParent (lodGroup.transform); + cubeLOD2.transform.SetParent (lodGroup.transform); + cylinderLOD1.transform.SetParent (null); + + // add LOD group + var lodGroupComp = lodGroup.AddComponent(); + Assert.That (lodGroupComp, Is.Not.Null); + + LOD[] lods = new LOD[3]; + lods [0] = new LOD (1, new Renderer[]{ sphereLOD0.GetComponent(), capsuleLOD0.GetComponent() }); + lods [1] = new LOD (0.75f, new Renderer[] { cylinderLOD1.GetComponent() }); + lods [2] = new LOD (0.5f, new Renderer[] { cubeLOD2.GetComponent() }); + lodGroupComp.SetLODs (lods); + lodGroupComp.RecalculateBounds (); + + // test export all + // expected LODs exported: Sphere_LOD0, Capsule_LOD0, Cube_LOD2 + string filename = ExportToFbx(lodGroup, lodExportType:EditorTools.ExportSettings.LODExportType.All); + GameObject fbxObj = AssetDatabase.LoadMainAssetAtPath (filename) as GameObject; + Assert.IsTrue (fbxObj); + + HashSet expectedChildren = new HashSet () { sphereLOD0.name, capsuleLOD0.name, cubeLOD2.name }; + CompareGameObjectChildren (fbxObj, expectedChildren); + + // test export highest + // expected LODs exported: Sphere_LOD0, Capsule_LOD0 + filename = ExportToFbx(lodGroup, lodExportType:EditorTools.ExportSettings.LODExportType.Highest); + fbxObj = AssetDatabase.LoadMainAssetAtPath (filename) as GameObject; + Assert.IsTrue (fbxObj); + + expectedChildren = new HashSet () { sphereLOD0.name, capsuleLOD0.name }; + CompareGameObjectChildren (fbxObj, expectedChildren); + + // test export lowest + // expected LODs exported: Cube_LOD2 + filename = ExportToFbx(lodGroup, lodExportType:EditorTools.ExportSettings.LODExportType.Lowest); + fbxObj = AssetDatabase.LoadMainAssetAtPath (filename) as GameObject; + Assert.IsTrue (fbxObj); + + expectedChildren = new HashSet () { cubeLOD2.name }; + CompareGameObjectChildren (fbxObj, expectedChildren); + + // test convert to prefab + // this should have the same result as "export all" + // expected LODs exported: Sphere_LOD0, Capsule_LOD0, Cube_LOD2 + // NOTE: Cylinder_LOD1 is not exported as it is not under the LODGroup hierarchy being exported + filename = GetRandomFbxFilePath(); + var convertedHierarchy = ConvertToModel.Convert(lodGroup, fbxFullPath: filename); + Assert.That (convertedHierarchy, Is.Not.Null); + + // check both converted hierarchy and fbx + expectedChildren = new HashSet () { sphereLOD0.name, capsuleLOD0.name, cubeLOD2.name }; + CompareGameObjectChildren (convertedHierarchy, expectedChildren); + + fbxObj = AssetDatabase.LoadMainAssetAtPath (filename) as GameObject; + Assert.IsTrue (fbxObj); + + expectedChildren = new HashSet () { sphereLOD0.name, capsuleLOD0.name, cubeLOD2.name }; + CompareGameObjectChildren (fbxObj, expectedChildren); + } + + + /// + /// Compares obj's children to the expected children in the hashset. + /// Doesn't recurse through the children. + /// + /// Object. + /// Expected children. + private void CompareGameObjectChildren(GameObject obj, HashSet expectedChildren){ + Assert.That (obj.transform.childCount, Is.EqualTo (expectedChildren.Count)); + + foreach (Transform child in obj.transform) { + Assert.That (expectedChildren.Contains (child.name)); + expectedChildren.Remove (child.name); + } + } } }