feat(physics): add differential flat BSP traversal
Port every current containment, overlap, walkable, and six-path moving-collision query to immutable integer-indexed assets behind a graph-authoritative referee. Exact synthetic, installed-DAT, complete-resolver, and zero-allocation gates prove bit-identical behavior before connected dual publication. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
9cd42417a8
commit
f7ff9f4eea
13 changed files with 3907 additions and 46 deletions
|
|
@ -96,7 +96,7 @@ public static class BSPQuery
|
|||
///
|
||||
/// <para>ACE: Polygon.cs polygon_hits_sphere_precise.</para>
|
||||
/// </summary>
|
||||
private static bool PolygonHitsSpherePrecise(
|
||||
internal static bool PolygonHitsSpherePrecise(
|
||||
in Plane polyPlane,
|
||||
ReadOnlySpan<Vector3> verts,
|
||||
Vector3 sphereCenter,
|
||||
|
|
@ -2339,6 +2339,31 @@ public static class BSPQuery
|
|||
ref hitPolyId, ref hitNormal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocation-free graph-oracle seam used by the Slice I flat traversal
|
||||
/// differential harness.
|
||||
/// </summary>
|
||||
internal static bool SphereIntersectsPoly(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
Vector3 sphereCenter,
|
||||
float sphereRadius,
|
||||
out ushort hitPolyId,
|
||||
out Vector3 hitNormal)
|
||||
{
|
||||
hitPolyId = 0;
|
||||
hitNormal = Vector3.Zero;
|
||||
if (node is null) return false;
|
||||
|
||||
return SphereIntersectsPolyStaticRecurse(
|
||||
node,
|
||||
resolved,
|
||||
sphereCenter,
|
||||
sphereRadius,
|
||||
ref hitPolyId,
|
||||
ref hitNormal);
|
||||
}
|
||||
|
||||
private static bool SphereIntersectsPolyStaticRecurse(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
|
|
@ -2431,6 +2456,38 @@ public static class BSPQuery
|
|||
return hitTime < float.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocation-free graph-oracle seam used by the Slice I flat traversal
|
||||
/// differential harness.
|
||||
/// </summary>
|
||||
internal static bool SphereIntersectsPolyWithTime(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
Vector3 sphereCenter,
|
||||
float sphereRadius,
|
||||
Vector3 movement,
|
||||
out ushort hitPolyId,
|
||||
out Vector3 hitNormal,
|
||||
out float hitTime)
|
||||
{
|
||||
hitPolyId = 0;
|
||||
hitNormal = Vector3.Zero;
|
||||
hitTime = float.MaxValue;
|
||||
if (node is null) return false;
|
||||
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node,
|
||||
resolved,
|
||||
sphereCenter,
|
||||
sphereRadius,
|
||||
movement,
|
||||
ref hitPolyId,
|
||||
ref hitNormal,
|
||||
ref hitTime);
|
||||
|
||||
return hitTime < float.MaxValue;
|
||||
}
|
||||
|
||||
private static void SphereIntersectsPolyWithTimeRecurse(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
|
|
|
|||
|
|
@ -159,15 +159,19 @@ public static class CellTransit
|
|||
// neighbour cell whether the sphere intersects its CellBSP.
|
||||
// The portal-plane side test is only the unloaded-cell load hint.
|
||||
var otherCell = cache.GetCellStruct(otherId);
|
||||
if (otherCell?.CellBSP?.Root is not null)
|
||||
if (otherCell is not null &&
|
||||
CollisionTraversal.HasCellContainment(cache, otherCell))
|
||||
{
|
||||
for (int i = 0; i < sphereCount; i++)
|
||||
{
|
||||
var sphere = worldSpheres[i];
|
||||
var otherLocalCenter = Vector3.Transform(
|
||||
sphere.Origin, otherCell.InverseWorldTransform);
|
||||
bool hit = BSPQuery.SphereIntersectsCellBsp(
|
||||
otherCell.CellBSP.Root, otherLocalCenter, sphere.Radius);
|
||||
bool hit = CollisionTraversal.SphereIntersectsCell(
|
||||
cache,
|
||||
otherCell,
|
||||
otherLocalCenter,
|
||||
sphere.Radius);
|
||||
if (hit)
|
||||
{
|
||||
candidates.Add(otherId);
|
||||
|
|
@ -372,7 +376,8 @@ public static class CellTransit
|
|||
continue;
|
||||
|
||||
var otherCell = cache.GetCellStruct(portal.OtherCellId);
|
||||
if (otherCell?.CellBSP?.Root is null)
|
||||
if (otherCell is null ||
|
||||
!CollisionTraversal.HasCellContainment(cache, otherCell))
|
||||
{
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
|
|
@ -398,8 +403,11 @@ public static class CellTransit
|
|||
{
|
||||
var sphere = worldSpheres[i];
|
||||
var localCenter = Vector3.Transform(sphere.Origin, otherCell.InverseWorldTransform);
|
||||
inside = BSPQuery.SphereIntersectsCellBsp(
|
||||
otherCell.CellBSP.Root, localCenter, sphere.Radius);
|
||||
inside = CollisionTraversal.SphereIntersectsCell(
|
||||
cache,
|
||||
otherCell,
|
||||
localCenter,
|
||||
sphere.Radius);
|
||||
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
|
|
@ -599,19 +607,25 @@ public static class CellTransit
|
|||
if (start is null) return 0u;
|
||||
|
||||
// this->point_in_cell(point) → return this (:311402-311405)
|
||||
if (PointInCell(start, worldPoint)) return startCellId;
|
||||
if (PointInCell(cache, start, worldPoint)) return startCellId;
|
||||
|
||||
if (useStabList)
|
||||
{
|
||||
// arg3 != 0 → iterate stab_list, GetVisible + point_in_cell (:311444-311465)
|
||||
foreach (uint id in start.VisibleCellIds)
|
||||
if (PointInCell(cache.GetCellStruct(id), worldPoint)) return id;
|
||||
if (PointInCell(cache, cache.GetCellStruct(id), worldPoint)) return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// arg3 == 0 → iterate direct portals, GetOtherCell + point_in_cell (:311411-311434)
|
||||
foreach (var portal in start.Portals)
|
||||
if (PointInCell(cache.GetCellStruct(portal.OtherCellId), worldPoint)) return portal.OtherCellId;
|
||||
if (PointInCell(
|
||||
cache,
|
||||
cache.GetCellStruct(portal.OtherCellId),
|
||||
worldPoint))
|
||||
{
|
||||
return portal.OtherCellId;
|
||||
}
|
||||
}
|
||||
|
||||
return 0u;
|
||||
|
|
@ -623,11 +637,19 @@ public static class CellTransit
|
|||
/// A cell with no hydrated <see cref="CellPhysics.CellBSP"/> returns false (see
|
||||
/// <see cref="FindVisibleChildCell"/>'s adaptation note).
|
||||
/// </summary>
|
||||
private static bool PointInCell(CellPhysics? cell, Vector3 worldPoint)
|
||||
private static bool PointInCell(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics? cell,
|
||||
Vector3 worldPoint)
|
||||
{
|
||||
if (cell?.CellBSP?.Root is null) return false;
|
||||
if (cell is null ||
|
||||
!CollisionTraversal.HasCellContainment(cache, cell))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var local = Vector3.Transform(worldPoint, cell.InverseWorldTransform);
|
||||
return BSPQuery.PointInsideCellBsp(cell.CellBSP.Root, local);
|
||||
return CollisionTraversal.PointInsideCell(cache, cell, local);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -901,9 +923,14 @@ public static class CellTransit
|
|||
{
|
||||
// Interior candidate — point_in_cell via the cell BSP (vtable[0x84]).
|
||||
var cand = cache.GetCellStruct(candId);
|
||||
if (cand?.CellBSP?.Root is null) continue;
|
||||
if (cand is null ||
|
||||
!CollisionTraversal.HasCellContainment(cache, cand))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = Vector3.Transform(worldSphereCenter, cand.InverseWorldTransform);
|
||||
if (BSPQuery.PointInsideCellBsp(cand.CellBSP.Root, local))
|
||||
if (CollisionTraversal.PointInsideCell(cache, cand, local))
|
||||
return candId; // interior-wins, stop (pseudo_c:308819)
|
||||
}
|
||||
else if (outdoorResult == 0u && containingOutdoorId != 0u && outdoorPickAllowed)
|
||||
|
|
@ -950,10 +977,15 @@ public static class CellTransit
|
|||
if (currentLow >= 0x0100u)
|
||||
{
|
||||
var cur = cache.GetCellStruct(currentCellId);
|
||||
if (cur?.CellBSP?.Root is not null)
|
||||
if (cur is not null &&
|
||||
CollisionTraversal.HasCellContainment(cache, cur))
|
||||
{
|
||||
var curLocal = Vector3.Transform(worldSphereCenter, cur.InverseWorldTransform);
|
||||
if (!BSPQuery.SphereIntersectsCellBsp(cur.CellBSP.Root, curLocal, sphereRadius))
|
||||
if (!CollisionTraversal.SphereIntersectsCell(
|
||||
cache,
|
||||
cur,
|
||||
curLocal,
|
||||
sphereRadius))
|
||||
{
|
||||
uint recovered = FindVisibleChildCell(
|
||||
cache, currentCellId, worldSphereCenter, useStabList: true);
|
||||
|
|
|
|||
226
src/AcDream.Core/Physics/CollisionTraversal.cs
Normal file
226
src/AcDream.Core/Physics/CollisionTraversal.cs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary Slice I referee mode. Graph remains production-authoritative;
|
||||
/// Flat is enabled only by differential tests until dual publication and the
|
||||
/// connected shadow gate complete.
|
||||
/// </summary>
|
||||
internal enum CollisionTraversalMode
|
||||
{
|
||||
Graph,
|
||||
Flat,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Narrow representation switch used while the graph and flat collision
|
||||
/// worlds coexist. I6 removes the graph branch after acceptance.
|
||||
/// </summary>
|
||||
internal static class CollisionTraversal
|
||||
{
|
||||
internal static bool HasCellContainment(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment");
|
||||
return flat.RootIndex >= 0;
|
||||
}
|
||||
|
||||
return cell.CellBSP?.Root is not null;
|
||||
}
|
||||
|
||||
internal static bool HasPhysics(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
||||
throw MissingFlat("cell physics");
|
||||
return flat.RootIndex >= 0;
|
||||
}
|
||||
|
||||
return cell.BSP?.Root is not null;
|
||||
}
|
||||
|
||||
internal static bool HasPhysics(
|
||||
PhysicsDataCache cache,
|
||||
GfxObjPhysics gfxObject)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
|
||||
throw MissingFlat("GfxObj physics");
|
||||
return flat.RootIndex >= 0;
|
||||
}
|
||||
|
||||
return gfxObject.BSP.Root is not null;
|
||||
}
|
||||
|
||||
internal static FlatCollisionSphere RootBoundingSphere(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
||||
throw MissingFlat("cell physics");
|
||||
return flat.Nodes[flat.RootIndex].BoundingSphere;
|
||||
}
|
||||
|
||||
DatReaderWriter.Types.Sphere sphere = cell.BSP!.Root!.BoundingSphere;
|
||||
return new FlatCollisionSphere(sphere.Origin, sphere.Radius);
|
||||
}
|
||||
|
||||
internal static bool PointInsideCell(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell,
|
||||
Vector3 localPoint)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment");
|
||||
return FlatBspQuery.PointInsideCellBsp(flat, localPoint);
|
||||
}
|
||||
|
||||
return BSPQuery.PointInsideCellBsp(cell.CellBSP?.Root, localPoint);
|
||||
}
|
||||
|
||||
internal static bool SphereIntersectsCell(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell,
|
||||
Vector3 localCenter,
|
||||
float radius)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment");
|
||||
return FlatBspQuery.SphereIntersectsCellBsp(
|
||||
flat,
|
||||
localCenter,
|
||||
radius);
|
||||
}
|
||||
|
||||
return BSPQuery.SphereIntersectsCellBsp(
|
||||
cell.CellBSP?.Root,
|
||||
localCenter,
|
||||
radius);
|
||||
}
|
||||
|
||||
internal static TransitionState FindCollisions(
|
||||
PhysicsDataCache cache,
|
||||
CellPhysics cell,
|
||||
Transition transition,
|
||||
Vector3 localSphereCenter,
|
||||
float localSphereRadius,
|
||||
bool hasLocalSphere1,
|
||||
Vector3 localSphere1Center,
|
||||
float localSphere1Radius,
|
||||
Vector3 localCurrentCenter,
|
||||
Vector3 localSpaceZ,
|
||||
float scale,
|
||||
Quaternion localToWorld,
|
||||
PhysicsEngine? engine,
|
||||
Vector3 worldOrigin)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
||||
throw MissingFlat("cell physics");
|
||||
return FlatBspQuery.FindCollisions(
|
||||
flat,
|
||||
transition,
|
||||
localSphereCenter,
|
||||
localSphereRadius,
|
||||
hasLocalSphere1,
|
||||
localSphere1Center,
|
||||
localSphere1Radius,
|
||||
localCurrentCenter,
|
||||
localSpaceZ,
|
||||
scale,
|
||||
localToWorld,
|
||||
engine,
|
||||
worldOrigin);
|
||||
}
|
||||
|
||||
return BSPQuery.FindCollisions(
|
||||
cell.BSP?.Root,
|
||||
cell.Resolved,
|
||||
transition,
|
||||
localSphereCenter,
|
||||
localSphereRadius,
|
||||
hasLocalSphere1,
|
||||
localSphere1Center,
|
||||
localSphere1Radius,
|
||||
localCurrentCenter,
|
||||
localSpaceZ,
|
||||
scale,
|
||||
localToWorld,
|
||||
engine,
|
||||
worldOrigin);
|
||||
}
|
||||
|
||||
internal static TransitionState FindCollisions(
|
||||
PhysicsDataCache cache,
|
||||
GfxObjPhysics gfxObject,
|
||||
Transition transition,
|
||||
Vector3 localSphereCenter,
|
||||
float localSphereRadius,
|
||||
bool hasLocalSphere1,
|
||||
Vector3 localSphere1Center,
|
||||
float localSphere1Radius,
|
||||
Vector3 localCurrentCenter,
|
||||
Vector3 localSpaceZ,
|
||||
float scale,
|
||||
Quaternion localToWorld,
|
||||
PhysicsEngine? engine,
|
||||
Vector3 worldOrigin)
|
||||
{
|
||||
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
|
||||
{
|
||||
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
|
||||
throw MissingFlat("GfxObj physics");
|
||||
return FlatBspQuery.FindCollisions(
|
||||
flat,
|
||||
transition,
|
||||
localSphereCenter,
|
||||
localSphereRadius,
|
||||
hasLocalSphere1,
|
||||
localSphere1Center,
|
||||
localSphere1Radius,
|
||||
localCurrentCenter,
|
||||
localSpaceZ,
|
||||
scale,
|
||||
localToWorld,
|
||||
engine,
|
||||
worldOrigin);
|
||||
}
|
||||
|
||||
return BSPQuery.FindCollisions(
|
||||
gfxObject.BSP.Root,
|
||||
gfxObject.Resolved,
|
||||
transition,
|
||||
localSphereCenter,
|
||||
localSphereRadius,
|
||||
hasLocalSphere1,
|
||||
localSphere1Center,
|
||||
localSphere1Radius,
|
||||
localCurrentCenter,
|
||||
localSpaceZ,
|
||||
scale,
|
||||
localToWorld,
|
||||
engine,
|
||||
worldOrigin);
|
||||
}
|
||||
|
||||
private static InvalidOperationException MissingFlat(string kind) =>
|
||||
new(
|
||||
$"Flat collision traversal requires a prepared {kind} asset. " +
|
||||
"Gameplay must not fall back silently.");
|
||||
}
|
||||
2132
src/AcDream.Core/Physics/FlatBspQuery.cs
Normal file
2132
src/AcDream.Core/Physics/FlatBspQuery.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -22,6 +22,14 @@ public sealed class PhysicsDataCache
|
|||
private readonly ConcurrentDictionary<uint, SetupPhysics> _setup = new();
|
||||
private readonly ConcurrentDictionary<uint, CellPhysics> _cellStruct = new();
|
||||
|
||||
/// <summary>
|
||||
/// Slice I graph/flat differential selector. Production remains on the
|
||||
/// graph oracle until I6; tests use the flat value to run the same complete
|
||||
/// resolver from cloned inputs.
|
||||
/// </summary>
|
||||
internal CollisionTraversalMode CollisionTraversalMode { get; set; } =
|
||||
CollisionTraversalMode.Graph;
|
||||
|
||||
// ── Phase 2: building portal cache for outdoor→indoor entry ───────────
|
||||
private readonly ConcurrentDictionary<uint, BuildingPhysics> _buildings = new();
|
||||
|
||||
|
|
@ -541,6 +549,12 @@ public sealed class GfxObjPhysics
|
|||
/// Populated once at cache time so BSP queries don't pay per-test lookup cost.
|
||||
/// </summary>
|
||||
public required Dictionary<ushort, ResolvedPolygon> Resolved { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Prepared integer-indexed shadow representation. Optional until Slice I5
|
||||
/// dual publication is complete.
|
||||
/// </summary>
|
||||
public FlatPhysicsBsp? FlatPhysicsBsp { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Cached collision shape data for a Setup (character/creature capsule).</summary>
|
||||
|
|
@ -578,6 +592,11 @@ public sealed class CellPhysics
|
|||
/// </summary>
|
||||
public required Dictionary<ushort, ResolvedPolygon> Resolved { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Prepared integer-indexed physics BSP shadow. Optional until Slice I5.
|
||||
/// </summary>
|
||||
public FlatPhysicsBsp? FlatPhysicsBsp { get; init; }
|
||||
|
||||
// ── Indoor walking Phase 2 (2026-05-19): portal-graph fields ───────
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -590,6 +609,12 @@ public sealed class CellPhysics
|
|||
/// </summary>
|
||||
public DatReaderWriter.Types.CellBSPTree? CellBSP { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Prepared integer-indexed cell-containment BSP shadow. Optional until
|
||||
/// Slice I5.
|
||||
/// </summary>
|
||||
public FlatCellContainmentBsp? FlatContainmentBsp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Portal connections to neighbouring cells, in cell-local space.
|
||||
/// Default: empty list. Source: <c>envCell.CellPortals</c>.
|
||||
|
|
|
|||
|
|
@ -2298,7 +2298,11 @@ public sealed class Transition
|
|||
|
||||
var cell = engine.DataCache.GetCellStruct(cellId);
|
||||
// R2 guard: stale CellPhysics loaded for render but not physics.
|
||||
if (cell?.BSP?.Root is null) continue;
|
||||
if (cell is null ||
|
||||
!CollisionTraversal.HasPhysics(engine.DataCache, cell))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transform sphere into THIS cell's local space. Mirrors the
|
||||
// primary-cell pattern at TransitionTypes.cs (FindEnvCollisions,
|
||||
|
|
@ -2330,8 +2334,8 @@ public sealed class Transition
|
|||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
PhysicsDiagnostics.LastBspHitPoly = null;
|
||||
|
||||
var result = BSPQuery.FindCollisions(
|
||||
cell.BSP.Root, cell.Resolved, this,
|
||||
var result = CollisionTraversal.FindCollisions(
|
||||
engine.DataCache!, cell, this,
|
||||
localCenter,
|
||||
sphereRadius,
|
||||
hasLocalSphere1,
|
||||
|
|
@ -2348,7 +2352,10 @@ public sealed class Transition
|
|||
? "poly=n/a"
|
||||
: System.FormattableString.Invariant(
|
||||
$"poly=0x{hit.Id:X4} n=({hit.Plane.Normal.X:F3},{hit.Plane.Normal.Y:F3},{hit.Plane.Normal.Z:F3}) d={hit.Plane.D:F3} sides={hit.SidesType}");
|
||||
var bs = cell.BSP.Root.BoundingSphere;
|
||||
FlatCollisionSphere bs =
|
||||
CollisionTraversal.RootBoundingSphere(
|
||||
engine.DataCache,
|
||||
cell);
|
||||
string bsDesc = System.FormattableString.Invariant(
|
||||
$"bs=({bs.Origin.X:F3},{bs.Origin.Y:F3},{bs.Origin.Z:F3}) br={bs.Radius:F3}");
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
|
|
@ -2620,7 +2627,8 @@ public sealed class Transition
|
|||
if (cellLow >= 0x0100 && engine.DataCache is not null)
|
||||
{
|
||||
var cellPhysics = engine.DataCache.GetCellStruct(sp.CheckCellId);
|
||||
if (cellPhysics?.BSP?.Root is not null)
|
||||
if (cellPhysics is not null &&
|
||||
CollisionTraversal.HasPhysics(engine.DataCache!, cellPhysics))
|
||||
{
|
||||
// Transform player sphere to cell-local space.
|
||||
var localCenter = Vector3.Transform(footCenter, cellPhysics.InverseWorldTransform);
|
||||
|
|
@ -2677,9 +2685,9 @@ public sealed class Transition
|
|||
cellOrigin = cellPhysics.WorldTransform.Translation;
|
||||
}
|
||||
|
||||
var cellState = BSPQuery.FindCollisions(
|
||||
cellPhysics.BSP.Root,
|
||||
cellPhysics.Resolved,
|
||||
var cellState = CollisionTraversal.FindCollisions(
|
||||
engine.DataCache!,
|
||||
cellPhysics,
|
||||
this,
|
||||
localCenter,
|
||||
sphereRadius,
|
||||
|
|
@ -3134,12 +3142,14 @@ public sealed class Transition
|
|||
{
|
||||
Vector3 dxy = obj.Position - sp.GlobalCurrCenter[0].Origin;
|
||||
float distXY = MathF.Sqrt(dxy.X * dxy.X + dxy.Y * dxy.Y);
|
||||
bool cacheHit = physics?.BSP?.Root is not null;
|
||||
bool cacheHit = physics is not null &&
|
||||
CollisionTraversal.HasPhysics(engine.DataCache!, physics);
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[bsp-test] obj=0x{obj.EntityId:X8} gfx=0x{obj.GfxObjId:X8} state=0x{obj.State:X8} radius={obj.Radius:F3} pos=({obj.Position.X:F2},{obj.Position.Y:F2},{obj.Position.Z:F2}) distXY={distXY:F3} cacheHit={cacheHit}"));
|
||||
}
|
||||
|
||||
if (physics?.BSP?.Root is null)
|
||||
if (physics is null ||
|
||||
!CollisionTraversal.HasPhysics(engine.DataCache!, physics))
|
||||
{
|
||||
// Clear obstruction_ethereal before skipping — retail's per-object
|
||||
// clear (pc:276989) fires after shape tests; we clear early here to
|
||||
|
|
@ -3179,9 +3189,9 @@ public sealed class Transition
|
|||
// Use the retail 6-path dispatcher with pre-resolved polygons.
|
||||
// Pass the object's scale so collision response offsets (in
|
||||
// unscaled local space) are multiplied back to world space.
|
||||
result = BSPQuery.FindCollisions(
|
||||
physics.BSP.Root,
|
||||
physics.Resolved,
|
||||
result = CollisionTraversal.FindCollisions(
|
||||
engine.DataCache!,
|
||||
physics,
|
||||
this,
|
||||
localSphere0Center,
|
||||
localSphere0Radius,
|
||||
|
|
@ -3476,7 +3486,11 @@ public sealed class Transition
|
|||
// CacheBuilding site resolves 0x02 Setup models to their first part
|
||||
// — retail tests part_array->parts[0] only, 0x006b5320).
|
||||
var physics = engine.DataCache.GetGfxObj(building.ModelId);
|
||||
if (physics?.BSP?.Root is null) return TransitionState.OK;
|
||||
if (physics is null ||
|
||||
!CollisionTraversal.HasPhysics(engine.DataCache, physics))
|
||||
{
|
||||
return TransitionState.OK;
|
||||
}
|
||||
|
||||
var sp = SpherePath;
|
||||
var ci = CollisionInfo;
|
||||
|
|
@ -3509,9 +3523,9 @@ public sealed class Transition
|
|||
TransitionState result;
|
||||
try
|
||||
{
|
||||
result = BSPQuery.FindCollisions(
|
||||
physics.BSP.Root,
|
||||
physics.Resolved,
|
||||
result = CollisionTraversal.FindCollisions(
|
||||
engine.DataCache!,
|
||||
physics,
|
||||
this,
|
||||
localSphere0Center,
|
||||
localSphere0Radius,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue