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; }