refactor(physics): remove parsed collision graphs
Publish prepared GfxObj, Setup, CellStruct, and EnvCell collision records without retaining their parsed DAT BSP, polygon, vertex, or shape graphs. Strip temporary physics bundles at stable world commit, keep graph traversal only as an explicit test/tooling oracle, and report graph residency from actual retained fields. Validated by 115 focused collision/streaming tests, a zero-warning Release build, and 8,413 passing Release tests with five pre-existing skips. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
feb80a67d9
commit
82f8d4f82e
18 changed files with 923 additions and 104 deletions
|
|
@ -0,0 +1,39 @@
|
|||
using AcDream.App.Streaming;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class GpuWorldStateCollisionResidencyTests
|
||||
{
|
||||
[Fact]
|
||||
public void StableSpatialState_DropsPublicationDatBundle()
|
||||
{
|
||||
const uint landblockId = 0xA9B4_FFFFu;
|
||||
var bundle = new PhysicsDatBundle(
|
||||
new LandBlockInfo(),
|
||||
new Dictionary<uint, EnvCell>
|
||||
{
|
||||
[0xA9B4_0100u] = new EnvCell(),
|
||||
},
|
||||
new Dictionary<uint, DatReaderWriter.DBObjs.Environment>(),
|
||||
new Dictionary<uint, Setup>
|
||||
{
|
||||
[0x0200_0001u] = new Setup(),
|
||||
},
|
||||
new Dictionary<uint, GfxObj>());
|
||||
var state = new GpuWorldState();
|
||||
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
landblockId,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>(),
|
||||
bundle));
|
||||
|
||||
Assert.True(state.TryGetLandblock(
|
||||
landblockId,
|
||||
out LoadedLandblock? retained));
|
||||
Assert.NotNull(retained);
|
||||
Assert.Same(PhysicsDatBundle.Empty, retained.PhysicsDats);
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +244,9 @@ public sealed class LandblockBuildFactoryTests
|
|||
Assert.Empty(envCells.Shells);
|
||||
Assert.NotNull(physics.Info);
|
||||
Assert.Equal(2, physics.EnvCells.Count);
|
||||
Assert.Single(physics.Environments);
|
||||
Assert.Empty(physics.Environments);
|
||||
Assert.Equal(2, result.Collisions!.CellStructures.Count);
|
||||
Assert.Equal(2, result.Collisions.EnvCells.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -385,13 +387,23 @@ public sealed class LandblockBuildFactoryTests
|
|||
physics.EnvCells.Keys.Order(),
|
||||
first.Collisions.CellStructures.Keys.Order());
|
||||
Assert.Equal(physics.Setups.Keys.Order(), first.Collisions.Setups.Keys.Order());
|
||||
Assert.Equal(physics.GfxObjs.Keys.Order(), first.Collisions.GfxObjs.Keys.Order());
|
||||
Assert.Empty(physics.Environments);
|
||||
Assert.Empty(physics.GfxObjs);
|
||||
uint[] expectedGfxObjIds = first.Landblock.Entities
|
||||
.SelectMany(static entity => entity.MeshRefs)
|
||||
.Select(static mesh => mesh.GfxObjId)
|
||||
.Distinct()
|
||||
.Order()
|
||||
.ToArray();
|
||||
Assert.Equal(
|
||||
expectedGfxObjIds,
|
||||
first.Collisions.GfxObjs.Keys.Order());
|
||||
foreach (WorldEntity entity in first.Landblock.Entities)
|
||||
{
|
||||
if ((entity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
|
||||
Assert.Contains(entity.SourceGfxObjOrSetupId, physics.Setups.Keys);
|
||||
foreach (MeshRef mesh in entity.MeshRefs)
|
||||
Assert.Contains(mesh.GfxObjId, physics.GfxObjs.Keys);
|
||||
Assert.Contains(mesh.GfxObjId, first.Collisions.GfxObjs.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
|
@ -87,4 +89,57 @@ public class CellSurfaceTests
|
|||
Assert.NotNull(z);
|
||||
Assert.InRange(z!.Value, 5f, 8f); // approximate
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedPolygonTable_MatchesLegacyWorldSurface()
|
||||
{
|
||||
var vertices = new Dictionary<ushort, Vector3>
|
||||
{
|
||||
[0] = new(-2f, -2f, 1f),
|
||||
[1] = new(2f, -2f, 2f),
|
||||
[2] = new(2f, 2f, 4f),
|
||||
[3] = new(-2f, 2f, 3f),
|
||||
};
|
||||
var table = new FlatPolygonTable(
|
||||
ImmutableArray.Create(
|
||||
new FlatCollisionPolygon(
|
||||
0,
|
||||
default,
|
||||
CullMode.None,
|
||||
4,
|
||||
new FlatIndexRange(0, 4))),
|
||||
ImmutableArray.Create(
|
||||
vertices[0],
|
||||
vertices[1],
|
||||
vertices[2],
|
||||
vertices[3]));
|
||||
Quaternion rotation = Quaternion.CreateFromAxisAngle(
|
||||
Vector3.UnitZ,
|
||||
0.37f);
|
||||
Vector3 translation = new(17f, -9f, 6f);
|
||||
var prepared = new CellSurface(
|
||||
0x0100,
|
||||
table,
|
||||
rotation,
|
||||
translation);
|
||||
|
||||
Vector3 localSample = new(0.25f, -0.5f, 0f);
|
||||
Vector3 worldSample =
|
||||
Vector3.Transform(localSample, rotation) + translation;
|
||||
var transformedVertices = vertices.ToDictionary(
|
||||
static pair => pair.Key,
|
||||
pair => Vector3.Transform(pair.Value, rotation) + translation);
|
||||
var legacy = new CellSurface(
|
||||
0x0100,
|
||||
transformedVertices,
|
||||
[new List<short> { 0, 1, 2, 3 }]);
|
||||
|
||||
float? expected = legacy.SampleFloorZ(worldSample.X, worldSample.Y);
|
||||
float? actual = prepared.SampleFloorZ(worldSample.X, worldSample.Y);
|
||||
|
||||
Assert.Equal(expected.HasValue, actual.HasValue);
|
||||
Assert.Equal(
|
||||
BitConverter.SingleToInt32Bits(expected!.Value),
|
||||
BitConverter.SingleToInt32Bits(actual!.Value));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
|
|
@ -58,4 +60,104 @@ public sealed class PhysicsDataCacheProductionTests
|
|||
|
||||
Assert.Same(prepared, cache.GetFlatSetup(0x0200_0001u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProductionPreparedRecords_RetainNoParsedCollisionGraph()
|
||||
{
|
||||
const uint gfxObjId = 0x0100_0001u;
|
||||
const uint setupId = 0x0200_0001u;
|
||||
const uint cellId = 0xA9B4_0100u;
|
||||
PhysicsDataCache cache = PhysicsDataCache.CreateProduction();
|
||||
Assert.Null(cache.CollisionShadow);
|
||||
|
||||
var node = new FlatPhysicsBspNode(
|
||||
BSPNodeType.Leaf,
|
||||
default,
|
||||
-1,
|
||||
-1,
|
||||
0,
|
||||
0,
|
||||
new FlatCollisionSphere(Vector3.Zero, 3f),
|
||||
new FlatIndexRange(0, 0));
|
||||
var physicsBsp = new FlatPhysicsBsp(
|
||||
0,
|
||||
ImmutableArray.Create(node),
|
||||
ImmutableArray<int>.Empty,
|
||||
FlatPolygonTable.Empty);
|
||||
var gfx = new FlatGfxObjCollisionAsset(
|
||||
physicsBsp,
|
||||
new FlatCollisionSphere(Vector3.Zero, 3f),
|
||||
new FlatGfxObjVisualBounds(
|
||||
-Vector3.One,
|
||||
Vector3.One,
|
||||
Vector3.Zero,
|
||||
1.7320508f,
|
||||
Vector3.One));
|
||||
var setup = new FlatSetupCollision(
|
||||
ImmutableArray.Create(
|
||||
new FlatCollisionCylinder(
|
||||
Vector3.UnitZ,
|
||||
0.4f,
|
||||
1.2f)),
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
1.2f,
|
||||
0.4f,
|
||||
0.5f,
|
||||
0.5f);
|
||||
var structure = new FlatCellStructureCollisionAsset(
|
||||
physicsBsp,
|
||||
new FlatCellContainmentBsp(
|
||||
-1,
|
||||
ImmutableArray<FlatCellBspNode>.Empty),
|
||||
FlatPolygonTable.Empty);
|
||||
var topology = new FlatEnvCellTopology(
|
||||
ImmutableArray<FlatEnvCellPortal>.Empty,
|
||||
ImmutableArray<uint>.Empty,
|
||||
seenOutside: false);
|
||||
|
||||
cache.CacheGfxObj(gfxObjId, gfx);
|
||||
cache.CacheSetup(setupId, setup);
|
||||
cache.CacheCellStruct(
|
||||
cellId,
|
||||
new EnvCell(),
|
||||
Matrix4x4.Identity,
|
||||
structure,
|
||||
topology);
|
||||
|
||||
GfxObjPhysics retainedGfx = Assert.IsType<GfxObjPhysics>(
|
||||
cache.GetGfxObj(gfxObjId));
|
||||
Assert.Null(retainedGfx.BSP);
|
||||
Assert.Null(retainedGfx.PhysicsPolygons);
|
||||
Assert.Null(retainedGfx.Vertices);
|
||||
Assert.Empty(retainedGfx.Resolved);
|
||||
|
||||
SetupPhysics retainedSetup = Assert.IsType<SetupPhysics>(
|
||||
cache.GetSetup(setupId));
|
||||
Assert.Empty(retainedSetup.CylSpheres);
|
||||
Assert.Empty(retainedSetup.Spheres);
|
||||
|
||||
CellPhysics retainedCell = Assert.IsType<CellPhysics>(
|
||||
cache.GetCellStruct(cellId));
|
||||
Assert.Null(retainedCell.BSP);
|
||||
Assert.Null(retainedCell.CellBSP);
|
||||
Assert.Null(retainedCell.PhysicsPolygons);
|
||||
Assert.Null(retainedCell.Vertices);
|
||||
Assert.Null(retainedCell.PortalPolygons);
|
||||
Assert.Empty(retainedCell.Resolved);
|
||||
|
||||
Assert.Equal(0, cache.GraphGfxObjCount);
|
||||
Assert.Equal(0, cache.GraphSetupCount);
|
||||
Assert.Equal(0, cache.GraphCellStructCount);
|
||||
Assert.Equal(1, cache.FlatGfxObjCount);
|
||||
Assert.Equal(1, cache.FlatSetupCount);
|
||||
Assert.Equal(1, cache.FlatCellStructCount);
|
||||
Assert.Equal(1, cache.FlatEnvCellCount);
|
||||
var runtimeCell =
|
||||
Assert.IsType<AcDream.Core.World.Cells.EnvCell>(
|
||||
cache.CellGraph.GetVisible(cellId));
|
||||
Assert.Null(runtimeCell.ContainmentBsp);
|
||||
Assert.Same(
|
||||
structure.ContainmentBsp,
|
||||
runtimeCell.FlatContainmentBsp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,60 @@ public class TransitionTypesTests
|
|||
Assert.Equal(sentinelAllowance, transition.SpherePath.WalkableAllowance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryFindIndoorWalkablePlane_FlatOnlyMatchesGraphResult()
|
||||
{
|
||||
CellPhysics graph = BuildTwoFloorCellPhysics(
|
||||
lowerZ: 0f,
|
||||
upperZ: 3f);
|
||||
FlatPhysicsBsp flat = FlatCollisionAssetBuilder.FlattenPhysicsBsp(
|
||||
graph.BSP!.Root,
|
||||
graph.Resolved);
|
||||
var flatOnly = new CellPhysics
|
||||
{
|
||||
WorldTransform = graph.WorldTransform,
|
||||
InverseWorldTransform = graph.InverseWorldTransform,
|
||||
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
|
||||
FlatPhysicsBsp = flat,
|
||||
};
|
||||
var graphTransition = new Transition();
|
||||
var flatTransition = new Transition();
|
||||
graphTransition.SpherePath.WalkInterp = 1f;
|
||||
flatTransition.SpherePath.WalkInterp = 1f;
|
||||
Vector3 foot = new(0.5f, 0.5f, 0.4f);
|
||||
|
||||
bool graphFound = graphTransition.TryFindIndoorWalkablePlane(
|
||||
graph,
|
||||
foot,
|
||||
0.48f,
|
||||
out Plane graphPlane,
|
||||
out Vector3[] graphVertices,
|
||||
out uint graphId);
|
||||
bool flatFound = flatTransition.TryFindIndoorWalkablePlane(
|
||||
flatOnly,
|
||||
foot,
|
||||
0.48f,
|
||||
out Plane flatPlane,
|
||||
out Vector3[] flatVertices,
|
||||
out uint flatId);
|
||||
|
||||
Assert.Equal(graphFound, flatFound);
|
||||
Assert.Equal(graphId, flatId);
|
||||
Assert.Equal(
|
||||
BitConverter.SingleToInt32Bits(graphPlane.Normal.X),
|
||||
BitConverter.SingleToInt32Bits(flatPlane.Normal.X));
|
||||
Assert.Equal(
|
||||
BitConverter.SingleToInt32Bits(graphPlane.Normal.Y),
|
||||
BitConverter.SingleToInt32Bits(flatPlane.Normal.Y));
|
||||
Assert.Equal(
|
||||
BitConverter.SingleToInt32Bits(graphPlane.Normal.Z),
|
||||
BitConverter.SingleToInt32Bits(flatPlane.Normal.Z));
|
||||
Assert.Equal(
|
||||
BitConverter.SingleToInt32Bits(graphPlane.D),
|
||||
BitConverter.SingleToInt32Bits(flatPlane.D));
|
||||
Assert.Equal(graphVertices, flatVertices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a minimal CellPhysics with two horizontal walkable polygons at
|
||||
/// local Z=lowerZ and Z=upperZ. Identity world transform so world == local.
|
||||
|
|
@ -76,6 +130,7 @@ public class TransitionTypesTests
|
|||
Plane = new Plane(Vector3.UnitZ, -lowerZ),
|
||||
NumPoints = 4,
|
||||
SidesType = CullMode.None,
|
||||
Id = 0,
|
||||
},
|
||||
[1] = new ResolvedPolygon
|
||||
{
|
||||
|
|
@ -83,6 +138,7 @@ public class TransitionTypesTests
|
|||
Plane = new Plane(Vector3.UnitZ, -upperZ),
|
||||
NumPoints = 4,
|
||||
SidesType = CullMode.None,
|
||||
Id = 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue