feat(vfx): bind effects to live animated poses
This commit is contained in:
parent
96ddfdf175
commit
542dcfc384
41 changed files with 3246 additions and 741 deletions
89
src/AcDream.App/Rendering/Vfx/AnimationHookFrameQueue.cs
Normal file
89
src/AcDream.App/Rendering/Vfx/AnimationHookFrameQueue.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Vfx;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.App.Rendering.Vfx;
|
||||
|
||||
/// <summary>
|
||||
/// Defers animation-hook delivery until every entity and equipped child has
|
||||
/// published its final pose for the frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail stages hooks during sequence advance, then the same object update
|
||||
/// commits the final part frame and runs <c>CPhysicsObj::UpdateChild</c>
|
||||
/// (<c>0x00512D50</c>) before particles advance or anything draws. Capturing
|
||||
/// here preserves that observable order and avoids firing a hand or weapon
|
||||
/// effect against the previous animation frame.
|
||||
/// </remarks>
|
||||
public sealed class AnimationHookFrameQueue
|
||||
{
|
||||
private readonly AnimationHookRouter _router;
|
||||
private readonly IEntityEffectPoseSource _poses;
|
||||
private readonly List<Entry> _entries = new();
|
||||
|
||||
public AnimationHookFrameQueue(
|
||||
AnimationHookRouter router,
|
||||
IEntityEffectPoseSource poses)
|
||||
{
|
||||
_router = router ?? throw new ArgumentNullException(nameof(router));
|
||||
_poses = poses ?? throw new ArgumentNullException(nameof(poses));
|
||||
}
|
||||
|
||||
public int Count => _entries.Count;
|
||||
|
||||
public void Capture(uint ownerLocalId, AnimationSequencer sequencer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sequencer);
|
||||
Capture(ownerLocalId, sequencer, sequencer.ConsumePendingHooks());
|
||||
}
|
||||
|
||||
internal void Capture(
|
||||
uint ownerLocalId,
|
||||
AnimationSequencer sequencer,
|
||||
IReadOnlyList<AnimationHook> hooks)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sequencer);
|
||||
ArgumentNullException.ThrowIfNull(hooks);
|
||||
_entries.Add(new Entry(
|
||||
ownerLocalId,
|
||||
sequencer,
|
||||
hooks));
|
||||
}
|
||||
|
||||
public void Drain()
|
||||
{
|
||||
for (int i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
Entry entry = _entries[i];
|
||||
bool hasRootPose = _poses.TryGetRootPose(
|
||||
entry.OwnerLocalId,
|
||||
out Matrix4x4 rootWorld);
|
||||
Vector3 worldPosition = hasRootPose
|
||||
? rootWorld.Translation
|
||||
: Vector3.Zero;
|
||||
for (int hi = 0; hi < entry.Hooks.Count; hi++)
|
||||
{
|
||||
AnimationHook? hook = entry.Hooks[hi];
|
||||
if (hook is null)
|
||||
continue;
|
||||
if (hasRootPose)
|
||||
_router.OnHook(entry.OwnerLocalId, worldPosition, hook);
|
||||
if (hook is AnimationDoneHook)
|
||||
entry.Sequencer.Manager.AnimationDone(success: true);
|
||||
}
|
||||
|
||||
// Retail sweeps zero-tick motion entries even on frames without a
|
||||
// hook. It remains paired with every sequencer advanced this frame.
|
||||
entry.Sequencer.Manager.UseTime();
|
||||
}
|
||||
_entries.Clear();
|
||||
}
|
||||
|
||||
public void Clear() => _entries.Clear();
|
||||
|
||||
private readonly record struct Entry(
|
||||
uint OwnerLocalId,
|
||||
AnimationSequencer Sequencer,
|
||||
IReadOnlyList<AnimationHook> Hooks);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue