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. 二叉搜索树的实现及指针问题的一点思考(C++)

    今天实现二叉搜索树的时候因为指针的问题卡了一上午(实在不应该...),一直segmentation fault,个人感觉还是需要记录一下的. 首先贴一下做的题的意思: 输入一系列整数,建立二叉排序数, ...

  2. hdu 4412 2012杭州赛区网络赛 期望

    虽然dp方程很好写,就是这个期望不知道怎么求,昨晚的BC也是 题目问题抽象之后为:在一个x坐标轴上有N个点,每个点上有一个概率值,可以修M个工作站, 求怎样安排这M个工作站的位置,使得这N个点都走到工 ...

  3. AOP动态代理解析4-jdk代理的实现

    JDKProxy的使用关键是创建自定义的InvocationHandler,而InvocationHandler中包含了需要覆盖的函数getProxy,而当前的方法正是完成了这个操作.在此确认一下JD ...

  4. POJ 2549 二分+HASH

    题目链接:http://poj.org/problem?id=2002 题意:给定一个n个数字组成的序列,然后求出4个数使得a+b+c=d,求d的最大值.其中a,b,c,d要求是给定序列的数,并且不能 ...

  5. loopback 03

    使用微信开发前准备 微信公众开发者平台 注册开发者账号获取权限: 安装包: wechat, wechat-oauth 微信公众平台操作 登录之后,得到appID和appsecret 根据appID和a ...

  6. hdu 1520 Anniversary party 基础树dp

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission( ...

  7. MySQL 挺有意思

    1, 修改密码 mysql -u root -p update user set Password = PASSWORD('NEWPWD') WHERE user = 'root'; FLUSH PR ...

  8. MFC 动态修改对话框标题

    在对应对话框的初始化函数OnInitDialog()中添加以下代码: CString title; title.Format("%d",Id);//在标题栏动态显示Id的值 thi ...

  9. 持续集成基础-Jenkins(一)

    什么是jenkins: Jenkins是持续集成的一个系统,它是一种软件开发实践活动(经常执行集成,可能每天) 持续集成的价值: 1.减少风险 - 能够尽早的发生问题 2.减少重复过程 - 把重复的东 ...

  10. git 学习笔记6--remote & log

    git 学习笔记6--remote & log 创建SSH Keys ssh-keygen -t rsa -C "1050244110@qq.com" 本地关联远程 git ...