Major refactor and organization, optimizations to chunk, world and renderer

This commit is contained in:
max
2026-03-24 22:31:40 +01:00
parent dbc546fd0e
commit eb6294c09e
25 changed files with 410 additions and 441 deletions

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{
public class BlockData
{
}
}

106
Blocks.cs
View File

@@ -1,106 +0,0 @@
namespace Voxel
{
public enum Blocks : byte
{
Air,
Stone,
Dirt,
OakPlanks,
Grass,
Bedrock,
Sand,
TNT
}
public enum Orientation : byte
{
West = 0, // + X
East = 1, // - X
Top = 2, // + Y
Bottom = 3,// - Y
North = 4, // + Z
South = 5, // - Z
}
public class BlockDefinition
{
public Blocks BlockType;
public Textures[] FaceTextures;
public BlockDefinition(Blocks type, Textures singleTexture)
{
BlockType = type;
FaceTextures = new Voxel.Textures[6];
for (int i = 0; i < 6; i++) FaceTextures[i] = singleTexture;
}
public BlockDefinition(
Blocks type,
Textures west,
Textures east,
Textures top,
Textures bottom,
Textures north,
Textures south
)
{
BlockType = type;
FaceTextures = new Textures[]
{
west, east, top, bottom, north, south
};
}
}
public static class BlockDefinitions
{
public static readonly Dictionary<Blocks, BlockDefinition> Blocks;
static BlockDefinitions()
{
Blocks = new Dictionary<Blocks, BlockDefinition>
{
{Voxel.Blocks.Stone, new BlockDefinition(
Voxel.Blocks.Stone, Textures.Stone
)},
{Voxel.Blocks.Dirt, new BlockDefinition(
Voxel.Blocks.Dirt, Textures.Dirt
)},
{Voxel.Blocks.OakPlanks, new BlockDefinition(
Voxel.Blocks.OakPlanks, Textures.OakPlanks
)},
{Voxel.Blocks.Bedrock, new BlockDefinition(
Voxel.Blocks.Bedrock, Textures.Bedrock
)},
{Voxel.Blocks.Sand, new BlockDefinition(
Voxel.Blocks.Sand, Textures.Sand
)},
{ Voxel.Blocks.Grass, new BlockDefinition(
Voxel.Blocks.Grass,
Voxel.Textures.GrassSide, // West
Voxel.Textures.GrassSide, // East
Voxel.Textures.GrassTop, // Top
Voxel.Textures.Dirt, // Bottom
Voxel.Textures.GrassSide, // North
Voxel.Textures.GrassSide // South
)},
{ Voxel.Blocks.TNT, new BlockDefinition(
Voxel.Blocks.TNT,
Voxel.Textures.TntSide, // West
Voxel.Textures.TntSide, // East
Voxel.Textures.TntTop, // Top
Voxel.Textures.TntBottom, // Bottom
Voxel.Textures.TntSide, // North
Voxel.Textures.TntSide // South
)},
};
}
}
}

239
Chunk.cs
View File

