using System.Numerics;
using AcDream.Core.Selection;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Rendering.Selection;
///
/// Decodes the exact CPU geometry consumed by retail mouse selection. The broad
/// sphere comes from the drawing-BSP root; polygons retain GfxObj DAT order.
///
internal sealed class RetailSelectionGeometryCache
{
private readonly DatCollection _dats;
private readonly object _datLock;
// Render-thread owned. Dictionary permits a cached null for a GfxObj which
// legitimately has no drawing BSP; ConcurrentDictionary does not.
private readonly Dictionary _cache = new();
public RetailSelectionGeometryCache(DatCollection dats, object datLock)
{
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
_datLock = datLock ?? throw new ArgumentNullException(nameof(datLock));
}
public RetailSelectionMesh? Resolve(uint gfxObjId)
{
if (_cache.TryGetValue(gfxObjId, out var cached))
return cached;
RetailSelectionMesh? loaded = Load(gfxObjId);
_cache[gfxObjId] = loaded;
return loaded;
}
private RetailSelectionMesh? Load(uint gfxObjId)
{
GfxObj? gfx;
lock (_datLock)
gfx = _dats.Get(gfxObjId);
var root = gfx?.DrawingBSP?.Root;
if (gfx is null || root is null || root.BoundingSphere.Radius <= 0f)
return null;
var polygons = new List(gfx.Polygons.Count);
foreach (var entry in gfx.Polygons)
{
var source = entry.Value;
if (source.VertexIds.Count < 3)
continue;
var vertices = new Vector3[source.VertexIds.Count];
bool valid = true;
for (int i = 0; i < source.VertexIds.Count; i++)
{
if (!gfx.VertexArray.Vertices.TryGetValue((ushort)source.VertexIds[i], out var vertex))
{
valid = false;
break;
}
vertices[i] = vertex.Origin;
}
if (!valid)
continue;
polygons.Add(new RetailSelectionPolygon(
vertices,
SingleSided: (int)source.SidesType == 0));
}
return new RetailSelectionMesh(
root.BoundingSphere.Origin,
root.BoundingSphere.Radius,
polygons);
}
}