using System.Collections.Generic; using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; public class ClipFrameAssemblerTests { private static ViewPolygon Square(float cx, float cy, float half) => new(new[] { new Vector2(cx - half, cy - half), new Vector2(cx + half, cy - half), new Vector2(cx + half, cy + half), new Vector2(cx - half, cy + half), }); private static CellView ViewOf(params ViewPolygon[] polys) { var view = new CellView(); foreach (var p in polys) view.Add(p); return view; } [Fact] public void TwoVisibleCells_PlusOutsideView_ProducesCorrectSlotMapAndCounts() { const uint cellA = 0xA9B40100; const uint cellB = 0xA9B40101; var pv = new PortalVisibilityFrame(); pv.CellViews[cellA] = ViewOf(Square(-0.3f, 0f, 0.3f)); pv.CellViews[cellB] = ViewOf(Square(0.3f, 0f, 0.2f)); pv.OrderedVisibleCells.Add(cellA); pv.OrderedVisibleCells.Add(cellB); pv.OutsideView.Add(Square(0f, 0.5f, 0.25f)); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.Equal(4, asm.Frame.SlotCount); // slot 0 + two cells + one outside slice Assert.Contains(cellA, asm.CellIdToSlot.Keys); Assert.Contains(cellB, asm.CellIdToSlot.Keys); Assert.NotEqual(0, asm.CellIdToSlot[cellA]); Assert.NotEqual(0, asm.CellIdToSlot[cellB]); Assert.NotEqual(asm.CellIdToSlot[cellA], asm.CellIdToSlot[cellB]); Assert.Equal(4, asm.PerCellPlaneCounts[cellA]); Assert.Equal(4, asm.PerCellPlaneCounts[cellB]); Assert.True(asm.OutdoorVisible); Assert.NotEqual(0, asm.OutdoorSlot); Assert.Single(asm.OutsideViewSlices); Assert.Equal(asm.OutdoorSlot, asm.OutsideViewSlices[0].Slot); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); Assert.Equal(4, asm.OutsidePlaneCount); Assert.Equal(0, asm.ScissorFallbacks); } [Fact] public void NothingVisibleCell_IsExcludedFromSlotMap_AndNotAppended() { const uint cellA = 0xA9B40100; const uint cellB = 0xA9B40101; var pv = new PortalVisibilityFrame(); pv.CellViews[cellA] = ViewOf(Square(0f, 0f, 0.3f)); pv.CellViews[cellB] = new CellView(); pv.OrderedVisibleCells.Add(cellA); pv.OrderedVisibleCells.Add(cellB); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.Equal(2, asm.Frame.SlotCount); Assert.Contains(cellA, asm.CellIdToSlot.Keys); Assert.DoesNotContain(cellB, asm.CellIdToSlot.Keys); Assert.False(asm.OutdoorVisible); Assert.Empty(asm.OutsideViewSlices); Assert.Equal(TerrainClipMode.Skip, asm.TerrainMode); Assert.Equal(0, asm.OutsidePlaneCount); } [Fact] public void OutsideViewMultiPolygon_PreservesRetailSlices() { const uint cellA = 0xA9B40100; var pv = new PortalVisibilityFrame(); pv.CellViews[cellA] = ViewOf(Square(0f, 0f, 0.3f)); pv.OrderedVisibleCells.Add(cellA); pv.OutsideView.Add(Square(-0.5f, 0f, 0.1f)); pv.OutsideView.Add(Square(0.5f, 0f, 0.1f)); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.True(asm.OutdoorVisible); Assert.NotEqual(0, asm.OutdoorSlot); Assert.Equal(2, asm.OutsideViewSlices.Length); Assert.NotEqual(asm.OutsideViewSlices[0].Slot, asm.OutsideViewSlices[1].Slot); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); Assert.Equal(4, asm.OutsidePlaneCount); Assert.Equal(0, asm.ScissorFallbacks); Assert.Equal(4, asm.Frame.SlotCount); // slot 0 + cell + two outside slices Assert.Equal(Vector4.Zero, asm.TerrainScissorNdcAabb); } [Fact] public void CellMultiPolygonView_PreservesRetailViewSlices() { const uint cellA = 0xA9B40100; var pv = new PortalVisibilityFrame(); pv.CellViews[cellA] = ViewOf( Square(-0.4f, 0f, 0.1f), Square(0.4f, 0f, 0.1f)); pv.OrderedVisibleCells.Add(cellA); pv.OutsideView.Add(Square(0f, 0f, 0.3f)); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.True(asm.CellIdToSlot[cellA] > 0); Assert.Equal(2, asm.CellIdToViewSlots[cellA].Length); Assert.Equal(2, asm.CellIdToViewSlices[cellA].Length); Assert.NotEqual(asm.CellIdToViewSlots[cellA][0], asm.CellIdToViewSlots[cellA][1]); Assert.Equal(4, asm.PerCellPlaneCounts[cellA]); Assert.Single(asm.OutsideViewSlices); Assert.Equal(4, asm.Frame.SlotCount); // slot 0 + two cell slices + outside slice Assert.Equal(0, asm.ScissorFallbacks); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); } [Fact] public void Assemble_OutsideViewWithExitPortal_HasOutsideViewTrue_AabbMatchesBounds() { var pv = new PortalVisibilityFrame(); pv.OutsideView.Add(Square(-0.3f, 0.2f, 0.25f)); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.True(asm.HasOutsideView); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); Assert.Single(asm.OutsideViewSlices); var expected = new Vector4( pv.OutsideView.MinX, pv.OutsideView.MinY, pv.OutsideView.MaxX, pv.OutsideView.MaxY); Assert.Equal(expected, asm.OutsideViewNdcAabb); } [Fact] public void Assemble_OutsideViewMultiPolygon_PreservesSlicesAndUnionAabb() { var pv = new PortalVisibilityFrame(); pv.OutsideView.Add(Square(-0.6f, 0f, 0.15f)); pv.OutsideView.Add(Square(0.6f, 0f, 0.15f)); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.True(asm.HasOutsideView); Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode); Assert.Equal(2, asm.OutsideViewSlices.Length); var expected = new Vector4( pv.OutsideView.MinX, pv.OutsideView.MinY, pv.OutsideView.MaxX, pv.OutsideView.MaxY); Assert.Equal(expected, asm.OutsideViewNdcAabb); Assert.Equal(Vector4.Zero, asm.TerrainScissorNdcAabb); } [Fact] public void Assemble_EmptyOutsideView_HasOutsideViewFalse_AabbZero() { const uint cellA = 0xA9B40100; var pv = new PortalVisibilityFrame(); pv.CellViews[cellA] = ViewOf(Square(0f, 0f, 0.3f)); pv.OrderedVisibleCells.Add(cellA); var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv); Assert.False(asm.HasOutsideView); Assert.Equal(TerrainClipMode.Skip, asm.TerrainMode); Assert.Equal(Vector4.Zero, asm.OutsideViewNdcAabb); } [Fact] public void Reset_ReusesFrame_NoSlotLeakAcrossAssemblies() { var frame = ClipFrame.NoClip(); var pv1 = new PortalVisibilityFrame(); pv1.CellViews[0xA9B40100] = ViewOf(Square(-0.3f, 0f, 0.2f)); pv1.CellViews[0xA9B40101] = ViewOf(Square(0.3f, 0f, 0.2f)); pv1.OrderedVisibleCells.Add(0xA9B40100); pv1.OrderedVisibleCells.Add(0xA9B40101); pv1.OutsideView.Add(Square(0f, 0.4f, 0.2f)); var asm1 = ClipFrameAssembler.Assemble(frame, pv1); Assert.Equal(4, asm1.Frame.SlotCount); var pv2 = new PortalVisibilityFrame(); pv2.CellViews[0xA9B40200] = ViewOf(Square(0f, 0f, 0.25f)); pv2.OrderedVisibleCells.Add(0xA9B40200); var asm2 = ClipFrameAssembler.Assemble(frame, pv2); Assert.Equal(2, asm2.Frame.SlotCount); Assert.Contains(0xA9B40200, asm2.CellIdToSlot.Keys); Assert.DoesNotContain(0xA9B40100, asm2.CellIdToSlot.Keys); Assert.False(asm2.OutdoorVisible); Assert.Equal(TerrainClipMode.Skip, asm2.TerrainMode); } [Fact] public void Assemble_ReusedAssembly_ClearsStateAndReusesExactLengthArrays() { const uint firstCell = 0xA9B40100; const uint secondCell = 0xA9B40200; var frame = ClipFrame.NoClip(); var reuse = new ClipFrameAssembly(); var first = new PortalVisibilityFrame(); first.CellViews[firstCell] = ViewOf( Square(-0.4f, 0f, 0.15f), Square(0.4f, 0f, 0.15f)); first.OrderedVisibleCells.Add(firstCell); first.OutsideView.Add(Square(0f, 0.5f, 0.2f)); ClipFrameAssembly firstResult = ClipFrameAssembler.Assemble(frame, first, reuse); Assert.Same(reuse, firstResult); int sliceAllocations = reuse.SliceArrayAllocationCount; int slotAllocations = reuse.SlotArrayAllocationCount; var second = new PortalVisibilityFrame(); second.CellViews[secondCell] = ViewOf( Square(-0.4f, 0f, 0.15f), Square(0.4f, 0f, 0.15f)); second.OrderedVisibleCells.Add(secondCell); second.OutsideView.Add(Square(0f, 0.5f, 0.2f)); for (int i = 0; i < 12; i++) { ClipFrameAssembly actual = ClipFrameAssembler.Assemble(frame, second, reuse); Assert.Same(reuse, actual); Assert.DoesNotContain(firstCell, actual.CellIdToViewSlices.Keys); Assert.Contains(secondCell, actual.CellIdToViewSlices.Keys); Assert.Equal(2, actual.CellIdToViewSlices[secondCell].Length); Assert.Equal(2, actual.CellIdToViewSlots[secondCell].Length); Assert.Single(actual.OutsideViewSlices); Assert.Equal(sliceAllocations, reuse.SliceArrayAllocationCount); Assert.Equal(slotAllocations, reuse.SlotArrayAllocationCount); } var empty = new PortalVisibilityFrame(); ClipFrameAssembly cleared = ClipFrameAssembler.Assemble(frame, empty, reuse); Assert.Empty(cleared.CellIdToSlot); Assert.Empty(cleared.CellIdToViewSlots); Assert.Empty(cleared.CellIdToViewSlices); Assert.Empty(cleared.PerCellPlaneCounts); Assert.Empty(cleared.OutsideViewSlices); Assert.Equal(TerrainClipMode.Skip, cleared.TerrainMode); } [Fact] public void Assemble_ReusedAssembly_ChangingCardinalityStaysBoundedThenReachesSteadyState() { const uint cellId = 0xA9B40100; var frame = ClipFrame.NoClip(); var reuse = new ClipFrameAssembly(); static PortalVisibilityFrame FrameWithSlices(uint id, int count) { var portalFrame = new PortalVisibilityFrame(); var view = new CellView(); for (int i = 0; i < count; i++) { // The assembler consumes the retail view_poly list directly. Distinct placement is // irrelevant to this cache-cardinality stress; avoid CellView dedup collapsing it. view.Polygons.Add(Square(i * 0.001f, 0f, 0.0004f)); } portalFrame.CellViews[id] = view; portalFrame.OrderedVisibleCells.Add(id); return portalFrame; } for (int count = 1; count <= 180; count++) ClipFrameAssembler.Assemble(frame, FrameWithSlices(cellId, count), reuse); // Flush the final live arrays into the cache too. ClipFrameAssembler.Assemble(frame, new PortalVisibilityFrame(), reuse); Assert.InRange(reuse.RetainedSliceItems, 0, ClipFrameAssembly.MaxRetainedSliceItems); Assert.InRange(reuse.RetainedSlotItems, 0, ClipFrameAssembly.MaxRetainedSlotItems); Assert.InRange(reuse.RetainedSliceArrays, 0, ClipFrameAssembly.MaxRetainedArraysPerPool); Assert.InRange(reuse.RetainedSlotArrays, 0, ClipFrameAssembly.MaxRetainedArraysPerPool); PortalVisibilityFrame steady = FrameWithSlices(cellId, 73); ClipFrameAssembler.Assemble(frame, steady, reuse); int sliceAllocations = reuse.SliceArrayAllocationCount; int slotAllocations = reuse.SlotArrayAllocationCount; for (int i = 0; i < 16; i++) { ClipFrameAssembler.Assemble(frame, steady, reuse); Assert.Equal(sliceAllocations, reuse.SliceArrayAllocationCount); Assert.Equal(slotAllocations, reuse.SlotArrayAllocationCount); } } }