From bff19550660428df96f759885d21ba5a1e1f6ea9 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 6 Jun 2026 21:59:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(render):=20IndoorDrawPlan.ShellPass=20?= =?UTF-8?q?=E2=80=94=20every=20visible=20cell,=20no=20drawable=20filter=20?= =?UTF-8?q?(R1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure port of retail DrawCells membership: reverse cell_draw_list, per-slice. Pins the grey regression — a cell in OrderedVisibleCells is never dropped from the shell pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/Rendering/IndoorDrawPlan.cs | 30 ++++++++++++ .../Rendering/IndoorDrawPlanTests.cs | 46 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/AcDream.App/Rendering/IndoorDrawPlan.cs create mode 100644 tests/AcDream.App.Tests/Rendering/IndoorDrawPlanTests.cs diff --git a/src/AcDream.App/Rendering/IndoorDrawPlan.cs b/src/AcDream.App/Rendering/IndoorDrawPlan.cs new file mode 100644 index 00000000..a3aab2a2 --- /dev/null +++ b/src/AcDream.App/Rendering/IndoorDrawPlan.cs @@ -0,0 +1,30 @@ +// IndoorDrawPlan.cs +// +// Pure (GL-free) port of the membership half of retail PView::DrawCells (0x5a4840): +// the reverse cell_draw_list iterated per portal_view slice. EVERY visible cell with a +// non-empty view is included — there is NO "drawable" filter. Dropping cells without a +// clip-slot was the grey-walls bug (the cell's sealed shell never drew → clear color showed). +using System.Collections.Generic; + +namespace AcDream.App.Rendering; + +public readonly record struct CellDrawEntry(uint CellId, IReadOnlyList Slices); + +public static class IndoorDrawPlan +{ + /// Reverse OrderedVisibleCells (far→near), each visible cell with its view + /// slices. Mirrors DrawCells' shell/object loops. Cells whose view is empty are skipped + /// (they are not actually visible); no other cell is ever dropped. + public static List ShellPass(PortalVisibilityFrame frame) + { + var result = new List(frame.OrderedVisibleCells.Count); + for (int i = frame.OrderedVisibleCells.Count - 1; i >= 0; i--) + { + uint cellId = frame.OrderedVisibleCells[i]; + if (!frame.CellViews.TryGetValue(cellId, out var view) || view.IsEmpty) + continue; + result.Add(new CellDrawEntry(cellId, view.Polygons)); + } + return result; + } +} diff --git a/tests/AcDream.App.Tests/Rendering/IndoorDrawPlanTests.cs b/tests/AcDream.App.Tests/Rendering/IndoorDrawPlanTests.cs new file mode 100644 index 00000000..6b0de649 --- /dev/null +++ b/tests/AcDream.App.Tests/Rendering/IndoorDrawPlanTests.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using AcDream.App.Rendering; +using Xunit; + +namespace AcDream.App.Tests.Rendering; + +public class IndoorDrawPlanTests +{ + private static ViewPolygon Quad() => new(new[] + { new Vector2(-1, -1), new Vector2(1, -1), new Vector2(1, 1), new Vector2(-1, 1) }); + + private static PortalVisibilityFrame FrameWith(params uint[] orderedCells) + { + var f = new PortalVisibilityFrame(); + foreach (var id in orderedCells) + { + f.OrderedVisibleCells.Add(id); + var v = new CellView(); v.Add(Quad()); + f.CellViews[id] = v; + } + return f; + } + + [Fact] + public void ShellPass_IncludesEveryVisibleCell_NoFilter() + { + // The grey bug: a cell in OrderedVisibleCells must NEVER be dropped from the + // shell pass. (Old code dropped cells lacking a ClipFrameAssembler slot.) + var f = FrameWith(0x01, 0x02, 0x03); + var plan = IndoorDrawPlan.ShellPass(f); + Assert.Equal(new uint[] { 0x03, 0x02, 0x01 }, plan.Select(e => e.CellId).ToArray()); // reverse = far→near + Assert.All(plan, e => Assert.NotEmpty(e.Slices)); + } + + [Fact] + public void ShellPass_ExcludesEmptyViewCells() + { + var f = FrameWith(0x01); + f.OrderedVisibleCells.Add(0x02); // present in the list… + f.CellViews[0x02] = new CellView(); // …but empty view → not drawable + var plan = IndoorDrawPlan.ShellPass(f); + Assert.Equal(new uint[] { 0x01 }, plan.Select(e => e.CellId).ToArray()); + } +}