@@ -1,239 +0,0 @@
using System.Runtime.CompilerServices;
namespace Voxel
{
public class Chunk
{
public const int Size = 16;
public const int Height = 256;
public const int TotalBlocks = Size * Size * Height;
public readonly int X;
public readonly int Y;
private ChunkMesh _chunkMesh;
private Dictionary<ushort, BlockData> _blockData;
private Blocks[] _blocks;
public Dictionary<Orientation, Chunk> Neighbors = new();
private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6]
{
( 1, 0, 0), // +X
(-1, 0, 0), // -X
( 0, 1, 0), // +Y
( 0, -1, 0), // -Y
( 0, 0, 1), // +Z
( 0, 0, -1) // -Z
};
private static readonly Dictionary<int, Orientation> FaceToNeighborMap = new()
{
{ 0, Orientation.West }, // +X face
{ 1, Orientation.East }, // -X face
{ 4, Orientation.North }, // +Z face
{ 5, Orientation.South } // -Z face
};
public Chunk(int x, int y)
{
X = x;
Y = y;
_blockData = new Dictionary<ushort, BlockData>();
_blocks = new Blocks[TotalBlocks];
_chunkMesh = new ChunkMesh(X, Y);
Initialize();
}
public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true)
{
int i = GetBlockIndex(x, y, z);
if (i == -1) return;
_blocks[i] = block;
if (updateMesh)
{
UpdateChunkMesh();
Renderer.MarkBuffersDirty();
}
}
public void SetBlockIndex(int i, Blocks block)
{
if (i < 0 || i >= TotalBlocks) return;
_blocks[i] = block;
}
public Blocks GetBlock(int x, int y, int z)
{
int i = GetBlockIndex(x, y, z);
if (i == -1) return Blocks.Air;
return _blocks[i];
}
public BlockData GetBlockData(int x, int y, int z)
{
return new BlockData(); // TODO: Implement
}
private void Initialize()
{
for (int x = 0; x < Size; x++)
{
for (int z = 0; z < Size; z++)
{
var position = GetWorldCoordinates(x, 0, z);
int height = Worldgen.GetHeight(position.x, position.z);
for (int y = 0; y < Height; y++)
{
Blocks block = Worldgen.GetBlock(y, height);
SetBlock(x, y, z, block, false);
}
}
}
UpdateChunkMesh();
}
public (int x, int y, int z) IndexToPosition(int i)
{
if (i < 0 || i >= TotalBlocks)
return (0, 0, 0);
int y = i >> 8; // y * 256
int remainder = i & 0xFF; // Lower 8 bits: x + z * 16
int z = remainder >> 4; // z * 16
int x = remainder & 0xF; // x
return (x, y, z);
}
public (int x, int y, int z) GetWorldCoordinates(int localX, int localY, int localZ)
{
return (localX + (X * Size), localY, localZ + (Y * Size));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBlockIndex(int x, int y, int z)
{
// bit shifting
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size)
return -1;
return x + (z << 4) + (y << 8); // x * 1, z * 16, y * 256
}
public void UpdateChunkMesh()
{
List<FaceData> faces = new List<FaceData>(TotalBlocks / 2); // Approximate capacity
GenerateFaces(faces);
_chunkMesh.SetFaces(faces);
}
private void GenerateFaces(List<FaceData> faces)
{
for (int x = 0; x < Size; x++)
{
for (int z = 0; z < Size; z++)
{
for (int y = 0; y < Height; y++)
{
ProcessBlockFaces(x, y, z, faces);
}
}
}
}
private void ProcessBlockFaces(int x, int y, int z, List<FaceData> faces)
{
int blockIndex = GetBlockIndex(x, y, z);
if (blockIndex == -1) return;
Blocks block = _blocks[blockIndex];
if (block == Blocks.Air) return;
var blockDef = BlockDefinitions.Blocks[block];
for (int face = 0; face < 6; face++)
{
if (ShouldAddFace(x, y, z, face))
{
AddFace(x, y, z, face, blockDef.FaceTextures[face], faces);
}
}
}
private bool ShouldAddFace(int x, int y, int z, int face)
{
var offset = Offsets[face];
int nx = x + offset.dx;
int ny = y + offset.dy;
int nz = z + offset.dz;
int neighborIndex = GetBlockIndex(nx, ny, nz);
if (neighborIndex != -1)
{
return _blocks[neighborIndex] == Blocks.Air;
}
return IsFaceVisibleAtChunkBoundary(x, y, z, face);
}
private bool IsFaceVisibleAtChunkBoundary(int x, int y, int z, int face)
{
if (!FaceToNeighborMap.TryGetValue(face, out Orientation neighborOrientation))
{
// top bottom faces always visible at chunk bounds
return true;
}
if (!Neighbors.TryGetValue(neighborOrientation, out Chunk neighbor) || neighbor == null)
{
return true; // no neighbor, face is visible
}
// Calculate coordinates in neighbor chunk
var offset = Offsets[face];
int localX = x;
int localZ = z;
if (offset.dx != 0) // east, west face
{
localX = WrapCoordinate(x + offset.dx, Size);
}
else if (offset.dz != 0) // north, south face
{
localZ = WrapCoordinate(z + offset.dz, Size);
}
return neighbor.GetBlock(localX, y, localZ) == Blocks.Air;
}
private int WrapCoordinate(int coord, int size)
{
if (coord < 0) return coord + size;
if (coord >= size) return coord - size;
return coord;
}
private void AddFace(int x, int y, int z, int face, Textures texture, List<FaceData> faces)
{
byte lightLevel = 15;
faces.Add(new FaceData(
(byte)x,
(byte)y,
(byte)z,
(Orientation)face,
texture,
lightLevel
));
}
public ChunkMesh GetChunkMesh()
{
return _chunkMesh;
}
}
}

View File

@@ -1,7 +1,7 @@
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Voxel
namespace Voxel.Core
{
public static class Input
{

View File

@@ -3,8 +3,9 @@ using OpenTK.Windowing.Common;
using OpenTK.Windowing.Common.Input;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Voxel.Graphics;
namespace Voxel
namespace Voxel.Core
{
public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
{

View File

@@ -5,8 +5,10 @@ using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Voxel.Core;
using Voxel.Physics;
namespace Voxel
namespace Voxel.Entities
{
public class Entity
{

View File

@@ -1,8 +1,10 @@
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Voxel.Core;
using Voxel.Graphics;
namespace Voxel
namespace Voxel.Entities
{
public class Player : Entity
{
@@ -152,23 +154,12 @@ namespace Voxel
public void SwitchBlock(MouseWheelEventArgs e)
{
var keys = BlockDefinitions.Blocks.Keys.ToList();
int count = BlockDefinitions.CreativeInventory.Length;
int direction = e.OffsetY < 0 ? -1 : 1;
bool inverted = false;
if (e.OffsetY < 0)
inverted = true;
_blockIndex = (_blockIndex + direction + count) % count;
_selectedBlock = BlockDefinitions.CreativeInventory[_blockIndex];
if (inverted)
if (_blockIndex == 0)
_blockIndex = keys.Count -1;
else
_blockIndex -= 1;
else
_blockIndex += 1;
_blockIndex = _blockIndex % keys.Count;
_selectedBlock = keys[_blockIndex];
Console.WriteLine(_selectedBlock);
}
}

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace Voxel
namespace Voxel.Graphics
{
static class Camera
{

View File

@@ -1,4 +1,4 @@
namespace Voxel
namespace Voxel.Graphics
{
public class ChunkMesh
{

View File

@@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
using Voxel.Core;
namespace Voxel
namespace Voxel.Graphics
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FaceData

View File

@@ -1,8 +1,9 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using Voxel.Core;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Voxel
namespace Voxel.Graphics
{
static class Renderer
{
@@ -19,7 +20,7 @@ namespace Voxel
{
string vertexPath = "Shaders/shader.vert";
string fragmentPath = "Shaders/shader.frag";
string texturePath = "atlas.png";
string texturePath = "Assets/atlas.png";
_shader = new Shader(vertexPath, fragmentPath);
_texture = new Texture(texturePath);
@@ -56,6 +57,34 @@ namespace Voxel
RenderUi();
}
public static void UpdateChunkBuffer(Chunk chunk)
{
if (_world == null || !_chunkBufferSizes.TryGetValue((chunk.X, chunk.Y), out int faceOffset))
return;
ChunkMesh chunkMesh = chunk.GetChunkMesh();
byte[] data = chunkMesh.GetPackedData();
int newByteSize = chunkMesh.Size * 4;
int currentAllocatedBytes = GetAllocatedSizeForChunk(chunk.X, chunk.Y);
if (newByteSize <= currentAllocatedBytes)
{
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
GL.BufferSubData(BufferTarget.ShaderStorageBuffer, (IntPtr)(faceOffset * 4), newByteSize, data);
}
else
{
MarkBuffersDirty();
}
}
private static int GetAllocatedSizeForChunk(int x, int y)
{
// todo, memory manager
return 0;
}
private static void UpdateAllChunksBuffer()
{
if (_world == null) return;

View File

@@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace Voxel
namespace Voxel.Graphics
{
public class Shader
{
@@ -27,7 +27,7 @@ namespace Voxel
Console.WriteLine(infoLog);
}
GL.CompileShader(fragmentShader);
GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out success);
if (success == 0)
{
string infoLog = GL.GetShaderInfoLog(fragmentShader);

View File

@@ -19,14 +19,18 @@ namespace Voxel
private void LoadFromFile()
{
StbImage.stbi_set_flip_vertically_on_load(1);
ImageResult image = ImageResult.FromStream(File.OpenRead(_path), ColorComponents.RedGreenBlueAlpha);
GL.BindTexture(TextureTarget.Texture2D, _handle);
GL.TexImage2D(
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
image.Width, image.Height, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data
);
using (var stream = File.OpenRead(_path))
{
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
GL.BindTexture(TextureTarget.Texture2D, _handle);
GL.TexImage2D(
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
image.Width, image.Height, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data
);
}
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace Voxel
namespace Voxel.Physics
{
public struct AABB
{

View File

@@ -1,5 +1,7 @@
using OpenTK.Mathematics;
using Voxel;
using Voxel.Core;
using Voxel.Entities;
using Voxel.Graphics;
internal class Program
{

0
README.md Normal file
View File

View File

@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<None Update="atlas.png">
<None Update="Assets\atlas.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Shaders\shader.frag">

120
World/Blocks.cs Normal file
View File

@@ -0,0 +1,120 @@
using System.Runtime.CompilerServices;
namespace Voxel.Core
{
public enum Blocks : byte
{
Air,
Stone,
Dirt,
OakPlanks,
Grass,
Bedrock,
Sand,
TNT
}
public enum Orientation : byte
{
West = 0, // + X
East = 1, // - X
Top = 2, // + Y
Bottom = 3,// - Y
North = 4, // + Z
South = 5, // - Z
}
public class BlockDefinition
{
public Blocks BlockType;
public Textures[] FaceTextures;
public BlockDefinition(Blocks type, Textures singleTexture)
{
BlockType = type;
FaceTextures = new Textures[6];
for (int i = 0; i < 6; i++) FaceTextures[i] = singleTexture;
}
public BlockDefinition(
Blocks type,
Textures west,
Textures east,
Textures top,
Textures bottom,
Textures north,
Textures south
)
{
BlockType = type;
FaceTextures = new Textures[]
{
west, east, top, bottom, north, south
};
}
}
public static class BlockDefinitions
{
private static readonly BlockDefinition[] _definitions = new BlockDefinition[256];
public static readonly Blocks[] CreativeInventory = new[]
{
Blocks.Stone,
Blocks.Dirt,
Blocks.OakPlanks,
Blocks.Bedrock,
Blocks.Sand,
Blocks.TNT
};
static BlockDefinitions()
{
// simple blocks
Define(Blocks.Stone, Textures.Stone);
Define(Blocks.Dirt, Textures.Dirt);
Define(Blocks.OakPlanks, Textures.OakPlanks);
Define(Blocks.Bedrock, Textures.Bedrock);
Define(Blocks.Sand, Textures.Sand);
// multi-texture blocks
// west (+X)
// east (-X)
// top
// bottom
// north (+Z)
// south (-Z)
Define(Blocks.Grass,
Textures.GrassSide,
Textures.GrassSide,
Textures.GrassTop,
Textures.Dirt,
Textures.GrassSide,
Textures.GrassSide
);
Define(Blocks.TNT,
Textures.TntSide,
Textures.TntSide,
Textures.TntTop,
Textures.TntBottom,
Textures.TntSide,
Textures.TntSide
);
}
/// highly optimized getter for the hot-path (Meshing/Raycasting)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BlockDefinition Get(Blocks type)
{
return _definitions[(byte)type];
}
// internal helper to map the array
private static void Define(Blocks type, Textures tex)
=> _definitions[(byte)type] = new BlockDefinition(type, tex);
private static void Define(Blocks type, Textures w, Textures e, Textures t, Textures b, Textures n, Textures s)
=> _definitions[(byte)type] = new BlockDefinition(type, w, e, t, b, n, s);
}
}

176
World/Chunk.cs Normal file
View File

@@ -0,0 +1,176 @@
using System.Runtime.CompilerServices;
using Voxel.Graphics;
namespace Voxel.Core
{
public class Chunk
{
public const int Size = 16;
public const int Height = 256;
public const int TotalBlocks = Size * Size * Height;
public readonly int X;
public readonly int Y;
private ChunkMesh _chunkMesh;
public bool IsDirty;
private Blocks[] _blocks;
private readonly Chunk[] _neighbors = new Chunk[6];
private static readonly List<FaceData> _faceBuffer = new List<FaceData>(8192);
private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6]
{
( 1, 0, 0), // West (+X)
(-1, 0, 0), // East (-X)
( 0, 1, 0), // Top (+Y)
( 0, -1, 0), // Bottom(-Y)
( 0, 0, 1), // North (+Z)
( 0, 0, -1) // South (-Z)
};
public Chunk(int x, int y)
{
X = x;
Y = y;
_blocks = new Blocks[TotalBlocks];
_chunkMesh = new ChunkMesh(X, Y);
Initialize();
}
public void SetNeighbor(Orientation orientation, Chunk neighbor)
=> _neighbors[(int)orientation] = neighbor;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Chunk GetNeighbor(Orientation orientation)
{
return _neighbors[(int)orientation];
}
public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true)
{
int i = GetBlockIndex(x, y, z);
if (i == -1 || _blocks[i] == block) return;
_blocks[i] = block;
if (updateMesh)
{
IsDirty = true;
MarkNeighborsDirtyIfEdge(x, y, z);
}
}
private void MarkNeighborsDirtyIfEdge(int x, int y, int z)
{
if (x == Size - 1) _neighbors[(int)Orientation.West]?.MarkDirty();
if (x == 0) _neighbors[(int)Orientation.East]?.MarkDirty();
if (z == Size - 1) _neighbors[(int)Orientation.North]?.MarkDirty();
if (z == 0) _neighbors[(int)Orientation.South]?.MarkDirty();
}
public void MarkDirty() => IsDirty = true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Blocks GetBlock(int x, int y, int z)
{
int i = GetBlockIndex(x, y, z);
return i == -1 ? Blocks.Air : _blocks[i];
}
private void Initialize()
{
for (int x = 0; x < Size; x++)
{
for (int z = 0; z < Size; z++)
{
int worldX = x + (X * Size);
int worldZ = z + (Y * Size);
int height = Worldgen.GetHeight(worldX, worldZ);
for (int y = 0; y < Height; y++)
{
_blocks[GetBlockIndex(x, y, z)] = Worldgen.GetBlock(y, height);
}
}
}
IsDirty = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBlockIndex(int x, int y, int z)
{
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size) return -1;
return x + (z << 4) + (y << 8); // x + z*16 + y*256
}
public void UpdateChunkMesh()
{
// Lock or use ThreadLocal for multi-threading later
lock (_faceBuffer)
{
_faceBuffer.Clear();
GenerateFaces(_faceBuffer);
_chunkMesh.SetFaces(_faceBuffer);
}
IsDirty = false;
}
private void GenerateFaces(List<FaceData> faces)
{
for (int y = 0; y < Height; y++)
{
int yOff = y << 8;
for (int z = 0; z < Size; z++)
{
int zyOff = yOff + (z << 4);
for (int x = 0; x < Size; x++)
{
int i = zyOff + x;
Blocks block = _blocks[i];
if (block == Blocks.Air) continue;
var blockDef = BlockDefinitions.Get(block);
for (int f = 0; f < 6; f++)
{
if (ShouldAddFace(x, y, z, f))
{
faces.Add(new FaceData((byte)x, (byte)y, (byte)z, (Orientation)f, blockDef.FaceTextures[f], 15));
}
}
}
}
}
}
private bool ShouldAddFace(int x, int y, int z, int faceIndex)
{
var offset = Offsets[faceIndex];
int nx = x + offset.dx;
int ny = y + offset.dy;
int nz = z + offset.dz;
if ((uint)nx < Size && (uint)ny < Height && (uint)nz < Size)
{
return _blocks[nx + (nz << 4) + (ny << 8)] == Blocks.Air;
}
// external boundary check
Chunk neighbor = _neighbors[faceIndex];
if (neighbor == null) return true; // At edge of loaded world
// wrap coordinates for the neighbor
int lx = (nx + Size) % Size;
int lz = (nz + Size) % Size;
return neighbor.GetBlock(lx, ny, lz) == Blocks.Air;
}
public ChunkMesh GetChunkMesh()
{
return _chunkMesh;
}
}
}

View File

@@ -1,6 +1,8 @@
using OpenTK.Mathematics;
using Voxel.Graphics;
using Voxel.Physics;
namespace Voxel
namespace Voxel.Core
{
public class World
{
@@ -43,13 +45,14 @@ namespace Voxel
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
{
var neighbors = GetChunkNeighbors(chunk).ToList();
_chunks.Remove((chunkX, chunkZ));
// update neighbor references to this chunk
foreach (var (orientation, neighbor) in neighbors)
{
var opposite = GetOppositeOrientation(orientation);
neighbor.Neighbors[opposite] = null;
neighbor.SetNeighbor(opposite, null);
neighbor.UpdateChunkMesh();
}
}
@@ -111,16 +114,18 @@ namespace Voxel
public void UpdateNeighboringChunks(Chunk chunk)
{
chunk.Neighbors[Orientation.West] = null;
chunk.Neighbors[Orientation.East] = null;
chunk.Neighbors[Orientation.North] = null;
chunk.Neighbors[Orientation.South] = null;
for (int i = 0; i < 6; i++)
{
chunk.SetNeighbor((Orientation)i, null);
}
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
{
Orientation opposite = GetOppositeOrientation(orientation);
neighbor.Neighbors[opposite] = chunk;
chunk.Neighbors[orientation] = neighbor;
neighbor.SetNeighbor(opposite, chunk);
chunk.SetNeighbor(orientation, neighbor);
neighbor.UpdateChunkMesh();
}
}
@@ -162,25 +167,24 @@ namespace Voxel
chunk.SetBlock(localX, worldY, localZ, block, updateMesh: updateMesh);
// temporary tnt functionality
if (block == Blocks.TNT)
{
int radius = 4;
Explode(worldX, worldY, worldZ, 4);
return;
}
if (updateMesh == false) return;
if (!updateMesh) return;
if (block == Blocks.Air && IsOnChunkBorder(worldX, worldZ))
{
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
{
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
{
neighbor.UpdateChunkMesh();
}
}
}
UpdateNeighborsAtBoundary(chunk, localX, worldY, localZ);
RebuildDirtyChunks();
}
private void UpdateNeighborsAtBoundary(Chunk chunk, int lx, int ly, int lz)
{
if (lx == Chunk.Size - 1) chunk.GetNeighbor(Orientation.West)?.MarkDirty();
else if (lx == 0) chunk.GetNeighbor(Orientation.East)?.MarkDirty();
if (lz == Chunk.Size - 1) chunk.GetNeighbor(Orientation.North)?.MarkDirty();
else if (lz == 0) chunk.GetNeighbor(Orientation.South)?.MarkDirty();
}
public bool IsOnChunkBorder(int worldX, int worldZ)
@@ -198,51 +202,46 @@ namespace Voxel
// bounding box
int minX = centerX - radius;
int maxX = centerX + radius;
int minY = centerY - radius;
int maxY = centerY + radius;
int minY = Math.Max(0, centerY - radius);
int maxY = Math.Min(Chunk.Height - 1, centerY + radius);
int minZ = centerZ - radius;
int maxZ = centerZ + radius;
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
for (int z = minZ; z <= maxZ; z++)
{
for (int z = minZ; z <= maxZ; z++)
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(x, z);
Chunk chunk = GetChunk(chunkX, chunkZ);
if (chunk == null) continue;
for (int y = minY; y <= maxY; y++)
{
int dx = x - centerX;
int dy = y - centerY;
int dz = z - centerZ;
if (dx * dx + dy * dy + dz * dz <= radiusSq)
{
// no update
SetBlock(x, y, z, Blocks.Air, updateMesh: false);
// add chunk to update list
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(x, z);
Chunk chunk = GetChunk(chunkX, chunkZ);
if (chunk != null)
affectedChunks.Add(chunk);
if (IsOnChunkBorder(x, z))
{
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
{
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
{
affectedChunks.Add(neighbor);
}
}
}
chunk.SetBlock(localX, y, localZ, Blocks.Air, updateMesh: true);
}
}
}
}
// Now rebuild meshes for all affected chunks
foreach (var chunk in affectedChunks)
RebuildDirtyChunks();
}
private void RebuildDirtyChunks()
{
foreach (var chunk in _chunks.Values)
{
chunk.UpdateChunkMesh();
if (chunk.IsDirty)
{
chunk.UpdateChunkMesh();
Renderer.UpdateChunkBuffer(chunk);
chunk.IsDirty = false;
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Voxel.Core;
namespace Voxel
{