diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs
index 4a3b8e8..7364ec4 100644
--- a/src/AcDream.Core/Physics/TransitionTypes.cs
+++ b/src/AcDream.Core/Physics/TransitionTypes.cs
@@ -1167,20 +1167,16 @@ public sealed class Transition
// -----------------------------------------------------------------------
///
- /// Indoor walking Phase 2 follow-up (2026-05-19). Finds the walkable floor
- /// polygon directly under within
- /// . Used when the indoor cell-BSP query
- /// returns OK (no wall collision) — we need to provide a walkable contact
- /// plane from the cell's geometry instead of falling through to outdoor
- /// terrain (which is below the cell floor due to the +0.02f Z-bump
- /// applied at GameWindow.BuildInteriorEntitiesForStreaming).
+ /// Synthesize the indoor walkable contact plane for the player's current
+ /// position when the cell BSP returns OK (no wall collision).
///
///
- /// Iterates physics polygons; selects
- /// the one with the most upward-facing normal (Z >= 0.6664 = walkable
- /// slope threshold matching retail's WalkableSlopeMin) whose XY projection
- /// contains the player's local foot XY. Returns the polygon's plane +
- /// vertices in WORLD space for the ValidateWalkable call.
+ /// 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).
///
///
///
@@ -1188,10 +1184,17 @@ public sealed class Transition
/// 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 static bool TryFindIndoorWalkablePlane(
+ internal bool TryFindIndoorWalkablePlane(
CellPhysics cellPhysics,
Vector3 localFootCenter,
+ float sphereRadius,
out System.Numerics.Plane worldPlane,
out Vector3[] worldVertices,
out uint hitPolyId)
@@ -1200,57 +1203,76 @@ public sealed class Transition
worldVertices = System.Array.Empty();
hitPolyId = 0;
- foreach (var (id, poly) in cellPhysics.Resolved)
+ 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
{
- // Walkable slope threshold matches retail WalkableSlopeMin (0.6664...)
- // and our existing TerrainSurface.WalkableSlopeMin check.
- if (poly.Plane.Normal.Z < 0.6664f) continue;
- if (poly.Vertices is null || poly.Vertices.Length < 3) continue;
+ Origin = localFootCenter,
+ Radius = sphereRadius,
+ };
- // Point-in-polygon test in XY (ignore Z). Ray-casting even-odd rule.
- if (!PointInPolygonXY(localFootCenter, poly.Vertices)) continue;
+ // 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;
- // Found a floor poly under the player. Transform plane + vertices
- // to world space.
- var worldNormal = Vector3.TransformNormal(poly.Plane.Normal, cellPhysics.WorldTransform);
- worldNormal = Vector3.Normalize(worldNormal);
- // Take vertex 0, transform to world, recompute D so the plane
- // equation normal·p + D = 0 holds at the world-space vertex.
- var worldV0 = Vector3.Transform(poly.Vertices[0], cellPhysics.WorldTransform);
- float worldD = -Vector3.Dot(worldNormal, worldV0);
- worldPlane = new System.Numerics.Plane(worldNormal, worldD);
+ ResolvedPolygon? hitPoly = null;
+ ushort hitId = 0;
+ Vector3 adjustedCenter;
+ bool found;
- worldVertices = new Vector3[poly.Vertices.Length];
- for (int i = 0; i < poly.Vertices.Length; i++)
- worldVertices[i] = Vector3.Transform(poly.Vertices[i], cellPhysics.WorldTransform);
-
- hitPolyId = id;
- return true;
+ 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;
}
- return false;
+ 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;
}
///
- /// Point-in-polygon test in the XY plane (ignores Z). Standard ray-casting
- /// even-odd rule. Works for convex and concave polygons.
+ /// 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.
///
- internal static bool PointInPolygonXY(Vector3 point, Vector3[] vertices)
- {
- bool inside = false;
- int n = vertices.Length;
- for (int i = 0, j = n - 1; i < n; j = i++)
- {
- var vi = vertices[i];
- var vj = vertices[j];
- if (((vi.Y > point.Y) != (vj.Y > point.Y)) &&
- (point.X < (vj.X - vi.X) * (point.Y - vi.Y) / (vj.Y - vi.Y) + vi.X))
- {
- inside = !inside;
- }
- }
- return inside;
- }
+ private const float INDOOR_WALKABLE_PROBE_DISTANCE = 0.5f;
///
/// Query the outdoor terrain at CheckPos and apply ValidateWalkable logic.
@@ -1355,7 +1377,7 @@ public sealed class Transition
// Retail: CEnvCell::find_env_collisions returns from the cell
// branch with the cell's walkable plane set — no fall-through
// to terrain.
- if (TryFindIndoorWalkablePlane(cellPhysics, localCenter,
+ if (TryFindIndoorWalkablePlane(cellPhysics, localCenter, sphereRadius,
out var indoorPlane,
out var indoorVertices,
out uint _))
diff --git a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs b/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs
index 1a455c6..466c5d0 100644
--- a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs
@@ -1,18 +1,23 @@
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 and
-/// .
+/// Unit tests for .
///
-/// Indoor walking Phase 2 follow-up (2026-05-19): these helpers synthesize
+/// 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
{
@@ -20,9 +25,27 @@ 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.
+ /// (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)
{
@@ -44,11 +67,15 @@ public class IndoorWalkablePlaneTests
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 = new Dictionary { [0] = floorPoly },
+ Resolved = resolved,
};
}
@@ -59,12 +86,14 @@ public class IndoorWalkablePlaneTests
[Fact]
public void TryFindIndoorWalkablePlane_PlayerDirectlyOverFloor_ReturnsTrue()
{
- var cell = BuildCellWithFloor(floorZ: 0f);
- var localFoot = new Vector3(0f, 0f, 0.5f); // centred over the 10×10 square
+ 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,
- out var plane, out var verts, out uint polyId);
+ bool found = transition.TryFindIndoorWalkablePlane(
+ cell, localFoot, sphereRadius: 0.48f,
+ out _, out _, out _);
Assert.True(found);
}
@@ -72,11 +101,13 @@ public class IndoorWalkablePlaneTests
[Fact]
public void TryFindIndoorWalkablePlane_PlayerDirectlyOverFloor_PlaneNormalIsUp()
{
- var cell = BuildCellWithFloor(floorZ: 0f);
- var localFoot = new Vector3(0f, 0f, 0.5f);
+ var cell = BuildCellWithFloor(floorZ: 0f);
+ var transition = new Transition();
+ var localFoot = new Vector3(0f, 0f, 0.4f);
- Transition.TryFindIndoorWalkablePlane(
- cell, localFoot, out var plane, out _, out _);
+ 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,
@@ -88,10 +119,13 @@ public class IndoorWalkablePlaneTests
{
const float floorZ = 2.5f;
var cell = BuildCellWithFloor(floorZ);
- var localFoot = new Vector3(0f, 0f, floorZ + 0.5f);
+ 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, out var plane, out _, out _);
+ 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).
@@ -103,35 +137,32 @@ public class IndoorWalkablePlaneTests
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.5f);
+ var localFoot = new Vector3(20f, 20f, 0.4f);
- bool found = Transition.TryFindIndoorWalkablePlane(
- cell, localFoot, out _, out _, out _);
+ bool found = transition.TryFindIndoorWalkablePlane(
+ cell, localFoot, sphereRadius: 0.48f,
+ out _, out _, out _);
Assert.False(found);
}
[Fact]
- public void TryFindIndoorWalkablePlane_NoWalkablePolys_ReturnsFalse()
+ public void TryFindIndoorWalkablePlane_NoBsp_ReturnsFalse()
{
- // A polygon whose normal points sideways (wall) — normal.Z < 0.6664.
- var wallPoly = new ResolvedPolygon
- {
- Vertices = new[] { Vector3.Zero, Vector3.UnitY, Vector3.UnitZ },
- Plane = new Plane(new Vector3(1f, 0f, 0f), 0f), // normal.Z = 0
- NumPoints = 3,
- SidesType = CullMode.None,
- };
+ // CellPhysics without a BSP → BSP?.Root is null → early return false.
var cell = new CellPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
- Resolved = new Dictionary { [1] = wallPoly },
+ Resolved = new Dictionary(),
};
+ var transition = new Transition();
- bool found = Transition.TryFindIndoorWalkablePlane(
- cell, new Vector3(0f, 0f, 0.5f), out _, out _, out _);
+ bool found = transition.TryFindIndoorWalkablePlane(
+ cell, new Vector3(0f, 0f, 0.4f), sphereRadius: 0.48f,
+ out _, out _, out _);
Assert.False(found);
}
@@ -139,15 +170,20 @@ public class IndoorWalkablePlaneTests
[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.5f), out _, out _, out _);
+ bool found = transition.TryFindIndoorWalkablePlane(
+ cell, new Vector3(0f, 0f, 0.4f), sphereRadius: 0.48f,
+ out _, out _, out _);
Assert.False(found);
}
@@ -173,18 +209,24 @@ public class IndoorWalkablePlaneTests
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 = new Dictionary { [0] = floorPoly },
+ Resolved = resolved,
};
- // The player's local foot is at (0,0,0.5) in local space.
- var localFoot = new Vector3(0f, 0f, 0.5f);
+ // 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, out var plane, out var worldVerts, out _);
+ 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).
@@ -195,46 +237,4 @@ public class IndoorWalkablePlaneTests
Assert.True(MathF.Abs(worldVerts[0].Z - 94f) < 1e-3f,
$"Expected worldVerts[0].Z ≈ 94, got {worldVerts[0].Z}");
}
-
- // -----------------------------------------------------------------------
- // PointInPolygonXY
- // -----------------------------------------------------------------------
-
- [Theory]
- [InlineData( 0f, 0f, true)] // centre
- [InlineData( 4f, 4f, true)] // near corner, inside
- [InlineData( 5f, 5f, false)] // on the corner — outside by convention
- [InlineData(10f, 0f, false)] // clearly outside
- [InlineData(-4f, -4f, true)] // near opposite corner, inside
- public void PointInPolygonXY_UnitSquare(float px, float py, bool expected)
- {
- var square = new[]
- {
- new Vector3(-5f, -5f, 0f),
- new Vector3( 5f, -5f, 0f),
- new Vector3( 5f, 5f, 0f),
- new Vector3(-5f, 5f, 0f),
- };
- bool result = Transition.PointInPolygonXY(new Vector3(px, py, 99f), square);
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void PointInPolygonXY_IgnoresZ()
- {
- // Same XY, different Z — should still be inside.
- var square = new[]
- {
- new Vector3(-5f, -5f, 0f),
- new Vector3( 5f, -5f, 0f),
- new Vector3( 5f, 5f, 0f),
- new Vector3(-5f, 5f, 0f),
- };
- // Point has the same XY as the inside case but a very different Z.
- bool atLowZ = Transition.PointInPolygonXY(new Vector3(0f, 0f, -1000f), square);
- bool atHighZ = Transition.PointInPolygonXY(new Vector3(0f, 0f, 1000f), square);
-
- Assert.True(atLowZ);
- Assert.True(atHighZ);
- }
}
diff --git a/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs b/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs
new file mode 100644
index 0000000..e4dd879
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Physics/TransitionTypesTests.cs
@@ -0,0 +1,111 @@
+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,
+ };
+ }
+}