OSG使用更新回调来更改模型
?????? 使用回調類實現對場景圖形節(jié)點的更新。本節(jié)將講解如何使用回調來實現在每幀的更新遍歷(update traversal)中進行節(jié)點的更新。
?????? 回調概覽
?????? 用戶可以使用回調來實現與場景圖形的交互。回調可以被理解成是一種用戶自定義的函數,根據遍歷方式的不同(更新update,揀選cull,繪制draw),回調函數將自動地執(zhí)行。回調可以與個別的節(jié)點或者選定類型(及子類型)的節(jié)點相關聯。在場景圖形的各次遍歷中,如果遇到的某個節(jié)點已經與用戶定義的回調類和函數相關聯,則這個節(jié)點的回調將被執(zhí)行。
?????????創(chuàng)建一個更新回調
?????? 更新回調將在場景圖形每一次運行更新遍歷時被執(zhí)行。與更新回調相關的代碼可以在每一幀被執(zhí)行,且實現過程是在揀選回調之前,因此回調相關的代碼可以插入到主仿真循環(huán)的viewer.update()和viewer.frame()函數之間。而OSG的回調也提供了維護更為方便的接口來實現上述的功能。善于使用回調的程序代碼也可以在多線程的工作中更加高效地運行。
從前一個教程展開來說,如果我們需要自動更新與坦克模型的炮塔航向角和機槍傾角相關聯的DOF(自由度)節(jié)點,我們可以采取多種方式來完成這一任務。譬如,針對我們將要操作的各個節(jié)點編寫相應的回調函數:包括一個與機槍節(jié)點相關聯的回調,一個與炮塔節(jié)點相關聯的回調,等等。這種方法的缺陷是,與不同模型相關聯的函數無法被集中化,因此增加了代碼閱讀、維護和更新的復雜性。另一種(極端的)方法是,只編寫一個更新回調函數,來完成整個場景的節(jié)點操作。本質上來說,這種方法和上一種具有同樣的問題,因為所有的代碼都會集中到仿真循環(huán)當中。當仿真的復雜程度不斷增加時,這個唯一的更新回調函數也會變得愈發(fā)難以閱讀、維護和修改。關于編寫場景中節(jié)點/子樹回調函數的方法,并沒有一定之規(guī)。在本例中我們將創(chuàng)建單一的坦克節(jié)點回調,這個回調函數將負責更新炮塔和機槍的自由度節(jié)點。
為了實現這一回調,我們需要在節(jié)點類原有的基礎上添加新的數據。我們需要獲得與炮塔和機槍相關聯的DOF節(jié)點的句柄,以更新炮塔旋轉和機槍俯仰的角度值。角度值的變化要建立在上一次變化的基礎上。因為回調是作為場景遍歷的一部分進行初始化的,我們所需的參數通常只有兩個:一個是與回調相關聯的節(jié)點指針,一個是用于執(zhí)行遍歷的節(jié)點訪問器指針。為了獲得更多的參數數據(炮塔和機槍DOF的句柄,旋轉和俯仰角度值),我們可以使用節(jié)點類的userData數據成員。userData是一個指向用戶定義類的指針,其中包含了關聯某個特定節(jié)點時所需的一切數據集。而對于用戶自定義類,只有一個條件是必需的,即,它必須繼承自osg::Referenced類。Referenced類提供了智能指針的功能,用于協(xié)助用戶管理內存分配。智能指針記錄了分配給一個類的實例的引用計數值。這個類的實例只有在引用計數值到達0的時候才會被刪除。有關osg::Referenced的更詳細敘述,請參閱本章后面的部分。基于上述的需求,我們向坦克節(jié)點添加如下的代碼:
class tankDataType : public osg::Referenced { public://公有成員 protected:osgSim::DOFTransform* tankTurretNode;osgSim::DOFTransform* tankGunNode;double rotation; //(弧度值)double elevation; //(弧度值) };??????? 為了正確實現tankData類,我們需要獲取DOF節(jié)點的句柄。這一工作可以在類的構造函數中使用前一教程所述的findNodeVisitor類完成。findNodeVisitor將從一個起始節(jié)點開始遍歷。本例中我們將從表示坦克的子樹的根節(jié)點開始執(zhí)行遍歷,因此我們需要向tankDataType的構造函數傳遞坦克節(jié)點的指針。因此,tankDataType類的構造函數代碼應當編寫為:(向特定節(jié)點分配用戶數據的步驟將隨后給出)
tankDataType::tankDataType(osg::Node* n) {rotation = 0;elevation = 0;findNodeVisitor findTurret("turret");n->accept(findTurret);tankTurretNode = dynamic_cast <osgSim::DOFTransform*> (findTurret.getFirst());findNodeVisitor findGun("gun");n->accept(findGun);tankGunNode = dynamic_cast< osgSim::DOFTransform*> (findGun.getFirst()); }??????? 我們也可以在tankDataType類中定義更新炮塔旋轉和機槍俯仰的方法。現在我們只需要簡單地讓炮塔和機槍角度每幀改變一個固定值即可。對于機槍的俯仰角,我們需要判斷它是否超過了實際情況的限制值。如果達到限制值,則重置仰角為0。炮塔的旋轉可以在一個圓周內自由進行。
void tankDataType::updateTurretRotation() //控制坦克的炮塔將旋轉不同的角度 {rotation += 0.01;tankTurretNode->setCurrentHPR( osg::Vec3(rotation,0,0) ); }void tankDataType::updateGunElevation() //控制坦克的槍管在y方向上的仰角,控制槍管的升降 {elevation += 0.01;tankGunNode->setCurrentHPR( osg::Vec3(0,elevation,0) );if (elevation > 0.5)elevation = 0.0; }????? 將上述代碼添加到類的內容后,我們新定義的類如下所示:
class tankDataType : public osg::Referenced { public:tankDataType(osg::Node*n); void updateTurretRotation();void updateGunElevation(); protected:osgSim::DOFTransform* tankTurretNode;osgSim::DOFTransform* tankGunNode;double rotation; //(弧度值)double elevation; //(弧度值) };?????? 下一個步驟是創(chuàng)建回調,并將其關聯到坦克節(jié)點上。為了創(chuàng)建這個回調,我們需要重載“()”操作符,它包括兩個參數:節(jié)點的指針和節(jié)點訪問器的指針。在這個函數中我們將執(zhí)行DOF節(jié)點的更新。因此,我們需要執(zhí)行tankData實例的更新方法,其中tankData實例使用坦克節(jié)點的userData成員與坦克節(jié)點相關聯。坦克節(jié)點的指針可以通過使用getUserData方法來獲取。由于這個方法的返回值是一個osg::Referenced基類的指針,因此需要將其安全地轉換為tankDataType類的指針。為了保證用戶數據的引用計數值是正確的,我們使用模板類型osg::ref_ptr<tankDataType>指向用戶數據。整個類的定義如下:
class tankNodeCallback : public osg::NodeCallback { public:virtual void operator()(osg::Node* node, osg::NodeVisitor* nv){osg::ref_ptr<tankDataType> tankData = dynamic_cast<tankDataType*> (node->getUserData() );if(tankData){tankData->updateTurretRotation();tankData->updateGunElevation();}traverse(node, nv); } };?????? 下一步的工作是“安裝”回調:將其關聯給我們要修改的坦克節(jié)點,以實現每幀的更新函數執(zhí)行。因此,我們首先要保證坦克節(jié)點的用戶數據(tankDataType類的實例)是正確的。然后,我們使用osg::Node類的setUpdateCallback方法將回調與正確的節(jié)點相關聯。代碼如下所示:
// 初始化變量和模型,建立場景osg::ref_ptr<osg::Node> tankNode = osgDB::readNodeFile("t72-tank_des.flt");tankDataType* tankData = new tankDataType(tankNode);tankNode->setUserData( tankData );tankNode->setUpdateCallback(new tankNodeCallback);??????? 創(chuàng)建了回調之后,我們進入仿真循環(huán)。仿真循環(huán)的代碼不用加以改變。當我們調用視口類實例的frame()方法時,我們即進入一個更新遍歷。當更新遍歷及至坦克節(jié)點時,將觸發(fā)tankNodeCallback類的操作符“()”函數。
?????? 完整的源程序代碼如下:
#include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <osg/NodeVisitor> #include <osg/Node> #include <osg/Group> #include <osgSim/DOFTransform> #include <osgUtil/Optimizer> #include <osg/NodeVisitor> #include <iostream> #include <vector> //模型中使用DOF節(jié)點,以便清晰表達坦克的某個部分。例如炮塔節(jié)點可以旋轉,機槍節(jié)點可以升高 class findNodeVisitor : public osg::NodeVisitor { public: findNodeVisitor(); findNodeVisitor(const std::string &searchName) ;virtual void apply(osg::Node &searchNode);virtual void apply(osg::Transform &searchNode);void setNameToFind(const std::string &searchName);osg::Node* getFirst();typedef std::vector<osg::Node*> nodeListType; nodeListType& getNodeList() { return foundNodeList; } private: std::string searchForName; nodeListType foundNodeList; }; findNodeVisitor::findNodeVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN),searchForName() { } findNodeVisitor::findNodeVisitor(const std::string &searchName):osg::NodeVisitor(TRAVERSE_ALL_CHILDREN),searchForName(searchName) { } void findNodeVisitor::setNameToFind(const std::string &searchName) { searchForName = searchName; foundNodeList.clear(); } osg::Node* findNodeVisitor::getFirst() {return *(foundNodeList.begin()); } void findNodeVisitor::apply(osg::Node &searchNode) { if (searchNode.getName() == searchForName) { foundNodeList.push_back(&searchNode); } traverse(searchNode); } void findNodeVisitor::apply(osg::Transform &searchNode) { osgSim::DOFTransform* dofNode = dynamic_cast<osgSim::DOFTransform*> (&searchNode); if (dofNode) { dofNode->setAnimationOn(false); } apply ( (osg::Node&) searchNode); traverse(searchNode); } class tankDataType : public osg::Referenced { public:tankDataType(osg::Node*n); void updateTurretRotation();void updateGunElevation(); protected:osgSim::DOFTransform* tankTurretNode;osgSim::DOFTransform* tankGunNode;double rotation; //(弧度值)double elevation; //(弧度值) };tankDataType::tankDataType(osg::Node* n) {rotation = 0;elevation = 0;findNodeVisitor findTurret("turret"); n->accept(findTurret);tankTurretNode = dynamic_cast <osgSim::DOFTransform*> (findTurret.getFirst());findNodeVisitor findGun("gun"); n->accept(findGun);tankGunNode = dynamic_cast< osgSim::DOFTransform*> (findGun.getFirst()); }void tankDataType::updateTurretRotation() //控制坦克的炮塔將旋轉不同的角度 { rotation += 0.02;tankTurretNode->setCurrentHPR( osg::Vec3(rotation,0,0) ); }void tankDataType::updateGunElevation() //控制坦克的槍管在y方向上的仰角,控制槍管的升降 {//elevation += 0.02; //控制坦克的槍管在y方向上的升降tankGunNode->setCurrentHPR( osg::Vec3(0,elevation,0) );if (elevation > 0.5)elevation = 0.0; }class tankNodeCallback : public osg::NodeCallback { public:virtual void operator()(osg::Node* node, osg::NodeVisitor* nv){osg::ref_ptr<tankDataType> tankData = dynamic_cast<tankDataType*> (node->getUserData() );if(tankData){tankData->updateTurretRotation();tankData->updateGunElevation();}traverse(node, nv); } };int main(void) {// 初始化變量和模型,建立場景osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();osg::ref_ptr<osg::Node> tankNode = osgDB::readNodeFile("t72-tank_des.flt");tankDataType* tankData = new tankDataType(tankNode);tankNode->setUserData( tankData );tankNode->setUpdateCallback(new tankNodeCallback);root->addChild(tankNode.get());//優(yōu)化場景數據osgUtil::Optimizer optimizer;optimizer.optimize(root.get());//設置場景數據viewer->setSceneData(root.get());//初始化并創(chuàng)建窗口viewer->realize();//開始渲染viewer->run();return 0; }??????? 最終的效果圖如下所示:
?
總結
以上是生活随笔為你收集整理的OSG使用更新回调来更改模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenGL 光照方程的计算
- 下一篇: 七种方式求斐波那契(Fibonacci)