문제는 수치 적 안정성 중 하나였습니다. 2 개월 동안이 작업에 대해 약 30 시간의 작업이있었습니다. 회전 행렬을 대상 코드에 꽂기 전에 직교 정규화하면 소스 곱하기의 간단한 솔루션 * 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);
}