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 bool m_autoTerrainStitching = true; public bool m_assembliesAreReloading = false; 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 static TerrainLoaderManager GetInstanceWithoutCreation() { if (instance == null) { GameObject go = GaiaUtils.GetTerrainLoaderManagerObject(false); if (go != null) { instance = go.GetComponent(); } } return instance; } 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))) { int xCoord = -99; int zCoord = -99; if (TerrainScene.GetCoords(fileInfo.Name, ref xCoord, ref zCoord)) { //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 { TerrainLoaderManager tlm = GetInstanceWithoutCreation(); if (tlm != null) { return tlm.m_terrainSceneStorage != null; } else { return false; } } } 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); } } } } /// /// Used to determine if the terrain loader manager is ready to start loading terrains during runtime /// private bool m_runtimeInitialized; public bool RuntimeIsInitialized { get { return m_runtimeInitialized; } } private bool m_showLocalTerrain = true; [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(); m_assembliesAreReloading = false; SubscribeToAssemblyReloadEvents(); } public void SubscribeToAssemblyReloadEvents() { #if UNITY_EDITOR AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; #endif } void OnDisable() { m_assembliesAreReloading = false; #if UNITY_EDITOR AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; #endif } public void OnBeforeAssemblyReload() { m_assembliesAreReloading = true; } public void OnAfterAssemblyReload() { m_assembliesAreReloading = false; } 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); } } } /// /// Creates a small loading bounds at a vector3 position in world space to load the terrains located there. /// /// Position to load the terrains at. /// The range to load around that point. /// The game object referencing that point in the world. public void LoadTerrainByPosition(Vector3 position, float range = 1, GameObject referenceGO = null) { BoundsDouble loadingBounds = new BoundsDouble(position, new Vector3Double(range, range, range)); if (referenceGO == null) { referenceGO = this.gameObject; } UpdateTerrainLoadState(loadingBounds, loadingBounds, referenceGO); } /// /// 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 //Do not accept any changes to load state while assemblies are being reloaded, this leads to errors in the editor. if (m_assembliesAreReloading) { return; } 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 }); } } /// /// Tries to get the neighboring terrain scene for a scene in the given direction /// /// The terrain scene we are looking up the neighbors for /// The cardinal direction to look in /// The neighboring terrain scene if found, returns null otherwise public TerrainScene TryGetNeighbor(TerrainScene terrainScene, StitchDirection direction) { if (terrainScene == null) { //Can't find a neighbor without an input scene return null; } int coordX = -9999; int coordZ = -9999; if (TerrainScene.GetCoords(terrainScene.GetTerrainName(), ref coordX, ref coordZ)) { TerrainScene returnScene = null; string searchString = ""; switch (direction) { case StitchDirection.North: searchString = $"_{coordX}_{coordZ + 1}-"; break; case StitchDirection.South: searchString = $"_{coordX}_{coordZ - 1}-"; break; case StitchDirection.West: searchString = $"_{coordX - 1}_{coordZ}-"; break; case StitchDirection.East: searchString = $"_{coordX + 1}_{coordZ}-"; break; } returnScene = TerrainSceneStorage.m_terrainScenes.Find(x => x.GetTerrainName().Contains(searchString)); return returnScene; } else { return null; } } 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(); } } /// /// Removes all references to terrains or impostors originating by the given game object - this will allow the affected terrains to unload (assuming the GameObject was the only reference) /// /// The GameObject whose references will be removed public void RemoveAllReferencesOfGameObject(GameObject go) { if (m_terrainSceneStorage != null) { foreach (TerrainScene terrainScene in m_terrainSceneStorage.m_terrainScenes.Where(x => x.RegularReferences.Contains(go) || x.ImpostorReferences.Contains(go))) { terrainScene.RemoveRegularReference(go); terrainScene.RemoveImpostorReference(go); terrainScene.m_nextUpdateTimestamp = 0; } } } 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 DirtyStorageData() { #if UNITY_EDITOR EditorUtility.SetDirty(m_terrainSceneStorage); #endif } public void SaveStorageData() { #if UNITY_EDITOR EditorUtility.SetDirty(m_terrainSceneStorage); AssetDatabase.SaveAssets(); LoadStorageData(); #endif } /// /// //Update impostor state in the addressable / build scriptable object, if the scene of the terrain loader manager is found in the current configuration. /// public void UpdateImpostorStateInBuildSettings() { #if UNITY_EDITOR BuildConfig config = GaiaUtils.GetOrCreateBuildConfig(); SceneBuildEntry sceneBuildEntry = config.m_sceneBuildEntries.Find(x => x.m_masterScene.name == this.gameObject.scene.name); if (sceneBuildEntry != null) { if (TerrainSceneStorage.m_terrainScenes.Count > 0) { if (TerrainSceneStorage.m_terrainScenes.Exists(x => !string.IsNullOrEmpty(x.m_impostorScenePath))) { sceneBuildEntry.m_impostorState = ImpostorState.ImpostorsCreated; } else { sceneBuildEntry.m_impostorState = ImpostorState.ImpostorsNotCreated; } } else { sceneBuildEntry.m_impostorState = ImpostorState.NoTerrainLoading; } } EditorUtility.SetDirty(config); #endif } /// /// Gets a terrain scene from the terrain scene storage by the x-z tile index /// /// /// /// public TerrainScene GetTerrainSceneByIndices(int x, int z) { return m_terrainSceneStorage.m_terrainScenes.Find(k => k.m_scenePath.Contains($"_{x}_{z}-")); } 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); } public static void FloatingPointFix_Add() { #if GAIA_PRO_PRESENT //Add main fix to player 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 = GameObject.Find(GaiaConstants.m_carPlayerPrefabName); } if (playerObj == null) { playerObj = GameObject.Find(GaiaConstants.playerXRName); } if (playerObj != null) { FloatingPointFix fix = playerObj.GetComponent(); if (fix == null) { fix = playerObj.AddComponent(); } fix.threshold = GaiaUtils.GetGaiaSettings().m_FPFDefaultThreshold; } //Check if we are in a placeholder / terrain loading setup - if yes switch on all placeholders for the fix if (GaiaUtils.HasDynamicLoadedTerrains()) { foreach (TerrainScene ts in TerrainLoaderManager.TerrainScenes) { ts.m_useFloatingPointFix = true; } if (GaiaUtils.DisplayDialogNoEditor("Adjust unloaded Terrains?", "You are using dynamic terrain loading with terrain placeholders. Do you want to load all terrains after another to apply the fix to them and make all objects non-static?", "OK", "Cancel")) { GaiaUtils.CallFunctionOnDynamicLoadedTerrains(AddFloatingPointFixToTerrain, true); } } else { //regular terrain setup - add the membership to all terrains in the scene instead. foreach (Terrain terrain in Terrain.activeTerrains) { AddFloatingPointFixToTerrain(terrain); } } #endif } public static void AddFloatingPointFixToTerrain(Terrain terrain) { #if GAIA_PRO_PRESENT FloatingPointFixMember ffMember = terrain.gameObject.GetComponent(); if (ffMember == null) { ffMember = terrain.gameObject.AddComponent(); } SetAllChildsNonStatic(terrain.transform); #endif } private static void RemoveFloatingPointFixToTerrain(Terrain terrain) { #if GAIA_PRO_PRESENT FloatingPointFixMember ffMember = terrain.gameObject.GetComponent(); if (ffMember != null) { Component.DestroyImmediate(ffMember); } #endif } public static void SetAllChildsNonStatic(Transform transform) { transform.gameObject.isStatic = false; foreach (Transform t in transform) { SetAllChildsNonStatic(t); } } /// /// Removes floating point fix system from the scene /// public static void FloatingPointFix_Remove() { #if GAIA_PRO_PRESENT //Remove main fix to player 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 = GameObject.Find(GaiaConstants.m_carPlayerPrefabName); } if (playerObj == null) { playerObj = GameObject.Find(GaiaConstants.playerXRName); } if (playerObj != null) { FloatingPointFix fix = playerObj.GetComponent(); if (fix != null) { Component.DestroyImmediate(fix); } } //Remove membership from water GameObject waterGO = GameObject.Find(GaiaConstants.waterSurfaceObject); if (waterGO != null) { FloatingPointFixMember ffMember = waterGO.transform.parent.gameObject.GetComponent(); if (ffMember != null) { Component.DestroyImmediate(ffMember); } } //Check if we are in a terrain loading setup - if yes switch off all placeholders for the fix if (GaiaUtils.HasDynamicLoadedTerrains()) { foreach (TerrainScene ts in TerrainLoaderManager.TerrainScenes) { ts.m_useFloatingPointFix = false; } if (GaiaUtils.DisplayDialogNoEditor("Adjust unloaded Terrains?", "You are using dynamic terrain loading with terrain placeholders. Do you want to load all terrains one after another to remove the fix from those as well?", "OK", "Cancel")) { GaiaUtils.CallFunctionOnDynamicLoadedTerrains(RemoveFloatingPointFixToTerrain, true); } } else { //regular terrain setup - add the membership to all terrains in the scene instead. foreach (Terrain terrain in Terrain.activeTerrains) { RemoveFloatingPointFixToTerrain(terrain); } } #endif } } }