Keywords: UE4, Animation, Transform Bone, Anim Graph Node, AnimGraphNode

Transform SkeletalMeshComponent’s bone in Blueprint

Blueprint Node

Transform (Modify) Bone

Example

Transform PoseableMeshComponent’s bone in C++

Steps

  1. Add UPoseableMeshComponent in your Actor class, and set Skeletal Mesh asset for it.

    Because the transformation of PoseableMeshComponent is entirely handled by user, so there’s no property to set the Animation Blueprint.

  2. Modify bones transform by UPoseableMeshComponent’s functions:

     UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
     void SetBoneTransformByName(FName BoneName, const FTransform& InTransform, EBoneSpaces::Type BoneSpace);
    
     UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
     void SetBoneLocationByName(FName BoneName, FVector InLocation, EBoneSpaces::Type BoneSpace);
    
     UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
     void SetBoneRotationByName(FName BoneName, FRotator InRotation, EBoneSpaces::Type BoneSpace);
    
     UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
     void SetBoneScaleByName(FName BoneName, FVector InScale3D, EBoneSpaces::Type BoneSpace);
    
Transform SkeletalMeshComponent’s bone in C++

Reference by Engine source:

Engine\Source\Editor\AnimGraph\Classes\AnimGraphNode_ModifyBone.h

This is the GraphNode Transform (Modify) Bone in Animation Blueprint Editor. The property FAnimNode_ModifyBone is the definite native class for transforming bones.

UCLASS(meta=(Keywords = "Modify Transform"))
class ANIMGRAPH_API UAnimGraphNode_ModifyBone : public UAnimGraphNode_SkeletalControlBase
{
    GENERATED_UCLASS_BODY()

    UPROPERTY(EditAnywhere, Category=Settings)
    FAnimNode_ModifyBone Node;

public:
    // UEdGraphNode interface
    virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
    virtual FText GetTooltipText() const override;
    // End of UEdGraphNode interface

protected:
    // UAnimGraphNode_Base interface
    virtual void ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) override;
    virtual FEditorModeID GetEditorMode() const override;
    virtual void CopyNodeDataToPreviewNode(FAnimNode_Base* InPreviewNode) override;
    virtual void CopyPinDefaultsToNodeData(UEdGraphPin* InPin) override;
    // End of UAnimGraphNode_Base interface

    // UAnimGraphNode_SkeletalControlBase interface
    virtual FText GetControllerDescription() const override;
    virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; }
    // End of UAnimGraphNode_SkeletalControlBase interface

private:
    /** Constructing FText strings can be costly, so we cache the node's title */
    FNodeTitleTextTable CachedNodeTitles;

    // storing current widget mode 
    int32 CurWidgetMode;
};

Engine\Source\Runtime\AnimGraphRuntime\Public\BoneControllers\AnimNode_ModifyBone.h

UENUM()
enum EBoneModificationMode
{
    /** The modifier ignores this channel (keeps the existing bone translation, rotation, or scale). */
    BMM_Ignore UMETA(DisplayName = "Ignore"),

    /** The modifier replaces the existing translation, rotation, or scale. */
    BMM_Replace UMETA(DisplayName = "Replace Existing"),

    /** The modifier adds to the existing translation, rotation, or scale. */
    BMM_Additive UMETA(DisplayName = "Add to Existing")
};

/**
 *	Simple controller that replaces or adds to the translation/rotation of a single bone.
 */
USTRUCT(BlueprintInternalUseOnly)
struct ANIMGRAPHRUNTIME_API FAnimNode_ModifyBone : public FAnimNode_SkeletalControlBase
{
    GENERATED_USTRUCT_BODY()

    /** Name of bone to control. This is the main bone chain to modify from. **/
    UPROPERTY(EditAnywhere, Category=SkeletalControl) 
    FBoneReference BoneToModify;

    /** New translation of bone to apply. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Translation, meta=(PinShownByDefault))
    FVector Translation;

    /** New rotation of bone to apply. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Rotation, meta=(PinShownByDefault))
    FRotator Rotation;

    /** New Scale of bone to apply. This is only worldspace. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Scale, meta=(PinShownByDefault))
    FVector Scale;

    /** Whether and how to modify the translation of this bone. */
    UPROPERTY(EditAnywhere, Category=Translation)
    TEnumAsByte<EBoneModificationMode> TranslationMode;

    /** Whether and how to modify the translation of this bone. */
    UPROPERTY(EditAnywhere, Category=Rotation)
    TEnumAsByte<EBoneModificationMode> RotationMode;

    /** Whether and how to modify the translation of this bone. */
    UPROPERTY(EditAnywhere, Category=Scale)
    TEnumAsByte<EBoneModificationMode> ScaleMode;

    /** Reference frame to apply Translation in. */
    UPROPERTY(EditAnywhere, Category=Translation)
    TEnumAsByte<enum EBoneControlSpace> TranslationSpace;

    /** Reference frame to apply Rotation in. */
    UPROPERTY(EditAnywhere, Category=Rotation)
    TEnumAsByte<enum EBoneControlSpace> RotationSpace;

    /** Reference frame to apply Scale in. */
    UPROPERTY(EditAnywhere, Category=Scale)
    TEnumAsByte<enum EBoneControlSpace> ScaleSpace;

    FAnimNode_ModifyBone();

    // FAnimNode_Base interface
    virtual void GatherDebugData(FNodeDebugData& DebugData) override;
    // End of FAnimNode_Base interface

