diff --git a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/BeeBuildProgramCommon.Data.gen.csproj b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/BeeBuildProgramCommon.Data.gen.csproj index 2a20527d3f..3672103003 100644 --- a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/BeeBuildProgramCommon.Data.gen.csproj +++ b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/BeeBuildProgramCommon.Data.gen.csproj @@ -1,5 +1,6 @@  - + + BeeBuildProgramCommon.Data netstandard2.1 @@ -12,4 +13,5 @@ + diff --git a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs index 6e100ee65e..0a7938fabc 100644 --- a/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/BeeBuildProgramCommon.Data/Data.cs @@ -34,7 +34,6 @@ public class ConfigurationData public string UnityVersion; public Version UnityVersionNumeric; public string UnitySourceCodePath; - public bool AdvancedLicense; public bool Batchmode; public bool EmitDataForBeeWhy; public string NamedPipeOrUnixSocket; diff --git a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs index 35507fb75a..48b52443eb 100644 --- a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/Data.cs @@ -92,7 +92,12 @@ public class Il2CppConfig public string[] AdditionalCppFiles = new string[0]; public string[] AdditionalArgs = new string[0]; public string CompilerFlags; + public string[] AdditionalLibraries; + public string[] AdditionalDefines; + public string[] AdditionalIncludeDirectories; + public string[] AdditionalLinkDirectories; public string LinkerFlags; + public string LinkerFlagsFile; public string ExtraTypes; public bool CreateSymbolFiles; public bool AllowDebugging; @@ -109,6 +114,7 @@ public class Services public bool EnablePerformanceReporting; public bool EnableAnalytics; public bool EnableCrashReporting; + public bool EnableInsights; } public class StreamingAssetsFile diff --git a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/PlayerBuildProgramLibrary.Data.gen.csproj b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/PlayerBuildProgramLibrary.Data.gen.csproj index ca26ba36b7..3f29c8f429 100644 --- a/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/PlayerBuildProgramLibrary.Data.gen.csproj +++ b/Editor/IncrementalBuildPipeline/PlayerBuildProgramLibrary.Data/PlayerBuildProgramLibrary.Data.gen.csproj @@ -1,5 +1,6 @@  - + + PlayerBuildProgramLibrary.Data netstandard2.1 @@ -14,4 +15,5 @@ + diff --git a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs index e496ccbdd9..fc674d689c 100644 --- a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs +++ b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/Data.cs @@ -56,6 +56,7 @@ public class AssemblyData public class ScriptCompilationData_Out { public AssemblyData_Out[] Assemblies; + public bool LocalizeCompilerMessages; } public class AssemblyData_Out diff --git a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/ScriptCompilationBuildProgram.Data.gen.csproj b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/ScriptCompilationBuildProgram.Data.gen.csproj index 010f8a8c43..d714f512c1 100644 --- a/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/ScriptCompilationBuildProgram.Data.gen.csproj +++ b/Editor/IncrementalBuildPipeline/ScriptCompilationBuildProgram.Data/ScriptCompilationBuildProgram.Data.gen.csproj @@ -1,5 +1,6 @@  - + + ScriptCompilationBuildProgram.Data netstandard2.1 @@ -13,4 +14,5 @@ + diff --git a/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs b/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs index fe01b8454a..bf615783af 100644 --- a/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs +++ b/Editor/Mono/2D/SpriteAtlas/EditorSpriteAtlas.bindings.cs @@ -7,6 +7,7 @@ using UnityEngine.U2D; using UnityEngine.Bindings; using System; +using UnityEditor.AssetImporters; namespace UnityEditor.U2D { @@ -100,12 +101,33 @@ public static class SpriteAtlasExtensions extern public static void Remove([NotNull] this SpriteAtlas spriteAtlas, UnityEngine.Object[] objects); extern internal static void RemoveAt([NotNull] this SpriteAtlas spriteAtlas, int index); extern public static UnityEngine.Object[] GetPackables([NotNull] this SpriteAtlas spriteAtlas); + extern internal static void SetV2([NotNull] this SpriteAtlas spriteAtlas); + internal static void RegisterAndPackAtlas(this SpriteAtlas spriteAtlas, AssetImportContext context, AssetImporter importer, U2D.ScriptablePacker scriptablePacker) + { + RegisterAndPackAtlasInternal(spriteAtlas, context, importer, scriptablePacker); + } + extern private static void RegisterAndPackAtlasInternal([NotNull] this SpriteAtlas spriteAtlas, [NotNull] AssetImportContext context, [NotNull] AssetImporter importer, UnityEngine.Object scriptablePacker); extern public static SpriteAtlasTextureSettings GetTextureSettings([NotNull] this SpriteAtlas spriteAtlas); extern public static void SetTextureSettings([NotNull] this SpriteAtlas spriteAtlas, SpriteAtlasTextureSettings src); extern public static SpriteAtlasPackingSettings GetPackingSettings([NotNull] this SpriteAtlas spriteAtlas); extern public static void SetPackingSettings([NotNull] this SpriteAtlas spriteAtlas, SpriteAtlasPackingSettings src); - extern public static TextureImporterPlatformSettings GetPlatformSettings([NotNull] this SpriteAtlas spriteAtlas, string buildTarget); - extern public static void SetPlatformSettings([NotNull] this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src); + + [NativeName("GetPlatformSettings")] + extern private static TextureImporterPlatformSettings GetPlatformSettings_Internal([NotNull] this SpriteAtlas spriteAtlas, string buildTarget); + public static TextureImporterPlatformSettings GetPlatformSettings(this SpriteAtlas spriteAtlas, string buildTarget) + { + buildTarget = TextureImporter.GetTexturePlatformSerializationName(buildTarget); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + return GetPlatformSettings_Internal(spriteAtlas, buildTarget); + } + + [NativeName("SetPlatformSettings")] + extern private static void SetPlatformSettings_Internal([NotNull] this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src); + public static void SetPlatformSettings(this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src) + { + src.name = TextureImporter.GetTexturePlatformSerializationName(src.name); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + SetPlatformSettings_Internal(spriteAtlas, src); + } + extern public static void SetIncludeInBuild([NotNull] this SpriteAtlas spriteAtlas, bool value); extern public static void SetIsVariant([NotNull] this SpriteAtlas spriteAtlas, bool value); extern public static void SetMasterAtlas([NotNull] this SpriteAtlas spriteAtlas, SpriteAtlas value); @@ -119,8 +141,23 @@ public static class SpriteAtlasExtensions extern internal static TextureFormat GetTextureFormat([NotNull] this SpriteAtlas spriteAtlas, BuildTarget target); extern internal static Sprite[] GetPackedSprites([NotNull] this SpriteAtlas spriteAtlas); extern internal static Hash128 GetStoredHash([NotNull] this SpriteAtlas spriteAtlas); - extern internal static TextureImporterPlatformSettings GetSecondaryPlatformSettings([NotNull] this SpriteAtlas spriteAtlas, string buildTarget, string secondaryTextureName); - extern internal static void SetSecondaryPlatformSettings([NotNull] this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src, string secondaryTextureName); + + [NativeName("GetSecondaryPlatformSettings")] + extern private static TextureImporterPlatformSettings GetSecondaryPlatformSettings_Internal([NotNull] this SpriteAtlas spriteAtlas, string buildTarget, string secondaryTextureName); + internal static TextureImporterPlatformSettings GetSecondaryPlatformSettings(this SpriteAtlas spriteAtlas, string buildTarget, string secondaryTextureName) + { + buildTarget = TextureImporter.GetTexturePlatformSerializationName(buildTarget); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + return GetSecondaryPlatformSettings_Internal(spriteAtlas, buildTarget, secondaryTextureName); + } + + [NativeName("SetSecondaryPlatformSettings")] + extern private static void SetSecondaryPlatformSettings_Internal([NotNull] this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src, string secondaryTextureName); + internal static void SetSecondaryPlatformSettings(this SpriteAtlas spriteAtlas, TextureImporterPlatformSettings src, string secondaryTextureName) + { + src.name = TextureImporter.GetTexturePlatformSerializationName(src.name); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + SetSecondaryPlatformSettings_Internal(spriteAtlas, src, secondaryTextureName); + } + extern internal static bool GetSecondaryColorSpace([NotNull] this SpriteAtlas spriteAtlas, string secondaryTextureName); extern internal static void SetSecondaryColorSpace([NotNull] this SpriteAtlas spriteAtlas, string secondaryTextureName, bool srGB); extern internal static void DeleteSecondaryPlatformSettings([NotNull] this SpriteAtlas spriteAtlas, string secondaryTextureName); diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs index 71a6d39d3a..12a8178c4f 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs @@ -15,7 +15,7 @@ namespace UnityEditor.U2D { // SpriteAtlas Importer lets you modify [[SpriteAtlas]] - [HelpURL("https://docs.unity3d.com/2023.2/Documentation/Manual/SpriteAtlasV2.html")] + [HelpURL("https://docs.unity3d.com/6000.2/Documentation/Manual/sprite/atlas/v2/sprite-atlas-v2.html")] [NativeHeader("Editor/Src/2D/SpriteAtlas/SpriteAtlasImporter.h")] public sealed partial class SpriteAtlasImporter : AssetImporter { @@ -24,11 +24,41 @@ public sealed partial class SpriteAtlasImporter : AssetImporter extern public bool includeInBuild { get; set; } extern public SpriteAtlasPackingSettings packingSettings { get; set; } extern public SpriteAtlasTextureSettings textureSettings { get; set; } - extern public void SetPlatformSettings(TextureImporterPlatformSettings src); - extern public TextureImporterPlatformSettings GetPlatformSettings(string buildTarget); + + [NativeName("SetPlatformSettings")] + extern private void SetPlatformSettings_Internal(TextureImporterPlatformSettings src); + public void SetPlatformSettings(TextureImporterPlatformSettings src) + { + src.name = TextureImporter.GetTexturePlatformSerializationName(src.name); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + SetPlatformSettings_Internal(src); + } + + [NativeName("GetPlatformSettings")] + extern private TextureImporterPlatformSettings GetPlatformSettings_Internal(string buildTarget); + public TextureImporterPlatformSettings GetPlatformSettings(string buildTarget) + { + buildTarget = TextureImporter.GetTexturePlatformSerializationName(buildTarget); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + return GetPlatformSettings_Internal(buildTarget); + } + extern internal TextureFormat GetTextureFormat(BuildTarget target); - extern internal TextureImporterPlatformSettings GetSecondaryPlatformSettings(string buildTarget, string secondaryTextureName); - extern internal void SetSecondaryPlatformSettings(TextureImporterPlatformSettings src, string secondaryTextureName); + + [NativeName("GetSecondaryPlatformSettings")] + extern private TextureImporterPlatformSettings GetSecondaryPlatformSettings_Internal(string buildTarget, string secondaryTextureName); + internal TextureImporterPlatformSettings GetSecondaryPlatformSettings(string buildTarget, string secondaryTextureName) + { + buildTarget = TextureImporter.GetTexturePlatformSerializationName(buildTarget); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + return GetSecondaryPlatformSettings_Internal(buildTarget, secondaryTextureName); + } + + [NativeName("SetSecondaryPlatformSettings")] + extern private void SetSecondaryPlatformSettings_Internal(TextureImporterPlatformSettings src, string secondaryTextureName); + internal void SetSecondaryPlatformSettings(TextureImporterPlatformSettings src, string secondaryTextureName) + { + src.name = TextureImporter.GetTexturePlatformSerializationName(src.name); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + SetSecondaryPlatformSettings_Internal(src, secondaryTextureName); + } + extern internal bool GetSecondaryColorSpace(string secondaryTextureName); extern internal void SetSecondaryColorSpace(string secondaryTextureName, bool srGB); extern internal void DeleteSecondaryPlatformSettings(string secondaryTextureName); diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs index 6ad7b5f4ee..2251994103 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs @@ -430,7 +430,7 @@ protected void PackPreviewGUI() using (new GUILayout.HorizontalScope()) { - using (new EditorGUI.DisabledScope(!HasModified() || !IsValidAtlas())) + using (new EditorGUI.DisabledScope(!HasModified() || !IsValidAtlas() || Application.isPlaying)) { GUILayout.FlexibleSpace(); @@ -708,7 +708,7 @@ private void HandlePlatformSettingUI(string secondaryTextureName) ITexturePlatformSettingsView view = isSecondary ? m_SecondaryTexturePlatformSettingsView : m_TexturePlatformSettingsView; if (shownTextureFormatPage == -1) { - if (m_TexturePlatformSettingsController.HandleDefaultSettings(defaultPlatformSettings, m_TexturePlatformSettingsView, m_TexturePlatformSettingTextureHelper)) + if (m_TexturePlatformSettingsController.HandleDefaultSettings(defaultPlatformSettings, view, m_TexturePlatformSettingTextureHelper)) { for (var i = 0; i < defaultPlatformSettings.Count; ++i) { @@ -746,7 +746,7 @@ private void HandlePlatformSettingUI(string secondaryTextureName) } m_TexturePlatformSettingsView.buildPlatformTitle = buildPlatform.title.text; - if (m_TexturePlatformSettingsController.HandlePlatformSettings(buildPlatform.defaultTarget, platformSettings, m_TexturePlatformSettingsView, m_TexturePlatformSettingTextureHelper)) + if (m_TexturePlatformSettingsController.HandlePlatformSettings(buildPlatform.defaultTarget, platformSettings, view, m_TexturePlatformSettingTextureHelper)) { for (var i = 0; i < platformSettings.Count; ++i) { diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs index 58e37d59fc..cebb1e79ff 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasInspector.cs @@ -194,6 +194,17 @@ bool AllTargetsAreMaster() void OnEnable() { + if (targets == null) + return; + var validCount = 0; + foreach (var so in targets) + { + if (so != null) + validCount++; + } + if (validCount == 0) + return; + m_FilterMode = serializedObject.FindProperty("m_EditorData.textureSettings.filterMode"); m_AnisoLevel = serializedObject.FindProperty("m_EditorData.textureSettings.anisoLevel"); m_GenerateMipMaps = serializedObject.FindProperty("m_EditorData.textureSettings.generateMipMaps"); @@ -374,22 +385,25 @@ public override void OnInspectorGUI() if (targets.Length == 1 && AllTargetsAreMaster()) HandlePackableListUI(); - bool spriteAtlasPackignEnabled = (EditorSettings.spritePackerMode == SpritePackerMode.BuildTimeOnlyAtlas + bool spriteAtlasPackingEnabled = (EditorSettings.spritePackerMode == SpritePackerMode.BuildTimeOnlyAtlas || EditorSettings.spritePackerMode == SpritePackerMode.AlwaysOnAtlas || EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2); - if (spriteAtlasPackignEnabled) + if (spriteAtlasPackingEnabled && !Application.isPlaying) { - if (GUILayout.Button(styles.packButton, GUILayout.ExpandWidth(false))) + using (new EditorGUI.DisabledScope(!Editor.IsPersistent(spriteAtlas))) { - SpriteAtlas[] spriteAtlases = new SpriteAtlas[targets.Length]; - for (int i = 0; i < spriteAtlases.Length; ++i) - spriteAtlases[i] = (SpriteAtlas)targets[i]; + if (GUILayout.Button(styles.packButton, GUILayout.ExpandWidth(false))) + { + SpriteAtlas[] spriteAtlases = new SpriteAtlas[targets.Length]; + for (int i = 0; i < spriteAtlases.Length; ++i) + spriteAtlases[i] = (SpriteAtlas)targets[i]; - SpriteAtlasUtility.PackAtlases(spriteAtlases, EditorUserBuildSettings.activeBuildTarget); + SpriteAtlasUtility.PackAtlases(spriteAtlases, EditorUserBuildSettings.activeBuildTarget); - // Packing an atlas might change platform settings in the process, reinitialize - SyncPlatformSettings(); + // Packing an atlas might change platform settings in the process, reinitialize + SyncPlatformSettings(); - GUIUtility.ExitGUI(); + GUIUtility.ExitGUI(); + } } } else @@ -413,7 +427,10 @@ private void HandleCommonSettingUI() EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = atlasType == AtlasType.Undefined; - atlasType = (AtlasType)EditorGUILayout.IntPopup(styles.atlasTypeLabel, (int)atlasType, styles.atlasTypeOptions, styles.atlasTypeValues); + using (new EditorGUI.DisabledScope(!Editor.IsPersistent(spriteAtlas))) + { + atlasType = (AtlasType)EditorGUILayout.IntPopup(styles.atlasTypeLabel, (int)atlasType, styles.atlasTypeOptions, styles.atlasTypeValues); + } EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { diff --git a/Editor/Mono/Animation/AnimationUtility.bindings.cs b/Editor/Mono/Animation/AnimationUtility.bindings.cs index e93273d6c0..0b40f052e6 100644 --- a/Editor/Mono/Animation/AnimationUtility.bindings.cs +++ b/Editor/Mono/Animation/AnimationUtility.bindings.cs @@ -81,6 +81,15 @@ internal enum PolynomialValid TooManySegments = 3 } + internal enum DiscreteBindingResult + { + Valid = 0, + InvalidScript = 1, + MissingField = 2, + IncompatibleFieldType = 3, + MissingDiscreteAttribute = 4 + } + public delegate void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, CurveModifiedType type); public static OnCurveWasModified onCurveWasModified; @@ -121,7 +130,12 @@ public static AnimationClip[] GetAnimationClips(GameObject gameObject) var extraClips = new List(); clipSources[i].GetAnimationClips(extraClips); - allClips.AddRange(extraClips); + allClips.Capacity = allClips.Count + extraClips.Count; + foreach (var clip in extraClips) + { + if (clip != null) + allClips.Add(clip); + } } return allClips.ToArray(); @@ -174,11 +188,11 @@ internal static System.Type GetEditorCurveValueType(ScriptableObject scriptableO extern public static bool GetDiscreteIntValue([NotNull] GameObject root, EditorCurveBinding binding, out int data); public static bool GetObjectReferenceValue(GameObject root, EditorCurveBinding binding, out Object data) { - data = Internal_GetObjectReferenceValue(root, binding); - return data != null; + data = Internal_GetObjectReferenceValue(root, binding, out bool result); + return result; } - extern private static Object Internal_GetObjectReferenceValue([NotNull] GameObject root, EditorCurveBinding binding); + extern private static Object Internal_GetObjectReferenceValue([NotNull] GameObject root, EditorCurveBinding binding, out bool result); extern public static Object GetAnimatedObject([NotNull] GameObject root, EditorCurveBinding binding); @@ -270,6 +284,8 @@ internal static void SetEditorCurveNoSync(AnimationClip clip, EditorCurveBinding [NativeThrows] extern private static void Internal_SetEditorCurve([NotNull] AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve, bool syncEditorCurves); + extern internal static DiscreteBindingResult IsDiscreteIntBinding(EditorCurveBinding binding); + extern internal static void SyncEditorCurves([NotNull] AnimationClip clip); private static void Internal_InvokeOnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, CurveModifiedType type) diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchy.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchy.cs index 4adb9ff208..cd32c12f4d 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchy.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchy.cs @@ -5,6 +5,9 @@ using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState; namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyBuilder.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyBuilder.cs index 55a8c81379..51618a0dbd 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyBuilder.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyBuilder.cs @@ -9,6 +9,8 @@ using System.Linq; using UnityEditor.IMGUI.Controls; using Object = UnityEngine.Object; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewUtility = UnityEditor.IMGUI.Controls.TreeViewUtility; namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs index 312076b97d..cb9654a3ff 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyDataSource.cs @@ -4,6 +4,8 @@ using UnityEditor; using UnityEditor.IMGUI.Controls; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; +using TreeViewDataSource = UnityEditor.IMGUI.Controls.TreeViewDataSource; namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs index b1b39169dc..bd964f16b6 100644 --- a/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AddCurvesPopupHierarchyGUI.cs @@ -7,6 +7,9 @@ using UnityEngine; using System.Collections.Generic; using UnityEditor.ShortcutManagement; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewGUI = UnityEditor.IMGUI.Controls.TreeViewGUI; namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs index da5fb4aa72..4223a5ea38 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowControl.cs @@ -129,7 +129,6 @@ private static bool HasFlag(ResampleFlags flags, ResampleFlags flag) private AnimationClipPlayable m_CandidateClipPlayable; private AnimationClipPlayable m_DefaultPosePlayable; private bool m_UsesPostProcessComponents = false; - HashSet m_ObjectsModifiedDuringAnimationMode = new HashSet(); private static ProfilerMarker s_ResampleAnimationMarker = new ProfilerMarker("AnimationWindowControl.ResampleAnimation"); @@ -717,42 +716,30 @@ private bool AllowRecordingPrefabPropertyOverridesFor(UnityEngine.Object compone if (componentOrGameObject == null) throw new ArgumentNullException(nameof(componentOrGameObject)); - GameObject inputGameObject = null; - if (componentOrGameObject is Component) - { - inputGameObject = ((Component)componentOrGameObject).gameObject; - } - else if (componentOrGameObject is GameObject) - { - inputGameObject = (GameObject)componentOrGameObject; - } - else - { + if (componentOrGameObject is not Component && componentOrGameObject is not GameObject) return true; - } var rootOfAnimation = state.activeRootGameObject; if (rootOfAnimation == null) return true; - // If the input object is a child of the current root of animation then disallow recording of prefab property overrides - // since the input object is currently being setup for animation recording - return inputGameObject.transform.IsChildOf(rootOfAnimation.transform) == false; + return false; } void OnExitingAnimationMode() { Undo.postprocessModifications -= PostprocessAnimationRecordingModifications; PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor; + } - // Ensures Prefab instance overrides are recorded for properties that was changed while in AnimationMode - foreach (var obj in m_ObjectsModifiedDuringAnimationMode) + void RecordPropertyOverridesForNonAnimatedProperties(UndoPropertyModification[] modifications) + { + PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor; + foreach (var mod in modifications) { - if (obj != null) - EditorUtility.SetDirty(obj); + PrefabUtility.RecordPrefabInstancePropertyModifications(mod.currentValue.target); } - - m_ObjectsModifiedDuringAnimationMode.Clear(); + PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor; } private UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications) @@ -769,17 +756,15 @@ private UndoPropertyModification[] PostprocessAnimationRecordingModifications(Un else if (previewing) modifications = RegisterCandidates(modifications); + // Fix for UUM-61742: Unrecorded Prefab overloads should be recorded immediately + RecordPropertyOverridesForNonAnimatedProperties(modifications); + RefreshDisplayNamesOnArrayTopologicalChange(modifications); // Only resample when playable graph has been customized with post process nodes. if (m_UsesPostProcessComponents) ResampleAnimation(ResampleFlags.None); - foreach (var mod in modifications) - { - m_ObjectsModifiedDuringAnimationMode.Add(mod.currentValue.target); - } - return modifications; } @@ -920,7 +905,7 @@ private List GetKeys(PropertyModification[] modificatio int keyIndex = curve.GetKeyframeIndex(state.time); if (keyIndex >= 0) { - keys.Add(curve.m_Keyframes[keyIndex]); + keys.Add(curve.keyframes[keyIndex]); } } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs index 966a51b453..c7d0cf939e 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs @@ -15,7 +15,7 @@ internal class AnimationWindowCurve : IComparable, IEquata { public const float timeEpsilon = 0.00001f; - public List m_Keyframes; + private List m_Keyframes; private EditorCurveBinding m_Binding; private int m_BindingHashCode; @@ -46,6 +46,8 @@ internal class AnimationWindowCurve : IComparable, IEquata public bool animationIsEditable { get { return m_SelectionBinding != null ? m_SelectionBinding.animationIsEditable : true; } } public int selectionID { get { return m_SelectionBinding != null ? m_SelectionBinding.id : 0; } } + public IReadOnlyList keyframes => m_Keyframes; + private object defaultValue { get @@ -190,17 +192,10 @@ public AnimationCurve ToAnimationCurve() AnimationCurve animationCurve = new AnimationCurve(); List keys = new List(); - float lastFrameTime = float.MinValue; - for (int i = 0; i < length; i++) { - // Make sure we don't get two keyframes in an exactly the same time. We just ignore those. - if (Mathf.Abs(m_Keyframes[i].time - lastFrameTime) > AnimationWindowCurve.timeEpsilon) - { - Keyframe newKeyframe = m_Keyframes[i].ToKeyframe(); - keys.Add(newKeyframe); - lastFrameTime = m_Keyframes[i].time; - } + Keyframe newKeyframe = m_Keyframes[i].ToKeyframe(); + keys.Add(newKeyframe); } animationCurve.keys = keys.ToArray(); @@ -212,17 +207,10 @@ public ObjectReferenceKeyframe[] ToObjectCurve() int length = m_Keyframes.Count; List keys = new List(); - float lastFrameTime = float.MinValue; - for (int i = 0; i < length; i++) { - // Make sure we don't get two keyframes in an exactly the same time. We just ignore those. - if (Mathf.Abs(m_Keyframes[i].time - lastFrameTime) > AnimationWindowCurve.timeEpsilon) - { - ObjectReferenceKeyframe newKeyframe = m_Keyframes[i].ToObjectReferenceKeyframe(); - lastFrameTime = newKeyframe.time; - keys.Add(newKeyframe); - } + ObjectReferenceKeyframe newKeyframe = m_Keyframes[i].ToObjectReferenceKeyframe(); + keys.Add(newKeyframe); } keys.Sort((a, b) => a.time.CompareTo(b.time)); @@ -301,6 +289,11 @@ public void RemoveKeyframe(AnimationKeyTime time) } } + public void RemoveKeyframe(AnimationWindowKeyframe keyframe) + { + m_Keyframes.Remove(keyframe); + } + public bool HasKeyframe(AnimationKeyTime time) { return GetKeyframeIndex(time) != -1; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchy.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchy.cs index 2147351f6a..a5e353264b 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchy.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchy.cs @@ -6,6 +6,9 @@ using UnityEditor; using System.Collections.Generic; using UnityEditor.IMGUI.Controls; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState; namespace UnityEditorInternal { @@ -44,6 +47,7 @@ internal class AnimationWindowHierarchy // Animation window shared state private AnimationWindowState m_State; private TreeViewController m_TreeView; + private AnimationWindowHierarchyGUI m_HierarchyGUI; public Vector2 GetContentSize() { @@ -65,6 +69,7 @@ public void OnGUI(Rect position) { m_TreeView.OnEvent(); m_TreeView.OnGUI(position, GUIUtility.GetControlID(FocusType.Keyboard)); + m_HierarchyGUI.ReclaimPendingFieldFocus(); } public void Init(EditorWindow owner, Rect rect) @@ -74,9 +79,10 @@ public void Init(EditorWindow owner, Rect rect) m_TreeView = new TreeViewController(owner, m_State.hierarchyState); m_State.hierarchyData = new AnimationWindowHierarchyDataSource(m_TreeView, m_State); + m_HierarchyGUI = new AnimationWindowHierarchyGUI(m_TreeView, m_State); m_TreeView.Init(rect, m_State.hierarchyData, - new AnimationWindowHierarchyGUI(m_TreeView, m_State), + m_HierarchyGUI, null ); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs index 9352c8c9d7..00dbb47ccd 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyDataSource.cs @@ -7,6 +7,11 @@ using UnityEditor.IMGUI.Controls; using UnityEngine; using Object = UnityEngine.Object; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewUtility = UnityEditor.IMGUI.Controls.TreeViewUtility; +using TreeViewDataSource = UnityEditor.IMGUI.Controls.TreeViewDataSource; + namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs index 1d1ae47c7e..af65ed17fd 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs @@ -9,6 +9,10 @@ using System.Linq; using UnityEditor.IMGUI.Controls; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; +using TreeViewGUI = UnityEditor.IMGUI.Controls.TreeViewGUI; +using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController; + namespace UnityEditorInternal { internal class AnimationWindowHierarchyGUI : TreeViewGUI @@ -30,6 +34,9 @@ internal class AnimationWindowHierarchyGUI : TreeViewGUI private int[] m_HierarchyItemValueControlIDs; private int[] m_HierarchyItemButtonControlIDs; + private bool m_NeedsToReclaimFieldFocus; + private int m_FieldToReclaimFocus; + private const float k_RowRightOffset = 10; private const float k_ValueFieldDragWidth = 15; private const float k_ValueFieldWidth = 80; @@ -342,7 +349,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) if (curve.valueType == typeof(bool)) { - value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], (float)value != 0, GUIContent.none, EditorStyles.toggle) ? 1f : 0f; + value = GUI.Toggle(valueFieldRect, m_HierarchyItemValueControlIDs[row], Convert.ToSingle(value) != 0f, GUIContent.none, EditorStyles.toggle) ? 1f : 0f; } else { @@ -352,11 +359,12 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) && Event.current.type == EventType.KeyDown && (Event.current.character == '\n' || (int)Event.current.character == 3)); - // Force back keyboard focus to float field editor when editing it. - // TreeView forces keyboard focus on itself at mouse down and we lose focus here. + // Force back keyboard focus to float field editor when editing it since the TreeView forces keyboard focus on itself at mouse down. + // The focus will be reclaimed after the TreeViewController.OnGUI call. if (EditorGUI.s_RecycledEditor.controlID == id && Event.current.type == EventType.MouseDown && valueFieldRect.Contains(Event.current.mousePosition)) { - GUIUtility.keyboardControl = id; + m_NeedsToReclaimFieldFocus = true; + m_FieldToReclaimFocus = id; } if (curve.isDiscreteCurve) @@ -365,7 +373,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) valueFieldRect, valueFieldDragRect, id, - (int)value, + Convert.ToInt32(value), EditorGUI.kIntFieldFormatString, m_AnimationSelectionTextField, true, @@ -382,7 +390,7 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) valueFieldRect, valueFieldDragRect, id, - (float)value, + Convert.ToSingle(value), "g5", m_AnimationSelectionTextField, true); @@ -392,7 +400,8 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) Event.current.Use(); } - if (float.IsInfinity((float)value) || float.IsNaN((float)value)) + var floatValue = Convert.ToSingle(value); + if (float.IsInfinity(floatValue) || float.IsNaN(floatValue)) value = 0f; } } @@ -420,6 +429,15 @@ private void DoValueField(Rect rect, AnimationWindowHierarchyNode node, int row) } } + internal void ReclaimPendingFieldFocus() + { + if (m_NeedsToReclaimFieldFocus) + { + GUIUtility.keyboardControl = m_FieldToReclaimFocus; + m_NeedsToReclaimFieldFocus = false; + } + } + private bool DoTreeViewButton(int id, Rect position, GUIContent content, GUIStyle style) { Event evt = Event.current; @@ -491,7 +509,7 @@ private void DoCurveColorIndicator(Rect rect, AnimationWindowHierarchyNode node) { foreach (var curve in node.curves) { - if (curve.m_Keyframes.Any(key => state.time.ContainsTime(key.time))) + if (curve.keyframes.Any(key => state.time.ContainsTime(key.time))) { hasKey = true; } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs index bcd960f940..833ebd9bc4 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyNode.cs @@ -6,6 +6,8 @@ using UnityEngine; using System.Collections.Generic; using UnityEditor.IMGUI.Controls; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; + namespace UnityEditorInternal { diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs index cbdfa58b5e..e9fa64f7fa 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs @@ -149,9 +149,9 @@ public int GetHash() public int GetIndex() { - for (int i = 0; i < curve.m_Keyframes.Count; i++) + for (int i = 0; i < curve.keyframes.Count; i++) { - if (curve.m_Keyframes[i] == this) + if (curve.keyframes[i] == this) { return i; } @@ -167,11 +167,11 @@ public Keyframe ToKeyframe() // case 1395978 // Negative int values converted to float create NaN values. Limiting discrete int values to only positive values // until we rewrite the animation backend with dedicated int curves. - floatValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(Math.Max((int)value, 0)); + floatValue = UnityEngine.Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(Math.Max(Convert.ToInt32(value), 0)); } else { - floatValue = (float)value; + floatValue = Convert.ToSingle(value); } var keyframe = new Keyframe(time, floatValue, inTangent, outTangent); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs index c7fc0557f5..6de5dd2e79 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using UnityEditor.IMGUI.Controls; using Object = UnityEngine.Object; +using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; namespace UnityEditorInternal { @@ -514,7 +515,7 @@ private void SaveSelectedKeys(string undoLabel) List toBeDeleted = new List(); // If selected keys are dragged over non-selected keyframe at exact same time, then delete the unselected ones underneath - foreach (AnimationWindowKeyframe other in snapshot.curve.m_Keyframes) + foreach (AnimationWindowKeyframe other in snapshot.curve.keyframes) { // Keyframe is in selection, skip. if (snapshot.selectedKeys.Exists(liveEditKey => liveEditKey.key == other)) @@ -529,7 +530,7 @@ private void SaveSelectedKeys(string undoLabel) foreach (AnimationWindowKeyframe deletedKey in toBeDeleted) { - snapshot.curve.m_Keyframes.Remove(deletedKey); + snapshot.curve.RemoveKeyframe(deletedKey); } } @@ -697,7 +698,7 @@ void RebuildAllCurvesCacheIfNecessary() m_AllCurvesCache.Clear(); var animationClip = activeAnimationClip; - if (animationClip == null) + if (animationClip == null || (!selection.animationIsEditable && !showReadOnly)) return; EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClip); @@ -903,14 +904,14 @@ public CurveWrapper[] activeCurveWrappers { List activeCurveWrappers = new List(); foreach (AnimationWindowCurve curve in activeCurves) - if (!curve.isDiscreteCurve) - activeCurveWrappers.Add(AnimationWindowUtility.GetCurveWrapper(curve, curve.clip)); + if (AnimationWindowUtility.GetCurveWrapper(curve, curve.clip) is CurveWrapper wrapper) + activeCurveWrappers.Add(wrapper); // If there are no active curves, we would end up with empty curve editor so we just give all curves insteads if (!activeCurveWrappers.Any()) foreach (AnimationWindowCurve curve in filteredCurves) - if (!curve.isDiscreteCurve) - activeCurveWrappers.Add(AnimationWindowUtility.GetCurveWrapper(curve, curve.clip)); + if (AnimationWindowUtility.GetCurveWrapper(curve, curve.clip) is CurveWrapper wrapper) + activeCurveWrappers.Add(wrapper); m_ActiveCurveWrappersCache = activeCurveWrappers.ToArray(); } @@ -984,7 +985,7 @@ public AnimationWindowKeyframe activeKeyframe { foreach (AnimationWindowCurve curve in filteredCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { if (keyframe.GetHash() == m_ActiveKeyframeHash) m_ActiveKeyframeCache = keyframe; @@ -1009,7 +1010,7 @@ public List selectedKeys m_SelectedKeysCache = new List(); foreach (AnimationWindowCurve curve in filteredCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { if (KeyIsSelected(keyframe)) { @@ -1139,7 +1140,7 @@ public void DeleteKeys(List keys) curves.Add(keyframe.curve); UnselectKey(keyframe); - keyframe.curve.m_Keyframes.Remove(keyframe); + keyframe.curve.RemoveKeyframe(keyframe); } SaveCurves(activeAnimationClip, curves, kEditCurveUndoLabel); @@ -1162,7 +1163,7 @@ public void StartLiveEdit() { LiveEditCurve snapshot = new LiveEditCurve(); snapshot.curve = selectedKey.curve; - foreach (AnimationWindowKeyframe key in selectedKey.curve.m_Keyframes) + foreach (AnimationWindowKeyframe key in selectedKey.curve.keyframes) { LiveEditKeyframe liveEditKey = new LiveEditKeyframe(); liveEditKey.keySnapshot = new AnimationWindowKeyframe(key); @@ -1400,7 +1401,7 @@ public void CopyAllActiveCurves() { foreach (AnimationWindowCurve curve in activeCurves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { s_KeyframeClipboard.Add(new AnimationWindowKeyframe(keyframe)); } @@ -1476,14 +1477,16 @@ public void PasteKeys() // Only allow pasting of key frame from numerical curves to numerical curves or from pptr curves to pptr curves. if ((newKeyframe.time >= 0.0f) && (newKeyframe.curve != null) && (newKeyframe.curve.isPPtrCurve == keyframe.curve.isPPtrCurve)) { - if (newKeyframe.curve.HasKeyframe(AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate))) - newKeyframe.curve.RemoveKeyframe(AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate)); + var keyTime = AnimationKeyTime.Time(newKeyframe.time, newKeyframe.curve.clip.frameRate); + + if (newKeyframe.curve.HasKeyframe(keyTime)) + newKeyframe.curve.RemoveKeyframe(keyTime); // When copy-pasting multiple keyframes (curve), its a continous thing. This is why we delete the existing keyframes in the pasted range. if (lastTargetCurve == newKeyframe.curve) newKeyframe.curve.RemoveKeysAtRange(lastTime, newKeyframe.time); - newKeyframe.curve.m_Keyframes.Add(newKeyframe); + newKeyframe.curve.AddKeyframe(newKeyframe, keyTime); SelectKey(newKeyframe); // TODO: Optimize to only save curve once instead once per keyframe SaveCurve(newKeyframe.curve.clip, newKeyframe.curve, kEditCurveUndoLabel); @@ -1838,7 +1841,7 @@ public float clipFrameRate // Reposition all keyframes to match the new sampling rate foreach (var curve in allCurves) { - foreach (var key in curve.m_Keyframes) + foreach (var key in curve.keyframes) { int frame = AnimationKeyTime.Time(key.time, clipFrameRate).frame; key.time = AnimationKeyTime.Frame(frame, value).time; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs index 89ae6d3ddf..df9a25a1a8 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs @@ -280,7 +280,7 @@ public static AnimationWindowKeyframe AddKeyframeToCurve(AnimationWindowCurve cu } else if (type == typeof(bool) || type == typeof(float) || type == typeof(int)) { - Keyframe tempKey = new Keyframe(time.time, (float)value); + Keyframe tempKey = new Keyframe(time.time, Convert.ToSingle(value)); if (type == typeof(bool)) { AnimationUtility.SetKeyLeftTangentMode(ref tempKey, TangentMode.Constant); @@ -888,7 +888,7 @@ public static float GetNextKeyframeTime(AnimationWindowCurve[] curves, float cur foreach (AnimationWindowCurve curve in curves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); if (keyTime.frame <= candidateKeyTime.frame && keyTime.frame >= nextTime.frame) @@ -914,7 +914,7 @@ public static float GetPreviousKeyframeTime(AnimationWindowCurve[] curves, float foreach (AnimationWindowCurve curve in curves) { - foreach (AnimationWindowKeyframe keyframe in curve.m_Keyframes) + foreach (AnimationWindowKeyframe keyframe in curve.keyframes) { AnimationKeyTime keyTime = AnimationKeyTime.Time(keyframe.time, frameRate); if (keyTime.frame >= candidateKeyTime.frame && keyTime.frame <= previousTime.frame) @@ -1279,6 +1279,10 @@ private static CurveWrapper.PreProcessKeyMovement CreateKeyPreprocessorForCurve( public static CurveWrapper GetCurveWrapper(AnimationWindowCurve curve, AnimationClip clip) { + //Discrete and PPtr curves are not allowed to create curve wrappers. + if (curve.isDiscreteCurve || curve.isPPtrCurve) + return null; + CurveWrapper curveWrapper = new CurveWrapper(); curveWrapper.renderer = CreateRendererForCurve(curve); curveWrapper.preProcessKeyMovementDelegate = CreateKeyPreprocessorForCurve(curve); @@ -1299,8 +1303,8 @@ public static AnimationWindowKeyframe CurveSelectionToAnimationWindowKeyframe(Cu { int curveID = curve.GetHashCode(); if (curveID == curveSelection.curveID) - if (curve.m_Keyframes.Count > curveSelection.key) - return curve.m_Keyframes[curveSelection.key]; + if (curve.keyframes.Count > curveSelection.key) + return curve.keyframes[curveSelection.key]; } return null; diff --git a/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs b/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs index 05000b27e2..6678280c1d 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveBindingUtility.cs @@ -29,7 +29,10 @@ public static object GetCurrentValue(AnimationWindowState state, EditorCurveBind // Otherwise, evaluate AnimationWindowCurve at current time. public static object GetCurrentValue(AnimationWindowState state, AnimationWindowCurve curve) { - if (state.previewing && curve.rootGameObject != null) + // UUM-66112 - state.linkedWithSequencer - Padding for issue in Timeline where muscle + // values are not updated in the editor when previewing in the Animation Window. + // Fallback to curve values. + if (state.previewing && curve.rootGameObject != null && !state.linkedWithSequencer) { return GetCurrentValue(state, curve.binding); } diff --git a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs index ccf91351f7..650bd7ee7e 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs @@ -3833,6 +3833,10 @@ WrapMode WrapModeIconPopup(Keyframe key, WrapMode oldWrap, float hOffset) case EventType.MouseDown: if (evt.button == 0 && r.Contains(evt.mousePosition)) { + if (Application.platform == RuntimePlatform.OSXEditor) + { + r.y = r.y - selectedPopupIndex * 16 - 19; + } EditorGUI.PopupCallbackInfo.instance = new EditorGUI.PopupCallbackInfo(controlID); EditorUtility.DisplayCustomMenu(r, popupStrings, selectedPopupIndex, EditorGUI.PopupCallbackInfo.instance.SetEnumValueDelegate, null); GUIUtility.keyboardControl = controlID; @@ -3842,6 +3846,10 @@ WrapMode WrapModeIconPopup(Keyframe key, WrapMode oldWrap, float hOffset) case EventType.KeyDown: if (evt.MainActionKeyForControl(controlID)) { + if (Application.platform == RuntimePlatform.OSXEditor) + { + r.y = r.y - selectedPopupIndex * 16 - 19; + } EditorGUI.PopupCallbackInfo.instance = new EditorGUI.PopupCallbackInfo(controlID); EditorUtility.DisplayCustomMenu(r, popupStrings, selectedPopupIndex, EditorGUI.PopupCallbackInfo.instance.SetEnumValueDelegate, null); evt.Use(); diff --git a/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs b/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs index 4d93f8e54d..0de9352fa1 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveEditorWindow.cs @@ -30,12 +30,13 @@ public enum NormalizationMode } //const int kToolbarHeight = 17; - const int kPresetsHeight = 46; + const int kPresetsHeight = 50; static CurveEditorWindow s_SharedCurveEditor; - CurveEditor m_CurveEditor; + internal CurveEditor m_CurveEditor; + Vector2 m_PresetScrollPosition; AnimationCurve m_Curve; Color m_Color; @@ -44,11 +45,13 @@ public enum NormalizationMode [SerializeField] GUIView m_DelegateView; - internal GUIView delegateView => m_DelegateView; public const string CurveChangedCommand = "CurveChanged"; public const string CurveChangeCompletedCommand = "CurveChangeCompleted"; + // Watch out when accessing this, it will create a new curve editor window if none exists yet. + // If you don't display the window (call Show()), it will cause an error when validating the layout + // (e.g. when the user maximizes a window). public static CurveEditorWindow instance { get @@ -100,6 +103,11 @@ public static Color color } } + internal static GUIView delegateView + { + get { return visible ? CurveEditorWindow.instance.m_DelegateView : null; } + } + public static bool visible { get { return s_SharedCurveEditor != null; } @@ -469,20 +477,38 @@ void OnGUI() m_CurveEditor.OnGUI(); // Preset swatch area - GUI.Box(new Rect(0, position.height - kPresetsHeight, position.width, kPresetsHeight), "", ms_Styles.curveSwatchArea); + var presetRect = new Rect(0, position.height - kPresetsHeight, position.width, kPresetsHeight); + GUI.Box(presetRect, "", ms_Styles.curveSwatchArea); + Color curveColor = m_Color; curveColor.a *= 0.6f; - const float margin = 45f; const float width = 40f; const float height = 25f; - float yPos = position.height - kPresetsHeight + (kPresetsHeight - height) * 0.5f; + const float spaceBetweenSwatches = 5f; + const float presetDropdownSize = 16f; + const float horizontalScrollbarHeight = 15f; + const float presetDropdownCenteringOffset = 2f; + float yPos = (kPresetsHeight - height) * 0.5f; InitCurvePresets(); CurvePresetLibrary curveLibrary = m_CurvePresets.GetPresetLibraryEditor().GetCurrentLib(); if (curveLibrary != null) { - for (int i = 0; i < curveLibrary.Count(); i++) + var numPresets = curveLibrary.Count(); + var presetDropDownRect = new Rect(spaceBetweenSwatches, yPos + presetDropdownCenteringOffset, presetDropdownSize, presetDropdownSize); + Rect contentRect = new Rect(0, 0, numPresets * (width + spaceBetweenSwatches) + presetDropDownRect.xMax, presetRect.height - horizontalScrollbarHeight); + m_PresetScrollPosition = GUI.BeginScrollView( + presetRect, // Rectangle of the visible area + m_PresetScrollPosition, // Current scroll position + contentRect, // Rectangle containing all content + false, // Always show horizontal scrollbar + false // Always show vertical scrollbar + ); + + PresetDropDown(presetDropDownRect); + + Rect swatchRect = new Rect(presetDropDownRect.xMax + spaceBetweenSwatches, yPos, width, height); + for (int i = 0; i < numPresets; i++) { - Rect swatchRect = new Rect(margin + (width + 5f) * i, yPos, width, height); m_GUIContent.tooltip = curveLibrary.GetName(i); if (GUI.Button(swatchRect, m_GUIContent, ms_Styles.curveSwatch)) { @@ -496,14 +522,11 @@ void OnGUI() if (Event.current.type == EventType.Repaint) curveLibrary.Draw(swatchRect, i); - if (swatchRect.xMax > position.width - 2 * margin) - break; + swatchRect.x += width + spaceBetweenSwatches; } + GUI.EndScrollView(); } - Rect presetDropDownButtonRect = new Rect(margin - 20f, yPos + 5f, 20, 20); - PresetDropDown(presetDropDownButtonRect); - // For adding default preset curves //if (EditorGUI.DropdownButton(new Rect (position.width -26, yPos, 20, 20), GUIContent.none, FocusType.Passive, "OL Plus")) // AddDefaultPresetsToCurrentLib (); diff --git a/Editor/Mono/Animation/AnimationWindow/DopeLine.cs b/Editor/Mono/Animation/AnimationWindow/DopeLine.cs index c576b1cf2c..da6b52f5f0 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeLine.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeLine.cs @@ -98,7 +98,7 @@ public List keys { m_Keys = new List(); foreach (AnimationWindowCurve curve in m_Curves) - foreach (AnimationWindowKeyframe key in curve.m_Keyframes) + foreach (AnimationWindowKeyframe key in curve.keyframes) m_Keys.Add(key); m_Keys.Sort((a, b) => a.time.CompareTo(b.time)); diff --git a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs index eb2bb1b48a..ae4b219977 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs @@ -806,7 +806,7 @@ private void AssignSpriteToSpriteRenderer(AnimationWindowCurve curve) if (rootGameObject == null) return; - var hasValidCurve = curve.m_Keyframes.Count > 0 && curve.binding.type == typeof(SpriteRenderer); + var hasValidCurve = curve.keyframes.Count > 0 && curve.binding.type == typeof(SpriteRenderer); if (!hasValidCurve) return; @@ -815,7 +815,7 @@ private void AssignSpriteToSpriteRenderer(AnimationWindowCurve curve) if (!hasValidSpriteRenderer) return; - var keyframe = curve.m_Keyframes[0]; + var keyframe = curve.keyframes[0]; var sprite = keyframe.value as Sprite; if (sprite != null) { @@ -1126,12 +1126,12 @@ public void FrameSelected() { foreach (AnimationWindowCurve curve in state.activeCurves) { - int keyCount = curve.m_Keyframes.Count; + int keyCount = curve.keyframes.Count; if (keyCount > 1) { - Vector2 pt1 = new Vector2(curve.m_Keyframes[0].time, 0.0f); - Vector2 pt2 = new Vector2(curve.m_Keyframes[keyCount - 1].time, 0.0f); + Vector2 pt1 = new Vector2(curve.keyframes[0].time, 0.0f); + Vector2 pt2 = new Vector2(curve.keyframes[keyCount - 1].time, 0.0f); if (firstKey) { diff --git a/Editor/Mono/Animation/AnimatorController.cs b/Editor/Mono/Animation/AnimatorController.cs index b0495c9602..f5a2c0e500 100644 --- a/Editor/Mono/Animation/AnimatorController.cs +++ b/Editor/Mono/Animation/AnimatorController.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using System.IO; using System.Collections.Generic; @@ -81,6 +82,16 @@ internal void RemoveLayers(List layerIndexes) AnimatorControllerLayer[] layerVector = this.layers; foreach (var layerIndex in layerIndexes) { + for (var i = 0; i < layerVector.Length; ++i) + { + var syncedLayerIndex = layerVector[i].syncedLayerIndex; + if (syncedLayerIndex > layerIndex) + { + // synced layer is after the layer being removed, so it's going to be shifted upon removal + layerVector[i].syncedLayerIndex = syncedLayerIndex - 1; + } + } + RemoveLayerInternal(layerIndex, ref layerVector); } this.layers = layerVector; @@ -265,6 +276,15 @@ public void SetStateEffectiveMotion(AnimatorState state, Motion motion) public void SetStateEffectiveMotion(AnimatorState state, Motion motion, int layerIndex) { + //delete existing nested blend tree asset + Motion selectedMotion = GetStateEffectiveMotion(state, layerIndex); + BlendTree blendTree = selectedMotion as BlendTree; + + if (blendTree != null && !AssetDatabase.IsMainAsset(blendTree)) + { + MecanimUtilities.DestroyBlendTreeRecursive(blendTree); + } + if (layers[layerIndex].syncedLayerIndex == -1) { undoHandler.DoUndo(state, "Set Motion"); @@ -286,14 +306,10 @@ public Motion GetStateEffectiveMotion(AnimatorState state) public Motion GetStateEffectiveMotion(AnimatorState state, int layerIndex) { - if (layers[layerIndex].syncedLayerIndex == -1) - { - return state.motion; - } - else - { - return layers[layerIndex].GetOverrideMotion(state); - } + if (layerIndex < 0 || layerIndex > layers.Length) + return null; + + return layers[layerIndex].syncedLayerIndex == -1 ? state.motion : layers[layerIndex].GetOverrideMotion(state); } public void SetStateEffectiveBehaviours(AnimatorState state, int layerIndex, StateMachineBehaviour[] behaviours) @@ -312,6 +328,9 @@ public void SetStateEffectiveBehaviours(AnimatorState state, int layerIndex, Sta public StateMachineBehaviour[] GetStateEffectiveBehaviours(AnimatorState state, int layerIndex) { + if (layerIndex < 0 || layerIndex > layers.Length) + return Array.Empty(); + return Internal_GetEffectiveBehaviours(state, layerIndex) as StateMachineBehaviour[]; } diff --git a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs index b8e5a4bfbb..334acb0ff6 100644 --- a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs +++ b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs @@ -8,6 +8,8 @@ using UnityEngine.Playables; using UnityEngine.Scripting.APIUpdating; using UnityEngine.Internal; +using UnityEngine; +using static UnityEditor.AnimationUtility; namespace UnityEditor { @@ -128,6 +130,17 @@ static public EditorCurveBinding DiscreteCurve(string inPath, System.Type inType binding.m_isSerializeReferenceCurve = 0; binding.m_isUnknownCurve = 0; + DiscreteBindingResult result = AnimationUtility.IsDiscreteIntBinding(binding); + if (result == DiscreteBindingResult.IncompatibleFieldType || result == DiscreteBindingResult.MissingDiscreteAttribute) + { + Debug.LogWarning( + $"Property [" + inPropertyName + "] is not a supported discrete curve binding. " + + "Discrete curves only support [" + typeof(Enum) + "] and [" + typeof(int) + " with the `DiscreteEvaluation` attribute]."); + + binding.m_isDiscreteCurve = 0; + binding.m_isUnknownCurve = 1; + } + return binding; } diff --git a/Editor/Mono/Animation/MaterialAnimationUtility.cs b/Editor/Mono/Animation/MaterialAnimationUtility.cs index ca1e0f4a0c..27cfc5e01d 100644 --- a/Editor/Mono/Animation/MaterialAnimationUtility.cs +++ b/Editor/Mono/Animation/MaterialAnimationUtility.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using UnityEditor; using UnityEngine; +using ShaderPropertyType = UnityEngine.Rendering.ShaderPropertyType; using Object = UnityEngine.Object; namespace UnityEditorInternal @@ -81,21 +82,21 @@ static public bool OverridePropertyColor(MaterialProperty materialProp, Renderer var propertyPaths = new List(); string basePropertyPath = kMaterialPrefix + materialProp.name; - if (materialProp.type == MaterialProperty.PropType.Texture) + if (materialProp.propertyType == ShaderPropertyType.Texture) { propertyPaths.Add(basePropertyPath + "_ST.x"); propertyPaths.Add(basePropertyPath + "_ST.y"); propertyPaths.Add(basePropertyPath + "_ST.z"); propertyPaths.Add(basePropertyPath + "_ST.w"); } - else if (materialProp.type == MaterialProperty.PropType.Color) + else if (materialProp.propertyType == ShaderPropertyType.Color) { propertyPaths.Add(basePropertyPath + ".r"); propertyPaths.Add(basePropertyPath + ".g"); propertyPaths.Add(basePropertyPath + ".b"); propertyPaths.Add(basePropertyPath + ".a"); } - else if (materialProp.type == MaterialProperty.PropType.Vector) + else if (materialProp.propertyType == ShaderPropertyType.Vector) { propertyPaths.Add(basePropertyPath + ".x"); propertyPaths.Add(basePropertyPath + ".y"); @@ -138,31 +139,31 @@ static public void TearDownMaterialPropertyBlock(Renderer target) static public bool ApplyMaterialModificationToAnimationRecording(MaterialProperty materialProp, int changedMask, Renderer target, object oldValue) { bool applied = false; - switch (materialProp.type) + switch (materialProp.propertyType) { - case MaterialProperty.PropType.Color: + case ShaderPropertyType.Color: SetupMaterialPropertyBlock(materialProp, changedMask, target); applied = ApplyMaterialModificationToAnimationRecording(MaterialPropertyToPropertyModifications(materialProp, target, (Color)oldValue), MaterialPropertyToPropertyModifications(materialProp, target, materialProp.colorValue)); if (!applied) TearDownMaterialPropertyBlock(target); return applied; - case MaterialProperty.PropType.Vector: + case ShaderPropertyType.Vector: SetupMaterialPropertyBlock(materialProp, changedMask, target); applied = ApplyMaterialModificationToAnimationRecording(MaterialPropertyToPropertyModifications(materialProp, target, (Vector4)oldValue), MaterialPropertyToPropertyModifications(materialProp, target, materialProp.vectorValue)); if (!applied) TearDownMaterialPropertyBlock(target); return applied; - case MaterialProperty.PropType.Float: - case MaterialProperty.PropType.Range: + case ShaderPropertyType.Float: + case ShaderPropertyType.Range: SetupMaterialPropertyBlock(materialProp, changedMask, target); applied = ApplyMaterialModificationToAnimationRecording(MaterialPropertyToPropertyModifications(materialProp, target, (float)oldValue), MaterialPropertyToPropertyModifications(materialProp, target, materialProp.floatValue)); if (!applied) TearDownMaterialPropertyBlock(target); return applied; - case MaterialProperty.PropType.Texture: + case ShaderPropertyType.Texture: { if (MaterialProperty.IsTextureOffsetAndScaleChangedMask(changedMask)) { @@ -183,17 +184,17 @@ static public bool ApplyMaterialModificationToAnimationRecording(MaterialPropert static public PropertyModification[] MaterialPropertyToPropertyModifications(MaterialProperty materialProp, Renderer target) { - switch (materialProp.type) + switch (materialProp.propertyType) { - case MaterialProperty.PropType.Color: + case ShaderPropertyType.Color: return MaterialPropertyToPropertyModifications(materialProp, target, materialProp.colorValue); - case MaterialProperty.PropType.Vector: + case ShaderPropertyType.Vector: return MaterialPropertyToPropertyModifications(materialProp, target, materialProp.vectorValue); - case MaterialProperty.PropType.Float: - case MaterialProperty.PropType.Range: + case ShaderPropertyType.Float: + case ShaderPropertyType.Range: return MaterialPropertyToPropertyModifications(materialProp, target, materialProp.floatValue); - case MaterialProperty.PropType.Texture: + case ShaderPropertyType.Texture: { string name = materialProp.name + "_ST"; return MaterialPropertyToPropertyModifications(name, target, materialProp.vectorValue); diff --git a/Editor/Mono/Animation/StateMachine.cs b/Editor/Mono/Animation/StateMachine.cs index f438f78a37..7a0a5c021d 100644 --- a/Editor/Mono/Animation/StateMachine.cs +++ b/Editor/Mono/Animation/StateMachine.cs @@ -100,11 +100,12 @@ public void RemoveCondition(AnimatorCondition condition) } } - + [HelpURL("StateMachineTransitions")] internal class AnimatorDefaultTransition : ScriptableObject { } + [HelpURL("class-State")] public partial class AnimatorState : Object { private PushUndoIfNeeded undoHandler = new PushUndoIfNeeded(true); @@ -242,6 +243,7 @@ public int uniqueNameHash } } + [HelpURL("NestedStateMachines")] public partial class AnimatorStateMachine : Object { private PushUndoIfNeeded undoHandler = new PushUndoIfNeeded(true); diff --git a/Editor/Mono/Animation/TransitionPreview.cs b/Editor/Mono/Animation/TransitionPreview.cs index 1c2433fb7a..6b26f509e6 100644 --- a/Editor/Mono/Animation/TransitionPreview.cs +++ b/Editor/Mono/Animation/TransitionPreview.cs @@ -269,7 +269,7 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l AnimatorStateInfo currentState = m_AvatarPreview.Animator.GetCurrentAnimatorStateInfo(m_LayerIndex); m_LeftStateWeightA = currentState.normalizedTime; m_LeftStateTimeA = currentTime; - while (!hasFinished && currentTime < maxDuration) + while (!hasFinished) { m_AvatarPreview.Animator.Update(stepTime); @@ -303,7 +303,7 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l m_LeftStateTimeB = currentTime; } - if (hasTransitioned) + if (hasTransitioned || hasFinished) { m_RightStateWeightB = currentState.normalizedTime; m_RightStateTimeB = currentTime; @@ -329,6 +329,11 @@ private void ResampleTransition(AnimatorStateTransition transition, AvatarMask l float leftDuration = (m_LeftStateTimeB - m_LeftStateTimeA) / (m_LeftStateWeightB - m_LeftStateWeightA); float rightDuration = (m_RightStateTimeB - m_RightStateTimeA) / (m_RightStateWeightB - m_RightStateWeightA); + // Ensure step times make sense based on these timings + // If step time is too small, the samping will take too long + currentStateStepTime = Mathf.Max(currentStateStepTime, leftDuration / 600.0f); + nextStateStepTime = Mathf.Max(nextStateStepTime, rightDuration / 600.0f); + if (m_MustSampleMotions) { // Do this as infrequently as possible diff --git a/Editor/Mono/Annotation/AnnotationUtility.bindings.cs b/Editor/Mono/Annotation/AnnotationUtility.bindings.cs index 885ae4e3d1..1efe191cbc 100644 --- a/Editor/Mono/Annotation/AnnotationUtility.bindings.cs +++ b/Editor/Mono/Annotation/AnnotationUtility.bindings.cs @@ -18,7 +18,7 @@ struct Annotation } [NativeHeader("Editor/Mono/Annotation/AnnotationUtility.bindings.h")] - [NativeHeader("Editor/Src/AnnotationManager.h")] + [NativeHeader("Editor/Src/Gizmos/AnnotationManager.h")] static class AnnotationUtility { // Similar values as in Annotation (in AnnotationManager.h) diff --git a/Editor/Mono/Annotation/AnnotationWindow.cs b/Editor/Mono/Annotation/AnnotationWindow.cs index 3ae0d13aeb..8442759474 100644 --- a/Editor/Mono/Annotation/AnnotationWindow.cs +++ b/Editor/Mono/Annotation/AnnotationWindow.cs @@ -53,19 +53,20 @@ private enum EnabledState Mixed = 2 } - const float kWindowWidth = 270; - const float kLabelWidth = 150; - const float scrollBarWidth = 14; - const float listElementHeight = 18; + const float k_WindowWidth = 270; + const float k_ScrollBarWidth = 14; + const float k_ListElementHeight = 18; + const float k_FrameWidth = 1f; float iconSize = 16; float gizmoRightAlign; float gizmoTextRightAlign; float iconRightAlign; float iconTextRightAlign; - const float frameWidth = 1f; static AnnotationWindow s_AnnotationWindow = null; static long s_LastClosedTime; + const long k_JustClosedPeriod = 400; + static Styles m_Styles; List m_RecentAnnotations; List m_BuiltinAnnotations; @@ -171,7 +172,7 @@ internal static bool ShowAtPosition(Rect buttonRect, bool isGameView) { // We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time. long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; - bool justClosed = nowMilliSeconds < s_LastClosedTime + 50; + bool justClosed = nowMilliSeconds < s_LastClosedTime + k_JustClosedPeriod; if (!justClosed) { Event.current.Use(); @@ -205,9 +206,9 @@ void Init(Rect buttonRect, bool isGameView) SyncToState(); - float windowHeight = 2 * frameWidth + GetTopSectionHeight() + DrawNormalList(false, 100, 0, 10000); + float windowHeight = 2 * k_FrameWidth + GetTopSectionHeight() + DrawNormalList(false, 100, 0, 10000); windowHeight = Mathf.Min(windowHeight, 900); - Vector2 windowSize = new Vector2(kWindowWidth, windowHeight); + Vector2 windowSize = new Vector2(k_WindowWidth, windowHeight); ShowAsDropDown(buttonRect, windowSize); } @@ -229,6 +230,7 @@ private void IconHasChanged() } } } + SyncToState(); Repaint(); } @@ -314,7 +316,7 @@ internal void OnGUI() void DrawTopSection(float topSectionHeight) { // Bg - GUI.Label(new Rect(frameWidth, 0, position.width - 2 * frameWidth, topSectionHeight), "", EditorStyles.inspectorBig); + GUI.Label(new Rect(k_FrameWidth, 0, position.width - 2 * k_FrameWidth, topSectionHeight), "", EditorStyles.inspectorBig); float topmargin = 7; float margin = 11; @@ -423,18 +425,18 @@ void DrawAnnotationList(float startY, float height) Rect scrollViewRect = new Rect(0, startY + barHeight, position.width - 4, - height - barHeight - frameWidth); + height - barHeight - k_FrameWidth); float totalContentHeight = DrawNormalList(false, 0, 0, 100000); Rect contentRect = new Rect(0, 0, 1, totalContentHeight); bool isScrollbarVisible = totalContentHeight > scrollViewRect.height; float listElementWidth = scrollViewRect.width; if (isScrollbarVisible) - listElementWidth -= scrollBarWidth; + listElementWidth -= k_ScrollBarWidth; // Scrollview m_ScrollPosition = GUI.BeginScrollView(scrollViewRect, m_ScrollPosition, contentRect, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, EditorStyles.scrollViewAlt); { - DrawNormalList(true, listElementWidth, m_ScrollPosition.y - listElementHeight, m_ScrollPosition.y + totalContentHeight); + DrawNormalList(true, listElementWidth, m_ScrollPosition.y - k_ListElementHeight, m_ScrollPosition.y + totalContentHeight); } GUI.EndScrollView(); } @@ -514,11 +516,11 @@ float DrawListSection(float y, string sectionHeader, List listElement Flip(ref even); if (curY > startY && curY < endY) { - Rect rect = new Rect(1, curY, listElementWidth - 2, listElementHeight); + Rect rect = new Rect(1, curY, listElementWidth - 2, k_ListElementHeight); if (doDraw) DrawListElement(rect, even, listElements[i]); } - curY += listElementHeight; + curY += k_ListElementHeight; } if (useSeperator) diff --git a/Editor/Mono/Annotation/LayerVisibilityWindow.cs b/Editor/Mono/Annotation/LayerVisibilityWindow.cs index adbcf89971..177ad6e7cd 100644 --- a/Editor/Mono/Annotation/LayerVisibilityWindow.cs +++ b/Editor/Mono/Annotation/LayerVisibilityWindow.cs @@ -35,17 +35,18 @@ public Styles() } } - const float kScrollBarWidth = 14; - const float kFrameWidth = 1f; - const float kToggleSize = 17; - const float kSeparatorHeight = 6; - const string kLayerVisible = "Show/Hide Layer"; - const string kLayerPickable = "Toggle Pickable status this Layer. Non-Pickable items cannot be selected in the Scene View."; + const float k_ScrollBarWidth = 14; + const float k_FrameWidth = 1f; + const float k_ToggleSize = 17; + const float k_SeparatorHeight = 6; + const string k_LayerVisible = "Show/Hide Layer"; + const string k_LayerPickable = "Toggle Pickable status this Layer. Non-Pickable items cannot be selected in the Scene View."; private static LayerVisibilityWindow s_LayerVisibilityWindow; private static long s_LastClosedTime; - private static Styles s_Styles; + const long k_JustClosedPeriod = 400; + private static Styles s_Styles; private List s_LayerNames; private List s_LayerMasks; private int m_AllLayersMask; @@ -86,7 +87,7 @@ internal static bool ShowAtPosition(Rect buttonRect) { // We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time. long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; - bool justClosed = nowMilliSeconds < s_LastClosedTime + 50; + bool justClosed = nowMilliSeconds < s_LastClosedTime + k_JustClosedPeriod; if (!justClosed) { Event.current.Use(); @@ -107,16 +108,16 @@ private void Init(Rect buttonRect) var rowCount = (s_LayerNames.Count + 2 + 1 + 1); - var windowHeight = rowCount * EditorGUI.kSingleLineHeight + kSeparatorHeight; + var windowHeight = rowCount * EditorGUI.kSingleLineHeight + k_SeparatorHeight; int sortingLayerCount = InternalEditorUtility.GetSortingLayerCount(); if (sortingLayerCount > 1) { - windowHeight += kSeparatorHeight + EditorGUI.kSingleLineHeight; + windowHeight += k_SeparatorHeight + EditorGUI.kSingleLineHeight; windowHeight += sortingLayerCount * EditorGUI.kSingleLineHeight; } m_ContentHeight = windowHeight; - windowHeight += 2 * kFrameWidth; + windowHeight += 2 * k_FrameWidth; windowHeight = Mathf.Min(windowHeight, 600); var windowSize = new Vector2(180, windowHeight); @@ -132,12 +133,12 @@ internal void OnGUI() if (s_Styles == null) s_Styles = new Styles(); - var scrollViewRect = new Rect(kFrameWidth, kFrameWidth, position.width - 2 * kFrameWidth, position.height - 2 * kFrameWidth); + var scrollViewRect = new Rect(k_FrameWidth, k_FrameWidth, position.width - 2 * k_FrameWidth, position.height - 2 * k_FrameWidth); var contentRect = new Rect(0, 0, 1, m_ContentHeight); bool isScrollbarVisible = m_ContentHeight > scrollViewRect.height; float listElementWidth = scrollViewRect.width; if (isScrollbarVisible) - listElementWidth -= kScrollBarWidth; + listElementWidth -= k_ScrollBarWidth; m_ScrollPosition = GUI.BeginScrollView(scrollViewRect, m_ScrollPosition, contentRect, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, EditorStyles.scrollViewAlt); Draw(listElementWidth); @@ -174,9 +175,9 @@ private void DrawHeader(ref Rect rect, string text, ref bool even) private void DrawSeparator(ref Rect rect, bool even) { - DrawListBackground(new Rect(rect.x + 1, rect.y, rect.width - 2, kSeparatorHeight), even); + DrawListBackground(new Rect(rect.x + 1, rect.y, rect.width - 2, k_SeparatorHeight), even); GUI.Label(new Rect(rect.x + 5, rect.y + 3, rect.width - 10, 3), GUIContent.none, s_Styles.separator); - rect.y += kSeparatorHeight; + rect.y += k_SeparatorHeight; } private void Draw(float listElementWidth) @@ -279,16 +280,16 @@ private void DoLayerEntry(Rect rect, string layerName, bool even, bool showVisib EditorGUI.BeginChangeCheck(); // text (works as visibility toggle as well) Rect textRect = rect; - textRect.width -= kToggleSize * 2; + textRect.width -= k_ToggleSize * 2; visible = GUI.Toggle(textRect, visible, EditorGUIUtility.TempContent(layerName), s_Styles.listTextStyle); // visible checkbox - var toggleRect = new Rect(rect.width - kToggleSize * 2, rect.y + (rect.height - kToggleSize) * 0.5f, kToggleSize, kToggleSize); + var toggleRect = new Rect(rect.width - k_ToggleSize * 2, rect.y + (rect.height - k_ToggleSize) * 0.5f, k_ToggleSize, k_ToggleSize); visibleChanged = false; if (showVisible) { var iconRect = toggleRect; - var gc = new GUIContent(string.Empty, visible ? s_Styles.visibleOn : s_Styles.visibleOff, kLayerVisible); + var gc = new GUIContent(string.Empty, visible ? s_Styles.visibleOn : s_Styles.visibleOff, k_LayerVisible); GUI.Toggle(iconRect, visible, gc, GUIStyle.none); visibleChanged = EditorGUI.EndChangeCheck(); } @@ -297,9 +298,9 @@ private void DoLayerEntry(Rect rect, string layerName, bool even, bool showVisib lockedChanged = false; if (showLock) { - toggleRect.x += kToggleSize; + toggleRect.x += k_ToggleSize; EditorGUI.BeginChangeCheck(); - var gc = new GUIContent(string.Empty, picked ? s_Styles.notpickable : s_Styles.pickable, kLayerPickable); + var gc = new GUIContent(string.Empty, picked ? s_Styles.notpickable : s_Styles.pickable, k_LayerPickable); GUI.Toggle(toggleRect, picked, gc, GUIStyle.none); lockedChanged = EditorGUI.EndChangeCheck(); } diff --git a/Editor/Mono/Annotation/SceneFXWindow.cs b/Editor/Mono/Annotation/SceneFXWindow.cs index c8b3b6eb94..2b2c8c2143 100644 --- a/Editor/Mono/Annotation/SceneFXWindow.cs +++ b/Editor/Mono/Annotation/SceneFXWindow.cs @@ -22,7 +22,7 @@ private class Styles public override Vector2 GetWindowSize() { - var windowHeight = 2f * kFrameWidth + EditorGUI.kSingleLineHeight * 6; + var windowHeight = 2f * kFrameWidth + EditorGUI.kSingleLineHeight * 7; if (UnityEngine.VFX.VFXManager.activateVFX) { windowHeight += EditorGUI.kSingleLineHeight; diff --git a/Editor/Mono/Annotation/SceneRenderModeWindow.cs b/Editor/Mono/Annotation/SceneRenderModeWindow.cs index 2f49b30033..58674ab1c4 100644 --- a/Editor/Mono/Annotation/SceneRenderModeWindow.cs +++ b/Editor/Mono/Annotation/SceneRenderModeWindow.cs @@ -83,11 +83,13 @@ internal class SceneRenderModeWindow : PopupWindowContent { static class Styles { - private static GUIStyle s_MenuItem; - private static GUIStyle s_Separator; + private static GUIStyle menuItem; + private static GUIStyle separator; + private static GUIContent debuggerLabel; - public static GUIStyle sMenuItem => s_MenuItem ?? (s_MenuItem = "MenuItem"); - public static GUIStyle sSeparator => s_Separator ?? (s_Separator = "sv_iconselector_sep"); + public static GUIStyle s_MenuItem => menuItem ?? (menuItem = "MenuItem"); + public static GUIStyle s_Separator => separator ?? (separator = "sv_iconselector_sep"); + public static GUIContent s_DebuggerLabel => debuggerLabel ??= EditorGUIUtility.TrTextContent("Rendering Debugger..."); private static readonly string kShadingMode = "Shading Mode"; private static readonly string kMiscellaneous = "Miscellaneous"; @@ -136,7 +138,7 @@ static class Styles new SceneView.CameraMode(DrawCameraMode.AlphaChannel, "Alpha Channel", kMiscellaneous), new SceneView.CameraMode(DrawCameraMode.Overdraw, "Overdraw", kMiscellaneous), new SceneView.CameraMode(DrawCameraMode.Mipmaps, "Mipmaps", kMiscellaneous), - new SceneView.CameraMode(DrawCameraMode.TextureStreaming, "Texture Streaming", kMiscellaneous), + new SceneView.CameraMode(DrawCameraMode.TextureStreaming, "Texture Mipmap Streaming", kMiscellaneous), new SceneView.CameraMode(DrawCameraMode.SpriteMask, "Sprite Mask", kMiscellaneous), new SceneView.CameraMode(DrawCameraMode.ValidateAlbedo, "Validate Albedo", kMiscellaneous), new SceneView.CameraMode(DrawCameraMode.ValidateMetalSpecular, "Validate Metal Specular", kMiscellaneous), @@ -144,6 +146,18 @@ static class Styles } + // UUM-96180: Default CameraMode should be DrawCameraMode.GIContributorsReceivers + internal static SceneView.CameraMode defaultCameraMode + { + get + { + var mode = Styles.sBuiltinCameraModes [3]; + if(mode.drawMode != DrawCameraMode.GIContributorsReceivers) + Debug.LogError("Default Draw Camera mode should be set to DrawCameraMode.GIContributorsReceivers."); + return mode; + } + } + private Dictionary foldoutStates = new Dictionary(); private float windowHeight @@ -161,7 +175,7 @@ private float windowHeight modes = Styles.sBuiltinCameraModes.Count(mode => m_SceneView.IsCameraDrawModeSupported(mode) && mode.show) + SceneView.userDefinedModes.Count(mode => m_SceneView.IsCameraDrawModeSupported(mode) && mode.show); - return UpdatedHeight(headers, modes, GraphicsSettings.renderPipelineAsset != null); + return UpdatedHeight(headers, modes, GraphicsSettings.isScriptableRenderPipelineEnabled); } } @@ -220,27 +234,27 @@ private void DrawSeparator(ref Rect rect) labelRect.width -= kHeaderHorizontalPadding * 2; labelRect.height = kSeparatorHeight; - GUI.Label(labelRect, GUIContent.none, Styles.sSeparator); + GUI.Label(labelRect, GUIContent.none, Styles.s_Separator); rect.y += kSeparatorHeight; } //Opens render debugger window located at \newSRP\unity\Packages\com.unity.render-pipelines.core\Editor\Debugging\DebugWindow.cs private void DrawRenderingDebuggerShortCut(Rect rect) { - GUIContent label = new GUIContent("Rendering Debugger..."); var labelRect = rect; - labelRect.y += (kHeaderVerticalPadding * 2f); - labelRect.x += kHeaderHorizontalPadding + 15f; - labelRect.width = EditorStyles.foldout.CalcSize(label).x; - labelRect.height = EditorStyles.foldout.CalcSize(label).y; + labelRect.y += kHeaderVerticalPadding; + EditorGUI.LabelField(labelRect,string.Empty, Styles.s_MenuItem); + labelRect.x -= kHeaderHorizontalPadding; + var debuggerLabelSize = EditorStyles.foldout.CalcSize(Styles.s_DebuggerLabel); + labelRect.height = debuggerLabelSize.y; - if (GUI.Button(labelRect, label, EditorStyles.label)) + if (GUI.Button(labelRect, Styles.s_DebuggerLabel, Styles.s_MenuItem)) { EditorApplication.ExecuteMenuItem("Window/Analysis/Rendering Debugger"); editorWindow.Close(); } + rect.y += labelRect.height; - rect.y += EditorGUI.kSingleLineHeight; } private void Draw(float listElementWidth) @@ -268,10 +282,11 @@ private void Draw(float listElementWidth) } bool previousState = foldoutStates[lastSection]; - foldoutStates[lastSection] = EditorGUI.Foldout( - new Rect(drawPos.x + kHeaderHorizontalPadding, drawPos.y, drawPos.width, - EditorGUI.kSingleLineHeight), foldoutStates[lastSection], - EditorGUIUtility.TextContent(lastSection), true); + Rect foldoutRect = new Rect(drawPos.x, drawPos.y, drawPos.width, EditorGUI.kSingleLineHeight); + + EditorGUI.LabelField(foldoutRect,string.Empty, Styles.s_MenuItem); + foldoutStates[lastSection] = EditorGUI.Foldout(foldoutRect, foldoutStates[lastSection], EditorGUIUtility.TextContent(lastSection), true); + drawPos.y += EditorGUI.kSingleLineHeight; if (previousState != foldoutStates[lastSection]) @@ -292,7 +307,7 @@ private void Draw(float listElementWidth) } } - if (GraphicsSettings.renderPipelineAsset != null) + if (GraphicsSettings.isScriptableRenderPipelineEnabled) { DrawSeparator(ref drawPos); DrawRenderingDebuggerShortCut(drawPos); @@ -336,7 +351,7 @@ private float RecalculateWindowHeight() } } - return UpdatedHeight(headers, modes, GraphicsSettings.renderPipelineAsset != null); + return UpdatedHeight(headers, modes, GraphicsSettings.isScriptableRenderPipelineEnabled); } private float UpdatedHeight(int headers, int modes, bool isSRP) @@ -358,7 +373,7 @@ void DoBuiltinMode(ref Rect rect, SceneView.CameraMode mode) EditorGUI.BeginChangeCheck(); GUI.Toggle(rect, m_SceneView.cameraMode == mode, EditorGUIUtility.TextContent(mode.name), - Styles.sMenuItem); + Styles.s_MenuItem); if (EditorGUI.EndChangeCheck()) { m_SceneView.cameraMode = mode; diff --git a/Editor/Mono/AssemblyHelper.cs b/Editor/Mono/AssemblyHelper.cs index 7e8b6b3e90..188e621637 100644 --- a/Editor/Mono/AssemblyHelper.cs +++ b/Editor/Mono/AssemblyHelper.cs @@ -39,13 +39,12 @@ static public bool IsUnityEngineModule(Assembly assembly) public static string[] GetDefaultAssemblySearchPaths() { // Add the path to all available precompiled assemblies - var group = EditorUserBuildSettings.activeBuildTargetGroup; var target = EditorUserBuildSettings.activeBuildTarget; var precompiledAssemblyPaths = InternalEditorUtility.GetPrecompiledAssemblyPaths(); HashSet searchPaths = new HashSet(precompiledAssemblyPaths); - var precompiledAssemblies = InternalEditorUtility.GetUnityAssemblies(true, group, target); + var precompiledAssemblies = InternalEditorUtility.GetUnityAssemblies(true, target); foreach (var asm in precompiledAssemblies) searchPaths.Add(Path.GetDirectoryName(asm.Path)); diff --git a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs index 6cb3f8edab..5a99613788 100644 --- a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs +++ b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs @@ -13,3 +13,202 @@ // You can start by moving them to EditorCoreModuleAssemblyInfo.cs to reduce internal visibility // To remove a line in there, the target assembly must not depend on _any_ internal API from built-in Editor modules + +// ADD_NEW_PLATFORM_HERE +[assembly: InternalsVisibleTo("Unity.LiveNotes")] +[assembly: InternalsVisibleTo("Unity.Audio.Tests")] +[assembly: InternalsVisibleTo("Unity.Burst")] +[assembly: InternalsVisibleTo("Unity.Burst.Editor")] +[assembly: InternalsVisibleTo("Unity.Cloud.Collaborate.Editor")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.Editor")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.EditorTests")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.UI")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.UI.Tests")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.Client")] +[assembly: InternalsVisibleTo("Unity.CollabProxy.Client.Tests")] +[assembly: InternalsVisibleTo("UnityEditor.Advertisements")] +[assembly: InternalsVisibleTo("Unity.PackageManager")] +[assembly: InternalsVisibleTo("Unity.PackageManagerStandalone")] +[assembly: InternalsVisibleTo("Unity.AndroidBuildPipeline")] +[assembly: InternalsVisibleTo("Unity.Automation")] +[assembly: InternalsVisibleTo("UnityEngine.Common")] +[assembly: InternalsVisibleTo("Unity.PureCSharpTests")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Android")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Android.CommonUtils")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Animation")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.AssetImporting")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.BuildPipeline")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Builds")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.CrashReporting")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.DeploymentTargets")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.EditorApplication")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.EditorUI")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.GameCore")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.GameView")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Lightmapping")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Metro")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Misc")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.PackageManager")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Profiler")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.PS4")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.PS5")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Switch")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Rendering")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.SceneVisibility")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.ScriptCompilation")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.ShortcutManager")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.UIToolkit")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Framework.PackageManager")] +[assembly: InternalsVisibleTo("Unity.DeploymentTests.Services")] +[assembly: InternalsVisibleTo("Unity.PerformanceIntegrationTests")] +[assembly: InternalsVisibleTo("Unity.Timeline.Editor")] +[assembly: InternalsVisibleTo("Unity.PackageManagerUI.Develop.Editor")] +[assembly: InternalsVisibleTo("Unity.DeviceSimulator.Editor")] +[assembly: InternalsVisibleTo("Unity.Timeline.EditorTests")] +[assembly: InternalsVisibleTo("UnityEditor.Graphs")] +[assembly: InternalsVisibleTo("UnityEditor.UWP.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.iOS.Extensions.Common")] +[assembly: InternalsVisibleTo("UnityEditor.Apple.Extensions.Common")] +[assembly: InternalsVisibleTo("UnityEditor.iOS.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.VisionOS.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.AppleTV.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Android.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.XboxOne.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.PS4.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.PS5.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Switch.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.WebGL.Extensions")] +[assembly: InternalsVisibleTo("Unity.Automation.Players.WebGL")] +[assembly: InternalsVisibleTo("Unity.WebGL.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.LinuxStandalone.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.CloudRendering.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.EmbeddedLinux.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.QNX.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Kepler.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.WindowsStandalone.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.OSXStandalone.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Lumin.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.GameCoreScarlett.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.GameCoreXboxOne.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.GameCoreCommon.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Networking")] +[assembly: InternalsVisibleTo("UnityEngine.Networking")] +[assembly: InternalsVisibleTo("Unity.Analytics.Editor")] +[assembly: InternalsVisibleTo("UnityEditor.Analytics")] +[assembly: InternalsVisibleTo("UnityEditor.Purchasing")] +[assembly: InternalsVisibleTo("UnityEditor.Lumin")] +[assembly: InternalsVisibleTo("UnityEditor.Switch.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.EditorTestsRunner")] +[assembly: InternalsVisibleTo("UnityEditor.TestRunner")] +[assembly: InternalsVisibleTo("UnityEditor.TestRunner.Tests")] +[assembly: InternalsVisibleTo("Unity.Compiler.Client")] +[assembly: InternalsVisibleTo("ExternalCSharpCompiler")] +[assembly: InternalsVisibleTo("UnityEngine.TestRunner")] +[assembly: InternalsVisibleTo("UnityEditor.VR")] +[assembly: InternalsVisibleTo("Unity.RuntimeTests")] +[assembly: InternalsVisibleTo("Unity.RuntimeTests.Framework")] +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-firstpass-testable")] +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("UnityEditor.InteractiveTutorialsFramework")] +[assembly: InternalsVisibleTo("UnityEditor.Networking")] +[assembly: InternalsVisibleTo("UnityEditor.UI")] +[assembly: InternalsVisibleTo("UnityEditor.AR")] +[assembly: InternalsVisibleTo("UnityEditor.SpatialTracking")] +[assembly: InternalsVisibleTo("Unity.WindowsMRAutomation")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.001")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.002")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.003")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.004")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.005")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.006")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.007")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.008")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.009")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.010")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.011")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.012")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.013")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.014")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.015")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.016")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.017")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.018")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.019")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.020")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.021")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.022")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.023")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridge.024")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.001")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.002")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.003")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.004")] +[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.005")] +[assembly: InternalsVisibleTo("Unity.XR.Remoting.Editor")] +[assembly: InternalsVisibleTo("UnityEngine.Common")] +[assembly: InternalsVisibleTo("Unity.UI.Builder.Editor")] +[assembly: InternalsVisibleTo("Unity.UI.TestFramework.Editor.Tests")] // for UI Test Framework +[assembly: InternalsVisibleTo("UnityEditor.UIBuilderModule")] +[assembly: InternalsVisibleTo("Unity.UI.Builder.EditorTests")] +[assembly: InternalsVisibleTo("Unity.GraphViewTestUtilities.Editor")] +[assembly: InternalsVisibleTo("Unity.ProBuilder.Editor")] +[assembly: InternalsVisibleTo("Unity.2D.Sprite.Editor")] +[assembly: InternalsVisibleTo("Unity.2D.Sprite.EditorTests")] +[assembly: InternalsVisibleTo("Unity.2D.Tilemap.Editor")] +[assembly: InternalsVisibleTo("Unity.2D.Tilemap.EditorTests")] +[assembly: InternalsVisibleTo("Unity.PackageCleanConsoleTest.Editor")] +[assembly: InternalsVisibleTo("Unity.TextCore.Editor.Tests")] +[assembly: InternalsVisibleTo("UnityEditor.TextCoreTextEngineModule")] +[assembly: InternalsVisibleTo("Unity.TextMeshPro.Editor")] +[assembly: InternalsVisibleTo("Unity.Animation.Editor.AnimationWindow")] +[assembly: InternalsVisibleTo("Unity.VisualEffectGraph.Editor")] +[assembly: InternalsVisibleTo("Unity.Testing.VisualEffectGraph.EditorTests")] +[assembly: InternalsVisibleTo("Unity.VisualEffectGraph.EditorTests")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.Multiple_SRP.EditorTests")] +[assembly: InternalsVisibleTo("Unity.ShaderGraph.Editor")] + +[assembly: InternalsVisibleTo("Unity.SceneTemplate.Editor")] +[assembly: InternalsVisibleTo("com.unity.purchasing.udp.Editor")] +[assembly: InternalsVisibleTo("com.unity.search.extensions.editor")] + +[assembly: InternalsVisibleTo("UnityEditor.Android.Extensions")] + +[assembly: InternalsVisibleTo("Unity.Entities.Build")] + +[assembly: InternalsVisibleTo("Unity.Muse.Chat.Bridge")] +[assembly: InternalsVisibleTo("Unity.AI.Assistant.Bridge.Editor")] + +[assembly: InternalsVisibleTo("Unity.Multiplayer.Playmode.Editor.Bridge")] +[assembly: InternalsVisibleTo("Unity.DedicatedServer.Editor.Bridge")] + +[assembly: InternalsVisibleTo("Unity.Scenes")] + + +[assembly: InternalsVisibleTo("UnityEditor.Switch.Tests")] + +[assembly: InternalsVisibleTo("UnityEditor.BuildProfileModule.Tests")] + +[assembly: InternalsVisibleTo("UnityEditor.PS4.Tests")] + +[assembly: InternalsVisibleTo("UnityEditor.PS5.Tests")] + +[assembly: InternalsVisibleTo("Unity.Automation.Players.EmbeddedLinux")] +[assembly: InternalsVisibleTo("Unity.Automation.Players.QNX")] + +[assembly: InternalsVisibleTo("Unity.GraphToolsAuthoringFramework.InternalEditorBridge")] + +// Module test assemblies +[assembly: InternalsVisibleTo("Unity.Modules.iOSExtensions.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Licensing.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.PlatformIcons.Tests.Editor")] +// This should move with the AnimationWindow to a module at some point +[assembly: InternalsVisibleTo("Unity.Modules.Animation.AnimationWindow.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Physics.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Tests.Shared")] + +// Test Assemblies +[assembly: InternalsVisibleTo("Unity.Core.UnityEvent.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Core.Scripting.AssemblyVersion.Tests.Editor")] + diff --git a/Editor/Mono/AssemblyReloadEvents.cs b/Editor/Mono/AssemblyReloadEvents.cs index d3b83c5e7d..b5eb6a24fb 100644 --- a/Editor/Mono/AssemblyReloadEvents.cs +++ b/Editor/Mono/AssemblyReloadEvents.cs @@ -27,15 +27,27 @@ public static event AssemblyReloadCallback afterAssemblyReload [RequiredByNativeCode] static void OnBeforeAssemblyReload() { + if (!m_BeforeAssemblyReloadEvent.hasSubscribers) + return; + using var scope = new ProgressScope("OnBeforeAssemblyReload Callback", "", forceUpdate: true); foreach (var evt in m_BeforeAssemblyReloadEvent) + { + scope.SetText($"{evt.Method?.DeclaringType?.FullName}.{evt.Method?.Name}", true); evt(); + } } [RequiredByNativeCode] static void OnAfterAssemblyReload() { + if (!m_AfterAssemblyReloadEvent.hasSubscribers) + return; + using var scope = new ProgressScope("OnAfterAssemblyReload Callback", "", forceUpdate: true); foreach (var evt in m_AfterAssemblyReloadEvent) + { + scope.SetText($"{evt.Method?.DeclaringType?.FullName}.{evt.Method?.Name}", true); evt(); + } } } } diff --git a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs index fedd4d13e0..1597fff5e5 100644 --- a/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs +++ b/Editor/Mono/AssetDatabase/AssetDatabaseSearching.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using UnityEditor.Utils; using UnityEngine; +using UnityEngine.Bindings; using Object = UnityEngine.Object; namespace UnityEditor @@ -36,6 +37,7 @@ internal static string[] FindAssets(SearchFilter searchFilter) return FindAllAssets(searchFilter).Select(property => property.guid).Distinct().ToArray(); } + [VisibleToOtherModules("UnityEditor.UIBuilderModule")] internal static IEnumerable FindAllAssets(SearchFilter searchFilter) { var enumerator = EnumerateAllAssets(searchFilter); diff --git a/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs b/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs index b691b90d82..752db88303 100644 --- a/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs +++ b/Editor/Mono/AssetDatabase/AssetPreview.bindings.cs @@ -22,16 +22,16 @@ public static Texture2D GetAssetPreview(Object asset) return null; } - internal static Texture2D GetAssetPreview(int instanceID) + internal static Texture2D GetAssetPreview(EntityId entityId) { - return GetAssetPreview(instanceID, kSharedClientID); + return GetAssetPreview(entityId, kSharedClientID); } [FreeFunction("AssetPreviewBindings::GetAssetPreview")] - internal static extern Texture2D GetAssetPreview(int instanceID, int clientID); + internal static extern Texture2D GetAssetPreview(EntityId entityId, int clientID); [FreeFunction("AssetPreviewBindings::HasAssetPreview")] - internal static extern bool HasAssetPreview(int instanceID, int clientID); + internal static extern bool HasAssetPreview(EntityId entityId, int clientID); internal static Texture2D GetAssetPreviewFromGUID(string guid) { @@ -46,8 +46,13 @@ public static bool IsLoadingAssetPreview(int instanceID) return IsLoadingAssetPreview(instanceID, kSharedClientID); } + public static bool IsLoadingAssetPreview(EntityId entityId) + { + return IsLoadingAssetPreview(entityId, kSharedClientID); + } + [FreeFunction("AssetPreviewBindings::IsLoadingAssetPreview")] - internal static extern bool IsLoadingAssetPreview(int instanceID, int clientID); + internal static extern bool IsLoadingAssetPreview(EntityId entityId, int clientID); public static bool IsLoadingAssetPreviews() { diff --git a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs index beed6aae72..ba0ca9ed58 100644 --- a/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs +++ b/Editor/Mono/AssetPipeline/AssetImportContext.bindings.cs @@ -116,6 +116,8 @@ public string GetArtifactFilePath(GUID guid, string fileName) [NativeName("DependsOnImportedAsset")] private extern void DependsOnImportedAssetInternal(string path); + public extern Object GetReferenceToAssetMainObject(string path); + public void DependsOnArtifact(ArtifactKey key) { if (!key.isValid) diff --git a/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs b/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs index dae989380c..f2846c569b 100644 --- a/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/AssetImporter.bindings.cs @@ -107,6 +107,9 @@ public static ImportLog GetImportLog(string path) [NativeName("SetAssetBundleName")] extern public void SetAssetBundleNameAndVariant(string assetBundleName, string assetBundleVariant); + [NativeMethod("SetThumbnailFromTexture2D")] + extern internal void SetThumbnailFromTexture2D(Texture2D image, int instanceID); + [FreeFunction("FindAssetImporterAtAssetPath")] extern public static AssetImporter GetAtPath(string path); diff --git a/Editor/Mono/AssetPipeline/LocalCacheServer.cs b/Editor/Mono/AssetPipeline/LocalCacheServer.cs index 08ac81fec4..e4a6ea2d66 100644 --- a/Editor/Mono/AssetPipeline/LocalCacheServer.cs +++ b/Editor/Mono/AssetPipeline/LocalCacheServer.cs @@ -2,27 +2,13 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System.Diagnostics; using System.IO; -using System; -using System.Net; -using System.Net.Sockets; -using UnityEditor.Scripting; using UnityEditor.Utils; -using UnityEngine; -using UnityEngine.Scripting; namespace UnityEditor { - internal class LocalCacheServer : ScriptableSingleton + internal class LocalCacheServer { - [SerializeField] public string path; - [SerializeField] public int port; - [SerializeField] public ulong size; - [SerializeField] public int pid = -1; - [SerializeField] public string time; - - public const string SizeKey = "LocalCacheServerSize"; public const string PathKey = "LocalCacheServerPath"; public const string CustomPathKey = "LocalCacheServerCustomPath"; @@ -36,195 +22,21 @@ public static string GetCacheLocation() return result; } - public static void CreateCacheDirectory() - { - string cacheDirectoryPath = GetCacheLocation(); - if (Directory.Exists(cacheDirectoryPath) == false) - Directory.CreateDirectory(cacheDirectoryPath); - } - - void Create(int _port, ulong _size) - { - var nodeExecutable = Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "nodejs"); - if (Application.platform == RuntimePlatform.WindowsEditor) - nodeExecutable = Paths.Combine(nodeExecutable, "node.exe"); - else - nodeExecutable = Paths.Combine(nodeExecutable, "bin", "node"); - - CreateCacheDirectory(); - path = GetCacheLocation(); - var cacheServerJs = Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "CacheServer", "main.js"); - var processStartInfo = new ProcessStartInfo(nodeExecutable) - { - Arguments = "\"" + cacheServerJs + "\"" - + " --port " + _port - + " --path \"" + path - + "\" --nolegacy" - + " --monitor-parent-process " + Process.GetCurrentProcess().Id - // node.js has issues running on windows with stdout not redirected. - // so we silence logging to avoid that. And also to avoid CacheServer - // spamming the editor logs on OS X. - + " --silent" - + " --size " + _size, - UseShellExecute = false, - CreateNoWindow = true - }; - - var p = new Process(); - p.StartInfo = processStartInfo; - p.Start(); - - port = _port; - pid = p.Id; - size = _size; - time = p.StartTime.ToString(); - Save(true); - } - - public static int GetRandomUnusedPort() - { - var listener = new TcpListener(IPAddress.Any, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - - public static bool PingHost(string host, int port, int timeout) - { - try - { - using (var client = new TcpClient()) - { - var result = client.BeginConnect(host, port, null, null); - result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout)); - return client.Connected; - } - } - catch - { - return false; - } - } - - public static bool WaitForServerToComeAlive(int port) - { - DateTime start = DateTime.Now; - DateTime maximum = start.AddSeconds(5); - while (DateTime.Now < maximum) - { - if (PingHost("localhost", port, 10)) - { - System.Console.WriteLine("Server Came alive after {0} ms", (DateTime.Now - start).TotalMilliseconds); - return true; - } - } - return false; - } - - public static void Kill() - { - if (instance.pid == -1) - return; - - Process p = null; - try - { - p = Process.GetProcessById(instance.pid); - p.Kill(); - instance.pid = -1; - } - catch - { - // if we could not get a process, there is non alive. continue. - } - } - - public static void CreateIfNeeded() - { - // See if we can get an existing process with the PID we remembered. - Process p = null; - try - { - p = Process.GetProcessById(instance.pid); - } - catch - { - // if we could not get a process, there is non alive. continue. - } - - ulong size = (ulong)EditorPrefs.GetInt(SizeKey, 10) * 1024 * 1024 * 1024; - // Check if this process is really the one we used (and not another one reusing the PID). - if (p != null && p.StartTime.ToString() == instance.time) - { - if (instance.size == size && instance.path == GetCacheLocation()) - { - // We have a server running for this setup, which we can reuse, but make sure that the cache server directory exists in case it was cleaned earlier - CreateCacheDirectory(); - return; - } - else - { - // This server does not match our setup. Kill it, so we can start a new one. - Kill(); - } - } - - // No existing server we can use. Start a new one. - instance.Create(GetRandomUnusedPort(), size); - WaitForServerToComeAlive(instance.port); - } - public static void Setup() { var mode = (AssetPipelinePreferences.CacheServerMode)EditorPrefs.GetInt("CacheServerMode"); if (mode == AssetPipelinePreferences.CacheServerMode.Local) - CreateIfNeeded(); - else - Kill(); - } - - [UsedByNativeCode] - public static int GetLocalCacheServerPort() - { - Setup(); - return instance.port; + { + EditorGUILayout.HelpBox("Local CacheServer is no longer supported", MessageType.Info, true); + } } public static void Clear() { - Kill(); string cacheDirectoryPath = GetCacheLocation(); if (Directory.Exists(cacheDirectoryPath)) Directory.Delete(cacheDirectoryPath, true); } - - public static bool CheckCacheLocationExists() - { - return Directory.Exists(GetCacheLocation()); - } - - public static bool CheckValidCacheLocation(string path) - { - if (Directory.Exists(path)) - { - var contents = Directory.GetFileSystemEntries(path); - foreach (var dir in contents) - { - var name = Path.GetFileName(dir).ToLower(); - if (name.Length == 2) - continue; - if (name == "temp") - continue; - if (name == ".ds_store") - continue; - if (name == "desktop.ini") - continue; - return false; - } - } - return true; - } } } diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Importer.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Importer.cs new file mode 100644 index 0000000000..eec1335d57 --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Importer.cs @@ -0,0 +1,1607 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.IO; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +using UnityEditor.AssetImporters; + +using STVertex = UnityEditor.SpeedTree.Importer.Vertex; +using Material = UnityEngine.Material; +using Color = UnityEngine.Color; + +using static UnityEditor.SpeedTree.Importer.SpeedTreeImporterCommon; +using static UnityEditor.SpeedTree.Importer.WindConfigSDK; +using static UnityEditor.SpeedTree.Importer.SpeedTree9Importer; +using static UnityEditor.SpeedTree.Importer.SpeedTree9Reader; + +namespace UnityEditor.SpeedTree.Importer +{ + // [2024-09-27] version: 3 + // Fixed code that would lead to m_LODCount vs m_PerLODSettings.arraySize mismatching in GUI + [ScriptedImporter(version: 3, ext: "st9", AllowCaching = true)] + public class SpeedTree9Importer : ScriptedImporter + { + const int SPEEDTREE_9_WIND_VERSION = 1; + const int SPEEDTREE_9_MATERIAL_VERSION = 1; + + internal static class ImporterSettings + { + internal const string kGameObjectName = "SpeedTree"; + internal const string kLegacyShaderName = "Nature/SpeedTree9"; + internal const string kWindAssetName = "SpeedTreeWind"; + internal const string kSRPDependencyName = "SpeedTree9Importer_DefaultShader"; + internal const string kMaterialSettingsDependencyname = "SpeedTree9Importer_MaterialSettings"; + internal const string kIconName = "UnityEditor/SpeedTree9Importer Icon"; + } + + private static class Styles + { + internal static readonly Texture2D kIcon = EditorGUIUtility.FindTexture(ImporterSettings.kIconName); + } + + private struct STMeshGeometry + { + public Vector3[] vertices; + public Vector3[] normals; + public Vector4[] tangents; + public Color32[] colors32; + public Vector4[][] uvs; + public int lodIndex; + + public STMeshGeometry(int vertexCount, int UVCount, int indexLod) + { + vertices = new Vector3[vertexCount]; + normals = new Vector3[vertexCount]; + tangents = new Vector4[vertexCount]; + colors32 = new Color32[vertexCount]; + uvs = new Vector4[UVCount][]; + lodIndex = indexLod; + + for (int i = 0; i < UVCount; ++i) + { + uvs[i] = new Vector4[vertexCount]; + } + } + } + + [SerializeField] + internal MeshSettings m_MeshSettings = new MeshSettings(); + + [SerializeField] + internal MaterialSettings m_MaterialSettings = new MaterialSettings(); + + [SerializeField] + internal LightingSettings m_LightingSettings = new LightingSettings(); + + [SerializeField] + internal AdditionalSettings m_AdditionalSettings = new AdditionalSettings(); + + [SerializeField] + internal LODSettings m_LODSettings = new LODSettings(); + + [SerializeField] + internal List m_PerLODSettings = new List(); + + [SerializeField] + internal WindSettings m_WindSettings = new WindSettings(); + + [SerializeField] + internal int m_MaterialVersion = SPEEDTREE_9_MATERIAL_VERSION; + + /// + /// Necessary to set default HDRP properties and materials upgrade. + /// + /// The main object used by the importer, containing the data. + public delegate void OnAssetPostProcess(GameObject mainObject); + + /// + /// Exposes the Diffuse Profile property in the importer inspector with compatible render pipelines. + /// + /// The serialized property of the diffusion profile asset. + /// The serialized property of the diffusion profile hash. + /// + /// Necessary to expose the Diffuse Profile property in the inspector, since the importer is not + /// aware of HDRP as it's a package. This property is highly used by artists, so exposing it is a big win. + /// + public delegate void OnCustomEditorSettings(ref SerializedProperty diffusionProfileAsset, ref SerializedProperty diffusionProfileHash); + + [SerializeField] + internal SpeedTreeImporterOutputData m_OutputImporterData; + + private static ulong s_DefaultShaderHash; + private static readonly TimeSpan k_CheckDependencyFrequency = TimeSpan.FromSeconds(5); + private static DateTime s_LastCheck; + + // Cache main objects, created during import process. + private AssetImportContext m_Context; + private SpeedTree9Reader m_Tree; + private Shader m_Shader; + private SpeedTreeWindAsset m_WindAsset; + + // Values cached at the begining of the import process. + private bool m_HasFacingData; + private bool m_HasBranch2Data; + private bool m_LastLodIsBillboard; + private bool m_WindEnabled; + private uint m_LODCount; + private uint m_CollisionObjectsCount; + private string m_PathFromDirectory; + + internal bool MaterialsShouldBeRegenerated => m_MaterialVersion != SPEEDTREE_9_MATERIAL_VERSION; + + public override bool SupportsRemappedAssetType(Type type) + { + return true; + } + + public override void OnImportAsset(AssetImportContext ctx) + { + m_Context = ctx; + m_Tree = new SpeedTree9Reader(); + + FileStatus status = m_Tree.Initialize(ctx.assetPath); + if (status != FileStatus.Valid) + { + ctx.LogImportError($"Error while initializing the SpeedTree9 reader: {status}."); + return; + } + + m_Tree.ReadContent(); + + CacheTreeImporterValues(ctx.assetPath); + + ctx.DependsOnCustomDependency(ImporterSettings.kSRPDependencyName); + + if (!TryGetShaderForCurrentRenderPipeline(out m_Shader)) + { + ctx.LogImportError("SpeedTree9 shader is invalid, cannot create Materials for this SpeedTree asset."); + return; + } + + m_OutputImporterData = SpeedTreeImporterOutputData.Create(); + m_OutputImporterData.hasBillboard = m_LastLodIsBillboard; + + CalculateScaleFactorFromUnit(); + + if (m_WindEnabled) + { + m_OutputImporterData.m_WindConfig = CopySpeedTree9WindConfig(m_Tree.Wind, m_MeshSettings.scaleFactor, m_Tree.Bounds); + SetWindParameters(ref m_OutputImporterData.m_WindConfig); + + m_WindAsset = new SpeedTreeWindAsset(SPEEDTREE_9_WIND_VERSION, m_OutputImporterData.m_WindConfig) + { + name = ImporterSettings.kWindAssetName, + }; + } + + GameObject mainObject = new GameObject(ImporterSettings.kGameObjectName); + + if (m_AdditionalSettings.generateRigidbody) + { + CreateAndAddRigidBodyToAsset(mainObject); + } + + ctx.AddObjectToAsset(ImporterSettings.kGameObjectName, mainObject); + ctx.SetMainObject(mainObject); + + SetThumbnailFromTexture2D(Styles.kIcon, mainObject.GetInstanceID()); + + m_OutputImporterData.mainObject = mainObject; + + CalculateBillboardAndPerLODSettings(); + + CreateMeshAndMaterials(); + + CreateAssetIdentifiersAndAddMaterialsToContext(); + + if (m_AdditionalSettings.generateColliders && m_CollisionObjectsCount > 0) + { + CreateAndAddCollidersToAsset(); + } + + if (m_WindEnabled) + { + ctx.AddObjectToAsset(ImporterSettings.kWindAssetName, m_WindAsset); + } + + ctx.AddObjectToAsset(m_OutputImporterData.name, m_OutputImporterData); + ctx.DependsOnCustomDependency(ImporterSettings.kMaterialSettingsDependencyname); + + AddDependencyOnExtractedMaterials(); + + TriggerAllCallbacks(); + } + + private void TriggerAllCallbacks() + { + var allMethods = AttributeHelper.GetMethodsWithAttribute().methodsWithAttributes; + foreach (var method in allMethods) + { + var callback = Delegate.CreateDelegate(typeof(OnAssetPostProcess), method.info) as OnAssetPostProcess; + callback?.Invoke(m_OutputImporterData.mainObject); + } + } + + private void CacheTreeImporterValues(string assetPath) + { + // Variables used a lot are cached, since accessing any Reader array has a non-negligeable cost. + m_LODCount = (uint)m_Tree.Lod.Length; + if(m_LODCount > LODGroupGUI.kLODColors.Length) + { + Debug.LogWarningFormat("Number of LOD meshes in asset ({0}) is larger than the maximum number supported by Unity GUI ({1})." + + "\nImporting only the first {1} LOD meshes." + , m_LODCount, LODGroupGUI.kLODColors.Length); + + // LODGroup GUI won't draw if we're above this limit, so we prevent future assertions here. + m_LODCount = (uint)LODGroupGUI.kLODColors.Length; + } + + m_HasFacingData = TreeHasFacingData(); + m_HasBranch2Data = m_Tree.Wind.DoBranch2; + m_LastLodIsBillboard = m_Tree.BillboardInfo.LastLodIsBillboard; + m_CollisionObjectsCount = (uint)m_Tree.CollisionObjects.Length; + + WindConfigSDK windCfg = m_Tree.Wind; + m_WindEnabled = (windCfg.DoShared || windCfg.DoBranch1 || windCfg.DoBranch2 || windCfg.DoRipple) + && m_WindSettings.enableWind; + + m_PathFromDirectory = Path.GetDirectoryName(assetPath) + "/"; + } + + internal void RegenerateMaterials() + { + m_OutputImporterData = AssetDatabase.LoadAssetAtPath(assetPath); + + if (m_OutputImporterData.hasEmbeddedMaterials) + { + // TODO: Verify if we could only generate the embedded materials + // instead of reimporting entirely the asset. + SaveAndReimport(); + return; + } + + try + { + AssetDatabase.StartAssetEditing(); + + RegenerateAndPopulateExternalMaterials(this.assetPath); + + TriggerAllCallbacks(); + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + + [InitializeOnLoadMethod] + static void InitializeEditorCallback() + { + EditorApplication.update += DirtyCustomDependencies; + s_DefaultShaderHash = ComputeDefaultShaderHash(); + AssetDatabase.RegisterCustomDependency(ImporterSettings.kSRPDependencyName, new Hash128(s_DefaultShaderHash, 0)); + } + + + static ulong CombineHash(ulong h1, ulong h2) + { + unchecked + { + return h1 ^ h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2); // Similar to c++ boost::hash_combine + } + } + + static ulong ComputeDefaultShaderHash() + { + ulong newDefaultShaderHash = 0UL; + if (GraphicsSettings.GetDefaultShader(DefaultShaderType.SpeedTree9) == null) + { + newDefaultShaderHash = 0; + } + else + { + if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(GraphicsSettings.GetDefaultShader(DefaultShaderType.SpeedTree9), out var guid, + out long fileId)) + { + newDefaultShaderHash = CombineHash((ulong)guid.GetHashCode(), (ulong)fileId); + } + } + + return newDefaultShaderHash; + } + + static void DirtyCustomDependencies() + { + DateTime now = DateTime.UtcNow; + if (Application.isPlaying || ((now - s_LastCheck) < k_CheckDependencyFrequency)) + { + return; + } + + s_LastCheck = now; + + ulong newDefaultShaderHash = ComputeDefaultShaderHash(); + if (s_DefaultShaderHash != newDefaultShaderHash) + { + s_DefaultShaderHash = newDefaultShaderHash; + AssetDatabase.RegisterCustomDependency(ImporterSettings.kSRPDependencyName, new Hash128(s_DefaultShaderHash, 0)); + AssetDatabase.Refresh(); + } + } + + #region Mesh Geometry & Renderers + private Mesh CreateMeshAndGeometry(Lod lod, int lodIndex) + { + bool isBillboard = m_LastLodIsBillboard && (lodIndex == (m_LODCount - 1)); + int vertexCount = (int)lod.Vertices.Length; + int numUVs = CalculateNumUVs(isBillboard); + + STMeshGeometry sTMeshGeometry = new STMeshGeometry(vertexCount, numUVs, lodIndex); + + CalculateMeshGeometry(sTMeshGeometry, lod, isBillboard); + + Mesh mesh = new Mesh() + { + name = "LOD" + sTMeshGeometry.lodIndex + "_Mesh", + indexFormat = (sTMeshGeometry.vertices.Length > 65535) ? IndexFormat.UInt32 : IndexFormat.UInt16, + subMeshCount = (int)lod.DrawCalls.Length, + vertices = sTMeshGeometry.vertices, + normals = sTMeshGeometry.normals, + tangents = sTMeshGeometry.tangents, + colors32 = sTMeshGeometry.colors32 + }; + + mesh.SetUVs(0, sTMeshGeometry.uvs[0]); + mesh.SetUVs(1, sTMeshGeometry.uvs[1]); + if (!isBillboard) + { + // SpeedTree shader expects certain UV2 & UV3 values for leaf facing & wind. + // if we don't claim them here now, tree rendering may break when Unity + // uses UV2 & UV3 and the shader finds unexpected values. + mesh.SetUVs(2, sTMeshGeometry.uvs[2]); // Branch2Pos, Branch2Dir, Branch2Weight, + mesh.SetUVs(3, sTMeshGeometry.uvs[3]); // 2/3 Anchor XYZ, FacingFlag + } + + return mesh; + } + + private void SetMeshIndices(Mesh mesh, Lod lod, DrawCall draw, int drawIndex) + { + int[] indices = new int[draw.IndexCount]; + + uint[] lodIndices = lod.Indices; + + for (int index = 0; index < draw.IndexCount; ++index) + { + indices[index] = unchecked((int)lodIndices[unchecked((int)(draw.IndexStart + index))]); + } + mesh.SetIndices(indices, MeshTopology.Triangles, drawIndex, true); + } + + private void CreateMeshAndLODObjects(Mesh mesh, int lodIndex, ref LOD[] lods) + { + mesh.RecalculateUVDistributionMetrics(); + GameObject lodObject = new GameObject("LOD" + lodIndex); + lodObject.transform.parent = m_OutputImporterData.mainObject.transform; + + MeshFilter meshFilter = lodObject.AddComponent(); + meshFilter.mesh = mesh; + + MeshRenderer renderer = lodObject.AddComponent(); + { + lods[lodIndex] = new LOD(m_PerLODSettings[lodIndex].height, new Renderer[] { renderer }); + + SetMeshRendererSettings(renderer, lodIndex); + } + + AddTreeComponentToLODObject(lodObject); + + m_Context.AddObjectToAsset(mesh.name, mesh); + } + + private void CalculateMeshGeometry(STMeshGeometry sTMeshGeometry, Lod lod, bool isBillboard) + { + STVertex[] vertices = lod.Vertices; + + for (int i = 0; i < sTMeshGeometry.vertices.Length; ++i) + { + STVertex vertex = vertices[i]; + + sTMeshGeometry.vertices[i].Set( + vertex.Anchor.X + (vertex.CameraFacing ? -vertex.Offset.X : vertex.Offset.X), + vertex.Anchor.Y + vertex.Offset.Y, + vertex.Anchor.Z + vertex.Offset.Z); + + sTMeshGeometry.vertices[i] *= m_MeshSettings.scaleFactor; + + sTMeshGeometry.normals[i].Set(vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z); + + Vector3 vertexTangent = new Vector3(vertex.Tangent.X, vertex.Tangent.Y, vertex.Tangent.Z); + Vector3 binormal = Vector3.Cross(sTMeshGeometry.normals[i], vertexTangent); + Vector2 vertexBinormal = new Vector3(vertex.Binormal.X, vertex.Binormal.Y, vertex.Binormal.Z); + + float dot = Vector3.Dot(binormal, vertexBinormal); + float flip = (dot < 0.0f) ? -1.0f : 1.0f; + + sTMeshGeometry.tangents[i].Set(vertex.Tangent.X, vertex.Tangent.Y, vertex.Tangent.Z, flip); + + sTMeshGeometry.colors32[i] = new Color( + vertex.Color.X * vertex.AmbientOcclusion, + vertex.Color.Y * vertex.AmbientOcclusion, + vertex.Color.Z * vertex.AmbientOcclusion, + vertex.BlendWeight); + + // Texcoord setup: + // 0 Diffuse UV, Branch1Pos, Branch1Dir + // 1 Lightmap UV, Branch1Weight, RippleWeight + + // If Branch2 is available: + // 2 Branch2Pos, Branch2Dir, Branch2Weight, + + // If camera-facing geom is available: + // 2/3 Anchor XYZ, FacingFlag + + int currentUV = 0; + sTMeshGeometry.uvs[currentUV++][i].Set( + vertex.TexCoord.X, + vertex.TexCoord.Y, + vertex.BranchWind1.X, + vertex.BranchWind1.Y); + + sTMeshGeometry.uvs[currentUV++][i].Set( + vertex.LightmapTexCoord.X, + vertex.LightmapTexCoord.Y, + vertex.BranchWind1.Z, + vertex.RippleWeight); + + if (!isBillboard) + { + float anchorX = m_HasFacingData ? vertex.Anchor.X * m_MeshSettings.scaleFactor : 0.0f; + float anchorY = m_HasFacingData ? vertex.Anchor.Y * m_MeshSettings.scaleFactor : 0.0f; + float anchorZ = m_HasFacingData ? vertex.Anchor.Z * m_MeshSettings.scaleFactor : 0.0f; + float leafFacingFlag = vertex.CameraFacing ? 1.0f : 0.0f; + + sTMeshGeometry.uvs[currentUV++][i].Set( + m_HasBranch2Data ? vertex.BranchWind2.X : anchorX, + m_HasBranch2Data ? vertex.BranchWind2.Y : anchorY, + m_HasBranch2Data ? vertex.BranchWind2.Z : anchorZ, + m_HasBranch2Data ? 0.0f /*UNUSED*/ : leafFacingFlag); + + bool useUV3 = m_HasBranch2Data && m_HasFacingData; + sTMeshGeometry.uvs[currentUV++][i].Set( + useUV3 ? anchorX : 0.0f, + useUV3 ? anchorY : 0.0f, + useUV3 ? anchorZ : 0.0f, + useUV3 ? leafFacingFlag : 0.0f); + } + } + } + + private void SetMeshRendererSettings(MeshRenderer renderer, int lodIndex) + { + ShadowCastingMode castMode = (m_LightingSettings.enableShadowCasting) ? ShadowCastingMode.On : ShadowCastingMode.Off; + LightProbeUsage probeUsage = (m_LightingSettings.enableLightProbes) ? LightProbeUsage.BlendProbes : LightProbeUsage.Off; + ReflectionProbeUsage reflectionProbe = m_LightingSettings.reflectionProbeEnumValue; + bool receiveShadows = m_LightingSettings.enableShadowReceiving; + + if (m_PerLODSettings[lodIndex].enableSettingOverride) + { + castMode = m_PerLODSettings[lodIndex].castShadows ? ShadowCastingMode.On : ShadowCastingMode.Off; + probeUsage = m_PerLODSettings[lodIndex].useLightProbes ? LightProbeUsage.BlendProbes : LightProbeUsage.Off; + receiveShadows = m_PerLODSettings[lodIndex].receiveShadows; + reflectionProbe = m_PerLODSettings[lodIndex].reflectionProbeUsage; + } + + renderer.sharedMaterials = RetrieveMaterialsForCurrentLod(lodIndex); + renderer.motionVectorGenerationMode = m_AdditionalSettings.motionVectorModeEnumValue; + renderer.receiveShadows = receiveShadows; + renderer.shadowCastingMode = castMode; + renderer.lightProbeUsage = probeUsage; + renderer.reflectionProbeUsage = reflectionProbe; + } + + private int CalculateNumUVs(bool isBillboard) + { + int numUVs = 2; + if (!isBillboard) + { + numUVs += 2; // reserve UV2 & UV3 for 3D-geometry to detect leaf facing (VS effect) correctly + } + return numUVs; + } + #endregion + + #region LODs + private PerLODSettings InstantiateAndInitializeLODSettingsObject(bool isBillboardLOD) + { + return new PerLODSettings() + { + enableSettingOverride = isBillboardLOD, + enableBump = m_MaterialSettings.enableBumpMapping, + enableHue = m_MaterialSettings.enableHueVariation, + enableSubsurface = m_MaterialSettings.enableSubsurfaceScattering, + castShadows = true, + receiveShadows = true, + useLightProbes = !isBillboardLOD, + reflectionProbeUsage = isBillboardLOD ? ReflectionProbeUsage.Off : ReflectionProbeUsage.BlendProbes, + }; + } + + private void CalculateBillboardAndPerLODSettings() + { + if (m_PerLODSettings.Count > m_LODCount) + { + m_PerLODSettings.RemoveRange((int)m_LODCount, m_PerLODSettings.Count); + } + else if (m_PerLODSettings.Count < m_LODCount) + { + m_PerLODSettings.Clear(); + for (int i = 0; i < m_LODCount; ++i) + { + bool isBillboardLOD = m_LastLodIsBillboard && i == m_LODCount - 1; + + PerLODSettings lodSettings = InstantiateAndInitializeLODSettingsObject(isBillboardLOD); + + m_PerLODSettings.Add(lodSettings); + } + + // Always reset LOD heights if size doesn't match. + for (int i = 0; i < m_LODCount; ++i) + { + m_PerLODSettings[i].height = (i == 0) ? 0.5f : m_PerLODSettings[i - 1].height * 0.5f; + } + + if (m_LODCount > 0) + { + // Using this pattern to avoid using 'Last' from Linq. + m_PerLODSettings[^1].height = 0.01f; + } + } + + Debug.Assert(m_PerLODSettings.Count == m_LODCount); + } + + private Material[] RetrieveMaterialsForCurrentLod(int lodIndex) + { + List materials = m_OutputImporterData.lodMaterials.materials; + + if (materials == null || materials.Count == 0) + return null; + + List matIDs = m_OutputImporterData.lodMaterials.lodToMaterials[lodIndex]; + + // Using this pattern to avoid using 'Select' from Linq. + Material[] lodMaterials = new Material[matIDs.Count]; + for (int i = 0; i < matIDs.Count; ++i) + { + lodMaterials[i] = materials[matIDs[i]].material; + } + + return lodMaterials; + } + + private void AddLODGroupToMainObjectAndSetTransition(LOD[] lods) + { + LODGroup lodGroup = m_OutputImporterData.mainObject.AddComponent(); + lodGroup.SetLODs(lods); + + int numLODs = lods.Length; + + if (m_LODSettings.enableSmoothLODTransition && numLODs > 0) + { + lodGroup.fadeMode = LODFadeMode.CrossFade; + lodGroup.animateCrossFading = m_LODSettings.animateCrossFading; + lodGroup.lastLODBillboard = m_LastLodIsBillboard; + + if (!m_LODSettings.animateCrossFading) + { + int lastLod = numLODs - 1; + lods[lastLod].fadeTransitionWidth = m_LODSettings.fadeOutWidth; + + if (m_LastLodIsBillboard && numLODs > 2) + { + int lastMeshLOD = numLODs - 2; + lods[lastMeshLOD].fadeTransitionWidth = m_LODSettings.billboardTransitionCrossFadeWidth; + } + } + } + + lodGroup.RecalculateBounds(); + } + + private void AddTreeComponentToLODObject(GameObject mainObject) + { + // Register the wind asset ptr through Tree component. + Tree treeComponent = mainObject.AddComponent(); + if (m_WindEnabled) + { + treeComponent.windAsset = m_WindAsset; + } + } + #endregion + + #region Materials + private void CreateMeshAndMaterials(bool regenerateMaterials = false) + { + LOD[] lods = new LOD[m_LODCount]; + + // Loop each LOD (mesh) of the asset. + for (int lodIndex = 0; lodIndex < m_LODCount; ++lodIndex) + { + Lod lod = m_Tree.Lod[lodIndex]; + Mesh mesh = CreateMeshAndGeometry(lod, lodIndex); + + // Loop each DrawCall (material) of the current mesh LOD. + for (int drawIndex = 0; drawIndex < lod.DrawCalls.Length; ++drawIndex) + { + DrawCall draw = lod.DrawCalls[drawIndex]; + + STMaterial stMaterial = m_Tree.Materials[(int)draw.MaterialIndex]; + + CreateMaterialsForCurrentLOD(stMaterial, lodIndex, regenerateMaterials); + + SetMeshIndices(mesh, lod, draw, drawIndex); + } + + CreateMeshAndLODObjects(mesh, lodIndex, ref lods); + } + + AddLODGroupToMainObjectAndSetTransition(lods); + } + + private void CreateMaterialsForCurrentLOD(STMaterial stMaterial, int lodIndex, bool regenerateMaterials) + { + bool matOverrided = m_PerLODSettings[lodIndex].enableSettingOverride; + bool isBillboard = m_LastLodIsBillboard && (lodIndex == (m_LODCount - 1)); + + // Overrided LODs have they own unique material. + string stMatName = stMaterial.Name; + if (matOverrided && !isBillboard) + stMatName += String.Format("LOD{0}", lodIndex); + + // Retrieve previously extracted material and update serialized index. + if (TryGetExternalMaterial(stMatName, out var extractedMat)) + { + // Explicity regenerate materials, should happen when bumping the material version for example. + if (regenerateMaterials) + { + extractedMat = CreateMaterial(stMaterial, lodIndex, extractedMat.name, m_PathFromDirectory); + + SetMaterialTextureAndColorProperties(stMaterial, extractedMat, lodIndex, m_PathFromDirectory); + } + else + { + RetrieveMaterialSpecialProperties(extractedMat); + } + + var existedMatIndex = m_OutputImporterData.lodMaterials.materials.FindIndex(m => m.defaultName == stMatName); + if (existedMatIndex == -1) + { + m_OutputImporterData.lodMaterials.materials.Add(new MaterialInfo { material = extractedMat, defaultName = stMatName, exported = true }); + m_OutputImporterData.lodMaterials.matNameToIndex[stMatName] = m_OutputImporterData.lodMaterials.materials.Count - 1; + } + else + { + m_OutputImporterData.lodMaterials.matNameToIndex[stMatName] = existedMatIndex; + } + } + // Create material if it doesn't exist yet. + else if (!m_OutputImporterData.lodMaterials.matNameToIndex.ContainsKey(stMatName)) + { + Material newMat = CreateMaterial(stMaterial, lodIndex, stMatName, m_PathFromDirectory); + + m_OutputImporterData.lodMaterials.materials.Add(new MaterialInfo { material = newMat, defaultName = stMatName, exported = false }); + m_OutputImporterData.lodMaterials.matNameToIndex.Add(stMatName, m_OutputImporterData.lodMaterials.materials.Count - 1); + } + + // Map the material id to the current LOD. + int indexMat = m_OutputImporterData.lodMaterials.matNameToIndex[stMatName]; + m_OutputImporterData.lodMaterials.AddLodMaterialIndex(lodIndex, indexMat); + } + + private void CreateAssetIdentifiersAndAddMaterialsToContext() + { + m_OutputImporterData.materialsIdentifiers.Clear(); + + foreach (MaterialInfo matInfo in m_OutputImporterData.lodMaterials.materials) + { + m_OutputImporterData.hasEmbeddedMaterials |= !matInfo.exported; + + if (!matInfo.exported) + { + m_Context.AddObjectToAsset(matInfo.material.name, matInfo.material); + + // It looks like a limitation from the default AssetImporter system. When deleting extracted materials manually, + // the external object map still contains a null reference, even after a reimport of the asset. + if (TryGetSourceAssetIdentifierFromName(matInfo.material.name, out var assetIdentifier)) + { + RemoveRemap(assetIdentifier); + } + } + + m_OutputImporterData.materialsIdentifiers.Add(new AssetIdentifier(matInfo.material.GetType(), matInfo.defaultName)); + } + } + + private void RegenerateMaterialsFromTree() + { + for (int lodIndex = 0; lodIndex < m_LODCount; lodIndex++) + { + Lod stLOD = m_Tree.Lod[lodIndex]; + + // Loop necessary materials for current LOD. + for (int drawIndex = 0; drawIndex < stLOD.DrawCalls.Length; ++drawIndex) + { + int matIndex = (int)stLOD.DrawCalls[drawIndex].MaterialIndex; + STMaterial stMaterial = m_Tree.Materials[matIndex]; + + CreateMaterialsForCurrentLOD(stMaterial, lodIndex, regenerateMaterials: true); + } + } + } + + private void RegenerateAndPopulateExternalMaterials(string assetPath) + { + // This object could potentially be cached, but this function is rarely triggered (only when bumping the material version) + // so the cost of caching it is not really interesting. + m_Tree = new SpeedTree9Reader(); + + FileStatus status = m_Tree.Initialize(assetPath); + if (status != FileStatus.Valid) + { + Debug.LogError($"Error while initializing the SpeedTree9 reader: {status}."); + return; + } + + m_Tree.ReadContent(); + + CacheTreeImporterValues(assetPath); + + if (!TryGetShaderForCurrentRenderPipeline(out m_Shader)) + { + Debug.LogError("SpeedTree9 shader is invalid, cannot create Materials for this SpeedTree asset."); + return; + } + + m_OutputImporterData = AssetDatabase.LoadAssetAtPath(assetPath); + m_OutputImporterData.lodMaterials.materials.Clear(); + + if (m_WindEnabled) + { + m_OutputImporterData.m_WindConfig = CopySpeedTree9WindConfig(m_Tree.Wind, m_MeshSettings.scaleFactor, m_Tree.Bounds); + } + + RegenerateMaterialsFromTree(); + + foreach (MaterialInfo matInfo in m_OutputImporterData.lodMaterials.materials) + { + m_OutputImporterData.hasEmbeddedMaterials |= !matInfo.exported; + + m_OutputImporterData.materialsIdentifiers.Add(new AssetIdentifier(matInfo.material.GetType(), matInfo.material.name)); + + // Remap the new material to the importer 'ExternalObjectMap'. + if (TryGetExternalMaterial(matInfo.defaultName, out var extractedMat)) + { + string newMatPath = AssetDatabase.GetAssetPath(extractedMat); + + // Not ideal, but it's safer to regenerate entirely the material (in case the pipeline has changed) + // and to avoid any potential issue with the 'ExternalObject' system (material null during next import) + if (File.Exists(newMatPath)) + { + File.Delete(newMatPath); + AssetDatabase.CreateAsset(matInfo.material, newMatPath); + } + + if (TryGetSourceAssetIdentifierFromName(matInfo.defaultName, out var assetIdentifier)) + { + AddRemap(assetIdentifier, matInfo.material); + } + } + } + } + + private bool TryGetExternalMaterial(string name, out Material material) + { + var externalObjMap = GetExternalObjectMap(); + + foreach (var obj in externalObjMap) + { + if (obj.Key.name == name) + { + material = obj.Value as Material; + return material != null; + } + } + + material = null; + return false; + } + + private bool TryGetSourceAssetIdentifierFromName(string name, out SourceAssetIdentifier assetIdentifier) + { + var externalObjMap = GetExternalObjectMap(); + + foreach (var obj in externalObjMap) + { + if (obj.Key.name == name) + { + assetIdentifier = obj.Key; + return true; + } + } + + assetIdentifier = new SourceAssetIdentifier(); + return false; + } + + private Material CreateMaterial(STMaterial stMaterial, int lod, string matName, string path) + { + Material mat = new Material(m_Shader) + { + name = matName + }; + + RetrieveMaterialSpecialProperties(mat); + + SetMaterialTextureAndColorProperties(stMaterial, mat, lod, path); + + SetMaterialOtherProperties(stMaterial, mat); + + SetWindKeywords(mat, stMaterial.Billboard); + + return mat; + } + + private bool SetMaterialTexture(Material mat, STMaterial stMaterial, int indexMap, string path, int property) + { + if (stMaterial.Maps.Length > indexMap) + { + MaterialMap stMatMap = stMaterial.Maps[indexMap]; + string mapPath = stMatMap.Path; + + if (!stMatMap.Used) + return false; + + if (!string.IsNullOrEmpty(mapPath)) + { + Texture2D texture = LoadTexture(mapPath, path); + + if (texture != null) + { + mat.SetTexture(property, texture); + return true; + } + } + } + + return false; + } + + private Texture2D LoadTexture(string mapPath, string path) + { + string texturePath = path + mapPath; + + Texture2D texture = (m_Context != null) + ? m_Context.GetReferenceToAssetMainObject(texturePath) as Texture2D + : AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)) as Texture2D; + + if (texture != null) + return texture; + + // Textures are not located near the asset, let's check if they were moved somewhere else. + string mapPathWithoutExtension = Path.GetFileNameWithoutExtension(mapPath); + string[] textureAssets = AssetDatabase.FindAssets(mapPathWithoutExtension); + + if (textureAssets != null && textureAssets.Length > 0) + { + string assetPathFromGUID = AssetDatabase.GUIDToAssetPath(textureAssets[0]); + + texture = (m_Context != null) + ? m_Context.GetReferenceToAssetMainObject(assetPathFromGUID) as Texture2D + : AssetDatabase.LoadAssetAtPath(assetPathFromGUID, typeof(Texture2D)) as Texture2D; + + return texture; + } + + return null; + } + + private bool TryGetInstanceIDFromMaterialProperty(Material material, int propertyName, out int id) + { + if (!material.HasProperty(propertyName)) + { + id = 0; + return false; + } + + var property = material.GetTexture(propertyName); + id = property.GetInstanceID(); + + return true; + } + + // Not all pipelines support the following properties, so we don't draw them in the inspector if that's the case. + private void RetrieveMaterialSpecialProperties(Material mat) + { + m_OutputImporterData.hasAlphaClipThreshold |= mat.HasProperty(MaterialProperties.AlphaClipThresholdID); + m_OutputImporterData.hasTransmissionScale |= mat.HasProperty(MaterialProperties.TransmissionScaleID); + } + + private void SetMaterialTextureAndColorProperties(STMaterial stMaterial, Material mat, int lodIndex, string path) + { + bool enableHueVariation = m_MaterialSettings.enableHueVariation; + bool enableBumpMapping = m_MaterialSettings.enableBumpMapping; + bool enableSubsurfaceScattering = m_MaterialSettings.enableSubsurfaceScattering; + + if (m_PerLODSettings[lodIndex].enableSettingOverride) + { + enableHueVariation = m_PerLODSettings[lodIndex].enableHue; + enableBumpMapping = m_PerLODSettings[lodIndex].enableBump; + enableSubsurfaceScattering = m_PerLODSettings[lodIndex].enableSubsurface; + } + + // MainTex and Color + { + bool colorTex = SetMaterialTexture(mat, stMaterial, 0, path, MaterialProperties.MainTexID); + + if (colorTex && TryGetInstanceIDFromMaterialProperty(mat, MaterialProperties.MainTexID, out int id) && id != 0) + { + mat.SetColor(MaterialProperties.ColorTintID, m_MaterialSettings.mainColor); + } + else if (colorTex) + { + Vec4 stColorVec = stMaterial.Maps[3].Color; + Color rpColor = new Color(stColorVec.X, stColorVec.Y, stColorVec.Z, stColorVec.W); + + mat.SetColor(MaterialProperties.ColorTintID, m_MaterialSettings.mainColor * rpColor); + } + } + + // Bump map + { + bool hasNormalMap = SetMaterialTexture(mat, stMaterial, 1, path, MaterialProperties.NormalMapID); + bool enableFeature = hasNormalMap && enableBumpMapping; + + mat.SetFloat(MaterialProperties.NormalMapKwToggleID, (enableFeature) ? 1.0f : 0.0f); + } + + // Glossiness, metallic, AO + { + bool foundExtra = SetMaterialTexture(mat, stMaterial, 2, path, MaterialProperties.ExtraTexID); + + int id = 0; + if (foundExtra && TryGetInstanceIDFromMaterialProperty(mat, MaterialProperties.ExtraTexID, out id) && id != 0) + { + // _Glossiness (== _Smoothness) is multipled in the shader with the texture values if ExtraTex is present. + // Set default value 1.0f to override the default value 0.5, otherwise, the original texture values will + // be scaled down to half as much. Same goes for _Metallic + mat.SetFloat(MaterialProperties.GlossinessID, 1.0f); + mat.SetFloat(MaterialProperties.MetallicID, 1.0f); + } + else if (foundExtra) + { + Vec4 stColor = stMaterial.Maps[2].Color; + mat.SetFloat(MaterialProperties.GlossinessID, stColor.X); + mat.SetFloat(MaterialProperties.MetallicID, stColor.Y); + } + + mat.SetFloat(MaterialProperties.ExtraMapKwToggleID, (foundExtra && id != 0) ? 1.0f : 0.0f); + } + + // Extra and SSS + if (stMaterial.TwoSided || stMaterial.Billboard) + { + bool hasSSSTex = SetMaterialTexture(mat, stMaterial, 3, path, MaterialProperties.SubsurfaceTexID); + bool setToggle = hasSSSTex && enableSubsurfaceScattering; + + // TODO: To implement in ST9 Shader. + mat.SetFloat(MaterialProperties.SubsurfaceKwToggleID, (setToggle) ? 1.0f : 0.0f); + + if (hasSSSTex && TryGetInstanceIDFromMaterialProperty(mat, MaterialProperties.SubsurfaceTexID, out int id) && id != 0) + { + mat.SetColor(MaterialProperties.SubsurfaceColorID, new Color(1.0f, 1.0f, 1.0f, 1.0f)); + } + else if (hasSSSTex) + { + Vec4 stColor = stMaterial.Maps[3].Color; + mat.SetColor(MaterialProperties.SubsurfaceColorID, new Color(stColor.X, stColor.Y, stColor.Z, stColor.W)); + } + + if (m_OutputImporterData.hasAlphaClipThreshold && mat.HasFloat(MaterialProperties.AlphaClipThresholdID)) + { + mat.SetFloat(MaterialProperties.AlphaClipThresholdID, m_MaterialSettings.alphaClipThreshold); + } + + if (m_OutputImporterData.hasTransmissionScale && mat.HasFloat(MaterialProperties.TransmissionScaleID)) + { + mat.SetFloat(MaterialProperties.TransmissionScaleID, m_MaterialSettings.transmissionScale); + } + } + + // Hue effect + { + mat.SetFloat(MaterialProperties.HueVariationKwToggleID, enableHueVariation ? 1.0f : 0.0f); + mat.SetColor(MaterialProperties.HueVariationColorID, m_MaterialSettings.hueVariation); + } + } + + private void SetMaterialOtherProperties(STMaterial stMaterial, Material mat) + { + bool isBillboard = stMaterial.Billboard; + + // Other properties + mat.SetFloat(MaterialProperties.BillboardKwToggleID, isBillboard ? 1.0f : 0.0f); + if (isBillboard) + { + mat.EnableKeyword(MaterialKeywords.BillboardID); + } + mat.SetFloat(MaterialProperties.LeafFacingKwToggleID, m_HasFacingData ? 1.0f : 0.0f); + + if (mat.HasFloat(MaterialProperties.DoubleSidedToggleID)) + mat.SetFloat(MaterialProperties.DoubleSidedToggleID, stMaterial.TwoSided ? 1.0f : 0.0f); + + if (mat.HasFloat(MaterialProperties.DoubleSidedNormalModeID)) + mat.SetFloat(MaterialProperties.DoubleSidedNormalModeID, stMaterial.FlipNormalsOnBackside ? 0.0f : 2.0f); + + if (mat.HasVector(MaterialProperties.DiffusionProfileAssetID)) + mat.SetVector(MaterialProperties.DiffusionProfileAssetID, m_MaterialSettings.diffusionProfileAssetID); + + if (mat.HasFloat(MaterialProperties.DiffusionProfileID)) + mat.SetFloat(MaterialProperties.DiffusionProfileID, m_MaterialSettings.diffusionProfileID); + + if (mat.HasFloat(MaterialProperties.BackfaceNormalModeID)) + mat.SetFloat(MaterialProperties.BackfaceNormalModeID, stMaterial.FlipNormalsOnBackside ? 0.0f : 2.0f); + + if (mat.HasFloat(MaterialProperties.TwoSidedID)) + mat.SetFloat(MaterialProperties.TwoSidedID, stMaterial.TwoSided ? 0.0f : 2.0f); // matches cull mode. 0: no cull + + mat.enableInstancing = true; + mat.doubleSidedGI = stMaterial.TwoSided; + } + + private void SetWindKeywords(Material material, bool isBillboardMat) + { + if (material == null) + return; + + //------------------------------------------------------------------------ + // Note: + // mat.SetFloat(...) : Legacy rendering pipeline keyword toggle + // mat.EnableKeyword(...) : SRP keyword toggle + //------------------------------------------------------------------------ + SpeedTreeWindConfig9 windCfg = m_OutputImporterData.m_WindConfig; + + if (windCfg.doShared != 0) + { + material.SetFloat(MaterialProperties.WindSharedKwToggle, 1.0f); + } + if (!isBillboardMat) + { + if (windCfg.doBranch2 != 0) + { + material.SetFloat(MaterialProperties.WindBranch2KwToggle, 1.0f); + } + if (windCfg.doBranch1 != 0) + { + material.SetFloat(MaterialProperties.WindBranch1KwToggle, 1.0f); + } + if (windCfg.doRipple != 0) + { + material.SetFloat(MaterialProperties.WindRippleKwToggle, 1.0f); + if (windCfg.doShimmer != 0) + { + material.SetFloat(MaterialProperties.WindShimmerKwToggle, 1.0f); + } + } + } + } + + internal bool SearchAndRemapMaterials(string materialFolderPath) + { + bool changedMappings = false; + + if (materialFolderPath == null) + throw new ArgumentNullException("materialFolderPath"); + + if (string.IsNullOrEmpty(materialFolderPath)) + throw new ArgumentException(string.Format("Invalid material folder path: {0}.", materialFolderPath), "materialFolderPath"); + + string[] guids = AssetDatabase.FindAssets("t:Material", new string[] { materialFolderPath }); + List> materials = new List>(); + foreach (string guid in guids) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + // ensure that we only load material assets, not embedded materials + Material material = AssetDatabase.LoadMainAssetAtPath(path) as Material; + if (material) + materials.Add(new Tuple(path, material)); + } + + m_OutputImporterData = AssetDatabase.LoadAssetAtPath(assetPath); + AssetIdentifier[] importedMaterials = m_OutputImporterData.materialsIdentifiers.ToArray(); + + foreach (Tuple material in materials) + { + string materialName = material.Item2.name; + string materialFile = material.Item1; + + // the legacy materials have the LOD in the path, while the new materials have the LOD as part of the name + bool isLegacyMaterial = !materialName.Contains("LOD") && !materialName.Contains("Billboard"); + bool hasLOD = isLegacyMaterial && materialFile.Contains("LOD"); + string lod = Path.GetFileNameWithoutExtension(Path.GetDirectoryName(materialFile)); + AssetIdentifier importedMaterial = Array.Find(importedMaterials, x => x.name.Contains(materialName) && (!hasLOD || x.name.Contains(lod))); + + if (!string.IsNullOrEmpty(importedMaterial.name)) + { + SourceAssetIdentifier importedIdentifier = new SourceAssetIdentifier(material.Item2); + + AddRemap(importedIdentifier, material.Item2); + changedMappings = true; + } + } + + return changedMappings; + } + + internal string GetMaterialFolderPath() + { + return FileUtil.DeleteLastPathNameComponent(assetPath) + "/"; + } + + internal void SetMaterialsVersionToCurrent() + { + m_MaterialVersion = SPEEDTREE_9_MATERIAL_VERSION; + MarkDirty(); + } + + private void AddDependencyOnExtractedMaterials() + { + Dictionary extMap = GetExternalObjectMap(); + + foreach (var entry in extMap) + { + if (entry.Value != null) + { + string matPath = AssetDatabase.GetAssetPath(entry.Value); + + m_Context.DependsOnImportedAsset(matPath); + + // Necessary to avoid the warning "Import of asset setup artifact dependency to but dependency isn't used + // and therefore not registered in the asset database". + AssetDatabase.LoadAssetAtPath(matPath, typeof(Material)); + } + } + } + #endregion + + #region Wind + private unsafe SpeedTreeWindConfig9 CopySpeedTree9WindConfig(WindConfigSDK wind, float scaleFactor, in Bounds3 treeBounds) + { + const bool CHECK_ZERO = true; + const bool DONT_CHECK_ZERO = false; + + void CopyCurve(in float[] src, float* dst) + { + const int NUM_CURVE_ELEMENTS = 20; + Debug.Assert(src.Length == NUM_CURVE_ELEMENTS); + for (global::System.Int32 i = 0; i < NUM_CURVE_ELEMENTS; i++) + { + dst[i] = src[i]; + } + } + + void CopyCurveScale(in float[] src, float* dst, float scaleFactor) + { + const int NUM_CURVE_ELEMENTS = 20; + Debug.Assert(src.Length == NUM_CURVE_ELEMENTS); + for (global::System.Int32 i = 0; i < NUM_CURVE_ELEMENTS; i++) + { + dst[i] = src[i] * scaleFactor; + } + } + + bool ValidCurve(float[] curve, bool bCheckZero = CHECK_ZERO) + { + bool bNonZero = false; + for (int i = 0; i < curve.Length; ++i) + { + bNonZero |= curve[i] != 0.0f; + if (float.IsNaN(curve[i])) + { + return false; + } + } + + if (bCheckZero) + { + return bNonZero; + } + return true; + } + + bool BranchHasAllCurvesValid(in WindBranch b) + { + return ValidCurve(b.Bend) + && ValidCurve(b.Oscillation) + && ValidCurve(b.Speed, CHECK_ZERO) + && ValidCurve(b.Turbulence) + && ValidCurve(b.Flexibility, DONT_CHECK_ZERO + ); + } + + bool RippleHasAllCurvesValid(in WindRipple r) + { + return ValidCurve(r.Planar) + && ValidCurve(r.Directional) + && ValidCurve(r.Speed) + && ValidCurve(r.Flexibility, DONT_CHECK_ZERO + ); + } + + SpeedTreeWindConfig9 cfg = new SpeedTreeWindConfig9(); + + // common + WindConfigCommon common = wind.Common; + cfg.strengthResponse = common.StrengthResponse; + cfg.directionResponse = common.DirectionResponse; + cfg.gustFrequency = common.GustFrequency; + cfg.gustStrengthMin = common.GustStrengthMin; + cfg.gustStrengthMax = common.GustStrengthMax; + cfg.gustDurationMin = common.GustDurationMin; + cfg.gustDurationMax = common.GustDurationMax; + cfg.gustRiseScalar = common.GustRiseScalar; + cfg.gustFallScalar = common.GustFallScalar; + + // st9 + cfg.branch1StretchLimit = wind.Branch1StretchLimit * scaleFactor; + cfg.branch2StretchLimit = wind.Branch2StretchLimit * scaleFactor; + cfg.importScale = scaleFactor; + cfg.treeExtentX = (treeBounds.Max.X - treeBounds.Min.X) * scaleFactor; + cfg.treeExtentY = (treeBounds.Max.Y - treeBounds.Min.Y) * scaleFactor; + cfg.treeExtentZ = (treeBounds.Max.Z - treeBounds.Min.Z) * scaleFactor; + + if (wind.DoShared) + { + WindConfigSDK.WindBranch shared = wind.Shared; + CopyCurveScale(shared.Bend, cfg.bendShared, scaleFactor); + CopyCurveScale(shared.Oscillation, cfg.oscillationShared, scaleFactor); + CopyCurve(shared.Speed, cfg.speedShared); + CopyCurve(shared.Turbulence, cfg.turbulenceShared); + CopyCurve(shared.Flexibility, cfg.flexibilityShared); + cfg.independenceShared = shared.Independence; + cfg.sharedHeightStart = wind.SharedStartHeight; // this is a % value + if (BranchHasAllCurvesValid(in shared)) + { + cfg.doShared = 1; + } + } + + if (wind.DoBranch1) + { + WindConfigSDK.WindBranch branch1 = wind.Branch1; + CopyCurveScale(branch1.Bend, cfg.bendBranch1, scaleFactor); + CopyCurveScale(branch1.Oscillation, cfg.oscillationBranch1, scaleFactor); + CopyCurve(branch1.Speed, cfg.speedBranch1); + CopyCurve(branch1.Turbulence, cfg.turbulenceBranch1); + CopyCurve(branch1.Flexibility, cfg.flexibilityBranch1); + cfg.independenceBranch1 = branch1.Independence; + if (BranchHasAllCurvesValid(in branch1)) + { + cfg.doBranch1 = 1; + } + } + + if (wind.DoBranch2) + { + WindConfigSDK.WindBranch branch2 = wind.Branch2; + CopyCurveScale(branch2.Bend, cfg.bendBranch2, scaleFactor); + CopyCurveScale(branch2.Oscillation, cfg.oscillationBranch2, scaleFactor); + CopyCurve(branch2.Speed, cfg.speedBranch2); + CopyCurve(branch2.Turbulence, cfg.turbulenceBranch2); + CopyCurve(branch2.Flexibility, cfg.flexibilityBranch2); + cfg.independenceBranch2 = branch2.Independence; + if (BranchHasAllCurvesValid(in branch2)) + { + cfg.doBranch2 = 1; + } + } + + if (wind.DoRipple) + { + WindConfigSDK.WindRipple ripple = wind.Ripple; + CopyCurveScale(ripple.Planar, cfg.planarRipple, scaleFactor); + CopyCurveScale(ripple.Directional, cfg.directionalRipple, scaleFactor); + CopyCurve(ripple.Speed, cfg.speedRipple); + CopyCurve(ripple.Flexibility, cfg.flexibilityRipple); + cfg.independenceRipple = ripple.Independence; + if (RippleHasAllCurvesValid(in ripple)) + { + cfg.doRipple = 1; + if (wind.DoShimmer) + { + cfg.doShimmer = 1; + cfg.shimmerRipple = ripple.Shimmer; + } + } + } + return cfg; + } + + private void SetWindParameters(ref SpeedTreeWindConfig9 cfg) + { + cfg.strengthResponse = m_WindSettings.strenghResponse; + cfg.directionResponse = m_WindSettings.directionResponse; + cfg.windIndependence = m_WindSettings.randomness; + } + + #endregion + + #region Others + private void CalculateScaleFactorFromUnit() + { + switch (m_MeshSettings.unitConversion) + { + // Use units in the imported file without any conversion. + case STUnitConversion.kLeaveAsIs: + m_MeshSettings.scaleFactor = 1.0f; + break; + case STUnitConversion.kFeetToMeters: + m_MeshSettings.scaleFactor = SpeedTreeConstants.kFeetToMetersRatio; + break; + case STUnitConversion.kCentimetersToMeters: + m_MeshSettings.scaleFactor = SpeedTreeConstants.kCentimetersToMetersRatio; + break; + case STUnitConversion.kInchesToMeters: + m_MeshSettings.scaleFactor = SpeedTreeConstants.kInchesToMetersRatio; + break; + case STUnitConversion.kCustomConversion: + /* no-op */ + break; + } + } + + private bool TreeHasFacingData() + { + for (int lodIndex = 0; lodIndex < m_LODCount; ++lodIndex) + { + Lod lod = m_Tree.Lod[lodIndex]; + + for (int drawIndex = 0; drawIndex < lod.DrawCalls.Length; ++drawIndex) + { + DrawCall draw = lod.DrawCalls[drawIndex]; + if(draw.ContainsFacingGeometry) + return true; + } + } + return false; + } + + internal static bool TryGetShaderForCurrentRenderPipeline(out Shader shader) + { + shader = GraphicsSettings.GetDefaultShader(DefaultShaderType.SpeedTree9); + if (shader == null) + { + shader = Shader.Find(ImporterSettings.kLegacyShaderName); + } + + return shader != null; + } + + private void CreateAndAddRigidBodyToAsset(GameObject mainObject) + { + Rigidbody rb = mainObject.AddComponent(); + if (rb != null) + { + rb.useGravity = false; + rb.isKinematic = true; + } + } + + private void CreateAndAddCollidersToAsset() + { + for (int iCollider = 0; iCollider < m_CollisionObjectsCount; ++iCollider) + { + CollisionObject stCollider = m_Tree.CollisionObjects[iCollider]; + + GameObject collisionObject = new GameObject("Collider" + (iCollider + 1)); + collisionObject.transform.parent = m_OutputImporterData.mainObject.transform; + + Vector3 vOne = new Vector3(stCollider.Position.X, stCollider.Position.Y, stCollider.Position.Z); + Vector3 vTwo = new Vector3(stCollider.Position2.X, stCollider.Position2.Y, stCollider.Position2.Z); + + vOne *= m_MeshSettings.scaleFactor; + vTwo *= m_MeshSettings.scaleFactor; + + collisionObject.transform.position = (vOne + vTwo) * 0.5f; + + if ((vOne - vTwo).sqrMagnitude < 0.001f) + { + SphereCollider collider = collisionObject.AddComponent(); + collider.radius = stCollider.Radius * m_MeshSettings.scaleFactor; + } + else + { + CapsuleCollider collider = collisionObject.AddComponent(); + collider.direction = 2; + collider.radius = stCollider.Radius * m_MeshSettings.scaleFactor; + collider.height = (vOne - vTwo).magnitude; + collisionObject.transform.LookAt(vTwo); + } + + m_Context.AddObjectToAsset(collisionObject.name, collisionObject); + } + } + + internal float[] GetPerLODSettingsHeights() + { + float[] heightsArray = new float[m_PerLODSettings.Count]; + + for (int i = 0; i < m_PerLODSettings.Count; ++i) + { + heightsArray[i] = m_PerLODSettings[i].height; + } + + return heightsArray; + } + #endregion + } + + // Use a postprocessor to set various settings on the textures since these dont stick during first import. + class SpeedTree9Postprocessor : AssetPostprocessor + { + private static void OnPostprocessAllAssets( + string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths, + bool didDomainReload) + { + foreach (string assetFilename in importedAssets) + { + if (Path.GetExtension(assetFilename) == ".st9") + { + try + { + AssetDatabase.StartAssetEditing(); + + ChangeTextureImporterSettingsForSt9Files(assetFilename); + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + } + + if (didDomainReload) + { + if (TryGetHashSpeedTreeAttributeMaterialSettings(out List strToHash)) + { + Hash128 hash = new Hash128(); + + foreach (string str in strToHash) + { + hash.Append(str); + } + + AssetDatabase.RegisterCustomDependency(ImporterSettings.kMaterialSettingsDependencyname, hash); + } + else + { + AssetDatabase.UnregisterCustomDependencyPrefixFilter(ImporterSettings.kMaterialSettingsDependencyname); + } + } + } + + private static bool TryGetHashSpeedTreeAttributeMaterialSettings(out List strToHash) + { + var allMethods = AttributeHelper.GetMethodsWithAttribute().methodsWithAttributes; + + strToHash = new List(); + + foreach (var method in allMethods) + { + MaterialSettingsCallbackAttribute attribute = method.attribute as MaterialSettingsCallbackAttribute; + + strToHash.Add($"{method.info.Name}-{method.info.DeclaringType.AssemblyQualifiedName}-{attribute.MethodVersion.ToString()}"); + } + + strToHash.Sort(); + return strToHash.Count > 0; + } + + private static void ChangeTextureImporterSettingsForSt9Files(string assetPath) + { + SpeedTree9Reader tree = new SpeedTree9Reader(); + + FileStatus status = tree.Initialize(assetPath); + if (status != FileStatus.Valid) + { + Debug.LogError($"Error while initializing the SpeedTree9 reader: {status}."); + return; + } + + tree.ReadContent(); + + string path = Path.GetDirectoryName(assetPath) + "/"; + for (int matIndex = 0; matIndex < tree.Materials.Length; ++matIndex) + { + STMaterial stMaterial = tree.Materials[matIndex]; + + if (TryGetTextureImporterFromIndex(stMaterial, 0, path, out TextureImporter texImporterColor)) + ApplyColorTextureSettings(texImporterColor); + + if (TryGetTextureImporterFromIndex(stMaterial, 1, path, out TextureImporter texImporterNormal)) + ApplyNormalTextureSettings(texImporterNormal); + + if (TryGetTextureImporterFromIndex(stMaterial, 2, path, out TextureImporter texImporterExtra)) + ApplyExtraTextureSettings(texImporterExtra); + } + } + + private static bool TryGetTextureImporterFromIndex( + STMaterial stMaterial, + int index, + string directoryPath, + out TextureImporter textureImporter) + { + textureImporter = null; + + if (stMaterial.Maps.Length <= index) + return false; + + MaterialMap mat = stMaterial.Maps[index]; + if (!mat.Used || string.IsNullOrEmpty(mat.Path)) + return false; + + TextureImporter texImporter = TextureImporter.GetAtPath(directoryPath + mat.Path) as TextureImporter; + if (texImporter == null) + return false; + + textureImporter = texImporter; + return true; + } + + private static void ApplyColorTextureSettings(TextureImporter texImporter) + { + if (texImporter.alphaIsTransparency && + texImporter.mipMapsPreserveCoverage && + texImporter.alphaTestReferenceValue == 0.1f) + return; + + texImporter.alphaIsTransparency = true; + texImporter.mipMapsPreserveCoverage = true; + texImporter.alphaTestReferenceValue = 0.1f; + + EditorUtility.SetDirty(texImporter); + texImporter.SaveAndReimport(); + } + + private static void ApplyNormalTextureSettings(TextureImporter texImporter) + { + if (texImporter.textureType == TextureImporterType.NormalMap) + return; + + texImporter.textureType = TextureImporterType.NormalMap; + + EditorUtility.SetDirty(texImporter); + texImporter.SaveAndReimport(); + } + + private static void ApplyExtraTextureSettings(TextureImporter texImporter) + { + if (texImporter.sRGBTexture == false) + return; + + texImporter.sRGBTexture = false; + + EditorUtility.SetDirty(texImporter); + texImporter.SaveAndReimport(); + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs new file mode 100644 index 0000000000..081cd3b494 --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs @@ -0,0 +1,309 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEditor.AssetImporters; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace UnityEditor.SpeedTree.Importer +{ + [CustomEditor(typeof(SpeedTree9Importer))] + [CanEditMultipleObjects] + internal class SpeedTree9ImporterEditor : AssetImporterTabbedEditor + { + private static class Styles + { + public static GUIContent ApplyAndGenerate = EditorGUIUtility.TrTextContent("Apply & Generate Materials", "Apply current importer settings and generate materials with new settings."); + public static GUIContent Regenerate = EditorGUIUtility.TrTextContent("Regenerate Materials", "Regenerate materials from the current importer settings."); + public static GUIContent RegenerateRemapped = EditorGUIUtility.TrTextContent("Regenerate Materials", "Regenerate the remapped materials from the current import settings."); + public static GUIContent ApplyAndGenerateRemapped = EditorGUIUtility.TrTextContent("Apply & Generate Materials", "Apply current importer settings and regenerate the remapped materials with new settings."); + + public static readonly string ModelTabName = "Model"; + public static readonly string MaterialsTabName = "Materials"; + public static readonly string WindTabName = "Wind"; + } + + private bool m_HasRemappedMaterials = false; + private SpeedTreeImporterOutputData m_OutputImporterData = null; + private SpeedTree9Importer m_STImporter = null; + + internal IEnumerable importers + { + get + { + SpeedTree9Importer[] st9Importers = new SpeedTree9Importer[targets.Length]; + + for (int i = 0; i < targets.Length; ++i) + { + st9Importers[i] = targets[i] as SpeedTree9Importer; + } + + return st9Importers; + } + } + + public override void OnEnable() + { + m_STImporter = target as SpeedTree9Importer; + + if (tabs == null) + { + tabs = new BaseAssetImporterTabUI[] + { + new SpeedTree9ImporterModelEditor(this), + new SpeedTree9ImporterMaterialEditor(this), + new SpeedTree9ImporterWindEditor(this) + }; + m_TabNames = new string[] { Styles.ModelTabName, Styles.MaterialsTabName, Styles.WindTabName }; + } + + m_OutputImporterData = AssetDatabase.LoadAssetAtPath(m_STImporter.assetPath); + Debug.Assert(m_OutputImporterData != null); + + base.OnEnable(); + } + + public override void OnDisable() + { + foreach (var tab in tabs) + { + tab.OnDisable(); + } + base.OnDisable(); + } + + // None of the ModelImporter sub editors support multi preview. + public override bool HasPreviewGUI() + { + return base.HasPreviewGUI() && targets.Length < 2; + } + + // Only show the imported GameObject when the Model tab is active. + public override bool showImportedObject { get { return activeTab is SpeedTree9ImporterModelEditor; } } + + public override GUIContent GetPreviewTitle() + { + var tab = activeTab as ModelImporterClipEditor; + if (tab != null) + return new GUIContent(tab.selectedClipName); + + return base.GetPreviewTitle(); + } + + internal bool upgradeMaterials + { + get + { + foreach (var importer in importers) + { + if (importer != null && importer.MaterialsShouldBeRegenerated) + return true; + } + return false; + } + } + + protected override bool OnApplyRevertGUI() + { + bool applied = base.OnApplyRevertGUI(); + + bool hasModified = HasModified(); + if (tabs == null) // Hitting apply, we lose the tabs object within base.OnApplyRevertGUI() + { + if (hasModified) + Apply(); + return applied; + } + + bool doMatsHaveDifferentShaders = (tabs[0] as SpeedTree9ImporterModelEditor).DoMaterialsHaveDifferentShader(); + + // We show the "Generate" button when we have extracted materials since the user should have 2 choices: + // - Apply the importer settings changes on top of the extracted materials (by regenerating them) + // - Only apply the importer settings changes to the embedded materials and not the extracted ones, + // since the users might want to be able to keep their own changes without the importer to erase them. + m_HasRemappedMaterials = HasRemappedMaterials(); + + if (upgradeMaterials || doMatsHaveDifferentShaders || m_HasRemappedMaterials) + { + // Force material upgrade when a custom render pipeline is active so that render pipeline-specific material + // modifications may be applied. + bool upgrade = upgradeMaterials || (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline != null); + + if (GUILayout.Button(GetGenerateButtonText(hasModified, upgrade))) + { + // Apply the changes and generate the materials before importing so that asset previews are up-to-date. + if (hasModified) + Apply(); + + if (upgrade) + { + foreach (var importer in importers) + { + importer.SetMaterialsVersionToCurrent(); + } + } + + GenerateMaterials(); + + if (hasModified || upgrade) + { + // Necessary since we remap the newly generated materials to the mesh LOD(s). + SaveChanges(); + applied = true; + } + } + } + + return applied; + } + + internal GUIContent GetGenerateButtonText(bool modified, bool upgrade) + { + if (modified || upgrade) + { + if (m_HasRemappedMaterials) + return Styles.ApplyAndGenerate; + else + return Styles.ApplyAndGenerateRemapped; + } + else + { + if (m_HasRemappedMaterials) + return Styles.Regenerate; + else + return Styles.RegenerateRemapped; + } + } + + private bool HasEmbeddedMaterials + { + get + { + bool materialsValid = m_OutputImporterData.lodMaterials.materials.Count > 0 + && m_OutputImporterData.lodMaterials.materials.TrueForAll(p => p.material != null); + + return m_OutputImporterData.hasEmbeddedMaterials && materialsValid; + } + } + + private void GenerateMaterials() + { + List paths = new List(); + List matFolders = new List(); + List importersWithEmbeddedMaterials = new List(); + + // TODO: Add support for multi-edit. + if (HasEmbeddedMaterials) + { + importersWithEmbeddedMaterials.Add(m_STImporter); + } + + foreach (var importer in importersWithEmbeddedMaterials) + { + var remappedAssets = importer.GetExternalObjectMap(); + + List materials = new List(); + + foreach (var asset in remappedAssets) + { + if (asset.Value is Material && asset.Value != null) + { + materials.Add(asset.Value); + } + } + + foreach (var material in materials) + { + string path = AssetDatabase.GetAssetPath(material); + paths.Add(path); + matFolders.Add(FileUtil.DeleteLastPathNameComponent(path)); + } + } + + bool doGenerate = true; + if (paths.Count > 0) + { + doGenerate = AssetDatabase.MakeEditable(paths.ToArray(), $"Materials will be checked out in:\n{string.Join("\n", matFolders.ToArray())}"); + } + + if (doGenerate) + { + foreach (var importer in importers) + { + importer.RegenerateMaterials(); + } + } + } + + private bool HasRemappedMaterials() + { + m_HasRemappedMaterials = true; + + if (m_OutputImporterData.materialsIdentifiers.Count == 0) + return true; + + // if the m_ExternalObjecs map has any unapplied changes, keep the state of the button as is + if (serializedObject.hasModifiedProperties) + return m_HasRemappedMaterials; + + m_HasRemappedMaterials = true; + foreach (var importer in importers) + { + var externalObjectMap = importer.GetExternalObjectMap(); + var materialArray = m_OutputImporterData.materialsIdentifiers.ToArray();// importer.SourceMaterials.ToArray(); + + int remappedMaterialCount = 0; + foreach (var entry in externalObjectMap) + { + if (entry.Key.type == typeof(Material) && Array.Exists(materialArray, x => x.name == entry.Key.name && entry.Value != null)) + ++remappedMaterialCount; + } + + m_HasRemappedMaterials = m_HasRemappedMaterials && remappedMaterialCount != 0; + if (!m_HasRemappedMaterials) + break; + } + return m_HasRemappedMaterials; + } + } + + internal abstract class BaseSpeedTree9ImporterTabUI : BaseAssetImporterTabUI + { + protected SpeedTreeImporterOutputData m_OutputImporterData; + + internal BaseSpeedTree9ImporterTabUI(AssetImporterEditor panelContainer) + : base(panelContainer) + { + } + internal override void OnEnable() + { + TryLoadOutputImporterData(); + } + + protected IEnumerable importers + { + get { return (panelContainer as SpeedTree9ImporterEditor).importers; } + } + + protected bool upgradeMaterials + { + get { return (panelContainer as SpeedTree9ImporterEditor).upgradeMaterials; } + } + + protected bool TryLoadOutputImporterData() + { + m_OutputImporterData = null; + + // Doesn't support multi-edit for now. + foreach (SpeedTree9Importer importer in importers) + { + m_OutputImporterData = AssetDatabase.LoadAssetAtPath(importer.assetPath); + break; + } + + return m_OutputImporterData != null; + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterMaterialEditor.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterMaterialEditor.cs new file mode 100644 index 0000000000..2bda72c40a --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterMaterialEditor.cs @@ -0,0 +1,314 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEditor.AssetImporters; +using UnityEngine; + +namespace UnityEditor.SpeedTree.Importer +{ + class SpeedTree9ImporterMaterialEditor : BaseSpeedTree9ImporterTabUI + { + private static class Styles + { + public static GUIContent RemapOptions = EditorGUIUtility.TrTextContent("On Demand Remap"); + public static GUIContent RemapMaterialsInProject = EditorGUIUtility.TrTextContent("Search and Remap...", "Click on this button to search and remap the materials from the project."); + public static GUIContent ExternalMaterialMappings = EditorGUIUtility.TrTextContent("Remapped Materials", "External materials to use for each embedded material."); + + public static GUIContent Materials = EditorGUIUtility.TrTextContent("Materials"); + public static GUIContent ExtractEmbeddedMaterials = EditorGUIUtility.TrTextContent("Extract Materials...", "Click on this button to extract the embedded materials."); + + public static GUIContent InternalMaterialHelp = EditorGUIUtility.TrTextContent("Materials are embedded inside the imported asset."); + public static GUIContent MaterialAssignmentsHelp = EditorGUIUtility.TrTextContent("Material assignments can be remapped below."); + + public static GUIContent ExternalMaterialSearchHelp = EditorGUIUtility.TrTextContent("Searches the user provided directory and matches the materials that share the same name and LOD with the originally imported material."); + public static GUIContent SelectMaterialFolder = EditorGUIUtility.TrTextContent("Select Materials Folder"); + } + + private SpeedTree9Importer m_STImporter; + private SerializedProperty m_ExternalObjects; + + private bool m_ShowMaterialRemapOptions; + private bool m_HasEmbeddedMaterials; + + private bool ImporterHasEmbeddedMaterials + { + get + { + bool materialsValid = m_OutputImporterData.lodMaterials.materials.Count > 0 + && m_OutputImporterData.lodMaterials.materials.TrueForAll(p => p.material != null); + + return m_OutputImporterData.hasEmbeddedMaterials && materialsValid; + } + } + + public SpeedTree9ImporterMaterialEditor(AssetImporterEditor panelContainer) + : base(panelContainer) + { } + + internal override void OnEnable() + { + base.OnEnable(); + + m_STImporter = target as SpeedTree9Importer; + + m_ExternalObjects = serializedObject.FindProperty("m_ExternalObjects"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + ShowMaterialGUI(); + + serializedObject.ApplyModifiedProperties(); + } + + private bool HasEmbeddedMaterials() + { + if (m_OutputImporterData.materialsIdentifiers.Count == 0 || !ImporterHasEmbeddedMaterials) + return false; + + // if the m_ExternalObjecs map has any unapplied changes, keep the state of the button as is + if (m_ExternalObjects.serializedObject.hasModifiedProperties) + return m_HasEmbeddedMaterials; + + m_HasEmbeddedMaterials = true; + foreach (var t in m_ExternalObjects.serializedObject.targetObjects) + { + var externalObjectMap = m_STImporter.GetExternalObjectMap(); + var materialsList = m_OutputImporterData.materialsIdentifiers.ToArray();// m_STImporter.m_Materials.ToArray(); + + int remappedMaterialCount = 0; + foreach (var entry in externalObjectMap) + { + bool isMatValid = Array.Exists(materialsList, x => x.name == entry.Key.name && entry.Value != null); + if (entry.Key.type == typeof(Material) && isMatValid) + ++remappedMaterialCount; + } + + m_HasEmbeddedMaterials = m_HasEmbeddedMaterials && remappedMaterialCount != materialsList.Length; + } + return m_HasEmbeddedMaterials; + } + + private void ShowMaterialGUI() + { + serializedObject.UpdateIfRequiredOrScript(); + + string materialHelp = string.Empty; + int arraySize = m_OutputImporterData.materialsIdentifiers.Count; + + if (arraySize > 0 && HasEmbeddedMaterials()) + { + // we're generating materials inside the prefab + materialHelp = Styles.InternalMaterialHelp.text; + } + + if (targets.Length == 1 && arraySize > 0) + { + materialHelp += " " + Styles.MaterialAssignmentsHelp.text; + } + + if (ExtractMaterialsGUI()) + { + // Necessary to avoid the error "BeginLayoutGroup must be called first". + GUIUtility.ExitGUI(); + return; + } + + if (!string.IsNullOrEmpty(materialHelp)) + { + EditorGUILayout.HelpBox(materialHelp, MessageType.Info); + } + + // The material remap list + if (targets.Length == 1 && arraySize > 0) + { + GUILayout.Label(Styles.ExternalMaterialMappings, EditorStyles.boldLabel); + + if (MaterialRemapOptions()) + return; + + // The list of material names is immutable, whereas the map of external objects can change based on user actions. + // For each material name, map the external object associated with it. + // The complexity comes from the fact that we may not have an external object in the map, so we can't make a property out of it + for (int materialIdx = 0; materialIdx < arraySize; ++materialIdx) + { + string name = m_OutputImporterData.materialsIdentifiers[materialIdx].name; + string type = m_OutputImporterData.materialsIdentifiers[materialIdx].type; + + SerializedProperty materialProp = null; + Material material = null; + int propertyIdx = 0; + + for (int externalObjectIdx = 0, count = m_ExternalObjects.arraySize; externalObjectIdx < count; ++externalObjectIdx) + { + SerializedProperty pair = m_ExternalObjects.GetArrayElementAtIndex(externalObjectIdx); + string externalName = pair.FindPropertyRelative("first.name").stringValue; + string externalType = pair.FindPropertyRelative("first.type").stringValue; + + // Cannot do a strict comparison, since externalType is set to "UnityEngine:Material" (C++) + // and type "UnityEngine.Material" (C#). + bool typeMatching = externalType.Contains("Material") && type.Contains("Material"); + + if (externalName == name && typeMatching) + { + materialProp = pair.FindPropertyRelative("second"); + material = materialProp != null ? materialProp.objectReferenceValue as Material : null; + + // If 'material' is null, it's likely because it was deleted. So we assign null to 'materialProp' + // to avoid the 'missing material' reference error in the UI. + materialProp = (material != null) ? pair.FindPropertyRelative("second") : null; + propertyIdx = externalObjectIdx; + break; + } + } + + GUIContent nameLabel = EditorGUIUtility.TextContent(name); + nameLabel.tooltip = name; + if (materialProp != null) + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.ObjectField(materialProp, typeof(Material), nameLabel); + if (EditorGUI.EndChangeCheck()) + { + if (materialProp.objectReferenceValue == null) + { + m_ExternalObjects.DeleteArrayElementAtIndex(propertyIdx); + } + } + } + else + { + EditorGUI.BeginChangeCheck(); + material = EditorGUILayout.ObjectField(nameLabel, material, typeof(Material), false) as Material; + if (EditorGUI.EndChangeCheck()) + { + if (material != null) + { + int newIndex = m_ExternalObjects.arraySize++; + SerializedProperty pair = m_ExternalObjects.GetArrayElementAtIndex(newIndex); + pair.FindPropertyRelative("first.name").stringValue = name; + pair.FindPropertyRelative("first.type").stringValue = type; + pair.FindPropertyRelative("second").objectReferenceValue = material; + } + } + } + } + } + } + + private bool ExtractMaterialsGUI() + { + bool buttonPressed = false; + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.PrefixLabel(Styles.Materials); + using (new EditorGUI.DisabledScope(!HasEmbeddedMaterials())) + { + buttonPressed = GUILayout.Button(Styles.ExtractEmbeddedMaterials); + + if (buttonPressed) + { + // use the first target for selecting the destination folder, but apply that path for all targets + string destinationPath = m_STImporter.assetPath; + destinationPath = EditorUtility.SaveFolderPanel(Styles.SelectMaterialFolder.text, + FileUtil.DeleteLastPathNameComponent(destinationPath), ""); + if (string.IsNullOrEmpty(destinationPath)) + { + // Cancel the extraction if the user did not select a folder. + EditorGUILayout.EndHorizontal(); + return buttonPressed; + } + destinationPath = FileUtil.GetProjectRelativePath(destinationPath); + + try + { + // batch the extraction of the materials + AssetDatabase.StartAssetEditing(); + + PrefabUtility.ExtractMaterialsFromAsset(targets, destinationPath); + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + } + } + EditorGUILayout.EndHorizontal(); + + // AssetDatabase.StopAssetEditing() invokes OnEnable(), which invalidates all the serialized properties, so we must return. + return buttonPressed; + } + + private bool MaterialRemapOptions() + { + bool buttonPressed = false; + + m_ShowMaterialRemapOptions = EditorGUILayout.Foldout(m_ShowMaterialRemapOptions, Styles.RemapOptions); + if (m_ShowMaterialRemapOptions) + { + EditorGUI.indentLevel++; + + EditorGUILayout.HelpBox(Styles.ExternalMaterialSearchHelp.text, MessageType.Info); + + EditorGUI.indentLevel--; + + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.FlexibleSpace(); + using (new EditorGUI.DisabledScope(assetTarget == null)) + { + buttonPressed = GUILayout.Button(Styles.RemapMaterialsInProject); + if (buttonPressed) + { + bool bStartedAssetEditing = false; + try + { + foreach (var t in targets) + { + string folderToSearch = m_STImporter.GetMaterialFolderPath(); + folderToSearch = EditorUtility.OpenFolderPanel(Styles.SelectMaterialFolder.text, folderToSearch, ""); + + bool bUserSelectedAFolder = folderToSearch != ""; // folderToSearch is empty if the user cancels the window + if (bUserSelectedAFolder) + { + string projectRelativePath = FileUtil.GetProjectRelativePath(folderToSearch); + bool bRelativePathIsNotEmpty = projectRelativePath != ""; + if (bRelativePathIsNotEmpty) + { + AssetDatabase.StartAssetEditing(); + bStartedAssetEditing = true; + m_STImporter.SearchAndRemapMaterials(projectRelativePath); + + AssetDatabase.WriteImportSettingsIfDirty(m_STImporter.assetPath); + AssetDatabase.ImportAsset(m_STImporter.assetPath, ImportAssetOptions.ForceUpdate); + } + else + { + Debug.LogWarning("Selected folder is outside of the project's folder hierarchy, please provide a folder from the project.\n"); + } + } + } + } + finally + { + if (bStartedAssetEditing) + { + AssetDatabase.StopAssetEditing(); + } + } + } + } + } + EditorGUILayout.Space(); + } + + return buttonPressed; + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterModelEditor.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterModelEditor.cs new file mode 100644 index 0000000000..549b0b0313 --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterModelEditor.cs @@ -0,0 +1,755 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEditor.AssetImporters; +using System; +using UnityEditor.AnimatedValues; +using UnityEngine; +using System.IO; +using System.Collections.Generic; +using static UnityEditor.SpeedTree.Importer.SpeedTree9Importer; + +using Styles = UnityEditor.SpeedTree.Importer.SpeedTreeImporterCommonEditor.Styles; + +namespace UnityEditor.SpeedTree.Importer +{ + class SpeedTree9ImporterModelEditor : BaseSpeedTree9ImporterTabUI + { + // Mesh properties + private SerializedProperty m_UnitConversion; + private SerializedProperty m_ScaleFactor; + + // Material properties + private SerializedProperty m_MainColor; + private SerializedProperty m_EnableHueVariation; + private SerializedProperty m_HueVariation; + private SerializedProperty m_AlphaClipThreshold; + private SerializedProperty m_TransmissionScale; + private SerializedProperty m_EnableBumpMapping; + private SerializedProperty m_EnableSubsurfaceScattering; + private SerializedProperty m_DiffusionProfileAssetID; + private SerializedProperty m_DiffusionProfileID; + + // Lighting properties + private SerializedProperty m_EnableShadowCasting; + private SerializedProperty m_EnableShadowReceiving; + private SerializedProperty m_EnableLightProbes; + private SerializedProperty m_ReflectionProbeEnumValue; + + // Additional Settings properties + private SerializedProperty m_MotionVectorModeEnumValue; + private SerializedProperty m_GenerateColliders; + private SerializedProperty m_GenerateRigidbody; + + // LOD properties + private SerializedProperty m_EnableSmoothLOD; + private SerializedProperty m_AnimateCrossFading; + private SerializedProperty m_BillboardTransitionCrossFadeWidth; + private SerializedProperty m_FadeOutWidth; + private SerializedProperty m_PerLODSettings; + + // LODGroup GUI + private int m_SelectedLODSlider = -1; + private int m_SelectedLODRange = 0; + private SavedBool[] m_LODGroupFoldoutHeaderValues = null; + private Texture2D[] m_LODColorTextures; + + private AnimBool m_ShowSmoothLODOptions = new AnimBool(); + private AnimBool m_ShowCrossFadeWidthOptions = new AnimBool(); + + private SpeedTree9Importer m_StEditor; + + public SpeedTree9ImporterModelEditor(AssetImporterEditor panelContainer) + : base(panelContainer) + { } + + internal override void OnEnable() + { + base.OnEnable(); + + m_StEditor = target as SpeedTree9Importer; + + // Mesh properties + { + MeshSettings meshSettings = m_StEditor.m_MeshSettings; + string meshSettingsStr = nameof(m_StEditor.m_MeshSettings); + + m_UnitConversion = FindPropertyFromName(meshSettingsStr, nameof(meshSettings.unitConversion)); + m_ScaleFactor = FindPropertyFromName(meshSettingsStr, nameof(meshSettings.scaleFactor)); + } + + // Material properties + { + MaterialSettings matSettings = m_StEditor.m_MaterialSettings; + string matSettingsStr = nameof(m_StEditor.m_MaterialSettings); + + m_MainColor = FindPropertyFromName(matSettingsStr, nameof(matSettings.mainColor)); + m_EnableHueVariation = FindPropertyFromName(matSettingsStr, nameof(matSettings.enableHueVariation)); + m_HueVariation = FindPropertyFromName(matSettingsStr, nameof(matSettings.hueVariation)); + m_AlphaClipThreshold = FindPropertyFromName(matSettingsStr, nameof(matSettings.alphaClipThreshold)); + m_TransmissionScale = FindPropertyFromName(matSettingsStr, nameof(matSettings.transmissionScale)); + m_EnableBumpMapping = FindPropertyFromName(matSettingsStr, nameof(matSettings.enableBumpMapping)); + m_EnableSubsurfaceScattering = FindPropertyFromName(matSettingsStr, nameof(matSettings.enableSubsurfaceScattering)); + + m_DiffusionProfileAssetID = FindPropertyFromName(matSettingsStr, nameof(matSettings.diffusionProfileAssetID)); + m_DiffusionProfileID = FindPropertyFromName(matSettingsStr, nameof(matSettings.diffusionProfileID)); + } + + // Lighting properties + { + LightingSettings lightSettings = m_StEditor.m_LightingSettings; + string lightSettingsStr = nameof(m_StEditor.m_LightingSettings); + + m_EnableShadowCasting = FindPropertyFromName(lightSettingsStr, nameof(lightSettings.enableShadowCasting)); + m_EnableShadowReceiving = FindPropertyFromName(lightSettingsStr, nameof(lightSettings.enableShadowReceiving)); + m_EnableLightProbes = FindPropertyFromName(lightSettingsStr, nameof(lightSettings.enableLightProbes)); + m_ReflectionProbeEnumValue = FindPropertyFromName(lightSettingsStr, nameof(lightSettings.reflectionProbeEnumValue)); + } + + // Additional Settings properties + { + AdditionalSettings addSettings = m_StEditor.m_AdditionalSettings; + string addSettingsStr = nameof(m_StEditor.m_AdditionalSettings); + + m_MotionVectorModeEnumValue = FindPropertyFromName(addSettingsStr, nameof(addSettings.motionVectorModeEnumValue)); + m_GenerateColliders = FindPropertyFromName(addSettingsStr, nameof(addSettings.generateColliders)); + m_GenerateRigidbody = FindPropertyFromName(addSettingsStr, nameof(addSettings.generateRigidbody)); + } + + // LOD properties + { + LODSettings lodSettings = m_StEditor.m_LODSettings; + string lodSettingsStr = nameof(m_StEditor.m_LODSettings); + + m_EnableSmoothLOD = FindPropertyFromName(lodSettingsStr, nameof(lodSettings.enableSmoothLODTransition)); + m_AnimateCrossFading = FindPropertyFromName(lodSettingsStr, nameof(lodSettings.animateCrossFading)); + m_BillboardTransitionCrossFadeWidth = FindPropertyFromName(lodSettingsStr, nameof(lodSettings.billboardTransitionCrossFadeWidth)); + m_FadeOutWidth = FindPropertyFromName(lodSettingsStr, nameof(lodSettings.fadeOutWidth)); + m_PerLODSettings = serializedObject.FindProperty(nameof(m_StEditor.m_PerLODSettings)); + } + + // Other + { + m_ShowSmoothLODOptions.value = m_EnableSmoothLOD.hasMultipleDifferentValues || m_EnableSmoothLOD.boolValue; + m_ShowSmoothLODOptions.valueChanged.AddListener(Repaint); + + m_ShowCrossFadeWidthOptions.value = m_AnimateCrossFading.hasMultipleDifferentValues || !m_AnimateCrossFading.boolValue; + m_ShowCrossFadeWidthOptions.valueChanged.AddListener(Repaint); + } + + ResetFoldoutLists(); + } + + internal override void OnDisable() + { + base.OnDisable(); + + m_ShowSmoothLODOptions.valueChanged.RemoveListener(Repaint); + m_ShowCrossFadeWidthOptions.valueChanged.RemoveListener(Repaint); + } + + void TriggerCallbacks() + { + var allMethods = AttributeHelper.GetMethodsWithAttribute().methodsWithAttributes; + foreach (var method in allMethods) + { + var callback = Delegate.CreateDelegate(typeof(OnCustomEditorSettings), method.info) as OnCustomEditorSettings; + callback?.Invoke(ref m_DiffusionProfileAssetID, ref m_DiffusionProfileID); + } + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + // Mesh properties + SpeedTreeImporterCommonEditor.ShowMeshGUI(ref m_UnitConversion, ref m_ScaleFactor); + + EditorGUILayout.Space(); + + // Material Properties + SpeedTreeImporterCommonEditor.ShowMaterialGUI( + ref m_MainColor, + ref m_EnableHueVariation, + ref m_HueVariation, + ref m_AlphaClipThreshold, + ref m_EnableBumpMapping, + ref m_EnableSubsurfaceScattering, + renderHueVariationDropdown: m_EnableHueVariation.boolValue, + renderAlphaTestRef: m_OutputImporterData.hasAlphaClipThreshold + ); + + if (m_OutputImporterData.hasTransmissionScale) + { + EditorGUILayout.PropertyField(m_TransmissionScale, Styles.TransmissionScale); + } + + // Update the Diffuse Profile is necessary + TriggerCallbacks(); + + EditorGUILayout.Space(); + + // Lighting Properties + SpeedTreeImporterCommonEditor.ShowLightingGUI( + ref m_EnableShadowCasting, + ref m_EnableShadowReceiving, + ref m_EnableLightProbes, + ref m_ReflectionProbeEnumValue); + + EditorGUILayout.Space(); + + // Additional Settings + SpeedTreeImporterCommonEditor.ShowAdditionalSettingsGUI( + ref m_MotionVectorModeEnumValue, + ref m_GenerateColliders, + ref m_GenerateRigidbody); + + EditorGUILayout.Space(); + + // LOD properties + SpeedTreeImporterCommonEditor.ShowLODGUI( + ref m_EnableSmoothLOD, + ref m_AnimateCrossFading, + ref m_BillboardTransitionCrossFadeWidth, + ref m_FadeOutWidth, + ref m_ShowSmoothLODOptions, + ref m_ShowCrossFadeWidthOptions); + + EditorGUILayout.Space(); + + ShowLODGUI(); + + ShowMaterialWarnings(); + + serializedObject.ApplyModifiedProperties(); + } + + private void ShowMaterialWarnings() + { + EditorGUILayout.Space(); + + bool materialsNeedToBeUpgraded = upgradeMaterials; + + if (materialsNeedToBeUpgraded) + { + EditorGUILayout.HelpBox( + String.Format("SpeedTree materials need to be upgraded. " + + "Please back them up (if modified manually) then hit the \"{0}\" button below.", Styles.ApplyAndGenerate.text) + , MessageType.Warning + ); + } + else if (!materialsNeedToBeUpgraded && DoMaterialsHaveDifferentShader()) + { + EditorGUILayout.HelpBox( + String.Format("There is a different SpeedTree shader provided by the current render pipeline " + + "which probably is more suitable for rendering. Hit the \"{0}\" button to regenerate the materials." + , (panelContainer as SpeedTree9ImporterEditor).GetGenerateButtonText(HasModified() + , materialsNeedToBeUpgraded).text + ) + , MessageType.Warning + ); + } + } + + private SerializedProperty FindPropertyFromName(string parentProperty, string childProperty) + { + const string dotStr = "."; + + string finalName = String.Concat(parentProperty, dotStr, childProperty); + + return serializedObject.FindProperty(finalName); + } + + internal bool DoMaterialsHaveDifferentShader() + { + if (assetTargets is null || assetTargets.Length == 0) + { + return false; + } + + GameObject[] prefabs = new GameObject[assetTargets.Length]; + for (int i = 0; i < assetTargets.Length; ++i) + { + prefabs[i] = assetTargets[i] as GameObject; + } + + List importerArray = new List(); + foreach (SpeedTree9Importer importer in importers) + { + importerArray.Add(importer); + } + + string defaultShaderName = String.Empty; + if (TryGetShaderForCurrentRenderPipeline(out var shader)) + { + defaultShaderName = shader.name; + } + else + { + Debug.LogWarning("SpeedTree9 shader is invalid, cannot create Materials for this SpeedTree asset."); + } + + // In tests assetTargets can become null + for (int i = 0; i < Math.Min(importerArray.Count, prefabs?.Length ?? 0); ++i) + { + foreach (var mr in prefabs[i].transform.GetComponentsInChildren()) + { + foreach (var mat in mr.sharedMaterials) + { + if (mat?.shader.name != defaultShaderName) + return true; + } + } + } + + return false; + } + + // TODO: Abstract the following code, so it can be shared between ST8 and ST9. + + private void ShowLODGUI() + { + // LOD slider + Customizations + if (HasSameLODConfig()) + { + var area = GUILayoutUtility.GetRect(0, LODGroupGUI.kSliderBarHeight, GUILayout.ExpandWidth(true)); + var lods = GetLODInfoArray(area); + bool bDrawLODCustomizationGUI = m_SelectedLODRange != -1 && lods.Count > 0; + + EditorGUILayout.Space(); + + DrawLODLevelSlider(area, lods); + + if (bDrawLODCustomizationGUI) + { + GUILayout.Space(5); + DrawLODGroupFoldouts(lods); + } + } + + // Mixed Value LOD Slider + else + { + if (CanUnifyLODConfig()) + { + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + Rect buttonRect = GUILayoutUtility.GetRect(Styles.ResetLOD, EditorStyles.miniButton); + if (GUI.Button(buttonRect, Styles.ResetLOD, EditorStyles.miniButton)) + { + var dropDownMenu = new GenericMenu(); + foreach (SpeedTree9Importer importer in importers) + { + float[] heights = importer.GetPerLODSettingsHeights(); + string[] heightsFormated = new string[heights.Length]; + for (int i = 0; i < heights.Length; ++i) + { + heightsFormated[i] = string.Format("{0:0}%", heights[i] * 100); + } + + var menuText = String.Format("{0}: {1}", + Path.GetFileNameWithoutExtension(importer.assetPath), + String.Join(" | ", heightsFormated)); + dropDownMenu.AddItem(new GUIContent(menuText), false, OnResetLODMenuClick, importer); + } + dropDownMenu.DropDown(buttonRect); + } + EditorGUILayout.EndHorizontal(); + } + else + { + EditorGUILayout.HelpBox(Styles.MultiSelectionLODNotSupported.text, MessageType.Info); + } + } + + EditorGUILayout.Space(); + } + + private readonly int m_LODSliderId = "LODSliderIDHash".GetHashCode(); + + private void DrawLODLevelSlider(Rect sliderPosition, List lods) + { + int sliderId = GUIUtility.GetControlID(m_LODSliderId, FocusType.Passive); + Event evt = Event.current; + + switch (evt.GetTypeForControl(sliderId)) + { + case EventType.Repaint: + { + LODGroupGUI.DrawLODSlider(sliderPosition, lods, m_SelectedLODRange); + break; + } + case EventType.MouseDown: + { + // Slightly grow position on the x because edge buttons overflow by 5 pixels + var barPosition = sliderPosition; + barPosition.x -= 5; + barPosition.width += 10; + + if (barPosition.Contains(evt.mousePosition)) + { + evt.Use(); + GUIUtility.hotControl = sliderId; + + // Check for button click + var clickedButton = false; + + // case:464019 have to re-sort the LOD array for these buttons to get the overlaps in the right order... + List lodsLeft = new List(); + List lodsRight = new List(); + + foreach (LODGroupGUI.LODInfo lodInfo in lods) + { + if (lodInfo.ScreenPercent > 0.5f) + { + lodsLeft.Add(lodInfo); + } + else + { + lodsRight.Add(lodInfo); + } + } + + // Descending order. + lodsLeft.Sort(new Comparison((i1, i2) => i2.LODLevel.CompareTo(i1.LODLevel))); + + // Ascending order. + lodsRight.Sort(new Comparison((i1, i2) => i1.LODLevel.CompareTo(i2.LODLevel))); + + var lodButtonOrder = new List(); + lodButtonOrder.AddRange(lodsLeft); + lodButtonOrder.AddRange(lodsRight); + + foreach (var lod in lodButtonOrder) + { + if (lod.m_ButtonPosition.Contains(evt.mousePosition)) + { + m_SelectedLODSlider = lod.LODLevel; + m_SelectedLODRange = lod.LODLevel; + clickedButton = true; + break; + } + } + + if (!clickedButton) + { + // Check for range click + foreach (var lod in lodButtonOrder) + { + if (lod.m_RangePosition.Contains(evt.mousePosition)) + { + m_SelectedLODSlider = -1; + m_SelectedLODRange = lod.LODLevel; + ExpandSelectedHeaderAndCloseRemaining(m_SelectedLODRange); + break; + } + } + } + } + break; + } + case EventType.MouseUp: + { + if (GUIUtility.hotControl == sliderId) + { + GUIUtility.hotControl = 0; + evt.Use(); + } + break; + } + case EventType.MouseDrag: + { + if (GUIUtility.hotControl == sliderId && m_SelectedLODSlider >= 0 && lods[m_SelectedLODSlider] != null) + { + evt.Use(); + + var cameraPercent = LODGroupGUI.GetCameraPercent(evt.mousePosition, sliderPosition); + // Bias by 0.1% so that there is no skipping when sliding + LODGroupGUI.SetSelectedLODLevelPercentage(cameraPercent - 0.001f, m_SelectedLODSlider, lods); + m_PerLODSettings.GetArrayElementAtIndex(m_SelectedLODSlider).FindPropertyRelative("height").floatValue = lods[m_SelectedLODSlider].RawScreenPercent; + } + break; + } + } + } + + private void DrawLODGroupFoldouts(List lods) + { + // check camera and bail if null + Camera camera = null; + if (SceneView.lastActiveSceneView && SceneView.lastActiveSceneView.camera) + camera = SceneView.lastActiveSceneView.camera; + if (camera == null) + return; + + // draw lod foldouts + for (int i = 0; i < m_PerLODSettings.arraySize; i++) + { + DrawLODGroupFoldout(camera, i, ref m_LODGroupFoldoutHeaderValues[i], lods); + } + } + + private string GetLODSubmeshAndTriCountLabel(int numLODs, int lodGroupIndex, SpeedTree9Importer im, LODGroup lodGroup) + { + LOD[] lods = lodGroup.GetLODs(); + + if(lods.Length != numLODs) + { + Debug.LogWarningFormat("Number of LODs mismatch between serialized object & LODGroup: {0}\nPlease re-import the asset and kindly report a bug if this warning keeps coming back.", im.assetPath); + numLODs = lods.Length; + } + + int[][] primitiveCounts = new int[numLODs][]; + int[] submeshCounts = new int[numLODs]; + for (int i = 0; i < lods.Length; i++) + { + Renderer[] renderers = lods[i].renderers; + primitiveCounts[i] = new int[renderers.Length]; + + for (int j = 0; j < renderers.Length; j++) + { + bool hasMismatchingSubMeshTopologyTypes = LODGroupEditor.CheckIfMeshesHaveMatchingTopologyTypes(renderers); + + Mesh rendererMesh = LODGroupEditor.GetMeshFromRendererIfAvailable(renderers[j]); + if (rendererMesh == null) + continue; + + submeshCounts[i] += rendererMesh.subMeshCount; + + if (hasMismatchingSubMeshTopologyTypes) + { + primitiveCounts[i][j] = rendererMesh.vertexCount; + } + else + { + for (int subMeshIndex = 0; subMeshIndex < rendererMesh.subMeshCount; subMeshIndex++) + { + primitiveCounts[i][j] += (int)rendererMesh.GetIndexCount(subMeshIndex) / 3; + } + } + } + } + + int totalTriCount = 0; + if (primitiveCounts.Length > 0 && primitiveCounts[lodGroupIndex] != null) + { + Array.ForEach(primitiveCounts[lodGroupIndex], delegate (int i) { totalTriCount += i; }); + } + + int sumPrimitiveCounts = 0; + Array.ForEach(primitiveCounts[0], delegate (int i) { sumPrimitiveCounts += i; }); + + int lod0TriCount = sumPrimitiveCounts; + + var triCountChange = lod0TriCount != 0 ? (float)totalTriCount / lod0TriCount * 100 : 0; + string triangleChangeLabel = lodGroupIndex > 0 && lod0TriCount != 0 ? $"({triCountChange.ToString("f2")}% LOD0)" : ""; + + bool wideInspector = Screen.width >= 480; + triangleChangeLabel = wideInspector ? triangleChangeLabel : ""; + string submeshCountLabel = wideInspector ? $"- {submeshCounts[lodGroupIndex]} Sub Mesh(es)" : ""; + + return $"{totalTriCount} {LODGroupGUI.GUIStyles.m_TriangleCountLabel.text} {triangleChangeLabel} {submeshCountLabel}"; + } + + private Color GetLODGroupColor(int lodIndex) + { + return LODGroupGUI.kLODColors[lodIndex % LODGroupGUI.kLODColors.Length]; + } + + private void DrawLODGroupFoldout(Camera camera, int lodGroupIndex, ref SavedBool foldoutState, List lodInfoList) + { + GameObject[] ObjectArrayToGameObjectArray(UnityEngine.Object[] objects) + { + if (objects == null) + return null; + + GameObject[] gameObjects = new GameObject[objects.Length]; + + for (int i = 0; i < objects.Length; ++i) + { + gameObjects[i] = objects[i] as GameObject; + } + + return gameObjects; + } + + List importersList = new List(importers); + + GameObject[] prefabs = ObjectArrayToGameObjectArray(assetTargets); // In tests assetTargets can become null + SpeedTree9Importer[] importerArray = importersList.ToArray(); + + int numSelectedAssets = Math.Min(importerArray.Length, prefabs?.Length ?? 0); + bool isDrawingSelectedLODGroup = m_SelectedLODRange == lodGroupIndex; + + // even though multiple assets may be selected, this code path + // ensures the numLODs match for all the selected assets (see HasSameLODConfig() calls) + int numLODs = m_PerLODSettings.arraySize; + bool hasBillboard = m_OutputImporterData.hasBillboard; + + string LODFoldoutHeaderLabel = (hasBillboard && lodGroupIndex == m_PerLODSettings.arraySize - 1) + ? "Billboard" + : $"LOD {lodGroupIndex}"; + + // primitive and submesh counts are displayed only when a single asset is selected + string LODFoldoutHeaderGroupAdditionalText = numSelectedAssets == 1 + ? GetLODSubmeshAndTriCountLabel(numLODs, lodGroupIndex, importerArray[0], prefabs[0].GetComponentInChildren()) + : ""; + + // ------------------------------------------------------------------------------------------------------------------------------ + + if (isDrawingSelectedLODGroup) + LODGroupGUI.DrawRoundedBoxAroundLODDFoldout(lodGroupIndex, m_SelectedLODRange); + else + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + foldoutState.value = LODGroupGUI.FoldoutHeaderGroupInternal( + GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebarFlat) + , foldoutState.value + , LODFoldoutHeaderLabel + , m_LODColorTextures[lodGroupIndex] + , GetLODGroupColor(lodGroupIndex) * 0.6f // 0.5f magic number is copied from LODGroupsGUI.cs + , LODFoldoutHeaderGroupAdditionalText + ); + + if (foldoutState.value) // expanded LOD-specific options panel + { + DrawLODSettingCustomizationGUI(lodInfoList, lodGroupIndex); + } + + if (isDrawingSelectedLODGroup) + GUILayoutUtility.EndLayoutGroup(); + else + EditorGUILayout.EndVertical(); + } + + private void DrawLODSettingCustomizationGUI(List lods, int lodIndex) + { + bool isBillboard = (lodIndex == lods.Count - 1) && m_OutputImporterData.hasBillboard; + + SerializedProperty selectedLODProp = m_PerLODSettings.GetArrayElementAtIndex(lodIndex); + SerializedProperty lodSettingOverride = selectedLODProp.FindPropertyRelative("enableSettingOverride"); + + // We don't want to clutter the GUI with same options but for billboards, instead + // we treat the Billboard LOD level as always 'overrideSettings' and display the + // billboard options below without the 'enableSettingOverride' warning text. + if (isBillboard) + { + EditorGUILayout.LabelField("Billboard Options", EditorStyles.boldLabel); + EditorGUILayout.HelpBox(Styles.BillboardSettingsHelp.text, MessageType.Info); + } + else + { + // Toggle + GUIContent customizationLabel = EditorGUIUtility.TrTextContent(String.Format("Customize {0} options", lods[lodIndex].LODName), "To override options for a certain LOD, check this box and select the LOD from the LOD slider above"); + EditorGUILayout.PropertyField(lodSettingOverride, customizationLabel); + + // Warning + if (lodSettingOverride.boolValue) + { + EditorGUILayout.HelpBox(Styles.EnableLodCustomizationsWarn.text, MessageType.Warning); + } + } + EditorGUILayout.Space(); + + // settings + using (new EditorGUI.DisabledScope(!lodSettingOverride.boolValue)) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Lighting", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("castShadows"), Styles.CastShadows); + + using (new EditorGUI.DisabledScope(!UnityEngine.Rendering.SupportedRenderingFeatures.active.receiveShadows)) + { + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("receiveShadows"), Styles.ReceiveShadows); + } + + var useLightProbes = selectedLODProp.FindPropertyRelative("useLightProbes"); + EditorGUILayout.PropertyField(useLightProbes, Styles.UseLightProbes); + if (!useLightProbes.hasMultipleDifferentValues && useLightProbes.boolValue && isBillboard) + EditorGUILayout.HelpBox("Enabling Light Probe for billboards breaks batched rendering and may cause performance problem.", MessageType.Warning); + + // TODO: reflection probe support when PBS is implemented + //EditorGUILayout.PropertyField(SelectedLODProp.FindPropertyRelative("useReflectionProbes"), Styles.UseReflectionProbes); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Material", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableHue"), Styles.EnableColorVariation); + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableBump"), Styles.EnableBump); + + EditorGUILayout.PropertyField(selectedLODProp.FindPropertyRelative("enableSubsurface"), Styles.EnableSubsurface); + } + } + + private void OnResetLODMenuClick(object userData) + { + //var toHeights = (userData as SpeedTreeImporter).LODHeights; + for (int i = 0; i < m_StEditor.m_PerLODSettings.Count; ++i) + { + var toHeight = m_StEditor.m_PerLODSettings[i].height; + m_PerLODSettings.GetArrayElementAtIndex(i).FindPropertyRelative("height").floatValue = toHeight; + } + } + + private bool HasSameLODConfig() + { + if (m_PerLODSettings.FindPropertyRelative("Array.size").hasMultipleDifferentValues) + return false; + + for (int i = 0; i < m_PerLODSettings.arraySize; ++i) + { + if (m_PerLODSettings.GetArrayElementAtIndex(i).FindPropertyRelative("height").hasMultipleDifferentValues) + return false; + } + return true; + } + + private void ExpandSelectedHeaderAndCloseRemaining(int index) + { + // need this to safeguard against drag & drop on Culled section + // as that sets the LOD index to 8 which is outside of the total + // allowed LOD range + if (index >= m_PerLODSettings.arraySize) + return; + + Array.ForEach(m_LODGroupFoldoutHeaderValues, el => el.value = false); + m_LODGroupFoldoutHeaderValues[index].value = true; + } + + private List GetLODInfoArray(Rect area) + { + int lodCount = m_PerLODSettings.arraySize; + return LODGroupGUI.CreateLODInfos( + lodCount, area, + i => i == lodCount - 1 && m_OutputImporterData.hasBillboard ? "Billboard" : String.Format("LOD {0}", i), + i => m_PerLODSettings.GetArrayElementAtIndex(i).FindPropertyRelative("height").floatValue); + } + + private bool CanUnifyLODConfig() + { + // TODO: Add multi-selection support for LOD UI. + return false; + } + + void InitAndSetFoldoutLabelTextures() + { + m_LODColorTextures = new Texture2D[m_PerLODSettings.arraySize]; + for (int i = 0; i < m_LODColorTextures.Length; i++) + { + m_LODColorTextures[i] = new Texture2D(1, 1); + m_LODColorTextures[i].SetPixel(0, 0, GetLODGroupColor(i)); + } + } + + void ResetFoldoutLists() + { + int lodArraySize = Mathf.Min(m_PerLODSettings.arraySize, LODGroupGUI.kLODColors.Length); + m_LODGroupFoldoutHeaderValues = new SavedBool[lodArraySize]; + for (int i = 0; i < lodArraySize; i++) + { + m_LODGroupFoldoutHeaderValues[i] = new SavedBool($"{target.GetType()}.lodFoldout{i}", false); + } + InitAndSetFoldoutLabelTextures(); + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterWindEditor.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterWindEditor.cs new file mode 100644 index 0000000000..8f6e6fd9ab --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterWindEditor.cs @@ -0,0 +1,70 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEditor.AssetImporters; +using System; +using UnityEngine; + +namespace UnityEditor.SpeedTree.Importer +{ + class SpeedTree9ImporterWindEditor : BaseSpeedTree9ImporterTabUI + { + private class Styles + { + public static GUIContent EnableWind = EditorGUIUtility.TrTextContent("Enable Wind"); + public static GUIContent StrengthResponse = EditorGUIUtility.TrTextContent("Strength Response", "The strength response of the wind."); + public static GUIContent DirectionResponse = EditorGUIUtility.TrTextContent("Direction Response", "The direction response of the wind."); + public static GUIContent WindRandomness = EditorGUIUtility.TrTextContent("Randomness", "Amount of world position based noise applied to each tree."); + } + + private SerializedProperty m_EnableWind; + private SerializedProperty m_StrengthResponse; + private SerializedProperty m_DirectionResponse; + private SerializedProperty m_WindRandomness; + + private SpeedTree9Importer m_StEditor; + + public SpeedTree9ImporterWindEditor(AssetImporterEditor panelContainer) + : base(panelContainer) + { } + + internal override void OnEnable() + { + m_StEditor = target as SpeedTree9Importer; + + WindSettings windSettings = m_StEditor.m_WindSettings; + string windSettingsStr = nameof(m_StEditor.m_WindSettings); + + m_EnableWind = FindPropertyFromName(windSettingsStr, nameof(windSettings.enableWind)); + m_StrengthResponse = FindPropertyFromName(windSettingsStr, nameof(windSettings.strenghResponse)); + m_DirectionResponse = FindPropertyFromName(windSettingsStr, nameof(windSettings.directionResponse)); + m_WindRandomness = FindPropertyFromName(windSettingsStr, nameof(windSettings.randomness)); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.PropertyField(m_EnableWind, Styles.EnableWind); + + using (new EditorGUI.DisabledScope(!m_EnableWind.boolValue)) + { + EditorGUILayout.PropertyField(m_StrengthResponse, Styles.StrengthResponse); + EditorGUILayout.PropertyField(m_DirectionResponse, Styles.DirectionResponse); + EditorGUILayout.PropertyField(m_WindRandomness, Styles.WindRandomness); + } + + serializedObject.ApplyModifiedProperties(); + } + + private SerializedProperty FindPropertyFromName(string parentProperty, string childProperty) + { + const string dotStr = "."; + + string finalName = String.Concat(parentProperty, dotStr, childProperty); + + return serializedObject.FindProperty(finalName); + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Reader.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Reader.cs new file mode 100644 index 0000000000..99e3a64086 --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9Reader.cs @@ -0,0 +1,634 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.IO; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace UnityEditor.SpeedTree.Importer +{ + #region Data Classes + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Vec2 + { + private float x, y; + + public float X + { + get { return x; } + set { x = value; } + } + + public float Y + { + get { return y; } + set { y = value; } + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Vec3 + { + private float x, y, z; + + public float X + { + get { return x; } + set { x = value; } + } + + public float Y + { + get { return y; } + set { y = value; } + } + + public float Z + { + get { return z; } + set { z = value; } + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Vec4 + { + private float x, y, z, w; + + public float X + { + get { return x; } + set { x = value; } + } + + public float Y + { + get { return y; } + set { y = value; } + } + + public float Z + { + get { return z; } + set { z = value; } + } + + public float W + { + get { return w; } + set { w = value; } + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Bounds3 + { + public Vec3 Min { get; private set; } + public Vec3 Max { get; private set; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Vertex + { + public Vec3 Anchor { get; private set; } + public Vec3 Offset { get; private set; } + public Vec3 LodOffset { get; private set; } + public Vec3 Normal { get; private set; } + public Vec3 Tangent { get; private set; } + public Vec3 Binormal { get; private set; } + public Vec2 TexCoord { get; private set; } + public Vec2 LightmapTexCoord { get; private set; } + public Vec3 Color { get; private set; } + public float AmbientOcclusion { get; private set; } + public float BlendWeight { get; private set; } + public Vec3 BranchWind1 { get; private set; } // pos, dir, weight + public Vec3 BranchWind2 { get; private set; } + public float RippleWeight { get; private set; } + public bool CameraFacing { get; private set; } + public uint BoneID { get; private set; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct DrawCall + { + public uint MaterialIndex { get; private set; } + public bool ContainsFacingGeometry { get; private set; } + public uint IndexStart { get; private set; } + public uint IndexCount { get; private set; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct Bone + { + public uint ID { get; private set; } + public bool ParentID { get; private set; } + public Vec3 Start { get; private set; } + public Vec3 End { get; private set; } + public float Radius { get; private set; } + } + + #endregion + + #region Data Tables + interface ReaderData + { + public void Initialize(Byte[] data, int offset); + } + + class Lod : ReaderData + { + private enum OffsetData + { + Vertices = 0, + Indices = 1, + DrawCalls = 2 + } + + public Vertex[] Vertices { get; private set; } + public uint[] Indices { get; private set; } + public DrawCall[] DrawCalls { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Vertices = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Vertices); + Indices = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Indices); + DrawCalls = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.DrawCalls); + } + } + + class BillboardInfo : ReaderData + { + private enum OffsetData + { + LastLodIsBillboard = 0, + IncludesTopDown = 1, + SideViewCount = 2 + } + + public bool LastLodIsBillboard { get; private set; } + public bool IncludesTopDown { get; private set; } + public uint SideViewCount { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + LastLodIsBillboard = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.LastLodIsBillboard); + IncludesTopDown = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.IncludesTopDown); + SideViewCount = SpeedTree9ReaderUtility.GetUInt(data, offset, (int)OffsetData.SideViewCount); + } + } + + class CollisionObject : ReaderData + { + private enum OffsetData + { + Position = 0, + Position2 = 1, + Radius = 2, + UserData = 3 + } + + public Vec3 Position { get; private set; } + public Vec3 Position2 { get; private set; } + public float Radius { get; private set; } + public string UserData { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Position = SpeedTree9ReaderUtility.GetStruct(data, offset, (int)OffsetData.Position); + Position2 = SpeedTree9ReaderUtility.GetStruct(data, offset, (int)OffsetData.Position2); + Radius = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Radius); + UserData = SpeedTree9ReaderUtility.GetString(data, offset, (int)OffsetData.UserData); + } + } + + class MaterialMap : ReaderData + { + private enum OffsetData + { + Used = 0, + Path = 1, + Color = 2 + } + + public bool Used { get; private set; } + public string Path { get; private set; } + public Vec4 Color { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Used = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.Used); + Path = SpeedTree9ReaderUtility.GetString(data, offset, (int)OffsetData.Path); + Color = SpeedTree9ReaderUtility.GetStruct(data, offset, (int)OffsetData.Color); + } + } + + class STMaterial : ReaderData + { + private enum OffsetData + { + Name = 0, + TwoSided = 1, + FlipNormalsOnBackside = 2, + Billboard = 3, + Maps = 4 + } + + public string Name { get; private set; } + public bool TwoSided { get; private set; } + public bool FlipNormalsOnBackside { get; private set; } + public bool Billboard { get; private set; } + public MaterialMap[] Maps { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Name = SpeedTree9ReaderUtility.GetString(data, offset, (int)OffsetData.Name); + TwoSided = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.TwoSided); + FlipNormalsOnBackside = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.FlipNormalsOnBackside); + Billboard = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.Billboard); + Maps = SpeedTree9ReaderUtility.GetDataObjectArray(data, offset, (int)OffsetData.Maps); + } + } + #endregion + + #region Wind Data Tables + class WindConfigCommon : ReaderData + { + private enum OffsetData + { + StrengthResponse = 0, + DirectionResponse = 1, + GustFrequency = 5, + GustStrengthMin = 6, + GustStrengthMax = 7, + GustDurationMin = 8, + GustDurationMax = 9, + GustRiseScalar = 10, + GustFallScalar = 11, + CurrentStrength = 15 + } + + public float StrengthResponse { get; private set; } + public float DirectionResponse { get; private set; } + public float GustFrequency { get; private set; } + public float GustStrengthMin { get; private set; } + public float GustStrengthMax { get; private set; } + public float GustDurationMin { get; private set; } + public float GustDurationMax { get; private set; } + public float GustRiseScalar { get; private set; } + public float GustFallScalar { get; private set; } + public float CurrentStrength { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + StrengthResponse = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.StrengthResponse); + DirectionResponse = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.DirectionResponse); + GustFrequency = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustFrequency); + GustStrengthMin = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustStrengthMin); + GustStrengthMax = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustStrengthMax); + GustDurationMin = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustDurationMin); + GustDurationMax = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustDurationMax); + GustRiseScalar = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustRiseScalar); + GustFallScalar = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.GustFallScalar); + CurrentStrength = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.CurrentStrength); + } + }; + + class WindConfigSDK : ReaderData + { + internal class WindBranch : ReaderData + { + private enum OffsetData + { + Bend = 0, + Oscillation = 1, + Speed = 2, + Turbulence = 3, + Flexibility = 4, + Independence = 5 + } + + public float[] Bend { get; private set; } + public float[] Oscillation { get; private set; } + public float[] Speed { get; private set; } + public float[] Turbulence { get; private set; } + public float[] Flexibility { get; private set; } + public float Independence { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Bend = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Bend); + Oscillation = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Oscillation); + Speed = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Speed); + Turbulence = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Turbulence); + Flexibility = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Flexibility); + Independence = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Independence); + } + }; + + internal class WindRipple : ReaderData + { + private enum OffsetData + { + Planar = 0, + Directional = 1, + Speed = 2, + Flexibility = 3, + Shimmer = 4, + Independence = 5 + } + + public float[] Planar { get; private set; } + public float[] Directional { get; private set; } + public float[] Speed { get; private set; } + public float[] Flexibility { get; private set; } + public float Shimmer { get; private set; } + public float Independence { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Planar = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Planar); + Directional = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Directional); + Speed = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Speed); + Flexibility = SpeedTree9ReaderUtility.GetArray(data, offset, (int)OffsetData.Flexibility); + Shimmer = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Shimmer); + Independence = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Independence); + } + }; + + private enum OffsetData + { + Common = 0, + Shared = 1, + Branch1 = 2, + Branch2 = 3, + Ripple = 4, + SharedStartHeight = 10, + Branch1StretchLimit = 11, + Branch2StretchLimit = 12, + DoShared = 15, + DoBranch1 = 16, + DoBranch2 = 17, + DoRipple = 18, + DoShimmer = 19 + } + + public WindConfigCommon Common { get; private set; } + public WindBranch Shared { get; private set; } + public WindBranch Branch1 { get; private set; } + public WindBranch Branch2 { get; private set; } + public WindRipple Ripple { get; private set; } + + public float SharedStartHeight { get; private set; } + public float Branch1StretchLimit { get; private set; } + public float Branch2StretchLimit { get; private set; } + + public bool DoShared { get; private set; } + public bool DoBranch1 { get; private set; } + public bool DoBranch2 { get; private set; } + public bool DoRipple { get; private set; } + public bool DoShimmer { get; private set; } + + public void Initialize(Byte[] data, int offset) + { + Common = SpeedTree9ReaderUtility.GetDataObject(data, offset, (int)OffsetData.Common); + Shared = SpeedTree9ReaderUtility.GetDataObject(data, offset, (int)OffsetData.Shared); + Branch1 = SpeedTree9ReaderUtility.GetDataObject(data, offset, (int)OffsetData.Branch1); + Branch2 = SpeedTree9ReaderUtility.GetDataObject(data, offset, (int)OffsetData.Branch2); + Ripple = SpeedTree9ReaderUtility.GetDataObject(data, offset, (int)OffsetData.Ripple); + + SharedStartHeight = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.SharedStartHeight); + Branch1StretchLimit = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Branch1StretchLimit); + Branch2StretchLimit = SpeedTree9ReaderUtility.GetFloat(data, offset, (int)OffsetData.Branch2StretchLimit); + + DoShared = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.DoShared); + DoBranch1 = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.DoBranch1); + DoBranch2 = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.DoBranch2); + DoRipple = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.DoRipple); + DoShimmer = SpeedTree9ReaderUtility.GetBool(data, offset, (int)OffsetData.DoShimmer); + } + }; + #endregion + + #region Reader Utilities + static class SpeedTree9ReaderUtility + { + public static int GetOffset(in Byte[] data, int offsetIn, int index) + { + int offset = offsetIn + (index + 1) * 4; + return offsetIn + (int)BitConverter.ToUInt32(data, offset); + } + + public static float GetFloat(in Byte[] data, int offsetIn, int index) + { + return BitConverter.ToSingle(data, GetOffset(data, offsetIn, index)); + } + + public static string GetString(in Byte[] data, int offsetIn, int index) + { + int offset = GetOffset(data, offsetIn, index); + int length = (int)BitConverter.ToUInt32(data, offset); + return System.Text.Encoding.UTF8.GetString(data, offset + 4, length - 1); + } + + public static T GetStruct(in Byte[] data, int offsetIn, int index) where T : struct + { + int offset = GetOffset(data, offsetIn, index); + return MemoryMarshal.Cast(data.AsSpan().Slice(offset))[0]; + } + + public static bool GetBool(in Byte[] data, int offsetIn, int index) + { + return GetInt(data, offsetIn, index) != 0; + } + + public static int GetInt(in Byte[] data, int offsetIn, int index) + { + return BitConverter.ToInt32(data, GetOffset(data, offsetIn, index)); + } + + public static uint GetUInt(in Byte[] data, int offsetIn, int index) + { + return BitConverter.ToUInt32(data, GetOffset(data, offsetIn, index)); + } + + public static T[] GetArray(Byte[] data, int offset, int type) where T : struct + { + int offsetData = SpeedTree9ReaderUtility.GetOffset(data, offset, (int)type); + uint countData = BitConverter.ToUInt32(data, offsetData); + + T[] array = new T[countData]; + + for (int i = 0; i < countData; i++) + { + array[i] = MemoryMarshal.Cast(data.AsSpan().Slice(offsetData + 4))[i]; + } + + return array; + } + + public static T GetDataObject(in Byte[] data, int offset, int index) where T : ReaderData, new() + { + int dataOffset = SpeedTree9ReaderUtility.GetOffset(data, offset, index); + + T readData = new T(); + readData.Initialize(data, dataOffset); + + return readData; + } + + public static T[] GetDataObjectArray(in Byte[] data, int offset, int index) where T : ReaderData, new() + { + int dataOffset = SpeedTree9ReaderUtility.GetOffset(data, offset, index); + uint dataCount = BitConverter.ToUInt32(data, dataOffset); + + T[] readData = new T[dataCount]; + + for (int i = 0; i < dataCount; i++) + { + int offsetNew = SpeedTree9ReaderUtility.GetOffset(data, dataOffset, i); + + T newData = new T(); + newData.Initialize(data, offsetNew); + + readData[i] = newData; + } + + return readData; + } + } + #endregion + + internal class SpeedTree9Reader : IDisposable + { + internal enum FileStatus + { + Valid = 0, + InvalidPath = 1, + InvalidSignature = 2 + } + + enum OffsetData + { + VersionMajor = 0, + VersionMinor = 1, + Bounds = 2, + Lod = 5, + BillboardInfo = 6, + CollisionObjects = 7, + Materials = 10, + Wind = 15 + } + + const string k_TokenKey = "SpeedTree9______"; + + public uint VersionMajor { get; private set; } + public uint VersionMinor { get; private set; } + public Bounds3 Bounds { get; private set; } + public Lod[] Lod { get; private set; } + public BillboardInfo BillboardInfo { get; private set; } + public CollisionObject[] CollisionObjects { get; private set; } + public STMaterial[] Materials { get; private set; } + public WindConfigSDK Wind { get; private set; } + + private string m_AssetPath; + private FileStream m_FileStream; + private int m_Offset; + private bool m_Disposed; + + public FileStatus Initialize(string assetPath) + { + m_AssetPath = assetPath; + m_FileStream = new FileStream(m_AssetPath, FileMode.Open, FileAccess.Read); + + if (!File.Exists(assetPath)) + { + return FileStatus.InvalidPath; + } + + if (!ValidateFileSignature()) + { + return FileStatus.InvalidSignature; + } + + return FileStatus.Valid; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (!m_Disposed) + { + // Manual release of managed resources. + if (disposing) + { + if (m_FileStream != null) + { + m_FileStream.Close(); + } + } + + // Release unmanaged resources. + m_Disposed = true; + } + } + + public void ReadContent() + { + byte[] data = File.ReadAllBytes(m_AssetPath); + + VersionMajor = SpeedTree9ReaderUtility.GetUInt(data, m_Offset, (int)OffsetData.VersionMajor); + VersionMinor = SpeedTree9ReaderUtility.GetUInt(data, m_Offset, (int)OffsetData.VersionMinor); + Bounds = SpeedTree9ReaderUtility.GetStruct(data, m_Offset, (int)OffsetData.Bounds); + Lod = SpeedTree9ReaderUtility.GetDataObjectArray(data, m_Offset, (int)OffsetData.Lod); + BillboardInfo = SpeedTree9ReaderUtility.GetDataObject(data, m_Offset, (int)OffsetData.BillboardInfo); + CollisionObjects = SpeedTree9ReaderUtility.GetDataObjectArray(data, m_Offset, (int)OffsetData.CollisionObjects); + Materials = SpeedTree9ReaderUtility.GetDataObjectArray(data, m_Offset, (int)OffsetData.Materials); + Wind = SpeedTree9ReaderUtility.GetDataObject(data, m_Offset, (int)OffsetData.Wind); + + if (m_FileStream != null) + { + m_FileStream.Close(); + } + } + + private unsafe bool ValidateFileSignature() + { + byte[] buffer = new byte[k_TokenKey.Length]; + + try + { + int bytesRead = m_FileStream.Read(buffer, m_Offset, k_TokenKey.Length); + + if (bytesRead != buffer.Length) + { + return false; + } + } + catch (UnauthorizedAccessException ex) + { + Debug.LogError(ex.Message); + } + + bool valid = true; + for (int i = 0; i < k_TokenKey.Length && valid; ++i) + { + valid &= (k_TokenKey[i] == buffer[i]); + } + + if (valid) + { + m_Offset = k_TokenKey.Length; + } + + return valid; + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterCommon.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterCommon.cs new file mode 100644 index 0000000000..eaee480758 --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterCommon.cs @@ -0,0 +1,300 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using System; +using UnityEngine.Rendering; +using UnityEditor.AnimatedValues; + +namespace UnityEditor.SpeedTree.Importer +{ + class SpeedTreeConstants + { + internal static readonly float kFeetToMetersRatio = 0.3048f; + internal static readonly float kCentimetersToMetersRatio = 0.01f; + internal static readonly float kInchesToMetersRatio = 0.0254f; + internal static readonly float kAlphaTestRef = 0.33f; + internal static readonly string kBillboardMaterialName = "Billboard"; + } + + static class SpeedTreeImporterCommon + { + internal enum STUnitConversion + { + kLeaveAsIs = 0, + kFeetToMeters, + kCentimetersToMeters, + kInchesToMeters, + kCustomConversion + }; + + internal static class MaterialProperties + { + internal static readonly int MainTexID = Shader.PropertyToID("_MainTex"); + internal static readonly int ColorTintID = Shader.PropertyToID("_ColorTint"); + + internal static readonly int NormalMapKwToggleID = Shader.PropertyToID("_NormalMapKwToggle"); + internal static readonly int NormalMapID = Shader.PropertyToID("_NormalMap"); + + internal static readonly int ExtraMapKwToggleID = Shader.PropertyToID("_ExtraMapKwToggle"); + internal static readonly int ExtraTexID = Shader.PropertyToID("_ExtraTex"); + internal static readonly int GlossinessID = Shader.PropertyToID("_Glossiness"); + internal static readonly int MetallicID = Shader.PropertyToID("_Metallic"); + + internal static readonly int SubsurfaceKwToggleID = Shader.PropertyToID("_SubsurfaceKwToggle"); + internal static readonly int SubsurfaceTexID = Shader.PropertyToID("_SubsurfaceTex"); + internal static readonly int SubsurfaceColorID = Shader.PropertyToID("_SubsurfaceColor"); + internal static readonly int AlphaClipThresholdID = Shader.PropertyToID("_AlphaClipThreshold"); + internal static readonly int TransmissionScaleID = Shader.PropertyToID("_TransmissionScale"); + internal static readonly int DiffusionProfileAssetID = Shader.PropertyToID("_Diffusion_Profile_Asset"); + internal static readonly int DiffusionProfileID = Shader.PropertyToID("_DiffusionProfile"); + + internal static readonly int HueVariationKwToggleID = Shader.PropertyToID("_HueVariationKwToggle"); + internal static readonly int HueVariationColorID = Shader.PropertyToID("_HueVariationColor"); + + internal static readonly int LeafFacingKwToggleID = Shader.PropertyToID("_LeafFacingKwToggle"); + internal static readonly int BillboardKwToggleID = Shader.PropertyToID("_BillboardKwToggle"); + internal static readonly int DoubleSidedToggleID = Shader.PropertyToID("_DoubleSidedKwToggle"); + internal static readonly int DoubleSidedNormalModeID = Shader.PropertyToID("_DoubleSidedNormalMode"); + internal static readonly int BackfaceNormalModeID = Shader.PropertyToID("_BackfaceNormalMode"); + internal static readonly int TwoSidedID = Shader.PropertyToID("_TwoSided"); + + internal static readonly int WindSharedKwToggle = Shader.PropertyToID("_WIND_SHARED"); + internal static readonly int WindBranch2KwToggle = Shader.PropertyToID("_WIND_BRANCH2"); + internal static readonly int WindBranch1KwToggle = Shader.PropertyToID("_WIND_BRANCH1"); + internal static readonly int WindRippleKwToggle = Shader.PropertyToID("_WIND_RIPPLE"); + internal static readonly int WindShimmerKwToggle = Shader.PropertyToID("_WIND_SHIMMER"); + } + + internal static class MaterialKeywords + { + internal static readonly string VBSetupStandardID = "VB_SETUP_STANDARD"; + internal static readonly string VBSetupBranch2ID = "VB_SETUP_BRANCH2"; + internal static readonly string VBSetupCameraFacingID = "VB_SETUP_CAMERA_FACING"; + internal static readonly string VBSetupBranch2AndCameraFacingID = "VB_SETUP_BRANCH2_AND_CAMERA_FACING"; + internal static readonly string VBSetupBillboardID = "VB_SETUP_BILLBOARD"; + internal static readonly string VBSetupID = "VB_SETUP"; + internal static readonly string BillboardID = "_BILLBOARD"; + } + } + + static class SpeedTreeImporterCommonEditor + { + internal class Styles + { + // Meshes labels + public static GUIContent MeshesHeader = EditorGUIUtility.TrTextContent("Meshes"); + public static GUIContent UnitConversion = EditorGUIUtility.TrTextContent("Unit Conversion", "Select the unit conversion to apply to the imported SpeedTree asset."); + public static GUIContent ScaleFactor = EditorGUIUtility.TrTextContent("Scale Factor", "How much to scale the tree model, interpreting the exported units as meters. Must be positive."); + public static GUIContent[] UnitConversionNames = + { + new GUIContent("Leave As Is") + , new GUIContent("ft to m") + , new GUIContent("cm to m") + , new GUIContent("inch to m") + , new GUIContent("Custom") + }; + + // Materials labels + public static GUIContent MaterialHeader = EditorGUIUtility.TrTextContent("Material"); + public static GUIContent MainColor = EditorGUIUtility.TrTextContent("Main Color", "The color modulating the diffuse lighting component."); + public static GUIContent EnableColorVariation = EditorGUIUtility.TrTextContent("Color Variation", "Color is determined by linearly interpolating between the Main Color & Color Variation values based on the world position X, Y and Z values"); + public static GUIContent EnableBump = EditorGUIUtility.TrTextContent("Normal Map", "Enable normal (Bump) mapping."); + public static GUIContent EnableSubsurface = EditorGUIUtility.TrTextContent("Subsurface Scattering", "Enable subsurface scattering effects."); + public static GUIContent HueVariation = EditorGUIUtility.TrTextContent("Variation Color (RGB), Intensity (A)", "Tint the tree with the Variation Color"); + public static GUIContent AlphaTestRef = EditorGUIUtility.TrTextContent("Alpha Cutoff", "The alpha-test reference value."); + public static GUIContent TransmissionScale = EditorGUIUtility.TrTextContent("Transmission Scale", "The transmission scale value."); + + // Lighting labels + public static GUIContent LightingHeader = EditorGUIUtility.TrTextContent("Lighting"); + public static GUIContent CastShadows = EditorGUIUtility.TrTextContent("Cast Shadows", "The tree casts shadow"); + public static GUIContent ReceiveShadows = EditorGUIUtility.TrTextContent("Receive Shadows", "The tree receives shadow"); + public static GUIContent UseLightProbes = EditorGUIUtility.TrTextContent("Light Probes", "The tree uses light probe for lighting"); // TODO: update help text + public static GUIContent UseReflectionProbes = EditorGUIUtility.TrTextContent("Reflection Probes", "The tree uses reflection probe for rendering"); // TODO: update help text + public static GUIContent[] ReflectionProbeUsageNames = GetReflectionProbeUsageNames(); + + public static GUIContent[] GetReflectionProbeUsageNames() + { + string[] names = Enum.GetNames(typeof(ReflectionProbeUsage)); + GUIContent[] probUsageNames = new GUIContent[names.Length]; + + for (int i = 0; i < names.Length; ++i) + { + string varName = ObjectNames.NicifyVariableName(names[i]); + probUsageNames[i] = (new GUIContent(varName)); + } + + return probUsageNames; + } + + // Additional Settings labels + public static GUIContent AdditionalSettingsHeader = EditorGUIUtility.TrTextContent("Additional Settings"); + public static GUIContent MotionVectorMode = EditorGUIUtility.TrTextContent("Motion Vectors", "Motion vector mode to set for the mesh renderer of each LOD object"); + + // Wind labels + public static GUIContent WindHeader = EditorGUIUtility.TrTextContent("Wind"); + public static GUIContent WindQuality = EditorGUIUtility.TrTextContent("Wind Quality", "Controls the wind effect's quality."); + public static GUIContent[] MotionVectorModeNames = // Match SharedRendererDataTypes.h / enum MotionVectorGenerationMode +{ + new GUIContent("Camera Motion Only") // kMotionVectorCamera = 0, // Use camera motion for motion vectors + , new GUIContent("Per Object Motion") // kMotionVectorObject, // Use a per object motion vector pass for this object + , new GUIContent("Force No Motion") // kMotionVectorForceNoMotion, // Force no motion for this object (0 into motion buffer) + }; + + public static GUIContent[] GetWindQualityNames() + { + GUIContent[] windQualityNames = new GUIContent[SpeedTreeImporter.windQualityNames.Length]; + + for (int i = 0; i < SpeedTreeImporter.windQualityNames.Length; ++i) + { + windQualityNames[i] = new GUIContent(SpeedTreeImporter.windQualityNames[i]); + } + + return windQualityNames; + } + + // LOD labels + public static GUIContent LODHeader = EditorGUIUtility.TrTextContent("LOD"); + public static GUIContent ResetLOD = EditorGUIUtility.TrTextContent("Reset LOD to...", "Unify the LOD settings for all selected assets"); + public static GUIContent SmoothLOD = EditorGUIUtility.TrTextContent("Smooth Transitions", "Toggles smooth LOD transitions"); + public static GUIContent AnimateCrossFading = EditorGUIUtility.TrTextContent("Animate Cross-fading", "Cross-fading is animated instead of being calculated by distance"); + public static GUIContent CrossFadeWidth = EditorGUIUtility.TrTextContent("Crossfade Width", "Proportion of the last 3D mesh LOD region width which is used for cross-fading to billboard tree"); + public static GUIContent FadeOutWidth = EditorGUIUtility.TrTextContent("Fade Out Width", "The proportion of the billboard LOD region width that is used to fade out the billboard."); + + public static GUIContent EnableLodCustomizationsWarn = EditorGUIUtility.TrTextContent("Customizing LOD options may help with tuning the GPU performance but will likely negatively impact the instanced draw batching, i.e. CPU performance.\nPlease use the per-LOD customizations with careful memory and performance profiling for both CPU and GPU and remember that these options are a trade-off rather than a free win."); + public static GUIContent BillboardSettingsHelp = EditorGUIUtility.TrTextContent("Billboard options are separate from the 3D model options shown above.\nChange the options below for influencing billboard rendering."); + public static GUIContent MultiSelectionLODNotSupported = EditorGUIUtility.TrTextContent("Multi-selection is not supported for LOD settings."); + + public static GUIContent ApplyAndGenerate = EditorGUIUtility.TrTextContent("Apply & Generate Materials", "Apply current importer settings and generate asset materials with the new settings."); + } + + static internal void ShowMeshGUI( + ref SerializedProperty unitConversionEnumValue, + ref SerializedProperty scaleFactor) + { + GUILayout.Label(Styles.MeshesHeader, EditorStyles.boldLabel); + + EditorGUILayout.Popup(unitConversionEnumValue, Styles.UnitConversionNames, Styles.UnitConversion); + + bool bShowCustomScaleFactor = unitConversionEnumValue.intValue == Styles.UnitConversionNames.Length - 1; + if (bShowCustomScaleFactor) + { + EditorGUILayout.PropertyField(scaleFactor, Styles.ScaleFactor); + if (scaleFactor.floatValue < 0f) + { + scaleFactor.floatValue = 0f; + } + if (scaleFactor.floatValue == 0f) + { + EditorGUILayout.HelpBox("Scale factor must be positive.", MessageType.Warning); + } + } + } + + static internal void ShowMaterialGUI( + ref SerializedProperty mainColor, + ref SerializedProperty enableHueVariation, + ref SerializedProperty hueVariation, + ref SerializedProperty alphaTestRef, + ref SerializedProperty enableBumpMapping, + ref SerializedProperty enableSubsurfaceScattering, + bool renderHueVariationDropdown = false, + bool renderAlphaTestRef = false) + { + EditorGUILayout.LabelField(Styles.MaterialHeader, EditorStyles.boldLabel); + + EditorGUILayout.PropertyField(mainColor, Styles.MainColor); + EditorGUILayout.PropertyField(enableHueVariation, Styles.EnableColorVariation); + + if (renderHueVariationDropdown) + { + EditorGUILayout.PropertyField(hueVariation, Styles.HueVariation); + } + + if (renderAlphaTestRef) + EditorGUILayout.Slider(alphaTestRef, 0f, 1f, Styles.AlphaTestRef); + + EditorGUILayout.PropertyField(enableBumpMapping, Styles.EnableBump); + EditorGUILayout.PropertyField(enableSubsurfaceScattering, Styles.EnableSubsurface); + } + + static internal void ShowLightingGUI( + ref SerializedProperty enableShadowCasting, + ref SerializedProperty enableShadowReceiving, + ref SerializedProperty enableLightProbeUsage, + ref SerializedProperty reflectionProbeUsage) + { + GUILayout.Label(Styles.LightingHeader, EditorStyles.boldLabel); + + EditorGUILayout.PropertyField(enableShadowCasting, Styles.CastShadows); + + // from the docs page: https://docs.unity3d.com/Manual/SpeedTree.html + // Known issues: As with any other renderer, the Receive Shadows option has no effect while using deferred rendering. + // TODO: test and conditionally expose this field + using (new EditorGUI.DisabledScope(!UnityEngine.Rendering.SupportedRenderingFeatures.active.receiveShadows)) + { + EditorGUILayout.PropertyField(enableShadowReceiving, Styles.ReceiveShadows); + } + + EditorGUILayout.PropertyField(enableLightProbeUsage, Styles.UseLightProbes); + } + + static internal void ShowAdditionalSettingsGUI( + ref SerializedProperty motionVectorModeEnumValue, + ref SerializedProperty generateColliders, + ref SerializedProperty generateRigidbody) + { + GUILayout.Label(Styles.AdditionalSettingsHeader, EditorStyles.boldLabel); + EditorGUILayout.Popup(motionVectorModeEnumValue, Styles.MotionVectorModeNames, Styles.MotionVectorMode); + + EditorGUILayout.PropertyField(generateColliders); + EditorGUILayout.PropertyField(generateRigidbody); + } + + static internal void ShowWindGUI( + ref SerializedProperty bestWindQuality, + ref SerializedProperty selectedWindQuality) + { + GUILayout.Label(Styles.WindHeader, EditorStyles.boldLabel); + + int NumAvailableWindQualityOptions = 1 + bestWindQuality.intValue; // 0 is None, we want at least 1 value + ArraySegment availableWindQualityOptions = new ArraySegment(Styles.GetWindQualityNames(), 0, NumAvailableWindQualityOptions); + EditorGUILayout.Popup(selectedWindQuality, availableWindQualityOptions.ToArray(), Styles.WindQuality); + } + + static internal void ShowLODGUI( + ref SerializedProperty enableSmoothLOD, + ref SerializedProperty animateCrossFading, + ref SerializedProperty billboardTransitionCrossFadeWidth, + ref SerializedProperty fadeOutWidth, + ref AnimBool showSmoothLODOptions, + ref AnimBool showCrossFadeWidthOptions) + { + showSmoothLODOptions.target = enableSmoothLOD.hasMultipleDifferentValues || enableSmoothLOD.boolValue; + showCrossFadeWidthOptions.target = animateCrossFading.hasMultipleDifferentValues || !animateCrossFading.boolValue; + + GUILayout.Label(Styles.LODHeader, EditorStyles.boldLabel); + + EditorGUILayout.PropertyField(enableSmoothLOD, Styles.SmoothLOD); + + using (new EditorGUI.IndentLevelScope()) + { + // Note: FadeGroupScope doesn't work here, as if the 'faded' is not updated correctly. + + if (EditorGUILayout.BeginFadeGroup(showSmoothLODOptions.faded)) + { + EditorGUILayout.PropertyField(animateCrossFading, Styles.AnimateCrossFading); + + if (EditorGUILayout.BeginFadeGroup(showCrossFadeWidthOptions.faded)) + { + EditorGUILayout.Slider(billboardTransitionCrossFadeWidth, 0.0f, 1.0f, Styles.CrossFadeWidth); + EditorGUILayout.Slider(fadeOutWidth, 0.0f, 1.0f, Styles.FadeOutWidth); + } + EditorGUILayout.EndFadeGroup(); + } + EditorGUILayout.EndFadeGroup(); + } + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterOutputData.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterOutputData.cs new file mode 100644 index 0000000000..bc11ceb74d --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterOutputData.cs @@ -0,0 +1,68 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using System.Collections.Generic; + +namespace UnityEditor.SpeedTree.Importer +{ + internal class SpeedTreeImporterOutputData : ScriptableObject + { + /// + /// The main object of the asset. + /// + public GameObject mainObject; + + /// + /// Contains the materials data and materials are assigned per LOD with an index. + /// + public LODMaterials lodMaterials = new LODMaterials(); + + /// + /// The materials metadata used for the materials extraction code. + /// + public List materialsIdentifiers = new List(); + + /// + /// Represents the wind configuration parameters. + /// + public SpeedTreeWindConfig9 m_WindConfig = new SpeedTreeWindConfig9(); + + /// + /// Determines if the current asset has embedded materials or not. + /// + public bool hasEmbeddedMaterials = false; + + /// + /// Determines if the current Shader supports alpha clip threshold. + /// + public bool hasAlphaClipThreshold = false; + + /// + /// Determines if the current Shader supports transmision scale. + /// + public bool hasTransmissionScale = false; + + /// + /// Determines if the current asset has a biilboard or not. + /// + public bool hasBillboard = false; + + /// + /// Create an instance of SpeedTreeImporterOutputData with default settings. + /// + /// The newly created SpeedTreeImporterOutputData instance. + static public SpeedTreeImporterOutputData Create() + { + SpeedTreeImporterOutputData outputImporterData = CreateInstance(); + outputImporterData.name = "ImporterOutputData"; + + // Ideally, we should use this flag so the file is not visible in the asset prefab. + // Though, it breaks the 'AssetDatabase.LoadAssetAtPath' function (the file is not loaded anymore). + // outputImporterData.hideFlags = HideFlags.HideInHierarchy; + + return outputImporterData; + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterSettings.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterSettings.cs new file mode 100644 index 0000000000..a6a84b45ee --- /dev/null +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTreeImporterSettings.cs @@ -0,0 +1,224 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; +using System; +using UnityEngine.Rendering; + +using System.Collections.Generic; +using static UnityEditor.SpeedTree.Importer.SpeedTreeImporterCommon; + +namespace UnityEditor.SpeedTree.Importer +{ + [Serializable] + internal class MeshSettings + { + public STUnitConversion unitConversion = STUnitConversion.kFeetToMeters; + public float scaleFactor = SpeedTreeConstants.kFeetToMetersRatio; + } + + [Serializable] + internal class MaterialSettings + { + public Color mainColor = Color.white; + public Color hueVariation = new Color(1.0f, 0.5f, 0.0f, 0.1f); + public float alphaClipThreshold = 0.10f; + public float transmissionScale = 0.0f; + + public bool enableHueVariation = false; + public bool enableBumpMapping = true; + public bool enableSubsurfaceScattering = true; + + public Vector4 diffusionProfileAssetID = Vector4.zero; + public uint diffusionProfileID = 0; + } + + [Serializable] + internal class LightingSettings + { + public bool enableShadowCasting = true; + public bool enableShadowReceiving = true; + public bool enableLightProbes = true; + public ReflectionProbeUsage reflectionProbeEnumValue = ReflectionProbeUsage.BlendProbes; + } + + [Serializable] + internal class AdditionalSettings + { + public MotionVectorGenerationMode motionVectorModeEnumValue = MotionVectorGenerationMode.Object; + public bool generateColliders = true; + public bool generateRigidbody = true; + } + + [Serializable] + internal class LODSettings + { + public bool enableSmoothLODTransition = true; + public bool animateCrossFading = true; + public float billboardTransitionCrossFadeWidth = 0.25f; + public float fadeOutWidth = 0.25f; + public bool hasBillboard = false; + } + + [Serializable] + internal class PerLODSettings + { + public bool enableSettingOverride = false; + + // LOD + public float height = 0.5f; + + // Lighting + public bool castShadows = false; + public bool receiveShadows = false; + public bool useLightProbes = false; + public ReflectionProbeUsage reflectionProbeUsage = ReflectionProbeUsage.Off; + + // Material + public bool enableBump = false; + public bool enableHue = false; + public bool enableSubsurface = false; + }; + + [Serializable] + internal class MaterialInfo + { + public Material material = null; + public string defaultName = null; + public bool exported = false; + } + + [Serializable] + internal class LODMaterials + { + public List materials = new List(); + public Dictionary> lodToMaterials = new Dictionary>(); + public Dictionary matNameToIndex = new Dictionary(); + + public void AddLodMaterialIndex(int lod, int matIndex) + { + if (lodToMaterials.ContainsKey(lod)) + { + lodToMaterials[lod].Add(matIndex); + } + else + { + lodToMaterials.Add(lod, new List() { matIndex}); + } + } + } + + [Serializable] + internal class WindSettings + { + public bool enableWind = true; + + [Range(1.0f, 20.0f)] + public float strenghResponse = 5.0f; + + [Range(1.0f, 20.0f)] + public float directionResponse = 2.5f; + + [Range(0.0f, 1.0f)] + public float randomness = 0.5f; + } + + // Ideally, we should use 'AssetImporter.SourceAssetIdentifier', but this struct has many problems: + // 1 - 'Type' is not a serializable type, so it's always equal to null in our context. + // 2 - The C# struct is not matching the C++ class. + // 3 - Missing the 'Serializable' attribute on top of the struct. + [Serializable] + internal class AssetIdentifier + { + public string type; + public string name; + + public AssetIdentifier(UnityEngine.Object asset) + { + if (asset == null) + { + throw new ArgumentNullException("asset"); + } + + this.type = asset.GetType().ToString(); + this.name = asset.name; + } + + public AssetIdentifier(Type type, string name) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (name == null) + { + throw new ArgumentNullException("name"); + } + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("The name is empty", "name"); + } + + this.type = type.ToString(); + this.name = name; + } + } + + /// + /// This attribute is used as a callback to set SRP specific properties from the importer. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class MaterialSettingsCallbackAttribute : Attribute + { + /// + /// The version of the method. + /// + public int MethodVersion; + + /// + /// Initializes a new instance of the MaterialSettingsCallbackAttribute with the given method version. + /// + /// The given method version. + public MaterialSettingsCallbackAttribute(int methodVersion) + { + MethodVersion = methodVersion; + } + + [RequiredSignature] + static void SignatureExample(GameObject mainObject) + { + throw new InvalidOperationException(); + } + } + + /// + /// This attribute is used as a callback to extend the inspector by adding + /// the Diffuse Profile property when the HDRP package is in use. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class DiffuseProfileCallbackAttribute : Attribute + { + /// + /// The version of the method. + /// + public int MethodVersion; + + /// + /// Initializes a new instance of the DiffuseProfileCallbackAttribute with the given method version. + /// + /// The given method version. + public DiffuseProfileCallbackAttribute(int methodVersion) + { + MethodVersion = methodVersion; + } + + [RequiredSignature] + static void SignatureExample(ref SerializedProperty diffusionProfileAsset, ref SerializedProperty diffusionProfileHash) + { + throw new InvalidOperationException(); + } + } +} diff --git a/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs b/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs index 44272ebbd2..dd19442061 100644 --- a/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/SpeedTreeImporter.bindings.cs @@ -90,6 +90,11 @@ public extern bool isV8 public extern int bestWindQuality { get; } public extern int selectedWindQuality { get; set; } + ///////////////////////////////////////////////////////////////////////////// + // Physics settings + + public extern bool generateRigidbody { get; set; } + public extern bool generateColliders { get; set; } ///////////////////////////////////////////////////////////////////////////// // LOD settings @@ -300,9 +305,14 @@ static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAsse bool st8 = asset.EndsWith(".st", StringComparison.OrdinalIgnoreCase); if(st8) { + SpeedTreeImporter importer = AssetImporter.GetAtPath(asset) as SpeedTreeImporter; + if (importer == null) + continue; + // Check the external materials in case the user has extracted - Dictionary externalAssets = (AssetImporter.GetAtPath(asset) as SpeedTreeImporter).GetExternalObjectMap(); - FixExtraTexture_sRGB(externalAssets.Values); + Dictionary externalAssets = importer.GetExternalObjectMap(); + if(externalAssets != null) + FixExtraTexture_sRGB(externalAssets.Values); // Check the object subassets -- updates the materials if they're embedded in the SpeedTree asset UnityEngine.Object[] subAssets = AssetDatabase.LoadAllAssetsAtPath(asset); diff --git a/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs b/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs index ceb38f59a1..7c1eb764c4 100644 --- a/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs @@ -22,6 +22,7 @@ public struct SpriteImportData private SpriteAlignment m_Alignment; private Vector2 m_Pivot; private Vector4 m_Border; + private string m_CustomData; private float m_TessellationDetail; private string m_SpriteID; private List m_Outline; @@ -31,6 +32,7 @@ public struct SpriteImportData public SpriteAlignment alignment { get { return m_Alignment; } set { m_Alignment = value; } } public Vector2 pivot { get { return m_Pivot; } set { m_Pivot = value; } } public Vector4 border { get { return m_Border; } set { m_Border = value; } } + internal string customData { get { return m_CustomData; } set { m_CustomData = value; } } public List outline { get { return m_Outline; } set { m_Outline = value; } } public float tessellationDetail {get { return m_TessellationDetail; } set { m_TessellationDetail = value; } } public string spriteID {get { return m_SpriteID; } set { m_SpriteID = value; } } @@ -246,11 +248,16 @@ public static unsafe class TextureGenerator { public static TextureGenerationOutput GenerateTexture(TextureGenerationSettings settings, NativeArray colorBuffer) { - return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf()); + return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf(), 4); + } + + public static TextureGenerationOutput GenerateTexture(TextureGenerationSettings settings, NativeArray colorBuffer) + { + return GenerateTextureImpl(settings, colorBuffer.GetUnsafeReadOnlyPtr(), colorBuffer.Length * UnsafeUtility.SizeOf(), 16); } [NativeThrows] [NativeMethod("GenerateTextureScripting")] - extern static unsafe TextureGenerationOutput GenerateTextureImpl(TextureGenerationSettings settings, void* colorBuffer, int colorBufferLength); + extern static unsafe TextureGenerationOutput GenerateTextureImpl(TextureGenerationSettings settings, void* colorBuffer, int colorBufferLength, int bytesPerPixel); } } diff --git a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs index 77c87e4adf..e304c3437b 100644 --- a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs @@ -17,11 +17,12 @@ namespace UnityEditor [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporter.h")] [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporter.deprecated.h")] [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporterUtils.h")] + [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/TextureImporterPlatformSettingsUtils.h")] [NativeHeader("Editor/Src/EditorUserBuildSettings.h")] public sealed partial class TextureImporter : AssetImporter { [FreeFunction] - private static extern string GetFixedPlatformName(string platform); + internal static extern string GetTexturePlatformSerializationName(string platformName); [Obsolete("textureFormat is no longer accessible at the TextureImporter level. For old 'simple' formats use the textureCompression property for the equivalent automatic choice (Uncompressed for TrueColor, Compressed and HQCommpressed for 16 bits). For platform specific formats use the [[PlatformTextureSettings]] API. Using this setter will setup various parameters to match the new automatic system as well as possible. Getter will return the last value set.")] public extern TextureImporterFormat textureFormat @@ -103,8 +104,7 @@ public bool GetPlatformTextureSettings(string platform, out int maxTextureSize, // public API will always return a valid TextureImporterPlatformSettings, creating it based on the default one if it did not exist. public TextureImporterPlatformSettings GetPlatformTextureSettings(string platform) { - // make sure we are converting the settings to use the proper BuildTargetGroupName to get them (the way it works on other importers) - platform = GetFixedPlatformName(platform); + platform = GetTexturePlatformSerializationName(platform); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". TextureImporterPlatformSettings dest = GetPlatformTextureSetting_Internal(platform); if (platform != dest.name) @@ -122,27 +122,24 @@ public TextureImporterPlatformSettings GetDefaultPlatformTextureSettings() public TextureImporterFormat GetAutomaticFormat(string platform) { - platform = GetFixedPlatformName(platform); + platform = GetTexturePlatformSerializationName(platform); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". TextureImporterSettings settings = new TextureImporterSettings(); ReadTextureSettings(settings); TextureImporterPlatformSettings platformSettings = GetPlatformTextureSettings(platform); - List validPlatforms = BuildPlatforms.instance.GetValidPlatforms(); - foreach (BuildPlatform bp in validPlatforms) + BuildTarget buildTarget = BuildPipeline.GetBuildTargetByName(platform); + if (buildTarget != BuildTarget.NoTarget) { - if (bp.name == platform) - { - return DefaultFormatFromTextureParameters(settings, - !platformSettings.overridden ? GetDefaultPlatformTextureSettings() : platformSettings, - DoesSourceTextureHaveAlpha(), - IsSourceTextureHDR(), - bp.defaultTarget); - - // Regarding the "GetDefaultPlatformTextureSettings" call: in case 1281084, we made it so that platform settings stop automatically - // resetting to the default platform's settings when the platform override is disabled. This introduced a regression where - // "GetAutomaticFormat" would not return the actual format used by platforms with a disabled override, (as in, the one indicated in - // the default platform's settings) which is why we pass in the default platform's settings instead. - } + return DefaultFormatFromTextureParameters(settings, + !platformSettings.overridden ? GetDefaultPlatformTextureSettings() : platformSettings, + DoesSourceTextureHaveAlpha(), + IsSourceTextureHDR(), + buildTarget); + + // Regarding the "GetDefaultPlatformTextureSettings" call: in case 1281084, we made it so that platform settings stop automatically + // resetting to the default platform's settings when the platform override is disabled. This introduced a regression where + // "GetAutomaticFormat" would not return the actual format used by platforms with a disabled override, (as in, the one indicated in + // the default platform's settings) which is why we pass in the default platform's settings instead. } return TextureImporterFormat.Automatic; @@ -183,8 +180,7 @@ public void SetPlatformTextureSettings(string platform, int maxTextureSize, Text // Set specific target platform settings public void SetPlatformTextureSettings(TextureImporterPlatformSettings platformSettings) { - // we need to fix the name in case the user changed it to some mismatching value - platformSettings.name = GetFixedPlatformName(platformSettings.name); + platformSettings.name = GetTexturePlatformSerializationName(platformSettings.name); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". SetPlatformTextureSettings_Internal(platformSettings); } @@ -194,7 +190,8 @@ public void SetPlatformTextureSettings(TextureImporterPlatformSettings platformS public void ClearPlatformTextureSettings(string platform) { - ClearPlatformTextureSettings_Internal(GetFixedPlatformName(platform)); + platform = GetTexturePlatformSerializationName(platform); // String may refer to a platform group: if != "Standalone", ensure it refers to a platform instead. E.g.: "iOS", not "iPhone". + ClearPlatformTextureSettings_Internal(platform); } [FreeFunction] @@ -452,9 +449,30 @@ public void ReadTextureSettings(TextureImporterSettings dest) // Set texture importers settings from [[TextureImporterSettings]] class. public void SetTextureSettings(TextureImporterSettings src) { + ValidateAndCorrectTextureImporterSettings(src); settings = src; } + private void ValidateAndCorrectTextureImporterSettings(TextureImporterSettings m_Settings) + { + switch (m_Settings.textureType) + { + case TextureImporterType.Sprite: + m_Settings.npotScale = ValidateAndCorrectSetting(m_Settings.npotScale, TextureImporterNPOTScale.None, nameof(m_Settings.npotScale)); + break; + } + } + + private T ValidateAndCorrectSetting(T actual, T expected, string settingName) + { + if (!actual.Equals(expected)) + { + Debug.LogWarning($"You cannot set {settingName} to {actual} for this texture type. It has been reset to {expected}."); + return expected; + } + return actual; + } + private extern TextureImporterSettings settings { get; set; } [NativeName("GetImportInspectorWarning")] diff --git a/Editor/Mono/AssetPipeline/TextureImporterEnums.cs b/Editor/Mono/AssetPipeline/TextureImporterEnums.cs index ec3a8245d1..67aab13541 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterEnums.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterEnums.cs @@ -3,6 +3,7 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.ComponentModel; using UnityEngine.Scripting; namespace UnityEditor @@ -119,13 +120,22 @@ public enum TextureImporterFormat DXT5Crunched = 29, // PowerVR (iPhone) 2 bits/pixel compressed color texture format. + [System.Obsolete("Texture compression format PVRTC has been deprecated and will be removed in a future release")] + [EditorBrowsable(EditorBrowsableState.Never)] PVRTC_RGB2 = 30, // PowerVR (iPhone) 2 bits/pixel compressed with alpha channel texture format. + [System.Obsolete("Texture compression format PVRTC has been deprecated and will be removed in a future release")] + [EditorBrowsable(EditorBrowsableState.Never)] PVRTC_RGBA2 = 31, // PowerVR (iPhone) 4 bits/pixel compressed color texture format. + [System.Obsolete("Texture compression format PVRTC has been deprecated and will be removed in a future release")] + [EditorBrowsable(EditorBrowsableState.Never)] PVRTC_RGB4 = 32, // PowerVR (iPhone) 4 bits/pixel compressed with alpha channel texture format. + [System.Obsolete("Texture compression format PVRTC has been deprecated and will be removed in a future release")] + [EditorBrowsable(EditorBrowsableState.Never)] PVRTC_RGBA4 = 33, + // ETC (GLES2.0) 4 bits/pixel compressed RGB texture format. ETC_RGB4 = 34, diff --git a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs index 9132f3c1ab..a282894dd5 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs @@ -18,6 +18,7 @@ public struct SpriteMetaData public int alignment; public Vector2 pivot; public Vector4 border; + internal string customData; } // Note: MUST match memory layout of TextureImporterSettings in TextureImporter.h! diff --git a/Editor/Mono/AssetPreviewUpdater.cs b/Editor/Mono/AssetPreviewUpdater.cs index 9ab79cf291..c58d13d430 100644 --- a/Editor/Mono/AssetPreviewUpdater.cs +++ b/Editor/Mono/AssetPreviewUpdater.cs @@ -3,8 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using UnityEngine; -using UnityEngine.Scripting; using UnityEngine.Rendering; +using UnityEngine.Scripting; namespace UnityEditor { @@ -19,17 +19,11 @@ public static Texture2D CreatePreviewForAsset(Object obj, Object[] subAssets, st // Generate a preview texture for an asset public static Texture2D CreatePreview(Object obj, Object[] subAssets, string assetPath, int width, int height) { - if (obj == null) - return null; - - if (!RenderPipelineManager.pipelineSwitchCompleted) - return null; - - System.Type type = CustomEditorAttributes.FindCustomEditorType(obj, false); + var type = CustomEditorAttributes.FindCustomEditorType(obj, false); if (type == null) return null; - System.Reflection.MethodInfo info = type.GetMethod("RenderStaticPreview"); + var info = type.GetMethod("RenderStaticPreview"); if (info == null) { Debug.LogError("Fail to find RenderStaticPreview base method"); @@ -39,13 +33,11 @@ public static Texture2D CreatePreview(Object obj, Object[] subAssets, string ass if (info.DeclaringType == typeof(Editor)) return null; - - Editor editor = Editor.CreateEditor(obj); - + var editor = Editor.CreateEditor(obj); if (editor == null) return null; - - Texture2D tex = editor.RenderStaticPreview(assetPath, subAssets, width, height); + + var previewTexture = editor.RenderStaticPreview(assetPath, subAssets, width, height); // For debugging we write the preview to a file (keep) //{ @@ -56,8 +48,7 @@ public static Texture2D CreatePreview(Object obj, Object[] subAssets, string ass //} Object.DestroyImmediate(editor); - - return tex; + return previewTexture; } } } diff --git a/Editor/Mono/AssetStore/AssetStoreAsset.cs b/Editor/Mono/AssetStore/AssetStoreAsset.cs index 137b5b3546..3b893e6003 100644 --- a/Editor/Mono/AssetStore/AssetStoreAsset.cs +++ b/Editor/Mono/AssetStore/AssetStoreAsset.cs @@ -681,7 +681,7 @@ private static string intToSizeString(int inValue) if (idx < 0) return ""; - return UnityString.Format("{0:#.##} {1}", val, scale[idx]); + return string.Format("{0:#.##} {1}", val, scale[idx]); } public override bool HasPreviewGUI() diff --git a/Editor/Mono/AssetStore/AssetStorePreviewManager.cs b/Editor/Mono/AssetStore/AssetStorePreviewManager.cs index f7579935f2..30fa6b3c6d 100644 --- a/Editor/Mono/AssetStore/AssetStorePreviewManager.cs +++ b/Editor/Mono/AssetStore/AssetStorePreviewManager.cs @@ -107,7 +107,7 @@ public static int Downloading public static string StatsString() { - return string.Format("Reqs: {0}, Ok: {1}, Abort: {2}, CacheDel: {3}, Cache: {4}/{5}, CacheHit: {6}", + return string.Format("Reqs: {0}, OK: {1}, Abort: {2}, CacheDel: {3}, Cache: {4}/{5}, CacheHit: {6}", Instance.Requested, Instance.m_Success, Instance.m_Aborted, Instance.m_CacheRemove, AssetStorePreviewManager.CachedAssetStoreImages.Count, Instance.m_MaxCachedAssetStoreImages, diff --git a/Editor/Mono/AssetStore/AssetStoreWindow.cs b/Editor/Mono/AssetStore/AssetStoreWindow.cs index b497413d8a..94f781af9e 100644 --- a/Editor/Mono/AssetStore/AssetStoreWindow.cs +++ b/Editor/Mono/AssetStore/AssetStoreWindow.cs @@ -29,7 +29,7 @@ public static AssetStoreWindow Init() } } - [MenuItem("Window/Asset Store", false, 1497)] + [MenuItem("Window/Package Management/Asset Store", false, 2000)] public static void OpenAssetStoreInBrowser() { string assetStoreUrl = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudAssetStoreUrl); @@ -39,10 +39,10 @@ public static void OpenAssetStoreInBrowser() else Application.OpenURL(assetStoreUrl); } - [MenuItem("Window/My Assets", false, 1498)] + [MenuItem("Window/Package Management/My Assets", false, 1501)] public static void OpenMyAssetsInPackageManager() { - PackageManagerWindow.SelectPackageAndPageStatic(pageId: PackageManager.UI.Internal.MyAssetsPage.k_Id); + PackageManagerWindow.OpenAndSelectPage(PackageManager.UI.Internal.MyAssetsPage.k_Id); } public void OnEnable() @@ -100,7 +100,7 @@ private void OnVisitWebsiteButtonClicked() private void OnLaunchPackageManagerButtonClicked() { - PackageManagerWindow.OpenPackageManager(null); + PackageManagerWindow.OpenAndSelectPackage(null); } private void SetMinMaxSizes() diff --git a/Editor/Mono/AssetStoreCachePathManager.cs b/Editor/Mono/AssetStoreCachePathManager.cs index 6cf4345d18..31fba1756c 100644 --- a/Editor/Mono/AssetStoreCachePathManager.cs +++ b/Editor/Mono/AssetStoreCachePathManager.cs @@ -19,7 +19,7 @@ public enum ConfigStatus InvalidPath, ReadOnly, EnvironmentOverride, - NotFound, + Default, Failed }; diff --git a/Editor/Mono/AssetsMenuUtility.bindings.cs b/Editor/Mono/AssetsMenuUtility.bindings.cs index f70de369da..b7c1f404ed 100644 --- a/Editor/Mono/AssetsMenuUtility.bindings.cs +++ b/Editor/Mono/AssetsMenuUtility.bindings.cs @@ -8,22 +8,24 @@ namespace UnityEditor { internal enum ScriptTemplate { - CSharp_NewSceneTemplatePipelineScript = 0, - CSharp_NewBehaviourScript, - CSharp_NewTestScript, - Shader_NewSurfaceShader, - Shader_NewUnlitShader, - Shader_NewImageEffectShader, - CSharp_NewStateMachineBehaviourScript, - CSharp_NewSubStateMachineBehaviourScript, + CSharp_NewBehaviourScript = 0, CSharp_NewPlayableBehaviour, + Shader_NewSurfaceShader, + CSharp_NewSceneTemplatePipelineScript, CSharp_NewPlayableAsset, - Shader_NewComputeShader, - AsmDef_NewAssembly, + CSharp_NewScriptableObjectScript, + Shader_NewUnlitShader, AsmDef_NewEditModeTestAssembly, AsmDef_NewTestAssembly, + AsmDef_NewAssembly, AsmRef_NewAssemblyReference, + CSharp_NewEmptyScript, + CSharp_NewTestScript, + Shader_NewImageEffectShader, + Shader_NewComputeShader, Shader_NewRayTracingShader, + CSharp_NewStateMachineBehaviourScript, + CSharp_NewSubStateMachineBehaviourScript, Count } diff --git a/Editor/Mono/AttributeHelper.cs b/Editor/Mono/AttributeHelper.cs index 95d72e45d3..2719d77a50 100644 --- a/Editor/Mono/AttributeHelper.cs +++ b/Editor/Mono/AttributeHelper.cs @@ -112,7 +112,7 @@ static MonoGizmoMethod[] ExtractGizmos(Assembly assembly) } [RequiredByNativeCode] - static string GetComponentMenuName(Type type) + static object GetComponentMenuName(Type type) { var attrs = type.GetCustomAttributes(typeof(AddComponentMenu), false); if (attrs.Length > 0) diff --git a/Editor/Mono/Audio/AudioContainerListDragAndDropManipulator.cs b/Editor/Mono/Audio/AudioContainerListDragAndDropManipulator.cs index 441cc93852..67f85fa2b5 100644 --- a/Editor/Mono/Audio/AudioContainerListDragAndDropManipulator.cs +++ b/Editor/Mono/Audio/AudioContainerListDragAndDropManipulator.cs @@ -28,11 +28,11 @@ protected override void RegisterCallbacksOnTarget() protected override void UnregisterCallbacksFromTarget() { - target.UnregisterCallback(OnDragUpdate); - target.UnregisterCallback(OnDragPerform); + target?.UnregisterCallback(OnDragUpdate); + target?.UnregisterCallback(OnDragPerform); } - void OnDragUpdate(DragUpdatedEvent _) + static void OnDragUpdate(DragUpdatedEvent _) { DragAndDrop.visualMode = DragAndDropVisualMode.Generic; } @@ -44,13 +44,15 @@ void OnDragPerform(DragPerformEvent evt) foreach (var path in DragAndDrop.paths) { var audioClip = AssetDatabase.LoadAssetAtPath(path); - if (audioClip != null) audioClips.Add(audioClip); + + if (audioClip != null) + audioClips.Add(audioClip); } if (audioClips.Count > 0) { DragAndDrop.AcceptDrag(); - addAudioClipsDelegate(audioClips); + addAudioClipsDelegate?.Invoke(audioClips); } } } diff --git a/Editor/Mono/Audio/AudioContainerWindow.cs b/Editor/Mono/Audio/AudioContainerWindow.cs index 83f97cda1e..03355b2a14 100644 --- a/Editor/Mono/Audio/AudioContainerWindow.cs +++ b/Editor/Mono/Audio/AudioContainerWindow.cs @@ -7,16 +7,28 @@ using System.Linq; using UnityEditor.Audio; using UnityEditor.Audio.UIElements; +using UnityEditor.ShortcutManagement; using UnityEditor.UIElements; using UnityEngine; +using UnityEngine.Assertions; using UnityEngine.Audio; using UnityEngine.Scripting; using UnityEngine.UIElements; +using Object = UnityEngine.Object; namespace UnityEditor; sealed class AudioContainerWindow : EditorWindow { + enum Icons + { + Play = 0, + Stop = 1, + Skip = 2, + DiceOff = 3, + DiceOn = 4 + } + /// /// The cached instance of the window, if it is open. /// @@ -24,13 +36,21 @@ sealed class AudioContainerWindow : EditorWindow internal readonly AudioContainerWindowState State = new(); + /// + /// Holds the added list elements in the list interaction callbacks. + /// Only used locally in these methods, but it's a global member to avoid GC. + /// + readonly List m_AddedElements = new(); + readonly string k_EmptyGuidString = Guid.Empty.ToString("N"); + readonly Texture2D[] k_IconTextureCache = new Texture2D[Enum.GetNames(typeof(Icons)).Length]; + VisualElement m_ContainerRootVisualElement; VisualElement m_Day0RootVisualElement; // Preview section Label m_AssetNameLabel; - Button m_PlayButton; - VisualElement m_PlayButtonImage; + Button m_PlayStopButton; + VisualElement m_PlayStopButtonImage; Button m_SkipButton; VisualElement m_SkipButtonImage; @@ -80,12 +100,16 @@ sealed class AudioContainerWindow : EditorWindow Label m_AutomaticTriggerModeLabel; Label m_LoopLabel; - // Shared icon references - Texture2D m_DiceIconOff; - Texture2D m_DiceIconOn; - + bool m_IsVisible; bool m_IsInitializing; + bool m_Day0ElementsInitialized; + bool m_ContainerElementsInitialized; + bool m_IsSubscribedToGUICallbacksAndEvents; + bool m_ClipFieldProgressBarsAreCleared = true; + /// + /// Holds the previous state of the list elements for undo/delete housekeeping + /// List m_CachedElements = new(); [RequiredByNativeCode] @@ -95,12 +119,10 @@ internal static void CreateAudioRandomContainerWindow() window.Show(); } - /// - /// Updates the state, which will implicitly refresh the window content if needed. - /// - internal void Refresh() + // Used by tests. + internal bool IsInitializedForTargetDisplay() { - State.UpdateTarget(); + return m_ContainerElementsInitialized && m_IsSubscribedToGUICallbacksAndEvents; } static void OnCreateButtonClicked() @@ -110,71 +132,70 @@ static void OnCreateButtonClicked() void OnEnable() { - Instance = this; - State.TargetChanged += OnTargetChanged; - State.TransportStateChanged += OnTransportStateChanged; - State.EditorPauseStateChanged += EditorPauseStateChanged; - m_DiceIconOff = EditorGUIUtility.IconContent("AudioRandomContainer On Icon").image as Texture2D; - m_DiceIconOn = EditorGUIUtility.IconContent("AudioRandomContainer Icon").image as Texture2D; - SetTitle(State.IsDirty()); + if (Instance == null) + { + Instance = this; + } + + SetTitle(); } void OnDisable() { - Instance = null; - - if (m_PlayButton != null) - m_PlayButton.clicked -= OnPlayStopButtonClicked; - - if (m_SkipButton != null) - m_SkipButton.clicked -= OnSkipButtonClicked; - - m_VolumeRandomizationRangeSlider?.UnregisterValueChangedCallback(OnVolumeRandomizationRangeChanged); - m_VolumeRandomizationRangeField?.UnregisterValueChangedCallback(OnVolumeRandomizationRangeChanged); - - m_PitchRandomizationRangeSlider?.UnregisterValueChangedCallback(OnPitchRandomizationRangeChanged); - m_PitchRandomizationRangeField?.UnregisterValueChangedCallback(OnPitchRandomizationRangeChanged); - - m_TimeRandomizationRangeSlider?.UnregisterValueChangedCallback(OnTimeRandomizationRangeChanged); - m_TimeRandomizationRangeField?.UnregisterValueChangedCallback(OnTimeRandomizationRangeChanged); - - State.TargetChanged -= OnTargetChanged; - State.TransportStateChanged -= OnTransportStateChanged; - State.EditorPauseStateChanged -= EditorPauseStateChanged; State.OnDestroy(); - + UnsubscribeFromGUICallbacksAndEvents(); + m_IsInitializing = false; + m_Day0ElementsInitialized = false; + m_ContainerElementsInitialized = false; m_CachedElements.Clear(); + m_AddedElements.Clear(); + + if (Instance == this) + { + Instance = null; + } } - void Update() + void OnFocus() { - UpdateClipFieldProgressBars(); + if (State.AudioContainer != null) + { + UpdateTransportButtonStates(); + } + } - if (m_Meter == null) + void Update() + { + if (!m_IsVisible) return; - if (State != null) - m_Meter.Value = State.GetMeterValue(); - else - m_Meter.Value = -80.0f; - } + if (State.IsPreviewPlayingOrPaused()) { UpdateClipFieldProgressBars(); } + else if (!m_ClipFieldProgressBarsAreCleared) { ClearClipFieldProgressBars(); } - void OnBecameInvisible() - { - State.Stop(); - ClearClipFieldProgressBars(); + if (m_Meter != null) + { + if (State.IsPreviewPlayingOrPaused()) + { + if (State != null) { m_Meter.Value = State.GetMeterValue(); } + else { m_Meter.Value = -80.0f; } + } + else + { + if (m_Meter.Value != -80.0f) { m_Meter.Value = -80.0f; } + } + } } - void SetTitle(bool targetIsDirty) + void SetTitle() { var titleString = "Audio Random Container"; - if (targetIsDirty) + if (State.IsDirty()) titleString += "*"; titleContent = new GUIContent(titleString) { - image = m_DiceIconOff + image = GetIconTexture(Icons.DiceOff) }; } @@ -186,28 +207,59 @@ void CreateGUI() return; m_IsInitializing = true; - rootVisualElement.Clear(); - var rootAsset = UIToolkitUtilities.LoadUxml("UXML/Audio/AudioRandomContainer.uxml"); - rootAsset.CloneTree(rootVisualElement); + var root = rootVisualElement; - var styleSheet = UIToolkitUtilities.LoadStyleSheet("StyleSheets/Audio/AudioRandomContainer.uss"); - rootVisualElement.styleSheets.Add(styleSheet); + if (root.childCount == 0) + { + var rootAsset = UIToolkitUtilities.LoadUxml("UXML/Audio/AudioRandomContainer.uxml"); + Assert.IsNotNull(rootAsset); + rootAsset.CloneTree(root); + + var styleSheet = UIToolkitUtilities.LoadStyleSheet("StyleSheets/Audio/AudioRandomContainer.uss"); + Assert.IsNotNull(styleSheet); + root.styleSheets.Add(styleSheet); + root.Add(State.GetResourceTrackerElement()); + + m_ContainerRootVisualElement = UIToolkitUtilities.GetChildByName(root, "ARC_ScrollView"); + Assert.IsNotNull(m_ContainerRootVisualElement); + m_Day0RootVisualElement = UIToolkitUtilities.GetChildByName(root, "Day0"); + Assert.IsNotNull(m_Day0RootVisualElement); + } - m_ContainerRootVisualElement = UIToolkitUtilities.GetChildByName(rootVisualElement, "ARC_ScrollView"); - m_Day0RootVisualElement = UIToolkitUtilities.GetChildByName(rootVisualElement, "Day0"); + if (m_ContainerElementsInitialized) + root.Unbind(); if (State.AudioContainer == null) { + if (!m_Day0ElementsInitialized) + { + InitializeDay0Elements(); + m_Day0ElementsInitialized = true; + } + + m_Day0RootVisualElement.style.display = DisplayStyle.Flex; m_ContainerRootVisualElement.style.display = DisplayStyle.None; - InitDay0GUI(); - return; } + else + { + if (!m_ContainerElementsInitialized) + { + InitializeContainerElements(); + m_ContainerElementsInitialized = true; + EditorApplication.update += OneTimeEditorApplicationUpdate; + } - if (m_Day0RootVisualElement != null) - m_Day0RootVisualElement.style.display = DisplayStyle.None; + if (!m_IsSubscribedToGUICallbacksAndEvents) + SubscribeToGUICallbacksAndEvents(); - InitAudioRandomContainerGUI(); + BindAndTrackObjectAndProperties(); + + m_Day0RootVisualElement.style.display = DisplayStyle.None; + m_ContainerRootVisualElement.style.display = DisplayStyle.Flex; + m_CachedElements = State.AudioContainer.elements.ToList(); + m_ClipsListView.Rebuild(); // Force a list rebuild when the list has changed or it will not always render correctly due to a UI toolkit bug. + } } finally { @@ -215,42 +267,93 @@ void CreateGUI() } } - void InitDay0GUI() + bool IsDisplayingTarget() + { + return + m_Day0RootVisualElement != null + && m_Day0RootVisualElement.style.display == DisplayStyle.None + && m_ContainerRootVisualElement != null + && m_ContainerRootVisualElement.style.display == DisplayStyle.Flex; + } + + void InitializeDay0Elements() { - var day0Element = UIToolkitUtilities.GetChildByName(rootVisualElement, "Day0"); - var createButtonLabel = UIToolkitUtilities.GetChildByName