Przeglądaj źródła

Added simple vertex colors to Marching Cubes algorithm

James Peret 2 lat temu
rodzic
commit
587998658c
3 zmienionych plików z 129 dodań i 33 usunięć
  1. 69 15
      ChunkTerrainGenerator.cs
  2. 57 17
      MarchingCubes3.cs
  3. 3 1
      TerrainEngine.asmdef

+ 69 - 15
ChunkTerrainGenerator.cs

@@ -1,60 +1,114 @@
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
+using KairoEngine.Core;
 using KairoEngine.Chunks;
+using KairoEngine.NoiseUtilities;
 using Sirenix.OdinInspector;
+using UniRx;
 
 namespace KairoEngine.TerrainEngine
 {
+    [HideMonoScript]
     public class ChunkTerrainGenerator : MonoBehaviour
     {
         public MarchingCubes3 marchingCubes3;
         public MarchingCubes2 marchingCubes2;
         public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
-        public Vector3Int chunks = new Vector3Int(2, 1, 2);
-        public float noiseMultiplier = 1.5f;
-        public ChunkSystem<BlockBase> chunkSystem;
+        public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
+        //public float noiseMultiplier = 1.5f;
+        [InlineProperty, HideLabel, FoldoutGroup("Noise Generator")] public NoiseCombinator noiseCombinator = new NoiseCombinator();
 
-        [Button("Generate")]
+        [HideInInspector] public ChunkSystem<BlockBase> chunkSystem;
+
+        [Button("Generate"), ButtonGroup("Buttons")]
         void CreateChunks()
         {
+            if(noiseCombinator == null)
+            {
+                Debug.LogError("Missing Noise Combinator", this.gameObject);
+                return;
+            }
+            DestroyTerrain();
+            Debug.Log("Starting chunk generation");
             chunkSystem = new ChunkSystem<BlockBase>(chunkSize, true);
-            for (int x = 0; x < chunks.x; x++) {
-                for (int z = 0; z < chunks.z; z++) {
-                    for (int y = 0; y < chunks.y; y++) {
+            for (int x = 0; x < chunkLimit.x; x++) {
+                for (int z = 0; z < chunkLimit.z; z++) {
+                    for (int y = 0; y < chunkLimit.y; y++) {
                         Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
                         chunkSystem.CreateChunk(chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
                         GenerateChunkTerrain(chunkPos);
                     }
                 }
             }
-            for (int x = 0; x < chunks.x; x++) {
-                for (int z = 0; z < chunks.z; z++) {
-                    for (int y = 0; y < chunks.y; y++) {
+            for (int x = 0; x < chunkLimit.x; x++) {
+                for (int z = 0; z < chunkLimit.z; z++) {
+                    for (int y = 0; y < chunkLimit.y; y++) {
                         Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
                         if(marchingCubes3 != null) marchingCubes3.Generate(chunkSystem, chunkPos);
                         else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
                     }
                 }
             }
+            #if UNITY_EDITOR
+                UpdateEditor ();
+            #endif
         }
 
         void GenerateChunkTerrain(Vector3Int initialPos)
         {
+            float width = (float)(chunkLimit.x * chunkSize.x);
+            float height = (float)(chunkLimit.y * chunkSize.y);
+            float length = (float)(chunkLimit.z * chunkSize.z);
             for (int x = 0; x < chunkSize.x; x++) {
                 for (int z = 0; z < chunkSize.z; z++) {
                     for (int y = 0; y < chunkSize.y; y++) {
                         // Get a terrain height using regular old Perlin noise.
                         Vector3Int pos = initialPos + new Vector3Int(x, y, z);
-                        float width = (float)(chunks.x * chunkSize.x);
-                        float height = (float)(chunks.y * chunkSize.y);
-                        float length = (float)(chunks.z * chunkSize.z);;
-                        float thisHeight = height * Mathf.PerlinNoise((float)pos.x / width * noiseMultiplier + 0.001f, (float)pos.z / length * noiseMultiplier + 0.001f);
-                        BlockBase block = new BlockBase(0, (uint)Mathf.FloorToInt(thisHeight * 1000));
+                        // Old example noise:
+                        //float thisHeight = height * Mathf.PerlinNoise((float)pos.x / width * noiseMultiplier + 0.001f, (float)pos.z / length * noiseMultiplier + 0.001f);
+                        float thisHeight = height * noiseCombinator.SamplePoint((float)pos.x / width, (float)pos.z / length);
+                        uint code = CalculateCode(thisHeight, height);
+                        BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight));
                         chunkSystem.SetBlock(pos, block);
                     }
                 }
             }
         }
+
+        void UpdateEditor ()
+        {
+            Timer.ExecuteRealTime(50, () => {
+                if(marchingCubes3 != null)
+                {
+                    marchingCubes3.UpdateFinishedJobs();
+                    if(!marchingCubes3.IsGeneratorDone()) UpdateEditor();
+                }
+            });
+        }
+
+        [Button("Destroy"), ButtonGroup("Buttons")]
+        private void DestroyTerrain()
+        {
+            var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
+            if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length - 1} terrain objects");
+            for (int i = 0; i < childTransforms.Length; i++)
+            {
+                if(childTransforms[i] == this.transform) continue;
+                #if UNITY_EDITOR
+                DestroyImmediate(childTransforms[i].gameObject);
+                #else
+                Destroy(childTransforms[i].gameObject);
+                #endif
+            }
+        }
+
+        private uint CalculateCode(float height, float maxHeight)
+        {
+            var value = height/maxHeight;
+            if(value > 0.8) return 2;
+            else if(value > 0.4) return 1;
+            else return 0;
+        }
     }
 }

+ 57 - 17
MarchingCubes3.cs

@@ -30,6 +30,7 @@ namespace KairoEngine.TerrainEngine
         int height = 8;
 
         float[,,] terrainMap;
+        uint[,,] terrainCodes;
 
         List<Vector3> vertices = new List<Vector3>();
         List<int> triangles = new List<int>();
@@ -39,8 +40,23 @@ namespace KairoEngine.TerrainEngine
         private List<MarchingCubesJob> jobs = new List<MarchingCubesJob>();
 
         private void Update()
+        {
+            UpdateFinishedJobs();
+        }
+
+        public void OnDestroy()
+        {
+            // Make sure we run our jobs to completion before exiting.
+            for (int i = 0; i < jobHandles.Count; i++)
+            {
+                jobHandles[i].Complete();
+            }
+        }
+
+        public void UpdateFinishedJobs()
         {
             //jobHandle.Complete();
+            Debug.Log("Updating finished jobs - " + jobHandles.Count);
             for (int i = 0; i < jobHandles.Count; i++)
             {
                 
@@ -58,9 +74,11 @@ namespace KairoEngine.TerrainEngine
                 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();
-                BuildMesh(target, vertices, triangles);
+                Color[] vertexColors = jobs[i].vertexColors.Take(verticeCount).ToArray();
+                BuildMesh(target, vertices, triangles, vertexColors);
                 // Remove this
                 jobs[i].blocks.Dispose();
+                jobs[i].codes.Dispose();
                 jobs[i].vertices.Dispose();
                 jobs[i].triangles.Dispose();
                 jobs[i].arrayIndexes.Dispose();
@@ -70,47 +88,44 @@ namespace KairoEngine.TerrainEngine
             }
         }
 
-        public void OnDestroy()
-        {
-            // Make sure we run our jobs to completion before exiting.
-            for (int i = 0; i < jobHandles.Count; i++)
-            {
-                jobHandles[i].Complete();
-            }
-        }
+        public bool IsGeneratorDone() => jobHandles.Count == 0 ? true : false;
 
         public void Generate(ChunkSystem<BlockBase> chunkSystem, Vector3Int initialPosition = new Vector3Int()) 
         {
+            Debug.Log("Generating chunk " + initialPosition);
             this.chunkSystem = chunkSystem;
             width = chunkSystem.chunkSize.x;
             length = chunkSystem.chunkSize.z;
             height = chunkSystem.chunkSize.y;
             //transform.tag = "Terrain";
             terrainMap = new float[width + 1, height + 1, length + 1];
+            terrainCodes = new uint[width + 1, height + 1, length + 1];
             PopulateTerrainMap(chunkSystem, initialPosition);
             ScheduleJob(initialPosition);
             //CreateMeshData();
             
         }
 
-        void PopulateTerrainMap (ChunkSystem<BlockBase> chunkSystem, Vector3Int initialPosition = new Vector3Int()) {
-
+        void PopulateTerrainMap (ChunkSystem<BlockBase> chunkSystem, Vector3Int initialPosition = new Vector3Int()) 
+        {
             // The data points for terrain are stored at the corners of our "cubes", so the terrainMap needs to be 1 larger
             // than the width/height of our mesh.
             for (int x = 0; x < width + 1; x++) {
                 for (int z = 0; z < length + 1; z++) {
                     for (int y = 0; y < height + 1; y++) {
-                        BlockBase block = chunkSystem.GetBlock(initialPosition + new Vector3Int(x, y, z));
+                        Vector3Int pos = initialPosition + new Vector3Int(x, y, z);
+                        BlockBase block = chunkSystem.GetBlock(pos);
                         // Set the value of this point in the terrainMap.
-                        terrainMap[x, y, z] = (float)y - (float)block.value/1000;
+                        terrainMap[x, y, z] = (float)pos.y - (float)block.value;
+                        terrainCodes[x, y, z] = block.code;
                     }
                 }
             }
         }
 
-        void BuildMesh (GameObject target, Vector3[] vertices, int[] triangles) 
+        void BuildMesh (GameObject target, Vector3[] vertices, int[] triangles, Color[] vertexColors) 
         {
-            Debug.Log($"Building mesh with {vertices.Length} vertices and {triangles.Length} triangles");
+            //Debug.Log($"Building mesh with {vertices.Length} vertices and {triangles.Length} triangles");
             MeshRenderer meshRederer = target.GetComponent<MeshRenderer>();
             if(meshRederer == null) meshRederer = target.AddComponent<MeshRenderer>();
             meshRederer.sharedMaterial = material;
@@ -121,6 +136,7 @@ namespace KairoEngine.TerrainEngine
             Mesh mesh = new Mesh();
             mesh.vertices = vertices;
             mesh.triangles = triangles;
+            mesh.colors = vertexColors;
             mesh.RecalculateNormals();
             meshFilter.mesh = mesh;
             meshCollider.sharedMesh = mesh;
@@ -136,8 +152,10 @@ namespace KairoEngine.TerrainEngine
                 smoothTerrain = smoothTerrain,
                 flatShaded = flatShaded,
                 blocks = new NativeArray<float>(terrainMap.Length, Allocator.TempJob),
+                codes = new NativeArray<uint>(terrainMap.Length, Allocator.TempJob),
                 vertices = new NativeArray<Vector3>(15000, Allocator.TempJob),
                 triangles = new NativeArray<int>(25000, Allocator.TempJob),
+                vertexColors = new NativeArray<Color>(15000, Allocator.TempJob),
                 arrayIndexes = new NativeArray<int>(2, Allocator.TempJob),
                 initialPosition = initialPosition
             };
@@ -146,6 +164,7 @@ namespace KairoEngine.TerrainEngine
                 for (int y = 0; y < height + 1; y++) {
                     for (int z = 0; z < length + 1; z++) {
                         marchingCubesJob.blocks[blockIndex] = terrainMap[x, y, z];
+                        marchingCubesJob.codes[blockIndex] = terrainCodes[x, y, z];
                         blockIndex += 1;
                     }
                 }
@@ -156,6 +175,7 @@ namespace KairoEngine.TerrainEngine
         }
     }
 
+    [ExecuteInEditMode]
     struct MarchingCubesJob : IJob
     {
         public Vector3Int chunkSize;
@@ -163,8 +183,10 @@ namespace KairoEngine.TerrainEngine
         public bool smoothTerrain;
         public bool flatShaded;
         public NativeArray<float> blocks;
+        public NativeArray<uint> codes;
         public NativeArray<Vector3> vertices;
         public NativeArray<int> triangles;
+        public NativeArray<Color> vertexColors;
         public Vector3Int initialPosition;
         public NativeArray<int> arrayIndexes; 
 
@@ -235,12 +257,13 @@ namespace KairoEngine.TerrainEngine
                     {
                         vertices[verticesArrayIndex] = vertPosition;
                         triangles[trianglesArrayIndex] = verticesArrayIndex;
+                        vertexColors[verticesArrayIndex] = SampleTerrainColor(position);
                         verticesArrayIndex += 1;
                         trianglesArrayIndex += 1;
                     } 
                     else 
                     {
-                        triangles[trianglesArrayIndex] = VertForIndice(vertPosition);
+                        triangles[trianglesArrayIndex] = VertForIndice(vertPosition, position);
                         trianglesArrayIndex += 1;
                     }
                     edgeIndex++;
@@ -255,6 +278,22 @@ namespace KairoEngine.TerrainEngine
             return blocks[index];
         }
 
+        Color SampleTerrainColor (Vector3Int point) 
+        {
+            int index = (((chunkSize.y + 1) * (chunkSize.z + 1)) * point.x) + ((chunkSize.z + 1) * point.y) + point.z;
+            uint code = codes[index];
+            switch (code)
+            {
+                default:
+                case 0:
+                    return Color.red;
+                case 1:
+                    return Color.green;
+                case 2: 
+                    return Color.blue;
+            } 
+        }
+
         int GetCubeConfiguration (float[] cube) 
         {
             // Starting with a configuration of zero, loop through each point in the cube and check if it is below the terrain surface.
@@ -268,7 +307,7 @@ namespace KairoEngine.TerrainEngine
             return configurationIndex;
         }
         
-        int VertForIndice (Vector3 vert) 
+        int VertForIndice (Vector3 vert, Vector3Int point) 
         {
             // Loop through all the vertices currently in the vertices list.
             for (int i = 0; i < verticesArrayIndex; i++) 
@@ -278,6 +317,7 @@ namespace KairoEngine.TerrainEngine
             }
             // If we didn't find a match, add this vert to the list and return last index.
             vertices[verticesArrayIndex] = vert;
+            vertexColors[verticesArrayIndex] = SampleTerrainColor(point);
             verticesArrayIndex += 1;
             return verticesArrayIndex - 1;
         }

+ 3 - 1
TerrainEngine.asmdef

@@ -5,7 +5,9 @@
         "GUID:e048eeec9bdb9d30448017b829deb3f6",
         "GUID:7e5ae6a38d1532248b4c890eca668b06",
         "GUID:22b3fea425824d6448cb89ed3c76c9a8",
-        "GUID:e0cd26848372d4e5c891c569017e11f1"
+        "GUID:d09ad6089c871be4581e7b220db144be",
+        "GUID:e0cd26848372d4e5c891c569017e11f1",
+        "GUID:560b04d1a97f54a4e82edc0cbbb69285"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],