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.