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;
}
}