Animation retargeting (additionally referred to as movement retargeting) is the switch of motion from one character to a different in animation (i.e. from one skeleton to a different), permitting the reuse of animation on totally different fashions.
I have been attempting to implement this for weeks now with out luck. The most recent algorithm I wrote is the “greatest” to date, however continues to be very unhealthy:
- It takes all the animation keyframes and orders them by time.
- It loops over them, and for every ker, interpolates the supply bone rotation/pos/scale at that keyframe time, transfers to world, then calculates the delta between it and the bind pose (in world), then we add that delta to focus on skeleton’s bone bind-pose-in-world, then we switch it to the native area of the goal bone.
public static AnimationClip RetargetAnimationClip(
this AnimationClip animationOfSourceRig,
Listing> rigsMapInBindPoses
)
the place T : IReadOnlyName, IReadOnlyTransform, IHierarchicalNode
{
AnimationClip clipClone = animationOfSourceRig.Clone();
Listing finalChannels = new();
var sorted = SortKeyframesByTime(animationOfSourceRig.NodeAnimationChannels);
foreach (var keyframe in sorted)
{
foreach(var map in rigsMapInBindPoses)
{
var src = map.Key;
var dst = map.Worth;
if (keyframe.Channel.NodeName != src.Identify)
proceed;
var idx = animationOfSourceRig.NodeAnimationChannels.IndexOf(keyframe.Channel);
var modifiedChannel = clipClone.NodeAnimationChannels[idx];
if(!finalChannels.Comprises(modifiedChannel))
{
finalChannels.Add(modifiedChannel);
modifiedChannel.NodeName = dst.Identify;
for (var i = 0; i
Utility capabilities used:
public static T InterpolateTreeUpwards(
T bone,
IList NodeAnimationChannels,
float timeInTicks
)
the place T : IReadOnlyName, IReadOnlyTransform, IHierarchicalNode
{
var channel = NodeAnimationChannels.FirstOrDefault(x => x.NodeName == bone.Identify);
var place = bone.Place;
var rotation = bone.Rotation;
var scale = bone.Scale;
if (channel != null)
{
place = AnimationPlayer.InterpolateVectorBetweenTwoFrames(channel.PositionKeys, timeInTicks);
rotation = AnimationPlayer.InterpolateQuaternionBetweenTwoFrames(channel.RotationKeys, timeInTicks);
scale = AnimationPlayer.InterpolateVectorBetweenTwoFrames(channel.ScaleKeys, timeInTicks);
}
// We do not return right here if null as a result of it couldn't have a channel however the guardian has.
T? guardian = default; // null
if (bone.Guardian != null)
guardian = InterpolateTreeUpwards((T)bone.Guardian, NodeAnimationChannels, timeInTicks);
attempt
{
var @params = new object?[] { guardian, bone.Identify, place, rotation, scale }; // constructor
bone = (T)Activator.CreateInstance(typeof(T), @params);
}
catch(Exception)
{
// todo: perhaps customized error msg?
throw;
}
if (guardian != null)
{
var kids = (ICollection)guardian.Youngsters;
kids.Add(bone);
}
return bone;
}
// Equal of Unity InverseTransformPoint
inside static System.Numerics.Vector3 CalcLocalPos(T sourceBone, System.Numerics.Vector3 level) the place T : IReadOnlyTransform, IHierarchicalNode
{
var worldPos = CalcWorldPos(sourceBone);
var worldRot = CalcWorldRot(sourceBone);
var relativePos = level - worldPos;
var localPos = System.Numerics.Vector3.Remodel(relativePos, System.Numerics.Quaternion.Inverse(worldRot));
return localPos;
}
// Equal of unity calcWorldMatrix
inside static System.Numerics.Vector3 CalcWorldPos(T sourceBone) the place T : IReadOnlyTransform, IHierarchicalNode
{
if (sourceBone.Guardian == null)
return sourceBone.Place;
else
{
var parentWorldPos = CalcWorldPos(sourceBone.Guardian);
var parentWorldRot = CalcWorldRot(sourceBone.Guardian);
return parentWorldPos + System.Numerics.Vector3.Remodel(sourceBone.Place, parentWorldRot);
}
}
// Equal of Unity TransformPoint
inside static System.Numerics.Vector3 CalcWorldPos(T sourceBone, System.Numerics.Vector3 pt) the place T : IReadOnlyTransform, IHierarchicalNode
{
var worldPos = CalcWorldPos(sourceBone);
var worldRot = CalcWorldRot(sourceBone);
return worldPos + System.Numerics.Vector3.Remodel(pt, worldRot);
}
inside static System.Numerics.Quaternion CalcLocalRot(T sourceBone, System.Numerics.Quaternion rotation) the place T : IReadOnlyTransform, IHierarchicalNode
{
var localRotation = System.Numerics.Quaternion.Inverse(CalcWorldRot(sourceBone)) * rotation;
return localRotation;
}
inside static System.Numerics.Quaternion CalcWorldRot(T sourceBone) the place T : IReadOnlyTransform, IHierarchicalNode
{
if (sourceBone.Guardian == null)
return sourceBone.Rotation;
else
return CalcWorldRot(sourceBone.Guardian) * sourceBone.Rotation;
}
inside static System.Numerics.Quaternion CalcWorldRot(T sourceBone, System.Numerics.Quaternion qt) the place T : IReadOnlyTransform, IHierarchicalNode
{
return CalcWorldRot(sourceBone) * qt;
}
This is a reference on the terminology used:
This is the output animation for the present algorithm. I am unsure what’s flawed.