using System.Numerics;
namespace AcDream.App.Rendering.Walk;
/// A cell-local portal polygon with its plane (retail
/// CPolygon: vertices + plane @+0x20).
public sealed class WalkPolygon
{
public Vector3[] Vertices = [];
public WalkPlane Plane;
}
/// Retail CCellPortal (stride 0x18). The exit-to-landscape
/// sentinel is OtherCellId == 0xFFFFFFFF; OtherPortalId == -1
/// means "no reciprocal".
public struct WalkCellPortal
{
public uint OtherCellId;
public int PolygonIndex;
public int PortalSide;
public int OtherPortalId;
public bool ExactMatch;
}
///
/// The flood's cell model (retail CEnvCell as the walk sees it).
/// Retail stores the walk state ON the cell (num_view / portal_view stack /
/// cached neighbor pointers); this port keeps the same placement so the
/// transcription stays line-by-line — production adapters construct these
/// from the committed cell registry (FW3 wiring).
///
public sealed class WalkCell
{
public uint CellId;
public WalkCellPortal[] Portals = [];
public WalkPolygon[] PortalPolygons = [];
public uint[] StabList = [];
/// Cell-local → landblock-local (retail CEnvCell.pos;
/// the frame the flood's plane tests and projections run in).
public Matrix4x4 WorldTransform = Matrix4x4.Identity;
public Matrix4x4 InverseWorldTransform = Matrix4x4.Identity;
// ---- walk state (retail: fields on CEnvCell) ----
public int NumView;
public readonly List PortalViews = new();
public WalkCell?[] CachedNeighbors = [];
public WalkPortalView TopView => PortalViews[NumView - 1];
/// CEnvCell::curr_view_push @0x005a5090: push one
/// view-recursion level (lazy slot, exactly three counters reset).
public void PushView()
{
while (PortalViews.Count <= NumView)
PortalViews.Add(new WalkPortalView());
PortalViews[NumView].ResetForPush();
NumView++;
if (CachedNeighbors.Length < Portals.Length)
CachedNeighbors = new WalkCell?[Portals.Length];
}
public void PopView() => NumView--;
}
///
/// The per-frame context retail keeps in globals (Render::FrameCurrent
/// after positionPush(3, cell.pos), the projection state, the visible
/// cell registry, and the cliplandscape toggle — .data default 1).
///
public interface IWalkFrameContext
{
/// The eye position in the cell's local frame (retail: the
/// pushed frame's viewer.viewpoint).
Vector3 ViewpointIn(WalkCell cell);
/// Object(cell-local)→clip matrix for projecting the cell's
/// portal polygons (retail: the pushed frame composed with
/// WorldToView·ViewToClip).
Matrix4x4 ObjectToClip(WalkCell cell);
/// CEnvCell::GetVisible: the committed/visible cell
/// registry. Returning null skips the portal silently (retail behavior —
/// but implementations should count the miss for the fail-loud rule).
WalkCell? GetVisible(uint cellId);
IWalkRayCaster Rays { get; }
Vector3 WorldViewpoint { get; }
float ViewportWidth { get; }
float ViewportHeight { get; }
/// Retail global cliplandscape (.data @0x00820f4c = 1).
bool ClipLandscape => true;
}