refactor(render): move look-in dynamics onto walk turns
This commit is contained in:
parent
966836895f
commit
f9a80fbc44
9 changed files with 190 additions and 113 deletions
|
|
@ -204,9 +204,6 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
|||
private readonly Action _drawExitSeals;
|
||||
private readonly HashSet<uint> _singleCellScratch = new();
|
||||
private readonly List<uint> _singleCellListScratch = new();
|
||||
private readonly HashSet<uint> _lookInParticleOwnerScratch = new();
|
||||
private RenderFrameView _entityFrame;
|
||||
private bool _entityFrameBound;
|
||||
|
||||
internal WalkProductionLeafRenderer(
|
||||
RetailPViewPassExecutor passes,
|
||||
|
|
@ -248,53 +245,11 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
|||
_passes.DrawLandscapeStaticParticles(
|
||||
_frame, new RetailPViewLandscapeStaticParticleContext(ownerIds));
|
||||
|
||||
/// <summary>Binds the packed frame product built after the walk's Collect
|
||||
/// pass and before Replay. The walk records route indices during Collect;
|
||||
/// Replay consumes the matching product ranges through this exact view.</summary>
|
||||
internal void BindEntityFrame(in RenderFrameView view)
|
||||
public void DrawCellParticles(uint cellId, IReadOnlySet<uint> ownerIds)
|
||||
{
|
||||
_entityFrame = view;
|
||||
_entityFrameBound = true;
|
||||
}
|
||||
|
||||
public void DrawLookInDynamics(
|
||||
uint cellId,
|
||||
int routeIndex,
|
||||
IReadOnlySet<uint> staticParticleOwnerIds)
|
||||
{
|
||||
if (!_entityFrameBound)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Walk look-in dynamics replayed before the packed entity frame was bound.");
|
||||
}
|
||||
|
||||
_passes.UseIndoorMembershipOnlyRouting();
|
||||
_passes.DrawEntityRoute(
|
||||
_frame.Camera,
|
||||
in _entityFrame,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex,
|
||||
cellId,
|
||||
_frame.PlayerLandblockId ?? 0u);
|
||||
|
||||
_lookInParticleOwnerScratch.Clear();
|
||||
_lookInParticleOwnerScratch.UnionWith(staticParticleOwnerIds);
|
||||
RenderFrameRouteOwnerSelector.Union(
|
||||
_lookInParticleOwnerScratch,
|
||||
in _entityFrame,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex,
|
||||
cellId);
|
||||
if (_lookInParticleOwnerScratch.Count > 0)
|
||||
{
|
||||
_passes.DrawCellParticles(
|
||||
_frame,
|
||||
new RetailPViewCellSliceContext(
|
||||
cellId,
|
||||
default,
|
||||
_lookInParticleOwnerScratch));
|
||||
}
|
||||
|
||||
_passes.DrawCellParticles(
|
||||
_frame,
|
||||
new RetailPViewCellSliceContext(cellId, default, ownerIds));
|
||||
}
|
||||
|
||||
public void DrawExitSeals() => _drawExitSeals();
|
||||
|
|
|
|||
|
|
@ -550,8 +550,6 @@ public sealed class RetailPViewRenderer
|
|||
frameViewBorrowed = true;
|
||||
frameEntityPasses!.BeginEntityFrame(in frameView);
|
||||
entityFrameOpen = true;
|
||||
if (walkActive)
|
||||
walkLeafRenderer!.BindEntityFrame(in frameView);
|
||||
}
|
||||
|
||||
// The retained scene product is the production object source.
|
||||
|
|
|
|||
|
|
@ -1207,16 +1207,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
for (int index = 0; index < input.LookInCellTurns.Count; index++)
|
||||
_lookInCellScratch.Add(input.LookInCellTurns[index]);
|
||||
|
||||
// Campaign FW3.2b-2: LandscapeOutdoorStatic, LandscapeBuildingShell,
|
||||
// and CellStatic no longer emit here — WalkFrameDriver draws every
|
||||
// outdoor static, building shell, and cell static (including
|
||||
// look-in cell statics) directly through OrderedDrawStream (plan
|
||||
// §FW3 "FW3.2b-2 — the production rooting", item 3). LookInObject
|
||||
// keeps the packed classifier for live animation/fades, but its
|
||||
// route ranges are keyed to THE WALK'S own look-in cell turns.
|
||||
// The legacy PortalVisibilityFrame list no longer has a production
|
||||
// admission or ordering role.
|
||||
BuildLookInRoutes(writer, in input);
|
||||
// FW4: the walk now owns the complete look-in object-list turn —
|
||||
// statics, live dynamics, fades, picking, and particles. Do not
|
||||
// advertise a packed LookInObject range the dispatcher cannot and
|
||||
// must not consume a second time.
|
||||
BuildOutsideDynamicRoutes(writer, in input);
|
||||
BuildDynamicLastRoute(writer, in input);
|
||||
writer.Publish();
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ internal interface IWalkFrameWorldData
|
|||
/// look-in's) static content — <c>RenderProjectionClass.IndoorCellStatic</c>.</summary>
|
||||
WalkFrameStaticRecords GetCellStatics(uint cellId);
|
||||
|
||||
/// <summary>The live dynamic occupants of one indoor cell. These records
|
||||
/// enter the ordered stream at that cell's own object-list turn; a
|
||||
/// building look-in supplies its exact installed portal view for the
|
||||
/// per-part <c>viewconeCheck</c>.</summary>
|
||||
WalkFrameStaticRecords GetCellDynamics(uint cellId);
|
||||
|
||||
/// <summary>One visited landscape (outdoor) cell's static content —
|
||||
/// <c>RenderProjectionClass.OutdoorStatic</c>, keyed by the SAME
|
||||
/// <c>(landblockId & 0xFFFF0000) | (cellIndex+1)</c> id
|
||||
|
|
@ -117,16 +123,10 @@ 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>Submits one indoor cell's static + dynamic particle owners at
|
||||
/// that cell's own object-list turn. Meshes have already entered the
|
||||
/// ordered stream before this leaf event.</summary>
|
||||
void DrawCellParticles(uint cellId, IReadOnlySet<uint> ownerIds);
|
||||
|
||||
/// <summary><c>PView::DrawCells</c> @0x005a4840's gated full depth clear
|
||||
/// (pc:432731-432732) between the outside stage and the interior root's
|
||||
|
|
@ -270,11 +270,9 @@ internal enum WalkFrameEventKind : byte
|
|||
/// `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><see cref="IWalkFrameLeafRenderer.DrawCellParticles"/> —
|
||||
/// <see cref="WalkFrameEvent.CellId"/> is the cell.</summary>
|
||||
CellParticles,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -359,8 +357,8 @@ 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 CellParticles(uint cellId) =>
|
||||
new(WalkFrameEventKind.CellParticles, 0, cellId, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent ClearInteriorDepth() =>
|
||||
new(WalkFrameEventKind.ClearInteriorDepth, 0, 0, 0f, null);
|
||||
|
|
@ -724,15 +722,20 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
if (_staticParticleOwnerScratch.Count > 0)
|
||||
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
||||
break;
|
||||
case WalkFrameEventKind.LookInDynamics:
|
||||
case WalkFrameEventKind.CellParticles:
|
||||
_staticParticleOwnerScratch.Clear();
|
||||
UnionOwners(
|
||||
_worldData.GetCellStatics(e.CellId),
|
||||
_staticParticleOwnerScratch);
|
||||
_leafRenderer.DrawLookInDynamics(
|
||||
e.CellId,
|
||||
e.IntArg,
|
||||
UnionOwners(
|
||||
_worldData.GetCellDynamics(e.CellId),
|
||||
_staticParticleOwnerScratch);
|
||||
if (_staticParticleOwnerScratch.Count > 0)
|
||||
{
|
||||
_leafRenderer.DrawCellParticles(
|
||||
e.CellId,
|
||||
_staticParticleOwnerScratch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -990,7 +993,20 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
LookInCellTurns.Add(cellId);
|
||||
LookInCells.Add(cellId);
|
||||
CaptureLookInViews(cellId);
|
||||
_events.Add(WalkFrameEvent.LookInDynamics(cellId, routeIndex));
|
||||
|
||||
WalkFrameStaticRecords dynamics = _worldData.GetCellDynamics(cellId);
|
||||
_populator.PopulateCellDynamics(
|
||||
_stream,
|
||||
cellId,
|
||||
dynamics.Records,
|
||||
dynamics.TupleLandblockId,
|
||||
_cameraWorldPosition,
|
||||
_viewProjection,
|
||||
this,
|
||||
routeIndex);
|
||||
MarkIfGrown();
|
||||
if (HasAnyOwner(records) || HasAnyOwner(dynamics))
|
||||
_events.Add(WalkFrameEvent.CellParticles(cellId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,12 +61,14 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
|||
private int _renderCenterLbY;
|
||||
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellCache = new();
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellDynamicCache = new();
|
||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _outdoorByCell = new();
|
||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _shellsByAnchor = new();
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _outdoorMaterialized = new();
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _shellMaterialized = new();
|
||||
private RenderProjectionRecord[] _sweepScratch = new RenderProjectionRecord[1024];
|
||||
private RenderProjectionRecord[] _cellScratch = new RenderProjectionRecord[256];
|
||||
private RenderProjectionRecord[] _cellDynamicScratch = new RenderProjectionRecord[256];
|
||||
|
||||
// Campaign FW3.4a: the per-frame, grow-only materialization arena — see
|
||||
// this type's own doc comment.
|
||||
|
|
@ -98,6 +100,7 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
|||
_renderCenterLbX = renderCenterLbX;
|
||||
_renderCenterLbY = renderCenterLbY;
|
||||
_cellCache.Clear();
|
||||
_cellDynamicCache.Clear();
|
||||
_outdoorMaterialized.Clear();
|
||||
_shellMaterialized.Clear();
|
||||
_arenaLength = 0;
|
||||
|
|
@ -180,6 +183,25 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
|||
return records;
|
||||
}
|
||||
|
||||
public WalkFrameStaticRecords GetCellDynamics(uint cellId)
|
||||
{
|
||||
if (_cellDynamicCache.TryGetValue(cellId, out WalkFrameStaticRecords cached))
|
||||
return cached;
|
||||
int required = _scene.GetCellDynamicCount(cellId);
|
||||
if (required > _cellDynamicScratch.Length)
|
||||
{
|
||||
_cellDynamicScratch = new RenderProjectionRecord[
|
||||
Math.Max(required, _cellDynamicScratch.Length * 2)];
|
||||
}
|
||||
int count = _scene.CopyCellDynamicsTo(cellId, _cellDynamicScratch);
|
||||
WalkFrameStaticRecords records = count == 0
|
||||
? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId }
|
||||
: new WalkFrameStaticRecords(
|
||||
AppendToArena(_cellDynamicScratch.AsSpan(0, count)), _tupleLandblockId);
|
||||
_cellDynamicCache[cellId] = records;
|
||||
return records;
|
||||
}
|
||||
|
||||
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId)
|
||||
{
|
||||
if (_outdoorMaterialized.TryGetValue(cellId, out WalkFrameStaticRecords cached))
|
||||
|
|
|
|||
|
|
@ -97,6 +97,33 @@ internal sealed class WalkStaticStreamPopulator
|
|||
stream, WalkDrawStage.OutdoorStatic, cellId, records,
|
||||
tupleLandblockId, cameraWorldPosition, viewProjection);
|
||||
|
||||
internal void PopulateCellDynamics(
|
||||
OrderedDrawStream stream,
|
||||
uint cellId,
|
||||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection,
|
||||
IWalkLookInViewSource? lookInViews = null,
|
||||
int lookInRouteIndex = -1)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
ClassifyAndAppend(
|
||||
stream,
|
||||
WalkDrawStage.Dynamic,
|
||||
cellId,
|
||||
in records[i],
|
||||
tupleLandblockId,
|
||||
cameraWorldPosition,
|
||||
viewProjection,
|
||||
liveDynamic: true,
|
||||
lookInViews,
|
||||
lookInRouteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClassifyAndAppend(
|
||||
OrderedDrawStream stream,
|
||||
WalkDrawStage stage,
|
||||
|
|
@ -104,12 +131,22 @@ internal sealed class WalkStaticStreamPopulator
|
|||
in RenderProjectionRecord record,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
Matrix4x4 viewProjection,
|
||||
bool liveDynamic = false,
|
||||
IWalkLookInViewSource? lookInViews = null,
|
||||
int lookInRouteIndex = -1)
|
||||
{
|
||||
_batchScratch.Clear();
|
||||
_selectionScratch.Clear();
|
||||
_dispatcher.ClassifyEntityForWalk(
|
||||
in record, tupleLandblockId, _batchScratch, _selectionScratch);
|
||||
in record,
|
||||
tupleLandblockId,
|
||||
_batchScratch,
|
||||
_selectionScratch,
|
||||
liveDynamic,
|
||||
lookInViews,
|
||||
lookInRouteIndex,
|
||||
cellId);
|
||||
|
||||
for (int i = 0; i < _batchScratch.Count; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.Enums;
|
||||
|
|
@ -182,13 +183,20 @@ public sealed partial class WbDrawDispatcher
|
|||
in RenderProjectionRecord projection,
|
||||
uint tupleLandblockId,
|
||||
List<WalkClassifiedBatch> batches,
|
||||
List<WalkClassifiedSelectionPart> selectionParts)
|
||||
List<WalkClassifiedSelectionPart> selectionParts,
|
||||
bool liveDynamic = false,
|
||||
IWalkLookInViewSource? lookInViews = null,
|
||||
int lookInRouteIndex = -1,
|
||||
uint lookInCellId = 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(batches);
|
||||
ArgumentNullException.ThrowIfNull(selectionParts);
|
||||
|
||||
RenderInstanceCandidate entity =
|
||||
RenderInstanceCandidate.FromProjection(in projection, tupleLandblockId);
|
||||
RenderInstanceCandidate.FromProjection(
|
||||
in projection,
|
||||
tupleLandblockId,
|
||||
animated: liveDynamic);
|
||||
|
||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||
_clipRoutingActive, entity.ServerGuid, entity.ParentCell,
|
||||
|
|
@ -217,7 +225,11 @@ public sealed partial class WbDrawDispatcher
|
|||
MeshRef meshRef = meshRefs[partIndex];
|
||||
ObjectRenderData? renderData = _meshAdapter.TryGetRenderData(meshRef.GfxObjId);
|
||||
if (renderData is null)
|
||||
{
|
||||
if (_missRequested.Add(meshRef.GfxObjId))
|
||||
_meshAdapter.EnsureLoaded(meshRef.GfxObjId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderData.IsSetup && renderData.SetupParts.Count > 0)
|
||||
{
|
||||
|
|
@ -236,16 +248,42 @@ public sealed partial class WbDrawDispatcher
|
|||
(ulong gfxObjId, Matrix4x4 partTransform) = renderData.SetupParts[setupPartIndex];
|
||||
ObjectRenderData? partData = _meshAdapter.TryGetRenderData(gfxObjId);
|
||||
if (partData is null)
|
||||
{
|
||||
if (_missRequested.Add(gfxObjId))
|
||||
_meshAdapter.EnsureLoaded(gfxObjId);
|
||||
continue;
|
||||
}
|
||||
|
||||
float opacity = liveDynamic
|
||||
? PackedPartOpacity(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
(uint)setupPartIndex)
|
||||
: 1f;
|
||||
if (opacity <= 0f)
|
||||
continue;
|
||||
|
||||
Matrix4x4 restPose = partTransform * meshRef.PartTransform;
|
||||
Matrix4x4 model = restPose * entity.RootWorld;
|
||||
int selectionPartIndex = unchecked((partIndex << 16) | (setupPartIndex & 0xFFFF));
|
||||
|
||||
if (!PartVisibleInLookInTurn(
|
||||
lookInViews,
|
||||
lookInRouteIndex,
|
||||
lookInCellId,
|
||||
in entity,
|
||||
selectionPartIndex,
|
||||
(uint)gfxObjId,
|
||||
partData,
|
||||
model))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EmitClassifiedBatches(
|
||||
partData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubset, slot, lights, indoor, selectionLighting,
|
||||
detailCategory, batches);
|
||||
detailCategory, opacity, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, selectionPartIndex,
|
||||
(uint)gfxObjId, model));
|
||||
|
|
@ -253,11 +291,32 @@ public sealed partial class WbDrawDispatcher
|
|||
}
|
||||
else
|
||||
{
|
||||
float opacity = liveDynamic
|
||||
? PackedPartOpacity(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
(uint)partIndex)
|
||||
: 1f;
|
||||
if (opacity <= 0f)
|
||||
continue;
|
||||
|
||||
Matrix4x4 model = meshRef.PartTransform * entity.RootWorld;
|
||||
if (!PartVisibleInLookInTurn(
|
||||
lookInViews,
|
||||
lookInRouteIndex,
|
||||
lookInCellId,
|
||||
in entity,
|
||||
partIndex,
|
||||
(uint)meshRef.GfxObjId,
|
||||
renderData,
|
||||
model))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
EmitClassifiedBatches(
|
||||
renderData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubsetOverride: null, slot, lights, indoor,
|
||||
selectionLighting, detailCategory, batches);
|
||||
selectionLighting, detailCategory, opacity, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, partIndex,
|
||||
(uint)meshRef.GfxObjId, model));
|
||||
|
|
@ -283,6 +342,7 @@ public sealed partial class WbDrawDispatcher
|
|||
bool indoor,
|
||||
Vector2 selectionLighting,
|
||||
uint detailCategory,
|
||||
float opacity,
|
||||
List<WalkClassifiedBatch> sink)
|
||||
{
|
||||
bool entityHasCutoutSubset = entityHasCutoutSubsetOverride ?? renderData.HasCutoutSubset;
|
||||
|
|
@ -290,13 +350,13 @@ public sealed partial class WbDrawDispatcher
|
|||
{
|
||||
bool survives = TryClassifyBatch(
|
||||
renderData, batchIdx, in entity, meshRef, paletteIdentity,
|
||||
opacityMultiplier: 1.0f, entityHasCutoutSubset,
|
||||
opacityMultiplier: opacity, entityHasCutoutSubset,
|
||||
out GroupKey key, out _);
|
||||
if (!survives)
|
||||
continue;
|
||||
|
||||
sink.Add(new WalkClassifiedBatch(
|
||||
key, model, slot, lights, indoor ? 1u : 0u, Alpha: 1f,
|
||||
key, model, slot, lights, indoor ? 1u : 0u, Alpha: opacity,
|
||||
selectionLighting, detailCategory, IsOpaque: IsOpaque(key.Translucency),
|
||||
LocalSortCenter: renderData.SortCenter));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue