【问题标题】:How to convert the positions of connected joints to relative delta rotations如何将连接关节的位置转换为相对增量旋转
【发布时间】:2012-08-10 23:10:58
【问题描述】:

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

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

【问题讨论】:

  • 你了解问题的几何吗?你到底被什么困住了?
  • 你知道目前唯一的变换是不是旋转?由于平移(或任何其他变换),这些点是否也可以具有不同的坐标?
  • 这不是一个真正的 C++ 问题,除非你已经弄清楚了数学并需要实施帮助,在这种情况下你应该告诉我们你尝试了什么。
  • 至少如果没有记错的话,将点转换为四元数意味着您的四元数将是 [x, y, z, 0]。

标签: c++ quaternions


【解决方案1】:

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

假设

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

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

代码是为了可读性而编写的,并不是为了优化。

【讨论】:

  • 感谢您的回复。让我再次解释我的问题。我已成功跟踪帧序列中点的 x、y、z 坐标。我想用对象模型映射这些坐标。通过研究一个已经生成的模型,我了解到它由一个相对于彼此移动的关节系统组成。每个关节都有一个父节点,它们的运动相对于其父节点以四元数格式写入。因此,我想将我在 3D 空间中相对于同一原点的 x、y、z 坐标转换为相对于其父级的四元数格式。
  • 好的,这更容易解决。 :) 既然我更好地理解了你的问题,我已经重写了我的答案。您可能想要编辑原始问题并向其中添加您的解释。
  • 感谢 Jens 的回复。但是我关于模型动画的领域知识有点低。所以我无法理解您发布的代码。因此,如果您能给我更多的代码解释,那就太好了。实际上我需要你的局部旋转相对于原始模型的四元数。谢谢。
  • 好的,让我知道您不了解哪些部分,我会尽力澄清。这段代码只会给你每帧局部旋转的变化,所以它们是相对于原始四元数的(如果你愿意的话,开始旋转)。我想这就是你想要的?
  • “Vector3 v1 = frameData->m_positionStart - parentFrameData->m_positionStart; Vector3 v2 = frameData->m_positionEnd - parentFrameData->m_positionEnd;”是什么意思因为这对我来说有些不清楚。另外,为什么要在“四元数 invStartRot = startRotation.inverse();”中计算逆。我不明白存储有关每个节点的子节点的数据的含义。如果你能给我一个伪代码之类的东西,那就太棒了。谢谢。
【解决方案2】:
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。然后我计算了模型中每个关节的相对向量。这被称为递归。

【讨论】:

    猜你喜欢
    • 2015-02-28
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 2012-11-29
    • 2012-12-07
    • 1970-01-01
    相关资源
    最近更新 更多