using System.Numerics; using AcDream.App.Rendering; using AcDream.App.Rendering.Walk; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// Campaign FW4 slice 1 — : /// an interior root's outside-view slices come from the walk's OWN /// outside_view (pixel screen points, origin top-left, +Y down), /// converted to the assembler's standard-NDC s. /// public class WalkOutsideViewReassemblyTests { private const float W = 1024f, H = 720f; private sealed class StubRays : IWalkRayCaster { public Vector3 RayThrough(float screenX, float screenY) => Vector3.Normalize(new Vector3(screenX - W / 2f, screenY - H / 2f, 1000f)); } private static WalkPortalView WalkViewOfPixelQuads(params Vector2[][] pixelQuads) { var view = new WalkPortalView(); var rays = new StubRays(); foreach (Vector2[] quad in pixelQuads) { var pts = new WalkScreenPoint[quad.Length]; for (int i = 0; i < quad.Length; i++) pts[i] = new WalkScreenPoint(quad[i].X, quad[i].Y, 0f, 1f); Assert.True(WalkCopyView.Append(view, pts, rays, Vector3.Zero)); } return view; } private static ClipFrameAssembly AssembledWithOldOutside() { var pv = new PortalVisibilityFrame(); pv.OutsideView.Add(new ViewPolygon(new[] { new Vector2(-0.9f, -0.9f), new Vector2(0.9f, -0.9f), new Vector2(0.9f, 0.9f), new Vector2(-0.9f, 0.9f), })); return ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); } [Fact] public void PixelDoorway_MapsToExpectedNdcAabbAndPlanes() { ClipFrameAssembly asm = AssembledWithOldOutside(); // Pixel quad x∈[256,512], y∈[180,360] → NDC x∈[-0.5,0], y∈[0,0.5] // (yNdc = 1 − 2·py/H flips the axis: py=180 → +0.5, py=360 → 0). WalkPortalView walkView = WalkViewOfPixelQuads(new[] { new Vector2(256f, 180f), new Vector2(512f, 180f), new Vector2(512f, 360f), new Vector2(256f, 360f), }); ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H); ClipViewSlice slice = Assert.Single(asm.OutsideViewSlices); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); Assert.True(asm.OutdoorVisible); Assert.Equal(4, asm.OutsidePlaneCount); Assert.Equal(slice.Slot, asm.OutdoorSlot); Assert.NotEqual(0, slice.Slot); Assert.Equal(-0.5f, slice.NdcAabb.X, 3); Assert.Equal(0f, slice.NdcAabb.Y, 3); Assert.Equal(0f, slice.NdcAabb.Z, 3); Assert.Equal(0.5f, slice.NdcAabb.W, 3); Assert.Equal(slice.NdcAabb, asm.OutsideViewNdcAabb); // Every corner of the doorway satisfies every inward plane // (n·p + d >= 0), and a point far outside fails at least one. Vector2[] ndcCorners = { new(-0.5f, 0f), new(0f, 0f), new(0f, 0.5f), new(-0.5f, 0.5f), }; foreach (Vector2 corner in ndcCorners) { foreach (Vector4 plane in slice.Planes) Assert.True(plane.X * corner.X + plane.Y * corner.Y + plane.W >= -1e-4f); } bool outsideFails = false; foreach (Vector4 plane in slice.Planes) outsideFails |= plane.X * 0.9f + plane.Y * -0.9f + plane.W < 0f; Assert.True(outsideFails); } [Fact] public void FullViewportWalkQuad_CoversFullNdc() { ClipFrameAssembly asm = AssembledWithOldOutside(); var walkView = new WalkPortalView(); Assert.True(WalkCopyView.AppendFullViewportQuad( walkView, new StubRays(), Vector3.Zero, W, H)); ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H); ClipViewSlice slice = Assert.Single(asm.OutsideViewSlices); Assert.Equal(new Vector4(-1f, -1f, 1f, 1f), slice.NdcAabb); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); } [Fact] public void EmptyWalkView_YieldsSkipMode() { ClipFrameAssembly asm = AssembledWithOldOutside(); Assert.True(asm.OutdoorVisible); // the old view was visible pre-cutover ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, new WalkPortalView(), W, H); Assert.Empty(asm.OutsideViewSlices); Assert.False(asm.OutdoorVisible); Assert.False(asm.HasOutsideView); Assert.Equal(TerrainClipMode.Skip, asm.TerrainMode); Assert.Equal(0, asm.OutsidePlaneCount); Assert.Equal(0, asm.OutdoorSlot); } [Fact] public void TwoWalkViews_ProduceTwoSlicesWithDistinctSlots() { ClipFrameAssembly asm = AssembledWithOldOutside(); WalkPortalView walkView = WalkViewOfPixelQuads( new[] { new Vector2(100f, 100f), new Vector2(300f, 100f), new Vector2(300f, 300f), new Vector2(100f, 300f), }, new[] { new Vector2(600f, 400f), new Vector2(900f, 400f), new Vector2(900f, 650f), new Vector2(600f, 650f), }); ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H); Assert.Equal(2, asm.OutsideViewSlices.Length); Assert.NotEqual(asm.OutsideViewSlices[0].Slot, asm.OutsideViewSlices[1].Slot); // The union AABB spans both doorways. Assert.True(asm.OutsideViewNdcAabb.X < asm.OutsideViewSlices[0].NdcAabb.Z); Assert.True(asm.OutsideViewNdcAabb.Z >= asm.OutsideViewSlices[1].NdcAabb.X); } }