問題は数値安定性の1つでした。この作業には2か月間で約30時間かかりましたが、最初からそれを行っていたことがわかりました。回転行列をリターゲットコードに接続する前にオルソ正規化すると、source * inverse(target)を乗算するという単純なソリューションが完全に機能しました。もちろん、リターゲッティングにはそれ以上のものがあります(特に、スケルトンのさまざまな形状、つまり肩幅などを考慮に入れる)。誰かが興味を持っている場合、私はシンプルで素朴なアプローチに使用しているコードは次のとおりです。
public static SkeletalAnimation retarget(SkeletalAnimation animation, Skeleton target, string boneMapFilePath)
{
if(animation == null) throw new ArgumentNullException("animation");
if(target == null) throw new ArgumentNullException("target");
Skeleton source = animation.skeleton;
if(source == target) return animation;
int nSourceBones = source.count;
int nTargetBones = target.count;
int nFrames = animation.nFrames;
AnimationData[] sourceData = animation.data;
Matrix[] sourceTransforms = new Matrix[nSourceBones];
Matrix[] targetTransforms = new Matrix[nTargetBones];
AnimationData[] temp = new AnimationData[nSourceBones];
AnimationData[] targetData = new AnimationData[nTargetBones * nFrames];
// Get a map where map[iTargetBone] = iSourceBone or -1 if no such bone
int[] map = parseBoneMap(source, target, boneMapFilePath);
for(int iFrame = 0; iFrame < nFrames; iFrame++)
{
int sourceBase = iFrame * nSourceBones;
int targetBase = iFrame * nTargetBones;
// Copy the root translation and rotation directly over
AnimationData rootData = targetData[targetBase] = sourceData[sourceBase];
// Get the source pose for this frame
Array.Copy(sourceData, sourceBase, temp, 0, nSourceBones);
source.getAbsoluteTransforms(temp, sourceTransforms);
// Rotate target bones to face that direction
Matrix m;
AnimationData.toMatrix(ref rootData, out m);
Matrix.Multiply(ref m, ref target.relatives[0], out targetTransforms[0]);
for(int iTargetBone = 1; iTargetBone < nTargetBones; iTargetBone++)
{
int targetIndex = targetBase + iTargetBone;
int iTargetParent = target.hierarchy[iTargetBone];
int iSourceBone = map[iTargetBone];
if(iSourceBone <= 0)
{
targetData[targetIndex].rotation = Quaternion.Identity;
Matrix.Multiply(ref target.relatives[iTargetBone], ref targetTransforms[iTargetParent], out targetTransforms[iTargetBone]);
}
else
{
Matrix currentTransform, inverseCurrent, sourceTransform, final, m2;
Quaternion rot;
// Get the "current" transformation (transform that would be applied if rot is Quaternion.Identity)
Matrix.Multiply(ref target.relatives[iTargetBone], ref targetTransforms[iTargetParent], out currentTransform);
Math2.orthoNormalize(ref currentTransform);
Matrix.Invert(ref currentTransform, out inverseCurrent);
Math2.orthoNormalize(ref inverseCurrent);
// Get the final rotation
Math2.orthoNormalize(ref sourceTransforms[iSourceBone], out sourceTransform);
Matrix.Multiply(ref sourceTransform, ref inverseCurrent, out final);
Math2.orthoNormalize(ref final);
Quaternion.RotationMatrix(ref final, out rot);
// Calculate this bone's absolute position to use as next bone's parent
targetData[targetIndex].rotation = rot;
Matrix.RotationQuaternion(ref rot, out m);
Matrix.Multiply(ref m, ref target.relatives[iTargetBone], out m2);
Matrix.Multiply(ref m2, ref targetTransforms[iTargetParent], out targetTransforms[iTargetBone]);
}
}
}
return new SkeletalAnimation(target, targetData, animation.fps, nFrames);
}