|
@@ -5,6 +5,7 @@ using KairoEngine.Core;
|
|
using KairoEngine.Chunks;
|
|
using KairoEngine.Chunks;
|
|
using KairoEngine.NoiseUtilities;
|
|
using KairoEngine.NoiseUtilities;
|
|
using KairoEngine.UniverseGenerator;
|
|
using KairoEngine.UniverseGenerator;
|
|
|
|
+using Utilities = KairoEngine.Utility.Utilities;
|
|
using Sirenix.OdinInspector;
|
|
using Sirenix.OdinInspector;
|
|
using UniRx;
|
|
using UniRx;
|
|
|
|
|
|
@@ -18,13 +19,30 @@ namespace KairoEngine.TerrainEngine
|
|
public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
|
|
public Vector3 voxelSize = new Vector3(1f, 1f, 1f);
|
|
public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
|
|
public Vector3Int chunkSize = new Vector3Int(16, 16, 16);
|
|
public Vector3Int chunkLimit = new Vector3Int(2, 1, 2);
|
|
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;
|
|
//public float noiseMultiplier = 1.5f;
|
|
[InlineEditor(InlineEditorObjectFieldModes.Foldout)] public PlanetTemplate planetTemplate;
|
|
[InlineEditor(InlineEditorObjectFieldModes.Foldout)] public PlanetTemplate planetTemplate;
|
|
|
|
|
|
public ChunkSystem<BlockBase> chunkSystem;
|
|
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)
|
|
if(planetTemplate == null)
|
|
{
|
|
{
|
|
@@ -33,14 +51,14 @@ namespace KairoEngine.TerrainEngine
|
|
}
|
|
}
|
|
DestroyTerrain();
|
|
DestroyTerrain();
|
|
Debug.Log("Starting chunk generation");
|
|
Debug.Log("Starting chunk generation");
|
|
- chunkSystem = new ChunkSystem<BlockBase>(chunkSize,voxelSize, true);
|
|
|
|
|
|
+ Initialize();
|
|
for (int x = 0; x < chunkLimit.x; x++) {
|
|
for (int x = 0; x < chunkLimit.x; x++) {
|
|
for (int z = 0; z < chunkLimit.z; z++) {
|
|
for (int z = 0; z < chunkLimit.z; z++) {
|
|
for (int y = 0; y < chunkLimit.y; y++) {
|
|
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);
|
|
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);
|
|
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));
|
|
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++) {
|
|
for (int y = 0; y < chunkLimit.y; y++) {
|
|
//Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z);
|
|
//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);
|
|
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);
|
|
//else if(marchingCubes2 != null) marchingCubes2.Generate(chunkSystem, chunkPos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -59,7 +77,26 @@ namespace KairoEngine.TerrainEngine
|
|
#endif
|
|
#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 width = (float)(chunkLimit.x * chunkSize.x * voxelSize.x);
|
|
float height = (float)(chunkLimit.y * chunkSize.y * voxelSize.y);
|
|
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 elevation = planetTemplate.SamplePoint((float)pos.x / width, (float)pos.z / length);
|
|
float thisHeight = height * elevation;
|
|
float thisHeight = height * elevation;
|
|
uint code = planetTemplate.SamplePointCode((float)pos.x / width, (float)pos.z / length, 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);
|
|
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, () => {
|
|
Timer.ExecuteRealTime(50, () => {
|
|
if(marchingCubes3 != null)
|
|
if(marchingCubes3 != null)
|
|
{
|
|
{
|
|
- marchingCubes3.UpdateFinishedJobs();
|
|
|
|
|
|
+ UpdateGenerator();
|
|
if(!marchingCubes3.IsGeneratorDone()) UpdateEditor();
|
|
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")]
|
|
[Button("Destroy"), ButtonGroup("Buttons")]
|
|
- private void DestroyTerrain()
|
|
|
|
|
|
+ public void DestroyTerrain()
|
|
{
|
|
{
|
|
var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
|
|
var childTransforms = this.gameObject.transform.GetComponentsInChildren<Transform>();
|
|
if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length - 1} terrain objects");
|
|
if(childTransforms.Length > 0) Debug.Log($"Destroying {childTransforms.Length - 1} terrain objects");
|
|
@@ -106,6 +257,99 @@ namespace KairoEngine.TerrainEngine
|
|
Destroy(childTransforms[i].gameObject);
|
|
Destroy(childTransforms[i].gameObject);
|
|
#endif
|
|
#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;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|