204 lines
8.3 KiB
C#
204 lines
8.3 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Rendering.Wb;
|
|
|
|
namespace AcDream.App.Rendering.Walk;
|
|
|
|
/// <summary>
|
|
/// Campaign FW stage FW3.2a: the walk→draw population layer. Turns one
|
|
/// cell's already-classified static content into <see cref="OrderedDrawCommand"/>
|
|
/// appends (opaque, walk order), per-instance deferred-alpha submissions
|
|
/// (translucent), and selection-scene publications (picking) — WITHOUT any
|
|
/// production frame wiring. <c>WorldSceneRenderer</c> does not call this
|
|
/// class yet; FW3.2b roots it into the real frame.
|
|
///
|
|
/// <para><b>Why per-entity append instead of the classic material grouping</b>:
|
|
/// under <see cref="WorldDepthContract"/>'s depth-compare Less, opaque draw
|
|
/// order is pixel-relevant ONLY for coplanar surfaces (a rug on a floor, a
|
|
/// shell over terrain) — and retail resolves those by first-drawn-wins in
|
|
/// ITS walk/cell-content order, not by any texture/material grouping. So
|
|
/// this populator appends one <see cref="OrderedDrawCommand"/> per (entity,
|
|
/// opaque batch) in the SAME order the caller's <c>records</c> span presents
|
|
/// them — never material-grouped, never re-sorted. Translucent batches go to
|
|
/// the SAME <see cref="RetailAlphaQueue"/> the classic/packed paths already
|
|
/// use (global far→near re-sort still applies; walk-order submission only
|
|
/// improves retail's submission-order tie-break fidelity for coincident
|
|
/// distances — <c>WbDrawDispatcher.DeferTransparentGroups</c>'s own doc
|
|
/// comment cites the same <c>CShadowPart::insertion_sort</c> stability this
|
|
/// preserves).</para>
|
|
///
|
|
/// <para>Reads no retained scene state itself: every method takes the target
|
|
/// <see cref="OrderedDrawStream"/> and a caller-supplied span of already
|
|
/// -queried <see cref="RenderProjectionRecord"/>s (from
|
|
/// <c>RenderSceneQuery.CopyCellStaticsTo</c> / <c>CopyIndexTo</c>). Owns only
|
|
/// two small per-call scratch lists — the classify seam's per-entity output
|
|
/// — reused across records to keep this hot path allocation-free after
|
|
/// warmup.</para>
|
|
/// </summary>
|
|
internal sealed class WalkStaticStreamPopulator
|
|
{
|
|
private readonly WbDrawDispatcher _dispatcher;
|
|
private readonly List<WbDrawDispatcher.WalkClassifiedBatch> _batchScratch = new();
|
|
private readonly List<WbDrawDispatcher.WalkClassifiedSelectionPart> _selectionScratch = new();
|
|
|
|
internal WalkStaticStreamPopulator(WbDrawDispatcher dispatcher)
|
|
{
|
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates <paramref name="stream"/> from one indoor cell's static
|
|
/// content (<c>RenderProjectionClass.IndoorCellStatic</c> —
|
|
/// <c>RenderSceneQuery.CopyCellStaticsTo</c>). <paramref name="stage"/>
|
|
/// is caller-selected per the walk turn this cell's content belongs to
|
|
/// — <see cref="WalkDrawStage.CellStatic"/> for an ordinary
|
|
/// <c>PView::DrawCells</c> flood, <see cref="WalkDrawStage.LookInStatic"/>
|
|
/// for a <c>ConstructView(CBldPortal)</c> look-in, or
|
|
/// <see cref="WalkDrawStage.BuildingShell"/> for a building's own
|
|
/// exterior shell content.
|
|
/// </summary>
|
|
internal void PopulateCell(
|
|
OrderedDrawStream stream,
|
|
WalkDrawStage stage,
|
|
uint cellId,
|
|
ReadOnlySpan<RenderProjectionRecord> records,
|
|
uint tupleLandblockId,
|
|
Vector3 cameraWorldPosition,
|
|
Matrix4x4 viewProjection,
|
|
IWalkLookInViewSource? views = null,
|
|
int viewRouteIndex = -1,
|
|
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(stream);
|
|
for (int i = 0; i < records.Length; i++)
|
|
{
|
|
ClassifyAndAppend(
|
|
stream, stage, cellId, in records[i], tupleLandblockId,
|
|
cameraWorldPosition, viewProjection,
|
|
liveDynamic: false, views, viewRouteIndex, alphaSubmissions);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The landscape entry point: outdoor static content
|
|
/// (<c>RenderProjectionClass.OutdoorStatic</c> —
|
|
/// <c>RenderSceneQuery.CopyIndexTo(RenderSceneIndex.OutdoorStatic, ...)</c>)
|
|
/// at <see cref="WalkDrawStage.OutdoorStatic"/>, the terrain-adjacent
|
|
/// stage <c>LScape::draw</c> visits a landblock's static objects at
|
|
/// alongside its ground mesh (see that stage value's own doc comment).
|
|
/// <paramref name="cellId"/> is the OUTDOOR landblock id — outdoor
|
|
/// statics have no EnvCell of their own, so this is walk-order
|
|
/// provenance only, not a clip-slot key.
|
|
/// </summary>
|
|
internal void PopulateOutdoorStatics(
|
|
OrderedDrawStream stream,
|
|
uint cellId,
|
|
ReadOnlySpan<RenderProjectionRecord> records,
|
|
uint tupleLandblockId,
|
|
Vector3 cameraWorldPosition,
|
|
Matrix4x4 viewProjection,
|
|
IWalkLookInViewSource? views = null,
|
|
int viewRouteIndex = -1,
|
|
ISet<RenderProjectionId>? drawnOnce = null,
|
|
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(stream);
|
|
for (int i = 0; i < records.Length; i++)
|
|
{
|
|
if (drawnOnce is not null && !drawnOnce.Add(records[i].Id))
|
|
continue;
|
|
ClassifyAndAppend(
|
|
stream, WalkDrawStage.OutdoorStatic, cellId, in records[i],
|
|
tupleLandblockId, cameraWorldPosition, viewProjection,
|
|
liveDynamic: false, views, viewRouteIndex, alphaSubmissions);
|
|
}
|
|
}
|
|
|
|
internal void PopulateCellDynamics(
|
|
OrderedDrawStream stream,
|
|
uint cellId,
|
|
ReadOnlySpan<RenderProjectionRecord> records,
|
|
uint tupleLandblockId,
|
|
Vector3 cameraWorldPosition,
|
|
Matrix4x4 viewProjection,
|
|
IWalkLookInViewSource? lookInViews = null,
|
|
int lookInRouteIndex = -1,
|
|
ISet<RenderProjectionId>? drawnOnce = null,
|
|
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(stream);
|
|
for (int i = 0; i < records.Length; i++)
|
|
{
|
|
if (drawnOnce is not null && !drawnOnce.Add(records[i].Id))
|
|
continue;
|
|
ClassifyAndAppend(
|
|
stream,
|
|
WalkDrawStage.Dynamic,
|
|
cellId,
|
|
in records[i],
|
|
tupleLandblockId,
|
|
cameraWorldPosition,
|
|
viewProjection,
|
|
liveDynamic: true,
|
|
lookInViews,
|
|
lookInRouteIndex,
|
|
alphaSubmissions);
|
|
}
|
|
}
|
|
|
|
private void ClassifyAndAppend(
|
|
OrderedDrawStream stream,
|
|
WalkDrawStage stage,
|
|
uint cellId,
|
|
in RenderProjectionRecord record,
|
|
uint tupleLandblockId,
|
|
Vector3 cameraWorldPosition,
|
|
Matrix4x4 viewProjection,
|
|
bool liveDynamic = false,
|
|
IWalkLookInViewSource? lookInViews = null,
|
|
int lookInRouteIndex = -1,
|
|
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
|
{
|
|
_batchScratch.Clear();
|
|
_selectionScratch.Clear();
|
|
_dispatcher.ClassifyEntityForWalk(
|
|
in record,
|
|
tupleLandblockId,
|
|
_batchScratch,
|
|
_selectionScratch,
|
|
liveDynamic,
|
|
lookInViews,
|
|
lookInRouteIndex,
|
|
cellId);
|
|
|
|
for (int i = 0; i < _batchScratch.Count; i++)
|
|
{
|
|
WbDrawDispatcher.WalkClassifiedBatch batch = _batchScratch[i];
|
|
if (batch.IsOpaque)
|
|
{
|
|
stream.Append(new OrderedDrawCommand(
|
|
batch.Key, batch.Transform, stage, cellId, batch.ClipSlot,
|
|
batch.Lights, batch.IndoorFlag, batch.Alpha,
|
|
batch.SelectionLighting, batch.DetailCategory));
|
|
}
|
|
else
|
|
{
|
|
if (alphaSubmissions is null)
|
|
{
|
|
_dispatcher.SubmitWalkAlphaInstance(
|
|
in batch, cameraWorldPosition, viewProjection);
|
|
}
|
|
else
|
|
{
|
|
alphaSubmissions.Add(batch);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _selectionScratch.Count; i++)
|
|
{
|
|
WbDrawDispatcher.WalkClassifiedSelectionPart part = _selectionScratch[i];
|
|
_dispatcher.PublishWalkSelectionPart(in part);
|
|
}
|
|
}
|
|
}
|