using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Numerics; using AcDream.Core.Physics; using DatReaderWriter.Enums; using Xunit; namespace AcDream.Core.Tests.Physics; public class CellSurfaceTests { /// /// Build a minimal CellSurface representing a flat square floor /// centered at (originX, originY) with the given half-size and Z. /// The floor polygon is a quad: 4 vertices at the corners. /// private static CellSurface MakeFlatFloor( uint cellId, float originX, float originY, float z, float halfSize = 10f) { // 4 vertices forming a square floor at the given Z, in WORLD space. var vertices = new Dictionary { [0] = new(originX - halfSize, originY - halfSize, z), [1] = new(originX + halfSize, originY - halfSize, z), [2] = new(originX + halfSize, originY + halfSize, z), [3] = new(originX - halfSize, originY + halfSize, z), }; // One quad polygon with 4 vertex IDs. var polygonVertexIds = new List> { new() { 0, 1, 2, 3 }, }; return new CellSurface(cellId, vertices, polygonVertexIds); } [Fact] public void SampleFloorZ_InsideFlat_ReturnsZ() { var surface = MakeFlatFloor(0x0100, originX: 50f, originY: 50f, z: 10f); float? z = surface.SampleFloorZ(50f, 50f); Assert.NotNull(z); Assert.Equal(10f, z!.Value, precision: 2); } [Fact] public void SampleFloorZ_OutsideFloor_ReturnsNull() { var surface = MakeFlatFloor(0x0100, originX: 50f, originY: 50f, z: 10f, halfSize: 5f); float? z = surface.SampleFloorZ(100f, 100f); Assert.Null(z); } [Fact] public void SampleFloorZ_AtEdge_ReturnsZ() { var surface = MakeFlatFloor(0x0100, originX: 50f, originY: 50f, z: 10f, halfSize: 10f); // Right at the edge of the polygon. float? z = surface.SampleFloorZ(60f, 50f); Assert.NotNull(z); Assert.Equal(10f, z!.Value, precision: 2); } [Fact] public void SampleFloorZ_SlopedFloor_InterpolatesZ() { // A triangular floor that slopes from Z=0 to Z=20. var vertices = new Dictionary { [0] = new(0f, 0f, 0f), [1] = new(20f, 0f, 0f), [2] = new(10f, 20f, 20f), }; var polygons = new List> { new() { 0, 1, 2 } }; var surface = new CellSurface(0x0100, vertices, polygons); // At the centroid (10, 6.67): Z should be roughly 6.67 float? z = surface.SampleFloorZ(10f, 6.67f); Assert.NotNull(z); Assert.InRange(z!.Value, 5f, 8f); // approximate } [Fact] public void PreparedPolygonTable_MatchesLegacyWorldSurface() { var vertices = new Dictionary { [0] = new(-2f, -2f, 1f), [1] = new(2f, -2f, 2f), [2] = new(2f, 2f, 4f), [3] = new(-2f, 2f, 3f), }; var table = new FlatPolygonTable( ImmutableArray.Create( new FlatCollisionPolygon( 0, default, CullMode.None, 4, new FlatIndexRange(0, 4))), ImmutableArray.Create( vertices[0], vertices[1], vertices[2], vertices[3])); Quaternion rotation = Quaternion.CreateFromAxisAngle( Vector3.UnitZ, 0.37f); Vector3 translation = new(17f, -9f, 6f); var prepared = new CellSurface( 0x0100, table, rotation, translation); Vector3 localSample = new(0.25f, -0.5f, 0f); Vector3 worldSample = Vector3.Transform(localSample, rotation) + translation; var transformedVertices = vertices.ToDictionary( static pair => pair.Key, pair => Vector3.Transform(pair.Value, rotation) + translation); var legacy = new CellSurface( 0x0100, transformedVertices, [new List { 0, 1, 2, 3 }]); float? expected = legacy.SampleFloorZ(worldSample.X, worldSample.Y); float? actual = prepared.SampleFloorZ(worldSample.X, worldSample.Y); Assert.Equal(expected.HasValue, actual.HasValue); Assert.Equal( BitConverter.SingleToInt32Bits(expected!.Value), BitConverter.SingleToInt32Bits(actual!.Value)); } }