使用回调类实现对场景图形节点的更新。本节将讲解如何使用回调来实现在每帧的更新遍历(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>指向用户数据。整个类的定义如下:

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

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

  1. // 初始化变量和模型,建立场景
  2. osg::ref_ptr<osg::Node> tankNode = osgDB::readNodeFile("t72-tank_des.flt");
  3. tankDataType* tankData = new tankDataType(tankNode);
  4. tankNode->setUserData( tankData );
  5. 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]OSG使用更新回调来更改模型的更多相关文章

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

    OSG使用更新回调来更改模型 转自:http://blog.sina.com.cn/s/blog_668aae7801017gl7.html 使用回调类实现对场景图形节点的更新.本节将讲解如何使用回调 ...

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

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

  3. [OSG]OSG例子程序简介

    1.example_osganimate一)演示了路径动画的使用(AnimationPath.AnimationPathCallback),路径动画回调可以作用在Camera.CameraView.M ...

  4. [原][OSG]OSG例子程序简介

    1.example_osganimate一)演示了路径动画的使用(AnimationPath.AnimationPathCallback),路径动画回调可以作用在Camera.CameraView.M ...

  5. [osg]osg绘制动态改变顶点的几何体

    最简单的顶点数据更新方法是预先获取setVertexArray()所用的数组数据,并对其进行更新.但是对于开启显示列表支持的几何体(这是默认的情况)来说,有一个问题需要特别需要引起注意,即显示列表中的 ...

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

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

  7. [osg]osg窗口显示和单屏幕显示

    osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFile("cow.osg"); osg::ref_ptr&l ...

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

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

  9. jt格式文件读取,osg显示插件更新

    osgdb_jt 最近还是更新了一下 osgdb_jt 插件.解码jt格式核心库jt_toolkit,通过静态链接到Plugin jt产生osgdb_jt插件,使得osg可以可视化jt格式文件. 用法 ...

随机推荐

  1. \r\n回车换行\r回车\n换行的区别

    在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33,Linux/Unix下的tty概念也来自于此)的玩意,每秒钟可以打10个字符.但是它有一个问题,就是打完一行换行的时候 ...

  2. 关于Android Camera2 API 的自动对焦的坑

    https://www.jianshu.com/p/280e5301b7b9 一.使用.关于Camera2的API使用,参考Google官方的例子: Camera2Basic Camera2Raw C ...

  3. springmvc的ajax返回406问题

    在springmvc中ajax请求写为XXX.html,如果在controller的如:@RequestMapping(value="/login/doLogin.html",pr ...

  4. P2473 [SCOI2008]奖励关(期望)

    P2473 [SCOI2008]奖励关 $n<=15$,显然的状压 设$f[i][w]$表示前$i$轮,状态$w$的最大期望 蓝后我们发现一个问题:$f[i][w]$可能是非法的 于是我们从$f ...

  5. devexpress vs2015 安装亲测

  6. Android之电话拨号和短信

    拨打电话号码,需要添加权限设置,在安装的应用程序信息中可以看到此权限信息Intent doSth=new Intent();//意图:你想做什么呢?doSth.setAction(Intent.ACT ...

  7. Java类型信息

    一.引言 最近在阅读<Java编程思想>,学习一下java类型信息,现在做一下总结.Java如何让我们在运行时识别对象和类的信息的.主要有两种方式:一种是传统的“RTTI”,它假定我们在编 ...

  8. Codeforces Round #425 (Div. 2) Problem C Strange Radiation (Codeforces 832C) - 二分答案 - 数论

    n people are standing on a coordinate axis in points with positive integer coordinates strictly less ...

  9. Django 中如何让外部访问本地的静态资源

    简单使用 在Django中打开一个入口,让别人可以访问media文件 在settings中配置 MEDIA_ROOT=os.path.join(BASE_DIR,'media') 在路由中配置 fro ...

  10. sha0dow0socks

    秋水的一键安装脚本 #!/usr/bin/env bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bi ...