feat(runtime): seal dormant SetPosition evaluations
This commit is contained in:
parent
22651c823d
commit
99f867f053
16 changed files with 2298 additions and 62 deletions
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ public sealed class PhysicsSetPositionTests
|
|||
var local = new Vector3(193f, 12f, 7f);
|
||||
uint expectedCell = Cell;
|
||||
Vector3 expectedLocal = local;
|
||||
Assert.True(LandDefs.AdjustToOutside(
|
||||
Assert.True(AcDream.Core.Physics.LandDefs.AdjustToOutside(
|
||||
ref expectedCell,
|
||||
ref expectedLocal));
|
||||
|
||||
|
|
@ -46,6 +48,9 @@ public sealed class PhysicsSetPositionTests
|
|||
Assert.Equal(expectedCell, result.CellId);
|
||||
Assert.Equal(expectedLocal, result.CellLocalPosition);
|
||||
Assert.Equal(new Vector3(193f, 12f, 7f), result.Position);
|
||||
Assert.Equal(
|
||||
new[] { Cell, expectedCell }.Distinct(),
|
||||
result.QueriedCellIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -82,6 +87,194 @@ public sealed class PhysicsSetPositionTests
|
|||
Assert.Equal(0u, result.CellId);
|
||||
Assert.Equal(local, result.CellLocalPosition);
|
||||
Assert.Equal(local, result.Position);
|
||||
Assert.Equal(new[] { southWestCell, 0u }, result.QueriedCellIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryFootprintIncludesAdjustPositionVisibleChildProbes()
|
||||
{
|
||||
const uint start = Landblock | 0x0101u;
|
||||
const uint sibling = Landblock | 0x0102u;
|
||||
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
|
||||
engine.DataCache.RegisterCellStructForTest(
|
||||
start,
|
||||
ContainmentCell(
|
||||
new Plane(new Vector3(0f, -1f, 0f), 3f),
|
||||
[sibling]));
|
||||
engine.DataCache.RegisterCellStructForTest(
|
||||
sibling,
|
||||
ContainmentCell(
|
||||
new Plane(new Vector3(0f, 1f, 0f), -7f),
|
||||
[]));
|
||||
|
||||
PhysicsSetPositionResult result = engine.SetPosition(
|
||||
Request(
|
||||
start,
|
||||
new Vector3(0f, 8f, 1f),
|
||||
new Vector3(0f, 8f, 1f)) with
|
||||
{
|
||||
MoverPhysicsState = PhysicsStateFlags.Missile,
|
||||
});
|
||||
|
||||
Assert.Contains(start, result.QueriedCellIds);
|
||||
Assert.Contains(sibling, result.QueriedCellIds);
|
||||
Assert.True(
|
||||
result.QueriedCellIds.IndexOf(start)
|
||||
< result.QueriedCellIds.IndexOf(sibling));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LateralVisibleChildRecoveryRecordsRejectedAndWinningSiblingsUnionOnly()
|
||||
{
|
||||
const uint start = Landblock | 0x0101u;
|
||||
const uint rejected = Landblock | 0x0102u;
|
||||
const uint winner = Landblock | 0x0103u;
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.RegisterCellStructForTest(
|
||||
start,
|
||||
ContainmentCell(
|
||||
new Plane(new Vector3(0f, -1f, 0f), 3f),
|
||||
[rejected, winner]));
|
||||
cache.RegisterCellStructForTest(
|
||||
rejected,
|
||||
ContainmentCell(
|
||||
new Plane(new Vector3(0f, 1f, 0f), -20f),
|
||||
[]));
|
||||
cache.RegisterCellStructForTest(
|
||||
winner,
|
||||
ContainmentCell(
|
||||
new Plane(new Vector3(0f, 1f, 0f), -7f),
|
||||
[]));
|
||||
var candidates = new CellArray();
|
||||
var queryFootprint = new CellArray();
|
||||
candidates.UnionTarget = queryFootprint;
|
||||
var spheres = new[]
|
||||
{
|
||||
new Sphere
|
||||
{
|
||||
Origin = new Vector3(0f, 8f, 1f),
|
||||
Radius = 0.48f,
|
||||
},
|
||||
};
|
||||
|
||||
uint containing = CellTransit.FindCellSet(
|
||||
cache,
|
||||
spheres,
|
||||
spheres.Length,
|
||||
start,
|
||||
candidates);
|
||||
|
||||
Assert.Equal(winner, containing);
|
||||
Assert.Equal(new[] { start }, candidates.OrderedIds);
|
||||
Assert.Equal(
|
||||
new[] { start, rejected, winner },
|
||||
queryFootprint.OrderedIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectedPortalContainmentProbeIsRecordedUnionOnly()
|
||||
{
|
||||
const uint start = Landblock | 0x0101u;
|
||||
const uint rejected = Landblock | 0x0102u;
|
||||
const ushort portalPolygonId = 10;
|
||||
var portalPolygon = new ResolvedPolygon
|
||||
{
|
||||
Id = portalPolygonId,
|
||||
Vertices =
|
||||
[
|
||||
new Vector3(10f, -1f, 0f),
|
||||
new Vector3(10f, 1f, 0f),
|
||||
new Vector3(10f, 1f, 2f),
|
||||
],
|
||||
Plane = new Plane(Vector3.UnitX, -10f),
|
||||
NumPoints = 3,
|
||||
SidesType = CullMode.None,
|
||||
};
|
||||
var startCell = new CellPhysics
|
||||
{
|
||||
BSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode { Type = BSPNodeType.Leaf },
|
||||
},
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
|
||||
CellBSP = new CellBSPTree
|
||||
{
|
||||
Root = new CellBSPNode { Type = BSPNodeType.Leaf },
|
||||
},
|
||||
Portals =
|
||||
[
|
||||
new PortalInfo(
|
||||
(ushort)(rejected & 0xFFFFu),
|
||||
portalPolygonId,
|
||||
0),
|
||||
],
|
||||
PortalPolygons = new Dictionary<ushort, ResolvedPolygon>
|
||||
{
|
||||
[portalPolygonId] = portalPolygon,
|
||||
},
|
||||
};
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.RegisterCellStructForTest(start, startCell);
|
||||
cache.RegisterCellStructForTest(
|
||||
rejected,
|
||||
ContainmentCell(
|
||||
new Plane(Vector3.UnitX, -100f),
|
||||
[]));
|
||||
var candidates = new CellArray();
|
||||
var queryFootprint = new CellArray();
|
||||
candidates.UnionTarget = queryFootprint;
|
||||
var spheres = new[]
|
||||
{
|
||||
new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 0.48f,
|
||||
},
|
||||
};
|
||||
|
||||
uint containing = CellTransit.FindCellSet(
|
||||
cache,
|
||||
spheres,
|
||||
spheres.Length,
|
||||
start,
|
||||
candidates);
|
||||
|
||||
Assert.Equal(start, containing);
|
||||
Assert.Equal(new[] { start }, candidates.OrderedIds);
|
||||
Assert.Equal(new[] { start, rejected }, queryFootprint.OrderedIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectedBuildingContainmentProbeIsRecordedUnionOnly()
|
||||
{
|
||||
const uint rejected = Landblock | 0x0102u;
|
||||
var building = new BuildingPhysics
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
Portals =
|
||||
[
|
||||
new BldPortalInfo(
|
||||
rejected,
|
||||
otherPortalId: 0,
|
||||
flags: 0),
|
||||
],
|
||||
};
|
||||
var candidates = new CellArray();
|
||||
var queryFootprint = new CellArray();
|
||||
candidates.UnionTarget = queryFootprint;
|
||||
|
||||
CellTransit.CheckBuildingTransit(
|
||||
new PhysicsDataCache(),
|
||||
building,
|
||||
Vector3.Zero,
|
||||
sphereRadius: 0.48f,
|
||||
candidates);
|
||||
|
||||
Assert.Empty(candidates);
|
||||
Assert.Equal(new[] { rejected }, queryFootprint.OrderedIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -867,6 +1060,91 @@ public sealed class PhysicsSetPositionTests
|
|||
Assert.Equal(new Vector3(11f, 11f, 10f), result.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalThenScatterUnionsEveryLandblockProbeInStableOrder()
|
||||
{
|
||||
PhysicsEngine engine = FlatEngine();
|
||||
const uint eastLandblock = 0xAAB40000u;
|
||||
AddFlatLandblock(engine, eastLandblock, worldOffsetX: 192f);
|
||||
int pass = 0;
|
||||
engine.TransitionCellCollisionTestHook =
|
||||
(_, phase, _, observed) => phase
|
||||
is TransitionCellCollisionPhase.Environment
|
||||
&& pass++ == 0
|
||||
? TransitionState.Collided
|
||||
: observed;
|
||||
var random = new Queue<double>([1d, 0.5d]);
|
||||
engine.SetPositionRandomUnit = random.Dequeue;
|
||||
|
||||
PhysicsSetPositionResult result = engine.SetPosition(
|
||||
Request(
|
||||
Cell,
|
||||
new Vector3(180f, 12f, 7f),
|
||||
new Vector3(180f, 12f, 7f)) with
|
||||
{
|
||||
MoverPhysicsState = PhysicsStateFlags.Missile,
|
||||
Flags = PhysicsSetPositionFlags.Placement
|
||||
| PhysicsSetPositionFlags.Scatter,
|
||||
ScatterRadiusX = 20f,
|
||||
ScatterAttempts = 1u,
|
||||
});
|
||||
|
||||
Assert.True(result.IsCommitted);
|
||||
int source = IndexOfLandblock(result.QueriedCellIds, Landblock);
|
||||
int east = IndexOfLandblock(
|
||||
result.QueriedCellIds,
|
||||
eastLandblock);
|
||||
Assert.True(source >= 0);
|
||||
Assert.True(east > source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiScatterUnionsFailedAndSuccessfulAttemptLandblocks()
|
||||
{
|
||||
PhysicsEngine engine = FlatEngine();
|
||||
const uint westLandblock = 0xA8B40000u;
|
||||
const uint eastLandblock = 0xAAB40000u;
|
||||
AddFlatLandblock(engine, westLandblock, worldOffsetX: -192f);
|
||||
AddFlatLandblock(engine, eastLandblock, worldOffsetX: 192f);
|
||||
int pass = 0;
|
||||
engine.TransitionCellCollisionTestHook =
|
||||
(_, phase, _, observed) => phase
|
||||
is TransitionCellCollisionPhase.Environment
|
||||
&& pass++ == 0
|
||||
? TransitionState.Collided
|
||||
: observed;
|
||||
var random = new Queue<double>([0d, 0.5d, 1d, 0.5d]);
|
||||
engine.SetPositionRandomUnit = random.Dequeue;
|
||||
|
||||
PhysicsSetPositionResult result = engine.SetPosition(
|
||||
Request(
|
||||
Cell,
|
||||
new Vector3(10f, 12f, 7f),
|
||||
new Vector3(10f, 12f, 7f)) with
|
||||
{
|
||||
MoverPhysicsState = PhysicsStateFlags.Missile,
|
||||
Flags = PhysicsSetPositionFlags.RandomScatter,
|
||||
ScatterRadiusX = 200f,
|
||||
ScatterAttempts = 2u,
|
||||
});
|
||||
|
||||
Assert.True(result.IsCommitted);
|
||||
int west = IndexOfLandblock(
|
||||
result.QueriedCellIds,
|
||||
westLandblock);
|
||||
int east = IndexOfLandblock(
|
||||
result.QueriedCellIds,
|
||||
eastLandblock);
|
||||
Assert.True(west >= 0);
|
||||
Assert.True(east > west);
|
||||
Assert.DoesNotContain(
|
||||
result.CrossCellIds,
|
||||
cell => (cell & 0xFFFF0000u) == westLandblock);
|
||||
Assert.Contains(
|
||||
result.CrossCellIds,
|
||||
cell => (cell & 0xFFFF0000u) == eastLandblock);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scatter_ReusesOneTransitionAndFailedInnerProbeCannotLeakIntoSuccess()
|
||||
{
|
||||
|
|
@ -1038,4 +1316,43 @@ public sealed class PhysicsSetPositionTests
|
|||
worldOffsetX,
|
||||
worldOffsetY);
|
||||
}
|
||||
|
||||
private static CellPhysics ContainmentCell(
|
||||
Plane plane,
|
||||
uint[] visibleCells) => new()
|
||||
{
|
||||
BSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode { Type = BSPNodeType.Leaf },
|
||||
},
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
|
||||
CellBSP = new CellBSPTree
|
||||
{
|
||||
Root = new CellBSPNode
|
||||
{
|
||||
SplittingPlane = plane,
|
||||
PosNode = new CellBSPNode { Type = BSPNodeType.Leaf },
|
||||
},
|
||||
},
|
||||
Portals = [new PortalInfo(0xFFFF, 0, 0)],
|
||||
PortalPolygons = new Dictionary<ushort, ResolvedPolygon>(),
|
||||
VisibleCellIds = new HashSet<uint>(visibleCells),
|
||||
};
|
||||
|
||||
private static int IndexOfLandblock(
|
||||
ImmutableArray<uint> cells,
|
||||
uint landblock)
|
||||
{
|
||||
for (int index = 0; index < cells.Length; index++)
|
||||
{
|
||||
if ((cells[index] & 0xFFFF0000u)
|
||||
== (landblock & 0xFFFF0000u))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue