feat(vfx): bind effects to live animated poses

This commit is contained in:
Erik 2026-07-14 10:56:01 +02:00
parent 96ddfdf175
commit 542dcfc384
41 changed files with 3246 additions and 741 deletions

View file

@ -25,8 +25,8 @@ public sealed class EquippedChildAttachmentTests
};
var parentPose = new[]
{
new MeshRef(1, Matrix4x4.Identity),
new MeshRef(2, Matrix4x4.CreateTranslation(10, 0, 0)),
Matrix4x4.Identity,
Matrix4x4.CreateTranslation(10, 0, 0),
};
var child = new Setup
{
@ -42,12 +42,14 @@ public sealed class EquippedChildAttachmentTests
};
var template = new[] { new MeshRef(0x01000001u, Matrix4x4.Identity) };
bool ok = EquippedChildAttachment.TryCompose(
bool ok = EquippedChildAttachment.TryComposePose(
parent, parentPose, child, ParentLocation.LeftHand,
Placement.LeftHand, template, 1.0f, out var result);
Placement.LeftHand, template, 1.0f, out EquippedChildPose pose);
Assert.True(ok);
Assert.Equal(15f, Assert.Single(result).PartTransform.Translation.X);
Assert.Equal(12f, pose.RootLocal.Translation.X);
Assert.Equal(3f, Assert.Single(pose.PartLocal).Translation.X);
Assert.Equal(15f, Assert.Single(pose.AttachedParts).PartTransform.Translation.X);
}
[Fact]
@ -88,6 +90,115 @@ public sealed class EquippedChildAttachmentTests
Assert.Empty(result);
}
[Fact]
public void TryComposePoseInto_UnavailableValidPartRejectsWithoutRootFallback()
{
var parent = ParentWithRightHand(partId: 1, FrameAt(4, 0, 0));
var child = ChildWithOnePart();
bool ok = EquippedChildAttachment.TryComposePoseInto(
parent,
[Matrix4x4.Identity, Matrix4x4.CreateTranslation(10, 0, 0)],
[true, false],
child,
ParentLocation.RightHand,
Placement.Default,
[new MeshRef(0x01000001u, Matrix4x4.Identity)],
1f,
null,
null,
out _);
Assert.False(ok);
}
[Fact]
public void TryComposePoseInto_OutOfRangePartUsesRetailRootAndReusesBuffers()
{
var parent = ParentWithRightHand(partId: 99, FrameAt(4, 0, 0));
var child = ChildWithOnePart();
var poseBuffer = new Matrix4x4[1];
var meshBuffer = new MeshRef[1];
bool ok = EquippedChildAttachment.TryComposePoseInto(
parent,
[Matrix4x4.CreateTranslation(10, 0, 0)],
[true],
child,
ParentLocation.RightHand,
Placement.Default,
[new MeshRef(0x01000001u, Matrix4x4.Identity)],
1f,
poseBuffer,
meshBuffer,
out EquippedChildPose pose);
Assert.True(ok);
Assert.Same(poseBuffer, pose.PartLocal);
Assert.Same(meshBuffer, pose.AttachedParts);
Assert.Equal(4f, pose.RootLocal.Translation.X);
}
[Fact]
public void NestedChildRootsComposeThroughImmediateParentWorldPose()
{
Matrix4x4 topLevelWorld = Matrix4x4.CreateTranslation(100, 0, 0);
Matrix4x4 childRootLocal = Matrix4x4.CreateTranslation(10, 0, 0);
Matrix4x4 grandchildRootLocal = Matrix4x4.CreateTranslation(2, 0, 0);
Matrix4x4 childWorld = childRootLocal * topLevelWorld;
Matrix4x4 grandchildWorld = grandchildRootLocal * childWorld;
Assert.Equal(112f, grandchildWorld.Translation.X);
}
[Fact]
public void ChildRigidPoseExcludesDefaultScaleAndScalesOnlyOrigin()
{
var parent = ParentWithRightHand(partId: -1, FrameAt(4, 0, 0));
var child = new Setup
{
Parts = { 0x01000001u },
DefaultScale = { new Vector3(5f, 6f, 7f) },
PlacementFrames =
{
[Placement.Default] = new AnimationFrame(1)
{
Frames = { FrameAt(3, 0, 0) },
},
},
};
Assert.True(EquippedChildAttachment.TryComposePose(
parent,
Array.Empty<Matrix4x4>(),
child,
ParentLocation.RightHand,
Placement.Default,
[new MeshRef(0x01000001u, Matrix4x4.Identity)],
childScale: 2f,
out EquippedChildPose pose));
Matrix4x4 rigid = Assert.Single(pose.PartLocal);
Assert.Equal(new Vector3(6f, 0f, 0f), rigid.Translation);
Assert.Equal(Vector3.One, new Vector3(rigid.M11, rigid.M22, rigid.M33));
Matrix4x4 visual = Assert.Single(pose.AttachedParts).PartTransform;
Assert.Equal(new Vector3(10f, 12f, 14f), new Vector3(visual.M11, visual.M22, visual.M33));
}
private static Setup ChildWithOnePart() => new()
{
Parts = { 0x01000001u },
DefaultScale = { Vector3.One },
PlacementFrames =
{
[Placement.Default] = new AnimationFrame(1)
{
Frames = { FrameAt(1, 0, 0) },
},
},
};
private static Setup ParentWithRightHand(int partId, Frame frame) => new()
{
HoldingLocations =