Introduction
Video games have resources. Resources are raw data that need to be manipulated, baked and become ready to be used in game. Textures, meshes, animations and sometimes metadatas are all counted as resources. These resources are consuming significant amount of memory. Re-using and manipulating resources is essential for a game engine.
In terms of animation, there exists plenty of actions which can be used to manage animations as resources and one is motion retargeting.
With motion retargeting, one can use a specific animation on different skeletons with different reference or binding poses, different joint size and different heights. For example, you just have a walk animation and want to use it for 5 different characters with different physical shapes. Motion retargeting systems can do this nicely so you don't need to have five different walks for those 5 different characters. You just have one walk animation which can be used for all characters. This represents lower amount of animations and therefore less needed resources.
Motion retargeting systems apply some modifications on top of animation data to make them suitable for different skeletons. These modifications include:
1- Defining a generic but modifiable skeleton template for bipeds or quadrupeds
2- Root motion reasonable scaling
3- Ability to edit skeleton reference pose
4- Joint movement limitations
5- Animation mirroring
6- Adding a run-time rig on top of the skeleton template.
Creating a motion retargeting system needs a vast amount of work and it's a huge topic. In this post I just want to show you how you can mirror character animations. Motion retargeting systems are usually supporting animation mirroring. It's useful for different purposes. Mirrored animations can be used to avoid foot-skating and also for achieving responsiveness and again, by mirroring an input pose, you can avoid creating new mirrored animations and you just using the same animation data, no new animation needed here. You can select the animation or its mirrored based on the foot phases.
In the next post, I will show you how you can use mirrored animations in action but this post is just concentrating on mirroring an input pose from an animation.
For this post, I used Unreal Engine 4. Unreal Engine has a very robust, flexible and optimized animation system but its motion retargeting is still immature. At this time, it can't be compared with Unity3D or Havok Animation motion retargeting.
Mirror Animations
To mirror animations, two types of bones should be considered. First the bones that have a mirrored bone in the skeleton hierarchy like hands, arms, legs, foots and facial bones. Let's call these mirrored bones, twins. Second, the bones which have no twin, like pelvis, spines, neck and head.
So to create a mirroring system, we have to define some meta data about the skeleton. It should save each bone twins, if it has any. For this reason, I define a class named AnimationMirrorData which saves and manipulate required data such as mirror-mapped bones, rotation mirror axis and position negation direction.
To mirror animations, I defined a custom animation node which can be used in unreal engine animation graph. It receives a pose in local space and mirrors it. It also has two input pins. One is for an animation mirror data object which should be initialized by the user and one is a boolean which let the node to be turned on or off. As you can see in the picture, there is no extra animation needed here and the node just accepts the current pose and mirrors it and you can turn it on or off based on the game or animation circumstances.
Here I discuss how to mirror each type of bones:
1- Mirroring bones which has a twin in the hierarchy
These kind of bones like hands and legs have a twin in the hierarchy. To mirror them, we need to swap the transforms of the two bones. For example the left upper arm transform should be pasted on the right upper arm, and the right upper arm transform should be pasted on the left upper arm. To do this, we have to deduct the the binding pose from the current transform of the bone at the current frame. In Unreal Engine 4 the local poses are calculated in their parent space as well as the binding poses. We don't want to mirror the binding poses of the bones and we just need to mirror the deducted transform. By doing this, we can make sure that the character can stay on the same spot and it won't rotate 180 degrees. Remember, this only works if the binding poses of the twin bones in the skeleton are already mirrored. This means that the rigger should have mirrored the twin bones when he/she wanted to rig the mesh.
2- Mirroring bones with no twin
These kind of bones are like root, pelvis or spine which don't have any twin in the hierarchy. For these kind of bones, again we have to deduct the binding pose from the current bone transform. Now the current deducted transform should be mirrored. This time we need a mirror axis. The mirror axis should be selected by the user. Mostly it is x,y or z in the bone's binding pose space. So for rotations, if you select X as the mirror axis, you should negate the y and z components of the quaternion. To mirror the translations, things are a little different because for translations we never want to change the up and forward direction of the translations. That means by mirroring the animation, we don't want the character to move upside down and also backward. We just want the side movement to be negated. So here for the translations we just need to negate one component of the translation vector. So it is not counted as a mirror, mathematically.
Following, I placed some parts of the code which I wrote for the mirror animation node:
Here is the AnimationMirrorData header file:
And here are two functions which are mainly responsible to mirror animations:
I haven't placed the whole source code here. If you need them, just contact me and I will send them to you.
Here is the AnimationMirrorData header file:
#pragma once
#include "Object.h"
#include "AnimationMirrorData.generated.h"
/**
*
*/
UENUM(BlueprintType)
enum class MirrorDir : uint8
{
None = 0,
X_Axis = 1,
Y_Axis = 2,
Z_Axis = 3
};
UCLASS(BlueprintType)
class ANIMATIONMIRRORING_API UAnimationMirrorData : public UObject
{
GENERATED_BODY()
public:
UAnimationMirrorData();
//Shows mirror axis. 0 = None, 1 = X, 2 = Y, 3 = Z
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mirror Animation")
MirrorDir MirrorAxis_Rot;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mirror Animation")
MirrorDir RightAxis;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mirror Animation")
MirrorDir PelvisMirrorAxis_Rot;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mirror Animation")
MirrorDir PelvisRightAxis;
//Functions
UFUNCTION(BlueprintCallable, Category = "Mirror Animation")
void SetMirrorMappedBone(const FName bone_name, const FName mirror_bone_name);
UFUNCTION(BlueprintCallable, Category = "Mirror Animation")
FName GetMirroMappedBone(const FName bone_name) const;
TArray<FName> GetBoneMirrorDataStructure() const;
protected:
TArray<FName> mMirrorData;
}
And here are two functions which are mainly responsible to mirror animations:
/***********************************************/
void FAnimMirror::Evaluate(FPoseContext& Output)
{
mBasePose.Evaluate(Output);
if (!mAnimMirrorData)
{
return;
}
if (Output.AnimInstance)
{
TArray<FCompactPoseBoneIndex> lAr;
int32 lCurrentMirroredBoneInd = 0;
int32 lMirBoneCount = mAnimMirrorData->GetBoneMirrorDataStructure().Num();
//Mirror Mapped Bones
for (uint8 i = 0; i < lMirBoneCount; i += 2)
{
FCompactPoseBoneIndex lInd1 = FCompactPoseBoneIndex(Output.AnimInstance->GetSkelMeshComponent()->GetBoneIndex(mAnimMirrorData->GetBoneMirrorDataStructure()[i]));
FCompactPoseBoneIndex lInd2 = FCompactPoseBoneIndex(Output.AnimInstance->GetSkelMeshComponent()->GetBoneIndex(mAnimMirrorData->GetBoneMirrorDataStructure()[i + 1]));
FTransform lT1 = Output.Pose[lInd1];
FTransform lT2 = Output.Pose[lInd2];
Output.Pose[lInd1].SetRotation(Output.Pose.GetRefPose(lInd1).GetRotation() * Output.Pose.GetRefPose(lInd2).GetRotation().Inverse() * lT2.GetRotation());
Output.Pose[lInd2].SetRotation(Output.Pose.GetRefPose(lInd2).GetRotation() * Output.Pose.GetRefPose(lInd1).GetRotation().Inverse() * lT1.GetRotation());
Output.Pose[lInd1].SetLocation((Output.Pose.GetRefPose(lInd2).GetRotation().Inverse() * lT2.GetRotation() * (lT2.GetLocation() - Output.Pose.GetRefPose(lInd2).GetLocation()))
+ Output.Pose.GetRefPose(lInd1).GetLocation());
Output.Pose[lInd2].SetLocation((Output.Pose.GetRefPose(lInd1).GetRotation().Inverse() * lT1.GetRotation() * (lT1.GetLocation() - Output.Pose.GetRefPose(lInd1).GetLocation()))
+ Output.Pose.GetRefPose(lInd2).GetLocation());
lAr.Add(lInd1);
lAr.Add(lInd2);
}
//Mirror Unmapped Bones
FCompactPoseBoneIndex lPoseBoneCount = FCompactPoseBoneIndex(Output.Pose.GetNumBones());
for (FCompactPoseBoneIndex i = FCompactPoseBoneIndex(0); i < lPoseBoneCount;)
{
if (!lAr.Contains(i))
{
if (!i.IsRootBone())
{
FTransform lT = Output.Pose[i];
lT.SetRotation(Output.Pose.GetRefPose(i).GetRotation().Inverse() * Output.Pose[i].GetRotation());
lT.SetLocation(Output.Pose[i].GetLocation() - Output.Pose.GetRefPose(i).GetLocation());
if (i.GetInt() != 1)
{
MirrorPose(lT, (uint8)mAnimMirrorData->MirrorAxis_Rot, (uint8)mAnimMirrorData->RightAxis);
Output.Pose[i].SetRotation(Output.Pose.GetRefPose(i).GetRotation() * lT.GetRotation());
Output.Pose[i].SetLocation(Output.Pose.GetRefPose(i).GetLocation() + lT.GetLocation());
}
else
{
MirrorPose(lT, (uint8)mAnimMirrorData->PelvisMirrorAxis_Rot, (uint8)mAnimMirrorData ->PelvisRightAxis);
Output.Pose[i].SetRotation(Output.Pose.GetRefPose(i).GetRotation() * lT.GetRotation());
Output.Pose[i].SetLocation(Output.Pose.GetRefPose(i).GetLocation() + lT.GetLocation());
}
}
}
++i;
}
}
};
void FAnimMirror::MirrorPose(FTransform& input_pose, const uint8 mirror_axis, const uint8 pos_fwd_mirror)
{
FVector lMirroredLoc = input_pose.GetLocation();
if (pos_fwd_mirror == 1)
{
lMirroredLoc.X = -lMirroredLoc.X;
}
else
{
if (pos_fwd_mirror == 2)
{
lMirroredLoc.Y = -lMirroredLoc.Y;
}
else
{
if (pos_fwd_mirror == 3)
{
lMirroredLoc.Z = -lMirroredLoc.Z;
}
}
}
input_pose.SetLocation(lMirroredLoc);
switch (mirror_axis)
{
case 1:
{
const float lY = -input_pose.GetRotation().Y;
const float lZ = -input_pose.GetRotation().Z;
input_pose.SetRotation(FQuat(input_pose.GetRotation().X, lY, lZ, input_pose.GetRotation().W));
break;
}
case 2:
{
const float lX = -input_pose.GetRotation().X;
const float lZ = -input_pose.GetRotation().Z;
input_pose.SetRotation(FQuat(lX, input_pose.GetRotation().Y, lZ, input_pose.GetRotation().W));
break;
}
case 3:
{
const float lX = -input_pose.GetRotation().X;
const float lY = -input_pose.GetRotation().Y;
input_pose.SetRotation(FQuat(lX, lY, input_pose.GetRotation().Z, input_pose.GetRotation().W));
break;
}
}
};
I haven't placed the whole source code here. If you need them, just contact me and I will send them to you.
Hey Peyman I was wondering if you could help out with getting this to work. Is this the only way to get mirrored animations?
ReplyDeleteHi Arty,
DeleteYou can implement every module in different ways. This is the way I've implemented. However I've changed many features of this module after this post.
So what kind of a help do you need?
Hey Peyman, I was wondering if I could grab the full source for this post, I'd love to take a look at it.
ReplyDeleteI would also be very thankful to have a look at the rest of the code if you are still ok with sharing it.
ReplyDeleteHello Kyle and theodor,
ReplyDeleteSorry for the delayed reply. You may send me your emails and I will send you the full source code via email.
Here is my email: peyman.massoudi@gmail.com
Cheers
Can you send full sourse code for understanding this mirroring tables things. My email is roeffes@gmail.com
ReplyDeleteCould you send me the whole source code? My email is nagune1213@gmail.com
ReplyDeleteThank you, Peyman, for sharing this! I'm not a programmer, unfortunately, so at this point I don't believe I'd be able to implement this solution. To your knowledge, is there any way to mirror a character's animation during gameplay using blueprint? I believe there was a long-standing feature request for this (since 2014), but Epic removed it.
ReplyDeletei've seen your tutorials and are great, i have also a Turninplace node (not Rama's one) that if you can help me to solve, got some issues with it. wonder if the rest of the mirror's code is just the animgraph ? . can you emailme the rest? huatson@live.com
ReplyDeleteHello, can you send me source?
ReplyDeletemalbork2410@gmail.com
Thanks
This comment has been removed by the author.
ReplyDeleteVery intressting article, thanks!
ReplyDeleteI wonder how do you handle animation notifies of the mirrored animation? E.g. when spawning a dust particle when the feet contacts the ground.
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
DeleteThis comment has been removed by the author.
ReplyDeleteoh!! very good~!
ReplyDeleteplease261@gmail.com
Thanks
Please send me the source code (I would appreciate it)
ReplyDeletesydney.lu@hotmail.com
Hello Peyman, anyway to have a look to the full code? Would be nice from you if you could send it to simon.mazuri@outlook.com
ReplyDeleteThanks!
Hi All,
ReplyDeleteSo unfortunately I haven't yet found time to clean up the codes and I prefer to clean up everything before making the codes and project public however one of the enthusiastic guys has developed a mirroring system based on my work and made it public so you can follow his work here. I myself haven't checked its contents yet but as the post mentions it should work with different characters:
https://forums.unrealengine.com/development-discussion/animation/112726-working-animation-mirroring-system
hello, that's cool ! could you send me the source code? thanks, gaolijungame@gmail.com
ReplyDeleteThis is awesome! Would be nice if you send me the whole source code to phuccoker88@gmail.com
ReplyDeleteThanks