Replace the projected Setup-sphere rectangle and independent physics-wall ray with retail's render-coupled picker: only visible server-object parts participate, each exact drawing sphere broad-phases the camera-eye ray, and first-in-DAT-order visual polygon hits globally outrank sphere fallbacks. Replace the devtools-only procedural triangles with the retained gameplay VividTargetIndicator using retail client-enum surfaces 1..4, radar-blip colorization, Setup selection-sphere framing, and the exact eight-pixel viewport clamp. Release build succeeds with zero warnings and all 5,886 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
76 lines
2.5 KiB
C#
76 lines
2.5 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Selection;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
|
|
namespace AcDream.App.Rendering.Selection;
|
|
|
|
/// <summary>
|
|
/// Decodes the exact CPU geometry consumed by retail mouse selection. The broad
|
|
/// sphere comes from the drawing-BSP root; polygons retain GfxObj DAT order.
|
|
/// </summary>
|
|
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<uint, RetailSelectionMesh?> _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<GfxObj>(gfxObjId);
|
|
|
|
var root = gfx?.DrawingBSP?.Root;
|
|
if (gfx is null || root is null || root.BoundingSphere.Radius <= 0f)
|
|
return null;
|
|
|
|
var polygons = new List<RetailSelectionPolygon>(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);
|
|
}
|
|
}
|