feat: add block model with IBlock flyweight and registry
This commit is contained in:
52
VoxelEngine.Tests/BlockRegistryTests.cs
Normal file
52
VoxelEngine.Tests/BlockRegistryTests.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using VoxelEngine.World;
|
||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace VoxelEngine.Tests;
|
||||||
|
|
||||||
|
public class BlockRegistryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Get_ReturnsRegisteredBlockById()
|
||||||
|
{
|
||||||
|
var registry = new BlockRegistry();
|
||||||
|
|
||||||
|
Assert.IsType<AirBlock>(registry.Get(0));
|
||||||
|
Assert.IsType<StoneBlock>(registry.Get(1));
|
||||||
|
Assert.IsType<DirtBlock>(registry.Get(2));
|
||||||
|
Assert.IsType<GrassBlock>(registry.Get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Air_IsNotOpaqueOrSolid_OthersAre()
|
||||||
|
{
|
||||||
|
var registry = new BlockRegistry();
|
||||||
|
|
||||||
|
Assert.False(registry.Get(0).IsOpaque);
|
||||||
|
Assert.False(registry.Get(0).IsSolid);
|
||||||
|
Assert.True(registry.Get(1).IsOpaque);
|
||||||
|
Assert.True(registry.Get(1).IsSolid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Grass_HasDifferentTexturesPerFace()
|
||||||
|
{
|
||||||
|
var registry = new BlockRegistry();
|
||||||
|
IBlock grass = registry.Get(3);
|
||||||
|
|
||||||
|
Assert.Equal(0, grass.GetTextureIndex(BlockFace.Top));
|
||||||
|
Assert.Equal(1, grass.GetTextureIndex(BlockFace.Bottom));
|
||||||
|
Assert.Equal(3, grass.GetTextureIndex(BlockFace.North));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NamedAccessors_MatchIds()
|
||||||
|
{
|
||||||
|
var registry = new BlockRegistry();
|
||||||
|
|
||||||
|
Assert.Equal((ushort)0, registry.Air.Id);
|
||||||
|
Assert.Equal((ushort)1, registry.Stone.Id);
|
||||||
|
Assert.Equal((ushort)2, registry.Dirt.Id);
|
||||||
|
Assert.Equal((ushort)3, registry.Grass.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
World/BlockFace.cs
Normal file
11
World/BlockFace.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace VoxelEngine.World;
|
||||||
|
|
||||||
|
public enum BlockFace
|
||||||
|
{
|
||||||
|
Top, // +Y
|
||||||
|
Bottom, // -Y
|
||||||
|
North, // -Z
|
||||||
|
South, // +Z
|
||||||
|
East, // +X
|
||||||
|
West // -X
|
||||||
|
}
|
||||||
26
World/BlockRegistry.cs
Normal file
26
World/BlockRegistry.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World;
|
||||||
|
|
||||||
|
public sealed class BlockRegistry
|
||||||
|
{
|
||||||
|
private readonly IBlock[] _byId;
|
||||||
|
|
||||||
|
public AirBlock Air { get; } = new();
|
||||||
|
public StoneBlock Stone { get; } = new();
|
||||||
|
public DirtBlock Dirt { get; } = new();
|
||||||
|
public GrassBlock Grass { get; } = new();
|
||||||
|
|
||||||
|
public BlockRegistry()
|
||||||
|
{
|
||||||
|
// Index by id. Order does not matter; slot = block.Id.
|
||||||
|
var blocks = new IBlock[] { Air, Stone, Dirt, Grass };
|
||||||
|
_byId = new IBlock[blocks.Length];
|
||||||
|
foreach (IBlock block in blocks)
|
||||||
|
{
|
||||||
|
_byId[block.Id] = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBlock Get(ushort id) => _byId[id];
|
||||||
|
}
|
||||||
10
World/Blocks/AirBlock.cs
Normal file
10
World/Blocks/AirBlock.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
public sealed class AirBlock : IBlock
|
||||||
|
{
|
||||||
|
public ushort Id => 0;
|
||||||
|
public string Name => "Air";
|
||||||
|
public bool IsOpaque => false;
|
||||||
|
public bool IsSolid => false;
|
||||||
|
public int GetTextureIndex(BlockFace face) => 0;
|
||||||
|
}
|
||||||
10
World/Blocks/DirtBlock.cs
Normal file
10
World/Blocks/DirtBlock.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
public sealed class DirtBlock : IBlock
|
||||||
|
{
|
||||||
|
public ushort Id => 2;
|
||||||
|
public string Name => "Dirt";
|
||||||
|
public bool IsOpaque => true;
|
||||||
|
public bool IsSolid => true;
|
||||||
|
public int GetTextureIndex(BlockFace face) => 1;
|
||||||
|
}
|
||||||
16
World/Blocks/GrassBlock.cs
Normal file
16
World/Blocks/GrassBlock.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
public sealed class GrassBlock : IBlock
|
||||||
|
{
|
||||||
|
public ushort Id => 3;
|
||||||
|
public string Name => "Grass";
|
||||||
|
public bool IsOpaque => true;
|
||||||
|
public bool IsSolid => true;
|
||||||
|
|
||||||
|
public int GetTextureIndex(BlockFace face) => face switch
|
||||||
|
{
|
||||||
|
BlockFace.Top => 0, // grass top
|
||||||
|
BlockFace.Bottom => 1, // dirt
|
||||||
|
_ => 3 // grass side
|
||||||
|
};
|
||||||
|
}
|
||||||
10
World/Blocks/StoneBlock.cs
Normal file
10
World/Blocks/StoneBlock.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
public sealed class StoneBlock : IBlock
|
||||||
|
{
|
||||||
|
public ushort Id => 1;
|
||||||
|
public string Name => "Stone";
|
||||||
|
public bool IsOpaque => true;
|
||||||
|
public bool IsSolid => true;
|
||||||
|
public int GetTextureIndex(BlockFace face) => 2;
|
||||||
|
}
|
||||||
21
World/IBlock.cs
Normal file
21
World/IBlock.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace VoxelEngine.World;
|
||||||
|
|
||||||
|
public interface IBlock
|
||||||
|
{
|
||||||
|
ushort Id { get; }
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
// Drives face culling: a face is emitted only if its neighbor is not opaque.
|
||||||
|
bool IsOpaque { get; }
|
||||||
|
|
||||||
|
// Reserved for future collision (not used in the baseline).
|
||||||
|
bool IsSolid { get; }
|
||||||
|
|
||||||
|
// Atlas tile index for the given face.
|
||||||
|
int GetTextureIndex(BlockFace face);
|
||||||
|
|
||||||
|
// Interaction hooks — capability present now, system built post-baseline.
|
||||||
|
void OnInteract() { }
|
||||||
|
void OnPlaced() { }
|
||||||
|
void OnBroken() { }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user