Files
2024-11-20 15:21:28 +01:00

637 lines
26 KiB
C#

using ProcedualWorlds.WaterSystem;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
using UnityEngine;
#if UNITY_POST_PROCESSING_STACK_V2
using UnityEngine.Rendering.PostProcessing;
#endif
using static Gaia.GaiaConstants;
namespace Gaia
{
/// <summary>
/// A generic spawning system.
/// </summary>
[System.Serializable]
public class BiomeController : MonoBehaviour
{
[SerializeField]
private BiomeControllerSettings settings;
/// <summary>
/// The current biome controller settings
/// </summary>
public BiomeControllerSettings m_settings
{
get
{
if (settings == null && gameObject!=null)
{
settings = ScriptableObject.CreateInstance<BiomeControllerSettings>();
settings.name = gameObject.name;
}
return settings;
}
set
{
settings = value;
}
}
public bool m_autoSpawnersToggleAll = false;
public List<AutoSpawner> m_autoSpawners = new List<AutoSpawner>();
#if UNITY_POST_PROCESSING_STACK_V2
public PostProcessProfile m_postProcessProfile;
public BiomePostProcessingVolumeSpawnMode m_ppVSpawnMode = BiomePostProcessingVolumeSpawnMode.Add;
#endif
public bool m_autoSpawnRequested;
public bool m_drawPreview;
public bool m_biomePreviewDirty;
private Terrain m_lastActiveTerrain;
private float m_minWorldHeight;
private float m_maxWorldHeight;
private RenderTexture m_cachedPreviewRT;
private GaiaSettings m_gaiaSettings;
private GaiaSettings GaiaSettings
{
get
{
if (m_gaiaSettings == null)
{
m_gaiaSettings = GaiaUtils.GetGaiaSettings();
}
return m_gaiaSettings;
}
}
public AutoSpawnerArea m_autoSpawnerArea = AutoSpawnerArea.Local;
public bool m_showSeaLevelPlane = true;
public bool m_showSeaLevelinPreview = true;
public bool m_showBoundingBox = true;
public string m_oldName;
public bool m_changesMadeSinceLastSave;
public bool m_biomeWasSpawned;
#if GAIA_PRO_PRESENT
private TerrainLoader m_terrainLoader;
public LoadMode m_loadTerrainMode = LoadMode.Disabled;
public TerrainLoader TerrainLoader
{
get
{
if (m_terrainLoader == null)
{
if (this != null)
{
m_terrainLoader = gameObject.GetComponent<TerrainLoader>();
if (m_terrainLoader == null)
{
m_terrainLoader = gameObject.AddComponent<TerrainLoader>();
m_terrainLoader.hideFlags = HideFlags.HideInInspector;
}
}
}
return m_terrainLoader;
}
}
#endif
private GaiaSessionManager m_sessionManager;
private GaiaSessionManager SessionManager
{
get
{
if (m_sessionManager == null)
{
m_sessionManager = GaiaSessionManager.GetSessionManager(false);
}
return m_sessionManager;
}
}
/// <summary>
/// Adds child game objects that contain a spawner component to the list of spawners
/// </summary>
public void UpdateSpawnerList()
{
for (int i =0; i<transform.childCount; i++)
{
Transform t = transform.GetChild(i);
Spawner spawner = t.GetComponent<Spawner>();
if (spawner != null)
{
if (m_autoSpawners.Find(x => x.spawner == spawner) == null)
{
m_autoSpawners.Insert(i, new AutoSpawner() { isActive = true, spawner = spawner });
}
}
}
}
public void UpdateAutoLoadRange()
{
#if GAIA_PRO_PRESENT
if (m_loadTerrainMode != LoadMode.Disabled)
{
float width = m_settings.m_range * 2f;
//reduce the loading width a bit => this is to prevent loading in terrains when the spawner bounds end exactly at the border of
//surrounding terrains, this loads in a lot of extra terrains which are not required for the spawn
width -= 0.5f;
TerrainLoader.m_loadingBounds = new BoundsDouble(transform.position, new Vector3(width, width, width));
}
TerrainLoader.LoadMode = m_loadTerrainMode;
#endif
}
//void OnEnable()
//{
// if (m_gaiaSettings == null)
// {
// m_gaiaSettings = GaiaUtils.GetGaiaSettings();
// }
//}
/// <summary>
/// Position and fit the spawner to the terrain
/// </summary>
public void FitToTerrain(Terrain t = null)
{
if (t == null)
{
t = Gaia.TerrainHelper.GetTerrain(transform.position,false);
if (t == null)
{
t = Terrain.activeTerrain;
}
if (t == null)
{
Debug.LogWarning("Could not fit to terrain - no terrain present");
return;
}
}
Bounds b = new Bounds();
if (TerrainHelper.GetTerrainBounds(t, ref b))
{
transform.position = new Vector3(b.center.x, t.transform.position.y, b.center.z);
m_settings.m_range = b.extents.x;
}
}
/// <summary>
/// Position and fit the spawner to the terrain
/// </summary>
public void FitToAllTerrains()
{
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null)
{
Debug.LogError("Could not fit to terrain - no active terrain present");
return;
}
BoundsDouble b = new Bounds();
if (TerrainHelper.GetTerrainBounds(ref b))
{
transform.position = b.center;
m_settings.m_range = (float)b.extents.x;
}
}
private void OnDestroy()
{
m_settings.ClearImageMaskTextures();
}
void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
if (Selection.activeObject == gameObject)
{
if (m_showBoundingBox)
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(transform.position, new Vector3(m_settings.m_range * 2f, m_settings.m_range * 2f, m_settings.m_range * 2f));
}
//Water
if (m_showSeaLevelPlane && PWS_WaterSystem.Instance == null)
{
BoundsDouble bounds = new BoundsDouble();
if (TerrainHelper.GetTerrainBounds(ref bounds) == true)
{
bounds.center = new Vector3Double(bounds.center.x, SessionManager.GetSeaLevel(), bounds.center.z);
bounds.size = new Vector3Double(bounds.size.x, 0.05f, bounds.size.z);
Gizmos.color = new Color(Color.blue.r, Color.blue.g, Color.blue.b, Color.blue.a / 4f);
Gizmos.DrawCube(bounds.center, bounds.size);
}
}
}
#endif
}
public void LoadSettings(BiomeControllerSettings settingsToLoad)
{
m_settings.ClearImageMaskTextures();
//set position according to the stored settings
transform.position = new Vector3(settingsToLoad.m_x, settingsToLoad.m_y, settingsToLoad.m_z);
//Set existing settings = null to force a new scriptable object
m_settings = null;
m_settings = Instantiate(settingsToLoad);
}
public void LoadFromPreset(BiomePreset presetToLoad)
{
try
{
presetToLoad.RefreshSpawnerListEntries();
m_autoSpawners.Clear();
//Remove all child spawners
for (int i = transform.childCount - 1; i >= 0; i--)
{
DestroyImmediate(transform.GetChild(i).gameObject);
}
//Track created spawners
List<Spawner> createdSpawners = new List<Spawner>();
int totalCount = presetToLoad.m_spawnerPresetList.Count();
int currentCount = 0;
foreach (BiomeSpawnerListEntry spawnerListEntry in presetToLoad.m_spawnerPresetList)
{
createdSpawners.Add(spawnerListEntry.m_spawnerSettings.CreateSpawner(false, transform));
//GaiaUtils.DisplayProgressBarNoEditor("Creating Tools", "Creating Biome " + this.name, ++currentStep / totalSteps);
if (ProgressBar.Show(ProgressBarPriority.CreateBiomeTools, "Loading Biome Preset", "Creating Tools", ++currentCount, totalCount, false, true))
{
break;
}
}
if (createdSpawners.Count > 0)
{
m_settings.m_range = createdSpawners[0].m_settings.m_spawnRange;
}
foreach (Spawner spawner in createdSpawners)
{
m_autoSpawners.Add(new AutoSpawner() { isActive = true, status = AutoSpawnerStatus.Initial, spawner = spawner });
}
}
catch (Exception ex)
{
Debug.LogError("Error while loading biome preset settings, Message: " + ex.Message + ", Stack Trace: " + ex.StackTrace);
}
finally
{
ProgressBar.Clear(ProgressBarPriority.CreateBiomeTools);
}
}
public Terrain GetCurrentTerrain()
{
Terrain currentTerrain = Gaia.TerrainHelper.GetTerrain(transform.position, false);
//Check if the stamper is over a terrain currently
//if not, we will draw a preview based on the last active terrain we were over
//if that is null either we can't draw a stamp preview
if (currentTerrain)
{
//Update last active terrain with current
if (m_lastActiveTerrain != currentTerrain)
{
//if the current terrain is a new terrain, we should refresh the min max values in case this terrain has never been calculated before
SessionManager.GetWorldMinMax(ref m_minWorldHeight, ref m_maxWorldHeight);
}
m_lastActiveTerrain = currentTerrain;
}
//if not, we check if there is any terrain within the bounds of the biome spawner
if (currentTerrain == null)
{
float width = m_settings.m_range * 2f;
Bounds stamperBounds = new Bounds(transform.position, new Vector3(width, width, width));
foreach (Terrain t in Terrain.activeTerrains)
{
//only look at this terrain if it matches the selected world map mode
if (!TerrainHelper.IsWorldMapTerrain(t) && t.terrainData!=null)
{
Bounds worldSpaceBounds = t.terrainData.bounds;
worldSpaceBounds.center = new Vector3(worldSpaceBounds.center.x + t.transform.position.x, worldSpaceBounds.center.y + t.transform.position.y, worldSpaceBounds.center.z + t.transform.position.z);
if (worldSpaceBounds.Intersects(stamperBounds))
{
currentTerrain = t;
break;
}
}
}
}
return currentTerrain;
}
public void UpdateMinMaxHeight()
{
SessionManager.GetWorldMinMax(ref m_minWorldHeight, ref m_maxWorldHeight, false);
float seaLevel = SessionManager.GetSeaLevel(false);
//Iterate through all image masks and set up the current min max height
//This is fairly important to display the height-dependent mask settings correctly
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
mask.m_seaLevel = seaLevel;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Biome Controller", this.name, m_settings.m_imageMasks);
}
public void DrawBiomePreview()
{
if (m_drawPreview)
{
//Set up a multi-terrain operation once, all rules can then draw from the data collected here
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null)
{
return;
}
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_range * 2f);
operation.GetHeightmap();
//only re-generate all textures etc. if settings have changed and the preview is dirty, otherwise we can just use the cached textures
if (m_biomePreviewDirty == true)
{
//Get additional op data (required for certain image masks)
operation.GetNormalmap();
operation.CollectTerrainBakedMasks();
//Clear texture cache first
if (m_cachedPreviewRT != null)
{
m_cachedPreviewRT.Release();
DestroyImmediate(m_cachedPreviewRT);
}
m_cachedPreviewRT = new RenderTexture(operation.RTheightmap);
RenderTexture currentRT = RenderTexture.active;
RenderTexture.active = m_cachedPreviewRT;
GL.Clear(true, true, Color.black);
RenderTexture.active = currentRT;
Graphics.Blit(ApplyBrush(operation), m_cachedPreviewRT);
RenderTexture.active = currentRT;
//ImageProcessing.WriteRenderTexture("D:\\previewRT.png", m_cachedPreviewRT);
//Everything processed, preview not dirty anymore
m_biomePreviewDirty = false;
}
//Now draw the preview according to the cached textures
Material material = GaiaMultiTerrainOperation.GetDefaultGaiaSpawnerPreviewMaterial();
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
//assign the first color texture in the material
material.SetTexture("_colorTexture0", m_cachedPreviewRT);
//remove all other potential color textures, there can be caching issues if other visualisers were used in the meantime
for (int colorIndex = 1; colorIndex < GaiaConstants.maxPreviewedTextures; colorIndex++)
{
material.SetTexture("_colorTexture" + colorIndex, null);
}
//set the color
material.SetColor("_previewColor0", m_settings.m_visualisationColor);
Color seaLevelColor = GaiaSettings.m_stamperSeaLevelTintColor;
if (!m_showSeaLevelinPreview)
{
seaLevelColor.a = 0f;
}
material.SetColor("_seaLevelTintColor", seaLevelColor);
material.SetFloat("_seaLevel", SessionManager.m_session.m_seaLevel);
operation.Visualize(MultiTerrainOperationType.Heightmap, operation.RTheightmap, material, 1);
//Clean up
operation.CloseOperation();
//Clean up temp textures
GaiaUtils.ReleaseAllTempRenderTextures();
}
}
private RenderTexture ApplyBrush(GaiaMultiTerrainOperation operation, MultiTerrainOperationType opType = MultiTerrainOperationType.Heightmap)
{
Terrain currentTerrain = GetCurrentTerrain();
RenderTextureDescriptor rtDescriptor = operation.RTheightmap.descriptor;
switch (opType)
{
case MultiTerrainOperationType.Heightmap:
rtDescriptor = operation.RTheightmap.descriptor;
break;
case MultiTerrainOperationType.Texture:
rtDescriptor = operation.RTtextureSplatmap.descriptor;
break;
case MultiTerrainOperationType.TerrainDetail:
rtDescriptor = operation.RTdetailmap.descriptor;
break;
case MultiTerrainOperationType.Tree:
rtDescriptor = operation.RTterrainTree.descriptor;
break;
case MultiTerrainOperationType.GameObject:
rtDescriptor = operation.RTgameObject.descriptor;
break;
default:
rtDescriptor = operation.RTheightmap.descriptor;
break;
}
//Random write needs to be enabled for certain mask types to function!
rtDescriptor.enableRandomWrite = true;
RenderTexture inputTexture = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture currentRT = RenderTexture.active;
RenderTexture.active = inputTexture;
GL.Clear(true, true, Color.white);
RenderTexture.active = currentRT;
//Iterate through all image masks and set up the current paint context in case the shader uses heightmap data
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_multiTerrainOperation = operation;
mask.m_seaLevel = SessionManager.GetSeaLevel();
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
}
//Get the combined masks for the biome
RenderTexture biomeOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(ImageProcessing.ApplyMaskStack(inputTexture, biomeOutputTexture, m_settings.m_imageMasks, ImageMaskInfluence.Local), biomeOutputTexture);
ReleaseRenderTexture(inputTexture);
inputTexture = null;
return biomeOutputTexture;
}
private void ReleaseRenderTexture(RenderTexture texture)
{
if (texture != null)
{
RenderTexture.ReleaseTemporary(texture);
texture = null;
}
}
public void RemoveForeignTrees(List<SpawnerSettings> biomeSpawnerSettings, List<string> validTerrainNames = null)
{
ProgressBar.Show(ProgressBarPriority.BiomeRemoval, "Removing Foreign Trees", "Removing...");
//Collect the "allowed" tree prefabs in a list, any tree that is not in this list will be affected by the removal operation
List<GameObject> domesticTreePrefabs = new List<GameObject>();
//List<TreePrototype> treeProtosToRemove = new List<TreePrototype>();
Terrain currentTerrain = GetCurrentTerrain();
foreach (SpawnerSettings spawnerSettings in biomeSpawnerSettings)
{
foreach (SpawnRule sr in spawnerSettings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTree)
{
domesticTreePrefabs.Add(spawnerSettings.m_resources.m_treePrototypes[sr.m_resourceIdx].m_desktopPrefab);
}
}
}
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_range * 2f, false, validTerrainNames);
operation.GetHeightmap();
operation.GetNormalmap();
operation.CollectTerrainDetails();
operation.CollectTerrainTrees();
operation.CollectTerrainGameObjects();
operation.CollectTerrainBakedMasks();
operation.RemoveForeignTrees(ApplyBrush(operation,MultiTerrainOperationType.Tree), domesticTreePrefabs, m_settings.m_removeForeignTreesStrength);
ProgressBar.Clear(ProgressBarPriority.BiomeRemoval);
operation.CloseOperation();
}
public void RemoveForeignGameObjects(List<SpawnerSettings> biomeSpawnerSettings, List<string> validTerrainNames=null)
{
ProgressBar.Show(ProgressBarPriority.BiomeRemoval, "Removing Foreign GameObjects", "Removing...");
List<ResourceProtoGameObjectInstance> knownProtoInstances = new List<ResourceProtoGameObjectInstance>();
List<ResourceProtoGameObjectInstance> GoProtoInstancesToRemove = new List<ResourceProtoGameObjectInstance>();
Terrain currentTerrain = GetCurrentTerrain();
foreach (SpawnerSettings spawnerSettings in biomeSpawnerSettings)
{
foreach (SpawnRule sr in spawnerSettings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.GameObject)
{
foreach (ResourceProtoGameObjectInstance instance in spawnerSettings.m_resources.m_gameObjectPrototypes[sr.m_resourceIdx].m_instances)
{
knownProtoInstances.Add(instance);
}
}
}
}
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_range * 2f, false, validTerrainNames);
operation.GetHeightmap();
operation.GetNormalmap();
operation.CollectTerrainDetails();
operation.CollectTerrainTrees();
operation.CollectTerrainGameObjects();
operation.CollectTerrainBakedMasks();
int protoIndex = 0;
var allSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
foreach (Spawner spawner in allSpawners)
{
//During the removal we have to put the spawn settings in remove mode
SpawnMode originalMode = spawner.m_settings.spawnMode;
spawner.m_settings.spawnMode = SpawnMode.Remove;
ProgressBar.Show(ProgressBarPriority.BiomeRemoval, "Removing Foreign GameObjects", "Removing Game Objects...", protoIndex , allSpawners.Length);
foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.GameObject)
{
ResourceProtoGameObject protoGO = spawner.m_settings.m_resources.m_gameObjectPrototypes[sr.m_resourceIdx];
foreach (ResourceProtoGameObjectInstance instance in protoGO.m_instances)
{
if (!knownProtoInstances.Contains(instance))
{
operation.SetTerrainGameObjects(ApplyBrush(operation, MultiTerrainOperationType.GameObject), protoGO, sr, spawner.m_settings, 0, ref sr.m_spawnedInstances, m_settings.m_removeForeignGameObjectStrength,false);
//no need to look at other instances if this one triggered the removal already
break;
}
}
}
}
spawner.m_settings.spawnMode = originalMode;
protoIndex++;
}
operation.CloseOperation();
#if UNITY_EDITOR
//need to dirty the scene when we remove game objects
EditorSceneManager.MarkSceneDirty(gameObject.scene);
#endif
ProgressBar.Clear(ProgressBarPriority.BiomeRemoval);
}
public void RemoveForeignTerrainDetails(List<SpawnerSettings> biomeSpawnerSettings, List<string> validTerrainNames = null)
{
ProgressBar.Show(ProgressBarPriority.BiomeRemoval, "Removing Foreign Terrain Details", "Removing...");
List<ResourceProtoDetail> knownTerrainDetails = new List<ResourceProtoDetail>();
Terrain currentTerrain = GetCurrentTerrain();
foreach (SpawnerSettings spawnerSettings in biomeSpawnerSettings)
{
foreach (SpawnRule sr in spawnerSettings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainDetail)
{
knownTerrainDetails.Add(spawnerSettings.m_resources.m_detailPrototypes[sr.m_resourceIdx]);
}
}
}
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_range * 2f, false, validTerrainNames);
operation.GetHeightmap();
operation.GetNormalmap();
operation.CollectTerrainDetails();
operation.CollectTerrainTrees();
operation.CollectTerrainGameObjects();
operation.CollectTerrainBakedMasks();
ProgressBar.Show(ProgressBarPriority.BiomeRemoval, "Removing Foreign Terrain Details", "Removing Terrain Details...");
operation.RemoveForeignTerrainDetails(ApplyBrush(operation, MultiTerrainOperationType.TerrainDetail), knownTerrainDetails, m_settings.m_removeForeignTerrainDetailsStrength);
operation.CloseOperation();
ProgressBar.Clear(ProgressBarPriority.BiomeRemoval);
}
}
}