// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using System.Linq; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace Microsoft.MixedReality.Toolkit.SceneSystem { /// /// Configuration profile settings for setting up scene system. /// [CreateAssetMenu(menuName = "Mixed Reality/Toolkit/Profiles/Mixed Reality Scene System Profile", fileName = "MixedRealitySceneSystemProfile", order = (int)CreateProfileMenuItemIndices.SceneSystem)] [MixedRealityServiceProfile(typeof(IMixedRealitySceneSystem))] [HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/scene-system/scene-system-getting-started")] public class MixedRealitySceneSystemProfile : BaseMixedRealityProfile { /// /// Internal class used to cache lighting settings associated with a scene. /// [Serializable] internal sealed class CachedLightingSettings { public string SceneName; public RuntimeRenderSettings RenderSettings; public RuntimeLightingSettings LightingSettings; public RuntimeSunlightSettings SunlightSettings; public DateTime TimeStamp; } public bool UseManagerScene { get { return useManagerScene && !managerScene.IsEmpty; } } public bool UseLightingScene { get { return useLightingScene && lightingScenes.Count > 0; } } public SceneInfo ManagerScene => managerScene; public SceneInfo DefaultLightingScene { get { return lightingScenes[defaultLightingSceneIndex]; } } public IEnumerable LightingScenes { get { return lightingScenes; } } public IEnumerable ContentScenes { get { return contentScenes; } } public IEnumerable ContentTags { get { return contentTags; } } public int NumLightingScenes { get { return lightingScenes.Count; } } public int NumContentScenes { get { return contentScenes.Count; } } public IEnumerable PermittedLightingSceneComponentTypes { get { foreach (SystemType systemType in permittedLightingSceneComponentTypes) { yield return systemType.Type; } } } #if UNITY_EDITOR public bool EditorManageBuildSettings => editorManageBuildSettings; public bool EditorManageLoadedScenes => editorManageLoadedScenes; public bool EditorEnforceSceneOrder => editorEnforceSceneOrder; public bool EditorEnforceLightingSceneTypes => editorEnforceLightingSceneTypes; public bool EditorLightingCacheOutOfDate => editorLightingCacheOutOfDate; public bool EditorLightingCacheUpdateRequested { get; set; } #endif [SerializeField] private bool useManagerScene = true; [SerializeField] private SceneInfo managerScene = default(SceneInfo); [SerializeField] private bool useLightingScene = true; [SerializeField] private int defaultLightingSceneIndex = 0; [SerializeField] private List lightingScenes = new List(); [SerializeField] private List contentScenes = new List(); [SerializeField] private SystemType[] permittedLightingSceneComponentTypes = new SystemType[] { new SystemType(typeof(Transform)), new SystemType(typeof(GameObject)), new SystemType(typeof(Light)), new SystemType(typeof(ReflectionProbe)), new SystemType(typeof(LightProbeGroup)), new SystemType(typeof(LightProbeProxyVolume)), }; // These will be hidden by the default inspector. [SerializeField] [Tooltip("Cached content tags found in your content scenes")] private List contentTags = new List(); // These will be hidden by the default inspector. [SerializeField] [Tooltip("Cached lighting settings from your lighting scenes")] private List cachedLightingSettings = new List(); #region editor settings // CS414 is disabled during this section because these properties are being used in the editor // scenario - when this file is build for player scenario, these serialized fields still exist // but are not used. #pragma warning disable 414 [SerializeField] [Tooltip("If true, the service will update your build settings automatically, ensuring that all manager, lighting and content scenes are added. Disable this if you want total control over build settings.")] private bool editorManageBuildSettings = true; [SerializeField] [Tooltip("If true, the service will ensure manager scene is displayed first in scene hierarchy, followed by lighting and then content. Disable this if you want total control over scene hierarchy.")] private bool editorEnforceSceneOrder = true; [SerializeField] [Tooltip("If true, service will ensure that manager scenes and lighting scenes are always loaded. Disable if you want total control over which scenes are loaded in editor.")] private bool editorManageLoadedScenes = true; [SerializeField] [Tooltip("If true, service will ensure that only lighting-related components are allowed in lighting scenes. Disable if you want total control over the content of lighting scenes.")] private bool editorEnforceLightingSceneTypes = true; [SerializeField] private bool editorLightingCacheOutOfDate = false; #pragma warning restore 414 #endregion public bool GetLightingSceneSettings( string lightingSceneName, out SceneInfo lightingScene, out RuntimeLightingSettings lightingSettings, out RuntimeRenderSettings renderSettings, out RuntimeSunlightSettings sunlightSettings) { lightingSettings = default(RuntimeLightingSettings); renderSettings = default(RuntimeRenderSettings); sunlightSettings = default(RuntimeSunlightSettings); lightingScene = SceneInfo.Empty; for (int i = 0; i < lightingScenes.Count; i++) { if (lightingScenes[i].Name == lightingSceneName) { lightingScene = lightingScenes[i]; break; } } if (lightingScene.IsEmpty) { // If we didn't find a lighting scene, don't bother looking for a cache return false; } bool foundCache = false; for (int i = 0; i < cachedLightingSettings.Count; i++) { CachedLightingSettings cache = cachedLightingSettings[i]; if (cache.SceneName == lightingSceneName) { lightingSettings = cache.LightingSettings; renderSettings = cache.RenderSettings; sunlightSettings = cache.SunlightSettings; foundCache = true; break; } } return foundCache; } public IEnumerable GetContentSceneNamesByTag(string tag) { foreach (SceneInfo contentScene in contentScenes) { if (contentScene.Tag == tag) yield return contentScene.Name; } } #if UNITY_EDITOR #region validation private void OnValidate() { if (Application.isPlaying || EditorApplication.isCompiling) { return; } bool saveChanges = false; // Remove any duplicate entries from our lighting and content scene lists saveChanges |= (RemoveOrClearDuplicateEntries(lightingScenes) || RemoveOrClearDuplicateEntries(contentScenes)); // Ensure that manager scenes are not contained in content or lighting scenes saveChanges |= (UseManagerScene && (RemoveScene(lightingScenes, managerScene) || RemoveScene(contentScenes, managerScene))); // Ensure that content scenes are not included in lighting scenes saveChanges |= (UseLightingScene && RemoveScenes(lightingScenes, contentScenes)); // Build our content tags List newContentTags = new List(); foreach (SceneInfo contentScene in contentScenes) { if (string.IsNullOrEmpty(contentScene.Tag)) { continue; } if (contentScene.Tag == "Untagged") { continue; } if (!newContentTags.Contains(contentScene.Tag)) { newContentTags.Add(contentScene.Tag); } } // See if our content tags have changed if (!contentTags.SequenceEqual(newContentTags)) { contentTags = newContentTags; saveChanges = true; } defaultLightingSceneIndex = Mathf.Clamp(defaultLightingSceneIndex, 0, lightingScenes.Count - 1); if (saveChanges) { // We need to tie this directly to lighting scenes somehow editorLightingCacheOutOfDate = true; // Make sure our changes are saved to disk! AssetDatabase.Refresh(); EditorUtility.SetDirty(this); AssetDatabase.SaveAssets(); } } /// /// Clears cached lighting settings. /// Used to ensure we don't end up with 'dead' cached data. /// public void ClearLightingCache() { cachedLightingSettings.Clear(); } /// /// Used to update the cached lighting / render settings. /// Since extracting them is complex and requires scene loading, I thought it best to avoid having the profile do it. /// /// The scene these settings belong to. public void SetLightingCache(SceneInfo sceneInfo, RuntimeLightingSettings lightingSettings, RuntimeRenderSettings renderSettings, RuntimeSunlightSettings sunlightSettings) { CachedLightingSettings settings = new CachedLightingSettings(); settings.SceneName = sceneInfo.Name; settings.LightingSettings = lightingSettings; settings.RenderSettings = renderSettings; settings.SunlightSettings = sunlightSettings; settings.TimeStamp = DateTime.Now; cachedLightingSettings.Add(settings); editorLightingCacheOutOfDate = false; } /// /// Sets editorLightingCacheOutOfDate to true and saves the profile. /// public void SetLightingCacheDirty() { editorLightingCacheOutOfDate = true; AssetDatabase.Refresh(); EditorUtility.SetDirty(this); AssetDatabase.SaveAssets(); } public DateTime GetEarliestLightingCacheTimestamp() { if (cachedLightingSettings.Count <= 0) { return DateTime.MinValue; } DateTime earliestTimeStamp = DateTime.MaxValue; foreach (CachedLightingSettings settings in cachedLightingSettings) { if (settings.TimeStamp < earliestTimeStamp) { earliestTimeStamp = settings.TimeStamp; } } return earliestTimeStamp; } private static bool RemoveScenes(List sceneList, List scenesToRemove) { bool changed = false; for (int i = sceneList.Count - 1; i >= 0; i--) { if (sceneList[i].IsEmpty) { continue; } foreach (SceneInfo sceneToRemove in scenesToRemove) { if (sceneToRemove.IsEmpty) { continue; } if (sceneList[i].Asset == sceneToRemove.Asset) { Debug.LogWarning("Removing scene " + sceneToRemove.Name + " from scene list."); sceneList[i] = SceneInfo.Empty; changed = true; break; } } } return changed; } private static bool RemoveScene(List sceneList, SceneInfo sceneToRemove) { bool changed = false; for (int i = sceneList.Count - 1; i >= 0; i--) { if (sceneList[i].IsEmpty) { continue; } if (sceneList[i].Asset == sceneToRemove.Asset) { Debug.LogWarning("Removing manager scene " + sceneToRemove.Name + " from scene list."); sceneList[i] = SceneInfo.Empty; changed = true; } } return changed; } private static bool RemoveOrClearDuplicateEntries(List sceneList) { HashSet scenePaths = new HashSet(); bool changed = false; for (int i = 0; i < sceneList.Count; i++) { if (sceneList[i].IsEmpty) { continue; } if (!scenePaths.Add(sceneList[i].Path)) { // If we encounter a duplicate, just set it to empty. // This will ensure we don't get duplicates when we add new elements to the array. Debug.LogWarning("Found duplicate entry in scene list at " + i + ", removing"); sceneList[i] = SceneInfo.Empty; changed = true; } } return changed; } #endregion #endif } }