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

时间:2021-04-07 11:32:44

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

[osg]OSG使用更新回调来更改模型
       回调概览
       用户可以使用回调来实现与场景图形的交互。回调可以被理解成是一种用户自定义的函数,根据遍历方式的不同(更新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使用更新回调来更改模型[]

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

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

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

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

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

  3. &lbrack;OSG&rsqb;OSG例子程序简介

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

  4. &lbrack;原&rsqb;&lbrack;OSG&rsqb;OSG例子程序简介

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

  5. &lbrack;osg&rsqb;osg绘制动态改变顶点的几何体

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

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

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

  7. &lbrack;osg&rsqb;osg窗口显示和单屏幕显示

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

  8. Qt 5&period;3更新无数,更改C&plus;&plus;控制台输出最为赞(这样就和普通C&plus;&plus; 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. Oracle常用SQL查询

    一.ORACLE的启动和关闭 1.在单机环境下要想启动或关闭oracle系统必须首先切换到oracle用户,如下: su - oracle a.启动Oracle系统 oracle>svrmgrl ...

  2. oracle ebs request一直pending

    如果提交请求以后,状态一直是pending状态,可以在“工具”打开“Manager”,查看一下Maximum是否有设置错,另外pending的数量当前是多少. 如果Maximum是1,pending是 ...

  3. int和char的相同和不同。

    int和char在存储量上有不同而且在编程的时候,这样才是正确的,如果这样的话,这是一个区别. 第二:这个和上面的道理应该是差不多的.输出97   98. 总的来说,int和char都是一个定义量器的 ...

  4. Commix命令注入漏洞利用

    介绍 项目地址:https://github.com/stasinopoulos/commix Commix是一个使用Python开发的漏洞测试工具,这个工具是为了方便的检测一个请求是否存在命令注入漏 ...

  5. index&lowbar;merge引发的死锁排查

    概述 前几天排查了一个死锁问题,最开始百思不得其解,因为发生死锁的两个事务是单语句事务,语句类型相同(where属性列相同,仅值不同),而且语句都走了相同的索引,但最终确实发生了死锁.通过定位排查发现 ...

  6. 2&period;移植3&period;4内核-使内核支持烧写yaffs2

    在上章-制作文件系统,并使内核成功启动jffs2文件系统了 本章便开始使内核支持烧写yaffs2文件系统 1.首先获取yaffs2源码(参考git命令使用详解) cd /work/nfs_root g ...

  7. 比较ASP&period;NET和ASP&period;NET Core&lbrack;经典 Asp&period;Net v和 Asp&period;Net Core &lpar;Asp&period;Net Core MVC&rpar;&rsqb;

    ASP.NET Core是.与.Net Core FrameWork一起发布的ASP.NET 新版本,最初被称为ASP.NET vNext,有一系列的命名变化,ASP.NET 5.0,ASP.NET ...

  8. &lbrack;ExcelHome&rsqb;VLOOKUP的别样用法

    请看题: 如上图所示,是某小区多名业主的信息表.如诸君所见,A列是业主的姓名,B列是一些有趣的信息,要求在C列,使用VLOOKUP函数,提取出B列的手机号码. B列的信息真是奇葩,除了手机号码,还有职 ...

  9. 20165321 学习基础与C语言学习心得

    一.技能学习 我其实在小时候学过挺多东西,在我小学的时候,我曾经短时间地学过小提琴.拉丁舞.国画.书法,但是,由于各种原因,都没有继续学习下去.后来,在我小学四年级的时候,我接触到了二胡,于是,我开始 ...

  10. 如何使用 MSBuild&period;exe 生成解决方案中的特定目标

    以前都是直接使用VS或者msbuild生成整个解决方案,或者只构建单个工程. 这回使用msbuild构建单个工程的时候出现了问题,因为工程中使用了SolutionDir这个宏来定位第三方库路径. 对于 ...