Переглянути джерело

Added voxel terrain editor tools

James Peret 2 роки тому
батько
коміт
0816104c08

+ 155 - 0
Runtime/ChunkTerrainCullingSystem.cs

@@ -0,0 +1,155 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+using KairoEngine.Chunks;
+
+namespace KairoEngine.TerrainEngine
+{
+    #if UNITY_EDITOR
+    [UnityEditor.InitializeOnLoad]
+    #endif
+    [HideMonoScript]
+    public class ChunkTerrainCullingSystem : MonoBehaviour
+    {
+        public ChunkTerrainGenerator chunkTerrainGenerator;
+        public int maxChunks = 512;
+        public float destroyDistanceModifier = 3f;
+        
+        public List<CullingTarget> targets;
+
+        private void Start()
+        {
+            chunkTerrainGenerator.DestroyTerrain();
+            chunkTerrainGenerator.Initialize();
+            for (int i = 0; i < targets.Count; i++) targets[i].lastPosition = new Vector3();
+        }
+
+        private void Update()
+        {
+            CullChunks();
+        }
+
+        [ShowInInspector] private bool cullObjectsInEditor = false;
+
+        [Button("Cull Chunks")]
+        private void EditorCulling()
+        {
+            cullObjectsInEditor = !cullObjectsInEditor;
+            if(cullObjectsInEditor) 
+            {
+                chunkTerrainGenerator.DestroyTerrain();
+                chunkTerrainGenerator.Initialize();
+                UnityEditor.EditorApplication.update += CullChunks;
+                for (int i = 0; i < targets.Count; i++) targets[i].lastPosition = new Vector3();
+                CullChunks();
+            }
+            else UnityEditor.EditorApplication.update -= CullChunks;
+        }
+
+        
+        public void CullChunks()
+        {
+            if(chunkTerrainGenerator == null) return;
+            if(!chunkTerrainGenerator.IsInitialized()) return;
+            for (int i = 0; i < targets.Count; i++)
+            {
+                Vector3 chunkPos = chunkTerrainGenerator.chunkSystem.GetChunkGlobalPositionFromGlobalPosition(targets[i].target.position);
+                if(!chunkPos.Equals(targets[i].lastPosition))
+                {
+                    chunkTerrainGenerator.ClearChunkDataBuffer();
+                    targets[i].chunkPositions.Clear();
+                    targets[i].chunkPositions.InsertRange(0, GetChunkPositions(targets[i].radius, chunkPos));
+                    for (int a = 0; a < targets[i].chunkPositions.Count; a++)
+                    {
+                        chunkTerrainGenerator.GenerateChunk(targets[i].chunkPositions[a]);
+                    }
+                    
+                }
+                targets[i].lastPosition = chunkPos;
+            }
+            //DestroyChunks();
+            #if UNITY_EDITOR
+                chunkTerrainGenerator.UpdateEditor ();
+            #endif
+        }
+
+        private List<Vector3> GetChunkPositions(int radius, Vector3 origin)
+        {
+            List<Vector2Int> points = new List<Vector2Int>();
+            for (int i = 1; i < radius; i++)
+            {
+                for (int j = 1; j < radius; j++)
+                {
+                    if (i * i + j * j < (radius * radius))
+                    {
+                        points.Add(new Vector2Int(i,j));
+                        points.Add(new Vector2Int(-i,j));
+                        points.Add(new Vector2Int(i,-j));
+                        points.Add(new Vector2Int(-i,-j));
+                        points.Add(new Vector2Int(i, 0));
+                        points.Add(new Vector2Int(-i, 0));
+                        points.Add(new Vector2Int(0, j));
+                        points.Add(new Vector2Int(0, -j));
+                    }
+                }
+            }
+            //Debug.Log($"Found {points.Count} points");
+            List<Vector3> chunkPositions = new List<Vector3>();
+            chunkPositions.Add(origin);
+            var voxelSize = chunkTerrainGenerator.voxelSize;
+            var chunkSize = chunkTerrainGenerator.chunkSize;
+            for (int i = 0; i < points.Count; i++)
+            {
+                for (int j = 0; j < chunkTerrainGenerator.chunkLimit.y; j++)
+                {
+                    Vector3 pos = new Vector3(points[i].x * chunkSize.x * voxelSize.x, j * chunkSize.y * voxelSize.y, points[i].y * chunkSize.z * voxelSize.z);
+                    pos += origin;
+                    chunkPositions.Add(pos);
+                }
+            }
+            //Debug.Log($"Found {chunkPositions.Count} chunk positions");
+            return chunkPositions;
+        }
+
+        public void DestroyChunks()
+        {
+            int chunkCount = chunkTerrainGenerator.chunkSystem.chunkList.Count;
+            if(chunkCount < maxChunks) return;
+            int destroyCount = chunkCount - maxChunks;
+            Vector3 chunkSize = chunkTerrainGenerator.chunkSize;
+            Vector3 voxelSize = chunkTerrainGenerator.voxelSize;
+            Vector3 size = new Vector3(chunkSize.x * voxelSize.x, chunkSize.y * voxelSize.y, chunkSize.z * voxelSize.z);
+            List<Vector3> destroyList = new List<Vector3>(); 
+            for (int i = 0; i < chunkCount; i++)
+            {
+                Vector3 chunkPos = chunkTerrainGenerator.chunkSystem.chunkList[i];
+                List<bool> values = new List<bool>();
+                for (int a = 0; a < targets.Count; a++)
+                {
+                    Vector3 targetPos = targets[a].target.position;
+                    Vector3 pos = targetPos + (size * targets[a].radius * destroyDistanceModifier);
+                    pos.y = targetPos.y;
+                    if(Vector3.Distance(targetPos, chunkPos) > Vector3.Distance(targetPos, pos)) values.Add(true);
+                }
+                if(values.Count == targets.Count) destroyList.Add(chunkPos);
+                if(destroyList.Count >= destroyCount) break;
+            }
+            for (int i = 0; i < destroyList.Count; i++)
+            {
+                chunkTerrainGenerator.DestroyChunk(destroyList[i]);
+            }
+        }
+
+        [System.Serializable]
+        public class CullingTarget
+        {
+            [HorizontalGroup("Target", 0.8f), HideLabel] public Transform target;
+            [HorizontalGroup("Target"), HideLabel, Range(0, 16)] public int radius;
+            public Vector3 lastPosition;
+            public List<Vector3> chunkPositions;
+        }
+
+    }
+}
+

