298 lines
11 KiB
C#
298 lines
11 KiB
C#
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<Vector3> _vertices = new List<Vector3>();
|
|
private readonly List<int> _triangles = new List<int>();
|
|
private readonly List<int> _transparentTirangles = new List<int>();
|
|
private readonly List<Vector2> _uvs = new List<Vector2>();
|
|
private readonly List<Color> _colors = new List<Color>();
|
|
private readonly List<Vector3> _normals = new List<Vector3>();
|
|
|
|
public readonly BlockState[,,] BlockMap = new BlockState[WorldData.ChunkSize.x, WorldData.ChunkSize.y, WorldData.ChunkSize.x];
|
|
|
|
public readonly Queue<BlockMod> Modifications = new Queue<BlockMod>();
|
|
|
|
public Chunk(in World world, in Vector2Int position) {
|
|
Position = position;
|
|
_world = world;
|
|
}
|
|
|
|
public void Generate() {
|
|
_object = new GameObject();
|
|
_filter = _object.AddComponent<MeshFilter>();
|
|
_renderer = _object.AddComponent<MeshRenderer>();
|
|
|
|
_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<Vector3Int> litVoxels = new Queue<Vector3Int>();
|
|
|
|
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<ChunkLoadAnimation>();
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|