66 lines
2.5 KiB
C#
66 lines
2.5 KiB
C#
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);
|
|
}
|
|
}
|