天天看點

osg中三維模型的位置變換

對模型的平移和旋轉等操作是我們在做三維軟體開發時必然會解決的事情。但是由于基本變換預設是以世界坐标系的原點為變換中心,是以我們在進行平移旋轉等操作時往往會發現其并非如我們想象的那樣去執行。舉個簡單的例子:當對物體進行先x軸方向20個機關的平移操作,再将物體繞自身z軸旋轉60度的操作時。我們希望的是左圖所示結果,但往往我們得到的是右圖的結果(Z軸未标出)。

osg中三維模型的位置變換
osg中三維模型的位置變換

問題出現的原因就在于我們想要的是以物體自身的中心為變換中心,而實際得到的卻是以世界坐标的原點為變換中心的。為了解決這個問題,最好的辦法就是先将物體的變換中心移到世界坐标的原點,再進行級聯變換,然後再将物體移回原先的位置。

在osg中可以按照以下步驟進行:

1)osg場景中的物體通常是以Node節點的形式存在的,是以我們需将變換的物體轉換成osg::MatrixTransform類型,再通過getMatrix()方法得到目前矩陣。

2)為了将物體中心同世界坐标原點重合,需獲得物體中心。可以采用getBound()或getSphere()方法擷取模型包圍盒,再采用包圍盒的center()方法獲得包圍盒中心即為物體中心。

3)按上述方法變換模型。

實作代碼如下所示:

osg::MatrixTransform* picked= static_cast<osg::MatrixTransform*>(pNode); //轉換成MatrixTransform
osg::Vec3d center= picked->getBound().center(); //獲得包圍盒中心
osg::Matrixd originPos= picked->getMatrix(); //獲得目前矩陣
picked->setMatrix(originPos*osg::Matrixd::translate(-center) //先将物體中心平移到世界坐标的原點 
     *osg::Matrixd::scale(0.5,0.5, 0.5) //縮放 
     *osg::Matrixd::rotate(osg::DegreesToRadians(60.0),0,0,1)//旋轉
     *osg::Matrixd::translate(20,0,0)//平移
     *osg::Matrixd::translate(center));//變換後再将物體移回
           

對于單一的矩陣挂接,上述問題似乎很容易就得到了解決。但是,在實際的模型或許并沒那麼簡單,往往是在多重的矩陣挂接下才得到模型的位置。而我們通過上述的getMatrix()方法得到的僅僅是模型的目前矩陣,還有父級矩陣甚至是父父級矩陣等。此時我們需要做的步驟仍是上述操作,但是需先得到物體世界坐标位置,并将其移到世界坐标原點,再進行級聯變換,最後将其移回原先位置。

我們先來看一個這樣的例子,場景如下:

osg中三維模型的位置變換

根據上述讨論我們知道,最後模型的世界坐标被變換為:

N*M*M2

現在,若對模型進行平移變換T,由于我們本意是對模型N本身坐标系進行變換,是以本應得到以下變換公式:

設矩陣B= osg::Matrix::translate(-(center*M*M2)) //移回坐标原點

            C=osg::Matrix::translate(center*M*M2)  //移回原先位置

N*M*M2*B*T*C

顯然,我們所需要變換的僅僅是矩陣M,而不能在ROOT和M2之間加一個B*T*C,是以就有了下列公式:

M重新指派為 N*(M*M2)*B*T*C*inv(M*M2)*M

這樣,新的場景圖則對應于:

N*(M*M2)*B*T*C*inv(M*M2)*M*M2=N*M*M2*B*T*C

具體代碼如下:

osg::MatrixTransform* picked= static_cast<osg::MatrixTransform*>(pNode);
osg::Matrixd originPos= picked->getMatrix();
osg::Vec3d center= picked->getBound().center();
osg::Node* pParentNode;
osg::MatrixTransform* pParent;
pParentNode= pNode->getParent(0);
pParent= dynamic_cast<osg::MatrixTransform*>(pParentNode);
while(pParent!= NULL)
{
     originPos= originPos*pParent->getMatrix();
     pParentNode= pParentNode->getParent(0);
     pParent= dynamic_cast<osg::MatrixTransform*>(pParentNode);
}
osg::Matrixd inverseAll= osg::Matrixd::inverse(originPos)*picked->getMatrix();
osg::Vec3d originCenter=center*(osg::Matrixd::inverse(picked->getMatrix())*originPos);
picked->setMatrix(originPos*osg::Matrix::translate(-originCenter)
                  *osg::Matrixd::scale(0.5,0.5, 0.5)
                  *osg::Matrix::rotate(osg::Quat(osg::DegreesToRadians(60.0),0,0,1))
                  *osg::Matrix::translate(20,0, 0)
                  *osg::Matrix::translate(originCenter)*invertAll);
           

在上述代碼中,originPos最終得到的是模型在世界坐标下的變換矩陣,osg中常常将其命名為localToWorld,也就是局部到世界的轉變,而對其求逆則稱為worldToLocal,顯然是世界到局部的轉變。關于這兩個矩陣獲得,osg中還有一些自帶的函數去獲得(上述方法可以說是最原始的擷取方法),具體可以檢視osg源碼或幫助文檔中MatrixTransform類或是osgManipulator類等,在此就不在詳細讨論。