feat(vfx): port retail hidden and teleport presentation
Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
This commit is contained in:
parent
a51ebc66e9
commit
1e98d81448
46 changed files with 4883 additions and 127 deletions
166
tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs
Normal file
166
tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Physics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.Physics;
|
||||
|
||||
public sealed class RemotePhysicsUpdaterTests
|
||||
{
|
||||
[Fact]
|
||||
public void TickHidden_AppliesPositionManagerOffsetWithoutAdvancingPhysics()
|
||||
{
|
||||
var motion = new GameWindow.RemoteMotion();
|
||||
motion.Body.Position = Vector3.Zero;
|
||||
motion.Body.Orientation = Quaternion.Identity;
|
||||
motion.Body.Velocity = new Vector3(4f, 0f, 5f);
|
||||
motion.Body.Acceleration = new Vector3(0f, 0f, -9.8f);
|
||||
motion.CellId = 0x01010001u;
|
||||
motion.Interp.Enqueue(
|
||||
new Vector3(1f, 0f, 0f),
|
||||
heading: 0f,
|
||||
isMovingTo: false,
|
||||
currentBodyPosition: Vector3.Zero);
|
||||
var entity = new WorldEntity
|
||||
{
|
||||
Id = 1u,
|
||||
ServerGuid = 0x70000001u,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
};
|
||||
|
||||
var updater = new RemotePhysicsUpdater(
|
||||
new PhysicsEngine(),
|
||||
(_, _) => (0.48f, 1.835f),
|
||||
(_, _, _, _) => { });
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
poses.Publish(entity, Array.Empty<Matrix4x4>());
|
||||
updater.TickHidden(motion, entity, 0.1f);
|
||||
Assert.True(poses.UpdateRoot(entity));
|
||||
|
||||
Assert.InRange(motion.Body.Position.X, 0.01f, 1f);
|
||||
Assert.Equal(0f, motion.Body.Position.Z);
|
||||
Assert.Equal(new Vector3(4f, 0f, 5f), motion.Body.Velocity);
|
||||
Assert.Equal(motion.Body.Position, entity.Position);
|
||||
Assert.Equal(motion.CellId, entity.ParentCellId);
|
||||
Assert.True(poses.TryGetRootPose(entity.Id, out Matrix4x4 root));
|
||||
Assert.Equal(entity.Position, root.Translation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TickHidden_CrossCellCommitUpdatesCanonicalCellBeforeUnhide()
|
||||
{
|
||||
var engine = BuildBoundaryEngine();
|
||||
var updater = new RemotePhysicsUpdater(
|
||||
engine,
|
||||
(_, _) => (0.48f, 1.835f),
|
||||
(_, _, _, _) => { });
|
||||
var motion = new GameWindow.RemoteMotion();
|
||||
motion.Body.Position = new Vector3(191f, 10f, 50f);
|
||||
motion.Body.Orientation = Quaternion.Identity;
|
||||
motion.Body.TransientState = TransientStateFlags.Contact
|
||||
| TransientStateFlags.OnWalkable;
|
||||
uint canonicalCell = 0xA9B40039u;
|
||||
int canonicalWrites = 0;
|
||||
motion.BindCanonicalCell(
|
||||
() => canonicalCell,
|
||||
value =>
|
||||
{
|
||||
canonicalCell = value;
|
||||
canonicalWrites++;
|
||||
});
|
||||
motion.CellId = canonicalCell;
|
||||
canonicalWrites = 0;
|
||||
motion.Interp.Enqueue(
|
||||
new Vector3(193f, 10f, 50f),
|
||||
heading: 0f,
|
||||
isMovingTo: false,
|
||||
currentBodyPosition: motion.Body.Position);
|
||||
var entity = new WorldEntity
|
||||
{
|
||||
Id = 1u,
|
||||
ServerGuid = 0x70000002u,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = motion.Body.Position,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
};
|
||||
|
||||
updater.TickHidden(motion, entity, 2f);
|
||||
|
||||
Assert.Equal(0xAAB40001u, canonicalCell);
|
||||
Assert.Equal(canonicalCell, entity.ParentCellId);
|
||||
Assert.True(canonicalWrites > 0);
|
||||
Assert.True(entity.Position.X > 192f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TickHidden_LandingSynchronizesRemoteAirborneState()
|
||||
{
|
||||
var engine = BuildBoundaryEngine();
|
||||
var updater = new RemotePhysicsUpdater(
|
||||
engine,
|
||||
(_, _) => (0.48f, 1.835f),
|
||||
(_, _, _, _) => { });
|
||||
var motion = new GameWindow.RemoteMotion
|
||||
{
|
||||
Airborne = true,
|
||||
};
|
||||
motion.Body.Position = new Vector3(10f, 10f, 50f);
|
||||
motion.Body.Orientation = Quaternion.Identity;
|
||||
motion.Body.TransientState = TransientStateFlags.Active;
|
||||
motion.CellId = 0xA9B40001u;
|
||||
motion.Interp.Enqueue(
|
||||
new Vector3(11f, 10f, 50f),
|
||||
heading: 0f,
|
||||
isMovingTo: false,
|
||||
currentBodyPosition: motion.Body.Position);
|
||||
var entity = new WorldEntity
|
||||
{
|
||||
Id = 3u,
|
||||
ServerGuid = 0x70000003u,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = motion.Body.Position,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
};
|
||||
|
||||
updater.TickHidden(motion, entity, 0.1f);
|
||||
|
||||
Assert.True(motion.Body.InContact);
|
||||
Assert.True(motion.Body.OnWalkable);
|
||||
Assert.False(motion.Airborne);
|
||||
}
|
||||
|
||||
private static PhysicsEngine BuildBoundaryEngine()
|
||||
{
|
||||
static TerrainSurface FlatTerrain()
|
||||
{
|
||||
var heights = new byte[81];
|
||||
var table = new float[256];
|
||||
Array.Fill(table, 50f);
|
||||
return new TerrainSurface(heights, table);
|
||||
}
|
||||
|
||||
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
|
||||
engine.AddLandblock(
|
||||
0xA9B4FFFFu,
|
||||
FlatTerrain(),
|
||||
Array.Empty<CellSurface>(),
|
||||
Array.Empty<PortalPlane>(),
|
||||
0f,
|
||||
0f);
|
||||
engine.AddLandblock(
|
||||
0xAAB4FFFFu,
|
||||
FlatTerrain(),
|
||||
Array.Empty<CellSurface>(),
|
||||
Array.Empty<PortalPlane>(),
|
||||
192f,
|
||||
0f);
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue