Pure helper splitting a frame's entities into live-dynamic / per-cell statics / outdoor scenery, by the same precedence as WbDrawDispatcher.ResolveEntitySlot (serverGuid first — live entities have no ParentCellId). Feeds the per-cell DrawInside loop. 3 unit tests, GL-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.6 KiB
C#
62 lines
2.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Splits a frame's landblock entities into the three draw buckets the per-cell
|
|
/// <see cref="InteriorRenderer"/> needs, using the SAME precedence as
|
|
/// <see cref="Wb.WbDrawDispatcher.ResolveEntitySlot"/>:
|
|
/// <list type="number">
|
|
/// <item><b>ServerGuid != 0</b> (player / NPCs / items / doors) ⇒ <see cref="Result.LiveDynamic"/>
|
|
/// — drawn unclipped (depth only). These have no <c>ParentCellId</c> so they MUST be tested first.</item>
|
|
/// <item><b>ParentCellId</b> in the visible set ⇒ <see cref="Result.ByCell"/>[cell] — per-cell, portal-clipped.</item>
|
|
/// <item><b>ParentCellId == null</b> (outdoor scenery / building shell) ⇒ <see cref="Result.Outdoor"/>
|
|
/// — drawn through the doorway, clipped to OutsideView.</item>
|
|
/// </list>
|
|
/// A static whose <c>ParentCellId</c> is NOT in <paramref name="visibleCells"/> is dropped (its cell
|
|
/// isn't drawn this frame). Entities with no <c>MeshRefs</c> are skipped. Pure; GL-free; unit-tested.
|
|
/// </summary>
|
|
public static class InteriorEntityPartition
|
|
{
|
|
public sealed class Result
|
|
{
|
|
public Dictionary<uint, List<WorldEntity>> ByCell { get; } = new();
|
|
public List<WorldEntity> Outdoor { get; } = new();
|
|
public List<WorldEntity> LiveDynamic { get; } = new();
|
|
}
|
|
|
|
public static Result Partition(
|
|
HashSet<uint> visibleCells,
|
|
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
|
IReadOnlyList<WorldEntity> Entities,
|
|
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> landblockEntries)
|
|
{
|
|
var result = new Result();
|
|
foreach (var entry in landblockEntries)
|
|
{
|
|
foreach (var e in entry.Entities)
|
|
{
|
|
if (e.MeshRefs.Count == 0) continue;
|
|
|
|
if (e.ServerGuid != 0) // live-dynamic — precedence first (no ParentCellId)
|
|
{
|
|
result.LiveDynamic.Add(e);
|
|
}
|
|
else if (e.ParentCellId is uint cell)
|
|
{
|
|
if (!visibleCells.Contains(cell)) continue; // its cell isn't drawn this frame
|
|
if (!result.ByCell.TryGetValue(cell, out var list))
|
|
result.ByCell[cell] = list = new List<WorldEntity>();
|
|
list.Add(e);
|
|
}
|
|
else // outdoor scenery / building shell
|
|
{
|
|
result.Outdoor.Add(e);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|