using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;
using static Gaia.GaiaConstants;
#if !UNITY_2021_2_OR_NEWER
using UnityEngine.Experimental.TerrainAPI;
#else
using UnityEngine.TerrainTools;
#endif
using UnityEditor;
namespace Gaia
{
public enum StitchDirection { North, South, West, East };
///
/// Terrain utility functions
///
public class TerrainHelper : MonoBehaviour
{
[Range(1, 5), Tooltip("Number of smoothing interations to run. Can be run multiple times.")]
public int m_smoothIterations = 1;
//Knock ourselves out if we happen to be left there in play mode
void Awake()
{
gameObject.SetActive(false);
}
///
/// Flatten all the active terrains
///
public static void Flatten()
{
FlattenTerrain(Terrain.activeTerrains);
}
///
/// Flatten the terrain passed in
///
/// Terrain to be flattened
public static void FlattenTerrain(Terrain terrain)
{
float[,] heights = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
terrain.terrainData.SetHeights(0, 0, heights);
}
///
/// Flatten all the terrains passed in
///
/// Terrains to be flattened
public static void FlattenTerrain(Terrain[] terrains)
{
foreach (Terrain terrain in terrains)
{
float[,] heights = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
terrain.terrainData.SetHeights(0, 0, heights);
}
}
///
/// Stitch the terrains together with unity set neighbors calls
///
public static void Stitch()
{
StitchTerrains(Terrain.activeTerrains);
}
///
/// Stitch the terrains together - wont align them although should update this to support that as well.
///
/// Array of terrains to organise as neighbors
public static void StitchTerrains(Terrain[] terrains)
{
Terrain right = null;
Terrain left = null;
Terrain bottom = null;
Terrain top = null;
foreach (Terrain terrain in terrains)
{
right = null;
left = null;
bottom = null;
top = null;
foreach (Terrain neighbor in terrains)
{
//Check to see if neighbor is above or below
if (neighbor.transform.position.x == terrain.transform.position.x)
{
if ((neighbor.transform.position.z + neighbor.terrainData.size.z) == terrain.transform.position.z)
{
top = neighbor;
}
else if ((terrain.transform.position.z + terrain.terrainData.size.z) == neighbor.transform.position.z)
{
bottom = neighbor;
}
}
else if (neighbor.transform.position.z == terrain.transform.position.z)
{
if ((neighbor.transform.position.x + neighbor.terrainData.size.z) == terrain.transform.position.z)
{
left = neighbor;
}
else if ((terrain.transform.position.x + terrain.terrainData.size.x) == neighbor.transform.position.x)
{
right = neighbor;
}
}
}
terrain.SetNeighbors(left, top, right, bottom);
}
}
///
/// Smooth the active terrain - needs to be extended to all and to handle edges
///
/// Number of smoothing iterations
public void Smooth()
{
Smooth(m_smoothIterations);
}
///
/// Smooth the active terrain - needs to be extended to all and to handle edges
///
/// Number of smoothing iterations
public static void Smooth(int iterations)
{
UnityHeightMap hm = new UnityHeightMap(Terrain.activeTerrain);
hm.Smooth(iterations);
hm.SaveToTerrain(Terrain.activeTerrain);
}
///
/// Sets up the given material on all terrains
///
/// The material to apply
/// If not empty, this title will be displayed with the other string parameters in a dialog so the user can decide if they want to apply the material or not.
public static void SetTerrainMaterial(Material material, string title="", string message ="", string OK="", string cancel="")
{
if(title == "" || GaiaUtils.DisplayDialogNoEditor("Set up Terrain Material?",message,"Continue","Cancel"))
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
Action act = (terrain) => { terrain.materialTemplate = material; };
GaiaUtils.CallFunctionOnDynamicLoadedTerrains(act, true, null, "Applying Material to Terrains...");
}
else
{
foreach (Terrain terrain in Terrain.activeTerrains)
{
terrain.materialTemplate = material;
}
}
}
}
///
/// Get the vector of the centre of the active terrain, and flush to ground level if asked to
///
/// If true set it flush to the ground
/// Vector3.zero if no terrain, otherwise the centre of it
public static Vector3 GetActiveTerrainCenter(bool flushToGround = true)
{
Bounds b = new Bounds();
Terrain t = GetActiveTerrain();
if (GetTerrainBounds(t, ref b))
{
if (flushToGround == true)
{
return new Vector3(b.center.x, t.SampleHeight(b.center), b.center.z);
}
else
{
return b.center;
}
}
return Vector3.zero;
}
///
/// Gets the world map terrain from the scene
///
/// The world map terrain
public static Terrain GetWorldMapTerrain()
{
foreach (Terrain t in Terrain.activeTerrains)
{
if (TerrainHelper.IsWorldMapTerrain(t))
{
return t;
}
}
//still no world map terrain? might be a deactivated GameObject, check those as well
GameObject worldMapGO = GaiaUtils.FindObjectDeactivated(GaiaConstants.worldMapTerrainPrefix + "_", false);
if (worldMapGO != null)
{
Terrain t = worldMapGO.GetComponent();
if (t != null)
{
return t;
}
}
return null;
}
///
/// Get any active terrain - pref active terrain
///
/// Any active terrain or null
public static Terrain GetActiveTerrain()
{
//Grab active terrain if we can
Terrain terrain = Terrain.activeTerrain;
if (terrain != null && terrain.isActiveAndEnabled)
{
return terrain;
}
//Then check rest of terrains
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain != null && terrain.isActiveAndEnabled)
{
return terrain;
}
}
return null;
}
///
/// Get the layer mask of the active terrain, or default if there isnt one
///
/// Layermask of activer terrain or default if there isnt one
public static LayerMask GetActiveTerrainLayer()
{
LayerMask layer = new LayerMask();
Terrain terrain = GetActiveTerrain();
if (terrain != null)
{
layer.value = 1 << terrain.gameObject.layer;
return layer;
}
layer.value = 1 << LayerMask.NameToLayer("Default");
return layer;
}
///
/// Get the layer mask of the active terrain, or default if there isnt one
///
/// Layermask of activer terrain or default if there isnt one
public static LayerMask GetActiveTerrainLayerAsInt()
{
LayerMask layerValue = GetActiveTerrainLayer().value;
for (int layerIdx = 0; layerIdx < 32; layerIdx++)
{
if (layerValue == (1 << layerIdx))
{
return layerIdx;
}
}
return LayerMask.NameToLayer("Default");
}
///
/// Get the number of active terrain tiles in this scene
///
/// Number of terrains in the scene
public static int GetActiveTerrainCount()
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
//with terrain loading, we can simply count the loaded scenes, those should be the active terrains
return TerrainLoaderManager.TerrainScenes.Where(x => x.m_regularLoadState == LoadState.Loaded).Count() + TerrainLoaderManager.TerrainScenes.Where(x => x.m_impostorLoadState == LoadState.Loaded).Count();
}
else
{
//For non-terrain loading we need to take a look at what we can find in the scene
//Regular terrains
Terrain terrain;
int terrainCount = 0;
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain != null && terrain.isActiveAndEnabled)
{
terrainCount++;
}
}
//Mesh Terrains from a terrain export
GameObject exportContainer = GaiaUtils.GetTerrainExportObject(false);
if (exportContainer != null)
{
//Iterate through the objects in here, if it is active and the name checks out we can assume it is a mesh terrain.
foreach (Transform t in exportContainer.transform)
{
if (t.gameObject.GetComponent() != null)
{
if (t.gameObject.activeInHierarchy && (t.name.StartsWith(GaiaConstants.MeshTerrainName) || t.name.StartsWith(GaiaConstants.MeshTerrainLODGroupPrefix)))
{
terrainCount++;
}
}
}
}
return terrainCount;
}
}
#if GAIA_PRO_PRESENT
///
/// Get the terrain scene that matches this location, otherwise return null
///
/// Location to check in world units
/// Terrain here or null
public static TerrainScene GetDynamicLoadedTerrain(Vector3 locationWU, GaiaSessionManager gsm = null)
{
if (gsm == null)
{
gsm = GaiaSessionManager.GetSessionManager(false);
}
foreach (TerrainScene terrainScene in TerrainLoaderManager.TerrainScenes)
{
if (terrainScene.m_bounds.min.x <= locationWU.x && terrainScene.m_bounds.min.z <= locationWU.z && terrainScene.m_bounds.max.x >= locationWU.x && terrainScene.m_bounds.max.z >= locationWU.z)
{
return terrainScene;
}
}
return null;
}
#endif
///
/// Returns the Quaternion rotation from the active terrain normal
///
///
///
///
public static Vector3 GetRotationFromTerrainNormal(Terrain terrain, GameObject playerObj)
{
if (terrain != null && playerObj != null)
{
float scalarX = (playerObj.transform.position.x - terrain.transform.position.x) / (float)terrain.terrainData.size.x;
float scalarZ = (playerObj.transform.position.z - terrain.transform.position.z) / (float)terrain.terrainData.size.z;
Vector3 interpolatedNormal = terrain.terrainData.GetInterpolatedNormal(scalarX, scalarZ);
Quaternion quaternion = Quaternion.FromToRotation(Vector3.up, interpolatedNormal) * playerObj.transform.rotation;
return quaternion.eulerAngles;
}
else
{
return Vector3.zero;
}
}
///
/// Get the terrain that matches this location, otherwise return null
///
/// Location to check in world units
/// Terrain here or null
public static Terrain GetTerrain(Vector3 locationWU, bool selectWorldMapTerrains = false)
{
Terrain terrain;
Vector3 terrainMin = new Vector3();
Vector3 terrainMax = new Vector3();
//First check active terrain - most likely already selected
terrain = Terrain.activeTerrain;
if (terrain != null && terrain.terrainData !=null &&(selectWorldMapTerrains == TerrainHelper.IsWorldMapTerrain(terrain)))
{
terrainMin = terrain.GetPosition();
terrainMax = terrainMin + terrain.terrainData.size;
if (locationWU.x >= terrainMin.x && locationWU.x <= terrainMax.x)
{
if (locationWU.z >= terrainMin.z && locationWU.z <= terrainMax.z)
{
return terrain;
}
}
}
//Then check rest of terrains
Terrain closestTerrain = null;
float closestDistance = float.MaxValue;
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain.terrainData==null || (selectWorldMapTerrains != TerrainHelper.IsWorldMapTerrain(terrain)))
{
continue;
}
terrainMin = terrain.GetPosition();
terrainMax = terrainMin + terrain.terrainData.size;
if (locationWU.x >= terrainMin.x && locationWU.x <= terrainMax.x)
{
if (locationWU.z >= terrainMin.z && locationWU.z <= terrainMax.z)
{
return terrain;
}
}
if (closestTerrain == null || Vector3.Distance(terrain.transform.position, locationWU) < closestDistance)
{
closestTerrain = terrain;
}
}
return closestTerrain;
}
///
/// Get the bounds of the space encapsulated by the supplied terrain
///
/// Terrain to get bounds for
/// Bounds to update
/// True if we got some terrain bounds
public static bool GetTerrainBounds(Terrain terrain, ref Bounds bounds)
{
if (terrain == null)
{
return false;
}
bounds.center = terrain.transform.position;
bounds.size = terrain.terrainData.size;
bounds.center += bounds.extents;
return true;
}
///
/// Get the bounds of the terrain at this location or fail with a null
///
/// Location to check and get terrain for
/// Bounds of selected terrain or null if invalid for some reason
public static bool GetTerrainBounds(ref BoundsDouble bounds, bool activeTerrainsOnly = false)
{
//Terrain terrain = GetTerrain(locationWU);
//if (terrain == null)
//{
// return false;
//}
//bounds.center = terrain.transform.position;
//bounds.size = terrain.terrainData.size;
//bounds.center += bounds.extents;
Vector3Double accumulatedCenter = new Vector3Double();
//Do we use dynamic loaded terrains in the scene?
if (GaiaUtils.HasDynamicLoadedTerrains() && !activeTerrainsOnly)
{
#if GAIA_PRO_PRESENT
//we do have dynamic terrains -> calculate the bounds according to the terrain scene data in the session
GaiaSessionManager gsm = GaiaSessionManager.GetSessionManager(false);
foreach (TerrainScene t in TerrainLoaderManager.TerrainScenes)
{
accumulatedCenter += t.m_bounds.center;
}
bounds.center = accumulatedCenter / TerrainLoaderManager.TerrainScenes.Count;
foreach (TerrainScene t in TerrainLoaderManager.TerrainScenes)
{
bounds.Encapsulate(t.m_bounds);
}
#endif
}
else
{
//no placeholder -> calculate bounds according to the active terrains in the scene
if (Terrain.activeTerrains.Length > 0)
{
foreach (Terrain t in Terrain.activeTerrains)
{
if (!TerrainHelper.IsWorldMapTerrain(t))
{
if (t.terrainData != null)
{
accumulatedCenter += new Vector3Double(t.transform.position) + new Vector3Double(t.terrainData.bounds.extents);
}
else
{
Debug.LogWarning("Terrain " + t.name + " in the scene is missing the terrain data object!");
}
}
}
bounds.center = accumulatedCenter / Terrain.activeTerrains.Length;
foreach (Terrain t in Terrain.activeTerrains)
{
if (!TerrainHelper.IsWorldMapTerrain(t))
{
if (t.terrainData != null)
{
Bounds newBounds = new Bounds();
newBounds.center = t.transform.position;
newBounds.size = t.terrainData.size;
newBounds.center += t.terrainData.bounds.extents;
bounds.Encapsulate(newBounds);
}
}
}
}
else
{
bounds = new BoundsDouble(Vector3Double.zero, Vector3Double.zero);
//No active terrains? There might be mesh terrains we can use then
GameObject meshTerrainExportObject = GaiaUtils.GetTerrainExportObject(false);
if (meshTerrainExportObject != null)
{
foreach (Transform t in meshTerrainExportObject.transform)
{
MeshRenderer mr = t.GetComponent();
if (mr != null)
{
bounds.Encapsulate(mr.bounds);
}
}
}
}
}
return true;
}
///
/// Get a random location on the terrain supplied
///
/// Terrain to check
/// Start locaton
/// Radius to hunt in
///
public static Vector3 GetRandomPositionOnTerrain(Terrain terrain, Vector3 start, float radius)
{
Vector3 newLocation;
Vector3 terrainMin = terrain.GetPosition();
Vector3 terrainMax = terrainMin + terrain.terrainData.size;
while (true)
{
//Get a new location
newLocation = UnityEngine.Random.insideUnitSphere * radius;
newLocation = start + newLocation;
//Make sure the new location is within the terrain bounds
if (newLocation.x >= terrainMin.x && newLocation.x <= terrainMax.x)
{
if (newLocation.z >= terrainMin.z && newLocation.z <= terrainMax.z)
{
//Update it to be on the terrain surface
newLocation.y = terrain.SampleHeight(newLocation);
return newLocation;
}
}
}
}
///
/// Returns the bounds of a terrain in world space (The bounds in the terrainData object is in local space of the terrain itself)
///
/// The terrain to get the bounds in world space for.
///
public static Bounds GetWorldSpaceBounds(Terrain 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);
return worldSpaceBounds;
}
///
/// Resizes the terrain splatmap to a new resolution.
///
///
///
public static void ResizeSplatmaps(Terrain terrain, int targetResolution)
{
TerrainData terrainData = terrain.terrainData;
Material blitMaterial = TerrainPaintUtility.GetCopyTerrainLayerMaterial(); // special blit that forces copy from highest mip only
RenderTexture[] resizedSplatMaps = new RenderTexture[terrainData.alphamapTextureCount];
int targetResolutionU = targetResolution;
int targetResolutionV = targetResolution;
float invTargetResolutionU = 1.0f / targetResolutionU;
float invTargetResolutiuonV = 1.0f / targetResolutionV;
RenderTexture currentRT = RenderTexture.active;
for (int i = 0; i < terrainData.alphamapTextureCount; i++)
{
Texture2D oldSplatmap = terrainData.alphamapTextures[i];
int sourceResolutionU = oldSplatmap.width;
int sourceResolutionV = oldSplatmap.height;
float invSourceResoultionU = 1.0f / sourceResolutionU;
float invSourceResolutionV = 1.0f / sourceResolutionV;
resizedSplatMaps[i] = RenderTexture.GetTemporary(targetResolution, targetResolution, 0, oldSplatmap.graphicsFormat);
float scaleU = (1.0f - invSourceResoultionU) / (1.0f - invTargetResolutionU);
float scaleV = (1.0f - invSourceResolutionV) / (1.0f - invTargetResolutiuonV);
float offsetU = 0.5f * (invSourceResoultionU - scaleU * invTargetResolutionU);
float offsetV = 0.5f * (invSourceResolutionV - scaleV * invTargetResolutiuonV);
Vector2 scale = new Vector2(scaleU, scaleV);
Vector2 offset = new Vector2(offsetU, offsetV);
blitMaterial.mainTexture = oldSplatmap;
blitMaterial.mainTextureScale = scale;
blitMaterial.mainTextureOffset = offset;
oldSplatmap.filterMode = FilterMode.Bilinear;
RenderTexture.active = resizedSplatMaps[i];
GL.PushMatrix();
GL.LoadPixelMatrix(0, targetResolution, 0, targetResolution);
blitMaterial.SetPass(2);
RectInt targetPixelRect = new RectInt(0, 0, targetResolution, targetResolution);
RectInt sourcePixelRect = new RectInt(0, 0, sourceResolutionU, sourceResolutionV);
if ((targetPixelRect.width > 0) && (targetPixelRect.height > 0))
{
Rect sourceUVs = new Rect(
(sourcePixelRect.x) / (float)oldSplatmap.width,
(sourcePixelRect.y) / (float)oldSplatmap.height,
(sourcePixelRect.width) / (float)oldSplatmap.width,
(sourcePixelRect.height) / (float)oldSplatmap.height);
Rect sourceUVs2 = new Rect(
(sourcePixelRect.x) / (float)oldSplatmap.width,
(sourcePixelRect.y) / (float)oldSplatmap.height,
(sourcePixelRect.width) / (float)oldSplatmap.width,
(sourcePixelRect.height) / (float)oldSplatmap.height);
GL.Begin(GL.QUADS);
GL.Color(new Color(1.0f, 1.0f, 1.0f, 1.0f));
GL.MultiTexCoord2(0, sourceUVs.x, sourceUVs.y);
GL.MultiTexCoord2(1, sourceUVs2.x, sourceUVs2.y);
GL.Vertex3(targetPixelRect.x, targetPixelRect.y, 0.0f);
GL.MultiTexCoord2(0, sourceUVs.x, sourceUVs.yMax);
GL.MultiTexCoord2(1, sourceUVs2.x, sourceUVs2.yMax);
GL.Vertex3(targetPixelRect.x, targetPixelRect.yMax, 0.0f);
GL.MultiTexCoord2(0, sourceUVs.xMax, sourceUVs.yMax);
GL.MultiTexCoord2(1, sourceUVs2.xMax, sourceUVs2.yMax);
GL.Vertex3(targetPixelRect.xMax, targetPixelRect.yMax, 0.0f);
GL.MultiTexCoord2(0, sourceUVs.xMax, sourceUVs.y);
GL.MultiTexCoord2(1, sourceUVs2.xMax, sourceUVs2.y);
GL.Vertex3(targetPixelRect.xMax, targetPixelRect.y, 0.0f);
GL.End();
}
GL.PopMatrix();
}
terrainData.alphamapResolution = targetResolution;
for (int i = 0; i < resizedSplatMaps.Length; i++)
{
RenderTexture.active = resizedSplatMaps[i];
terrainData.CopyActiveRenderTextureToTexture(TerrainData.AlphamapTextureName, i, new RectInt(0, 0, targetResolution, targetResolution), Vector2Int.zero, false);
}
terrainData.SetBaseMapDirty();
RenderTexture.active = currentRT;
for (int i = 0; i < resizedSplatMaps.Length; i++)
RenderTexture.ReleaseTemporary(resizedSplatMaps[i]);
}
///
/// Resizes the heightmap of a terrain to the target resolution
///
///
///
public static void ResizeHeightmap(Terrain terrain, int targetResolution)
{
RenderTexture currentRT = RenderTexture.active;
RenderTexture oldHeightmap = RenderTexture.GetTemporary(terrain.terrainData.heightmapTexture.descriptor);
Graphics.Blit(terrain.terrainData.heightmapTexture, oldHeightmap);
RenderTexture oldHoles = RenderTexture.GetTemporary(terrain.terrainData.holesTexture.width, terrain.terrainData.holesTexture.height, 0, Terrain.holesRenderTextureFormat);
Graphics.Blit(terrain.terrainData.holesTexture, oldHoles);
int dWidth = terrain.terrainData.heightmapResolution;
int sWidth = targetResolution;
Vector3 oldSize = terrain.terrainData.size;
terrain.terrainData.heightmapResolution = targetResolution;
terrain.terrainData.size = oldSize;
oldHeightmap.filterMode = FilterMode.Bilinear;
float k = (dWidth - 1.0f) / (sWidth - 1.0f) / dWidth;
float scaleX = (sWidth * k);
float offsetX = (float)(0.5 / dWidth - 0.5 * k);
Vector2 scale = new Vector2(scaleX, scaleX);
Vector2 offset = new Vector2(offsetX, offsetX);
Graphics.Blit(oldHeightmap, terrain.terrainData.heightmapTexture, scale, offset);
RenderTexture.ReleaseTemporary(oldHeightmap);
oldHoles.filterMode = FilterMode.Point;
RenderTexture newHoles = RenderTexture.GetTemporary(terrain.terrainData.holesTexture.width, terrain.terrainData.holesTexture.height, 0, Terrain.holesRenderTextureFormat);
Graphics.Blit(oldHoles, newHoles);
Graphics.CopyTexture(newHoles, terrain.terrainData.holesTexture);
RenderTexture.ReleaseTemporary(oldHoles);
RenderTexture.ReleaseTemporary(newHoles);
RenderTexture.active = currentRT;
terrain.terrainData.DirtyHeightmapRegion(new RectInt(0, 0, terrain.terrainData.heightmapTexture.width, terrain.terrainData.heightmapTexture.height), TerrainHeightmapSyncControl.HeightAndLod);
terrain.terrainData.DirtyTextureRegion(TerrainData.HolesTextureName, new RectInt(0, 0, terrain.terrainData.holesTexture.width, terrain.terrainData.holesTexture.height), false);
}
///
/// Averages the heightmaps between two unity terrains so that they align at the seam.
///
/// The first terrain.
/// The second terrain.
/// Extra seam in heightmap pixels where the heights are averaged out to better disguise the seam.
/// A maximum difference in height for which align pixels to - if no pixes are within the tolerance, no action will be performed at all. This can be used to ensure the terrains somewhat match up before stitching them
public static void StitchTerrainHeightmaps(Terrain terrain1, Terrain terrain2, int extraSeamSize = 1, float maxDifference = 1f)
{
StitchDirection stitchDirection = StitchDirection.North;
//What is larger, the difference on x or z axis?
if (Mathf.Abs(terrain1.transform.position.x - terrain2.transform.position.x) > Mathf.Abs(terrain1.transform.position.z - terrain2.transform.position.z))
{
if (terrain1.transform.position.x > terrain2.transform.position.x)
{
stitchDirection = StitchDirection.West;
}
else
{
stitchDirection = StitchDirection.East;
}
}
else
{
if (terrain1.transform.position.z > terrain2.transform.position.z)
{
stitchDirection = StitchDirection.South;
}
else
{
stitchDirection = StitchDirection.North;
}
}
int terrain1XBase = 0, terrain1YBase = 0;
int terrain2XBase = 0, terrain2YBase = 0;
int seamWidth = 0, seamHeight = 0;
int seamDiameter = extraSeamSize + 1;
float[,] terrain1Heights = new float[0, 0];
float[,] terrain2Heights = new float[0, 0];
switch (stitchDirection)
{
case StitchDirection.North:
terrain1XBase = Mathf.RoundToInt(Mathf.Max(0, terrain2.transform.position.x - terrain1.transform.position.x) / terrain1.terrainData.heightmapScale.x);
terrain1YBase = terrain1.terrainData.heightmapResolution - 1 - extraSeamSize;
seamWidth = terrain1.terrainData.heightmapResolution - Mathf.RoundToInt(Mathf.Abs(terrain1.transform.position.x - terrain2.transform.position.x) / terrain1.terrainData.heightmapScale.x);
seamHeight = seamDiameter;
terrain1Heights = terrain1.terrainData.GetHeights(terrain1XBase, terrain1YBase, seamWidth, seamHeight);
terrain2XBase = Mathf.RoundToInt(Mathf.Max(0, terrain1.transform.position.x - terrain2.transform.position.x) / terrain2.terrainData.heightmapScale.x);
terrain2YBase = 0;
terrain2Heights = terrain2.terrainData.GetHeights(terrain2XBase, terrain2YBase, seamWidth, seamHeight);
StitchBordersWithSeam(stitchDirection, seamWidth, extraSeamSize, ref terrain1Heights, ref terrain2Heights);
break;
case StitchDirection.South:
terrain1XBase = Mathf.RoundToInt(Mathf.Max(0, terrain1.transform.position.x - terrain2.transform.position.x) / terrain2.terrainData.heightmapScale.x);
terrain1YBase = 0;
seamWidth = terrain1.terrainData.heightmapResolution - Mathf.RoundToInt(Mathf.Abs(terrain1.transform.position.x - terrain2.transform.position.x) / terrain1.terrainData.heightmapScale.x);
seamHeight = seamDiameter;
terrain1Heights = terrain1.terrainData.GetHeights(terrain1XBase, terrain1YBase, seamWidth, seamHeight);
terrain2XBase = Mathf.RoundToInt(Mathf.Max(0, terrain2.transform.position.x - terrain1.transform.position.x) / terrain1.terrainData.heightmapScale.x);
terrain2YBase = terrain1.terrainData.heightmapResolution - 1 - extraSeamSize;
terrain2Heights = terrain2.terrainData.GetHeights(terrain2XBase, terrain2YBase, seamWidth, seamDiameter);
StitchBordersWithSeam(stitchDirection, seamWidth, extraSeamSize, ref terrain2Heights, ref terrain1Heights);
break;
case StitchDirection.West:
terrain1XBase = 0;
terrain1YBase = Mathf.RoundToInt(Mathf.Max(0, terrain2.transform.position.z - terrain1.transform.position.z) / terrain1.terrainData.heightmapScale.z);
seamWidth = seamDiameter;
seamHeight = terrain1.terrainData.heightmapResolution - Mathf.RoundToInt(Mathf.Abs(terrain1.transform.position.z - terrain2.transform.position.z) / terrain1.terrainData.heightmapScale.z);
terrain1Heights = terrain1.terrainData.GetHeights(terrain1XBase, terrain1YBase, seamWidth, seamHeight);
terrain2XBase = terrain2.terrainData.heightmapResolution - 1 - extraSeamSize;
terrain2YBase = Mathf.RoundToInt(Mathf.Max(0, terrain1.transform.position.z - terrain2.transform.position.z) / terrain2.terrainData.heightmapScale.z);
terrain2Heights = terrain2.terrainData.GetHeights(terrain2XBase, terrain2YBase, seamWidth, seamHeight);
StitchBordersWithSeam(stitchDirection, seamHeight, extraSeamSize, ref terrain2Heights, ref terrain1Heights);
break;
case StitchDirection.East:
terrain1XBase = terrain2.terrainData.heightmapResolution - 1 - extraSeamSize;
terrain1YBase = Mathf.RoundToInt(Mathf.Max(0, terrain1.transform.position.z - terrain2.transform.position.z) / terrain2.terrainData.heightmapScale.z);
seamWidth = seamDiameter;
seamHeight = terrain1.terrainData.heightmapResolution - Mathf.RoundToInt(Mathf.Abs(terrain1.transform.position.z - terrain2.transform.position.z) / terrain1.terrainData.heightmapScale.z);
terrain1Heights = terrain1.terrainData.GetHeights(terrain1XBase, terrain1YBase, seamWidth, seamHeight);
terrain2XBase = 0;
terrain2YBase = Mathf.RoundToInt(Mathf.Max(0, terrain2.transform.position.z - terrain1.transform.position.z) / terrain1.terrainData.heightmapScale.z);
terrain2Heights = terrain2.terrainData.GetHeights(terrain2XBase, terrain2YBase, seamWidth, seamHeight);
StitchBordersWithSeam(stitchDirection, seamHeight, extraSeamSize, ref terrain1Heights, ref terrain2Heights);
break;
}
terrain1.terrainData.SetHeights(terrain1XBase, terrain1YBase, terrain1Heights);
terrain2.terrainData.SetHeights(terrain2XBase, terrain2YBase, terrain2Heights);
//bool wasStitched = AverageHeightPixels(terrain1, terrain2, terrain1XBase, terrain1YBase, terrain2XBase, terrain2YBase, seamWidth, seamHeight, 1, 1, true, true, maxDifference);
//if (extraSeamSize > 0 && wasStitched)
//{
// for (int s = 1; s <= extraSeamSize; s++)
// {
// float terrain1Weight = Mathf.InverseLerp(0, extraSeamSize, s);
// float terrain2Weight = Mathf.InverseLerp(extraSeamSize, 0, s);
// switch (stitchDirection)
// {
// case StitchDirection.North:
// AverageHeightPixels(terrain1, terrain1, terrain1XBase, terrain1YBase - s, terrain1XBase, terrain1YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// AverageHeightPixels(terrain2, terrain2, terrain2XBase, terrain2YBase + s, terrain2XBase, terrain2YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// break;
// case StitchDirection.South:
// AverageHeightPixels(terrain1, terrain1, terrain1XBase, terrain1YBase + s, terrain1XBase, terrain1YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// AverageHeightPixels(terrain2, terrain2, terrain2XBase, terrain2YBase - s, terrain2XBase, terrain2YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// break;
// case StitchDirection.West:
// AverageHeightPixels(terrain1, terrain1, terrain1XBase + s, terrain1YBase, terrain1XBase, terrain1YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// AverageHeightPixels(terrain2, terrain2, terrain2XBase - s, terrain2YBase, terrain2XBase, terrain2YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// break;
// case StitchDirection.East:
// AverageHeightPixels(terrain1, terrain1, terrain1XBase - s, terrain1YBase, terrain1XBase, terrain1YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// AverageHeightPixels(terrain2, terrain2, terrain2XBase + s, terrain2YBase, terrain2XBase, terrain2YBase, seamWidth, seamHeight, terrain1Weight, terrain2Weight, true, false, 1);
// break;
// }
// }
//}
}
private static void StitchBordersWithSeam(StitchDirection stitchDirection, int seamLength, int extraSeamSize, ref float[,] terrain1Heights, ref float[,] terrain2Heights)
{
for (int dimension1 = 0; dimension1 < seamLength; dimension1++)
{
float terrain1EndHeight = 0f;
float terrain2EndHeight = 0f;
bool isHorizontalSeam = stitchDirection == StitchDirection.North || stitchDirection == StitchDirection.South;
if (isHorizontalSeam)
{
terrain1EndHeight = terrain1Heights[0, dimension1];
terrain2EndHeight = terrain2Heights[extraSeamSize, dimension1];
}
else
{
terrain1EndHeight = terrain1Heights[dimension1, 0];
terrain2EndHeight = terrain2Heights[dimension1, extraSeamSize];
}
//float[] oldPoints = terrain1Heights[dimension1,];
//if (terrain1EndHeight > 0 || terrain2EndHeight > 0)
//{
// string message = "BEFORE:";
// message = "\r\nTerrain 1 End: " + terrain1EndHeight.ToString();
// message += "\r\nTerrain 2 End: " + terrain2EndHeight.ToString();
// message += "\r\n";
// for (int dimension2 = 1; dimension2 < extraSeamSize * 2; dimension2++)
// {
// if (dimension2 <= extraSeamSize)
// {
// message += "\r\n" + dimension2.ToString() + ": " + terrain1Heights[dimension2, dimension1].ToString();
// }
// else
// {
// message += "\r\n" + dimension2.ToString() + ": " + terrain2Heights[dimension2 - extraSeamSize, dimension1].ToString();
// }
// }
// Debug.Log(message);
//}
for (int dimension2 = 1; dimension2 < extraSeamSize * 2; dimension2++)
{
float linearHeight = Mathf.Lerp(terrain1EndHeight, terrain2EndHeight, Mathf.InverseLerp(1, extraSeamSize * 2 - 1, dimension2));
if (dimension2 == extraSeamSize)
{
//Do not process the actual seam between the two terrains, this will be done after the rest of the points have been processed
}
else
{
if (dimension2 < extraSeamSize)
{
if (isHorizontalSeam)
{
terrain1Heights[dimension2, dimension1] = Mathf.Lerp(terrain1Heights[dimension2, dimension1], linearHeight, Mathf.InverseLerp(0, extraSeamSize, dimension2));
}
else
{
terrain1Heights[dimension1, dimension2] = Mathf.Lerp(terrain1Heights[dimension1, dimension2], linearHeight, Mathf.InverseLerp(0, extraSeamSize, dimension2));
}
}
else
{
if (isHorizontalSeam)
{
terrain2Heights[dimension2 - extraSeamSize, dimension1] = Mathf.Lerp(linearHeight, terrain2Heights[dimension2 - extraSeamSize, dimension1], Mathf.InverseLerp(0, extraSeamSize, dimension2 - extraSeamSize));
}
else
{
terrain2Heights[dimension1, dimension2-extraSeamSize] = Mathf.Lerp(linearHeight, terrain2Heights[dimension1, dimension2 - extraSeamSize], Mathf.InverseLerp(0, extraSeamSize, dimension2 - extraSeamSize));
}
}
}
}
//Now do the actual seam between the two points based on the changed data
//just do the average between the closest points at the actual seam - this gives the best results without any visible bends between two terrains
if (isHorizontalSeam)
{
terrain1Heights[extraSeamSize, dimension1] = (terrain1Heights[extraSeamSize - 1, dimension1] + terrain2Heights[extraSeamSize + 1 - extraSeamSize, dimension1]) / 2f;
terrain2Heights[0, dimension1] = terrain1Heights[extraSeamSize, dimension1];
}
else
{
terrain1Heights[dimension1, extraSeamSize] = (terrain1Heights[dimension1, extraSeamSize - 1] + terrain2Heights[dimension1, extraSeamSize + 1 - extraSeamSize]) / 2f;
terrain2Heights[dimension1, 0] = terrain1Heights[dimension1, extraSeamSize];
}
//if (terrain1EndHeight > 0 || terrain2EndHeight > 0)
//{
// string message = "AFTER:";
// message = "\r\nTerrain 1 End: " + terrain1EndHeight.ToString();
// message += "\r\nTerrain 2 End: " + terrain2EndHeight.ToString();
// message += "\r\n";
// for (int dimension2 = 1; dimension2 < extraSeamSize * 2; dimension2++)
// {
// if (dimension2 <= extraSeamSize)
// {
// message += "\r\n" + dimension2.ToString() + ": " + terrain1Heights[dimension2, dimension1].ToString();
// }
// else
// {
// message += "\r\n" + dimension2.ToString() + ": " + terrain2Heights[dimension2 - extraSeamSize, dimension1].ToString();
// }
// }
// Debug.Log(message);
// Debug.Log("################################################");
//}
}
}
///
/// Tries to find a neighbor terrain in the given direction, and will stitch it if found
///
/// The directon in which to look for a neighboring terrain.
/// The terrain where the stitching operation originates.
/// Amount of extra heightmap pixels to align near the border. More pixels result in smoother transition, but may also remove details on the individual terrains due to the averaging.
/// The maximum amount of (scalar) height difference the stitching process will attempt to stitch up. If =1, the stitching process will fix gaps of any size between two terrains.
/// True if stitching performed, false if not
public static bool TryStitch(Terrain terrain, StitchDirection direction, int extraSeamSize = 1, float maxDifference = 0.01f)
{
if (TerrainLoaderManager.Instance.m_autoTerrainStitching == false)
{
return false;
}
bool stitched = false;
Terrain neighbor = GetTerrainNeighbor(terrain, direction);
if (neighbor != null)
{
StitchTerrainHeightmaps(terrain, neighbor, extraSeamSize, maxDifference);
stitched = true;
}
else
{
stitched = false;
}
//store the stitiching result for loaded terrains
if (GaiaUtils.HasDynamicLoadedTerrains())
{
TerrainScene ts = TerrainLoaderManager.Instance.GetTerrainSceneAtPosition(terrain.transform.position + terrain.terrainData.size * 0.5f);
if (ts != null)
{
switch (direction)
{
case StitchDirection.North:
ts.m_stitchedNorthBorder = stitched;
break;
case StitchDirection.South:
ts.m_stitchedSouthBorder = stitched;
break;
case StitchDirection.West:
ts.m_stitchedWestBorder = stitched;
break;
case StitchDirection.East:
ts.m_stitchedEastBorder = stitched;
break;
}
//Log the stitch state for the opposite border for the neighbor, otherwise the neighbor would try to re-stitch the terrain when being reloaded
TerrainScene tsN = TerrainLoaderManager.Instance.TryGetNeighbor(ts, direction);
if (tsN != null)
{
switch (direction)
{
///OPPOSITE BORDERS!
case StitchDirection.North:
tsN.m_stitchedSouthBorder = stitched;
break;
case StitchDirection.South:
tsN.m_stitchedNorthBorder = stitched;
break;
case StitchDirection.West:
tsN.m_stitchedEastBorder = stitched;
break;
case StitchDirection.East:
tsN.m_stitchedWestBorder = stitched;
break;
}
}
}
if (!Application.isPlaying)
{
TerrainLoaderManager.Instance.DirtyStorageData();
}
}
return stitched;
}
public static Terrain GetTerrainNeighbor(Terrain terrain, StitchDirection direction)
{
Bounds boundsOriginTerrain = GetWorldSpaceBounds(terrain);
Terrain neighbor = null;
foreach (Terrain t in Terrain.activeTerrains)
{
Bounds boundsTerrain = GetWorldSpaceBounds(t);
if (boundsTerrain.Intersects(boundsOriginTerrain))
{
switch (direction)
{
case StitchDirection.North:
if (boundsOriginTerrain.max.z == boundsTerrain.min.z && boundsTerrain.max.x > boundsOriginTerrain.min.x && boundsTerrain.min.x < boundsOriginTerrain.max.x)
{
neighbor = t;
}
break;
case StitchDirection.South:
if (boundsOriginTerrain.min.z == boundsTerrain.max.z && boundsTerrain.max.x > boundsOriginTerrain.min.x && boundsTerrain.min.x < boundsOriginTerrain.max.x)
{
neighbor = t;
}
break;
case StitchDirection.East:
if (boundsOriginTerrain.max.x == boundsTerrain.min.x && boundsTerrain.max.z > boundsOriginTerrain.min.z && boundsTerrain.min.z < boundsOriginTerrain.max.z)
{
neighbor = t;
}
break;
case StitchDirection.West:
if (boundsOriginTerrain.min.x == boundsTerrain.max.x && boundsTerrain.max.z > boundsOriginTerrain.min.z && boundsTerrain.min.z < boundsOriginTerrain.max.z)
{
neighbor = t;
}
break;
}
if (neighbor != null)
{ break; }
}
}
return neighbor;
}
private static bool AverageHeightPixels(Terrain terrain1, Terrain terrain2, int terrain1XBase, int terrain1YBase, int terrain2XBase, int terrain2YBase, int seamWidth, int seamHeight, float terrain1Weight, float terrain2Weight, bool applyTo1, bool applyTo2, float maxDifference)
{
float[,] terrain1Heights = terrain1.terrainData.GetHeights(terrain1XBase, terrain1YBase, seamWidth, seamHeight);
float[,] terrain2Heights = terrain2.terrainData.GetHeights(terrain2XBase, terrain2YBase, seamWidth, seamHeight);
float[,] avgheights = new float[seamHeight, seamWidth];
bool withinMaxDistance = false;
for (int x = 0; x < avgheights.GetLength(0); x++)
{
for (int y = 0; y < avgheights.GetLength(1); y++)
{
if (Mathf.Abs(terrain1Heights[x, y] - terrain2Heights[x, y]) < maxDifference)
{
withinMaxDistance = true;
}
avgheights[x, y] = (terrain1Heights[x, y] * terrain1Weight + terrain2Heights[x, y] * terrain2Weight) / (terrain1Weight + terrain2Weight);
//avgheights[x, y] = Mathf.Lerp(terrain1Heights[x, y], terrain2Heights[x, y], terrain2Weight);
//avgheights[x,y] = Mathf.Min(terrain1Heights[x,y], terrain2Heights[x,y]);
}
}
if (withinMaxDistance)
{
if (applyTo1)
{
terrain1.terrainData.SetHeights(terrain1XBase, terrain1YBase, avgheights);
}
if (applyTo2)
{
terrain2.terrainData.SetHeights(terrain2XBase, terrain2YBase, avgheights);
}
}
return withinMaxDistance;
}
///
/// Resizes the terrain details to a new resolution setting.
///
///
///
///
public static void ResizeTerrainDetails(Terrain terrain, int targetDetailRes, int resolutionPerPatch)
{
TerrainData terrainData = terrain.terrainData;
if (targetDetailRes == terrainData.detailResolution)
{
var layers = new List();
for (int i = 0; i < terrainData.detailPrototypes.Length; i++)
layers.Add(terrainData.GetDetailLayer(0, 0, terrainData.detailWidth, terrainData.detailHeight, i));
terrainData.SetDetailResolution(targetDetailRes, resolutionPerPatch);
for (int i = 0; i < layers.Count; i++)
terrainData.SetDetailLayer(0, 0, i, layers[i]);
}
else
{
terrainData.SetDetailResolution(targetDetailRes, resolutionPerPatch);
}
}
///
/// Clear all the trees on all the terrains
///
public static void ClearSpawns(SpawnerResourceType resourceType, ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List terrainNames = null, Spawner spawner = null)
{
if (terrainNames == null)
{
if (clearSpawnFor == ClearSpawnFor.AllTerrains)
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaSessionManager sessionManager = GaiaSessionManager.GetSessionManager();
terrainNames = TerrainLoaderManager.TerrainScenes.Select(x => x.GetTerrainName()).ToList();
}
else
{
terrainNames = Terrain.activeTerrains.Select(x => x.name).ToList();
}
}
else
{
terrainNames = new List { spawner.GetCurrentTerrain().name };
}
}
string progressBarTitle = "Clearing...";
Action act = null;
switch (resourceType)
{
case SpawnerResourceType.TerrainTexture:
progressBarTitle = "Clearing Textures";
//Not supported, should not be required
throw new NotSupportedException("Clearing of Textures is currently not supported via the terrain helper");
case SpawnerResourceType.TerrainDetail:
progressBarTitle = "Clearing Terrain Details";
act = (t) => ClearDetailsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.TerrainTree:
progressBarTitle = "Clearing Trees";
act = (t) => ClearTreesOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.GameObject:
progressBarTitle = "Clearing Game Objects";
act = (t) => ClearGameObjectsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.Probe:
progressBarTitle = "Clearing Probes";
act = (t) => ClearGameObjectsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.SpawnExtension:
progressBarTitle = "Clearing Spawn Extensions";
act = (t) => ClearSpawnExtensionsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.StampDistribution:
//Not supported, should not be required
throw new NotSupportedException("Clearing of Stamps is currently not supported via the terrain helper");
}
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaUtils.CallFunctionOnDynamicLoadedTerrains(act, true, terrainNames);
}
else
{
for (int idx = 0; idx < terrainNames.Count; idx++)
{
ProgressBar.Show(ProgressBarPriority.Spawning ,progressBarTitle, progressBarTitle, idx + 1, terrainNames.Count(), true);
GameObject go = GameObject.Find(terrainNames[idx]);
if (go != null)
{
Terrain terrain = go.GetComponent();
act(terrain);
}
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
}
}
private static void ClearTreesOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
if (spawner == null || clearSpawnFrom == ClearSpawnFrom.AnySource)
{
//No tree prototypes passed in => we delete any tree from any source
terrain.terrainData.treeInstances = new TreeInstance[0];
}
else
{
//We need to get the correct prototype Ids for this terrain only
//Prototype Ids might be different from terrain to terrain, depending on when / how the prototype was added
List treePrototypeIds = new List();
foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTree)
{
int treePrototypeIndex = spawner.m_settings.m_resources.PrototypeIdxInTerrain(sr.m_resourceType, sr.m_resourceIdx, terrain);
if (treePrototypeIndex != -1)
{
treePrototypeIds.Add(treePrototypeIndex);
}
}
}
//Reapply the tree instances on this terrain, but leave all "to be deleted" ids out via the where clause
terrain.terrainData.SetTreeInstances(terrain.terrainData.treeInstances.Where(x => !treePrototypeIds.Contains(x.prototypeIndex)).ToArray(), true);
}
terrain.Flush();
}
///
/// Clear all the details (grass) on all the terrains
///
private static void ClearDetailsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
int[,] details = new int[terrain.terrainData.detailWidth, terrain.terrainData.detailHeight];
if (spawner == null || clearSpawnFrom == ClearSpawnFrom.AnySource)
{
for (int dtlIdx = 0; dtlIdx < terrain.terrainData.detailPrototypes.Length; dtlIdx++)
{
terrain.terrainData.SetDetailLayer(0, 0, dtlIdx, details);
}
}
else
{
//We need to get the correct prototype Ids for this terrain only
//Prototype Ids might be different from terrain to terrain, depending on when / how the prototype was added
List detailPrototypeIds = new List();
foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainDetail)
{
int detailPrototypeIndex = spawner.m_settings.m_resources.PrototypeIdxInTerrain(sr.m_resourceType, sr.m_resourceIdx, terrain);
if (detailPrototypeIndex != -1)
{
detailPrototypeIds.Add(detailPrototypeIndex);
}
}
}
for (int dtlIdx = 0; dtlIdx < detailPrototypeIds.Count; dtlIdx++)
{
terrain.terrainData.SetDetailLayer(0, 0, detailPrototypeIds[dtlIdx], details);
}
}
terrain.Flush();
}
private static void ClearGameObjectsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
Spawner[] allAffectedSpawners;
if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
{
allAffectedSpawners = new Spawner[1] { spawner };
}
else
{
allAffectedSpawners = Resources.FindObjectsOfTypeAll();
}
foreach (Spawner sp in allAffectedSpawners)
{
foreach (SpawnRule sr in sp.m_settings.m_spawnerRules.Where(x=>x.m_resourceType == SpawnerResourceType.GameObject || x.m_resourceType == SpawnerResourceType.SpawnExtension || x.m_resourceType == SpawnerResourceType.Probe))
Spawner.ClearGameObjectsForRule(sp, sr, false, terrain);
}
}
private static void ClearSpawnExtensionsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
Spawner[] allAffectedSpawners;
if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
{
allAffectedSpawners = new Spawner[1] { spawner };
}
else
{
allAffectedSpawners = Resources.FindObjectsOfTypeAll();
}
foreach (Spawner sp in allAffectedSpawners)
{
foreach (SpawnRule sr in sp.m_settings.m_spawnerRules)
{
sp.ClearSpawnExtensionsForRule(sr, sp.m_settings);
Spawner.ClearGameObjectsForRule(sp, sr, false, terrain);
}
}
}
///
/// Get the range from the terrain
///
///
public static float GetRangeFromTerrain()
{
Terrain t = Gaia.TerrainHelper.GetActiveTerrain();
if (t != null)
{
return Mathf.Max(t.terrainData.size.x, t.terrainData.size.z) / 2f;
}
return 0f;
}
///
/// Returns true if this terrain is a world map terrain.
///
///
///
public static bool IsWorldMapTerrain(Terrain t)
{
return t.name.StartsWith(GaiaConstants.worldMapTerrainPrefix);
}
public static bool IsWorldMapTerrain(TerrainData td)
{
return td.name.StartsWith(GaiaConstants.worldMapTerrainPrefix);
}
///
/// Returns terrain names of terrains that intersect with the given bounds object
///
/// A bounds object to check against the terrains. Needs to be in absolute world space position, mind the current origin offset!
///
public static string[] GetTerrainsIntersectingBounds(BoundsDouble bounds)
{
//Reduce the bounds size a bit to prevent selecting terrains that are perfectly aligned with the bounds border
//-this leads to too many terrains being logged as affected by an operation otherwise.
Bounds intersectingBounds = new BoundsDouble();
intersectingBounds.center = bounds.center;
intersectingBounds.size = bounds.size - new Vector3Double(0.001f, 0.001f, 0.001f);
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaSessionManager sessionManager = GaiaSessionManager.GetSessionManager();
if (sessionManager == null)
{
Debug.LogError("Trying to get terrains that intersect with bounds, but there is no session manager in scene.");
return null;
}
return TerrainLoaderManager.TerrainScenes.Where(x => x.m_bounds.Intersects(intersectingBounds)).Select(x => x.GetTerrainName()).ToArray();
}
else
{
List affectedTerrainNames = new List();
foreach (Terrain t in Terrain.activeTerrains)
{
if (intersectingBounds.Intersects(TerrainHelper.GetWorldSpaceBounds(t)))
{
affectedTerrainNames.Add(t.name);
}
}
return affectedTerrainNames.ToArray();
}
}
public static TerrainLayer GetLayerFromPrototype(ResourceProtoTexture proto)
{
foreach (Terrain t in Terrain.activeTerrains)
{
foreach (TerrainLayer layer in t.terrainData.terrainLayers)
{
if (proto != null && layer != null)
{
if (proto.m_texture != null && layer.diffuseTexture != null)
{
if (PWCommon5.Utils.IsSameTexture(proto.m_texture, layer.diffuseTexture, false) == true)
{
return layer;
}
}
}
}
}
return null;
}
public static int GetTreePrototypeIDFromSpawnRule(SpawnRule sr, Terrain terrain)
{
Spawner spawner = CollisionMask.m_allTreeSpawners.FirstOrDefault(x => x.m_settings.m_spawnerRules.Contains(sr));
if (spawner != null)
{
GameObject treePrefabInRule = spawner.m_settings.m_resources.m_treePrototypes[sr.m_resourceIdx].m_desktopPrefab;
for (int i = 0; i < terrain.terrainData.treePrototypes.Length; i++)
{
TreePrototype tp = terrain.terrainData.treePrototypes[i];
if (tp.prefab == treePrefabInRule)
{
return i;
}
}
}
return -1;
}
public static Vector3 GetWorldCenter(bool sampleHeight = false)
{
BoundsDouble bounds = new BoundsDouble();
GetTerrainBounds(ref bounds);
if (sampleHeight)
{
Terrain t = GetTerrain(bounds.center);
if (t != null)
{
Vector3 centerOnTerrain = t.transform.position + new Vector3(t.terrainData.size.x / 2f, 0f, t.terrainData.size.z / 2f);
float height = t.SampleHeight(centerOnTerrain);
return new Vector3(centerOnTerrain.x, height, centerOnTerrain.z);
}
else
{
//No terrain? The user might be using mesh terrains then. Send out a raycast at the center to determine height
RaycastHit raycastHit = new RaycastHit();
if (Physics.Raycast(new Vector3Double(bounds.center.x, 1000000f, bounds.center.z), Vector3.down, out raycastHit))
{
return raycastHit.point;
}
else
{
return bounds.center;
}
}
}
else
{
return new Vector3((float)bounds.center.x, (float)bounds.center.y, (float)bounds.center.z);
}
}
}
}