IK Retargeter root motion not workign due to the process overwriting bone compressed data

When exporting with IK Retargeter root motion data seems to be lost.

What happens, however, is not that root motion data is lost, but that the bone table is incorrectly ordered by being overwritten by the cache.

[Image Removed]

If you export the asset to fbx and reimport the asset again the data gets fixed.

[Image Removed]

This makes the retargeting workflow process not viable as root motion will not play until you export all the animations outside of the engine and reimport them back as fbx.

Steps to Reproduce

  1. Open the IK Retargeter called RTG_NewSkeleton
  2. Export the animation called NewAnimation
  3. Observe how root motion is not being displayed in the viewport with root motion enabled even though root bone is clearly moving
  4. Look at the anim compressed data on the asset and observe how bones are ordered incorrectly ( root is not bone 0 )
  5. Export the asset to fbx and reimport with that file ( observe how now the root motion works and the bones are in correct order )

Example affected file: Exported_RootMotionEnabled

Example corrected file after reimport: Exported_RootMotionEnabled_Reimported

If it helps, I think the override of the data happens here:

[Image Removed]So I suspect there is invalid data on the cache or it’s not serialized properly.

( Maybe it has the data from the initial duplicated asset from the retarget )

And then when it serializes it into the asset things break.

After the following call, the data becomes incorrect:

[Image Removed]

Hi, thanks for reporting this. It turns out that this is a bug in how we map the bone tracks from the source animation to the target. When we duplicate the original asset during the duplicate and retarget, we start with an anim sequence that has all the bone tracks from the original skeleton. Then during the retarget process we call UAnimSequencerController::UpdateWithSkeleton which removes tracks for bones that were in the source skeleton, but not in the target skeleton. However, it doesn’t add tracks for the other bones that only exist in the target skeleton (in your case ‘root’ being one of those). That happens later in the retarget process when we call UAnimSequencerController::AddBoneCurve and pass the retargeted transform.

The problem is that call to AddBoneCurve adds the remaining bone tracks after the existing tracks (ie. after the tracks that existed on both the source and the target skeleton). So as a result, ‘root’ ends up with an index of 66. Later, when we try to sample the animation during playback, we have a check in UAnimSequence::ExtractRootTrackTransform_Lockless that only extracts root motion from bone index 0. Because ‘root’ is index 66, you get no root motion.

So really there are two different issues going on here:

  • The retargeted animation ends up with a different order of bone indices
  • The bone sampling code only supports root motion from bone 0

I think both of these are wrong. To fix the first one, you can make the following changes. First to UIKRetargetBatchOperation::RetargetAssets:

			// set the retarget source to the target skeletal mesh
			AnimSequenceToRetarget->RetargetSource = NAME_None;
			AnimSequenceToRetarget->SetRetargetSourceAsset(Context.TargetMesh);
 
			// removes all bone tracks not belonging to this skeleton, adds those that do
			Controller.RemoveAllBoneTracks(bShouldTransact);  // new line
			Controller.UpdateWithSkeleton(NewSkeleton, bShouldTransact);
 
			// done editing sequence data, close bracket
			Controller.CloseBracket(bShouldTransact);
		}

And also to UIKRetargetBatchOperation::ConvertAnimation:

		TargetSeqController.NotifyPopulated(); // must set bIsPopulated=true otherwise "UpdateWithSkeleton" will early out
		// this removes all bone tracks and reinitializes the FKControlRig to use the new skeleton.
		// NOTE: we do NOT ResetModel() because we want to keep curves and attributes from the source
		TargetSeqController.RemoveAllBoneTracks(bShouldTransact);  // new line
		TargetSeqController.UpdateWithSkeleton(const_cast<USkeleton*>(TargetSkeleton.SkeletalMesh->GetSkeleton()), bShouldTransact);
 
		// number of frames in this animation

Adding the calls to RemoveAllBoneTracks before calling UpdateWithSkeleton means that when we eventually re-add all the bone tracks, they’ll be ordered correctly with ‘root’ at 0. I’m going to talk to the dev team about whether this is the right long term fix, or whether we want to make some changes to how UpdateWithSkeleton works. But I think you should be good with this at least as a workaround for now.

In terms of the root motion extraction from non-zero index bones, that sounds like it’s something that we’re already aware of and are thinking about changing. But it’ll require a number of changes throughout the codebase so it’ll take a bit of time to get done.

Let me know if you’ve got any questions.

I think we can probably get this into the 5.7.1 hotfix. It seems serious enough to me, at least. I’ll keep this thread open for now and follow up to confirm one way or the other once I’ve committed those changes.

Last update on this one. I’ve discussed it with release management, and we’re going to get this into 5.7.2. The first hotfix is going to be focused specifically on stability issues, so they want to punt this one to the second hotfix. That’ll mean it’ll likely be next year until it’s available unfortunately, but at least it’ll be before 5.8.

Are you happy for me to close this thread out at this point?

No problem, thanks again for letting us know about this one.

Thanks for being so fast in responding.

To confirm, and I suspect the answer is no but one can have hope, do you think it’s possible for the fix to come with a 5.7 hotfix or is it already too late for that?

And once again, thanks for the info!

Perfect, and thanks again for this

Thank you so much for all the info and transparency regarding this, I appreciate it a lot.

Go ahead and close the thread, and once again, thank you so much for your time and effort.