using System.Numerics;
using AcDream.App.Rendering.Walk;
using AcDream.Core.Vfx;
namespace AcDream.App.Rendering;
/// Campaign FW3.2b-2 (step 1): the walk driver's leaf draws over
/// this executor's renderers. Same leaf mechanics as the packed slice
/// methods — the cutover changes ORDER only.
internal sealed partial class RetailPViewPassExecutor
{
/// 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
/// pixels while the driver sees ONE turn.
internal void DrawWalkSky(
RetailPViewFrameInput frame, ClipFrameAssembly clipAssembly)
{
if (!frame.RenderSky)
return;
ReadOnlySpan slices = clipAssembly.OutsideViewSlices;
for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++)
{
ClipViewSlice slice = slices[sliceIndex];
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
_surface.BindTerrainClip();
EnableClipDistances();
_sky?.RenderSky(
frame.Camera,
frame.CameraWorldPosition,
frame.DayFraction,
frame.ActiveDayGroup,
frame.SkyKeyframe,
frame.EnvironOverrideActive);
DisableClipDistances();
if (_particles is not null && _particleRenderer is not null)
{
_particleRenderer.Draw(
frame.Camera,
frame.CameraWorldPosition,
ParticleRenderPass.SkyPreScene);
}
if (scissor)
_surface.EndScissor();
}
}
/// The walk's per-slice terrain turn — the terrain block of
/// DrawLandscapeSlice (scissor + terrain clip + slice planes +
/// NDC AABB), without the sky/entity halves the walk owns separately.
internal void DrawWalkTerrainSlice(
RetailPViewFrameInput frame, ClipFrameAssembly clipAssembly, int sliceIndex)
{
ReadOnlySpan slices = clipAssembly.OutsideViewSlices;
if ((uint)sliceIndex >= (uint)slices.Length)
{
throw new ArgumentOutOfRangeException(
nameof(sliceIndex), sliceIndex,
$"walk terrain turn: slice {sliceIndex} of {slices.Length}");
}
ClipViewSlice slice = slices[sliceIndex];
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
_surface.BindTerrainClip();
EnableClipDistances();
_terrainDiagnostics.Begin();
_terrain?.Draw(
frame.Camera,
frame.Frustum,
neverCullLandblockId: frame.PlayerLandblockId,
clipPlanes: slice.Planes,
ndcClipAabb: slice.NdcAabb);
_terrainDiagnostics.Complete();
DisableClipDistances();
if (scissor)
_surface.EndScissor();
}
/// The walk's punch-fan turn — DrawPortalPolyInternal
/// @0x0059bc90's far-Z punch through PortalDepthMaskRenderer,
/// clipped by the pinned view's slice planes (retail
/// building_view @0x0059f3bf). FW3.3: fans draw at the dat
/// aperture verbatim (the ShellDrawLiftZ retirement).
internal void DrawWalkPunchFan(
RetailPViewFrameInput frame,
ClipFrameAssembly clipAssembly,
WalkPolygon worldPolygon,
int activeViewIndex)
{
if (_portalDepthMask is null)
return;
Vector3[] vertices = worldPolygon.Vertices;
if (vertices.Length < 3)
return;
ReadOnlySpan slices = clipAssembly.OutsideViewSlices;
ReadOnlySpan planes = (uint)activeViewIndex < (uint)slices.Length
? slices[activeViewIndex].Planes
: default;
Span world = stackalloc Vector3[32];
int count = Math.Min(vertices.Length, world.Length);
for (int vertex = 0; vertex < count; vertex++)
world[vertex] = vertices[vertex];
_portalDepthMask.DrawDepthFan(
world[..count],
frame.ViewProjection,
planes,
forceFarZ: true);
}
}
///
/// Campaign FW stage FW3.2b-2 (step 1, additive): the production
/// over this executor's leaf renderers.
/// Constructed per frame by the FW3.2b-2 rooting (plan §FW3 "FW3.2b-2 —
/// the production rooting"); nothing invokes it until the static cutover
/// flips. Each member maps a turn onto the
/// SAME leaf calls the packed path uses today, so the cutover changes
/// ORDER, never the leaf mechanics:
///
///
/// - → the sky block of DrawLandscapeSlice,
/// looped over the active slices internally (retail draws GameSky once
/// inside LScape::draw clipped by the active views; the per-slice
/// scissor+clip here reproduces today's pixels while the driver still sees
/// ONE sky turn).
/// - → the terrain block of
/// DrawLandscapeSlice (scissor + terrain clip + slice planes).
/// - → EnvCellRenderer opaque +
/// transparent-ordered for ONE cell (retail DrawEnvCell
/// @0x0059f170 draws per cell at its flood turn).
/// - → PortalDepthMaskRenderer.DrawDepthFan
/// with forceFarZ, clipped by the pinned view's slice planes
/// (retail building_view @0x0059f3bf); FW3.3 draws fans at the dat
/// aperture verbatim (the ShellDrawLiftZ retirement).
/// - / →
/// caller-supplied actions (the renderer owns the pass scope and the
/// root-flood seal iteration; the adapter only provides the turns).
/// - → FlushLandscapeAlphaFartherThan
/// (the DrawBuilding barrier; retail's flush-all
/// FlushAlphaList(0f) @0x0059f30b is the FW4 adjudication
/// candidate recorded on the driver's interface).
///
///
internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
{
private readonly RetailPViewPassExecutor _passes;
private readonly RetailPViewFrameInput _frame;
private readonly ClipFrameAssembly _clipAssembly;
private readonly Action _clearInteriorDepth;
private readonly Action _drawExitSeals;
private readonly HashSet _singleCellScratch = new();
private readonly List _singleCellListScratch = new();
internal WalkProductionLeafRenderer(
RetailPViewPassExecutor passes,
RetailPViewFrameInput frame,
ClipFrameAssembly clipAssembly,
Action clearInteriorDepth,
Action drawExitSeals)
{
_passes = passes ?? throw new ArgumentNullException(nameof(passes));
_frame = frame ?? throw new ArgumentNullException(nameof(frame));
_clipAssembly = clipAssembly;
_clearInteriorDepth = clearInteriorDepth
?? throw new ArgumentNullException(nameof(clearInteriorDepth));
_drawExitSeals = drawExitSeals
?? throw new ArgumentNullException(nameof(drawExitSeals));
}
public void DrawSky() => _passes.DrawWalkSky(_frame, _clipAssembly);
public void DrawTerrainSlice(int sliceIndex) =>
_passes.DrawWalkTerrainSlice(_frame, _clipAssembly, sliceIndex);
public void DrawCellShell(uint cellId)
{
_singleCellScratch.Clear();
_singleCellScratch.Add(cellId);
_passes.DrawOpaqueCellShells(_singleCellScratch);
if (_passes.CellHasTransparentShell(cellId))
{
_singleCellListScratch.Clear();
_singleCellListScratch.Add(cellId);
_passes.DrawTransparentCellShellsOrdered(_singleCellListScratch);
}
}
public void ClearInteriorDepth() => _clearInteriorDepth();
public void DrawExitSeals() => _drawExitSeals();
public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
_passes.DrawWalkPunchFan(_frame, _clipAssembly, worldPolygon, activeViewIndex);
public void AlphaBarrier(float viewerDistance) =>
_passes.FlushLandscapeAlphaFartherThan(viewerDistance);
}