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 MainDetailsX; public List MainDetailsY; public List> DetailsX; public List> DetailsY; public List> OldDetailsValue; //public Dictionary DetailValues; public int[] detailsCount; public float DetailToHeightRatio; //public Dictionary DetailHasProcessed; public HashSet DetailHasProcessed; //public List> OldDetailsValue; public int DetailMaxIndex; public HashSet DetailLayersSkip; //Trees public List TreesCurrent; public List 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; } } /// Checks all terrains and adds RoadTerrain if necessary private static void CheckAllTerrains() { Object[] allTerrains = GameObject.FindObjectsOfType(); RoadTerrain TID; GameObject terrainObj; foreach (Terrain terrain in allTerrains) { terrainObj = terrain.transform.gameObject; TID = terrainObj.GetComponent(); if (TID == null) { TID = terrainObj.AddComponent(); } TID.CheckID(); } } /// Checks if every Terrain uses a RoadTerrain script and set it to 0 on y public static void CheckAllTerrainsHeight0() { CheckAllTerrains(); Object[] allTerrains = GameObject.FindObjectsOfType(); 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; } } } /// Stores terrain infos and starts terrain calculations 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 TempTerrainDict = new Dictionary(); //Populate dictionary: Object[] allTerrains = GameObject.FindObjectsOfType(); 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(); if (TID != null) { TTD.uID = TID.UID; TempTerrainDict.Add(terrain, TTD); } } //Details: RootUtils.EndStartProfiling(_road, "Details"); if (_road.isDetailModificationEnabled) { //TTD.DetailValues = new Dictionary(); TTD.DetailLayersCount = terrain.terrainData.detailPrototypes.Length; //TTD.DetailHasProcessed = new Dictionary(); TTD.DetailHasProcessed = new HashSet(); TTD.MainDetailsX = new List(); TTD.MainDetailsY = new List(); 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(); // 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 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(); //TTD.DetailsY = new List(); //TTD.OldDetailsValue = new List(); TTD.DetailsX = new List>(); TTD.DetailsY = new List>(); TTD.OldDetailsValue = new List>(); //TTD.DetailHasProcessed = new List>(); 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()); TTD.DetailsY.Add(new List()); TTD.OldDetailsValue.Add(new List()); } //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(terrain.terrainData.treeInstances); TTD.TreeSize = TTD.TreesCurrent.Count; TTD.treesCount = 0; TTD.TreesOld = new List(); } RootUtils.EndProfiling(_road); } } //Figure out relevant TTD to spline: List EditorTTDList = new List(); 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); } } /// Returns an 2D rect of the terrain 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); } /// Assign calculated values to terrains public static void ProcessRoadTerrainHook2(SplineC _spline, ref List _TTDList) { RootUtils.StartProfiling(_spline.road, "ProcessRoadTerrainHook2"); ProcessRoadTerrainHook2Do(ref _spline, ref _TTDList); RootUtils.EndProfiling(_spline.road); } private static void ProcessRoadTerrainHook2Do(ref SplineC _spline, ref List _TTDList) { if (!_spline.road.isTreeModificationEnabled && !_spline.road.isHeightModificationEnabled && !_spline.road.isDetailModificationEnabled) { //Exit if no mod taking place. return; } Object[] TIDs = GameObject.FindObjectsOfType(); 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(); 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(); 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(); } } }