Motion Matching Node Crashes due to LOD Update, Restoring Pose Context, and Copying Attributes

  1. Add a Motion Matching node to an Animation Blueprint
  2. Have it select the same animation every frame. This animation must have an attribute on a bone whose index changes when LODing
  3. PIE
  4. Change LOD in level (I did it by zooming out)
  5. Crash when Finalizing Evaluation (if the index is out of range - otherwise it’s bad data)

Steps to Reproduce
Hello!

I recently uncovered a bug with the Motion Matching AnimNode when switching LODs.

Repro:

  1. Add a Motion Matching node to an Animation Blueprint
  2. Have it select the same animation every frame. This animation must have an attribute on a bone whose index changes when LODing
  3. PIE
  4. Change LOD in level (I did it by zooming out)
  5. Crash when Finalizing Evaluation (if the index is out of range - otherwise it’s bad data)

This seems to happen due to a faulty copy inside FBlendStackAnimPlayer::RestorePoseContext. There is a branch for checking if the bone container is up to date, but attributes don’t get the same treatment despite needing to know the same change.

This is my fix below with a bit of inefficiency in the copy. I’d love to know how you would fix this with the proper function.

`void FBlendStackAnimPlayer::RestorePoseContext(FPoseContext& PoseContext) const
{
check(!SequencePlayerNode.GetSequence() && !BlendSpacePlayerNode.GetBlendSpace());

if (StoredBoneContainer.IsValid())
{
// Serial number mismatch means a potential bone LOD mismatch, even if we have the same number of bones.
// Remap the pose manually in those cases.
if (PoseContext.Pose.GetBoneContainer().GetSerialNumber() == StoredBoneContainer.GetSerialNumber())
{
if (StoredBones.IsEmpty())
{
PoseContext.Pose.ResetToRefPose();
}
else
{
check(PoseContext.Pose.GetNumBones() == StoredBones.Num());
FMemory::Memcpy(PoseContext.Pose.GetMutableBones().GetData(), StoredBones.GetData(), sizeof(FTransform) * PoseContext.Pose.GetNumBones());
}

// Move the original CopyFrom to here because the BoneContainer didn’t change.
PoseContext.CustomAttributes.CopyFrom(StoredAttributes);
}
else
{
const FBoneContainer CurrentBoneContainer = PoseContext.Pose.GetBoneContainer();
for (FCompactPoseBoneIndex CompactPoseIndex : PoseContext.Pose.ForEachBoneIndex())
{
// Map the current compact pose index to skeleton index, and map this back to the stored compact pose index.
const FSkeletonPoseBoneIndex SkeletonPoseIndex = CurrentBoneContainer.GetSkeletonPoseIndexFromCompactPoseIndex(CompactPoseIndex);
const FCompactPoseBoneIndex StoredCompactPoseIndex = StoredBoneContainer.GetCompactPoseIndexFromSkeletonPoseIndex(SkeletonPoseIndex);
if (StoredCompactPoseIndex == INDEX_NONE)
{
// If our stored pose doesn’t have the bone, reset to ref pose.
PoseContext.Pose[CompactPoseIndex] = CurrentBoneContainer.GetRefPoseTransform(CompactPoseIndex);
}
else
{
PoseContext.Pose[CompactPoseIndex] = StoredBones[StoredCompactPoseIndex.GetInt()];
}
}

// This is the change. There’s not a direct CopyFrom between FCompactPoseBoneIndex, so I’m using the MeshAttribute.
// This can probably be done more efficiently with an explicit function within AttributesContainer.h
UE::Anim::FMeshAttributeContainer StoredMeshAttributeContainer;
StoredMeshAttributeContainer.CopyFrom(StoredAttributes, StoredBoneContainer);
PoseContext.CustomAttributes.CopyFrom(StoredMeshAttributeContainer, CurrentBoneContainer);
}
}
else
{
PoseContext.Pose.ResetToRefPose();
}

PoseContext.Curve.CopyFrom(StoredCurve);
}`Thanks!

-Nathaniel

Hi, it sounds like you’re running into this issue which we fixed recently. That was down to the wrong attribute container type being used in the motion matching code. When using mesh bone indices for attributes, FMeshAttributeContainer should be used rather than FHeapAttributeContainer since it uses the mesh to skeleton bone mapping which should still be valid when the LOD switches. The CL with the fix in UE5-main is 43555282 if you want to take a look at integrating that.