123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using NavMeshSurface = UnityEngine.AI.NavMeshSurface;
- using KairoEngine.Core;
- using KairoEngine.Chunks;
- using KairoEngine.NoiseUtilities;
- using Utilities = KairoEngine.Utility.Utilities;
- using Sirenix.OdinInspector;
- using UniRx;
- namespace KairoEngine.TerrainEngine
- {
- [HideMonoScript]
- public class ChunkTerrainGenerator : MonoBehaviour
- {
- [BoxGroup("Configurations")] public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
- [BoxGroup("Configurations")] public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
- [BoxGroup("Configurations")] public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
- [BoxGroup("Configurations")] public StartConfig startConfiguration = StartConfig.DoNothing;
- [BoxGroup("Configurations"), ShowIf("@startConfiguration == StartConfig.WaitForEvent")] public string generateWorldEvent = "GenerateWorld";
- [BoxGroup("Configurations")] public int dataProcessLimit = 4;
- [BoxGroup("Configurations")] public int parallelMeshJobs = 4;
- [HorizontalGroup("Configurations/NavMesh")] public bool processNavMesh = false;
- [HorizontalGroup("Configurations/NavMesh"), ShowIf("@processNavMesh"), HideLabel] public NavMeshSurface navMeshSurface;
- [HorizontalGroup("Configurations/OnChunkGeneratedEvent")] public bool triggerOnChunkGeneratedEvent = true;
- [HorizontalGroup("Configurations/OnChunkGeneratedEvent"), ShowIf("@triggerOnChunkGeneratedEvent"), HideLabel] public string onChunkGeneratedEvent;
- [HorizontalGroup("Configurations/OnTerrainGeneratedEvent")] public bool triggerOnTerrainGeneratedEvent = true;
- [HorizontalGroup("Configurations/OnTerrainGeneratedEvent"), ShowIf("@triggerOnTerrainGeneratedEvent"), HideLabel] public string onTerrainGeneratedEvent;
- [BoxGroup("Configurations")] public bool showChunkData = false;
- [BoxGroup("Configurations")] public bool showDebug = false;
-
- [InlineProperty, HideLabel, BoxGroup("Marching Cubes")] public MarchingCubes4 marchingCubes = new();
- [BoxGroup("Map Data Generator")] public bool useRandomSeed = false;
- [BoxGroup("Map Data Generator"), InlineProperty, HideLabel, SerializeReference] public IMapDataGenerator mapDataGenerator;
-
- [ShowIf("@showChunkData"), BoxGroup("Chunk System"), InlineProperty, HideLabel] public ChunkSystem<BlockBase> chunkSystem;
-
- public Dictionary<Vector3, GameObject> terrainMeshes = new Dictionary<Vector3, GameObject>();
- [ShowInInspector] private List<Vector3> chunkDataBuffer = new List<Vector3>(); // Store list of chunks for populating with data
- [ShowInInspector] private List<Vector3> chunkMeshBuffer = new List<Vector3>(); // Store list of chunks for generating the mesh
- [ShowInInspector] private List<Vector3> chunkMeshProcessBuffer = new List<Vector3>(); // Store list of chunks that are waiting for their meshes
- private bool initialized = false;
- private bool buildingNavmesh = false;
- public class VoxelUpdateData
- {
- public Vector3 position;
- public BlockBase block;
- public VoxelUpdateData(Vector3 position, BlockBase block)
- {
- this.position = position;
- this.block = block;
- }
- }
- private Dictionary<Vector3,List<VoxelUpdateData>> chunkUpdates = new Dictionary<Vector3, List<VoxelUpdateData>>();
- private void Start()
- {
- if(startConfiguration == StartConfig.GenerateOnStart) GenerateWorld();
- else if(startConfiguration == StartConfig.WaitForEvent) GenericEvents.StartListening(generateWorldEvent, GenerateWorld);
- }
- private void Update()
- {
- marchingCubes.UpdateFinishedJobs();
- }
- private void OnDisable()
- {
- if(startConfiguration == StartConfig.WaitForEvent) GenericEvents.StopListening(generateWorldEvent, GenerateWorld);
- }
- private void OnDestroy()
- {
- marchingCubes.OnDestroy();
- }
- public void Initialize ()
- {
- chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
- initialized = true;
- if(useRandomSeed) mapDataGenerator.SetSeed(Random.Range(0, 100000));
- }
- public bool IsInitialized() => initialized;
- [Button("Generate World"), ButtonGroup("Buttons")]
- void GenerateWorld()
- {
- if(mapDataGenerator == null)
- {
- Debug.LogError("Missing planet template", this.gameObject);
- return;
- }
- DestroyTerrain();
- Debug.Log("Starting chunk generation");
- 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));
- PopulateChunkData(chunkGlobalPos);
- }
- }
- }
- 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);
- Vector3 chunkPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
- GenerateTerrainMesh(chunkPos);
- //else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
- }
- }
- }
- #if UNITY_EDITOR
- UpdateEditor ();
- #endif
- }
- 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);
- float length = (float)(chunkLimit.z * chunkSize.z * voxelSize.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.
- Vector3 pos = initialPos + new Vector3(x * voxelSize.x, y * voxelSize.y, z * voxelSize.z);
- // Old example noise:
- //float thisHeight = height * Mathf.PerlinNoise((float)pos.x / width * noiseMultiplier + 0.001f, (float)pos.z / length * noiseMultiplier + 0.001f);
- float elevation = mapDataGenerator.SamplePoint((float)pos.x / width, (float)pos.z / length);
- float thisHeight = height * elevation;
- uint code = mapDataGenerator.SamplePointColor((float)pos.x / width, (float)pos.z / length, elevation);
- BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight * 1000));
- chunkSystem.SetBlock(pos, block);
- }
- }
- }
- chunkUpdates.TryGetValue(initialPos, out List<VoxelUpdateData> updates);
- if(updates == null) return;
- for (int i = 0; i < updates.Count; i++)
- {
- chunkSystem.SetBlock(updates[i].position, updates[i].block);
- }
- }
- 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 && obj != null) return;
- if(!overwrite) terrainMeshes.Add(position, null);
- if(marchingCubes != null)
- {
- chunkMeshProcessBuffer.Add(position);
- marchingCubes.Generate(chunkSystem, this, position);
- }
- }
- public void UpdateGenerator()
- {
- if(marchingCubes.GetJobCount() == 0) ProcessChunkDataBuffer();
- ProcessChunkMeshBuffer();
- }
- public void UpdateEditor ()
- {
- Timer.ExecuteRealTime(50, () => {
- if(marchingCubes != null)
- {
- UpdateGenerator();
- if(!marchingCubes.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()
- {
- marchingCubes.UpdateFinishedJobs();
- int limit = marchingCubes.GetJobCount() < parallelMeshJobs ? parallelMeshJobs - marchingCubes.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")]
- public void DestroyTerrain()
- {
- var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
- if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length} 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
- }
- 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);
- // Get positions
- Vector3Int localVoxelPos = chunkSystem.GetVoxelFromGlobalPosition(voxelPos);
- Vector3 chunkPos = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(voxelPos);
- // Save data update
- VoxelUpdateData updateData = new VoxelUpdateData(voxelPos, block);
- chunkUpdates.TryGetValue(chunkPos, out List<VoxelUpdateData> chunkUpdate);
- if(chunkUpdate != null)
- {
- bool exists = false;
- for (int h = 0; h < chunkUpdate.Count; h++)
- {
- if(chunkUpdate[h].position.Equals(updateData.position))
- {
- exists = true;
- chunkUpdate[h].block = updateData.block;
- }
- }
- if(!exists) chunkUpdate.Add(updateData);
- }
- else chunkUpdates.Add(chunkPos, new List<VoxelUpdateData>{updateData});
- // Check if changed voxels are in the edge of the chunk and if true add the neightboring chunk to the update list
- List<Vector3> chunks = new List<Vector3>();
- 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)
- {
- chunkMeshProcessBuffer.Remove(chunkPosition);
- 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;
- }
- if(triggerOnChunkGeneratedEvent) GenericEvents.Trigger(onChunkGeneratedEvent, chunkPosition);
- if(!buildingNavmesh && chunkMeshProcessBuffer.Count == 0 && chunkMeshBuffer.Count == 0 && chunkDataBuffer.Count == 0)
- {
- buildingNavmesh = true;
- Timer.ExecuteRealTime(50, () => {
- if(processNavMesh && navMeshSurface != null)
- {
- var cron = new Cronometer();
- navMeshSurface.BuildNavMesh();
- //navMeshSurface.UpdateNavMesh(navMeshSurface.navMeshData);
- cron.Stop();
- if(showDebug) Debug.Log($"Generated Navmesh in {cron.Print()}");
- buildingNavmesh = false;
- }
- if(triggerOnTerrainGeneratedEvent) GenericEvents.Trigger(onTerrainGeneratedEvent);
- });
- }
- }
- [Button("Save"), ButtonGroup("Buttons")]
- public void SaveChunkUpdateData()
- {
- }
- public enum StartConfig
- {
- DoNothing,
- GenerateOnStart,
- WaitForEvent
- }
- }
- }
|