+ 11 - 0
Runtime/ChunkTerrainCullingSystem.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 51112fbc16405824ba0ecb329be13c38
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 254 - 10
Runtime/ChunkTerrainGenerator.cs

@@ -5,6 +5,7 @@ using KairoEngine.Core;
 using KairoEngine.Chunks;
 using KairoEngine.NoiseUtilities;
 using KairoEngine.UniverseGenerator;
+using Utilities = KairoEngine.Utility.Utilities;
 using Sirenix.OdinInspector;
 using UniRx;
 
@@ -18,13 +19,30 @@ namespace KairoEngine.TerrainEngine
         public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
         public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
         public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
+        public int dataProcessLimit = 4;
+        public int parallelMeshJobs = 4;
+        public Dictionary<Vector3, GameObject> terrainMeshes = new Dictionary<Vector3, GameObject>();
+
         //public float noiseMultiplier = 1.5f;
         [InlineEditor(InlineEditorObjectFieldModes.Foldout)] public PlanetTemplate planetTemplate;
 
         public ChunkSystem<BlockBase> chunkSystem;
 
-        [Button("Generate"), ButtonGroup("Buttons")]
-        void CreateChunks()
+        private List<Vector3> chunkDataBuffer = new List<Vector3>(); // Store list of chunks for populating with data
+        private List<Vector3> chunkMeshBuffer = new List<Vector3>(); // Store list of chunks for generating the mesh
+
+        private bool initialized = false;
+
+        public void Initialize ()
+        {
+            chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
+            initialized = true;
+        }
+
+        public bool IsInitialized() => initialized;
+
+        [Button("Generate World"), ButtonGroup("Buttons")]
+        void GenerateWorld()
         {
             if(planetTemplate == null)
             {
@@ -33,14 +51,14 @@ namespace KairoEngine.TerrainEngine
             }
             DestroyTerrain();
             Debug.Log("Starting chunk generation");
-            chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
+            Initialize();
             for (int x = 0; x < chunkLimit.x; x++) {
                 for (int z = 0; z < chunkLimit.z; z++) {
                     for (int y = 0; y < chunkLimit.y; y++) {
                         Vector3 chunkGlobalPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
                         Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
                         chunkSystem.CreateChunk(chunkGlobalPos, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
-                        GenerateChunkTerrain(chunkGlobalPos);
+                        PopulateChunkData(chunkGlobalPos);
                     }
                 }
             }
@@ -49,7 +67,7 @@ namespace KairoEngine.TerrainEngine
                     for (int y = 0; y < chunkLimit.y; y++) {
                         //Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
                         Vector3 chunkPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
-                        if(marchingCubes3 != null) marchingCubes3.Generate(chunkSystem, chunkPos);
+                        GenerateTerrainMesh(chunkPos);
                         //else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
                     }
                 }
@@ -59,7 +77,26 @@ namespace KairoEngine.TerrainEngine
             #endif
         }
 
