Can sockets be used with motion warping interchangeably with bones?

We have a setup for traversal abilities that uses motion warping to adjust for e.g. hand placement. We use the Skew Warp root motion modifier, set the Warp Point Anim Provider to Bone, and set the Warp Point Anim Bone Name to a socket that we use for character alignment.

This has been working for us for a good long while now, but at some point it seems to have broken and I’m having trouble figuring out what changed exactly. In the current codebase there doesn’t seem to be any way for this to have ever worked, but we certainly thought it was working for a long time so I’m very confused about what’s going on.

So my questions are:

1) Is this expected to work? (if so, I assume we managed to mess something up on our end…)

2) Did something around this change in the last year-ish? If so, I’d love to understand what and why.

3) Is there any good reason for us to not go in and enable socket use for this purpose?

[Attachment Removed]

Steps to Reproduce
This issue can be seen in the GASP project, just change any Warp Point Anim Bone Name to reference a socket instead of a bone. Notice how the socket isn’t found by the motion warping component and thus motion warping doesn’t play out as expected.

[Attachment Removed]

Hi, just looking through the code, I don’t think that we’ve ever supported sockets on the warp target. We get the target bone index in CalculateRootTransformRelativeToWarpPointAtTime with the following code:

const FBoneContainer& FullBoneContainer = AnimInstance->GetRequiredBones();
const int32 BoneIndex = FullBoneContainer.GetPoseBoneIndexForBoneName(WarpPointBoneName);

And that will only return bone indices, not sockets. Looking back in P4, it seems the code has always been that way.

Having said that, I don’t think there’s any reason why we shouldn’t add socket support for this. To do this properly, I would look at adding a new EWarpPointAnimProvider type and then specifying the socket via a FBoneSocketTarget since that has an API for getting the warp offset nicely. But if you just want a quick fix for this, something like the following should get sockets working for you:

FTransform UMotionWarpingUtilities::CalculateRootTransformRelativeToWarpPointAtTime(const UMotionWarpingBaseAdapter& WarpingAdapter, const UAnimSequenceBase* Animation, float Time, const FName& WarpPointBoneName)
{
	if (const USkeletalMeshComponent* Mesh = WarpingAdapter.GetMesh())
	{
		if (const UAnimInstance* AnimInstance = Mesh->GetAnimInstance())
		{
			FTransform WarpPointLocalOffset = FTransform::Identity;
			FName WarpPointOwningBoneName = WarpPointBoneName;
			if (const USkeletalMeshSocket* Socket = Mesh->GetSocketByName(WarpPointBoneName))
			{
				WarpPointLocalOffset = Socket->GetSocketLocalTransform();
				WarpPointOwningBoneName = Socket->BoneName;
			}
 
			const FBoneContainer& FullBoneContainer = AnimInstance->GetRequiredBones();
			const int32 BoneIndex = FullBoneContainer.GetPoseBoneIndexForBoneName(WarpPointOwningBoneName);
			if (BoneIndex != INDEX_NONE)
			{
				TArray<FBoneIndexType> RequiredBoneIndexArray = { 0, (FBoneIndexType)BoneIndex };
				FullBoneContainer.GetReferenceSkeleton().EnsureParentsExistAndSort(RequiredBoneIndexArray);
 
				FBoneContainer LimitedBoneContainer(RequiredBoneIndexArray, UE::Anim::FCurveFilterSettings(UE::Anim::ECurveFilterMode::DisallowAll), *FullBoneContainer.GetAsset());
 
				FCSPose<FCompactPose> Pose;
				UMotionWarpingUtilities::ExtractComponentSpacePose(Animation, LimitedBoneContainer, Time, false, Pose);
 
				const FCompactPoseBoneIndex WarpPointBoneIndex = LimitedBoneContainer.MakeCompactPoseIndex(FMeshPoseBoneIndex(BoneIndex));
 
				// Inverse of mesh's relative rotation. Used to convert root and warp point in the animation from Y forward to X forward
				const FTransform MeshCompRelativeRotInverse = FTransform(WarpingAdapter.GetBaseVisualRotationOffset().Inverse());
				const FTransform RootTransform = MeshCompRelativeRotInverse * Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(0));			
				const FTransform WarpPointTransform = MeshCompRelativeRotInverse * (WarpPointLocalOffset * Pose.GetComponentSpaceTransform(WarpPointBoneIndex));
 
				return RootTransform.GetRelativeTransform(WarpPointTransform);
			}
		}
	}
 
	return FTransform::Identity;
}

Note that there’s an extra change in there. Previously, we were extracting the warp target transform by doing this:

onst FTransform WarpPointTransform = MeshCompRelativeRotInverse * Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(1));But that assumes the warp target is a child of the root. So the code snippet above changes that to use the target bone index. You’d also need to make similar changes to the other overload of CalculateRootTransformRelativeToWarpPointAtTime.

Let me know if you’ve got any questions

[Attachment Removed]

Thanks for the reply confirming the current state of the code.

The only thing I’m mystified about at this point is that we have been using sockets for _at least_ a year and I’d have sworn that it was fully tested and working when we set it up.

[Attachment Removed]

Sorry for the delay in following up. I’d wanted to get a fix submitted for this before getting back to you, but it’s taking longer than I’d hoped with other work going on.

In terms of why this might have been working in a previous version, I’m not too sure. The code CalculateRootTransformRelativeToWarpPointAtTime has been like that pretty much since Motion Warping was initially implemented, and the required bones list has never contained sockets, just bones that own sockets.

Either way, I’ll get a fix into the 5.9 release so you won’t need to maintain the engine change after that. I’ll close out this thread since we have a resolution.

[Attachment Removed]

Euan, just thought I’d share some other modifications we made to the Motion Warping module itself to make it easier for the editor and runtime to share the same underlying code changes for socket support (plus other unrelated mods we’ve made in the past).

// Inject a preview motion warping component, with adapter, into the animation editor's preview actor.
// This allows us to fetch the preview actor's UMotionWarpingBaseAdapter and thereby interact with it
// the same way between editor and runtime contexts.
#if WITH_EDITOR
#include "Animation/DebugSkelMeshComponent.h"
#include "IPersonaPreviewScene.h"
#include "PersonaModule.h"
#include "MotionWarpingAdapter.h"
#include "MotionWarpingComponent.h"
#endif // WITH_EDITOR
static void MotionWarping_InjectPreviewComponent()
{
#if WITH_EDITOR
	if (GIsEditor)
	{
		FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>(TEXT("Persona"));
		PersonaModule.OnPreviewSceneCreated().AddLambda([](const TSharedRef<IPersonaPreviewScene>& Scene){
			AActor* PreviewActor = Scene->GetActor();
			if (PreviewActor && !PreviewActor->GetComponentByClass<UMotionWarpingComponent>())
			{
				auto* MotionWarpingComponent = NewObject<UMotionWarpingComponent>(PreviewActor);
				auto* MotionWarpingAdapter = MotionWarpingComponent->CreateOwnerAdapter<UMotionWarpingEditorPreviewAdapter>();
				MotionWarpingAdapter->TargetActor = PreviewActor;
				MotionWarpingAdapter->TargetMesh = Scene->GetPreviewMeshComponent();
				PreviewActor->AddInstanceComponent(MotionWarpingComponent);
				MotionWarpingComponent->RegisterComponent();
			}
		});
	}
#endif // WITH_EDITOR
}
 
void FMotionWarpingModule::StartupModule()
{
	MotionWarping_InjectPreviewComponent();
}
/**
 * UMotionWarpingEditorPreviewAdapter: simple adapter for editor preview functionality (since editor previews use AAnimationEditorPreviewActor rather than ACharacter).
 * See also MotionWarping_InjectPreviewComponent in MotionWarping.cpp.
 */
UCLASS(MinimalAPI)
class UMotionWarpingEditorPreviewAdapter : public UMotionWarpingBaseAdapter
{
	GENERATED_BODY()
 
public:
	virtual AActor* GetActor() const { return TargetActor.Get(); }
	virtual USkeletalMeshComponent* GetMesh() const { return TargetMesh.Get(); }
	virtual FVector GetVisualRootLocation() const;
	virtual FVector GetBaseVisualTranslationOffset() const;
	virtual FQuat GetBaseVisualRotationOffset() const;
 
	TWeakObjectPtr<AActor> TargetActor;
	TWeakObjectPtr<USkeletalMeshComponent> TargetMesh;
};
 
FVector UMotionWarpingEditorPreviewAdapter::GetVisualRootLocation() const
{
	if (AActor* Actor = GetActor())
	{
		return Actor->GetActorLocation();
	}
	return FVector::ZeroVector;
}
 
FVector UMotionWarpingEditorPreviewAdapter::GetBaseVisualTranslationOffset() const
{
	if (const USkeletalMeshComponent* Mesh = GetMesh())
	{
		return Mesh->GetRelativeLocation();
	}
	return FVector::ZeroVector;
}
 
FQuat UMotionWarpingEditorPreviewAdapter::GetBaseVisualRotationOffset() const
{
	if (const USkeletalMeshComponent* Mesh = GetMesh())
	{
		return Mesh->GetRelativeRotation().Quaternion();
	}
	return FQuat::Identity;
}	

The payoff is here in GetDebugWarpPointTransform where we are no longer doing warp point math. Instead we call the same utility func that our runtime calls (which we’ve customized for our needs, but hopefully the editor/runtime impl. sharing objective is coming across):

FTransform URootMotionModifier_SkewWarp::GetDebugWarpPointTransform(USkeletalMeshComponent* MeshComp, const UAnimSequenceBase* InAnimation, const UMirrorDataTable* MirrorTable, const float NotifyEndTime) const
{
	FTransform WarpPointTransform = FTransform::Identity;
	if (WarpPointAnimProvider == EWarpPointAnimProvider::None)
	{
		//...
	}
	else if (WarpPointAnimProvider == EWarpPointAnimProvider::Static)
	{
		//...
	}
 
	else if (WarpPointAnimProvider == EWarpPointAnimProvider::Bone)
	{		
		// We inject a motion warping component into the preview actor (see ArenaNet's modification in FMotionWarpingModule::StartupModule()).
		// So here in the editor we can simply fetch the motion warping adapter and perform the same calculation as the runtime path.
		const AActor* MeshOwner = MeshComp->GetOwner();
		const UMotionWarpingComponent* MotionWarpingComp = MeshOwner ? MeshOwner->GetComponentByClass<UMotionWarpingComponent>() : nullptr;
		const UMotionWarpingBaseAdapter* OwnerAdapter = MotionWarpingComp ? MotionWarpingComp->GetOwnerAdapter() : nullptr;
		if (OwnerAdapter)
		{
			FTransform UnusedRootTransformRelativeToWarpPointBone;
			UMotionWarpingUtilities::ArenaNet_CalculateRootTransformRelativeToWarpPointAtTime(*OwnerAdapter, InAnimation, NotifyEndTime, WarpPointAnimBoneName, WarpPointAnimBoneUseRootRotation, UnusedRootTransformRelativeToWarpPointBone, WarpPointTransform);
		}
	}

[Attachment Removed]

Hi [mention removed]​, sorry for the delay in following up. Thanks for sharing the code, I also prefer an approach that doesn’t have us repeating the same calculations in multiple places. I wanted to get something like this implemented last week, but I didn’t have a chance in the end, and I’m now going to be out of office for a couple of weeks. I’ve logged a JIRA instead, and I’ll pick it up again once I’m back.

[Attachment Removed]