fix(render): #113 root cause #2 - GfxObj meshes draw only DrawingBSP-referenced polys (the REAL phantom staircase)
The user gate + bisect overturned the coincident-cell attribution: the phantom staircase persists in the PRE-session build (bisect screenshot at the hall wall) and is drawn by the ENTITY pipeline, untouched by any clip. Root cause (dat-proven, DumpHallModel_PolyFlagHistogram): retail renders a GfxObj by TRAVERSING its drawing BSP (D3DPolyRender); polygons present in the Polygons dictionary but referenced by NO DrawingBSP node are never drawn - they are physics/no-draw geometry. The Holtburg meeting hall (0x010014C3) keeps its exterior stair-ramp as dictionary polys 0+1: in the PhysicsBSP (ACE walks The Sentry on it at z 117-118; invisible-but- walkable in retail) but orphaned from the draw tree (true at ALL degrade levels - the LOD theory is dead, Degrades[0] IS the base model). The hill cottage (0x01000827) carries 8 such orphans. Our extraction iterated the dictionary -> drew the collision skeleton: the wall staircase up close, the flying stairs over the cottage roofline from afar (orphan ramp spans world 221-232 at z 116-124.5; visible over the cottage roof from the west). Fix: PrepareGfxObjMeshData filters to CollectDrawingBspPolygonIds(gfxObj) when a drawing BSP exists; models without one draw everything (unchanged). Physics untouched (collision keeps the full physics set - retail parity). CellStruct extraction not touched (different conventions; no orphan evidence there yet). Dat-backed pins: Issue113DrawingBspFilterTests (hall orphans == 0+1, cottage orphans == 0..7). Suites: App 226 / Core 1392 + the 4 pre-existing #99-era failures / UI 420 / Net 294. Note: the earlier shell-clip enable (927fd8f, scoped9ce335e) remains correct and orthogonal - it crops interior CELL geometry to apertures outdoors; this commit removes the phantom SHELL geometry at its source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6c9bbce433
commit
e46d3d9273
3 changed files with 202 additions and 1 deletions
|
|
@ -988,6 +988,27 @@ namespace AcDream.App.Rendering.Wb {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #113: the set of polygon ids referenced by the GfxObj's drawing BSP —
|
||||
/// the polys retail actually renders (D3DPolyRender traverses the BSP;
|
||||
/// dictionary-orphaned polys are physics/no-draw geometry). Returns null
|
||||
/// when the model has no drawing BSP (caller draws everything).
|
||||
/// </summary>
|
||||
internal static HashSet<ushort>? CollectDrawingBspPolygonIds(GfxObj gfxObj) {
|
||||
if (gfxObj.DrawingBSP?.Root is null) return null;
|
||||
var ids = new HashSet<ushort>();
|
||||
CollectDrawingBspPolygonIds(gfxObj.DrawingBSP.Root, ids);
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static void CollectDrawingBspPolygonIds(DatReaderWriter.Types.DrawingBSPNode node, HashSet<ushort> ids) {
|
||||
if (node.Polygons is not null)
|
||||
foreach (var pid in node.Polygons)
|
||||
ids.Add((ushort)pid);
|
||||
if (node.PosNode is not null) CollectDrawingBspPolygonIds(node.PosNode, ids);
|
||||
if (node.NegNode is not null) CollectDrawingBspPolygonIds(node.NegNode, ids);
|
||||
}
|
||||
|
||||
private ObjectMeshData? PrepareGfxObjMeshData(ulong id, GfxObj gfxObj, Vector3 scale, CancellationToken ct) {
|
||||
var vertices = new List<VertexPositionNormalTexture>();
|
||||
var UVLookup = new Dictionary<(ushort vertId, ushort uvIdx, bool isNeg), ushort>();
|
||||
|
|
@ -996,8 +1017,23 @@ namespace AcDream.App.Rendering.Wb {
|
|||
var (min, max) = ComputeBounds(gfxObj, scale);
|
||||
var boundingBox = new BoundingBox(min, max);
|
||||
|
||||
foreach (var poly in gfxObj.Polygons.Values) {
|
||||
// #113 (2026-06-11): retail draws a GfxObj by TRAVERSING its drawing
|
||||
// BSP — a polygon present in the Polygons dictionary but referenced by
|
||||
// no DrawingBSP node is never rendered (physics/no-draw geometry).
|
||||
// The Holtburg meeting hall (0x010014C3) keeps its walkable exterior
|
||||
// stair-ramp as dictionary polys {0,1}: in the PhysicsBSP (NPCs walk
|
||||
// it) but absent from every DrawingBSP node — retail shows a plain
|
||||
// wall; iterating the dictionary drew the "phantom staircase"
|
||||
// (invisible-but-walkable in retail, visible in acdream). The hill
|
||||
// cottage (0x01000827) carries 8 such orphans. Filter to the BSP-
|
||||
// referenced set when a drawing BSP exists; models without one draw
|
||||
// everything (unchanged).
|
||||
var drawnPolyIds = CollectDrawingBspPolygonIds(gfxObj);
|
||||
|
||||
foreach (var polyEntry in gfxObj.Polygons) {
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (drawnPolyIds is not null && !drawnPolyIds.Contains(polyEntry.Key)) continue;
|
||||
var poly = polyEntry.Value;
|
||||
if (poly.VertexIds.Count < 3) continue;
|
||||
|
||||
// Handle Positive Surface
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue