fix(physics): hold retail cell across inner retries
This commit is contained in:
parent
67d1e9b331
commit
4ca7230b36
4 changed files with 168 additions and 83 deletions
|
|
@ -385,33 +385,97 @@ public sealed class PhysicsEngine
|
||||||
float localX = worldX - lb.WorldOffsetX;
|
float localX = worldX - lb.WorldOffsetX;
|
||||||
float localY = worldY - lb.WorldOffsetY;
|
float localY = worldY - lb.WorldOffsetY;
|
||||||
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
|
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
|
||||||
{
|
return BuildTerrainWalkableSample(kvp.Key, lb, localX, localY);
|
||||||
var sample = lb.Terrain.SampleSurfacePolygon(localX, localY);
|
|
||||||
var vertices = new TerrainTriangleVertices(
|
|
||||||
OffsetTerrainVertex(sample.Vertices.V0, lb),
|
|
||||||
OffsetTerrainVertex(sample.Vertices.V1, lb),
|
|
||||||
OffsetTerrainVertex(sample.Vertices.V2, lb));
|
|
||||||
|
|
||||||
var normal = sample.Normal;
|
|
||||||
float d = -Vector3.Dot(normal, vertices[0]);
|
|
||||||
var plane = new System.Numerics.Plane(normal, d);
|
|
||||||
|
|
||||||
float waterDepth = lb.Terrain.SampleWaterDepth(localX, localY);
|
|
||||||
bool isWater = waterDepth >= 0.45f;
|
|
||||||
uint lowCellId = lb.Terrain.ComputeOutdoorCellId(localX, localY);
|
|
||||||
uint fullCellId = (kvp.Key & 0xFFFF0000u) | lowCellId;
|
|
||||||
|
|
||||||
return new TerrainWalkableSample(
|
|
||||||
plane,
|
|
||||||
vertices,
|
|
||||||
waterDepth,
|
|
||||||
isWater,
|
|
||||||
fullCellId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Samples only the fixed outdoor cell supplied to retail
|
||||||
|
/// <c>CTransition::insert_into_cell</c>. The target point may move into a
|
||||||
|
/// neighboring cell during a retry, but retail continues dispatching the
|
||||||
|
/// captured <c>CObjCell*</c> until that inner call returns.
|
||||||
|
/// </summary>
|
||||||
|
internal TerrainWalkableSample? SampleTerrainWalkableInCell(
|
||||||
|
uint cellId,
|
||||||
|
float worldX,
|
||||||
|
float worldY)
|
||||||
|
{
|
||||||
|
uint lowCellId = cellId & 0xFFFFu;
|
||||||
|
if (lowCellId is < 1u or > 0x40u)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
foreach (var kvp in _landblocks)
|
||||||
|
{
|
||||||
|
uint requestedPrefix = cellId & 0xFFFF0000u;
|
||||||
|
if (requestedPrefix != 0u &&
|
||||||
|
(kvp.Key & 0xFFFF0000u) != requestedPrefix)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LandblockPhysics lb = kvp.Value;
|
||||||
|
float localX = worldX - lb.WorldOffsetX;
|
||||||
|
float localY = worldY - lb.WorldOffsetY;
|
||||||
|
if (requestedPrefix == 0u &&
|
||||||
|
(localX < 0f || localX >= 192f ||
|
||||||
|
localY < 0f || localY >= 192f))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int cellIndex = (int)lowCellId - 1;
|
||||||
|
int cellX = cellIndex / TerrainSurface.CellsPerSide;
|
||||||
|
int cellY = cellIndex % TerrainSurface.CellsPerSide;
|
||||||
|
float minX = cellX * TerrainSurface.CellSize;
|
||||||
|
float minY = cellY * TerrainSurface.CellSize;
|
||||||
|
float maxX = minX + TerrainSurface.CellSize;
|
||||||
|
float maxY = minY + TerrainSurface.CellSize;
|
||||||
|
|
||||||
|
if (localX < minX || localX >= maxX ||
|
||||||
|
localY < minY || localY >= maxY)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildTerrainWalkableSample(
|
||||||
|
kvp.Key,
|
||||||
|
lb,
|
||||||
|
localX,
|
||||||
|
localY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TerrainWalkableSample BuildTerrainWalkableSample(
|
||||||
|
uint landblockId,
|
||||||
|
LandblockPhysics landblock,
|
||||||
|
float localX,
|
||||||
|
float localY)
|
||||||
|
{
|
||||||
|
TerrainSurfacePolygon sample = landblock.Terrain.SampleSurfacePolygon(
|
||||||
|
localX,
|
||||||
|
localY);
|
||||||
|
var vertices = new TerrainTriangleVertices(
|
||||||
|
OffsetTerrainVertex(sample.Vertices.V0, landblock),
|
||||||
|
OffsetTerrainVertex(sample.Vertices.V1, landblock),
|
||||||
|
OffsetTerrainVertex(sample.Vertices.V2, landblock));
|
||||||
|
|
||||||
|
Vector3 normal = sample.Normal;
|
||||||
|
float d = -Vector3.Dot(normal, vertices[0]);
|
||||||
|
var plane = new System.Numerics.Plane(normal, d);
|
||||||
|
|
||||||
|
float waterDepth = landblock.Terrain.SampleWaterDepth(localX, localY);
|
||||||
|
bool isWater = waterDepth >= 0.45f;
|
||||||
|
uint lowCellId = landblock.Terrain.ComputeOutdoorCellId(localX, localY);
|
||||||
|
uint fullCellId = (landblockId & 0xFFFF0000u) | lowCellId;
|
||||||
|
|
||||||
|
return new TerrainWalkableSample(
|
||||||
|
plane,
|
||||||
|
vertices,
|
||||||
|
waterDepth,
|
||||||
|
isWater,
|
||||||
|
fullCellId);
|
||||||
|
}
|
||||||
|
|
||||||
private static Vector3 OffsetTerrainVertex(Vector3 vertex, LandblockPhysics landblock)
|
private static Vector3 OffsetTerrainVertex(Vector3 vertex, LandblockPhysics landblock)
|
||||||
=> new(
|
=> new(
|
||||||
vertex.X + landblock.WorldOffsetX,
|
vertex.X + landblock.WorldOffsetX,
|
||||||
|
|
@ -466,11 +530,11 @@ public sealed class PhysicsEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TEST-ONLY outdoor cell re-derive. The single caller is
|
/// TEST-ONLY outdoor cell re-derive. The sole caller is
|
||||||
/// <c>Transition.FindEnvCollisions</c>'s cache-null fallback
|
/// <c>Transition.RunCheckOtherCellsAndAdvance</c>'s cache-null fallback
|
||||||
/// (PhysicsEngineTests run engines without a <see cref="DataCache"/>,
|
/// (PhysicsEngineTests run engines without a <see cref="DataCache"/>,
|
||||||
/// so <see cref="CellTransit.FindCellSet"/> is unavailable). Production
|
/// so <see cref="CellTransit.FindCellSet"/> is unavailable). Normal
|
||||||
/// membership flows exclusively through the collide-then-pick advance
|
/// production membership flows exclusively through the collide-then-pick advance
|
||||||
/// (<c>RunCheckOtherCellsAndAdvance</c> → <c>FindCellSet</c>).
|
/// (<c>RunCheckOtherCellsAndAdvance</c> → <c>FindCellSet</c>).
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
|
|
|
||||||
|
|
@ -2138,7 +2138,7 @@ public sealed class Transition
|
||||||
TransitionState state = TransitionState.OK;
|
TransitionState state = TransitionState.OK;
|
||||||
for (int attempt = 0; attempt < numAttempts; attempt++)
|
for (int attempt = 0; attempt < numAttempts; attempt++)
|
||||||
{
|
{
|
||||||
state = FindPrimaryCellCollisions(engine, attempt);
|
state = FindPrimaryCellCollisions(engine, cellId, attempt);
|
||||||
if (state is TransitionState.OK or TransitionState.Collided)
|
if (state is TransitionState.OK or TransitionState.Collided)
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
|
|
@ -2159,31 +2159,30 @@ public sealed class Transition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private TransitionState FindPrimaryCellCollisions(
|
private TransitionState FindPrimaryCellCollisions(
|
||||||
PhysicsEngine engine,
|
PhysicsEngine engine,
|
||||||
|
uint cellId,
|
||||||
int innerAttempt)
|
int innerAttempt)
|
||||||
{
|
{
|
||||||
TransitionState actualEnvironment = FindEnvCollisions(engine);
|
|
||||||
uint currentCellId = SpherePath.CheckCellId;
|
|
||||||
TransitionState environment = ObservePrimaryCellPhase(
|
TransitionState environment = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Environment,
|
TransitionCellCollisionPhase.Environment,
|
||||||
currentCellId,
|
cellId,
|
||||||
actualEnvironment);
|
FindEnvCollisions(engine, cellId));
|
||||||
if (environment != TransitionState.OK)
|
if (environment != TransitionState.OK)
|
||||||
return environment;
|
return environment;
|
||||||
|
|
||||||
TransitionState building = ObservePrimaryCellPhase(
|
TransitionState building = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Building,
|
TransitionCellCollisionPhase.Building,
|
||||||
currentCellId,
|
cellId,
|
||||||
FindBuildingCollisions(engine, currentCellId));
|
FindBuildingCollisions(engine, cellId));
|
||||||
if (building != TransitionState.OK)
|
if (building != TransitionState.OK)
|
||||||
return building;
|
return building;
|
||||||
|
|
||||||
TransitionState objects = ObservePrimaryCellPhase(
|
TransitionState objects = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Objects,
|
TransitionCellCollisionPhase.Objects,
|
||||||
currentCellId,
|
cellId,
|
||||||
FindObjCollisionsInCell(engine, currentCellId));
|
FindObjCollisionsInCell(engine, cellId));
|
||||||
DumpPhase2(innerAttempt, environment, objects);
|
DumpPhase2(innerAttempt, environment, objects);
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
@ -3125,7 +3124,9 @@ public sealed class Transition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TransitionState FindEnvCollisions(PhysicsEngine engine)
|
internal TransitionState FindEnvCollisions(
|
||||||
|
PhysicsEngine engine,
|
||||||
|
uint primaryCellId)
|
||||||
{
|
{
|
||||||
var sp = SpherePath;
|
var sp = SpherePath;
|
||||||
var ci = CollisionInfo;
|
var ci = CollisionInfo;
|
||||||
|
|
@ -3153,23 +3154,19 @@ public sealed class Transition
|
||||||
// first (the indoor BSP block / the terrain block below), then advance the cell only in the
|
// first (the indoor BSP block / the terrain block below), then advance the cell only in the
|
||||||
// post-collision step (RunCheckOtherCellsAndAdvance) — retail's collide-then-pick order.
|
// post-collision step (RunCheckOtherCellsAndAdvance) — retail's collide-then-pick order.
|
||||||
//
|
//
|
||||||
// Cache-null fallback: PhysicsEngineTests use engines without a DataCache (no cell registry,
|
// insert_into_cell receives one CObjCell* from transitional_insert
|
||||||
// so FindCellSet is unavailable). Keep the old outdoor re-derive for them only.
|
// and invokes that same object's virtual find_collisions method for
|
||||||
if (engine.DataCache is null)
|
// every inner retry. primaryCellId is therefore deliberately fixed
|
||||||
{
|
// even if a response mutates sphere_path.check_cell; only the next
|
||||||
uint resolvedOutdoorCellId = engine.ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, sp.CheckCellId);
|
// outer transitional_insert attempt captures the replacement cell.
|
||||||
if (resolvedOutdoorCellId != sp.CheckCellId)
|
|
||||||
sp.SetCheckPos(sp.CheckPos, resolvedOutdoorCellId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Indoor cell BSP collision ────────────────────────────────────
|
// ── Indoor cell BSP collision ────────────────────────────────────
|
||||||
// If the player is in an indoor cell (low 16 bits >= 0x0100),
|
// If the player is in an indoor cell (low 16 bits >= 0x0100),
|
||||||
// query the CellStruct's PhysicsBSP for wall/floor/ceiling collision.
|
// query the CellStruct's PhysicsBSP for wall/floor/ceiling collision.
|
||||||
// ACE: EnvCell.find_env_collisions -> CellStructure.PhysicsBSP.find_collisions
|
// ACE: EnvCell.find_env_collisions -> CellStructure.PhysicsBSP.find_collisions
|
||||||
uint cellLow = sp.CheckCellId & 0xFFFFu;
|
uint cellLow = primaryCellId & 0xFFFFu;
|
||||||
if (cellLow >= 0x0100 && engine.DataCache is not null)
|
if (cellLow >= 0x0100 && engine.DataCache is not null)
|
||||||
{
|
{
|
||||||
var cellPhysics = engine.DataCache.GetCellStruct(sp.CheckCellId);
|
var cellPhysics = engine.DataCache.GetCellStruct(primaryCellId);
|
||||||
|
|
||||||
// AP-71 (Campaign P Slice P4, 2026-07-30): retail CEnvCell::
|
// AP-71 (Campaign P Slice P4, 2026-07-30): retail CEnvCell::
|
||||||
// find_env_collisions (pc:309576) calls check_entry_restrictions
|
// find_env_collisions (pc:309576) calls check_entry_restrictions
|
||||||
|
|
@ -3241,7 +3238,7 @@ public sealed class Transition
|
||||||
// so we degrade to "translation-only" instead of the prior
|
// so we degrade to "translation-only" instead of the prior
|
||||||
// "both broken".
|
// "both broken".
|
||||||
Console.WriteLine(System.FormattableString.Invariant(
|
Console.WriteLine(System.FormattableString.Invariant(
|
||||||
$"[indoor-bsp] WARN cellPhysics.WorldTransform did not decompose cleanly for cell 0x{sp.CheckCellId:X8} — falling back to identity rotation"));
|
$"[indoor-bsp] WARN cellPhysics.WorldTransform did not decompose cleanly for cell 0x{primaryCellId:X8} — falling back to identity rotation"));
|
||||||
cellRotation = Quaternion.Identity;
|
cellRotation = Quaternion.Identity;
|
||||||
cellOrigin = cellPhysics.WorldTransform.Translation;
|
cellOrigin = cellPhysics.WorldTransform.Translation;
|
||||||
}
|
}
|
||||||
|
|
@ -3270,7 +3267,7 @@ public sealed class Transition
|
||||||
: System.FormattableString.Invariant(
|
: System.FormattableString.Invariant(
|
||||||
$"n=({hit.Plane.Normal.X:F3},{hit.Plane.Normal.Y:F3},{hit.Plane.Normal.Z:F3}) sides={hit.SidesType}");
|
$"n=({hit.Plane.Normal.X:F3},{hit.Plane.Normal.Y:F3},{hit.Plane.Normal.Z:F3}) sides={hit.SidesType}");
|
||||||
Console.WriteLine(System.FormattableString.Invariant(
|
Console.WriteLine(System.FormattableString.Invariant(
|
||||||
$"[indoor-bsp] cell=0x{sp.CheckCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) lpos=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) lprev=({localCurrCenter.X:F3},{localCurrCenter.Y:F3},{localCurrCenter.Z:F3}) r={sphereRadius:F3} result={cellState} ")
|
$"[indoor-bsp] cell=0x{primaryCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) lpos=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) lprev=({localCurrCenter.X:F3},{localCurrCenter.Y:F3},{localCurrCenter.Z:F3}) r={sphereRadius:F3} result={cellState} ")
|
||||||
+ polyDesc);
|
+ polyDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3314,7 +3311,10 @@ public sealed class Transition
|
||||||
// Runs against the carried (outdoor) cell. The post-collision pick below then promotes
|
// Runs against the carried (outdoor) cell. The post-collision pick below then promotes
|
||||||
// to an interior cell if the sphere has re-entered a building (replacing the removed
|
// to an interior cell if the sphere has re-entered a building (replacing the removed
|
||||||
// pre-pick's outdoor→indoor promotion).
|
// pre-pick's outdoor→indoor promotion).
|
||||||
var terrainWalkable = engine.SampleTerrainWalkable(footCenter.X, footCenter.Y);
|
var terrainWalkable = engine.SampleTerrainWalkableInCell(
|
||||||
|
primaryCellId,
|
||||||
|
footCenter.X,
|
||||||
|
footCenter.Y);
|
||||||
if (terrainWalkable is not null)
|
if (terrainWalkable is not null)
|
||||||
{
|
{
|
||||||
// Per-point water depth: 0.9 on fully water cells, 0.45 on partial-
|
// Per-point water depth: 0.9 on fully water cells, 0.45 on partial-
|
||||||
|
|
@ -3355,7 +3355,21 @@ public sealed class Transition
|
||||||
PhysicsEngine engine, Vector3 footCenter, float sphereRadius)
|
PhysicsEngine engine, Vector3 footCenter, float sphereRadius)
|
||||||
{
|
{
|
||||||
var sp = SpherePath;
|
var sp = SpherePath;
|
||||||
if (engine.DataCache is null) return TransitionState.OK;
|
|
||||||
|
// Test-only engines can omit the cell registry required for retail's
|
||||||
|
// find_cell_list. Preserve their outdoor membership update here at
|
||||||
|
// the post-primary boundary, never from inside the fixed-cell
|
||||||
|
// insert_into_cell transaction.
|
||||||
|
if (engine.DataCache is null)
|
||||||
|
{
|
||||||
|
uint resolvedOutdoorCellId = engine.ResolveCellId(
|
||||||
|
sp.GlobalSphere[0].Origin,
|
||||||
|
sphereRadius,
|
||||||
|
sp.CheckCellId);
|
||||||
|
if (resolvedOutdoorCellId != sp.CheckCellId)
|
||||||
|
sp.SetCheckPos(sp.CheckPos, resolvedOutdoorCellId);
|
||||||
|
return TransitionState.OK;
|
||||||
|
}
|
||||||
|
|
||||||
// Retail check_other_cells (acclient_2013_pseudo_c.txt:272735) calls each
|
// Retail check_other_cells (acclient_2013_pseudo_c.txt:272735) calls each
|
||||||
// other cell's find_collisions with `this`, so it reads the CURRENT
|
// other cell's find_collisions with `this`, so it reads the CURRENT
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ public class IndoorContactPlaneRetentionTests
|
||||||
t.SpherePath.SetCheckPos(newPos, IndoorCellId);
|
t.SpherePath.SetCheckPos(newPos, IndoorCellId);
|
||||||
|
|
||||||
// Simulate FindEnvCollisions as the physics loop calls it.
|
// Simulate FindEnvCollisions as the physics loop calls it.
|
||||||
t.FindEnvCollisions(engine);
|
t.FindEnvCollisions(engine, IndoorCellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Assert ────────────────────────────────────────────────────────────
|
// ── Assert ────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -190,39 +190,68 @@ public sealed class TransitionInsertIntoCellRetryTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NoDataCache_CrossLandblockRepick_UsesRefreshedPrimaryCell()
|
public void NestedRetry_FixesPrimaryCellUntilNextOuterAttempt()
|
||||||
{
|
{
|
||||||
PhysicsEngine engine = BuildCrossLandblockEngine();
|
PhysicsEngine engine = BuildEngine(preparedFlat: false);
|
||||||
var cells = new List<uint>();
|
var cells = new List<uint>();
|
||||||
var phases = new List<TransitionCellCollisionPhase>();
|
var phases = new List<TransitionCellCollisionPhase>();
|
||||||
|
int environmentCalls = 0;
|
||||||
|
const uint nextOuterCell = 0xA9B40002u;
|
||||||
engine.TransitionCellCollisionTestHook =
|
engine.TransitionCellCollisionTestHook =
|
||||||
(_, phase, cellId, actual) =>
|
(transition, phase, cellId, actual) =>
|
||||||
{
|
{
|
||||||
phases.Add(phase);
|
phases.Add(phase);
|
||||||
cells.Add(cellId);
|
cells.Add(cellId);
|
||||||
|
|
||||||
|
if (phase == TransitionCellCollisionPhase.Environment)
|
||||||
|
{
|
||||||
|
environmentCalls++;
|
||||||
|
if (environmentCalls == 1)
|
||||||
|
{
|
||||||
|
transition.SpherePath.SetCheckPos(
|
||||||
|
transition.SpherePath.CheckPos,
|
||||||
|
nextOuterCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentCalls <= 3)
|
||||||
|
return TransitionState.Adjusted;
|
||||||
|
}
|
||||||
|
|
||||||
return actual;
|
return actual;
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector3 current = new(191.99f, 10f, 5f);
|
Vector3 current = new(10f, 10f, 5f);
|
||||||
Vector3 target = new(192.01f, 10f, 5f);
|
Vector3 target = current + new Vector3(0.05f, 0f, 0f);
|
||||||
ResolveResult result = ResolveAirborne(
|
ResolveResult result = ResolveAirborne(
|
||||||
engine,
|
engine,
|
||||||
current,
|
current,
|
||||||
target,
|
target,
|
||||||
0xA9B40039u);
|
Cell);
|
||||||
|
|
||||||
Assert.True(result.Ok);
|
Assert.True(result.Ok);
|
||||||
Assert.Equal(target, result.Position);
|
Assert.Equal(target, result.Position);
|
||||||
Assert.Equal(0xAAB40001u, result.CellId);
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
|
TransitionCellCollisionPhase.Environment,
|
||||||
|
TransitionCellCollisionPhase.Environment,
|
||||||
|
TransitionCellCollisionPhase.Environment,
|
||||||
TransitionCellCollisionPhase.Environment,
|
TransitionCellCollisionPhase.Environment,
|
||||||
TransitionCellCollisionPhase.Building,
|
TransitionCellCollisionPhase.Building,
|
||||||
TransitionCellCollisionPhase.Objects,
|
TransitionCellCollisionPhase.Objects,
|
||||||
},
|
},
|
||||||
phases);
|
phases);
|
||||||
Assert.All(cells, cellId => Assert.Equal(0xAAB40001u, cellId));
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Cell,
|
||||||
|
Cell,
|
||||||
|
Cell,
|
||||||
|
nextOuterCell,
|
||||||
|
nextOuterCell,
|
||||||
|
nextOuterCell,
|
||||||
|
},
|
||||||
|
cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResolveResult ResolveAirborne(
|
private static ResolveResult ResolveAirborne(
|
||||||
|
|
@ -309,26 +338,4 @@ public sealed class TransitionInsertIntoCellRetryTests
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PhysicsEngine BuildCrossLandblockEngine()
|
|
||||||
{
|
|
||||||
var engine = new PhysicsEngine { DataCache = null };
|
|
||||||
var heights = new byte[81];
|
|
||||||
var heightTable = new float[256];
|
|
||||||
Array.Fill(heightTable, -1000f);
|
|
||||||
engine.AddLandblock(
|
|
||||||
0xA9B4FFFFu,
|
|
||||||
new TerrainSurface(heights, heightTable),
|
|
||||||
Array.Empty<CellSurface>(),
|
|
||||||
Array.Empty<PortalPlane>(),
|
|
||||||
0f,
|
|
||||||
0f);
|
|
||||||
engine.AddLandblock(
|
|
||||||
0xAAB4FFFFu,
|
|
||||||
new TerrainSurface(heights, heightTable),
|
|
||||||
Array.Empty<CellSurface>(),
|
|
||||||
Array.Empty<PortalPlane>(),
|
|
||||||
192f,
|
|
||||||
0f);
|
|
||||||
return engine;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue