diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index a6e041c..f9c742d 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -123,7 +123,17 @@ public sealed class GameWindow : IDisposable Console.WriteLine($"loaded landblock 0x{landblockId:X8}"); - var meshData = LandblockMesh.Build(block); + // Load the non-linear LandHeightTable from the Region dat. AC encodes + // per-vertex heights as byte indices into this 256-entry float table, + // not as a simple * 2.0 ramp — building placements depend on the real + // table, so terrain rendered with the simplified scale would leave + // buildings floating or buried. + var region = _dats.Get(0x13000000u); + var heightTable = region?.LandDefs.LandHeightTable; + if (heightTable is null || heightTable.Length < 256) + throw new InvalidOperationException("Region.LandDefs.LandHeightTable missing or truncated"); + + var meshData = LandblockMesh.Build(block, heightTable); _terrain = new TerrainRenderer(_gl, meshData, _shader); _textureCache = new TextureCache(_gl, _dats); diff --git a/src/AcDream.Core/Terrain/LandblockMesh.cs b/src/AcDream.Core/Terrain/LandblockMesh.cs index 0cb4f7d..e294445 100644 --- a/src/AcDream.Core/Terrain/LandblockMesh.cs +++ b/src/AcDream.Core/Terrain/LandblockMesh.cs @@ -11,17 +11,25 @@ public static class LandblockMesh private const int VerticesPerSide = 9; // 9x9 heightmap grid private const int CellsPerSide = VerticesPerSide - 1; // 8x8 cells private const float CellSize = 24.0f; // world units per cell edge - private const float HeightScale = 2.0f; // byte height -> world z - public static LandblockMeshData Build(LandBlock block) + /// + /// Build the CPU mesh for one landblock's heightmap. + /// is the 256-entry non-linear height lookup from Region.LandDefs.LandHeightTable — + /// AC encodes per-vertex heights as indices into this table, not raw world-Z. + /// + public static LandblockMeshData Build(LandBlock block, float[] heightTable) { + ArgumentNullException.ThrowIfNull(heightTable); + if (heightTable.Length < 256) + throw new ArgumentException("heightTable must have 256 entries", nameof(heightTable)); + var vertices = new Vertex[VerticesPerSide * VerticesPerSide]; for (int y = 0; y < VerticesPerSide; y++) { for (int x = 0; x < VerticesPerSide; x++) { int i = y * VerticesPerSide + x; - float height = block.Height[i] * HeightScale; + float height = heightTable[block.Height[i]]; vertices[i] = new Vertex( Position: new Vector3(x * CellSize, y * CellSize, height), Normal: Vector3.UnitZ, diff --git a/tests/AcDream.Core.Tests/Terrain/LandblockMeshTests.cs b/tests/AcDream.Core.Tests/Terrain/LandblockMeshTests.cs index c4e63ad..bc81aac 100644 --- a/tests/AcDream.Core.Tests/Terrain/LandblockMeshTests.cs +++ b/tests/AcDream.Core.Tests/Terrain/LandblockMeshTests.cs @@ -7,6 +7,14 @@ namespace AcDream.Core.Tests.Terrain; public class LandblockMeshTests { + /// + /// Synthetic height table that mirrors Phase 1's simplified "* 2.0f" scale so + /// the existing tests continue to describe the same behavior. Real AC uses a + /// non-linear table from Region.LandDefs.LandHeightTable loaded at runtime. + /// + private static readonly float[] IdentityHeightTable = + Enumerable.Range(0, 256).Select(i => i * 2f).ToArray(); + private static LandBlock BuildFlatLandBlock(byte heightIndex = 0) { var block = new LandBlock @@ -28,7 +36,7 @@ public class LandblockMeshTests { var block = BuildFlatLandBlock(); - var mesh = LandblockMesh.Build(block); + var mesh = LandblockMesh.Build(block, IdentityHeightTable); Assert.Equal(81, mesh.Vertices.Length); Assert.Equal(128 * 3, mesh.Indices.Length); @@ -39,7 +47,7 @@ public class LandblockMeshTests { var block = BuildFlatLandBlock(); - var mesh = LandblockMesh.Build(block); + var mesh = LandblockMesh.Build(block, IdentityHeightTable); var minX = mesh.Vertices.Min(v => v.Position.X); var maxX = mesh.Vertices.Max(v => v.Position.X); @@ -57,7 +65,7 @@ public class LandblockMeshTests { var block = BuildFlatLandBlock(heightIndex: 10); - var mesh = LandblockMesh.Build(block); + var mesh = LandblockMesh.Build(block, IdentityHeightTable); var zs = mesh.Vertices.Select(v => v.Position.Z).Distinct().ToArray(); Assert.Single(zs); @@ -68,7 +76,7 @@ public class LandblockMeshTests { var block = BuildFlatLandBlock(heightIndex: 5); - var mesh = LandblockMesh.Build(block); + var mesh = LandblockMesh.Build(block, IdentityHeightTable); // 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);