diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs index 64036b1c..b082960b 100644 --- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs +++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs @@ -10,6 +10,11 @@ namespace AcDream.App.Rendering; /// methods — the cutover changes ORDER only. internal sealed partial class RetailPViewPassExecutor { + private IReadOnlySet? _walkTerrainInViewLandcells; + + internal void SetWalkTerrainInViewLandcells(IReadOnlySet? cells) => + _walkTerrainInViewLandcells = cells; + /// The walk's single sky turn: retail draws GameSky once /// inside LScape::draw, clipped by the active views; looping /// today's per-slice scissor + terrain-clip block reproduces today's @@ -105,7 +110,8 @@ internal sealed partial class RetailPViewPassExecutor frame.Frustum, neverCullLandblockId: frame.PlayerLandblockId, clipPlanes: default, - ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f)); + ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f), + inViewLandcells: _walkTerrainInViewLandcells); _terrainDiagnostics.Complete(); return; } @@ -120,7 +126,8 @@ internal sealed partial class RetailPViewPassExecutor frame.Frustum, neverCullLandblockId: frame.PlayerLandblockId, clipPlanes: slice.Planes, - ndcClipAabb: slice.NdcAabb); + ndcClipAabb: slice.NdcAabb, + inViewLandcells: _walkTerrainInViewLandcells); _terrainDiagnostics.Complete(); DisableClipDistances(); if (scissor) diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 8cf99e66..f0d5d00c 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -1349,7 +1349,15 @@ public sealed class RetailPViewRenderer Walk.WalkFrameDriver driver) { var (frame, encoder) = passes.RequireWalkSubmission(); - driver.Replay(frame, encoder); + passes.SetWalkTerrainInViewLandcells(driver.VisitedLandscapeCellIds); + try + { + driver.Replay(frame, encoder); + } + finally + { + passes.SetWalkTerrainInViewLandcells(null); + } // Landscape-stage static-owner particles (candles, the cathedral // falls) submit AT THEIR OWN WALK TURNS inside Replay diff --git a/src/AcDream.App/Rendering/TerrainModernRenderer.cs b/src/AcDream.App/Rendering/TerrainModernRenderer.cs index 1790655f..67381655 100644 --- a/src/AcDream.App/Rendering/TerrainModernRenderer.cs +++ b/src/AcDream.App/Rendering/TerrainModernRenderer.cs @@ -68,6 +68,7 @@ public sealed partial class TerrainModernRenderer : IDisposable // Reusable per-frame buffers. private readonly List _visibleSlots = new(); private readonly HashSet _visibleCellIds = new(); + private readonly HashSet _walkVisibleLandblocks = new(); private DrawElementsIndirectCommand[] _deicScratch = Array.Empty(); // Diag. @@ -204,34 +205,58 @@ public sealed partial class TerrainModernRenderer : IDisposable FrustumPlanes? frustum = null, uint? neverCullLandblockId = null, ReadOnlySpan clipPlanes = default, - Vector4? ndcClipAabb = null) + Vector4? ndcClipAabb = null, + IReadOnlySet? inViewLandcells = null) { if (_alloc.LoadedCount == 0) return; Matrix4x4 viewProjection = camera.View * camera.Projection; + // Campaign FW4: RetailFrameWalk is the sole visibility authority. + // Terrain remains one full-landblock MDI draw, but only landblocks + // containing a walk-admitted landcell participate, and the published + // landcell set is the walk's exact in_view set rather than a second + // frustum-derived approximation. + _walkVisibleLandblocks.Clear(); + if (inViewLandcells is not null) + { + foreach (uint cellId in inViewLandcells) + { + _walkVisibleLandblocks.Add(cellId & 0xFFFF0000u); + _visibleCellIds.Add(cellId); + } + } + // Build visible slot list with per-slot frustum cull. _visibleSlots.Clear(); for (int slot = 0; slot < _slots.Length; slot++) { var data = _slots[slot]; if (data is null) continue; + if (inViewLandcells is not null + && !_walkVisibleLandblocks.Contains(data.LandblockId & 0xFFFF0000u)) + { + continue; + } if (frustum is not null && data.LandblockId != neverCullLandblockId) { if (!FrustumCuller.IsAabbVisible(frustum.Value, data.AabbMin, data.AabbMax)) continue; } _visibleSlots.Add(slot); - CollectVisibleCells( - _visibleCellIds, - data.LandblockId, - data.WorldOrigin, - data.AabbMin.Z, - data.AabbMax.Z, - frustum, - viewProjection, - clipPlanes, - ndcClipAabb); + if (inViewLandcells is null) + { + CollectVisibleCells( + _visibleCellIds, + data.LandblockId, + data.WorldOrigin, + data.AabbMin.Z, + data.AabbMax.Z, + frustum, + viewProjection, + clipPlanes, + ndcClipAabb); + } } if (_visibleSlots.Count == 0) return;