diff --git a/Runtime/TerrainBuilder.cs b/Runtime/TerrainBuilder.cs index b31566ff9ddaa2ed39f118fffa32d687c364cadc..4c16d2737761ffbf1eb450d2923300add0681cc9 100644 --- a/Runtime/TerrainBuilder.cs +++ b/Runtime/TerrainBuilder.cs @@ -1,27 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Unity.Collections; -using Unity.VisualScripting.YamlDotNet.Core.Events; using UnityEditor; using UnityEngine; -using UnityEngine.Networking; namespace Wander { - - - struct MapTile - { - internal int numRetries; - internal int deltaTileX; // Relative tile index with respect to origin up left corner. So, 0, -1, etc. - internal int deltaTileY; - internal UnityWebRequest www; - } - [Serializable] public struct TerrainLayer2 { @@ -29,31 +12,19 @@ namespace Wander public Color [] colors; } - [ExecuteAlways()] public class TerrainBuilder : MonoBehaviour { - enum State - { - None, - WaitForTerrain, - WaitForDownloadHeight, - WaitForCreateHeightmap - } - - State state = State.None; - - [ReadOnly] public Vector2 rdOrigin; - [ReadOnly] public Vector2Int tileOrigin; + [ReadOnly] public Vector3 adjustedSize; [ReadOnly] public double tileSize; [ReadOnly] public int tilePixelSize; [ReadOnly] public int nTilesWide; [ReadOnly] public int nTilesHigh; - [ReadOnly] public TerrainData terrainData; - [ReadOnly] public Terrain terrain; + [ReadOnly] public Dictionary<uint, byte> pixelToLayerIndex; + [ReadOnly] public List<TerrainTile> tiles = new List<TerrainTile>(); public Vector2 originWGS84 = new Vector2(51.985365f, 5.664941f); // Copy directly from Google maps. This is Forum on Campus WUR. - public Vector2Int terrainOffset = new Vector2Int(0, 0); + public Vector2Int numTiles = new Vector2Int(1,1); public int controlResolution = 8192; public int heightMapResolution = 1024; public bool loadHeight = true; @@ -63,102 +34,18 @@ namespace Wander public bool runOnStart = false; public Material terrainMat = null; public List<TerrainLayer2> layers; - - private List<MapTile> requests; - private byte[] controlData; - private Dictionary<uint, byte> pixelToLayerIndex; - private List<Task> writeControlTextureDataTasks; - private AsyncHeightData asyncHeightHandle; - private Task<float[,]> normalizeHeightTask; - - private void Start() - { - if (Application.isPlaying) - { - if (runOnStart) - { - Build(); - } - else Destroy( this ); - } - } - - internal async void CancelOrClean() + + internal void CancelAndClean() { - state = State.None; - if ( normalizeHeightTask != null ) - { - await normalizeHeightTask; - normalizeHeightTask = null; - } - if (writeControlTextureDataTasks != null) - { - writeControlTextureDataTasks.ForEach( async l => await l ); - } - writeControlTextureDataTasks = null; - requests = null; - controlData = null; + tiles.ForEach( t => t.CancelAndClean() ); + tiles = new List<TerrainTile>(); } internal void Build() { - CancelOrClean(); - controlData = new byte[controlResolution*controlResolution]; - writeControlTextureDataTasks = new List<Task>(); CalcPreliminaries(); CreatePixelToLayerIndex(); - DownloadTiles(); - CreateTerrainData(); - LinkLayersToMaterial(); - state = State.WaitForTerrain; - } - - void Update() - { - switch (state) - { - case State.None: - break; - - case State.WaitForTerrain: - CheckDownloadRequests(); - if (requests !=null && requests.Count==0) - { - if (loadHeight) - { - var url = GeoTiffHeight.BuildPDOKWCSUrl( rdOrigin.x, rdOrigin.x+adjustedSize.x, rdOrigin.y, rdOrigin.y+adjustedSize.y, heightMapResolution, heightMapResolution ); - asyncHeightHandle = GeoTiffHeight.LoadFromUrl( url, true ); - state = State.WaitForDownloadHeight; - } - else - { - DoFinalSteps( null ); - } - } - break; - - case State.WaitForDownloadHeight: - if (asyncHeightHandle.IsFinished()) - { - if (asyncHeightHandle.Valid) - { - normalizeHeightTask = Task.Run( () => CreateHeightMap( asyncHeightHandle.HeightData ) ); - state = State.WaitForCreateHeightmap; - } - } - break; - - case State.WaitForCreateHeightmap: - if (normalizeHeightTask.IsCompleted) - { - if (normalizeHeightTask.IsCompletedSuccessfully) - { - DoFinalSteps( normalizeHeightTask.Result ); - } - else DoFinalSteps( null ); - } - break; - } + CreateTerrain(); } void CalcPreliminaries() @@ -169,17 +56,11 @@ namespace Wander return; } - // Size tileSize = RDUtils.CalcTileSizeRD( zoom ); tilePixelSize = RDUtils.RDDefaultTileRes; nTilesWide = controlResolution / (tilePixelSize); nTilesHigh = controlResolution / (tilePixelSize); adjustedSize = new Vector3( (float)(nTilesWide*tileSize), terrainHeight, (float)(nTilesHigh*tileSize) ); - - // Location - RDUtils.GPS2RD( originWGS84.x, originWGS84.y, out double rdX, out double rdY ); - tileOrigin = RDUtils.RD2Tile( new Vector2( (float)rdX+adjustedSize.x*terrainOffset.x, (float)rdY+adjustedSize.z*terrainOffset.y ), zoom ); - rdOrigin = RDUtils.Tile2RD( tileOrigin, zoom ); } void CreatePixelToLayerIndex() @@ -213,239 +94,25 @@ namespace Wander } } - void LinkLayersToMaterial() - { - if (layers == null || layers.Count == 0) - { - Debug.LogWarning( "No layers specified!" ); - return; - } - for (int i = 0;i< layers.Count;i++) - { - var l = layers[i]; - terrain.materialTemplate.SetTexture( "Albedo" + i, l.layer.diffuseTexture ); - } - } - - void CreateTerrainData() - { - terrainData = new TerrainData(); - - GameObject go = new GameObject("GeneratedTerrain"); - terrain = go.AddComponent<Terrain>(); - - terrain.terrainData = terrainData; - terrain.materialTemplate = new Material( terrainMat ); - terrainData.terrainLayers = layers.Select( l => l.layer ).ToArray(); - terrainData.heightmapResolution = heightMapResolution; - terrainData.size = adjustedSize; - - go.AddComponent<TerrainCollider>().terrainData = terrainData; - go.transform.position = new Vector3( terrainOffset.x*adjustedSize.x, 0, terrainOffset.y*adjustedSize.z ); - } - - internal void SyncLayersToMaterial() - { - Texture2DArray albedoArray = new Texture2DArray(4, 4, 4, TextureFormat.RGB24, 5, false); - albedoArray.anisoLevel = 8; - albedoArray.SetPixelData( layers[0].layer.diffuseTexture.GetPixelData<Color32>( 0 ), 0, 0, 0 ); - albedoArray.SetPixelData( layers[1].layer.diffuseTexture.GetPixelData<Color32>( 0 ), 1, 0, 0 ); - terrain.materialTemplate.SetTexture( "AlbedoMaps", albedoArray ); - } - - internal void SyncSplatMap() - { - int res = terrainData.alphamapResolution; - float [,,] splatMap = terrainData.GetAlphamaps( 0, 0, res, res ); - if (splatMap.GetLength( 2 ) == 0) - return; - Texture2D controlTex = new Texture2D(res, res, TextureFormat.R8, false ); - byte [] textureIndexArray = new byte[res*res]; - for (int x = 0;x < res;x++) - { - for (int y = 0;y < res;y++) - { - float max = splatMap[x,y, 0]; - byte chosenIndex = 0; - for (int texIdx = 1;texIdx < splatMap.GetLength( 2 );texIdx++) - { - float d = splatMap[x,y,texIdx]; - if (d > max) - { - max = d; - chosenIndex = (byte)texIdx; - } - } - textureIndexArray[y*res+x] = chosenIndex; - } - } - controlTex.SetPixelData( textureIndexArray, 0, 0 ); - controlTex.Apply(); - terrain.materialTemplate.SetTexture( "ControlTexture", controlTex ); - } - - void WriteTileToControlTexture( MapTile tile, int width, int height, NativeArray<Color32> pixelData ) - { - byte layerIndex = 0; - uint cachedMask = 0; - int tx = tile.deltaTileX * tilePixelSize; - int ty = tile.deltaTileY * tilePixelSize; - int of = ty*controlResolution+tx; - int pixelIdx = 0; - // ! No need to lock controlData because every tile writes to its own designated area exactly, no oversampling or undersampling. ! - for (int y = 0;y < height;y++) - { - if (state== State.None) - return; // Cancelling. - - for (int x = 0;x < width;x++) - { - Color32 col = pixelData[pixelIdx++]; - uint colMask = ((uint)col.g<<16) | ((uint)col.b<<8) | (col.a); // Color is stored as ARGB, so for this Color32 struct becomes g,b,a ... because we want RGB. - if (cachedMask!=colMask || layerIndex==255) - { - if (pixelToLayerIndex.TryGetValue( colMask, out byte queriedLayeredIndex )) - { - layerIndex = queriedLayeredIndex; - } - cachedMask = colMask; - } - - controlData[of + x] = layerIndex; - } - - of += controlResolution; - } - } - - string GetMapUrl( int tileX, int tileY, int zoom ) - { - // $"https://service.pdok.nl/lv/bgt/wmts/v1_0/achtergrondvisualisatie/EPSG:28992/{zoom}/{tileX}/{tileY}.png"; - var url = $"https://service.pdok.nl/lv/bgt/wmts/v1_0/standaardvisualisatie/EPSG:28992/{zoom}/{tileX}/{tileY}.png"; - return url; - } - - void DownloadTiles() - { - requests = new List<MapTile>(); - for (int x = 0;x < nTilesWide;x++) - { - for (int y = 0;y < nTilesHigh;y++) - { - int tileX = x + tileOrigin.x; - int tileY = -y + tileOrigin.y; - string url = GetMapUrl(tileX, tileY, zoom); - UnityWebRequest www = UnityWebRequestTexture.GetTexture(url); - www.SendWebRequest(); - MapTile td = new MapTile(); - td.www = www; - td.deltaTileX = x; - td.deltaTileY = y; - requests.Add( td ); - } - } - } - - void CheckDownloadRequests() - { - if (requests==null) - return; - - float tileSize = (float) RDUtils.CalcTileSizeRD(zoom); - for (int i = 0;i < requests.Count;i++) - { - var tile = requests[i]; - var www = tile.www; - if (www.isDone) - { - if (www.result != UnityWebRequest.Result.Success) - { - Debug.Log( www.error ); - if (tile.numRetries < numDLRetries) - { - tile.numRetries++; - tile.www = UnityWebRequestTexture.GetTexture( www.url ); - tile.www.SendWebRequest(); - requests[i] = tile; - } - else - { - requests.RemoveAt( i ); - i--; - } - } - else - { - var texture = ((DownloadHandlerTexture)www.downloadHandler).texture; - var height = texture.height; - var width = texture.width; - var pixelData = texture.GetPixelData<Color32>( 0 ); // Must be called on Main thread. - var writeControlTextureTask = Task.Run( () => WriteTileToControlTexture( tile, width, height, pixelData ) ); - writeControlTextureDataTasks.Add( writeControlTextureTask ); - requests.RemoveAt( i ); - i--; - } - } - } - } - - Texture2D CreateControlTexture() - { - if (terrain == null) - return null; // Can be deleted in scene. - var controlTexture = new Texture2D( controlResolution, controlResolution, TextureFormat.R8, false ); - terrain.materialTemplate.SetTexture( "ControlTexture", controlTexture ); - controlTexture.SetPixelData( controlData, 0, 0 ); - controlTexture.Apply( false, false ); - return controlTexture; - } - - float[,] CreateHeightMap( HeightData heightData ) + void CreateTerrain() { - var heights = new float [heightMapResolution,heightMapResolution]; - int height = heightData.height; - for (int y = 0;y<height;y++) + RDUtils.GPS2RD( originWGS84.x, originWGS84.y, out double rdX, out double rdY ); + for ( int y = -numTiles.y/2; y <= numTiles.y/2; y++ ) { - if (state== State.None) - return null; // Cancelling. - - for (int x = 0;x<heightData.width;x++) + for (int x = -numTiles.y/2;x <= numTiles.x/2;x++) { - float absHeight = heightData.data[y*heightData.width+x]; - // absHeight += terrainHeight*0.5f; - absHeight /= terrainHeight; - heights[height-y-1, x] = absHeight; + + GameObject go = new GameObject($"GeneratedTerrain_{x}_{y}"); + var tile = go.AddComponent<TerrainTile>(); + tiles.Add( tile ); + tile.builder = this; + tile.tileOrigin = RDUtils.RD2Tile( new Vector2( (float)rdX+adjustedSize.x * x, (float)rdY+adjustedSize.z * y ), zoom ); + tile.rdOrigin = RDUtils.Tile2RD( tile.tileOrigin, zoom ); + tile.terrainOffset = new Vector2Int( x, y ); + tile.Build(); } } - return heights; } - - void DoFinalSteps( float[,] heightData ) - { - if ( heightData != null && terrainData != null ) - { - terrainData.SetHeights( 0, 0, heightData ); - } - var controlTex = CreateControlTexture(); - SaveToDisk( controlTex ); - CancelOrClean(); - } - -#if UNITY_EDITOR - void SaveToDisk( Texture2D controlTexture ) - { - if (controlTexture == null) - return; - var generatedContent = $"BGT_terrain_{originWGS84.x}_{originWGS84.y}"; - if (!Directory.Exists( Path.Combine( Application.dataPath, generatedContent ) )) - { - Directory.CreateDirectory( Path.Combine( Application.dataPath, generatedContent ) ); - } - AssetDatabase.CreateAsset( terrainData, Path.Combine( "Assets", generatedContent, $"bgt_terrain_{terrainOffset.x}_{terrainOffset.y}.asset" ) ); - AssetDatabase.CreateAsset( controlTexture, Path.Combine( "Assets", generatedContent, $"control_texture_{terrainOffset.x}_{terrainOffset.y}.asset" ) ); - } -#endif - } #if UNITY_EDITOR @@ -457,25 +124,17 @@ namespace Wander { TerrainBuilder builder = (TerrainBuilder)target; - EditorGUILayout.HelpBox( "Copy coordinates from Google maps by clicking a location into WGS84.", MessageType.Info, true ); + EditorGUILayout.HelpBox( "Look up a location in Google maps and copy GPS coordinates in Origin WGS84 X, Y.", MessageType.Info, true ); GUILayout.BeginHorizontal(); { if (GUILayout.Button( "Build" )) { builder.Build(); - } - if (GUILayout.Button( "Sync Terrain Mat" )) - { - builder.SyncLayersToMaterial(); - } - if (GUILayout.Button( "Sync Splat Map" )) - { - builder.SyncSplatMap(); } - if (GUILayout.Button( "Cancel" )) + if (GUILayout.Button( "Cancel and Clean" )) { - builder.CancelOrClean(); + builder.CancelAndClean(); } } GUILayout.EndHorizontal(); diff --git a/Runtime/TerrainTile.cs b/Runtime/TerrainTile.cs new file mode 100644 index 0000000000000000000000000000000000000000..c35a9c99b8840bb3e903f5a56528eec8df59e285 --- /dev/null +++ b/Runtime/TerrainTile.cs @@ -0,0 +1,379 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Unity.Collections; +using UnityEditor; +using UnityEngine; +using UnityEngine.Networking; + +namespace Wander +{ + struct MapTile + { + internal int numRetries; + internal int deltaTileX; // Relative tile index with respect to origin up left corner. So, 0, -1, etc. + internal int deltaTileY; + internal UnityWebRequest www; + } + + [ExecuteAlways()] + public class TerrainTile : MonoBehaviour + { + enum State + { + None, + Processing + } + + State state = State.None; + + [ReadOnly] public Vector2 rdOrigin; + [ReadOnly] public Vector2Int tileOrigin; + [ReadOnly] public TerrainBuilder builder; + [ReadOnly] public TerrainData terrainData; + [ReadOnly] public Terrain terrain; + [ReadOnly] public Vector2Int terrainOffset = new Vector2Int(0, 0); + + private List<MapTile> requests; + private byte[] controlData; + private List<Task> writeControlTextureDataTasks; + private AsyncHeightData asyncHeightHandle; + private Task<float[,]> normalizeHeightTask; + private Task filterControlDataTask; + + internal async void CancelAndClean() + { + state = State.None; + if ( normalizeHeightTask != null ) + { + await normalizeHeightTask; + normalizeHeightTask = null; + } + if (writeControlTextureDataTasks != null) + { + writeControlTextureDataTasks.ForEach( async l => await l ); + } + if ( filterControlDataTask != null ) + { + await filterControlDataTask; + filterControlDataTask = null; + } + writeControlTextureDataTasks = null; + requests = null; + controlData = null; + } + + internal void Build() + { + CancelAndClean(); + controlData = new byte[builder.controlResolution*builder.controlResolution]; + writeControlTextureDataTasks = new List<Task>(); + DownloadTiles(); + DownloadHeight(); + CreateTerrainData(); + LinkLayersToMaterial(); + state = State.Processing; + } + + void Update() + { + switch (state) + { + case State.None: + break; + + /* Instead of sequentially going from one task to another, execute all at once and keep checking. */ + case State.Processing: + CheckTerrainRequests(); + + // If all tiles finished. + if (requests !=null && requests.Count==0) + { + if (filterControlDataTask == null && writeControlTextureDataTasks.All( t => t.IsCompleted ) ) + { + filterControlDataTask = Task.Run( () => FilterControlTexture() ); + } + } + + if ( builder.loadHeight ) + { + if (normalizeHeightTask == null && asyncHeightHandle.IsFinished() && asyncHeightHandle.Valid ) + { + normalizeHeightTask = Task.Run( () => CreateHeightMap( asyncHeightHandle.HeightData ) ); + } + } + + // Check if we are done. + if ( /*Terrain*/ (filterControlDataTask != null && filterControlDataTask.IsCompleted) && + /*Height*/ (!builder.loadHeight || (normalizeHeightTask != null && normalizeHeightTask.IsCompleted)) ) + { + float [,] heightData = builder.loadHeight ? normalizeHeightTask.Result : null; + DoFinalSteps( heightData ); + } + break; + } + } + + void LinkLayersToMaterial() + { + if (builder.layers == null || builder.layers.Count == 0) + { + Debug.LogWarning( "No layers specified!" ); + return; + } + for (int i = 0;i< builder.layers.Count;i++) + { + var l = builder.layers[i]; + terrain.materialTemplate.SetTexture( "Albedo" + i, l.layer.diffuseTexture ); + } + } + + void CreateTerrainData() + { + terrain = gameObject.AddComponent<Terrain>(); + + terrainData = new TerrainData(); + + terrain.terrainData = terrainData; + terrain.materialTemplate = new Material( builder.terrainMat ); + terrainData.terrainLayers = builder.layers.Select( l => l.layer ).ToArray(); + terrainData.heightmapResolution = builder.heightMapResolution; + terrainData.size = builder.adjustedSize; + + gameObject.AddComponent<TerrainCollider>().terrainData = terrainData; + gameObject.transform.position = new Vector3( terrainOffset.x*builder.adjustedSize.x, 0, terrainOffset.y*builder.adjustedSize.z ); + } + + internal void SyncLayersToMaterial() + { + Texture2DArray albedoArray = new Texture2DArray(4, 4, 4, TextureFormat.RGB24, 5, false); + albedoArray.anisoLevel = 8; + albedoArray.SetPixelData( builder.layers[0].layer.diffuseTexture.GetPixelData<Color32>( 0 ), 0, 0, 0 ); + albedoArray.SetPixelData( builder.layers[1].layer.diffuseTexture.GetPixelData<Color32>( 0 ), 1, 0, 0 ); + terrain.materialTemplate.SetTexture( "AlbedoMaps", albedoArray ); + } + + internal void SyncSplatMap() + { + int res = terrainData.alphamapResolution; + float [,,] splatMap = terrainData.GetAlphamaps( 0, 0, res, res ); + if (splatMap.GetLength( 2 ) == 0) + return; + Texture2D controlTex = new Texture2D(res, res, TextureFormat.R8, false ); + byte [] textureIndexArray = new byte[res*res]; + for (int x = 0;x < res;x++) + { + for (int y = 0;y < res;y++) + { + float max = splatMap[x,y, 0]; + byte chosenIndex = 0; + for (int texIdx = 1;texIdx < splatMap.GetLength( 2 );texIdx++) + { + float d = splatMap[x,y,texIdx]; + if (d > max) + { + max = d; + chosenIndex = (byte)texIdx; + } + } + textureIndexArray[y*res+x] = chosenIndex; + } + } + controlTex.SetPixelData( textureIndexArray, 0, 0 ); + controlTex.Apply(); + terrain.materialTemplate.SetTexture( "ControlTexture", controlTex ); + } + + void WriteTileToControlTexture( MapTile tile, int width, int height, NativeArray<Color32> pixelData ) + { + byte layerIndex = 254; + uint cachedMask = 0; + int tx = tile.deltaTileX * builder.tilePixelSize; + int ty = tile.deltaTileY * builder.tilePixelSize; + int of = ty*builder.controlResolution+tx; + int pixelIdx = 0; + // ! No need to lock controlData because every tile writes to its own designated area exactly, no oversampling or undersampling. ! + for (int y = 0;y < height;y++) + { + if (state== State.None) + return; // Cancelling. + + for (int x = 0;x < width;x++) + { + Color32 col = pixelData[pixelIdx++]; + uint colMask = ((uint)col.g<<16) | ((uint)col.b<<8) | (col.a); // Color is stored as ARGB, so for this Color32 struct becomes g,b,a ... because we want RGB. + if (cachedMask!=colMask || layerIndex==254) + { + if (builder.pixelToLayerIndex.TryGetValue( colMask, out byte queriedLayeredIndex )) + { + layerIndex = queriedLayeredIndex; + } + else layerIndex = 255; + cachedMask = colMask; + } + + controlData[of + x] = layerIndex; + } + + of += builder.controlResolution; + } + } + + void FilterControlTexture() + { + var res = builder.controlResolution; + controlData.FilterFloatData( res, res, 255, 0, 5 ); + } + + string GetMapUrl( int tileX, int tileY, int zoom ) + { + // $"https://service.pdok.nl/lv/bgt/wmts/v1_0/achtergrondvisualisatie/EPSG:28992/{zoom}/{tileX}/{tileY}.png"; + var url = $"https://service.pdok.nl/lv/bgt/wmts/v1_0/standaardvisualisatie/EPSG:28992/{zoom}/{tileX}/{tileY}.png"; + return url; + } + + void DownloadTiles() + { + requests = new List<MapTile>(); + for (int x = 0;x < builder.nTilesWide;x++) + { + for (int y = 0;y < builder.nTilesHigh;y++) + { + int tileX = x + tileOrigin.x; + int tileY = -y + tileOrigin.y; + string url = GetMapUrl(tileX, tileY, builder.zoom); + UnityWebRequest www = UnityWebRequestTexture.GetTexture(url); + www.SendWebRequest(); + MapTile td = new MapTile(); + td.www = www; + td.deltaTileX = x; + td.deltaTileY = y; + requests.Add( td ); + } + } + } + + void DownloadHeight() + { + if (!builder.loadHeight) + return; + var url = GeoTiffHeight.BuildPDOKWCSUrl( rdOrigin.x, rdOrigin.x+builder.adjustedSize.x, rdOrigin.y, rdOrigin.y+builder.adjustedSize.y, builder.heightMapResolution, builder.heightMapResolution ); + asyncHeightHandle = GeoTiffHeight.LoadFromUrl( url, true ); + } + + void CheckTerrainRequests() + { + if (requests==null) + return; + + for (int i = 0;i < requests.Count;i++) + { + var tile = requests[i]; + var www = tile.www; + if (www.isDone) + { + if (www.result != UnityWebRequest.Result.Success) + { + Debug.Log( www.error ); + if (tile.numRetries < builder.numDLRetries) + { + tile.numRetries++; + tile.www = UnityWebRequestTexture.GetTexture( www.url ); + tile.www.SendWebRequest(); + requests[i] = tile; + } + else + { + requests.RemoveAt( i ); + i--; + } + } + else + { + var texture = ((DownloadHandlerTexture)www.downloadHandler).texture; + var height = texture.height; + var width = texture.width; + var pixelData = texture.GetPixelData<Color32>( 0 ); // Must be called on Main thread. + var writeControlTextureTask = Task.Run( () => WriteTileToControlTexture( tile, width, height, pixelData ) ); + writeControlTextureDataTasks.Add( writeControlTextureTask ); + requests.RemoveAt( i ); + i--; + } + } + } + } + + Texture2D CreateControlTexture() + { + if (terrain == null) + return null; // Can be deleted in scene. + var controlTexture = new Texture2D( builder.controlResolution, builder.controlResolution, TextureFormat.R8, false ); + terrain.materialTemplate.SetTexture( "ControlTexture", controlTexture ); + controlTexture.SetPixelData( controlData, 0, 0 ); + controlTexture.Apply( false, false ); + return controlTexture; + } + + float[,] CreateHeightMap( HeightData heightData ) + { + var heights = new float[builder.heightMapResolution, builder.heightMapResolution]; + int height = heightData.height; + for (int y = 0;y<height;y++) + { + if (state== State.None) + return null; // Cancelling. + + for (int x = 0;x<heightData.width;x++) + { + float absHeight = heightData.data[y*heightData.width+x]; + // absHeight += terrainHeight*0.5f; + absHeight /= builder.terrainHeight; + heights[height-y-1, x] = absHeight; + } + } + return heights; + } + + void DoFinalSteps( float[,] heightData ) + { + if ( heightData != null && terrainData != null ) + { + terrainData.SetHeights( 0, 0, heightData ); + } + var controlTex = CreateControlTexture(); + SaveToDisk( controlTex ); + CancelAndClean(); + } + +#if UNITY_EDITOR + void SaveToDisk( Texture2D controlTexture ) + { + if (controlTexture == null) + return; + var generatedContent = $"BGT_terrain_{builder.originWGS84.x}_{builder.originWGS84.y}"; + if (!Directory.Exists( Path.Combine( Application.dataPath, generatedContent ) )) + { + Directory.CreateDirectory( Path.Combine( Application.dataPath, generatedContent ) ); + } + AssetDatabase.CreateAsset( terrainData, Path.Combine( "Assets", generatedContent, $"bgt_terrain_{terrainOffset.x}_{terrainOffset.y}.asset" ) ); + AssetDatabase.CreateAsset( controlTexture, Path.Combine( "Assets", generatedContent, $"control_texture_{terrainOffset.x}_{terrainOffset.y}.asset" ) ); + } +#endif + + } + +#if UNITY_EDITOR + [CustomEditor( typeof( TerrainTile ) )] + [InitializeOnLoad] + public class TerrainTileEditor : Editor + { + public override void OnInspectorGUI() + { + //TerrainTile tile = (TerrainTile)target; + DrawDefaultInspector(); + } + } + +#endif +} \ No newline at end of file diff --git a/Runtime/TerrainTile.cs.meta b/Runtime/TerrainTile.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3989ec354a72eccdd857870abe362210f50d7d78 --- /dev/null +++ b/Runtime/TerrainTile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc82087c57cb8dc4fa5b4eafe4365846 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: