refactor(render): remove the production portal frame carrier
This commit is contained in:
parent
69e69408e8
commit
cb22691beb
10 changed files with 128 additions and 443 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
// ClipFrameAssembler.cs
|
// ClipFrameAssembler.cs
|
||||||
//
|
//
|
||||||
// Retail PView assembly policy. PortalVisibilityBuilder produces a retail-like
|
// Retail view assembly policy. The production frame walk supplies its
|
||||||
// view graph: one portal_view list per visible cell plus an outside_view list.
|
// outside_view directly; the legacy PortalVisibilityBuilder overload remains
|
||||||
// This assembler packs each visible polygon as an individual GPU clip slot so
|
// only for isolated research/replay tests. Each visible polygon is packed as
|
||||||
// the renderer can draw the exact PView order:
|
// an individual GPU clip slot.
|
||||||
//
|
//
|
||||||
// outside_view landscape slices
|
// outside_view landscape slices
|
||||||
// reverse cell_draw_list exit masks
|
// reverse cell_draw_list exit masks
|
||||||
|
|
@ -40,15 +40,7 @@ public enum TerrainClipMode
|
||||||
public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes);
|
public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Identifies one cell inside one nested building look-in. The same EnvCell can
|
/// Populated clip buffers plus routing data consumed by the render walk.
|
||||||
/// be reached by more than one building PView, so a cell id alone is not a
|
|
||||||
/// sufficient routing key.
|
|
||||||
/// </summary>
|
|
||||||
public readonly record struct LookInClipCell(int FrameIndex, uint CellId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Result of <see cref="ClipFrameAssembler.Assemble"/>: populated clip buffers
|
|
||||||
/// plus routing data consumed by the render orchestration.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ClipFrameAssembly
|
public sealed class ClipFrameAssembly
|
||||||
{
|
{
|
||||||
|
|
@ -64,12 +56,6 @@ public sealed class ClipFrameAssembly
|
||||||
/// <summary>Full retail portal_view slices per visible cell.</summary>
|
/// <summary>Full retail portal_view slices per visible cell.</summary>
|
||||||
public Dictionary<uint, ClipViewSlice[]> CellIdToViewSlices { get; } = new();
|
public Dictionary<uint, ClipViewSlice[]> CellIdToViewSlices { get; } = new();
|
||||||
|
|
||||||
/// <summary>First drawable slice slot per nested look-in cell.</summary>
|
|
||||||
public Dictionary<LookInClipCell, int> LookInCellToSlot { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>All retail portal_view slices per nested look-in cell.</summary>
|
|
||||||
public Dictionary<LookInClipCell, ClipViewSlice[]> LookInCellToViewSlices { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>Full retail outside_view slices.</summary>
|
/// <summary>Full retail outside_view slices.</summary>
|
||||||
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty<ClipViewSlice>();
|
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty<ClipViewSlice>();
|
||||||
|
|
||||||
|
|
@ -106,8 +92,6 @@ public sealed class ClipFrameAssembly
|
||||||
Frame = frame;
|
Frame = frame;
|
||||||
foreach (ClipViewSlice[] slices in CellIdToViewSlices.Values)
|
foreach (ClipViewSlice[] slices in CellIdToViewSlices.Values)
|
||||||
ReturnSlices(slices);
|
ReturnSlices(slices);
|
||||||
foreach (ClipViewSlice[] slices in LookInCellToViewSlices.Values)
|
|
||||||
ReturnSlices(slices);
|
|
||||||
foreach (int[] slots in CellIdToViewSlots.Values)
|
foreach (int[] slots in CellIdToViewSlots.Values)
|
||||||
ReturnSlots(slots);
|
ReturnSlots(slots);
|
||||||
if (OutsideViewSlices.Length != 0)
|
if (OutsideViewSlices.Length != 0)
|
||||||
|
|
@ -116,8 +100,6 @@ public sealed class ClipFrameAssembly
|
||||||
CellIdToSlot.Clear();
|
CellIdToSlot.Clear();
|
||||||
CellIdToViewSlots.Clear();
|
CellIdToViewSlots.Clear();
|
||||||
CellIdToViewSlices.Clear();
|
CellIdToViewSlices.Clear();
|
||||||
LookInCellToSlot.Clear();
|
|
||||||
LookInCellToViewSlices.Clear();
|
|
||||||
PerCellPlaneCounts.Clear();
|
PerCellPlaneCounts.Clear();
|
||||||
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
||||||
SliceScratch.Clear();
|
SliceScratch.Clear();
|
||||||
|
|
@ -272,6 +254,53 @@ public sealed class ClipFrameAssembly
|
||||||
|
|
||||||
public static class ClipFrameAssembler
|
public static class ClipFrameAssembler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts one production walk assembly without constructing a parallel
|
||||||
|
/// PortalVisibilityFrame. Outdoor roots begin with retail's full-screen
|
||||||
|
/// default view; interior roots begin empty and are populated from
|
||||||
|
/// <see cref="Walk.RetailFrameWalk.InteriorOutsideView"/> after Collect.
|
||||||
|
/// </summary>
|
||||||
|
public static ClipFrameAssembly BeginWalkFrame(
|
||||||
|
ClipFrame frame,
|
||||||
|
bool outdoorRoot,
|
||||||
|
ClipFrameAssembly? reuseAssembly = null)
|
||||||
|
{
|
||||||
|
System.ArgumentNullException.ThrowIfNull(frame);
|
||||||
|
frame.Reset();
|
||||||
|
ClipFrameAssembly assembly = reuseAssembly ?? new ClipFrameAssembly();
|
||||||
|
assembly.Reset(frame);
|
||||||
|
|
||||||
|
if (outdoorRoot)
|
||||||
|
{
|
||||||
|
List<ClipViewSlice> slices = assembly.SliceScratch;
|
||||||
|
slices.Clear();
|
||||||
|
var fullScreen = new Vector4(-1f, -1f, 1f, 1f);
|
||||||
|
slices.Add(new ClipViewSlice(0, fullScreen, System.Array.Empty<Vector4>()));
|
||||||
|
assembly.SetOutsideViewSlices(assembly.CopySlices(slices));
|
||||||
|
assembly.OutdoorSlot = 0;
|
||||||
|
assembly.OutdoorVisible = true;
|
||||||
|
assembly.TerrainMode = TerrainClipMode.Scissor;
|
||||||
|
assembly.TerrainScissorNdcAabb = fullScreen;
|
||||||
|
assembly.HasOutsideView = true;
|
||||||
|
assembly.OutsideViewNdcAabb = fullScreen;
|
||||||
|
assembly.OutsidePlaneCount = 0;
|
||||||
|
assembly.ScissorFallbacks = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assembly.OutdoorSlot = 0;
|
||||||
|
assembly.OutdoorVisible = false;
|
||||||
|
assembly.TerrainMode = TerrainClipMode.Skip;
|
||||||
|
assembly.TerrainScissorNdcAabb = Vector4.Zero;
|
||||||
|
assembly.HasOutsideView = false;
|
||||||
|
assembly.OutsideViewNdcAabb = Vector4.Zero;
|
||||||
|
assembly.OutsidePlaneCount = 0;
|
||||||
|
assembly.ScissorFallbacks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return assembly;
|
||||||
|
}
|
||||||
|
|
||||||
public static ClipFrameAssembly Assemble(
|
public static ClipFrameAssembly Assemble(
|
||||||
ClipFrame frame,
|
ClipFrame frame,
|
||||||
PortalVisibilityFrame pvFrame,
|
PortalVisibilityFrame pvFrame,
|
||||||
|
|
@ -529,70 +558,6 @@ public static class ClipFrameAssembler
|
||||||
assembly.ScissorFallbacks = scissorFallbacks;
|
assembly.ScissorFallbacks = scissorFallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Appends the cell views used by nested <c>DrawBuilding -> DrawPortal</c>
|
|
||||||
/// PViews to the already assembled frame. Retail installs each nested
|
|
||||||
/// cell's own <c>portal_view</c> before drawing its shell and object list;
|
|
||||||
/// these slots must therefore be published with the main frame before the
|
|
||||||
/// first draw is recorded.
|
|
||||||
/// </summary>
|
|
||||||
public static void AppendLookInFrames(
|
|
||||||
ClipFrame frame,
|
|
||||||
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
|
|
||||||
ClipFrameAssembly assembly)
|
|
||||||
{
|
|
||||||
System.ArgumentNullException.ThrowIfNull(frame);
|
|
||||||
System.ArgumentNullException.ThrowIfNull(lookInFrames);
|
|
||||||
System.ArgumentNullException.ThrowIfNull(assembly);
|
|
||||||
if (!ReferenceEquals(frame, assembly.Frame))
|
|
||||||
throw new System.ArgumentException(
|
|
||||||
"The look-in slots must be appended to the assembly's clip frame.",
|
|
||||||
nameof(frame));
|
|
||||||
|
|
||||||
for (int frameIndex = 0; frameIndex < lookInFrames.Count; frameIndex++)
|
|
||||||
{
|
|
||||||
PortalVisibilityFrame lookIn = lookInFrames[frameIndex];
|
|
||||||
foreach (uint cellId in lookIn.OrderedVisibleCells)
|
|
||||||
{
|
|
||||||
if (!lookIn.CellViews.TryGetValue(cellId, out CellView? view))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
List<ClipViewSlice> slices = assembly.SliceScratch;
|
|
||||||
slices.Clear();
|
|
||||||
foreach (ViewPolygon poly in view.Polygons)
|
|
||||||
{
|
|
||||||
ClipPlaneSet cps = ClipPlaneSet.From(poly);
|
|
||||||
if (cps.IsNothingVisible)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int slot;
|
|
||||||
Vector4[] planes;
|
|
||||||
if (cps.Count > 0)
|
|
||||||
{
|
|
||||||
planes = cps.PlaneArray;
|
|
||||||
slot = frame.AppendSlot(planes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
planes = System.Array.Empty<Vector4>();
|
|
||||||
slot = 0;
|
|
||||||
assembly.ScissorFallbacks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slices.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ClipViewSlice[] packed = assembly.CopySlices(slices);
|
|
||||||
var key = new LookInClipCell(frameIndex, cellId);
|
|
||||||
assembly.LookInCellToViewSlices.Add(key, packed);
|
|
||||||
assembly.LookInCellToSlot.Add(key, packed[0].Slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector4 AabbOf(ViewPolygon poly) =>
|
private static Vector4 AabbOf(ViewPolygon poly) =>
|
||||||
new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY);
|
new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,10 +205,10 @@ internal sealed partial class RetailPViewPassExecutor :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClipFrameAssembly AssembleClipFrame(
|
public ClipFrameAssembly BeginWalkClipFrame(
|
||||||
PortalVisibilityFrame portalFrame,
|
bool outdoorRoot,
|
||||||
ClipFrameAssembly reuseAssembly) =>
|
ClipFrameAssembly reuseAssembly) =>
|
||||||
ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
|
ClipFrameAssembler.BeginWalkFrame(_clipFrame, outdoorRoot, reuseAssembly);
|
||||||
|
|
||||||
public void PrepareClipFrame(int terrainUploadCount) =>
|
public void PrepareClipFrame(int terrainUploadCount) =>
|
||||||
_surface.PrepareClipFrame(terrainUploadCount);
|
_surface.PrepareClipFrame(terrainUploadCount);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace AcDream.App.Rendering;
|
||||||
internal sealed class RetailPViewRenderer
|
internal sealed class RetailPViewRenderer
|
||||||
{
|
{
|
||||||
private readonly RenderSceneShadowRuntime _renderSceneShadow;
|
private readonly RenderSceneShadowRuntime _renderSceneShadow;
|
||||||
private readonly PortalVisibilityFrame _mainPortalFrameScratch = new();
|
|
||||||
private readonly ClipFrameAssembly _clipAssemblyScratch = new();
|
private readonly ClipFrameAssembly _clipAssemblyScratch = new();
|
||||||
private readonly RetailPViewFrameResult _frameResultScratch = new();
|
private readonly RetailPViewFrameResult _frameResultScratch = new();
|
||||||
|
|
||||||
|
|
@ -98,19 +97,8 @@ internal sealed class RetailPViewRenderer
|
||||||
RetailPViewPassExecutor walkExecutor = passes as RetailPViewPassExecutor
|
RetailPViewPassExecutor walkExecutor = passes as RetailPViewPassExecutor
|
||||||
?? throw new InvalidOperationException(
|
?? throw new InvalidOperationException(
|
||||||
"The retail frame walk requires RetailPViewPassExecutor.");
|
"The retail frame walk requires RetailPViewPassExecutor.");
|
||||||
// Compatibility carrier only. Production visibility is populated
|
ClipFrameAssembly clipAssembly = passes.BeginWalkClipFrame(
|
||||||
// exclusively by RetailFrameWalk below; this frame supplies the clip
|
ctx.RootCell.IsOutdoorNode,
|
||||||
// assembler's outdoor full-screen seed until that carrier is removed.
|
|
||||||
PortalVisibilityFrame pvFrame = _mainPortalFrameScratch;
|
|
||||||
pvFrame.ResetForBuild();
|
|
||||||
if (ctx.RootCell.IsOutdoorNode)
|
|
||||||
{
|
|
||||||
pvFrame.OutsideView.SetFullScreen();
|
|
||||||
pvFrame.OrderedVisibleCells.Add(ctx.RootCell.CellId);
|
|
||||||
}
|
|
||||||
|
|
||||||
var clipAssembly = passes.AssembleClipFrame(
|
|
||||||
pvFrame,
|
|
||||||
_clipAssemblyScratch);
|
_clipAssemblyScratch);
|
||||||
// FW4 slice 1: PrepareClipFrame (the one clip-region publication)
|
// FW4 slice 1: PrepareClipFrame (the one clip-region publication)
|
||||||
// moved BELOW the walk block — an interior-rooted walk frame
|
// moved BELOW the walk block — an interior-rooted walk frame
|
||||||
|
|
@ -123,8 +111,7 @@ internal sealed class RetailPViewRenderer
|
||||||
// (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells).
|
// (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells).
|
||||||
// Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained).
|
// Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained).
|
||||||
_drawableCellsScratch.Clear();
|
_drawableCellsScratch.Clear();
|
||||||
_drawableCellsScratch.UnionWith(pvFrame.OrderedVisibleCells);
|
HashSet<uint> drawableCells = _drawableCellsScratch;
|
||||||
var drawableCells = _drawableCellsScratch;
|
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
|
|
||||||
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
|
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
|
||||||
|
|
@ -336,7 +323,6 @@ internal sealed class RetailPViewRenderer
|
||||||
RenderFrameDiagnosticCounts counts = WalkDiagnosticCounts(retainedCounts);
|
RenderFrameDiagnosticCounts counts = WalkDiagnosticCounts(retainedCounts);
|
||||||
RenderProjectionCounts sourceCounts = retainedCounts;
|
RenderProjectionCounts sourceCounts = retainedCounts;
|
||||||
RetailPViewFrameResult result = _frameResultScratch.Reset(
|
RetailPViewFrameResult result = _frameResultScratch.Reset(
|
||||||
pvFrame,
|
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
drawableCells,
|
drawableCells,
|
||||||
prepareCells,
|
prepareCells,
|
||||||
|
|
@ -695,7 +681,6 @@ public sealed class RetailPViewFrameInput
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RetailPViewFrameResult
|
public sealed class RetailPViewFrameResult
|
||||||
{
|
{
|
||||||
public PortalVisibilityFrame PortalFrame { get; private set; } = null!;
|
|
||||||
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
|
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
|
||||||
public HashSet<uint> DrawableCells { get; private set; } = null!;
|
public HashSet<uint> DrawableCells { get; private set; } = null!;
|
||||||
|
|
||||||
|
|
@ -712,7 +697,6 @@ public sealed class RetailPViewFrameResult
|
||||||
{ get; private set; }
|
{ get; private set; }
|
||||||
|
|
||||||
internal RetailPViewFrameResult Reset(
|
internal RetailPViewFrameResult Reset(
|
||||||
PortalVisibilityFrame portalFrame,
|
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
HashSet<uint> visibleCells,
|
HashSet<uint> visibleCells,
|
||||||
|
|
@ -720,7 +704,6 @@ public sealed class RetailPViewFrameResult
|
||||||
RenderProjectionCounts sourceCounts,
|
RenderProjectionCounts sourceCounts,
|
||||||
InteriorEntityPartition.Result? diagnosticPartition)
|
InteriorEntityPartition.Result? diagnosticPartition)
|
||||||
{
|
{
|
||||||
PortalFrame = portalFrame;
|
|
||||||
ClipAssembly = clipAssembly;
|
ClipAssembly = clipAssembly;
|
||||||
DrawableCells = drawableCells;
|
DrawableCells = drawableCells;
|
||||||
VisibleCells = visibleCells;
|
VisibleCells = visibleCells;
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,8 @@ internal sealed class WorldRenderDiagnostics
|
||||||
|
|
||||||
public void EmitPViewInput(
|
public void EmitPViewInput(
|
||||||
bool enabled,
|
bool enabled,
|
||||||
PortalVisibilityFrame portalFrame,
|
IReadOnlySet<uint> visibleCells,
|
||||||
|
int outsideViewCount,
|
||||||
Matrix4x4 viewProjection,
|
Matrix4x4 viewProjection,
|
||||||
bool outdoorRoot,
|
bool outdoorRoot,
|
||||||
Vector3 eye,
|
Vector3 eye,
|
||||||
|
|
@ -294,7 +295,7 @@ internal sealed class WorldRenderDiagnostics
|
||||||
char root = outdoorRoot ? 'Y' : 'n';
|
char root = outdoorRoot ? 'Y' : 'n';
|
||||||
Matrix4x4 vp = viewProjection;
|
Matrix4x4 vp = viewProjection;
|
||||||
_log.WriteLine(FormattableString.Invariant(
|
_log.WriteLine(FormattableString.Invariant(
|
||||||
$"[pv-input] outRoot={root} flood={portalFrame.OrderedVisibleCells.Count} eye=({eye.X:F6},{eye.Y:F6},{eye.Z:F6}) player=({player.X:F6},{player.Y:F6},{player.Z:F6}) rawPlayer=({rawPlayer.X:F6},{rawPlayer.Y:F6},{rawPlayer.Z:F6}) yaw={yaw:F8} {terrain} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]"));
|
$"[pv-input] outRoot={root} visible={visibleCells.Count} outsideViews={outsideViewCount} eye=({eye.X:F6},{eye.Y:F6},{eye.Z:F6}) player=({player.X:F6},{player.Y:F6},{player.Z:F6}) rawPlayer=({rawPlayer.X:F6},{rawPlayer.Y:F6},{rawPlayer.Z:F6}) yaw={yaw:F8} {terrain} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EmitRetailPViewDiagnostics(
|
public void EmitRetailPViewDiagnostics(
|
||||||
|
|
@ -312,8 +313,8 @@ internal sealed class WorldRenderDiagnostics
|
||||||
{
|
{
|
||||||
AcDream.Core.Rendering.RenderingDiagnostics.EmitVis(
|
AcDream.Core.Rendering.RenderingDiagnostics.EmitVis(
|
||||||
clipRoot.CellId,
|
clipRoot.CellId,
|
||||||
result.PortalFrame.OrderedVisibleCells,
|
result.VisibleCells.OrderBy(static id => id).ToArray(),
|
||||||
result.PortalFrame.OutsideView.Polygons.Count,
|
result.ClipAssembly.OutsideViewSlices.Length,
|
||||||
result.ClipAssembly.OutsidePlaneCount,
|
result.ClipAssembly.OutsidePlaneCount,
|
||||||
result.ClipAssembly.PerCellPlaneCounts,
|
result.ClipAssembly.PerCellPlaneCounts,
|
||||||
result.ClipAssembly.ScissorFallbacks);
|
result.ClipAssembly.ScissorFallbacks);
|
||||||
|
|
@ -353,18 +354,12 @@ internal sealed class WorldRenderDiagnostics
|
||||||
bool skyDrawn,
|
bool skyDrawn,
|
||||||
bool depthClear,
|
bool depthClear,
|
||||||
bool outdoorSceneryDrawn,
|
bool outdoorSceneryDrawn,
|
||||||
bool outdoorPortalDrawn,
|
|
||||||
int outdoorRootObjectCount,
|
|
||||||
int liveDynamicDrawnCount,
|
int liveDynamicDrawnCount,
|
||||||
string sceneParticles,
|
string sceneParticles,
|
||||||
PortalVisibilityFrame? portalFrame,
|
IReadOnlySet<uint>? visibleCells,
|
||||||
ClipFrameAssembly? clipAssembly,
|
ClipFrameAssembly? clipAssembly,
|
||||||
IReadOnlySet<uint>? drawableCells,
|
IReadOnlySet<uint>? drawableCells,
|
||||||
InteriorEntityPartition.Result? partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
PortalVisibilityFrame? exteriorPortalFrame,
|
|
||||||
ClipFrameAssembly? exteriorClipAssembly,
|
|
||||||
IReadOnlySet<uint>? exteriorDrawableCells,
|
|
||||||
InteriorEntityPartition.Result? exteriorPartition,
|
|
||||||
Vector3 cameraPosition,
|
Vector3 cameraPosition,
|
||||||
Vector3 playerPosition)
|
Vector3 playerPosition)
|
||||||
{
|
{
|
||||||
|
|
@ -400,7 +395,7 @@ internal sealed class WorldRenderDiagnostics
|
||||||
if (clipAssembly is not null)
|
if (clipAssembly is not null)
|
||||||
{
|
{
|
||||||
text.Append(" outSlices=").Append(clipAssembly.OutsideViewSlices.Length);
|
text.Append(" outSlices=").Append(clipAssembly.OutsideViewSlices.Length);
|
||||||
text.Append(" outPolys=").Append(portalFrame?.OutsideView.Polygons.Count ?? 0);
|
text.Append(" outPolys=").Append(clipAssembly.OutsideViewSlices.Length);
|
||||||
text.Append(" outMode=").Append(clipAssembly.TerrainMode);
|
text.Append(" outMode=").Append(clipAssembly.TerrainMode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -408,12 +403,11 @@ internal sealed class WorldRenderDiagnostics
|
||||||
text.Append(" outSlices=0 outPolys=0 outMode=none");
|
text.Append(" outSlices=0 outPolys=0 outMode=none");
|
||||||
}
|
}
|
||||||
|
|
||||||
text.Append(" ids=").Append(FormatIds(portalFrame?.OrderedVisibleCells, true));
|
text.Append(" ids=").Append(FormatIds(visibleCells, false));
|
||||||
text.Append(" draw=").Append(FormatIds(drawableCells, false));
|
text.Append(" draw=").Append(FormatIds(drawableCells, false));
|
||||||
text.Append(" miss=").Append(FormatMissingDrawableCells(portalFrame, drawableCells));
|
text.Append(" miss=").Append(FormatMissingDrawableCells(visibleCells, drawableCells));
|
||||||
text.Append(" obj=").Append(FormatPartitionCounts(partition));
|
text.Append(" obj=").Append(FormatPartitionCounts(partition));
|
||||||
text.Append(" outdoorDoor=").Append(outdoorSceneryDrawn ? 'Y' : 'n');
|
text.Append(" outdoorDoor=").Append(outdoorSceneryDrawn ? 'Y' : 'n');
|
||||||
text.Append(" outdoorRootObjs=").Append(outdoorRootObjectCount);
|
|
||||||
text.Append(" liveDynDraw=").Append(liveDynamicDrawnCount);
|
text.Append(" liveDynDraw=").Append(liveDynamicDrawnCount);
|
||||||
text.Append(" outRoot=").Append(clipRoot is { IsOutdoorNode: true } ? 'Y' : 'n');
|
text.Append(" outRoot=").Append(clipRoot is { IsOutdoorNode: true } ? 'Y' : 'n');
|
||||||
if (partition is not null)
|
if (partition is not null)
|
||||||
|
|
@ -431,21 +425,6 @@ internal sealed class WorldRenderDiagnostics
|
||||||
text.Append(" bshell=").Append(totalShells).Append('/').Append(shellsWithMeshes);
|
text.Append(" bshell=").Append(totalShells).Append('/').Append(shellsWithMeshes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outdoorPortalDrawn || exteriorPortalFrame is not null
|
|
||||||
|| exteriorClipAssembly is not null)
|
|
||||||
{
|
|
||||||
text.Append(" extPortal=").Append(outdoorPortalDrawn ? 'Y' : 'n');
|
|
||||||
text.Append(" extSlices=").Append(exteriorClipAssembly?.OutsideViewSlices.Length ?? 0);
|
|
||||||
text.Append(" extIds=").Append(FormatIds(
|
|
||||||
exteriorPortalFrame?.OrderedVisibleCells,
|
|
||||||
true));
|
|
||||||
text.Append(" extDraw=").Append(FormatIds(exteriorDrawableCells, false));
|
|
||||||
text.Append(" extMiss=").Append(FormatMissingDrawableCells(
|
|
||||||
exteriorPortalFrame,
|
|
||||||
exteriorDrawableCells));
|
|
||||||
text.Append(" extObj=").Append(FormatPartitionCounts(exteriorPartition));
|
|
||||||
}
|
|
||||||
|
|
||||||
string signature = text.ToString();
|
string signature = text.ToString();
|
||||||
if (signature == _lastRenderSignature)
|
if (signature == _lastRenderSignature)
|
||||||
{
|
{
|
||||||
|
|
@ -506,32 +485,22 @@ internal sealed class WorldRenderDiagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatMissingDrawableCells(
|
private static string FormatMissingDrawableCells(
|
||||||
PortalVisibilityFrame? portalFrame,
|
IReadOnlySet<uint>? visibleCells,
|
||||||
IReadOnlySet<uint>? drawableCells)
|
IReadOnlySet<uint>? drawableCells)
|
||||||
{
|
{
|
||||||
if (portalFrame is null || drawableCells is null)
|
if (visibleCells is null || drawableCells is null)
|
||||||
return "[]";
|
return "[]";
|
||||||
|
|
||||||
var text = new StringBuilder(96).Append('[');
|
var text = new StringBuilder(96).Append('[');
|
||||||
int written = 0;
|
int written = 0;
|
||||||
const int MaximumCells = 8;
|
const int MaximumCells = 8;
|
||||||
foreach (uint id in portalFrame.OrderedVisibleCells)
|
foreach (uint id in visibleCells.OrderBy(static id => id))
|
||||||
{
|
{
|
||||||
if (drawableCells.Contains(id))
|
if (drawableCells.Contains(id))
|
||||||
continue;
|
continue;
|
||||||
if (written > 0)
|
if (written > 0)
|
||||||
text.Append(',');
|
text.Append(',');
|
||||||
text.Append("0x").Append(id.ToString("X8"));
|
text.Append("0x").Append(id.ToString("X8"));
|
||||||
if (portalFrame.CellViews.TryGetValue(id, out var view))
|
|
||||||
{
|
|
||||||
text.Append(":p").Append(view.Polygons.Count);
|
|
||||||
if (view.IsEmpty)
|
|
||||||
text.Append(":empty");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text.Append(":noView");
|
|
||||||
}
|
|
||||||
if (++written >= MaximumCells)
|
if (++written >= MaximumCells)
|
||||||
{
|
{
|
||||||
text.Append(",...");
|
text.Append(",...");
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ internal interface IWorldSceneDiagnostics
|
||||||
CameraCellResolution CameraCellResolution { get; }
|
CameraCellResolution CameraCellResolution { get; }
|
||||||
|
|
||||||
void EmitPViewInput(
|
void EmitPViewInput(
|
||||||
PortalVisibilityFrame portalFrame,
|
IReadOnlySet<uint> visibleCells,
|
||||||
|
int outsideViewCount,
|
||||||
Matrix4x4 viewProjection,
|
Matrix4x4 viewProjection,
|
||||||
LoadedCell clipRoot,
|
LoadedCell clipRoot,
|
||||||
Vector3 cameraPosition,
|
Vector3 cameraPosition,
|
||||||
|
|
@ -90,7 +91,8 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
public CameraCellResolution CameraCellResolution => _pview.CameraCellResolution;
|
public CameraCellResolution CameraCellResolution => _pview.CameraCellResolution;
|
||||||
|
|
||||||
public void EmitPViewInput(
|
public void EmitPViewInput(
|
||||||
PortalVisibilityFrame portalFrame,
|
IReadOnlySet<uint> visibleCells,
|
||||||
|
int outsideViewCount,
|
||||||
Matrix4x4 viewProjection,
|
Matrix4x4 viewProjection,
|
||||||
LoadedCell clipRoot,
|
LoadedCell clipRoot,
|
||||||
Vector3 cameraPosition,
|
Vector3 cameraPosition,
|
||||||
|
|
@ -101,7 +103,8 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
|
|
||||||
_diagnostics.EmitPViewInput(
|
_diagnostics.EmitPViewInput(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
portalFrame,
|
visibleCells,
|
||||||
|
outsideViewCount,
|
||||||
viewProjection,
|
viewProjection,
|
||||||
clipRoot.IsOutdoorNode,
|
clipRoot.IsOutdoorNode,
|
||||||
cameraPosition,
|
cameraPosition,
|
||||||
|
|
@ -150,18 +153,12 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
skyDrawn,
|
skyDrawn,
|
||||||
depthClear,
|
depthClear,
|
||||||
outdoorSceneryDrawn,
|
outdoorSceneryDrawn,
|
||||||
outdoorPortalDrawn: false,
|
|
||||||
outdoorRootObjectCount: 0,
|
|
||||||
liveDynamicDrawnCount,
|
liveDynamicDrawnCount,
|
||||||
sceneParticles,
|
sceneParticles,
|
||||||
pviewResult?.PortalFrame,
|
pviewResult?.VisibleCells,
|
||||||
pviewResult?.ClipAssembly,
|
pviewResult?.ClipAssembly,
|
||||||
pviewResult?.DrawableCells,
|
pviewResult?.DrawableCells,
|
||||||
pviewResult?.DiagnosticPartition,
|
pviewResult?.DiagnosticPartition,
|
||||||
exteriorPortalFrame: null,
|
|
||||||
exteriorClipAssembly: null,
|
|
||||||
exteriorDrawableCells: null,
|
|
||||||
exteriorPartition: null,
|
|
||||||
cameraPosition,
|
cameraPosition,
|
||||||
playerPosition);
|
playerPosition);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,8 @@ internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
|
||||||
_particleVisibility.MarkVisibleCells(pviewResult.VisibleCells);
|
_particleVisibility.MarkVisibleCells(pviewResult.VisibleCells);
|
||||||
_frames.ObserveDrawableCells(pviewResult.VisibleCells);
|
_frames.ObserveDrawableCells(pviewResult.VisibleCells);
|
||||||
_diagnostics.EmitPViewInput(
|
_diagnostics.EmitPViewInput(
|
||||||
pviewResult.PortalFrame,
|
pviewResult.VisibleCells,
|
||||||
|
pviewResult.ClipAssembly.OutsideViewSlices.Length,
|
||||||
camera.ViewProjection,
|
camera.ViewProjection,
|
||||||
clipRoot,
|
clipRoot,
|
||||||
camera.Position,
|
camera.Position,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,48 @@ public class ClipFrameAssemblerTests
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BeginWalkFrame_OutdoorRoot_SeedsOneFullScreenDefaultView()
|
||||||
|
{
|
||||||
|
using ClipFrame frame = ClipFrame.NoClip();
|
||||||
|
|
||||||
|
ClipFrameAssembly assembly = ClipFrameAssembler.BeginWalkFrame(
|
||||||
|
frame,
|
||||||
|
outdoorRoot: true);
|
||||||
|
|
||||||
|
ClipViewSlice slice = Assert.Single(assembly.OutsideViewSlices);
|
||||||
|
Assert.Equal(0, slice.Slot);
|
||||||
|
Assert.Empty(slice.Planes);
|
||||||
|
Assert.Equal(new Vector4(-1f, -1f, 1f, 1f), slice.NdcAabb);
|
||||||
|
Assert.True(assembly.OutdoorVisible);
|
||||||
|
Assert.True(assembly.HasOutsideView);
|
||||||
|
Assert.Equal(TerrainClipMode.Scissor, assembly.TerrainMode);
|
||||||
|
Assert.Equal(1, frame.SlotCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BeginWalkFrame_InteriorRoot_ResetsReusedOutdoorAssemblyToEmpty()
|
||||||
|
{
|
||||||
|
using ClipFrame frame = ClipFrame.NoClip();
|
||||||
|
ClipFrameAssembly reuse = ClipFrameAssembler.BeginWalkFrame(
|
||||||
|
frame,
|
||||||
|
outdoorRoot: true);
|
||||||
|
|
||||||
|
ClipFrameAssembly assembly = ClipFrameAssembler.BeginWalkFrame(
|
||||||
|
frame,
|
||||||
|
outdoorRoot: false,
|
||||||
|
reuse);
|
||||||
|
|
||||||
|
Assert.Same(reuse, assembly);
|
||||||
|
Assert.Empty(assembly.OutsideViewSlices);
|
||||||
|
Assert.False(assembly.OutdoorVisible);
|
||||||
|
Assert.False(assembly.HasOutsideView);
|
||||||
|
Assert.Equal(TerrainClipMode.Skip, assembly.TerrainMode);
|
||||||
|
Assert.Equal(Vector4.Zero, assembly.OutsideViewNdcAabb);
|
||||||
|
Assert.Equal(0, assembly.ScissorFallbacks);
|
||||||
|
Assert.Equal(1, frame.SlotCount);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TwoVisibleCells_PlusOutsideView_ProducesCorrectSlotMapAndCounts()
|
public void TwoVisibleCells_PlusOutsideView_ProducesCorrectSlotMapAndCounts()
|
||||||
{
|
{
|
||||||
|
|
@ -128,39 +170,6 @@ public class ClipFrameAssemblerTests
|
||||||
Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode);
|
Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AppendLookInFrames_PacksEveryNestedCellViewWithoutReplacingMainRoutes()
|
|
||||||
{
|
|
||||||
const uint mainCell = 0xA9B40100;
|
|
||||||
const uint lookInCell = 0xA9B40106;
|
|
||||||
var main = new PortalVisibilityFrame();
|
|
||||||
main.CellViews[mainCell] = ViewOf(Square(0f, 0f, 0.8f));
|
|
||||||
main.OrderedVisibleCells.Add(mainCell);
|
|
||||||
main.OutsideView.Add(Square(0f, 0f, 0.7f));
|
|
||||||
|
|
||||||
var nested = new PortalVisibilityFrame();
|
|
||||||
nested.CellViews[lookInCell] = ViewOf(
|
|
||||||
Square(-0.35f, 0f, 0.15f),
|
|
||||||
Square(0.35f, 0f, 0.15f));
|
|
||||||
nested.OrderedVisibleCells.Add(lookInCell);
|
|
||||||
|
|
||||||
using ClipFrame frame = ClipFrame.NoClip();
|
|
||||||
ClipFrameAssembly assembly = ClipFrameAssembler.Assemble(frame, main);
|
|
||||||
int mainSlot = assembly.CellIdToSlot[mainCell];
|
|
||||||
int slotsBeforeLookIn = frame.SlotCount;
|
|
||||||
|
|
||||||
ClipFrameAssembler.AppendLookInFrames(frame, [nested], assembly);
|
|
||||||
|
|
||||||
var key = new LookInClipCell(0, lookInCell);
|
|
||||||
ClipViewSlice[] slices = assembly.LookInCellToViewSlices[key];
|
|
||||||
Assert.Equal(2, slices.Length);
|
|
||||||
Assert.All(slices, slice => Assert.True(slice.Slot >= slotsBeforeLookIn));
|
|
||||||
Assert.NotEqual(slices[0].Slot, slices[1].Slot);
|
|
||||||
Assert.Equal(slices[0].Slot, assembly.LookInCellToSlot[key]);
|
|
||||||
Assert.Equal(mainSlot, assembly.CellIdToSlot[mainCell]);
|
|
||||||
Assert.DoesNotContain(lookInCell, assembly.CellIdToSlot.Keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Assemble_OutsideViewWithExitPortal_HasOutsideViewTrue_AabbMatchesBounds()
|
public void Assemble_OutsideViewWithExitPortal_HasOutsideViewTrue_AabbMatchesBounds()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using AcDream.App.Rendering;
|
|
||||||
using DatReaderWriter;
|
|
||||||
using DatReaderWriter.DBObjs;
|
|
||||||
using DatReaderWriter.Options;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Rendering;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sanctuary cathedral seam captured 2026-08-27. The stationary player is in
|
|
||||||
/// F4180104 while chase-camera zoom crosses the coincident outside portals at
|
|
||||||
/// world Y=48 and the viewer root switches between two separate buildings:
|
|
||||||
/// B3={0103,0104,0105} and B4={0106..0111}. The opposite cathedral half must
|
|
||||||
/// remain available through the interior-root building look-in on both sides.
|
|
||||||
/// </summary>
|
|
||||||
[Trait("Lane", "InstalledDat")]
|
|
||||||
public sealed class SanctuaryPortalSeamTests
|
|
||||||
{
|
|
||||||
private const uint Landblock = 0xF4180000u;
|
|
||||||
private const uint Cell0104 = Landblock | 0x0104u;
|
|
||||||
private const uint Cell0106 = Landblock | 0x0106u;
|
|
||||||
|
|
||||||
private static Matrix4x4 ViewProjection(Vector3 eye)
|
|
||||||
{
|
|
||||||
// Derived from the two exact flap-sweep points. Extending their zoom
|
|
||||||
// ray reaches the stable chase target at the player's head.
|
|
||||||
var target = new Vector3(36.033f, 49.638f, 171.353f);
|
|
||||||
var view = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ);
|
|
||||||
var projection = Matrix4x4.CreatePerspectiveFieldOfView(
|
|
||||||
1.2f, 893f / 522f, 1f, 5000f);
|
|
||||||
return view * projection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IReadOnlyList<LoadedCell> Cells(
|
|
||||||
Dictionary<uint, LoadedCell> cells,
|
|
||||||
uint firstLow,
|
|
||||||
uint lastLow)
|
|
||||||
{
|
|
||||||
var result = new List<LoadedCell>();
|
|
||||||
for (uint low = firstLow; low <= lastLow; low++)
|
|
||||||
result.Add(cells[Landblock | low]);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CapturedZoomHandoff_BothRootsBuildTheExactOppositeCathedralSeed()
|
|
||||||
{
|
|
||||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
||||||
if (datDir is null)
|
|
||||||
{
|
|
||||||
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
||||||
LandBlockInfo info = Assert.IsType<LandBlockInfo>(dats.Get<LandBlockInfo>(Landblock | 0xfffeu));
|
|
||||||
Dictionary<uint, LoadedCell> cells =
|
|
||||||
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
||||||
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
||||||
|
|
||||||
IReadOnlyList<LoadedCell> building3 = Cells(cells, 0x0103u, 0x0105u);
|
|
||||||
IReadOnlyList<LoadedCell> building4 = Cells(cells, 0x0106u, 0x0111u);
|
|
||||||
Assert.Equal(0x01001FB2u, info.Buildings[3].ModelId);
|
|
||||||
Assert.Equal(0x01001FB3u, info.Buildings[4].ModelId);
|
|
||||||
|
|
||||||
var captures = new[]
|
|
||||||
{
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Name = "near/root0104",
|
|
||||||
Eye = new Vector3(32.742317f, 48.034306f, 172.447845f),
|
|
||||||
Root = cells[Cell0104],
|
|
||||||
Opposite = building4,
|
|
||||||
ExpectedOppositeCell = Cell0106,
|
|
||||||
ExpectedSeedPortal = 2,
|
|
||||||
OppositeBuildingIndex = 4,
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Name = "far/root0106",
|
|
||||||
Eye = new Vector3(32.308865f, 47.823200f, 172.592255f),
|
|
||||||
Root = cells[Cell0106],
|
|
||||||
Opposite = building3,
|
|
||||||
ExpectedOppositeCell = Cell0104,
|
|
||||||
ExpectedSeedPortal = 0,
|
|
||||||
OppositeBuildingIndex = 3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var capture in captures)
|
|
||||||
{
|
|
||||||
Matrix4x4 viewProjection = ViewProjection(capture.Eye);
|
|
||||||
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
||||||
capture.Root, capture.Eye, Lookup, viewProjection);
|
|
||||||
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
||||||
capture.Opposite,
|
|
||||||
capture.Eye,
|
|
||||||
Lookup,
|
|
||||||
viewProjection,
|
|
||||||
maxSeedDistance: float.PositiveInfinity,
|
|
||||||
seedRegion: main.OutsideView.Polygons);
|
|
||||||
|
|
||||||
Assert.Contains(capture.ExpectedOppositeCell, lookIn.OrderedVisibleCells);
|
|
||||||
Assert.True(
|
|
||||||
lookIn.CellViews.TryGetValue(capture.ExpectedOppositeCell, out CellView? view)
|
|
||||||
&& view.Polygons.Count > 0,
|
|
||||||
$"{capture.Name} must retain a clipped aperture for the opposite cathedral half");
|
|
||||||
ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
|
|
||||||
Assert.Equal(capture.ExpectedOppositeCell, seed.CellId);
|
|
||||||
Assert.Equal(capture.ExpectedSeedPortal, seed.PortalIndex);
|
|
||||||
Assert.NotEmpty(seed.View.Polygons);
|
|
||||||
|
|
||||||
using ClipFrame clipFrame = ClipFrame.NoClip();
|
|
||||||
ClipFrameAssembly assembly = ClipFrameAssembler.Assemble(
|
|
||||||
clipFrame,
|
|
||||||
main);
|
|
||||||
ClipFrameAssembler.AppendLookInFrames(
|
|
||||||
clipFrame,
|
|
||||||
[lookIn],
|
|
||||||
assembly);
|
|
||||||
ClipViewSlice[] nestedSlices =
|
|
||||||
assembly.LookInCellToViewSlices[
|
|
||||||
new LookInClipCell(0, capture.ExpectedOppositeCell)];
|
|
||||||
Assert.Equal(view.Polygons.Count, nestedSlices.Length);
|
|
||||||
Assert.All(nestedSlices, slice => Assert.True(slice.Slot > 0));
|
|
||||||
|
|
||||||
Assert.Contains(
|
|
||||||
info.Buildings[capture.OppositeBuildingIndex].Portals,
|
|
||||||
portal => portal.OtherCellId == (capture.ExpectedOppositeCell & 0xffffu)
|
|
||||||
&& portal.OtherPortalId == capture.ExpectedSeedPortal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReportedLateralTransition_BothRootsKeepTheOppositeFacadePortal()
|
|
||||||
{
|
|
||||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
||||||
if (datDir is null)
|
|
||||||
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
|
|
||||||
|
|
||||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
||||||
Dictionary<uint, LoadedCell> cells =
|
|
||||||
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
||||||
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
||||||
|
|
||||||
static (PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) Build(
|
|
||||||
Vector3 eye,
|
|
||||||
Vector3 target,
|
|
||||||
LoadedCell root,
|
|
||||||
IReadOnlyList<LoadedCell> opposite,
|
|
||||||
Func<uint, LoadedCell?> lookup)
|
|
||||||
{
|
|
||||||
Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
|
|
||||||
* Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 1.6f, 0.1f, 5000f);
|
|
||||||
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
||||||
root, eye, lookup, viewProjection);
|
|
||||||
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
||||||
opposite, eye, lookup, viewProjection, seedRegion: main.OutsideView.Polygons);
|
|
||||||
return (main, lookIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
(PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0106 = Build(
|
|
||||||
new Vector3(31.189594f, 46.280170f, 171.783646f),
|
|
||||||
new Vector3(32.787731f, 46.275948f, 171.354993f),
|
|
||||||
cells[Cell0106],
|
|
||||||
Cells(cells, 0x0103u, 0x0105u),
|
|
||||||
Lookup);
|
|
||||||
(PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0104 = Build(
|
|
||||||
new Vector3(31.198704f, 49.733287f, 171.783646f),
|
|
||||||
new Vector3(32.796841f, 49.729065f, 171.354993f),
|
|
||||||
cells[Cell0104],
|
|
||||||
Cells(cells, 0x0106u, 0x0111u),
|
|
||||||
Lookup);
|
|
||||||
|
|
||||||
Assert.Equal([Cell0106, Landblock | 0x010Fu], from0106.Main.OrderedVisibleCells);
|
|
||||||
Assert.Equal([Cell0104], from0106.LookIn.OrderedVisibleCells);
|
|
||||||
ExteriorPortalSeed seed0104 = Assert.Single(from0106.LookIn.ExteriorSeedPortals);
|
|
||||||
Assert.Equal(Cell0104, seed0104.CellId);
|
|
||||||
Assert.Equal(0, seed0104.PortalIndex);
|
|
||||||
Assert.NotEmpty(seed0104.View.Polygons);
|
|
||||||
|
|
||||||
Assert.Equal([Cell0104], from0104.Main.OrderedVisibleCells);
|
|
||||||
Assert.Equal([Cell0106, Landblock | 0x010Fu], from0104.LookIn.OrderedVisibleCells);
|
|
||||||
ExteriorPortalSeed seed0106 = Assert.Single(from0104.LookIn.ExteriorSeedPortals);
|
|
||||||
Assert.Equal(Cell0106, seed0106.CellId);
|
|
||||||
Assert.Equal(2, seed0106.PortalIndex);
|
|
||||||
Assert.NotEmpty(seed0106.View.Polygons);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReportedExactSteamSeam_Root0104KeepsTheOppositeCathedralPortal()
|
|
||||||
{
|
|
||||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
||||||
if (datDir is null)
|
|
||||||
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
|
|
||||||
|
|
||||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
||||||
Dictionary<uint, LoadedCell> cells =
|
|
||||||
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
||||||
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
||||||
|
|
||||||
// Live ACDREAM_PROBE_CELL capture for the user-reported stationary
|
|
||||||
// frame at player [32.792290, 48.000618, 169.804993]. The chase eye is
|
|
||||||
// only ~4 mm across the coincident 0104/0106 exterior portal plane.
|
|
||||||
var eye = new Vector3(31.189665f, 48.004852f, 171.784988f);
|
|
||||||
var target = new Vector3(32.792290f, 48.000618f, 171.354993f);
|
|
||||||
Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
|
|
||||||
* Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1555f / 1019f, 1f, 5000f);
|
|
||||||
|
|
||||||
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
||||||
cells[Cell0104], eye, Lookup, viewProjection);
|
|
||||||
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
||||||
Cells(cells, 0x0106u, 0x0111u),
|
|
||||||
eye,
|
|
||||||
Lookup,
|
|
||||||
viewProjection,
|
|
||||||
seedRegion: main.OutsideView.Polygons);
|
|
||||||
|
|
||||||
Assert.Contains(Cell0106, lookIn.OrderedVisibleCells);
|
|
||||||
ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
|
|
||||||
Assert.Equal(Cell0106, seed.CellId);
|
|
||||||
Assert.Equal(2, seed.PortalIndex);
|
|
||||||
Assert.NotEmpty(seed.View.Polygons);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -228,18 +228,12 @@ public sealed class WorldRenderDiagnosticsTests
|
||||||
skyDrawn: false,
|
skyDrawn: false,
|
||||||
depthClear: false,
|
depthClear: false,
|
||||||
outdoorSceneryDrawn: false,
|
outdoorSceneryDrawn: false,
|
||||||
outdoorPortalDrawn: false,
|
|
||||||
outdoorRootObjectCount: 0,
|
|
||||||
liveDynamicDrawnCount: 0,
|
liveDynamicDrawnCount: 0,
|
||||||
sceneParticles: "none",
|
sceneParticles: "none",
|
||||||
portalFrame: null,
|
visibleCells: null,
|
||||||
clipAssembly: null,
|
clipAssembly: null,
|
||||||
drawableCells: null,
|
drawableCells: null,
|
||||||
partition: null,
|
partition: null,
|
||||||
exteriorPortalFrame: null,
|
|
||||||
exteriorClipAssembly: null,
|
|
||||||
exteriorDrawableCells: null,
|
|
||||||
exteriorPartition: null,
|
|
||||||
cameraPosition: Vector3.Zero,
|
cameraPosition: Vector3.Zero,
|
||||||
playerPosition: Vector3.Zero);
|
playerPosition: Vector3.Zero);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -739,24 +739,16 @@ public sealed class WorldSceneRendererTests
|
||||||
// Distinct flood-only vs in-view sets: 0x01010003 is a look-in
|
// Distinct flood-only vs in-view sets: 0x01010003 is a look-in
|
||||||
// cell that is drawn but never part of the main flood.
|
// cell that is drawn but never part of the main flood.
|
||||||
_interiorResult = new RetailPViewFrameResult().Reset(
|
_interiorResult = new RetailPViewFrameResult().Reset(
|
||||||
new PortalVisibilityFrame(),
|
ClipFrameAssembler.BeginWalkFrame(
|
||||||
new ClipFrameAssembly(),
|
ClipFrame.NoClip(), outdoorRoot: false),
|
||||||
[0x01010001u],
|
[0x01010001u],
|
||||||
[0x01010001u, 0x01010003u],
|
[0x01010001u, 0x01010003u],
|
||||||
default,
|
default,
|
||||||
default,
|
default,
|
||||||
diagnosticPartition: null);
|
diagnosticPartition: null);
|
||||||
var outdoorPortalFrame = new PortalVisibilityFrame();
|
|
||||||
outdoorPortalFrame.OutsideView.Add(new ViewPolygon(
|
|
||||||
[
|
|
||||||
new Vector2(-1f, -1f),
|
|
||||||
new Vector2(1f, -1f),
|
|
||||||
new Vector2(1f, 1f),
|
|
||||||
new Vector2(-1f, 1f),
|
|
||||||
]));
|
|
||||||
_outdoorResult = new RetailPViewFrameResult().Reset(
|
_outdoorResult = new RetailPViewFrameResult().Reset(
|
||||||
outdoorPortalFrame,
|
ClipFrameAssembler.BeginWalkFrame(
|
||||||
ClipFrameAssembler.Assemble(ClipFrame.NoClip(), outdoorPortalFrame),
|
ClipFrame.NoClip(), outdoorRoot: true),
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
default,
|
default,
|
||||||
|
|
@ -886,7 +878,8 @@ public sealed class WorldSceneRendererTests
|
||||||
public CameraCellResolution CameraCellResolution => CameraCellResolution.None;
|
public CameraCellResolution CameraCellResolution => CameraCellResolution.None;
|
||||||
|
|
||||||
public void EmitPViewInput(
|
public void EmitPViewInput(
|
||||||
PortalVisibilityFrame portalFrame,
|
IReadOnlySet<uint> visibleCells,
|
||||||
|
int outsideViewCount,
|
||||||
Matrix4x4 viewProjection,
|
Matrix4x4 viewProjection,
|
||||||
LoadedCell clipRoot,
|
LoadedCell clipRoot,
|
||||||
Vector3 cameraPosition,
|
Vector3 cameraPosition,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue