5

我目前正在实现一个 C++ 解决方案来跟踪多个对象的运动。在那里,我在帧序列中跟踪了这些对象的点,使得每帧中有多个点。因此,我有整个帧序列的这些点的 x、y、z 坐标。通过研究一个已经生成的模型,我了解到它由一个相对于彼此移动的关节系统组成。每个关节都有一个父节点,它们的运动相对于其父节点以四元数格式写入。因此,我想将我的 x、y、z 坐标(相对于同一原点在 3D 空间中)转换为相对于其父级写入的四元数格式。然后我可以使用四元数为它们设置动画。

我不明白如何计算它需要的角度。您能否提供一个示例代码(在 C++ 中)或任何有用的资源来解决这个问题。

4

2 回答 2

4

所以我们有一个连接关节系统,我们想找出关节从一帧到另一帧的相对增量旋转。我将把相对旋转称为局部旋转,因为它本身的“相对旋转”并不能告诉我们它相对于什么。(它是相对于一个物体,还是相对于宇宙的中心等?)

假设

我将假设一个关节的树形结构,这样每个关节只有一个父节点,我们只有一个根节点(没有父节点)。如果每个关节有多个父母,您应该能够使用相同的解决方案,但是您将每个关节将每个父母进行一次相对旋转,并且您需要为每个父母进行一次计算。如果你有几个没有父节点的关节,那么每个关节都可以被认为是它自己的树中的一个根,该树由连接的关节组成。

我假设你有一个四元数数学库可以;从轴和角度创建,设置为恒等、逆和累积四元数。如果不这样做,您应该从 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()函数遍历树。

为了使代码更易于阅读,我有一个从联合树节点JointDataFrameData的访问器。您可能不希望在您的实现中具有如此直接的依赖关系。

// 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 );   
}   

代码是为了可读性而编写的,而不是最佳的。

于 2012-08-17T22:24:42.033 回答
0
Point3d Controller::calRelativeToParent(int parentID,Point3d point,int frameID){
if(parentID == 0){
    QUATERNION temp = calChangeAxis(-1,parentID,frameID);
    return getVect(multiplyTwoQuats(multiplyTwoQuats(temp,getQuat(point)),getConj(temp)));
}else{
    Point3d ref = calRelativeToParent(originalRelativePointMap[parentID].parentID,point,frameID);
    QUATERNION temp = calChangeAxis(originalRelativePointMap[parentID].parentID,parentID,frameID);  
    return getVect(multiplyTwoQuats(multiplyTwoQuats(temp,getQuat(ref)),getConj(temp)));
}}
QUATERNION Controller::calChangeAxis(int parentID,int qtcId,int frameID){ //currentid = id of the position of the orientation to be changed
if(parentID == -1){
    QUATERNION out = multiplyTwoQuats(quatOrigin.toChange,originalRelativePointMap[qtcId].orientation);
    return out;
}
else{
    //QUATERNION temp = calChangeAxis(originalRelativePointMap[parentID].parentID,qtcId,frameID);
    //return multiplyTwoQuats(finalQuatMap[frameID][parentID].toChange,temp);
    return multiplyTwoQuats(finalQuatMap[frameID][parentID].toChange,originalRelativePointMap[qtcId].orientation);  
}}

这是我使用的算法。我首先计算了每个帧与其父帧的相对向量。这里根的父ID为0。然后我计算了模型中每个关节的相对向量。这被称为递归。

于 2012-08-29T16:54:06.873 回答