-        void GenerateChunkTerrain(Vector3 initialPos)
+        public void GenerateChunk(Vector3 position)
+        {
+            position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
+            bool buffered = false;
+            for (int i = 0; i < chunkDataBuffer.Count; i++)
+            {
+                if(chunkDataBuffer[i] == position) buffered = true;
+            }
+            for (int i = 0; i < chunkMeshBuffer.Count; i++)
+            {
+                if(chunkMeshBuffer[i] == position) buffered = true;
+            }
+            //if(chunkSystem.ChunkExists(position)) buffered = true;
+            //bool meshGenerated = terrainMeshes.TryGetValue(position, out GameObject obj);
+            //if(meshGenerated) buffered = true;
+            if(buffered) return;
+            chunkDataBuffer.Add(position);
+        }
+
+        void PopulateChunkData(Vector3 initialPos)
         {
             float width = (float)(chunkLimit.x * chunkSize.x * voxelSize.x);
             float height = (float)(chunkLimit.y * chunkSize.y * voxelSize.y);
@@ -74,26 +111,140 @@ namespace KairoEngine.TerrainEngine
                         float elevation = planetTemplate.SamplePoint((float)pos.x / width, (float)pos.z / length);
                         float thisHeight = height * elevation;
                         uint code = planetTemplate.SamplePointCode((float)pos.x / width, (float)pos.z / length, elevation);
-                        BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight / voxelSize.y));
+                        BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight * 1000));
                         chunkSystem.SetBlock(pos, block);
                     }
                 }
             }
         }
 