    // FAnimNode_SkeletalControlBase interface
    virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override;
    virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override;
    // End of FAnimNode_SkeletalControlBase interface

private:
    // FAnimNode_SkeletalControlBase interface
    virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override;
    // End of FAnimNode_SkeletalControlBase interface
};

FAnimNode_ModifyBone inherits from FAnimNode_SkeletalControlBase and implements father’s function EvaluateSkeletalControl_AnyThread which computing transformation of bones

void FAnimNode_ModifyBone::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
    check(OutBoneTransforms.Num() == 0);

    // the way we apply transform is same as FMatrix or FTransform
    // we apply scale first, and rotation, and translation
    // if you'd like to translate first, you'll need two nodes that first node does translate and second nodes to rotate.
    const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();

    FCompactPoseBoneIndex CompactPoseBoneToModify = BoneToModify.GetCompactPoseIndex(BoneContainer);
    FTransform NewBoneTM = Output.Pose.GetComponentSpaceTransform(CompactPoseBoneToModify);
    FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();

    if (ScaleMode != BMM_Ignore)
    {
        // Convert to Bone Space.
        FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, ScaleSpace);

        if (ScaleMode == BMM_Additive)
        {
            NewBoneTM.SetScale3D(NewBoneTM.GetScale3D() * Scale);
        }
        else
        {
            NewBoneTM.SetScale3D(Scale);
        }

        // Convert back to Component Space.
        FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, ScaleSpace);
    }

    if (RotationMode != BMM_Ignore)
    {
        // Convert to Bone Space.
        FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, RotationSpace);

        const FQuat BoneQuat(Rotation);
        if (RotationMode == BMM_Additive)
        {	
            NewBoneTM.SetRotation(BoneQuat * NewBoneTM.GetRotation());
        }
        else
        {
            NewBoneTM.SetRotation(BoneQuat);
        }

        // Convert back to Component Space.
        FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, RotationSpace);
    }
	
    if (TranslationMode != BMM_Ignore)
    {
        // Convert to Bone Space.
        FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, TranslationSpace);

        if (TranslationMode == BMM_Additive)
        {
            NewBoneTM.AddToTranslation(Translation);
        }
        else
        {
            NewBoneTM.SetTranslation(Translation);
        }

        // Convert back to Component Space.
        FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, TranslationSpace);
    }
	
    OutBoneTransforms.Add( FBoneTransform(BoneToModify.GetCompactPoseIndex(BoneContainer), NewBoneTM) );
}

Taking Rotation as an example, computing rotation of bones is in these code (as shown in the following source) which is in function EvaluateSkeletalControl_AnyThread():

const FQuat BoneQuat(Rotation);
if (RotationMode == BMM_Additive)
{	
    NewBoneTM.SetRotation(BoneQuat * NewBoneTM.GetRotation());
}
else
{
    NewBoneTM.SetRotation(BoneQuat);
}

Reference: Creating Custom Animation Nodes ( This article was obsolete! )
https://www.unrealengine.com/en-US/blog/creating-custom-animation-nodes

Example Source: UE4 Plugin for custom speed & irientation warping under Anim Graph Node.( Recommended )
https://github.com/dawnarc/ue4_custom_anim_graph_node

Reference

Transform skeletal bone in C++
https://answers.unrealengine.com/questions/59010/transform-skeletal-bone-in-c.html

Transform Bone
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/TransformBone

Transform a location/rotation in bone relative space to world space.

Transform from Bone Space

Transform a location/rotation from world space to bone relative space.
This is handy if you know the location in world space for a bone attachment, as AttachComponent takes location/rotation in bone-relative space.

TransformToBoneSpace

C++

void USkinnedMeshComponent::TransformToBoneSpace(FName BoneName, FVector InPosition, FRotator InRotation, FVector& OutPosition, FRotator& OutRotation) const

void USkinnedMeshComponent::TransformFromBoneSpace(FName BoneName, FVector InPosition, FRotator InRotation, FVector& OutPosition, FRotator& OutRotation)

Issues

Properties need to be set before transform bones

If you want to control transform of bones at real-time, following properties need to be set:

// prevent anim frame skipping optimization based on visibility etc
Mesh->bEnableUpdateRateOptimizations = false;

// update animation even when mesh is not visible
Mesh->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::AlwaysTickPoseAndRefreshBones;

Otherwise tick function in animation blueprint would not be fired.

Why EvaluateSkeletalControl_AnyThread could not be fired

Issue:
EvaluateSkeletalControl_AnyThread in Anim Node could not be fired even add an Anim Node (e.g. Modify Transform) in Animation Blueprint.

Caused by:
Maybe IsValidToEvaluate() return false.

Solution:
Ensure IsValidToEvaluate() return true.

Why does PlaySlotAnimationAsDynamicMontage override Transform (Modify) Bone

If Slot node was placed in the behind of Transform (Modify) Bone, and you invoked PlaySlotAnimationAsDynamicMontage to play animation, then transform of bones would be overrided by the pose which was outputed by native API PlaySlotAnimationAsDynamicMontage.

Ensure Transform (Modify) Bone node be placed after Slot node.


发生过的事,以后还会发生;做过的事,将来还要再做。太阳底下没有新的事。有谁能说,看,这是新事?不,在我们出生之前早就有了。以往的事没有人去追忆,今后的事也没有人去掛念。 ──《旧约·传道书》