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
|
|
@ -188,9 +188,9 @@ internal sealed class WorldLifecycleResourceSnapshotSource
|
|||
DatObjectCacheHits: datObjectCacheStats.Hits,
|
||||
DatObjectCacheMisses: datObjectCacheStats.Misses,
|
||||
DatObjectCacheEvictions: datObjectCacheStats.Evictions,
|
||||
PhysicsGraphGfxObjs: _physics.GfxObjCount,
|
||||
PhysicsGraphSetups: _physics.SetupCount,
|
||||
PhysicsGraphCells: _physics.CellStructCount,
|
||||
PhysicsGraphGfxObjs: _physics.GraphGfxObjCount,
|
||||
PhysicsGraphSetups: _physics.GraphSetupCount,
|
||||
PhysicsGraphCells: _physics.GraphCellStructCount,
|
||||
PhysicsFlatGfxObjs: _physics.FlatGfxObjCount,
|
||||
PhysicsFlatSetups: _physics.FlatSetupCount,
|
||||
PhysicsFlatCells: _physics.FlatCellStructCount,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ using DatReaderWriter.DBObjs;
|
|||
namespace AcDream.App.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the graph and prepared collision views for DAT objects discovered
|
||||
/// Publishes package-prepared collision views for DAT objects discovered
|
||||
/// through the live-object path. Streamed statics carry their prepared closure
|
||||
/// on <c>LandblockBuild</c>; live CreateObject/appearance data arrives outside
|
||||
/// that transaction and therefore uses this matching strict publisher.
|
||||
/// that transaction and therefore uses this matching strict publisher. The
|
||||
/// production cache consumes source metadata transiently and never retains its
|
||||
/// parsed collision graph.
|
||||
/// </summary>
|
||||
internal sealed class LiveCollisionAssetPublisher
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ internal sealed class LiveEntityCollisionBuilder
|
|||
PhysicsDataCache physicsData,
|
||||
LiveEntityDefaultPoseResolver defaultPose)
|
||||
: this(
|
||||
id => physicsData.GetGfxObj(id)?.FlatPhysicsBsp?.RootIndex >= 0,
|
||||
id => physicsData.GetFlatGfxObj(id)?.PhysicsBsp.RootIndex >= 0,
|
||||
id =>
|
||||
{
|
||||
FlatPhysicsBsp? flat =
|
||||
physicsData.GetGfxObj(id)?.FlatPhysicsBsp;
|
||||
physicsData.GetFlatGfxObj(id)?.PhysicsBsp;
|
||||
return flat is { RootIndex: >= 0 }
|
||||
? flat.Nodes[flat.RootIndex].BoundingSphere.Radius
|
||||
: null;
|
||||
|
|
|
|||
|
|
@ -268,7 +268,8 @@ internal sealed class LiveEntityMotionRuntimeController
|
|||
public (float Radius, float Height) GetSetupCylinder(
|
||||
uint serverGuid, AcDream.Core.World.WorldEntity entity)
|
||||
{
|
||||
var setup = _physicsDataCache.GetSetup(entity.SourceGfxObjOrSetupId);
|
||||
FlatSetupCollision? setup =
|
||||
_physicsDataCache.GetFlatSetup(entity.SourceGfxObjOrSetupId);
|
||||
if (setup is null)
|
||||
return (0f, 0f);
|
||||
// Live spawns bake ObjScale into MeshRefs and never populate
|
||||
|
|
|
|||
|
|
@ -634,7 +634,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
source.LandblockId,
|
||||
source.Heightmap,
|
||||
entities ?? new List<WorldEntity>(source.Entities),
|
||||
source.PhysicsDats);
|
||||
PhysicsDatBundle.Empty);
|
||||
|
||||
private static List<WorldEntity> MutableEntities(LoadedLandblock landblock) =>
|
||||
(List<WorldEntity>)landblock.Entities;
|
||||
|
|
|
|||
|
|
@ -101,9 +101,19 @@ public sealed class LandblockBuildFactory
|
|||
return build;
|
||||
}
|
||||
|
||||
AcDream.Core.World.LandblockCollisionBuild collisions =
|
||||
BuildPreparedCollisionClosure(build.Landblock);
|
||||
AcDream.Core.World.PhysicsDatBundle publicationDats =
|
||||
(build.Landblock.PhysicsDats
|
||||
?? AcDream.Core.World.PhysicsDatBundle.Empty)
|
||||
.WithoutCollisionGraphs();
|
||||
return build with
|
||||
{
|
||||
Collisions = BuildPreparedCollisionClosure(build.Landblock),
|
||||
Landblock = build.Landblock with
|
||||
{
|
||||
PhysicsDats = publicationDats,
|
||||
},
|
||||
Collisions = collisions,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -402,16 +402,19 @@ public sealed class LandblockPhysicsPublisher
|
|||
{
|
||||
long cacheStarted = Stopwatch.GetTimestamp();
|
||||
uint gfxObjectId = publication.GfxObjectIds[publication.GfxCursor];
|
||||
if (datBundle.GfxObjs.TryGetValue(gfxObjectId, out var gfx))
|
||||
if (publication.Build.Collisions?.GfxObjs.TryGetValue(
|
||||
gfxObjectId,
|
||||
out FlatGfxObjCollisionAsset? prepared) == true)
|
||||
{
|
||||
FlatGfxObjCollisionAsset? prepared = null;
|
||||
publication.Build.Collisions?.GfxObjs.TryGetValue(
|
||||
gfxObjectId,
|
||||
out prepared);
|
||||
_physicsDataCache.CacheGfxObj(
|
||||
gfxObjectId,
|
||||
gfx,
|
||||
prepared);
|
||||
_physicsDataCache.CacheGfxObj(gfxObjectId, prepared);
|
||||
}
|
||||
else if (datBundle.GfxObjs.TryGetValue(
|
||||
gfxObjectId,
|
||||
out var source))
|
||||
{
|
||||
// Graph-oracle fixture seam. Production near builds always
|
||||
// carry the strict prepared closure.
|
||||
_physicsDataCache.CacheGfxObj(gfxObjectId, source);
|
||||
}
|
||||
publication.GfxCursor++;
|
||||
_gfxCacheTicks += Stopwatch.GetTimestamp() - cacheStarted;
|
||||
|
|
@ -420,13 +423,16 @@ public sealed class LandblockPhysicsPublisher
|
|||
{
|
||||
uint setupId =
|
||||
publication.SetupObjectIds[publication.SetupCursor];
|
||||
if (datBundle.Setups.TryGetValue(setupId, out var setup))
|
||||
{
|
||||
FlatSetupCollision? prepared = null;
|
||||
publication.Build.Collisions?.Setups.TryGetValue(
|
||||
if (publication.Build.Collisions?.Setups.TryGetValue(
|
||||
setupId,
|
||||
out prepared);
|
||||
_physicsDataCache.CacheSetup(setupId, setup, prepared);
|
||||
out FlatSetupCollision? prepared) == true)
|
||||
{
|
||||
_physicsDataCache.CacheSetup(setupId, prepared);
|
||||
}
|
||||
else if (datBundle.Setups.TryGetValue(setupId, out var source))
|
||||
{
|
||||
// Graph-oracle fixture seam only.
|
||||
_physicsDataCache.CacheSetup(setupId, source);
|
||||
}
|
||||
publication.SetupCursor++;
|
||||
}
|
||||
|
|
@ -505,8 +511,97 @@ public sealed class LandblockPhysicsPublisher
|
|||
LoadedLandblock landblock = publication.Build.Landblock;
|
||||
uint envCellId =
|
||||
(landblock.LandblockId & 0xFFFF0000u) | (0x0100u + offset);
|
||||
if (!datBundle.EnvCells.TryGetValue(envCellId, out var envCell)
|
||||
|| envCell.EnvironmentId == 0
|
||||
if (!datBundle.EnvCells.TryGetValue(envCellId, out var envCell))
|
||||
return;
|
||||
|
||||
if (publication.Build.Collisions is not { } collisions)
|
||||
{
|
||||
PublishGraphFixtureCell(
|
||||
publication,
|
||||
datBundle,
|
||||
envCellId,
|
||||
envCell);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!collisions.CellStructures.TryGetValue(
|
||||
envCellId,
|
||||
out FlatCellStructureCollisionAsset? preparedStructure)
|
||||
|| !collisions.EnvCells.TryGetValue(
|
||||
envCellId,
|
||||
out FlatEnvCellTopology? preparedTopology))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion rotation = envCell.Position.Orientation;
|
||||
Vector3 cellOriginWorld = envCell.Position.Origin + publication.Origin;
|
||||
Matrix4x4 physicsCellTransform =
|
||||
Matrix4x4.CreateFromQuaternion(rotation)
|
||||
* Matrix4x4.CreateTranslation(cellOriginWorld);
|
||||
|
||||
_physicsDataCache.CacheCellStruct(
|
||||
envCellId,
|
||||
envCell,
|
||||
physicsCellTransform,
|
||||
preparedStructure,
|
||||
preparedTopology);
|
||||
|
||||
FlatPolygonTable physicsPolygons =
|
||||
preparedStructure.PhysicsBsp.PolygonTable;
|
||||
|
||||
var portalPlanes = new List<PortalPlane>();
|
||||
FlatPolygonTable portalPolygons = preparedStructure.PortalPolygons;
|
||||
for (int portalIndex = 0;
|
||||
portalIndex < preparedTopology.Portals.Length;
|
||||
portalIndex++)
|
||||
{
|
||||
FlatEnvCellPortal portal = preparedTopology.Portals[portalIndex];
|
||||
if ((uint)portal.PolygonIndex
|
||||
>= (uint)portalPolygons.Polygons.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FlatCollisionPolygon polygon =
|
||||
portalPolygons.Polygons[portal.PolygonIndex];
|
||||
if (polygon.VertexRange.Count < 3)
|
||||
continue;
|
||||
var portalVertices = new Vector3[polygon.VertexRange.Count];
|
||||
for (int index = 0; index < portalVertices.Length; index++)
|
||||
{
|
||||
Vector3 local = portalPolygons.Vertices[
|
||||
polygon.VertexRange.Start + index];
|
||||
portalVertices[index] =
|
||||
Vector3.Transform(local, rotation) + cellOriginWorld;
|
||||
}
|
||||
|
||||
portalPlanes.Add(PortalPlane.FromVertices(
|
||||
portalVertices.AsSpan(),
|
||||
portal.OtherCellId,
|
||||
envCellId & 0xFFFFu,
|
||||
(ushort)portal.Flags));
|
||||
}
|
||||
|
||||
publication.CellSurfaces.Add(new CellSurface(
|
||||
envCellId,
|
||||
physicsPolygons,
|
||||
rotation,
|
||||
cellOriginWorld));
|
||||
publication.PortalPlanes.AddRange(portalPlanes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parsed-graph publication is retained only for unit/conformance fixtures
|
||||
/// constructed without a prepared collision closure. Production cannot
|
||||
/// reach this branch because near-build admission requires that closure.
|
||||
/// </summary>
|
||||
private void PublishGraphFixtureCell(
|
||||
LandblockPhysicsPublication publication,
|
||||
PhysicsDatBundle datBundle,
|
||||
uint envCellId,
|
||||
DatReaderWriter.DBObjs.EnvCell envCell)
|
||||
{
|
||||
if (envCell.EnvironmentId == 0
|
||||
|| !datBundle.Environments.TryGetValue(
|
||||
0x0D000000u | envCell.EnvironmentId,
|
||||
out var environment)
|
||||
|
|
@ -518,51 +613,33 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
|
||||
Quaternion rotation = envCell.Position.Orientation;
|
||||
Vector3 cellOriginWorld = envCell.Position.Origin + publication.Origin;
|
||||
Vector3 cellOriginWorld =
|
||||
envCell.Position.Origin + publication.Origin;
|
||||
Matrix4x4 physicsCellTransform =
|
||||
Matrix4x4.CreateFromQuaternion(rotation)
|
||||
* Matrix4x4.CreateTranslation(cellOriginWorld);
|
||||
|
||||
FlatCellStructureCollisionAsset? preparedStructure = null;
|
||||
FlatEnvCellTopology? preparedTopology = null;
|
||||
publication.Build.Collisions?.CellStructures.TryGetValue(
|
||||
envCellId,
|
||||
out preparedStructure);
|
||||
publication.Build.Collisions?.EnvCells.TryGetValue(
|
||||
envCellId,
|
||||
out preparedTopology);
|
||||
_physicsDataCache.CacheCellStruct(
|
||||
envCellId,
|
||||
envCell,
|
||||
cellStruct,
|
||||
physicsCellTransform,
|
||||
preparedStructure,
|
||||
preparedTopology);
|
||||
physicsCellTransform);
|
||||
|
||||
var worldVertices = new Dictionary<ushort, Vector3>(
|
||||
cellStruct.VertexArray.Vertices.Count);
|
||||
foreach ((ushort vertexId, var vertex) in
|
||||
cellStruct.VertexArray.Vertices)
|
||||
{
|
||||
Vector3 worldPosition =
|
||||
worldVertices[vertexId] =
|
||||
Vector3.Transform(vertex.Origin, rotation)
|
||||
+ cellOriginWorld;
|
||||
worldVertices[(ushort)vertexId] = worldPosition;
|
||||
}
|
||||
|
||||
var polygonVertexIds = new List<List<short>>(
|
||||
cellStruct.PhysicsPolygons.Count);
|
||||
foreach (var polygon in cellStruct.PhysicsPolygons.Values)
|
||||
{
|
||||
var vertexIds = new List<short>(polygon.VertexIds.Count);
|
||||
foreach (short vertexId in polygon.VertexIds)
|
||||
vertexIds.Add(vertexId);
|
||||
polygonVertexIds.Add(vertexIds);
|
||||
}
|
||||
polygonVertexIds.Add([.. polygon.VertexIds]);
|
||||
|
||||
var portalPlanes = new List<PortalPlane>();
|
||||
// CellPortal.PolygonId indexes rendering Polygons, not
|
||||
// PhysicsPolygons (ACViewer EnvCell.find_transit_cells).
|
||||
foreach (var portal in envCell.CellPortals)
|
||||
{
|
||||
if (!cellStruct.Polygons.TryGetValue(
|
||||
|
|
@ -589,7 +666,7 @@ public sealed class LandblockPhysicsPublisher
|
|||
continue;
|
||||
|
||||
portalPlanes.Add(PortalPlane.FromVertices(
|
||||
portalVertices.AsSpan(),
|
||||
portalVertices,
|
||||
portal.OtherCellId,
|
||||
envCellId & 0xFFFFu,
|
||||
(ushort)portal.Flags));
|
||||
|
|
@ -689,17 +766,26 @@ public sealed class LandblockPhysicsPublisher
|
|||
LogMultipartRegistration(landblock, entity, bspShapes);
|
||||
}
|
||||
|
||||
SetupPhysics? setup =
|
||||
_physicsDataCache.GetSetup(entity.SourceGfxObjOrSetupId);
|
||||
FlatSetupCollision? setup =
|
||||
_physicsDataCache.GetFlatSetup(entity.SourceGfxObjOrSetupId);
|
||||
if (setup is null
|
||||
&& _physicsDataCache.GetSetup(
|
||||
entity.SourceGfxObjOrSetupId) is { } graphSetup)
|
||||
{
|
||||
// Graph-oracle fixture seam only. Production Setup publication is
|
||||
// already package-flat and never allocates this conversion.
|
||||
setup = FlatCollisionAssetBuilder.FlattenSetup(graphSetup);
|
||||
}
|
||||
if (setup is not null && entityBspCount == 0)
|
||||
{
|
||||
float scale = entity.Scale > 0f ? entity.Scale : 1f;
|
||||
var setupShapes = new List<ShadowShape>();
|
||||
for (int cylinderIndex = 0;
|
||||
cylinderIndex < setup.CylSpheres.Count;
|
||||
cylinderIndex < setup.Cylinders.Length;
|
||||
cylinderIndex++)
|
||||
{
|
||||
CylSphere cylinder = setup.CylSpheres[cylinderIndex];
|
||||
FlatCollisionCylinder cylinder =
|
||||
setup.Cylinders[cylinderIndex];
|
||||
float radius = cylinder.Radius * scale;
|
||||
float baseHeight = cylinder.Height > 0f
|
||||
? cylinder.Height
|
||||
|
|
@ -719,13 +805,14 @@ public sealed class LandblockPhysicsPublisher
|
|||
CylHeight: height));
|
||||
}
|
||||
|
||||
if (setup.CylSpheres.Count == 0)
|
||||
if (setup.Cylinders.Length == 0)
|
||||
{
|
||||
for (int sphereIndex = 0;
|
||||
sphereIndex < setup.Spheres.Count;
|
||||
sphereIndex < setup.Spheres.Length;
|
||||
sphereIndex++)
|
||||
{
|
||||
Sphere sphere = setup.Spheres[sphereIndex];
|
||||
FlatCollisionSphere sphere =
|
||||
setup.Spheres[sphereIndex];
|
||||
if (sphere.Radius <= 0f)
|
||||
continue;
|
||||
|
||||
|
|
@ -746,8 +833,8 @@ public sealed class LandblockPhysicsPublisher
|
|||
}
|
||||
}
|
||||
|
||||
if (setup.CylSpheres.Count == 0
|
||||
&& setup.Spheres.Count == 0
|
||||
if (setup.Cylinders.Length == 0
|
||||
&& setup.Spheres.Length == 0
|
||||
&& setup.Radius > 0f)
|
||||
{
|
||||
float radius = setup.Radius * scale;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue