ChunkTerrainGenerator.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using NavMeshSurface = UnityEngine.AI.NavMeshSurface;
  5. using KairoEngine.Core;
  6. using KairoEngine.Chunks;
  7. using KairoEngine.NoiseUtilities;
  8. using Utilities = KairoEngine.Utility.Utilities;
  9. using Sirenix.OdinInspector;
  10. using UniRx;
  11. namespace KairoEngine.TerrainEngine
  12. {
  13. [HideMonoScript]
  14. public class ChunkTerrainGenerator : MonoBehaviour
  15. {
  16. [BoxGroup("Configurations")] public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
  17. [BoxGroup("Configurations")] public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
  18. [BoxGroup("Configurations")] public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
  19. [BoxGroup("Configurations")] public StartConfig startConfiguration = StartConfig.DoNothing;
  20. [BoxGroup("Configurations"), ShowIf("@startConfiguration == StartConfig.WaitForEvent")] public string generateWorldEvent = "GenerateWorld";
  21. [BoxGroup("Configurations")] public int dataProcessLimit = 4;
  22. [BoxGroup("Configurations")] public int parallelMeshJobs = 4;
  23. [HorizontalGroup("Configurations/NavMesh")] public bool processNavMesh = false;
  24. [HorizontalGroup("Configurations/NavMesh"), ShowIf("@processNavMesh"), HideLabel] public NavMeshSurface navMeshSurface;
  25. [HorizontalGroup("Configurations/OnChunkGeneratedEvent")] public bool triggerOnChunkGeneratedEvent = true;
  26. [HorizontalGroup("Configurations/OnChunkGeneratedEvent"), ShowIf("@triggerOnChunkGeneratedEvent"), HideLabel] public string onChunkGeneratedEvent;
  27. [HorizontalGroup("Configurations/OnTerrainGeneratedEvent")] public bool triggerOnTerrainGeneratedEvent = true;
  28. [HorizontalGroup("Configurations/OnTerrainGeneratedEvent"), ShowIf("@triggerOnTerrainGeneratedEvent"), HideLabel] public string onTerrainGeneratedEvent;
  29. [BoxGroup("Configurations")] public bool showChunkData = false;
  30. [BoxGroup("Configurations")] public bool showDebug = false;
  31. [InlineProperty, HideLabel, BoxGroup("Marching Cubes")] public MarchingCubes4 marchingCubes = new();
  32. [BoxGroup("Map Data Generator")] public bool useRandomSeed = false;
  33. [BoxGroup("Map Data Generator"), InlineProperty, HideLabel, SerializeReference] public IMapDataGenerator mapDataGenerator;
  34. [ShowIf("@showChunkData"), BoxGroup("Chunk System"), InlineProperty, HideLabel] public ChunkSystem<BlockBase> chunkSystem;
  35. public Dictionary<Vector3, GameObject> terrainMeshes = new Dictionary<Vector3, GameObject>();
  36. [ShowInInspector] private List<Vector3> chunkDataBuffer = new List<Vector3>(); // Store list of chunks for populating with data
  37. [ShowInInspector] private List<Vector3> chunkMeshBuffer = new List<Vector3>(); // Store list of chunks for generating the mesh
  38. [ShowInInspector] private List<Vector3> chunkMeshProcessBuffer = new List<Vector3>(); // Store list of chunks that are waiting for their meshes
  39. private bool initialized = false;
  40. private bool buildingNavmesh = false;
  41. public class VoxelUpdateData
  42. {
  43. public Vector3 position;
  44. public BlockBase block;
  45. public VoxelUpdateData(Vector3 position, BlockBase block)
  46. {
  47. this.position = position;
  48. this.block = block;
  49. }
  50. }
  51. private Dictionary<Vector3,List<VoxelUpdateData>> chunkUpdates = new Dictionary<Vector3, List<VoxelUpdateData>>();
  52. private void Start()
  53. {
  54. if(startConfiguration == StartConfig.GenerateOnStart) GenerateWorld();
  55. else if(startConfiguration == StartConfig.WaitForEvent) GenericEvents.StartListening(generateWorldEvent, GenerateWorld);
  56. }
  57. private void Update()
  58. {
  59. marchingCubes.UpdateFinishedJobs();
  60. }
  61. private void OnDisable()
  62. {
  63. if(startConfiguration == StartConfig.WaitForEvent) GenericEvents.StopListening(generateWorldEvent, GenerateWorld);
  64. }
  65. private void OnDestroy()
  66. {
  67. marchingCubes.OnDestroy();
  68. }
  69. public void Initialize ()
  70. {
  71. chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
  72. initialized = true;
  73. if(useRandomSeed) mapDataGenerator.SetSeed(Random.Range(0, 100000));
  74. }
  75. public bool IsInitialized() => initialized;
  76. [Button("Generate World"), ButtonGroup("Buttons")]
  77. void GenerateWorld()
  78. {
  79. if(mapDataGenerator == null)
  80. {
  81. Debug.LogError("Missing planet template", this.gameObject);
  82. return;
  83. }
  84. DestroyTerrain();
  85. Debug.Log("Starting chunk generation");
  86. Initialize();
  87. for (int x = 0; x < chunkLimit.x; x++) {
  88. for (int z = 0; z < chunkLimit.z; z++) {
  89. for (int y = 0; y < chunkLimit.y; y++) {
  90. Vector3 chunkGlobalPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
  91. Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
  92. chunkSystem.CreateChunk(chunkGlobalPos, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
  93. PopulateChunkData(chunkGlobalPos);
  94. }
  95. }
  96. }
  97. for (int x = 0; x < chunkLimit.x; x++) {
  98. for (int z = 0; z < chunkLimit.z; z++) {
  99. for (int y = 0; y < chunkLimit.y; y++) {
  100. //Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
  101. Vector3 chunkPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
  102. GenerateTerrainMesh(chunkPos);
  103. //else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
  104. }
  105. }
  106. }
  107. #if UNITY_EDITOR
  108. UpdateEditor ();
  109. #endif
  110. }
  111. public void GenerateChunk(Vector3 position)
  112. {
  113. position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
  114. bool buffered = false;
  115. for (int i = 0; i < chunkDataBuffer.Count; i++)
  116. {
  117. if(chunkDataBuffer[i] == position) buffered = true;
  118. }
  119. for (int i = 0; i < chunkMeshBuffer.Count; i++)
  120. {
  121. if(chunkMeshBuffer[i] == position) buffered = true;
  122. }
  123. //if(chunkSystem.ChunkExists(position)) buffered = true;
  124. //bool meshGenerated = terrainMeshes.TryGetValue(position, out GameObject obj);
  125. //if(meshGenerated) buffered = true;
  126. if(buffered) return;
  127. chunkDataBuffer.Add(position);
  128. }
  129. void PopulateChunkData(Vector3 initialPos)
  130. {
  131. float width = (float)(chunkLimit.x * chunkSize.x * voxelSize.x);
  132. float height = (float)(chunkLimit.y * chunkSize.y * voxelSize.y);
  133. float length = (float)(chunkLimit.z * chunkSize.z * voxelSize.z);
  134. for (int x = 0; x < chunkSize.x; x++) {
  135. for (int z = 0; z < chunkSize.z; z++) {
  136. for (int y = 0; y < chunkSize.y; y++) {
  137. // Get a terrain height using regular old Perlin noise.
  138. Vector3 pos = initialPos + new Vector3(x * voxelSize.x, y * voxelSize.y, z * voxelSize.z);
  139. // Old example noise:
  140. //float thisHeight = height * Mathf.PerlinNoise((float)pos.x / width * noiseMultiplier + 0.001f, (float)pos.z / length * noiseMultiplier + 0.001f);
  141. float elevation = mapDataGenerator.SamplePoint((float)pos.x / width, (float)pos.z / length);
  142. float thisHeight = height * elevation;
  143. uint code = mapDataGenerator.SamplePointColor((float)pos.x / width, (float)pos.z / length, elevation);
  144. BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight * 1000));
  145. chunkSystem.SetBlock(pos, block);
  146. }
  147. }
  148. }
  149. chunkUpdates.TryGetValue(initialPos, out List<VoxelUpdateData> updates);
  150. if(updates == null) return;
  151. for (int i = 0; i < updates.Count; i++)
  152. {
  153. chunkSystem.SetBlock(updates[i].position, updates[i].block);
  154. }
  155. }
  156. public void GenerateChunkData(Vector3 position)
  157. {
  158. position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
  159. Vector3Int chunkPos = chunkSystem.GetChunkPositionFromGlobalPosition(position);
  160. if(!chunkSystem.ChunkExists(position))
  161. {
  162. //Debug.Log($"Generating Terrain Data ({position} | {chunkPos})");
  163. chunkSystem.CreateChunk(position, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
  164. PopulateChunkData(position);
  165. }
  166. }
  167. public void GenerateTerrainMesh(Vector3 position, bool overwrite = false)
  168. {
  169. bool meshExists = terrainMeshes.TryGetValue(position, out GameObject obj);
  170. if(meshExists && !overwrite && obj != null) return;
  171. if(!overwrite) terrainMeshes.Add(position, null);
  172. if(marchingCubes != null)
  173. {
  174. chunkMeshProcessBuffer.Add(position);
  175. marchingCubes.Generate(chunkSystem, this, position);
  176. }
  177. }
  178. public void UpdateGenerator()
  179. {
  180. if(marchingCubes.GetJobCount() == 0) ProcessChunkDataBuffer();
  181. ProcessChunkMeshBuffer();
  182. }
  183. public void UpdateEditor ()
  184. {
  185. Timer.ExecuteRealTime(50, () => {
  186. if(marchingCubes != null)
  187. {
  188. UpdateGenerator();
  189. if(!marchingCubes.IsGeneratorDone()) UpdateEditor();
  190. }
  191. });
  192. }
  193. private void ProcessChunkDataBuffer()
  194. {
  195. int limit = chunkDataBuffer.Count < dataProcessLimit ? chunkDataBuffer.Count : dataProcessLimit;
  196. for (int i = 0; i < limit; i++)
  197. {
  198. Vector3 position = chunkDataBuffer[i];
  199. List<Vector3> positions = new List<Vector3>{
  200. new Vector3( 0, 0, 0),
  201. new Vector3( 1, 0, 0),
  202. new Vector3( 1, 0, -1),
  203. new Vector3( 0, 0, -1),
  204. new Vector3(-1, 0, -1),
  205. new Vector3(-1, 0, 0),
  206. new Vector3(-1, 0, 1),
  207. new Vector3( 0, 0, 1),
  208. new Vector3( 1, 0, 1),
  209. new Vector3( 0, 1, 0),
  210. new Vector3( 1, 1, 0),
  211. new Vector3( 1, 1, -1),
  212. new Vector3( 0, 1, -1),
  213. new Vector3(-1, 1, -1),
  214. new Vector3(-1, 1, 0),
  215. new Vector3(-1, 1, 1),
  216. new Vector3( 0, 1, 1),
  217. new Vector3( 1, 1, 1),
  218. new Vector3( 0, -1, 0),
  219. new Vector3( 1, -1, 0),
  220. new Vector3( 1, -1, -1),
  221. new Vector3( 0, -1, -1),
  222. new Vector3(-1, -1, -1),
  223. new Vector3(-1, -1, 0),
  224. new Vector3(-1, -1, 1),
  225. new Vector3( 0, -1, 1),
  226. new Vector3( 1, -1, 1),
  227. };
  228. for (int a = 0; a < positions.Count; a++)
  229. {
  230. //position = new Vector3(position.x + chunkSize.x * voxelSize.x, position.y, position.z);
  231. Vector3 chunkPos = new Vector3(
  232. position.x + (positions[a].x * chunkSize.x * voxelSize.x),
  233. position.y + (positions[a].y * chunkSize.y * voxelSize.y),
  234. position.z + (positions[a].z * chunkSize.z * voxelSize.z)
  235. );
  236. GenerateChunkData(chunkPos);
  237. }
  238. chunkMeshBuffer.Add(chunkDataBuffer[i]);
  239. }
  240. chunkDataBuffer.RemoveRange(0, limit);
  241. }
  242. private void ProcessChunkMeshBuffer()
  243. {
  244. marchingCubes.UpdateFinishedJobs();
  245. int limit = marchingCubes.GetJobCount() < parallelMeshJobs ? parallelMeshJobs - marchingCubes.GetJobCount() : 0;
  246. limit = limit < chunkMeshBuffer.Count ? limit : chunkMeshBuffer.Count;
  247. for (int i = 0; i < limit; i++)
  248. {
  249. GenerateTerrainMesh(chunkMeshBuffer[i]);
  250. }
  251. chunkMeshBuffer.RemoveRange(0, limit);
  252. }
  253. public void DestroyChunk(Vector3 position)
  254. {
  255. DestroyChunkData(position);
  256. DestroyChunkMesh(position);
  257. }
  258. public void DestroyChunkData(Vector3 position)
  259. {
  260. chunkSystem.DestroyChunk(position);
  261. }
  262. public void DestroyChunkMesh(Vector3 position)
  263. {
  264. terrainMeshes.TryGetValue(position, out GameObject obj);
  265. if(obj != null)
  266. {
  267. Destroy(obj);
  268. #if UNITY_EDITOR
  269. DestroyImmediate(obj);
  270. #endif
  271. }
  272. terrainMeshes.Remove(position);
  273. }
  274. public void ClearChunkDataBuffer() => chunkDataBuffer.Clear();
  275. [Button("Destroy"), ButtonGroup("Buttons")]
  276. public void DestroyTerrain()
  277. {
  278. var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
  279. if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length} terrain objects");
  280. for (int i = 0; i < childTransforms.Length; i++)
  281. {
  282. if(childTransforms[i] == this.transform) continue;
  283. #if UNITY_EDITOR
  284. DestroyImmediate(childTransforms[i].gameObject);
  285. #else
  286. Destroy(childTransforms[i].gameObject);
  287. #endif
  288. }
  289. terrainMeshes = new Dictionary<Vector3, GameObject>();
  290. chunkDataBuffer = new List<Vector3>();
  291. chunkMeshBuffer = new List<Vector3>();
  292. chunkSystem.chunks = new Dictionary<Vector3Int, Chunk<BlockBase>>();
  293. chunkSystem.chunkList = new List<Vector3>();
  294. }
  295. public enum VoxelOperation
  296. {
  297. Add,
  298. Remove,
  299. Level
  300. }
  301. public void ChangeVoxels(Vector3 pos, uint code, VoxelOperation operation, int radius = 3)
  302. {
  303. int startHeigth = 0, endHeigth = 0, targetHeigth = 0, updatedVoxels = 0;
  304. switch (operation)
  305. {
  306. case VoxelOperation.Add:
  307. startHeigth = -1;
  308. endHeigth = radius;
  309. targetHeigth = (int)(((chunkSystem.GetBlock(pos).value/1000) + (radius * voxelSize.y)) * 1000);
  310. break;
  311. case VoxelOperation.Remove:
  312. startHeigth = -1 * (radius + 1);
  313. endHeigth = radius + 1;
  314. targetHeigth = (int)(Mathf.Clamp((chunkSystem.GetBlock(pos).value/1000) - (radius * voxelSize.y), 0, 10000) * 1000);
  315. break;
  316. case VoxelOperation.Level:
  317. startHeigth = -1 * radius;
  318. endHeigth = radius;
  319. targetHeigth = (int)chunkSystem.GetBlock(pos).value;
  320. break;
  321. default:
  322. return;
  323. }
  324. Vector3 chunkPosition = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(pos);
  325. List<Vector2Int> points = Utilities.GetPointsInRadius(radius, true);
  326. List<Vector3> chunkPositions = new List<Vector3>();
  327. for (int j = 0; j < points.Count; j++)
  328. {
  329. for (int y = startHeigth; y < endHeigth + 1; y++)
  330. {
  331. updatedVoxels += 1;
  332. Vector3 voxelPos = new Vector3(pos.x + points[j].x * voxelSize.x, pos.y + y * voxelSize.y, pos.z + points[j].y * voxelSize.z);
  333. BlockBase block = chunkSystem.GetBlock(voxelPos);
  334. block.code = code;
  335. block.value = (uint)targetHeigth;
  336. chunkSystem.SetBlock(voxelPos, block);
  337. // Get positions
  338. Vector3Int localVoxelPos = chunkSystem.GetVoxelFromGlobalPosition(voxelPos);
  339. Vector3 chunkPos = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(voxelPos);
  340. // Save data update
  341. VoxelUpdateData updateData = new VoxelUpdateData(voxelPos, block);
  342. chunkUpdates.TryGetValue(chunkPos, out List<VoxelUpdateData> chunkUpdate);
  343. if(chunkUpdate != null)
  344. {
  345. bool exists = false;
  346. for (int h = 0; h < chunkUpdate.Count; h++)
  347. {
  348. if(chunkUpdate[h].position.Equals(updateData.position))
  349. {
  350. exists = true;
  351. chunkUpdate[h].block = updateData.block;
  352. }
  353. }
  354. if(!exists) chunkUpdate.Add(updateData);
  355. }
  356. else chunkUpdates.Add(chunkPos, new List<VoxelUpdateData>{updateData});
  357. // Check if changed voxels are in the edge of the chunk and if true add the neightboring chunk to the update list
  358. List<Vector3> chunks = new List<Vector3>();
  359. chunks.Add(chunkPos);
  360. if(localVoxelPos.x == 0) chunks.Add(new Vector3(chunkPos.x - chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
  361. if(localVoxelPos.x == 16) chunks.Add(new Vector3(chunkPos.x + chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
  362. if(localVoxelPos.y == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y - chunkSize.y * voxelSize.y, chunkPos.z));
  363. if(localVoxelPos.y == 16) chunks.Add(new Vector3(chunkPos.x, chunkPos.y + chunkSize.y * voxelSize.y, chunkPos.z));
  364. if(localVoxelPos.z == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y, chunkPos.z - chunkSize.z * voxelSize.z));
  365. if(localVoxelPos.z == 16) chunks.Add(new Vector3(chunkPos.x , chunkPos.y, chunkPos.z + chunkSize.z * voxelSize.z));
  366. for (int i = 0; i < chunks.Count; i++)
  367. {
  368. bool duplicate = false;
  369. for (int k = 0; k < chunkPositions.Count; k++)
  370. {
  371. if(chunkPositions[k].Equals(chunks[i]))
  372. {
  373. duplicate = true;
  374. break;
  375. }
  376. }
  377. if(!duplicate) chunkPositions.Add(chunks[i]);
  378. }
  379. }
  380. }
  381. for (int c = 0; c < chunkPositions.Count; c++) GenerateTerrainMesh(chunkPositions[c], true);
  382. Debug.Log($"Changing voxel at {pos}, updated {updatedVoxels} voxels and {chunkPositions.Count} chunks");
  383. }
  384. public void AddGeneratedTerrainMesh(Vector3 chunkPosition, GameObject meshObj)
  385. {
  386. chunkMeshProcessBuffer.Remove(chunkPosition);
  387. bool meshExists = terrainMeshes.TryGetValue(chunkPosition, out GameObject currentMesh);
  388. if(currentMesh != null)
  389. {
  390. //Debug.Log($"Updating chunk mesh at {chunkPosition}");
  391. DestroyChunkMesh(chunkPosition);
  392. terrainMeshes[chunkPosition] = meshObj;
  393. }
  394. else
  395. {
  396. //Debug.Log($"Adding chunk mesh at {chunkPosition}");
  397. //terrainMeshes.Add(chunkPosition, meshObj);
  398. terrainMeshes[chunkPosition] = meshObj;
  399. }
  400. if(triggerOnChunkGeneratedEvent) GenericEvents.Trigger(onChunkGeneratedEvent, chunkPosition);
  401. if(!buildingNavmesh && chunkMeshProcessBuffer.Count == 0 && chunkMeshBuffer.Count == 0 && chunkDataBuffer.Count == 0)
  402. {
  403. buildingNavmesh = true;
  404. Timer.ExecuteRealTime(50, () => {
  405. if(processNavMesh && navMeshSurface != null)
  406. {
  407. var cron = new Cronometer();
  408. navMeshSurface.BuildNavMesh();
  409. //navMeshSurface.UpdateNavMesh(navMeshSurface.navMeshData);
  410. cron.Stop();
  411. if(showDebug) Debug.Log($"Generated Navmesh in {cron.Print()}");
  412. buildingNavmesh = false;
  413. }
  414. if(triggerOnTerrainGeneratedEvent) GenericEvents.Trigger(onTerrainGeneratedEvent);
  415. });
  416. }
  417. }
  418. [Button("Save"), ButtonGroup("Buttons")]
  419. public void SaveChunkUpdateData()
  420. {
  421. }
  422. public enum StartConfig
  423. {
  424. DoNothing,
  425. GenerateOnStart,
  426. WaitForEvent
  427. }
  428. }
  429. }