feat(content): bake and read flat collision assets

Append strict collision/topology payloads to the existing prepared package so later physics cutover can drop parsed DAT graphs without adding a second mapping or changing traversal behavior. The full 2,232,170-key catalog is deterministic across worker counts, exact-byte aliased, corruption-isolated, and cancellation-safe.
This commit is contained in:
Erik 2026-07-25 15:22:08 +02:00
parent d9300c7854
commit 9cd42417a8
29 changed files with 2868 additions and 63 deletions

View file

@ -43,6 +43,24 @@ public sealed class FlatCollisionInstalledDatTests
FlatCollisionAssetBuilder.FlattenCell(source);
FlatCellCollisionAsset second =
FlatCollisionAssetBuilder.FlattenCell(source);
EnvCell datCell = Assert.IsType<EnvCell>(
dats.Get<EnvCell>(cellId));
var environment =
Assert.IsType<DatReaderWriter.DBObjs.Environment>(
dats.Get<DatReaderWriter.DBObjs.Environment>(
0x0D00_0000u | datCell.EnvironmentId));
Assert.True(
environment.Cells.TryGetValue(
datCell.CellStructure,
out CellStruct? cellStruct));
Assert.NotNull(cellStruct);
FlatCellStructureCollisionAsset directStructure =
FlatCollisionAssetBuilder.FlattenCellStructure(cellStruct!);
FlatEnvCellTopology directTopology =
FlatCollisionAssetBuilder.FlattenEnvCellTopology(
cellId,
datCell,
directStructure.PortalPolygons);
AssertPhysicsSourceMatches(source.BSP?.Root, source.Resolved, first.Structure.PhysicsBsp);
AssertContainmentSourceMatches(
@ -60,6 +78,22 @@ public sealed class FlatCollisionInstalledDatTests
Assert.Equal(
first.Topology.VisibleCellIds.AsEnumerable(),
second.Topology.VisibleCellIds.AsEnumerable());
FlatCollisionAssetBuilderTests.AssertFlatPhysicsEqual(
first.Structure.PhysicsBsp,
directStructure.PhysicsBsp);
Assert.Equal(
first.Structure.ContainmentBsp.Nodes.AsEnumerable(),
directStructure.ContainmentBsp.Nodes.AsEnumerable());
AssertPolygonTableEqual(
first.Structure.PortalPolygons,
directStructure.PortalPolygons);
Assert.Equal(
first.Topology.Portals.AsEnumerable(),
directTopology.Portals.AsEnumerable());
Assert.Equal(
first.Topology.VisibleCellIds.AsEnumerable(),
directTopology.VisibleCellIds.AsEnumerable());
Assert.Equal(first.Topology.SeenOutside, directTopology.SeenOutside);
_output.WriteLine(
$"cell 0x{cellId:X8}: physicsNodes={first.Structure.PhysicsBsp.Nodes.Length}, " +
@ -97,6 +131,8 @@ public sealed class FlatCollisionInstalledDatTests
FlatCollisionAssetBuilder.FlattenGfxObj(
source,
cache.GetVisualBounds(gfxObjId));
FlatGfxObjCollisionAsset direct =
FlatCollisionAssetBuilder.FlattenGfxObj(gfxObj);
AssertPhysicsSourceMatches(
source.BSP.Root,
@ -107,6 +143,15 @@ public sealed class FlatCollisionInstalledDatTests
AssertVisualBoundsBits(
Assert.IsType<GfxObjVisualBounds>(cache.GetVisualBounds(gfxObjId)),
flat.VisualBounds!.Value);
FlatCollisionAssetBuilderTests.AssertFlatPhysicsEqual(
flat.PhysicsBsp,
direct.PhysicsBsp);
AssertFlatNullableSphereBits(
flat.BoundingSphere,
direct.BoundingSphere);
AssertFlatNullableVisualBoundsBits(
flat.VisualBounds,
direct.VisualBounds);
_output.WriteLine(
$"gfx 0x{gfxObjId:X8}: nodes={flat.PhysicsBsp.Nodes.Length}, " +
@ -127,7 +172,10 @@ public sealed class FlatCollisionInstalledDatTests
FlatSetupCollision flat =
FlatCollisionAssetBuilder.FlattenSetup(source);
FlatSetupCollision direct =
FlatCollisionAssetBuilder.FlattenSetup(setup);
AssertSetupSourceMatches(source, flat);
AssertSetupFlatEqual(flat, direct);
_output.WriteLine(
$"setup 0x{setupId:X8}: cylinders={flat.Cylinders.Length}, " +
@ -327,4 +375,114 @@ public sealed class FlatCollisionInstalledDatTests
source.HalfExtents,
flat.HalfExtents);
}
private static void AssertFlatNullableSphereBits(
FlatCollisionSphere? expected,
FlatCollisionSphere? actual)
{
Assert.Equal(expected.HasValue, actual.HasValue);
if (!expected.HasValue || !actual.HasValue)
return;
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Value.Origin,
actual.Value.Origin);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Value.Radius,
actual.Value.Radius);
}
private static void AssertFlatNullableVisualBoundsBits(
FlatGfxObjVisualBounds? expected,
FlatGfxObjVisualBounds? actual)
{
Assert.Equal(expected.HasValue, actual.HasValue);
if (!expected.HasValue || !actual.HasValue)
return;
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Value.Min,
actual.Value.Min);
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Value.Max,
actual.Value.Max);
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Value.Center,
actual.Value.Center);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Value.Radius,
actual.Value.Radius);
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Value.HalfExtents,
actual.Value.HalfExtents);
}
private static void AssertSetupFlatEqual(
FlatSetupCollision expected,
FlatSetupCollision actual)
{
Assert.Equal(expected.Cylinders.Length, actual.Cylinders.Length);
Assert.Equal(expected.Spheres.Length, actual.Spheres.Length);
for (int i = 0; i < expected.Cylinders.Length; i++)
{
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Cylinders[i].Origin,
actual.Cylinders[i].Origin);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Cylinders[i].Radius,
actual.Cylinders[i].Radius);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Cylinders[i].Height,
actual.Cylinders[i].Height);
}
for (int i = 0; i < expected.Spheres.Length; i++)
{
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Spheres[i].Origin,
actual.Spheres[i].Origin);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Spheres[i].Radius,
actual.Spheres[i].Radius);
}
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Height,
actual.Height);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.Radius,
actual.Radius);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.StepUpHeight,
actual.StepUpHeight);
FlatCollisionAssetBuilderTests.AssertFloatBits(
expected.StepDownHeight,
actual.StepDownHeight);
}
private static void AssertPolygonTableEqual(
FlatPolygonTable expected,
FlatPolygonTable actual)
{
Assert.Equal(expected.Polygons.Length, actual.Polygons.Length);
Assert.Equal(expected.Vertices.Length, actual.Vertices.Length);
for (int i = 0; i < expected.Polygons.Length; i++)
{
Assert.Equal(expected.Polygons[i].Id, actual.Polygons[i].Id);
Assert.Equal(
expected.Polygons[i].SidesType,
actual.Polygons[i].SidesType);
Assert.Equal(
expected.Polygons[i].NumPoints,
actual.Polygons[i].NumPoints);
Assert.Equal(
expected.Polygons[i].VertexRange,
actual.Polygons[i].VertexRange);
FlatCollisionAssetBuilderTests.AssertPlaneBits(
expected.Polygons[i].Plane,
actual.Polygons[i].Plane);
}
for (int i = 0; i < expected.Vertices.Length; i++)
{
FlatCollisionAssetBuilderTests.AssertVectorBits(
expected.Vertices[i],
actual.Vertices[i]);
}
}
}