using System.Collections.Generic; using System.Numerics; using AcDream.Core.World; namespace AcDream.App.Rendering; /// /// Splits a frame's landblock entities into the three draw buckets the per-cell /// needs, using the SAME precedence as /// : /// /// ServerGuid != 0 (player / NPCs / items / doors) ⇒ /// — drawn unclipped (depth only). These have no ParentCellId so they MUST be tested first. /// ParentCellId in the visible set ⇒ [cell] — per-cell, portal-clipped. /// ParentCellId == null (outdoor scenery / building shell) ⇒ /// — drawn through the doorway, clipped to OutsideView. /// /// A static whose ParentCellId is NOT in is dropped (its cell /// isn't drawn this frame). Entities with no MeshRefs are skipped. Pure; GL-free; unit-tested. /// public static class InteriorEntityPartition { public sealed class Result { public Dictionary> ByCell { get; } = new(); public List Outdoor { get; } = new(); public List LiveDynamic { get; } = new(); } public static Result Partition( HashSet visibleCells, IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList Entities, IReadOnlyDictionary? 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(); list.Add(e); } else // outdoor scenery / building shell { result.Outdoor.Add(e); } } } return result; } }