本章教程將繼續使用回調和節點路徑(NodePath)來檢索節點的世界坐標。
本章目標:
在一個典型的仿真過程中,用戶可能需要從場景中的各種車輛和人物里選擇一個進行跟隨。本章將介紹一種將攝像機“依附”到場景圖形節點的方法。此時視口的攝像機將跟隨節點的世界坐標進行放置。
----------------------------------------------------------------------
概述:
視口類包括了一系列的矩陣控制器(osgGA::MatrixManipulator)。因而提供了“驅動控制(Drive)”,“軌跡球 (Trackball)”,“飛行(Fly)”等交互方法。矩陣控制器類用於更新攝像機位置矩陣。它通常用於回應GUI事件(鼠標點擊,拖動,按鍵,等 等)。本文所述的功能需要依賴於相機位置矩陣,並參照場景圖形節點的世界坐標。這樣的話,相機就可以跟隨場景圖形中的節點進行運動了。
為了獲得場景圖形中節點的世界坐標,我們需要使用節點訪問器的節點路徑功能來具現一個新的類。這個類將提供一種方法將自己的實例關聯到場景圖形,並因此提 供訪問任意節點世界坐標的方法。此坐標矩陣(場景中任意節點的世界坐標)將作為相機位置的矩陣,由osgGA::MatrixManipulator實例 使用。
實現:
首先我們創建一個類,計算場景圖形中的多個變換矩陣的累加結果。很顯然,所有的節點訪問器都會訪問當前的節點路徑。節點路徑本質上是根節點到當前節點的所有節點列表。有了節點路徑的實例之后,我們就可以使用場景圖形的方法computeWorldToLocal( osg::NodePath)來獲取表達節點世界坐標的矩陣了。
這個類的核心是使用更新回調來獲取某個給定節點之前所有節點的矩陣和。整個類的定義如下:
struct updateAccumulatedMatrix : public osg::NodeCallback
{
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
matrix = osg::computeWorldToLocal(nv->getNodePath() );
traverse(node,nv);
}
osg::Matrix matrix;
};
下一步,我們需要在場景圖形的更新遍歷中啟動回調類。因此,我們將創建一個類,其中包括一個osg::Node實例作為數據成員。此節點數據成員的更新回 調是上述updateAccumulatedMatrix類的實例,同時此節點也將設置為場景的一部分。為了讀取用於描繪節點世界坐標的矩陣(該矩陣與節 點實例相關聯),我們需要為矩陣提供一個“get”方法。我們還需要提供添加節點到場景圖形的方法。我們需要注意的是,用戶應如何將節點關聯到場景中。此 節點應當有且只有一個父節點。因此,為了保證這個類的實例只有一個相關聯的節點,我們還需要記錄這個類的父節點。類的定義如下面的代碼所示:
struct transformAccumulator
{
public:
transformAccumulator();
bool attachToGroup(osg::Group* g);
osg::Matrix getMatrix();
protected:
osg::ref_ptr parent;
osg::Node* node;
updateAccumulatedMatrix* mpcb;
};
類的實現代碼如下所示:
transformAccumulator::transformAccumulator()
{
parent = NULL;
node = new osg::Node;
mpcb = new updateAccumulatedMatrix();
node->setUpdateCallback(mpcb);
}
osg::Matrix transformAccumulator::getMatrix()
{
return mpcb->matrix;
}
bool transformAccumulator::attachToGroup(osg::Group* g)
// 注意不要在回調中調用這個函數。
{
bool success = false;
if (parent != NULL)
{
int n = parent->getNumChildren();
for (int i = 0; i < n; i++)
{
if (node == parent->getChild(i) )
{
parent->removeChild(i,1);
success = true;
}
}
if (! success)
{
return success;
}
}
g->addChild(node);
return true;
}
現在,我們已經提供了類和方法來獲取場景中節點的世界坐標矩陣,我們所需的只是學習如何使用這個矩陣來變換相機的位置。 osgGA::MatrixManipulator類即可提供一種更新相機位置矩陣的方法。我們可以從MatrixManipulator繼承一個新的 類,以實現利用場景中某個節點的世界坐標矩陣來改變相機的位置。為了實現這一目的,這個類需要提供一個數據成員,作為上述的 accumulateTransform實例的句柄。新建類同時還需要保存相機位置矩陣的相應數據。
MatrixManipulator類的核心是“handle”方法。這個方法用於檢查選中的GUI事件並作出響應。對我們的類而言,唯一需要響應的 GUI事件就是“FRAME”事件。在每一個“幀事件”中,我們都需要設置相機位置矩陣與transformAccumulator矩陣的數值相等。我們 可以在類的成員中創建一個簡單的updateMatrix方法來實現這一操作。由於我們使用了虛基類,因此某些方法必須在這里進行定義(矩陣的設置及讀 取,以及反轉)。綜上所述,類的實現代碼如下所示:
class followNodeMatrixManipulator : public osgGA::MatrixManipulator
{
public:
followNodeMatrixManipulator( transformAccumulator* ta);
bool handle (const osgGA::GUIEventAdapter&ea, osgGA::GUIActionAdapter&aa);
void updateTheMatrix();
virtual void setByMatrix(const osg::Matrixd& mat) {theMatrix = mat;}
virtual void setByInverseMatrix(const osg::Matrixd&mat) {}
virtual osg::Matrixd getInverseMatrix() const;
virtual osg::Matrixd getMatrix() const;
protected:
~followNodeMatrixManipulator() {}
transformAccumulator* worldCoordinatesOfNode;
osg::Matrixd theMatrix;
};
The class implementation is as follows:
followNodeMatrixManipulator::followNodeMatrixManipulator( transformAccumulator* ta)
{
worldCoordinatesOfNode = ta; theMatrix = osg::Matrixd::identity();
}
void followNodeMatrixManipulator::updateTheMatrix()
{
theMatrix = worldCoordinatesOfNode->getMatrix();
}
osg::Matrixd followNodeMatrixManipulator::getMatrix() const
{
return theMatrix;
}
osg::Matrixd followNodeMatrixManipulator::getInverseMatrix() const
{
// 將矩陣從Y軸向上旋轉到Z軸向上
osg::Matrixd m;
m = theMatrix * osg::Matrixd::rotate(-M_PI/2.0, osg::Vec3(1,0,0) );
return m;
}
void followNodeMatrixManipulator::setByMatrix(const osg::Matrixd& mat)
{
theMatrix = mat;
}
void followNodeMatrixManipulator::setByInverseMatrix(const osg::Matrixd& mat)
{
theMatrix = mat.inverse();
}
bool followNodeMatrixManipulator::handle
(const osgGA::GUIEventAdapter&ea, osgGA::GUIActionAdapter&aa)
{
switch(ea.getEventType())
{
case (osgGA::GUIEventAdapter::FRAME):
{
updateTheMatrix();
return false;
}
}
return false;
}
上述的所有類都定義完畢之后,我們即可直接對其進行使用。我們需要聲明一個transformAccumulator類的實例。該實例應當與場景圖形中的某個節點相關聯。然后,我們需要聲明nodeFollowerMatrixManipulator類的實例。此操縱器類的構造函數將獲取transformAccumulator實例的指針。最后,將新的矩陣操縱器添加到視口操控器列表中。上述步驟的實現如下:
// 設置場景和視口(包括tankTransform節點的添加)……
transformAccumulator* tankWorldCoords = new transformAccumulator();
tankWorldCoords->attachToGroup(tankTransform);
followNodeMatrixManipulator* followTank =
new followNodeMatrixManipulator(tankWorldCoords);
osgGA::KeySwitchMatrixManipulator *ksmm = new osgGA::KeySwitchMatrixManipulator();
if (!ksmm)