acdream/src/AcDream.Core/Meshing/SetupPartTransforms.cs

62 lines
2.2 KiB
C#

using System.Collections.Generic;
using System.Numerics;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
using DatReaderWriter.Types;
namespace AcDream.Core.Meshing;
/// <summary>
/// Computes retail's rigid per-part frames for attachments and effects.
/// Visual GfxObj scale is deliberately excluded.
/// </summary>
/// <remarks>
/// Pose selection matches <see cref="SetupMesh.Flatten"/>: an explicit motion
/// frame, then Resting, Default, then the first placement frame. Retail
/// <c>CPartArray::UpdateParts</c> (<c>0x005190F0</c>) scales only the frame
/// origin by the object's scale. <c>CPartArray::SetScaleInternal</c>
/// (<c>0x00518A00</c>) stores Setup DefaultScale separately on the visual
/// <c>CPhysicsPart::gfxobj_scale</c>, so it never enters this rigid frame.
/// </remarks>
public static class SetupPartTransforms
{
public static IReadOnlyList<Matrix4x4> Compute(
Setup setup,
AnimationFrame? motionFrameOverride = null,
float objectScale = 1.0f)
{
ArgumentNullException.ThrowIfNull(setup);
AnimationFrame? source = motionFrameOverride;
if (source is null && setup.PlacementFrames.TryGetValue(Placement.Resting, out var resting))
{
source = resting;
}
else if (source is null && setup.PlacementFrames.TryGetValue(Placement.Default, out var def))
{
source = def;
}
else if (source is null)
{
foreach (var kvp in setup.PlacementFrames)
{
source = kvp.Value;
break;
}
}
if (source is null)
return Array.Empty<Matrix4x4>();
int partCount = setup.Parts.Count;
var result = new Matrix4x4[partCount];
for (int i = 0; i < partCount; i++)
{
Frame frame = i < source.Frames.Count
? source.Frames[i]
: new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity };
result[i] = Matrix4x4.CreateFromQuaternion(frame.Orientation)
* Matrix4x4.CreateTranslation(frame.Origin * objectScale);
}
return result;
}
}