feat(physics): Phase 2 — BuildingPhysics + CheckBuildingTransit
Closes the outdoor→indoor entry path. New BuildingPhysics type holds the per-SortCell BldPortal list + building world transform; PhysicsDataCache caches it (CacheBuilding + GetBuilding); CellTransit.CheckBuildingTransit tests each portal's destination cell via PointInsideCellBsp. PhysicsEngine.ResolveCellId's outdoor branch now hooks CheckBuildingTransit after the terrain-grid lookup: if the matched landcell has a cached building stab, check whether the sphere has crossed into one of its interior EnvCells before returning. GameWindow at landblock-load time iterates LandBlockInfo.Buildings and caches each via PhysicsDataCache.CacheBuilding. The landcell-id derivation uses retail's row-major cell-index formula (gridX * 8 + gridY + 1). Polish items from Subagent B/C reviews folded in: - visited HashSet in FindCellList's BFS (avoids O(N^2) re-enqueue) - ResolveCellId_NoDataCache_ReturnsFallback test (closes coverage gap) - DataCache-asymmetry comment in PhysicsEngine.ResolveCellId - Replaced misleading FindCellList outdoor-branch TODO with explicit note that ResolveCellId bypasses this branch — wired in ResolveCellId directly. - Removed unused 'using DatReaderWriter.Types;' from CellTransit.cs - 2 new CellTransitFindCellListTests integration tests - 1 new CellTransitCheckBuildingTransitTests test (null-CellBSP guard case; happy path deferred to visual verification). Spec: docs/superpowers/specs/2026-05-19-indoor-portal-cell-tracking-design.md Plan: docs/superpowers/plans/2026-05-19-indoor-portal-cell-tracking.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aad697602e
commit
069534a372
8 changed files with 301 additions and 7 deletions
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
|
|
@ -151,6 +150,36 @@ public static class CellTransit
|
|||
candidates.Add(lbPrefix | low);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outdoor→indoor entry path. Ported from retail's
|
||||
/// <c>BuildingObj::find_building_transit_cells</c> +
|
||||
/// <c>EnvCell::check_building_transit</c>. For each portal of the
|
||||
/// outdoor building, look up the destination interior cell and test
|
||||
/// whether the sphere center is inside it via
|
||||
/// <see cref="BSPQuery.PointInsideCellBsp"/>. If so, add the interior
|
||||
/// cell to <paramref name="candidates"/>.
|
||||
/// </summary>
|
||||
public static void CheckBuildingTransit(
|
||||
PhysicsDataCache cache,
|
||||
BuildingPhysics building,
|
||||
Vector3 worldSphereCenter,
|
||||
float sphereRadius,
|
||||
HashSet<uint> candidates)
|
||||
{
|
||||
foreach (var portal in building.Portals)
|
||||
{
|
||||
var otherCell = cache.GetCellStruct(portal.OtherCellId);
|
||||
if (otherCell?.CellBSP?.Root is null) continue;
|
||||
|
||||
// Sphere center in the OTHER cell's local space.
|
||||
var localCenter = Vector3.Transform(worldSphereCenter, otherCell.InverseWorldTransform);
|
||||
if (BSPQuery.PointInsideCellBsp(otherCell.CellBSP.Root, localCenter))
|
||||
{
|
||||
candidates.Add(portal.OtherCellId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Top-level cell-tracking driver, ported from retail's
|
||||
/// <c>CObjCell::find_cell_list</c> (sphere variant).
|
||||
|
|
@ -188,7 +217,9 @@ public static class CellTransit
|
|||
|
||||
// BFS the portal graph (one hop per pass — usually 1-2 passes is enough).
|
||||
var pending = new Queue<uint>();
|
||||
var visited = new HashSet<uint>();
|
||||
pending.Enqueue(currentCellId);
|
||||
visited.Add(currentCellId);
|
||||
int maxIterations = 16; // hard cap; portal graphs are small
|
||||
while (pending.Count > 0 && maxIterations-- > 0)
|
||||
{
|
||||
|
|
@ -203,10 +234,9 @@ public static class CellTransit
|
|||
|
||||
if (candidates.Count > sizeBefore)
|
||||
{
|
||||
// Snapshot the new candidates to avoid mutating during iteration.
|
||||
foreach (var c in candidates)
|
||||
{
|
||||
if (c != cellId) // skip seed
|
||||
if (visited.Add(c)) // only enqueue if NEW
|
||||
pending.Enqueue(c);
|
||||
}
|
||||
}
|
||||
|
|
@ -220,9 +250,28 @@ public static class CellTransit
|
|||
}
|
||||
else
|
||||
{
|
||||
// Outdoor seed.
|
||||
// Outdoor seed: expand neighbour landcells AND check for building stabs
|
||||
// with portals into interior EnvCells.
|
||||
AddAllOutsideCells(worldSphereCenter, sphereRadius, currentCellId, candidates);
|
||||
// Outdoor→indoor entry (CheckBuildingTransit) wires in a follow-up commit.
|
||||
|
||||
// For each landcell candidate, see if it carries a building stab; if so,
|
||||
// check whether the sphere has crossed into any of the building's interior
|
||||
// EnvCells via CheckBuildingTransit.
|
||||
//
|
||||
// NOTE: PhysicsEngine.ResolveCellId currently bypasses this entire branch
|
||||
// for outdoor seeds (it uses its own _landblocks terrain grid loop). The
|
||||
// outdoor→indoor production path therefore runs through ResolveCellId's
|
||||
// OWN outdoor branch (see below for the call there too). This block is
|
||||
// exercised by direct-FindCellList callers (tests, future re-entry from
|
||||
// an indoor cell exiting through a portal that lands outside near a
|
||||
// building).
|
||||
var landcellSnapshot = new List<uint>(candidates);
|
||||
foreach (uint landcellId in landcellSnapshot)
|
||||
{
|
||||
var building = cache.GetBuilding(landcellId);
|
||||
if (building is null) continue;
|
||||
CheckBuildingTransit(cache, building, worldSphereCenter, sphereRadius, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
// Containment test: for each candidate, transform worldSphereCenter to
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue