OSG使用更新回调来更改模型

转自:http://blog.sina.com.cn/s/blog_668aae7801017gl7.html

使用回调类实现对场景图形节点的更新。本节将讲解如何使用回调来实现在每帧的更新遍历(update traversal)中进行节点的更新。


回调概览
用户可以使用回调来实现与场景图形的交互。回调可以被理解成是一种用户自定义的函数,根据遍历方式的不同(更新update,拣选cull,绘制draw),回调函数将自动地执行。回调可以与个别的节点或者选定类型(及子类型)的节点相关联。在场景图形的各次遍历中,如果遇到的某个节点已经与用户定义的回调类和函数相关联,则这个节点的回调将被执行。

创建一个更新回调

更新回调将在场景图形每一次运行更新遍历时被执行。与更新回调相关的代码可以在每一帧被执行,且实现过程是在拣选回调之前,因此回调相关的代码可以插入到主仿真循环的viewer.update()和viewer.frame()函数之间。而OSG的回调也提供了维护更为方便的接口来实现上述的功能。善于使用回调的程序代码也可以在多线程的工作中更加高效地运行。

  从前一个教程展开来说,如果我们需要自动更新与坦克模型的炮塔航向角和机枪倾角相关联的DOF(自由度)节点,我们可以采取多种方式来完成这一任务。譬如,针对我们将要操作的各个节点编写相应的回调函数:包括一个与机枪节点相关联的回调,一个与炮塔节点相关联的回调,等等。这种方法的缺陷是,与不同模型相关联的函数无法被集中化,因此增加了代码阅读、维护和更新的复杂性。另一种(极端的)方法是,只编写一个更新回调函数,来完成整个场景的节点操作。本质上来说,这种方法和上一种具有同样的问题,因为所有的代码都会集中到仿真循环当中。当仿真的复杂程度不断增加时,这个唯一的更新回调函数也会变得愈发难以阅读、维护和修改。关于编写场景中节点/子树回调函数的方法,并没有一定之规。在本例中我们将创建单一的坦克节点回调,这个回调函数将负责更新炮塔和机枪的自由度节点。

  为了实现这一回调,我们需要在节点类原有的基础上添加新的数据。我们需要获得与炮塔和机枪相关联的DOF节点的句柄,以更新炮塔旋转和机枪俯仰的角度值。角度值的变化要建立在上一次变化的基础上。因为回调是作为场景遍历的一部分进行初始化的,我们所需的参数通常只有两个:一个是与回调相关联的节点指针,一个是用于执行遍历的节点访问器指针。为了获得更多的参数数据(炮塔和机枪DOF的句柄,旋转和俯仰角度值),我们可以使用节点类的userData数据成员。userData是一个指向用户定义类的指针,其中包含了关联某个特定节点时所需的一切数据集。而对于用户自定义类,只有一个条件是必需的,即,它必须继承自osg::Referenced类。Referenced类提供了智能指针的功能,用于协助用户管理内存分配。智能指针记录了分配给一个类的实例的引用计数值。这个类的实例只有在引用计数值到达0的时候才会被删除。有关osg::Referenced的更详细叙述,请参阅本章后面的部分。基于上述的需求,我们向坦克节点添加如下的代码:

 
  1. class tankDataType : public osg::Referenced
  2. {
  3. public:
  4. //公有成员
  5. protected:
  6. osgSim::DOFTransform* tankTurretNode;
  7. osgSim::DOFTransform* tankGunNode;
  8. double rotation; //(弧度值)
  9. double elevation; //(弧度值)
  10. };

为了正确实现tankData类,我们需要获取DOF节点的句柄。这一工作可以在类的构造函数中使用前一教程所述的findNodeVisitor类完成。findNodeVisitor将从一个起始节点开始遍历。本例中我们将从表示坦克的子树的根节点开始执行遍历,因此我们需要向tankDataType的构造函数传递坦克节点的指针。因此,tankDataType类的构造函数代码应当编写为:(向特定节点分配用户数据的步骤将随后给出)

 
  1. tankDataType::tankDataType(osg::Node* n)
  2. {
  3. rotation = 0;
  4. elevation = 0;
  5. findNodeVisitor findTurret("turret");
  6. n->accept(findTurret);
  7. tankTurretNode = dynamic_cast <osgSim::DOFTransform*> (findTurret.getFirst());
  8. findNodeVisitor findGun("gun");
  9. n->accept(findGun);
  10. tankGunNode = dynamic_cast< osgSim::DOFTransform*> (findGun.getFirst());
  11. }

我们也可以在tankDataType类中定义更新炮塔旋转和机枪俯仰的方法。现在我们只需要简单地让炮塔和机枪角度每帧改变一个固定值即可。对于机枪的俯仰角,我们需要判断它是否超过了实际情况的限制值。如果达到限制值,则重置仰角为0。炮塔的旋转可以在一个圆周内自由进行。

 
  1. void tankDataType::updateTurretRotation() //控制坦克的炮塔将旋转不同的角度
  2. {
  3. rotation += 0.01;
  4. tankTurretNode->setCurrentHPR( osg::Vec3(rotation,0,0) );
  5. }
  6. void tankDataType::updateGunElevation() //控制坦克的枪管在y方向上的仰角,控制枪管的升降
  7. {
  8. elevation += 0.01;
  9. tankGunNode->setCurrentHPR( osg::Vec3(0,elevation,0) );
  10. if (elevation > 0.5)
  11. elevation = 0.0;
  12. }

将上述代码添加到类的内容后,我们新定义的类如下所示:

 
  1. class tankDataType : public osg::Referenced
  2. {
  3. public:
  4. tankDataType(osg::Node*n);
  5. void updateTurretRotation();
  6. void updateGunElevation();
  7. protected:
  8. osgSim::DOFTransform* tankTurretNode;
  9. osgSim::DOFTransform* tankGunNode;
  10. double rotation; //(弧度值)
  11. double elevation; //(弧度值)
  12. };

下一个步骤是创建回调,并将其关联到坦克节点上。为了创建这个回调,我们需要重载“()”操作符,它包括两个参数:节点的指针和节点访问器的指针。在这个函数中我们将执行DOF节点的更新。因此,我们需要执行tankData实例的更新方法,其中tankData实例使用坦克节点的userData成员与坦克节点相关联。坦克节点的指针可以通过使用getUserData方法来获取。由于这个方法的返回值是一个osg::Referenced基类的指针,因此需要将其安全地转换为tankDataType类的指针。为了保证用户数据的引用计数值是正确的,我们使用模板类型osg::ref_ptr<tankDataType>指向用户数据。整个类的定义如下:

class tankNodeCallback : public osg::NodeCallback
  1. {
  2. public:
  3. virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
  4. {
  5. osg::ref_ptr<tankDataType> tankData = dynamic_cast<tankDataType*> (node->getUserData() );
  6. if(tankData)
  7. {
  8. tankData->updateTurretRotation();
  9. tankData->updateGunElevation();
  10. }
  11. traverse(node, nv);
  12. }
  13. };

下一步的工作是“安装”回调:将其关联给我们要修改的坦克节点,以实现每帧的更新函数执行。因此,我们首先要保证坦克节点的用户数据(tankDataType类的实例)是正确的。然后,我们使用osg::Node类的setUpdateCallback方法将回调与正确的节点相关联。代码如下所示:

// 初始化变量和模型,建立场景
  1. osg::ref_ptr<osg::Node> tankNode = osgDB::readNodeFile("t72-tank_des.flt");
  2. tankDataType* tankData = new tankDataType(tankNode);
  3. tankNode->setUserData( tankData );
  4. tankNode->setUpdateCallback(new tankNodeCallback);

创建了回调之后,我们进入仿真循环。仿真循环的代码不用加以改变。当我们调用视口类实例的frame()方法时,我们即进入一个更新遍历。当更新遍历及至坦克节点时,将触发tankNodeCallback类的操作符“()”函数。

