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
|
|
@ -315,7 +315,7 @@ issue is closed, the strip was missed; delete both.
|
||||||
| `ACDREAM_PROBE_CHILD_CELL` | c4 route 7 | `=1` | gates one `[child-cell]` line per Runtime committed-child canonical-cell write in `RuntimeLiveEntitySessionController`, `RuntimeEntityObjectLifetime`, `RuntimeEntityDirectory` (parent/child guid, old/new cell, cause tag) | print-only | `PhysicsDiagnostics.ProbeChildCellEnabled` |
|
| `ACDREAM_PROBE_CHILD_CELL` | c4 route 7 | `=1` | gates one `[child-cell]` line per Runtime committed-child canonical-cell write in `RuntimeLiveEntitySessionController`, `RuntimeEntityObjectLifetime`, `RuntimeEntityDirectory` (parent/child guid, old/new cell, cause tag) | print-only | `PhysicsDiagnostics.ProbeChildCellEnabled` |
|
||||||
| `ACDREAM_PROBE_CLIPROUTE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[clip-route]` / `[clip-route-disp]` / `[clip-route-scis]` lines: outside-slice clip routing, region-SSBO bytes, terrain-UBO head, actual GL/RHI scissor state | print-only | `RenderingDiagnostics.ProbeClipRouteEnabled` |
|
| `ACDREAM_PROBE_CLIPROUTE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[clip-route]` / `[clip-route-disp]` / `[clip-route-scis]` lines: outside-slice clip routing, region-SSBO bytes, terrain-UBO head, actual GL/RHI scissor state | print-only | `RenderingDiagnostics.ProbeClipRouteEnabled` |
|
||||||
| `ACDREAM_PROBE_WALK_SHADOW` | Campaign FW3.2b-2 flip apparatus — dies with the flip commit | `=1` | runs the PRODUCTION retail frame walk per frame in SHADOW (no draws change) and prints one `[walk-shadow]` line per frame whose visited cell/building sets diverge from the old visibility path (the I5 dual-shadow pattern applied to the FW3 static cutover) | print-only; adds the per-frame CPU cost of the shadow walk while set | `RenderingDiagnostics.ProbeWalkShadowEnabled` |
|
| `ACDREAM_PROBE_WALK_SHADOW` | Campaign FW3.2b-2 flip apparatus — dies with the flip commit | `=1` | runs the PRODUCTION retail frame walk per frame in SHADOW (no draws change) and prints one `[walk-shadow]` line per frame whose visited cell/building sets diverge from the old visibility path (the I5 dual-shadow pattern applied to the FW3 static cutover) | print-only; adds the per-frame CPU cost of the shadow walk while set | `RenderingDiagnostics.ProbeWalkShadowEnabled` |
|
||||||
| `ACDREAM_PROBE_WALK_ROOT` | the cathedral Phase I instrumentation (synthesis doc `docs/research/2026-08-30-cathedral-synthesis.md`) — dies with that investigation | `=1` | prints `[walk-root]` rooting lines every ~30th frame AND for 8 frames after each interior/outdoor root flip (viewer/root cell, camera-cell resolution, slice count, the walk's OWN exit-view count, the TRUE root flood separated from the visited union, eye + forward); `[walk-cam]` replayable camera dumps (eye, viewport, full view-projection) every ~300 frames and at flips; `[falls]` lines on EVERY particle submission carrying a real falls owner (0xCF418000-13) with route + phase; `[falls-adm]` admission verdicts for F418 emitters (~every 120th ApplyRetailView); plus the earlier family — `[walk-part]` per-route submissions, `[walk-alpha]` drains, `[walk-dyn]` outside-stage verdicts (~120th product frame), `[walk-portal]` per-portal side/clip decisions (~90th frame), and `[dyn-route]` per-entity ON-CHANGE DynamicLast admission traces (parent cell, look-in exclusion, viewcone verdict, the cell's lifted-plane shape — "0" marks a pass-all zero-plane slice) | print-only; chatty near particle-heavy scenes while set | `RenderingDiagnostics.ProbeWalkRootEnabled` |
|
| `ACDREAM_PROBE_WALK_ROOT` | the cathedral Phase I/FW4 instrumentation (synthesis doc `docs/research/2026-08-30-cathedral-synthesis.md`) — dies with that investigation | `=1` | prints `[walk-root]` rooting lines every ~30th frame AND for 8 frames after each interior/outdoor root flip (viewer/root cell, camera-cell resolution, slice count, the walk's OWN exit-view count, the TRUE root flood separated from the visited union, eye + forward); `[walk-cam]` replayable camera dumps (eye, viewport, full view-projection) every ~300 frames and at flips; `[lookin-frame]` ON-CHANGE/~30-frame comparisons of diagnostic-only legacy look-in frames against the walk's exact look-in cells, explicit 0xF4180112 membership, and walk-vs-legacy punch counts; `[lookin-draw]` identifies the walk-turn route index/cell actually submitted; `[lookin-punch]` fires if the legacy punch routine executes; `[falls]` lines on EVERY particle submission carrying a real falls owner (0xCF418000-13) with route + phase; `[falls-adm]` admission verdicts for F418 emitters (~every 120th ApplyRetailView); plus the earlier family — `[walk-part]` per-route submissions, `[walk-alpha]` drains, `[walk-dyn]` outside-stage verdicts (~120th product frame), `[walk-portal]` per-portal side/clip decisions (~90th frame), and `[dyn-route]` per-entity ON-CHANGE DynamicLast admission traces (parent cell, look-in exclusion, viewcone verdict, the cell's lifted-plane shape — "0" marks a pass-all zero-plane slice) | print-only; chatty near particle-heavy scenes while set | `RenderingDiagnostics.ProbeWalkRootEnabled` |
|
||||||
| `ACDREAM_PROBE_CONTACT_PLANE` | spike-only, 2026-05-20 | `=1` | gates one `[cp-write]` line per write to `CollisionInfo.ContactPlane*`/`LastKnownContactPlane*` fields (field, old→new, caller method via stack walk, source line); only logs on actual value changes | print-only, but performs a stack walk to identify the caller method when firing — real CPU cost per write while on (not just a string format); suppresses no-op writes to bound volume | `PhysicsDiagnostics.ProbeContactPlaneEnabled` |
|
| `ACDREAM_PROBE_CONTACT_PLANE` | spike-only, 2026-05-20 | `=1` | gates one `[cp-write]` line per write to `CollisionInfo.ContactPlane*`/`LastKnownContactPlane*` fields (field, old→new, caller method via stack walk, source line); only logs on actual value changes | print-only, but performs a stack walk to identify the caller method when firing — real CPU cost per write while on (not just a string format); suppresses no-op writes to bound volume | `PhysicsDiagnostics.ProbeContactPlaneEnabled` |
|
||||||
| `ACDREAM_PROBE_ENT` | #138 | `="1"` | Traces the persistent player entity across teleport streaming churn: presence in the render draw-set flat view vs. survival of the dynamics cull, to distinguish "missing from draw set" vs "present but culled" | print-only, "Observation-only — emits no behavior change" (doc comment). `LogPlayerDynOnChange` dedupes by transition to avoid per-frame spam. Marked STRIP-once-root-caused (like the dense-town FPS apparatus). | `EntityVanishProbe.Enabled` (`EntityVanishProbe.cs:23-24`), issue #138-B |
|
| `ACDREAM_PROBE_ENT` | #138 | `="1"` | Traces the persistent player entity across teleport streaming churn: presence in the render draw-set flat view vs. survival of the dynamics cull, to distinguish "missing from draw set" vs "present but culled" | print-only, "Observation-only — emits no behavior change" (doc comment). `LogPlayerDynOnChange` dedupes by transition to avoid per-frame spam. Marked STRIP-once-root-caused (like the dense-town FPS apparatus). | `EntityVanishProbe.Enabled` (`EntityVanishProbe.cs:23-24`), issue #138-B |
|
||||||
| `ACDREAM_PROBE_FLAP` | "throwaway apparatus — strip once the flap mechanism is confirmed" | `=1` | EVERY FRAME (unthrottled, not change-gated) while the camera root is indoor: `[flap]` from `PortalVisibilityBuilder.Build` (portal side-test/traverse/cull/projection) + paired `[flap-cam]` from `PhysicsCameraCollisionProbe`/`[flap-sweep]` (FindCameraCell resolution, eye positions) | print-only, but unthrottled per-frame `StringBuilder` allocation + `Console.WriteLine` on multiple call sites while indoor — heavy log volume/allocation under sustained indoor play; does not alter rendered output | `RenderingDiagnostics.ProbeFlapEnabled` |
|
| `ACDREAM_PROBE_FLAP` | "throwaway apparatus — strip once the flap mechanism is confirmed" | `=1` | EVERY FRAME (unthrottled, not change-gated) while the camera root is indoor: `[flap]` from `PortalVisibilityBuilder.Build` (portal side-test/traverse/cull/projection) + paired `[flap-cam]` from `PhysicsCameraCollisionProbe`/`[flap-sweep]` (FindCameraCell resolution, eye positions) | print-only, but unthrottled per-frame `StringBuilder` allocation + `Console.WriteLine` on multiple call sites while indoor — heavy log volume/allocation under sustained indoor play; does not alter rendered output | `RenderingDiagnostics.ProbeFlapEnabled` |
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AcDream.App.Rendering.Scene;
|
||||||
using AcDream.App.Rendering.Walk;
|
using AcDream.App.Rendering.Walk;
|
||||||
using AcDream.Core.Vfx;
|
using AcDream.Core.Vfx;
|
||||||
|
|
||||||
|
|
@ -151,6 +152,8 @@ internal sealed partial class RetailPViewPassExecutor
|
||||||
int count = Math.Min(vertices.Length, world.Length);
|
int count = Math.Min(vertices.Length, world.Length);
|
||||||
for (int vertex = 0; vertex < count; vertex++)
|
for (int vertex = 0; vertex < count; vertex++)
|
||||||
world[vertex] = vertices[vertex];
|
world[vertex] = vertices[vertex];
|
||||||
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||||
|
WalkLookInPunchCountThisFrame++;
|
||||||
_portalDepthMask.DrawDepthFan(
|
_portalDepthMask.DrawDepthFan(
|
||||||
world[..count],
|
world[..count],
|
||||||
frame.ViewProjection,
|
frame.ViewProjection,
|
||||||
|
|
@ -201,6 +204,9 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
||||||
private readonly Action _drawExitSeals;
|
private readonly Action _drawExitSeals;
|
||||||
private readonly HashSet<uint> _singleCellScratch = new();
|
private readonly HashSet<uint> _singleCellScratch = new();
|
||||||
private readonly List<uint> _singleCellListScratch = new();
|
private readonly List<uint> _singleCellListScratch = new();
|
||||||
|
private readonly HashSet<uint> _lookInParticleOwnerScratch = new();
|
||||||
|
private RenderFrameView _entityFrame;
|
||||||
|
private bool _entityFrameBound;
|
||||||
|
|
||||||
internal WalkProductionLeafRenderer(
|
internal WalkProductionLeafRenderer(
|
||||||
RetailPViewPassExecutor passes,
|
RetailPViewPassExecutor passes,
|
||||||
|
|
@ -242,6 +248,55 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
||||||
_passes.DrawLandscapeStaticParticles(
|
_passes.DrawLandscapeStaticParticles(
|
||||||
_frame, new RetailPViewLandscapeStaticParticleContext(ownerIds));
|
_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)
|
||||||
|
{
|
||||||
|
_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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawExitSeals() => _drawExitSeals();
|
public void DrawExitSeals() => _drawExitSeals();
|
||||||
|
|
||||||
public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
|
public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,13 @@ internal sealed partial class RetailPViewPassExecutor :
|
||||||
private readonly Dictionary<uint, int> _singleCellClipRouting = new(1);
|
private readonly Dictionary<uint, int> _singleCellClipRouting = new(1);
|
||||||
private readonly Dictionary<uint, int> _noCellClipRouting = new(0);
|
private readonly Dictionary<uint, int> _noCellClipRouting = new(0);
|
||||||
|
|
||||||
|
// ACDREAM_PROBE_WALK_ROOT / cathedral FW4: observation-only counters.
|
||||||
|
// They distinguish the walk-owned far-Z portal punches from the retired
|
||||||
|
// legacy look-in punch route. RetailPViewRenderer samples them for the
|
||||||
|
// diagnostic comparison after the walk-turn look-in routes have replayed.
|
||||||
|
internal int WalkLookInPunchCountThisFrame { get; private set; }
|
||||||
|
internal int LegacyLookInPunchCountThisFrame { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Borrowed until the next late landscape pass. The outdoor-root post-world
|
/// Borrowed until the next late landscape pass. The outdoor-root post-world
|
||||||
/// particle pass consumes this synchronously before another PView frame.
|
/// particle pass consumes this synchronously before another PView frame.
|
||||||
|
|
@ -181,7 +188,11 @@ internal sealed partial class RetailPViewPassExecutor :
|
||||||
{
|
{
|
||||||
_particleClassifications.BeginFrame();
|
_particleClassifications.BeginFrame();
|
||||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||||
|
{
|
||||||
AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "pre";
|
AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "pre";
|
||||||
|
WalkLookInPunchCountThisFrame = 0;
|
||||||
|
LegacyLookInPunchCountThisFrame = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Campaign FW3.2b-2: the shared dispatcher, for
|
/// <summary>Campaign FW3.2b-2: the shared dispatcher, for
|
||||||
|
|
@ -596,8 +607,19 @@ internal sealed partial class RetailPViewPassExecutor :
|
||||||
public void DrawLookInPortalPunch(
|
public void DrawLookInPortalPunch(
|
||||||
RetailPViewFrameInput frame,
|
RetailPViewFrameInput frame,
|
||||||
RetailPViewCellSliceContext context,
|
RetailPViewCellSliceContext context,
|
||||||
int portalIndex) =>
|
int portalIndex)
|
||||||
|
{
|
||||||
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||||
|
{
|
||||||
|
LegacyLookInPunchCountThisFrame++;
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[lookin-punch] source=legacy viewer=0x{frame.ViewerCellId:X8} "
|
||||||
|
+ $"root=0x{frame.RootCell.CellId:X8} cell=0x{context.CellId:X8} "
|
||||||
|
+ $"portal={portalIndex} planes={context.Slice.Planes.Length} "
|
||||||
|
+ $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}");
|
||||||
|
}
|
||||||
DrawPortalDepthWrite(context, frame, forceFarZ: true, portalIndex);
|
DrawPortalDepthWrite(context, frame, forceFarZ: true, portalIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawUnattachedSceneParticles(
|
public void DrawUnattachedSceneParticles(
|
||||||
RetailPViewFrameInput frame,
|
RetailPViewFrameInput frame,
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,12 @@ public sealed class RetailPViewRenderer
|
||||||
private bool? _probeWalkRootPrevOutdoor;
|
private bool? _probeWalkRootPrevOutdoor;
|
||||||
private int _probeWalkRootFramesLeft;
|
private int _probeWalkRootFramesLeft;
|
||||||
private ulong _probeWalkRootFrame;
|
private ulong _probeWalkRootFrame;
|
||||||
|
private string? _probeLookInState;
|
||||||
|
|
||||||
|
// The parked remote player's authoritative parent cell for the current
|
||||||
|
// cathedral FW4 investigation. This is diagnostic scope only: it changes
|
||||||
|
// no visibility or draw decision.
|
||||||
|
private const uint ProbeCathedralRemoteCellId = 0xF4180112u;
|
||||||
|
|
||||||
// Campaign FW3.2b-2: the walk's production world-data registries
|
// Campaign FW3.2b-2: the walk's production world-data registries
|
||||||
// (published/retired by LandblockRenderPublisher) plus the per-frame
|
// (published/retired by LandblockRenderPublisher) plus the per-frame
|
||||||
|
|
@ -263,6 +269,7 @@ public sealed class RetailPViewRenderer
|
||||||
// runs at append time, not at Replay time), so the world data must
|
// runs at append time, not at Replay time), so the world data must
|
||||||
// already be rebuilt for this frame before the walk starts.
|
// already be rebuilt for this frame before the walk starts.
|
||||||
Walk.WalkFrameDriver? walkDriver = null;
|
Walk.WalkFrameDriver? walkDriver = null;
|
||||||
|
WalkProductionLeafRenderer? walkLeafRenderer = null;
|
||||||
if (walkActive)
|
if (walkActive)
|
||||||
{
|
{
|
||||||
Matrix4x4 view = ctx.CameraView;
|
Matrix4x4 view = ctx.CameraView;
|
||||||
|
|
@ -343,9 +350,12 @@ public sealed class RetailPViewRenderer
|
||||||
Action drawExitSeals = () =>
|
Action drawExitSeals = () =>
|
||||||
DrawWalkExitPortalMasks(ctx, passes, clipAssembly, walkDriver!);
|
DrawWalkExitPortalMasks(ctx, passes, clipAssembly, walkDriver!);
|
||||||
|
|
||||||
var leafRenderer = new WalkProductionLeafRenderer(
|
walkLeafRenderer = new WalkProductionLeafRenderer(
|
||||||
walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals);
|
walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals);
|
||||||
walkDriver = new Walk.WalkFrameDriver(walkExecutor!.Dispatcher, leafRenderer, _walkWorldData);
|
walkDriver = new Walk.WalkFrameDriver(
|
||||||
|
walkExecutor!.Dispatcher,
|
||||||
|
walkLeafRenderer,
|
||||||
|
_walkWorldData);
|
||||||
|
|
||||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||||
{
|
{
|
||||||
|
|
@ -527,7 +537,8 @@ public sealed class RetailPViewRenderer
|
||||||
pvFrame,
|
pvFrame,
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
viewcone,
|
viewcone,
|
||||||
_lookInFrames,
|
walkActive ? walkDriver!.LookInCellTurns : [],
|
||||||
|
walkActive ? walkDriver : null,
|
||||||
outsideStageFlood,
|
outsideStageFlood,
|
||||||
ctx.Cells,
|
ctx.Cells,
|
||||||
ctx.AnimatedEntityIds,
|
ctx.AnimatedEntityIds,
|
||||||
|
|
@ -539,6 +550,8 @@ public sealed class RetailPViewRenderer
|
||||||
frameViewBorrowed = true;
|
frameViewBorrowed = true;
|
||||||
frameEntityPasses!.BeginEntityFrame(in frameView);
|
frameEntityPasses!.BeginEntityFrame(in frameView);
|
||||||
entityFrameOpen = true;
|
entityFrameOpen = true;
|
||||||
|
if (walkActive)
|
||||||
|
walkLeafRenderer!.BindEntityFrame(in frameView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The retained scene product is the production object source.
|
// The retained scene product is the production object source.
|
||||||
|
|
@ -651,6 +664,7 @@ public sealed class RetailPViewRenderer
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
capturedPartition,
|
capturedPartition,
|
||||||
capturedViewcone,
|
capturedViewcone,
|
||||||
|
capturedDriver,
|
||||||
capturedPasses,
|
capturedPasses,
|
||||||
in capturedView);
|
in capturedView);
|
||||||
};
|
};
|
||||||
|
|
@ -666,6 +680,7 @@ public sealed class RetailPViewRenderer
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
partition,
|
partition,
|
||||||
viewcone,
|
viewcone,
|
||||||
|
walkDriver!,
|
||||||
frameEntityPasses,
|
frameEntityPasses,
|
||||||
in frameView);
|
in frameView);
|
||||||
}
|
}
|
||||||
|
|
@ -1404,7 +1419,7 @@ public sealed class RetailPViewRenderer
|
||||||
_cellParticleOwnerScratch.Clear();
|
_cellParticleOwnerScratch.Clear();
|
||||||
foreach (uint cellId in driver.VisitedCells)
|
foreach (uint cellId in driver.VisitedCells)
|
||||||
{
|
{
|
||||||
if (_lookInCellIds.Contains(cellId))
|
if (driver.LookInCells.Contains(cellId))
|
||||||
continue;
|
continue;
|
||||||
UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch);
|
UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch);
|
||||||
}
|
}
|
||||||
|
|
@ -1431,15 +1446,16 @@ public sealed class RetailPViewRenderer
|
||||||
/// split — the walk now draws every STATIC route (see
|
/// split — the walk now draws every STATIC route (see
|
||||||
/// <see cref="DrawWalkDrivenStatics"/>); this method keeps ONLY what
|
/// <see cref="DrawWalkDrivenStatics"/>); this method keeps ONLY what
|
||||||
/// stays on the OLD visibility pipeline per the plan's dual-compute
|
/// stays on the OLD visibility pipeline per the plan's dual-compute
|
||||||
/// split: outdoor-cell unattached particles, LookInObject dynamics + their
|
/// split: outdoor-cell unattached particles, the late per-slice outside-
|
||||||
/// per-cell particles, the late per-slice outside-dynamics/weather loop,
|
/// dynamics/weather loop, and the late particle union submission. Look-in
|
||||||
/// and the late particle union submission.</summary>
|
/// dynamics and their cell particles are now walk-turn-owned.</summary>
|
||||||
private void DrawLandscapeDynamicsPhase(
|
private void DrawLandscapeDynamicsPhase(
|
||||||
RetailPViewFrameInput ctx,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
InteriorEntityPartition.Result? partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
|
Walk.WalkFrameDriver walkDriver,
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
||||||
in RenderFrameView frameView)
|
in RenderFrameView frameView)
|
||||||
{
|
{
|
||||||
|
|
@ -1452,8 +1468,10 @@ public sealed class RetailPViewRenderer
|
||||||
// (the walk owns its own alpha barriers — WalkFrameDriver.OnBuildingTurn).
|
// (the walk owns its own alpha barriers — WalkFrameDriver.OnBuildingTurn).
|
||||||
passes.DrawUnattachedSceneParticles(ctx, outdoorCells: true);
|
passes.DrawUnattachedSceneParticles(ctx, outdoorCells: true);
|
||||||
|
|
||||||
DrawBuildingLookInDynamics(
|
// Trace-only now. Look-in dynamics no longer have a late production
|
||||||
ctx, passes, clipAssembly, partition, frameEntityPasses, in frameView);
|
// phase: WalkFrameDriver replays each packed route at the walk's own
|
||||||
|
// per-cell DrawCells turn, before the enclosing building shell.
|
||||||
|
ProbeBuildingLookInFrames(ctx, passes, walkDriver);
|
||||||
|
|
||||||
// LATE phase (per slice): outside-stage dynamics' meshes + weather —
|
// LATE phase (per slice): outside-stage dynamics' meshes + weather —
|
||||||
// unchanged from DrawLandscapeThroughOutsideView's own late loop.
|
// unchanged from DrawLandscapeThroughOutsideView's own late loop.
|
||||||
|
|
@ -1527,129 +1545,72 @@ public sealed class RetailPViewRenderer
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Campaign FW3.2b-2: the DYNAMICS-only remainder of the old
|
/// <summary>ACDREAM_PROBE_WALK_ROOT companion for FW4's surviving
|
||||||
/// <see cref="DrawBuildingLookIns"/> — punches, shells, and look-in cell
|
/// through-wall dynamic. It compares the now-diagnostic-only legacy
|
||||||
/// STATICS are now walk-owned (<see cref="Walk.WalkFrameDriver"/>'s
|
/// look-in frames with the production walk's exact look-in set and reports
|
||||||
/// Building/BuildingShell/LookInStatic turns); this method keeps ONLY the
|
/// the two punch producers separately. Print-only; never participates in
|
||||||
/// LookInObject route (now dynamic-classified — see
|
/// admission.</summary>
|
||||||
/// <c>RenderScenePViewFrameBuilder.BuildLookInRoutes</c>) and the per-cell
|
private void ProbeBuildingLookInFrames(
|
||||||
/// particle union that route's owners feed, unioned with the walk's
|
|
||||||
/// static owners for that SAME cell (plan §FW3 item 4 — GetCellStatics
|
|
||||||
/// fills the gap the retired CellStatic-route particle submission left
|
|
||||||
/// for look-in cells specifically).</summary>
|
|
||||||
private void DrawBuildingLookInDynamics(
|
|
||||||
RetailPViewFrameInput ctx,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
Walk.WalkFrameDriver walkDriver)
|
||||||
InteriorEntityPartition.Result? partition,
|
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView)
|
|
||||||
{
|
{
|
||||||
if (_lookInFrames.Count == 0)
|
if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int lookInRouteIndex = 0;
|
uint[] walkLookInCells = walkDriver.LookInCells
|
||||||
for (int frameIndex = 0; frameIndex < _lookInFrames.Count; frameIndex++)
|
.OrderBy(cellId => cellId)
|
||||||
|
.ToArray();
|
||||||
|
bool legacyHasTarget = _lookInFrames.Any(
|
||||||
|
frame => frame.OrderedVisibleCells.Contains(ProbeCathedralRemoteCellId));
|
||||||
|
bool walkHasTarget = Array.IndexOf(
|
||||||
|
walkLookInCells, ProbeCathedralRemoteCellId) >= 0;
|
||||||
|
string legacyShape = string.Join(
|
||||||
|
";",
|
||||||
|
_lookInFrames.Select((frame, index) =>
|
||||||
|
$"{index}:b{frame.SourceBuildingKey:x8}/lb{frame.SourceBuildingLandblockId:x8}"
|
||||||
|
+ $"/s{frame.ExteriorSeedPortals.Count}/c["
|
||||||
|
+ string.Join(",", frame.OrderedVisibleCells.Select(cell => cell.ToString("x8")))
|
||||||
|
+ "]"));
|
||||||
|
string walkShape = string.Join(",", walkLookInCells.Select(cell => cell.ToString("x8")));
|
||||||
|
int walkPunches = passes is RetailPViewPassExecutor concrete
|
||||||
|
? concrete.WalkLookInPunchCountThisFrame
|
||||||
|
: -1;
|
||||||
|
int legacyPunches = passes is RetailPViewPassExecutor concreteLegacy
|
||||||
|
? concreteLegacy.LegacyLookInPunchCountThisFrame
|
||||||
|
: -1;
|
||||||
|
string state =
|
||||||
|
$"viewer={ctx.ViewerCellId:x8}|root={ctx.RootCell.CellId:x8}"
|
||||||
|
+ $"|phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"
|
||||||
|
+ $"|legacy={legacyShape}|walk={walkShape}"
|
||||||
|
+ $"|p={walkPunches}/{legacyPunches}";
|
||||||
|
bool emit = state != _probeLookInState || _probeWalkRootFrame % 30 == 0;
|
||||||
|
_probeLookInState = state;
|
||||||
|
if (!emit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[lookin-frame] f={_probeWalkRootFrame} "
|
||||||
|
+ $"viewer=0x{ctx.ViewerCellId:X8} root=0x{ctx.RootCell.CellId:X8} "
|
||||||
|
+ $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase} "
|
||||||
|
+ $"legacyFrames={_lookInFrames.Count} legacy112={(legacyHasTarget ? 1 : 0)} "
|
||||||
|
+ $"walkLookIn={walkLookInCells.Length} walk112={(walkHasTarget ? 1 : 0)} "
|
||||||
|
+ $"punches=walk:{walkPunches},legacy:{legacyPunches}");
|
||||||
|
for (int index = 0; index < _lookInFrames.Count; index++)
|
||||||
{
|
{
|
||||||
PortalVisibilityFrame frame = _lookInFrames[frameIndex];
|
PortalVisibilityFrame frame = _lookInFrames[index];
|
||||||
for (int i = frame.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
Console.WriteLine(
|
||||||
{
|
$"[lookin-frame] f={_probeWalkRootFrame} index={index} "
|
||||||
uint cellId = frame.OrderedVisibleCells[i];
|
+ $"building=0x{frame.SourceBuildingKey:X8} "
|
||||||
var clipKey = new LookInClipCell(frameIndex, cellId);
|
+ $"landblock=0x{frame.SourceBuildingLandblockId:X8} "
|
||||||
if (!clipAssembly.LookInCellToViewSlices.TryGetValue(
|
+ $"seeds={frame.ExteriorSeedPortals.Count} "
|
||||||
clipKey,
|
+ $"has112={(frame.OrderedVisibleCells.Contains(ProbeCathedralRemoteCellId) ? 1 : 0)} "
|
||||||
out ClipViewSlice[]? cellSlices)
|
+ $"cells=[{string.Join(",", frame.OrderedVisibleCells.Select(cell => $"0x{cell:X8}"))}]");
|
||||||
|| cellSlices.Length == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cellStaticScratch.Clear();
|
|
||||||
if (partition is not null)
|
|
||||||
{
|
|
||||||
foreach (var e in partition.Dynamics)
|
|
||||||
if (e.ParentCellId == cellId)
|
|
||||||
_cellStaticScratch.Add(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cellDrewObjects = false;
|
|
||||||
_cellParticleUnionScratch.Clear();
|
|
||||||
foreach (ClipViewSlice slice in cellSlices)
|
|
||||||
{
|
|
||||||
int routeIndex = lookInRouteIndex++;
|
|
||||||
passes.UseCellPortalViewRouting(cellId, slice);
|
|
||||||
|
|
||||||
if (frameEntityPasses is not null)
|
|
||||||
{
|
|
||||||
RenderFrameRouteOwnerSelector.Replace(
|
|
||||||
_cellParticleOwnerScratch,
|
|
||||||
in frameView,
|
|
||||||
RenderFrameCandidateRoute.LookInObject,
|
|
||||||
routeIndex,
|
|
||||||
cellId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ReplaceOwnerIds(
|
|
||||||
_cellParticleOwnerScratch,
|
|
||||||
_cellStaticScratch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameEntityPasses is not null
|
|
||||||
|| _cellStaticScratch.Count > 0)
|
|
||||||
{
|
|
||||||
_candidateObserver?.ObservePViewBucket(
|
|
||||||
CurrentRenderPViewRoute.LookInObject,
|
|
||||||
routeIndex,
|
|
||||||
cellId,
|
|
||||||
_cellStaticScratch);
|
|
||||||
_oneCell.Clear();
|
|
||||||
_oneCell.Add(cellId);
|
|
||||||
DrawEntityRouteOrLegacy(
|
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView,
|
|
||||||
RenderFrameCandidateRoute.LookInObject,
|
|
||||||
routeIndex,
|
|
||||||
cellId,
|
|
||||||
_cellStaticScratch,
|
|
||||||
_oneCell);
|
|
||||||
|
|
||||||
cellDrewObjects = true;
|
|
||||||
_cellParticleUnionScratch.UnionWith(
|
|
||||||
_cellParticleOwnerScratch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The walk already drew this cell's STATIC content
|
|
||||||
// (WalkFrameDriver's LookInStatic turn) but never submits
|
|
||||||
// particles for it — GetCellStatics fills that gap, unioned
|
|
||||||
// with the dynamic route's own owners so ONE
|
|
||||||
// DrawCellParticles call covers both.
|
|
||||||
if (_walkWorldData is not null)
|
|
||||||
{
|
|
||||||
Walk.WalkFrameStaticRecords statics =
|
|
||||||
_walkWorldData.GetCellStatics(cellId);
|
|
||||||
foreach (RenderProjectionRecord record in statics.Records)
|
|
||||||
{
|
|
||||||
if (record.Source.LocalEntityId != 0)
|
|
||||||
{
|
|
||||||
_cellParticleUnionScratch.Add(record.Source.LocalEntityId);
|
|
||||||
cellDrewObjects = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellDrewObjects)
|
|
||||||
{
|
|
||||||
passes.DrawCellParticles(ctx, new RetailPViewCellSliceContext(
|
|
||||||
cellId, NoClipSlice, _cellParticleUnionScratch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
|
||||||
}
|
}
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[lookin-frame] f={_probeWalkRootFrame} walkCells=["
|
||||||
|
+ string.Join(",", walkLookInCells.Select(cell => $"0x{cell:X8}"))
|
||||||
|
+ "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawLandscapeThroughOutsideView(
|
private void DrawLandscapeThroughOutsideView(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AcDream.Core.Selection;
|
using AcDream.Core.Selection;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
|
|
||||||
namespace AcDream.App.Rendering.Scene;
|
namespace AcDream.App.Rendering.Scene;
|
||||||
|
|
||||||
|
|
@ -173,6 +174,13 @@ internal readonly struct RenderFrameView
|
||||||
|
|
||||||
public ClipFrameAssembly? ClipAssembly => Arena.ClipAssembly;
|
public ClipFrameAssembly? ClipAssembly => Arena.ClipAssembly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exact portal-view cones captured by the retail frame walk for each
|
||||||
|
/// building look-in turn. The packed dispatcher consumes these at
|
||||||
|
/// per-GfxObj granularity, matching RenderDeviceD3D::DrawMesh.
|
||||||
|
/// </summary>
|
||||||
|
public IWalkLookInViewSource? WalkLookInViews => Arena.WalkLookInViews;
|
||||||
|
|
||||||
public RenderFrameDiagnosticCounts DiagnosticCounts =>
|
public RenderFrameDiagnosticCounts DiagnosticCounts =>
|
||||||
Arena.DiagnosticCounts;
|
Arena.DiagnosticCounts;
|
||||||
|
|
||||||
|
|
@ -247,6 +255,8 @@ internal sealed class RenderFrameArena
|
||||||
|
|
||||||
public ClipFrameAssembly? ClipAssembly { get; private set; }
|
public ClipFrameAssembly? ClipAssembly { get; private set; }
|
||||||
|
|
||||||
|
public IWalkLookInViewSource? WalkLookInViews { get; private set; }
|
||||||
|
|
||||||
public RenderSceneDigest SourceDigest { get; private set; }
|
public RenderSceneDigest SourceDigest { get; private set; }
|
||||||
|
|
||||||
public RenderFrameDiagnosticCounts DiagnosticCounts =>
|
public RenderFrameDiagnosticCounts DiagnosticCounts =>
|
||||||
|
|
@ -328,6 +338,7 @@ internal sealed class RenderFrameArena
|
||||||
_alphaClassificationCount = 0;
|
_alphaClassificationCount = 0;
|
||||||
PortalFrame = null;
|
PortalFrame = null;
|
||||||
ClipAssembly = null;
|
ClipAssembly = null;
|
||||||
|
WalkLookInViews = null;
|
||||||
SourceDigest = default;
|
SourceDigest = default;
|
||||||
_sourceDigestSet = false;
|
_sourceDigestSet = false;
|
||||||
Generation = generation;
|
Generation = generation;
|
||||||
|
|
@ -341,11 +352,13 @@ internal sealed class RenderFrameArena
|
||||||
internal void SetBorrowedProducts(
|
internal void SetBorrowedProducts(
|
||||||
ulong epoch,
|
ulong epoch,
|
||||||
PortalVisibilityFrame? portalFrame,
|
PortalVisibilityFrame? portalFrame,
|
||||||
ClipFrameAssembly? clipAssembly)
|
ClipFrameAssembly? clipAssembly,
|
||||||
|
IWalkLookInViewSource? walkLookInViews)
|
||||||
{
|
{
|
||||||
EnsureBuilding(epoch);
|
EnsureBuilding(epoch);
|
||||||
PortalFrame = portalFrame;
|
PortalFrame = portalFrame;
|
||||||
ClipAssembly = clipAssembly;
|
ClipAssembly = clipAssembly;
|
||||||
|
WalkLookInViews = walkLookInViews;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetSourceDigest(ulong epoch, in RenderSceneDigest digest)
|
internal void SetSourceDigest(ulong epoch, in RenderSceneDigest digest)
|
||||||
|
|
@ -549,6 +562,7 @@ internal sealed class RenderFrameArena
|
||||||
ClearReferenceStorage();
|
ClearReferenceStorage();
|
||||||
PortalFrame = null;
|
PortalFrame = null;
|
||||||
ClipAssembly = null;
|
ClipAssembly = null;
|
||||||
|
WalkLookInViews = null;
|
||||||
SourceDigest = default;
|
SourceDigest = default;
|
||||||
_sourceDigestSet = false;
|
_sourceDigestSet = false;
|
||||||
_state = ArenaState.Available;
|
_state = ArenaState.Available;
|
||||||
|
|
@ -655,8 +669,13 @@ internal readonly struct RenderFrameWriter
|
||||||
|
|
||||||
public void SetBorrowedProducts(
|
public void SetBorrowedProducts(
|
||||||
PortalVisibilityFrame? portalFrame,
|
PortalVisibilityFrame? portalFrame,
|
||||||
ClipFrameAssembly? clipAssembly) =>
|
ClipFrameAssembly? clipAssembly,
|
||||||
Arena.SetBorrowedProducts(_epoch, portalFrame, clipAssembly);
|
IWalkLookInViewSource? walkLookInViews = null) =>
|
||||||
|
Arena.SetBorrowedProducts(
|
||||||
|
_epoch,
|
||||||
|
portalFrame,
|
||||||
|
clipAssembly,
|
||||||
|
walkLookInViews);
|
||||||
|
|
||||||
public void SetSourceDigest(in RenderSceneDigest digest) =>
|
public void SetSourceDigest(in RenderSceneDigest digest) =>
|
||||||
Arena.SetSourceDigest(_epoch, in digest);
|
Arena.SetSourceDigest(_epoch, in digest);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
|
|
||||||
namespace AcDream.App.Rendering.Scene;
|
namespace AcDream.App.Rendering.Scene;
|
||||||
|
|
||||||
|
|
@ -9,7 +10,8 @@ internal readonly record struct RenderScenePViewBuildInput(
|
||||||
PortalVisibilityFrame PortalFrame,
|
PortalVisibilityFrame PortalFrame,
|
||||||
ClipFrameAssembly ClipAssembly,
|
ClipFrameAssembly ClipAssembly,
|
||||||
ViewconeCuller Viewcone,
|
ViewconeCuller Viewcone,
|
||||||
IReadOnlyList<PortalVisibilityFrame> LookInFrames,
|
IReadOnlyList<uint> LookInCellTurns,
|
||||||
|
IWalkLookInViewSource? WalkLookInViews,
|
||||||
HashSet<uint> DrawableCells,
|
HashSet<uint> DrawableCells,
|
||||||
IRetailPViewCellSource Cells,
|
IRetailPViewCellSource Cells,
|
||||||
HashSet<uint>? AnimatedEntityIds,
|
HashSet<uint>? AnimatedEntityIds,
|
||||||
|
|
@ -185,7 +187,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
PortalVisibilityFrame portalFrame,
|
PortalVisibilityFrame portalFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
|
IReadOnlyList<uint> lookInCellTurns,
|
||||||
|
IWalkLookInViewSource? walkLookInViews,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
IRetailPViewCellSource cells,
|
IRetailPViewCellSource cells,
|
||||||
HashSet<uint>? animatedEntityIds,
|
HashSet<uint>? animatedEntityIds,
|
||||||
|
|
@ -195,7 +198,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
ArgumentNullException.ThrowIfNull(portalFrame);
|
ArgumentNullException.ThrowIfNull(portalFrame);
|
||||||
ArgumentNullException.ThrowIfNull(clipAssembly);
|
ArgumentNullException.ThrowIfNull(clipAssembly);
|
||||||
ArgumentNullException.ThrowIfNull(viewcone);
|
ArgumentNullException.ThrowIfNull(viewcone);
|
||||||
ArgumentNullException.ThrowIfNull(lookInFrames);
|
ArgumentNullException.ThrowIfNull(lookInCellTurns);
|
||||||
ArgumentNullException.ThrowIfNull(drawableCells);
|
ArgumentNullException.ThrowIfNull(drawableCells);
|
||||||
ArgumentNullException.ThrowIfNull(cells);
|
ArgumentNullException.ThrowIfNull(cells);
|
||||||
|
|
||||||
|
|
@ -213,7 +216,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
portalFrame,
|
portalFrame,
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
viewcone,
|
viewcone,
|
||||||
lookInFrames,
|
lookInCellTurns,
|
||||||
|
walkLookInViews,
|
||||||
drawableCells,
|
drawableCells,
|
||||||
cells,
|
cells,
|
||||||
animatedEntityIds,
|
animatedEntityIds,
|
||||||
|
|
@ -248,7 +252,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
portalFrame,
|
portalFrame,
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
viewcone,
|
viewcone,
|
||||||
lookInFrames,
|
FlattenLegacyLookInCells(lookInFrames),
|
||||||
|
walkLookInViews: null,
|
||||||
drawableCells,
|
drawableCells,
|
||||||
cells,
|
cells,
|
||||||
animatedEntityIds,
|
animatedEntityIds,
|
||||||
|
|
@ -267,6 +272,19 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uint[] FlattenLegacyLookInCells(
|
||||||
|
IReadOnlyList<PortalVisibilityFrame> lookInFrames)
|
||||||
|
{
|
||||||
|
var cells = new List<uint>();
|
||||||
|
for (int frameIndex = 0; frameIndex < lookInFrames.Count; frameIndex++)
|
||||||
|
{
|
||||||
|
PortalVisibilityFrame frame = lookInFrames[frameIndex];
|
||||||
|
for (int index = frame.OrderedVisibleCells.Count - 1; index >= 0; index--)
|
||||||
|
cells.Add(frame.OrderedVisibleCells[index]);
|
||||||
|
}
|
||||||
|
return cells.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
public void CompleteProduction(in RenderFrameView view)
|
public void CompleteProduction(in RenderFrameView view)
|
||||||
{
|
{
|
||||||
_packedClassification =
|
_packedClassification =
|
||||||
|
|
@ -1171,7 +1189,8 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
{
|
{
|
||||||
writer.SetBorrowedProducts(
|
writer.SetBorrowedProducts(
|
||||||
input.PortalFrame,
|
input.PortalFrame,
|
||||||
input.ClipAssembly);
|
input.ClipAssembly,
|
||||||
|
input.WalkLookInViews);
|
||||||
RenderSceneDigest digest = input.SourceDigest;
|
RenderSceneDigest digest = input.SourceDigest;
|
||||||
writer.SetSourceDigest(in digest);
|
writer.SetSourceDigest(in digest);
|
||||||
_projectionIds.Clear();
|
_projectionIds.Clear();
|
||||||
|
|
@ -1182,19 +1201,11 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
// outdoor static, building shell, and cell static (including
|
// outdoor static, building shell, and cell static (including
|
||||||
// look-in cell statics) directly through OrderedDrawStream (plan
|
// look-in cell statics) directly through OrderedDrawStream (plan
|
||||||
// §FW3 "FW3.2b-2 — the production rooting", item 3). LookInObject
|
// §FW3 "FW3.2b-2 — the production rooting", item 3). LookInObject
|
||||||
// keeps emitting, but BuildLookInRoutes below is now filtered to
|
// keeps the packed classifier for live animation/fades, but its
|
||||||
// DYNAMIC candidates only — the walk owns that route's statics.
|
// route ranges are keyed to THE WALK'S own look-in cell turns.
|
||||||
int lookInRouteIndex = 0;
|
// The legacy PortalVisibilityFrame list no longer has a production
|
||||||
for (int frameIndex = 0;
|
// admission or ordering role.
|
||||||
frameIndex < input.LookInFrames.Count;
|
BuildLookInRoutes(writer, in input);
|
||||||
frameIndex++)
|
|
||||||
{
|
|
||||||
BuildLookInRoutes(
|
|
||||||
writer,
|
|
||||||
in input,
|
|
||||||
frameIndex,
|
|
||||||
ref lookInRouteIndex);
|
|
||||||
}
|
|
||||||
BuildOutsideDynamicRoutes(writer, in input);
|
BuildOutsideDynamicRoutes(writer, in input);
|
||||||
BuildDynamicLastRoute(writer, in input);
|
BuildDynamicLastRoute(writer, in input);
|
||||||
writer.Publish();
|
writer.Publish();
|
||||||
|
|
@ -1284,45 +1295,37 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
/// item 3).</summary>
|
/// item 3).</summary>
|
||||||
private void BuildLookInRoutes(
|
private void BuildLookInRoutes(
|
||||||
RenderFrameWriter writer,
|
RenderFrameWriter writer,
|
||||||
in RenderScenePViewBuildInput input,
|
in RenderScenePViewBuildInput input)
|
||||||
int frameIndex,
|
|
||||||
ref int routeIndex)
|
|
||||||
{
|
{
|
||||||
PortalVisibilityFrame frame = input.LookInFrames[frameIndex];
|
for (int routeIndex = 0; routeIndex < input.LookInCellTurns.Count; routeIndex++)
|
||||||
for (int i = frame.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
uint cellId = frame.OrderedVisibleCells[i];
|
uint cellId = input.LookInCellTurns[routeIndex];
|
||||||
var clipKey = new LookInClipCell(frameIndex, cellId);
|
|
||||||
if (!input.ClipAssembly.LookInCellToViewSlices.TryGetValue(
|
|
||||||
clipKey,
|
|
||||||
out ClipViewSlice[]? slices)
|
|
||||||
|| slices.Length == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = LoadCell(
|
int count = LoadCell(
|
||||||
input.Scene,
|
input.Scene,
|
||||||
cellId,
|
cellId,
|
||||||
includeStatics: false,
|
includeStatics: false,
|
||||||
includeDynamics: true);
|
includeDynamics: true);
|
||||||
for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++)
|
if (count == 0)
|
||||||
{
|
continue;
|
||||||
int currentRouteIndex = routeIndex++;
|
|
||||||
if (count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (int index = 0; index < count; index++)
|
int survivorCount = 0;
|
||||||
AddProjection(
|
EnsureCapacity(ref _survivors, count);
|
||||||
writer,
|
for (int index = 0; index < count; index++)
|
||||||
in _cell[index],
|
{
|
||||||
input.AnimatedEntityIds);
|
RenderProjectionRecord record = _cell[index];
|
||||||
writer.AddRouteRange(
|
_survivors[survivorCount++] = record;
|
||||||
RenderFrameCandidateRoute.LookInObject,
|
AddProjection(
|
||||||
currentRouteIndex,
|
writer,
|
||||||
cellId,
|
in record,
|
||||||
_cell.AsSpan(0, count));
|
input.AnimatedEntityIds);
|
||||||
}
|
}
|
||||||
|
if (survivorCount == 0)
|
||||||
|
continue;
|
||||||
|
writer.AddRouteRange(
|
||||||
|
RenderFrameCandidateRoute.LookInObject,
|
||||||
|
routeIndex,
|
||||||
|
cellId,
|
||||||
|
_survivors.AsSpan(0, survivorCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1384,14 +1387,8 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
in RenderScenePViewBuildInput input)
|
in RenderScenePViewBuildInput input)
|
||||||
{
|
{
|
||||||
_lookInCellScratch.Clear();
|
_lookInCellScratch.Clear();
|
||||||
for (int frameIndex = 0;
|
for (int index = 0; index < input.LookInCellTurns.Count; index++)
|
||||||
frameIndex < input.LookInFrames.Count;
|
_lookInCellScratch.Add(input.LookInCellTurns[index]);
|
||||||
frameIndex++)
|
|
||||||
{
|
|
||||||
PortalVisibilityFrame frame = input.LookInFrames[frameIndex];
|
|
||||||
for (int i = 0; i < frame.OrderedVisibleCells.Count; i++)
|
|
||||||
_lookInCellScratch.Add(frame.OrderedVisibleCells[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
EnsureCapacity(ref _survivors, _dynamicCount);
|
EnsureCapacity(ref _survivors, _dynamicCount);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Gpu;
|
using AcDream.App.Rendering.Gpu;
|
||||||
using AcDream.App.Rendering.Scene;
|
using AcDream.App.Rendering.Scene;
|
||||||
|
|
@ -118,6 +119,17 @@ internal interface IWalkFrameLeafRenderer
|
||||||
/// positional invariant this carries (the #132 falls containment).</summary>
|
/// positional invariant this carries (the #132 falls containment).</summary>
|
||||||
void DrawStaticParticles(IReadOnlySet<uint> ownerIds);
|
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
|
/// <summary><c>PView::DrawCells</c> @0x005a4840's gated full depth clear
|
||||||
/// (pc:432731-432732) between the outside stage and the interior root's
|
/// (pc:432731-432732) between the outside stage and the interior root's
|
||||||
/// own flood — production maps this to <c>IWorldPassScope.ClearInteriorDepth</c>
|
/// 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
|
/// cathedral bleed; the old pipeline's user-verified #132 fix
|
||||||
/// `e102fb36` encoded the same invariant).</summary>
|
/// `e102fb36` encoded the same invariant).</summary>
|
||||||
StaticParticles,
|
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
|
/// <summary>See <see cref="WalkFrameEventKind"/> for what each field means per
|
||||||
/// kind. A single struct (rather than a kind hierarchy) keeps Collect's
|
/// kind. A single struct (rather than a kind hierarchy) keeps Collect's
|
||||||
/// per-turn list a flat, allocation-cheap <c>List<WalkFrameEvent></c> —
|
/// 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) =>
|
internal static WalkFrameEvent BuildingShellParticles(WalkBuilding building) =>
|
||||||
new(WalkFrameEventKind.StaticParticles, 0, 0, 0f, null, 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() =>
|
internal static WalkFrameEvent ClearInteriorDepth() =>
|
||||||
new(WalkFrameEventKind.ClearInteriorDepth, 0, 0, 0f, null);
|
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
|
/// @0x0059f2a0 (the <c>part->gfxobj[deg_level]!=0</c> gate @0x0059f2d3
|
||||||
/// and the alpha-barrier → portal-pass → shell order @0x0059f30b–0x0059f345).</para>
|
/// and the alpha-barrier → portal-pass → shell order @0x0059f30b–0x0059f345).</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class WalkFrameDriver : IWalkEventSink
|
internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
{
|
{
|
||||||
private readonly WbDrawDispatcher _dispatcher;
|
private readonly WbDrawDispatcher _dispatcher;
|
||||||
private readonly WalkStaticStreamPopulator _populator;
|
private readonly WalkStaticStreamPopulator _populator;
|
||||||
|
|
@ -422,6 +466,23 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
||||||
// walk pass dedicated to nothing but set-gathering.
|
// walk pass dedicated to nothing but set-gathering.
|
||||||
internal HashSet<uint> VisitedCells { get; } = new();
|
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,
|
/// <summary>FW4 slice 2: the interior root's ORDERED flood cell list,
|
||||||
/// exactly as retail's <c>PView::DrawCells</c> iterates it for the
|
/// exactly as retail's <c>PView::DrawCells</c> iterates it for the
|
||||||
/// exit-portal seals (pc:432785-432786) — captured at
|
/// exit-portal seals (pc:432785-432786) — captured at
|
||||||
|
|
@ -459,6 +520,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
||||||
private bool _skyDrawnThisFrame;
|
private bool _skyDrawnThisFrame;
|
||||||
private WalkDrawStage? _currentDcStage;
|
private WalkDrawStage? _currentDcStage;
|
||||||
private bool _readyToReplay;
|
private bool _readyToReplay;
|
||||||
|
private int _lookInRouteIndex;
|
||||||
|
|
||||||
internal WalkFrameDriver(
|
internal WalkFrameDriver(
|
||||||
WbDrawDispatcher dispatcher,
|
WbDrawDispatcher dispatcher,
|
||||||
|
|
@ -534,7 +596,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
||||||
/// GPU work — see this type's own doc comment.
|
/// GPU work — see this type's own doc comment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void BeginFrame(
|
internal void BeginFrame(
|
||||||
IWalkBuildingFrameContext ctx,
|
IRetailFrameWalkContext ctx,
|
||||||
Matrix4x4 viewProjection,
|
Matrix4x4 viewProjection,
|
||||||
Vector3 cameraWorldPosition)
|
Vector3 cameraWorldPosition)
|
||||||
{
|
{
|
||||||
|
|
@ -557,9 +619,16 @@ internal sealed class WalkFrameDriver : IWalkEventSink
|
||||||
_events.Clear();
|
_events.Clear();
|
||||||
_markPositions.Clear();
|
_markPositions.Clear();
|
||||||
VisitedCells.Clear();
|
VisitedCells.Clear();
|
||||||
|
LookInCellTurns.Clear();
|
||||||
|
_lookInTurns.Clear();
|
||||||
|
_lookInSlices.Clear();
|
||||||
|
_lookInPlanes.Clear();
|
||||||
|
_lookInCyPlane = ctx.CyPlane;
|
||||||
|
LookInCells.Clear();
|
||||||
VisitedBuildings.Clear();
|
VisitedBuildings.Clear();
|
||||||
VisitedLandscapeCellIds.Clear();
|
VisitedLandscapeCellIds.Clear();
|
||||||
InteriorFloodCells.Clear();
|
InteriorFloodCells.Clear();
|
||||||
|
_lookInRouteIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Records the final segment mark (plan §FW3.2b-1's "at frame
|
/// <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)
|
if (_staticParticleOwnerScratch.Count > 0)
|
||||||
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
||||||
break;
|
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(
|
_populator.PopulateCell(
|
||||||
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
||||||
_cameraWorldPosition, _viewProjection);
|
_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
|
/// <summary>Campaign FW3.4a: the collect-time analogue of the old
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using AcDream.Core.Lighting;
|
||||||
using AcDream.Core.Meshing;
|
using AcDream.Core.Meshing;
|
||||||
using AcDream.Core.Selection;
|
using AcDream.Core.Selection;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
using DatReaderWriter.Enums;
|
using DatReaderWriter.Enums;
|
||||||
|
|
||||||
namespace AcDream.App.Rendering.Wb;
|
namespace AcDream.App.Rendering.Wb;
|
||||||
|
|
@ -40,6 +41,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
private RenderSceneGeneration _packedProductionGeneration;
|
private RenderSceneGeneration _packedProductionGeneration;
|
||||||
private ulong _packedProductionFrameSequence;
|
private ulong _packedProductionFrameSequence;
|
||||||
private int _packedProductionNextRange;
|
private int _packedProductionNextRange;
|
||||||
|
private readonly Dictionary<(int RouteIndex, uint LocalEntityId, int PartIndex, uint GfxObjId), string>
|
||||||
|
_probeLookInPartStates = [];
|
||||||
|
|
||||||
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
||||||
PackedDispatcherSubmissions => _packedSubmissions;
|
PackedDispatcherSubmissions => _packedSubmissions;
|
||||||
|
|
@ -295,7 +298,12 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
source.MeshPartOffset,
|
source.MeshPartOffset,
|
||||||
source.MeshPartCount),
|
source.MeshPartCount),
|
||||||
ref anyVao,
|
ref anyVao,
|
||||||
publishSelection);
|
publishSelection,
|
||||||
|
range.Route is RenderFrameCandidateRoute.LookInObject
|
||||||
|
? view.WalkLookInViews
|
||||||
|
: null,
|
||||||
|
range.RouteIndex,
|
||||||
|
range.CellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PackedRangeClassification(
|
return new PackedRangeClassification(
|
||||||
|
|
@ -334,7 +342,10 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
in RenderInstanceCandidate entity,
|
in RenderInstanceCandidate entity,
|
||||||
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
||||||
ref uint anyVao,
|
ref uint anyVao,
|
||||||
bool publishSelection)
|
bool publishSelection,
|
||||||
|
IWalkLookInViewSource? lookInViews,
|
||||||
|
int lookInRouteIndex,
|
||||||
|
uint lookInCellId)
|
||||||
{
|
{
|
||||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||||
_clipRoutingActive,
|
_clipRoutingActive,
|
||||||
|
|
@ -361,7 +372,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
: new Vector2(0f, 1f);
|
: new Vector2(0f, 1f);
|
||||||
|
|
||||||
PackedProjectionClassificationEntry? cacheEntry = null;
|
PackedProjectionClassificationEntry? cacheEntry = null;
|
||||||
if (!entity.Animated)
|
bool lookInConeActive = lookInViews is not null;
|
||||||
|
if (!entity.Animated && !lookInConeActive)
|
||||||
{
|
{
|
||||||
PackedClassificationIdentity identity =
|
PackedClassificationIdentity identity =
|
||||||
PackedClassificationIdentity.From(in projection);
|
PackedClassificationIdentity.From(in projection);
|
||||||
|
|
@ -394,7 +406,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
entity.ProjectionId,
|
entity.ProjectionId,
|
||||||
in identity);
|
in identity);
|
||||||
}
|
}
|
||||||
else
|
else if (entity.Animated)
|
||||||
{
|
{
|
||||||
_packedClassificationCache.RecordAnimatedClassification();
|
_packedClassificationCache.RecordAnimatedClassification();
|
||||||
}
|
}
|
||||||
|
|
@ -485,6 +497,21 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
partTransform * meshRef.PartTransform;
|
partTransform * meshRef.PartTransform;
|
||||||
Matrix4x4 model =
|
Matrix4x4 model =
|
||||||
restPose * entity.RootWorld;
|
restPose * entity.RootWorld;
|
||||||
|
int selectionPartIndex = unchecked(
|
||||||
|
(partIndex << 16)
|
||||||
|
| (setupPartIndex & 0xFFFF));
|
||||||
|
if (!PartVisibleInLookInTurn(
|
||||||
|
lookInViews,
|
||||||
|
lookInRouteIndex,
|
||||||
|
lookInCellId,
|
||||||
|
in entity,
|
||||||
|
selectionPartIndex,
|
||||||
|
(uint)gfxObjId,
|
||||||
|
partData,
|
||||||
|
model))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!ClassifyPackedBatches(
|
if (!ClassifyPackedBatches(
|
||||||
partData,
|
partData,
|
||||||
restPose,
|
restPose,
|
||||||
|
|
@ -502,9 +529,6 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
{
|
{
|
||||||
reusableAcrossFrames = false;
|
reusableAcrossFrames = false;
|
||||||
}
|
}
|
||||||
int selectionPartIndex = unchecked(
|
|
||||||
(partIndex << 16)
|
|
||||||
| (setupPartIndex & 0xFFFF));
|
|
||||||
cacheEntry?.SelectionParts.Add(
|
cacheEntry?.SelectionParts.Add(
|
||||||
new PackedClassifiedSelectionPart(
|
new PackedClassifiedSelectionPart(
|
||||||
selectionPartIndex,
|
selectionPartIndex,
|
||||||
|
|
@ -538,6 +562,18 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
|
|
||||||
Matrix4x4 restPose = meshRef.PartTransform;
|
Matrix4x4 restPose = meshRef.PartTransform;
|
||||||
Matrix4x4 model = restPose * entity.RootWorld;
|
Matrix4x4 model = restPose * entity.RootWorld;
|
||||||
|
if (!PartVisibleInLookInTurn(
|
||||||
|
lookInViews,
|
||||||
|
lookInRouteIndex,
|
||||||
|
lookInCellId,
|
||||||
|
in entity,
|
||||||
|
partIndex,
|
||||||
|
meshRef.GfxObjId,
|
||||||
|
renderData,
|
||||||
|
model))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!ClassifyPackedBatches(
|
if (!ClassifyPackedBatches(
|
||||||
renderData,
|
renderData,
|
||||||
restPose,
|
restPose,
|
||||||
|
|
@ -576,6 +612,97 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retail RenderDeviceD3D::DrawMesh @0x005A0860 admits each CGfxObj
|
||||||
|
/// independently by transforming its drawing sphere into the active
|
||||||
|
/// portal view. A whole-entity sphere is not equivalent for multipart
|
||||||
|
/// creatures: the union can be roughly ten metres wide and intersect an
|
||||||
|
/// aperture while every actual body/equipment part is behind its wall.
|
||||||
|
/// </summary>
|
||||||
|
private bool PartVisibleInLookInTurn(
|
||||||
|
IWalkLookInViewSource? lookInViews,
|
||||||
|
int routeIndex,
|
||||||
|
uint cellId,
|
||||||
|
in RenderInstanceCandidate entity,
|
||||||
|
int partIndex,
|
||||||
|
uint gfxObjId,
|
||||||
|
ObjectRenderData renderData,
|
||||||
|
Matrix4x4 localToWorld)
|
||||||
|
{
|
||||||
|
if (lookInViews is null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// ObjectRenderData.SelectionSphere is retained per GfxObj by the
|
||||||
|
// prepared mesh payload. It is never the entity-union sphere and is
|
||||||
|
// therefore the correct granularity for DrawMesh admission. The
|
||||||
|
// current package stores a conservative vertex-derived sphere here;
|
||||||
|
// this preserves whole-mesh drawing while avoiding the invalid
|
||||||
|
// aggregate-character admission that caused the cathedral bleed.
|
||||||
|
if (renderData.SelectionSphere is not { Radius: > 0f } sphere)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool visible = LookInDrawingSphereVisible(
|
||||||
|
lookInViews,
|
||||||
|
routeIndex,
|
||||||
|
sphere,
|
||||||
|
localToWorld,
|
||||||
|
out Vector3 center,
|
||||||
|
out float radius);
|
||||||
|
|
||||||
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
|
||||||
|
&& cellId == 0xF4180112u)
|
||||||
|
{
|
||||||
|
var key = (routeIndex, entity.LocalEntityId, partIndex, gfxObjId);
|
||||||
|
string state =
|
||||||
|
$"visible={(visible ? 1 : 0)} "
|
||||||
|
+ $"center=({center.X:F2},{center.Y:F2},{center.Z:F2}) "
|
||||||
|
+ $"r={radius:F2}";
|
||||||
|
if (!_probeLookInPartStates.TryGetValue(key, out string? previous)
|
||||||
|
|| previous != state)
|
||||||
|
{
|
||||||
|
_probeLookInPartStates[key] = state;
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[lookin-part] route={routeIndex} cell=0x{cellId:X8} "
|
||||||
|
+ $"id={entity.LocalEntityId:x} part={partIndex} "
|
||||||
|
+ $"gfx=0x{gfxObjId:X8} {state}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool LookInDrawingSphereVisible(
|
||||||
|
IWalkLookInViewSource lookInViews,
|
||||||
|
int routeIndex,
|
||||||
|
DatReaderWriter.Types.Sphere sphere,
|
||||||
|
Matrix4x4 localToWorld,
|
||||||
|
out Vector3 center,
|
||||||
|
out float radius)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(lookInViews);
|
||||||
|
ArgumentNullException.ThrowIfNull(sphere);
|
||||||
|
|
||||||
|
center = Vector3.Transform(sphere.Origin, localToWorld);
|
||||||
|
float scaleX = new Vector3(
|
||||||
|
localToWorld.M11,
|
||||||
|
localToWorld.M12,
|
||||||
|
localToWorld.M13).Length();
|
||||||
|
float scaleY = new Vector3(
|
||||||
|
localToWorld.M21,
|
||||||
|
localToWorld.M22,
|
||||||
|
localToWorld.M23).Length();
|
||||||
|
float scaleZ = new Vector3(
|
||||||
|
localToWorld.M31,
|
||||||
|
localToWorld.M32,
|
||||||
|
localToWorld.M33).Length();
|
||||||
|
radius = sphere.Radius
|
||||||
|
* MathF.Max(scaleX, MathF.Max(scaleY, scaleZ));
|
||||||
|
return lookInViews.SphereVisibleInLookInTurn(
|
||||||
|
routeIndex,
|
||||||
|
in center,
|
||||||
|
radius);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mirrors the production dispatcher's no-VAO early return. That return
|
/// Mirrors the production dispatcher's no-VAO early return. That return
|
||||||
/// records an empty submission with transparent deferral disabled even
|
/// records an empty submission with transparent deferral disabled even
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,23 @@ using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Scene;
|
using AcDream.App.Rendering.Scene;
|
||||||
using AcDream.App.Rendering.Scene.Arch;
|
using AcDream.App.Rendering.Scene.Arch;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Rendering;
|
namespace AcDream.App.Tests.Rendering;
|
||||||
|
|
||||||
public sealed class RenderScenePViewFrameProductTests
|
public sealed class RenderScenePViewFrameProductTests
|
||||||
{
|
{
|
||||||
|
private sealed class RejectAllLookInViews(uint cellId) : IWalkLookInViewSource
|
||||||
|
{
|
||||||
|
public IReadOnlyList<uint> LookInCellTurns { get; } = [cellId];
|
||||||
|
|
||||||
|
public bool SphereVisibleInLookInTurn(
|
||||||
|
int routeIndex,
|
||||||
|
in Vector3 center,
|
||||||
|
float radius) => false;
|
||||||
|
}
|
||||||
|
|
||||||
private const uint Landblock = 0xA9B4FFFF;
|
private const uint Landblock = 0xA9B4FFFF;
|
||||||
private const uint Cell = 0xA9B40170;
|
private const uint Cell = 0xA9B40170;
|
||||||
private static readonly RenderSceneGeneration Generation =
|
private static readonly RenderSceneGeneration Generation =
|
||||||
|
|
@ -159,6 +170,7 @@ public sealed class RenderScenePViewFrameProductTests
|
||||||
clip,
|
clip,
|
||||||
viewcone,
|
viewcone,
|
||||||
[],
|
[],
|
||||||
|
null,
|
||||||
[Cell],
|
[Cell],
|
||||||
EmptyCellSource.Instance,
|
EmptyCellSource.Instance,
|
||||||
[],
|
[],
|
||||||
|
|
@ -168,6 +180,134 @@ public sealed class RenderScenePViewFrameProductTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Builder_KeysLookInDynamicsToWalkTurnsAndExcludesThemFromDynamicLast()
|
||||||
|
{
|
||||||
|
const uint lookInCell = 0xA9B4_0171;
|
||||||
|
using var scene = new ArchRenderScene(Generation);
|
||||||
|
RenderProjectionRecord rootDynamic = Record(
|
||||||
|
0x0100_0000_0000_0021,
|
||||||
|
RenderProjectionClass.LiveDynamicRoot,
|
||||||
|
parentCell: Cell);
|
||||||
|
RenderProjectionRecord lookInDynamic = Record(
|
||||||
|
0x0100_0000_0000_0022,
|
||||||
|
RenderProjectionClass.LiveDynamicRoot,
|
||||||
|
parentCell: lookInCell);
|
||||||
|
scene.Apply(
|
||||||
|
[
|
||||||
|
RenderProjectionDelta.Register(Generation, 1, rootDynamic),
|
||||||
|
RenderProjectionDelta.Register(Generation, 2, lookInDynamic),
|
||||||
|
]);
|
||||||
|
|
||||||
|
PortalVisibilityFrame portal = Portal(Cell);
|
||||||
|
ClipFrameAssembly clip = FullScreenClip(Cell);
|
||||||
|
ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||||
|
RenderSceneQuery query = scene.OpenQuery();
|
||||||
|
var input = new RenderScenePViewBuildInput(
|
||||||
|
query,
|
||||||
|
new RenderSceneDigest(query.Generation, query.Counts, default),
|
||||||
|
portal,
|
||||||
|
clip,
|
||||||
|
viewcone,
|
||||||
|
LookInCellTurns: [lookInCell],
|
||||||
|
WalkLookInViews: null,
|
||||||
|
DrawableCells: [Cell, lookInCell],
|
||||||
|
Cells: EmptyCellSource.Instance,
|
||||||
|
AnimatedEntityIds: [],
|
||||||
|
RootIsOutdoor: false,
|
||||||
|
RootFloodCells: new HashSet<uint> { Cell });
|
||||||
|
var exchange = new RenderFrameExchange();
|
||||||
|
var builder = new RenderScenePViewFrameBuilder();
|
||||||
|
|
||||||
|
builder.Build(exchange, frameSequence: 1, in input);
|
||||||
|
RenderFrameView frame = exchange.BorrowLatest(Generation, 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RenderFrameCandidateRange lookInRange = Assert.Single(
|
||||||
|
frame.RouteRanges.ToArray(),
|
||||||
|
range => range.Route == RenderFrameCandidateRoute.LookInObject);
|
||||||
|
Assert.Equal(0, lookInRange.RouteIndex);
|
||||||
|
Assert.Equal(lookInCell, lookInRange.CellId);
|
||||||
|
Assert.Equal(
|
||||||
|
[lookInDynamic.Id],
|
||||||
|
frame.RouteCandidates
|
||||||
|
.Slice(lookInRange.Offset, lookInRange.Count)
|
||||||
|
.ToArray()
|
||||||
|
.Select(static record => record.Id));
|
||||||
|
|
||||||
|
RenderFrameCandidateRange dynamicLast = Assert.Single(
|
||||||
|
frame.RouteRanges.ToArray(),
|
||||||
|
range => range.Route == RenderFrameCandidateRoute.DynamicLast);
|
||||||
|
RenderProjectionId[] lastIds = frame.RouteCandidates
|
||||||
|
.Slice(dynamicLast.Offset, dynamicLast.Count)
|
||||||
|
.ToArray()
|
||||||
|
.Select(static record => record.Id)
|
||||||
|
.ToArray();
|
||||||
|
Assert.Contains(rootDynamic.Id, lastIds);
|
||||||
|
Assert.DoesNotContain(lookInDynamic.Id, lastIds);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
exchange.Release(in frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Builder_CarriesExactWalkConeForPerPartDispatcherAdmission()
|
||||||
|
{
|
||||||
|
const uint lookInCell = 0xA9B4_0171;
|
||||||
|
using var scene = new ArchRenderScene(Generation);
|
||||||
|
RenderProjectionRecord lookInDynamic = Record(
|
||||||
|
0x0100_0000_0000_0022,
|
||||||
|
RenderProjectionClass.LiveDynamicRoot,
|
||||||
|
parentCell: lookInCell);
|
||||||
|
scene.Apply([RenderProjectionDelta.Register(Generation, 1, lookInDynamic)]);
|
||||||
|
|
||||||
|
PortalVisibilityFrame portal = Portal(Cell);
|
||||||
|
ClipFrameAssembly clip = FullScreenClip(Cell);
|
||||||
|
ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||||
|
RenderSceneQuery query = scene.OpenQuery();
|
||||||
|
var input = new RenderScenePViewBuildInput(
|
||||||
|
query,
|
||||||
|
new RenderSceneDigest(query.Generation, query.Counts, default),
|
||||||
|
portal,
|
||||||
|
clip,
|
||||||
|
viewcone,
|
||||||
|
LookInCellTurns: [lookInCell],
|
||||||
|
WalkLookInViews: new RejectAllLookInViews(lookInCell),
|
||||||
|
DrawableCells: [Cell, lookInCell],
|
||||||
|
Cells: EmptyCellSource.Instance,
|
||||||
|
AnimatedEntityIds: [],
|
||||||
|
RootIsOutdoor: false,
|
||||||
|
RootFloodCells: new HashSet<uint> { Cell });
|
||||||
|
var exchange = new RenderFrameExchange();
|
||||||
|
var builder = new RenderScenePViewFrameBuilder();
|
||||||
|
|
||||||
|
builder.Build(exchange, frameSequence: 1, in input);
|
||||||
|
RenderFrameView frame = exchange.BorrowLatest(Generation, 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RenderFrameCandidateRange range = Assert.Single(
|
||||||
|
frame.RouteRanges.ToArray(),
|
||||||
|
candidate => candidate.Route == RenderFrameCandidateRoute.LookInObject);
|
||||||
|
Assert.Equal(
|
||||||
|
[lookInDynamic.Id],
|
||||||
|
frame.RouteCandidates
|
||||||
|
.Slice(range.Offset, range.Count)
|
||||||
|
.ToArray()
|
||||||
|
.Select(static record => record.Id));
|
||||||
|
Assert.NotNull(frame.WalkLookInViews);
|
||||||
|
Assert.False(frame.WalkLookInViews!.SphereVisibleInLookInTurn(
|
||||||
|
routeIndex: 0,
|
||||||
|
center: Vector3.Zero,
|
||||||
|
radius: 1f));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
exchange.Release(in frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,18 @@ public sealed class WalkFrameDriverTests
|
||||||
sorted.Sort();
|
sorted.Sort();
|
||||||
log.Add($"PARTICLES:{string.Join(",", sorted.ConvertAll(o => o.ToString("x")))}");
|
log.Add($"PARTICLES:{string.Join(",", sorted.ConvertAll(o => o.ToString("x")))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DrawLookInDynamics(
|
||||||
|
uint cellId,
|
||||||
|
int routeIndex,
|
||||||
|
IReadOnlySet<uint> staticParticleOwnerIds)
|
||||||
|
{
|
||||||
|
var sorted = new List<uint>(staticParticleOwnerIds);
|
||||||
|
sorted.Sort();
|
||||||
|
log.Add(
|
||||||
|
$"LOOKIN:{cellId:x8}:{routeIndex}:"
|
||||||
|
+ string.Join(",", sorted.ConvertAll(o => o.ToString("x"))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RecordingTrace(List<string> log) : IWalkFrameDriverTrace
|
private sealed class RecordingTrace(List<string> log) : IWalkFrameDriverTrace
|
||||||
|
|
@ -417,10 +429,28 @@ public sealed class WalkFrameDriverTests
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
"ALPHA:12.50", "PUNCH:4@v0", "SHELL:00000104",
|
"ALPHA:12.50", "PUNCH:4@v0", "SHELL:00000104",
|
||||||
"FLUSH:1:LookInStatic", "FLUSH:1:BuildingShell", "PARTICLES:c9",
|
"FLUSH:1:LookInStatic", "LOOKIN:00000104:0:ca",
|
||||||
|
"FLUSH:1:BuildingShell", "PARTICLES:c9",
|
||||||
},
|
},
|
||||||
log);
|
log);
|
||||||
|
|
||||||
|
Assert.Equal([0x104u], driver.LookInCellTurns);
|
||||||
|
Assert.Equal([0x104u], driver.LookInCells);
|
||||||
|
WalkPortalView capturedView = ctx.Cells[0x104].PortalViews[0];
|
||||||
|
WalkViewPoly capturedPoly = Assert.Single(capturedView.View.Polys);
|
||||||
|
Vector2 capturedCenter = Vector2.Zero;
|
||||||
|
for (int edge = 0; edge < capturedPoly.VertexCount; edge++)
|
||||||
|
{
|
||||||
|
capturedCenter += capturedView.View.Vertices[
|
||||||
|
capturedPoly.VertexIndex + edge].Point;
|
||||||
|
}
|
||||||
|
capturedCenter /= capturedPoly.VertexCount;
|
||||||
|
Vector3 insideCone = ctx.Rays.RayThrough(capturedCenter.X, capturedCenter.Y);
|
||||||
|
Assert.True(driver.SphereVisibleInLookInTurn(
|
||||||
|
0, in insideCone, 0.1f));
|
||||||
|
Assert.False(driver.SphereVisibleInLookInTurn(
|
||||||
|
0, new Vector3(10_000f, 0f, 10f), 0.1f));
|
||||||
|
|
||||||
// The punch polygon reached the leaf renderer in WORLD space: the
|
// The punch polygon reached the leaf renderer in WORLD space: the
|
||||||
// building-local Quad(-2f) vertex (-0.5,-0.5,-2) translates by
|
// building-local Quad(-2f) vertex (-0.5,-0.5,-2) translates by
|
||||||
// (10,0,0) under the caller-supplied building world transform.
|
// (10,0,0) under the caller-supplied building world transform.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
|
using AcDream.App.Rendering.Wb;
|
||||||
|
using DatReaderWriter.Types;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.Rendering.Wb;
|
||||||
|
|
||||||
|
public sealed class WbDrawDispatcherLookInConeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DrawingSphereAdmissionUsesTheIndividualPartTransformAndLargestScale()
|
||||||
|
{
|
||||||
|
var cone = new RecordingLookInViews();
|
||||||
|
var sphere = new Sphere
|
||||||
|
{
|
||||||
|
Origin = new Vector3(1f, 2f, 3f),
|
||||||
|
Radius = 0.5f,
|
||||||
|
};
|
||||||
|
Matrix4x4 transform =
|
||||||
|
Matrix4x4.CreateScale(2f, 3f, 4f)
|
||||||
|
* Matrix4x4.CreateTranslation(10f, 20f, 30f);
|
||||||
|
|
||||||
|
bool visible = WbDrawDispatcher.LookInDrawingSphereVisible(
|
||||||
|
cone,
|
||||||
|
routeIndex: 7,
|
||||||
|
sphere,
|
||||||
|
transform,
|
||||||
|
out Vector3 center,
|
||||||
|
out float radius);
|
||||||
|
|
||||||
|
Assert.True(visible);
|
||||||
|
Assert.Equal(7, cone.RouteIndex);
|
||||||
|
Assert.Equal(new Vector3(12f, 26f, 42f), center);
|
||||||
|
Assert.Equal(center, cone.Center);
|
||||||
|
Assert.Equal(2f, radius);
|
||||||
|
Assert.Equal(radius, cone.Radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RecordingLookInViews : IWalkLookInViewSource
|
||||||
|
{
|
||||||
|
public IReadOnlyList<uint> LookInCellTurns { get; } = [0xF4180112u];
|
||||||
|
|
||||||
|
public int RouteIndex { get; private set; }
|
||||||
|
|
||||||
|
public Vector3 Center { get; private set; }
|
||||||
|
|
||||||
|
public float Radius { get; private set; }
|
||||||
|
|
||||||
|
public bool SphereVisibleInLookInTurn(
|
||||||
|
int routeIndex,
|
||||||
|
in Vector3 center,
|
||||||
|
float radius)
|
||||||
|
{
|
||||||
|
RouteIndex = routeIndex;
|
||||||
|
Center = center;
|
||||||
|
Radius = radius;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue