fix(animation): retain trailing PartArray poses
Port CPartArray::UpdateParts minimum-count behavior: preserve the prior visual and rigid transforms for setup parts absent from a short authored AnimFrame, including the initial hydrated rest pose. Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
f004ac5562
commit
a2fd61684a
6 changed files with 326 additions and 59 deletions
|
|
@ -181,29 +181,36 @@ internal sealed class LiveEntityAnimationPresenter
|
|||
{
|
||||
int partCount = animation.PartTemplate.Count;
|
||||
EmitPartDiagnostics(record, animation, sequenceFrames, partCount);
|
||||
EnsureRetainedPoses(animation);
|
||||
List<MeshRef> meshRefs = animation.MeshRefsScratch;
|
||||
meshRefs.Clear();
|
||||
List<Matrix4x4> rigidPoses = animation.EffectPartPosesScratch;
|
||||
rigidPoses.Clear();
|
||||
List<Matrix4x4> visualPoses = animation.VisualPartPosesScratch;
|
||||
Matrix4x4 scale = animation.Scale == 1f
|
||||
? Matrix4x4.Identity
|
||||
: Matrix4x4.CreateScale(animation.Scale);
|
||||
|
||||
for (int i = 0; i < partCount; i++)
|
||||
{
|
||||
ResolvePartFrame(animation, sequenceFrames, i, out Vector3 origin, out Quaternion orientation);
|
||||
Vector3 defaultScale = i < animation.Setup.DefaultScale.Count
|
||||
? animation.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
Matrix4x4 visual = Matrix4x4.CreateScale(defaultScale)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin);
|
||||
if (animation.Scale != 1f)
|
||||
visual *= scale;
|
||||
|
||||
Matrix4x4 rigid = Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin * animation.Scale);
|
||||
rigidPoses.Add(rigid);
|
||||
if (TryResolvePartFrame(
|
||||
animation,
|
||||
sequenceFrames,
|
||||
i,
|
||||
out Vector3 origin,
|
||||
out Quaternion orientation))
|
||||
{
|
||||
Vector3 defaultScale = i < animation.Setup.DefaultScale.Count
|
||||
? animation.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
Matrix4x4 visual = Matrix4x4.CreateScale(defaultScale)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin);
|
||||
if (animation.Scale != 1f)
|
||||
visual *= scale;
|
||||
visualPoses[i] = visual;
|
||||
rigidPoses[i] = Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin * animation.Scale);
|
||||
}
|
||||
|
||||
LiveAnimationPartTemplate template = animation.PartTemplate[i];
|
||||
if (!template.IsDrawable
|
||||
|
|
@ -211,7 +218,7 @@ internal sealed class LiveEntityAnimationPresenter
|
|||
{
|
||||
continue;
|
||||
}
|
||||
meshRefs.Add(new MeshRef(template.GfxObjId, visual)
|
||||
meshRefs.Add(new MeshRef(template.GfxObjId, visualPoses[i])
|
||||
{
|
||||
SurfaceOverrides = template.SurfaceOverrides,
|
||||
});
|
||||
|
|
@ -226,7 +233,7 @@ internal sealed class LiveEntityAnimationPresenter
|
|||
_effectPoses.Publish(entity, rigidPoses, animation.PartAvailability);
|
||||
}
|
||||
|
||||
private static void ResolvePartFrame(
|
||||
private static bool TryResolvePartFrame(
|
||||
LiveEntityAnimationState animation,
|
||||
IReadOnlyList<PartTransform>? sequenceFrames,
|
||||
int partIndex,
|
||||
|
|
@ -239,13 +246,11 @@ internal sealed class LiveEntityAnimationPresenter
|
|||
{
|
||||
origin = sequenceFrames[partIndex].Origin;
|
||||
orientation = sequenceFrames[partIndex].Orientation;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin = Vector3.Zero;
|
||||
orientation = Quaternion.Identity;
|
||||
}
|
||||
return;
|
||||
origin = default;
|
||||
orientation = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
int frameIndex = (int)Math.Floor(animation.CurrFrame);
|
||||
|
|
@ -270,12 +275,59 @@ internal sealed class LiveEntityAnimationPresenter
|
|||
var next = partIndex < nextFrames.Count ? nextFrames[partIndex] : first;
|
||||
origin = Vector3.Lerp(first.Origin, next.Origin, t);
|
||||
orientation = Quaternion.Slerp(first.Orientation, next.Orientation, t);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
origin = default;
|
||||
orientation = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void EnsureRetainedPoses(LiveEntityAnimationState animation)
|
||||
{
|
||||
int partCount = animation.PartTemplate.Count;
|
||||
if (animation.PresentationPosesInitialized
|
||||
&& animation.VisualPartPosesScratch.Count == partCount
|
||||
&& animation.EffectPartPosesScratch.Count == partCount)
|
||||
{
|
||||
origin = Vector3.Zero;
|
||||
orientation = Quaternion.Identity;
|
||||
return;
|
||||
}
|
||||
|
||||
List<Matrix4x4> visual = animation.VisualPartPosesScratch;
|
||||
List<Matrix4x4> rigid = animation.EffectPartPosesScratch;
|
||||
visual.Clear();
|
||||
rigid.Clear();
|
||||
bool[] consumed = new bool[animation.Entity.MeshRefs.Count];
|
||||
for (int i = 0; i < partCount; i++)
|
||||
{
|
||||
Matrix4x4 rigidRest = i < animation.Entity.IndexedPartTransforms.Count
|
||||
? animation.Entity.IndexedPartTransforms[i]
|
||||
: Matrix4x4.Identity;
|
||||
rigid.Add(rigidRest);
|
||||
|
||||
LiveAnimationPartTemplate template = animation.PartTemplate[i];
|
||||
Matrix4x4 visualRest = default;
|
||||
bool found = false;
|
||||
for (int meshIndex = 0; meshIndex < animation.Entity.MeshRefs.Count; meshIndex++)
|
||||
{
|
||||
MeshRef mesh = animation.Entity.MeshRefs[meshIndex];
|
||||
if (consumed[meshIndex] || mesh.GfxObjId != template.GfxObjId)
|
||||
continue;
|
||||
consumed[meshIndex] = true;
|
||||
visualRest = mesh.PartTransform;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
Vector3 defaultScale = i < animation.Setup.DefaultScale.Count
|
||||
? animation.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
visualRest = Matrix4x4.CreateScale(defaultScale * animation.Scale)
|
||||
* rigidRest;
|
||||
}
|
||||
visual.Add(visualRest);
|
||||
}
|
||||
animation.PresentationPosesInitialized = true;
|
||||
}
|
||||
|
||||
private void EmitSequenceDiagnostics(
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
public readonly Frame RootFrameScratch = new();
|
||||
public readonly List<MeshRef> MeshRefs = new();
|
||||
public readonly List<Matrix4x4> PartPoses = new();
|
||||
public readonly List<Matrix4x4> VisualPartPoses = new();
|
||||
}
|
||||
|
||||
private readonly IAnimationLoader _animationLoader;
|
||||
|
|
@ -389,8 +390,8 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
|
||||
private static void Compose(Owner owner, IReadOnlyList<PartTransform> frames)
|
||||
{
|
||||
EnsureRetainedPoses(owner);
|
||||
owner.MeshRefs.Clear();
|
||||
owner.PartPoses.Clear();
|
||||
Matrix4x4 objectScale = owner.Entity.Scale == 1f
|
||||
? Matrix4x4.Identity
|
||||
: Matrix4x4.CreateScale(owner.Entity.Scale);
|
||||
|
|
@ -398,28 +399,27 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
int partCount = owner.Setup.Parts.Count;
|
||||
for (int i = 0; i < partCount; i++)
|
||||
{
|
||||
Vector3 origin = i < frames.Count ? frames[i].Origin : Vector3.Zero;
|
||||
Quaternion orientation = i < frames.Count
|
||||
? frames[i].Orientation
|
||||
: Quaternion.Identity;
|
||||
Vector3 defaultScale = i < owner.Setup.DefaultScale.Count
|
||||
? owner.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
if (i < frames.Count)
|
||||
{
|
||||
Vector3 origin = frames[i].Origin;
|
||||
Quaternion orientation = frames[i].Orientation;
|
||||
Vector3 defaultScale = i < owner.Setup.DefaultScale.Count
|
||||
? owner.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
|
||||
Matrix4x4 visual =
|
||||
Matrix4x4.CreateScale(defaultScale)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin);
|
||||
if (owner.Entity.Scale != 1f)
|
||||
visual *= objectScale;
|
||||
|
||||
owner.PartPoses.Add(
|
||||
Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin * owner.Entity.Scale));
|
||||
Matrix4x4 visual = Matrix4x4.CreateScale(defaultScale)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin);
|
||||
if (owner.Entity.Scale != 1f)
|
||||
visual *= objectScale;
|
||||
owner.VisualPartPoses[i] = visual;
|
||||
owner.PartPoses[i] = Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin * owner.Entity.Scale);
|
||||
}
|
||||
if (!owner.PartAvailable[i])
|
||||
continue;
|
||||
|
||||
owner.MeshRefs.Add(new MeshRef(owner.PartGfxIds[i], visual)
|
||||
owner.MeshRefs.Add(new MeshRef(owner.PartGfxIds[i], owner.VisualPartPoses[i])
|
||||
{
|
||||
SurfaceOverrides = owner.SurfaceOverrides[i],
|
||||
});
|
||||
|
|
@ -429,6 +429,49 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
owner.Entity.SetIndexedPartPoses(owner.PartPoses, owner.PartAvailable);
|
||||
}
|
||||
|
||||
private static void EnsureRetainedPoses(Owner owner)
|
||||
{
|
||||
int partCount = owner.Setup.Parts.Count;
|
||||
if (owner.VisualPartPoses.Count == partCount
|
||||
&& owner.PartPoses.Count == partCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
owner.VisualPartPoses.Clear();
|
||||
owner.PartPoses.Clear();
|
||||
bool[] consumed = new bool[owner.Entity.MeshRefs.Count];
|
||||
for (int i = 0; i < partCount; i++)
|
||||
{
|
||||
Matrix4x4 rigid = i < owner.Entity.IndexedPartTransforms.Count
|
||||
? owner.Entity.IndexedPartTransforms[i]
|
||||
: Matrix4x4.Identity;
|
||||
owner.PartPoses.Add(rigid);
|
||||
|
||||
Matrix4x4 visual = default;
|
||||
bool found = false;
|
||||
for (int meshIndex = 0; meshIndex < owner.Entity.MeshRefs.Count; meshIndex++)
|
||||
{
|
||||
MeshRef mesh = owner.Entity.MeshRefs[meshIndex];
|
||||
if (consumed[meshIndex] || mesh.GfxObjId != owner.PartGfxIds[i])
|
||||
continue;
|
||||
consumed[meshIndex] = true;
|
||||
visual = mesh.PartTransform;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
Vector3 defaultScale = i < owner.Setup.DefaultScale.Count
|
||||
? owner.Setup.DefaultScale[i]
|
||||
: Vector3.One;
|
||||
visual = Matrix4x4.CreateScale(defaultScale * owner.Entity.Scale)
|
||||
* rigid;
|
||||
}
|
||||
owner.VisualPartPoses.Add(visual);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint[] BuildPartGfxIds(Setup setup, WorldEntity entity)
|
||||
{
|
||||
var result = new uint[setup.Parts.Count];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue