可以简单写为:q = [sin(theta/2)axis, sin(theta/2)w] 或者 [x,y,z,w] 或者[v,w]
3D几何意义:轴变换了方向,角度也换了方向,刚好 -q = q。这个特性比较郁闷,在3D中任意方位,都有2种不同的四元数表示方法,竟然不是唯一的。
D3DXQuaternionIsIdentity
D3DXQuaternionNormalize,这个公式是求标准四元数,类似矢量的正则化。
3D几何意义:相当于转了一个相反的角。由于3D中只有单位长度的四元数才有意义,共轭和逆是一样的。
3D几何意义:绝对值越大,2个方位越近似,夹角的余弦。补充:对于单位四元数,有-1 <= a.b <= +1,我们一般只关心其绝对值,和矢量点乘类似,绝对值越大,表示2个角度的角位移越小(即相似)。
(Q1Q2...Q(n-1)Qn)-1 = Qn-1Q(n-1)-1...Q2-1Q1-1
这个差找不到相应的DIRECTX公式。
exp(Q) = (cos(theta), sin(theta) * v)
其中,二四元数之间的夹角余弦可以用点积来计算:x1x2+y1y2+z1z2+w1w2;
( D3DXQUATERNION *pOut, CONST D3DXVECTOR3 *pV, FLOAT Angle );
插值的例子:
bool AnimationModelClass::UpdateMD5Model(float deltaTime, int animation) { MD5Model.m_animations[animation].currAnimTime += deltaTime; // Update the current animation time if(MD5Model.m_animations[animation].currAnimTime > MD5Model.m_animations[animation].totalAnimTime) MD5Model.m_animations[animation].currAnimTime = 0.0f; // Which frame are we on float currentFrame = MD5Model.m_animations[animation].currAnimTime * MD5Model.m_animations[animation].frameRate; int frame0 = floorf( currentFrame ); int frame1 = frame0 + 1; // Make sure we don't go over the number of frames if(frame0 == MD5Model.m_animations[animation].numFrames-1) frame1 = 0; float interpolation = currentFrame - frame0; // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor std::vector<Joint> interpolatedSkeleton; // Create a frame skeleton to store the interpolated skeletons in // Compute the interpolated skeleton for( int i = 0; i < MD5Model.m_animations[animation].numJoints; i++) { Joint tempJoint; Joint joint0 = MD5Model.m_animations[animation].frameSkeleton[frame0][i]; // Get the i'th joint of frame0's skeleton Joint joint1 = MD5Model.m_animations[animation].frameSkeleton[frame1][i]; // Get the i'th joint of frame1's skeleton tempJoint.parentID = joint0.parentID; // Set the tempJoints parent id // Turn the two quaternions into XMVECTORs for easy computations D3DXQUATERNION joint0Orient = D3DXQUATERNION(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w); D3DXQUATERNION joint1Orient = D3DXQUATERNION(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w); // Interpolate positions tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x)); tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y)); tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z)); // Interpolate orientations using spherical interpolation (Slerp) D3DXQUATERNION qtemp; D3DXQuaternionSlerp(&qtemp, &joint0Orient, &joint1Orient, interpolation); tempJoint.orientation.x = qtemp.x; tempJoint.orientation.y = qtemp.y; tempJoint.orientation.z = qtemp.z; tempJoint.orientation.w = qtemp.w; // Push the joint back into our interpolated skeleton interpolatedSkeleton.push_back(tempJoint); } for ( int k = 0; k < MD5Model.numSubsets; k++) { for ( int i = 0; i < MD5Model.m_subsets[k].numVertices; ++i ) { Vertex tempVert = MD5Model.m_subsets[k].m_vertices[i]; // Make sure the vertex's pos is cleared first tempVert.x = 0; tempVert.y = 0; tempVert.z = 0; // Clear vertices normal tempVert.nx = 0; tempVert.ny = 0; tempVert.nz = 0; // Sum up the joints and weights information to get vertex's position and normal for ( int j = 0; j < tempVert.WeightCount; ++j ) { Weight tempWeight = MD5Model.m_subsets[k].m_weights[tempVert.StartWeight + j]; Joint tempJoint = interpolatedSkeleton[tempWeight.jointID]; // Convert joint orientation and weight pos to vectors for easier computation D3DXQUATERNION tempJointOrientation = D3DXQUATERNION(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w); D3DXQUATERNION tempWeightPos = D3DXQUATERNION(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f); // We will need to use the conjugate of the joint orientation quaternion D3DXQUATERNION tempJointOrientationConjugate; D3DXQuaternionInverse(&tempJointOrientationConjugate, &tempJointOrientation); // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate" D3DXVECTOR3 rotatedPoint; D3DXQUATERNION qqtemp; D3DXQuaternionMultiply(&qqtemp, &tempJointOrientation, &tempWeightPos); D3DXQuaternionMultiply(&qqtemp, &qqtemp, &tempJointOrientationConjugate); rotatedPoint.x = qqtemp.x; rotatedPoint.y = qqtemp.y; rotatedPoint.z = qqtemp.z; // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account tempVert.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias; tempVert.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias; tempVert.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias; // Compute the normals for this frames skeleton using the weight normals from before // We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate) D3DXQUATERNION tempWeightNormal = D3DXQUATERNION(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f); D3DXQuaternionMultiply(&qqtemp, &tempJointOrientation, &tempWeightNormal); D3DXQuaternionMultiply(&qqtemp, &qqtemp, &tempJointOrientationConjugate); // Rotate the normal rotatedPoint.x = qqtemp.x; rotatedPoint.y = qqtemp.y; rotatedPoint.z = qqtemp.z; // Add to vertices normal and ake weight bias into account tempVert.nx -= rotatedPoint.x * tempWeight.bias; tempVert.ny -= rotatedPoint.y * tempWeight.bias; tempVert.nz -= rotatedPoint.z * tempWeight.bias; } // Store the vertices position in the position vector instead of straight into the vertex vector MD5Model.m_subsets[k].m_positions[i].x = tempVert.x; MD5Model.m_subsets[k].m_positions[i].y = tempVert.y; MD5Model.m_subsets[k].m_positions[i].z = tempVert.z; // Store the vertices normal MD5Model.m_subsets[k].m_vertices[i].nx = tempVert.nx; MD5Model.m_subsets[k].m_vertices[i].ny = tempVert.ny; MD5Model.m_subsets[k].m_vertices[i].nz = tempVert.nz; // Create the temp D3DXVECTOR3 for normalize D3DXVECTOR3 dtemp = D3DXVECTOR3(0,0,0); dtemp.x = MD5Model.m_subsets[k].m_vertices[i].nx; dtemp.y = MD5Model.m_subsets[k].m_vertices[i].ny; dtemp.z = MD5Model.m_subsets[k].m_vertices[i].nz; D3DXVec3Normalize(&dtemp, &dtemp); MD5Model.m_subsets[k].m_vertices[i].nx = dtemp.x; MD5Model.m_subsets[k].m_vertices[i].ny = dtemp.y; MD5Model.m_subsets[k].m_vertices[i].nz = dtemp.z; // Put the positions into the vertices for this subset MD5Model.m_subsets[k].m_vertices[i].x = MD5Model.m_subsets[k].m_positions[i].x; MD5Model.m_subsets[k].m_vertices[i].y = MD5Model.m_subsets[k].m_positions[i].y; MD5Model.m_subsets[k].m_vertices[i].z = MD5Model.m_subsets[k].m_positions[i].z; } // Update the subsets vertex buffer // First lock the buffer void* mappedVertBuff; bool result; result = MD5Model.m_subsets[k].vertBuff->Map(D3D10_MAP_WRITE_DISCARD, 0, &mappedVertBuff); if(FAILED(result)) { return false; } // Copy the data into the vertex buffer. memcpy(mappedVertBuff, &MD5Model.m_subsets[k].m_vertices[0], (sizeof(Vertex) * MD5Model.m_subsets[k].numVertices)); MD5Model.m_subsets[k].vertBuff->Unmap(); } return true; }
D3DXQUATERNION* WINAPI D3DXQuaternionBaryCentric//三个四元数的重心
D3DXQUATERNION* D3DXQuaternionConjugate //求共轭四元数
FLOAT D3DXQuaternionDot //四元数点乘
D3DXQUATERNION* WINAPI D3DXQuaternionExp //四元数指数
D3DXQUATERNION* WINAPI D3DXQuaternionInverse//共轭变换并规格化四元数
D3DXQUATERNION* D3DXQuaternionIdentity //单位四元数
BOOL D3DXQuaternionIsIdentity //判断单位四元数
FLOAT D3DXQuaternionLength //返回四元数长度
FLOAT D3DXQuaternionLengthSq //返回四元数长度平方
D3DXQUATERNION* WINAPI D3DXQuaternionLn //计算自然对数
D3DXQUATERNION* WINAPI D3DXQuaternionMultiply //两个四元数相乘
D3DXQUATERNION* WINAPI D3DXQuaternionNormalize //单位长度的四元数
D3DXQUATERNION* WINAPI D3DXQuaternionRotationAxis//绕轴旋转四元数
D3DXQUATERNION* WINAPI D3DXQuaternionRotationMatrix//旋转矩阵创建四元数
D3DXQUATERNION* WINAPI D3DXQuaternionRotationYawPitchRoll//绕XYZ旋转创建的四元数
D3DXQUATERNION* WINAPI D3DXQuaternionSlerp //两个四元数球面插值
D3DXQUATERNION* WINAPI D3DXQuaternionSquad //球面四边形插四元数
void WINAPI D3DXQuaternionSquadSetup //创建球面四边形插值的控制点
void WINAPI D3DXQuaternionToAxisAngle//向量绕自身旋转角度计算四元数
四元数旋转物品:
首先我们需要定义一些变量来储存所需信息
D3DXQUATERNION g_qNow, g_qDown; //分别是 现在的旋转用四元数 按下鼠标时的旋转用四元数
D3DXVECTOR3 g_vDownPt, g_vCurrentPt; //分别是 按下鼠标时的球面上的位置向量 当前的位置向量
bool g_bDrag = false; //是否按下鼠标左键 用来判断按键拖拽
然后初始化这些变量
D3DXQuaternionIdentity(&g_qNow);
D3DXQuaternionIdentity(&g_qDown);
g_bDrag = false;
好了,初始化工作做完了,然后具体讲下算法。
在鼠标按下处理过程
case WM_LBUTTONDOWN:
{
int iMouseX = ( short )LOWORD( lParam );
int iMouseY = ( short )HIWORD( lParam ); //得到屏幕坐标
g_bDrag = true;
g_qDown = g_qNow;
g_vDownPt = ScreenToVector( ( float )iMouseX, ( float )iMouseY ); //得到投影窗口下的坐标
}
在鼠标移动处理过程
case WM_MOUSEMOVE:
{
if( g_bDrag )
{
int iMouseX = ( short )LOWORD( lParam );
int iMouseY = ( short )HIWORD( lParam ); //得到屏幕坐标
g_vCurrentPt = ScreenToVector( ( float )iMouseX, ( float )iMouseY );//得到投影窗口下的坐标
g_qNow = g_qDown * QuatFromBallPoints( g_vDownPt, g_vCurrentPt );
}
}
//看看QuatFromBallPoints函数
D3DXQUATERNION QuatFromBallPoints( const D3DXVECTOR3& vFrom, const D3DXVECTOR3& vTo )
{
D3DXVECTOR3 vPart;
float fDot = D3DXVec3Dot( &vFrom, &vTo ); //取得两向量的点乘,因为两个都是单位向量,所以fDot等于cos theta
D3DXVec3Cross( &vPart, &vFrom, &vTo );//叉乘,获得的是垂直于两个向量的一个向量,即旋转轴。其模等于|a||b|sin theta等于sin theta
return D3DXQUATERNION( vPart.x, vPart.y, vPart.z, fDot );//正好构成一个旋转2*theta角度的四元数
}
首先说一下旋转四元数,一个(x*sin theta, y*sin theta, z*sin theta, cos theta)的四元数被用来旋转2*theta角度。在上面的代码中通过两个单位向量得到了一个旋转四元数。从效果上来说就是鼠标在球体上做过theta角度,物体就旋转2*theta角度。这是一个很方便的解决方法,不仅省却了换算,也使得物体能够在一次拖拽中旋转360度。现在大部分事情都清楚了,接下来只要在绘制之前把世界矩阵按所得的旋转四元数旋转之后绘制物体就可以了。
我们使用D3DXMatrixRotationQuaternion这个函数从四元数得到一个旋转矩阵作为世界矩阵。
D3DXMATRIXA16 matWorld;
D3DXMatrixRotationQuaternion( &matWorld, &g_qNow );
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
至此完成,我们只需要在每一次绘制的时候按当前的旋转四元数即g_qNow来设置世界矩阵即可。
// for (unsigned int i = 0; i < bones->size(); i++) { // unsigned long t0, t1; D3DXQUATERNION q0, q1; D3DXVECTOR3 p0, p1; //======================================== //======================================== if (ite_keyFrames[i] != keyFrames[i].end()) { // t0 = (*ite_keyFrames[i]).frameNo; // boneRot[i] = q0 = (*ite_keyFrames[i]).rotation; // bonePos[i] = p0 = (*ite_keyFrames[i]).position; // if (++ite_keyFrames[i] != keyFrames[i].end()) { // KeyFrame k = *ite_keyFrames[i]; // t1 = k.frameNo; // q1 = k.rotation; // p1 = k.position; //======================================== //(25 - 10) / 30 - 10 = 3 / 4 = 0.75 → 75% //======================================== float s = (float)(time - t0) / (float)(t1 - t0); //======================================== //======================================== D3DXQuaternionSlerp(&boneRot[i], &q0, &q1, k.bezie_r->GetY(s)); bonePos[i].x = p0.x + (p1.x - p0.x)*k.bezie_x->GetY(s); bonePos[i].y = p0.y + (p1.y - p0.y)*k.bezie_y->GetY(s); bonePos[i].z = p0.z + (p1.z - p0.z)*k.bezie_z->GetY(s); if (time != t1) --ite_keyFrames[i]; } } //======================================== //======================================== D3DXMATRIX rot, trans; D3DXMatrixRotationQuaternion(&rot, &boneRot[i]); D3DXMatrixTranslation(&trans, bonePos[i].x, bonePos[i].y, bonePos[i].z); //======================================== //======================================== (*bones)[i].boneMatBL = rot*trans*(*bones)[i].initMatBL; }
参考:
http://blog.csdn.net/silangquan/article/details/50390570
http://blog.csdn.net/ycl295644/article/details/50961201