fix(render): outdoor look-in draws interior cells only through real doorway apertures (no see-through walls)
Cutover-flip follow-up: see-through buildings from outside. When the outdoor-node flood reaches a building, each interior cell is meant to draw clipped to its doorway aperture. But DrawEnvCellShells falls back to the no-clip slot 0 (full-screen) when a cell's aperture degenerates — screen-covering when you get close, or edge-on. Indoors that fallback is load-bearing (it seals the room the camera stands in; near walls hide the over-draw). From OUTSIDE it paints the building interior across the whole screen, depth-tested, so it shows wherever the solid exterior does not cover — the see-through walls, appearing 'past a threshold' exactly where the aperture degenerates. Fix: for the outdoor-node root only, skip a flooded interior cell with no real plane-clip slot (HasRealClipSlot). From outside, 'no real aperture' means 'do not paint this interior', not 'paint it everywhere'. Interior roots keep the seal-everything slot-0 fallback unchanged. Applied to DrawEnvCellShells AND DrawCellObjectLists so a skipped cell shows neither walls nor furniture; the dead DrawPortal exterior look-in gets the same gate. Root cause traced over the WB EnvCell render path: CellMesh.cs is physics-only; ObjectMeshManager.PrepareCellStructMeshData builds double-sided walls, so this was never a culling bug. App 216/0, build green. Visual gate pending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88caa0dc8b
commit
0030dacaaa
1 changed files with 47 additions and 9 deletions
|
|
@ -76,11 +76,18 @@ public sealed class RetailPViewRenderer
|
||||||
|
|
||||||
ctx.EmitDiagnostics?.Invoke(result);
|
ctx.EmitDiagnostics?.Invoke(result);
|
||||||
|
|
||||||
|
// Render unification: from the OUTDOOR-node root a flooded interior cell must draw ONLY within
|
||||||
|
// its real doorway aperture (a plane-clip slot). A cell whose aperture degenerates to the
|
||||||
|
// no-clip slot 0 (screen-covering / edge-on) would otherwise draw FULL-SCREEN and paint the
|
||||||
|
// building interior across the whole screen — the see-through-walls bug. Indoor roots keep the
|
||||||
|
// seal-everything slot-0 fallback (load-bearing: it seals the room the camera stands in).
|
||||||
|
bool outdoorRoot = ctx.RootCell.IsOutdoorNode;
|
||||||
|
|
||||||
DrawLandscapeThroughOutsideView(ctx, clipAssembly, partition);
|
DrawLandscapeThroughOutsideView(ctx, clipAssembly, partition);
|
||||||
UseIndoorMembershipOnlyRouting();
|
UseIndoorMembershipOnlyRouting();
|
||||||
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
|
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
|
||||||
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells);
|
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells, outdoorRoot);
|
||||||
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition);
|
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition, outdoorRoot);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +135,11 @@ public sealed class RetailPViewRenderer
|
||||||
ctx.EmitDiagnostics?.Invoke(result);
|
ctx.EmitDiagnostics?.Invoke(result);
|
||||||
|
|
||||||
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
|
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
|
||||||
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells);
|
// DrawPortal is the exterior look-in (camera outside, peering in) → same outdoor gate: an
|
||||||
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition);
|
// interior cell with no real doorway aperture must not draw full-screen. (This path is dead
|
||||||
|
// after the cutover flip; kept compiling until the Step-D deletion.)
|
||||||
|
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells, outdoorRoot: true);
|
||||||
|
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition, outdoorRoot: true);
|
||||||
RestoreNoClip(ctx.SetTerrainClipUbo);
|
RestoreNoClip(ctx.SetTerrainClipUbo);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -181,15 +191,19 @@ public sealed class RetailPViewRenderer
|
||||||
IRetailPViewCellDrawCallbacks ctx,
|
IRetailPViewCellDrawCallbacks ctx,
|
||||||
PortalVisibilityFrame pvFrame,
|
PortalVisibilityFrame pvFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells) // param kept this task; removed in Task 4
|
HashSet<uint> drawableCells, // param kept this task; removed in Task 4
|
||||||
|
bool outdoorRoot)
|
||||||
{
|
{
|
||||||
// Retail DrawCells Loop 2: every visible cell's shell, reverse cell_draw_list
|
// Retail DrawCells Loop 2: every visible cell's shell, reverse cell_draw_list
|
||||||
// (far→near), per portal_view slice. No drawableCells filter — a cell without a
|
// (far→near), per portal_view slice. Indoors a cell without a clip-slot falls through
|
||||||
// clip-slot falls through GetCellSlicesOrNoClip to NoClipSlice and draws unclipped
|
// GetCellSlicesOrNoClip to NoClipSlice and draws unclipped (sealed — load-bearing R1 seal).
|
||||||
// (sealed; per-slice trim returns in Task 4).
|
// Outdoors (outdoor-node root) that same unclipped draw IS the see-through bug, so a cell with
|
||||||
|
// no real plane-clip aperture is skipped entirely (see DrawInside).
|
||||||
foreach (var entry in IndoorDrawPlan.ShellPass(pvFrame))
|
foreach (var entry in IndoorDrawPlan.ShellPass(pvFrame))
|
||||||
{
|
{
|
||||||
uint cellId = entry.CellId;
|
uint cellId = entry.CellId;
|
||||||
|
if (outdoorRoot && !HasRealClipSlot(clipAssembly, cellId))
|
||||||
|
continue;
|
||||||
_oneCell.Clear();
|
_oneCell.Clear();
|
||||||
_oneCell.Add(cellId);
|
_oneCell.Add(cellId);
|
||||||
|
|
||||||
|
|
@ -207,7 +221,8 @@ public sealed class RetailPViewRenderer
|
||||||
PortalVisibilityFrame pvFrame,
|
PortalVisibilityFrame pvFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
InteriorEntityPartition.Result partition)
|
InteriorEntityPartition.Result partition,
|
||||||
|
bool outdoorRoot)
|
||||||
{
|
{
|
||||||
for (int i = pvFrame.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
for (int i = pvFrame.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
|
@ -215,6 +230,11 @@ public sealed class RetailPViewRenderer
|
||||||
if (!drawableCells.Contains(cellId))
|
if (!drawableCells.Contains(cellId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Outdoor-node root: skip a cell with no real doorway aperture (would draw full-screen) —
|
||||||
|
// matches DrawEnvCellShells so a skipped interior cell shows neither walls nor furniture.
|
||||||
|
if (outdoorRoot && !HasRealClipSlot(clipAssembly, cellId))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!partition.ByCell.TryGetValue(cellId, out var bucket) || bucket.Count == 0)
|
if (!partition.ByCell.TryGetValue(cellId, out var bucket) || bucket.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -240,6 +260,24 @@ public sealed class RetailPViewRenderer
|
||||||
return new[] { NoClipSlice };
|
return new[] { NoClipSlice };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True iff <paramref name="cellId"/> has at least one view slice backed by a real plane-clip
|
||||||
|
/// slot (slot != 0) in this frame's assembly — i.e. a genuine doorway aperture. A cell with no
|
||||||
|
/// entry, or only the no-clip slot 0 (screen-covering / degenerate aperture → scissor fallback),
|
||||||
|
/// returns false. Used by the outdoor-node root to refuse drawing an interior cell that would
|
||||||
|
/// otherwise paint full-screen (the see-through-walls bug). Slot 0 is reserved as no-clip
|
||||||
|
/// (<see cref="NoClipSlice"/>), so "real aperture" is precisely "some slot != 0".
|
||||||
|
/// </summary>
|
||||||
|
private static bool HasRealClipSlot(ClipFrameAssembly clipAssembly, uint cellId)
|
||||||
|
{
|
||||||
|
if (!clipAssembly.CellIdToViewSlices.TryGetValue(cellId, out var slices))
|
||||||
|
return false;
|
||||||
|
foreach (var slice in slices)
|
||||||
|
if (slice.Slot != 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void UseIndoorMembershipOnlyRouting()
|
private void UseIndoorMembershipOnlyRouting()
|
||||||
{
|
{
|
||||||
// Retail's PView portal views decide which cells/objects are eligible,
|
// Retail's PView portal views decide which cells/objects are eligible,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue