Major refactor and organization, optimizations to chunk, world and renderer
This commit is contained in:
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
12
BlockData.cs
12
BlockData.cs
@@ -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
106
Blocks.cs
@@ -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
239
Chunk.cs
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using OpenTK.Windowing.Common;
|
using OpenTK.Windowing.Common;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Core
|
||||||
{
|
{
|
||||||
public static class Input
|
public static class Input
|
||||||
{
|
{
|
||||||
@@ -3,8 +3,9 @@ using OpenTK.Windowing.Common;
|
|||||||
using OpenTK.Windowing.Common.Input;
|
using OpenTK.Windowing.Common.Input;
|
||||||
using OpenTK.Windowing.Desktop;
|
using OpenTK.Windowing.Desktop;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
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 })
|
public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
|
||||||
{
|
{
|
||||||
@@ -5,8 +5,10 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Voxel.Core;
|
||||||
|
using Voxel.Physics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Entities
|
||||||
{
|
{
|
||||||
public class Entity
|
public class Entity
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using OpenTK.Windowing.Common;
|
using OpenTK.Windowing.Common;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
using Voxel.Core;
|
||||||
|
using Voxel.Graphics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Entities
|
||||||
{
|
{
|
||||||
public class Player : Entity
|
public class Player : Entity
|
||||||
{
|
{
|
||||||
@@ -152,23 +154,12 @@ namespace Voxel
|
|||||||
|
|
||||||
public void SwitchBlock(MouseWheelEventArgs e)
|
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;
|
_blockIndex = (_blockIndex + direction + count) % count;
|
||||||
|
_selectedBlock = BlockDefinitions.CreativeInventory[_blockIndex];
|
||||||
|
|
||||||
if (e.OffsetY < 0)
|
|
||||||
inverted = true;
|
|
||||||
|
|
||||||
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);
|
Console.WriteLine(_selectedBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Graphics
|
||||||
{
|
{
|
||||||
static class Camera
|
static class Camera
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Voxel
|
namespace Voxel.Graphics
|
||||||
{
|
{
|
||||||
public class ChunkMesh
|
public class ChunkMesh
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Voxel.Core;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Graphics
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct FaceData
|
public struct FaceData
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using OpenTK.Graphics.OpenGL4;
|
using OpenTK.Graphics.OpenGL4;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
using Voxel.Core;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Graphics
|
||||||
{
|
{
|
||||||
static class Renderer
|
static class Renderer
|
||||||
{
|
{
|
||||||
@@ -19,7 +20,7 @@ namespace Voxel
|
|||||||
{
|
{
|
||||||
string vertexPath = "Shaders/shader.vert";
|
string vertexPath = "Shaders/shader.vert";
|
||||||
string fragmentPath = "Shaders/shader.frag";
|
string fragmentPath = "Shaders/shader.frag";
|
||||||
string texturePath = "atlas.png";
|
string texturePath = "Assets/atlas.png";
|
||||||
|
|
||||||
_shader = new Shader(vertexPath, fragmentPath);
|
_shader = new Shader(vertexPath, fragmentPath);
|
||||||
_texture = new Texture(texturePath);
|
_texture = new Texture(texturePath);
|
||||||
@@ -56,6 +57,34 @@ namespace Voxel
|
|||||||
RenderUi();
|
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()
|
private static void UpdateAllChunksBuffer()
|
||||||
{
|
{
|
||||||
if (_world == null) return;
|
if (_world == null) return;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using OpenTK.Graphics.OpenGL4;
|
using OpenTK.Graphics.OpenGL4;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Graphics
|
||||||
{
|
{
|
||||||
public class Shader
|
public class Shader
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ namespace Voxel
|
|||||||
Console.WriteLine(infoLog);
|
Console.WriteLine(infoLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
GL.CompileShader(fragmentShader);
|
GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out success);
|
||||||
if (success == 0)
|
if (success == 0)
|
||||||
{
|
{
|
||||||
string infoLog = GL.GetShaderInfoLog(fragmentShader);
|
string infoLog = GL.GetShaderInfoLog(fragmentShader);
|
||||||
@@ -19,14 +19,18 @@ namespace Voxel
|
|||||||
private void LoadFromFile()
|
private void LoadFromFile()
|
||||||
{
|
{
|
||||||
StbImage.stbi_set_flip_vertically_on_load(1);
|
StbImage.stbi_set_flip_vertically_on_load(1);
|
||||||
ImageResult image = ImageResult.FromStream(File.OpenRead(_path), ColorComponents.RedGreenBlueAlpha);
|
|
||||||
|
|
||||||
GL.BindTexture(TextureTarget.Texture2D, _handle);
|
using (var stream = File.OpenRead(_path))
|
||||||
GL.TexImage2D(
|
{
|
||||||
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
|
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
||||||
image.Width, image.Height, 0,
|
|
||||||
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data
|
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.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Physics
|
||||||
{
|
{
|
||||||
public struct AABB
|
public struct AABB
|
||||||
{
|
{
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using Voxel;
|
using Voxel.Core;
|
||||||
|
using Voxel.Entities;
|
||||||
|
using Voxel.Graphics;
|
||||||
|
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="atlas.png">
|
<None Update="Assets\atlas.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Shaders\shader.frag">
|
<None Update="Shaders\shader.frag">
|
||||||
|
|||||||
120
World/Blocks.cs
Normal file
120
World/Blocks.cs
Normal 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
176
World/Chunk.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
using Voxel.Graphics;
|
||||||
|
using Voxel.Physics;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel.Core
|
||||||
{
|
{
|
||||||
public class World
|
public class World
|
||||||
{
|
{
|
||||||
@@ -43,13 +45,14 @@ namespace Voxel
|
|||||||
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
||||||
{
|
{
|
||||||
var neighbors = GetChunkNeighbors(chunk).ToList();
|
var neighbors = GetChunkNeighbors(chunk).ToList();
|
||||||
|
|
||||||
_chunks.Remove((chunkX, chunkZ));
|
_chunks.Remove((chunkX, chunkZ));
|
||||||
|
|
||||||
// update neighbor references to this chunk
|
// update neighbor references to this chunk
|
||||||
foreach (var (orientation, neighbor) in neighbors)
|
foreach (var (orientation, neighbor) in neighbors)
|
||||||
{
|
{
|
||||||
var opposite = GetOppositeOrientation(orientation);
|
var opposite = GetOppositeOrientation(orientation);
|
||||||
neighbor.Neighbors[opposite] = null;
|
neighbor.SetNeighbor(opposite, null);
|
||||||
neighbor.UpdateChunkMesh();
|
neighbor.UpdateChunkMesh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,16 +114,18 @@ namespace Voxel
|
|||||||
|
|
||||||
public void UpdateNeighboringChunks(Chunk chunk)
|
public void UpdateNeighboringChunks(Chunk chunk)
|
||||||
{
|
{
|
||||||
chunk.Neighbors[Orientation.West] = null;
|
for (int i = 0; i < 6; i++)
|
||||||
chunk.Neighbors[Orientation.East] = null;
|
{
|
||||||
chunk.Neighbors[Orientation.North] = null;
|
chunk.SetNeighbor((Orientation)i, null);
|
||||||
chunk.Neighbors[Orientation.South] = null;
|
}
|
||||||
|
|
||||||
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
||||||
{
|
{
|
||||||
Orientation opposite = GetOppositeOrientation(orientation);
|
Orientation opposite = GetOppositeOrientation(orientation);
|
||||||
neighbor.Neighbors[opposite] = chunk;
|
|
||||||
chunk.Neighbors[orientation] = neighbor;
|
neighbor.SetNeighbor(opposite, chunk);
|
||||||
|
chunk.SetNeighbor(orientation, neighbor);
|
||||||
|
|
||||||
neighbor.UpdateChunkMesh();
|
neighbor.UpdateChunkMesh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,25 +167,24 @@ namespace Voxel
|
|||||||
chunk.SetBlock(localX, worldY, localZ, block, updateMesh: updateMesh);
|
chunk.SetBlock(localX, worldY, localZ, block, updateMesh: updateMesh);
|
||||||
|
|
||||||
// temporary tnt functionality
|
// temporary tnt functionality
|
||||||
|
|
||||||
if (block == Blocks.TNT)
|
if (block == Blocks.TNT)
|
||||||
{
|
{
|
||||||
int radius = 4;
|
|
||||||
Explode(worldX, worldY, worldZ, 4);
|
Explode(worldX, worldY, worldZ, 4);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateMesh == false) return;
|
if (!updateMesh) return;
|
||||||
|
|
||||||
if (block == Blocks.Air && IsOnChunkBorder(worldX, worldZ))
|
UpdateNeighborsAtBoundary(chunk, localX, worldY, localZ);
|
||||||
{
|
RebuildDirtyChunks();
|
||||||
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
|
}
|
||||||
{
|
|
||||||
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
|
private void UpdateNeighborsAtBoundary(Chunk chunk, int lx, int ly, int lz)
|
||||||
{
|
{
|
||||||
neighbor.UpdateChunkMesh();
|
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)
|
public bool IsOnChunkBorder(int worldX, int worldZ)
|
||||||
@@ -198,51 +202,46 @@ namespace Voxel
|
|||||||
// bounding box
|
// bounding box
|
||||||
int minX = centerX - radius;
|
int minX = centerX - radius;
|
||||||
int maxX = centerX + radius;
|
int maxX = centerX + radius;
|
||||||
int minY = centerY - radius;
|
int minY = Math.Max(0, centerY - radius);
|
||||||
int maxY = centerY + radius;
|
int maxY = Math.Min(Chunk.Height - 1, centerY + radius);
|
||||||
int minZ = centerZ - radius;
|
int minZ = centerZ - radius;
|
||||||
int maxZ = centerZ + radius;
|
int maxZ = centerZ + radius;
|
||||||
|
|
||||||
for (int x = minX; x <= maxX; x++)
|
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 dx = x - centerX;
|
||||||
int dy = y - centerY;
|
int dy = y - centerY;
|
||||||
int dz = z - centerZ;
|
int dz = z - centerZ;
|
||||||
|
|
||||||
if (dx * dx + dy * dy + dz * dz <= radiusSq)
|
if (dx * dx + dy * dy + dz * dz <= radiusSq)
|
||||||
{
|
{
|
||||||
// no update
|
chunk.SetBlock(localX, y, localZ, Blocks.Air, updateMesh: true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now rebuild meshes for all affected chunks
|
RebuildDirtyChunks();
|
||||||
foreach (var chunk in affectedChunks)
|
}
|
||||||
|
|
||||||
|
private void RebuildDirtyChunks()
|
||||||
|
{
|
||||||
|
foreach (var chunk in _chunks.Values)
|
||||||
{
|
{
|
||||||
chunk.UpdateChunkMesh();
|
if (chunk.IsDirty)
|
||||||
|
{
|
||||||
|
chunk.UpdateChunkMesh();
|
||||||
|
Renderer.UpdateChunkBuffer(chunk);
|
||||||
|
chunk.IsDirty = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Voxel.Core;
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user