using System.Collections.Generic; using UnityEngine; using Utils; namespace Terrain { public class Chunk { public Vector2Int Position; public Vector3 WorldPosition; private GameObject _object; private MeshRenderer _renderer; private MeshFilter _filter; private World _world; private bool _active; private int _vertexIndex; private readonly List _vertices = new List(); private readonly List _triangles = new List(); private readonly List _transparentTirangles = new List(); private readonly List _uvs = new List(); private readonly List _colors = new List(); private readonly List _normals = new List(); public readonly BlockState[,,] BlockMap = new BlockState[WorldData.ChunkSize.x, WorldData.ChunkSize.y, WorldData.ChunkSize.x]; public readonly Queue Modifications = new Queue(); public Chunk(in World world, in Vector2Int position) { Position = position; _world = world; } public void Generate() { _object = new GameObject(); _filter = _object.AddComponent(); _renderer = _object.AddComponent(); _renderer.materials = new[] { _world.material, _world.transparent }; //_renderer.material = _world.material; _object.transform.SetParent(_world.transform); _object.transform.position = new Vector3(Position.x * WorldData.ChunkSize.x, 0.0f, Position.y * WorldData.ChunkSize.x); _object.name = $"Chunk [{Position.x}:{Position.y}]"; WorldPosition = _object.transform.position; PopulateBlockMap(); } public bool Active { get => _active; set { _active = value; if (_object != null) _object.SetActive(value); } } public bool Generated { get; private set; } public bool Editable => Generated; public void Update() { while (Modifications.Count > 0) { BlockMod mod = Modifications.Dequeue(); int x = Mathf.FloorToInt(mod.Position.x - WorldPosition.x); int y = Mathf.FloorToInt(mod.Position.y); int z = Mathf.FloorToInt(mod.Position.z - WorldPosition.z); BlockMap[x, y, z].Block = mod.Block; } ClearMeshData(); CalculateLight(); for (int y = 0; y < WorldData.ChunkSize.y; y++) { for (int x = 0; x < WorldData.ChunkSize.x; x++) { for (int z = 0; z < WorldData.ChunkSize.x; z++) { if (_world.blockTypes[BlockMap[x, y, z].Block].solid) UpdateMeshData(new Vector3(x, y, z)); } } } lock (_world.ChunksToDraw) { _world.ChunksToDraw.Enqueue(this); } } private void CalculateLight() { Queue litVoxels = new Queue(); for (int x = 0; x < WorldData.ChunkSize.x; x++) { for (int z = 0; z < WorldData.ChunkSize.x; z++) { float lightRay = 1.0f; for (int y = WorldData.ChunkSize.y - 1; y >= 0; y--) { BlockState state = BlockMap[x, y, z]; if (state.Block > 0 && _world.blockTypes[state.Block].transparency < lightRay) lightRay = _world.blockTypes[state.Block].transparency; state.GlobalLightPercent = lightRay; BlockMap[x, y, z] = state; if (lightRay > WorldData.LightFalloff) litVoxels.Enqueue(new Vector3Int(x, y, z)); } } } while (litVoxels.Count > 0) { Vector3Int pos = litVoxels.Dequeue(); for (int i = 0; i < 6; i++) { Vector3Int block = new Vector3Int((int)(pos.x + WorldData.FaceChecks[i].x), (int)(pos.y + WorldData.FaceChecks[i].y), (int)(pos.z + WorldData.FaceChecks[i].z)); if (IsBlockInChunk(block)) { BlockState state = BlockMap[block.x, block.y, block.z]; if (state.GlobalLightPercent < BlockMap[pos.x, pos.y, pos.z].GlobalLightPercent - WorldData.LightFalloff) { state.GlobalLightPercent = BlockMap[pos.x, pos.y, pos.z].GlobalLightPercent - WorldData.LightFalloff; if (state.GlobalLightPercent > WorldData.LightFalloff) litVoxels.Enqueue(block); BlockMap[block.x, block.y, block.z] = state; } } } } } public BlockState GetBlockFromWorldPosition(in Vector3 pos) { int x = Mathf.FloorToInt(pos.x - WorldPosition.x); int y = Mathf.FloorToInt(pos.y); int z = Mathf.FloorToInt(pos.z - WorldPosition.z); return BlockMap[x, y, z]; } private void PopulateBlockMap() { for (int y = 0; y < WorldData.ChunkSize.y; y++) { for (int x = 0; x < WorldData.ChunkSize.x; x++) { for (int z = 0; z < WorldData.ChunkSize.x; z++) { BlockMap[x, y, z] = new BlockState(_world.GenerateBlock(new Vector3(x, y, z) + WorldPosition)); } } } lock (_world.ChunkUpdateThreadLock) { _world.ChunksToUpdate.Add(this); } Generated = true; if (World.Settings.enableChunkLoadingAnimation) _object.AddComponent(); } public void EditBlock(in Vector3 pos, in byte block) { int x = Mathf.FloorToInt(pos.x - WorldPosition.x); int y = Mathf.FloorToInt(pos.y); int z = Mathf.FloorToInt(pos.z - WorldPosition.z); BlockMap[x, y, z].Block = block; lock (_world.ChunkUpdateThreadLock) { _world.ChunksToUpdate.Insert(0, this); UpdateSurroundingBlocks(new Vector3(x, y, z)); } } private void UpdateSurroundingBlocks(in Vector3 block) { for (int i = 0; i < 6; i++) { Vector3 currentBlock = block + WorldData.FaceChecks[i]; if (!IsBlockInChunk(currentBlock)) _world.ChunksToUpdate.Insert(0, _world.GetChunkFromWorldPosition(currentBlock + WorldPosition)); } } private void ClearMeshData() { _vertexIndex = 0; _vertices.Clear(); _triangles.Clear(); _transparentTirangles.Clear(); _uvs.Clear(); _colors.Clear(); _normals.Clear(); } private bool IsBlockInChunk(in Vector3 block) { return !(block.x < 0 || block.x > WorldData.ChunkSize.x - 1 || block.y < 0 || block.y > WorldData.ChunkSize.y - 1 || block.z < 0 || block.z > WorldData.ChunkSize.x - 1); } private BlockState CheckBlock(in Vector3 pos) { int x = Mathf.FloorToInt(pos.x); int y = Mathf.FloorToInt(pos.y); int z = Mathf.FloorToInt(pos.z); if (!IsBlockInChunk(new Vector3(x, y, z))) return _world.GetBlock(pos + WorldPosition); return BlockMap[x, y, z]; } private void UpdateMeshData(in Vector3 pos) { int x = Mathf.FloorToInt(pos.x); int y = Mathf.FloorToInt(pos.y); int z = Mathf.FloorToInt(pos.z); byte block = BlockMap[x, y, z].Block; BlockType type = _world.blockTypes[block]; for (byte i = 0; i < 6; i++) { BlockState neighbour = CheckBlock(pos + WorldData.FaceChecks[i]); if (!_world.blockTypes[neighbour.Block].renderNeighbourFaces) continue; _vertices.Add(pos + WorldData.BlockVerts[WorldData.BlockTris[i, 0]]); _vertices.Add(pos + WorldData.BlockVerts[WorldData.BlockTris[i, 1]]); _vertices.Add(pos + WorldData.BlockVerts[WorldData.BlockTris[i, 2]]); _vertices.Add(pos + WorldData.BlockVerts[WorldData.BlockTris[i, 3]]); for (int j = 0; j <= 3; j++) { _normals.Add(WorldData.FaceChecks[i]); } AddTexture(type.GetTexture(i)); float lightLevel = neighbour.GlobalLightPercent; _colors.Add(new Color(0, 0, 0, lightLevel)); _colors.Add(new Color(0, 0, 0, lightLevel)); _colors.Add(new Color(0, 0, 0, lightLevel)); _colors.Add(new Color(0, 0, 0, lightLevel)); if (type.renderNeighbourFaces) { _transparentTirangles.Add(_vertexIndex); _transparentTirangles.Add(_vertexIndex + 1); _transparentTirangles.Add(_vertexIndex + 2); _transparentTirangles.Add(_vertexIndex + 2); _transparentTirangles.Add(_vertexIndex + 1); _transparentTirangles.Add(_vertexIndex + 3); } else { _triangles.Add(_vertexIndex); _triangles.Add(_vertexIndex + 1); _triangles.Add(_vertexIndex + 2); _triangles.Add(_vertexIndex + 2); _triangles.Add(_vertexIndex + 1); _triangles.Add(_vertexIndex + 3); } _vertexIndex += 4; } } public void CreateMesh() { Mesh mesh = new Mesh(); mesh.vertices = _vertices.ToArray(); mesh.subMeshCount = 2; mesh.SetTriangles(_triangles.ToArray(), 0); mesh.SetTriangles(_transparentTirangles.ToArray(), 1); //mesh.triangles = _triangles.ToArray(); mesh.uv = _uvs.ToArray(); mesh.colors = _colors.ToArray(); mesh.normals = _normals.ToArray(); //mesh.RecalculateNormals(); _filter.mesh = mesh; } private void AddTexture(in short texture) { float y = texture / WorldData.TextureAtlasSizeInBlocks; float x = texture % WorldData.TextureAtlasSizeInBlocks; x *= WorldData.NormalizedBlockTextureSize; y *= WorldData.NormalizedBlockTextureSize; y = 1.0f - y - WorldData.NormalizedBlockTextureSize; _uvs.Add(new Vector2(x, y)); _uvs.Add(new Vector2(x, y + WorldData.NormalizedBlockTextureSize)); _uvs.Add(new Vector2(x + WorldData.NormalizedBlockTextureSize, y)); _uvs.Add(new Vector2(x + WorldData.NormalizedBlockTextureSize, y + WorldData.NormalizedBlockTextureSize)); } } }