-        void UpdateEditor ()
+        public void GenerateChunkData(Vector3 position)
+        {
+            position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
+            Vector3Int chunkPos = chunkSystem.GetChunkPositionFromGlobalPosition(position);
+            if(!chunkSystem.ChunkExists(position))
+            {
+                //Debug.Log($"Generating Terrain Data ({position} | {chunkPos})");
+                chunkSystem.CreateChunk(position, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
+                PopulateChunkData(position);
+            }
+        }
+
+        public void GenerateTerrainMesh(Vector3 position, bool overwrite = false)
+        {
+            bool meshExists = terrainMeshes.TryGetValue(position, out GameObject obj);
+            if(meshExists && !overwrite) return;
+            if(!overwrite) terrainMeshes.Add(position, null);
+            if(marchingCubes3 != null) marchingCubes3.Generate(chunkSystem, this, position);
+        }
+
+        public void UpdateGenerator()
+        {
+            if(marchingCubes3.GetJobCount() == 0) ProcessChunkDataBuffer();
+            ProcessChunkMeshBuffer();
+        }
+
+        public void UpdateEditor ()
         {
             Timer.ExecuteRealTime(50, () => {
                 if(marchingCubes3 != null)
                 {
-                    marchingCubes3.UpdateFinishedJobs();
+                    UpdateGenerator();
                     if(!marchingCubes3.IsGeneratorDone()) UpdateEditor();
                 }
             });
         }
 
+        private void ProcessChunkDataBuffer()
+        {
+            int limit = chunkDataBuffer.Count < dataProcessLimit ? chunkDataBuffer.Count : dataProcessLimit;
+            for (int i = 0; i < limit; i++)
+            {
+                Vector3 position = chunkDataBuffer[i];
+                List<Vector3> positions = new List<Vector3>{
+                    new Vector3( 0,  0,  0),
+                    new Vector3( 1,  0,  0),
+                    new Vector3( 1,  0, -1),
+                    new Vector3( 0,  0, -1),
+                    new Vector3(-1,  0, -1),
+                    new Vector3(-1,  0,  0),
+                    new Vector3(-1,  0,  1),
+                    new Vector3( 0,  0,  1),
+                    new Vector3( 1,  0,  1),
+                    new Vector3( 0,  1,  0),
+                    new Vector3( 1,  1,  0),
+                    new Vector3( 1,  1, -1),
+                    new Vector3( 0,  1, -1),
+                    new Vector3(-1,  1, -1),
+                    new Vector3(-1,  1,  0),
+                    new Vector3(-1,  1,  1),
+                    new Vector3( 0,  1,  1),
+                    new Vector3( 1,  1,  1),
+                    new Vector3( 0, -1,  0),
+                    new Vector3( 1, -1,  0),
+                    new Vector3( 1, -1, -1),
+                    new Vector3( 0, -1, -1),
+                    new Vector3(-1, -1, -1),
+                    new Vector3(-1, -1,  0),
+                    new Vector3(-1, -1,  1),
+                    new Vector3( 0, -1,  1),
+                    new Vector3( 1, -1,  1),
+                };
+                for (int a = 0; a < positions.Count; a++)
+                {
+                    //position = new Vector3(position.x + chunkSize.x * voxelSize.x, position.y, position.z);
+                    Vector3 chunkPos = new Vector3(
+                        position.x + (positions[a].x * chunkSize.x * voxelSize.x), 
+                        position.y + (positions[a].y * chunkSize.y * voxelSize.y), 
+                        position.z + (positions[a].z * chunkSize.z * voxelSize.z)
+                    );
+                    GenerateChunkData(chunkPos);
+                }
+                chunkMeshBuffer.Add(chunkDataBuffer[i]);
+            }
+            chunkDataBuffer.RemoveRange(0, limit);
+        }
+
+        private void ProcessChunkMeshBuffer()
+        {
+            marchingCubes3.UpdateFinishedJobs();
+            int limit = marchingCubes3.GetJobCount() < parallelMeshJobs ? parallelMeshJobs - marchingCubes3.GetJobCount() : 0;
+            limit = limit < chunkMeshBuffer.Count ? limit : chunkMeshBuffer.Count;
+            for (int i = 0; i < limit; i++)
+            {
+                GenerateTerrainMesh(chunkMeshBuffer[i]);
+            }
+            chunkMeshBuffer.RemoveRange(0, limit);
+        }
+
+        public void DestroyChunk(Vector3 position)
+        {
+            DestroyChunkData(position);
+            DestroyChunkMesh(position);
+        }
+
+        public void DestroyChunkData(Vector3 position)
+        {
+            chunkSystem.DestroyChunk(position);
+        }
+
+        public void DestroyChunkMesh(Vector3 position)
+        {
+            terrainMeshes.TryGetValue(position, out GameObject obj);
+            if(obj != null) 
+            {
+                Destroy(obj);
+                #if UNITY_EDITOR
+                DestroyImmediate(obj);
+                #endif
+            }
+            terrainMeshes.Remove(position);
+        }
+
+        public void ClearChunkDataBuffer() => chunkDataBuffer.Clear();
+
         [Button("Destroy"), ButtonGroup("Buttons")]
-        private void DestroyTerrain()
+        public void DestroyTerrain()
         {
             var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
             if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length - 1} terrain objects");
@@ -106,6 +257,99 @@ namespace KairoEngine.TerrainEngine
                 Destroy(childTransforms[i].gameObject);
                 #endif
             }
