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

6025 lines
273 KiB
C#

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using static Gaia.GaiaConstants;
using System.Text;
using UnityEngine.SceneManagement;
using ProcedualWorlds.WaterSystem;
#if CTS_PRESENT
using CTS;
#endif
#if UNITY_EDITOR
using UnityEditor.UIElements;
using UnityEditor.SceneManagement;
using UnityEditor;
#endif
using ProcedualWorlds.HierachySystem;
namespace Gaia
{
[System.Serializable]
public enum ClearSpawnFrom { AnySource, OnlyThisSpawner }
public enum ClearSpawnFromBiomes { AnySource, OnlyThisBiome }
public enum ClearSpawnFor { CurrentTerrainOnly, AllTerrains }
/// <summary>
/// simple data structure to store a protototype id and a terrain together
/// used to log which prototype ids were already deleted from a terrain during world spawns
/// </summary>
public class TerrainPrototypeId
{
public Terrain terrain;
public int prototypeId;
}
/// <summary>
/// Simple data structure to store spawn rules with missing prototypes per terrain.
/// This is used during spawning to make sure prototypes are only placed on terrains where they are actually used.
/// </summary>
public class TerrainMissingSpawnRules
{
public Terrain terrain;
public List<SpawnRule> spawnRulesWithMissingResources = new List<SpawnRule>();
}
/// <summary>
/// Data structure to pass in terrain position information into the simulate compute shader
/// </summary>
struct TerrainPosition
{
public int terrainID;
public Vector2Int min;
public Vector2Int max;
public int affected;
};
/// <summary>
/// A generic spawning system.
/// </summary>
[ExecuteInEditMode]
[System.Serializable]
public class Spawner : MonoBehaviour
{
[SerializeField]
private SpawnerSettings settings;
/// <summary>
/// The current spawner settings
/// </summary>
public SpawnerSettings m_settings
{
get
{
if (settings == null)
{
settings = ScriptableObject.CreateInstance<SpawnerSettings>();
settings.m_resources = new GaiaResource();
settings.m_resources.m_name = "NewResources";
}
return settings;
}
set
{
settings = value;
}
}
[SerializeField]
private WorldCreationSettings worldCreationSettings;
/// <summary>
/// The settings for world creation if this is a random terrain generator spawner running on the world map
/// </summary>
public WorldCreationSettings m_worldCreationSettings
{
get
{
if (worldCreationSettings == null)
{
worldCreationSettings = ScriptableObject.CreateInstance<WorldCreationSettings>();
}
return worldCreationSettings;
}
set
{
worldCreationSettings = value;
}
}
[SerializeField]
private BaseTerrainSettings baseTerrainsettings;
/// <summary>
/// The settings for base terrain creation in this is a random terrain generation spawner on a world map
/// </summary>
public BaseTerrainSettings m_baseTerrainSettings
{
get
{
if (baseTerrainsettings == null)
{
baseTerrainsettings = ScriptableObject.CreateInstance<BaseTerrainSettings>();
}
return baseTerrainsettings;
}
set
{
baseTerrainsettings = value;
}
}
//Holds all the generated stamps for random generation
public List<StamperSettings> m_worldMapStamperSettings = new List<StamperSettings>();
#if GAIA_PRO_PRESENT
private TerrainLoader m_terrainLoader;
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;
}
}
public LoadMode m_loadTerrainMode = LoadMode.EditorSelected;
#endif
#if CTS_PRESENT
[System.NonSerialized]
private CTSProfile m_connectedCTSProfile = null;
//This construct ensures we only serialize the GUID of the CTS profile, but not the profile itself
//The GUID will "survive" when CTS is not installed in a project, while the CTS profile object would not
public CTSProfile ConnectedCTSProfile
{
get
{
if (m_connectedCTSProfile == null && m_connectedCTSProfileGUID != null)
{
#if UNITY_EDITOR
m_connectedCTSProfile = (CTSProfile)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(m_connectedCTSProfileGUID), typeof(CTSProfile));
#endif
}
return m_connectedCTSProfile;
}
set
{
#if UNITY_EDITOR
m_connectedCTSProfileGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(value));
m_connectedCTSProfile = value;
#endif
}
}
#endif
//need this serialized to remember the GUID even when PP is not installed in the project
[SerializeField]
private string m_connectedCTSProfileGUID = "";
public delegate void SpawnFinishedCallback();
#if UNITY_EDITOR
public event SpawnFinishedCallback OnSpawnFinished;
#endif
/// <summary>
/// The spawner ID
/// </summary>
public string m_spawnerID = Guid.NewGuid().ToString();
/// <summary>
/// Operational mode of the spawner
/// </summary>
public Gaia.GaiaConstants.OperationMode m_mode = GaiaConstants.OperationMode.DesignTime;
/// <summary>
/// Source for the random number generator
/// </summary>
public int m_seed = DateTime.Now.Millisecond;
/// <summary>
/// The world map terrain
/// </summary>
public Terrain m_worldMapTerrain;
/// <summary>
/// The shape of the spawner
/// </summary>
public Gaia.GaiaConstants.SpawnerShape m_spawnerShape = GaiaConstants.SpawnerShape.Box;
/// <summary>
/// The rule selection approach
/// </summary>
public Gaia.GaiaConstants.SpawnerRuleSelector m_spawnRuleSelector = GaiaConstants.SpawnerRuleSelector.WeightedFittest;
/// <summary>
/// The type of spawner
/// </summary>
public Gaia.GaiaConstants.SpawnerLocation m_spawnLocationAlgorithm = GaiaConstants.SpawnerLocation.RandomLocation;
/// <summary>
/// The type of check performed at every location
/// </summary>
public Gaia.GaiaConstants.SpawnerLocationCheckType m_spawnLocationCheckType = GaiaConstants.SpawnerLocationCheckType.PointCheck;
/// <summary>
/// The step amount used when EveryLocation is selected
/// </summary>
public float m_locationIncrement = 1f;
/// <summary>
/// The maximum random offset on a jittered location
/// </summary>
public float m_maxJitteredLocationOffsetPct = 0.9f;
/// <summary>
/// Number of times a check is made for a new spawn location every interval
/// </summary>
public int m_locationChecksPerInt = 1;
/// <summary>
/// In seeded mode, this will be the maximum number of individual spawns in a cluster before another locaiton is chosen
/// </summary>
public int m_maxRandomClusterSize = 50;
//public GaiaResource m_resources;
/// <summary>
/// This will allow the user to filter the relative strength of items spawned by distance from the center
/// </summary>
public AnimationCurve m_spawnFitnessAttenuator = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(1f, 1f));
/// <summary>
/// The image fitness filter mode to apply
/// </summary>
public Gaia.GaiaConstants.ImageFitnessFilterMode m_areaMaskMode = Gaia.GaiaConstants.ImageFitnessFilterMode.None;
/// <summary>
/// This will enable ot disable the collider cache at runtime - can be quite handy to keep them on some spawners
/// </summary>
public bool m_enableColliderCacheAtRuntime = false;
/// <summary>
/// This is used to filter the fitness based on the supplied texture, can be used in conjunction with th fitness attenuator
/// </summary>
public Texture2D m_imageMask;
/// <summary>
/// This is used to invert the fitness based on the supplied texture, can also be used in conjunction with the fitness attenuator
/// </summary>
public bool m_imageMaskInvert = false;
/// <summary>
/// This is used to normalise the fitness based on the supplied texture, can also be used in conjunction with the fitness attenuator
/// </summary>
public bool m_imageMaskNormalise = false;
/// <summary>
/// Flip the x, z of the image texture - sometimes required to match source with unity terrain
/// </summary>
public bool m_imageMaskFlip = false;
/// <summary>
/// This is used to smooth the supplied image mask texture
/// </summary>
public int m_imageMaskSmoothIterations = 3;
/// <summary>
/// The heightmap for the image filter
/// </summary>
[NonSerialized]
public HeightMap m_imageMaskHM;
/// <summary>
/// Our noise generator
/// </summary>
private Gaia.FractalGenerator m_noiseGenerator;
/// <summary>
/// Seed for noise based fractal
/// </summary>
public float m_noiseMaskSeed = 0;
/// <summary>
/// The amount of detail in the fractal - more octaves mean more detail and longer calc time.
/// </summary>
public int m_noiseMaskOctaves = 8;
/// <summary>
/// The roughness of the fractal noise. Controls how quickly amplitudes diminish for successive octaves. 0..1.
/// </summary>
public float m_noiseMaskPersistence = 0.25f;
/// <summary>
/// The frequency of the first octave
/// </summary>
public float m_noiseMaskFrequency = 1f;
/// <summary>
/// The frequency multiplier between successive octaves. Experiment between 1.5 - 3.5.
/// </summary>
public float m_noiseMaskLacunarity = 1.5f;
/// <summary>
/// The zoom level of the noise
/// </summary>
public float m_noiseZoom = 10f;
/// <summary>
/// Invert the boise value
/// </summary>
public bool m_noiseInvert = false;
/// <summary>
/// How often the spawner should check to release new instances in seconds
/// </summary>
public float m_spawnInterval = 5f;
/// <summary>
/// The player to use for distance checks
/// </summary>
public string m_triggerTags = "Player";
/// <summary>
/// System will only iterate through spawn rules if the player / trigger object is closer than this distance
/// </summary>
public float m_triggerRange = 130f;
/// <summary>
/// Used to constrain which layers the spawner will attempt to get collisions on - used for virgin detection, terrain detection, tree detection and game object detection
/// </summary>
public LayerMask m_spawnCollisionLayers;
/// <summary>
/// Set to the terrain layer so that colliders are correctly setup
/// </summary>
public int m_spawnColliderLayer = 0;
/// <summary>
/// Whether or not to show gizmos
/// </summary>
public bool m_showGizmos = true;
/// <summary>
/// Whether or not to show debug messages
/// </summary>
public bool m_showDebug = false;
/// <summary>
/// Set to true once the base terrain has been stamped in a world generator spawner
/// </summary>
public bool m_baseTerrainStamped = false;
/// <summary>
/// Whether or not to show statistics
/// </summary>
//public bool m_showStatistics = true;
/// <summary>
/// Whether or not to show the terrain helper
/// </summary>
//public bool m_showTerrainHelper = true;
/// <summary>
/// Random number generator for this spawner - generates locations
/// </summary>
public Gaia.XorshiftPlus m_rndGenerator;
/// <summary>
/// Whether or not we are currently caching texures
/// </summary>
private bool m_cacheDetails = false;
/// <summary>
/// Detail map cache - used when doing area updates on details - indexed by the ID of the terrain it comes from
/// </summary>
private Dictionary<int, List<HeightMap>> m_detailMapCache = new Dictionary<int, List<HeightMap>>();
/// <summary>
/// Whether or not we are currently caching texures
/// </summary>
private bool m_cacheTextures = false;
/// <summary>
/// Set to true if the texture map is modified and needs to be written back to the terrain
/// </summary>
private bool m_textureMapsDirty = false;
/// <summary>
/// Texture map cache - used when doing area updates / reads on textures - indexed by the ID of the terrain it comes from
/// </summary>
private Dictionary<int, List<HeightMap>> m_textureMapCache = new Dictionary<int, List<HeightMap>>();
/// <summary>
/// Whether or not we are currently caching tags
/// </summary>
private bool m_cacheTags = false;
/// <summary>
/// Tagged game object cache
/// </summary>
private Dictionary<string, Quadtree<GameObject>> m_taggedGameObjectCache = new Dictionary<string, Quadtree<GameObject>>();
/// <summary>
/// Whether or not the trees are cached
/// </summary>
//private bool m_cacheTrees = false;
/// <summary>
/// Tree cache
/// </summary>
public TreeManager m_treeCache = new TreeManager();
/// <summary>
/// Whether or not we are currently caching height maps
/// </summary>
private bool m_cacheHeightMaps = false;
/// <summary>
/// Set to true if the height map is modified and needs to be written back to the terrain
/// </summary>
private bool m_heightMapDirty = false;
/// <summary>
/// Height map cache - used when doing area updates / reads on heightmaps - indexed by the ID of the terrain it comes from
/// </summary>
private Dictionary<int, UnityHeightMap> m_heightMapCache = new Dictionary<int, UnityHeightMap>();
/// <summary>
/// Whether or not we are currently caching height maps
/// </summary>
//private bool m_cacheStamps = false;
/// <summary>
/// Stamp cache - used to cache stamps when interacting with heightmaps - activated when heightmap cache is activated
/// </summary>
private Dictionary<string, HeightMap> m_stampCache = new Dictionary<string, HeightMap>();
/// <summary>
/// The sphere collider cache - used to test for area bounds
/// </summary>
[NonSerialized]
public GameObject m_areaBoundsColliderCache;
/// <summary>
/// The game object collider cache - used to test for game object collisions
/// </summary>
[NonSerialized]
public GameObject m_goColliderCache;
/// <summary>
/// The game object parent transform - used to make it easier to rehome spawned game objects
/// </summary>
[NonSerialized]
public GameObject m_goParentGameObject;
/// <summary>
/// Set to true to cancel the spawn
/// </summary>
private static bool m_cancelSpawn = false;
/// <summary>
/// Handy counters for statistics
/// </summary>
//public int m_totalRuleCnt = 0;
//public int m_activeRuleCnt = 0;
//public int m_inactiveRuleCnt = 0;
//public ulong m_maxInstanceCnt = 0;
//public ulong m_activeInstanceCnt = 0;
//public ulong m_inactiveInstanceCnt = 0;
//public ulong m_totalInstanceCnt = 0;
/// <summary>
/// Handy check results - only one check at a time will ever be performed
/// </summary>
private float m_terrainHeight = 0f;
private RaycastHit m_checkHitInfo = new RaycastHit();
/// <summary>
/// Use for co-routine simulation
/// </summary>
public IEnumerator m_updateCoroutine;
/// <summary>
public IEnumerator m_updateCoroutine2;
/// Amount of time per allowed update
/// </summary>
public float m_updateTimeAllowed = 1f / 30f;
/// <summary>
/// Current status
/// </summary>
public float m_spawnProgress = 0f;
/// <summary>
/// Whether or not its completed processing
/// </summary>
public bool m_spawnComplete = true;
/// <summary>
/// The spawner bounds
/// </summary>
public Bounds m_spawnerBounds = new Bounds();
/// <summary>
/// Controls whether the spawn Preview needs to be redrawn
/// </summary>
public bool m_spawnPreviewDirty;
/// <summary>
/// The last active terrain this spawner was displayed for.
/// </summary>
public float m_lastActiveTerrainSize = 1024;
/// <summary>
/// The state of the "Toggle All" checkbox for regular spawn rules on top / end of the spawn rules list
/// </summary>
public bool m_spawnRuleRegularToggleAllState = true;
/// <summary>
/// The state of the "Toggle All" checkbox for stamp spawn rules on top / end of the spawn rules list
/// </summary>
public bool m_spawnRuleBiomeMasksToggleAllState = true;
/// <summary>
/// The state of the "Toggle All" checkbox for world biome mask spawn rules on top / end of the spawn rules list
/// </summary>
public bool m_spawnRuleStampsToggleAllState = true;
/// <summary>
/// Cached settings that are configired during the init call
/// </summary>
private bool m_isTextureSpawner = false;
private bool m_isDetailSpawner = false;
private bool m_isTreeSpawnwer = false;
private bool m_isGameObjectSpawner = false;
private RenderTexture m_cachedPreviewHeightmapRenderTexture;
private RenderTexture[] m_cachedPreviewColorRenderTextures = new RenderTexture[GaiaConstants.maxPreviewedTextures];
private GaiaSettings m_gaiaSettings;
private GaiaSettings GaiaSettings
{
get
{
if (m_gaiaSettings == null)
{
m_gaiaSettings = GaiaUtils.GetGaiaSettings();
}
return m_gaiaSettings;
}
}
public bool m_drawPreview = false;
public List<int> m_previewRuleIds = new List<int>();
public float m_maxWorldHeight;
public float m_minWorldHeight;
public bool m_showSeaLevelinStampPreview = true;
public bool m_rulePanelUnfolded;
public bool m_createBaseTerrainUnfolded = true;
public bool m_exportTerrainUnfolded;
public bool m_worldBiomeMasksUnfolded;
public bool m_createdfromBiomePreset;
public bool m_createdFromGaiaManager;
public bool m_showSeaLevelPlane = true;
public bool m_showBoundingBox = true;
public float m_seaLevel;
//Lists for cleared prototypes when doing multiterrain world spawns
//(Textures and terrain details are handled differently)
private List<TerrainPrototypeId> m_clearedTreeProtos = new List<TerrainPrototypeId>();
private List<TerrainPrototypeId> m_clearedDetailProtos = new List<TerrainPrototypeId>();
private List<TerrainPrototypeId> m_clearedGameObjectProtos = new List<TerrainPrototypeId>();
private List<TerrainPrototypeId> m_clearedSpawnExtensionProtos = new List<TerrainPrototypeId>();
private List<TerrainPrototypeId> m_clearedStampDistributionProtos = new List<TerrainPrototypeId>();
private AnimationCurve m_strengthTransformCurve = ImageMask.NewAnimCurveStraightUpwards();
private Texture2D m_strengthTransformCurveTexture;
public bool m_useExistingTerrainForWorldMapExport;
public bool m_stampOperationsFoldedOut;
public bool m_worldSizeAdvancedUnfolded;
public EnvironmentSize m_worldTileSize;
/// <summary>
/// Used to store the last world size that this (worldmap-)spawner exported to - this is required to check if the user changed the world size in the meantime.
/// </summary>
[SerializeField]
private Vector3 m_lastStampSpawnWorldSize;
/// <summary>
/// Used to store the last heightmap resolution that this (worldmap-)spawner exported to - this is required to check if the user changed the heightmap resolution in the meantime.
/// </summary>
private int m_lastExportedHeightMapResolution;
private Texture2D StrengthTransformCurveTexture
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_strengthTransformCurveTexture);
}
}
private GaiaSessionManager m_sessionManager;
public bool m_qualityPresetAdvancedUnfolded;
public bool m_biomeMaskPanelUnfolded;
public bool m_spawnStampsPanelUnfolded;
public bool m_ExportRunning;
public GaiaConstants.DroppableResourceType m_dropAreaResource;
private GaiaSessionManager SessionManager
{
get
{
if (m_sessionManager == null)
{
m_sessionManager = GaiaSessionManager.GetSessionManager(false);
}
return m_sessionManager;
}
}
/// <summary>
/// Called by unity in editor when this is enabled - unity initialisation is quite opaque!
/// </summary>
void OnEnable()
{
//Check layer mask
if (m_spawnCollisionLayers.value == 0)
{
m_spawnCollisionLayers = Gaia.TerrainHelper.GetActiveTerrainLayer();
}
m_spawnColliderLayer = Gaia.TerrainHelper.GetActiveTerrainLayerAsInt();
//Create the random generator if we dont have one
if (m_rndGenerator == null)
{
m_rndGenerator = new XorshiftPlus(m_seed);
}
//Get the min max height from the current terrain
UpdateMinMaxHeight();
if (m_connectedCTSProfileGUID == "1")
{
//This is just to get rid off the compilation warning when CTS is not installed in the project
}
}
public void ControlSpawnRuleGUIDs()
{
Spawner[] allSpawner = Resources.FindObjectsOfTypeAll<Spawner>();
foreach (SpawnRule rule in m_settings.m_spawnerRules)
{
//check if the spawn rule guid exists in this scene already - if yes, this rule must get a new ID then to avoid duplicate IDs
if (allSpawner.Select(x => x.m_settings.m_spawnerRules).Where(x => x.Find(y => y.GUID == rule.GUID) != null).Count() > 1)
{
rule.RegenerateGUID();
}
}
}
private void OnDestroy()
{
ImageMask.RefreshSpawnRuleGUIDs();
m_settings.ClearImageMaskTextures();
}
void OnDisable()
{
}
/// <summary>
/// Start editor updates
/// </summary>
public void StartEditorUpdates()
{
#if UNITY_EDITOR
m_spawnComplete = false;
EditorApplication.update += EditorUpdate;
#endif
}
//Stop editor updates
public void StopEditorUpdates()
{
#if UNITY_EDITOR
EditorApplication.update -= EditorUpdate;
m_spawnComplete = true;
if (OnSpawnFinished != null)
{
OnSpawnFinished();
}
#endif
}
public void UpdateMinMaxHeight()
{
SessionManager.GetWorldMinMax(ref m_minWorldHeight, ref m_maxWorldHeight, m_settings.m_isWorldmapSpawner);
float seaLevel = SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner);
//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
//General spawner mask first
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
mask.m_seaLevel = seaLevel;
mask.CheckHeightMaskMigration();
}
ImageMask.CheckMaskStackForInvalidTextureRules("Spawner", this.name, m_settings.m_imageMasks);
//Now the individual resource masks
for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
{
ImageMask[] maskStack = m_settings.m_spawnerRules[i].m_imageMasks;
if (maskStack != null && maskStack.Length > 0)
{
foreach (ImageMask mask in maskStack)
{
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
mask.m_seaLevel = seaLevel;
mask.CheckHeightMaskMigration();
}
ImageMask.CheckMaskStackForInvalidTextureRules("Spawner", this.name + ", Spawn Rule: '" + m_settings.m_spawnerRules[i].m_name + "'", maskStack);
}
}
}
/// <summary>
/// Store the last exported terrain size and heightmap resolution - used to determine if the user changed those on a world map spawner
/// </summary>
public void StoreWorldSize()
{
m_lastStampSpawnWorldSize = new Vector3(m_worldCreationSettings.m_xTiles * m_worldCreationSettings.m_tileSize, m_worldMapTerrain.terrainData.size.y, m_worldCreationSettings.m_zTiles * m_worldCreationSettings.m_tileSize);
}
//public void StoreHeightmapResolution()
//{
// m_lastExportedHeightMapResolution = worldCreationSettings.m_gaiaDefaults.m_heightmapResolution;
//}
/// <summary>
/// Returns true if the user changed the world size / heightmap resolution since the last world map export
/// </summary>
/// <returns></returns>
public bool HasWorldSizeChanged()
{
return m_lastStampSpawnWorldSize != new Vector3(m_worldCreationSettings.m_xTiles * m_worldCreationSettings.m_tileSize, m_worldCreationSettings.m_tileHeight, m_worldCreationSettings.m_zTiles * m_worldCreationSettings.m_tileSize);
}
//public bool HasHeightmapResolutionChanged()
//{
// return m_lastExportedHeightMapResolution != worldCreationSettings.m_gaiaDefaults.m_heightmapResolution;
//}
/// <summary>
/// This is executed only in the editor - using it to simulate co-routine execution and update execution
/// </summary>
void EditorUpdate()
{
#if UNITY_EDITOR
if (m_updateCoroutine == null)
{
StopEditorUpdates();
if (SessionManager.m_session.m_operations.Exists(x => x.sessionPlaybackState == SessionPlaybackState.Queued))
{
GaiaSessionManager.ContinueSessionPlayback();
}
else
{
//No session to continue -> destroy the temporary tools, if any
GaiaSessionManager.AfterSessionPlaybackCleanup();
}
return;
}
else
{
if (EditorWindow.mouseOverWindow != null)
{
m_updateTimeAllowed = 1 / 30f;
}
else
{
m_updateTimeAllowed = 1 / 2f;
}
if (m_updateCoroutine2 != null)
{
m_updateCoroutine2.MoveNext();
}
m_updateCoroutine.MoveNext();
}
#endif
}
/// <summary>
/// Use this for initialization - this will kick the spawner off
/// </summary>
void Start()
{
//Disable the colliders
if (Application.isPlaying)
{
//Disable area bounds colliders
Transform collTrans = this.transform.Find("Bounds_ColliderCache");
if (collTrans != null)
{
m_areaBoundsColliderCache = collTrans.gameObject;
m_areaBoundsColliderCache.SetActive(false);
}
if (!m_enableColliderCacheAtRuntime)
{
collTrans = this.transform.Find("GameObject_ColliderCache");
if (collTrans != null)
{
m_goColliderCache = collTrans.gameObject;
m_goColliderCache.SetActive(false);
}
}
}
if (m_mode == GaiaConstants.OperationMode.RuntimeInterval || m_mode == GaiaConstants.OperationMode.RuntimeTriggeredInterval)
{
//Initialise the spawner
Initialise();
//Start spawner checks in random period of time after game start, then every check interval
InvokeRepeating("RunSpawnerIteration", 1f, m_spawnInterval);
}
}
/// <summary>
/// Build the spawner dictionary - allows for efficient updating of instances etc based on name
/// </summary>
public void Initialise()
{
if (m_showDebug)
{
Debug.Log("Initialising spawner");
}
//Set up layer for spawner collisions
m_spawnColliderLayer = Gaia.TerrainHelper.GetActiveTerrainLayerAsInt();
//Destroy any children
List<Transform> transList = new List<Transform>();
foreach (Transform child in transform)
{
transList.Add(child);
}
foreach (Transform child in transList)
{
if (Application.isPlaying)
{
Destroy(child.gameObject);
}
else
{
DestroyImmediate(child.gameObject);
}
}
//Set up the spawner type flags
SetUpSpawnerTypeFlags();
//Create the game object parent transform
if (IsGameObjectSpawner())
{
m_goParentGameObject = new GameObject("Spawned_GameObjects");
m_goParentGameObject.transform.parent = this.transform;
m_areaBoundsColliderCache = new GameObject("Bounds_ColliderCache");
m_areaBoundsColliderCache.transform.parent = this.transform;
m_goColliderCache = new GameObject("GameObject_ColliderCache");
m_goColliderCache.transform.parent = this.transform;
}
//Reset the random number generator
ResetRandomGenertor();
//Get terrain height - assume all terrains same height
Terrain t = TerrainHelper.GetTerrain(transform.position);
if (t != null)
{
m_terrainHeight = t.terrainData.size.y;
}
//Set the spawner bounds
m_spawnerBounds = new Bounds(transform.position, new Vector3(m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f));
//Update the rule counters
foreach (SpawnRule rule in m_settings.m_spawnerRules)
{
rule.m_currInstanceCnt = 0;
rule.m_activeInstanceCnt = 0;
rule.m_inactiveInstanceCnt = 0;
}
//Update the counters
UpdateCounters();
}
/// <summary>
/// Call this prior to doing a Spawn to do any setup required - particularly relevant for re-constituted spanwes
/// </summary>
private void PreSpawnInitialise()
{
//Update bounds
m_spawnerBounds = new Bounds(transform.position, new Vector3(m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f));
//Make sure random number generator is online
if (m_rndGenerator == null)
{
ResetRandomGenertor();
}
//Debug.Log(string.Format("RNG {0} Seed = {1} State A = {2} State B = {3}", gameObject.name, m_rndGenerator.m_seed, m_rndGenerator.m_stateA, m_rndGenerator.m_stateB));
//Set up layer for spawner collisions
m_spawnColliderLayer = Gaia.TerrainHelper.GetActiveTerrainLayerAsInt();
//Set up the spawner type flags
SetUpSpawnerTypeFlags();
//Create the game object parent transform
if (IsGameObjectSpawner())
{
if (transform.Find("Spawned_GameObjects") == null)
{
m_goParentGameObject = new GameObject("Spawned_GameObjects");
m_goParentGameObject.transform.parent = this.transform;
}
if (transform.Find("Bounds_ColliderCache") == null)
{
m_areaBoundsColliderCache = new GameObject("Bounds_ColliderCache");
m_areaBoundsColliderCache.transform.parent = this.transform;
}
if (transform.Find("GameObject_ColliderCache") == null)
{
m_goColliderCache = new GameObject("GameObject_ColliderCache");
m_goColliderCache.transform.parent = this.transform;
}
}
//Initialise spawner themselves
foreach (SpawnRule rule in m_settings.m_spawnerRules)
{
rule.Initialise(this);
}
//Create and initialise the noise generator
if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.PerlinNoise)
{
m_noiseGenerator = new FractalGenerator(m_noiseMaskFrequency, m_noiseMaskLacunarity, m_noiseMaskOctaves, m_noiseMaskPersistence, m_noiseMaskSeed, FractalGenerator.Fractals.Perlin);
}
else if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.BillowNoise)
{
m_noiseGenerator = new FractalGenerator(m_noiseMaskFrequency, m_noiseMaskLacunarity, m_noiseMaskOctaves, m_noiseMaskPersistence, m_noiseMaskSeed, FractalGenerator.Fractals.Billow);
}
else if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.RidgedNoise)
{
m_noiseGenerator = new FractalGenerator(m_noiseMaskFrequency, m_noiseMaskLacunarity, m_noiseMaskOctaves, m_noiseMaskPersistence, m_noiseMaskSeed, FractalGenerator.Fractals.RidgeMulti);
}
//Update the counters
UpdateCounters();
}
/// <summary>
/// Caching spawner type flags
/// </summary>
public void SetUpSpawnerTypeFlags()
{
m_isDetailSpawner = false;
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
if (m_settings.m_spawnerRules[ruleIdx].m_resourceType == GaiaConstants.SpawnerResourceType.TerrainDetail)
{
m_isDetailSpawner = true;
break;
}
}
m_isTextureSpawner = false;
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
if (m_settings.m_spawnerRules[ruleIdx].m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTexture)
{
m_isTextureSpawner = true;
break;
}
}
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
if (m_settings.m_spawnerRules[ruleIdx].m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTree)
{
m_isTreeSpawnwer = true;
break;
}
}
m_isGameObjectSpawner = false;
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
if (m_settings.m_spawnerRules[ruleIdx].m_resourceType == GaiaConstants.SpawnerResourceType.GameObject)
{
m_isGameObjectSpawner = true;
break;
}
}
}
/// <summary>
/// Make sure the assets are set up properly in the resources file
/// </summary>
public void AssociateAssets()
{
if (m_settings.m_resources != null)
{
m_settings.m_resources.AssociateAssets();
}
else
{
Debug.LogWarning("Could not associated assets for " + name + " - resources file was missing");
}
}
/// <summary>
/// Get the index of any rules that are missing resources
/// </summary>
/// <returns>Array of the resources that are missing</returns>
/// <param name="terrains">The terrains to be checked. If left null, the active terrain will be checked only.</param>
public List<TerrainMissingSpawnRules> GetMissingResources(List<TerrainMissingSpawnRules> missingRes, Terrain[] terrains = null)
{
if (terrains == null)
{
terrains = new Terrain[1] { Terrain.activeTerrain };
}
if (missingRes == null)
{
missingRes = new List<TerrainMissingSpawnRules>();
}
//Loop over all terrains
for (int terrainID = 0; terrainID < terrains.Length; terrainID++)
{
//skip if no terrain
if (terrains[terrainID] == null)
{
continue;
}
//Initialise spawner themselves
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
if (m_settings.m_spawnerRules[ruleIdx].m_isActive == true) //Only care about active resources
{
//check if there is actually a prototyp
if (m_settings.m_spawnerRules[ruleIdx].ResourceIsNull(m_settings))
{
Debug.Log("Spawn Rule " + m_settings.m_spawnerRules[ruleIdx].m_name + " is active but has missing resources (Texture, Tree Prefab, GameObject etc. are empty) maintained. This rule might not work properly in spawning.");
}
else if (!m_settings.m_spawnerRules[ruleIdx].ResourceIsLoadedInTerrain(this, terrains[terrainID]))
{
TerrainMissingSpawnRules terrainSpawnRulesId = missingRes.Find(x => x.terrain == terrains[terrainID]);
if (terrainSpawnRulesId != null)
{
if (!terrainSpawnRulesId.spawnRulesWithMissingResources.Contains(m_settings.m_spawnerRules[ruleIdx]))
{
terrainSpawnRulesId.spawnRulesWithMissingResources.Add(m_settings.m_spawnerRules[ruleIdx]);
}
}
else
{
missingRes.Add(new TerrainMissingSpawnRules { terrain = terrains[terrainID], spawnRulesWithMissingResources = new List<SpawnRule>() { m_settings.m_spawnerRules[ruleIdx] } });
}
}
}
}
}
return missingRes;
}
/// <summary>
/// Add the resources related to the rules passed in into the terrain if they are not already there
/// </summary>
/// <param name="rules">Index of rules with resources that should be added to the terrain</param>
public void AddResourcesToTerrain(int[] rules, Terrain[] terrains = null)
{
for (int terrainId = 0; terrainId < terrains.Length; terrainId++)
{
for (int ruleIdx = 0; ruleIdx < rules.GetLength(0); ruleIdx++)
{
if (!m_settings.m_spawnerRules[rules[ruleIdx]].ResourceIsLoadedInTerrain(this, terrains[terrainId]))
{
m_settings.m_spawnerRules[rules[ruleIdx]].AddResourceToTerrain(this, new Terrain[1] { terrains[terrainId] });
}
}
}
}
/// <summary>
/// Call this at the end of a spawn
/// </summary>
private void PostSpawn()
{
//Signal that everything has stopped
m_spawnProgress = 0f;
m_spawnComplete = true;
m_updateCoroutine = null;
//Update the counters
UpdateCounters();
}
/// <summary>
/// Return true if this spawner spawns textures
/// </summary>
/// <returns>True if we spawn textures</returns>
public bool IsTextureSpawner()
{
return m_isTextureSpawner;
}
/// <summary>
/// Return true if this spawner spawns details
/// </summary>
/// <returns>True if we spawn details</returns>
public bool IsDetailSpawner()
{
return m_isDetailSpawner;
}
/// <summary>
/// Return true if this spawner spawns trees
/// </summary>
/// <returns>True if we spawn trees</returns>
public bool IsTreeSpawner()
{
return m_isTreeSpawnwer;
}
/// <summary>
/// Return true if this spawner spawns game objects
/// </summary>
/// <returns>True if we spawn game objects</returns>
public bool IsGameObjectSpawner()
{
return m_isGameObjectSpawner;
}
/// <summary>
/// Reste the spawner and delete everything it points to
/// </summary>
public void ResetSpawner()
{
Initialise();
}
public void UpdateAutoLoadRange()
{
//world map spawner should not load terrains
if (m_settings.m_isWorldmapSpawner)
{
return;
}
#if GAIA_PRO_PRESENT
if (m_loadTerrainMode != LoadMode.Disabled)
{
float width = m_settings.m_spawnRange * 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
}
/// <summary>
/// Cause any active spawn to cancel itself
/// </summary>
public void CancelSpawn()
{
m_cancelSpawn = true;
m_spawnComplete = true;
m_spawnProgress = 0f;
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
/// <summary>
/// Returns true if we are currently in process of spawning
/// </summary>
/// <returns>True if spawning, false otherwise</returns>
public bool IsSpawning()
{
return (m_spawnComplete != true);
}
/// <summary>
/// Check to see if this spawner can spawn instances
/// </summary>
/// <returns>True if it can spawn instances, false otherwise</returns>
private bool CanSpawnInstances()
{
SpawnRule rule;
bool canSpawnInstances = false;
for (int ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
rule = m_settings.m_spawnerRules[ruleIdx];
if (rule.m_isActive)
{
if (rule.m_ignoreMaxInstances)
{
return true;
}
if (rule.m_activeInstanceCnt < rule.m_maxInstances)
{
return true;
}
}
}
return canSpawnInstances;
}
public void DrawSpawnerPreview()
{
if (m_drawPreview)
{
GaiaStopwatch.StartEvent("Drawing Spawner Preview");
//early out if no preview rule is active
bool foundActive = false;
for (int i = 0; i < m_previewRuleIds.Count; i++)
{
if (m_previewRuleIds[i] < m_settings.m_spawnerRules.Count)
{
if (m_settings.m_spawnerRules[m_previewRuleIds[i]].m_isActive)
{
foundActive = true;
}
}
}
if (!foundActive)
{
return;
}
//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_spawnRange * 2f);
operation.m_isWorldMapOperation = m_settings.m_isWorldmapSpawner;
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_spawnPreviewDirty == true)
{
//To get a combined preview of different textures on a single mesh we need one color texture each per previewed
// rule to determine the color areas on the heightmap mesh
// We need to iterate over the rules that are previewed, and build those color textures in this process
//Get additional op data (required for certain image masks)
operation.GetNormalmap();
operation.CollectTerrainBakedMasks();
//Preparing a simple add operation on the image mask shader for the combined heightmap texture
//Material filterMat = new Material(Shader.Find("Hidden/Gaia/FilterImageMask"));
//filterMat.SetFloat("_Strength", 1f);
//filterMat.SetInt("_Invert", 0);
//Store the currently active render texture here before we start manipulating
RenderTexture currentRT = RenderTexture.active;
//Clear texture cache first
ClearColorTextureCache();
//bool firstActiveRule = true;
for (int i = 0; i < m_previewRuleIds.Count; i++)
{
if (m_settings.m_spawnerRules[m_previewRuleIds[i]].m_isActive)
{
//Initialise our color texture cache at this index with this context
InitialiseColorTextureCache(i, operation.RTheightmap);
//Store result for this rule in our cache render texture array
Graphics.Blit(ApplyBrush(operation, MultiTerrainOperationType.Heightmap, m_previewRuleIds[i]), m_cachedPreviewColorRenderTextures[i]);
RenderTexture.active = currentRT;
}
}
//Everything processed, check if the preview is not dirty anymore
m_spawnPreviewDirty = false;
}
//Now draw the preview according to the cached textures
Material material = GaiaMultiTerrainOperation.GetDefaultGaiaSpawnerPreviewMaterial();
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
//assign all color textures in the material
for (int i = 0; i < m_cachedPreviewColorRenderTextures.Length; i++)
{
material.SetTexture("_colorTexture" + i, m_cachedPreviewColorRenderTextures[i]);
}
//iterate through spawn rules, and if it is a previewed texture set its color accordingly in the color slot
int colorIndex = 0;
for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
{
if (m_previewRuleIds.Contains(i))
{
material.SetColor("_previewColor" + colorIndex.ToString(), m_settings.m_spawnerRules[m_previewRuleIds[colorIndex]].m_visualisationColor);
colorIndex++;
}
}
for (; colorIndex < GaiaConstants.maxPreviewedTextures; colorIndex++)
{
Color transparentColor = Color.white;
transparentColor.a = 0f;
material.SetColor("_previewColor" + colorIndex.ToString(), transparentColor);
}
Color seaLevelColor = GaiaSettings.m_stamperSeaLevelTintColor;
if (!m_showSeaLevelinStampPreview)
{
seaLevelColor.a = 0f;
}
material.SetColor("_seaLevelTintColor", seaLevelColor);
material.SetFloat("_seaLevel", SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner));
operation.Visualize(MultiTerrainOperationType.Heightmap, operation.RTheightmap, material, 1);
//Clean up
operation.CloseOperation();
//Clean up temp textures
GaiaUtils.ReleaseAllTempRenderTextures();
GaiaStopwatch.EndEvent("Drawing Spawner Preview");
GaiaStopwatch.Stop();
}
}
private void ClearCachedTexture(RenderTexture cachedRT)
{
if (cachedRT != null)
{
cachedRT.Release();
DestroyImmediate(cachedRT);
}
cachedRT = new RenderTexture(1, 1, 1);
RenderTexture currentRT = RenderTexture.active;
RenderTexture.active = cachedRT;
GL.Clear(true, true, Color.black);
RenderTexture.active = currentRT;
}
private void ClearColorTextureCache()
{
for (int i = 0; i < m_cachedPreviewColorRenderTextures.Length; i++)
{
ClearCachedTexture(m_cachedPreviewColorRenderTextures[i]);
}
}
/// <summary>
/// Inizialises or "resets" a color texture in the cache
/// </summary>
/// <param name="index">The index at which to initialise.</param>
/// <param name="rtToInitialiseFrom">A sample render texture with the correct resolution & format settings etc. to initialise from</param>
private void InitialiseColorTextureCache(int index, RenderTexture rtToInitialiseFrom)
{
ClearCachedTexture(m_cachedPreviewColorRenderTextures[index]);
m_cachedPreviewColorRenderTextures[index] = new RenderTexture(rtToInitialiseFrom);
}
private RenderTexture ApplyBrush(GaiaMultiTerrainOperation operation, MultiTerrainOperationType opType, int spawnRuleID = 0)
{
Terrain currentTerrain = GetCurrentTerrain();
RenderTextureDescriptor rtDescriptor;
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 inputTexture1 = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture inputTexture2 = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture inputTexture3 = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture currentRT = RenderTexture.active;
RenderTexture.active = inputTexture1;
GL.Clear(true, true, Color.white);
RenderTexture.active = inputTexture2;
GL.Clear(true, true, Color.white);
RenderTexture.active = inputTexture3;
GL.Clear(true, true, Color.white);
RenderTexture.active = currentRT;
//fetch the biome mask stack (if any)
BiomeController biomeController = Resources.FindObjectsOfTypeAll<BiomeController>().FirstOrDefault(x => x.m_autoSpawners.Find(y => y.spawner == this) != null);
ImageMask[] biomeControllerStack = new ImageMask[0];
if (biomeController != null && biomeController.m_settings.m_imageMasks.Length > 0)
{
biomeControllerStack = biomeController.m_settings.m_imageMasks;
biomeControllerStack[0].m_blendMode = ImageMaskBlendMode.Multiply;
//Iterate through all image masks and set up the current paint context in case the shader uses heightmap data
foreach (ImageMask mask in biomeControllerStack)
{
mask.m_multiTerrainOperation = operation;
mask.m_seaLevel = SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner);
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Biome Controller", biomeController.name, biomeControllerStack);
}
ImageMask[] spawnerStack = new ImageMask[0];
//set up the spawner mask stack, only if it has masks or a biome controller exists with masks
if (m_settings.m_imageMasks.Length > 0)
{
spawnerStack = m_settings.m_imageMasks;
//We start from a white texture, so we need the first mask action in the stack to always be "Multiply", otherwise there will be no result.
spawnerStack[0].m_blendMode = ImageMaskBlendMode.Multiply;
//Iterate through all image masks and set up the current paint context in case the shader uses heightmap data
foreach (ImageMask mask in spawnerStack)
{
mask.m_multiTerrainOperation = operation;
mask.m_seaLevel = SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner);
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Spawner", this.name, spawnerStack);
}
//set up the resource mask stack
ImageMask[] maskStack = m_settings.m_spawnerRules[spawnRuleID].m_imageMasks;
if (maskStack.Length > 0)
{
//We start from a white texture, so we need the first mask action in the stack to always be "Multiply", otherwise there will be no result.
maskStack[0].m_blendMode = ImageMaskBlendMode.Multiply;
//Iterate through all image masks and set up the current paint context in case the shader uses heightmap data
foreach (ImageMask mask in maskStack)
{
mask.m_multiTerrainOperation = operation;
mask.m_seaLevel = SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner);
mask.m_maxWorldHeight = m_maxWorldHeight;
mask.m_minWorldHeight = m_minWorldHeight;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Spawner", this.name + ", Spawn Rule: '" + m_settings.m_spawnerRules[spawnRuleID].m_name + "'", maskStack);
}
//Get the combined masks from the biomeController
RenderTexture biomeOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(ImageProcessing.ApplyMaskStack(inputTexture1, biomeOutputTexture, biomeControllerStack, ImageMaskInfluence.Local), biomeOutputTexture);
//Get the combined masks from the spawner
RenderTexture spawnerOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(ImageProcessing.ApplyMaskStack(inputTexture2, spawnerOutputTexture, spawnerStack, ImageMaskInfluence.Local), spawnerOutputTexture);
//Get the combined masks from the rule
RenderTexture ruleOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(ImageProcessing.ApplyMaskStack(inputTexture3, ruleOutputTexture, maskStack, ImageMaskInfluence.Local), ruleOutputTexture);
//Run them through the image mask shader for a simple multiply
Material filterMat = new Material(Shader.Find("Hidden/Gaia/FilterImageMask"));
filterMat.SetTexture("_InputTex", biomeOutputTexture);
ImageProcessing.BakeCurveTexture(m_strengthTransformCurve, StrengthTransformCurveTexture);
filterMat.SetTexture("_HeightTransformTex", StrengthTransformCurveTexture);
filterMat.SetTexture("_ImageMaskTex", spawnerOutputTexture);
RenderTexture combinedOutputTexture1 = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(inputTexture1, combinedOutputTexture1, filterMat, 0);
filterMat.SetTexture("_InputTex", combinedOutputTexture1);
filterMat.SetTexture("_ImageMaskTex", ruleOutputTexture);
RenderTexture finalOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
Graphics.Blit(inputTexture1, finalOutputTexture, filterMat, 0);
//clean up temporary textures
ReleaseRenderTexture(inputTexture1);
inputTexture1 = null;
ReleaseRenderTexture(inputTexture2);
inputTexture2 = null;
ReleaseRenderTexture(inputTexture3);
inputTexture3 = null;
ReleaseRenderTexture(biomeOutputTexture);
biomeOutputTexture = null;
ReleaseRenderTexture(spawnerOutputTexture);
spawnerOutputTexture = null;
ReleaseRenderTexture(ruleOutputTexture);
ruleOutputTexture = null;
ReleaseRenderTexture(combinedOutputTexture1);
combinedOutputTexture1 = null;
//Release the texture references from the biome controller, if any
if (biomeController != null)
{
biomeController.m_settings.ClearImageMaskTextures();
}
return finalOutputTexture;
}
public Terrain GetCurrentTerrain()
{
Terrain currentTerrain = Gaia.TerrainHelper.GetTerrain(transform.position, m_settings.m_isWorldmapSpawner);
//Check if the stamper is over a terrain currently
//if not, we check if there is any terrain within the bounds of the spawner
if (currentTerrain == null)
{
float width = m_settings.m_spawnRange * 2f;
Bounds spawnerBounds = 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 (m_settings.m_isWorldmapSpawner == TerrainHelper.IsWorldMapTerrain(t))
{
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(spawnerBounds))
{
currentTerrain = t;
break;
}
}
}
}
//if we still not have any terrain, we will draw a preview based on the last active terrain
//if that is null either we can't draw a stamp preview
if (currentTerrain)
{
m_lastActiveTerrainSize = currentTerrain.terrainData.size.x;
//Update last active terrain with current
}
return currentTerrain;
}
private void ReleaseRenderTexture(RenderTexture texture)
{
if (texture != null)
{
RenderTexture.ReleaseTemporary(texture);
texture = null;
}
}
//public ImageMask[] GetSpawnRuleImageMasksByIndex(int spawnRuleIndex)
//{
// //Get the right mask list from the resources according to the resource type that is used
// switch (m_spawnerRules[spawnRuleIndex].m_resourceType)
// {
// case GaiaConstants.SpawnerResourceType.TerrainTexture:
// return m_resources.m_texturePrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks;
// case GaiaConstants.SpawnerResourceType.TerrainDetail:
// return m_resources.m_detailPrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks;
// case GaiaConstants.SpawnerResourceType.TerrainTree:
// return m_resources.m_treePrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks;
// case GaiaConstants.SpawnerResourceType.GameObject:
// return m_resources.m_gameObjectPrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks;
// }
// return null;
//}
//public CollisionMask[] GetSpawnRuleCollisionMasksByIndices(int spawnRuleIndex, int maskIndex)
//{
// //Get the right collision mask list from the resources according to the resource type that is used
// return GetSpawnRuleImageMasksByIndex(spawnRuleIndex)[maskIndex].m_collisionMasks;
//}
//public void SetSpawnRuleImageMasksByIndex(int spawnRuleIndex, ImageMask[] imageMasks)
//{
// //Get the right mask list from the resources according to the resource type that is used
// switch (m_spawnerRules[spawnRuleIndex].m_resourceType)
// {
// case GaiaConstants.SpawnerResourceType.TerrainTexture:
// m_resources.m_texturePrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks = imageMasks;
// break;
// case GaiaConstants.SpawnerResourceType.TerrainDetail:
// m_resources.m_detailPrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks = imageMasks;
// break;
// case GaiaConstants.SpawnerResourceType.TerrainTree:
// m_resources.m_treePrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks = imageMasks;
// break;
// case GaiaConstants.SpawnerResourceType.GameObject:
// m_resources.m_gameObjectPrototypes[m_spawnerRules[spawnRuleIndex].m_resourceIdx].m_imageMasks = imageMasks;
// break;
// }
//}
//public void SetSpawnRuleCollisionMasksByIndices(int spawnRuleIndex, int maskIndex, CollisionMask[] collisionMasks)
//{
// m_spawnerRules[spawnRuleIndex].m_imageMasks[maskIndex].m_collisionMasks = collisionMasks;
//}
//public Color GetVisualisationColorBySpawnRuleIndex(int spawnRuleIndex)
//{
// switch (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceType)
// {
// case GaiaConstants.SpawnerResourceType.TerrainTexture:
// ResourceProtoTexture protoTexture = (ResourceProtoTexture)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoTexture != null)
// return protoTexture.m_visualisationColor;
// else
// return Color.red;
// case GaiaConstants.SpawnerResourceType.TerrainTree:
// ResourceProtoTree protoTree = (ResourceProtoTree)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoTree != null)
// return protoTree.m_visualisationColor;
// else
// return Color.red;
// case GaiaConstants.SpawnerResourceType.TerrainDetail:
// ResourceProtoDetail protoDetail = (ResourceProtoDetail)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoDetail != null)
// return protoDetail.m_visualisationColor;
// else
// return Color.red;
// case GaiaConstants.SpawnerResourceType.GameObject:
// ResourceProtoGameObject protoGameObject = (ResourceProtoGameObject)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoGameObject != null)
// return protoGameObject.m_visualisationColor;
// else
// return Color.red;
// }
// return Color.red;
//}
//public void SetVisualisationColorBySpawnRuleIndex(Color color, int spawnRuleIndex)
//{
// switch (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceType)
// {
// case GaiaConstants.SpawnerResourceType.TerrainTexture:
// ResourceProtoTexture protoTexture = (ResourceProtoTexture)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoTexture != null)
// {
// protoTexture.m_visualisationColor = color;
// }
// break;
// case GaiaConstants.SpawnerResourceType.TerrainTree:
// ResourceProtoTree protoTree = (ResourceProtoTree)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoTree != null)
// {
// protoTree.m_visualisationColor = color;
// }
// break;
// case GaiaConstants.SpawnerResourceType.TerrainDetail:
// ResourceProtoDetail protoDetail = (ResourceProtoDetail)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoDetail != null)
// {
// protoDetail.m_visualisationColor = color;
// }
// break;
// case GaiaConstants.SpawnerResourceType.GameObject:
// ResourceProtoGameObject protoGameObject = (ResourceProtoGameObject)GetResourceProtoBySpawnRuleIndex(spawnRuleIndex);
// if (protoGameObject != null)
// {
// protoGameObject.m_visualisationColor = color;
// }
// break;
// }
//}
public object GetResourceProtoBySpawnRuleIndex(int spawnRuleIndex)
{
switch (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceType)
{
case GaiaConstants.SpawnerResourceType.TerrainTexture:
if (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx < m_settings.m_resources.m_texturePrototypes.Length)
return m_settings.m_resources.m_texturePrototypes[m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx];
else
return null;
case GaiaConstants.SpawnerResourceType.TerrainTree:
if (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx < m_settings.m_resources.m_treePrototypes.Length)
return m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx];
else
return null;
case GaiaConstants.SpawnerResourceType.TerrainDetail:
if (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx < m_settings.m_resources.m_detailPrototypes.Length)
return m_settings.m_resources.m_detailPrototypes[m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx];
else
return null;
case GaiaConstants.SpawnerResourceType.GameObject:
if (m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx < m_settings.m_resources.m_gameObjectPrototypes.Length)
return m_settings.m_resources.m_gameObjectPrototypes[m_settings.m_spawnerRules[spawnRuleIndex].m_resourceIdx];
else
return null;
}
return null;
}
/// <summary>
/// Toggle the preview mesh on and off
/// </summary>
public void TogglePreview()
{
m_drawPreview = !m_drawPreview;
DrawSpawnerPreview();
}
/// <summary>
/// Run a spawner iteration - called by timed invoke or manually
/// </summary>
// public void RunSpawnerIteration()
// {
// //Reset status
// m_cancelSpawn = false;
// m_spawnComplete = false;
// //Perform a spawner iteration preinitialisation
// PreSpawnInitialise();
// //Check that there are rules that can be applied
// if (m_activeRuleCnt <= 0)
// {
// if (m_showDebug)
// {
// Debug.Log(string.Format("{0}: There are no active spawn rules. Can't spawn without rules.", gameObject.name));
// }
// m_spawnComplete = true;
// return;
// }
// //Check that we can actually add new instances
// if (!CanSpawnInstances())
// {
// if (m_showDebug)
// {
// Debug.Log(string.Format("{0}: Can't spawn or activate new instance - max instance count reached.", gameObject.name));
// }
// m_spawnComplete = true;
// return;
// }
// //Call out any issues with terrain height
// Terrain t = TerrainHelper.GetTerrain(transform.position);
// if (t != null)
// {
// m_terrainHeight = t.terrainData.size.y;
// if (m_resources != null && m_resources.m_terrainHeight != m_terrainHeight)
// {
// Debug.LogWarning(string.Format("There is a mismatch between your resources Terrain Height {0} and your actual Terrain Height {1}. Your Spawn may not work as intended!", m_resources.m_terrainHeight, m_terrainHeight));
// }
// }
// //Look for any tagged objects that are acting as triggers and check if they were in range
// if (m_mode == GaiaConstants.OperationMode.RuntimeTriggeredInterval)
// {
// m_checkDistance = m_triggerRange + 1f;
// List<GameObject> triggerObjects = new List<GameObject>();
// string[] tags = new string[0];
// if (!string.IsNullOrEmpty(m_triggerTags))
// {
// tags = m_triggerTags.Split(',');
// }
// else
// {
// Debug.LogError("You have not supplied a trigger tag. Spawner will not spawn!");
// }
// int idx = 0;
// if (m_triggerTags.Length > 0 && tags.Length > 0)
// {
// //Grab the tagged objects
// for (idx = 0; idx < tags.Length; idx++)
// {
// triggerObjects.AddRange(GameObject.FindGameObjectsWithTag(tags[idx]));
// }
// //Now look for anything in range
// for (idx = 0; idx < triggerObjects.Count; idx++)
// {
// m_checkDistance = Vector3.Distance(transform.position, triggerObjects[idx].transform.position);
// if (m_checkDistance <= m_triggerRange)
// {
// break;
// }
// }
// //And if its wasnt found then drop out
// if (m_checkDistance > m_triggerRange)
// {
// if (m_showDebug)
// {
// Debug.Log(string.Format("{0}: No triggers were close enough", gameObject.name));
// }
// m_spawnComplete = true;
// return; //Nothing to do - trigger is too far away
// }
// }
// else
// {
// //Nothing to see, drop out
// if (m_showDebug)
// {
// Debug.Log(string.Format("{0}: No triggers found", gameObject.name));
// }
// m_spawnComplete = true;
// return;
// }
// }
// //Update the session - but only of we are not playing
// if (!Application.isPlaying)
// {
// AddToSession(GaiaOperation.OperationType.Spawn, "Spawning " + transform.name);
// }
// //Run the spawner based on the location selection method chosen
// if (m_spawnLocationAlgorithm == GaiaConstants.SpawnerLocation.RandomLocation || m_spawnLocationAlgorithm == GaiaConstants.SpawnerLocation.RandomLocationClustered)
// {
// #if UNITY_EDITOR
// if (!Application.isPlaying)
// {
// m_updateCoroutine = RunRandomSpawnerIteration();
// StartEditorUpdates();
// }
// else
// {
// StartCoroutine(RunRandomSpawnerIteration());
// }
//#else
// StartCoroutine(RunRandomSpawnerIteration(terrainID));
//#endif
// }
// else
// {
// #if UNITY_EDITOR
// if (!Application.isPlaying)
// {
// m_updateCoroutine = RunAreaSpawnerIteration();
// StartEditorUpdates();
// }
// else
// {
// StartCoroutine(RunAreaSpawnerIteration());
// }
//#else
// StartCoroutine(RunAreaSpawnerIteration(terrainID));
//#endif
// }
// }
public float GetMaxSpawnerRange()
{
Terrain currentTerrain = null;
if (m_settings.m_isWorldmapSpawner)
{
currentTerrain = m_worldMapTerrain;
}
else
{
currentTerrain = GetCurrentTerrain();
}
if (currentTerrain != null)
{
return Mathf.Round((float)8192 / (float)currentTerrain.terrainData.heightmapResolution * currentTerrain.terrainData.size.x / 2f);
}
else
{
return 1000;
}
}
/// <summary>
/// Executes a List of spawners across an area in world space. The spawners are executed in steps defined by the world spawn range in the gaia settings.
/// </summary>
/// <param name="spawners">List of spawners to spawn across the area</param>
/// <param name="area">The area in world space to spawn across</param>
/// <param name="validTerrainNames">The terrain names that are valid to spawn on. If null, all terrains within operation range are assumed valid.</param>
public IEnumerator AreaSpawn(List<Spawner> spawners, BoundsDouble area, List<string> validTerrainNames = null)
{
GaiaStopwatch.StartEvent("Area Spawn");
m_cancelSpawn = false;
GaiaSettings gaiaSettings = GaiaUtils.GetGaiaSettings();
ClearPrototypeLists();
ImageMask.RefreshSpawnRuleGUIDs();
//remember the original world origin and loading range
double originalLoadingRange = TerrainLoaderManager.Instance.GetLoadingRange();
Vector3Double originalOrigin = TerrainLoaderManager.Instance.GetOrigin();
Vector3 startPosition;
float spawnRange;
//Track if we spawn GameObject -> If yes, the scenes affected must be dirtied for saving
bool spawnedGameObjects = false;
//Does the area exceed the range of the world spawn range? if yes,
//this means we need to spawn in multiple locations. Otherwise a single location spawn
//should do the trick.
//World map spawns are always local across the entire world map - since the world map is just a single terrain this works fine
//even if the world map spans 100s of km
float locationIncrement = gaiaSettings.m_spawnerWorldSpawnRange * 2;
if ((area.size.x > gaiaSettings.m_spawnerWorldSpawnRange * 2f || area.size.z > gaiaSettings.m_spawnerWorldSpawnRange * 2f) && !m_settings.m_isWorldmapSpawner)
{
//Multiple locations required
spawnRange = (float)Mathd.Min(gaiaSettings.m_spawnerWorldSpawnRange, Mathd.Min(area.extents.x, area.extents.z));
startPosition = new Vector3Double(area.min.x + spawnRange, 0, area.min.z + spawnRange);
}
else
{
//Single location spawn
spawnRange = (float)Mathd.Max(area.extents.x, area.extents.z);
startPosition = area.center;
//Override the location increment to be so large we only go through the terrain iteration loops once.
locationIncrement = float.MaxValue;
}
//Calculate the maximum amount of rules that need to be spawned in all iterations across the area for the progress bar
int activeSpawnRules = 0;
//Get all active rules
foreach (Spawner spawner in spawners.Where(x => x.isActiveAndEnabled))
{
//Clear all prototype lists while we are at it
spawner.ClearPrototypeLists();
foreach (SpawnRule rule in spawner.settings.m_spawnerRules.Where(x => x.m_resourceType != SpawnerResourceType.WorldBiomeMask))
{
if (rule.m_isActive)
{
activeSpawnRules++;
}
if (rule.m_resourceType == SpawnerResourceType.GameObject || rule.m_resourceType == SpawnerResourceType.SpawnExtension)
{
spawnedGameObjects = true;
}
}
}
//multiply with all locations we need to spawn in
int totalSpawns = activeSpawnRules * Spawner.GetAreaSpawnSteps(area, spawnRange);
int totalSpawnsCompleted = 0;
//Iterating across the area - X Axis
for (Vector3 currentSpawnCenter = startPosition; currentSpawnCenter.x <= (area.max.x + spawnRange / 2f); currentSpawnCenter += new Vector3(locationIncrement, 0f, 0f))
{
if (!m_cancelSpawn)
{
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar("Preparing next Location...", totalSpawns, totalSpawnsCompleted, 0, 0);
}
if (m_cancelSpawn)
{
break;
}
//Iterating across the area - Z Axis
for (currentSpawnCenter = new Vector3(currentSpawnCenter.x, currentSpawnCenter.y, startPosition.z); currentSpawnCenter.z <= (area.max.z + spawnRange / 2f); currentSpawnCenter += new Vector3(0f, 0f, locationIncrement))
{
if (!m_cancelSpawn)
{
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar("Preparing next Location...", totalSpawns, totalSpawnsCompleted, 0, 0);
}
if (m_cancelSpawn)
{
break;
}
//Clear collision mask cache, since it needs to be built up fresh in this location anyways. The cache is then shared between all spawners
BakedMaskCache collisionMaskCache = SessionManager.m_bakedMaskCache;
collisionMaskCache.ClearCacheForSpawn();
GaiaMultiTerrainOperation operation = null;
List<TerrainMissingSpawnRules> terrainsMissingSpawnRules = new List<TerrainMissingSpawnRules>();
//Iterating through all the spawners in this location
foreach (Spawner spawner in spawners)
{
try
{
spawner.UpdateMinMaxHeight();
//Depending on wether we are in a dynamic loading scenario or not, we need to either control the dynamic loading to load terrains in below the spawner
//or move the spawner across the world. The world map spawner should not need to load terrains - just spawns to the world map.
if (GaiaUtils.HasDynamicLoadedTerrains() && !m_settings.m_isWorldmapSpawner)
{
TerrainLoaderManager.Instance.SwitchToLocalMap();
TerrainLoaderManager.Instance.SetOrigin(currentSpawnCenter);
//Remove a tiny bit for the loading range - when the spawner directly aligns with terrain borders
//this will lead to a lot of terrains being loaded in unneccessarily, which takes loinger to process and
//creates issues during spawning.
TerrainLoaderManager.Instance.SetLoadingRange(spawnRange - 0.001f);
spawner.transform.position = Vector3.zero;
spawner.m_settings.m_spawnRange = spawnRange;
}
else
{
if (m_settings.m_isWorldmapSpawner)
{
TerrainLoaderManager.Instance.SwitchToWorldMap();
}
spawner.transform.position = currentSpawnCenter;
spawner.m_settings.m_spawnRange = spawnRange;
}
//spawner.UpdateAutoLoadRange();
//Fake the selection during the spawn so that the terrains will get loaded in any case for spawning
//spawner.TerrainLoader.m_isSelected = true;
//spawner.TerrainLoader.LoadMode = LoadMode.EditorSelected;
//spawner.TerrainLoader.UpdateTerrains();
//yield return null;
//Check for missing resources in the currently loaded terrains.
//This information is passed into the operation which can then
//add the resources "on demand" while spawning.
if (!m_settings.m_isWorldmapSpawner)
{
terrainsMissingSpawnRules = spawner.GetMissingResources(terrainsMissingSpawnRules, Terrain.activeTerrains);
}
Terrain currentTerrain = spawner.GetCurrentTerrain();
if (currentTerrain != null)
{
operation = new GaiaMultiTerrainOperation(currentTerrain, spawner.transform, spawnRange * 2f, true, validTerrainNames);
operation.m_isWorldMapOperation = m_settings.m_isWorldmapSpawner;
operation.m_terrainsMissingSpawnRules = terrainsMissingSpawnRules;
operation.GetHeightmap();
operation.GetNormalmap();
operation.CollectTerrainDetails();
operation.CollectTerrainTrees();
operation.CollectTerrainGameObjects();
operation.CollectTerrainBakedMasks();
spawner.ExecuteSpawn(operation, collisionMaskCache, totalSpawns, ref totalSpawnsCompleted);
if (spawnedGameObjects)
{
foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
{
#if UNITY_EDITOR
EditorSceneManager.MarkSceneDirty(t.gameObject.scene);
//apply the hierarchy hide settings
GaiaHierarchyUtils ghu = t.transform.GetComponentInChildren<GaiaHierarchyUtils>();
if (ghu != null)
{
ghu.SetupHideInHierarchy();
}
#endif
}
}
//Clean up between spawners
operation.CloseOperation();
}
else
{
Debug.LogError("Trying to spawn, but could not find any terrain for spawning!");
}
}
catch (Exception ex)
{
Debug.LogError("Error during spawning, Error Message: " + ex.Message + " Stack Trace: " + ex.StackTrace);
}
finally
{
GaiaUtils.ReleaseAllTempRenderTextures();
spawner.m_spawnPreviewDirty = true;
spawner.SetWorldBiomeMasksDirty();
spawner.m_settings.ClearImageMaskTextures();
}
yield return null;
} //spawners
#if GAIA_PRO_PRESENT
//De-select the loaders only after all spawners ran in this location, otherwise terrains are being loaded / unloaded constantly on yield
foreach (Spawner spawner in spawners)
{
spawner.TerrainLoader.m_isSelected = false;
}
#endif
yield return null;
}// for Z
} // for X
SpawnProgressBar.ClearProgressBar();
m_updateCoroutine = null;
#if UNITY_EDITOR && GAIA_PRO_PRESENT
//if the currently selected object is a spawner we switch back on the "selected" flag
if (Selection.activeObject != null)
{
//try catch needed for the GameObject cast
try
{
Spawner selectedSpawner = ((GameObject)Selection.activeObject).GetComponent<Spawner>();
if (selectedSpawner != null)
{
selectedSpawner.TerrainLoader.m_isSelected = true;
}
}
catch (Exception ex)
{
if (ex.Message == "123")
{
//Preventing compiler warning for unused "ex"
}
}
}
if (GaiaUtils.HasDynamicLoadedTerrains())
{
TerrainLoaderManager.Instance.SetLoadingRange(originalLoadingRange);
TerrainLoaderManager.Instance.SetOrigin(originalOrigin);
}
#endif
GaiaStopwatch.EndEvent("Area Spawn");
GaiaStopwatch.Stop();
yield return null;
}
public void ClearPrototypeLists()
{
m_clearedGameObjectProtos.Clear();
m_clearedSpawnExtensionProtos.Clear();
m_clearedTreeProtos.Clear();
m_clearedDetailProtos.Clear();
m_clearedStampDistributionProtos.Clear();
}
/// <summary>
/// Flags the world biome masks maintained in this spawner as dirty
/// </summary>
public void SetWorldBiomeMasksDirty()
{
if (m_settings.m_isWorldmapSpawner)
{
for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
{
if (m_settings.m_spawnerRules[i].m_resourceType == SpawnerResourceType.WorldBiomeMask)
{
SessionManager.m_bakedMaskCache.SetWorldBiomeMaskDirty(m_settings.m_spawnerRules[i].GUID);
}
}
}
}
private void ExecuteSpawn(GaiaMultiTerrainOperation operation, BakedMaskCache collisionMaskCache, int totalSpawns, ref int totalSpawsCompleted, bool allowStatic = true)
{
GaiaStopwatch.StartEvent("Execute Spawn");
int maxSpawnerRules = m_settings.m_spawnerRules.Where(x => x.m_isActive == true && x.m_resourceType != SpawnerResourceType.WorldBiomeMask).Count();
int completedSpawnerRules = 0;
//Create a new random generator that will use the seed entered in the spawner ui to generate one seed each per spawn rule.
//We can't simply pass down the seed in the rules, otherwise those will produce the same / too similar results
XorshiftPlus xorshiftPlus = new XorshiftPlus(m_settings.m_randomSeed);
xorshiftPlus.NextInt();
//pre generate the seeds for each rule, regardless if the rule is active or not - this allows to deactivate a single rule but still getting the same result from the seed.
int[] randomSeeds = new int[m_settings.m_spawnerRules.Count];
for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
{
randomSeeds[i] = xorshiftPlus.NextInt();
}
if (m_settings.m_isWorldmapSpawner)
{
//clear the stamp operation list first, we will later rebuild it by iterating through all spawned / remaining tokens
m_worldMapStamperSettings.Clear();
}
Terrain currentTerrain = GetCurrentTerrain();
for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
{
//wrap in try-catch to close any progress bars on potential errors & possibly at least continue to spawn the other rules
try
{
if (!m_cancelSpawn)
{
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
}
if (m_cancelSpawn)
{
break;
}
if (m_settings.m_spawnerRules[i].m_isActive)
{
switch (m_settings.m_spawnerRules[i].m_resourceType)
{
case GaiaConstants.SpawnerResourceType.TerrainTexture:
ResourceProtoTexture proto = m_settings.m_resources.m_texturePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
//Look for the layer file associated with the resource in any of the currently active terrains
TerrainLayer targetLayer = TerrainHelper.GetLayerFromPrototype(proto);
operation.GetSplatmap(targetLayer);
RenderTexture tempTextureRT = SimulateRule(operation, i);
//Add missing texture / terrain layer - but only if required according to the simulation!
foreach (TerrainMissingSpawnRules tmsr in operation.m_terrainsMissingSpawnRules)
{
if (operation.affectedTerrainPixels.Where(x => x.Key.terrain == tmsr.terrain && x.Key.operationType == MultiTerrainOperationType.Texture && x.Value.simulationPositive == true).Count() > 0)
{
operation.HandleMissingResources(this, m_settings.m_spawnerRules[i], tmsr.terrain);
}
}
//Look for the target layer again - it might have been added now
if (targetLayer == null)
{
targetLayer = TerrainHelper.GetLayerFromPrototype(proto);
}
if (targetLayer != null)
{
//need to call Get Splatmap again before calling SetSplatmap since texture masks inside there can jeopardize the spawn result.
operation.GetSplatmap(targetLayer);
operation.SetSplatmap(tempTextureRT, this, m_settings.m_spawnerRules[i], false);
}
break;
case GaiaConstants.SpawnerResourceType.TerrainDetail:
RenderTexture tempTerrainDetailRT = SimulateRule(operation, i);
int affectedDetailTerrainsCount = operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.TerrainDetail && x.Value.simulationPositive == true).Count();
if (m_settings.spawnMode == SpawnMode.Replace)
{
SpawnMode originalMode = m_settings.spawnMode;
m_settings.spawnMode = SpawnMode.Add;
foreach (Terrain t in Terrain.activeTerrains)
{
//We only may remove / reset this terrain detail rule once per terrain - otherwise this would destroy earlier detail spawn results in world spawns!
if (m_clearedDetailProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
int terrainDetailIndex = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx, t);
if (terrainDetailIndex != -1)
{
t.terrainData.SetDetailLayer(0, 0, terrainDetailIndex, new int[t.terrainData.detailWidth, t.terrainData.detailHeight]);
}
m_settings.spawnMode = SpawnMode.Replace;
m_clearedDetailProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
if (affectedDetailTerrainsCount > 0)
{
operation.SetTerrainDetails(tempTerrainDetailRT, m_settings, this, m_settings.m_spawnerRules[i], randomSeeds[i], false);
}
m_settings.spawnMode = originalMode;
}
else
{
if (affectedDetailTerrainsCount > 0)
{
operation.SetTerrainDetails(tempTerrainDetailRT, m_settings, this, m_settings.m_spawnerRules[i], randomSeeds[i], false);
}
}
break;
case GaiaConstants.SpawnerResourceType.TerrainTree:
foreach (Terrain t in Terrain.activeTerrains)
{
//We only may remove / reset this tree rule once per terrain - otherwise this would destroy earlier tree spawn results in world spawns!
if (m_settings.spawnMode == SpawnMode.Replace && m_clearedTreeProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
int treePrototypeIndex = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx, t);
t.terrainData.SetTreeInstances(t.terrainData.treeInstances.Where(x => x.prototypeIndex != treePrototypeIndex).ToArray(), true);
m_settings.m_spawnerRules[i].m_spawnedInstances = 0;
m_clearedTreeProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
RenderTexture tempTreeRT = SimulateRule(operation, i);
int affectedTreeTerrainsCount = operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.Tree && x.Value.simulationPositive == true).Count();
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
if (affectedTreeTerrainsCount > 0)
{
//Remember the scaling settings that were last used in a spawn - required to re-scale tree instances when doing a prototype refresh.
m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx].StoreLastUsedScaleSettings();
operation.SetTerrainTrees(tempTreeRT, m_settings, this, m_settings.m_spawnerRules[i], randomSeeds[i], false);
}
collisionMaskCache.SetTreeDirty(m_settings.m_spawnerRules[i].GUID);
break;
case GaiaConstants.SpawnerResourceType.GameObject:
//int goPrototypeIndex = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx);
//We only may remove / reset the Game Object spawner once per terrain- otherwise this would destroy earlier spawn results in world spawns!
foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
{
if (m_settings.spawnMode == SpawnMode.Replace && m_clearedGameObjectProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
ClearGameObjectsForRule(m_settings.m_spawnerRules[i], false, t);
m_clearedGameObjectProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
ResourceProtoGameObject protoGO = m_settings.m_resources.m_gameObjectPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
if (protoGO == null)
{
Debug.LogWarning("Could not find Game Object Prototype for Spawn Rule " + m_settings.m_spawnerRules[i].m_name);
break;
}
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
RenderTexture tempGameObjectRT = SimulateRule(operation, i);
int affectedGameObjectTerrainsCount = operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject && x.Value.simulationPositive == true).Count();
if (affectedGameObjectTerrainsCount > 0)
{
operation.SetTerrainGameObjects(tempGameObjectRT, protoGO, m_settings.m_spawnerRules[i], m_settings, randomSeeds[i], ref m_settings.m_spawnerRules[i].m_spawnedInstances, m_settings.m_spawnerRules[i].m_minRequiredFitness, false);
}
//Dirty affected collision masks - if we spawned Gameobject instances with tags that are used in other collision masks, we must dirty them so they will be re-baked upon request
foreach (ResourceProtoGameObjectInstance instance in protoGO.m_instances)
{
if (instance.m_desktopPrefab.tag != "Untagged")
{
collisionMaskCache.SetTagDirty(instance.m_desktopPrefab.tag);
}
}
break;
case GaiaConstants.SpawnerResourceType.SpawnExtension:
ResourceProtoSpawnExtension protoSpawnExtension = m_settings.m_resources.m_spawnExtensionPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
//We only may remove / reset the Spawn Extension once per terrain - otherwise this would destroy earlier spawn results in world spawns!
foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
{
if (m_settings.spawnMode == SpawnMode.Replace && m_clearedSpawnExtensionProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
ClearSpawnExtensionsForRule(m_settings.m_spawnerRules[i]);
m_clearedSpawnExtensionProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
foreach (ResourceProtoSpawnExtensionInstance instance in protoSpawnExtension.m_instances)
{
GameObject prefab = instance.m_spawnerPrefab;
//Get ALL spawn extensions - could potentially be multiple on prefab
var instanceSpawnExtensions = prefab.GetComponents<ISpawnExtension>();
foreach (ISpawnExtension spawnExtension in instanceSpawnExtensions)
{
spawnExtension.Init(this);
}
}
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
m_cancelSpawn = operation.SetSpawnExtensions(ApplyBrush(operation, MultiTerrainOperationType.GameObject, i), this, protoSpawnExtension, m_settings, i, m_settings.m_spawnerRules[i], randomSeeds[i], ref m_settings.m_spawnerRules[i].m_spawnedInstances, m_settings.m_spawnerRules[i].m_minRequiredFitness, false);
foreach (ResourceProtoSpawnExtensionInstance instance in protoSpawnExtension.m_instances)
{
GameObject prefab = instance.m_spawnerPrefab;
//Get ALL spawn extensions - could potentially be multiple on prefab
var instanceSpawnExtensions = prefab.GetComponents<ISpawnExtension>();
foreach (ISpawnExtension spawnExtension in instanceSpawnExtensions)
{
spawnExtension.Close();
}
}
break;
case GaiaConstants.SpawnerResourceType.StampDistribution:
ResourceProtoStampDistribution protoStampDistribution = m_settings.m_resources.m_stampDistributionPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
//We only may remove / reset the Stamp Distribution once per terrain - otherwise this would destroy earlier spawn results in world spawns!
foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
{
if (m_settings.spawnMode == SpawnMode.Replace && m_clearedStampDistributionProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
ClearStampDistributionForRule(m_settings.m_spawnerRules[i]);
m_clearedStampDistributionProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
//make sure we have defaults in there
if (m_worldCreationSettings.m_gaiaDefaults == null)
{
m_worldCreationSettings.m_gaiaDefaults = Instantiate(GaiaSettings.m_currentDefaults);
}
operation.SetWorldMapStamps(ApplyBrush(operation, MultiTerrainOperationType.GameObject, i), this, protoStampDistribution, m_settings.spawnMode, i, m_settings.m_spawnerRules[i], m_worldCreationSettings, randomSeeds[i], ref m_settings.m_spawnerRules[i].m_spawnedInstances);
break;
case GaiaConstants.SpawnerResourceType.Probe:
ResourceProtoProbe protoProbe = m_settings.m_resources.m_probePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
//We only may remove / reset the Stamp Distribution once per terrain - otherwise this would destroy earlier spawn results in world spawns!
foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
{
if (m_settings.spawnMode == SpawnMode.Replace && m_clearedStampDistributionProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
{
//Deletion is handled by the Clear Game Objects function since the probes are essentially game objects
ClearGameObjectsForRule(m_settings.m_spawnerRules[i]);
m_clearedStampDistributionProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
}
}
m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawns, totalSpawsCompleted, maxSpawnerRules, completedSpawnerRules);
float seaLevel = 0f;
bool seaLevelActive = false;
PWS_WaterSystem gaiawater = GameObject.FindObjectOfType<PWS_WaterSystem>();
if (gaiawater != null)
{
seaLevel = gaiawater.SeaLevel;
seaLevelActive = true;
}
operation.SetProbes(ApplyBrush(operation, MultiTerrainOperationType.GameObject, i), this, protoProbe, m_settings.spawnMode, i, m_settings.m_spawnerRules[i], randomSeeds[i], ref m_settings.m_spawnerRules[i].m_spawnedInstances, seaLevelActive, seaLevel);
break;
}
completedSpawnerRules++;
totalSpawsCompleted++;
}
if (m_cancelSpawn)
{
break;
}
}
catch (Exception ex)
{
SpawnProgressBar.ClearProgressBar();
Debug.LogError("Exception while spawning: " + ex.Message + " Stack Trace: " + ex.StackTrace);
}
}
if (m_settings.m_isWorldmapSpawner)
{
//update the session with the terrain tile settings we just spawned - those are required to display the stamper tokens in the correct size on the world map
TerrainLoaderManager.Instance.TerrainSceneStorage.m_terrainTilesX = m_worldCreationSettings.m_xTiles;
TerrainLoaderManager.Instance.TerrainSceneStorage.m_terrainTilesZ = m_worldCreationSettings.m_zTiles;
//Collect the actual created / remaining stamper settings from the tokens
Transform target = m_worldMapTerrain.transform.Find(GaiaConstants.worldMapStampTokenSpawnTarget);
if (target != null)
{
foreach (Transform t in target)
{
WorldMapStampToken token = t.GetComponent<WorldMapStampToken>();
if (token != null)
{
m_worldMapStamperSettings.Add(token.m_connectedStamperSettings);
}
}
}
}
GaiaStopwatch.EndEvent("Execute Spawn");
}
private RenderTexture SimulateRule(GaiaMultiTerrainOperation operation, int spawnRuleID)
{
SpawnRule spawnRule = m_settings.m_spawnerRules[spawnRuleID];
if (m_gaiaSettings == null)
{
m_gaiaSettings = GaiaUtils.GetGaiaSettings();
}
ComputeShader shader = m_gaiaSettings.m_spawnSimulateComputeShader;
int kernelHandle = shader.FindKernel("CSMain");
MultiTerrainOperationType multiTerrainOperationType = MultiTerrainOperationType.GameObject;
float fitnessThreshold = 0.5f;
switch (spawnRule.m_resourceType)
{
case SpawnerResourceType.TerrainDetail:
multiTerrainOperationType = MultiTerrainOperationType.TerrainDetail;
fitnessThreshold = spawnRule.m_terrainDetailMinFitness;
break;
case SpawnerResourceType.TerrainTexture:
multiTerrainOperationType = MultiTerrainOperationType.Texture;
fitnessThreshold = 0;
break;
case SpawnerResourceType.TerrainTree:
multiTerrainOperationType = MultiTerrainOperationType.Tree;
fitnessThreshold = spawnRule.m_minRequiredFitness;
break;
case SpawnerResourceType.GameObject:
multiTerrainOperationType = MultiTerrainOperationType.GameObject;
fitnessThreshold = spawnRule.m_minRequiredFitness;
break;
case SpawnerResourceType.SpawnExtension:
multiTerrainOperationType = MultiTerrainOperationType.GameObject;
fitnessThreshold = 0;
break;
case SpawnerResourceType.StampDistribution:
multiTerrainOperationType = MultiTerrainOperationType.GameObject;
fitnessThreshold = 0;
break;
case SpawnerResourceType.WorldBiomeMask:
//this should never happen
break;
}
RenderTexture opRenderTexture = ApplyBrush(operation, multiTerrainOperationType, spawnRuleID);
//Get the affected terrains according to operation type
var affectedTerrains = operation.affectedTerrainPixels.Where(x => x.Key.operationType == multiTerrainOperationType).ToArray();
//Build an input and output data buffer array to get info about the terrain positions in and out of the compute shader
TerrainPosition[] inputTerrainPositions = new TerrainPosition[affectedTerrains.Count()];
TerrainPosition[] outputTerrainPositions = new TerrainPosition[affectedTerrains.Count()];
for (int i = 0; i < affectedTerrains.Count(); i++)
{
var entry = affectedTerrains[i];
//assume first these terrain pixels will be affected, since this entry could still be set to false
//form a previous spawn.
affectedTerrains[i].Value.simulationPositive = true;
inputTerrainPositions[i] = new TerrainPosition()
{
terrainID = i,
min = entry.Value.affectedOperationPixels.min,
max = entry.Value.affectedOperationPixels.max,
affected = 0
};
}
//Configure & run the compute shader
ComputeBuffer buffer = new ComputeBuffer(affectedTerrains.Count(), 24);
buffer.SetData(inputTerrainPositions);
shader.SetTexture(kernelHandle, "Input", opRenderTexture);
shader.SetFloat("fitnessThreshold", spawnRule.m_terrainDetailMinFitness);
shader.SetInt("numberOfTerrains", affectedTerrains.Count());
shader.SetBuffer(kernelHandle, "outputBuffer", buffer);
shader.Dispatch(kernelHandle, opRenderTexture.width / 8, opRenderTexture.height / 8, 1);
buffer.GetData(outputTerrainPositions);
//We got the result, now take our initial array and check if those terrains listed in the OP are actually affected
for (int i = 0; i < affectedTerrains.Count(); i++)
{
TerrainPosition terrainPosition = outputTerrainPositions[i];
if (terrainPosition.affected <= 0)
{
//kick out this entry from the operation if the simulation result says it will not be affected
// => no need to execute those later when spawning
affectedTerrains[i].Value.simulationPositive = false;
}
}
buffer.Release();
return opRenderTexture;
}
/// <summary>
/// Returns the required spawner runs to cover the entire wolrd according to the current max spawner size when doing a spawn across a certain area
/// </summary>
/// <param name="area">The area we are iterating over</param>
/// <returns>The number of steps required to iterate across the world</returns>
public static int GetAreaSpawnSteps(BoundsDouble area, float range)
{
//Bounds spawnBounds = new Bounds();
//TerrainHelper.GetTerrainBounds(ref spawnBounds);
float spawnRange = (float)Mathd.Min(range, Mathd.Min(area.extents.x, area.extents.z));
return Mathd.CeilToInt(area.size.x / (spawnRange * 2f)) * Mathd.CeilToInt(area.size.z / (spawnRange * 2f));
}
// public void RunSpawnerIteration(bool worldSpawn, int totalSpawnRulesCompleted, int totalSpawnRules)
// {
// m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawnRules, totalSpawnRulesCompleted, 0, 0);
// //store all ids for which we have performed an asset clearing already
// List<TerrainPrototypeId> clearedTreeProtos = new List<TerrainPrototypeId>();
// List<int> clearedDetailProtos = new List<int>();
// List<TerrainPrototypeId> clearedGameObjectProtos = new List<TerrainPrototypeId>();
// List<TerrainPrototypeId> clearedSpawnExtensionProtos = new List<TerrainPrototypeId>();
// //store all collision masks that were baked already
// //List<CollisionMask> bakedCollisionMasks = new List<CollisionMask>();
// //Clear collision mask cache - this function is smart to not clear this multiple times in an autospawn
// CollisionMaskCache collisionMaskCache = GaiaSessionManager.GetSessionManager().m_collisionMaskCache;
// collisionMaskCache.ClearCacheForSpawn();
// Vector3 originalPosition = transform.position;
// Quaternion originalRotation = transform.rotation;
// float originalSpawnRange = m_settings.m_spawnRange;
// Bounds spawnBounds = new Bounds();
// Vector3 startPosition = transform.position;
// bool hasTerrainPlaceholders = Resources.FindObjectsOfTypeAll<GaiaTerrainPlaceHolder>().Length > 0;
// //Determine the bounds of the spawning operation, for a worldspawn we need to consider all terrains / placeholders,
// //for a local spawn we just take the spawner range into consideration
// if (worldSpawn)
// {
// TerrainHelper.GetTerrainBounds(ref spawnBounds);
// m_settings.m_spawnRange = Mathf.Min(m_gaiaSettings.m_spawnerWorldSpawnRange, Mathf.Min(spawnBounds.extents.x,spawnBounds.extents.z));
// startPosition = new Vector3(spawnBounds.min.x + m_settings.m_spawnRange, startPosition.y , spawnBounds.min.z + m_settings.m_spawnRange);
// //for world spawns we use the maximal spawner range defined in Gaia settings
// // terrainsToProcess = Terrain.activeTerrains;
// }
// else
// {
// spawnBounds = new Bounds(transform.position, new Vector3(m_settings.m_spawnRange * 2f, 1000f, m_settings.m_spawnRange * 2f));
// }
// bool needSpawnerRefresh = false;
// bool spawnedGameObjects = false;
// //Calculate the maximum amount of rules that need to be processed in all iterations for the progress bar
// //int maxIterations = Mathf.FloorToInt(spawnBounds.extents.x / m_settings.m_spawnRange) * Mathf.FloorToInt(spawnBounds.extents.z / m_settings.m_spawnRange);
// int maxSpawnerRules = m_settings.m_spawnerRules.FindAll(x => x.m_isActive).Count; // * maxIterations;
// if (worldSpawn)
// {
// maxSpawnerRules *= GetWorldSpawnSteps();
// }
// int completedSpawnerRules = 0;
// for (Vector3 currentSpawnCenter = startPosition; currentSpawnCenter.x <= spawnBounds.max.x; currentSpawnCenter += new Vector3(m_gaiaSettings.m_spawnerWorldSpawnRange * 2,0f,0f))
// {
// if (!m_cancelSpawn)
// {
// m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawnRules, totalSpawnRulesCompleted, maxSpawnerRules, completedSpawnerRules);
// }
// if (m_cancelSpawn)
// {
// break;
// }
// for (currentSpawnCenter = new Vector3(currentSpawnCenter.x, currentSpawnCenter.y, startPosition.z); currentSpawnCenter.z <= spawnBounds.max.z; currentSpawnCenter += new Vector3(0f, 0f, m_gaiaSettings.m_spawnerWorldSpawnRange * 2))
// {
// if (!m_cancelSpawn)
// {
// m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawnRules, totalSpawnRulesCompleted, maxSpawnerRules, completedSpawnerRules);
// }
// if (m_cancelSpawn)
// {
// break;
// }
// UpdateMinMaxHeight();
// transform.position = currentSpawnCenter;
// //Make sure terrain loader is started up and positioned properly
// if (hasTerrainPlaceholders)
// {
// UpdateAutoLoadRange();
// TerrainLoader.LoadTerrains();
// }
// Terrain currentTerrain = GetCurrentTerrain();
// //Set up a multi-terrain operation once, all rules can then draw from the data collected here
// GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_spawnRange * 2f, true);
// operation.GetHeightmap();
// operation.GetNormalmap();
// operation.CollectTerrainDetails();
// operation.CollectTerrainTrees();
// operation.CollectTerrainGameObjects();
// operation.CollectTerrainCollisions();
// for (int i = 0; i < m_settings.m_spawnerRules.Count; i++)
// {
// if (!m_cancelSpawn)
// {
// m_cancelSpawn = SpawnProgressBar.UpdateProgressBar(this.name, totalSpawnRules, totalSpawnRulesCompleted, maxSpawnerRules, completedSpawnerRules);
// }
// if (m_cancelSpawn)
// {
// break;
// }
// if (m_settings.m_spawnerRules[i].m_isActive)
// {
// switch (m_settings.m_spawnerRules[i].m_resourceType)
// {
// case GaiaConstants.SpawnerResourceType.TerrainTexture:
// //Get correct terrain layer id from spawner Rule id
// int layerID = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx,currentTerrain);
// //Splatmap deliberately handled without a sub-coroutine - creates issues during applying the splatmaps
// if (layerID != -1)
// {
// //need to call Apply Brush first before calling GetSplatmap since texture masks inside there can jeopardize the spawn result.
// operation.GetSplatmap(currentTerrain.terrainData.terrainLayers[layerID]);
// RenderTexture temp = ApplyBrush(operation, MultiTerrainOperationType.Texture, i);
// operation.GetSplatmap(currentTerrain.terrainData.terrainLayers[layerID]);
// operation.SetSplatmap(temp, false);
// temp = null;
// }
// else
// {
// Debug.LogWarning("Could not find texture " + m_settings.m_resources.m_texturePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx].m_name + " on the terrain! Skipping this texture for spawning...");
// }
// //RenderTexture textureResult = new RenderTexture(operation.RTtextureSplatmap);
// //RenderTexture combinedOutputTexture = RenderTexture.GetTemporary(operation.RTtextureSplatmap.descriptor);
// //Graphics.Blit(ApplyBrush(operation, MultiTerrainOperationType.Texture, i), textureResult);
// //switch (m_settings.spawnMode)
// //{
// // case SpawnMode.Add:
// // //Take the original splatmap for the texture and run the result through the image mask shader for a simple add (pass 3)
// // Material filterMat = new Material(Shader.Find("Hidden/Gaia/FilterImageMask"));
// // filterMat.SetTexture("_InputTex", operation.RTtextureSplatmap);
// // ImageProcessing.WriteRenderTexture("D:\\RTtexturesplatmap.png", operation.RTtextureSplatmap);
// // filterMat.SetFloat("_Strength", 1f);
// // filterMat.SetInt("_Invert", 0);
// // filterMat.SetTexture("_ImageMaskTex", textureResult);
// // ImageProcessing.WriteRenderTexture("D:\\textureResult.png", textureResult);
// // Graphics.Blit(textureResult,combinedOutputTexture, filterMat, 3);
// // ImageProcessing.WriteRenderTexture("D:\\combinedOutput.png", combinedOutputTexture);
// // operation.SetSplatmap(combinedOutputTexture);
// // break;
// // case SpawnMode.Remove:
// // //Take the original splatmap for the texture and run the result through the image mask shader for a simple subtract (pass 4)
// // Material filterMat2 = new Material(Shader.Find("Hidden/Gaia/FilterImageMask"));
// // filterMat2.SetTexture("_InputTex", operation.RTtextureSplatmap);
// // ImageProcessing.WriteRenderTexture("D:\\RTtexturesplatmap.png", operation.RTtextureSplatmap);
// // filterMat2.SetFloat("_Strength", 1f);
// // filterMat2.SetInt("_Invert", 0);
// // filterMat2.SetTexture("_ImageMaskTex", textureResult);
// // ImageProcessing.WriteRenderTexture("D:\\textureResult.png", textureResult);
// // Graphics.Blit(textureResult, combinedOutputTexture, filterMat2, 4);
// // ImageProcessing.WriteRenderTexture("D:\\combinedOutput.png", combinedOutputTexture);
// // operation.SetSplatmap(combinedOutputTexture);
// // break;
// // case SpawnMode.Replace:
// // //Just overwrite with the mask result directly
// // operation.SetSplatmap(textureResult);
// // break;
// //}
// //RenderTexture.ReleaseTemporary(textureResult);
// //textureResult = null;
// //RenderTexture.ReleaseTemporary(combinedOutputTexture);
// //combinedOutputTexture = null;
// break;
// case GaiaConstants.SpawnerResourceType.TerrainDetail:
// int detailLayerID = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx, currentTerrain);
// if (detailLayerID != -1)
// {
// if (m_settings.spawnMode == SpawnMode.Replace)
// {
// SpawnMode spawnMode = SpawnMode.Add;
// if (!clearedDetailProtos.Contains(m_settings.m_spawnerRules[i].m_resourceIdx))
// {
// spawnMode = SpawnMode.Replace;
// clearedDetailProtos.Add(m_settings.m_spawnerRules[i].m_resourceIdx);
// }
// operation.SetTerrainDetails(ApplyBrush(operation, MultiTerrainOperationType.TerrainDetail, i), detailLayerID, spawnMode, m_settings.m_spawnerRules[i].m_terrainDetailMinFitness, m_settings.m_spawnerRules[i].m_terrainDetailFitnessBeginFadeOut, m_settings.m_spawnerRules[i].m_terrainDetailDensity, ref m_settings.m_spawnerRules[i].m_spawnedInstances, false);
// }
// else
// {
// operation.SetTerrainDetails(ApplyBrush(operation, MultiTerrainOperationType.TerrainDetail, i), detailLayerID, m_settings.spawnMode, m_settings.m_spawnerRules[i].m_terrainDetailMinFitness, m_settings.m_spawnerRules[i].m_terrainDetailFitnessBeginFadeOut, m_settings.m_spawnerRules[i].m_terrainDetailDensity, ref m_settings.m_spawnerRules[i].m_spawnedInstances, false);
// }
// }
// else
// {
// Debug.LogWarning("Could not find terrain detail " + m_settings.m_resources.m_detailPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx].m_name + " on the terrain! Skipping this terrain detail for spawning...");
// }
// break;
// case GaiaConstants.SpawnerResourceType.TerrainTree:
// needSpawnerRefresh = true;
// int treePrototypeIndex = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx, currentTerrain);
// ResourceProtoTree protoTree = m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
// if (treePrototypeIndex != -1)
// {
// foreach (Terrain t in Terrain.activeTerrains)
// {
// //We only may remove / reset this tree rule once per terrain - otherwise this would destroy earlier tree spawn results in world spawns!
// if (m_settings.spawnMode == SpawnMode.Replace && clearedTreeProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
// {
// t.terrainData.SetTreeInstances(t.terrainData.treeInstances.Where(x => x.prototypeIndex != treePrototypeIndex).ToArray(), true);
// m_settings.m_spawnerRules[i].m_spawnedInstances = 0;
// clearedTreeProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
// }
// }
// operation.SetTerrainTrees(ApplyBrush(operation, MultiTerrainOperationType.Tree, i), treePrototypeIndex, protoTree, m_settings.m_spawnerRules[i], m_settings.spawnMode, ref m_settings.m_spawnerRules[i].m_spawnedInstances, m_settings.m_spawnerRules[i].m_minRequiredFitness, false);
// //Dirty affected collision masks - if we spawned trees that are used in other collision masks, we must dirty them so they will be re-baked upon request
// collisionMaskCache.SetTreeDirty(treePrototypeIndex);
// }
// else
// {
// if (m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx] != null)
// {
// Debug.LogWarning("Could not find terrain tree " + m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[i].m_resourceIdx].m_name + " on the terrain! Skipping this terrain tree for spawning...");
// }
// else
// {
// Debug.LogWarning("Could not find the tree resource for spawner rule " + m_settings.m_spawnerRules[i].m_name + "! Skipping this terrain tree for spawning...");
// }
// }
// break;
// case GaiaConstants.SpawnerResourceType.GameObject:
// needSpawnerRefresh = true;
// spawnedGameObjects = true;
// //int goPrototypeIndex = m_settings.m_resources.PrototypeIdxInTerrain(m_settings.m_spawnerRules[i].m_resourceType, m_settings.m_spawnerRules[i].m_resourceIdx);
// //We only may remove / reset the Game Object spawner once per terrain- otherwise this would destroy earlier spawn results in world spawns!
// foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
// {
// if (m_settings.spawnMode == SpawnMode.Replace && clearedGameObjectProtos.Find(x=>x.terrain==t&&x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx)==null)
// {
// ClearGameObjectsForRule(m_settings.m_spawnerRules[i],false, t);
// clearedGameObjectProtos.Add(new TerrainPrototypeId() { terrain=t, prototypeId=m_settings.m_spawnerRules[i].m_resourceIdx });
// }
// }
// ResourceProtoGameObject protoGO = m_settings.m_resources.m_gameObjectPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
// if (protoGO == null)
// {
// Debug.LogWarning("Could not find Game Object Prototype for Spawn Rule " + m_settings.m_spawnerRules[i].m_name);
// break;
// }
// operation.SetTerrainGameObjects(ApplyBrush(operation, MultiTerrainOperationType.GameObject, i), protoGO, m_settings.m_spawnerRules[i], m_settings.spawnMode, ref m_settings.m_spawnerRules[i].m_spawnedInstances, m_settings.m_spawnerRules[i].m_minRequiredFitness, false);
// //Dirty affected collision masks - if we spawned Gameobject instances with tags that are used in other collision masks, we must dirty them so they will be re-baked upon request
// foreach (ResourceProtoGameObjectInstance instance in protoGO.m_instances)
// {
// if (tag != "Untagged")
// {
// collisionMaskCache.SetTagDirty(tag);
// }
// }
// break;
// case GaiaConstants.SpawnerResourceType.SpawnExtension:
// needSpawnerRefresh = true;
// spawnedGameObjects = true;
// ResourceProtoSpawnExtension protoSpawnExtension = m_settings.m_resources.m_spawnExtensionPrototypes[m_settings.m_spawnerRules[i].m_resourceIdx];
// //We only may remove / reset the Spawn Extension once per terrain - otherwise this would destroy earlier spawn results in world spawns!
// foreach (Terrain t in operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.GameObject).Select(x => x.Key.terrain))
// {
// if (m_settings.spawnMode == SpawnMode.Replace && clearedSpawnExtensionProtos.Find(x => x.terrain == t && x.prototypeId == m_settings.m_spawnerRules[i].m_resourceIdx) == null)
// {
// ClearSpawnExtensionsForRule(m_settings.m_spawnerRules[i]);
// clearedSpawnExtensionProtos.Add(new TerrainPrototypeId() { terrain = t, prototypeId = m_settings.m_spawnerRules[i].m_resourceIdx });
// }
// }
// foreach (ResourceProtoSpawnExtensionInstance instance in protoSpawnExtension.m_instances)
// {
// GameObject prefab = instance.m_spawnerPrefab;
// //Get ALL spawn extensions - could potentially be multiple on prefab
// var instanceSpawnExtensions = prefab.GetComponents<ISpawnExtension>();
// foreach (ISpawnExtension spawnExtension in instanceSpawnExtensions)
// {
// spawnExtension.Init(this);
// }
// }
// m_cancelSpawn = operation.SetSpawnExtensions(ApplyBrush(operation, MultiTerrainOperationType.GameObject, i), this, protoSpawnExtension, m_settings.spawnMode, i, m_settings.m_spawnerRules[i], ref m_settings.m_spawnerRules[i].m_spawnedInstances, m_settings.m_spawnerRules[i].m_minRequiredFitness, false, m_gaiaSettings.m_hideObjectsInHierachy);
// foreach (ResourceProtoSpawnExtensionInstance instance in protoSpawnExtension.m_instances)
// {
// GameObject prefab = instance.m_spawnerPrefab;
// //Get ALL spawn extensions - could potentially be multiple on prefab
// var instanceSpawnExtensions = prefab.GetComponents<ISpawnExtension>();
// foreach (ISpawnExtension spawnExtension in instanceSpawnExtensions)
// {
// spawnExtension.Close();
// }
// }
// break;
// }
// }
// if (m_cancelSpawn)
// {
// break;
// }
// completedSpawnerRules++;
// totalSpawnRulesCompleted++;
// //GaiaUtils.ReleaseAllTempRenderTextures();
// //Update progress and yield periodiocally
// m_spawnProgress = (float)completedSpawnerRules / (float)maxSpawnerRules;
// //yield return null;
// }
// //Update progress and yield periodiocally
// m_spawnProgress = (float)completedSpawnerRules / (float)maxSpawnerRules;
// //yield return null;
// //Reset spawn progress to unlock the GUI in the spawner editor
// m_spawnProgress = 0;
//#if UNITY_EDITOR
// if (spawnedGameObjects)
// {
// foreach (Terrain t in operation.affectedTerrainPixels.Where(x=>x.Key.operationType==MultiTerrainOperationType.GameObject).Select(x=>x.Key.terrain))
// {
// EditorSceneManager.MarkSceneDirty(t.gameObject.scene);
// }
// }
// #endif
// //Clean up
// operation.CloseOperation();
// GaiaUtils.ReleaseAllTempRenderTextures();
// GC.Collect();
// //if (m_hierarchyUtils != null)
// //{
// // m_hierarchyUtils.SetupHideInHierarchy();
// //}
// }
// }
// //all done, if we spawned game objects we need to mark all affected scenes as dirty
// if (spawnedGameObjects)
// {
//#if UNITY_EDITOR
// EditorSceneManager.MarkSceneDirty(gameObject.scene);
//#endif
// }
// //restore original positon & rotation for this spawner
// //transform.position = originalPosition;
// //transform.rotation = originalRotation;
// //UpdateAutoLoadRange();
// if (needSpawnerRefresh)
// {
// m_spawnPreviewDirty = true;
// }
// m_spawnComplete = true;
// m_updateCoroutine = null;
// }
/// <summary>
/// Decreases all resource indexes by 1 for a certain resource type - used when a resource is removed from the spawner so all indices need to be corrected by 1 above the old index of the deleted resource.
/// </summary>
/// <param name="terrainTree"></param>
/// <param name="i"></param>
public void CorrectIndicesAfteResourceDeletion(SpawnerResourceType resourceType, int oldIndex)
{
foreach (SpawnRule sr in m_settings.m_spawnerRules.Where(x => x.m_resourceType == resourceType))
{
if (sr.m_resourceIdx >= oldIndex)
{
sr.m_resourceIdx--;
}
}
}
/// <summary>
/// Run a random location based spawner iteration - the spawner is always trying to spawn something on the underlying terrain
/// </summary>
public IEnumerator RunRandomSpawnerIteration()
{
//if (m_showDebug)
//{
// Debug.Log(string.Format("{0}: Running random iteration", gameObject.name));
//}
////Start iterating
//int ruleIdx;
//float fitness, maxFitness, selectedFitness;
//SpawnRule rule, fittestRule, selectedRule;
//SpawnInfo spawnInfo = new SpawnInfo();
//SpawnLocation spawnLocation;
//List<SpawnLocation> spawnLocations = new List<SpawnLocation>();
//int spawnLocationsIdx = 0;
//int failedSpawns = 0;
////Set progress
//m_spawnProgress = 0f;
//m_spawnComplete = false;
////Time control for enumeration
//float currentTime = Time.realtimeSinceStartup;
//float accumulatedTime = 0.0f;
////Create spawn caches
//CreateSpawnCaches();
////Load image filter
//LoadImageMask();
//for (int terrainID = 0; terrainID < Terrain.activeTerrains.Length; terrainID++)
//{
// //Set up the texture layer array in spawn info
// spawnInfo.m_textureStrengths = new float[Terrain.activeTerrains[terrainID].terrainData.alphamapLayers];
// //Run the location checks
// for (int checks = 0; checks < m_locationChecksPerInt; checks++)
// {
// //Create the spawn location
// spawnLocation = new SpawnLocation();
// //Choose a random location around the spawner
// if (m_spawnLocationAlgorithm == GaiaConstants.SpawnerLocation.RandomLocation)
// {
// spawnLocation.m_location = GetRandomV3(m_settings.m_spawnRange);
// spawnLocation.m_location = transform.position + spawnLocation.m_location;
// }
// else
// {
// if (spawnLocations.Count == 0 || spawnLocations.Count > m_maxRandomClusterSize || failedSpawns > m_maxRandomClusterSize)
// {
// spawnLocation.m_location = GetRandomV3(m_settings.m_spawnRange);
// spawnLocation.m_location = transform.position + spawnLocation.m_location;
// failedSpawns = 0;
// spawnLocationsIdx = 0;
// spawnLocations.Clear();
// }
// else
// {
// if (spawnLocationsIdx >= spawnLocations.Count)
// {
// spawnLocationsIdx = 0;
// }
// spawnLocation.m_location = GetRandomV3(spawnLocations[spawnLocationsIdx].m_seedDistance);
// spawnLocation.m_location = spawnLocations[spawnLocationsIdx++].m_location + spawnLocation.m_location;
// }
// }
// //Run a ray traced hit check to see what we have hit, use rules to determine fitness and select a rule to spawn
// if (CheckLocation(spawnLocation.m_location, ref spawnInfo))
// {
// //Now perform a rule check based on the selected algorithm
// //All rules
// if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.All)
// {
// for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
// {
// rule = m_settings.m_spawnerRules[ruleIdx];
// spawnInfo.m_fitness = rule.GetFitness(ref spawnInfo);
// if (TryExecuteRule(ref rule, ref spawnInfo) == true)
// {
// failedSpawns = 0;
// //spawnLocation.m_seedDistance = rule.GetSeedThrowRange(ref spawnInfo);
// spawnLocations.Add(spawnLocation);
// }
// else
// {
// failedSpawns++;
// }
// }
// }
// //Random spawn rule
// else if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.Random)
// {
// rule = m_settings.m_spawnerRules[GetRandomInt(0, m_settings.m_spawnerRules.Count - 1)];
// spawnInfo.m_fitness = rule.GetFitness(ref spawnInfo);
// if (TryExecuteRule(ref rule, ref spawnInfo) == true)
// {
// failedSpawns = 0;
// //spawnLocation.m_seedDistance = rule.GetSeedThrowRange(ref spawnInfo);
// spawnLocations.Add(spawnLocation);
// }
// else
// {
// failedSpawns++;
// }
// }
// //Fittest spawn rule
// else if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.Fittest)
// {
// fittestRule = null;
// maxFitness = 0f;
// for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
// {
// rule = m_settings.m_spawnerRules[ruleIdx];
// fitness = rule.GetFitness(ref spawnInfo);
// if (fitness > maxFitness)
// {
// maxFitness = fitness;
// fittestRule = rule;
// }
// else
// {
// //If they are approx equal then give another rule a chance as well to add interest
// if (Gaia.GaiaUtils.Math_ApproximatelyEqual(fitness, maxFitness, 0.005f))
// {
// if (GetRandomFloat(0f, 1f) > 0.5f)
// {
// maxFitness = fitness;
// fittestRule = rule;
// }
// }
// }
// }
// spawnInfo.m_fitness = maxFitness;
// if (TryExecuteRule(ref fittestRule, ref spawnInfo) == true)
// {
// failedSpawns = 0;
// spawnLocation.m_seedDistance = fittestRule.GetSeedThrowRange(ref spawnInfo);
// spawnLocations.Add(spawnLocation);
// }
// else
// {
// failedSpawns++;
// }
// }
// //Weighted fittest spawn rule - this implementation will favour fittest
// else
// {
// fittestRule = selectedRule = null;
// maxFitness = selectedFitness = 0f;
// for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
// {
// rule = m_settings.m_spawnerRules[ruleIdx];
// fitness = rule.GetFitness(ref spawnInfo);
// if (GetRandomFloat(0f, 1f) < fitness)
// {
// selectedRule = rule;
// selectedFitness = fitness;
// }
// if (fitness > maxFitness)
// {
// fittestRule = rule;
// maxFitness = fitness;
// }
// }
// //Check to see if we randomly bombed out - if so then choose fittest
// if (selectedRule == null)
// {
// selectedRule = fittestRule;
// selectedFitness = maxFitness;
// }
// //We could still bomb, check for this and avoid it
// if (selectedRule != null)
// {
// spawnInfo.m_fitness = selectedFitness;
// if (TryExecuteRule(ref selectedRule, ref spawnInfo) == true)
// {
// failedSpawns = 0;
// spawnLocation.m_seedDistance = selectedRule.GetSeedThrowRange(ref spawnInfo);
// spawnLocations.Add(spawnLocation);
// }
// else
// {
// failedSpawns++;
// }
// }
// }
// }
// //Update progress and yield periodiocally
// m_spawnProgress = (float)checks / (float)m_locationChecksPerInt;
// float newTime = Time.realtimeSinceStartup;
// float stepTime = newTime - currentTime;
// currentTime = newTime;
// accumulatedTime += stepTime;
// if (accumulatedTime > m_updateTimeAllowed)
// {
// accumulatedTime = 0f;
// yield return null;
// }
// //Check the instance count, exit if necessary
// if (!CanSpawnInstances())
// {
// break;
// }
// //Check for cancellation
// if (m_cancelSpawn)
// {
// break;
// }
// }
//}
////Delete spawn caches
//DeleteSpawnCaches();
////Perform final operations
//PostSpawn();
yield return null;
}
/// <summary>
/// Run an area spawner iteration
/// </summary>
public IEnumerator RunAreaSpawnerIteration()
{
if (m_showDebug)
{
Debug.Log(string.Format("{0}: Running area iteration", gameObject.name));
}
int ruleIdx;
float fitness, maxFitness, selectedFitness;
SpawnRule rule, fittestRule, selectedRule;
SpawnInfo spawnInfo = new SpawnInfo();
Vector3 location = new Vector3();
long currChecks, totalChecks;
float xWUMin, xWUMax, yMid, zWUMin, zWUMax, jitMin, jitMax;
float xWU, zWU;
//Set progress
m_spawnProgress = 0f;
m_spawnComplete = false;
//Time control for enumeration
float currentTime = Time.realtimeSinceStartup;
float accumulatedTime = 0.0f;
//Create spawn caches
CreateSpawnCaches();
//Load image filter
LoadImageMask();
//for (int terrainID = 0; terrainID < Terrain.activeTerrains.Length; terrainID++)
//{
//Determine check ranges
xWUMin = transform.position.x - m_settings.m_spawnRange + (m_locationIncrement / 2f);
xWUMax = xWUMin + (m_settings.m_spawnRange * 2f);
yMid = transform.position.y;
zWUMin = transform.position.z - m_settings.m_spawnRange + (m_locationIncrement / 2f);
zWUMax = zWUMin + (m_settings.m_spawnRange * 2f);
jitMin = (-1f * m_maxJitteredLocationOffsetPct) * m_locationIncrement;
jitMax = (1f * m_maxJitteredLocationOffsetPct) * m_locationIncrement;
//Update checks
currChecks = 0;
totalChecks = (long)(((xWUMax - xWUMin) / m_locationIncrement) * ((zWUMax - zWUMin) / m_locationIncrement));
//Iterate across these ranges
for (xWU = xWUMin; xWU < xWUMax; xWU += m_locationIncrement)
{
for (zWU = zWUMin; zWU < zWUMax; zWU += m_locationIncrement)
{
currChecks++;
//Set the location we want to test
location.x = xWU;
location.y = yMid;
location.z = zWU;
//Jitter it
if (m_spawnLocationAlgorithm == GaiaConstants.SpawnerLocation.EveryLocationJittered)
{
location.x += GetRandomFloat(jitMin, jitMax);
location.z += GetRandomFloat(jitMin, jitMax);
}
//Run a ray traced hit check to see what we have hit, use rules to determine fitness and select a rule to spawn
if (CheckLocation(location, ref spawnInfo))
{
//Now perform a rule check based on the selected algorithm
//All rules
if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.All)
{
for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
rule = m_settings.m_spawnerRules[ruleIdx];
spawnInfo.m_fitness = rule.GetFitness(ref spawnInfo);
TryExecuteRule(ref rule, ref spawnInfo);
}
}
//Random spawn rule
else if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.Random)
{
ruleIdx = GetRandomInt(0, m_settings.m_spawnerRules.Count - 1);
rule = m_settings.m_spawnerRules[ruleIdx];
spawnInfo.m_fitness = rule.GetFitness(ref spawnInfo);
TryExecuteRule(ref rule, ref spawnInfo);
}
//Fittest spawn rule
else if (m_spawnRuleSelector == GaiaConstants.SpawnerRuleSelector.Fittest)
{
fittestRule = null;
maxFitness = 0f;
for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
rule = m_settings.m_spawnerRules[ruleIdx];
fitness = rule.GetFitness(ref spawnInfo);
if (fitness > maxFitness)
{
maxFitness = fitness;
fittestRule = rule;
}
else
{
//If they are approx equal then give another rule a chance as well to add interest
if (Gaia.GaiaUtils.Math_ApproximatelyEqual(fitness, maxFitness, 0.005f))
{
if (GetRandomFloat(0f, 1f) > 0.5f)
{
maxFitness = fitness;
fittestRule = rule;
}
}
}
}
spawnInfo.m_fitness = maxFitness;
TryExecuteRule(ref fittestRule, ref spawnInfo);
}
//Weighted fittest spawn rule - this implementation will favour fittest
else
{
fittestRule = selectedRule = null;
maxFitness = selectedFitness = 0f;
for (ruleIdx = 0; ruleIdx < m_settings.m_spawnerRules.Count; ruleIdx++)
{
rule = m_settings.m_spawnerRules[ruleIdx];
fitness = rule.GetFitness(ref spawnInfo);
if (GetRandomFloat(0f, 1f) < fitness)
{
selectedRule = rule;
selectedFitness = fitness;
}
if (fitness > maxFitness)
{
fittestRule = rule;
maxFitness = fitness;
}
}
//Check to see if we randomly bombed out - if so then choose fittest
if (selectedRule == null)
{
selectedRule = fittestRule;
selectedFitness = maxFitness;
}
//We could still bomb, check for this and avoid it
if (selectedRule != null)
{
spawnInfo.m_fitness = selectedFitness;
TryExecuteRule(ref selectedRule, ref spawnInfo);
}
}
//If it caused textures to be updated then apply them
if (m_textureMapsDirty)
{
List<HeightMap> txtMaps = spawnInfo.m_spawner.GetTextureMaps(spawnInfo.m_hitTerrain.GetInstanceID());
if (txtMaps != null)
{
for (int idx = 0; idx < spawnInfo.m_textureStrengths.Length; idx++)
{
//if ((int)spawnInfo.m_hitLocationWU.z == 1023)
//{
// Debug.Log("Woopee");
//}
txtMaps[idx][spawnInfo.m_hitLocationNU.z, spawnInfo.m_hitLocationNU.x] = spawnInfo.m_textureStrengths[idx];
}
}
}
}
//Update progress and yield periodiocally
m_spawnProgress = (float)currChecks / (float)totalChecks;
float newTime = Time.realtimeSinceStartup;
float stepTime = newTime - currentTime;
currentTime = newTime;
accumulatedTime += stepTime;
if (accumulatedTime > m_updateTimeAllowed)
{
accumulatedTime = 0f;
yield return null;
}
//Check the instance count, exit if necessary
if (!CanSpawnInstances())
{
break;
}
//Check for cancelation
if (m_cancelSpawn == true)
{
break;
}
}
}
//}
//Determine whether or not we need to delete and apply spawn caches
DeleteSpawnCaches(true);
//Perform final operations
PostSpawn();
}
/// <summary>
/// Ground the spawner to the terrain
/// </summary>
public void GroundToTerrain()
{
Terrain t = Gaia.TerrainHelper.GetTerrain(transform.position);
if (t == null)
{
t = Terrain.activeTerrain;
}
if (t == null)
{
Debug.LogError("Could not fit to terrain - no terrain present");
return;
}
Bounds b = new Bounds();
if (TerrainHelper.GetTerrainBounds(t, ref b))
{
transform.position = new Vector3(transform.position.x, t.transform.position.y, transform.position.z);
}
}
/// <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, m_settings.m_isWorldmapSpawner);
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_spawnRange = 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_spawnRange = (float)b.extents.x;
}
}
/// <summary>
/// Check if the spawner has been fit to the terrain - ignoring height
/// </summary>
/// <returns>True if its a match</returns>
public bool IsFitToTerrain()
{
Terrain t = Gaia.TerrainHelper.GetTerrain(transform.position);
if (t == null)
{
t = Terrain.activeTerrain;
}
if (t == null)
{
Debug.LogError("Could not check if fit to terrain - no terrain present");
return false;
}
Bounds b = new Bounds();
if (TerrainHelper.GetTerrainBounds(t, ref b))
{
if (
b.center.x != transform.position.x ||
b.center.z != transform.position.z ||
b.extents.x != m_settings.m_spawnRange)
{
return false;
}
else
{
return true;
}
}
return false;
}
/// <summary>
/// Load the image mask if one was specified
/// </summary>
public bool LoadImageMask()
{
//Kill old image height map
m_imageMaskHM = null;
//Check mode & exit
if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.None || m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.PerlinNoise)
{
return false;
}
//Load the supplied image
if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.ImageRedChannel || m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.ImageGreenChannel ||
m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.ImageBlueChannel || m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.ImageAlphaChannel ||
m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.ImageGreyScale)
{
if (m_imageMask == null)
{
Debug.LogError("You requested an image mask but did not supply one. Please select mask texture.");
return false;
}
//Check the image rw
Gaia.GaiaUtils.MakeTextureReadable(m_imageMask);
//Make it uncompressed
Gaia.GaiaUtils.MakeTextureUncompressed(m_imageMask);
//Load the image
m_imageMaskHM = new HeightMap(m_imageMask.width, m_imageMask.height);
for (int x = 0; x < m_imageMaskHM.Width(); x++)
{
for (int z = 0; z < m_imageMaskHM.Depth(); z++)
{
switch (m_areaMaskMode)
{
case GaiaConstants.ImageFitnessFilterMode.ImageGreyScale:
m_imageMaskHM[x, z] = m_imageMask.GetPixel(x, z).grayscale;
break;
case GaiaConstants.ImageFitnessFilterMode.ImageRedChannel:
m_imageMaskHM[x, z] = m_imageMask.GetPixel(x, z).r;
break;
case GaiaConstants.ImageFitnessFilterMode.ImageGreenChannel:
m_imageMaskHM[x, z] = m_imageMask.GetPixel(x, z).g;
break;
case GaiaConstants.ImageFitnessFilterMode.ImageBlueChannel:
m_imageMaskHM[x, z] = m_imageMask.GetPixel(x, z).b;
break;
case GaiaConstants.ImageFitnessFilterMode.ImageAlphaChannel:
m_imageMaskHM[x, z] = m_imageMask.GetPixel(x, z).a;
break;
}
}
}
}
else
{
//Or get a new one
if (Terrain.activeTerrain == null)
{
Debug.LogError("You requested an terrain texture mask but there is no active terrain.");
return false;
}
Terrain t = Terrain.activeTerrain;
var splatPrototypes = GaiaSplatPrototype.GetGaiaSplatPrototypes(t);
switch (m_areaMaskMode)
{
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture0:
if (splatPrototypes.Length < 1)
{
Debug.LogError("You requested an terrain texture mask 0 but there is no active texture in slot 0.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 0);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture1:
if (splatPrototypes.Length < 2)
{
Debug.LogError("You requested an terrain texture mask 1 but there is no active texture in slot 1.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 1);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture2:
if (splatPrototypes.Length < 3)
{
Debug.LogError("You requested an terrain texture mask 2 but there is no active texture in slot 2.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 2);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture3:
if (splatPrototypes.Length < 4)
{
Debug.LogError("You requested an terrain texture mask 3 but there is no active texture in slot 3.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 3);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture4:
if (splatPrototypes.Length < 5)
{
Debug.LogError("You requested an terrain texture mask 4 but there is no active texture in slot 4.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 4);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture5:
if (splatPrototypes.Length < 6)
{
Debug.LogError("You requested an terrain texture mask 5 but there is no active texture in slot 5.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 5);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture6:
if (splatPrototypes.Length < 7)
{
Debug.LogError("You requested an terrain texture mask 6 but there is no active texture in slot 6.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 6);
break;
case GaiaConstants.ImageFitnessFilterMode.TerrainTexture7:
if (splatPrototypes.Length < 8)
{
Debug.LogError("You requested an terrain texture mask 7 but there is no active texture in slot 7.");
return false;
}
m_imageMaskHM = new HeightMap(t.terrainData.GetAlphamaps(0, 0, t.terrainData.alphamapWidth, t.terrainData.alphamapHeight), 7);
break;
}
//It came from terrain so flip it
m_imageMaskHM.Flip();
}
//Because images are noisy, smooth it
if (m_imageMaskSmoothIterations > 0)
{
m_imageMaskHM.Smooth(m_imageMaskSmoothIterations);
}
//Flip it
if (m_imageMaskFlip == true)
{
m_imageMaskHM.Flip();
}
//Normalise it if necessary
if (m_imageMaskNormalise == true)
{
m_imageMaskHM.Normalise();
}
//Invert it if necessessary
if (m_imageMaskInvert == true)
{
m_imageMaskHM.Invert();
}
return true;
}
/// <summary>
/// Startst the spawner, either in a local area or across all terrains ("world spawn")
/// </summary>
/// <param name="allTerrains">Whether the spawner should spawn across all terrains in the scene.</param>
public void Spawn(bool allTerrains)
{
m_spawnComplete = false;
BoundsDouble spawnArea = new BoundsDouble();
if (allTerrains)
{
TerrainHelper.GetTerrainBounds(ref spawnArea);
}
else
{
spawnArea.center = transform.position;
spawnArea.size = new Vector3(m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f);
}
try
{
GenerateNewRandomSeed();
SpawnOperationSettings soSettings = ScriptableObject.CreateInstance<SpawnOperationSettings>();
soSettings.m_spawnerSettingsList = new List<SpawnerSettings>() { m_settings };
soSettings.m_spawnArea = spawnArea;
soSettings.m_isWorldMapSpawner = m_settings.m_isWorldmapSpawner;
GaiaSessionManager.Spawn(soSettings, true, new List<Spawner>() { this });
// m_updateCoroutine = AreaSpawn(new List<Spawner>() { this }, spawnArea);
//StartEditorUpdates();
}
catch (Exception ex)
{
Debug.LogError("Spawner " + this.name + " failed with Exception: " + ex.Message + "\n\n" + "Stack trace: \n\n" + ex.StackTrace);
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
m_spawnComplete = true;
}
/// <summary>
/// Generates a new random seed for the spawner (if generation of a new seed is activated)
/// </summary>
public void GenerateNewRandomSeed()
{
if (m_settings.m_generateRandomSeed)
{
m_settings.m_randomSeed = UnityEngine.Random.Range(0, int.MaxValue);
}
}
/// <summary>
/// Create spawn caches
/// </summary>
/// <param name="checkResources">Base on resources or base on rules, takes active state into account</param>
public void CreateSpawnCaches()
{
//Determine whether or not we need to cache updates, in which case we needs to get the relevant caches
int idx;
m_cacheTextures = false;
m_textureMapsDirty = false;
for (idx = 0; idx < m_settings.m_spawnerRules.Count; idx++)
{
if (m_settings.m_spawnerRules[idx].CacheTextures(this))
{
foreach (Terrain t in Terrain.activeTerrains)
{
CacheTextureMapsFromTerrain(t.GetInstanceID());
}
m_cacheTextures = true;
break;
}
}
m_cacheDetails = false;
for (idx = 0; idx < m_settings.m_spawnerRules.Count; idx++)
{
if (m_settings.m_spawnerRules[idx].CacheDetails(this))
{
foreach (Terrain t in Terrain.activeTerrains)
{
CacheDetailMapsFromTerrain(t.GetInstanceID());
}
m_cacheDetails = true;
break;
}
}
CacheTreesFromTerrain();
m_cacheTags = false;
List<string> tagList = new List<string>();
for (idx = 0; idx < m_settings.m_spawnerRules.Count; idx++)
{
m_settings.m_spawnerRules[idx].AddProximityTags(this, ref tagList);
}
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
m_cacheHeightMaps = false;
for (idx = 0; idx < m_settings.m_spawnerRules.Count; idx++)
{
if (m_settings.m_spawnerRules[idx].CacheHeightMaps(this))
{
CacheHeightMapFromTerrain(Terrain.activeTerrain.GetInstanceID());
m_cacheHeightMaps = true;
break;
}
}
/*
m_cacheStamps = false;
List<string> stampList = new List<string>();
for (idx = 0; idx < m_spawnerRules.Count; idx++)
{
m_spawnerRules[idx].AddStamps(this, ref stampList);
}
if (stampList.Count > 0)
{
CacheStamps(stampList);
m_cacheStamps = true;
} */
}
/// <summary>
/// Create spawn cache fore specific resources
/// </summary>
/// <param name="resourceType"></param>
/// <param name="resourceIdx"></param>
public void CreateSpawnCaches(Gaia.GaiaConstants.SpawnerResourceType resourceType, int resourceIdx)
{
m_cacheTextures = false;
m_textureMapsDirty = false;
m_cacheDetails = false;
m_cacheTags = false;
switch (resourceType)
{
case GaiaConstants.SpawnerResourceType.TerrainTexture:
{
//Check indexes
if (resourceIdx >= m_settings.m_resources.m_texturePrototypes.Length)
{
break;
}
//If we are working with textures, then always cache the texture
foreach (Terrain t in Terrain.activeTerrains)
{
CacheTextureMapsFromTerrain(t.GetInstanceID());
}
m_cacheTextures = true;
//Check for proximity tags
List<string> tagList = new List<string>();
m_settings.m_resources.m_texturePrototypes[resourceIdx].AddTags(ref tagList);
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
break;
}
case GaiaConstants.SpawnerResourceType.TerrainDetail:
{
//Check indexes
if (resourceIdx >= m_settings.m_resources.m_detailPrototypes.Length)
{
break;
}
//If we are working with details, always cache details
foreach (Terrain t in Terrain.activeTerrains)
{
CacheDetailMapsFromTerrain(t.GetInstanceID());
}
m_cacheDetails = true;
//Check for textures
if (m_settings.m_resources.m_detailPrototypes[resourceIdx].ChecksTextures())
{
foreach (Terrain t in Terrain.activeTerrains)
{
CacheTextureMapsFromTerrain(t.GetInstanceID());
}
m_cacheTextures = true;
}
//Check for proximity tags
List<string> tagList = new List<string>();
m_settings.m_resources.m_detailPrototypes[resourceIdx].AddTags(ref tagList);
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
break;
}
case GaiaConstants.SpawnerResourceType.TerrainTree:
{
//Check indexes
if (resourceIdx >= m_settings.m_resources.m_treePrototypes.Length)
{
break;
}
//Cache textures
if (m_settings.m_resources.m_treePrototypes[resourceIdx].ChecksTextures())
{
foreach (Terrain t in Terrain.activeTerrains)
{
CacheTextureMapsFromTerrain(t.GetInstanceID());
}
m_cacheTextures = true;
}
//Cache trees
CacheTreesFromTerrain();
//Cache proximity tags
List<string> tagList = new List<string>();
m_settings.m_resources.m_treePrototypes[resourceIdx].AddTags(ref tagList);
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
break;
}
case GaiaConstants.SpawnerResourceType.GameObject:
{
//Check indexes
if (resourceIdx >= m_settings.m_resources.m_gameObjectPrototypes.Length)
{
break;
}
//Check for textures
if (m_settings.m_resources.m_gameObjectPrototypes[resourceIdx].ChecksTextures())
{
foreach (Terrain t in Terrain.activeTerrains)
{
CacheTextureMapsFromTerrain(t.GetInstanceID());
}
m_cacheTextures = true;
}
//Check for proximity tags
List<string> tagList = new List<string>();
m_settings.m_resources.m_gameObjectPrototypes[resourceIdx].AddTags(ref tagList);
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
break;
}
/*
default:
{
//Check indexes
if (resourceIdx >= m_resources.m_stampPrototypes.Length)
{
break;
}
//Check for textures
if (m_resources.m_stampPrototypes[resourceIdx].ChecksTextures())
{
CacheTextureMapsFromTerrain(Terrain.activeTerrain.GetInstanceID());
m_cacheTextures = true;
}
//Check for proximity tags
List<string> tagList = new List<string>();
m_resources.m_gameObjectPrototypes[resourceIdx].AddTags(ref tagList);
if (tagList.Count > 0)
{
CacheTaggedGameObjectsFromScene(tagList);
m_cacheTags = true;
}
//We are influencing terrain - so we always cache terrain
CacheHeightMapFromTerrain(Terrain.activeTerrain.GetInstanceID());
m_cacheHeightMaps = true;
break;
}
*/
}
}
/// <summary>
/// Destroy spawn caches
/// </summary>
/// <param name="flush">Fluch changes back to the environment</param>
public void DeleteSpawnCaches(bool flushDirty = false)
{
//Determine whether or not we need to apply cache updates
if (m_cacheTextures)
{
if (flushDirty && m_textureMapsDirty && m_cancelSpawn != true)
{
m_textureMapsDirty = false;
foreach (Terrain t in Terrain.activeTerrains)
{
SaveTextureMapsToTerrain(t.GetInstanceID());
}
}
DeleteTextureMapCache();
m_cacheTextures = false;
}
if (m_cacheDetails)
{
if (m_cancelSpawn != true)
{
foreach (Terrain t in Terrain.activeTerrains)
{
SaveDetailMapsToTerrain(t.GetInstanceID());
}
}
DeleteDetailMapCache();
m_cacheDetails = false;
}
if (m_cacheTags)
{
DeleteTagCache();
m_cacheTags = false;
}
if (m_cacheHeightMaps)
{
if (flushDirty && m_heightMapDirty && m_cancelSpawn != true)
{
m_heightMapDirty = false;
SaveHeightMapToTerrain(Terrain.activeTerrain.GetInstanceID());
}
DeleteHeightMapCache();
m_cacheHeightMaps = false;
}
}
/// <summary>
/// Attempt to execute a rule taking fitness, failure rate and instances into account
/// </summary>
/// <param name="rule">The rule to execute</param>
/// <param name="spawnInfo">The related spawninfo</param>
public bool TryExecuteRule(ref SpawnRule rule, ref SpawnInfo spawnInfo)
{
//Check null
if (rule != null)
{
//Check instances
if (rule.m_ignoreMaxInstances || (rule.m_activeInstanceCnt < rule.m_maxInstances))
{
//Update fitness based on distance evaluation
spawnInfo.m_fitness *= m_spawnFitnessAttenuator.Evaluate(Mathf.Clamp01(spawnInfo.m_hitDistanceWU / m_settings.m_spawnRange));
//Udpate fitness based on area mask
if (m_areaMaskMode != GaiaConstants.ImageFitnessFilterMode.None)
{
if (m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.PerlinNoise ||
m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.BillowNoise ||
m_areaMaskMode == GaiaConstants.ImageFitnessFilterMode.RidgedNoise)
{
if (!m_noiseInvert)
{
spawnInfo.m_fitness *= m_noiseGenerator.GetNormalisedValue(100000f + (spawnInfo.m_hitLocationWU.x * (1f / m_noiseZoom)), 100000f + (spawnInfo.m_hitLocationWU.z * (1f / m_noiseZoom)));
}
else
{
spawnInfo.m_fitness *= (1f - m_noiseGenerator.GetNormalisedValue(100000f + (spawnInfo.m_hitLocationWU.x * (1f / m_noiseZoom)), 100000f + (spawnInfo.m_hitLocationWU.z * (1f / m_noiseZoom))));
}
}
else
{
if (m_imageMaskHM.HasData())
{
float x = (spawnInfo.m_hitLocationWU.x - (transform.position.x - m_settings.m_spawnRange)) / (m_settings.m_spawnRange * 2f);
float z = (spawnInfo.m_hitLocationWU.z - (transform.position.z - m_settings.m_spawnRange)) / (m_settings.m_spawnRange * 2f);
spawnInfo.m_fitness *= m_imageMaskHM[x, z];
}
}
}
//Check fitness
if (spawnInfo.m_fitness > rule.m_minRequiredFitness)
{
//Only spawn if we pass a random failure check
if (GetRandomFloat(0f, 1f) > rule.m_failureRate)
{
rule.Spawn(ref spawnInfo);
return true;
}
}
}
}
return false;
}
/// <summary>
/// This is a fairly expensive raycast based location check that is capable of detecting things like tree collider hits on the terrain.
/// It will return the name and height of the thing that was hit, plus some underlying terrain information. In the scenario of terrain tree
/// hits you can comparing height of the rtaycast hit against the height of the terrain to detect this.
/// It will return true plus details if something is hit, otherwise false.
/// </summary>
/// <param name="locationWU">The location we are checking in world units</param>
/// <param name="spawnInfo">The information we gather about this location</param>
/// <returns>True if we hit something, false otherwise</returns>
public bool CheckLocation(Vector3 locationWU, ref SpawnInfo spawnInfo)
{
//Some initialisation
spawnInfo.m_spawner = this;
spawnInfo.m_outOfBounds = true;
spawnInfo.m_wasVirginTerrain = false;
spawnInfo.m_spawnRotationY = 0f;
spawnInfo.m_hitDistanceWU = Vector3.Distance(transform.position, locationWU);
spawnInfo.m_hitLocationWU = locationWU;
spawnInfo.m_hitNormal = Vector3.zero;
spawnInfo.m_hitObject = null;
spawnInfo.m_hitTerrain = null;
spawnInfo.m_terrainNormalWU = Vector3.one;
spawnInfo.m_terrainHeightWU = 0f;
spawnInfo.m_terrainSlopeWU = 0f;
spawnInfo.m_areaHitSlopeWU = 0f;
spawnInfo.m_areaMinSlopeWU = 0f;
spawnInfo.m_areaAvgSlopeWU = 0f;
spawnInfo.m_areaMaxSlopeWU = 0f;
//Make sure we are above it
locationWU.y = m_terrainHeight + 1000f;
//Run a ray traced hit check to see what we have hit - if we dont get a hit then we are off terrain and will ignore
if (Physics.Raycast(locationWU, Vector3.down, out m_checkHitInfo, Mathf.Infinity, m_spawnCollisionLayers))
{
//If its a grass spawner, and we got a sphere collider, try again so that we ignore the sphere collider
if (spawnInfo.m_spawner.IsDetailSpawner())
{
if ((m_checkHitInfo.collider is SphereCollider || m_checkHitInfo.collider is CapsuleCollider) && m_checkHitInfo.collider.name == "_GaiaCollider_Grass")
{
//Drop it slightly and run it again
locationWU.y = m_checkHitInfo.point.y - 0.01f;
//Run the raycast again - it should hit something
if (!Physics.Raycast(locationWU, Vector3.down, out m_checkHitInfo, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
}
}
//Update spawnInfo
spawnInfo.m_hitLocationWU = m_checkHitInfo.point;
spawnInfo.m_hitDistanceWU = Vector3.Distance(transform.position, spawnInfo.m_hitLocationWU);
spawnInfo.m_hitNormal = m_checkHitInfo.normal;
spawnInfo.m_hitObject = m_checkHitInfo.transform;
//Check distance - bomb out if out of range
if (m_spawnerShape == GaiaConstants.SpawnerShape.Box)
{
if (!m_spawnerBounds.Contains(spawnInfo.m_hitLocationWU))
{
return false;
}
}
else
{
if (spawnInfo.m_hitDistanceWU > m_settings.m_spawnRange)
{
return false;
}
}
spawnInfo.m_outOfBounds = false;
//Gather some terrain info at this location
Terrain terrain;
if (m_checkHitInfo.collider is TerrainCollider)
{
terrain = m_checkHitInfo.transform.GetComponent<Terrain>();
spawnInfo.m_wasVirginTerrain = true; //It might be virgin terrain
}
else
{
terrain = Gaia.TerrainHelper.GetTerrain(m_checkHitInfo.point);
}
if (terrain != null)
{
spawnInfo.m_hitTerrain = terrain;
spawnInfo.m_terrainHeightWU = terrain.SampleHeight(m_checkHitInfo.point);
Vector3 terrainLocalPos = terrain.transform.InverseTransformPoint(m_checkHitInfo.point);
Vector3 normalizedPos = new Vector3(Mathf.InverseLerp(0.0f, terrain.terrainData.size.x, terrainLocalPos.x),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.y, terrainLocalPos.y),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.z, terrainLocalPos.z));
spawnInfo.m_hitLocationNU = normalizedPos;
spawnInfo.m_terrainSlopeWU = terrain.terrainData.GetSteepness(normalizedPos.x, normalizedPos.z);
spawnInfo.m_areaHitSlopeWU = spawnInfo.m_areaMinSlopeWU = spawnInfo.m_areaAvgSlopeWU = spawnInfo.m_areaMaxSlopeWU = spawnInfo.m_terrainSlopeWU;
spawnInfo.m_terrainNormalWU = terrain.terrainData.GetInterpolatedNormal(normalizedPos.x, normalizedPos.z);
//Check for virgin terrain now that we know actual terrain height - difference will be tree colliders
if (spawnInfo.m_wasVirginTerrain == true)
{
//Use the tree manager to do hits on trees
if (spawnInfo.m_spawner.m_treeCache.Count(spawnInfo.m_hitLocationWU, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
//Set up the texture layer array in spawn info
spawnInfo.m_textureStrengths = new float[spawnInfo.m_hitTerrain.terrainData.alphamapLayers];
//Grab the textures
if (m_textureMapCache != null && m_textureMapCache.Count > 0)
{
List<HeightMap> hms = m_textureMapCache[terrain.GetInstanceID()];
for (int i = 0; i < spawnInfo.m_textureStrengths.Length; i++)
{
spawnInfo.m_textureStrengths[i] = hms[i][normalizedPos.z, normalizedPos.x];
}
}
else
{
float[,,] hms = terrain.terrainData.GetAlphamaps((int)(normalizedPos.x * (float)(terrain.terrainData.alphamapWidth - 1)), (int)(normalizedPos.z * (float)(terrain.terrainData.alphamapHeight - 1)), 1, 1);
for (int i = 0; i < spawnInfo.m_textureStrengths.Length; i++)
{
spawnInfo.m_textureStrengths[i] = hms[0, 0, i];
}
}
}
return true;
}
return false;
}
/// <summary>
/// This will do a bounded location check in order to calculate bounded slopes and checkd for bounded collisions
/// </summary>
/// <param name="spawnInfo"></param>
/// <param name="distance"></param>
/// <returns></returns>
public bool CheckLocationBounds(ref SpawnInfo spawnInfo, float distance)
{
//Initialise
spawnInfo.m_areaHitSlopeWU = spawnInfo.m_areaMinSlopeWU = spawnInfo.m_areaAvgSlopeWU = spawnInfo.m_areaMaxSlopeWU = spawnInfo.m_terrainSlopeWU;
if (spawnInfo.m_areaHitsWU == null)
{
spawnInfo.m_areaHitsWU = new Vector3[4];
}
spawnInfo.m_areaHitsWU[0] = new Vector3(spawnInfo.m_hitLocationWU.x + distance, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z);
spawnInfo.m_areaHitsWU[1] = new Vector3(spawnInfo.m_hitLocationWU.x - distance, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z);
spawnInfo.m_areaHitsWU[2] = new Vector3(spawnInfo.m_hitLocationWU.x, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z + distance);
spawnInfo.m_areaHitsWU[3] = new Vector3(spawnInfo.m_hitLocationWU.x, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z - distance);
//Run ray traced hits to check the lay of the land - if we dont get a hit then we are off terrain and will fail
RaycastHit hit;
//First check the main volume under the original position for non terrain related hits
Vector3 extents = new Vector3(distance, 0.1f, distance);
if (!Physics.BoxCast(new Vector3(spawnInfo.m_hitLocationWU.x, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z), extents, Vector3.down, out hit, Quaternion.identity, Mathf.Infinity, m_spawnCollisionLayers))
//if (!Physics.SphereCast(new Vector3(spawnInfo.m_hitLocationWU.x, spawnInfo.m_hitLocationWU.y + 3000f, spawnInfo.m_hitLocationWU.z), distance, Vector3.down, out hit, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
//Test virginity
if (spawnInfo.m_wasVirginTerrain == true)
{
if (hit.collider is TerrainCollider)
{
//Use the tree manager to do hits on trees
if (spawnInfo.m_spawner.m_treeCache.Count(hit.point, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
else
{
spawnInfo.m_wasVirginTerrain = false;
}
}
//Now test the first corner
if (!Physics.Raycast(spawnInfo.m_areaHitsWU[0], Vector3.down, out hit, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
//Update hit location
spawnInfo.m_areaHitsWU[0] = hit.point;
//Update slope calculations
Terrain terrain = hit.transform.GetComponent<Terrain>();
if (terrain == null)
{
terrain = Gaia.TerrainHelper.GetTerrain(hit.point);
}
Vector3 localPos = Vector3.zero;
Vector3 normPos = Vector3.zero;
//float terrainHeight = 0f;
float terrainSlope = 0f;
if (terrain != null)
{
//terrainHeight = terrain.SampleHeight(hit.point);
localPos = terrain.transform.InverseTransformPoint(hit.point);
normPos = new Vector3(Mathf.InverseLerp(0.0f, terrain.terrainData.size.x, localPos.x),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.y, localPos.y),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.z, localPos.z));
terrainSlope = terrain.terrainData.GetSteepness(normPos.x, normPos.z);
spawnInfo.m_areaAvgSlopeWU += terrainSlope;
if (terrainSlope > spawnInfo.m_areaMaxSlopeWU)
{
spawnInfo.m_areaMaxSlopeWU = terrainSlope;
}
if (terrainSlope < spawnInfo.m_areaMinSlopeWU)
{
spawnInfo.m_areaMinSlopeWU = terrainSlope;
}
//Check for virginity
if (spawnInfo.m_wasVirginTerrain == true)
{
if (hit.collider is TerrainCollider)
{
if (spawnInfo.m_spawner.m_treeCache.Count(hit.point, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
else
{
spawnInfo.m_wasVirginTerrain = false;
}
}
}
//Now test the next corner
if (!Physics.Raycast(spawnInfo.m_areaHitsWU[1], Vector3.down, out hit, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
//Update hit location
spawnInfo.m_areaHitsWU[1] = hit.point;
//Update slope calculations
terrain = hit.transform.GetComponent<Terrain>();
if (terrain == null)
{
terrain = Gaia.TerrainHelper.GetTerrain(hit.point);
}
if (terrain != null)
{
//terrainHeight = terrain.SampleHeight(hit.point);
localPos = terrain.transform.InverseTransformPoint(hit.point);
normPos = new Vector3(Mathf.InverseLerp(0.0f, terrain.terrainData.size.x, localPos.x),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.y, localPos.y),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.z, localPos.z));
terrainSlope = terrain.terrainData.GetSteepness(normPos.x, normPos.z);
spawnInfo.m_areaAvgSlopeWU += terrainSlope;
if (terrainSlope > spawnInfo.m_areaMaxSlopeWU)
{
spawnInfo.m_areaMaxSlopeWU = terrainSlope;
}
if (terrainSlope < spawnInfo.m_areaMinSlopeWU)
{
spawnInfo.m_areaMinSlopeWU = terrainSlope;
}
//Check for virginity
if (spawnInfo.m_wasVirginTerrain == true)
{
if (hit.collider is TerrainCollider)
{
if (spawnInfo.m_spawner.m_treeCache.Count(hit.point, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
else
{
spawnInfo.m_wasVirginTerrain = false;
}
}
}
//Now test the next corner
if (!Physics.Raycast(spawnInfo.m_areaHitsWU[2], Vector3.down, out hit, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
//Update hit location
spawnInfo.m_areaHitsWU[2] = hit.point;
//Update slope calculations
terrain = hit.transform.GetComponent<Terrain>();
if (terrain == null)
{
terrain = Gaia.TerrainHelper.GetTerrain(hit.point);
}
if (terrain != null)
{
//terrainHeight = terrain.SampleHeight(hit.point);
localPos = terrain.transform.InverseTransformPoint(hit.point);
normPos = new Vector3(Mathf.InverseLerp(0.0f, terrain.terrainData.size.x, localPos.x),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.y, localPos.y),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.z, localPos.z));
terrainSlope = terrain.terrainData.GetSteepness(normPos.x, normPos.z);
spawnInfo.m_areaAvgSlopeWU += terrainSlope;
if (terrainSlope > spawnInfo.m_areaMaxSlopeWU)
{
spawnInfo.m_areaMaxSlopeWU = terrainSlope;
}
if (terrainSlope < spawnInfo.m_areaMinSlopeWU)
{
spawnInfo.m_areaMinSlopeWU = terrainSlope;
}
//Check for virginity
if (spawnInfo.m_wasVirginTerrain == true)
{
if (hit.collider is TerrainCollider)
{
if (spawnInfo.m_spawner.m_treeCache.Count(hit.point, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
else
{
spawnInfo.m_wasVirginTerrain = false;
}
}
}
//Now test the next corner
if (!Physics.Raycast(spawnInfo.m_areaHitsWU[3], Vector3.down, out hit, Mathf.Infinity, m_spawnCollisionLayers))
{
return false;
}
//Update hit location
spawnInfo.m_areaHitsWU[3] = hit.point;
//Update slope calculations
terrain = hit.transform.GetComponent<Terrain>();
if (terrain == null)
{
terrain = Gaia.TerrainHelper.GetTerrain(hit.point);
}
if (terrain != null)
{
//terrainHeight = terrain.SampleHeight(hit.point);
localPos = terrain.transform.InverseTransformPoint(hit.point);
normPos = new Vector3(Mathf.InverseLerp(0.0f, terrain.terrainData.size.x, localPos.x),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.y, localPos.y),
Mathf.InverseLerp(0.0f, terrain.terrainData.size.z, localPos.z));
terrainSlope = terrain.terrainData.GetSteepness(normPos.x, normPos.z);
spawnInfo.m_areaAvgSlopeWU += terrainSlope;
if (terrainSlope > spawnInfo.m_areaMaxSlopeWU)
{
spawnInfo.m_areaMaxSlopeWU = terrainSlope;
}
if (terrainSlope < spawnInfo.m_areaMinSlopeWU)
{
spawnInfo.m_areaMinSlopeWU = terrainSlope;
}
//Check for virginity
if (spawnInfo.m_wasVirginTerrain == true)
{
if (hit.collider is TerrainCollider)
{
if (spawnInfo.m_spawner.m_treeCache.Count(hit.point, 0.5f) > 0)
{
spawnInfo.m_wasVirginTerrain = false;
}
}
else
{
spawnInfo.m_wasVirginTerrain = false;
}
}
}
//Now update the slopes and spawninfo
spawnInfo.m_areaAvgSlopeWU = spawnInfo.m_areaAvgSlopeWU / 5f;
float dx = spawnInfo.m_areaHitsWU[0].y - spawnInfo.m_areaHitsWU[1].y;
float dz = spawnInfo.m_areaHitsWU[2].y - spawnInfo.m_areaHitsWU[3].y;
spawnInfo.m_areaHitSlopeWU = Gaia.GaiaUtils.Math_Clamp(0f, 90f, (float)(Math.Sqrt((dx * dx) + (dz * dz))));
return true;
}
// public bool CheckForMissingResources(bool allTerrains, bool autoAssignResources = false)
// {
// //Get affected terrains first - we don't want to check for missing resources in a terrain which won't even be affected
// Terrain[] affectedTerrains;
// //Are we spawning across all terrains? Then all active are affected as well.
// if (allTerrains)
// {
// affectedTerrains = Terrain.activeTerrains;
// }
// else
// {
// //Local spawn only - simulate operation to get all terrains affected by the spawn range
// Terrain currentTerrain = GetCurrentTerrain();
// GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, m_settings.m_spawnRange * 2f);
// operation.m_isWorldMapOperation = m_settings.m_isWorldmapSpawner;
// operation.GetAffectedTerrainsOnly();
// affectedTerrains = operation.affectedTerrainPixels.Where(x => x.Key.operationType == MultiTerrainOperationType.AffectedTerrains).Select(y => y.Key.terrain).ToArray();
// operation.CloseOperation();
// }
// AssociateAssets();
// int[] missingResources = GetMissingResources(affectedTerrains);
// if (missingResources.GetLength(0) > 0)
// {
// SpawnRule missingRule;
// StringBuilder sb = new StringBuilder();
// for (int idx = 0; idx < missingResources.GetLength(0); idx++)
// {
// missingRule = m_settings.m_spawnerRules[missingResources[idx]];
// if (idx != 0)
// {
// sb.Append("\r\n");
// }
// sb.Append(missingRule.m_name);
// }
//#if UNITY_EDITOR
// if (autoAssignResources || EditorUtility.DisplayDialog("WARNING!", "The following resources are missing from one or more terrains! \r\n\r\n" + sb.ToString() + "\r\n\r\n Do you want to add them now?", "Add Resources", "Cancel"))
// {
// AddResourcesToTerrain(missingResources, affectedTerrains);
// }
// else
// {
// return true;
// }
//#endif
// }
// return false;
// }
/// <summary>
/// Update statistics counters
/// </summary>
public void UpdateCounters()
{
//m_totalRuleCnt = 0;
//m_activeRuleCnt = 0;
//m_inactiveRuleCnt = 0;
//m_maxInstanceCnt = 0;
//m_activeInstanceCnt = 0;
//m_inactiveInstanceCnt = 0;
//m_totalInstanceCnt = 0;
//foreach (SpawnRule rule in m_settings.m_spawnerRules)
//{
// m_totalRuleCnt++;
// if (rule.m_isActive)
// {
// m_activeRuleCnt++;
// m_maxInstanceCnt += rule.m_maxInstances;
// m_activeInstanceCnt += rule.m_activeInstanceCnt;
// m_inactiveInstanceCnt += rule.m_inactiveInstanceCnt;
// m_totalInstanceCnt += (rule.m_activeInstanceCnt + rule.m_inactiveInstanceCnt);
// }
// else
// {
// m_inactiveRuleCnt++;
// }
//}
}
/// <summary>
/// Draw gizmos
/// </summary>
void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
if (m_showGizmos && Selection.activeObject == gameObject)
{
if (m_showBoundingBox)
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(transform.position, new Vector3(m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f, m_settings.m_spawnRange * 2f));
}
//Water
if (m_settings.m_resources != null && m_showSeaLevelPlane)
{
BoundsDouble bounds = new BoundsDouble();
if (m_settings.m_isWorldmapSpawner)
{
if (m_worldMapTerrain == null)
{
m_worldMapTerrain = TerrainHelper.GetWorldMapTerrain();
}
bounds.center = m_worldMapTerrain.terrainData.bounds.center;
bounds.extents = m_worldMapTerrain.terrainData.bounds.extents;
//bounds need to be in world space + use the shifted origin
bounds.center = transform.position;
}
else
{
TerrainHelper.GetTerrainBounds(ref bounds);
}
bounds.center = new Vector3Double(bounds.center.x, SessionManager.GetSeaLevel(m_settings.m_isWorldmapSpawner), 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);
}
}
//Update the counters
UpdateCounters();
#endif
}
#region Texture map management
/// <summary>
/// Cache the texture maps for the terrain object id supplied - this is very memory intensive so use with care!
/// </summary>
public void CacheTextureMapsFromTerrain(int terrainID)
{
//Construct them of we dont have them
if (m_textureMapCache == null)
{
m_textureMapCache = new Dictionary<int, List<HeightMap>>();
}
//Now find the terrain and load them for the specified terrain
Terrain terrain;
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
float[,,] splatMaps = terrain.terrainData.GetAlphamaps(0, 0, terrain.terrainData.alphamapWidth, terrain.terrainData.alphamapHeight);
List<HeightMap> textureMapList = new List<HeightMap>();
for (int txtIdx = 0; txtIdx < terrain.terrainData.alphamapLayers; txtIdx++)
{
HeightMap txtMap = new HeightMap(splatMaps, txtIdx);
textureMapList.Add(txtMap);
}
m_textureMapCache[terrainID] = textureMapList;
return;
}
}
Debug.LogError("Attempted to get textures on terrain that does not exist!");
}
/// <summary>
/// Get the detail map list for the terrain
/// </summary>
/// <param name="terrainID">Object id of the terrain</param>
/// <returns>Detail map list or null</returns>
public List<HeightMap> GetTextureMaps(int terrainID)
{
List<HeightMap> mapList;
if (!m_textureMapCache.TryGetValue(terrainID, out mapList))
{
return null;
}
return mapList;
}
/// <summary>
/// Save the texture maps back into the terrain
/// </summary>
/// <param name="terrainID">ID of the terrain to do this for</param>
public void SaveTextureMapsToTerrain(int terrainID)
{
Terrain terrain;
HeightMap txtMap;
List<HeightMap> txtMapList;
//Make sure we can find it
if (!m_textureMapCache.TryGetValue(terrainID, out txtMapList))
{
Debug.LogError("Texture map list was not found for terrain ID : " + terrainID + " !");
return;
}
//Abort if we dont have anything in the list
if (txtMapList.Count <= 0)
{
Debug.LogError("Texture map list was empty for terrain ID : " + terrainID + " !");
return;
}
//Locate the terrain
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
//Make sure that the number of prototypes matches up
if (txtMapList.Count != terrain.terrainData.alphamapLayers)
{
Debug.LogError("Texture map prototype list does not match terrain prototype list for terrain ID : " + terrainID + " !");
return;
}
float[,,] splatMaps = new float[terrain.terrainData.alphamapWidth, terrain.terrainData.alphamapHeight, terrain.terrainData.alphamapLayers];
for (int txtIdx = 0; txtIdx < terrain.terrainData.alphamapLayers; txtIdx++)
{
txtMap = txtMapList[txtIdx];
for (int x = 0; x < txtMap.Width(); x++)
{
for (int z = 0; z < txtMap.Depth(); z++)
{
splatMaps[x, z, txtIdx] = txtMap[x, z];
}
}
}
terrain.terrainData.SetAlphamaps(0, 0, splatMaps);
return;
}
}
Debug.LogError("Attempted to locate a terrain that does not exist!");
}
/// <summary>
/// Remove the texture maps from memory
/// </summary>
public void DeleteTextureMapCache()
{
m_textureMapCache = new Dictionary<int, List<HeightMap>>();
}
/// <summary>
/// Set the texture maps dirty if we modified them
/// </summary>
public void SetTextureMapsDirty()
{
m_textureMapsDirty = true;
}
#endregion
#region Detail map management
/// <summary>
/// Get the detail maps for the terrain object id supplied - this is very memory intensive so use with care!
/// </summary>
public void CacheDetailMapsFromTerrain(int terrainID)
{
//Construct them of we dont have them
if (m_detailMapCache == null)
{
m_detailMapCache = new Dictionary<int, List<HeightMap>>();
}
//Now find the terrain and load them for the specified terrain
Terrain terrain;
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
List<HeightMap> detailMapList = new List<HeightMap>();
for (int dtlIdx = 0; dtlIdx < terrain.terrainData.detailPrototypes.Length; dtlIdx++)
{
HeightMap dtlMap = new HeightMap(terrain.terrainData.GetDetailLayer(0, 0, terrain.terrainData.detailWidth, terrain.terrainData.detailHeight, dtlIdx));
detailMapList.Add(dtlMap);
}
m_detailMapCache[terrainID] = detailMapList;
return;
}
}
Debug.LogError("Attempted to get details on terrain that does not exist!");
}
/// <summary>
/// Save the detail maps back into the terrain
/// </summary>
/// <param name="terrainID">ID of the terrain to do this for</param>
public void SaveDetailMapsToTerrain(int terrainID)
{
Terrain terrain;
HeightMap dtlMap;
List<HeightMap> dtlMapList;
//Make sure we can find it
if (!m_detailMapCache.TryGetValue(terrainID, out dtlMapList))
{
Debug.LogWarning(gameObject.name + "Detail map list was not found for terrain ID : " + terrainID + " !");
return;
}
//Abort if we dont have anything in the list
if (dtlMapList.Count <= 0)
{
Debug.LogWarning(gameObject.name + ": Detail map list was empty for terrain ID : " + terrainID + " !");
return;
}
//Locate the terrain
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
//Make sure that the number of prototypes matches up
if (dtlMapList.Count != terrain.terrainData.detailPrototypes.Length)
{
Debug.LogError("Detail map protoype list does not match terrain prototype list for terrain ID : " + terrainID + " !");
return;
}
//Mow iterate thru and apply back
int[,] dtlMapArray = new int[dtlMapList[0].Width(), dtlMapList[0].Depth()];
for (int dtlIdx = 0; dtlIdx < terrain.terrainData.detailPrototypes.Length; dtlIdx++)
{
dtlMap = dtlMapList[dtlIdx];
for (int x = 0; x < dtlMap.Width(); x++)
{
for (int z = 0; z < dtlMap.Depth(); z++)
{
dtlMapArray[x, z] = (int)dtlMap[x, z];
}
}
terrain.terrainData.SetDetailLayer(0, 0, dtlIdx, dtlMapArray);
}
terrain.Flush();
return;
}
}
Debug.LogError("Attempted to locate a terrain that does not exist!");
}
/// <summary>
/// Get the detail map list for the terrain
/// </summary>
/// <param name="terrainID">Object id of the terrain</param>
/// <returns>Detail map list or null</returns>
public List<HeightMap> GetDetailMaps(int terrainID)
{
List<HeightMap> mapList;
if (!m_detailMapCache.TryGetValue(terrainID, out mapList))
{
return null;
}
return mapList;
}
/// <summary>
/// Get the detail map for the specific detail
/// </summary>
/// <param name="terrainID">Terrain to query</param>
/// <param name="detailIndex">Detail prototype index</param>
/// <returns>Detail heightmap or null if not found</returns>
public HeightMap GetDetailMap(int terrainID, int detailIndex)
{
List<HeightMap> dtlMapList;
if (!m_detailMapCache.TryGetValue(terrainID, out dtlMapList))
{
return null;
}
if (detailIndex >= 0 && detailIndex < dtlMapList.Count)
{
return dtlMapList[detailIndex];
}
return null;
}
/// <summary>
/// Remove the detail maps from memory
/// </summary>
public void DeleteDetailMapCache()
{
m_detailMapCache = new Dictionary<int, List<HeightMap>>();
}
#endregion
#region Tree Management
public void CacheTreesFromTerrain()
{
m_treeCache.LoadTreesFromTerrain();
}
public void DeleteTreeCache()
{
m_treeCache = new TreeManager();
}
#endregion
#region Sessions and Serialisation
/// <summary>
/// Add the operationm to the session manager
/// </summary>
/// <param name="opType">The type of operation to add</param>
public void AddToSession(GaiaOperation.OperationType opType, string opName)
{
//Update the session
if (SessionManager != null && SessionManager.IsLocked() != true)
{
GaiaOperation op = new GaiaOperation();
op.m_description = opName;
//op.m_generatedByID = m_spawnerID;
//op.m_generatedByName = transform.name;
//op.m_generatedByType = this.GetType().ToString();
op.m_isActive = true;
op.m_operationDateTime = DateTime.Now.ToString();
op.m_operationType = opType;
//op.m_operationDataJson = new string[1];
//op.m_operationDataJson[0] = this.SerialiseJson();
SessionManager.AddOperation(op);
SessionManager.AddResource(m_settings.m_resources);
}
}
/// <summary>
/// Serialise this as json
/// </summary>
/// <returns></returns>
public string SerialiseJson()
{
//Grab the various paths
//#if UNITY_EDITOR
// m_settings.m_resourcesPath = AssetDatabase.GetAssetPath(m_settings.m_resources);
//#endif
// fsData data;
// fsSerializer serializer = new fsSerializer();
// serializer.TrySerialize(this, out data);
// //Debug.Log(fsJsonPrinter.PrettyJson(data));
// return fsJsonPrinter.CompressedJson(data);
return "";
}
/// <summary>
/// Deserialise the suplied json into this object
/// </summary>
/// <param name="json">Source json</param>
public void DeSerialiseJson(string json)
{
//fsData data = fsJsonParser.Parse(json);
//fsSerializer serializer = new fsSerializer();
//var spawner = this;
//serializer.TryDeserialize<Spawner>(data, ref spawner);
//spawner.m_settings.m_resources = GaiaUtils.GetAsset(m_settings.m_resourcesPath, typeof(Gaia.GaiaResource)) as Gaia.GaiaResource;
}
#endregion
#region Handy helpers
/// <summary>
/// Flatten all active terrains
/// </summary>
public void FlattenTerrain()
{
//Update the session
AddToSession(GaiaOperation.OperationType.FlattenTerrain, "Flattening terrain");
//Get an undo buffer
GaiaWorldManager mgr = new GaiaWorldManager(Terrain.activeTerrains);
mgr.FlattenWorld();
}
/// <summary>
/// Smooth all active terrains
/// </summary>
public void SmoothTerrain()
{
//Update the session
AddToSession(GaiaOperation.OperationType.SmoothTerrain, "Smoothing terrain");
//Smooth the world
GaiaWorldManager mgr = new GaiaWorldManager(Terrain.activeTerrains);
mgr.SmoothWorld();
}
/// <summary>
/// Clear trees
/// </summary>
public void ClearTrees(ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List<string> terrainNames = null)
{
TerrainHelper.ClearSpawns(SpawnerResourceType.TerrainTree, clearSpawnFor, clearSpawnFrom, terrainNames, this);
//iterate through all spawners, reset counter for tree rules
ResetAffectedSpawnerCounts(SpawnerResourceType.TerrainTree);
}
public void ActivateTreeStandIn(int m_spawnRuleIndexBeingDrawn)
{
//Before activation: Do other spawners currently use the stand-in? If yes, we need to turn them back before activating this one
var allSpawnersWithStandIns = Resources.FindObjectsOfTypeAll<Spawner>().Where(x => x.m_settings.m_spawnerRules.Find(y => y.m_usesBoxStandIn == true) != null).ToArray();
foreach (Spawner spawner in allSpawnersWithStandIns)
{
for (int i = 0; i < spawner.m_settings.m_spawnerRules.Count; i++)
{
SpawnRule sr = (SpawnRule)spawner.m_settings.m_spawnerRules[i];
if (sr.m_usesBoxStandIn)
{
spawner.DeactivateTreeStandIn(i);
}
}
}
ResourceProtoTree resourceProtoTree = m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[m_spawnRuleIndexBeingDrawn].m_resourceIdx];
foreach (Terrain t in Terrain.activeTerrains)
{
int treePrototypeID = -1;
int localTerrainIdx = 0;
foreach (TreePrototype proto in t.terrainData.treePrototypes)
{
if (PWCommon3.Utils.IsSameGameObject(resourceProtoTree.m_desktopPrefab, proto.prefab, false))
{
treePrototypeID = localTerrainIdx;
break;
}
localTerrainIdx++;
}
if (treePrototypeID != -1)
{
//reference the exisiting prototypes, then assign them - otherwise the terrain trees won't update properly
TreePrototype[] exisitingPrototypes = t.terrainData.treePrototypes;
exisitingPrototypes[treePrototypeID].prefab = GaiaSettings.m_boxStandInPrefab;
t.terrainData.treePrototypes = exisitingPrototypes;
}
m_settings.m_spawnerRules[m_spawnRuleIndexBeingDrawn].m_usesBoxStandIn = true;
}
}
public void DeactivateTreeStandIn(int m_spawnRuleIndexBeingDrawn)
{
ResourceProtoTree resourceProtoTree = m_settings.m_resources.m_treePrototypes[m_settings.m_spawnerRules[m_spawnRuleIndexBeingDrawn].m_resourceIdx];
foreach (Terrain t in Terrain.activeTerrains)
{
int treePrototypeID = -1;
int localTerrainIdx = 0;
foreach (TreePrototype proto in t.terrainData.treePrototypes)
{
if (PWCommon3.Utils.IsSameGameObject(m_gaiaSettings.m_boxStandInPrefab, proto.prefab, false))
{
treePrototypeID = localTerrainIdx;
break;
}
localTerrainIdx++;
}
if (treePrototypeID != -1)
{
//reference the exisiting prototypes, then assign them - otherwise the terrain trees won't update properly
TreePrototype[] exisitingPrototypes = t.terrainData.treePrototypes;
exisitingPrototypes[treePrototypeID].prefab = resourceProtoTree.m_desktopPrefab;
t.terrainData.treePrototypes = exisitingPrototypes;
}
}
m_settings.m_spawnerRules[m_spawnRuleIndexBeingDrawn].m_usesBoxStandIn = false;
}
private void ResetAffectedSpawnerCounts(SpawnerResourceType resourceType)
{
Spawner[] affectedSpawners;
if (m_settings.m_clearSpawnsFrom == ClearSpawnFrom.AnySource)
{
affectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
}
else
{
affectedSpawners = new Spawner[1] { this };
}
foreach (Spawner spawner in affectedSpawners)
{
foreach (SpawnRule spawnRule in spawner.m_settings.m_spawnerRules)
{
if (spawnRule.m_resourceType == resourceType)
{
spawnRule.m_spawnedInstances = 0;
}
}
}
}
/// <summary>
/// Clear all the grass off all the terrains
/// </summary>
public void ClearDetails(ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List<string> terrainNames = null)
{
TerrainHelper.ClearSpawns(SpawnerResourceType.TerrainDetail, clearSpawnFor, clearSpawnFrom, terrainNames, this);
ResetAffectedSpawnerCounts(SpawnerResourceType.TerrainDetail);
}
/// <summary>
/// Clears all Game object spawn rules at once.
/// </summary>
public void ClearGameObjects(ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List<string> terrainNames = null)
{
TerrainHelper.ClearSpawns(SpawnerResourceType.GameObject, clearSpawnFor, clearSpawnFrom, terrainNames, this);
ResetAffectedSpawnerCounts(SpawnerResourceType.GameObject);
//Spawner[] allAffectedSpawners;
//if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
//{
// allAffectedSpawners = new Spawner[1] { this };
//}
//else
//{
// allAffectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
//}
//int completedSpawners = 1;
//foreach (Spawner spawner in allAffectedSpawners)
//{
// GaiaUtils.DisplayProgressBarNoEditor("Clearing Game Objects...", "Spawner " + completedSpawners.ToString() + " of " + allAffectedSpawners.Count().ToString(), (float)completedSpawners / (float)allAffectedSpawners.Count());
// foreach (SpawnRule spawnRule in spawner.m_settings.m_spawnerRules)
// {
// if (spawnRule.m_resourceType == SpawnerResourceType.GameObject)
// {
// spawner.ClearGameObjectsForRule(spawnRule, clearSpawnFor == ClearSpawnFor.AllTerrains);
// }
// }
// completedSpawners++;
//}
//GaiaUtils.ClearProgressBarNoEditor();
}
/// <summary>
/// Clears all Game objects created by spawn extenisons at once.
/// </summary>
public void ClearAllSpawnExtensions(ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List<string> terrainNames = null)
{
TerrainHelper.ClearSpawns(SpawnerResourceType.SpawnExtension, clearSpawnFor, clearSpawnFrom, terrainNames, this);
ResetAffectedSpawnerCounts(SpawnerResourceType.SpawnExtension);
//Spawner[] allAffectedSpawners;
//if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
//{
// allAffectedSpawners = new Spawner[1] { this };
//}
//else
//{
// allAffectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
//}
//int completedSpawners = 1;
//foreach (Spawner spawner in allAffectedSpawners)
//{
// GaiaUtils.DisplayProgressBarNoEditor("Clearing Spawn Extensions...", "Spawner " + completedSpawners.ToString() + " of " + allAffectedSpawners.Count().ToString(), (float)completedSpawners / (float)allAffectedSpawners.Count());
// foreach (SpawnRule spawnRule in spawner.m_settings.m_spawnerRules)
// {
// if (spawnRule.m_resourceType == SpawnerResourceType.SpawnExtension)
// {
// spawner.ClearSpawnExtensionsForRule(spawnRule);
// spawner.ClearGameObjectsForRule(spawnRule, clearSpawnFor == ClearSpawnFor.AllTerrains);
// }
// }
// completedSpawners++;
//}
//GaiaUtils.ClearProgressBarNoEditor();
}
/// <summary>
/// Calls the Delete function on all Spawn Extensions of a certain rule
/// </summary>
/// <param name="spawnRule"></param>
public void ClearSpawnExtensionsForRule(SpawnRule spawnRule)
{
if (spawnRule.m_resourceIdx > m_settings.m_resources.m_spawnExtensionPrototypes.Length - 1)
{
return;
}
if (m_settings.m_resources.m_spawnExtensionPrototypes[spawnRule.m_resourceIdx] == null)
{
return;
}
ResourceProtoSpawnExtension protoSE = m_settings.m_resources.m_spawnExtensionPrototypes[spawnRule.m_resourceIdx];
//iterate through all instances
foreach (ResourceProtoSpawnExtensionInstance instance in protoSE.m_instances)
{
if (instance.m_spawnerPrefab == null)
{
continue;
}
foreach (ISpawnExtension spawnExtension in instance.m_spawnerPrefab.GetComponents<ISpawnExtension>())
{
spawnExtension.Delete();
}
}
}
/// <summary>
/// Clears all StampDistributions
/// </summary>
public void ClearStampDistributions(ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom)
{
Spawner[] allAffectedSpawners;
if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
{
allAffectedSpawners = new Spawner[1] { this };
}
else
{
allAffectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
}
int completedSpawners = 1;
foreach (Spawner spawner in allAffectedSpawners)
{
ProgressBar.Show(ProgressBarPriority.Spawning, "Clearing Stamps", "Clearing...", completedSpawners, allAffectedSpawners.Count(), true, false);
foreach (SpawnRule spawnRule in spawner.m_settings.m_spawnerRules)
{
if (spawnRule.m_resourceType == SpawnerResourceType.StampDistribution)
{
spawner.ClearStampDistributionForRule(spawnRule);
spawnRule.m_spawnedInstances = 0;
}
}
completedSpawners++;
}
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
/// <summary>
/// Clears all stamp tokens / stamper settings for the World Map Generation created by a certain rule
/// </summary>
/// <param name="spawnRule"></param>
private void ClearStampDistributionForRule(SpawnRule spawnRule)
{
ResourceProtoStampDistribution protoSD = m_settings.m_resources.m_stampDistributionPrototypes[spawnRule.m_resourceIdx];
//iterate through all Stamp Tokens and remove those belonging to the same feature type as spawned from this rule
List<string> allFeatureTypes = protoSD.m_featureSettings.Select(x => x.m_featureType).ToList();
if (m_worldMapTerrain == null)
{
m_worldMapTerrain = TerrainHelper.GetWorldMapTerrain();
}
Transform tokenContainer = m_worldMapTerrain.transform.Find(GaiaConstants.worldMapStampTokenSpawnTarget);
if (tokenContainer != null)
{
var allStampTokens = tokenContainer.GetComponentsInChildren<WorldMapStampToken>();
for (int i = allStampTokens.Length - 1; i >= 0; i--)
{
if (allFeatureTypes.Contains(allStampTokens[i].m_featureType))
{
m_worldMapStamperSettings.Remove(allStampTokens[i].m_connectedStamperSettings);
DestroyImmediate(allStampTokens[i].gameObject);
}
}
}
}
/// <summary>
/// Clear all the GameObjects created by this spawner off all the terrains
/// </summary>
public void ClearGameObjectsForRule(SpawnRule spawnRule, bool allTerrains = true, Terrain terrainToDeleteFrom = null)
{
//Update the session
string protoName = "";
switch (spawnRule.m_resourceType)
{
case SpawnerResourceType.GameObject:
ResourceProtoGameObject protoGO = m_settings.m_resources.m_gameObjectPrototypes[spawnRule.m_resourceIdx];
if (protoGO == null)
{
Debug.LogError("Could not find prototype info trying to delete Game Objects from rule " + spawnRule.m_name);
return;
}
protoName = protoGO.m_name;
break;
case SpawnerResourceType.SpawnExtension:
ResourceProtoSpawnExtension protoSE = m_settings.m_resources.m_spawnExtensionPrototypes[spawnRule.m_resourceIdx];
if (protoSE == null)
{
Debug.LogError("Could not find prototype info trying to delete Spawn Extensions Game Objects from rule " + spawnRule.m_name);
return;
}
protoName = protoSE.m_name;
break;
case SpawnerResourceType.Probe:
if (spawnRule.m_resourceIdx >= m_settings.m_resources.m_probePrototypes.Length)
{
return;
}
ResourceProtoProbe protoProbe = m_settings.m_resources.m_probePrototypes[spawnRule.m_resourceIdx];
if (protoProbe == null)
{
Debug.LogError("Could not find prototype info trying to delete probes from rule " + spawnRule.m_name);
return;
}
protoName = protoProbe.m_name;
break;
}
Terrain[] relevantTerrains;
if (allTerrains)
{
relevantTerrains = Terrain.activeTerrains;
}
else
{
if (terrainToDeleteFrom == null)
{
relevantTerrains = new Terrain[1] { GetCurrentTerrain() };
}
else
{
relevantTerrains = new Terrain[1] { terrainToDeleteFrom };
}
}
foreach (Terrain t in relevantTerrains)
{
bool deletedSomething = false;
Transform target = GaiaUtils.GetGOSpawnTarget(spawnRule, protoName, t);
Scene sceneWeDeletedFrom = target.gameObject.scene;
if (spawnRule.m_goSpawnTargetMode == SpawnerTargetMode.Terrain || allTerrains)
{
//Terrain based target, or user choose to delete from all Terrains - this means deletion can be done fast and easy by removing the target object
if (target != null)
{
deletedSomething = true;
DestroyImmediate(target.gameObject);
}
}
else
{
//There is a custom transform to spawn under and we want to delete on specific terrains only - which means we need to take a look at each Gameobject individually
float terrainMinX = t.transform.position.x;
float terrainMinZ = t.transform.position.z;
float terrainMaxX = t.transform.position.x + t.terrainData.size.x;
float terrainMaxZ = t.transform.position.z + t.terrainData.size.x;
for (int g = target.childCount - 1; g >= 0; g--)
{
GameObject GOtoDelete = target.GetChild(g).gameObject;
//is the gameobject placed on / above / below the terrain?
if (terrainMinX <= GOtoDelete.transform.position.x &&
terrainMinZ <= GOtoDelete.transform.position.z &&
terrainMaxX >= GOtoDelete.transform.position.x &&
terrainMaxZ >= GOtoDelete.transform.position.z)
{
DestroyImmediate(GOtoDelete);
deletedSomething = true;
}
}
//if the target is empty now, we can remove it as well to keep scene clean
if (target.childCount <= 0)
{
deletedSomething = true;
DestroyImmediate(target.gameObject);
}
}
//if we deleted something the scene we deleted from should be marked as dirty.
if (deletedSomething)
{
#if UNITY_EDITOR
EditorSceneManager.MarkSceneDirty(sceneWeDeletedFrom);
#endif
}
}
spawnRule.m_spawnedInstances = 0;
}
/// <summary>
/// Only serves the purpose of supressing the warning for the unused field when CTS is not installed.
/// </summary>
private void SupressCTSProfileWarning()
{
#if CTS_PRESENT
if (m_connectedCTSProfileGUID.Length > 1)
{
}
#endif
}
public static void HandleAutoSpawnerStack(List<AutoSpawner> autoSpawners, Transform transform, float range, bool allTerrains, BiomeControllerSettings biomeControllerSettings = null)
{
BoundsDouble spawnArea = new BoundsDouble();
if (allTerrains)
{
TerrainHelper.GetTerrainBounds(ref spawnArea);
}
else
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
spawnArea.center = new Vector3Double(transform.position) + TerrainLoaderManager.Instance.GetOrigin();
}
else
{
spawnArea.center = transform.position;
}
spawnArea.size = new Vector3(range * 2f, range * 2f, range * 2f);
}
try
{
TerrainLoaderManager.Instance.SwitchToLocalMap();
foreach (Spawner spawner in autoSpawners.Where(x => x.isActive == true).Select(x => x.spawner))
{
spawner.GenerateNewRandomSeed();
}
SpawnOperationSettings soSettings = ScriptableObject.CreateInstance<SpawnOperationSettings>();
soSettings.m_spawnerSettingsList = autoSpawners.Where(x => x.isActive == true).Select(x => x.spawner.m_settings).ToList();
if (biomeControllerSettings != null)
{
soSettings.m_biomeControllerSettings = biomeControllerSettings;
}
soSettings.m_spawnArea = spawnArea;
soSettings.m_isWorldMapSpawner = autoSpawners.Find(x => x.spawner.m_settings.m_isWorldmapSpawner) != null;
GaiaSessionManager.Spawn(soSettings, true, autoSpawners.Where(x => x.isActive == true).Select(x => x.spawner).ToList());
//autoSpawners[0].spawner.m_updateCoroutine = autoSpawners[0].spawner.AreaSpawn(autoSpawners.Select(x => x.spawner).ToList(), spawnArea);
//autoSpawners[0].spawner.StartEditorUpdates();
}
catch (Exception ex)
{
Debug.LogError("Autospawning failed with Exception: " + ex.Message + "\n\n" + "Stack trace: \n\n" + ex.StackTrace);
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
//AutoSpawner nextSpawner = autoSpawners.Find(x => x.status == AutoSpawnerStatus.Spawning);
//if (nextSpawner != null)
//{
// if (nextSpawner.spawner.IsSpawning())
// {
// return false;
// //Do Nothing, still spawning
// }
// else
// {
// //Auto Spawner is done, look for next spawner
// GaiaUtils.DisplayProgressBarNoEditor("Spawning", "Preparing next Spawner...",0);
// nextSpawner.status = AutoSpawnerStatus.Done;
// nextSpawner = autoSpawners.Find(x => x.status == AutoSpawnerStatus.Queued);
// }
//}
//else
//{
// //No spawner spawning atm, let's pick the first queued one
// nextSpawner = autoSpawners.Find(x => x.status == AutoSpawnerStatus.Queued);
//}
//if (nextSpawner != null && !m_cancelSpawn)
//{
// if (!nextSpawner.spawner.IsSpawning())
// {
// //nextSpawner.spawner.transform.position = new Vector3(m_stamper.transform.position.x, nextSpawner.spawner.transform.position.y, m_stamper.transform.position.z);
// //Terrain terrain = nextSpawner.spawner.GetCurrentTerrain();
// //nextSpawner.spawner.m_settings.m_spawnRange = terrain.terrainData.size.x * (m_stamper.m_settings.m_width / 100f);
// nextSpawner.spawner.UpdateMinMaxHeight();
// int totalSpawnRules = 0;
// int completedSpawnRules = 0;
// foreach (AutoSpawner autoSpawner in autoSpawners.Where(x => x.isActive))
// {
// foreach (SpawnRule rule in autoSpawner.spawner.settings.m_spawnerRules)
// {
// if (rule.m_isActive)
// {
// totalSpawnRules++;
// if (autoSpawner.status == AutoSpawnerStatus.Done)
// {
// completedSpawnRules++;
// }
// }
// }
// }
// if (allTerrains)
// {
// ////int worldSpawnSteps = nextSpawner.spawner.GetWorldSpawnSteps();
// //totalSpawnRules *= worldSpawnSteps;
// //completedSpawnRules *= worldSpawnSteps;
// }
// //nextSpawner.spawner.Spawn(allTerrains, completedSpawnRules, totalSpawnRules);
// nextSpawner.status = AutoSpawnerStatus.Spawning;
// }
// return false;
//}
//else
//{
// //no spawners left
// GaiaUtils.ClearProgressBarNoEditor();
// GaiaUtils.ReleaseAllTempRenderTextures();
// m_cancelSpawn = false;
// return true;
//}
}
#endregion
#region Height map management
/// <summary>
/// Cache the height map for the terrain object id supplied - this is very memory intensive so use with care!
/// </summary>
public void CacheHeightMapFromTerrain(int terrainID)
{
//Construct them of we dont have them
if (m_heightMapCache == null)
{
m_heightMapCache = new Dictionary<int, UnityHeightMap>();
}
//Now find the terrain and load them for the specified terrain
Terrain terrain;
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
m_heightMapCache[terrainID] = new UnityHeightMap(terrain);
return;
}
}
Debug.LogError("Attempted to get height maps on a terrain that does not exist!");
}
/// <summary>
/// Get the height map for the terrain
/// </summary>
/// <param name="terrainID">Object id of the terrain</param>
/// <returns>Heightmap or null</returns>
public UnityHeightMap GetHeightMap(int terrainID)
{
UnityHeightMap heightmap;
if (!m_heightMapCache.TryGetValue(terrainID, out heightmap))
{
return null;
}
return heightmap;
}
/// <summary>
/// Save the height map back into the terrain
/// </summary>
/// <param name="terrainID">ID of the terrain to do this for</param>
public void SaveHeightMapToTerrain(int terrainID)
{
Terrain terrain;
UnityHeightMap heightmap;
//Make sure we can find it
if (!m_heightMapCache.TryGetValue(terrainID, out heightmap))
{
Debug.LogError("Heightmap was not found for terrain ID : " + terrainID + " !");
return;
}
//Locate the terrain and update it
for (int terrIdx = 0; terrIdx < Terrain.activeTerrains.Length; terrIdx++)
{
terrain = Terrain.activeTerrains[terrIdx];
if (terrain.GetInstanceID() == terrainID)
{
heightmap.SaveToTerrain(terrain);
return;
}
}
Debug.LogError("Attempted to locate a terrain that does not exist!");
}
/// <summary>
/// Remove the texture maps from memory
/// </summary>
public void DeleteHeightMapCache()
{
m_heightMapCache = new Dictionary<int, UnityHeightMap>();
}
/// <summary>
/// Set the height maps dirty if we modified them
/// </summary>
public void SetHeightMapsDirty()
{
m_heightMapDirty = true;
}
#endregion
#region Stamp management
public void CacheStamps(List<string> stampList)
{
//Construct them of we dont have them
if (m_stampCache == null)
{
m_stampCache = new Dictionary<string, HeightMap>();
}
//Get the list of stamps for this spawner
for (int idx = 0; idx < stampList.Count; idx++)
{
}
}
#endregion
#region Tag management
/// <summary>
/// Load all the tags in the scene into the tag cache
/// </summary>
/// <param name="tagList"></param>
private void CacheTaggedGameObjectsFromScene(List<string> tagList)
{
//Create a new cache (essentially releasing the old one)
m_taggedGameObjectCache = new Dictionary<string, Quadtree<GameObject>>();
//Now load all the tagged objects into the cache
string tag;
bool foundTag;
Quadtree<GameObject> quadtree;
Rect pos = new Rect(Terrain.activeTerrain.transform.position.x, Terrain.activeTerrain.transform.position.z,
Terrain.activeTerrain.terrainData.size.x, Terrain.activeTerrain.terrainData.size.z);
for (int tagIdx = 0; tagIdx < tagList.Count; tagIdx++)
{
//Check that unity knows about the tag
tag = tagList[tagIdx].Trim();
foundTag = false;
if (!string.IsNullOrEmpty(tag))
{
#if UNITY_EDITOR
for (int idx = 0; idx < UnityEditorInternal.InternalEditorUtility.tags.Length; idx++)
{
if (UnityEditorInternal.InternalEditorUtility.tags[idx].Contains(tag))
{
foundTag = true;
break;
}
}
#else
foundTag = true;
#endif
}
//If its good then cache it
if (foundTag)
{
quadtree = null;
if (!m_taggedGameObjectCache.TryGetValue(tag, out quadtree))
{
quadtree = new Quadtree<GameObject>(pos);
m_taggedGameObjectCache.Add(tag, quadtree);
}
GameObject go;
Vector2 go2DPos;
GameObject[] gos = GameObject.FindGameObjectsWithTag(tag);
for (int goIdx = 0; goIdx < gos.Length; goIdx++)
{
go = gos[goIdx];
//Only add it if within our bounds
go2DPos = new Vector2(go.transform.position.x, go.transform.position.z);
if (pos.Contains(go2DPos))
{
quadtree.Insert(go2DPos, go);
}
}
}
}
}
/// <summary>
/// Delete the tag cache
/// </summary>
private void DeleteTagCache()
{
m_taggedGameObjectCache = null;
}
/// <summary>
/// Get the objects that match the tag list within the defined area
/// </summary>
/// <param name="tagList">List of tags to search</param>
/// <param name="area">Area to search</param>
/// <returns></returns>
public List<GameObject> GetNearbyObjects(List<string> tagList, Rect area)
{
string tag;
List<GameObject> gameObjects = new List<GameObject>();
Quadtree<GameObject> quadtree;
for (int tagIdx = 0; tagIdx < tagList.Count; tagIdx++)
{
quadtree = null;
tag = tagList[tagIdx];
//Process each tag
if (m_taggedGameObjectCache.TryGetValue(tag, out quadtree))
{
IEnumerable<GameObject> gameObjs = quadtree.Find(area);
foreach (GameObject go in gameObjs)
{
gameObjects.Add(go);
}
}
}
return gameObjects;
}
/// <summary>
/// Get the closest gameobject to the centre of the area supplied that matches the tag list
/// </summary>
/// <param name="tagList">List of tags to search</param>
/// <param name="area">The area to search</param>
/// <returns></returns>
public GameObject GetClosestObject(List<string> tagList, Rect area)
{
string tag;
float distance;
float closestDistance = float.MaxValue;
GameObject closestGo = null;
Quadtree<GameObject> quadtree;
for (int tagIdx = 0; tagIdx < tagList.Count; tagIdx++)
{
quadtree = null;
tag = tagList[tagIdx];
//Process each tag
if (m_taggedGameObjectCache.TryGetValue(tag, out quadtree))
{
IEnumerable<GameObject> gameObjs = quadtree.Find(area);
foreach (GameObject go in gameObjs)
{
distance = Vector2.Distance(area.center, new Vector2(go.transform.position.x, go.transform.position.z));
if (distance < closestDistance)
{
closestDistance = distance;
closestGo = go;
}
}
}
}
return closestGo;
}
/// <summary>
/// Get the closest gameobject to the centre of the area supplied that matches the tag
/// </summary>
/// <param name="tagList">Tag to search for</param>
/// <param name="area">The area to search</param>
/// <returns></returns>
public GameObject GetClosestObject(string tag, Rect area)
{
float distance, closestDistance = float.MaxValue;
GameObject closestGo = null;
Quadtree<GameObject> quadtree = null;
if (m_taggedGameObjectCache.TryGetValue(tag, out quadtree))
{
IEnumerable<GameObject> gameObjs = quadtree.Find(area);
foreach (GameObject go in gameObjs)
{
distance = Vector2.Distance(area.center, new Vector2(go.transform.position.x, go.transform.position.z));
if (distance < closestDistance)
{
closestDistance = distance;
closestGo = go;
}
}
}
return closestGo;
}
#endregion
#region Saving and Loading
public void LoadSettings(SpawnerSettings settingsToLoad)
{
m_settings.ClearImageMaskTextures();
//Set existing settings = null to force a new scriptable object
m_settings = null;
m_settings = Instantiate(settingsToLoad);
#if UNITY_EDITOR
m_settings.lastGUIDSaved = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(settingsToLoad));
#endif
//GaiaUtils.CopyFields(settingsToLoad, m_settings);
Spawner[] allSpawner = Resources.FindObjectsOfTypeAll<Spawner>();
#if HDPipeline
bool hdTerrainDetailMessageDisplayed = false;
#endif
foreach (SpawnRule rule in m_settings.m_spawnerRules)
{
//close down all foldouts neatly when freshly loaded
rule.m_isFoldedOut = false;
rule.m_resourceSettingsFoldedOut = false;
rule.m_spawnedInstances = 0;
//check if the spawn rule guid exists in this scene already - if yes, this rule must get a new ID then to avoid duplicate IDs
if (allSpawner.Select(x => x.m_settings.m_spawnerRules).Where(x => x.Find(y => y.GUID == rule.GUID) != null).Count() > 1)
{
rule.RegenerateGUID();
}
#if HDPipeline
if(rule.m_resourceType == SpawnerResourceType.TerrainDetail)
{
rule.m_isActive = false;
if (!hdTerrainDetailMessageDisplayed)
{
Debug.Log("Spawner '" + this.name + "' contains Terrain Detail Spawn Rules. These have been deactivated because HDRP does not support the terrain detail system. You can still activate these rules manually if you wish to spawn terrain details anyway.");
hdTerrainDetailMessageDisplayed = true;
}
}
#endif
}
//Refresh texture GUIDs since new ones could be added with these settings
ImageMask.RefreshSpawnRuleGUIDs();
m_rulePanelUnfolded = true;
if (m_settings.m_isWorldmapSpawner)
{
if (Gaia.TerrainHelper.GetTerrain(transform.position, m_settings.m_isWorldmapSpawner) != null)
{
FitToTerrain();
}
//Since this is a new world designer, assign these settings to be world biome mask settings
SessionManager.m_session.m_worldBiomeMaskSettings = m_settings;
SessionManager.SaveSession();
TerrainLoaderManager.Instance.TerrainSceneStorage.m_hasWorldMap = true;
}
UpdateMinMaxHeight();
}
#endregion
#region Random number utils
/// <summary>
/// Reset the random number generator
/// </summary>
public void ResetRandomGenertor()
{
m_rndGenerator = new XorshiftPlus(m_seed);
}
/// <summary>
/// Get a random integer
/// </summary>
/// <param name="min">Minimum value inclusive</param>
/// <param name="max">Maximum value inclusive</param>
/// <returns>Random integer between minimum and maximum values</returns>
public int GetRandomInt(int min, int max)
{
return m_rndGenerator.Next(min, max);
}
/// <summary>
/// Get a random float
/// </summary>
/// <param name="min">Minimum value inclusive</param>
/// <param name="max">Maximum value inclusive</param>
/// <returns>Random float between minimum and maximum values</returns>
public float GetRandomFloat(float min, float max)
{
return m_rndGenerator.Next(min, max);
}
/// <summary>
/// Get a random vector 3
/// </summary>
/// <param name="range">Range of values to return</param>
/// <returns>Vector 3 in the +- range supplied</returns>
public Vector3 GetRandomV3(float range)
{
return m_rndGenerator.NextVector(-range, range);
}
#endregion
}
}