diff --git a/src/AcDream.Core/Physics/BSPQuery.cs b/src/AcDream.Core/Physics/BSPQuery.cs
index 9f2be66..df0af71 100644
--- a/src/AcDream.Core/Physics/BSPQuery.cs
+++ b/src/AcDream.Core/Physics/BSPQuery.cs
@@ -1152,11 +1152,14 @@ public static class BSPQuery
///
///
///
- /// Intended call site: indoor walkable-plane synthesis in
- /// Transition.TryFindIndoorWalkablePlane when the indoor cell-BSP
- /// collision returns OK (no wall hit) and the resolver still needs a
- /// ContactPlane to feed ValidateWalkable. Outdoor terrain has its own path
- /// () and does not use this.
+ /// Out-of-band "find a walkable plane indoors" entry point for callers
+ /// that genuinely need to query a cell's walkable floor (spawn-placement
+ /// validation, teleport-target verification, future debug overlays).
+ /// NOT called from the per-frame physics resolver — the original
+ /// per-frame caller (TryFindIndoorWalkablePlane) was deleted 2026-05-20
+ /// because retail's BSPTREE::find_collisions does NOT re-synthesize the
+ /// ContactPlane on the OK path. The wrapper is kept here as the
+ /// underlying retail-faithful walkable-finder API.
///
///
///
diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs
index c941f67..a1cc43c 100644
--- a/src/AcDream.Core/Physics/TransitionTypes.cs
+++ b/src/AcDream.Core/Physics/TransitionTypes.cs
@@ -1266,120 +1266,6 @@ public sealed class Transition
// Environment collision — outdoor terrain
// -----------------------------------------------------------------------
- ///
- /// Synthesize the indoor walkable contact plane for the player's current
- /// position when the cell BSP returns OK (no wall collision).
- ///
- ///
- /// Routes through the retail-faithful BSP walkable-finder
- /// () — which traverses the cell
- /// PhysicsBSP and picks the polygon closest to the foot along the up vector.
- /// Phase 2 commit eb0f772 introduced a linear first-match XY scan as a
- /// stop-gap; that scan picked the wrong floor whenever two polygons
- /// overlapped in XY at different Z (cellars, 2nd floors, balconies).
- ///
- ///
- ///
- /// Returns false if no walkable floor poly is found under the
- /// player. The caller falls through to outdoor terrain in that case
- /// (defensive backstop — should not normally happen inside a sealed cell).
- ///
- ///
- ///
- /// Retail oracle: BSPLEAF::find_walkable (acclient_2013_pseudo_c.txt:326793),
- /// BSPNODE::find_walkable (:326211), CPolygon::walkable_hits_sphere (:323006),
- /// CPolygon::adjust_sphere_to_plane (:322032).
- ///
- ///
- internal bool TryFindIndoorWalkablePlane(
- CellPhysics cellPhysics,
- Vector3 localFootCenter,
- float sphereRadius,
- out System.Numerics.Plane worldPlane,
- out Vector3[] worldVertices,
- out uint hitPolyId)
- {
- worldPlane = default;
- worldVertices = System.Array.Empty();
- hitPolyId = 0;
-
- if (cellPhysics.BSP?.Root is null) return false;
-
- // Build foot sphere in cell-local space. Caller passes localFootCenter
- // already transformed into cell-local space and the resolver's
- // foot-sphere radius.
- var localSphere = new DatReaderWriter.Types.Sphere
- {
- Origin = localFootCenter,
- Radius = sphereRadius,
- };
-
- // Save/restore WalkableAllowance: CPolygon::walkable_hits_sphere reads
- // path.WalkableAllowance (acclient_2013_pseudo_c.txt:323010). For
- // "standing here, find my floor" we want the walkability slope
- // threshold FloorZ. The outer resolver may have set it to LandingZ
- // (airborne→ground transition) or another value; we must not leak our
- // change back to the resolver. try/finally so an exception inside
- // FindWalkableSphere doesn't leak the modified state.
- float savedWalkableAllowance = this.SpherePath.WalkableAllowance;
- this.SpherePath.WalkableAllowance = PhysicsGlobals.FloorZ;
-
- ResolvedPolygon? hitPoly = null;
- ushort hitId = 0;
- Vector3 adjustedCenter;
- bool found;
-
- try
- {
- found = BSPQuery.FindWalkableSphere(
- cellPhysics.BSP.Root,
- cellPhysics.Resolved,
- this,
- localSphere,
- INDOOR_WALKABLE_PROBE_DISTANCE,
- Vector3.UnitZ, // local Z is up for indoor cells (identity transform)
- out hitPoly,
- out hitId,
- out adjustedCenter);
- }
- finally
- {
- this.SpherePath.WalkableAllowance = savedWalkableAllowance;
- }
-
- // adjustedCenter (sphere slid onto polygon plane) is intentionally
- // discarded — ValidateWalkable recomputes contact geometry from the
- // world-space plane + foot position, consistent with the outdoor terrain
- // path (SampleTerrainWalkable returns only plane + vertices, no adjusted
- // sphere). The local is held only to satisfy the out param.
-
- if (!found || hitPoly is null) return false;
-
- // Transform hit polygon's plane + vertices to world space. Math is
- // unchanged from the previous TryFindIndoorWalkablePlane implementation.
- var worldNormal = Vector3.TransformNormal(hitPoly.Plane.Normal, cellPhysics.WorldTransform);
- worldNormal = Vector3.Normalize(worldNormal);
- var worldV0 = Vector3.Transform(hitPoly.Vertices[0], cellPhysics.WorldTransform);
- float worldD = -Vector3.Dot(worldNormal, worldV0);
- worldPlane = new System.Numerics.Plane(worldNormal, worldD);
-
- worldVertices = new Vector3[hitPoly.Vertices.Length];
- for (int i = 0; i < hitPoly.Vertices.Length; i++)
- worldVertices[i] = Vector3.Transform(hitPoly.Vertices[i], cellPhysics.WorldTransform);
-
- hitPolyId = hitId;
- return true;
- }
-
- ///
- /// Downward probe distance used by
- /// when scanning for the indoor walkable contact plane. 50 cm.
- /// Larger than the +0.02f cell-origin Z-bump and larger than any realistic
- /// step riser; smaller than a full cell height so we don't reach through
- /// a thin floor into the cell above/below.
- ///
- private const float INDOOR_WALKABLE_PROBE_DISTANCE = 0.5f;
-
///
/// Query the outdoor terrain at CheckPos and apply ValidateWalkable logic.
/// Indoor BSP collision is deferred to Task 6c.
@@ -1503,59 +1389,22 @@ public sealed class Transition
return cellState;
}
- // ── Synthesize indoor walkable contact plane ──────────────
- // Indoor walking Phase 2 follow-up (2026-05-19). When the BSP
- // returns OK (no wall collision), the player is standing on a
- // floor poly inside the cell. We must NOT fall through to
- // outdoor terrain (SampleTerrainWalkable) — the outdoor terrain
- // Z is below the indoor floor due to the +0.02f Z-bump applied
- // for render z-fight prevention. ValidateWalkable would then see
- // the player 0.5m above the outdoor plane → marks them as
- // airborne → walkable=False → falling animation, never recovers.
+ // Indoor BSP returned OK — no wall collision. ContactPlane
+ // is RETAINED from the prior tick's seed
+ // (PhysicsEngine.ResolveWithTransition:583, the
+ // init_contact_plane equivalent) OR refreshed by Path 3
+ // step-down / Path 4 land if those fired this tick. Either
+ // way, no synthesis is needed here — matches retail's
+ // BSPTREE::find_collisions OK path
+ // (acclient_2013_pseudo_c.txt:323938).
//
- // Retail: CEnvCell::find_env_collisions returns from the cell
- // branch with the cell's walkable plane set — no fall-through
- // to terrain.
- bool walkableHit = TryFindIndoorWalkablePlane(
- cellPhysics, localCenter, sphereRadius,
- out var indoorPlane,
- out var indoorVertices,
- out uint hitPolyId);
-
- if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
- {
- if (walkableHit)
- {
- // dz = signed gap between foot and synthesized plane.
- // Plane: N·p + D = 0 ⇒ pZ_on_plane = -D/N.z (for upward-facing planes)
- // gap = foot.Z - pZ_on_plane = foot.Z - (-D/N.z) = foot.Z + D/N.z
- float dz = footCenter.Z + indoorPlane.D / indoorPlane.Normal.Z;
- Console.WriteLine(System.FormattableString.Invariant(
- $"[indoor-walkable] cell=0x{sp.CheckCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) probe={INDOOR_WALKABLE_PROBE_DISTANCE:F2} result=HIT poly=0x{hitPolyId:X4} wn=({indoorPlane.Normal.X:F3},{indoorPlane.Normal.Y:F3},{indoorPlane.Normal.Z:F3}) wD={indoorPlane.D:F3} dz={dz:+0.00;-0.00;+0.00}"));
- }
- else
- {
- Console.WriteLine(System.FormattableString.Invariant(
- $"[indoor-walkable] cell=0x{sp.CheckCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) probe={INDOOR_WALKABLE_PROBE_DISTANCE:F2} result=MISS"));
- }
- }
-
- if (walkableHit)
- {
- return ValidateWalkable(
- footCenter,
- sphereRadius,
- indoorPlane,
- isWater: false,
- waterDepth: 0f,
- cellId: sp.CheckCellId,
- walkableVertices: indoorVertices);
- }
- // If no walkable floor was found under the player indoors
- // (rare — cell with only walls/ceiling), fall through to
- // outdoor terrain as a defensive backstop. Indoor walking
- // will report walkable=False until the player moves over a
- // cell with a proper floor poly.
+ // Do NOT fall through to outdoor terrain backstop: the
+ // player is in an indoor cell, and the outdoor terrain
+ // Z is below the indoor floor by ~0.02m (the render Z-bump),
+ // which would mark the player as airborne and trigger the
+ // falling-animation stuck symptom (the original Bug A).
+ // 2026-05-20 slice 2 of indoor ContactPlane retention.
+ return TransitionState.OK;
}
}
diff --git a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs b/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs
deleted file mode 100644
index 75f136e..0000000
--- a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-using System.Collections.Generic;
-using System.Numerics;
-using DatReaderWriter.Enums;
-using DatReaderWriter.Types;
-using AcDream.Core.Physics;
-using Xunit;
-
-namespace AcDream.Core.Tests.Physics;
-
-///
-/// Unit tests for .
-///
-/// Indoor walking Phase 2 follow-up (2026-05-19): the helper synthesizes
-/// a walkable contact plane from cell floor polys so the resolver does not
-/// fall through to outdoor terrain when the player is standing indoors.
-///
-/// Task 3 (2026-05-19): refactored to route through BSPQuery.FindWalkableSphere.
-/// Fixtures now include a PhysicsBSPTree with a Leaf node listing all polygon ids,
-/// and calls pass sphereRadius explicitly. PointInPolygonXY tests removed since
-/// that helper was deleted (it was the dead linear-scan body).
-///
-public class IndoorWalkablePlaneTests
-{
- // -----------------------------------------------------------------------
- // Helpers
- // -----------------------------------------------------------------------
-
- ///
- /// Build a BSP Leaf node that lists the given polygon ids, with a bounding
- /// sphere large enough to always contain the test geometry.
- ///
- private static PhysicsBSPTree BuildLeafBsp(IEnumerable polyIds,
- Vector3 center, float radius)
- {
- var node = new PhysicsBSPNode
- {
- Type = BSPNodeType.Leaf,
- BoundingSphere = new Sphere { Origin = center, Radius = radius },
- };
- foreach (var id in polyIds)
- node.Polygons.Add(id);
- return new PhysicsBSPTree { Root = node };
- }
-
- ///
- /// Builds a CellPhysics with a single upward-facing floor polygon
- /// (a 10×10 square in the XY plane at local Z=0), plus identity transforms
- /// and a BSP leaf that covers all polygons.
- ///
- private static CellPhysics BuildCellWithFloor(float floorZ = 0f)
- {
- var verts = new[]
- {
- new Vector3(-5f, -5f, floorZ),
- new Vector3( 5f, -5f, floorZ),
- new Vector3( 5f, 5f, floorZ),
- new Vector3(-5f, 5f, floorZ),
- };
- var normal = new Vector3(0f, 0f, 1f); // straight up
- float D = -Vector3.Dot(normal, verts[0]); // = -floorZ
-
- var floorPoly = new ResolvedPolygon
- {
- Vertices = verts,
- Plane = new Plane(normal, D),
- NumPoints = 4,
- SidesType = CullMode.None,
- };
-
- var resolved = new Dictionary { [0] = floorPoly };
- var bsp = BuildLeafBsp(new ushort[] { 0 }, new Vector3(0f, 0f, floorZ), 10f);
-
- return new CellPhysics
- {
- BSP = bsp,
- WorldTransform = Matrix4x4.Identity,
- InverseWorldTransform = Matrix4x4.Identity,
- Resolved = resolved,
- };
- }
-
- // -----------------------------------------------------------------------
- // TryFindIndoorWalkablePlane
- // -----------------------------------------------------------------------
-
- [Fact]
- public void TryFindIndoorWalkablePlane_PlayerDirectlyOverFloor_ReturnsTrue()
- {
- var cell = BuildCellWithFloor(floorZ: 0f);
- var transition = new Transition();
- // Foot sphere centre at Z=0.4, radius=0.48 → overlaps floor at Z=0.
- var localFoot = new Vector3(0f, 0f, 0.4f);
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cell, localFoot, sphereRadius: 0.48f,
- out _, out _, out _);
-
- Assert.True(found);
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_PlayerDirectlyOverFloor_PlaneNormalIsUp()
- {
- var cell = BuildCellWithFloor(floorZ: 0f);
- var transition = new Transition();
- var localFoot = new Vector3(0f, 0f, 0.4f);
-
- transition.TryFindIndoorWalkablePlane(
- cell, localFoot, sphereRadius: 0.48f,
- out var plane, out _, out _);
-
- // The floor's normal must point up (Z close to 1).
- Assert.True(plane.Normal.Z > 0.99f,
- $"Expected plane.Normal.Z > 0.99, got {plane.Normal.Z}");
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_PlayerDirectlyOverFloor_PlaneAtFloorZ()
- {
- const float floorZ = 2.5f;
- var cell = BuildCellWithFloor(floorZ);
- var transition = new Transition();
- // Foot sphere overlaps floor: centre at floorZ + 0.4, radius=0.48 → dist=0.4 < 0.48.
- var localFoot = new Vector3(0f, 0f, floorZ + 0.4f);
-
- transition.TryFindIndoorWalkablePlane(
- cell, localFoot, sphereRadius: 0.48f,
- out var plane, out _, out _);
-
- // With identity transform and an upward normal, plane.D = -floorZ.
- // The plane equation: normal·p + D = 0 → p.Z = floorZ when normal=(0,0,1).
- Assert.True(MathF.Abs(plane.D - (-floorZ)) < 1e-4f,
- $"Expected plane.D ≈ {-floorZ}, got {plane.D}");
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_PlayerOutsidePolygonXY_ReturnsFalse()
- {
- var cell = BuildCellWithFloor();
- var transition = new Transition();
- // XY = (20, 20) is far outside the 10×10 square (-5..5 in both axes).
- var localFoot = new Vector3(20f, 20f, 0.4f);
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cell, localFoot, sphereRadius: 0.48f,
- out _, out _, out _);
-
- Assert.False(found);
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_NoBsp_ReturnsFalse()
- {
- // CellPhysics without a BSP → BSP?.Root is null → early return false.
- var cell = new CellPhysics
- {
- WorldTransform = Matrix4x4.Identity,
- InverseWorldTransform = Matrix4x4.Identity,
- Resolved = new Dictionary(),
- };
- var transition = new Transition();
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cell, new Vector3(0f, 0f, 0.4f), sphereRadius: 0.48f,
- out _, out _, out _);
-
- Assert.False(found);
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_WallPolyInBsp_ReturnsFalse()
- {
- // A polygon with a horizontal normal (Z = 0) is a wall, not a floor.
- // walkable_hits_sphere rejects it: dp = dot(UnitZ, (0,1,0)) = 0 <= FloorZ.
- // Regression coverage for the previous NoWalkablePolys_ReturnsFalse intent
- // (the renamed NoBsp_ReturnsFalse only covers the null-BSP early-return).
- Vector3[] wallVerts =
- {
- new Vector3(0f, 0f, 0f),
- new Vector3(1f, 0f, 0f),
- new Vector3(1f, 0f, 1f),
- new Vector3(0f, 0f, 1f),
- };
- var resolved = new Dictionary
- {
- [0] = new ResolvedPolygon
- {
- Vertices = wallVerts,
- Plane = new Plane(new Vector3(0f, 1f, 0f), 0f), // wall facing +Y
- NumPoints = 4,
- SidesType = CullMode.None,
- },
- };
-
- var center = new Vector3(0.5f, 0f, 0.5f);
- var bsp = BuildLeafBsp(new ushort[] { 0 }, center, 2f);
-
- var cell = new CellPhysics
- {
- BSP = bsp,
- WorldTransform = Matrix4x4.Identity,
- InverseWorldTransform = Matrix4x4.Identity,
- Resolved = resolved,
- };
-
- var transition = new Transition();
- transition.SpherePath.WalkInterp = 1.0f;
-
- // Foot sphere positioned to overlap the wall's plane (|Y - 0| = 0 < radius 0.48).
- bool found = transition.TryFindIndoorWalkablePlane(
- cell,
- localFootCenter: new Vector3(0.5f, 0f, 0.5f),
- sphereRadius: 0.48f,
- out _,
- out _,
- out _);
-
- Assert.False(found);
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_EmptyResolved_ReturnsFalse()
- {
- // BSP leaf exists but references no polygons → FindWalkableSphere returns false.
- var bsp = BuildLeafBsp(System.Array.Empty(), Vector3.Zero, 10f);
- var cell = new CellPhysics
- {
- BSP = bsp,
- WorldTransform = Matrix4x4.Identity,
- InverseWorldTransform = Matrix4x4.Identity,
- Resolved = new Dictionary(),
- };
- var transition = new Transition();
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cell, new Vector3(0f, 0f, 0.4f), sphereRadius: 0.48f,
- out _, out _, out _);
-
- Assert.False(found);
- }
-
- [Fact]
- public void TryFindIndoorWalkablePlane_WithWorldTranslation_PlaneInWorldSpace()
- {
- // Cell is translated 100 units in X and 200 units in Y.
- var translation = Matrix4x4.CreateTranslation(100f, 200f, 94f);
- Matrix4x4.Invert(translation, out var inv);
-
- var localVerts = new[]
- {
- new Vector3(-5f, -5f, 0f),
- new Vector3( 5f, -5f, 0f),
- new Vector3( 5f, 5f, 0f),
- new Vector3(-5f, 5f, 0f),
- };
- var floorPoly = new ResolvedPolygon
- {
- Vertices = localVerts,
- Plane = new Plane(new Vector3(0f, 0f, 1f), 0f),
- NumPoints = 4,
- SidesType = CullMode.None,
- };
- var resolved = new Dictionary { [0] = floorPoly };
- var bsp = BuildLeafBsp(new ushort[] { 0 }, Vector3.Zero, 10f);
-
- var cell = new CellPhysics
- {
- BSP = bsp,
- WorldTransform = translation,
- InverseWorldTransform = inv,
- Resolved = resolved,
- };
-
- // The player's local foot sphere centre at (0,0,0.4) overlaps the floor at Z=0.
- var localFoot = new Vector3(0f, 0f, 0.4f);
- var transition = new Transition();
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cell, localFoot, sphereRadius: 0.48f,
- out var plane, out var worldVerts, out _);
-
- Assert.True(found);
- // World normal should still be (0,0,1).
- Assert.True(plane.Normal.Z > 0.99f);
- // World vertex[0] should be at local (-5,-5,0) + translation = (95, 195, 94).
- Assert.True(MathF.Abs(worldVerts[0].X - 95f) < 1e-3f);
- Assert.True(MathF.Abs(worldVerts[0].Y - 195f) < 1e-3f);
- Assert.True(MathF.Abs(worldVerts[0].Z - 94f) < 1e-3f,
- $"Expected worldVerts[0].Z ≈ 94, got {worldVerts[0].Z}");
- }
-}
diff --git a/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs b/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs
deleted file mode 100644
index e4dd879..0000000
--- a/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using System.Numerics;
-using DatReaderWriter.Enums;
-using DatReaderWriter.Types;
-using AcDream.Core.Physics;
-using Xunit;
-using Plane = System.Numerics.Plane;
-
-namespace AcDream.Core.Tests.Physics;
-
-public class TransitionTypesTests
-{
- [Fact]
- public void TryFindIndoorWalkablePlane_TwoOverlappingFloors_PicksClosestBelowFoot_PreservesAllowance()
- {
- // Build a CellPhysics with two horizontal walkable polygons at
- // local Z=0 and Z=3, both covering the unit square X[0..1] × Y[0..1].
- // Foot sphere at local Z=0.4 → sphere overlaps the Z=0 polygon
- // (|0.4| < radius 0.48); Z=3 is out of range. Expect the lower poly
- // to be returned. Sentinel WalkableAllowance value must be preserved
- // across the call.
-
- var cellPhysics = BuildTwoFloorCellPhysics(lowerZ: 0f, upperZ: 3f);
-
- var transition = new Transition();
- const float sentinelAllowance = 0.42f;
- transition.SpherePath.WalkableAllowance = sentinelAllowance;
- transition.SpherePath.WalkInterp = 1.0f;
-
- bool found = transition.TryFindIndoorWalkablePlane(
- cellPhysics,
- localFootCenter: new Vector3(0.5f, 0.5f, 0.4f),
- sphereRadius: 0.48f,
- out var worldPlane,
- out var worldVertices,
- out var hitPolyId);
-
- Assert.True(found);
- // Lower polygon's local plane Normal.Z = 1.0; identity world transform
- // means world Normal.Z is also 1.0.
- Assert.Equal(1.0f, worldPlane.Normal.Z, precision: 3);
- // World vertices match the lower polygon (Z=0 in world space, identity transform).
- Assert.Equal(4, worldVertices.Length);
- Assert.Equal(0f, worldVertices[0].Z, precision: 3);
- // hitPolyId is the dictionary key — lower polygon was inserted as key 0.
- Assert.Equal(0u, hitPolyId);
- // WalkableAllowance must be restored to the sentinel.
- Assert.Equal(sentinelAllowance, transition.SpherePath.WalkableAllowance);
- }
-
- ///
- /// Build a minimal CellPhysics with two horizontal walkable polygons at
- /// local Z=lowerZ and Z=upperZ. Identity world transform so world == local.
- ///
- private static CellPhysics BuildTwoFloorCellPhysics(float lowerZ, float upperZ)
- {
- Vector3[] lowerVerts =
- {
- new Vector3(0f, 0f, lowerZ),
- new Vector3(1f, 0f, lowerZ),
- new Vector3(1f, 1f, lowerZ),
- new Vector3(0f, 1f, lowerZ),
- };
- Vector3[] upperVerts =
- {
- new Vector3(0f, 0f, upperZ),
- new Vector3(1f, 0f, upperZ),
- new Vector3(1f, 1f, upperZ),
- new Vector3(0f, 1f, upperZ),
- };
-
- var resolved = new Dictionary
- {
- [0] = new ResolvedPolygon
- {
- Vertices = lowerVerts,
- Plane = new Plane(Vector3.UnitZ, -lowerZ),
- NumPoints = 4,
- SidesType = CullMode.None,
- },
- [1] = new ResolvedPolygon
- {
- Vertices = upperVerts,
- Plane = new Plane(Vector3.UnitZ, -upperZ),
- NumPoints = 4,
- SidesType = CullMode.None,
- },
- };
-
- var center = new Vector3(0.5f, 0.5f, (lowerZ + upperZ) * 0.5f);
- float halfHeight = MathF.Abs(upperZ - lowerZ) * 0.5f + 1.0f;
- float radius = MathF.Sqrt(0.5f * 0.5f + 0.5f * 0.5f + halfHeight * halfHeight);
-
- var root = new PhysicsBSPNode
- {
- Type = BSPNodeType.Leaf,
- BoundingSphere = new Sphere { Origin = center, Radius = radius },
- };
- root.Polygons.Add(0);
- root.Polygons.Add(1);
-
- var bsp = new PhysicsBSPTree { Root = root };
-
- return new CellPhysics
- {
- BSP = bsp,
- Resolved = resolved,
- WorldTransform = Matrix4x4.Identity,
- InverseWorldTransform = Matrix4x4.Identity,
- };
- }
-}