From ae2234fc9650daaec5b9ff41317873686052c96a Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 16:17:05 -0700 Subject: [PATCH 1/8] feat: LODLevel and SimplificationOptions implement IEquatable --- Runtime/LODLevel.cs | 36 +++++++++++++++++++++++++++++++- Runtime/SimplificationOptions.cs | 32 +++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Runtime/LODLevel.cs b/Runtime/LODLevel.cs index 6318bd5..3bf6bfc 100644 --- a/Runtime/LODLevel.cs +++ b/Runtime/LODLevel.cs @@ -34,7 +34,7 @@ namespace UnityMeshSimplifier /// A LOD (level of detail) level. /// [Serializable] - public struct LODLevel + public struct LODLevel : IEquatable { #region Fields [SerializeField, Range(0f, 1f), Tooltip("The screen relative height to use for the transition.")] @@ -242,5 +242,39 @@ public LODLevel(float screenRelativeTransitionHeight, float fadeTransitionWidth, this.reflectionProbeUsage = ReflectionProbeUsage.BlendProbes; } #endregion + + #region IEquatable implementation + public bool Equals(LODLevel other) + { + return screenRelativeTransitionHeight == other.screenRelativeTransitionHeight && + fadeTransitionWidth == other.fadeTransitionWidth && + quality == other.quality && + combineMeshes == other.combineMeshes && + combineSubMeshes == other.combineSubMeshes && + skinQuality == other.skinQuality && + shadowCastingMode == other.shadowCastingMode && + receiveShadows == other.receiveShadows && + motionVectorGenerationMode == other.motionVectorGenerationMode && + skinnedMotionVectors == other.skinnedMotionVectors && + lightProbeUsage == other.lightProbeUsage && + reflectionProbeUsage == other.reflectionProbeUsage; + } + public override bool Equals(object obj) => obj is LODLevel other && Equals(other); + public override int GetHashCode() + { + return (screenRelativeTransitionHeight, + fadeTransitionWidth, + quality, + combineMeshes, + combineSubMeshes, + skinQuality, + shadowCastingMode, + receiveShadows, + motionVectorGenerationMode, + skinnedMotionVectors, + lightProbeUsage, + reflectionProbeUsage).GetHashCode(); + } + #endregion } } diff --git a/Runtime/SimplificationOptions.cs b/Runtime/SimplificationOptions.cs index 25bab3f..6313bf7 100644 --- a/Runtime/SimplificationOptions.cs +++ b/Runtime/SimplificationOptions.cs @@ -35,7 +35,7 @@ namespace UnityMeshSimplifier /// [Serializable] [StructLayout(LayoutKind.Auto)] - public struct SimplificationOptions + public struct SimplificationOptions : IEquatable { /// /// The default simplification options. @@ -118,5 +118,35 @@ public struct SimplificationOptions /// [Range(0, 4), Tooltip("The UV component count. The same UV component count will be used on all UV channels.")] public int UVComponentCount; + + #region IEquatable implementation + public bool Equals(SimplificationOptions other) + { + return PreserveBorderEdges == other.PreserveBorderEdges && + PreserveUVSeamEdges == other.PreserveUVSeamEdges && + PreserveUVFoldoverEdges == other.PreserveUVFoldoverEdges && + PreserveSurfaceCurvature == other.PreserveSurfaceCurvature && + EnableSmartLink == other.EnableSmartLink && + VertexLinkDistance == other.VertexLinkDistance && + MaxIterationCount == other.MaxIterationCount && + Agressiveness == other.Agressiveness && + ManualUVComponentCount == other.ManualUVComponentCount && + UVComponentCount == other.UVComponentCount; + } + public override bool Equals(object obj) => obj is SimplificationOptions other && Equals(other); + public override int GetHashCode() + { + return (PreserveBorderEdges, + PreserveUVSeamEdges, + PreserveUVFoldoverEdges, + PreserveSurfaceCurvature, + EnableSmartLink, + VertexLinkDistance, + MaxIterationCount, + Agressiveness, + ManualUVComponentCount, + UVComponentCount).GetHashCode(); + } + #endregion } } From 9457f176cd842cf05cac7e0578194a057751b7c0 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 16:23:43 -0700 Subject: [PATCH 2/8] refactor: default LOD levels moved to LODLevel --- Runtime/Components/LODGeneratorHelper.cs | 37 +------------------ Runtime/LODLevel.cs | 45 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Runtime/Components/LODGeneratorHelper.cs b/Runtime/Components/LODGeneratorHelper.cs index f25f679..800e8e1 100644 --- a/Runtime/Components/LODGeneratorHelper.cs +++ b/Runtime/Components/LODGeneratorHelper.cs @@ -130,42 +130,7 @@ private void Reset() autoCollectRenderers = true; simplificationOptions = SimplificationOptions.Default; - levels = new LODLevel[] - { - new LODLevel(0.5f, 1f) - { - CombineMeshes = false, - CombineSubMeshes = false, - SkinQuality = SkinQuality.Auto, - ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, - ReceiveShadows = true, - SkinnedMotionVectors = true, - LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, - ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.BlendProbes, - }, - new LODLevel(0.17f, 0.65f) - { - CombineMeshes = true, - CombineSubMeshes = false, - SkinQuality = SkinQuality.Auto, - ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, - ReceiveShadows = true, - SkinnedMotionVectors = true, - LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, - ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Simple - }, - new LODLevel(0.02f, 0.4225f) - { - CombineMeshes = true, - CombineSubMeshes = true, - SkinQuality = SkinQuality.Bone2, - ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off, - ReceiveShadows = false, - SkinnedMotionVectors = false, - LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off, - ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off - } - }; + levels = LODLevel.GetDefaultLevels(); } #endregion } diff --git a/Runtime/LODLevel.cs b/Runtime/LODLevel.cs index 3bf6bfc..cad0df6 100644 --- a/Runtime/LODLevel.cs +++ b/Runtime/LODLevel.cs @@ -276,5 +276,50 @@ public override int GetHashCode() reflectionProbeUsage).GetHashCode(); } #endregion + + /// + /// Gets default LOD levels. + /// + /// Default LOD levels. + public static LODLevel[] GetDefaultLevels() + { + // TODO: Expose default levels as a project setting, rather than hard-coding values. + return new LODLevel[] + { + new LODLevel(0.5f, 1f) + { + CombineMeshes = false, + CombineSubMeshes = false, + SkinQuality = SkinQuality.Auto, + ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, + ReceiveShadows = true, + SkinnedMotionVectors = true, + LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, + ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.BlendProbes, + }, + new LODLevel(0.17f, 0.65f) + { + CombineMeshes = true, + CombineSubMeshes = false, + SkinQuality = SkinQuality.Auto, + ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, + ReceiveShadows = true, + SkinnedMotionVectors = true, + LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, + ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Simple + }, + new LODLevel(0.02f, 0.4225f) + { + CombineMeshes = true, + CombineSubMeshes = true, + SkinQuality = SkinQuality.Bone2, + ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off, + ReceiveShadows = false, + SkinnedMotionVectors = false, + LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off, + ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off + } + }; + } } } From ff23b3297b92999a3c54ac1adaf710f4847ee570 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 16:26:03 -0700 Subject: [PATCH 3/8] feat: LODGeneratorPreset scriptable object and editor --- Editor/LODGeneratorPresetEditor.cs | 235 ++++++++++++++++++++++++ Editor/LODGeneratorPresetEditor.cs.meta | 3 + Runtime/LODGeneratorPreset.cs | 97 ++++++++++ Runtime/LODGeneratorPreset.cs.meta | 3 + 4 files changed, 338 insertions(+) create mode 100644 Editor/LODGeneratorPresetEditor.cs create mode 100644 Editor/LODGeneratorPresetEditor.cs.meta create mode 100644 Runtime/LODGeneratorPreset.cs create mode 100644 Runtime/LODGeneratorPreset.cs.meta diff --git a/Editor/LODGeneratorPresetEditor.cs b/Editor/LODGeneratorPresetEditor.cs new file mode 100644 index 0000000..32ba14a --- /dev/null +++ b/Editor/LODGeneratorPresetEditor.cs @@ -0,0 +1,235 @@ +#region License +/* +MIT License + +Copyright(c) 2017-2020 Mattias Edlund + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion + +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEditor; +using UnityEditor.SceneManagement; + +namespace UnityMeshSimplifier.Editor +{ + [CustomEditor(typeof(LODGeneratorPreset))] + internal sealed class LODGeneratorPresetEditor : UnityEditor.Editor + { + private const string FadeModeFieldName = "fadeMode"; + private const string AnimateCrossFadingFieldName = "animateCrossFading"; + private const string SimplificationOptionsFieldName = "simplificationOptions"; + private const string LevelsFieldName = "levels"; + private const string LevelScreenRelativeHeightFieldName = "screenRelativeTransitionHeight"; + private const string LevelFadeTransitionWidthFieldName = "fadeTransitionWidth"; + private const string LevelQualityFieldName = "quality"; + private const string LevelCombineMeshesFieldName = "combineMeshes"; + private const string LevelCombineSubMeshesFieldName = "combineSubMeshes"; + private const string LevelRenderersFieldName = "renderers"; + private const string SimplificationOptionsEnableSmartLinkFieldName = "EnableSmartLink"; + private const string SimplificationOptionsVertexLinkDistanceFieldName = "VertexLinkDistance"; + private const float RemoveLevelButtonSize = 20f; + + private SerializedProperty fadeModeProperty = null; + private SerializedProperty animateCrossFadingProperty = null; + private SerializedProperty simplificationOptionsProperty = null; + private SerializedProperty levelsProperty = null; + + private bool[] settingsExpanded = null; + private LODGeneratorPreset lodGeneratorPreset = null; + + private static readonly GUIContent createLevelButtonContent = new GUIContent("Create Level", "Creates a new LOD level."); + private static readonly GUIContent deleteLevelButtonContent = new GUIContent("X", "Deletes this LOD level."); + private static readonly GUIContent settingsContent = new GUIContent("Settings", "The settings for the LOD level."); + private static readonly Color removeColor = new Color(1f, 0.6f, 0.6f, 1f); + + private void OnEnable() + { + fadeModeProperty = serializedObject.FindProperty(FadeModeFieldName); + animateCrossFadingProperty = serializedObject.FindProperty(AnimateCrossFadingFieldName); + simplificationOptionsProperty = serializedObject.FindProperty(SimplificationOptionsFieldName); + levelsProperty = serializedObject.FindProperty(LevelsFieldName); + + lodGeneratorPreset = target as LODGeneratorPreset; + } + + public override void OnInspectorGUI() + { + serializedObject.UpdateIfRequiredOrScript(); + + DrawView(); + + serializedObject.ApplyModifiedProperties(); + } + + private void DrawView() + { + EditorGUILayout.PropertyField(fadeModeProperty); + var fadeMode = (LODFadeMode)fadeModeProperty.intValue; + + bool hasCrossFade = (fadeMode == LODFadeMode.CrossFade || fadeMode == LODFadeMode.SpeedTree); + if (hasCrossFade) + { + EditorGUILayout.PropertyField(animateCrossFadingProperty); + } + + DrawSimplificationOptions(); + + if (settingsExpanded == null || settingsExpanded.Length != levelsProperty.arraySize) + { + var newSettingsExpanded = new bool[levelsProperty.arraySize]; + if (settingsExpanded != null) + { + System.Array.Copy(settingsExpanded, 0, newSettingsExpanded, 0, Mathf.Min(settingsExpanded.Length, newSettingsExpanded.Length)); + } + settingsExpanded = newSettingsExpanded; + } + + for (int levelIndex = 0; levelIndex < levelsProperty.arraySize; levelIndex++) + { + var levelProperty = levelsProperty.GetArrayElementAtIndex(levelIndex); + DrawLevel(levelIndex, levelProperty, hasCrossFade); + } + + if (GUILayout.Button(createLevelButtonContent)) + { + CreateLevel(); + } + } + + private void DrawSimplificationOptions() + { + if (EditorGUILayout.PropertyField(simplificationOptionsProperty, false)) + { + ++EditorGUI.indentLevel; + + var enableSmartLinkProperty = simplificationOptionsProperty.FindPropertyRelative(SimplificationOptionsEnableSmartLinkFieldName); + + var childProperties = simplificationOptionsProperty.GetChildProperties(); + foreach (var childProperty in childProperties) + { + if (!enableSmartLinkProperty.boolValue && string.Equals(childProperty.name, SimplificationOptionsVertexLinkDistanceFieldName)) + continue; + + EditorGUILayout.PropertyField(childProperty, true); + } + + --EditorGUI.indentLevel; + } + } + + private void DrawLevel(int index, SerializedProperty levelProperty, bool hasCrossFade) + { + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + GUILayout.Label(string.Format("Level {0}", index + 1), EditorStyles.boldLabel); + + var previousBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = removeColor; + if (GUILayout.Button(deleteLevelButtonContent, GUILayout.Width(RemoveLevelButtonSize))) + { + DeleteLevel(index); + } + GUI.backgroundColor = previousBackgroundColor; + EditorGUILayout.EndHorizontal(); + + ++EditorGUI.indentLevel; + + var screenRelativeHeightProperty = levelProperty.FindPropertyRelative(LevelScreenRelativeHeightFieldName); + EditorGUILayout.PropertyField(screenRelativeHeightProperty); + + var qualityProperty = levelProperty.FindPropertyRelative(LevelQualityFieldName); + EditorGUILayout.PropertyField(qualityProperty); + + bool animateCrossFading = (hasCrossFade ? animateCrossFadingProperty.boolValue : false); + settingsExpanded[index] = EditorGUILayout.Foldout(settingsExpanded[index], settingsContent); + if (settingsExpanded[index]) + { + ++EditorGUI.indentLevel; + + var combineMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineMeshesFieldName); + EditorGUILayout.PropertyField(combineMeshesProperty); + + if (combineMeshesProperty.boolValue) + { + var combineSubMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineSubMeshesFieldName); + EditorGUILayout.PropertyField(combineSubMeshesProperty); + } + + var childProperties = levelProperty.GetChildProperties(); + foreach (var childProperty in childProperties) + { + if (string.Equals(childProperty.name, LevelScreenRelativeHeightFieldName) || string.Equals(childProperty.name, LevelQualityFieldName) || + string.Equals(childProperty.name, LevelCombineMeshesFieldName) || string.Equals(childProperty.name, LevelCombineSubMeshesFieldName) || + string.Equals(childProperty.name, LevelRenderersFieldName)) + { + continue; + } + else if ((!hasCrossFade || !animateCrossFading) && string.Equals(childProperty.name, LevelFadeTransitionWidthFieldName)) + { + continue; + } + + EditorGUILayout.PropertyField(childProperty, true); + } + + --EditorGUI.indentLevel; + } + + --EditorGUI.indentLevel; + EditorGUILayout.EndVertical(); + } + + private void CreateLevel() + { + int newIndex = levelsProperty.arraySize; + levelsProperty.InsertArrayElementAtIndex(newIndex); + var newLevelProperty = levelsProperty.GetArrayElementAtIndex(newIndex); + var lastLevelProperty = (newIndex > 0 ? levelsProperty.GetArrayElementAtIndex(newIndex - 1) : null); + var newScreenRelativeHeightProperty = newLevelProperty.FindPropertyRelative(LevelScreenRelativeHeightFieldName); + var newQualityProperty = newLevelProperty.FindPropertyRelative(LevelQualityFieldName); + + if (lastLevelProperty != null) + { + var lastScreenRelativeHeightProperty = lastLevelProperty.FindPropertyRelative(LevelScreenRelativeHeightFieldName); + var lastQualityProperty = lastLevelProperty.FindPropertyRelative(LevelQualityFieldName); + newScreenRelativeHeightProperty.floatValue = lastScreenRelativeHeightProperty.floatValue * 0.5f; + newQualityProperty.floatValue = lastQualityProperty.floatValue * 0.65f; + } + else + { + newScreenRelativeHeightProperty.floatValue = 0.6f; + newQualityProperty.floatValue = 1f; + } + + serializedObject.ApplyModifiedProperties(); + GUIUtility.ExitGUI(); + } + + private void DeleteLevel(int index) + { + levelsProperty.DeleteArrayElementAtIndex(index); + serializedObject.ApplyModifiedProperties(); + GUIUtility.ExitGUI(); + } + } +} diff --git a/Editor/LODGeneratorPresetEditor.cs.meta b/Editor/LODGeneratorPresetEditor.cs.meta new file mode 100644 index 0000000..4c546d1 --- /dev/null +++ b/Editor/LODGeneratorPresetEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ba0c107493a0497aa0f6f0fc37b308da +timeCreated: 1658437406 \ No newline at end of file diff --git a/Runtime/LODGeneratorPreset.cs b/Runtime/LODGeneratorPreset.cs new file mode 100644 index 0000000..3cc8c2f --- /dev/null +++ b/Runtime/LODGeneratorPreset.cs @@ -0,0 +1,97 @@ +#region License +/* +MIT License + +Copyright(c) 2017-2020 Mattias Edlund + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#endregion + +using UnityEngine; + +namespace UnityMeshSimplifier +{ + [CreateAssetMenu( + fileName = "LOD Preset", + menuName = "Mesh Simplifier/LOD Preset")] + public sealed class LODGeneratorPreset : ScriptableObject + { + [SerializeField, Tooltip("The fade mode used by the created LOD group.")] + private LODFadeMode fadeMode = LODFadeMode.None; + + [SerializeField, Tooltip("If the cross-fading should be animated by time.")] + private bool animateCrossFading = false; + + [SerializeField, Tooltip("The simplification options.")] + private SimplificationOptions simplificationOptions = SimplificationOptions.Default; + + [SerializeField, Tooltip("The LOD levels.")] + private LODLevel[] levels = null; + + #region Properties + /// + /// Gets or sets the fade mode used by the created LOD group. + /// + public LODFadeMode FadeMode + { + get { return fadeMode; } + set { fadeMode = value; } + } + + /// + /// Gets or sets if the cross-fading should be animated by time. The animation duration + /// is specified globally as crossFadeAnimationDuration. + /// + public bool AnimateCrossFading + { + get { return animateCrossFading; } + set { animateCrossFading = value; } + } + + /// + /// Gets or sets the simplification options. + /// + public SimplificationOptions SimplificationOptions + { + get { return simplificationOptions; } + set { simplificationOptions = value; } + } + + /// + /// Gets or sets the LOD levels for this preset. + /// + public LODLevel[] Levels + { + get { return levels; } + set { levels = value; } + } + #endregion + + #region Unity Events + private void Reset() + { + fadeMode = LODFadeMode.None; + animateCrossFading = false; + simplificationOptions = SimplificationOptions.Default; + levels = LODLevel.GetDefaultLevels(); + } + #endregion + } +} diff --git a/Runtime/LODGeneratorPreset.cs.meta b/Runtime/LODGeneratorPreset.cs.meta new file mode 100644 index 0000000..d2d0700 --- /dev/null +++ b/Runtime/LODGeneratorPreset.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dfe269aae04c466699460584399ceb56 +timeCreated: 1658438456 \ No newline at end of file From 5286646620be3ed823e37d8593394d7765303e7a Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 16:43:11 -0700 Subject: [PATCH 4/8] feat(component): LODGeneratorHelper uses LODGeneratorPreset LODGeneratorHelper has two new properties, A LODGeneratorPreset obj ref and a "customization" toggle. If customization is enabled (default), the helper drives all properties itself. If customization is disabled, properties are driven by a specified preset. If no preset is specified, defaults matching previous behavior are used. --- Editor/LODGeneratorHelperEditor.cs | 11 ++- Runtime/Components/LODGeneratorHelper.cs | 121 +++++++++++++++++++++-- 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/Editor/LODGeneratorHelperEditor.cs b/Editor/LODGeneratorHelperEditor.cs index 38b4b26..22c0ab9 100644 --- a/Editor/LODGeneratorHelperEditor.cs +++ b/Editor/LODGeneratorHelperEditor.cs @@ -35,6 +35,8 @@ namespace UnityMeshSimplifier.Editor [CustomEditor(typeof(LODGeneratorHelper))] internal sealed class LODGeneratorHelperEditor : UnityEditor.Editor { + private const string LodGeneratorPresetFieldName = "lodGeneratorPreset"; + private const string CustomizeSettingsFieldName = "customizeSettings"; private const string FadeModeFieldName = "fadeMode"; private const string AnimateCrossFadingFieldName = "animateCrossFading"; private const string AutoCollectRenderersFieldName = "autoCollectRenderers"; @@ -54,6 +56,8 @@ internal sealed class LODGeneratorHelperEditor : UnityEditor.Editor private const float RendererButtonWidth = 60f; private const float RemoveRendererButtonSize = 20f; + private SerializedProperty lodGeneratorPresetProperty = null; + private SerializedProperty customizeSettingsProperty = null; private SerializedProperty fadeModeProperty = null; private SerializedProperty animateCrossFadingProperty = null; private SerializedProperty autoCollectRenderersProperty = null; @@ -82,6 +86,8 @@ internal sealed class LODGeneratorHelperEditor : UnityEditor.Editor private void OnEnable() { + lodGeneratorPresetProperty = serializedObject.FindProperty(LodGeneratorPresetFieldName); + customizeSettingsProperty = serializedObject.FindProperty(CustomizeSettingsFieldName); fadeModeProperty = serializedObject.FindProperty(FadeModeFieldName); animateCrossFadingProperty = serializedObject.FindProperty(AnimateCrossFadingFieldName); autoCollectRenderersProperty = serializedObject.FindProperty(AutoCollectRenderersFieldName); @@ -128,6 +134,9 @@ private void DrawGeneratedView() private void DrawNotGeneratedView() { + EditorGUILayout.ObjectField(lodGeneratorPresetProperty, typeof(LODGeneratorPreset)); + EditorGUILayout.PropertyField(customizeSettingsProperty); + EditorGUILayout.PropertyField(fadeModeProperty); var fadeMode = (LODFadeMode)fadeModeProperty.intValue; @@ -631,7 +640,7 @@ where go.transform.IsChildOf(ourTransform) if (prefabGameObjects.Any()) { - EditorUtility.DisplayDialog("Invalid GameObjects", "Some objects are not children of the LODGenerator GameObject," + + EditorUtility.DisplayDialog("Invalid GameObjects", "Some objects are not children of the LODGenerator GameObject," + " as well as being part of a prefab. They will not be added.", "OK"); } #endif diff --git a/Runtime/Components/LODGeneratorHelper.cs b/Runtime/Components/LODGeneratorHelper.cs index 800e8e1..d0e2670 100644 --- a/Runtime/Components/LODGeneratorHelper.cs +++ b/Runtime/Components/LODGeneratorHelper.cs @@ -35,6 +35,12 @@ namespace UnityMeshSimplifier public sealed class LODGeneratorHelper : MonoBehaviour { #region Fields + [SerializeField, Tooltip("The LOD Generator preset to use.")] + private LODGeneratorPreset lodGeneratorPreset = null; + + [SerializeField, Tooltip("Whether to enable customization of preset-derived generation settings.")] + private bool customizeSettings = true; + [SerializeField, Tooltip("The fade mode used by the created LOD group.")] private LODFadeMode fadeMode = LODFadeMode.None; [SerializeField, Tooltip("If the cross-fading should be animated by time.")] @@ -57,13 +63,41 @@ public sealed class LODGeneratorHelper : MonoBehaviour #endregion #region Properties + /// + /// Gets or sets a LOD generator preset. Presets can be used to drive simplification options and levels in a sharable way. + /// + public LODGeneratorPreset LodGeneratorPreset + { + get { return lodGeneratorPreset; } + set { lodGeneratorPreset = value; } + } + + /// + /// Gets or sets if the simplification options and levels should be customizable, versus driven by the specified preset. + /// + public bool CustomizeSettings + { + get { return customizeSettings; } + set { customizeSettings = value; } + } + /// /// Gets or sets the fade mode used by the created LOD group. /// public LODFadeMode FadeMode { get { return fadeMode; } - set { fadeMode = value; } + set + { + if (!customizeSettings) + { + fadeMode = value; + } + else + { + WarnDisabledCustomization(); + } + } } /// @@ -73,7 +107,17 @@ public LODFadeMode FadeMode public bool AnimateCrossFading { get { return animateCrossFading; } - set { animateCrossFading = value; } + set + { + if (!customizeSettings) + { + animateCrossFading = value; + } + else + { + WarnDisabledCustomization(); + } + } } /// @@ -91,7 +135,17 @@ public bool AutoCollectRenderers public SimplificationOptions SimplificationOptions { get { return simplificationOptions; } - set { simplificationOptions = value; } + set + { + if (!customizeSettings) + { + simplificationOptions = value; + } + else + { + WarnDisabledCustomization(); + } + } } /// @@ -110,7 +164,17 @@ public string SaveAssetsPath public LODLevel[] Levels { get { return levels; } - set { levels = value; } + set + { + if (!customizeSettings) + { + levels = value; + } + else + { + WarnDisabledCustomization(); + } + } } /// @@ -124,14 +188,57 @@ public bool IsGenerated #region Unity Events private void Reset() + { + autoCollectRenderers = true; + ResetPresetDerivedSettings(); + } + + private void OnValidate() + { + if (!customizeSettings) + { + UpdateSettingsFromPreset(); + } + } + #endregion + + private void WarnDisabledCustomization() + { + Debug.LogWarning($"Attempted to set a preset-driven property on a {typeof(LODGeneratorHelper)} while customization is disabled. Enable customization first."); + } + + private void ResetPresetDerivedSettings() { fadeMode = LODFadeMode.None; animateCrossFading = false; - autoCollectRenderers = true; simplificationOptions = SimplificationOptions.Default; - levels = LODLevel.GetDefaultLevels(); } - #endregion + + private void UpdateSettingsFromPreset() + { + // Retain copy of levels so any specified renderers can survive reset + LODLevel[] previousLevels = (LODLevel[])levels.Clone(); + + // Copy settings from preset, or use defaults if no preset specified + if (lodGeneratorPreset != null) + { + fadeMode = lodGeneratorPreset.FadeMode; + animateCrossFading = lodGeneratorPreset.AnimateCrossFading; + simplificationOptions = lodGeneratorPreset.SimplificationOptions; + levels = (LODLevel[])lodGeneratorPreset.Levels.Clone(); + } + else + { + ResetPresetDerivedSettings(); + } + + // Copy specified renderers over + int rendererCopyCount = Mathf.Min(levels.Length, previousLevels.Length); + for(int idx = 0; idx < rendererCopyCount; idx++) + { + levels[idx].Renderers = (Renderer[])previousLevels[idx].Renderers.Clone(); + } + } } } From 7bf06ba9254b880b8c1dcb29b910fc28147351d1 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 16:55:53 -0700 Subject: [PATCH 5/8] feat(editor): Better display of preset-driven settings on helper When a LOD Generator Helper has customization disabled, Properties driven by the specified preset are visible but greyed out. --- Editor/LODGeneratorHelperEditor.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Editor/LODGeneratorHelperEditor.cs b/Editor/LODGeneratorHelperEditor.cs index 22c0ab9..c99a023 100644 --- a/Editor/LODGeneratorHelperEditor.cs +++ b/Editor/LODGeneratorHelperEditor.cs @@ -134,20 +134,31 @@ private void DrawGeneratedView() private void DrawNotGeneratedView() { + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == true); EditorGUILayout.ObjectField(lodGeneratorPresetProperty, typeof(LODGeneratorPreset)); + EditorGUI.EndDisabledGroup(); + EditorGUILayout.PropertyField(customizeSettingsProperty); + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); EditorGUILayout.PropertyField(fadeModeProperty); + EditorGUI.EndDisabledGroup(); var fadeMode = (LODFadeMode)fadeModeProperty.intValue; bool hasCrossFade = (fadeMode == LODFadeMode.CrossFade || fadeMode == LODFadeMode.SpeedTree); + + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); if (hasCrossFade) { EditorGUILayout.PropertyField(animateCrossFadingProperty); } + EditorGUI.EndDisabledGroup(); EditorGUILayout.PropertyField(autoCollectRenderersProperty); + + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); DrawSimplificationOptions(); + EditorGUI.EndDisabledGroup(); bool newOverrideSaveAssetsPath = EditorGUILayout.Toggle(overrideSaveAssetsPathContent, overrideSaveAssetsPath); if (newOverrideSaveAssetsPath != overrideSaveAssetsPath) @@ -184,10 +195,12 @@ private void DrawNotGeneratedView() DrawLevel(levelIndex, levelProperty, hasCrossFade); } + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); if (GUILayout.Button(createLevelButtonContent)) { CreateLevel(); } + EditorGUI.EndDisabledGroup(); if (GUILayout.Button(generateLODButtonContent)) { @@ -218,6 +231,7 @@ private void DrawSimplificationOptions() private void DrawLevel(int index, SerializedProperty levelProperty, bool hasCrossFade) { + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(string.Format("Level {0}", index + 1), EditorStyles.boldLabel); @@ -285,6 +299,7 @@ private void DrawLevel(int index, SerializedProperty levelProperty, bool hasCros renderersProperty.DeleteArrayElementAtIndex(rendererIndex); } } + EditorGUI.EndDisabledGroup(); bool autoCollectRenderers = autoCollectRenderersProperty.boolValue; if (!autoCollectRenderers) From e721567c5a51470f06fbb539a52d76c07a691fc3 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 17:02:18 -0700 Subject: [PATCH 6/8] style(editor): DisabledGroup scoping Code between calls EditorGUI.BeginDisabledGroup and EditorGUI.EndDisabledGroup scoped for clarity. --- Editor/LODGeneratorHelperEditor.cs | 121 ++++++++++++++++------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/Editor/LODGeneratorHelperEditor.cs b/Editor/LODGeneratorHelperEditor.cs index c99a023..0e38adc 100644 --- a/Editor/LODGeneratorHelperEditor.cs +++ b/Editor/LODGeneratorHelperEditor.cs @@ -135,22 +135,28 @@ private void DrawGeneratedView() private void DrawNotGeneratedView() { EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == true); - EditorGUILayout.ObjectField(lodGeneratorPresetProperty, typeof(LODGeneratorPreset)); + { + EditorGUILayout.ObjectField(lodGeneratorPresetProperty, typeof(LODGeneratorPreset)); + } EditorGUI.EndDisabledGroup(); EditorGUILayout.PropertyField(customizeSettingsProperty); EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); - EditorGUILayout.PropertyField(fadeModeProperty); + { + EditorGUILayout.PropertyField(fadeModeProperty); + } EditorGUI.EndDisabledGroup(); var fadeMode = (LODFadeMode)fadeModeProperty.intValue; bool hasCrossFade = (fadeMode == LODFadeMode.CrossFade || fadeMode == LODFadeMode.SpeedTree); EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); - if (hasCrossFade) { - EditorGUILayout.PropertyField(animateCrossFadingProperty); + if (hasCrossFade) + { + EditorGUILayout.PropertyField(animateCrossFadingProperty); + } } EditorGUI.EndDisabledGroup(); @@ -196,9 +202,11 @@ private void DrawNotGeneratedView() } EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); - if (GUILayout.Button(createLevelButtonContent)) { - CreateLevel(); + if (GUILayout.Button(createLevelButtonContent)) + { + CreateLevel(); + } } EditorGUI.EndDisabledGroup(); @@ -231,72 +239,75 @@ private void DrawSimplificationOptions() private void DrawLevel(int index, SerializedProperty levelProperty, bool hasCrossFade) { - EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); - GUILayout.Label(string.Format("Level {0}", index + 1), EditorStyles.boldLabel); + var renderersProperty = levelProperty.FindPropertyRelative(LevelRenderersFieldName); - var previousBackgroundColor = GUI.backgroundColor; - GUI.backgroundColor = removeColor; - if (GUILayout.Button(deleteLevelButtonContent, GUILayout.Width(RemoveLevelButtonSize))) + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); { - DeleteLevel(index); - } - GUI.backgroundColor = previousBackgroundColor; - EditorGUILayout.EndHorizontal(); - - ++EditorGUI.indentLevel; - - var screenRelativeHeightProperty = levelProperty.FindPropertyRelative(LevelScreenRelativeHeightFieldName); - EditorGUILayout.PropertyField(screenRelativeHeightProperty); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + GUILayout.Label(string.Format("Level {0}", index + 1), EditorStyles.boldLabel); - var qualityProperty = levelProperty.FindPropertyRelative(LevelQualityFieldName); - EditorGUILayout.PropertyField(qualityProperty); + var previousBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = removeColor; + if (GUILayout.Button(deleteLevelButtonContent, GUILayout.Width(RemoveLevelButtonSize))) + { + DeleteLevel(index); + } + GUI.backgroundColor = previousBackgroundColor; + EditorGUILayout.EndHorizontal(); - bool animateCrossFading = (hasCrossFade ? animateCrossFadingProperty.boolValue : false); - settingsExpanded[index] = EditorGUILayout.Foldout(settingsExpanded[index], settingsContent); - if (settingsExpanded[index]) - { ++EditorGUI.indentLevel; - var combineMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineMeshesFieldName); - EditorGUILayout.PropertyField(combineMeshesProperty); + var screenRelativeHeightProperty = levelProperty.FindPropertyRelative(LevelScreenRelativeHeightFieldName); + EditorGUILayout.PropertyField(screenRelativeHeightProperty); - if (combineMeshesProperty.boolValue) - { - var combineSubMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineSubMeshesFieldName); - EditorGUILayout.PropertyField(combineSubMeshesProperty); - } + var qualityProperty = levelProperty.FindPropertyRelative(LevelQualityFieldName); + EditorGUILayout.PropertyField(qualityProperty); - var childProperties = levelProperty.GetChildProperties(); - foreach (var childProperty in childProperties) + bool animateCrossFading = (hasCrossFade ? animateCrossFadingProperty.boolValue : false); + settingsExpanded[index] = EditorGUILayout.Foldout(settingsExpanded[index], settingsContent); + if (settingsExpanded[index]) { - if (string.Equals(childProperty.name, LevelScreenRelativeHeightFieldName) || string.Equals(childProperty.name, LevelQualityFieldName) || - string.Equals(childProperty.name, LevelCombineMeshesFieldName) || string.Equals(childProperty.name, LevelCombineSubMeshesFieldName) || - string.Equals(childProperty.name, LevelRenderersFieldName)) + ++EditorGUI.indentLevel; + + var combineMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineMeshesFieldName); + EditorGUILayout.PropertyField(combineMeshesProperty); + + if (combineMeshesProperty.boolValue) { - continue; + var combineSubMeshesProperty = levelProperty.FindPropertyRelative(LevelCombineSubMeshesFieldName); + EditorGUILayout.PropertyField(combineSubMeshesProperty); } - else if ((!hasCrossFade || !animateCrossFading) && string.Equals(childProperty.name, LevelFadeTransitionWidthFieldName)) + + var childProperties = levelProperty.GetChildProperties(); + foreach (var childProperty in childProperties) { - continue; + if (string.Equals(childProperty.name, LevelScreenRelativeHeightFieldName) || string.Equals(childProperty.name, LevelQualityFieldName) || + string.Equals(childProperty.name, LevelCombineMeshesFieldName) || string.Equals(childProperty.name, LevelCombineSubMeshesFieldName) || + string.Equals(childProperty.name, LevelRenderersFieldName)) + { + continue; + } + else if ((!hasCrossFade || !animateCrossFading) && string.Equals(childProperty.name, LevelFadeTransitionWidthFieldName)) + { + continue; + } + + EditorGUILayout.PropertyField(childProperty, true); } - EditorGUILayout.PropertyField(childProperty, true); + --EditorGUI.indentLevel; } - --EditorGUI.indentLevel; - } - - // Remove any null renderers - var renderersProperty = levelProperty.FindPropertyRelative(LevelRenderersFieldName); - for (int rendererIndex = renderersProperty.arraySize - 1; rendererIndex >= 0; rendererIndex--) - { - var rendererProperty = renderersProperty.GetArrayElementAtIndex(rendererIndex); - var renderer = rendererProperty.objectReferenceValue as Renderer; - if (renderer == null) + // Remove any null renderers + for (int rendererIndex = renderersProperty.arraySize - 1; rendererIndex >= 0; rendererIndex--) { - renderersProperty.DeleteArrayElementAtIndex(rendererIndex); + var rendererProperty = renderersProperty.GetArrayElementAtIndex(rendererIndex); + var renderer = rendererProperty.objectReferenceValue as Renderer; + if (renderer == null) + { + renderersProperty.DeleteArrayElementAtIndex(rendererIndex); + } } } EditorGUI.EndDisabledGroup(); From e3e14f1ad32284790f29a73ca39df3b4bf6d9f46 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Fri, 22 Jul 2022 17:06:30 -0700 Subject: [PATCH 7/8] feat(editor): Property display order on LODGeneratorHelper Properties not driven by a preset are listed first. This way, there's only a single block of greyed-out properties when customization is disabled. --- Editor/LODGeneratorHelperEditor.cs | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Editor/LODGeneratorHelperEditor.cs b/Editor/LODGeneratorHelperEditor.cs index 0e38adc..45a9678 100644 --- a/Editor/LODGeneratorHelperEditor.cs +++ b/Editor/LODGeneratorHelperEditor.cs @@ -134,6 +134,27 @@ private void DrawGeneratedView() private void DrawNotGeneratedView() { + EditorGUILayout.PropertyField(autoCollectRenderersProperty); + + bool newOverrideSaveAssetsPath = EditorGUILayout.Toggle(overrideSaveAssetsPathContent, overrideSaveAssetsPath); + if (newOverrideSaveAssetsPath != overrideSaveAssetsPath) + { + overrideSaveAssetsPath = newOverrideSaveAssetsPath; + saveAssetsPathProperty.stringValue = string.Empty; + serializedObject.ApplyModifiedProperties(); + GUIUtility.ExitGUI(); + } + + if (overrideSaveAssetsPath) + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(saveAssetsPathProperty); + if (EditorGUI.EndChangeCheck()) + { + saveAssetsPathProperty.stringValue = IOUtils.MakeSafeRelativePath(saveAssetsPathProperty.stringValue); + } + } + EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == true); { EditorGUILayout.ObjectField(lodGeneratorPresetProperty, typeof(LODGeneratorPreset)); @@ -160,31 +181,10 @@ private void DrawNotGeneratedView() } EditorGUI.EndDisabledGroup(); - EditorGUILayout.PropertyField(autoCollectRenderersProperty); - EditorGUI.BeginDisabledGroup(customizeSettingsProperty.boolValue == false); DrawSimplificationOptions(); EditorGUI.EndDisabledGroup(); - bool newOverrideSaveAssetsPath = EditorGUILayout.Toggle(overrideSaveAssetsPathContent, overrideSaveAssetsPath); - if (newOverrideSaveAssetsPath != overrideSaveAssetsPath) - { - overrideSaveAssetsPath = newOverrideSaveAssetsPath; - saveAssetsPathProperty.stringValue = string.Empty; - serializedObject.ApplyModifiedProperties(); - GUIUtility.ExitGUI(); - } - - if (overrideSaveAssetsPath) - { - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(saveAssetsPathProperty); - if (EditorGUI.EndChangeCheck()) - { - saveAssetsPathProperty.stringValue = IOUtils.MakeSafeRelativePath(saveAssetsPathProperty.stringValue); - } - } - if (settingsExpanded == null || settingsExpanded.Length != levelsProperty.arraySize) { var newSettingsExpanded = new bool[levelsProperty.arraySize]; From 831384de5f6de296d29ace131b02ea8ec00585a5 Mon Sep 17 00:00:00 2001 From: Stephen Coan Date: Wed, 27 Jul 2022 16:05:29 -0700 Subject: [PATCH 8/8] fix(editor): Helper grabs preset settings before LOD generation --- Editor/LODGeneratorHelperEditor.cs | 1 + Runtime/Components/LODGeneratorHelper.cs | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Editor/LODGeneratorHelperEditor.cs b/Editor/LODGeneratorHelperEditor.cs index 45a9678..9eb46c0 100644 --- a/Editor/LODGeneratorHelperEditor.cs +++ b/Editor/LODGeneratorHelperEditor.cs @@ -544,6 +544,7 @@ private void GenerateLODs() try { EditorUtility.DisplayProgressBar("Generating LODs", "Generating LODs...", 0f); + lodGeneratorHelper?.TryUpdateSettingsFromPreset(); var lodGroup = LODGenerator.GenerateLODs(lodGeneratorHelper); if (lodGroup != null) { diff --git a/Runtime/Components/LODGeneratorHelper.cs b/Runtime/Components/LODGeneratorHelper.cs index d0e2670..ce57e5e 100644 --- a/Runtime/Components/LODGeneratorHelper.cs +++ b/Runtime/Components/LODGeneratorHelper.cs @@ -195,10 +195,7 @@ private void Reset() private void OnValidate() { - if (!customizeSettings) - { - UpdateSettingsFromPreset(); - } + TryUpdateSettingsFromPreset(); } #endregion @@ -215,8 +212,14 @@ private void ResetPresetDerivedSettings() levels = LODLevel.GetDefaultLevels(); } - private void UpdateSettingsFromPreset() + public void TryUpdateSettingsFromPreset() { + // Don't stomp customized settings + if (customizeSettings) + { + return; + } + // Retain copy of levels so any specified renderers can survive reset LODLevel[] previousLevels = (LODLevel[])levels.Clone();