完整的源程序代码如下:

 
  1. #include <osgViewer/Viewer>
  2. #include <osgDB/ReadFile>
  3. #include <osg/NodeVisitor>
  4. #include <osg/Node>
  5. #include <osg/Group>
  6. #include <osgSim/DOFTransform>
  7. #include <osgUtil/Optimizer>
  8. #include <osg/NodeVisitor>
  9. #include <iostream>
  10. #include <vector>
  11. //模型中使用DOF节点,以便清晰表达坦克的某个部分。例如炮塔节点可以旋转,机枪节点可以升高
  12. class findNodeVisitor : public osg::NodeVisitor
  13. {
  14. public:
  15. findNodeVisitor();
  16. findNodeVisitor(const std::string &searchName) ;
  17. virtual void apply(osg::Node &searchNode);
  18. virtual void apply(osg::Transform &searchNode);
  19. void setNameToFind(const std::string &searchName);
  20. osg::Node* getFirst();
  21. typedef std::vector<osg::Node*> nodeListType;
  22. nodeListType& getNodeList() { return foundNodeList; }
  23. private:
  24. std::string searchForName;
  25. nodeListType foundNodeList;
  26. };
  27. findNodeVisitor::findNodeVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN),searchForName()
  28. {
  29. }
  30. findNodeVisitor::findNodeVisitor(const std::string &searchName):osg::NodeVisitor(TRAVERSE_ALL_CHILDREN),searchForName(searchName)
  31. {
  32. }
  33. void findNodeVisitor::setNameToFind(const std::string &searchName)
  34. {
  35. searchForName = searchName;
  36. foundNodeList.clear();
  37. }
  38. osg::Node* findNodeVisitor::getFirst()
  39. {
  40. return *(foundNodeList.begin());
  41. }
  42. void findNodeVisitor::apply(osg::Node &searchNode)
  43. {
  44. if (searchNode.getName() == searchForName)
  45. {
  46. foundNodeList.push_back(&searchNode);
  47. }
  48. traverse(searchNode);
  49. }
  50. void findNodeVisitor::apply(osg::Transform &searchNode)
  51. {
  52. osgSim::DOFTransform* dofNode =
  53. dynamic_cast<osgSim::DOFTransform*> (&searchNode);
  54. if (dofNode)
  55. {
  56. dofNode->setAnimationOn(false);
  57. }
  58. apply ( (osg::Node&) searchNode);
  59. traverse(searchNode);
  60. }
  61. class tankDataType : public osg::Referenced
  62. {
  63. public:
  64. tankDataType(osg::Node*n);
  65. void updateTurretRotation();
  66. void updateGunElevation();
  67. protected:
  68. osgSim::DOFTransform* tankTurretNode;
  69. osgSim::DOFTransform* tankGunNode;
  70. double rotation; //(弧度值)
  71. double elevation; //(弧度值)
  72. };
  73. tankDataType::tankDataType(osg::Node* n)
  74. {
  75. rotation = 0;
  76. elevation = 0;
  77. findNodeVisitor findTurret("turret");
  78. n->accept(findTurret);
  79. tankTurretNode = dynamic_cast <osgSim::DOFTransform*> (findTurret.getFirst());
  80. findNodeVisitor findGun("gun");
  81. n->accept(findGun);
  82. tankGunNode = dynamic_cast< osgSim::DOFTransform*> (findGun.getFirst());
  83. }
  84. void tankDataType::updateTurretRotation() //控制坦克的炮塔将旋转不同的角度
  85. {
  86. rotation += 0.02;
  87. tankTurretNode->setCurrentHPR( osg::Vec3(rotation,0,0) );
  88. }
  89. void tankDataType::updateGunElevation() //控制坦克的枪管在y方向上的仰角,控制枪管的升降
  90. {
  91. //elevation += 0.02; //控制坦克的枪管在y方向上的升降
  92. tankGunNode->setCurrentHPR( osg::Vec3(0,elevation,0) );
  93. if (elevation > 0.5)
  94. elevation = 0.0;
  95. }
  96. class tankNodeCallback : public osg::NodeCallback
  97. {
  98. public:
  99. virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
  100. {
  101. osg::ref_ptr<tankDataType> tankData = dynamic_cast<tankDataType*> (node->getUserData() );
  102. if(tankData)
  103. {
  104. tankData->updateTurretRotation();
  105. tankData->updateGunElevation();
  106. }
  107. traverse(node, nv);
  108. }
  109. };
  110. int main(void)
  111. {
  112. // 初始化变量和模型,建立场景
  113. osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
  114. osg::ref_ptr<osg::Group> root = new osg::Group();
  115. osg::ref_ptr<osg::Node> tankNode = osgDB::readNodeFile("t72-tank_des.flt");
  116. tankDataType* tankData = new tankDataType(tankNode);
  117. tankNode->setUserData( tankData );
  118. tankNode->setUpdateCallback(new tankNodeCallback);
  119. root->addChild(tankNode.get());
  120. //优化场景数据
  121. osgUtil::Optimizer optimizer;
  122. optimizer.optimize(root.get());
  123. //设置场景数据
  124. viewer->setSceneData(root.get());
  125. //初始化并创建窗口
  126. viewer->realize();
  127. //开始渲染
  128. viewer->run();
  129. return 0;
  130. }

最终的效果图如下所示:

