Extracts the bilinear heightmap interpolation from GameWindow's inlined SampleTerrainZ into a reusable class. Also adds outdoor cell ID computation (8×8 grid of 24-unit cells, 0x0001..0x0040). First component of the physics collision engine. 6 new tests, all green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
3.2 KiB
C#
96 lines
3.2 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics;
|
|
|
|
public class TerrainSurfaceTests
|
|
{
|
|
// A height table where index N maps to N * 1.0f (linear).
|
|
// Makes test assertions predictable: height byte 10 → Z = 10.0.
|
|
private static float[] LinearHeightTable()
|
|
{
|
|
var table = new float[256];
|
|
for (int i = 0; i < 256; i++) table[i] = i * 1.0f;
|
|
return table;
|
|
}
|
|
|
|
// A flat heightmap where every vertex is height byte 50.
|
|
private static byte[] FlatHeightmap(byte value = 50)
|
|
{
|
|
var heights = new byte[81];
|
|
Array.Fill(heights, value);
|
|
return heights;
|
|
}
|
|
|
|
[Fact]
|
|
public void SampleZ_FlatTerrain_ReturnsSameValueEverywhere()
|
|
{
|
|
var surface = new TerrainSurface(FlatHeightmap(50), LinearHeightTable());
|
|
|
|
Assert.Equal(50f, surface.SampleZ(0f, 0f));
|
|
Assert.Equal(50f, surface.SampleZ(96f, 96f));
|
|
Assert.Equal(50f, surface.SampleZ(191f, 191f));
|
|
}
|
|
|
|
[Fact]
|
|
public void SampleZ_SlopeAlongX_InterpolatesLinearly()
|
|
{
|
|
// Heights increase along X: column 0 = byte 10, column 8 = byte 90.
|
|
// Each column step is (90-10)/8 = 10 bytes.
|
|
var heights = new byte[81];
|
|
for (int x = 0; x < 9; x++)
|
|
for (int y = 0; y < 9; y++)
|
|
heights[x * 9 + y] = (byte)(10 + x * 10);
|
|
|
|
var surface = new TerrainSurface(heights, LinearHeightTable());
|
|
|
|
// At x=0 (vertex 0): Z = 10
|
|
Assert.Equal(10f, surface.SampleZ(0f, 96f), precision: 1);
|
|
// At x=96 (midpoint, vertex 4): Z = 50
|
|
Assert.Equal(50f, surface.SampleZ(96f, 96f), precision: 1);
|
|
// At x=192 (vertex 8): Z = 90
|
|
Assert.Equal(90f, surface.SampleZ(192f, 96f), precision: 1);
|
|
// At x=48 (between vertex 2 and 3): Z = 30 + 0.5 * 10 = 35
|
|
// vertex 2 = byte 30, vertex 3 = byte 40, midpoint = 35
|
|
Assert.Equal(35f, surface.SampleZ(60f, 96f), precision: 1);
|
|
}
|
|
|
|
[Fact]
|
|
public void SampleZ_ClampsOutOfBounds()
|
|
{
|
|
var surface = new TerrainSurface(FlatHeightmap(42), LinearHeightTable());
|
|
|
|
// Negative coordinates clamp to 0
|
|
Assert.Equal(42f, surface.SampleZ(-10f, -10f));
|
|
// Beyond 192 clamps to boundary
|
|
Assert.Equal(42f, surface.SampleZ(300f, 300f));
|
|
}
|
|
|
|
[Fact]
|
|
public void ComputeOutdoorCellId_Origin_ReturnsFirst()
|
|
{
|
|
var surface = new TerrainSurface(FlatHeightmap(), LinearHeightTable());
|
|
|
|
// Cell (0,0) at position (0,0) → cell ID 0x0001
|
|
Assert.Equal(0x0001u, surface.ComputeOutdoorCellId(0f, 0f));
|
|
}
|
|
|
|
[Fact]
|
|
public void ComputeOutdoorCellId_SecondColumn_ReturnsCorrect()
|
|
{
|
|
var surface = new TerrainSurface(FlatHeightmap(), LinearHeightTable());
|
|
|
|
// 24 units in X = cell (1, 0) → cell ID 0x0001 + 1*8 = 0x0009
|
|
Assert.Equal(0x0009u, surface.ComputeOutdoorCellId(24f, 0f));
|
|
}
|
|
|
|
[Fact]
|
|
public void ComputeOutdoorCellId_LastCell_Returns0x0040()
|
|
{
|
|
var surface = new TerrainSurface(FlatHeightmap(), LinearHeightTable());
|
|
|
|
// Cell (7,7) at position (191,191) → 0x0001 + 7*8 + 7 = 0x0040
|
|
Assert.Equal(0x0040u, surface.ComputeOutdoorCellId(191f, 191f));
|
|
}
|
|
}
|