+            terrainMeshes = new Dictionary<Vector3, GameObject>();
+            chunkDataBuffer = new List<Vector3>();
+            chunkMeshBuffer = new List<Vector3>();
+            chunkSystem.chunks = new Dictionary<Vector3Int, Chunk<BlockBase>>();
+            chunkSystem.chunkList = new List<Vector3>();
+        }
+
+        public enum VoxelOperation
+        {
+            Add,
+            Remove,
+            Level
+        }
+        public void ChangeVoxels(Vector3 pos, uint code, VoxelOperation operation, int radius = 3)
+        {
+            int startHeigth = 0, endHeigth = 0, targetHeigth = 0, updatedVoxels = 0;
+            switch (operation)
+            {
+                case VoxelOperation.Add:
+                    startHeigth = -1;
+                    endHeigth = radius;
+                    targetHeigth = (int)(((chunkSystem.GetBlock(pos).value/1000) + (radius * voxelSize.y)) * 1000);
+                    break;
+                case VoxelOperation.Remove:
+                    startHeigth = -1 * (radius + 1);
+                    endHeigth = radius + 1;
+                    targetHeigth = (int)(Mathf.Clamp((chunkSystem.GetBlock(pos).value/1000) - (radius * voxelSize.y), 0, 10000) * 1000);
+                    break;
+                case VoxelOperation.Level:
+                    startHeigth = -1 * radius;
+                    endHeigth = radius;
+                    targetHeigth = (int)chunkSystem.GetBlock(pos).value;
+                    break;
+                default:
+                    return;
+            }
+            Vector3 chunkPosition = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(pos);
+            List<Vector2Int> points = Utilities.GetPointsInRadius(radius, true);
+            List<Vector3> chunkPositions = new List<Vector3>();
+            for (int j = 0; j < points.Count; j++)
+            {
+                for (int y = startHeigth; y < endHeigth + 1; y++)
+                {
+                    updatedVoxels += 1;
+                    Vector3 voxelPos = new Vector3(pos.x + points[j].x * voxelSize.x, pos.y + y * voxelSize.y, pos.z + points[j].y * voxelSize.z);
+                    BlockBase block = chunkSystem.GetBlock(voxelPos);
+                    block.code = code;
+                    block.value = (uint)targetHeigth;
+                    chunkSystem.SetBlock(voxelPos, block);
+                    Vector3Int localVoxelPos = chunkSystem.GetVoxelFromGlobalPosition(voxelPos);
+                    List<Vector3> chunks = new List<Vector3>();
+                    Vector3 chunkPos = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(voxelPos);
+                    chunks.Add(chunkPos);
+                    if(localVoxelPos.x == 0) chunks.Add(new Vector3(chunkPos.x - chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
+                    if(localVoxelPos.x == 16) chunks.Add(new Vector3(chunkPos.x + chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
+                    if(localVoxelPos.y == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y  - chunkSize.y * voxelSize.y, chunkPos.z));
+                    if(localVoxelPos.y == 16) chunks.Add(new Vector3(chunkPos.x, chunkPos.y + chunkSize.y * voxelSize.y, chunkPos.z));
+                    if(localVoxelPos.z == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y, chunkPos.z - chunkSize.z * voxelSize.z));
+                    if(localVoxelPos.z == 16) chunks.Add(new Vector3(chunkPos.x , chunkPos.y, chunkPos.z + chunkSize.z * voxelSize.z));
+                    for (int i = 0; i < chunks.Count; i++)
+                    {
+                        bool duplicate = false;
+                        for (int k = 0; k < chunkPositions.Count; k++)
+                        {
+                            if(chunkPositions[k].Equals(chunks[i]))
+                            {
+                                duplicate = true;
+                                break;
+                            }
+                        }
+                        if(!duplicate) chunkPositions.Add(chunks[i]);
+                    }
+                }
+            }
+            for (int c = 0; c < chunkPositions.Count; c++) GenerateTerrainMesh(chunkPositions[c], true);
+            Debug.Log($"Changing voxel at {pos}, updated {updatedVoxels} voxels and {chunkPositions.Count} chunks");
+        }
+
+        public void AddGeneratedTerrainMesh(Vector3 chunkPosition, GameObject meshObj)
+        {
+            bool meshExists = terrainMeshes.TryGetValue(chunkPosition, out GameObject currentMesh);
+            if(currentMesh != null)
+            {
+                //Debug.Log($"Updating chunk mesh at {chunkPosition}");
+                DestroyChunkMesh(chunkPosition);
+                terrainMeshes[chunkPosition] = meshObj;
+            }
+            else 
+            {
+                //Debug.Log($"Adding chunk mesh at {chunkPosition}");
+                //terrainMeshes.Add(chunkPosition, meshObj);
+                terrainMeshes[chunkPosition] = meshObj;
+            }
         }
     }
 }

