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: