using System.Collections.Generic; using System.Numerics; using AcDream.Core.World; namespace AcDream.App.Rendering; /// /// Splits a frame's landblock entities into the draw buckets used by the /// retail-style DrawInside flood. Indoor ownership wins for live dynamics too: /// a player, NPC, door, or item with a current indoor ParentCellId belongs to /// that cell's portal-clipped object list, not a global overlay pass. /// 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) { if (e.ParentCellId is uint liveCell) AddByCellOrOutdoor(e, liveCell, visibleCells, result); else result.LiveDynamic.Add(e); } else if (e.ParentCellId is uint cell) { AddByCellOrOutdoor(e, cell, visibleCells, result); } else { result.Outdoor.Add(e); } } } return result; } private static void AddByCellOrOutdoor( WorldEntity entity, uint cellId, HashSet visibleCells, Result result) { if (!IsIndoorCellId(cellId)) { result.Outdoor.Add(entity); return; } if (!visibleCells.Contains(cellId)) return; if (!result.ByCell.TryGetValue(cellId, out var list)) result.ByCell[cellId] = list = new List(); list.Add(entity); } private static bool IsIndoorCellId(uint cellId) { uint low = cellId & 0xFFFFu; return low >= 0x0100u && low != 0xFFFFu; } }