using System.Numerics;
using AcDream.App.Rendering.Scene;
namespace AcDream.App.Rendering.Walk;
///
/// Campaign FW3.2b-2: the production over
/// the retained scene () and the FW3.1
/// . Rebuilt facts per frame via
/// :
///
///
/// - Cell statics — on
/// demand, one pooled array per distinct cell per frame (a cell can be
/// visited once by the root flood OR once per admitting look-in portal; the
/// per-frame cache keeps the copy single).
/// - Outdoor statics — ONE
/// sweep bucketed by landscape cell id
/// ((lb & 0xFFFF0000) | (cellX*8 + cellY + 1) from the record's
/// world position — the same encoding
/// computes), EXCLUDING building shells (they draw at their building's own
/// shell turn, retail CPhysicsPart::Draw(parts, 0) @0x0059f331, not
/// at the cell's DrawObjCell turn).
/// - Building shells — the same sweep's IsBuildingShell records
/// bucketed by Source.BuildingShellAnchorCellId; a
/// maps to its anchor via its first
/// non-exit portal's destination (the SAME rule
/// LandblockLoader used to author the anchor).
///
///
/// The tuple landblock id handed to the classifier is the frame's player
/// landblock — the packed path's own convention for every
/// RenderFrameEntityDrawRequest.
///
internal sealed class WalkProductionWorldData : IWalkFrameWorldData
{
private readonly WalkBuildingRegistry _buildings;
private RenderSceneQuery _scene;
private uint _tupleLandblockId;
private readonly Dictionary _cellCache = new();
private readonly Dictionary> _outdoorByCell = new();
private readonly Dictionary> _shellsByAnchor = new();
private readonly Dictionary _outdoorMaterialized = new();
private readonly Dictionary _shellMaterialized = new();
private RenderProjectionRecord[] _sweepScratch = new RenderProjectionRecord[1024];
private RenderProjectionRecord[] _cellScratch = new RenderProjectionRecord[256];
internal WalkProductionWorldData(WalkBuildingRegistry buildings)
{
_buildings = buildings ?? throw new ArgumentNullException(nameof(buildings));
}
/// Rebuilds the frame's outdoor/shell buckets and clears the
/// per-cell cache. Call once per frame before the driver runs.
internal void BeginFrame(RenderSceneQuery scene, uint tupleLandblockId)
{
_scene = scene;
_tupleLandblockId = tupleLandblockId;
_cellCache.Clear();
_outdoorMaterialized.Clear();
_shellMaterialized.Clear();
foreach (List bucket in _outdoorByCell.Values)
bucket.Clear();
foreach (List bucket in _shellsByAnchor.Values)
bucket.Clear();
int count;
while (true)
{
count = _scene.CopyIndexTo(RenderSceneIndex.OutdoorStatic, _sweepScratch);
if (count < _sweepScratch.Length)
break;
_sweepScratch = new RenderProjectionRecord[_sweepScratch.Length * 2];
}
for (int i = 0; i < count; i++)
{
ref readonly RenderProjectionRecord record = ref _sweepScratch[i];
if (record.EntityPayload.IsBuildingShell)
{
uint anchor = record.Source.BuildingShellAnchorCellId;
if (!_shellsByAnchor.TryGetValue(anchor, out List? shells))
_shellsByAnchor[anchor] = shells = new List();
shells.Add(record);
continue;
}
uint cellId = LandscapeCellId(record.Transform.Position);
if (!_outdoorByCell.TryGetValue(cellId, out List? bucket))
_outdoorByCell[cellId] = bucket = new List();
bucket.Add(record);
}
}
/// The landscape cell owning a world position — retail's
/// 24 m cell grid inside the 192 m landblock, the same
/// (lb & 0xFFFF0000) | (cellX*8 + cellY + 1) encoding the walk's
/// landscape turn emits.
internal static uint LandscapeCellId(Vector3 worldPosition)
{
int lbX = (int)MathF.Floor(worldPosition.X / 192f);
int lbY = (int)MathF.Floor(worldPosition.Y / 192f);
float localX = worldPosition.X - lbX * 192f;
float localY = worldPosition.Y - lbY * 192f;
int cellX = Math.Clamp((int)(localX / 24f), 0, 7);
int cellY = Math.Clamp((int)(localY / 24f), 0, 7);
uint landblock = ((uint)(byte)lbX << 24) | ((uint)(byte)lbY << 16);
return landblock | (uint)(cellX * 8 + cellY + 1);
}
public WalkFrameStaticRecords GetCellStatics(uint cellId)
{
if (_cellCache.TryGetValue(cellId, out WalkFrameStaticRecords cached))
return cached;
int count;
while (true)
{
count = _scene.CopyCellStaticsTo(cellId, _cellScratch);
if (count < _cellScratch.Length)
break;
_cellScratch = new RenderProjectionRecord[_cellScratch.Length * 2];
}
WalkFrameStaticRecords records = count == 0
? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId }
: new WalkFrameStaticRecords(_cellScratch[..count], _tupleLandblockId);
_cellCache[cellId] = records;
return records;
}
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId)
{
if (_outdoorMaterialized.TryGetValue(cellId, out WalkFrameStaticRecords cached))
return cached;
WalkFrameStaticRecords records =
_outdoorByCell.TryGetValue(cellId, out List? bucket)
&& bucket.Count > 0
? new WalkFrameStaticRecords([.. bucket], _tupleLandblockId)
: WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
_outdoorMaterialized[cellId] = records;
return records;
}
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building)
{
uint anchor = AnchorCellId(building);
if (anchor == 0)
return WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
if (_shellMaterialized.TryGetValue(anchor, out WalkFrameStaticRecords cached))
return cached;
WalkFrameStaticRecords records =
_shellsByAnchor.TryGetValue(anchor, out List? shells)
&& shells.Count > 0
? new WalkFrameStaticRecords([.. shells], _tupleLandblockId)
: WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
_shellMaterialized[anchor] = records;
return records;
}
/// The building's authored shell anchor: its first non-exit
/// portal's destination cell — the SAME rule LandblockLoader used
/// when it stamped BuildingShellAnchorCellId on the shell entity.
internal static uint AnchorCellId(WalkBuilding building)
{
foreach (ref readonly WalkBldPortal portal in building.Portals.AsSpan())
{
if (portal.OtherCellId != 0xFFFFFFFFu)
return portal.OtherCellId;
}
return 0;
}
public Matrix4x4 GetBuildingWorldTransform(WalkBuilding building)
{
if (!_buildings.TryGetEntry(building, out WalkBuildingFactory.Entry? entry))
{
throw new InvalidOperationException(
$"walk building 0x{building.PositionCellId:X8} has no committed registry entry");
}
return entry.WorldTransform;
}
}