2024-11-19 11:48:21 +01:00

646 lines
27 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace RoadArchitect
{
public static class Terraforming
{
public class TempTerrainData
{
public int HM;
public int HMHeight;
public float[,] heights;
public bool[,] tHeights;
public float HMRatio;
public float MetersPerHM = 0f;
//Heights:
public ushort[] cX;
public ushort[] cY;
public float[] cH;
public float[] oldH;
public int Count = 0;
public int TerrainMaxIndex;
//Details:
public int DetailLayersCount;
public List<ushort> MainDetailsX;
public List<ushort> MainDetailsY;
public List<List<ushort>> DetailsX;
public List<List<ushort>> DetailsY;
public List<List<ushort>> OldDetailsValue;
//public Dictionary<int,int[,]> DetailValues;
public int[] detailsCount;
public float DetailToHeightRatio;
//public Dictionary<int,bool[,]> DetailHasProcessed;
public HashSet<int> DetailHasProcessed;
//public List<List<bool>> OldDetailsValue;
public int DetailMaxIndex;
public HashSet<int> DetailLayersSkip;
//Trees
public List<TreeInstance> TreesCurrent;
public List<TreeInstance> TreesOld;
public int treesCount;
public int TreeSize;
public Vector3 TerrainSize;
public Vector3 TerrainPos;
public int uID;
public void Nullify()
{
heights = null;
tHeights = null;
cX = null;
cY = null;
cH = null;
oldH = null;
//DetailsX = null;
//DetailsY = null;
//DetailValues = null;
OldDetailsValue = null;
//DetailsI = null;
TreesCurrent = null;
TreesOld = null;
}
}
/// <summary> Checks all terrains and adds RoadTerrain if necessary </summary>
private static void CheckAllTerrains()
{
Object[] allTerrains = GameObject.FindObjectsOfType<Terrain>();
RoadTerrain TID;
GameObject terrainObj;
foreach (Terrain terrain in allTerrains)
{
terrainObj = terrain.transform.gameObject;
TID = terrainObj.GetComponent<RoadTerrain>();
if (TID == null)
{
TID = terrainObj.AddComponent<RoadTerrain>();
}
TID.CheckID();
}
}
/// <summary> Checks if every Terrain uses a RoadTerrain script and set it to 0 on y </summary>
public static void CheckAllTerrainsHeight0()
{
CheckAllTerrains();
Object[] allTerrains = GameObject.FindObjectsOfType<Terrain>();
foreach (Terrain terrain in allTerrains)
{
if (!RootUtils.IsApproximately(terrain.transform.position.y, 0f, 0.0001f))
{
Vector3 tVect = terrain.transform.position;
tVect.y = 0f;
terrain.transform.position = tVect;
}
}
}
/// <summary> Stores terrain infos and starts terrain calculations </summary>
public static void ProcessRoadTerrainHook1(SplineC _spline, Road _road, bool _isMultithreaded = true)
{
ProcessRoadTerrainHook1Do(ref _spline, ref _road, _isMultithreaded);
}
private static void ProcessRoadTerrainHook1Do(ref SplineC _spline, ref Road _road, bool _isMultithreaded)
{
RootUtils.StartProfiling(_road, "ProcessRoadTerrainHook1");
//First lets make sure all terrains have a RoadTerrain script:
CheckAllTerrains();
//Reset the terrain:
RootUtils.StartProfiling(_road, "TerrainsReset");
TerrainsReset(_road);
RootUtils.EndProfiling(_road);
float heightDistance = _road.matchHeightsDistance;
//float treeDistance = _road.clearTreesDistance;
float detailDistance = _road.clearDetailsDistance;
Dictionary<Terrain, TempTerrainData> TempTerrainDict = new Dictionary<Terrain, TempTerrainData>();
//Populate dictionary:
Object[] allTerrains = GameObject.FindObjectsOfType<Terrain>();
RoadTerrain TID;
int aSize = 0;
int dSize = 0;
TempTerrainData TTD;
bool isContaining = false;
Construction2DRect tRect = null;
//Construction2DRect rRect = null;
foreach (Terrain terrain in allTerrains)
{
if (terrain.terrainData == null)
{
continue;
}
tRect = GetTerrainBounds(terrain);
isContaining = false;
//Debug.Log(terrain.transform.name + " bounds: " + tRect.ToStringRA());
//Debug.Log(" Road bounds: " + tSpline.RoadV0 + "," + tSpline.RoadV1 + "," + tSpline.RoadV2 + "," + tSpline.RoadV3);
// Check if the terrain overlaps with a part of the spline
if (isContaining != true && tRect.Contains(ref _spline.RoadV0))
{
isContaining = true;
}
else if (isContaining != true && tRect.Contains(ref _spline.RoadV1))
{
isContaining = true;
}
else if (isContaining != true && tRect.Contains(ref _spline.RoadV2))
{
isContaining = true;
}
else if (isContaining != true && tRect.Contains(ref _spline.RoadV3))
{
isContaining = true;
}
else
{
int nodeCount = _road.spline.GetNodeCount();
Vector2 tVect2D_321 = default(Vector2);
for (int index = 0; index < nodeCount; index++)
{
tVect2D_321 = new Vector2(_road.spline.nodes[index].pos.x, _road.spline.nodes[index].pos.z);
if (tRect.Contains(ref tVect2D_321))
{
isContaining = true;
break;
}
}
if (!isContaining)
{
float tDef = 5f / _spline.distance;
Vector2 x2D = default(Vector2);
Vector3 x3D = default(Vector3);
for (float index = 0f; index <= 1f; index += tDef)
{
x3D = _spline.GetSplineValue(index);
x2D = new Vector2(x3D.x, x3D.z);
if (tRect.Contains(ref x2D))
{
isContaining = true;
break;
}
}
}
}
//rRect = new RoadUtility.Construction2DRect(tSpline.RoadV0,tSpline.RoadV1,tSpline.RoadV2,tSpline.RoadV3);
if (isContaining && !TempTerrainDict.ContainsKey(terrain))
{
TTD = new TempTerrainData();
TTD.HM = terrain.terrainData.heightmapResolution;
TTD.HMHeight = terrain.terrainData.heightmapResolution;
TTD.heights = terrain.terrainData.GetHeights(0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution);
TTD.HMRatio = TTD.HM / terrain.terrainData.size.x;
TTD.MetersPerHM = terrain.terrainData.size.x / terrain.terrainData.heightmapResolution;
float DetailRatio = terrain.terrainData.detailResolution / terrain.terrainData.size.x;
//Heights:
RootUtils.StartProfiling(_road, "Heights");
if (_road.isHeightModificationEnabled)
{
aSize = (int)_spline.distance * ((int)(heightDistance * 1.65f * TTD.HMRatio) + 2);
if (aSize > (terrain.terrainData.heightmapResolution * terrain.terrainData.heightmapResolution))
{
aSize = terrain.terrainData.heightmapResolution * terrain.terrainData.heightmapResolution;
}
TTD.cX = new ushort[aSize];
TTD.cY = new ushort[aSize];
TTD.oldH = new float[aSize];
TTD.cH = new float[aSize];
TTD.Count = 0;
TTD.TerrainMaxIndex = terrain.terrainData.heightmapResolution;
TTD.TerrainSize = terrain.terrainData.size;
TTD.TerrainPos = terrain.transform.position;
TTD.tHeights = new bool[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
TID = terrain.transform.gameObject.GetComponent<RoadTerrain>();
if (TID != null)
{
TTD.uID = TID.UID;
TempTerrainDict.Add(terrain, TTD);
}
}
//Details:
RootUtils.EndStartProfiling(_road, "Details");
if (_road.isDetailModificationEnabled)
{
//TTD.DetailValues = new Dictionary<int, int[,]>();
TTD.DetailLayersCount = terrain.terrainData.detailPrototypes.Length;
//TTD.DetailHasProcessed = new Dictionary<int, bool[,]>();
TTD.DetailHasProcessed = new HashSet<int>();
TTD.MainDetailsX = new List<ushort>();
TTD.MainDetailsY = new List<ushort>();
TTD.detailsCount = new int[TTD.DetailLayersCount];
TTD.DetailToHeightRatio = (float)((float)terrain.terrainData.detailResolution) / ((float)terrain.terrainData.heightmapResolution);
TTD.DetailMaxIndex = terrain.terrainData.detailResolution;
TTD.DetailLayersSkip = new HashSet<int>();
// Get all of layer zero.
//int[] mMinMaxDetailEntryCount = new int[TTD.DetailLayersCount];
//RootUtils.StartProfiling(_road, "DetailValues");
//Vector3 bVect = default(Vector3);
//Vector2 bVect2D = default(Vector2);
//int DetailRes = tTerrain.terrainData.detailResolution;
//for(int i=0;i<TTD.DetailLayersCount;i++)
//{
// int[,] tInts = tTerrain.terrainData.GetDetailLayer(0,0,tTerrain.terrainData.detailWidth,tTerrain.terrainData.detailHeight,i);
// int Length1 = tInts.GetLength(0);
// int Length2 = tInts.GetLength(1);
// for (int y=0;y < Length1;y++)
// {
// for (int x=0;x < Length2;x++)
// {
// if(tInts[x,y] > 0)
// {
// bVect = new Vector3(((float)y/(float)DetailRes) * TTD.TerrainSize.x,0f,((float)x/(float)DetailRes) * TTD.TerrainSize.z);
// bVect = tTerrain.transform.TransformPoint(bVect);
// bVect2D = new Vector2(bVect.z,bVect.x);
// if(rRect.Contains(ref bVect2D))
// {
// mMinMaxDetailEntryCount[i] += 1;
// }
// }
// }
// }
// if(mMinMaxDetailEntryCount[i] < 1)
// {
// TTD.DetailLayersSkip.Add(i);
// tInts = null;
// }
// else
// {
// TTD.DetailValues.Add(i,tInts);
// TTD.DetailHasProcessed.Add(i,new bool[tTerrain.terrainData.detailWidth,tTerrain.terrainData.detailHeight]);
// }
// }
//RootUtils.EndProfiling(_road);
dSize = (int)_spline.distance * ((int)(detailDistance * 3f * DetailRatio) + 2);
if (dSize > (terrain.terrainData.detailResolution * terrain.terrainData.detailResolution))
{
dSize = terrain.terrainData.detailResolution * terrain.terrainData.detailResolution;
}
//TTD.DetailsX = new List<ushort[]>();
//TTD.DetailsY = new List<ushort[]>();
//TTD.OldDetailsValue = new List<ushort[]>();
TTD.DetailsX = new List<List<ushort>>();
TTD.DetailsY = new List<List<ushort>>();
TTD.OldDetailsValue = new List<List<ushort>>();
//TTD.DetailHasProcessed = new List<List<bool>>();
for (int index = 0; index < TTD.DetailLayersCount; index++)
{
//if(TTD.DetailLayersSkip.Contains(index))
//{
// TTD.DetailsX.Add(new ushort[0]);
// TTD.DetailsY.Add(new ushort[0]);
// TTD.OldDetailsValue.Add(new ushort[0]);
// continue;
//}
//int detailentrycount = (int)((float)mMinMaxDetailEntryCount[index] * 1.5f);
//int d_temp_Size = dSize;
//if(d_temp_Size > detailentrycount)
//{
// d_temp_Size = detailentrycount;
//}
//if(d_temp_Size < 1)
//{
// d_temp_Size = 1;
//}
//if(d_temp_Size > (tTerrain.terrainData.detailResolution * tTerrain.terrainData.detailResolution))
//{
// d_temp_Size = tTerrain.terrainData.detailResolution * tTerrain.terrainData.detailResolution;
//}
//
//TTD.DetailsX.Add(new ushort[d_temp_Size]);
//TTD.DetailsY.Add(new ushort[d_temp_Size]);
//TTD.OldDetailsValue.Add(new ushort[d_temp_Size]);
TTD.DetailsX.Add(new List<ushort>());
TTD.DetailsY.Add(new List<ushort>());
TTD.OldDetailsValue.Add(new List<ushort>());
}
//TTD.DetailsX = new ushort[TTD.DetailLayersCount,dSize];
//TTD.DetailsY = new ushort[TTD.DetailLayersCount,dSize];
//TTD.OldDetailsValue = new ushort[TTD.DetailLayersCount,dSize];
}
//Trees:
RootUtils.EndStartProfiling(_road, "Trees");
if (_road.isTreeModificationEnabled)
{
TTD.TreesCurrent = new List<TreeInstance>(terrain.terrainData.treeInstances);
TTD.TreeSize = TTD.TreesCurrent.Count;
TTD.treesCount = 0;
TTD.TreesOld = new List<TreeInstance>();
}
RootUtils.EndProfiling(_road);
}
}
//Figure out relevant TTD to spline:
List<TempTerrainData> EditorTTDList = new List<TempTerrainData>();
if (TempTerrainDict != null)
{
foreach (Terrain tTerrain in allTerrains)
{
if (TempTerrainDict.ContainsKey(tTerrain))
{
EditorTTDList.Add(TempTerrainDict[tTerrain]);
}
}
}
RootUtils.EndProfiling(_road);
//Start job now, for each relevant TTD:
_road.SetEditorTerrainCalcs(ref EditorTTDList);
if (_isMultithreaded)
{
Threading.TerrainCalcs terrainJob = new Threading.TerrainCalcs();
terrainJob.Setup(ref EditorTTDList, _spline, _road);
_road.TerrainCalcsJob = terrainJob;
terrainJob.Start();
}
else
{
Threading.TerrainCalcsStatic.RunMe(ref EditorTTDList, _spline, _road);
}
}
/// <summary> Returns an 2D rect of the terrain </summary>
public static Construction2DRect GetTerrainBounds(Terrain _terrain)
{
float terrainWidth = _terrain.terrainData.size.x;
float terrainLength = _terrain.terrainData.size.z;
//Vector3 tPos = tTerrain.transform.TransformPoint(tTerrain.transform.position);
Vector3 X0 = new Vector3(0f, 0f, 0f);
Vector3 X1 = new Vector3(terrainWidth, 0f, 0f);
Vector3 X2 = new Vector3(terrainWidth, 0f, terrainLength);
Vector3 X3 = new Vector3(0f, 0f, terrainLength);
X0 = _terrain.transform.TransformPoint(X0);
X1 = _terrain.transform.TransformPoint(X1);
X2 = _terrain.transform.TransformPoint(X2);
X3 = _terrain.transform.TransformPoint(X3);
Vector2 P0 = new Vector2(X0.x, X0.z);
Vector2 P1 = new Vector2(X1.x, X1.z);
Vector2 P2 = new Vector2(X2.x, X2.z);
Vector2 P3 = new Vector2(X3.x, X3.z);
//OLD CODE:
//Vector2 P0 = new Vector2(0f, 0f);
//Vector2 P1 = new Vector2(terrainWidth, 0f);
//Vector2 P2 = new Vector2(terrainWidth, terrainLength);
//Vector2 P3 = new Vector2(0f, terrainLength);
//P0 = tTerrain.transform.TransformPoint(P0);
//P1 = tTerrain.transform.TransformPoint(P1);
//P2 = tTerrain.transform.TransformPoint(P2);
//P3 = tTerrain.transform.TransformPoint(P3);
return new Construction2DRect(P0, P1, P2, P3, _terrain.transform.position.y);
}
/// <summary> Assign calculated values to terrains </summary>
public static void ProcessRoadTerrainHook2(SplineC _spline, ref List<TempTerrainData> _TTDList)
{
RootUtils.StartProfiling(_spline.road, "ProcessRoadTerrainHook2");
ProcessRoadTerrainHook2Do(ref _spline, ref _TTDList);
RootUtils.EndProfiling(_spline.road);
}
private static void ProcessRoadTerrainHook2Do(ref SplineC _spline, ref List<TempTerrainData> _TTDList)
{
if (!_spline.road.isTreeModificationEnabled && !_spline.road.isHeightModificationEnabled && !_spline.road.isDetailModificationEnabled)
{
//Exit if no mod taking place.
return;
}
Object[] TIDs = GameObject.FindObjectsOfType<RoadTerrain>();
Terrain terrain;
int[,] tDetails = null;
int IntBufferX = 0;
int IntBufferY = 0;
int tVal = 0;
foreach (TempTerrainData TTD in _TTDList)
{
foreach (RoadTerrain TID in TIDs)
{
if (TID.UID != TTD.uID)
{
continue;
}
terrain = TID.transform.gameObject.GetComponent<Terrain>();
if (terrain == null)
{
continue;
}
//Details:
if (_spline.road.isDetailModificationEnabled)
{
for (int index = 0; index < TTD.DetailLayersCount; index++)
{
//if(TTD.DetailLayersSkip.Contains(i) || TTD.DetailValues[i] == null)
//{
// continue;
//}
//if(TTD.DetailsI[i] > 0)
//{
// tTerrain.terrainData.SetDetailLayer(0, 0, i, TTD.DetailValues[i]);
//}
if (TTD.DetailLayersSkip.Contains(index) || TTD.MainDetailsX == null || TTD.MainDetailsX.Count < 1)
{
continue;
}
tDetails = terrain.terrainData.GetDetailLayer(0, 0, TTD.DetailMaxIndex, TTD.DetailMaxIndex, index);
int MaxCount = TTD.MainDetailsX.Count;
for (int j = 0; j < MaxCount; j++)
{
IntBufferX = TTD.MainDetailsX[j];
IntBufferY = TTD.MainDetailsY[j];
tVal = tDetails[IntBufferX, IntBufferY];
if (tVal > 0)
{
TTD.DetailsX[index].Add((ushort)IntBufferX);
TTD.DetailsY[index].Add((ushort)IntBufferY);
TTD.OldDetailsValue[index].Add((ushort)tVal);
tDetails[IntBufferX, IntBufferY] = 0;
}
}
TTD.detailsCount[index] = TTD.DetailsX[index].Count;
terrain.terrainData.SetDetailLayer(0, 0, index, tDetails);
tDetails = null;
TTD.DetailHasProcessed = null;
}
TTD.MainDetailsX = null;
TTD.MainDetailsY = null;
System.GC.Collect();
}
//Trees:
if (_spline.road.isTreeModificationEnabled && TTD.TreesCurrent != null && TTD.treesCount > 0)
{
terrain.terrainData.treeInstances = TTD.TreesCurrent.ToArray();
}
//Heights:
if (_spline.road.isHeightModificationEnabled && TTD.heights != null && TTD.Count > 0)
{
//Do heights last to trigger collisions and stuff properly:
terrain.terrainData.SetHeights(0, 0, TTD.heights);
}
}
}
}
public static void TerrainsReset(Road _road)
{
if (_road.TerrainHistory == null)
{
return;
}
if (_road.TerrainHistory.Count < 1)
{
return;
}
Object[] TIDs = GameObject.FindObjectsOfType<RoadTerrain>();
float[,] heights;
int[,] tDetails;
int ArrayCount;
foreach (TerrainHistoryMaker TH in _road.TerrainHistory)
{
Terrain terrain = null;
foreach (RoadTerrain TID in TIDs)
{
if (TID.UID == TH.terrainID)
{
terrain = TID.terrain;
}
}
if (!terrain)
{
continue;
}
if (TH.heightmapResolution != terrain.terrainData.heightmapResolution)
{
TH.Nullify();
continue;
}
//Heights:
if (TH.x1 != null)
{
heights = terrain.terrainData.GetHeights(0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution);
ArrayCount = TH.Count;
for (int index = 0; index < ArrayCount; index++)
{
heights[TH.x1[index], TH.y1[index]] = TH.height[index];
}
terrain.terrainData.SetHeights(0, 0, heights);
}
//Details:
if (TH.detailsCount != null && TH.detailsX != null && TH.detailsY != null && TH.detailsOldValue != null)
{
int RealLayerCount = terrain.terrainData.detailPrototypes.Length;
int StartIndex = 0;
int EndIndex = 0;
for (int index = 0; index < TH.detailLayersCount; index++)
{
if (index >= RealLayerCount)
{
break;
}
if (TH.detailsX.Length <= index)
{
break;
}
if (TH.detailsY.Length <= index)
{
break;
}
if (TH.detailsX.Length < 1)
{
continue;
}
tDetails = terrain.terrainData.GetDetailLayer(0, 0, terrain.terrainData.detailWidth, terrain.terrainData.detailHeight, index);
ArrayCount = TH.detailsCount[index];
if (ArrayCount == 0)
{
continue;
}
EndIndex += ArrayCount;
for (int j = StartIndex; j < EndIndex; j++)
{
tDetails[TH.detailsX[j], TH.detailsY[j]] = TH.detailsOldValue[j];
}
StartIndex = EndIndex;
terrain.terrainData.SetDetailLayer(0, 0, index, tDetails);
tDetails = null;
}
}
//Trees:
TreeInstance[] xTress = TH.MakeTrees();
if (xTress != null)
{
ArrayCount = xTress.Length;
if (ArrayCount > 0 && TH.oldTrees != null)
{
int TerrainTreeCount = terrain.terrainData.treeInstances.Length;
TreeInstance[] tTrees = new TreeInstance[ArrayCount + TerrainTreeCount];
System.Array.Copy(terrain.terrainData.treeInstances, 0, tTrees, 0, TerrainTreeCount);
System.Array.Copy(xTress, 0, tTrees, TerrainTreeCount, ArrayCount);
terrain.terrainData.treeInstances = tTrees;
}
xTress = null;
}
}
System.GC.Collect();
}
}
}