所以我们有一个连接关节系统,我们想找出关节从一帧到另一帧的相对增量旋转。我将把相对旋转称为局部旋转,因为它本身的“相对旋转”并不能告诉我们它相对于什么。(它是相对于一个物体,还是相对于宇宙的中心等?)
假设
我将假设一个关节的树形结构,这样每个关节只有一个父节点,我们只有一个根节点(没有父节点)。如果每个关节有多个父母,您应该能够使用相同的解决方案,但是您将每个关节将每个父母进行一次相对旋转,并且您需要为每个父母进行一次计算。如果你有几个没有父节点的关节,那么每个关节都可以被认为是它自己的树中的一个根,该树由连接的关节组成。
我假设你有一个四元数数学库可以;从轴和角度创建,设置为恒等、逆和累积四元数。如果不这样做,您应该从 Wikipedia 或 Google 中找到实施它们所需的所有信息。
计算旋转
下面的代码首先计算关节在帧开始和结束时的局部旋转。它使用两个向量进行此计算;来自它的父母的向量和从祖父母到父母的向量。然后为了计算增量旋转,它使用反向开始旋转通过应用它的反向旋转从结束旋转中“移除”开始旋转。所以我们最终得到了该帧的局部增量旋转。
对于联合层次结构的前两个级别,我们有可以直接解决的特殊情况。
伪代码
out 参数是一个名为result的多维数组。
注意:startPosition、endPosition、parentStartPosition、parentEndPosition、grandParentStartPosition、grandParentStartPosition都必须为循环的每次迭代更新。为了关注问题的核心,未显示该更新。
for each frame {
for each joint {
if no parent {
// no parent means no local rotation
result[joint,frame] = identityQuaternion
}
else {
startLink = startPosition - parentStartPosition
endLink = endPosition - parentEndPosition
if no grandParent {
// no grand parent - we can calculate the local rotation directly
result[joint,frame] = QuaternionFromVectors( startLink, endLink )
}
else {
parentStartLink = parentStartPosition - grandParentStartPosition
parentEndLink = parentEndPosition - grandParentEndPosition
// calculate the local rotations
// = the difference in rotation between parent link and child link
startRotation = QuaternionFromVectors( parentStartLink, startLink )
endRotation = QuaternionFromVectors( parentEndLink, endLink )
// calculate the delta local rotation
// = the difference between start and end local rotations
invertedStartRotation = Inverse( startRotation )
deltaRotation = invertedStartRotation.Rotate( endRotation )
result[joint,frame] = deltaRotation
}
}
}
}
QuaternionFromVectors( fromVector, toVector )
{
axis = Normalize( fromVector.Cross( toVector ) )
angle = Acos( fromVector.Dot( toVector ) )
return Quaternion( axis, angle )
}
C++ 实现
下面是 C++ 中未经测试的递归实现。对于每一帧,我们从JointData树的根开始,然后通过递归调用JointData::calculateRotations()函数遍历树。
为了使代码更易于阅读,我有一个从联合树节点JointData到FrameData的访问器。您可能不希望在您的实现中具有如此直接的依赖关系。
// Frame data holds the individual frame data for a joint
struct FrameData
{
Vector3 m_positionStart;
Vector3 m_positionEnd;
// this is our unknown
Quaternion m_localDeltaRotation;
}
class JointData
{
public:
...
JointData *getChild( int index );
int getNumberOfChildren();
FrameData *getFrame( int frameIndex );
void calculateDeltaRotation( int frameIndex, JointData *parent = NULL,
Vector3& parentV1 = Vector3(0),
Vector3& parentV2 = Vector3(0) );
...
}
void JointData::calculateDeltaRotation( int frameIndex, JointData *parent,
Vector3& parentV1, Vector3& parentV2 )
{
FrameData *frameData = getFrame( frameIndex );
if( !parent )
{
// this is the root, it has no local rotation
frameData->m_localDeltaRotation.setIdentity();
return;
}
FrameData *parentFrameData = parent->getFrame( frameIndex );
// calculate the vector from our parent
// for the start (v1) and the end (v2) of the frame
Vector3 v1 = frameData->m_positionStart - parentFrameData->m_positionStart;
Vector3 v2 = frameData->m_positionEnd - parentFrameData->m_positionEnd;
if( !getParent()->getParent() )
{
// child of the root is a special case,
// we can calculate it's rotation directly
frameData->m_localDeltaRotation = calculateQuaternion( v1, v2 );
}
else
{
// calculate start and end rotations
// apply inverse start rotation to end rotation
Quaternion startRotation = calculateQuaternion( parentV1, v1 );
Quaternion endRotation = calculateQuaternion( parentV2, v2 );
Quaternion invStartRot = startRotation.inverse();
frameData->m_localDeltaRotation = invStartRot.rotate( endRotation );
}
for( int i = 0; i < getNumberOfChildren(); ++i )
{
getChild( i )->calculateRotations( frameIndex, this, v1, v2 );
}
}
// helper function to calulate a quaternion from two vector
Quaternion calculateQuaternion( Vector3& fromVector, Vector3& toVector )
{
float angle = acos( fromVector.dot( toVector ) );
Vector3 axis = fromVector.cross( toVector );
axis.normalize();
return Quaternion( axis, angle );
}
代码是为了可读性而编写的,而不是最佳的。