我目前正在实现一个C++解决方案来跟踪多个对象的运动.因为我在帧序列中跟踪了这些对象的点,使得每帧中有多个点.结果,我有整个帧序列的那些点的x,y,z坐标.通过研究已经生成的模型,我理解它包括相对于彼此移动的关节系统.每个关节都有一个父母,他们的动作是以四元数格式相对于父母的.因此,我想将相对于同一原点在3D空间中的x,y,z坐标转换为相对于其父级写入的四元数格式.然后我可以使用四元数来动画它们.
我不明白如何计算它所需的角度.能否请您提供示例代码(用c ++)或任何有用的资源来解决这个问题.
因此,我们有一个连接关节的系统,我们想要找出关节从一个框架到另一个框架的相对增量旋转。我将相对旋转称为局部旋转,因为“相对旋转”本身并不能告诉我们它是相对于什么的。(它是相对于物体、宇宙中心等吗?)
我将假设关节的树结构,以便每个关节只有一个父关节,并且我们只有一个根关节(没有父关节)。如果每个关节有多个父级,您应该能够使用相同的解决方案,但是每个关节将每个父级进行一次相对旋转,并且您需要为每个父级进行一次计算。如果您有多个没有父级的关节,那么每个关节都可以被视为由连接的关节组成的自己的树中的一根根。
我假设你有一个四元数数学库,可以;从轴和角度创建,设置为恒等、逆并累积四元数。如果你不这样做,你应该从维基百科或谷歌找到实现它们所需的所有信息。
下面的代码首先计算帧开始和结束时关节的局部旋转。它使用两个向量进行此计算;来自其父母的向量和从祖父母到父母的向量。然后,为了计算增量旋转,它使用反转的开始旋转,通过应用其反转,从结束旋转中“删除”开始旋转。所以我们最终得到了该帧的局部增量旋转。
对于联合层次结构的前两个级别,我们有可以直接解决的特殊情况。
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 )
}
Run Code Online (Sandbox Code Playgroud)
下面是 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 );
}
Run Code Online (Sandbox Code Playgroud)
编写代码是为了提高可读性,而不是为了优化。