+ 1 - 1
Runtime/MarchingCubes3.cs

@@ -56,7 +56,7 @@ namespace KairoEngine.TerrainEngine
         public void UpdateFinishedJobs()
         {
             //jobHandle.Complete();
-            Debug.Log("Updating finished jobs - " + jobHandles.Count);
+            //Debug.Log("Updating finished jobs - " + jobHandles.Count);
             for (int i = 0; i < jobHandles.Count; i++)
             {
                 

+ 16 - 12
Runtime/MarchingCubes4.cs

@@ -40,6 +40,7 @@ namespace KairoEngine.TerrainEngine
         private ChunkSystem<BlockBase> chunkSystem;
         private List<JobHandle> jobHandles = new List<JobHandle>();
         private List<MarchingCubesJob4> jobs = new List<MarchingCubesJob4>();
+        private ChunkTerrainGenerator chunkTerrainGenerator;
 
         private void Update()
         {
@@ -58,49 +59,52 @@ namespace KairoEngine.TerrainEngine
         public void UpdateFinishedJobs()
         {
             //jobHandle.Complete();
-            Debug.Log("Updating finished jobs - " + jobHandles.Count);
+            //Debug.Log("Updating finished jobs - " + jobHandles.Count);
             for (int i = 0; i < jobHandles.Count; i++)
             {
                 
-                if(jobHandles[i].IsCompleted)
-                {
-                    
-                }
+                if(!jobHandles[i].IsCompleted) return;
                 jobHandles[i].Complete();
-                Debug.Log($"Job {i} is completed");    
+                //Debug.Log($"Job {i} is completed");    
                 if(prefab == null) prefab = new GameObject();
                 GameObject target = Instantiate(prefab, jobs[i].initialPosition, transform.rotation, this.transform);
                 // Create vertices and triangle arrays
                 int verticeCount = jobs[i].arrayIndexes[0];
                 int trianglesCount = jobs[i].arrayIndexes[1];
-                Debug.Log($"Completed job has {jobs[i].vertices.Count()}/{verticeCount} vertices and {jobs[i].triangles.Count()}/{trianglesCount} triangles");
+                //Debug.Log($"Completed job has {jobs[i].vertices.Count()}/{verticeCount} vertices and {jobs[i].triangles.Count()}/{trianglesCount} triangles");
                 Vector3[] vertices = jobs[i].vertices.Take(verticeCount).ToArray();
                 int[] triangles = jobs[i].triangles.Take(trianglesCount).ToArray();
                 Color[] vertexColors = jobs[i].vertexColors.Take(verticeCount).ToArray();
                 BuildMesh(target, vertices, triangles, vertexColors);
+                //chunkTerrainGenerator.terrainMeshes[jobs[i].initialPosition] = target;
+                chunkTerrainGenerator.AddGeneratedTerrainMesh(jobs[i].initialPosition, target);
                 // Remove this
                 jobs[i].blocks.Dispose();
                 jobs[i].codes.Dispose();
                 jobs[i].vertices.Dispose();
                 jobs[i].triangles.Dispose();
+                jobs[i].vertexColors.Dispose();
                 jobs[i].arrayIndexes.Dispose();
                 jobs[i].triangleTable.Dispose();
                 jobs.RemoveAt(i);
                 jobHandles.RemoveAt(i);
                 i -= 1;
+                
             }
         }
 
         public bool IsGeneratorDone() => jobHandles.Count == 0 ? true : false;
+        public int GetJobCount() => jobHandles.Count;
 
-        public void Generate(ChunkSystem<BlockBase> chunkSystem, Vector3 initialPosition = new Vector3()) 
+        public void Generate(ChunkSystem<BlockBase> chunkSystem, ChunkTerrainGenerator chunkTerrainGenerator, Vector3 initialPosition = new Vector3()) 
         {
-            Debug.Log("Generating chunk " + initialPosition);
+            //Debug.Log("Generating chunk " + initialPosition);
             this.chunkSystem = chunkSystem;
             width = chunkSystem.chunkSize.x;
             length = chunkSystem.chunkSize.z;
             height = chunkSystem.chunkSize.y;
             voxelSize = chunkSystem.voxelSize;
+            this.chunkTerrainGenerator = chunkTerrainGenerator;
             //transform.tag = "Terrain";
             terrainMap = new float[width + 1, height + 1, length + 1];
             terrainCodes = new uint[width + 1, height + 1, length + 1];
@@ -120,7 +124,7 @@ namespace KairoEngine.TerrainEngine
                         Vector3 pos = initialPosition + new Vector3(x * voxelSize.x, y * voxelSize.y, z * voxelSize.z);
                         BlockBase block = chunkSystem.GetBlock(pos);
                         // Set the value of this point in the terrainMap.
-                        terrainMap[x, y, z] = (float)pos.y - ((float)block.value * voxelSize.y);
+                        terrainMap[x, y, z] = (float)pos.y - ((float)block.value/1000);
                         terrainCodes[x, y, z] = block.code;
                     }
                 }
@@ -205,7 +209,7 @@ namespace KairoEngine.TerrainEngine
         {
             verticesArrayIndex = 0;
             trianglesArrayIndex = 0;
-            Debug.Log("Executing job");
+            //Debug.Log("Executing job");
             CreateTriangleTable();
             // Loop through each "cube" in our terrain.
             for (int x = 0; x < chunkSize.x; x++) {
@@ -226,7 +230,7 @@ namespace KairoEngine.TerrainEngine
             for (int i = 0; i < 8; i++) {
                 cube[i] = SampleTerrain(position + CornerTable(i));
             }
-            Vector3 realPosition = new Vector3(position.x * voxelSize.x, position.y * voxelSize.y, position.z * voxelSize.z);
+            //Vector3 realPosition = new Vector3(position.x * voxelSize.x, position.y * voxelSize.y, position.z * voxelSize.z);
             // Get the configuration index of this cube.
             int configIndex = GetCubeConfiguration(cube);
             // If the configuration of this cube is 0 or 255 (completely inside the terrain or completely outside of it) we don't need to do anything.

+ 4 - 1
Runtime/TerrainEngine.asmdef

@@ -8,7 +8,10 @@
         "GUID:d09ad6089c871be4581e7b220db144be",
         "GUID:3ffdbd0bbde813141a8a14a897160240",
         "GUID:e0cd26848372d4e5c891c569017e11f1",
-        "GUID:560b04d1a97f54a4e82edc0cbbb69285"
+        "GUID:560b04d1a97f54a4e82edc0cbbb69285",
+        "GUID:f452697229e6bcc469c0eff1574ac090",
+        "GUID:142285d3db5e7e849b02ea3a75bc2de7",
+        "GUID:165d83fc3bb2a4144925c85421871d8e"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],

+ 8 - 0
Runtime/Tools.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 776d0223006b2c74fa70d7c216a15a65
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 28 - 0
Runtime/Tools/AddVoxelTerrainTool.cs

@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.GameTools.InteractionTools;
+using KairoEngine.UI;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.TerrainEngine
+{
+    public class AddVoxelTerrainTool : MonoBehaviour, IInteractionTool
+    {
+        public VoxelTerrainEditor voxelTerrainEditor;
+        public Sprite icon;
+
+        public void DisableTool()
+        {
+            voxelTerrainEditor.currentTool = VoxelTerrainEditor.Tool.None;
+        }
+
+        public void EnableTool()
+        {
+            voxelTerrainEditor.currentTool = VoxelTerrainEditor.Tool.Add;
+        }
+
+        public Sprite GetIcon() => icon;
+    }
+}
+

+ 11 - 0
Runtime/Tools/AddVoxelTerrainTool.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 77c2947733c14a244b5ed6cfda9d2062
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 28 - 0
Runtime/Tools/RemoveVoxelTerrainTool.cs

@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.GameTools.InteractionTools;
+using KairoEngine.UI;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.TerrainEngine
+{
+    public class RemoveVoxelTerrainTool : MonoBehaviour, IInteractionTool
+    {
+        public VoxelTerrainEditor voxelTerrainEditor;
+        public Sprite icon;
+
+        public void DisableTool()
+        {
+            voxelTerrainEditor.currentTool = VoxelTerrainEditor.Tool.None;
+        }
+
+        public void EnableTool()
+        {
+            voxelTerrainEditor.currentTool = VoxelTerrainEditor.Tool.Remove;
+        }
+
+        public Sprite GetIcon() => icon;
+    }
+}
+

+ 11 - 0
Runtime/Tools/RemoveVoxelTerrainTool.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 13e8b14e477d5c24e83fbc12ab77e015
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
Runtime/Tools/VoxelTerrainEditor.cs

@@ -0,0 +1,49 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+using Utilities = KairoEngine.Utility.Utilities;
+using KairoEngine.GameTools.InteractionTools;
+using KairoEngine.UI;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.TerrainEngine
+{
+
+    public class VoxelTerrainEditor : MonoBehaviour
+    {
+        public enum Tool
+        {
+            None,
+            Add,
+            Remove,
+            Level
+        }
+        public ChunkTerrainGenerator chunkTerrainGenerator;
+        public LayerMask layerMask;
+        public int radius = 3;
+        public Tool currentTool = Tool.None;
+        public uint code = 0;
+
+        private void Update()
+        {
+            if(currentTool == Tool.None) return;
+            if(MouseInputUIBlocker.BlockedByUI) return;
+            Vector3 pos = Utilities.GetMouseWorldPosition(layerMask);
+            switch (currentTool)
+            {
+                case Tool.Add:
+                    if(Input.GetMouseButtonDown(0)) chunkTerrainGenerator.ChangeVoxels(pos, code, ChunkTerrainGenerator.VoxelOperation.Add, radius);
+                    break;
+                case Tool.Remove:
+                    if(Input.GetMouseButtonDown(0)) chunkTerrainGenerator.ChangeVoxels(pos, code, ChunkTerrainGenerator.VoxelOperation.Remove, radius);
+                    break;
+                case Tool.Level:
+                    if(Input.GetMouseButtonDown(0)) chunkTerrainGenerator.ChangeVoxels(pos, code, ChunkTerrainGenerator.VoxelOperation.Level, radius);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Tools/VoxelTerrainEditor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 56c2215775607014b951b3d92c72d3aa
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: