checkpoint: preserve cathedral look-in investigation state
This commit is contained in:
parent
c287db8651
commit
572de1ec30
11 changed files with 757 additions and 197 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
|
|
@ -118,6 +119,17 @@ internal interface IWalkFrameLeafRenderer
|
|||
/// positional invariant this carries (the #132 falls containment).</summary>
|
||||
void DrawStaticParticles(IReadOnlySet<uint> ownerIds);
|
||||
|
||||
/// <summary>Draws the packed dynamic occupants of one building look-in
|
||||
/// cell at that cell's OWN <c>PView::DrawCells</c> turn, then submits the
|
||||
/// cell's static + dynamic particle owners at the same turn. The route
|
||||
/// index is assigned in the exact order Collect encountered look-in cell
|
||||
/// turns and therefore matches the frame product's walk-keyed
|
||||
/// <c>LookInObject</c> ranges.</summary>
|
||||
void DrawLookInDynamics(
|
||||
uint cellId,
|
||||
int routeIndex,
|
||||
IReadOnlySet<uint> staticParticleOwnerIds);
|
||||
|
||||
/// <summary><c>PView::DrawCells</c> @0x005a4840's gated full depth clear
|
||||
/// (pc:432731-432732) between the outside stage and the interior root's
|
||||
/// own flood — production maps this to <c>IWorldPassScope.ClearInteriorDepth</c>
|
||||
|
|
@ -259,8 +271,37 @@ internal enum WalkFrameEventKind : byte
|
|||
/// cathedral bleed; the old pipeline's user-verified #132 fix
|
||||
/// `e102fb36` encoded the same invariant).</summary>
|
||||
StaticParticles,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawLookInDynamics"/> —
|
||||
/// <see cref="WalkFrameEvent.CellId"/> is the look-in cell and
|
||||
/// <see cref="WalkFrameEvent.IntArg"/> is its walk-ordered packed route
|
||||
/// index.</summary>
|
||||
LookInDynamics,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The exact portal-view cones installed at the walk's building look-in
|
||||
/// <c>DrawCells</c> turns. Retail <c>RenderDeviceD3D::DrawMesh</c>
|
||||
/// @0x005A0860 tests each object's drawing sphere against these views before
|
||||
/// drawing the mesh whole; cell membership alone is not an admission rule.
|
||||
/// </summary>
|
||||
internal interface IWalkLookInViewSource
|
||||
{
|
||||
IReadOnlyList<uint> LookInCellTurns { get; }
|
||||
|
||||
bool SphereVisibleInLookInTurn(
|
||||
int routeIndex,
|
||||
in Vector3 center,
|
||||
float radius);
|
||||
}
|
||||
|
||||
internal readonly record struct WalkLookInSlice(int PlaneStart, int PlaneCount);
|
||||
|
||||
internal readonly record struct WalkLookInTurn(
|
||||
uint CellId,
|
||||
int SliceStart,
|
||||
int SliceCount);
|
||||
|
||||
/// <summary>See <see cref="WalkFrameEventKind"/> for what each field means per
|
||||
/// kind. A single struct (rather than a kind hierarchy) keeps Collect's
|
||||
/// per-turn list a flat, allocation-cheap <c>List<WalkFrameEvent></c> —
|
||||
|
|
@ -320,6 +361,9 @@ internal readonly struct WalkFrameEvent
|
|||
internal static WalkFrameEvent BuildingShellParticles(WalkBuilding building) =>
|
||||
new(WalkFrameEventKind.StaticParticles, 0, 0, 0f, null, building);
|
||||
|
||||
internal static WalkFrameEvent LookInDynamics(uint cellId, int routeIndex) =>
|
||||
new(WalkFrameEventKind.LookInDynamics, routeIndex, cellId, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent ClearInteriorDepth() =>
|
||||
new(WalkFrameEventKind.ClearInteriorDepth, 0, 0, 0f, null);
|
||||
|
||||
|
|
@ -404,7 +448,7 @@ internal readonly struct WalkFrameEvent
|
|||
/// @0x0059f2a0 (the <c>part->gfxobj[deg_level]!=0</c> gate @0x0059f2d3
|
||||
/// and the alpha-barrier → portal-pass → shell order @0x0059f30b–0x0059f345).</para>
|
||||
/// </summary>
|
||||
internal sealed class WalkFrameDriver : IWalkEventSink
|
||||
internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||
{
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly WalkStaticStreamPopulator _populator;
|
||||
|
|
@ -422,6 +466,23 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
// walk pass dedicated to nothing but set-gathering.
|
||||
internal HashSet<uint> VisitedCells { get; } = new();
|
||||
|
||||
/// <summary>Building look-in cells in the exact order the walk encountered
|
||||
/// their <c>DrawCells</c> turns. Duplicates are intentional: two authored
|
||||
/// portal views can independently visit the same cell and therefore own
|
||||
/// distinct packed route indices.</summary>
|
||||
internal List<uint> LookInCellTurns { get; } = new();
|
||||
|
||||
private readonly List<WalkLookInTurn> _lookInTurns = new();
|
||||
private readonly List<WalkLookInSlice> _lookInSlices = new();
|
||||
private readonly List<WalkPlane> _lookInPlanes = new();
|
||||
private WalkPlane _lookInCyPlane;
|
||||
|
||||
IReadOnlyList<uint> IWalkLookInViewSource.LookInCellTurns => LookInCellTurns;
|
||||
|
||||
/// <summary>The set form of <see cref="LookInCellTurns"/>, for drawn-once
|
||||
/// exclusion and root-flood particle bookkeeping.</summary>
|
||||
internal HashSet<uint> LookInCells { get; } = new();
|
||||
|
||||
/// <summary>FW4 slice 2: the interior root's ORDERED flood cell list,
|
||||
/// exactly as retail's <c>PView::DrawCells</c> iterates it for the
|
||||
/// exit-portal seals (pc:432785-432786) — captured at
|
||||
|
|
@ -459,6 +520,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
private bool _skyDrawnThisFrame;
|
||||
private WalkDrawStage? _currentDcStage;
|
||||
private bool _readyToReplay;
|
||||
private int _lookInRouteIndex;
|
||||
|
||||
internal WalkFrameDriver(
|
||||
WbDrawDispatcher dispatcher,
|
||||
|
|
@ -534,7 +596,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
/// GPU work — see this type's own doc comment.
|
||||
/// </summary>
|
||||
internal void BeginFrame(
|
||||
IWalkBuildingFrameContext ctx,
|
||||
IRetailFrameWalkContext ctx,
|
||||
Matrix4x4 viewProjection,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
|
|
@ -557,9 +619,16 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
VisitedCells.Clear();
|
||||
LookInCellTurns.Clear();
|
||||
_lookInTurns.Clear();
|
||||
_lookInSlices.Clear();
|
||||
_lookInPlanes.Clear();
|
||||
_lookInCyPlane = ctx.CyPlane;
|
||||
LookInCells.Clear();
|
||||
VisitedBuildings.Clear();
|
||||
VisitedLandscapeCellIds.Clear();
|
||||
InteriorFloodCells.Clear();
|
||||
_lookInRouteIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Records the final segment mark (plan §FW3.2b-1's "at frame
|
||||
|
|
@ -655,6 +724,16 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
if (_staticParticleOwnerScratch.Count > 0)
|
||||
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
||||
break;
|
||||
case WalkFrameEventKind.LookInDynamics:
|
||||
_staticParticleOwnerScratch.Clear();
|
||||
UnionOwners(
|
||||
_worldData.GetCellStatics(e.CellId),
|
||||
_staticParticleOwnerScratch);
|
||||
_leafRenderer.DrawLookInDynamics(
|
||||
e.CellId,
|
||||
e.IntArg,
|
||||
_staticParticleOwnerScratch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -883,6 +962,76 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
|||
_populator.PopulateCell(
|
||||
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection);
|
||||
if (stage == WalkDrawStage.LookInStatic)
|
||||
{
|
||||
// Retail draws a look-in cell's complete object list at this
|
||||
// re-entrant DrawCells turn. The packed dynamic route used to run
|
||||
// much later at the pre-clear boundary, after nearer building
|
||||
// shells, which let the cathedral's 0x112 remote player overpaint
|
||||
// opaque walls. Keep animation/fade in the packed route, but replay
|
||||
// it here between this cell's content and the building shell.
|
||||
MarkIfGrown();
|
||||
int routeIndex = _lookInRouteIndex++;
|
||||
LookInCellTurns.Add(cellId);
|
||||
LookInCells.Add(cellId);
|
||||
CaptureLookInViews(cellId);
|
||||
_events.Add(WalkFrameEvent.LookInDynamics(cellId, routeIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureLookInViews(uint cellId)
|
||||
{
|
||||
IWalkBuildingFrameContext ctx = RequireOpenFrame();
|
||||
WalkCell? cell = ctx.GetVisible(cellId);
|
||||
if (cell is null || cell.NumView <= 0)
|
||||
{
|
||||
_lookInTurns.Add(new WalkLookInTurn(
|
||||
cellId, _lookInSlices.Count, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
WalkPortalView portalView = cell.TopView;
|
||||
int sliceStart = _lookInSlices.Count;
|
||||
for (int sliceIndex = 0; sliceIndex < portalView.ViewCount; sliceIndex++)
|
||||
{
|
||||
WalkViewPoly poly = portalView.View.Polys[sliceIndex];
|
||||
int planeStart = _lookInPlanes.Count;
|
||||
for (int edge = 0; edge < poly.VertexCount; edge++)
|
||||
{
|
||||
_lookInPlanes.Add(
|
||||
portalView.View.Vertices[poly.VertexIndex + edge].Plane);
|
||||
}
|
||||
_lookInSlices.Add(new WalkLookInSlice(
|
||||
planeStart, poly.VertexCount));
|
||||
}
|
||||
_lookInTurns.Add(new WalkLookInTurn(
|
||||
cellId, sliceStart, _lookInSlices.Count - sliceStart));
|
||||
}
|
||||
|
||||
public bool SphereVisibleInLookInTurn(
|
||||
int routeIndex,
|
||||
in Vector3 center,
|
||||
float radius)
|
||||
{
|
||||
if ((uint)routeIndex >= (uint)_lookInTurns.Count)
|
||||
return false;
|
||||
|
||||
WalkLookInTurn turn = _lookInTurns[routeIndex];
|
||||
for (int sliceOffset = 0; sliceOffset < turn.SliceCount; sliceOffset++)
|
||||
{
|
||||
WalkLookInSlice slice = _lookInSlices[turn.SliceStart + sliceOffset];
|
||||
if (WalkVisibilityMath.ViewconeCheck(
|
||||
center,
|
||||
radius,
|
||||
_lookInCyPlane,
|
||||
CollectionsMarshal.AsSpan(_lookInPlanes).Slice(
|
||||
slice.PlaneStart,
|
||||
slice.PlaneCount)) != WalkBoundingType.Outside)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.4a: the collect-time analogue of the old
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue