feat(streaming): shadow-publish flat collision assets
Carry one immutable prepared collision closure with each accepted near-tier generation and install graph plus flat views through the same retained publication receipt. Apply the same strict package-only rule to live entities, add exact sampled graph-authoritative comparison artifacts and lifecycle counters, and prove cancellation, demotion, rehydrate, revisit, teardown, reconnect, and the nine-stop route with 14,064 zero-mismatch samples. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
f7ff9f4eea
commit
d9446030e6
32 changed files with 2777 additions and 92 deletions
|
|
@ -410,6 +410,12 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
(LiveEntityCollisionBuilder)RuntimeHelpers.GetUninitializedObject(
|
||||
typeof(LiveEntityCollisionBuilder));
|
||||
|
||||
public LiveCollisionAssetPublisher CreateLiveCollisionAssetPublisher(
|
||||
PhysicsDataCache physicsData,
|
||||
IPreparedAssetSource preparedAssets) =>
|
||||
(LiveCollisionAssetPublisher)RuntimeHelpers.GetUninitializedObject(
|
||||
typeof(LiveCollisionAssetPublisher));
|
||||
|
||||
public EmitterDescRegistry CreateEmitterRegistry(IDatReaderWriter dats) => new();
|
||||
public ParticleSystem CreateParticleSystem(EmitterDescRegistry emitters) => new(emitters);
|
||||
public ParticleHookSink CreateParticleSink(
|
||||
|
|
|
|||
|
|
@ -381,6 +381,7 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0L, 0, 0L, 0L, 0L, 0L, 0, 0, 0, 0, 0, 0, 0, 0L, 0L,
|
||||
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
|
||||
0, 0, 0, 0, 0, 0, 0, default,
|
||||
default, default, 0d, 0d, null);
|
||||
|
||||
private static string NewDirectory()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
using System.Collections.Immutable;
|
||||
using AcDream.App.Physics;
|
||||
using AcDream.Content;
|
||||
using AcDream.Content.Pak;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.App.Tests.Physics;
|
||||
|
||||
public sealed class LiveCollisionAssetPublisherTests
|
||||
{
|
||||
[Fact]
|
||||
public void LiveGfxAndSetup_PublishExactPreparedViewOnce()
|
||||
{
|
||||
const uint GfxId = 0x0100_0042u;
|
||||
const uint SetupId = 0x0200_0042u;
|
||||
var cache = new PhysicsDataCache();
|
||||
var source = new RecordingSource();
|
||||
var publisher = new LiveCollisionAssetPublisher(cache, source);
|
||||
GfxObj gfx = PhysicsGfx();
|
||||
var setup = new Setup();
|
||||
|
||||
publisher.CacheGfxObj(GfxId, gfx);
|
||||
publisher.CacheGfxObj(GfxId, gfx);
|
||||
publisher.CacheSetup(SetupId, setup);
|
||||
publisher.CacheSetup(SetupId, setup);
|
||||
|
||||
Assert.Same(source.Gfx, cache.GetFlatGfxObj(GfxId));
|
||||
Assert.Same(
|
||||
source.Gfx.PhysicsBsp,
|
||||
cache.GetGfxObj(GfxId)!.FlatPhysicsBsp);
|
||||
Assert.Same(source.Setup, cache.GetFlatSetup(SetupId));
|
||||
Assert.Same(
|
||||
source.Setup,
|
||||
cache.GetSetup(SetupId)!.FlatCollision);
|
||||
Assert.Equal(1, source.GfxReads);
|
||||
Assert.Equal(1, source.SetupReads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisualOnlyGfx_RemembersPreparedEmptyAssetWithoutGraphRecord()
|
||||
{
|
||||
const uint GfxId = 0x0100_0043u;
|
||||
var cache = new PhysicsDataCache();
|
||||
var source = new RecordingSource();
|
||||
var publisher = new LiveCollisionAssetPublisher(cache, source);
|
||||
var gfx = new GfxObj();
|
||||
|
||||
publisher.CacheGfxObj(GfxId, gfx);
|
||||
publisher.CacheGfxObj(GfxId, gfx);
|
||||
|
||||
Assert.Null(cache.GetGfxObj(GfxId));
|
||||
Assert.Same(source.Gfx, cache.GetFlatGfxObj(GfxId));
|
||||
Assert.Equal(1, source.GfxReads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingPreparedAsset_RejectsGraphOnlyLivePublication()
|
||||
{
|
||||
var cache = new PhysicsDataCache();
|
||||
var source = new RecordingSource
|
||||
{
|
||||
GfxResult =
|
||||
PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
|
||||
.Missing,
|
||||
};
|
||||
var publisher = new LiveCollisionAssetPublisher(cache, source);
|
||||
|
||||
InvalidDataException fault = Assert.Throws<InvalidDataException>(
|
||||
() => publisher.CacheGfxObj(0x0100_0044u, PhysicsGfx()));
|
||||
|
||||
Assert.Contains("Missing", fault.Message, StringComparison.Ordinal);
|
||||
Assert.Equal(0, cache.GfxObjCount);
|
||||
Assert.Equal(0, cache.FlatGfxObjCount);
|
||||
}
|
||||
|
||||
private static GfxObj PhysicsGfx() => new()
|
||||
{
|
||||
Flags = GfxObjFlags.HasPhysics,
|
||||
PhysicsBSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
},
|
||||
},
|
||||
VertexArray = new VertexArray(),
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
};
|
||||
|
||||
private sealed class RecordingSource : IPreparedCollisionSource
|
||||
{
|
||||
private static readonly FlatPhysicsBsp EmptyPhysics = new(
|
||||
-1,
|
||||
ImmutableArray<FlatPhysicsBspNode>.Empty,
|
||||
ImmutableArray<int>.Empty,
|
||||
FlatPolygonTable.Empty);
|
||||
|
||||
public FlatGfxObjCollisionAsset Gfx { get; } =
|
||||
new(EmptyPhysics, null, null);
|
||||
public FlatSetupCollision Setup { get; } = new(
|
||||
ImmutableArray<FlatCollisionCylinder>.Empty,
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f);
|
||||
public int GfxReads { get; private set; }
|
||||
public int SetupReads { get; private set; }
|
||||
|
||||
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>?
|
||||
GfxResult { get; init; }
|
||||
|
||||
public PreparedAssetPresence ProbeCollision(
|
||||
PakAssetType type,
|
||||
uint sourceFileId) => PreparedAssetPresence.Available;
|
||||
|
||||
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
|
||||
ReadGfxObjCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
GfxReads++;
|
||||
return GfxResult ??
|
||||
PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
|
||||
.Loaded(Gfx);
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatSetupCollision>
|
||||
ReadSetupCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
SetupReads++;
|
||||
return PreparedCollisionReadResult<FlatSetupCollision>
|
||||
.Loaded(Setup);
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
|
||||
ReadCellStructureCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
|
||||
.Missing;
|
||||
|
||||
public PreparedCollisionReadResult<FlatEnvCellTopology>
|
||||
ReadEnvCellTopology(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
PreparedCollisionReadResult<FlatEnvCellTopology>.Missing;
|
||||
|
||||
public PreparedCollisionSourceStats CollisionStats => default;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Reflection;
|
||||
using System.Numerics;
|
||||
using System.Collections.Immutable;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Physics;
|
||||
|
|
@ -32,6 +33,7 @@ public sealed class LandblockBuildFactoryTests
|
|||
Assert.Empty(result.Landblock.Entities);
|
||||
Assert.Same(PhysicsDatBundle.Empty, result.Landblock.PhysicsDats);
|
||||
Assert.Null(result.EnvCells);
|
||||
Assert.Null(result.Collisions);
|
||||
Assert.Equal(HoltburgOrigin, result.Origin);
|
||||
Assert.Equal([(typeof(LandBlock), LandblockId)], proxy.Reads);
|
||||
}
|
||||
|
|
@ -81,6 +83,40 @@ public sealed class LandblockBuildFactoryTests
|
|||
var physics = Assert.IsType<PhysicsDatBundle>(result.Landblock.PhysicsDats);
|
||||
Assert.Null(physics.Info);
|
||||
Assert.Empty(physics.EnvCells);
|
||||
Assert.NotNull(result.Collisions);
|
||||
Assert.Empty(result.Collisions.GfxObjs);
|
||||
Assert.Empty(result.Collisions.Setups);
|
||||
Assert.Empty(result.Collisions.CellStructures);
|
||||
Assert.Empty(result.Collisions.EnvCells);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearPreparedFaultRejectsWholeGenerationWhileFarNeverReadsCollision()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
AddNearFixture(proxy, LandblockId, environmentId: 1);
|
||||
var source = new TestPreparedCollisionSource(
|
||||
PreparedAssetReadStatus.Missing);
|
||||
var factory = new LandblockBuildFactory(
|
||||
dat,
|
||||
source,
|
||||
new object(),
|
||||
new float[256]);
|
||||
|
||||
LandblockBuild? far = factory.Build(
|
||||
Request(LandblockStreamJobKind.LoadFar));
|
||||
Assert.NotNull(far);
|
||||
Assert.Null(far.Collisions);
|
||||
Assert.Equal(0, source.Reads);
|
||||
|
||||
InvalidDataException failure =
|
||||
Assert.Throws<InvalidDataException>(() =>
|
||||
factory.Build(Request(LandblockStreamJobKind.LoadNear)));
|
||||
Assert.Contains(
|
||||
"complete near-tier generation",
|
||||
failure.Message,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(source.Reads > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -103,6 +139,9 @@ public sealed class LandblockBuildFactoryTests
|
|||
var physics = Assert.IsType<PhysicsDatBundle>(result.Landblock.PhysicsDats);
|
||||
Assert.Single(physics.EnvCells);
|
||||
Assert.DoesNotContain(missingCell, physics.EnvCells.Keys);
|
||||
Assert.Single(result.Collisions!.CellStructures);
|
||||
Assert.Single(result.Collisions.EnvCells);
|
||||
Assert.DoesNotContain(missingCell, result.Collisions.EnvCells.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -215,9 +254,9 @@ public sealed class LandblockBuildFactoryTests
|
|||
var heights = Enumerable.Range(0, 256).Select(value => (float)value).ToArray();
|
||||
var factory = new LandblockBuildFactory(
|
||||
dat,
|
||||
TestPreparedCollisionSource.Instance,
|
||||
new object(),
|
||||
heights,
|
||||
new PhysicsDataCache());
|
||||
heights);
|
||||
heights[42] = -1f;
|
||||
|
||||
var snapshot = Assert.IsType<float[]>(typeof(LandblockBuildFactory)
|
||||
|
|
@ -234,9 +273,9 @@ public sealed class LandblockBuildFactoryTests
|
|||
|
||||
Assert.Throws<ArgumentException>(() => new LandblockBuildFactory(
|
||||
dat,
|
||||
TestPreparedCollisionSource.Instance,
|
||||
new object(),
|
||||
new float[255],
|
||||
new PhysicsDataCache()));
|
||||
new float[255]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -273,6 +312,7 @@ public sealed class LandblockBuildFactoryTests
|
|||
dependency.FullName?.Contains("LiveWorldOrigin", StringComparison.Ordinal) == true);
|
||||
Assert.DoesNotContain(dependencyTypes, dependency =>
|
||||
dependency.Namespace?.StartsWith("Silk.NET", StringComparison.Ordinal) == true);
|
||||
Assert.DoesNotContain(typeof(PhysicsDataCache), dependencyTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -288,11 +328,15 @@ public sealed class LandblockBuildFactoryTests
|
|||
float[]? heights = region?.LandDefs.LandHeightTable;
|
||||
Assert.NotNull(heights);
|
||||
Assert.True(heights.Length >= 256);
|
||||
string? pakPath = ResolvePreparedPackagePath(datDirectory);
|
||||
if (pakPath is null)
|
||||
throw SkipException.ForSkip("Installed acdream.pak is required.");
|
||||
using var prepared = new PakPreparedAssetSource(pakPath, bounded);
|
||||
var factory = new LandblockBuildFactory(
|
||||
bounded,
|
||||
prepared,
|
||||
new object(),
|
||||
heights,
|
||||
new PhysicsDataCache());
|
||||
heights);
|
||||
LandblockBuildRequest request = Request(LandblockStreamJobKind.LoadNear, generation: 4);
|
||||
|
||||
LandblockBuild? first = factory.Build(request);
|
||||
|
|
@ -302,6 +346,7 @@ public sealed class LandblockBuildFactoryTests
|
|||
Assert.NotNull(second);
|
||||
Assert.Equal(HoltburgOrigin, first.Origin);
|
||||
Assert.NotNull(first.EnvCells);
|
||||
Assert.NotNull(first.Collisions);
|
||||
Assert.NotEmpty(first.Landblock.Entities);
|
||||
AssertEntityBuildsEqual(first.Landblock.Entities, second.Landblock.Entities);
|
||||
Assert.Equal(
|
||||
|
|
@ -335,6 +380,12 @@ public sealed class LandblockBuildFactoryTests
|
|||
PhysicsDatBundle physics = Assert.IsType<PhysicsDatBundle>(
|
||||
first.Landblock.PhysicsDats);
|
||||
Assert.NotNull(physics.Info);
|
||||
Assert.Equal(physics.EnvCells.Keys.Order(), first.Collisions.EnvCells.Keys.Order());
|
||||
Assert.Equal(
|
||||
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());
|
||||
foreach (WorldEntity entity in first.Landblock.Entities)
|
||||
{
|
||||
if ((entity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
|
||||
|
|
@ -345,7 +396,11 @@ public sealed class LandblockBuildFactoryTests
|
|||
}
|
||||
|
||||
private static LandblockBuildFactory Factory(IDatReaderWriter dat, object gate) =>
|
||||
new(dat, gate, new float[256], new PhysicsDataCache());
|
||||
new(
|
||||
dat,
|
||||
TestPreparedCollisionSource.Instance,
|
||||
gate,
|
||||
new float[256]);
|
||||
|
||||
private static LandblockBuildRequest Request(
|
||||
LandblockStreamJobKind kind,
|
||||
|
|
@ -444,6 +499,26 @@ public sealed class LandblockBuildFactoryTests
|
|||
return Directory.Exists(documents) ? documents : null;
|
||||
}
|
||||
|
||||
private static string? ResolvePreparedPackagePath(string datDirectory)
|
||||
{
|
||||
string? configured =
|
||||
System.Environment.GetEnvironmentVariable("ACDREAM_PAK_PATH");
|
||||
if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured))
|
||||
return configured;
|
||||
|
||||
string besideDats = Path.Combine(datDirectory, "acdream.pak");
|
||||
if (File.Exists(besideDats))
|
||||
return besideDats;
|
||||
|
||||
string documents = Path.Combine(
|
||||
System.Environment.GetFolderPath(
|
||||
System.Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call",
|
||||
"acdream.pak");
|
||||
return File.Exists(documents) ? documents : null;
|
||||
}
|
||||
|
||||
private class RecordingDatProxy : DispatchProxy
|
||||
{
|
||||
private readonly Dictionary<(Type Type, uint Id), object> _objects = new();
|
||||
|
|
@ -506,4 +581,97 @@ public sealed class LandblockBuildFactoryTests
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestPreparedCollisionSource :
|
||||
IPreparedCollisionSource
|
||||
{
|
||||
private static readonly FlatPhysicsBsp EmptyPhysics = new(
|
||||
-1,
|
||||
ImmutableArray<FlatPhysicsBspNode>.Empty,
|
||||
ImmutableArray<int>.Empty,
|
||||
FlatPolygonTable.Empty);
|
||||
private static readonly FlatCellStructureCollisionAsset EmptyCell =
|
||||
new(
|
||||
EmptyPhysics,
|
||||
new FlatCellContainmentBsp(
|
||||
-1,
|
||||
ImmutableArray<FlatCellBspNode>.Empty),
|
||||
FlatPolygonTable.Empty);
|
||||
private static readonly FlatEnvCellTopology EmptyTopology = new(
|
||||
ImmutableArray<FlatEnvCellPortal>.Empty,
|
||||
ImmutableArray<uint>.Empty,
|
||||
seenOutside: false);
|
||||
|
||||
private readonly PreparedAssetReadStatus _status;
|
||||
|
||||
internal TestPreparedCollisionSource(
|
||||
PreparedAssetReadStatus status =
|
||||
PreparedAssetReadStatus.Loaded)
|
||||
{
|
||||
_status = status;
|
||||
}
|
||||
|
||||
internal static TestPreparedCollisionSource Instance { get; } = new();
|
||||
internal int Reads { get; private set; }
|
||||
|
||||
public PreparedAssetPresence ProbeCollision(
|
||||
AcDream.Content.Pak.PakAssetType type,
|
||||
uint sourceFileId) =>
|
||||
PreparedAssetPresence.Available;
|
||||
|
||||
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
|
||||
ReadGfxObjCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Result(
|
||||
new FlatGfxObjCollisionAsset(
|
||||
EmptyPhysics,
|
||||
null,
|
||||
null));
|
||||
|
||||
public PreparedCollisionReadResult<FlatSetupCollision>
|
||||
ReadSetupCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Result(
|
||||
new FlatSetupCollision(
|
||||
ImmutableArray<FlatCollisionCylinder>.Empty,
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f));
|
||||
|
||||
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
|
||||
ReadCellStructureCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Result(EmptyCell);
|
||||
|
||||
public PreparedCollisionReadResult<FlatEnvCellTopology>
|
||||
ReadEnvCellTopology(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Result(EmptyTopology);
|
||||
|
||||
public PreparedCollisionSourceStats CollisionStats => default;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private PreparedCollisionReadResult<T> Result<T>(T value)
|
||||
where T : class
|
||||
{
|
||||
Reads++;
|
||||
return _status switch
|
||||
{
|
||||
PreparedAssetReadStatus.Loaded =>
|
||||
PreparedCollisionReadResult<T>.Loaded(value),
|
||||
PreparedAssetReadStatus.Missing =>
|
||||
PreparedCollisionReadResult<T>.Missing,
|
||||
_ => PreparedCollisionReadResult<T>.Corrupt,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using AcDream.App.Streaming;
|
||||
|
|
@ -94,6 +95,254 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
Assert.Equal(0x01000077u, building.ModelId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DualPublication_InstallsExactPreparedClosureAndReleasesCellOwner()
|
||||
{
|
||||
const uint envCellId = 0xA9B40100u;
|
||||
const uint gfxObjId = 0x01000077u;
|
||||
var fixture = Fixture();
|
||||
PhysicsDatBundle baseBundle =
|
||||
CellPortalAndBuildingBundle(envCellId);
|
||||
DatReaderWriter.DBObjs.Environment environment =
|
||||
Assert.Single(baseBundle.Environments.Values);
|
||||
CellStruct cellStruct = Assert.Single(environment.Cells.Values);
|
||||
cellStruct.PhysicsBSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode
|
||||
{
|
||||
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 10f,
|
||||
},
|
||||
},
|
||||
};
|
||||
cellStruct.CellBSP = new CellBSPTree
|
||||
{
|
||||
Root = new CellBSPNode
|
||||
{
|
||||
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
|
||||
LeafIndex = 1,
|
||||
},
|
||||
};
|
||||
EnvCell envCell = baseBundle.EnvCells[envCellId];
|
||||
var setup = new Setup();
|
||||
setup.CylSpheres.Add(new CylSphere
|
||||
{
|
||||
Origin = new Vector3(0f, 0f, 0.6f),
|
||||
Radius = 0.4f,
|
||||
Height = 1.2f,
|
||||
});
|
||||
GfxObj gfx = PhysicsGfx();
|
||||
var bundle = new PhysicsDatBundle(
|
||||
baseBundle.Info,
|
||||
baseBundle.EnvCells,
|
||||
baseBundle.Environments,
|
||||
new Dictionary<uint, Setup> { [SetupId] = setup },
|
||||
new Dictionary<uint, GfxObj> { [gfxObjId] = gfx });
|
||||
FlatGfxObjCollisionAsset flatGfx =
|
||||
FlatCollisionAssetBuilder.FlattenGfxObj(gfx);
|
||||
FlatSetupCollision flatSetup =
|
||||
FlatCollisionAssetBuilder.FlattenSetup(setup);
|
||||
FlatCellStructureCollisionAsset flatCell =
|
||||
FlatCollisionAssetBuilder.FlattenCellStructure(cellStruct);
|
||||
FlatEnvCellTopology flatTopology =
|
||||
FlatCollisionAssetBuilder.FlattenEnvCellTopology(
|
||||
envCellId,
|
||||
envCell,
|
||||
flatCell.PortalPolygons);
|
||||
var collisions = new LandblockCollisionBuild(
|
||||
ImmutableDictionary<uint, FlatGfxObjCollisionAsset>.Empty
|
||||
.Add(gfxObjId, flatGfx),
|
||||
ImmutableDictionary<uint, FlatSetupCollision>.Empty
|
||||
.Add(SetupId, flatSetup),
|
||||
ImmutableDictionary<uint, FlatCellStructureCollisionAsset>.Empty
|
||||
.Add(envCellId, flatCell),
|
||||
ImmutableDictionary<uint, FlatEnvCellTopology>.Empty
|
||||
.Add(envCellId, flatTopology),
|
||||
[gfxObjId],
|
||||
[SetupId],
|
||||
[envCellId]);
|
||||
WorldEntity entity = new()
|
||||
{
|
||||
Id = 0x80A9B401u,
|
||||
SourceGfxObjOrSetupId = SetupId,
|
||||
Position = new Vector3(12f, 12f, 0f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = [new MeshRef(gfxObjId, Matrix4x4.Identity)],
|
||||
};
|
||||
|
||||
Publish(
|
||||
fixture.Publisher,
|
||||
Build(
|
||||
FirstLandblock,
|
||||
[entity],
|
||||
bundle,
|
||||
collisions: collisions));
|
||||
|
||||
Assert.Same(flatGfx, fixture.Cache.GetFlatGfxObj(gfxObjId));
|
||||
Assert.Same(
|
||||
flatGfx.PhysicsBsp,
|
||||
fixture.Cache.GetGfxObj(gfxObjId)!.FlatPhysicsBsp);
|
||||
Assert.Same(flatSetup, fixture.Cache.GetFlatSetup(SetupId));
|
||||
Assert.Same(
|
||||
flatSetup,
|
||||
fixture.Cache.GetSetup(SetupId)!.FlatCollision);
|
||||
Assert.Same(flatCell, fixture.Cache.GetFlatCellStruct(envCellId));
|
||||
Assert.Same(flatTopology, fixture.Cache.GetFlatEnvCell(envCellId));
|
||||
Assert.Same(
|
||||
flatCell.PhysicsBsp,
|
||||
fixture.Cache.GetCellStruct(envCellId)!.FlatPhysicsBsp);
|
||||
|
||||
fixture.Publisher.RemoveLandblock(FirstLandblock);
|
||||
|
||||
Assert.Equal(0, fixture.Cache.CellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
|
||||
Assert.Equal(0, fixture.Engine.ShadowObjects.RetainedRegistrationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DualPublication_DemotionRehydrateRevisitAndRemovalOwnFlatCellsExactlyOnce()
|
||||
{
|
||||
const uint firstCellId = 0xA9B40100u;
|
||||
const uint adjacentCellId = 0xAAB40100u;
|
||||
var fixture = Fixture();
|
||||
PhysicsDatBundle firstBundle =
|
||||
CellPortalAndBuildingBundle(firstCellId);
|
||||
PhysicsDatBundle adjacentBundle =
|
||||
CellPortalAndBuildingBundle(adjacentCellId);
|
||||
LandblockCollisionBuild firstCollision =
|
||||
FlatCellClosure(firstBundle, firstCellId);
|
||||
LandblockCollisionBuild adjacentCollision =
|
||||
FlatCellClosure(adjacentBundle, adjacentCellId);
|
||||
|
||||
Publish(
|
||||
fixture.Publisher,
|
||||
Build(
|
||||
FirstLandblock,
|
||||
bundle: firstBundle,
|
||||
collisions: firstCollision));
|
||||
Publish(
|
||||
fixture.Publisher,
|
||||
Build(
|
||||
AdjacentLandblock,
|
||||
bundle: adjacentBundle,
|
||||
collisions: adjacentCollision));
|
||||
|
||||
Assert.Same(
|
||||
firstCollision.CellStructures[firstCellId],
|
||||
fixture.Cache.GetFlatCellStruct(firstCellId));
|
||||
Assert.Same(
|
||||
adjacentCollision.CellStructures[adjacentCellId],
|
||||
fixture.Cache.GetFlatCellStruct(adjacentCellId));
|
||||
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
|
||||
|
||||
fixture.Publisher.DemoteToTerrain(FirstLandblock);
|
||||
|
||||
Assert.Null(fixture.Cache.GetFlatCellStruct(firstCellId));
|
||||
Assert.Null(fixture.Cache.GetFlatEnvCell(firstCellId));
|
||||
Assert.Same(
|
||||
adjacentCollision.CellStructures[adjacentCellId],
|
||||
fixture.Cache.GetFlatCellStruct(adjacentCellId));
|
||||
Assert.Equal(1, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(1, fixture.Cache.FlatEnvCellCount);
|
||||
|
||||
LandblockCollisionBuild rehydratedCollision =
|
||||
FlatCellClosure(firstBundle, firstCellId);
|
||||
Publish(
|
||||
fixture.Publisher,
|
||||
Build(
|
||||
FirstLandblock,
|
||||
bundle: firstBundle,
|
||||
collisions: rehydratedCollision));
|
||||
|
||||
Assert.Same(
|
||||
rehydratedCollision.CellStructures[firstCellId],
|
||||
fixture.Cache.GetFlatCellStruct(firstCellId));
|
||||
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
|
||||
|
||||
LandblockCollisionBuild revisitCollision =
|
||||
FlatCellClosure(firstBundle, firstCellId);
|
||||
Publish(
|
||||
fixture.Publisher,
|
||||
Build(
|
||||
FirstLandblock,
|
||||
bundle: firstBundle,
|
||||
collisions: revisitCollision));
|
||||
|
||||
Assert.Same(
|
||||
revisitCollision.CellStructures[firstCellId],
|
||||
fixture.Cache.GetFlatCellStruct(firstCellId));
|
||||
Assert.Equal(2, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(2, fixture.Cache.FlatEnvCellCount);
|
||||
|
||||
fixture.Publisher.RemoveLandblock(FirstLandblock);
|
||||
fixture.Publisher.RemoveLandblock(AdjacentLandblock);
|
||||
|
||||
Assert.Equal(0, fixture.Cache.CellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
|
||||
Assert.Equal(0, fixture.Engine.LandblockCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DualPublication_CancelledPreparedReceiptPublishesNeitherView()
|
||||
{
|
||||
const uint envCellId = 0xA9B40100u;
|
||||
var fixture = Fixture();
|
||||
PhysicsDatBundle bundle =
|
||||
CellPortalAndBuildingBundle(envCellId);
|
||||
CellStruct sourceCell =
|
||||
bundle.Environments.Values.Single().Cells.Values.Single();
|
||||
sourceCell.PhysicsBSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode
|
||||
{
|
||||
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 10f,
|
||||
},
|
||||
},
|
||||
};
|
||||
LandblockCollisionBuild collision =
|
||||
FlatCellClosure(bundle, envCellId);
|
||||
|
||||
LandblockPhysicsPublication cancelled =
|
||||
fixture.Publisher.PreparePublication(
|
||||
RenderReceipt(Build(
|
||||
FirstLandblock,
|
||||
bundle: bundle,
|
||||
collisions: collision)));
|
||||
|
||||
Assert.Equal(0, fixture.Engine.LandblockCount);
|
||||
Assert.Equal(0, fixture.Cache.CellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(0, fixture.Cache.FlatEnvCellCount);
|
||||
Assert.Equal(0, fixture.Engine.ShadowObjects.RetainedRegistrationCount);
|
||||
|
||||
LandblockPhysicsPublication accepted =
|
||||
fixture.Publisher.PreparePublication(
|
||||
RenderReceipt(Build(
|
||||
FirstLandblock,
|
||||
bundle: bundle,
|
||||
collisions: collision)));
|
||||
fixture.Publisher.BeginPublication(accepted);
|
||||
fixture.Publisher.CompletePublication(accepted);
|
||||
|
||||
Assert.Same(
|
||||
collision.CellStructures[envCellId],
|
||||
fixture.Cache.GetFlatCellStruct(envCellId));
|
||||
Assert.Equal(1, fixture.Cache.CellStructCount);
|
||||
Assert.Equal(1, fixture.Cache.FlatCellStructCount);
|
||||
Assert.Equal(1, fixture.Cache.FlatEnvCellCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompletePublication_PhysicsBspSuppressesSetupCylinderFallback()
|
||||
{
|
||||
|
|
@ -592,6 +841,25 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
|
||||
};
|
||||
|
||||
private static GfxObj PhysicsGfx() => new()
|
||||
{
|
||||
Flags = DatReaderWriter.Enums.GfxObjFlags.HasPhysics,
|
||||
PhysicsBSP = new PhysicsBSPTree
|
||||
{
|
||||
Root = new PhysicsBSPNode
|
||||
{
|
||||
Type = DatReaderWriter.Enums.BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 1.25f,
|
||||
},
|
||||
},
|
||||
},
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
VertexArray = new VertexArray(),
|
||||
};
|
||||
|
||||
private static PhysicsDatBundle CellPortalAndBuildingBundle(uint envCellId)
|
||||
{
|
||||
var cellStruct = new CellStruct
|
||||
|
|
@ -658,6 +926,33 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
new Dictionary<uint, GfxObj>());
|
||||
}
|
||||
|
||||
private static LandblockCollisionBuild FlatCellClosure(
|
||||
PhysicsDatBundle bundle,
|
||||
uint envCellId)
|
||||
{
|
||||
EnvCell envCell = bundle.EnvCells[envCellId];
|
||||
DatReaderWriter.DBObjs.Environment environment =
|
||||
bundle.Environments.Values.Single();
|
||||
CellStruct cellStruct = environment.Cells[envCell.CellStructure];
|
||||
FlatCellStructureCollisionAsset flatCell =
|
||||
FlatCollisionAssetBuilder.FlattenCellStructure(cellStruct);
|
||||
FlatEnvCellTopology topology =
|
||||
FlatCollisionAssetBuilder.FlattenEnvCellTopology(
|
||||
envCellId,
|
||||
envCell,
|
||||
flatCell.PortalPolygons);
|
||||
return new LandblockCollisionBuild(
|
||||
ImmutableDictionary<uint, FlatGfxObjCollisionAsset>.Empty,
|
||||
ImmutableDictionary<uint, FlatSetupCollision>.Empty,
|
||||
ImmutableDictionary<uint, FlatCellStructureCollisionAsset>.Empty
|
||||
.Add(envCellId, flatCell),
|
||||
ImmutableDictionary<uint, FlatEnvCellTopology>.Empty
|
||||
.Add(envCellId, topology),
|
||||
[],
|
||||
[],
|
||||
[envCellId]);
|
||||
}
|
||||
|
||||
private static WorldEntity CylinderEntity(
|
||||
uint id,
|
||||
Vector3 position,
|
||||
|
|
@ -674,7 +969,8 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
uint landblockId,
|
||||
IReadOnlyList<WorldEntity>? entities = null,
|
||||
PhysicsDatBundle? bundle = null,
|
||||
LandblockBuildOrigin? origin = null)
|
||||
LandblockBuildOrigin? origin = null,
|
||||
LandblockCollisionBuild? collisions = null)
|
||||
{
|
||||
var landblockInfo = new LandBlockInfo();
|
||||
PhysicsDatBundle physics = bundle ?? new PhysicsDatBundle(
|
||||
|
|
@ -689,7 +985,8 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
FlatHeightmap(),
|
||||
entities ?? Array.Empty<WorldEntity>(),
|
||||
physics),
|
||||
Origin: origin ?? new LandblockBuildOrigin(0xA9, 0xB4));
|
||||
Origin: origin ?? new LandblockBuildOrigin(0xA9, 0xB4),
|
||||
Collisions: collisions);
|
||||
}
|
||||
|
||||
private static LandBlock FlatHeightmap() => new()
|
||||
|
|
|
|||
294
tests/AcDream.Core.Tests/Physics/CollisionShadowVerifierTests.cs
Normal file
294
tests/AcDream.Core.Tests/Physics/CollisionShadowVerifierTests.cs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
public sealed class CollisionShadowVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
public void MatchingDualView_RefereesEveryTraversalWithoutChangingGraph()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics cell = MatchingCell(0xA9B4_0100u);
|
||||
cache.RegisterCellStructForTest(cell.SourceId, cell);
|
||||
var transition = SeedTransition();
|
||||
|
||||
Assert.True(CollisionTraversal.HasCellContainment(cache, cell));
|
||||
Assert.True(CollisionTraversal.HasPhysics(cache, cell));
|
||||
FlatCollisionSphere root =
|
||||
CollisionTraversal.RootBoundingSphere(cache, cell);
|
||||
Assert.Equal(10f, root.Radius);
|
||||
Assert.True(CollisionTraversal.PointInsideCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero));
|
||||
Assert.True(CollisionTraversal.SphereIntersectsCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero,
|
||||
0.48f));
|
||||
TransitionState state = CollisionTraversal.FindCollisions(
|
||||
cache,
|
||||
cell,
|
||||
transition,
|
||||
Vector3.Zero,
|
||||
0.48f,
|
||||
false,
|
||||
Vector3.Zero,
|
||||
0f,
|
||||
Vector3.Zero,
|
||||
Vector3.UnitZ,
|
||||
1f,
|
||||
Quaternion.Identity,
|
||||
engine: null,
|
||||
worldOrigin: Vector3.Zero);
|
||||
|
||||
Assert.Equal(TransitionState.OK, state);
|
||||
CollisionShadowStats stats = cache.CollisionShadowStats;
|
||||
Assert.Equal(6, stats.Queries);
|
||||
Assert.Equal(6, stats.Samples);
|
||||
Assert.Equal(6, stats.Matches);
|
||||
Assert.Equal(0, stats.Mismatches);
|
||||
Assert.Equal(0, stats.Faults);
|
||||
Assert.Empty(Directory.EnumerateFiles(directory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mismatch_WritesDeterministicArtifactAndReturnsGraphResult()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics matching = MatchingCell(0xA9B4_0100u);
|
||||
CellPhysics mismatched = WithFlatPhysics(
|
||||
matching,
|
||||
new FlatPhysicsBsp(
|
||||
-1,
|
||||
ImmutableArray<FlatPhysicsBspNode>.Empty,
|
||||
ImmutableArray<int>.Empty,
|
||||
FlatPolygonTable.Empty));
|
||||
|
||||
bool graphResult = CollisionTraversal.HasPhysics(cache, mismatched);
|
||||
|
||||
Assert.True(graphResult);
|
||||
CollisionShadowStats stats = cache.CollisionShadowStats;
|
||||
Assert.Equal(1, stats.Mismatches);
|
||||
Assert.Equal(0, stats.Faults);
|
||||
string artifact = Assert.Single(Directory.EnumerateFiles(directory));
|
||||
Assert.Equal(
|
||||
"collision-shadow-00000001.json",
|
||||
Path.GetFileName(artifact));
|
||||
using JsonDocument json = JsonDocument.Parse(File.ReadAllText(artifact));
|
||||
JsonElement root = json.RootElement;
|
||||
Assert.Equal(1, root.GetProperty("SchemaVersion").GetInt32());
|
||||
Assert.Equal("HasCellPhysics", root.GetProperty("Kind").GetString());
|
||||
Assert.Equal("0xA9B40100", root.GetProperty("SourceId").GetString());
|
||||
Assert.Equal("Result", root.GetProperty("Difference").GetString());
|
||||
Assert.Equal("True", root.GetProperty("Graph").GetString());
|
||||
Assert.Equal("False", root.GetProperty("Flat").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingFlat_IsRecordedAsFaultAndGraphRemainsAuthoritative()
|
||||
{
|
||||
string directory = NewArtifactDirectory();
|
||||
PhysicsDataCache cache = CacheWithShadow(directory);
|
||||
CellPhysics cell = WithoutFlatContainment(
|
||||
MatchingCell(0xA9B4_0100u));
|
||||
|
||||
bool graphResult = CollisionTraversal.PointInsideCell(
|
||||
cache,
|
||||
cell,
|
||||
Vector3.Zero);
|
||||
|
||||
Assert.True(graphResult);
|
||||
Assert.Equal(1, cache.CollisionShadowStats.Faults);
|
||||
string artifact = Assert.Single(Directory.EnumerateFiles(directory));
|
||||
Assert.Contains(
|
||||
"FlatFault",
|
||||
File.ReadAllText(artifact),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransitionCopyAndComparer_CoverCompleteLogicalState()
|
||||
{
|
||||
Transition source = SeedTransition();
|
||||
source.ObjectInfo.State =
|
||||
ObjectInfoState.Contact | ObjectInfoState.IsPlayer;
|
||||
source.ObjectInfo.TargetId = 0x5000_0001u;
|
||||
source.ObjectInfo.VelocityKilled = true;
|
||||
source.SpherePath.NumSphere = 2;
|
||||
source.SpherePath.LocalSphere[1].Origin = Vector3.UnitZ;
|
||||
source.SpherePath.LocalSphere[1].Radius = 0.48f;
|
||||
source.SpherePath.CarriedBlockOrigin = new Vector3(192f, -192f, 0f);
|
||||
source.SpherePath.SetWalkable(
|
||||
new Plane(Vector3.UnitZ, -1f),
|
||||
[
|
||||
Vector3.Zero,
|
||||
Vector3.UnitX,
|
||||
Vector3.UnitY,
|
||||
],
|
||||
Vector3.UnitZ);
|
||||
source.SpherePath.CellCandidates.Add(0xA9B4_0001u);
|
||||
source.SpherePath.CellCandidates.Add(0xA9B4_0100u);
|
||||
source.CollisionInfo.SetContactPlane(
|
||||
new Plane(Vector3.UnitZ, -1f),
|
||||
0xA9B4_0100u);
|
||||
source.CollisionInfo.SetSlidingNormal(Vector3.UnitX);
|
||||
source.CollisionInfo.CollideObjectGuids.Add(0x8000_0001u);
|
||||
source.CollisionInfo.LastCollidedObjectGuid = 0x8000_0001u;
|
||||
|
||||
var clone = new Transition();
|
||||
clone.CopyFrom(source);
|
||||
|
||||
Assert.False(TransitionExactComparer.TryFindDifference(
|
||||
source,
|
||||
clone,
|
||||
out _,
|
||||
out _,
|
||||
out _));
|
||||
clone.SpherePath.WalkableVertices![1].X =
|
||||
BitConverter.Int32BitsToSingle(
|
||||
BitConverter.SingleToInt32Bits(
|
||||
clone.SpherePath.WalkableVertices[1].X) + 1);
|
||||
Assert.True(TransitionExactComparer.TryFindDifference(
|
||||
source,
|
||||
clone,
|
||||
out string path,
|
||||
out _,
|
||||
out _));
|
||||
Assert.Equal("SpherePath.WalkableVertices[1]", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledGraphTraversal_AllocatesZeroBytes()
|
||||
{
|
||||
var cache = new PhysicsDataCache
|
||||
{
|
||||
CollisionShadow = null,
|
||||
};
|
||||
CellPhysics cell = MatchingCell(0xA9B4_0100u);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
_ = CollisionTraversal.HasPhysics(cache, cell);
|
||||
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int i = 0; i < 10_000; i++)
|
||||
_ = CollisionTraversal.HasPhysics(cache, cell);
|
||||
long allocated =
|
||||
GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
}
|
||||
|
||||
private static PhysicsDataCache CacheWithShadow(string directory)
|
||||
{
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.CollisionShadow = new CollisionShadowVerifier(1, directory);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static CellPhysics MatchingCell(uint sourceId)
|
||||
{
|
||||
var physicsRoot = new PhysicsBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = Vector3.Zero,
|
||||
Radius = 10f,
|
||||
},
|
||||
};
|
||||
var containmentRoot = new CellBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
LeafIndex = 1,
|
||||
};
|
||||
var resolved = new Dictionary<ushort, ResolvedPolygon>();
|
||||
return new CellPhysics
|
||||
{
|
||||
SourceId = sourceId,
|
||||
BSP = new PhysicsBSPTree { Root = physicsRoot },
|
||||
Resolved = resolved,
|
||||
CellBSP = new CellBSPTree { Root = containmentRoot },
|
||||
FlatPhysicsBsp =
|
||||
FlatCollisionAssetBuilder.FlattenPhysicsBsp(
|
||||
physicsRoot,
|
||||
resolved),
|
||||
FlatContainmentBsp =
|
||||
FlatCollisionAssetBuilder.FlattenCellContainmentBsp(
|
||||
containmentRoot),
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
};
|
||||
}
|
||||
|
||||
private static Transition SeedTransition()
|
||||
{
|
||||
var transition = new Transition();
|
||||
transition.SpherePath.InitPath(
|
||||
Vector3.Zero,
|
||||
Vector3.Zero,
|
||||
0xA9B4_0100u,
|
||||
0.48f);
|
||||
return transition;
|
||||
}
|
||||
|
||||
private static CellPhysics WithFlatPhysics(
|
||||
CellPhysics source,
|
||||
FlatPhysicsBsp flat) => new()
|
||||
{
|
||||
SourceId = source.SourceId,
|
||||
BSP = source.BSP,
|
||||
PhysicsPolygons = source.PhysicsPolygons,
|
||||
Vertices = source.Vertices,
|
||||
WorldTransform = source.WorldTransform,
|
||||
InverseWorldTransform = source.InverseWorldTransform,
|
||||
Resolved = source.Resolved,
|
||||
FlatPhysicsBsp = flat,
|
||||
CellBSP = source.CellBSP,
|
||||
FlatContainmentBsp = source.FlatContainmentBsp,
|
||||
FlatPortalPolygons = source.FlatPortalPolygons,
|
||||
FlatTopology = source.FlatTopology,
|
||||
Portals = source.Portals,
|
||||
PortalPolygons = source.PortalPolygons,
|
||||
VisibleCellIds = source.VisibleCellIds,
|
||||
SeenOutside = source.SeenOutside,
|
||||
};
|
||||
|
||||
private static CellPhysics WithoutFlatContainment(
|
||||
CellPhysics source) => new()
|
||||
{
|
||||
SourceId = source.SourceId,
|
||||
BSP = source.BSP,
|
||||
PhysicsPolygons = source.PhysicsPolygons,
|
||||
Vertices = source.Vertices,
|
||||
WorldTransform = source.WorldTransform,
|
||||
InverseWorldTransform = source.InverseWorldTransform,
|
||||
Resolved = source.Resolved,
|
||||
FlatPhysicsBsp = source.FlatPhysicsBsp,
|
||||
CellBSP = source.CellBSP,
|
||||
FlatContainmentBsp = null,
|
||||
FlatPortalPolygons = source.FlatPortalPolygons,
|
||||
FlatTopology = source.FlatTopology,
|
||||
Portals = source.Portals,
|
||||
PortalPolygons = source.PortalPolygons,
|
||||
VisibleCellIds = source.VisibleCellIds,
|
||||
SeenOutside = source.SeenOutside,
|
||||
};
|
||||
|
||||
private static string NewArtifactDirectory()
|
||||
{
|
||||
string directory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"acdream-collision-shadow-tests",
|
||||
Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue