Calculating local space position from reference pose on animated mesh

I’m not very good at geometry functions so I’m hoping to get some help here. Thanks in advance.

I’m trying to calculate the local(preskinned) position of a hit on a skeletal mesh. Now if the mesh is locked in its default, reference pose, all I have to do is subtract the ComponentTransform of the skeletal mesh from the ImpactPoint, and I can feed the result directly to my material and it will work well. However, I’m not sure how to get to the same result if the mesh is being animated.

I know I can access the reference skeleton, however, the locations of ref bones are all relative to their parent bone, as opposed to being relative to the skeletal mesh’s root transform. GetBoneTransform() returns the bone’s location relative to the root, which is something I could work with, however, I’m not sure what to do with the reference bones. Different approaches that I’ve tried yielded incorrect results, including the one described via this link.

According to the aforementioned article, this should have worked:

FTransform SocketTransformWorld = SkeletalMesh->GetSocketTransform(ImpactResult.BoneName, ERelativeTransformSpace::RTS_World);
FReferenceSkeleton RefSkeleton = SkeletalMesh->SkeletalMesh->RefSkeleton;
FTransform RefPoseTransform = RefSkeleton.GetRefBonePose()[RefSkeleton.FindBoneIndex(ImpactResult.BoneName)];
FVector InverseTransformPosition = SocketTransformWorld.InverseTransformPosition(ImpactResult.ImpactPoint);
FVector RefPoseInverseTransformPosition = RefPoseTransform.TransformPosition(InverseTransformPosition);

Except it doesn’t. The final transformed position is not relative to the root bone. So either I’m doing something wrong, or the author forgot to mention something crucial.

Either way, any help would be appreciated, and thanks again.

Hi Visor, I have read the same tutorial by Tom Looman.

In my project GetRefBonePose() return position relative to the parent bone, so I made a recursive function to have results relative to world origin.

// .h
// static FTransform GetWorldSpaceTransform(FReferenceSkeleton RefSkel, int32 BoneIdx);
// .cpp
FTransform USurvivalFunctionLibrary::GetWorldSpaceTransform(FReferenceSkeleton RefSkel, int32 BoneIdx)
{
	FTransform BoneTransform;

	if (BoneIdx > 0)
	{
		BoneTransform = RefSkel.GetRefBonePose()[BoneIdx];

		FMeshBoneInfo BoneInfo = RefSkel.GetRefBoneInfo()[BoneIdx];
		if (BoneInfo.ParentIndex != 0)
		{
			BoneTransform *= GetWorldSpaceTransform(RefSkel, BoneInfo.ParentIndex);
		}
	}

	return BoneTransform;
}

// .h
// UFUNCTION(BlueprintCallable, Category = "BPLibrary")
// static FTransform GetRefPoseBoneTransform(USkeletalMeshComponent* SkelMesh, FName BoneName);
// .cpp
FTransform USurvivalFunctionLibrary::GetRefPoseBoneTransform(USkeletalMeshComponent* SkelMesh, FName BoneName)
{
	FTransform BoneTransform;

	if (SkelMesh && !BoneName.IsNone())
	{
		SkelMesh->ClearRefPoseOverride();
		FReferenceSkeleton RefSkel;
		RefSkel = SkelMesh->SkeletalMesh->RefSkeleton;

		BoneTransform = GetWorldSpaceTransform(RefSkel, RefSkel.FindBoneIndex(BoneName));
	}

	return BoneTransform;
}

// .h
// UFUNCTION(BlueprintCallable, Category = "BPLibrary")
// static FTransform GetBoneTransform(USkeletalMeshComponent* SkelMesh, FName BoneName);
// .cpp
FTransform USurvivalFunctionLibrary::GetBoneTransform(USkeletalMeshComponent* SkelMesh, FName BoneName)
{
	FTransform BoneTransform;

	if (SkelMesh && !BoneName.IsNone())
	{
		FReferenceSkeleton RefSkel;
		RefSkel = SkelMesh->SkeletalMesh->RefSkeleton;
		BoneTransform = SkelMesh->GetBoneTransform(RefSkel.FindBoneIndex(BoneName));
	}

	return BoneTransform;
}

Thanks for taking the time to answer. Though it’s been 3 months since I posted the question and I’ve managed to figure out a solution on my own since then. It’s pretty much the same as yours except it’s caching all the positions instead of calculating them on the fly, to increase efficiency. Have an upvote nonetheless!

Thank you for this. There is a small error in the code. The root bone’s transform does not get taken into account because it has bone index 0 so it just gets ignored here. Fixed version below.

FTransform USurvivalFunctionLibrary::GetWorldSpaceTransform(FReferenceSkeleton RefSkel, int32 BoneIdx)
{
    FTransform BoneTransform;

    if (BoneIdx >= 0)
    {
        BoneTransform = RefSkel.GetRefBonePose()[BoneIdx];

        FMeshBoneInfo BoneInfo = RefSkel.GetRefBoneInfo()[BoneIdx];
        if (BoneIdx != 0 && BoneInfo.ParentIndex >= 0)
        {
            BoneTransform *= GetWorldSpaceTransform(RefSkel, BoneInfo.ParentIndex);
        }
    }

    return BoneTransform;
}

The given GetWorldSpaceTransform in these examples it’s not really the WorldSpaceTrasform, in case of weirdly tilted bones it won’t give the real value.
I solved using

FVector RefPoseLocation = FAnimationRuntime::GetComponentSpaceTransformRefPose(Root->GetSkinnedAsset()->GetRefSkeleton(), BoneIndex).GetLocation();