// Perfect Culling (C) 2021 Patrick König // using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Koenigz.PerfectCulling; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; namespace Koenigz.PerfectCulling { public static class PerfectCullingBakingManager { #pragma warning disable 0414 public static bool IsBaking => m_activeBake != null; private static IEnumerator m_activeBake = null; private static PerfectCullingBakingBehaviour m_activeBakingBehaviour = null; private static readonly Queue m_scheduledBakes = new Queue(); private static bool m_cancelActive = false; #pragma warning restore 0414 /// /// Schedules a bake but doesn't perform the bake yet. Call BakeAllScheduled to start the baking process for all scheduled bakes. /// /// Information about the bake to schedule public static void ScheduleBake(BakeInformation bakeInformation) { m_scheduledBakes.Enqueue(bakeInformation); } /// /// Starts to bake multiple baking behaviours immediately. /// /// All baking behaviours to bake /// Additional occluders public static void BakeNow(PerfectCullingBakingBehaviour[] cullingBakingBehaviours, HashSet additionalOccluders = null, bool cullAdditionalOccluders = true) { m_scheduledBakes.Clear(); HashSet bakeDatas = new HashSet(); foreach (PerfectCullingBakingBehaviour bakingBehaviour in cullingBakingBehaviours) { if (bakingBehaviour.BakeData == null) { continue; } // Only want to bake once if (bakeDatas.Add(bakingBehaviour.BakeData)) { ScheduleBake(new BakeInformation() { BakingBehaviour = bakingBehaviour, AdditionalOccluders = additionalOccluders, CullAdditionalOccluders = cullAdditionalOccluders, }); } } BakeAllScheduled(); } /// /// Starts to bake a single baking behaviour immediately. /// /// Baking behaviour to bake /// Additional occluders public static void BakeNow(PerfectCullingBakingBehaviour bakingBehaviour, HashSet additionalOccluders = null, bool cullAdditionalOccluders = true) { m_scheduledBakes.Clear(); ScheduleBake(new BakeInformation() { BakingBehaviour = bakingBehaviour, AdditionalOccluders = additionalOccluders, CullAdditionalOccluders = cullAdditionalOccluders, }); BakeAllScheduled(); } public static string _currentScenePath; public static void VerifyCurrentScenePath(string currentScenePath) { #if UNITY_EDITOR if (UnityEditor.AssetDatabase.LoadAssetAtPath(_currentScenePath) == null) { _currentScenePath = currentScenePath; } #endif } /// /// Starts to bake all scheduled bakes. /// public static void BakeAllScheduled() { #if UNITY_EDITOR m_cancelActive = false; if (m_scheduledBakes.Count <= 0) { PerfectCullingLogger.LogError("Nothing to bake."); return; } _currentScenePath = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().path; UnityEditor.EditorApplication.update += EditorUpdate; BakeInformation bakeInformation = m_scheduledBakes.Dequeue(); m_activeBakingBehaviour = bakeInformation.BakingBehaviour; m_activeBake = m_activeBakingBehaviour.PerformBakeAsync(true, bakeInformation.AdditionalOccluders, bakeInformation.CullAdditionalOccluders); #endif } private class SceneState { public string ScenePath; public bool IsLoaded; } public static void BakeMultiScene(List scenes, bool populateAdditionalOccluders = true) { #if UNITY_EDITOR List sceneStates = new List(); List actualScenes = new List(); for (int i = 0; i < scenes.Count; i++) { Scene scene = scenes[i]; if (!scene.IsValid() || string.IsNullOrEmpty(scene.path)) { // Exclude scenes that are not already saved on disk such as the untitled scene continue; } sceneStates.Add(new SceneState() { IsLoaded = scene.isLoaded, ScenePath = scenes[i].path, }); actualScenes.Add(scene); } Scene[] relevantScenes = actualScenes.Where(x => x.isLoaded).ToArray(); if (relevantScenes.Length != actualScenes.Count) { Debug.LogWarning("Some scenes are excluded because they have not been loaded."); } if (relevantScenes.Length <= 0) { Debug.LogWarning("No scenes to bake."); return; } if (!PerfectCullingEditorUtil.SaveModifiedScenesIfUserWantsTo(relevantScenes)) { return; } for (int i = 1; i < relevantScenes.Length; i++) { UnityEditor.SceneManagement.EditorSceneManager.MergeScenes(relevantScenes[i], relevantScenes[0]); } UnityEditor.SceneManagement.EditorSceneManager.SaveScene(relevantScenes[0], PerfectCullingConstants.MultiSceneTempPath); UnityEditor.SceneManagement.EditorSceneManager.OpenScene(PerfectCullingConstants.MultiSceneTempPath, UnityEditor.SceneManagement.OpenSceneMode.Single); PerfectCullingBakingBehaviour[] bakingBehaviours = UnityEngine.Object.FindObjectsOfType(); HashSet renderers = new HashSet(); if (populateAdditionalOccluders) { foreach (var b in bakingBehaviours) { foreach (var g in b.bakeGroups) { foreach (var r in g.renderers) { renderers.Add(r); } } } } void OnMultiBakeFinished() { try { foreach (SceneState sceneState in sceneStates) { UnityEditor.SceneManagement.EditorSceneManager.OpenScene(sceneState.ScenePath, sceneState.IsLoaded ? UnityEditor.SceneManagement.OpenSceneMode.Additive : UnityEditor.SceneManagement.OpenSceneMode.AdditiveWithoutLoading); } UnityEditor.SceneManagement.EditorSceneManager.CloseScene( UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene(), true); UnityEditor.AssetDatabase.DeleteAsset(PerfectCullingConstants.MultiSceneTempPath); } finally { PerfectCullingAPI.Bake.OnAllBakesFinished -= OnMultiBakeFinished; } } PerfectCullingAPI.Bake.OnAllBakesFinished += OnMultiBakeFinished; PerfectCullingBakingManager.BakeNow(bakingBehaviours, renderers); #endif } private static void EditorUpdate() { #if UNITY_EDITOR bool needsSceneReload = true; // Null check to make sure we unsubscribe at the end if this is invalid. if (m_activeBake != null) { if (!m_cancelActive && m_activeBake.MoveNext()) { if (m_activeBake.Current is PerfectCullingBakeAbortedYieldInstruction) { PerfectCullingLogger.Log("Bake(s) aborted."); // Abort all scheduled bakes, too. m_scheduledBakes.Clear(); } else if (m_activeBake.Current is PerfectCullingBakeNotStartedYieldInstruction) { needsSceneReload = false; PerfectCullingLogger.Log("Bake was not started. Aborting all bakes."); // Abort all scheduled bakes, too. m_scheduledBakes.Clear(); } else { return; } } if (m_activeBake is IDisposable disposable) { disposable.Dispose(); } PerfectCullingAPI.Bake.OnBakeFinished?.Invoke(m_activeBakingBehaviour); if (m_scheduledBakes.Count > 0) { BakeInformation bakeInformation = m_scheduledBakes.Dequeue(); m_activeBakingBehaviour = bakeInformation.BakingBehaviour; m_activeBake = m_activeBakingBehaviour.PerformBakeAsync(false, bakeInformation.AdditionalOccluders, bakeInformation.CullAdditionalOccluders); return; } m_activeBake = null; m_activeBakingBehaviour = null; } UnityEditor.EditorApplication.update -= EditorUpdate; if (needsSceneReload && PerfectCullingConstants.AllowSceneReload) { UnityEditor.SceneManagement.EditorSceneManager.OpenScene(_currentScenePath); } PerfectCullingAPI.Bake.OnAllBakesFinished?.Invoke(); #endif } public static void CancelBakes() { m_scheduledBakes.Clear(); m_cancelActive = true; } } }