OSG使用更新回调来更改模型的更多相关文章

  1. [osg]OSG使用更新回调来更改模型

    使用回调类实现对场景图形节点的更新.本节将讲解如何使用回调来实现在每帧的更新遍历(update traversal)中进行节点的更新.        回调概览       用户可以使用回调来实现与场景 ...

  2. OSG程序设计之更新回调

    更新回调(Update Callback)涉及到一个类:osg::NodeCallback.这个类重载了函数调用操作符.当回调动作发生时,将会执行这一操作符的内容. 如果节点绑定了更新回调函数,那么在 ...

  3. [osg]osgcallback各种回调使用的例子介绍

    观察MyReadFileCallback结构体的内容,可以发现它继承自osgDB::Registry::ReadFileCallback,并重载了一个函数readNode,分析源代码可知,该函数在os ...

  4. MVC UpdateModel的未能更新XXXXX的类型模型

    关于MVC  UpdateModel的未能更新XXXXX的类型模型 的问题: 最近做MVC3的项目,相信很多人都碰到过这个问题,在此记录一下,异常:UpdateModel的未能更新XXXXX的类型模型 ...

  5. osg中使用MatrixTransform来实现模型的平移/旋转/缩放

    osg中使用MatrixTransform来实现模型的平移/旋转/缩放 转自:http://www.cnblogs.com/kekec/archive/2011/08/15/2139893.html# ...

  6. Qt 5.3更新无数,更改C++控制台输出最为赞(这样就和普通C++ IDE没区别了)

    转载请注明文章:Qt 5.3更新无数,更改C++控制台输出最为赞 出处:多客博图 本人觉得有了这个更新,Qt Creator可谓几乎没有缺点了,起码仅仅开发C/C++,是不用再去安装VS了. Qt 5 ...

  7. OSG中相机参数的更改

    #pragma comment(lib, "osg.lib") #pragma comment(lib, "osgDB.lib") #pragma commen ...

  8. 【学习笔记】OSG中相机参数的更改

    #pragma comment(lib, "osg.lib") #pragma comment(lib, "osgDB.lib") #pragma commen ...

  9. osg fbx 绘制坐标轴、控制模型影藏与显示

    int main() { osg::ref_ptr<osgViewer::Viewer> viewer1 = new osgViewer::Viewer; osg::ref_ptr< ...

随机推荐

  1. VS2015 Xamarin for iOS

    VS2015环境配置 VS2015安装不多说.其实Xamarin 和微软感觉并不是什么好基友,Xamarin以前一直像个可怜的娃,以插件的形式寄生于VS中.现在只不过形势稍微好点了,VS2015 在明 ...

  2. 5-04用Sql语句创建表

    用Sql语句创建表的基本语法: USE E_Market--指向当前所操作的数据库 GO CREATE TABLE CommoditySort--创建表的名字 { sortID int IDENTIT ...

  3. HTTP访问的两种方式(HttpClient+HttpURLConnection)整合汇总对比(转)

    在Android上http 操作类有两种,分别是HttpClient和HttpURLConnection,其中两个类的详细介绍可以问度娘. HttpClient: HttpClient是Apache ...

  4. 同一个项目,项目名称不一致,这两个项目同时在Eclipse中出现

    在Eclispse中,实际同一个项目,项目名称不一致,这两个项目同时在Eclipse中出现. ①打开项目文件夹,找到“.cproject”文件 ② 在<name>节点重命名 ③ 导入Ecl ...

  5. LeetCode——Same Tree(判断两棵树是否相同)

    问题: Given two binary trees, write a function to check if they are equal or not. Two binary trees are ...

  6. 通讯录(ios自带无界面)

    1,添加框架AddressBook.framework 2,请求权限认证,在Appdelegate.m文件中 - (BOOL)application:(UIApplication *)applicat ...

  7. 关于如何在MFC工程中输入不同的数据进行调试

    我们可以采用c++的文件输入输出来进行调试 这样就绕过了不能使用黑窗口输入数据就不能调试的思维定式 不是黑窗口的我们都可以考虑用文件流输入输出 或者用控件来输入? http://blog.csdn.n ...

  8. ASP.NET MVC使用过滤器进行权限控制

    1.新建MVC项目 2.找到Models文件夹,新建 LoginCheckFilterAttribute 类 public class LoginCheckFilterAttribute : Acti ...

  9. Hadoop RPC机制的使用

    一.RPC基础概念 1.1 RPC的基础概念 RPC,即Remote Procdure Call,中文名:远程过程调用: (1)它允许一台计算机程序远程调用另外一台计算机的子程序,而不用去关心底层的网 ...

  10. 使用Eclipse将Web项目打Jar包方法

    1.对下载.安装和运行Eclipse,就不再说了: 2.找到待打包项目: 3.右键,Export-->Export: 4.选择,Jar: 5.按如图操作: 6.完成后: