564 lines
22 KiB
C#
564 lines
22 KiB
C#
// ClipFrameAssembler.cs
|
||
//
|
||
// Retail view assembly policy. The production frame walk supplies its
|
||
// outside_view directly; the legacy PortalVisibilityBuilder overload remains
|
||
// only for isolated research/replay tests. Each visible polygon is packed as
|
||
// an individual GPU clip slot.
|
||
//
|
||
// outside_view landscape slices
|
||
// reverse cell_draw_list exit masks
|
||
// reverse cell_draw_list EnvCell shells
|
||
// reverse cell_draw_list object lists
|
||
//
|
||
// Slot 0 is always no-clip. A slice whose polygon cannot be represented by the
|
||
// <=8 plane budget uses slot 0 and its NDC AABB; the renderer uses scissor for
|
||
// passes that need that fallback. Empty regions are omitted entirely.
|
||
using System.Collections.Generic;
|
||
using System.Numerics;
|
||
|
||
namespace AcDream.App.Rendering;
|
||
|
||
/// <summary>
|
||
/// How the landscape-through-outside_view pass should be interpreted.
|
||
/// </summary>
|
||
public enum TerrainClipMode
|
||
{
|
||
/// <summary>All outside_view slices have convex plane clips.</summary>
|
||
Planes,
|
||
|
||
/// <summary>At least one outside_view slice requires scissor fallback.</summary>
|
||
Scissor,
|
||
|
||
/// <summary>No outside_view slice is visible; skip landscape indoors.</summary>
|
||
Skip,
|
||
}
|
||
|
||
/// <summary>
|
||
/// One retail portal_view slice mapped to a GPU clip slot. The AABB is retained
|
||
/// for passes that cannot write gl_ClipDistance and must use scissor.
|
||
/// </summary>
|
||
public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes);
|
||
|
||
/// <summary>
|
||
/// Populated clip buffers plus routing data consumed by the render walk.
|
||
/// </summary>
|
||
public sealed class ClipFrameAssembly
|
||
{
|
||
public ClipFrame Frame { get; private set; } = null!;
|
||
|
||
/// <summary>First drawable slice slot per visible cell. Compatibility map
|
||
/// for renderer APIs that can accept only one slot at a time.</summary>
|
||
public Dictionary<uint, int> CellIdToSlot { get; } = new();
|
||
|
||
/// <summary>Slot-only cell slices, retained for older renderer APIs.</summary>
|
||
public Dictionary<uint, int[]> CellIdToViewSlots { get; } = new();
|
||
|
||
/// <summary>Full retail portal_view slices per visible cell.</summary>
|
||
public Dictionary<uint, ClipViewSlice[]> CellIdToViewSlices { get; } = new();
|
||
|
||
/// <summary>Full retail outside_view slices.</summary>
|
||
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty<ClipViewSlice>();
|
||
|
||
public int OutdoorSlot { get; internal set; }
|
||
public bool OutdoorVisible { get; internal set; }
|
||
public TerrainClipMode TerrainMode { get; internal set; }
|
||
public Vector4 TerrainScissorNdcAabb { get; internal set; }
|
||
public bool HasOutsideView { get; internal set; }
|
||
public Vector4 OutsideViewNdcAabb { get; internal set; }
|
||
|
||
// Probe data.
|
||
public int OutsidePlaneCount { get; internal set; }
|
||
public Dictionary<uint, int> PerCellPlaneCounts { get; } = new();
|
||
public int ScissorFallbacks { get; internal set; }
|
||
|
||
// The assembly is frame-scoped. An owner that passes it back to Assemble may reuse the
|
||
// dictionaries, per-cell arrays, and construction list after the prior frame is consumed.
|
||
// Exact-length pools keep the public array API honest: Length is always the live slice count.
|
||
private readonly Dictionary<int, Stack<ClipViewSlice[]>> _sliceArraysByLength = new();
|
||
private readonly Dictionary<int, Stack<int[]>> _slotArraysByLength = new();
|
||
internal List<ClipViewSlice> SliceScratch { get; } = new();
|
||
internal int SliceArrayAllocationCount { get; private set; }
|
||
internal int SlotArrayAllocationCount { get; private set; }
|
||
internal const int MaxRetainedSliceItems = 4096;
|
||
internal const int MaxRetainedSlotItems = 8192;
|
||
internal const int MaxRetainedArraysPerPool = 128;
|
||
internal int RetainedSliceItems { get; private set; }
|
||
internal int RetainedSlotItems { get; private set; }
|
||
internal int RetainedSliceArrays { get; private set; }
|
||
internal int RetainedSlotArrays { get; private set; }
|
||
|
||
internal void Reset(ClipFrame frame)
|
||
{
|
||
Frame = frame;
|
||
foreach (ClipViewSlice[] slices in CellIdToViewSlices.Values)
|
||
ReturnSlices(slices);
|
||
foreach (int[] slots in CellIdToViewSlots.Values)
|
||
ReturnSlots(slots);
|
||
if (OutsideViewSlices.Length != 0)
|
||
ReturnSlices(OutsideViewSlices);
|
||
|
||
CellIdToSlot.Clear();
|
||
CellIdToViewSlots.Clear();
|
||
CellIdToViewSlices.Clear();
|
||
PerCellPlaneCounts.Clear();
|
||
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
||
SliceScratch.Clear();
|
||
}
|
||
|
||
internal ClipViewSlice[] CopySlices(List<ClipViewSlice> source)
|
||
{
|
||
if (source.Count == 0)
|
||
return System.Array.Empty<ClipViewSlice>();
|
||
ClipViewSlice[] result = RentSlices(source.Count);
|
||
source.CopyTo(result, 0);
|
||
return result;
|
||
}
|
||
|
||
internal int[] CopySlots(ClipViewSlice[] slices)
|
||
{
|
||
if (slices.Length == 0)
|
||
return System.Array.Empty<int>();
|
||
int[] result = RentSlots(slices.Length);
|
||
for (int i = 0; i < slices.Length; i++)
|
||
result[i] = slices[i].Slot;
|
||
return result;
|
||
}
|
||
|
||
internal void SetOutsideViewSlices(ClipViewSlice[] slices) => OutsideViewSlices = slices;
|
||
|
||
/// <summary>FW4 slice 1: returns the outside-view slice array to the
|
||
/// pool ahead of a same-frame reassembly from the walk's own views
|
||
/// (<see cref="ClipFrameAssembler.ReassembleOutsideViewFromWalk"/>) —
|
||
/// without this the replaced array would leak from the slice pool for
|
||
/// the frame.</summary>
|
||
internal void ReturnOutsideViewSlicesForReassembly()
|
||
{
|
||
if (OutsideViewSlices.Length != 0)
|
||
ReturnSlices(OutsideViewSlices);
|
||
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
||
}
|
||
|
||
private ClipViewSlice[] RentSlices(int length)
|
||
{
|
||
if (_sliceArraysByLength.TryGetValue(length, out Stack<ClipViewSlice[]>? pool)
|
||
&& pool.Count != 0)
|
||
{
|
||
ClipViewSlice[] result = pool.Pop();
|
||
RetainedSliceItems -= result.Length;
|
||
RetainedSliceArrays--;
|
||
if (pool.Count == 0)
|
||
_sliceArraysByLength.Remove(length);
|
||
return result;
|
||
}
|
||
SliceArrayAllocationCount++;
|
||
return new ClipViewSlice[length];
|
||
}
|
||
|
||
private int[] RentSlots(int length)
|
||
{
|
||
if (_slotArraysByLength.TryGetValue(length, out Stack<int[]>? pool)
|
||
&& pool.Count != 0)
|
||
{
|
||
int[] result = pool.Pop();
|
||
RetainedSlotItems -= result.Length;
|
||
RetainedSlotArrays--;
|
||
if (pool.Count == 0)
|
||
_slotArraysByLength.Remove(length);
|
||
return result;
|
||
}
|
||
SlotArrayAllocationCount++;
|
||
return new int[length];
|
||
}
|
||
|
||
private void ReturnSlices(ClipViewSlice[] array)
|
||
{
|
||
// ClipViewSlice contains a Vector4[] reference. Clear before either retaining or dropping
|
||
// the array so a bounded cache cannot accidentally extend plane-payload lifetimes.
|
||
System.Array.Clear(array);
|
||
if (array.Length > MaxRetainedSliceItems)
|
||
return;
|
||
while (RetainedSliceItems + array.Length > MaxRetainedSliceItems
|
||
|| RetainedSliceArrays >= MaxRetainedArraysPerPool)
|
||
{
|
||
if (!EvictOneSliceArray())
|
||
break;
|
||
}
|
||
if (!_sliceArraysByLength.TryGetValue(array.Length, out Stack<ClipViewSlice[]>? pool))
|
||
{
|
||
pool = new Stack<ClipViewSlice[]>();
|
||
_sliceArraysByLength.Add(array.Length, pool);
|
||
}
|
||
pool.Push(array);
|
||
RetainedSliceItems += array.Length;
|
||
RetainedSliceArrays++;
|
||
}
|
||
|
||
private void ReturnSlots(int[] array)
|
||
{
|
||
if (array.Length > MaxRetainedSlotItems)
|
||
return;
|
||
while (RetainedSlotItems + array.Length > MaxRetainedSlotItems
|
||
|| RetainedSlotArrays >= MaxRetainedArraysPerPool)
|
||
{
|
||
if (!EvictOneSlotArray())
|
||
break;
|
||
}
|
||
if (!_slotArraysByLength.TryGetValue(array.Length, out Stack<int[]>? pool))
|
||
{
|
||
pool = new Stack<int[]>();
|
||
_slotArraysByLength.Add(array.Length, pool);
|
||
}
|
||
pool.Push(array);
|
||
RetainedSlotItems += array.Length;
|
||
RetainedSlotArrays++;
|
||
}
|
||
|
||
private bool EvictOneSliceArray()
|
||
{
|
||
int selectedLength = -1;
|
||
foreach ((int length, Stack<ClipViewSlice[]> pool) in _sliceArraysByLength)
|
||
{
|
||
if (pool.Count != 0 && length > selectedLength)
|
||
selectedLength = length;
|
||
}
|
||
if (selectedLength < 0)
|
||
return false;
|
||
Stack<ClipViewSlice[]> selected = _sliceArraysByLength[selectedLength];
|
||
ClipViewSlice[] evicted = selected.Pop();
|
||
RetainedSliceItems -= evicted.Length;
|
||
RetainedSliceArrays--;
|
||
if (selected.Count == 0)
|
||
_sliceArraysByLength.Remove(selectedLength);
|
||
return true;
|
||
}
|
||
|
||
private bool EvictOneSlotArray()
|
||
{
|
||
int selectedLength = -1;
|
||
foreach ((int length, Stack<int[]> pool) in _slotArraysByLength)
|
||
{
|
||
if (pool.Count != 0 && length > selectedLength)
|
||
selectedLength = length;
|
||
}
|
||
if (selectedLength < 0)
|
||
return false;
|
||
Stack<int[]> selected = _slotArraysByLength[selectedLength];
|
||
int[] evicted = selected.Pop();
|
||
RetainedSlotItems -= evicted.Length;
|
||
RetainedSlotArrays--;
|
||
if (selected.Count == 0)
|
||
_slotArraysByLength.Remove(selectedLength);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
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(
|
||
ClipFrame frame,
|
||
PortalVisibilityFrame pvFrame,
|
||
ClipFrameAssembly? reuseAssembly = null)
|
||
{
|
||
System.ArgumentNullException.ThrowIfNull(frame);
|
||
System.ArgumentNullException.ThrowIfNull(pvFrame);
|
||
|
||
frame.Reset();
|
||
ClipFrameAssembly assembly = reuseAssembly ?? new ClipFrameAssembly();
|
||
assembly.Reset(frame);
|
||
|
||
Dictionary<uint, int> cellIdToSlot = assembly.CellIdToSlot;
|
||
Dictionary<uint, int[]> cellIdToViewSlots = assembly.CellIdToViewSlots;
|
||
Dictionary<uint, ClipViewSlice[]> cellIdToViewSlices = assembly.CellIdToViewSlices;
|
||
Dictionary<uint, int> perCellPlaneCounts = assembly.PerCellPlaneCounts;
|
||
int scissorFallbacks = 0;
|
||
|
||
foreach (uint cellId in pvFrame.OrderedVisibleCells)
|
||
{
|
||
if (!pvFrame.CellViews.TryGetValue(cellId, out var view))
|
||
continue;
|
||
|
||
List<ClipViewSlice> slices = assembly.SliceScratch;
|
||
slices.Clear();
|
||
int maxPlaneCount = 0;
|
||
|
||
foreach (var poly in view.Polygons)
|
||
{
|
||
var cps = ClipPlaneSet.From(poly);
|
||
if (cps.IsNothingVisible)
|
||
continue;
|
||
|
||
int slot;
|
||
Vector4[] planes;
|
||
if (cps.Count > 0)
|
||
{
|
||
planes = cps.PlaneArray;
|
||
slot = frame.AppendSlot(planes);
|
||
if (cps.Count > maxPlaneCount)
|
||
maxPlaneCount = cps.Count;
|
||
}
|
||
else
|
||
{
|
||
planes = System.Array.Empty<Vector4>();
|
||
slot = 0;
|
||
scissorFallbacks++;
|
||
}
|
||
|
||
slices.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
||
}
|
||
|
||
if (slices.Count == 0)
|
||
continue;
|
||
|
||
ClipViewSlice[] sliceArray = assembly.CopySlices(slices);
|
||
cellIdToViewSlices[cellId] = sliceArray;
|
||
cellIdToViewSlots[cellId] = assembly.CopySlots(sliceArray);
|
||
cellIdToSlot[cellId] = sliceArray[0].Slot;
|
||
perCellPlaneCounts[cellId] = maxPlaneCount;
|
||
}
|
||
|
||
List<ClipViewSlice> outsideSlicesList = assembly.SliceScratch;
|
||
outsideSlicesList.Clear();
|
||
int outsideMaxPlaneCount = 0;
|
||
bool outsideHasScissorFallback = false;
|
||
|
||
foreach (var poly in pvFrame.OutsideView.Polygons)
|
||
{
|
||
var cps = ClipPlaneSet.From(poly);
|
||
if (cps.IsNothingVisible)
|
||
continue;
|
||
|
||
int slot;
|
||
Vector4[] planes;
|
||
if (cps.Count > 0)
|
||
{
|
||
planes = cps.PlaneArray;
|
||
slot = frame.AppendSlot(planes);
|
||
if (cps.Count > outsideMaxPlaneCount)
|
||
outsideMaxPlaneCount = cps.Count;
|
||
}
|
||
else
|
||
{
|
||
planes = System.Array.Empty<Vector4>();
|
||
slot = 0;
|
||
outsideHasScissorFallback = true;
|
||
scissorFallbacks++;
|
||
}
|
||
|
||
outsideSlicesList.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
||
}
|
||
|
||
ClipViewSlice[] outsideViewSlices = assembly.CopySlices(outsideSlicesList);
|
||
bool outdoorVisible = outsideViewSlices.Length > 0;
|
||
int outdoorSlot = outdoorVisible ? outsideViewSlices[0].Slot : 0;
|
||
TerrainClipMode terrainMode = !outdoorVisible
|
||
? TerrainClipMode.Skip
|
||
: (outsideHasScissorFallback ? TerrainClipMode.Scissor : TerrainClipMode.Planes);
|
||
|
||
Vector4 outsideViewNdcAabb = outdoorVisible
|
||
? new Vector4(pvFrame.OutsideView.MinX, pvFrame.OutsideView.MinY,
|
||
pvFrame.OutsideView.MaxX, pvFrame.OutsideView.MaxY)
|
||
: Vector4.Zero;
|
||
Vector4 terrainScissor = terrainMode == TerrainClipMode.Scissor
|
||
? outsideViewNdcAabb
|
||
: Vector4.Zero;
|
||
|
||
assembly.SetOutsideViewSlices(outsideViewSlices);
|
||
assembly.OutdoorSlot = outdoorSlot;
|
||
assembly.OutdoorVisible = outdoorVisible;
|
||
assembly.TerrainMode = terrainMode;
|
||
assembly.TerrainScissorNdcAabb = terrainScissor;
|
||
assembly.HasOutsideView = outdoorVisible;
|
||
assembly.OutsideViewNdcAabb = outsideViewNdcAabb;
|
||
assembly.OutsidePlaneCount = terrainMode == TerrainClipMode.Planes ? outsideMaxPlaneCount : 0;
|
||
assembly.ScissorFallbacks = scissorFallbacks;
|
||
return assembly;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Campaign FW4 slice 1 — the interior root's outside-view cutover.
|
||
/// Replaces the assembly's outside-view block (slices, terrain mode,
|
||
/// scissor/NDC bounds, plane count) with slices derived from THE WALK'S
|
||
/// OWN <c>outside_view</c> (<see cref="Walk.RetailFrameWalk.InteriorOutsideView"/>),
|
||
/// filled by the walk's <c>ConstructView</c> during Collect. Retail has
|
||
/// exactly ONE visibility structure per frame: <c>LScape::draw</c>'s
|
||
/// terrain clip, the punch fans' <c>building_view</c> planes, and the
|
||
/// landscape turn's view count all read the views the walk itself
|
||
/// installed. Feeding these from the old <c>PortalVisibilityBuilder</c>
|
||
/// assembly let the two systems desynchronize on camera-transition
|
||
/// boundary frames — terrain splashed through stale/fat old-apparatus
|
||
/// exit views over interior pixels the walk's flood never repainted
|
||
/// (the FW3 visual-gate stairwell/grass flash, probe-pinned
|
||
/// 2026-08-30), and punch fans indexed the old slice array with walk
|
||
/// view indices.
|
||
///
|
||
/// The walk stores view vertices as PIXEL screen points
|
||
/// (<c>copy_view</c>'s post-divide viewport coordinates, origin
|
||
/// top-left, +Y down — <see cref="Walk.WalkScreenClip.TransformToScreen"/>:
|
||
/// x=(W/2)(x_c+w), y=(H/2)(w−y_c)); inverting that mapping yields the
|
||
/// standard NDC this assembler's <see cref="ViewPolygon"/>s already use
|
||
/// (ndcX = 2x/W − 1, ndcY = 1 − 2y/H). Winding is normalized inside
|
||
/// <see cref="ClipPlaneSet.From(in ViewPolygon)"/>, and the closing
|
||
/// duplicate vertex <c>copy_view</c> stores is merged there too.
|
||
///
|
||
/// Must run AFTER the walk's Collect and BEFORE
|
||
/// <c>PrepareClipFrame</c> publishes the clip regions — appended slots
|
||
/// join the same single publication.
|
||
/// </summary>
|
||
public static void ReassembleOutsideViewFromWalk(
|
||
ClipFrameAssembly assembly,
|
||
Walk.WalkPortalView outsideView,
|
||
float viewportWidth,
|
||
float viewportHeight)
|
||
{
|
||
System.ArgumentNullException.ThrowIfNull(assembly);
|
||
System.ArgumentNullException.ThrowIfNull(outsideView);
|
||
if (viewportWidth <= 0f || viewportHeight <= 0f)
|
||
{
|
||
throw new System.ArgumentOutOfRangeException(
|
||
nameof(viewportWidth),
|
||
$"viewport {viewportWidth}x{viewportHeight} — the walk projected its "
|
||
+ "views through a real viewport; a non-positive extent here means the "
|
||
+ "caller handed a different frame's context (fail-loud rule).");
|
||
}
|
||
|
||
ClipFrame frame = assembly.Frame;
|
||
int viewCount = outsideView.ViewCount;
|
||
var polys = outsideView.View.Polys;
|
||
var pool = outsideView.View.Vertices;
|
||
if (polys.Count < viewCount)
|
||
{
|
||
throw new System.InvalidOperationException(
|
||
$"walk outside_view holds {polys.Count} polys for ViewCount={viewCount} — "
|
||
+ "the view set's append bookkeeping desynchronized (fail-loud rule).");
|
||
}
|
||
|
||
assembly.ReturnOutsideViewSlicesForReassembly();
|
||
|
||
List<ClipViewSlice> outsideSlicesList = assembly.SliceScratch;
|
||
outsideSlicesList.Clear();
|
||
int outsideMaxPlaneCount = 0;
|
||
bool outsideHasScissorFallback = false;
|
||
int scissorFallbacks = assembly.ScissorFallbacks;
|
||
float unionMinX = float.MaxValue, unionMinY = float.MaxValue;
|
||
float unionMaxX = float.MinValue, unionMaxY = float.MinValue;
|
||
|
||
for (int v = 0; v < viewCount; v++)
|
||
{
|
||
Walk.WalkViewPoly walkPoly = polys[v];
|
||
var vertices = new Vector2[walkPoly.VertexCount];
|
||
for (int k = 0; k < walkPoly.VertexCount; k++)
|
||
{
|
||
Vector2 px = pool[walkPoly.VertexIndex + k].Point;
|
||
vertices[k] = new Vector2(
|
||
px.X / viewportWidth * 2f - 1f,
|
||
1f - px.Y / viewportHeight * 2f);
|
||
}
|
||
var poly = new ViewPolygon(vertices);
|
||
if (!poly.IsEmpty)
|
||
{
|
||
if (poly.MinX < unionMinX) unionMinX = poly.MinX;
|
||
if (poly.MinY < unionMinY) unionMinY = poly.MinY;
|
||
if (poly.MaxX > unionMaxX) unionMaxX = poly.MaxX;
|
||
if (poly.MaxY > unionMaxY) unionMaxY = poly.MaxY;
|
||
}
|
||
|
||
var cps = ClipPlaneSet.From(poly);
|
||
if (cps.IsNothingVisible)
|
||
continue;
|
||
|
||
int slot;
|
||
Vector4[] planes;
|
||
if (cps.Count > 0)
|
||
{
|
||
planes = cps.PlaneArray;
|
||
slot = frame.AppendSlot(planes);
|
||
if (cps.Count > outsideMaxPlaneCount)
|
||
outsideMaxPlaneCount = cps.Count;
|
||
}
|
||
else
|
||
{
|
||
planes = System.Array.Empty<Vector4>();
|
||
slot = 0;
|
||
outsideHasScissorFallback = true;
|
||
scissorFallbacks++;
|
||
}
|
||
|
||
outsideSlicesList.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
||
}
|
||
|
||
ClipViewSlice[] outsideViewSlices = assembly.CopySlices(outsideSlicesList);
|
||
bool outdoorVisible = outsideViewSlices.Length > 0;
|
||
int outdoorSlot = outdoorVisible ? outsideViewSlices[0].Slot : 0;
|
||
TerrainClipMode terrainMode = !outdoorVisible
|
||
? TerrainClipMode.Skip
|
||
: (outsideHasScissorFallback ? TerrainClipMode.Scissor : TerrainClipMode.Planes);
|
||
|
||
Vector4 outsideViewNdcAabb = outdoorVisible
|
||
? new Vector4(unionMinX, unionMinY, unionMaxX, unionMaxY)
|
||
: Vector4.Zero;
|
||
Vector4 terrainScissor = terrainMode == TerrainClipMode.Scissor
|
||
? outsideViewNdcAabb
|
||
: Vector4.Zero;
|
||
|
||
assembly.SetOutsideViewSlices(outsideViewSlices);
|
||
assembly.OutdoorSlot = outdoorSlot;
|
||
assembly.OutdoorVisible = outdoorVisible;
|
||
assembly.TerrainMode = terrainMode;
|
||
assembly.TerrainScissorNdcAabb = terrainScissor;
|
||
assembly.HasOutsideView = outdoorVisible;
|
||
assembly.OutsideViewNdcAabb = outsideViewNdcAabb;
|
||
assembly.OutsidePlaneCount = terrainMode == TerrainClipMode.Planes ? outsideMaxPlaneCount : 0;
|
||
assembly.ScissorFallbacks = scissorFallbacks;
|
||
}
|
||
|
||
private static Vector4 AabbOf(ViewPolygon poly) =>
|
||
new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY);
|
||
|
||
}
|