using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; #endif using UnityEngine; using UnityEngine.SceneManagement; namespace Gaia { public enum CenterSceneViewLoadingOn { SceneViewCamera, WorldOrigin } public enum CacheSizePreset { _Off, _1GB, _2GB, _3GB, _4GB, _5GB, _6GB, _7GB, _8GB, Custom } public class TerrainLoaderManager : MonoBehaviour { /// /// Loading Bounds around the world origin - controlled directly in the scene view and displays the part of the world the user wants to edit. /// Use Get/SetLoadingRange and Get/SetOrigin to access /// [SerializeField] private BoundsDouble m_sceneViewOriginLoadingBounds = new BoundsDouble(Vector3Double.zero, new Vector3Double(500f, 500f, 500f)); [SerializeField] private BoundsDouble m_sceneViewImpostorLoadingBounds = new BoundsDouble(Vector3Double.zero, new Vector3Double(500f, 500f, 500f)); [SerializeField] private BoundsDouble m_sceneViewCameraLoadingBounds = new BoundsDouble(Vector3Double.zero, new Vector3Double(500f, 500f, 500f)); [SerializeField] private CenterSceneViewLoadingOn m_centerSceneViewLoadingOn = CenterSceneViewLoadingOn.WorldOrigin; #if GAIA_PRO_PRESENT public List m_allFloatingPointFixMembers = new List(); public List m_allWorldSpaceParticleSystems = new List(); public GaiaLoadingScreen m_loadingScreen; #endif public int m_originTargetTileX; public int m_originTargetTileZ; public bool m_showOriginLoadingBounds; public bool m_showOriginTerrainBoxes; public CacheSizePreset m_cacheMemoryThresholdPreset = CacheSizePreset._4GB; public long m_cacheMemoryThreshold = 4294967296; public long m_cacheKeepAliveTime = 300000; public bool m_cacheInRuntime = true; public bool m_cacheInEditor = true; public int m_terrainLoadingTresholdMS = 100; public long m_lastTerrainLoadedTimeStamp = 0; public long m_trackLoadingProgressTimeOut = 10000; private List m_terrainSceneActionQueue = new List(); private static TerrainLoaderManager instance = null; public static TerrainLoaderManager Instance { get { if (instance == null) { instance = GaiaUtils.GetTerrainLoaderManagerObject().GetComponent(); } return instance; } } [SerializeField] private TerrainSceneStorage m_terrainSceneStorage; public TerrainSceneStorage TerrainSceneStorage { get { if (m_terrainSceneStorage == null) { LoadStorageData(); } return m_terrainSceneStorage; } set { if (m_terrainSceneStorage != value) { //Remove all terrains from old storage file UnloadAll(true); m_terrainSceneStorage = value; #if UNITY_EDITOR m_lastUsedGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_terrainSceneStorage)); #endif //load back in the terrains from the new file RefreshSceneViewLoadingRange(); } } } public CenterSceneViewLoadingOn CenterSceneViewLoadingOn { get { return m_centerSceneViewLoadingOn; } set { if (m_centerSceneViewLoadingOn != value) { m_centerSceneViewLoadingOn = value; //if both loading ranges are set to 0, we return to the default loading ranges if (m_centerSceneViewLoadingOn == CenterSceneViewLoadingOn.SceneViewCamera) { m_showOriginLoadingBounds = false; } if (GetLoadingRange() == 0 && GetImpostorLoadingRange() == 0) { Double regularRange = TerrainLoaderManager.GetDefaultLoadingRangeForTilesize(TerrainSceneStorage.m_terrainTilesSize); SetLoadingRange(regularRange, regularRange * 3f); } else { SetLoadingRange(m_sceneViewCameraLoadingBounds.extents.x, m_sceneViewImpostorLoadingBounds.extents.x); } } } } public void ResetStorage() { m_terrainSceneStorage = null; } public void LoadStorageData() { #if UNITY_EDITOR //Try to get the terrain scene storage file from the last used GUID first if (!String.IsNullOrEmpty(m_lastUsedGUID)) { m_terrainSceneStorage = (TerrainSceneStorage)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(m_lastUsedGUID), typeof(TerrainSceneStorage)); } //No guid / storage object? Then we need to create one in the current session directory if (m_terrainSceneStorage == null) { GaiaSessionManager gsm = GaiaSessionManager.GetSessionManager(); if (gsm != null && gsm.m_session != null) { string path = GaiaDirectories.GetScenePath(gsm.m_session) + "/TerrainScenes.asset"; if (File.Exists(path)) { m_terrainSceneStorage = (TerrainSceneStorage)AssetDatabase.LoadAssetAtPath(path, typeof(TerrainSceneStorage)); } else { m_terrainSceneStorage = ScriptableObject.CreateInstance(); if (TerrainHelper.GetWorldMapTerrain() != null) { m_terrainSceneStorage.m_hasWorldMap = true; } AssetDatabase.CreateAsset(m_terrainSceneStorage, path); AssetDatabase.ImportAsset(path); } } else { m_terrainSceneStorage = ScriptableObject.CreateInstance(); } } //Check if there are scene files existing already and if they are in the storage data - if not, we should pick them up accordingly string directory = GaiaDirectories.GetTerrainScenePathForStorageFile(m_terrainSceneStorage); var dirInfo = new DirectoryInfo(directory); bool madeChanges = false; if (dirInfo != null) { FileInfo[] allFiles = dirInfo.GetFiles(); foreach (FileInfo fileInfo in allFiles) { if (fileInfo.Extension == ".unity") { string path = GaiaDirectories.GetPathStartingAtAssetsFolder(fileInfo.FullName); if (!m_terrainSceneStorage.m_terrainScenes.Exists(x => x.GetTerrainName() == x.GetTerrainName(path))) { string firstSegment = fileInfo.Name.Split('-')[0]; int xCoord = -99; int zCoord = -99; bool successX, successZ; try { successX = Int32.TryParse(firstSegment.Substring(firstSegment.IndexOf('_') + 1, firstSegment.LastIndexOf('_') - (firstSegment.IndexOf('_') + 1)), out xCoord); successZ = Int32.TryParse(firstSegment.Substring(firstSegment.LastIndexOf('_') + 1, firstSegment.Length - 1 - firstSegment.LastIndexOf('_')), out zCoord); } catch (Exception ex) { if (ex.Message == "123") { } successX = false; successZ = false; } if (successX && successZ) { //double centerX = (xCoord - (m_terrainSceneStorage.m_terrainTilesX / 2f)) * m_terrainSceneStorage.m_terrainTilesSize + (m_terrainSceneStorage.m_terrainTilesSize /2f); //double centerZ = (zCoord - (m_terrainSceneStorage.m_terrainTilesZ / 2f)) * m_terrainSceneStorage.m_terrainTilesSize + (m_terrainSceneStorage.m_terrainTilesSize / 2f); Vector2 offset = new Vector2(-m_terrainSceneStorage.m_terrainTilesSize * m_terrainSceneStorage.m_terrainTilesX * 0.5f, -m_terrainSceneStorage.m_terrainTilesSize * m_terrainSceneStorage.m_terrainTilesZ * 0.5f); Vector3Double position = new Vector3(m_terrainSceneStorage.m_terrainTilesSize * xCoord + offset.x, 0, m_terrainSceneStorage.m_terrainTilesSize * zCoord + offset.y); Vector3Double center = new Vector3Double(position + new Vector3Double(m_terrainSceneStorage.m_terrainTilesSize / 2f, 0f, m_terrainSceneStorage.m_terrainTilesSize / 2f)); BoundsDouble bounds = new BoundsDouble(center, new Vector3Double(m_terrainSceneStorage.m_terrainTilesSize, m_terrainSceneStorage.m_terrainTilesSize * 4, m_terrainSceneStorage.m_terrainTilesSize)); //Use forward slashes in the path - The Unity scene management classes expect it that way path = path.Replace("\\", "/"); TerrainScene terrainScene = new TerrainScene() { m_scenePath = path, m_pos = position, m_bounds = bounds, m_useFloatingPointFix = m_terrainSceneStorage.m_useFloatingPointFix }; if (File.Exists(path.Replace("Terrain", GaiaConstants.ImpostorTerrainName))) { terrainScene.m_impostorScenePath = path.Replace("Terrain", GaiaConstants.ImpostorTerrainName); } if (File.Exists(path.Replace("Terrain", "Collider"))) { terrainScene.m_colliderScenePath = path.Replace("Terrain", "Collider"); } if (File.Exists(path.Replace("Terrain", "Backup"))) { terrainScene.m_backupScenePath = path.Replace("Terrain", "Backup"); } m_terrainSceneStorage.m_terrainScenes.Add(terrainScene); madeChanges = true; } } } } if (madeChanges) { EditorUtility.SetDirty(m_terrainSceneStorage); AssetDatabase.SaveAssets(); } } m_lastUsedGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_terrainSceneStorage)); RefreshTerrainsWithCurrentData(); RefreshSceneViewLoadingRange(); ////Go over the currently open scene and close the ones that do not seem to have a reference on them //for (int i = EditorSceneManager.loadedSceneCount-1; i >= 0; i--) //{ // Scene scene = EditorSceneManager.GetSceneAt(i); // if (EditorSceneManager.GetActiveScene().Equals(scene)) // { // continue; // } // TerrainScene terrainScene = m_terrainSceneStorage.m_terrainScenes.Find(x => x.m_scenePath == scene.path || x.m_impostorScenePath == scene.path || x.m_colliderScenePath == scene.path); // if (terrainScene != null) // { // terrainScene.UpdateWithCurrentData(); // } // else // { // EditorSceneManager.UnloadSceneAsync(scene); // } //} #endif } private Terrain m_worldMapTerrain; public Terrain WorldMapTerrain { get { if (m_worldMapTerrain == null) { m_worldMapTerrain = TerrainHelper.GetWorldMapTerrain(); } return m_worldMapTerrain; } } private GameObject m_terrainGO; public GameObject TerrainGO { get { if (m_terrainGO == null) { m_terrainGO = GaiaUtils.GetTerrainObject(); } return m_terrainGO; } } public static List TerrainScenes { get { return Instance.TerrainSceneStorage.m_terrainScenes; } } public static bool TerrainSceneStorageCreated { get { return Instance.m_terrainSceneStorage != null; } } private bool m_showWorldMapTerrain; public bool ShowWorldMapTerrain { get { return m_showWorldMapTerrain; } private set { m_showWorldMapTerrain = value; if (WorldMapTerrain != null) { if (m_showWorldMapTerrain) { WorldMapTerrain.gameObject.SetActive(true); } else { WorldMapTerrain.gameObject.SetActive(false); } } } } private bool m_showLocalTerrain = true; private bool m_runtimeInitialized; [SerializeField] private string m_lastUsedGUID; private bool m_progressTrackingRunning; private long m_lastLoadingProgressTimeStamp; private float m_lastTrackedProgressValue; public bool ShowLocalTerrain { get { return m_showLocalTerrain; } private set { if (value != m_showLocalTerrain) { m_showLocalTerrain = value; if (GaiaUtils.HasDynamicLoadedTerrains()) { if (!m_showLocalTerrain) { TerrainLoaderManager.Instance.UnloadAll(); } } else { foreach (Transform child in TerrainGO.transform) { Terrain t = child.GetComponent(); if (t != null) { t.drawHeightmap = m_showLocalTerrain; t.drawTreesAndFoliage = m_showLocalTerrain; //Activate / deactivate all Childs below the terrain foreach (Transform subTrans in t.transform) { subTrans.gameObject.SetActive(m_showLocalTerrain); } } } } } } } public static bool ColliderOnlyLoadingActive { get { return Instance.TerrainSceneStorage.m_colliderOnlyLoading; } } #if GAIA_PRO_PRESENT /// /// Triggered when load progress tracking starts in the Terrain Loader Manager. /// public delegate void LoadProgressStartedCallback(); public event LoadProgressStartedCallback OnLoadProgressStarted; /// /// Triggered when there is an update on the load progress during load progress tracking. /// /// The load progress expressed as scalar value (0 to 1) public delegate void LoadProgressUpdatedCallback(float progress); public event LoadProgressUpdatedCallback OnLoadProgressUpdated; /// /// Triggered when load progress tracking ends in the Terrain Loader Manager. /// public delegate void LoadProgressEndedCallback(); public event LoadProgressEndedCallback OnLoadProgressEnded; /// /// Triggered when load progress tracking times out in the Terrain Loader Manager. /// /// List of terrain scenes which are referenced, but not loaded yet at the point of the timeout. public delegate void LoadProgressTimeOutCallback(List missingTerrainScenes); public event LoadProgressTimeOutCallback OnLoadProgressTimeOut; #endif public void Start() { if (instance == null) { instance = this; } else { if (instance != this) { Destroy(this); } } //Nothing to do if no terrain loading if (!GaiaUtils.HasDynamicLoadedTerrains()) { return; } LookUpLoadingScreen(); //Process the runtime deactivation if the "Collider Only" mode is active if (TerrainSceneStorage.m_colliderOnlyLoading) { DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimePlayer, GaiaConstants.gaiaPlayerObject); DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimeLighting, GaiaConstants.gaiaLightingObject); DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimeAudio, GaiaConstants.gaiaAudioObject); DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimeWeather, GaiaConstants.gaiaWeatherObject); DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimeWater, GaiaConstants.gaiaWaterObject); DeactivateIfRequested(m_terrainSceneStorage.m_deactivateRuntimeScreenShotter, GaiaConstants.gaiaScreenshotter); } UnloadAll(); m_runtimeInitialized = true; #if GAIA_PRO_PRESENT if (m_loadingScreen != null) { m_loadingScreen.gameObject.SetActive(true); } StartTrackingProgress(); #endif } private void Update() { #if GAIA_PRO_PRESENT if (m_progressTrackingRunning) { float progress = 0f; int loadedScenesCount = TerrainSceneStorage.m_terrainScenes.FindAll(x => x.RegularReferences.Count > 0 && x.m_regularLoadState == LoadState.Loaded).Count; int referencedScenesCount = TerrainSceneStorage.m_terrainScenes.FindAll(x => x.RegularReferences.Count > 0).Count; int loadedScenesImpostorCount = TerrainSceneStorage.m_terrainScenes.FindAll(x => x.RegularReferences.Count == 0 && x.ImpostorReferences.Count > 0 && x.m_impostorLoadState == LoadState.Loaded).Count; int referencedImpostorScenesCount = TerrainSceneStorage.m_terrainScenes.FindAll(x => x.RegularReferences.Count == 0 && x.ImpostorReferences.Count > 0).Count; float loadedScenesTotal = loadedScenesCount + loadedScenesImpostorCount; float referencedScenesTotal = referencedScenesCount + referencedImpostorScenesCount; if (referencedScenesTotal > 0) { progress = loadedScenesTotal / referencedScenesTotal; } else { m_progressTrackingRunning = false; if (OnLoadProgressEnded != null) { OnLoadProgressEnded(); } } if (OnLoadProgressUpdated !=null) { OnLoadProgressUpdated(progress); } if (progress >= 1f) { m_progressTrackingRunning = false; if (OnLoadProgressEnded != null) { OnLoadProgressEnded(); } } else { long currentTimeStamp = GaiaUtils.GetUnixTimestamp(); //Check if we made progress since the last update if (progress != m_lastTrackedProgressValue) { //we made loading progress, update the progress and timestamp m_lastTrackedProgressValue = progress; m_lastLoadingProgressTimeStamp = currentTimeStamp; } else { //no load progress anymore? Time out the loading tracking eventually if (m_lastLoadingProgressTimeStamp + m_trackLoadingProgressTimeOut < currentTimeStamp) { m_progressTrackingRunning = false; if (OnLoadProgressTimeOut != null) { List missingScenes = m_terrainSceneStorage.m_terrainScenes.FindAll(x => (x.RegularReferences.Count > 0 && x.m_regularLoadState != LoadState.Loaded) || (x.ImpostorReferences.Count > 0 && x.m_impostorLoadState != LoadState.Loaded)); OnLoadProgressTimeOut(missingScenes); } if (OnLoadProgressEnded != null) { OnLoadProgressEnded(); } } } } } #endif } public void StartTrackingProgress() { #if GAIA_PRO_PRESENT //Update all runtime loaders to make sure the current references are set when we start tracking var allLoaders = Resources.FindObjectsOfTypeAll(); foreach (TerrainLoader terrainLoader in allLoaders.Where(x => x.LoadMode == LoadMode.RuntimeAlways)) { terrainLoader.UpdateTerrains(); } m_progressTrackingRunning = true; m_lastLoadingProgressTimeStamp = GaiaUtils.GetUnixTimestamp(); if (OnLoadProgressStarted != null) { OnLoadProgressStarted(); } #endif } public void LookUpLoadingScreen() { #if GAIA_PRO_PRESENT if (m_loadingScreen == null && GaiaUtils.HasDynamicLoadedTerrains()) { var loadingScreens = Resources.FindObjectsOfTypeAll(); if (loadingScreens.Length > 0) { m_loadingScreen = loadingScreens[0]; } } #endif } /// /// Deactivates the Game Object with the given name if requested & if it exists /// /// If the deactivation is requested /// The name of the Game Object that will be deactivated private void DeactivateIfRequested(bool requested, string gameObjectName) { if (requested) { GameObject gameObject = GameObject.Find(gameObjectName); if (gameObject != null) { gameObject.SetActive(false); } } } public void Reset() { #if GAIA_PRO_PRESENT m_allFloatingPointFixMembers.Clear(); m_allWorldSpaceParticleSystems.Clear(); #endif } void OnApplicationQuit() { UnloadAll(); } private void OnEnable() { if (instance == null) { instance = this; } else { if (instance != this) { Destroy(this); } } GaiaSettings gaiaSettings = GaiaUtils.GetGaiaSettings(); } public void ResetTimestamps() { foreach (TerrainScene ts in m_terrainSceneStorage.m_terrainScenes) { ts.m_impostorCachedTimestamp = 0; ts.m_regularCachedTimestamp = 0; ts.m_nextUpdateTimestamp = 0; } } public Vector3Double GetOrigin() { return new Vector3Double(m_sceneViewOriginLoadingBounds.center); } public void SetOrigin(Vector3Double newOrigin) { #if GAIA_PRO_PRESENT if (newOrigin != m_sceneViewOriginLoadingBounds.center) { //a origin shift has occured, Vector3Double shiftDifference = newOrigin - m_sceneViewOriginLoadingBounds.center; //Don't shift on y-axis this will only lead to problems with sea level, height based rules, etc. //and should not be required under normal circumstances. shiftDifference.y = 0; //shift all tools such as stampers and spawners //Stamper[] allStampers = Resources.FindObjectsOfTypeAll(); //foreach (Stamper stamper in allStampers) //{ // stamper.transform.position = (Vector3)((Vector3Double)stamper.transform.position + m_originLoadingBounds.center - shiftDifference); //} //if not in playmode, shift the player, if exists, very confusing otherwise if (!Application.isPlaying) { GameObject playerObj = GameObject.Find(GaiaConstants.playerFlyCamName); if (playerObj == null) { playerObj = GameObject.Find(GaiaConstants.playerFirstPersonName); } if (playerObj == null) { playerObj = GameObject.Find(GaiaConstants.playerThirdPersonName); } if (playerObj != null) { playerObj.transform.position = (Vector3)((Vector3Double)playerObj.transform.position - shiftDifference); } //Move spawners also only when not in playmode Spawner[] allSpawners = Resources.FindObjectsOfTypeAll(); foreach (Spawner spawner in allSpawners) { spawner.transform.position = (Vector3)((Vector3Double)spawner.transform.position - shiftDifference); } //Move world map stamp tokens also only when not in playmode WorldMapStampToken[] mapStampTokens = Resources.FindObjectsOfTypeAll(); foreach (WorldMapStampToken token in mapStampTokens) { token.UpdateGizmoPos(); } //When the application is not playing we can look for all floating point fix members, if it is playing we should //rely on the list of members being filled correctly at the start of the scene m_allFloatingPointFixMembers = Resources.FindObjectsOfTypeAll().ToList(); } m_allFloatingPointFixMembers.RemoveAll(x => x == null); foreach (FloatingPointFixMember member in m_allFloatingPointFixMembers) { member.transform.position = (Vector3)((Vector3Double)member.transform.position - shiftDifference); } //shift world space particles accordingly - only worth dealing with during playmode if (Application.isPlaying) { m_allWorldSpaceParticleSystems.RemoveAll(x => x == null); foreach (ParticleSystem ps in m_allWorldSpaceParticleSystems) { bool wasPaused = ps.isPaused; bool wasPlaying = ps.isPlaying; ParticleSystem.Particle[] currentParticles = null; if (!wasPaused) ps.Pause(); if (currentParticles == null || currentParticles.Length < ps.main.maxParticles) { currentParticles = new ParticleSystem.Particle[ps.main.maxParticles]; } int num = ps.GetParticles(currentParticles); for (int i = 0; i < num; i++) { currentParticles[i].position -= (Vector3)shiftDifference; } ps.SetParticles(currentParticles, num); if (wasPlaying) ps.Play(); } } m_sceneViewOriginLoadingBounds.center = newOrigin; #if UNITY_EDITOR EditorUtility.SetDirty(this); #endif //if (WorldMapTerrain != null) //{ // WorldMapTerrain.transform.position = -m_originLoadingBounds.center - (new Vector3Double(WorldMapTerrain.terrainData.size.x / 2f, 0f, WorldMapTerrain.terrainData.size.z / 2f)); //} //Update terrain loading state for all terrains since the session manager loads itself around the origin RefreshSceneViewLoadingRange(); } #endif } public void SetOriginByTargetTile(int tileX = -99, int tileZ = -99) { if (tileX == -99) { tileX = m_originTargetTileX; } if (tileZ == -99) { tileZ = m_originTargetTileZ; } if (GaiaUtils.HasDynamicLoadedTerrains()) { //Get the terrain tile by X / Z tile in the scene path TerrainScene targetScene = TerrainLoaderManager.TerrainScenes.Find(x => x.m_scenePath.Contains("Terrain_" + tileX.ToString() + "_" + tileZ.ToString())); if (targetScene != null) { SetOrigin(new Vector3Double(targetScene.m_pos.x + (m_terrainSceneStorage.m_terrainTilesSize / 2f), 0f, targetScene.m_pos.z + (m_terrainSceneStorage.m_terrainTilesSize / 2f))); string terrainName = targetScene.GetTerrainName(); GameObject go = GameObject.Find(terrainName); if (go != null) { #if UNITY_EDITOR Selection.activeObject = go; #endif } } else { Debug.LogWarning("Could not find a terrain with the tile coordinates " + tileX.ToString() + "-" + tileZ.ToString() + " in the available terrains. Please check if these coordinates are within the available bounds."); } } else { Terrain t = Terrain.activeTerrains.Where(x => x.name.Contains("Terrain_" + tileX.ToString() + "_" + tileZ.ToString())).First(); if (t != null) { SetOrigin(new Vector3Double(t.transform.position.x + (t.terrainData.size.x / 2f), 0f, t.transform.position.z + (t.terrainData.size.z / 2f))); #if UNITY_EDITOR Selection.activeObject = t.gameObject; #endif } else { Debug.LogWarning("Could not find a terrain with the tile coordinates " + tileX.ToString() + "-" + tileZ.ToString() + " in the scene."); } } } /// /// Updates the caching mechanism with the new settings - effectively this does only unload cached terrains which are not allowed to be cached anymore due to the new settings. /// public void UpdateCaching() { long currentTimeStamp = GaiaUtils.GetUnixTimestamp(); foreach (TerrainScene terrainScene in m_terrainSceneStorage.m_terrainScenes) { if (terrainScene.m_impostorLoadState == LoadState.Cached && (!CachingAllowed() || terrainScene.m_impostorCachedTimestamp + m_cacheKeepAliveTime < currentTimeStamp)) { terrainScene.RemoveAllImpostorReferences(true); } if (terrainScene.m_regularLoadState == LoadState.Cached && (!CachingAllowed() || terrainScene.m_regularCachedTimestamp + m_cacheKeepAliveTime < currentTimeStamp)) { terrainScene.RemoveAllReferences(true); } } } /// /// Load and unload the terrain scenes stored in the current session for a certain object /// public void UpdateTerrainLoadState(BoundsDouble loadingBoundsRegular = null, BoundsDouble loadingBoundsImpostor = null, GameObject requestingObject = null, float minDistance = 0, float maxDistance = 0, float minThresholdMS = 0, float maxThresholdMS = 0) { //Do not accept changes to load state during runtime when there was no runtime init yet if (Application.isPlaying && !m_runtimeInitialized) { return; } //Do not perform any updates when terrain loading is disabled per default #if GAIA_PRO_PRESENT if (TerrainLoaderManager.Instance.m_terrainSceneStorage == null || !TerrainLoaderManager.Instance.m_terrainSceneStorage.m_terrainLoadingEnabled) { return; } #endif if (requestingObject == null) { requestingObject = gameObject; } long currentTimeStamp = GaiaUtils.GetUnixTimestamp(); m_terrainSceneActionQueue.Clear(); foreach (TerrainScene terrainScene in TerrainLoaderManager.TerrainScenes) { if (terrainScene.m_nextUpdateTimestamp > currentTimeStamp) { continue; } float distance = Vector3.Distance(terrainScene.m_bounds.center - terrainScene.m_currentOriginOffset, requestingObject.transform.position); terrainScene.m_currentOriginOffset = m_sceneViewOriginLoadingBounds.center; bool wasChanged = false; //only evaluate load state if local terrain is supposed to be displayed if (m_showLocalTerrain) { if (loadingBoundsImpostor != null && loadingBoundsImpostor.extents.magnitude > 0 && loadingBoundsImpostor.extents.magnitude > loadingBoundsRegular.extents.magnitude) { if (terrainScene.m_bounds.Intersects(loadingBoundsImpostor) && !TerrainSceneStorage.m_colliderOnlyLoading) { if (!terrainScene.HasImpostorReference(requestingObject) || terrainScene.m_impostorLoadState == LoadState.Unloaded) { //terrainScene.AddImpostorReference(requestingObject); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.AddImpostorReference, distance); terrainScene.m_useFloatingPointFix = m_terrainSceneStorage.m_useFloatingPointFix; wasChanged = true; } } else { if (terrainScene.HasImpostorReference(requestingObject) || terrainScene.m_impostorLoadState == LoadState.Loaded) { //terrainScene.RemoveImpostorReference(requestingObject, m_cacheMemoryThreshold); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.RemoveImpostorReference, distance); wasChanged = true; } if (terrainScene.m_impostorLoadState == LoadState.Cached && (!CachingAllowed() || terrainScene.m_impostorCachedTimestamp + m_cacheKeepAliveTime < currentTimeStamp)) { //terrainScene.RemoveImpostorReference(requestingObject, m_cacheMemoryThreshold, true); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.RemoveImpostorReference, distance, true); } } } else { if (terrainScene.HasImpostorReference(requestingObject)) { //terrainScene.RemoveImpostorReference(requestingObject, m_cacheMemoryThreshold); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.RemoveImpostorReference, distance); wasChanged = true; } } if (loadingBoundsRegular != null && loadingBoundsRegular.extents.magnitude > 0) { if (terrainScene.m_bounds.Intersects(loadingBoundsRegular)) { if (!terrainScene.HasRegularReference(requestingObject) || terrainScene.m_regularLoadState == LoadState.Unloaded) { //terrainScene.AddRegularReference(requestingObject); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.AddRegularReference, distance); terrainScene.m_useFloatingPointFix = m_terrainSceneStorage.m_useFloatingPointFix; wasChanged = true; } } else { if (terrainScene.HasRegularReference(requestingObject) || terrainScene.m_regularLoadState == LoadState.Loaded) { //terrainScene.RemoveRegularReference(requestingObject, m_cacheMemoryThreshold); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.RemoveRegularReference, distance); wasChanged = true; } if (terrainScene.m_regularLoadState == LoadState.Cached && (!CachingAllowed() || terrainScene.m_regularCachedTimestamp + m_cacheKeepAliveTime < currentTimeStamp)) { //terrainScene.RemoveRegularReference(requestingObject, m_cacheMemoryThreshold, true); AddToTerrainSceneActionQueue(terrainScene, ReferenceChange.RemoveRegularReference, distance, true); } } } } terrainScene.ShiftLoadedTerrain(); if (Application.isPlaying && !wasChanged) { long threshold = +(long)Mathf.Lerp(minThresholdMS, maxThresholdMS, Mathf.InverseLerp(minDistance, maxDistance, Vector3.Distance(loadingBoundsRegular.center, terrainScene.m_bounds.center))) + UnityEngine.Random.Range(10, 50); terrainScene.m_nextUpdateTimestamp = currentTimeStamp + threshold; } else { terrainScene.m_nextUpdateTimestamp = 0; } } for (int i = 0; i < m_terrainSceneActionQueue.Count; i++) { switch (m_terrainSceneActionQueue[i].m_referenceChange) { case ReferenceChange.AddImpostorReference: m_terrainSceneActionQueue[i].m_terrainScene.AddImpostorReference(requestingObject); break; case ReferenceChange.AddRegularReference: m_terrainSceneActionQueue[i].m_terrainScene.AddRegularReference(requestingObject); break; case ReferenceChange.RemoveImpostorReference: m_terrainSceneActionQueue[i].m_terrainScene.RemoveImpostorReference(requestingObject, m_cacheMemoryThreshold, m_terrainSceneActionQueue[i].m_forced); break; case ReferenceChange.RemoveRegularReference: m_terrainSceneActionQueue[i].m_terrainScene.RemoveRegularReference(requestingObject, m_cacheMemoryThreshold, m_terrainSceneActionQueue[i].m_forced); break; } } } private void AddToTerrainSceneActionQueue(TerrainScene terrainScene, ReferenceChange referenceChange, float distance, bool forced = false) { int index = m_terrainSceneActionQueue.FindIndex(x => x.m_distance > distance); if (index != -1) { //We found an entry with a larger distance -> insert it in before that m_terrainSceneActionQueue.Insert(index, new TerrainSceneActionQueueEntry { m_terrainScene = terrainScene, m_referenceChange = referenceChange, m_forced = forced, m_distance = distance }); } else { //no larger distance found, this needs to go to the end of the queue m_terrainSceneActionQueue.Add(new TerrainSceneActionQueueEntry { m_terrainScene = terrainScene, m_referenceChange = referenceChange, m_forced = forced, m_distance = distance }); } } public bool CachingAllowed() { return (Application.isPlaying && m_cacheInRuntime) || (!Application.isPlaying && m_cacheInEditor); } public double GetLoadingRange() { return m_sceneViewOriginLoadingBounds.extents.x; } public double GetImpostorLoadingRange() { return m_sceneViewImpostorLoadingBounds.extents.x; } public Vector3Double GetLoadingSize() { return new Vector3Double(m_sceneViewOriginLoadingBounds.size); } public Vector3Double GetImpostorLoadingSize() { return new Vector3Double(m_sceneViewImpostorLoadingBounds.size); } public Vector3Double GetLoadingCenter() { if (m_centerSceneViewLoadingOn == CenterSceneViewLoadingOn.SceneViewCamera) { return m_sceneViewCameraLoadingBounds.center; } else { return m_sceneViewOriginLoadingBounds.center; } } public void SwitchToWorldMap() { ShowWorldMapTerrain = true; ShowLocalTerrain = false; if (!Application.isPlaying) { UpdateTerrainLoadState(); } #if UNITY_EDITOR Selection.activeGameObject = GaiaUtils.GetOrCreateWorldDesigner(); #endif } public void SwitchToLocalMap(bool useInternalLoadingBounds = false) { ShowLocalTerrain = true; ShowWorldMapTerrain = false; if (!Application.isPlaying) { if (useInternalLoadingBounds) { UpdateTerrainLoadState(m_sceneViewOriginLoadingBounds); } else { UpdateTerrainLoadState(); } } } public void SetLoadingRange(Double regularLoadingRange, Double impostorLoadingRange) { #if UNITY_EDITOR if (regularLoadingRange > 0 || impostorLoadingRange > 0) { Vector3Double targetLoadingExtentsRegular = new Vector3Double(regularLoadingRange, regularLoadingRange, regularLoadingRange); Vector3Double targetLoadingExtentsImpostor = new Vector3Double(impostorLoadingRange, impostorLoadingRange, impostorLoadingRange); BoundsDouble loadingBounds = m_sceneViewOriginLoadingBounds; if (m_centerSceneViewLoadingOn == CenterSceneViewLoadingOn.SceneViewCamera) { var allCameras = SceneView.GetAllSceneCameras(); if (allCameras.Length > 0) { if (m_sceneViewCameraLoadingBounds.center.Equals((Vector3Double)allCameras[0].transform.position) && m_sceneViewCameraLoadingBounds.extents.Equals(targetLoadingExtentsRegular) && m_sceneViewImpostorLoadingBounds.extents.Equals(targetLoadingExtentsImpostor)) { //this exact setup is what is already currently loaded, skip the rest of processing return; } m_sceneViewCameraLoadingBounds.center = (Vector3Double)allCameras[0].transform.position; } loadingBounds = m_sceneViewCameraLoadingBounds; } loadingBounds.extents = targetLoadingExtentsRegular; m_sceneViewOriginLoadingBounds.extents = loadingBounds.extents; m_sceneViewImpostorLoadingBounds.center = loadingBounds.center; m_sceneViewImpostorLoadingBounds.extents = targetLoadingExtentsImpostor; EditorUtility.SetDirty(this); UpdateTerrainLoadState(loadingBounds, m_sceneViewImpostorLoadingBounds, gameObject); } else { m_sceneViewOriginLoadingBounds.extents = Vector3.zero; m_sceneViewImpostorLoadingBounds.extents = Vector3.zero; foreach (TerrainScene ts in m_terrainSceneStorage.m_terrainScenes) { ts.RemoveRegularReference(gameObject); ts.RemoveImpostorReference(gameObject); } } #endif } public void RefreshSceneViewLoadingRange() { if (m_centerSceneViewLoadingOn == CenterSceneViewLoadingOn.WorldOrigin) { UpdateTerrainLoadState(m_sceneViewOriginLoadingBounds, m_sceneViewImpostorLoadingBounds); } else { UpdateTerrainLoadState(m_sceneViewCameraLoadingBounds, m_sceneViewImpostorLoadingBounds); } } public void RefreshTerrainsWithCurrentData() { foreach (TerrainScene ts in TerrainSceneStorage.m_terrainScenes) { ts.UpdateWithCurrentData(); } } public void UnloadAll(bool forceUnload = false) { if (m_terrainSceneStorage != null) { foreach (TerrainScene terrainScene in m_terrainSceneStorage.m_terrainScenes) { terrainScene.RemoveAllReferences(forceUnload); terrainScene.m_nextUpdateTimestamp = 0; } } } public void UnloadAllImpostors(bool forceUnload = false) { if (m_terrainSceneStorage != null) { foreach (TerrainScene terrainScene in m_terrainSceneStorage.m_terrainScenes) { terrainScene.RemoveAllImpostorReferences(forceUnload); terrainScene.m_nextUpdateTimestamp = 0; } } } public void EmptyCache() { if (m_terrainSceneStorage != null) { foreach (TerrainScene terrainScene in m_terrainSceneStorage.m_terrainScenes.Where(x => x.RegularReferences.Count() <= 0)) { terrainScene.RemoveAllReferences(true); } } } public void SaveStorageData() { #if UNITY_EDITOR EditorUtility.SetDirty(m_terrainSceneStorage); AssetDatabase.SaveAssets(); LoadStorageData(); #endif } public TerrainScene GetTerrainSceneAtPosition(Vector3Double center) { return m_terrainSceneStorage.m_terrainScenes.Find(x => x.m_bounds.Contains(center)); } /// /// Returns the default loading range according to the tile size of a single terrain tile of the scene. /// /// The tile size to get the loading range for. /// The default loading range. public static double GetDefaultLoadingRangeForTilesize(int tileSize) { return Mathf.Round((tileSize * 1.9f) / 2f); } } }