ChunkTerrainGenerator.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using KairoEngine.Core;
  5. using KairoEngine.Chunks;
  6. using KairoEngine.NoiseUtilities;
  7. using KairoEngine.UniverseGenerator;
  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. public MarchingCubes4 marchingCubes3;
  17. public MarchingCubes2 marchingCubes2;
  18. public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
  19. public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
  20. public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
  21. public int dataProcessLimit = 4;
  22. public int parallelMeshJobs = 4;
  23. public Dictionary<Vector3, GameObject> terrainMeshes = new Dictionary<Vector3, GameObject>();
  24. //public float noiseMultiplier = 1.5f;
  25. [InlineEditor(InlineEditorObjectFieldModes.Foldout)] public PlanetTemplate planetTemplate;
  26. public ChunkSystem<BlockBase> chunkSystem;
  27. private List<Vector3> chunkDataBuffer = new List<Vector3>(); // Store list of chunks for populating with data
  28. private List<Vector3> chunkMeshBuffer = new List<Vector3>(); // Store list of chunks for generating the mesh
  29. private bool initialized = false;
  30. public void Initialize ()
  31. {
  32. chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
  33. initialized = true;
  34. }
  35. public bool IsInitialized() => initialized;
  36. [Button("Generate World"), ButtonGroup("Buttons")]
  37. void GenerateWorld()
  38. {
  39. if(planetTemplate == null)
  40. {
  41. Debug.LogError("Missing planet template", this.gameObject);
  42. return;
  43. }
  44. DestroyTerrain();
  45. Debug.Log("Starting chunk generation");
  46. Initialize();
  47. for (int x = 0; x < chunkLimit.x; x++) {
  48. for (int z = 0; z < chunkLimit.z; z++) {
  49. for (int y = 0; y < chunkLimit.y; y++) {
  50. Vector3 chunkGlobalPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
  51. Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
  52. chunkSystem.CreateChunk(chunkGlobalPos, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
  53. PopulateChunkData(chunkGlobalPos);
  54. }
  55. }
  56. }
  57. for (int x = 0; x < chunkLimit.x; x++) {
  58. for (int z = 0; z < chunkLimit.z; z++) {
  59. for (int y = 0; y < chunkLimit.y; y++) {
  60. //Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
  61. Vector3 chunkPos = new Vector3(x * chunkSize.x * voxelSize.x, y * chunkSize.y * voxelSize.y, z * chunkSize.z * voxelSize.z);
  62. GenerateTerrainMesh(chunkPos);
  63. //else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
  64. }
  65. }
  66. }
  67. #if UNITY_EDITOR
  68. UpdateEditor ();
  69. #endif
  70. }
  71. public void GenerateChunk(Vector3 position)
  72. {
  73. position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
  74. bool buffered = false;
  75. for (int i = 0; i < chunkDataBuffer.Count; i++)
  76. {
  77. if(chunkDataBuffer[i] == position) buffered = true;
  78. }
  79. for (int i = 0; i < chunkMeshBuffer.Count; i++)
  80. {
  81. if(chunkMeshBuffer[i] == position) buffered = true;
  82. }
  83. //if(chunkSystem.ChunkExists(position)) buffered = true;
  84. //bool meshGenerated = terrainMeshes.TryGetValue(position, out GameObject obj);
  85. //if(meshGenerated) buffered = true;
  86. if(buffered) return;
  87. chunkDataBuffer.Add(position);
  88. }
  89. void PopulateChunkData(Vector3 initialPos)
  90. {
  91. float width = (float)(chunkLimit.x * chunkSize.x * voxelSize.x);
  92. float height = (float)(chunkLimit.y * chunkSize.y * voxelSize.y);
  93. float length = (float)(chunkLimit.z * chunkSize.z * voxelSize.z);
  94. for (int x = 0; x < chunkSize.x; x++) {
  95. for (int z = 0; z < chunkSize.z; z++) {
  96. for (int y = 0; y < chunkSize.y; y++) {
  97. // Get a terrain height using regular old Perlin noise.
  98. Vector3 pos = initialPos + new Vector3(x * voxelSize.x, y * voxelSize.y, z * voxelSize.z);
  99. // Old example noise:
  100. //float thisHeight = height * Mathf.PerlinNoise((float)pos.x / width * noiseMultiplier + 0.001f, (float)pos.z / length * noiseMultiplier + 0.001f);
  101. float elevation = planetTemplate.SamplePoint((float)pos.x / width, (float)pos.z / length);
  102. float thisHeight = height * elevation;
  103. uint code = planetTemplate.SamplePointCode((float)pos.x / width, (float)pos.z / length, elevation);
  104. BlockBase block = new BlockBase(code, (uint)Mathf.FloorToInt(thisHeight * 1000));
  105. chunkSystem.SetBlock(pos, block);
  106. }
  107. }
  108. }
  109. }
  110. public void GenerateChunkData(Vector3 position)
  111. {
  112. position = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(position);
  113. Vector3Int chunkPos = chunkSystem.GetChunkPositionFromGlobalPosition(position);
  114. if(!chunkSystem.ChunkExists(position))
  115. {
  116. //Debug.Log($"Generating Terrain Data ({position} | {chunkPos})");
  117. chunkSystem.CreateChunk(position, chunkPos, (Chunk<BlockBase> c, Vector3Int pos) => new BlockBase(0, 0));
  118. PopulateChunkData(position);
  119. }
  120. }
  121. public void GenerateTerrainMesh(Vector3 position, bool overwrite = false)
  122. {
  123. bool meshExists = terrainMeshes.TryGetValue(position, out GameObject obj);
  124. if(meshExists && !overwrite) return;
  125. if(!overwrite) terrainMeshes.Add(position, null);
  126. if(marchingCubes3 != null) marchingCubes3.Generate(chunkSystem, this, position);
  127. }
  128. public void UpdateGenerator()
  129. {
  130. if(marchingCubes3.GetJobCount() == 0) ProcessChunkDataBuffer();
  131. ProcessChunkMeshBuffer();
  132. }
  133. public void UpdateEditor ()
  134. {
  135. Timer.ExecuteRealTime(50, () => {
  136. if(marchingCubes3 != null)
  137. {
  138. UpdateGenerator();
  139. if(!marchingCubes3.IsGeneratorDone()) UpdateEditor();
  140. }
  141. });
  142. }
  143. private void ProcessChunkDataBuffer()
  144. {
  145. int limit = chunkDataBuffer.Count < dataProcessLimit ? chunkDataBuffer.Count : dataProcessLimit;
  146. for (int i = 0; i < limit; i++)
  147. {
  148. Vector3 position = chunkDataBuffer[i];
  149. List<Vector3> positions = new List<Vector3>{
  150. new Vector3( 0, 0, 0),
  151. new Vector3( 1, 0, 0),
  152. new Vector3( 1, 0, -1),
  153. new Vector3( 0, 0, -1),
  154. new Vector3(-1, 0, -1),
  155. new Vector3(-1, 0, 0),
  156. new Vector3(-1, 0, 1),
  157. new Vector3( 0, 0, 1),
  158. new Vector3( 1, 0, 1),
  159. new Vector3( 0, 1, 0),
  160. new Vector3( 1, 1, 0),
  161. new Vector3( 1, 1, -1),
  162. new Vector3( 0, 1, -1),
  163. new Vector3(-1, 1, -1),
  164. new Vector3(-1, 1, 0),
  165. new Vector3(-1, 1, 1),
  166. new Vector3( 0, 1, 1),
  167. new Vector3( 1, 1, 1),
  168. new Vector3( 0, -1, 0),
  169. new Vector3( 1, -1, 0),
  170. new Vector3( 1, -1, -1),
  171. new Vector3( 0, -1, -1),
  172. new Vector3(-1, -1, -1),
  173. new Vector3(-1, -1, 0),
  174. new Vector3(-1, -1, 1),
  175. new Vector3( 0, -1, 1),
  176. new Vector3( 1, -1, 1),
  177. };
  178. for (int a = 0; a < positions.Count; a++)
  179. {
  180. //position = new Vector3(position.x + chunkSize.x * voxelSize.x, position.y, position.z);
  181. Vector3 chunkPos = new Vector3(
  182. position.x + (positions[a].x * chunkSize.x * voxelSize.x),
  183. position.y + (positions[a].y * chunkSize.y * voxelSize.y),
  184. position.z + (positions[a].z * chunkSize.z * voxelSize.z)
  185. );
  186. GenerateChunkData(chunkPos);
  187. }
  188. chunkMeshBuffer.Add(chunkDataBuffer[i]);
  189. }
  190. chunkDataBuffer.RemoveRange(0, limit);
  191. }
  192. private void ProcessChunkMeshBuffer()
  193. {
  194. marchingCubes3.UpdateFinishedJobs();
  195. int limit = marchingCubes3.GetJobCount() < parallelMeshJobs ? parallelMeshJobs - marchingCubes3.GetJobCount() : 0;
  196. limit = limit < chunkMeshBuffer.Count ? limit : chunkMeshBuffer.Count;
  197. for (int i = 0; i < limit; i++)
  198. {
  199. GenerateTerrainMesh(chunkMeshBuffer[i]);
  200. }
  201. chunkMeshBuffer.RemoveRange(0, limit);
  202. }
  203. public void DestroyChunk(Vector3 position)
  204. {
  205. DestroyChunkData(position);
  206. DestroyChunkMesh(position);
  207. }
  208. public void DestroyChunkData(Vector3 position)
  209. {
  210. chunkSystem.DestroyChunk(position);
  211. }
  212. public void DestroyChunkMesh(Vector3 position)
  213. {
  214. terrainMeshes.TryGetValue(position, out GameObject obj);
  215. if(obj != null)
  216. {
  217. Destroy(obj);
  218. #if UNITY_EDITOR
  219. DestroyImmediate(obj);
  220. #endif
  221. }
  222. terrainMeshes.Remove(position);
  223. }
  224. public void ClearChunkDataBuffer() => chunkDataBuffer.Clear();
  225. [Button("Destroy"), ButtonGroup("Buttons")]
  226. public void DestroyTerrain()
  227. {
  228. var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
  229. if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length - 1} terrain objects");
  230. for (int i = 0; i < childTransforms.Length; i++)
  231. {
  232. if(childTransforms[i] == this.transform) continue;
  233. #if UNITY_EDITOR
  234. DestroyImmediate(childTransforms[i].gameObject);
  235. #else
  236. Destroy(childTransforms[i].gameObject);
  237. #endif
  238. }
  239. terrainMeshes = new Dictionary<Vector3, GameObject>();
  240. chunkDataBuffer = new List<Vector3>();
  241. chunkMeshBuffer = new List<Vector3>();
  242. chunkSystem.chunks = new Dictionary<Vector3Int, Chunk<BlockBase>>();
  243. chunkSystem.chunkList = new List<Vector3>();
  244. }
  245. public enum VoxelOperation
  246. {
  247. Add,
  248. Remove,
  249. Level
  250. }
  251. public void ChangeVoxels(Vector3 pos, uint code, VoxelOperation operation, int radius = 3)
  252. {
  253. int startHeigth = 0, endHeigth = 0, targetHeigth = 0, updatedVoxels = 0;
  254. switch (operation)
  255. {
  256. case VoxelOperation.Add:
  257. startHeigth = -1;
  258. endHeigth = radius;
  259. targetHeigth = (int)(((chunkSystem.GetBlock(pos).value/1000) + (radius * voxelSize.y)) * 1000);
  260. break;
  261. case VoxelOperation.Remove:
  262. startHeigth = -1 * (radius + 1);
  263. endHeigth = radius + 1;
  264. targetHeigth = (int)(Mathf.Clamp((chunkSystem.GetBlock(pos).value/1000) - (radius * voxelSize.y), 0, 10000) * 1000);
  265. break;
  266. case VoxelOperation.Level:
  267. startHeigth = -1 * radius;
  268. endHeigth = radius;
  269. targetHeigth = (int)chunkSystem.GetBlock(pos).value;
  270. break;
  271. default:
  272. return;
  273. }
  274. Vector3 chunkPosition = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(pos);
  275. List<Vector2Int> points = Utilities.GetPointsInRadius(radius, true);
  276. List<Vector3> chunkPositions = new List<Vector3>();
  277. for (int j = 0; j < points.Count; j++)
  278. {
  279. for (int y = startHeigth; y < endHeigth + 1; y++)
  280. {
  281. updatedVoxels += 1;
  282. Vector3 voxelPos = new Vector3(pos.x + points[j].x * voxelSize.x, pos.y + y * voxelSize.y, pos.z + points[j].y * voxelSize.z);
  283. BlockBase block = chunkSystem.GetBlock(voxelPos);
  284. block.code = code;
  285. block.value = (uint)targetHeigth;
  286. chunkSystem.SetBlock(voxelPos, block);
  287. Vector3Int localVoxelPos = chunkSystem.GetVoxelFromGlobalPosition(voxelPos);
  288. List<Vector3> chunks = new List<Vector3>();
  289. Vector3 chunkPos = chunkSystem.GetChunkGlobalPositionFromGlobalPosition(voxelPos);
  290. chunks.Add(chunkPos);
  291. if(localVoxelPos.x == 0) chunks.Add(new Vector3(chunkPos.x - chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
  292. if(localVoxelPos.x == 16) chunks.Add(new Vector3(chunkPos.x + chunkSize.x * voxelSize.x, chunkPos.y, chunkPos.z));
  293. if(localVoxelPos.y == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y - chunkSize.y * voxelSize.y, chunkPos.z));
  294. if(localVoxelPos.y == 16) chunks.Add(new Vector3(chunkPos.x, chunkPos.y + chunkSize.y * voxelSize.y, chunkPos.z));
  295. if(localVoxelPos.z == 0) chunks.Add(new Vector3(chunkPos.x, chunkPos.y, chunkPos.z - chunkSize.z * voxelSize.z));
  296. if(localVoxelPos.z == 16) chunks.Add(new Vector3(chunkPos.x , chunkPos.y, chunkPos.z + chunkSize.z * voxelSize.z));
  297. for (int i = 0; i < chunks.Count; i++)
  298. {
  299. bool duplicate = false;
  300. for (int k = 0; k < chunkPositions.Count; k++)
  301. {
  302. if(chunkPositions[k].Equals(chunks[i]))
  303. {
  304. duplicate = true;
  305. break;
  306. }
  307. }
  308. if(!duplicate) chunkPositions.Add(chunks[i]);
  309. }
  310. }
  311. }
  312. for (int c = 0; c < chunkPositions.Count; c++) GenerateTerrainMesh(chunkPositions[c], true);
  313. Debug.Log($"Changing voxel at {pos}, updated {updatedVoxels} voxels and {chunkPositions.Count} chunks");
  314. }
  315. public void AddGeneratedTerrainMesh(Vector3 chunkPosition, GameObject meshObj)
  316. {
  317. bool meshExists = terrainMeshes.TryGetValue(chunkPosition, out GameObject currentMesh);
  318. if(currentMesh != null)
  319. {
  320. //Debug.Log($"Updating chunk mesh at {chunkPosition}");
  321. DestroyChunkMesh(chunkPosition);
  322. terrainMeshes[chunkPosition] = meshObj;
  323. }
  324. else
  325. {
  326. //Debug.Log($"Adding chunk mesh at {chunkPosition}");
  327. //terrainMeshes.Add(chunkPosition, meshObj);
  328. terrainMeshes[chunkPosition] = meshObj;
  329. }
  330. }
  331. }
  332. }