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

@ -27,6 +27,7 @@ public sealed class EntityEffectController : IAnimationHookSink
private readonly LiveEntityRuntime _liveEntities;
private readonly PhysicsScriptRunner _runner;
private readonly PhysicsScriptTableResolver _tables;
private readonly EntityEffectPoseRegistry _poses;
private readonly Func<uint, uint, uint?> _childAtPart;
private readonly Func<uint, uint?> _parentOfAttachedChild;
private readonly Action<uint> _ownerUnregistered;
@ -42,6 +43,7 @@ public sealed class EntityEffectController : IAnimationHookSink
LiveEntityRuntime liveEntities,
PhysicsScriptRunner runner,
PhysicsScriptTableResolver tables,
EntityEffectPoseRegistry poses,
Func<uint, uint, uint?>? childAtPart = null,
Func<uint, uint?>? parentOfAttachedChild = null,
Action<uint>? ownerUnregistered = null,
@ -50,6 +52,7 @@ public sealed class EntityEffectController : IAnimationHookSink
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_runner = runner ?? throw new ArgumentNullException(nameof(runner));
_tables = tables ?? throw new ArgumentNullException(nameof(tables));
_poses = poses ?? throw new ArgumentNullException(nameof(poses));
_childAtPart = childAtPart ?? ((_, _) => null);
_parentOfAttachedChild = parentOfAttachedChild ?? (_ => null);
_ownerUnregistered = ownerUnregistered ?? (_ => { });
@ -201,8 +204,8 @@ public sealed class EntityEffectController : IAnimationHookSink
_pendingByServerGuid.Clear();
}
/// <summary>Refreshes every live root anchor after animation/movement.</summary>
public void RefreshLiveOwnerAnchors()
/// <summary>Refreshes every live root after animation/movement.</summary>
public void RefreshLiveOwnerPoses()
{
foreach (uint serverGuid in _readyServerGuids.ToArray())
{
@ -353,7 +356,18 @@ public sealed class EntityEffectController : IAnimationHookSink
&& record.WorldEntity is { } entity
&& entity.Id == localId)
{
_runner.SetOwnerAnchor(localId, entity.Position);
// Top-level roots follow the WorldEntity directly. Attached roots
// were already composed through the parent's current part by
// EquippedChildRenderController; overwriting one with the child's
// bookkeeping Position would collapse a weapon effect to the
// parent's origin.
if (record.ProjectionKind is LiveEntityProjectionKind.World)
_poses.UpdateRoot(entity);
Vector3 anchor = _poses.TryGetRootPose(localId, out Matrix4x4 rootWorld)
? rootWorld.Translation
: entity.Position;
_runner.SetOwnerAnchor(localId, anchor);
}
}