fix(render): clip walk content to authored portal views
This commit is contained in:
parent
90eba0ec3c
commit
11e68aad82
10 changed files with 408 additions and 78 deletions
|
|
@ -209,6 +209,7 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
|||
private readonly Action _drawExitSeals;
|
||||
private readonly HashSet<uint> _singleCellScratch = new();
|
||||
private readonly List<uint> _singleCellListScratch = new();
|
||||
private readonly Dictionary<uint, int> _singleCellClipScratch = new(1);
|
||||
|
||||
internal WalkProductionLeafRenderer(
|
||||
RetailPViewPassExecutor passes,
|
||||
|
|
@ -231,16 +232,26 @@ internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
|||
public void DrawTerrainSlice(int sliceIndex) =>
|
||||
_passes.DrawWalkTerrainSlice(_frame, _clipAssembly, sliceIndex);
|
||||
|
||||
public void DrawCellShell(uint cellId)
|
||||
public void DrawCellShell(uint cellId, uint clipSlot)
|
||||
{
|
||||
_singleCellScratch.Clear();
|
||||
_singleCellScratch.Add(cellId);
|
||||
_passes.DrawOpaqueCellShells(_singleCellScratch);
|
||||
if (_passes.CellHasTransparentShell(cellId))
|
||||
_singleCellClipScratch.Clear();
|
||||
_singleCellClipScratch.Add(cellId, checked((int)clipSlot));
|
||||
_passes.SetCellShellClipRouting(_singleCellClipScratch);
|
||||
try
|
||||
{
|
||||
_singleCellListScratch.Clear();
|
||||
_singleCellListScratch.Add(cellId);
|
||||
_passes.DrawTransparentCellShellsOrdered(_singleCellListScratch);
|
||||
_singleCellScratch.Clear();
|
||||
_singleCellScratch.Add(cellId);
|
||||
_passes.DrawOpaqueCellShells(_singleCellScratch);
|
||||
if (_passes.CellHasTransparentShell(cellId))
|
||||
{
|
||||
_singleCellListScratch.Clear();
|
||||
_singleCellListScratch.Add(cellId);
|
||||
_passes.DrawTransparentCellShellsOrdered(_singleCellListScratch);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_passes.SetCellShellClipRouting(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,9 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
public void DrawOpaqueCellShells(HashSet<uint> cellIds) =>
|
||||
_envCells.Render(WbRenderPass.Opaque, cellIds);
|
||||
|
||||
public void SetCellShellClipRouting(IReadOnlyDictionary<uint, int>? routing) =>
|
||||
_envCells.SetClipRouting(routing);
|
||||
|
||||
public bool CellHasTransparentShell(uint cellId) =>
|
||||
_envCells.CellHasTransparent(cellId);
|
||||
|
||||
|
|
|
|||
|
|
@ -215,7 +215,8 @@ internal sealed class RetailPViewRenderer
|
|||
walkDriver = new Walk.WalkFrameDriver(
|
||||
walkExecutor!.Dispatcher,
|
||||
walkLeafRenderer,
|
||||
_walkWorldData);
|
||||
_walkWorldData,
|
||||
clipFrame: clipAssembly.Frame);
|
||||
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ internal interface IWalkFrameLeafRenderer
|
|||
/// second reverse loop for <c>DrawObjCellForDummies</c> @0x005a4b0d.
|
||||
/// The ordinary interior-root flood and every building look-in flood use
|
||||
/// this same two-pass discipline.</summary>
|
||||
void DrawCellShell(uint cellId);
|
||||
void DrawCellShell(uint cellId, uint clipSlot);
|
||||
|
||||
/// <summary>One landscape cell's or building shell's static-owner
|
||||
/// particle submission, at its own walk turn — see
|
||||
|
|
@ -231,7 +231,9 @@ internal enum WalkFrameEventKind : byte
|
|||
TerrainSlice,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawCellShell"/> —
|
||||
/// <see cref="WalkFrameEvent.CellId"/> is the cell.</summary>
|
||||
/// <see cref="WalkFrameEvent.CellId"/> is the cell and
|
||||
/// <see cref="WalkFrameEvent.IntArg"/> is the exact portal-view GPU clip
|
||||
/// slot for this retail <c>DrawEnvCell</c> turn.</summary>
|
||||
CellShell,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawPunchFan"/> —
|
||||
|
|
@ -288,9 +290,23 @@ internal interface IWalkLookInViewSource
|
|||
int routeIndex,
|
||||
in Vector3 center,
|
||||
float radius);
|
||||
|
||||
/// <summary>Returns the GPU clip slots for every view slice in this
|
||||
/// DrawObjCell turn which admits the part's retail drawing sphere. Retail
|
||||
/// runs DrawMeshInternal once per surviving portal_view slice; a boolean
|
||||
/// sphere gate alone is insufficient because the mesh polygons must remain
|
||||
/// clipped to that aperture.</summary>
|
||||
IReadOnlyList<uint> VisibleClipSlotsInLookInTurn(
|
||||
int routeIndex,
|
||||
in Vector3 center,
|
||||
float radius,
|
||||
bool testSphere);
|
||||
}
|
||||
|
||||
internal readonly record struct WalkLookInSlice(int PlaneStart, int PlaneCount);
|
||||
internal readonly record struct WalkLookInSlice(
|
||||
int PlaneStart,
|
||||
int PlaneCount,
|
||||
uint ClipSlot);
|
||||
|
||||
internal readonly record struct WalkLookInTurn(
|
||||
uint CellId,
|
||||
|
|
@ -341,8 +357,8 @@ internal readonly struct WalkFrameEvent
|
|||
internal static WalkFrameEvent TerrainSlice(int sliceIndex) =>
|
||||
new(WalkFrameEventKind.TerrainSlice, sliceIndex, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent CellShell(uint cellId) =>
|
||||
new(WalkFrameEventKind.CellShell, 0, cellId, 0f, null);
|
||||
internal static WalkFrameEvent CellShell(uint cellId, uint clipSlot) =>
|
||||
new(WalkFrameEventKind.CellShell, checked((int)clipSlot), cellId, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent PunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
|
||||
new(WalkFrameEventKind.PunchFan, activeViewIndex, 0, 0f, worldPolygon);
|
||||
|
|
@ -452,6 +468,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
private readonly IWalkFrameLeafRenderer _leafRenderer;
|
||||
private readonly IWalkFrameWorldData _worldData;
|
||||
private readonly IWalkFrameDriverTrace? _trace;
|
||||
private readonly ClipFrame? _clipFrame;
|
||||
private readonly OrderedDrawStream _stream = new();
|
||||
private readonly List<WalkFrameEvent> _events = new();
|
||||
private readonly List<int> _markPositions = new();
|
||||
|
|
@ -472,6 +489,8 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
private readonly List<WalkLookInTurn> _lookInTurns = new();
|
||||
private readonly List<WalkLookInSlice> _lookInSlices = new();
|
||||
private readonly List<WalkPlane> _lookInPlanes = new();
|
||||
private readonly List<uint> _visibleClipSlotScratch = new();
|
||||
private readonly List<int> _floodViewRouteScratch = new();
|
||||
private WalkPlane _lookInCyPlane;
|
||||
|
||||
IReadOnlyList<uint> IWalkLookInViewSource.LookInCellTurns => LookInCellTurns;
|
||||
|
|
@ -524,12 +543,14 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
WbDrawDispatcher dispatcher,
|
||||
IWalkFrameLeafRenderer leafRenderer,
|
||||
IWalkFrameWorldData worldData,
|
||||
IWalkFrameDriverTrace? trace = null)
|
||||
IWalkFrameDriverTrace? trace = null,
|
||||
ClipFrame? clipFrame = null)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
_leafRenderer = leafRenderer ?? throw new ArgumentNullException(nameof(leafRenderer));
|
||||
_worldData = worldData ?? throw new ArgumentNullException(nameof(worldData));
|
||||
_trace = trace;
|
||||
_clipFrame = clipFrame;
|
||||
_populator = new WalkStaticStreamPopulator(dispatcher);
|
||||
}
|
||||
|
||||
|
|
@ -621,6 +642,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_lookInTurns.Clear();
|
||||
_lookInSlices.Clear();
|
||||
_lookInPlanes.Clear();
|
||||
_visibleClipSlotScratch.Clear();
|
||||
_lookInCyPlane = ctx.CyPlane;
|
||||
LookInCells.Clear();
|
||||
VisitedBuildings.Clear();
|
||||
|
|
@ -699,7 +721,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_leafRenderer.DrawTerrainSlice(e.IntArg);
|
||||
break;
|
||||
case WalkFrameEventKind.CellShell:
|
||||
_leafRenderer.DrawCellShell(e.CellId);
|
||||
_leafRenderer.DrawCellShell(e.CellId, checked((uint)e.IntArg));
|
||||
break;
|
||||
case WalkFrameEventKind.PunchFan:
|
||||
_leafRenderer.DrawPunchFan(e.Polygon!, e.IntArg);
|
||||
|
|
@ -985,12 +1007,23 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
|
||||
private void EmitFloodTurns(WalkDrawStage stage, IReadOnlyList<uint> cells)
|
||||
{
|
||||
_floodViewRouteScratch.Clear();
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
_floodViewRouteScratch.Add(-1);
|
||||
|
||||
// PView::DrawCells @0x005A4840, loop 2 (005A4A00–005A4ADE):
|
||||
// cell_draw_list[count - 1] down to zero, DrawEnvCell only.
|
||||
// cell_draw_list[count - 1] down to zero. DrawEnvCell loops the
|
||||
// cell's live portal_view slices, so capture those slices here and
|
||||
// replay the shell once per exact GPU clip slot.
|
||||
for (int i = cells.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int viewRouteIndex = CaptureCellViewRoute(cells[i]);
|
||||
_floodViewRouteScratch[i] = viewRouteIndex;
|
||||
IReadOnlyList<uint> clipSlots = VisibleClipSlotsInLookInTurn(
|
||||
viewRouteIndex, default, 0f, testSphere: false);
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.CellShell(cells[i]));
|
||||
for (int slotIndex = 0; slotIndex < clipSlots.Count; slotIndex++)
|
||||
_events.Add(WalkFrameEvent.CellShell(cells[i], clipSlots[slotIndex]));
|
||||
}
|
||||
|
||||
// Loop 3 (005A4ADE–005A4B2D): restart at count - 1 and draw each
|
||||
|
|
@ -998,23 +1031,31 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
// static records and its dynamic/particle tail stay at that exact
|
||||
// retail turn.
|
||||
for (int i = cells.Count - 1; i >= 0; i--)
|
||||
EmitCellContentsTurn(stage, cells[i]);
|
||||
EmitCellContentsTurn(stage, cells[i], _floodViewRouteScratch[i]);
|
||||
}
|
||||
|
||||
private void EmitCellContentsTurn(WalkDrawStage stage, uint cellId)
|
||||
private int CaptureCellViewRoute(uint cellId)
|
||||
{
|
||||
int viewRouteIndex = _cellViewRouteIndex++;
|
||||
CaptureCellViews(cellId);
|
||||
return viewRouteIndex;
|
||||
}
|
||||
|
||||
private void EmitCellContentsTurn(
|
||||
WalkDrawStage stage,
|
||||
uint cellId,
|
||||
int viewRouteIndex)
|
||||
{
|
||||
// The shell loop captured the exact live portal_view for this cell.
|
||||
// Static and dynamic object lists reuse that same route: retail's
|
||||
// DrawEnvCell and DrawObjCell consume the same PortalList.
|
||||
WalkFrameStaticRecords records = _worldData.GetCellStatics(cellId);
|
||||
_populator.PopulateCell(
|
||||
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection);
|
||||
_cameraWorldPosition, _viewProjection,
|
||||
this, viewRouteIndex);
|
||||
MarkIfGrown();
|
||||
|
||||
// PView::DrawObjCellForDummies draws this cell's live objects at the
|
||||
// same turn as its statics. Capture the currently installed portal
|
||||
// views before the walk pops them, then apply retail's per-GfxObj
|
||||
// drawing-sphere viewcone check in the walk classifier.
|
||||
int viewRouteIndex = _cellViewRouteIndex++;
|
||||
CaptureCellViews(cellId);
|
||||
if (stage == WalkDrawStage.LookInStatic)
|
||||
{
|
||||
LookInCellTurns.Add(cellId);
|
||||
|
|
@ -1062,8 +1103,9 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_lookInPlanes.Add(
|
||||
portalView.View.Vertices[poly.VertexIndex + edge].Plane);
|
||||
}
|
||||
uint clipSlot = AppendClipSlot(portalView, poly);
|
||||
_lookInSlices.Add(new WalkLookInSlice(
|
||||
planeStart, poly.VertexCount));
|
||||
planeStart, poly.VertexCount, clipSlot));
|
||||
}
|
||||
_lookInTurns.Add(new WalkLookInTurn(
|
||||
cellId, sliceStart, _lookInSlices.Count - sliceStart));
|
||||
|
|
@ -1095,6 +1137,100 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyList<uint> VisibleClipSlotsInLookInTurn(
|
||||
int routeIndex,
|
||||
in Vector3 center,
|
||||
float radius,
|
||||
bool testSphere)
|
||||
{
|
||||
_visibleClipSlotScratch.Clear();
|
||||
if ((uint)routeIndex >= (uint)_lookInTurns.Count)
|
||||
return _visibleClipSlotScratch;
|
||||
|
||||
WalkLookInTurn turn = _lookInTurns[routeIndex];
|
||||
for (int sliceOffset = 0; sliceOffset < turn.SliceCount; sliceOffset++)
|
||||
{
|
||||
WalkLookInSlice slice = _lookInSlices[turn.SliceStart + sliceOffset];
|
||||
if (!testSphere
|
||||
|| WalkVisibilityMath.ViewconeCheck(
|
||||
center,
|
||||
radius,
|
||||
_lookInCyPlane,
|
||||
CollectionsMarshal.AsSpan(_lookInPlanes).Slice(
|
||||
slice.PlaneStart,
|
||||
slice.PlaneCount)) != WalkBoundingType.Outside)
|
||||
{
|
||||
_visibleClipSlotScratch.Add(slice.ClipSlot);
|
||||
}
|
||||
}
|
||||
return _visibleClipSlotScratch;
|
||||
}
|
||||
|
||||
/// <summary>Converts one retail pixel-space portal_view polygon into the
|
||||
/// clip-space half-planes consumed by mesh_modern.vert. copy_view already
|
||||
/// deduplicates and removes collinear vertices, so 3..8 points map
|
||||
/// directly. The rare >8-edge case uses its convex AABB as a conservative
|
||||
/// four-plane gate rather than falling back to slot 0 (which would erase
|
||||
/// the aperture entirely).</summary>
|
||||
private uint AppendClipSlot(WalkPortalView portalView, WalkViewPoly poly)
|
||||
{
|
||||
if (_clipFrame is null)
|
||||
return 0;
|
||||
|
||||
IRetailFrameWalkContext ctx = (IRetailFrameWalkContext)RequireOpenFrame();
|
||||
int count = poly.VertexCount;
|
||||
if (count < 3)
|
||||
return 0;
|
||||
|
||||
Span<Vector2> ndc = stackalloc Vector2[Math.Min(count, WalkCopyView.MaxVertices)];
|
||||
float minX = float.MaxValue, minY = float.MaxValue;
|
||||
float maxX = float.MinValue, maxY = float.MinValue;
|
||||
for (int i = 0; i < ndc.Length; i++)
|
||||
{
|
||||
Vector2 px = portalView.View.Vertices[poly.VertexIndex + i].Point;
|
||||
Vector2 point = new(
|
||||
px.X / ctx.ViewportWidth * 2f - 1f,
|
||||
1f - px.Y / ctx.ViewportHeight * 2f);
|
||||
ndc[i] = point;
|
||||
minX = MathF.Min(minX, point.X);
|
||||
minY = MathF.Min(minY, point.Y);
|
||||
maxX = MathF.Max(maxX, point.X);
|
||||
maxY = MathF.Max(maxY, point.Y);
|
||||
}
|
||||
|
||||
if (ndc.Length > ClipFrame.MaxPlanes)
|
||||
{
|
||||
Span<Vector4> aabbPlanes = stackalloc Vector4[4]
|
||||
{
|
||||
new(1f, 0f, 0f, -minX),
|
||||
new(-1f, 0f, 0f, maxX),
|
||||
new(0f, 1f, 0f, -minY),
|
||||
new(0f, -1f, 0f, maxY),
|
||||
};
|
||||
return checked((uint)_clipFrame.AppendSlot(aabbPlanes));
|
||||
}
|
||||
|
||||
float area2 = 0f;
|
||||
for (int i = 0; i < ndc.Length; i++)
|
||||
area2 += ndc[i].X * ndc[(i + 1) % ndc.Length].Y
|
||||
- ndc[(i + 1) % ndc.Length].X * ndc[i].Y;
|
||||
bool ccw = area2 >= 0f;
|
||||
Span<Vector4> planes = stackalloc Vector4[ClipFrame.MaxPlanes];
|
||||
for (int i = 0; i < ndc.Length; i++)
|
||||
{
|
||||
int current = ccw ? i : ndc.Length - 1 - i;
|
||||
int next = ccw
|
||||
? (i + 1) % ndc.Length
|
||||
: (ndc.Length - 2 - i + ndc.Length) % ndc.Length;
|
||||
Vector2 p = ndc[current];
|
||||
Vector2 q = ndc[next];
|
||||
Vector2 dir = q - p;
|
||||
Vector2 normal = Vector2.Normalize(new Vector2(-dir.Y, dir.X));
|
||||
planes[i] = new Vector4(normal.X, normal.Y, 0f, -Vector2.Dot(normal, p));
|
||||
}
|
||||
return checked((uint)_clipFrame.AppendSlot(planes[..ndc.Length]));
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.4a: the collect-time analogue of the old
|
||||
/// immediate driver's <c>FlushIfNonEmpty</c> — records a
|
||||
/// <see cref="WalkFrameEventKind.StreamMark"/> at the stream's current
|
||||
|
|
|
|||
|
|
@ -64,14 +64,17 @@ internal sealed class WalkStaticStreamPopulator
|
|||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
Matrix4x4 viewProjection,
|
||||
IWalkLookInViewSource? views = null,
|
||||
int viewRouteIndex = -1)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
ClassifyAndAppend(
|
||||
stream, stage, cellId, in records[i], tupleLandblockId,
|
||||
cameraWorldPosition, viewProjection);
|
||||
cameraWorldPosition, viewProjection,
|
||||
liveDynamic: false, views, viewRouteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -274,23 +274,15 @@ public sealed partial class WbDrawDispatcher
|
|||
Matrix4x4 model = restPose * entity.RootWorld;
|
||||
int selectionPartIndex = unchecked((partIndex << 16) | (setupPartIndex & 0xFFFF));
|
||||
|
||||
if (!PartVisibleInLookInTurn(
|
||||
lookInViews,
|
||||
lookInRouteIndex,
|
||||
lookInCellId,
|
||||
in entity,
|
||||
selectionPartIndex,
|
||||
(uint)gfxObjId,
|
||||
partData,
|
||||
model))
|
||||
{
|
||||
IReadOnlyList<uint>? partClipSlots = ResolvePartClipSlots(
|
||||
lookInViews, lookInRouteIndex, partData, model);
|
||||
if (partClipSlots is { Count: 0 })
|
||||
continue;
|
||||
}
|
||||
|
||||
EmitClassifiedBatches(
|
||||
partData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubset, slot, lights, indoor, selectionLighting,
|
||||
detailCategory, opacity, batches);
|
||||
detailCategory, opacity, partClipSlots, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, selectionPartIndex,
|
||||
(uint)gfxObjId, model));
|
||||
|
|
@ -308,22 +300,14 @@ public sealed partial class WbDrawDispatcher
|
|||
continue;
|
||||
|
||||
Matrix4x4 model = meshRef.PartTransform * entity.RootWorld;
|
||||
if (!PartVisibleInLookInTurn(
|
||||
lookInViews,
|
||||
lookInRouteIndex,
|
||||
lookInCellId,
|
||||
in entity,
|
||||
partIndex,
|
||||
(uint)meshRef.GfxObjId,
|
||||
renderData,
|
||||
model))
|
||||
{
|
||||
IReadOnlyList<uint>? partClipSlots = ResolvePartClipSlots(
|
||||
lookInViews, lookInRouteIndex, renderData, model);
|
||||
if (partClipSlots is { Count: 0 })
|
||||
continue;
|
||||
}
|
||||
EmitClassifiedBatches(
|
||||
renderData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubsetOverride: null, slot, lights, indoor,
|
||||
selectionLighting, detailCategory, opacity, batches);
|
||||
selectionLighting, detailCategory, opacity, partClipSlots, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, partIndex,
|
||||
(uint)meshRef.GfxObjId, model));
|
||||
|
|
@ -379,28 +363,29 @@ public sealed partial class WbDrawDispatcher
|
|||
lights = InstanceLightSet.From(selected);
|
||||
}
|
||||
|
||||
private static bool PartVisibleInLookInTurn(
|
||||
private static IReadOnlyList<uint>? ResolvePartClipSlots(
|
||||
IWalkLookInViewSource? lookInViews,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
in RenderInstanceCandidate entity,
|
||||
int partIndex,
|
||||
uint gfxObjId,
|
||||
ObjectRenderData renderData,
|
||||
Matrix4x4 localToWorld)
|
||||
{
|
||||
if (lookInViews is null)
|
||||
return true;
|
||||
return null;
|
||||
if (renderData.SelectionSphere is not { Radius: > 0f } sphere)
|
||||
return true;
|
||||
{
|
||||
return lookInViews.VisibleClipSlotsInLookInTurn(
|
||||
routeIndex,
|
||||
Vector3.Zero,
|
||||
radius: 0f,
|
||||
testSphere: false);
|
||||
}
|
||||
|
||||
return LookInDrawingSphereVisible(
|
||||
lookInViews,
|
||||
TransformDrawingSphere(sphere, localToWorld, out Vector3 center, out float radius);
|
||||
return lookInViews.VisibleClipSlotsInLookInTurn(
|
||||
routeIndex,
|
||||
sphere,
|
||||
localToWorld,
|
||||
out _,
|
||||
out _);
|
||||
in center,
|
||||
radius,
|
||||
testSphere: true);
|
||||
}
|
||||
|
||||
internal static bool LookInDrawingSphereVisible(
|
||||
|
|
@ -414,6 +399,19 @@ public sealed partial class WbDrawDispatcher
|
|||
ArgumentNullException.ThrowIfNull(lookInViews);
|
||||
ArgumentNullException.ThrowIfNull(sphere);
|
||||
|
||||
TransformDrawingSphere(sphere, localToWorld, out center, out radius);
|
||||
return lookInViews.SphereVisibleInLookInTurn(
|
||||
routeIndex,
|
||||
in center,
|
||||
radius);
|
||||
}
|
||||
|
||||
private static void TransformDrawingSphere(
|
||||
DatReaderWriter.Types.Sphere sphere,
|
||||
Matrix4x4 localToWorld,
|
||||
out Vector3 center,
|
||||
out float radius)
|
||||
{
|
||||
center = Vector3.Transform(sphere.Origin, localToWorld);
|
||||
float scaleX = new Vector3(
|
||||
localToWorld.M11,
|
||||
|
|
@ -429,10 +427,6 @@ public sealed partial class WbDrawDispatcher
|
|||
localToWorld.M33).Length();
|
||||
radius = sphere.Radius
|
||||
* MathF.Max(scaleX, MathF.Max(scaleY, scaleZ));
|
||||
return lookInViews.SphereVisibleInLookInTurn(
|
||||
routeIndex,
|
||||
in center,
|
||||
radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -454,6 +448,7 @@ public sealed partial class WbDrawDispatcher
|
|||
Vector2 selectionLighting,
|
||||
uint detailCategory,
|
||||
float opacity,
|
||||
IReadOnlyList<uint>? clipSlots,
|
||||
List<WalkClassifiedBatch> sink)
|
||||
{
|
||||
bool entityHasCutoutSubset = entityHasCutoutSubsetOverride ?? renderData.HasCutoutSubset;
|
||||
|
|
@ -466,10 +461,23 @@ public sealed partial class WbDrawDispatcher
|
|||
if (!survives)
|
||||
continue;
|
||||
|
||||
sink.Add(new WalkClassifiedBatch(
|
||||
key, model, slot, lights, indoor ? 1u : 0u, Alpha: opacity,
|
||||
selectionLighting, detailCategory, IsOpaque: IsOpaque(key.Translucency),
|
||||
LocalSortCenter: renderData.SortCenter));
|
||||
if (clipSlots is null)
|
||||
{
|
||||
sink.Add(new WalkClassifiedBatch(
|
||||
key, model, slot, lights, indoor ? 1u : 0u, Alpha: opacity,
|
||||
selectionLighting, detailCategory, IsOpaque: IsOpaque(key.Translucency),
|
||||
LocalSortCenter: renderData.SortCenter));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int clipIndex = 0; clipIndex < clipSlots.Count; clipIndex++)
|
||||
{
|
||||
sink.Add(new WalkClassifiedBatch(
|
||||
key, model, clipSlots[clipIndex], lights, indoor ? 1u : 0u,
|
||||
Alpha: opacity, selectionLighting, detailCategory,
|
||||
IsOpaque: IsOpaque(key.Translucency),
|
||||
LocalSortCenter: renderData.SortCenter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue