feat(core): add Vertex.TerrainLayer + LandblockMesh layer map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-10 20:16:25 +02:00
parent bc69f0cdf1
commit 324abed6eb
7 changed files with 64 additions and 29 deletions

View file

@ -15,6 +15,9 @@ public class LandblockMeshTests
private static readonly float[] IdentityHeightTable =
Enumerable.Range(0, 256).Select(i => i * 2f).ToArray();
private static readonly IReadOnlyDictionary<uint, uint> EmptyTerrainMap =
new Dictionary<uint, uint>();
private static LandBlock BuildFlatLandBlock(byte heightIndex = 0)
{
var block = new LandBlock
@ -36,7 +39,7 @@ public class LandblockMeshTests
{
var block = BuildFlatLandBlock();
var mesh = LandblockMesh.Build(block, IdentityHeightTable);
var mesh = LandblockMesh.Build(block, IdentityHeightTable, EmptyTerrainMap);
Assert.Equal(81, mesh.Vertices.Length);
Assert.Equal(128 * 3, mesh.Indices.Length);
@ -47,7 +50,7 @@ public class LandblockMeshTests
{
var block = BuildFlatLandBlock();
var mesh = LandblockMesh.Build(block, IdentityHeightTable);
var mesh = LandblockMesh.Build(block, IdentityHeightTable, EmptyTerrainMap);
var minX = mesh.Vertices.Min(v => v.Position.X);
var maxX = mesh.Vertices.Max(v => v.Position.X);
@ -65,7 +68,7 @@ public class LandblockMeshTests
{
var block = BuildFlatLandBlock(heightIndex: 10);
var mesh = LandblockMesh.Build(block, IdentityHeightTable);
var mesh = LandblockMesh.Build(block, IdentityHeightTable, EmptyTerrainMap);
var zs = mesh.Vertices.Select(v => v.Position.Z).Distinct().ToArray();
Assert.Single(zs);
@ -76,12 +79,36 @@ public class LandblockMeshTests
{
var block = BuildFlatLandBlock(heightIndex: 5);
var mesh = LandblockMesh.Build(block, IdentityHeightTable);
var mesh = LandblockMesh.Build(block, IdentityHeightTable, EmptyTerrainMap);
// AC's Land::LandHeightTable scales height byte index by 2.0f for the simple ramp case.
Assert.Equal(10.0f, mesh.Vertices[0].Position.Z);
}
[Fact]
public void Build_PerVertexTerrainLayer_UsesMappedLayerIndex()
{
var block = BuildFlatLandBlock();
// TerrainInfo is a struct with implicit conversion from ushort. The low 5 bits
// of the ushort encode TerrainTextureType via TerrainInfo.Type.
// Set vertex at x-major index (x=2, y=3) to terrain type 7.
block.Terrain[2 * 9 + 3] = (ushort)7; // low 5 bits = 7
var map = new Dictionary<uint, uint>
{
[0] = 0u, // default type → atlas layer 0
[7] = 4u, // type 7 → atlas layer 4
};
var mesh = LandblockMesh.Build(block, IdentityHeightTable, map);
// Vertex buffer internal order is y*9+x, so vertex at world (x=2, y=3) is at
// index 3*9+2 = 29.
Assert.Equal(4u, mesh.Vertices[3 * 9 + 2].TerrainLayer);
// An untouched vertex still has type 0, maps to layer 0.
Assert.Equal(0u, mesh.Vertices[0].TerrainLayer);
}
[Fact]
public void Build_HeightmapPackedAsXMajor_NotYMajor()
{
@ -98,7 +125,7 @@ public class LandblockMeshTests
var block = BuildFlatLandBlock();
block.Height[2 * 9 + 0] = 5; // x=2, y=0 in x-major packing
var mesh = LandblockMesh.Build(block, IdentityHeightTable);
var mesh = LandblockMesh.Build(block, IdentityHeightTable, EmptyTerrainMap);
// Find vertices by position. Vertex buffer uses y*9+x internally.
var vAt_x2_y0 = mesh.Vertices[0 * 9 + 2]; // world (48, 0)