BITFALL/Assets/Plugins/FImpossible Creations/Plugins - Level Design/Generators Shared/Helpers/FTerrainHelpers.cs

667 lines
23 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace FIMSpace.Generating
{
public static class FTerrainHelpers
{
#region Terrain Temp Datas
#region Splat paint temp datas
#region Main Struct
struct FTerrainSplatsTempData
{
public Terrain terrain;
public TerrainData terrainData;
public int alphamapWidth;
public int alphamapHeight;
public float[,,] splatmapData;
public int numTextures;
public int tScale;
public bool dirty;
public FTerrainSplatsTempData(Terrain terrain)
{
this.terrain = terrain;
terrainData = terrain.terrainData;
alphamapWidth = terrainData.alphamapWidth;
alphamapHeight = terrainData.alphamapHeight;
splatmapData = terrainData.GetAlphamaps(0, 0, alphamapWidth, alphamapHeight);
numTextures = splatmapData.Length / (alphamapWidth * alphamapHeight);
tScale = terrain.terrainData.heightmapResolution;
dirty = false;
}
public void ApplySplatmaps()
{
if (dirty == false) return;
terrain.terrainData.SetAlphamaps(0, 0, splatmapData);
dirty = false;
}
#region Utils
public void SplatPaintAt(int tZ, int tX, int splat, float power)
{
if (tX < 0 || tX >= splatmapData.GetLength(0)) { return; }
if (tZ < 0 || tZ >= splatmapData.GetLength(1)) { return; }
splatmapData[tZ, tX, splat] = power;
}
public void SplatPaintAtAdditive(int tZ, int tX, int splat, float power)
{
if (tX < 0 || tX >= splatmapData.GetLength(0)) { return; }
if (tZ < 0 || tZ >= splatmapData.GetLength(1)) { return; }
splatmapData[tZ, tX, splat] += power;
}
public void EraseSplatsAt(int tZ, int tX, int dominantSplat)
{
if (tX < 0 || tX >= splatmapData.GetLength(0)) { return; }
if (tZ < 0 || tZ >= splatmapData.GetLength(1)) { return; }
for (int i = 0; i < numTextures; i++)
{
if (i == dominantSplat) continue;
splatmapData[tZ, tX, i] = 0f;
}
}
public void SubtractSplatsAt(int tZ, int tX, int dominantSplat, float toSubtract)
{
if (tX < 0 || tX >= splatmapData.GetLength(0)) { return; }
if (tZ < 0 || tZ >= splatmapData.GetLength(1)) { return; }
for (int i = 0; i < numTextures; i++)
{
if (i == dominantSplat) continue;
splatmapData[tZ, tX, i] = Mathf.Max(0f, splatmapData[tZ, tX, i] - toSubtract);
}
}
public void NormalizeSplatsAt(int tZ, int tX, int dominantSplat, float sutract)
{
SubtractSplatsAt(tZ, tX, dominantSplat, sutract);
NormalizeSplatsAt(tZ, tX);
}
public void NormalizeSplatsAt(int tZ, int tX)
{
float alpha = 0f;
for (int i = 0; i < numTextures; i++) alpha += splatmapData[tZ, tX, i];
// Normalize splatmaps power
if (alpha > 0f) for (int i = 0; i < numTextures; i++) splatmapData[tZ, tX, i] /= alpha;
}
#endregion
}
#endregion
static Dictionary<Terrain, FTerrainSplatsTempData> TerrainSplatTempDatas = new Dictionary<Terrain, FTerrainSplatsTempData>();
public static void ResetSplatTempDatas() { TerrainSplatTempDatas.Clear(); }
public static void RefreshSplatTempDataFor(Terrain terr)
{
if (TerrainSplatTempDatas.ContainsKey(terr))
{
TerrainSplatTempDatas[terr] = new FTerrainSplatsTempData(terr);
return;
}
else
{
TerrainSplatTempDatas.Add(terr, new FTerrainSplatsTempData(terr));
}
}
static FTerrainSplatsTempData GetSplatTempDataFor(Terrain terr)
{
if (TerrainSplatTempDatas.ContainsKey(terr))
{
return TerrainSplatTempDatas[terr];
}
else
{
TerrainSplatTempDatas.Add(terr, new FTerrainSplatsTempData(terr));
return TerrainSplatTempDatas[terr];
}
}
#endregion
#region Height paint temp datas
#region Main Struct
struct FTerrainHeightTempData
{
public Terrain terrain;
public float[,] heights;
public int tScale;
public bool dirty;
public FTerrainHeightTempData(Terrain terr)
{
this.terrain = terr;
tScale = terr.terrainData.heightmapResolution;
heights = terr.terrainData.GetHeights(0, 0, tScale, tScale);
dirty = false;
}
public void ApplyHeights()
{
if (dirty == false) return;
terrain.terrainData.SetHeights(0, 0, heights);
dirty = false;
}
#region Utils
static AnimationCurve _DefaultHeightFalloff = AnimationCurve.EaseInOut(0.05f, 1f, 1f, 0f);
public void SetHeightAt(Vector3 wPos, float targetHeight, float power, float radius, AnimationCurve falloff = null, float extraRadiusOnBigDiff = 0f)
{
if (falloff == null) falloff = _DefaultHeightFalloff;
wPos.y = targetHeight;
Vector3 terrLocalPos = wPos - terrain.transform.position;
Vector3 terrSplatPos;
terrSplatPos.x = terrLocalPos.x / terrain.terrainData.size.x;
terrSplatPos.y = terrLocalPos.y / terrain.terrainData.size.y;
terrSplatPos.z = terrLocalPos.z / terrain.terrainData.size.z;
Vector2Int splatPosition = new Vector2Int();
splatPosition.x = (int)(terrSplatPos.x * tScale);
splatPosition.y = (int)(terrSplatPos.z * tScale);
targetHeight = terrSplatPos.y;
int radiusInSamples = FTerrainHelpers.UnitsToSamples(radius, terrain, terrain.terrainData.heightmapResolution);
// If difference between target height and current height of the terrain is big, we can apply further smoothing
if (extraRadiusOnBigDiff > 0f)
{
float diff = Mathf.Abs(heights[splatPosition.x, splatPosition.y] - targetHeight);
if (diff * terrain.terrainData.size.y > radius)
{
float factor = 0.3f + Mathf.InverseLerp(0f, extraRadiusOnBigDiff * 8f, diff * terrain.terrainData.size.y) * 1.7f;
radiusInSamples += FTerrainHelpers.UnitsToSamples(extraRadiusOnBigDiff * factor, terrain, terrain.terrainData.heightmapResolution);
}
}
for (int x = -radiusInSamples; x <= radiusInSamples; x++)
for (int z = -radiusInSamples; z <= radiusInSamples; z++)
{
int tZ = splatPosition.x + x;
int tX = splatPosition.y + z;
if (tX < 0 || tZ < 0 || tX >= heights.GetLength(0) || tZ >= heights.GetLength(1)) continue;
float fallf = falloff.Evaluate(Vector2.Distance(Vector2.zero, new Vector2(x, z)) / (float)radiusInSamples);
heights[tX, tZ] = Mathf.Lerp(heights[tX, tZ], targetHeight, fallf * power);
}
dirty = true;
}
#endregion
}
#endregion
static Dictionary<Terrain, FTerrainHeightTempData> TerrainHeightTempDatas = new Dictionary<Terrain, FTerrainHeightTempData>();
public static void ResetHeightTempDatas() { TerrainSplatTempDatas.Clear(); }
public static void RefreshHeightTempDataFor(Terrain terr)
{
if (TerrainHeightTempDatas.ContainsKey(terr))
{
TerrainHeightTempDatas[terr] = new FTerrainHeightTempData(terr);
return;
}
else
{
TerrainHeightTempDatas.Add(terr, new FTerrainHeightTempData(terr));
}
}
static FTerrainHeightTempData GetHeightTempDataFor(Terrain terr)
{
if (TerrainHeightTempDatas.ContainsKey(terr))
{
return TerrainHeightTempDatas[terr];
}
else
{
TerrainHeightTempDatas.Add(terr, new FTerrainHeightTempData(terr));
return TerrainHeightTempDatas[terr];
}
}
#endregion
#region Trees temp datas
#endregion
#region Details temp datas
#region Main Struct
static string Hash_Details(Terrain terr, int layerID)
{
if (terr == null) return "";
return terr.GetHashCode().ToString() + layerID.GetHashCode().ToString();
}
struct FTerrainDetailsTempData
{
public Terrain terrain;
public int detailLayer;
public int[,] details;
public int tScale;
public bool dirty;
public FTerrainDetailsTempData(Terrain terr, int detailLayer)
{
this.terrain = terr;
this.detailLayer = detailLayer;
tScale = terr.terrainData.detailResolution;
details = terr.terrainData.GetDetailLayer(0, 0, tScale, tScale, detailLayer);
dirty = false;
}
public void ApplyDetails()
{
if (dirty == false) return;
terrain.terrainData.SetDetailLayer(0, 0, detailLayer, details);
dirty = false;
}
#region Utils
public void SetDetailAt(Vector3 wPos, int newValue, float radius, bool square = false)
{
Vector3 terrLocalPos = WorldPosToTerrainNormalizedPos(wPos, terrain);
int posXInTerrain = (int)(terrLocalPos.x * tScale);
int posYInTerrain = (int)(terrLocalPos.z * tScale);
int radiusInSamples = Mathf.CeilToInt((radius * tScale) / terrain.terrainData.size.x);
for (int x = -radiusInSamples; x <= radiusInSamples; x++)
for (int z = -radiusInSamples; z <= radiusInSamples; z++)
{
int tZ = posXInTerrain + x;
int tX = posYInTerrain + z;
if (tX < 0 || tZ < 0 || tX >= details.GetLength(0) || tZ >= details.GetLength(1)) continue;
if (!square) if (Vector2.Distance(Vector2.zero, new Vector2(x, z)) > radiusInSamples) continue;
details[tX, tZ] = newValue;
}
dirty = true;
}
#endregion
}
#endregion
static Dictionary<string, FTerrainDetailsTempData> TerrainDetailsTempDatas = new Dictionary<string, FTerrainDetailsTempData>();
public static void ResetDetailsTempDatas() { TerrainSplatTempDatas.Clear(); }
public static void RefresDetailsTempDataFor(Terrain terr, int layer)
{
string key = Hash_Details(terr, layer);
if (TerrainDetailsTempDatas.ContainsKey(key))
{
TerrainDetailsTempDatas[key] = new FTerrainDetailsTempData(terr, layer);
return;
}
else
{
TerrainDetailsTempDatas.Add(key, new FTerrainDetailsTempData(terr, layer));
}
}
static FTerrainDetailsTempData GetDetailsTempDataFor(string key, Terrain terr, int detailLayer)
{
if (TerrainDetailsTempDatas.ContainsKey(key))
{
return TerrainDetailsTempDatas[key];
}
else
{
TerrainDetailsTempDatas.Add(key, new FTerrainDetailsTempData(terr, detailLayer));
return TerrainDetailsTempDatas[key];
}
}
static FTerrainDetailsTempData GetDetailsTempDataFor(Terrain terr, int detailLayer)
{
string key = Hash_Details(terr, detailLayer);
return GetDetailsTempDataFor(key, terr, detailLayer);
}
#endregion
public static void ApplyAllTempDatas()
{
foreach (var data in TerrainSplatTempDatas) data.Value.ApplySplatmaps();
foreach (var data in TerrainHeightTempDatas) data.Value.ApplyHeights();
foreach (var data in TerrainDetailsTempDatas) data.Value.ApplyDetails();
}
public static void ResetAllTempDatas()
{
TerrainSplatTempDatas.Clear();
TerrainHeightTempDatas.Clear();
TerrainDetailsTempDatas.Clear();
}
#endregion
public static int UnitsToSamples(float units, Terrain terr, float terrDataResolution)
{
return Mathf.CeilToInt((units * terrDataResolution) / terr.terrainData.size.x);
}
public static void PaintAt(Vector3 wPos, Terrain terr, int splatID, float unitsRadius, bool update = false)
{
var tempData = GetSplatTempDataFor(terr);
Vector3 splatCoord = WorldPosToSplatMapCoordinate(wPos, terr, terr.terrainData.alphamapResolution);
int tX = (int)splatCoord.x;
int tZ = (int)splatCoord.z;
tempData.SplatPaintAt(tZ, tX, splatID, 1f);
tempData.EraseSplatsAt(tZ, tX, splatID);
int radiusSamples = UnitsToSamples(unitsRadius, terr, terr.terrainData.alphamapResolution);
for (int r = 1; r <= radiusSamples; r++)
{
float powr = Mathf.Lerp(1f, 0.2f, (float)r / (float)radiusSamples);
tempData.SplatPaintAtAdditive(tZ + r, tX, splatID, powr);
tempData.SplatPaintAtAdditive(tZ, tX + r, splatID, powr);
tempData.SplatPaintAtAdditive(tZ - r, tX, splatID, powr);
tempData.SplatPaintAtAdditive(tZ, tX - r, splatID, powr);
tempData.SplatPaintAtAdditive(tZ + r, tX + r, splatID, powr);
tempData.SplatPaintAtAdditive(tZ - r, tX - r, splatID, powr);
tempData.SplatPaintAtAdditive(tZ - r, tX + r, splatID, powr);
tempData.SplatPaintAtAdditive(tZ + r, tX - r, splatID, powr);
}
for (int r = 1; r <= radiusSamples; r++)
{
tempData.NormalizeSplatsAt(tZ + r, tX);
tempData.NormalizeSplatsAt(tZ, tX + r);
tempData.NormalizeSplatsAt(tZ - r, tX);
tempData.NormalizeSplatsAt(tZ, tX - r);
tempData.NormalizeSplatsAt(tZ + r, tX + r);
tempData.NormalizeSplatsAt(tZ - r, tX - r);
tempData.NormalizeSplatsAt(tZ - r, tX + r);
tempData.NormalizeSplatsAt(tZ + r, tX - r);
}
tempData.dirty = true;
TerrainSplatTempDatas[terr] = tempData;
}
public static Terrain GetTerrainIn(Vector3 wPos)
{
if (Terrain.activeTerrain == null) return null;
if (Terrain.activeTerrains == null) return null;
for (int t = 0; t < Terrain.activeTerrains.Length; t++)
{
var terr = Terrain.activeTerrains[t];
if (terr)
{
Vector3 locPos = terr.transform.InverseTransformPoint(wPos);
if (locPos.x < 0) continue;
if (locPos.x > terr.terrainData.bounds.max.x) continue;
if (locPos.z < 0) continue;
if (locPos.z > terr.terrainData.bounds.max.z) continue;
return terr;
}
}
return null;
}
public static Vector3 WorldPosToTerrainNormalizedPos(Vector3 wPos, Terrain terr)
{
if (terr)
{
Vector3 onTerrain = ((wPos) - terr.gameObject.transform.position);
Vector3 terrLocalPos;
terrLocalPos.x = onTerrain.x / terr.terrainData.size.x;
terrLocalPos.y = onTerrain.y / terr.terrainData.size.y;
terrLocalPos.z = onTerrain.z / terr.terrainData.size.z;
wPos = terrLocalPos;
}
return wPos;
}
public static Vector3 WorldPosToSplatMapCoordinate(Vector3 worldPosition, Terrain terr, float resolution)
{
Vector3 splatPosition = new Vector3();
Vector3 terrLocalPos = worldPosition - terr.transform.position;
Vector3 terrSplatPos;
terrSplatPos.x = terrLocalPos.x / terr.terrainData.size.x;
terrSplatPos.z = terrLocalPos.z / terr.terrainData.size.z;
splatPosition.x = (int)(terrSplatPos.x * resolution);
splatPosition.z = (int)(terrSplatPos.z * resolution);
return splatPosition;
}
public static int GetTerrainLayerIdAtPosition(Vector3 position, Terrain terr)
{
if (terr == null) return -1;
//Vector3 terrainCord = WorldPosToTerrainNormalizedPos(position, terr);
int splatID = 0;
float largestOpacity = 0f;
var terrData = GetSplatTempDataFor(terr);
Vector3 terrainCord = WorldPosToSplatMapCoordinate(position, terr, terr.terrainData.alphamapResolution);
int tX = (int)terrainCord.x;
int tZ = (int)terrainCord.z;
if (tX < 0 || tX >= terrData.splatmapData.GetLength(0)) { /*UnityEngine.Debug.Log("toofarX " + terr.name);*/ return splatID; }
if (tZ < 0 || tZ >= terrData.splatmapData.GetLength(1)) { /*UnityEngine.Debug.Log("toofarZ " + terr.name);*/ return splatID; }
for (int i = 0; i < terrData.numTextures; i++)
{
if (terrData.splatmapData[tZ, tX, i] > largestOpacity)
{
splatID = i;
largestOpacity = terrData.splatmapData[tZ, tX, i];
}
}
return splatID;
}
public static bool[,] DetectTerrainAndCutHole(GameObject o, Terrain terr, float size, bool square)
{
bool[,] holes = null;
if (terr)
{
Vector3 terrLocalPos = WorldPosToTerrainNormalizedPos(o.transform.position, terr);
int tScale = terr.terrainData.holesResolution;
int posXInTerrain = (int)(terrLocalPos.x * tScale);
int posYInTerrain = (int)(terrLocalPos.z * tScale);
holes = terr.terrainData.GetHoles(0, 0, tScale, tScale);
bool[,] newHoles = terr.terrainData.GetHoles(0, 0, tScale, tScale);
int radiusInSamples = Mathf.CeilToInt((size * tScale) / terr.terrainData.size.x);
for (int x = -radiusInSamples; x <= radiusInSamples; x++)
for (int z = -radiusInSamples; z <= radiusInSamples; z++)
{
int tZ = posXInTerrain + x;
int tX = posYInTerrain + z;
if (tX < 0 || tZ < 0 || tX >= newHoles.GetLength(0) || tZ >= newHoles.GetLength(1)) continue;
if (!square) if (Vector2.Distance(Vector2.zero, new Vector2(x, z)) > radiusInSamples) continue;
newHoles[tX, tZ] = false;
}
terr.terrainData.SetHoles(0, 0, newHoles);
}
return holes;
}
public static void DetectTerrainAndRemoveTrees(GameObject o, float size, bool square)
{
if (o == null) return;
var terr = GetTerrainIn(o.transform.position);
if (terr)
{
Vector3 terrLocalPos = WorldPosToTerrainNormalizedPos(o.transform.position, terr);
Vector2 normPos = new Vector2(terrLocalPos.x, terrLocalPos.z);
float radiusInNormalizedcale = size / terr.terrainData.size.x;
List<TreeInstance> treeInstances = terr.terrainData.treeInstances.ToList();
if (!square)
{
for (int i = treeInstances.Count - 1; i >= 0; i--)
{
var tree = terr.terrainData.GetTreeInstance(i);
if (Vector2.Distance(new Vector2(tree.position.x, tree.position.z), normPos) <= radiusInNormalizedcale)
treeInstances.RemoveAt(i);
}
}
else
{
for (int i = treeInstances.Count - 1; i >= 0; i--)
{
var tree = terr.terrainData.GetTreeInstance(i);
if (DistanceManhattan2D(tree.position, terrLocalPos) <= radiusInNormalizedcale)
treeInstances.RemoveAt(i);
}
}
if (treeInstances.Count != terr.terrainData.treeInstances.Length)
{
terr.terrainData.treeInstances = treeInstances.ToArray();
}
}
}
public static Vector3 GetTerrainNormalInWorldPos(Terrain terr, Vector3 wPos)
{
Vector3 normPos = FTerrainHelpers.WorldPosToTerrainNormalizedPos(wPos, terr);
return terr.terrainData.GetInterpolatedNormal(normPos.x, normPos.z);
}
public static Vector3 GetTerrainNormalInWorldPos(Vector3 wPos)
{
Terrain terr = GetTerrainIn(wPos);
if (terr == null) return Vector3.up;
return GetTerrainNormalInWorldPos(terr, wPos);
}
public static float DistanceManhattan2D(Vector3 a, Vector3 b)
{
float diff = 0f;
diff += Mathf.Abs(a.x - b.x);
diff += Mathf.Abs(a.z - b.z);
return diff;
}
public static void AdjustTerrainHeightAt(Vector3 wPos, float power, float radius, float extraRadiusOnBigDiff = 0f, bool update = false)
{
var terrain = GetTerrainIn(wPos);
if (terrain == null) return;
var tempData = GetHeightTempDataFor(terrain);
tempData.SetHeightAt(wPos, wPos.y, power, radius, null, extraRadiusOnBigDiff);
if (update) tempData.ApplyHeights();
TerrainHeightTempDatas[terrain] = tempData;
}
public static void ChangeDetailAt(Vector3 wPos, int detailLayer, int changeDetailValueTo, float radius)
{
Terrain terr = GetTerrainIn(wPos);
if (terr == null) return;
ChangeDetailAt(wPos, terr, detailLayer, changeDetailValueTo, radius);
}
public static void ChangeDetailAt(Vector3 wPos, Terrain terr, int detailLayer, int changeDetailValueTo, float radius, bool update = false)
{
string key = Hash_Details(terr, detailLayer);
var data = GetDetailsTempDataFor(key, terr, detailLayer);
data.SetDetailAt(wPos, changeDetailValueTo, radius);
TerrainDetailsTempDatas[key] = data;
if (update) data.ApplyDetails();
}
}
}