fix(physics): traverse prepared indoor portal topology

This commit is contained in:
Erik 2026-07-27 00:02:44 +02:00
parent b12d94047c
commit 4d095be286
9 changed files with 400 additions and 5 deletions

View file

@ -101,12 +101,21 @@ public static class CellTransit
uint lbPrefix = currentCellId & 0xFFFF0000u;
int sphereCount = EffectiveSphereCount(worldSpheres, numSpheres);
if (currentCell.PortalPolygons is null || sphereCount == 0) return;
if (sphereCount == 0) return;
foreach (var portal in currentCell.Portals)
for (int portalIndex = 0;
portalIndex < currentCell.Portals.Count;
portalIndex++)
{
if (!currentCell.PortalPolygons.TryGetValue(portal.PolygonId, out var poly))
PortalInfo portal = currentCell.Portals[portalIndex];
if (!TryGetPortalPlane(
currentCell,
portalIndex,
portal,
out Plane portalPlane))
{
continue;
}
if (portal.OtherCellId == 0xFFFF)
{
@ -142,7 +151,9 @@ public static class CellTransit
float pad = sphere.Radius + FEpsilon;
var localCenter = Vector3.Transform(
sphere.Origin, currentCell.InverseWorldTransform);
float dist = Vector3.Dot(localCenter, poly.Plane.Normal) + poly.Plane.D;
float dist =
Vector3.Dot(localCenter, portalPlane.Normal) +
portalPlane.D;
if (dist > -pad && dist < pad)
{
exitOutside = true;
@ -190,7 +201,9 @@ public static class CellTransit
float rad = sphere.Radius + EPSILON;
var localCenter = Vector3.Transform(
sphere.Origin, currentCell.InverseWorldTransform);
float dist = Vector3.Dot(localCenter, poly.Plane.Normal) + poly.Plane.D;
float dist =
Vector3.Dot(localCenter, portalPlane.Normal) +
portalPlane.D;
bool hit = portal.PortalSide ? dist > -rad : dist < rad;
if (hit)
{
@ -201,6 +214,64 @@ public static class CellTransit
}
}
/// <summary>
/// Resolves the portal plane from whichever immutable representation owns
/// this cell. Graph fixtures retain the DAT polygon dictionary; production
/// cells intentionally retain only the prepared topology's direct polygon
/// index and flat polygon table.
/// </summary>
private static bool TryGetPortalPlane(
CellPhysics cell,
int portalIndex,
PortalInfo portal,
out Plane plane)
{
if (cell.PortalPolygons is not null &&
cell.PortalPolygons.TryGetValue(
portal.PolygonId,
out ResolvedPolygon? polygon))
{
plane = polygon.Plane;
return true;
}
FlatEnvCellTopology? topology = cell.FlatTopology;
FlatPolygonTable? polygonTable = cell.FlatPortalPolygons;
if (topology is null || polygonTable is null)
{
plane = default;
return false;
}
if ((uint)portalIndex >= (uint)topology.Portals.Length)
{
throw new InvalidDataException(
$"Cell 0x{cell.SourceId:X8} portal {portalIndex} is absent " +
"from its prepared topology.");
}
FlatEnvCellPortal flatPortal = topology.Portals[portalIndex];
if (flatPortal.OtherCellId != portal.OtherCellId ||
flatPortal.PolygonId != portal.PolygonId ||
flatPortal.Flags != portal.Flags)
{
throw new InvalidDataException(
$"Cell 0x{cell.SourceId:X8} portal {portalIndex} does not " +
"match its prepared topology.");
}
int polygonIndex = flatPortal.PolygonIndex;
if ((uint)polygonIndex >= (uint)polygonTable.Polygons.Length)
{
throw new InvalidDataException(
$"Cell 0x{cell.SourceId:X8} portal {portalIndex} references " +
$"invalid prepared polygon index {polygonIndex}.");
}
plane = polygonTable.Polygons[polygonIndex].Plane;
return true;
}
/// <summary>
/// Outdoor neighbour expansion. Ported from
/// <c>CLandCell::add_all_outside_cells</c> (sphere variant,