feat(rendering): establish current-path scene referee
Capture deterministic, landblock-aware fingerprints from the accepted partition only during lifecycle automation. This gives the F/G shadow scene an independent oracle without changing normal draw decisions or adding disabled-path per-entity cost.
This commit is contained in:
parent
5ecaa5612d
commit
b2b67341ac
9 changed files with 845 additions and 5 deletions
|
|
@ -30,6 +30,27 @@ namespace AcDream.App.Rendering;
|
|||
/// </summary>
|
||||
public static class InteriorEntityPartition
|
||||
{
|
||||
internal enum ProjectionClass : byte
|
||||
{
|
||||
OutdoorStatic,
|
||||
CellStatic,
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
internal interface IObserver
|
||||
{
|
||||
void BeginFrame();
|
||||
|
||||
void Observe(
|
||||
uint landblockId,
|
||||
WorldEntity entity,
|
||||
ProjectionClass projectionClass);
|
||||
|
||||
void Complete(Result result);
|
||||
|
||||
void AbortFrame();
|
||||
}
|
||||
|
||||
public sealed class Result
|
||||
{
|
||||
public Dictionary<uint, List<WorldEntity>> ByCell { get; } = new();
|
||||
|
|
@ -143,6 +164,79 @@ public static class InteriorEntityPartition
|
|||
result.PruneEmptyCellBuckets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current-path referee overload. The observer sees the exact branch that
|
||||
/// populated each accepted partition entry, including its owning
|
||||
/// landblock. A null observer is the production fast path and performs no
|
||||
/// diagnostic allocation or hashing.
|
||||
/// </summary>
|
||||
internal static void Partition(
|
||||
Result result,
|
||||
HashSet<uint> visibleCells,
|
||||
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> landblockEntries,
|
||||
IObserver? observer)
|
||||
{
|
||||
if (observer is null)
|
||||
{
|
||||
Partition(result, visibleCells, landblockEntries);
|
||||
return;
|
||||
}
|
||||
|
||||
observer.BeginFrame();
|
||||
try
|
||||
{
|
||||
result.ClearForReuse();
|
||||
foreach (var entry in landblockEntries)
|
||||
{
|
||||
foreach (var e in entry.Entities)
|
||||
{
|
||||
if (e.MeshRefs.Count == 0) continue;
|
||||
|
||||
// Retail contract: every server-spawned entity is a
|
||||
// DYNAMIC and draws in the last pass, regardless of cell.
|
||||
if (e.ServerGuid != 0)
|
||||
{
|
||||
result.Dynamics.Add(e);
|
||||
observer.Observe(
|
||||
entry.LandblockId,
|
||||
e,
|
||||
ProjectionClass.Dynamic);
|
||||
}
|
||||
else if (e.ParentCellId is uint cell && IsIndoorCellId(cell))
|
||||
{
|
||||
if (!visibleCells.Contains(cell))
|
||||
continue;
|
||||
if (!result.ByCell.TryGetValue(cell, out var list))
|
||||
result.ByCell[cell] = list = new List<WorldEntity>();
|
||||
list.Add(e);
|
||||
observer.Observe(
|
||||
entry.LandblockId,
|
||||
e,
|
||||
ProjectionClass.CellStatic);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.OutdoorStatic.Add(e);
|
||||
observer.Observe(
|
||||
entry.LandblockId,
|
||||
e,
|
||||
ProjectionClass.OutdoorStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.PruneEmptyCellBuckets();
|
||||
observer.Complete(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
observer.AbortFrame();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Shared indoor classification — keep DrawDynamicsLast, the
|
||||
/// outside-stage assignment (#118), and the partition in lockstep.</summary>
|
||||
public static bool IsIndoorCellId(uint cellId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue