Compare commits
7 Commits
66f26bc2e3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ffda6f5af | ||
|
|
01d86cd2aa | ||
|
|
f532eddfbb | ||
| 0641d22c9b | |||
| 35ecc36bdb | |||
| a1cdd6b5de | |||
| e96f78b6b0 |
218
Chunk.cs
218
Chunk.cs
@@ -1,9 +1,13 @@
|
|||||||
namespace Voxel
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Voxel
|
||||||
{
|
{
|
||||||
public class Chunk
|
public class Chunk
|
||||||
{
|
{
|
||||||
public static int Size = 16;
|
public const int Size = 16;
|
||||||
public static int Height = 256;
|
public const int Height = 256;
|
||||||
|
public const int TotalBlocks = Size * Size * Height;
|
||||||
|
|
||||||
public readonly int X;
|
public readonly int X;
|
||||||
public readonly int Y;
|
public readonly int Y;
|
||||||
private ChunkMesh _chunkMesh;
|
private ChunkMesh _chunkMesh;
|
||||||
@@ -12,13 +16,31 @@
|
|||||||
private Blocks[] _blocks;
|
private Blocks[] _blocks;
|
||||||
public Dictionary<Orientation, Chunk> Neighbors = new();
|
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)
|
public Chunk(int x, int y)
|
||||||
{
|
{
|
||||||
X = x;
|
X = x;
|
||||||
Y = y;
|
Y = y;
|
||||||
|
|
||||||
_blockData = new Dictionary<ushort, BlockData>();
|
_blockData = new Dictionary<ushort, BlockData>();
|
||||||
_blocks = new Blocks[Size * Size * Height];
|
_blocks = new Blocks[TotalBlocks];
|
||||||
_chunkMesh = new ChunkMesh(X, Y);
|
_chunkMesh = new ChunkMesh(X, Y);
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
@@ -39,6 +61,7 @@
|
|||||||
|
|
||||||
public void SetBlockIndex(int i, Blocks block)
|
public void SetBlockIndex(int i, Blocks block)
|
||||||
{
|
{
|
||||||
|
if (i < 0 || i >= TotalBlocks) return;
|
||||||
_blocks[i] = block;
|
_blocks[i] = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +74,7 @@
|
|||||||
|
|
||||||
public BlockData GetBlockData(int x, int y, int z)
|
public BlockData GetBlockData(int x, int y, int z)
|
||||||
{
|
{
|
||||||
return new BlockData();
|
return new BlockData(); // TODO: Implement
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
@@ -63,7 +86,7 @@
|
|||||||
var position = GetWorldCoordinates(x, 0, z);
|
var position = GetWorldCoordinates(x, 0, z);
|
||||||
int height = Worldgen.GetHeight(position.x, position.z);
|
int height = Worldgen.GetHeight(position.x, position.z);
|
||||||
|
|
||||||
for (int y = 0; y < 256; y++)
|
for (int y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
Blocks block = Worldgen.GetBlock(y, height);
|
Blocks block = Worldgen.GetBlock(y, height);
|
||||||
SetBlock(x, y, z, block, false);
|
SetBlock(x, y, z, block, false);
|
||||||
@@ -73,110 +96,139 @@
|
|||||||
UpdateChunkMesh();
|
UpdateChunkMesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo
|
|
||||||
public (int x, int y, int z) IndexToPosition(int i)
|
public (int x, int y, int z) IndexToPosition(int i)
|
||||||
{
|
{
|
||||||
int x = 0;
|
if (i < 0 || i >= TotalBlocks)
|
||||||
int y = 0;
|
return (0, 0, 0);
|
||||||
int z = 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);
|
return (x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (int x, int y, int z) GetWorldCoordinates(int x, int y, int z)
|
public (int x, int y, int z) GetWorldCoordinates(int localX, int localY, int localZ)
|
||||||
{
|
{
|
||||||
x += (Size * X);
|
return (localX + (X * Size), localY, localZ + (Y * Size));
|
||||||
z += (Size * Y);
|
|
||||||
return (x, y, z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private int GetBlockIndex(int x, int y, int z)
|
private int GetBlockIndex(int x, int y, int z)
|
||||||
{
|
{
|
||||||
if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15)
|
// bit shifting
|
||||||
|
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return x + z * Size + y * Size * Size;
|
return x + (z << 4) + (y << 8); // x * 1, z * 16, y * 256
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
public void UpdateChunkMesh()
|
public void UpdateChunkMesh()
|
||||||
{
|
{
|
||||||
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
|
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 x = 0; x < Size; x++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < Size; z++)
|
for (int z = 0; z < Size; z++)
|
||||||
{
|
{
|
||||||
for (int y = 0; y < Height; y++)
|
for (int y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
for (byte face = 0; face < 6; face++)
|
ProcessBlockFaces(x, y, z, faces);
|
||||||
{
|
|
||||||
int indexBase = y * Size * Size + z * Size + x;
|
|
||||||
Blocks block = _blocks[indexBase];
|
|
||||||
|
|
||||||
void AddFace()
|
|
||||||
{
|
|
||||||
FaceData faceData = new FaceData();
|
|
||||||
|
|
||||||
faceData.Facing = (Orientation)face;
|
|
||||||
faceData.Texture = BlockDefinitions.Blocks[block].FaceTextures[face];
|
|
||||||
|
|
||||||
faceData.X = (byte)x;
|
|
||||||
faceData.Y = (byte)y;
|
|
||||||
faceData.Z = (byte)z;
|
|
||||||
|
|
||||||
faces.Add(faceData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block == Blocks.Air) continue; // ignore if air
|
|
||||||
|
|
||||||
int nx = x + Offsets[face].dx;
|
|
||||||
int ny = y + Offsets[face].dy;
|
|
||||||
int nz = z + Offsets[face].dz;
|
|
||||||
|
|
||||||
// check neighbor, ignore if at chunk edge
|
|
||||||
|
|
||||||
int ni = GetBlockIndex(nx, ny, nz);
|
|
||||||
if (GetBlockIndex(nx, ny, nz) == -1)
|
|
||||||
{
|
|
||||||
if (Neighbors.TryGetValue((Orientation)face, out Chunk neighbor) && neighbor != null)
|
|
||||||
{
|
|
||||||
int localX = nx;
|
|
||||||
int localZ = nz;
|
|
||||||
|
|
||||||
if (nx < 0) localX = nx + Size;
|
|
||||||
if (nx >= Size) localX = nx - Size;
|
|
||||||
|
|
||||||
if (nz < 0) localZ = nz + Size;
|
|
||||||
if (nz >= Size) localZ = nz - Size;
|
|
||||||
|
|
||||||
Blocks neighborBlock = neighbor.GetBlock(localX, y, localZ);
|
|
||||||
if (neighborBlock != Blocks.Air)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AddFace();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_blocks[ni] == Blocks.Air)
|
|
||||||
{
|
|
||||||
AddFace();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_chunkMesh.SetFaces(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()
|
public ChunkMesh GetChunkMesh()
|
||||||
|
|||||||
39
ChunkMesh.cs
39
ChunkMesh.cs
@@ -1,25 +1,15 @@
|
|||||||
using OpenTK.Graphics.OpenGL4;
|
namespace Voxel
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Voxel
|
|
||||||
{
|
{
|
||||||
public class ChunkMesh
|
public class ChunkMesh
|
||||||
{
|
{
|
||||||
public int X;
|
public int X;
|
||||||
public int Y;
|
public int Y;
|
||||||
private byte[] _packedData;
|
private byte[] _packedData;
|
||||||
public bool NeedsUpdate = false;
|
|
||||||
public int Size = 0;
|
public int Size = 0;
|
||||||
private List<FaceData> _faces;
|
|
||||||
|
|
||||||
public ChunkMesh(int x, int y)
|
public ChunkMesh(int x, int y)
|
||||||
{
|
{
|
||||||
_packedData = new byte[0];
|
_packedData = Array.Empty<byte>();
|
||||||
X = x;
|
X = x;
|
||||||
Y = y;
|
Y = y;
|
||||||
}
|
}
|
||||||
@@ -27,18 +17,31 @@ namespace Voxel
|
|||||||
public void SetFaces(List<FaceData> faces)
|
public void SetFaces(List<FaceData> faces)
|
||||||
{
|
{
|
||||||
Size = faces.Count;
|
Size = faces.Count;
|
||||||
_faces = faces;
|
_packedData = PackFaces(faces);
|
||||||
NeedsUpdate = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetPackedData()
|
private static byte[] PackFaces(List<FaceData> faces)
|
||||||
{
|
{
|
||||||
if (NeedsUpdate)
|
const int BYTES_PER_FACE = 4;
|
||||||
|
int totalFaces = faces.Count;
|
||||||
|
var result = new byte[faces.Count * BYTES_PER_FACE];
|
||||||
|
|
||||||
|
for (int i = 0; i < totalFaces; i++)
|
||||||
{
|
{
|
||||||
_packedData = _faces.SelectMany(f => f.Pack()).ToArray();
|
var face = faces[i];
|
||||||
|
uint packed = face._data;
|
||||||
|
int offset = i * 4;
|
||||||
|
|
||||||
|
// Write little-endian (important!)
|
||||||
|
result[offset] = (byte)(packed);
|
||||||
|
result[offset + 1] = (byte)(packed >> 8);
|
||||||
|
result[offset + 2] = (byte)(packed >> 16);
|
||||||
|
result[offset + 3] = (byte)(packed >> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _packedData;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] GetPackedData() => _packedData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
FaceData.cs
45
FaceData.cs
@@ -2,25 +2,42 @@
|
|||||||
|
|
||||||
namespace Voxel
|
namespace Voxel
|
||||||
{
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct FaceData
|
public struct FaceData
|
||||||
{
|
{
|
||||||
public Orientation Facing;
|
public uint _data;
|
||||||
public byte X, Y, Z;
|
|
||||||
public Textures Texture;
|
// Bit layout:
|
||||||
public byte LightLevel;
|
// [31-24]: Y (8 bits) 0-255
|
||||||
|
// [23-20]: Z (4 bits) 0-15
|
||||||
|
// [19-16]: X (4 bits) 0-15
|
||||||
|
// [15-10]: Texture (6 bits) 0-63
|
||||||
|
// [9-7]: Facing (3 bits) 0-7
|
||||||
|
// [6-3]: Light (4 bits) 0-15
|
||||||
|
// [2-0]: unused (3 bits)
|
||||||
|
|
||||||
|
public FaceData(byte x, byte y, byte z, Orientation facing, Textures texture, byte lightLevel)
|
||||||
|
{
|
||||||
|
_data = (uint)(
|
||||||
|
((y & 0xFF) << 24) | // 8 bits
|
||||||
|
((z & 0x0F) << 20) | // 4 bits
|
||||||
|
((x & 0x0F) << 16) | // 4 bits
|
||||||
|
(((byte)texture & 0x3F) << 10) | // 6 bits (0-63)
|
||||||
|
(((byte)facing & 0x07) << 7) | // 3 bits
|
||||||
|
((lightLevel & 0x0F) << 3) // 4 bits
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte X => (byte)((_data >> 16) & 0x0F);
|
||||||
|
public byte Y => (byte)((_data >> 24) & 0xFF);
|
||||||
|
public byte Z => (byte)((_data >> 20) & 0x0F);
|
||||||
|
public Orientation Facing => (Orientation)((_data >> 7) & 0x07);
|
||||||
|
public Textures Texture => (Textures)((_data >> 10) & 0x3F);
|
||||||
|
public byte LightLevel => (byte)((_data >> 3) & 0x0F);
|
||||||
|
|
||||||
public byte[] Pack()
|
public byte[] Pack()
|
||||||
{
|
{
|
||||||
return new byte[]
|
return BitConverter.GetBytes(_data);
|
||||||
{
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
(byte)Facing,
|
|
||||||
(byte)Texture,
|
|
||||||
LightLevel,
|
|
||||||
0,0 // two bits empty
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using OpenTK.Windowing.Common;
|
using OpenTK.Windowing.Common;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Voxel
|
namespace Voxel
|
||||||
{
|
{
|
||||||
@@ -48,6 +42,8 @@ namespace Voxel
|
|||||||
|
|
||||||
public new void Tick()
|
public new void Tick()
|
||||||
{
|
{
|
||||||
|
_world.UpdateChunkLoading(Position);
|
||||||
|
|
||||||
previousPosition = Position;
|
previousPosition = Position;
|
||||||
|
|
||||||
float forwards = 0;
|
float forwards = 0;
|
||||||
@@ -64,7 +60,6 @@ namespace Voxel
|
|||||||
|
|
||||||
if (Input.GetKey(Keys.Space) && OnGround)
|
if (Input.GetKey(Keys.Space) && OnGround)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Jump");
|
|
||||||
Velocity = new Vector3(Velocity.X, 0.42f, Velocity.Z);
|
Velocity = new Vector3(Velocity.X, 0.42f, Velocity.Z);
|
||||||
OnGround = false;
|
OnGround = false;
|
||||||
}
|
}
|
||||||
|
|||||||
28
Program.cs
28
Program.cs
@@ -12,34 +12,6 @@ internal class Program
|
|||||||
World world = new World();
|
World world = new World();
|
||||||
Window window = new Window(sizeX, sizeY, title);
|
Window window = new Window(sizeX, sizeY, title);
|
||||||
|
|
||||||
Console.WriteLine("Generating map...");
|
|
||||||
|
|
||||||
int worldSizeX = 8;
|
|
||||||
int worldSizeY = 8;
|
|
||||||
|
|
||||||
float maxI = worldSizeX * worldSizeY;
|
|
||||||
int i = 0;
|
|
||||||
int lastPercentage = 0;
|
|
||||||
|
|
||||||
for (int x = 0; x < worldSizeX; x++)
|
|
||||||
{
|
|
||||||
for (int y = 0; y < worldSizeY; y++)
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
Chunk chunk = new Chunk(x, y);
|
|
||||||
world.AddChunk(chunk);
|
|
||||||
|
|
||||||
int percentage = (int)((i / maxI) * 100);
|
|
||||||
if (percentage > lastPercentage)
|
|
||||||
{
|
|
||||||
lastPercentage = percentage;
|
|
||||||
Console.WriteLine((percentage).ToString() + "%");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Generated " + maxI.ToString() + " chunks");
|
|
||||||
|
|
||||||
Renderer.SetWorld(world);
|
Renderer.SetWorld(world);
|
||||||
|
|
||||||
Vector3 startPos = new Vector3(15, 64, 15);
|
Vector3 startPos = new Vector3(15, 64, 15);
|
||||||
|
|||||||
44
Renderer.cs
44
Renderer.cs
@@ -60,46 +60,30 @@ namespace Voxel
|
|||||||
{
|
{
|
||||||
if (_world == null) return;
|
if (_world == null) return;
|
||||||
|
|
||||||
int offset = 0;
|
_chunkBufferSizes.Clear();
|
||||||
|
int faceOffset = 0;
|
||||||
|
|
||||||
foreach (Chunk chunk in _world.GetAllChunks())
|
foreach (Chunk chunk in _world.GetAllChunks())
|
||||||
{
|
{
|
||||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||||
|
|
||||||
if (chunkMesh.NeedsUpdate)
|
_chunkBufferSizes[(chunk.X, chunk.Y)] = faceOffset;
|
||||||
{
|
|
||||||
byte[] data = chunkMesh.GetPackedData();
|
|
||||||
GL.BufferSubData(BufferTarget.ShaderStorageBuffer, (IntPtr)offset * 8, chunkMesh.Size * 8, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
_chunkBufferSizes[(chunk.X, chunk.Y)] = offset;
|
byte[] data = chunkMesh.GetPackedData();
|
||||||
offset += chunkMesh.Size * 8;
|
|
||||||
|
GL.BufferSubData(BufferTarget.ShaderStorageBuffer,
|
||||||
|
(IntPtr)(faceOffset * 4), // faceOffset * 4 = byte offset
|
||||||
|
chunkMesh.Size * 4,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
faceOffset += chunkMesh.Size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderUi()
|
private static void RenderUi()
|
||||||
{
|
{
|
||||||
GL.Disable(EnableCap.DepthTest);
|
|
||||||
GL.Enable(EnableCap.Blend);
|
|
||||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
||||||
|
|
||||||
//_uiShader.Use();
|
|
||||||
|
|
||||||
//Matrix4 projection = Matrix4.CreateOrthographicOffCenter(
|
|
||||||
// 0, screenWidth, screenHeight, 0, -1, 1);
|
|
||||||
//_uiShader.SetMatrix4("projection", projection);
|
|
||||||
|
|
||||||
// Bind UI texture atlas
|
|
||||||
//_uiTexture.Bind();
|
|
||||||
|
|
||||||
// Draw all UI sprites (batch by texture for efficiency)
|
|
||||||
//foreach (var sprite in _uiSprites)
|
|
||||||
//{
|
|
||||||
//sprite.Draw();
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Restore 3D settings
|
|
||||||
GL.Disable(EnableCap.Blend);
|
|
||||||
GL.Enable(EnableCap.DepthTest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderWorld()
|
private static void RenderWorld()
|
||||||
|
|||||||
@@ -10,16 +10,23 @@ uniform vec3 cameraPosition;
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
float fogEnd = 512;
|
// Use squared distance
|
||||||
float fogStart = 32;
|
vec3 delta = cameraPosition - fragPos;
|
||||||
|
float distSq = dot(delta, delta);
|
||||||
float dist = length(cameraPosition - fragPos);
|
|
||||||
float fogFactor = (fogEnd - dist) / (fogEnd - fogStart);
|
// Precomputed fog parameters
|
||||||
fogFactor = clamp(fogFactor, 0, 1);
|
const float fogEndSq = 65536.0; // 128^2
|
||||||
|
const float fogStartSq = 1024.0; // 32^2
|
||||||
vec4 fogColor = vec4(0.8,0.8,0.8,1);
|
const float fogRangeInv = 1.0 / (fogEndSq - fogStartSq);
|
||||||
vec4 texColor = texture(uTexture, fragUV) * lighting;
|
|
||||||
vec4 color = mix(fogColor, texColor, fogFactor);
|
float fogFactor = (fogEndSq - distSq) * fogRangeInv;
|
||||||
|
fogFactor = clamp(fogFactor, 0.0, 1.0);
|
||||||
FragColor = vec4(color.rgb, texColor.a);
|
|
||||||
|
// Texture and lighting
|
||||||
|
vec4 texColor = texture(uTexture, fragUV);
|
||||||
|
texColor.rgb *= lighting;
|
||||||
|
|
||||||
|
// Fog blend
|
||||||
|
const vec3 fogColor = vec3(0.8, 0.8, 0.8);
|
||||||
|
FragColor = vec4(mix(fogColor, texColor.rgb, fogFactor), texColor.a);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
#version 430 core
|
#version 430 core
|
||||||
|
|
||||||
struct FaceData {
|
struct FaceData {
|
||||||
uint x;
|
uint pack; // All data in 4 bytes
|
||||||
uint y;
|
};
|
||||||
uint z;
|
|
||||||
uint facing; // 0=+X,1=-X,2=+Y,3=-Y,4=+Z,5=-Z
|
|
||||||
uint texture;
|
|
||||||
uint lightLevel;
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(std430, binding = 0) buffer FaceBuffer {
|
layout(std430, binding = 0) buffer FaceBuffer {
|
||||||
uint faces[];
|
uint faces[];
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
uniform mat4 projection;
|
uniform mat4 projection;
|
||||||
@@ -77,25 +72,38 @@ const vec3 offsets[6][6] = vec3[6][6](
|
|||||||
|
|
||||||
const vec2 uvs[6] = vec2[6](vec2(0,0), vec2(1,1), vec2(1,0), vec2(1,1), vec2(0,0), vec2(0,1));
|
const vec2 uvs[6] = vec2[6](vec2(0,0), vec2(1,1), vec2(1,0), vec2(1,1), vec2(0,0), vec2(0,1));
|
||||||
|
|
||||||
|
// Function to unpack the 4-byte face data
|
||||||
|
// Bit layout from C# (little-endian, but we read as uint):
|
||||||
|
// Bits 31-24: Y (8 bits) 0-255
|
||||||
|
// Bits 23-20: Z (4 bits) 0-15
|
||||||
|
// Bits 19-16: X (4 bits) 0-15
|
||||||
|
// Bits 15-10: Texture (6 bits) 0-63
|
||||||
|
// Bits 9-7: Facing (3 bits) 0-7
|
||||||
|
// Bits 6-3: Light (4 bits) 0-15
|
||||||
|
// Bits 2-0: unused (3 bits)
|
||||||
|
void unpackFace(uint pack, out uint x, out uint y, out uint z,
|
||||||
|
out uint facing, out uint texture, out uint lightLevel)
|
||||||
|
{
|
||||||
|
y = (pack >> 24) & 0xFFu; // Y: bits 24-31 (8 bits)
|
||||||
|
z = (pack >> 20) & 0x0Fu; // Z: bits 20-23 (4 bits)
|
||||||
|
x = (pack >> 16) & 0x0Fu; // X: bits 16-19 (4 bits)
|
||||||
|
texture = (pack >> 10) & 0x3Fu; // Texture: bits 10-15 (6 bits)
|
||||||
|
facing = (pack >> 7) & 0x07u; // Facing: bits 7-9 (3 bits)
|
||||||
|
lightLevel = (pack >> 3) & 0x0Fu;// Light: bits 3-6 (4 bits)
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
uint faceIndex = gl_VertexID / 6u;
|
uint faceIndex = gl_VertexID / 6u;
|
||||||
uint vertIndex = gl_VertexID % 6u;
|
uint vertIndex = gl_VertexID % 6u;
|
||||||
|
|
||||||
uint start = faceIndex * 2u; // 2 byte per face
|
uint pack = faces[faceIndex];
|
||||||
|
|
||||||
uint u0 = faces[start]; // data in uint 0
|
|
||||||
uint u1 = faces[start + 1]; // data in uint 1
|
|
||||||
|
|
||||||
// extract values from bits
|
uint x, y, z, facing, texture, lightLevel;
|
||||||
uint x = u0 & 0xFFu;
|
unpackFace(pack, x, y, z, facing, texture, lightLevel);
|
||||||
uint y = (u0 >> 8) & 0xFFu;
|
|
||||||
uint z = (u0 >> 16) & 0xFFu;
|
vec3 basePos = vec3(x, y, z) + vec3(chunkX, 0, chunkY) * 16.0;
|
||||||
uint facing = (u0 >> 24) & 0xFFu;
|
|
||||||
uint texture = u1 & 0xFFu;
|
|
||||||
uint lightLevel = (u1 >> 8) & 0xFFu;
|
|
||||||
|
|
||||||
vec3 basePos = vec3(x + chunkX * 16, y, z + chunkY * 16);
|
|
||||||
vec4 worldPos = vec4(basePos + offsets[facing][vertIndex], 1.0);
|
vec4 worldPos = vec4(basePos + offsets[facing][vertIndex], 1.0);
|
||||||
float light = float(lightLevel) / 255.0; // use later for caves and stuff
|
float light = float(lightLevel) / 255.0; // use later for caves and stuff
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Voxel
|
namespace Voxel
|
||||||
{
|
{
|
||||||
public enum Textures : uint
|
public enum Textures : byte
|
||||||
{
|
{
|
||||||
GrassTop,
|
GrassTop,
|
||||||
Stone,
|
Stone,
|
||||||
|
|||||||
7
UIElement.cs
Normal file
7
UIElement.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
public class UIElement
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
17
UISprite.cs
17
UISprite.cs
@@ -1,17 +0,0 @@
|
|||||||
using OpenTK.Mathematics;
|
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
public class UISprite
|
|
||||||
{
|
|
||||||
public Vector2 Position; // Screen pixels (not normalized)
|
|
||||||
public Vector2 Size; // Size in pixels
|
|
||||||
public Rectangle TextureRegion; // Which part of atlas to use
|
|
||||||
public Color Tint = Color.White;
|
|
||||||
public float Rotation = 0f;
|
|
||||||
public Vector2 Origin = Vector2.Zero; // Rotation/scale origin
|
|
||||||
|
|
||||||
public void Draw()
|
|
||||||
{
|
|
||||||
// Draw textured quad using TextureRegion coordinates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
209
World.cs
209
World.cs
@@ -8,6 +8,18 @@ namespace Voxel
|
|||||||
{
|
{
|
||||||
private Dictionary<(int, int), Chunk> _chunks;
|
private Dictionary<(int, int), Chunk> _chunks;
|
||||||
|
|
||||||
|
private (int x, int z) _lastCenter = (0, 0);
|
||||||
|
private int _loadDistance = 8;
|
||||||
|
bool chunkLoadingInitialized = false;
|
||||||
|
|
||||||
|
private static readonly Dictionary<Orientation, (int x, int y)> _neighborOffsets = new()
|
||||||
|
{
|
||||||
|
{ Orientation.West, (1, 0) },
|
||||||
|
{ Orientation.East, (-1, 0) },
|
||||||
|
{ Orientation.North, (0, 1) },
|
||||||
|
{ Orientation.South, (0, -1) }
|
||||||
|
};
|
||||||
|
|
||||||
public World()
|
public World()
|
||||||
{
|
{
|
||||||
_chunks = new Dictionary<(int, int), Chunk>();
|
_chunks = new Dictionary<(int, int), Chunk>();
|
||||||
@@ -23,26 +35,113 @@ namespace Voxel
|
|||||||
{
|
{
|
||||||
_chunks[(chunk.X, chunk.Y)] = chunk;
|
_chunks[(chunk.X, chunk.Y)] = chunk;
|
||||||
|
|
||||||
Dictionary<Orientation, Orientation> oppositeOrientation = new()
|
UpdateNeighboringChunks(chunk);
|
||||||
{
|
|
||||||
{Orientation.West, Orientation.East},
|
|
||||||
{Orientation.East, Orientation.West},
|
|
||||||
{Orientation.North, Orientation.South},
|
|
||||||
{Orientation.South, Orientation.North},
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
|
||||||
{
|
|
||||||
neighbor.Neighbors[oppositeOrientation[orientation]] = chunk;
|
|
||||||
chunk.Neighbors[orientation] = neighbor;
|
|
||||||
neighbor.UpdateChunkMesh();
|
|
||||||
}
|
|
||||||
chunk.UpdateChunkMesh();
|
chunk.UpdateChunkMesh();
|
||||||
|
Renderer.MarkBuffersDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveChunk(int chunkX, int chunkZ)
|
public void RemoveChunk(int chunkX, int chunkZ)
|
||||||
{
|
{
|
||||||
_chunks.Remove((chunkX, chunkZ));
|
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
||||||
|
{
|
||||||
|
var neighbors = GetChunkNeighbors(chunk).ToList();
|
||||||
|
_chunks.Remove((chunkX, chunkZ));
|
||||||
|
|
||||||
|
// 3. For each neighbor, remove reference to this chunk
|
||||||
|
foreach (var (orientation, neighbor) in neighbors)
|
||||||
|
{
|
||||||
|
var opposite = GetOppositeOrientation(orientation);
|
||||||
|
neighbor.Neighbors[opposite] = null;
|
||||||
|
neighbor.UpdateChunkMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void UpdateChunkLoading(Vector3 playerPosition)
|
||||||
|
{
|
||||||
|
int centerX = (int)Math.Floor(playerPosition.X / Chunk.Size);
|
||||||
|
int centerZ = (int)Math.Floor(playerPosition.Z / Chunk.Size);
|
||||||
|
|
||||||
|
// Quick check - skip if still in same chunk
|
||||||
|
if ((centerX == _lastCenter.x && centerZ == _lastCenter.z) && chunkLoadingInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastCenter = (centerX, centerZ);
|
||||||
|
|
||||||
|
// Calculate bounds
|
||||||
|
int minX = centerX - _loadDistance;
|
||||||
|
int maxX = centerX + _loadDistance;
|
||||||
|
int minZ = centerZ - _loadDistance;
|
||||||
|
int maxZ = centerZ + _loadDistance;
|
||||||
|
|
||||||
|
// Unload chunks outside range
|
||||||
|
UnloadDistantChunks(minX, maxX, minZ, maxZ);
|
||||||
|
|
||||||
|
// Load chunks inside range
|
||||||
|
LoadChunksInRange(minX, maxX, minZ, maxZ);
|
||||||
|
|
||||||
|
chunkLoadingInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnloadDistantChunks(int minX, int maxX, int minZ, int maxZ)
|
||||||
|
{
|
||||||
|
var chunksToRemove = new List<(int, int)>();
|
||||||
|
|
||||||
|
foreach (var (chunkX, chunkZ) in _chunks.Keys)
|
||||||
|
{
|
||||||
|
if (chunkX < minX || chunkX > maxX || chunkZ < minZ || chunkZ > maxZ)
|
||||||
|
{
|
||||||
|
chunksToRemove.Add((chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunkPos in chunksToRemove)
|
||||||
|
{
|
||||||
|
RemoveChunk(chunkPos.Item1, chunkPos.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadChunksInRange(int minX, int maxX, int minZ, int maxZ)
|
||||||
|
{
|
||||||
|
for (int x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
for (int z = minZ; z <= maxZ; z++)
|
||||||
|
{
|
||||||
|
if (!_chunks.ContainsKey((x, z)))
|
||||||
|
{
|
||||||
|
Chunk chunk = new Chunk(x, z);
|
||||||
|
AddChunk(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
||||||
|
{
|
||||||
|
Orientation opposite = GetOppositeOrientation(orientation);
|
||||||
|
neighbor.Neighbors[opposite] = chunk;
|
||||||
|
chunk.Neighbors[orientation] = neighbor;
|
||||||
|
neighbor.UpdateChunkMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Orientation GetOppositeOrientation(Orientation orientation)
|
||||||
|
{
|
||||||
|
return orientation switch
|
||||||
|
{
|
||||||
|
Orientation.West => Orientation.East,
|
||||||
|
Orientation.East => Orientation.West,
|
||||||
|
Orientation.North => Orientation.South,
|
||||||
|
Orientation.South => Orientation.North,
|
||||||
|
_ => orientation
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Chunk> GetAllChunks()
|
public IEnumerable<Chunk> GetAllChunks()
|
||||||
@@ -52,17 +151,47 @@ namespace Voxel
|
|||||||
|
|
||||||
public Blocks GetBlock(int worldX, int worldY, int worldZ)
|
public Blocks GetBlock(int worldX, int worldY, int worldZ)
|
||||||
{
|
{
|
||||||
int chunkX = worldX / Chunk.Size;
|
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(worldX, worldZ);
|
||||||
int chunkZ = worldZ / Chunk.Size;
|
|
||||||
Chunk chunk = GetChunk(chunkX, chunkZ);
|
Chunk chunk = GetChunk(chunkX, chunkZ);
|
||||||
if (chunk == null) return 0; // air if chunk not loaded
|
if (chunk == null) return 0; // air if chunk not loaded
|
||||||
|
|
||||||
int localX = worldX % Chunk.Size;
|
|
||||||
int localZ = worldZ % Chunk.Size;
|
|
||||||
|
|
||||||
return chunk.GetBlock(localX, worldY, localZ);
|
return chunk.GetBlock(localX, worldY, localZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetBlock(int worldX, int worldY, int worldZ, Blocks block)
|
||||||
|
{
|
||||||
|
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(worldX, worldZ);
|
||||||
|
|
||||||
|
Chunk chunk = GetChunk(chunkX, chunkZ);
|
||||||
|
if (chunk == null) return;
|
||||||
|
|
||||||
|
chunk.SetBlock(localX, worldY, localZ, block);
|
||||||
|
|
||||||
|
if (block == Blocks.Air && ((localX == Chunk.Size - 1 || localX == 0) || (localZ == Chunk.Size - 1 || localZ == 0)))
|
||||||
|
{
|
||||||
|
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
|
||||||
|
{
|
||||||
|
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
|
||||||
|
{
|
||||||
|
neighbor.UpdateChunkMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int chunkX, int chunkZ, int localX, int localZ) WorldToChunkCoords(int worldX, int worldZ)
|
||||||
|
{
|
||||||
|
int chunkX = worldX >= 0 ? worldX / Chunk.Size : (worldX + 1) / Chunk.Size - 1;
|
||||||
|
int chunkZ = worldZ >= 0 ? worldZ / Chunk.Size : (worldZ + 1) / Chunk.Size - 1;
|
||||||
|
|
||||||
|
int localX = ((worldX % Chunk.Size) + Chunk.Size) % Chunk.Size;
|
||||||
|
int localZ = ((worldZ % Chunk.Size) + Chunk.Size) % Chunk.Size;
|
||||||
|
|
||||||
|
return (chunkX, chunkZ, localX, localZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
|
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
|
||||||
{
|
{
|
||||||
var orientations = new List<Orientation>();
|
var orientations = new List<Orientation>();
|
||||||
@@ -75,46 +204,14 @@ namespace Voxel
|
|||||||
return orientations;
|
return orientations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBlock(int worldX, int worldY, int worldZ, Blocks block)
|
|
||||||
{
|
|
||||||
int chunkX = worldX / Chunk.Size;
|
|
||||||
int chunkZ = worldZ / Chunk.Size;
|
|
||||||
Chunk chunk = GetChunk(chunkX, chunkZ);
|
|
||||||
if (chunk == null) return;
|
|
||||||
|
|
||||||
int localX = worldX % Chunk.Size;
|
|
||||||
int localZ = worldZ % Chunk.Size;
|
|
||||||
|
|
||||||
chunk.SetBlock(localX, worldY, localZ, block);
|
|
||||||
|
|
||||||
if (block == Blocks.Air && (localX == 15 || localX == 0) || (localZ == 15 || localZ == 0))
|
|
||||||
{
|
|
||||||
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
|
|
||||||
{
|
|
||||||
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
|
|
||||||
{
|
|
||||||
neighbor.UpdateChunkMesh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
|
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
|
||||||
{
|
{
|
||||||
Dictionary<Orientation, (int x, int y)> offsets = new()
|
int chunkX = chunk.X;
|
||||||
{
|
int chunkY = chunk.Y;
|
||||||
{ Orientation.West, (1, 0) },
|
|
||||||
{ Orientation.East, (-1, 0) },
|
|
||||||
{ Orientation.North, (0, 1) },
|
|
||||||
{ Orientation.South, (0, -1) }
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var kv in offsets)
|
foreach (var kv in _neighborOffsets)
|
||||||
{
|
{
|
||||||
int nx = chunk.X + kv.Value.x;
|
Chunk neighbor = GetChunk(chunkX + kv.Value.x, chunkY + kv.Value.y);
|
||||||
int ny = chunk.Y + kv.Value.y;
|
|
||||||
|
|
||||||
Chunk neighbor = GetChunk(nx, ny);
|
|
||||||
if (neighbor != null)
|
if (neighbor != null)
|
||||||
yield return (kv.Key, neighbor);
|
yield return (kv.Key, neighbor);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user