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
66
src/AcDream.App/Rendering/Vfx/IndexedSetupPartPoseBuilder.cs
Normal file
66
src/AcDream.App/Rendering/Vfx/IndexedSetupPartPoseBuilder.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Rendering.Vfx;
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs retail's stable CPartArray indices from a drawable-only
|
||||
/// MeshRef projection. Rigid part frames come from Setup data while missing
|
||||
/// DAT parts keep an unavailable placeholder at their stable Setup index.
|
||||
/// Hydration omits by GfxObj DID and otherwise preserves Setup order, so
|
||||
/// duplicate DIDs are all present or all absent; first-unconsumed matching is
|
||||
/// lossless under that invariant.
|
||||
/// </summary>
|
||||
internal static class IndexedSetupPartPoseBuilder
|
||||
{
|
||||
public static (Matrix4x4[] Poses, bool[] Available) Build(
|
||||
Setup setup,
|
||||
WorldEntity entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(setup);
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
int partCount = setup.Parts.Count;
|
||||
var poses = new Matrix4x4[partCount];
|
||||
IReadOnlyList<Matrix4x4> defaults = SetupPartTransforms.Compute(
|
||||
setup,
|
||||
objectScale: entity.Scale);
|
||||
for (int i = 0; i < partCount; i++)
|
||||
poses[i] = i < defaults.Count ? defaults[i] : Matrix4x4.Identity;
|
||||
|
||||
var expectedGfx = new uint[partCount];
|
||||
for (int i = 0; i < partCount; i++)
|
||||
expectedGfx[i] = (uint)setup.Parts[i];
|
||||
for (int i = 0; i < entity.PartOverrides.Count; i++)
|
||||
{
|
||||
PartOverride replacement = entity.PartOverrides[i];
|
||||
if (replacement.PartIndex < expectedGfx.Length)
|
||||
expectedGfx[replacement.PartIndex] = replacement.GfxObjId;
|
||||
}
|
||||
|
||||
var available = new bool[partCount];
|
||||
var consumed = new bool[entity.MeshRefs.Count];
|
||||
for (int partIndex = 0; partIndex < partCount; partIndex++)
|
||||
{
|
||||
for (int drawableIndex = 0; drawableIndex < entity.MeshRefs.Count; drawableIndex++)
|
||||
{
|
||||
if (consumed[drawableIndex]
|
||||
|| entity.MeshRefs[drawableIndex].GfxObjId != expectedGfx[partIndex])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
consumed[drawableIndex] = true;
|
||||
available[partIndex] = true;
|
||||
// The drawable transform carries DefaultScale. Retail's
|
||||
// attachment/effect frame is the rigid Setup frame computed
|
||||
// above; matching only establishes which stable slot exists.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (poses, available);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue