当前位置:首页 > osg资料收集 - 图文
----------------------------------------------------------------------
回调概览
用户可以使用回调来实现与场景图形的交互。回调可以被理解成是一种用户自定义的函数,根据遍历方式的不同(更新update,拣选cull,绘制draw),回调函数将自动地执行。回调可以与个别的节点或者选定类型(及子类型)的节点相关联。在场景图形的各次遍历中,如果遇到的某个节点已经与用户定义的回调类和函数相关联,则这个节点的回调将被执行。如果希望了解有关遍历和回调的更多信息,请参阅David Eberly所著的《3D Game Engine Design》第四章,以及SGI的《Performer Programmer's Guide》第四章。相关的示例请参见osgCallback例子。
创建一个更新回调
更新回调将在场景图形每一次运行更新遍历时被执行。与更新回调相关的代码可以在每一帧被执行,且实现过程是在拣选回调之前,因此回调相关的代码可以插入到主仿真循环的viewer.update()和viewer.frame()函数之间。而OSG的回调也提供了维护更为方便的接口来实现上述的功能。善于使用回调的程序代码也可以在多线程的工作中更加高效地运行。 从前一个教程展开来说,如果我们需要自动更新与坦克模型的炮塔航向角和机枪倾角相关联的DOF(自由度)节点,我们可以采取多种方式来完成这一任务。譬如,针对我们将要操作的各个节点编写相应的回调函数:包括一个与机枪节点相关联的回调,一个与炮塔节点相关联的回调,等等。这种方法的缺陷是,与不同模型相关联的函数无法被集中化,因此增加了代码阅读、维护和更新的复杂性。另一种(极端的)方法是,只编写一个更新回调函数,来完成整个场景的节点操作。本质上来说,这种方法和上一种具有同样的问题,因为所有的代码都会集中到仿真循环当中。当仿真的复杂程度不断增加时,这个唯一的更新回调函数也会变得愈发难以阅读、维护和修改。关于编写场景中节点/子树回调函数的方法,并没有一定之规。在本例中我们将创建单一的坦克节点回调,这个回调函数将负责更新炮塔和机枪的自由度节点。
为了实现这一回调,我们需要在节点类原有的基础上添加新的数据。我们需要获得与炮塔和机枪相关联的DOF节点的句柄,以更新炮塔旋转和机枪俯仰的角度值。角度值的变化要建立在上一次变化的基础上。因为回调是作为场景遍历的一部分进行初始化的,我们所需的参数通常只有两个:一个是与回调相关联的节点指针,一个是用于执行遍历的节点访问器指针。为了获得更多的参数数据(炮塔和机枪DOF的句柄,旋转和俯仰角度值),我们可以使用节点类的userData数据成员。userData是一个指向用户定义类的指针,其中包含了关联某个特定节点时所需的一切数据集。而对于用户自定义类,只有一个条件是必需的,即,它必须继承自osg::Referenced类。Referenced类提供了智能指针的功能,用于协助用户管理内存分配。
智能指针记录了分配给一个类的实例的引用计数值。这个类的实例只有在引用计数值到达0的时候才会被删除。有关osg::Referenced的更详细叙述,请参阅本章后面的部分。基于上述的需求,我们向坦克节点添加如下的代码: class tankDataType : public osg::Referenced {
public:
// 公有成员…… protected:
osgSim::DOFTransform* tankTurretNode; osgSim::DOFTransform* tankGunNode; double rotation; double elevation; };
为了正确实现tankData类,我们需要获取DOF节点的句柄。这一工作可以在类的构造函数中使用前一教程所述的findNodeVisitor类完成。findNodeVisitor将从一个起始节点开始遍历。本例中我们将从表示坦克的子树的根节点开始执行遍历,因此我们需要向tankDataType的构造函数传递坦克节点的指针。因此,tankDataType类的构造函数代码应当编写为:(向特定节点分配用户数据的步骤将随后给出) tankDataType::tankDataType(osg::Node* n) {
rotation = 0; elevation = 0;
findNodeVisitor findTurret(\ n->accept(findTurret); tankTurretNode =
dynamic_cast
dynamic_cast< osgSim::DOFTransform*> (findGun.getFirst()); }
我们也可以在tankDataType类中定义更新炮塔旋转和机枪俯仰的方法。现在我们只需要简单地让炮塔和机枪角度每帧改变一个固定值即可。对于机枪的俯仰角,我们需要判断它是否超过了实际情况的限制值。如果达到限制值,则重置仰角为0。炮塔的旋转可以在一个圆周内自由进行。
void tankDataType::updateTurretRotation() {
rotation += 0.01;
tankTurretNode->setCurrentHPR( osg::Vec3(rotation,0,0) ); }
void tankDataType::updateGunElevation() {
elevation += 0.01;
tankGunNode->setCurrentHPR( osg::Vec3(0,elevation,0) );
if (elevation > .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; //(弧度值) };
下一个步骤是创建回调,并将其关联到坦克节点上。为了创建这个回调,我们需要重载“()”操作符,它包括两个参数:节点的指针和节点访问器的指针。在这个函数中我们将执行DOF节点的更新。因此,我们需要执行tankData实例的更新方法,其中tankData实例使用坦克节点的userData成员与坦克节点相关联。坦克节点的指针可以通过使用getUserData方法来获取。由于这个方法的返回值是一个osg::Referenced基类的指针,因此需要将其安全地转换为tankDataType类的指针。为了保证用户数据的引用计数值是正确的,我们使用模板类型osg::ref_ptr
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {
osg::ref_ptr
dynamic_cast
tankData->updateTurretRotation(); tankData->updateGunElevation(); }
traverse(node, nv); } };
下一步的工作是“安装”回调:将其关联给我们要修改的坦克节点,以实现每帧的更新函数执行。因此,我们首先要保证坦克节点的用户数据(tankDataType类的实例)是正确的。然后,我们使用osg::Node类的setUpdateCallback方法将回调与正确的节点相关联。代码如下所示: // 初始化变量和模型,建立场景……
tankDataType* tankData = new tankDataType(tankNode); tankNode->setUserData( tankData );
tankNode->setUpdateCallback(new tankNodeCallback);
创建了回调之后,我们进入仿真循环。仿真循环的代码不用加以改变。当我们调用视口类实例的frame()方法时,我们即进入一个更新遍历。当更新遍历及至坦克节点时,将触发tankNodeCallback类的操作符“()”函数。 // 视口的初始化,等等…… while( !viewer.done() ) {
viewer.frame(); }
return 0; }
祝各位好运!
18:18 | 添加评论 | 固定链接 | 写入日志 | NPS教程 11月7日
Navy07 - 搜索并控制开关和DOF节点(2) 按照“访问器”模式搜索有名节点
“访问器”的设计允许用户将某个特定节点的指定函数,应用到当前场景遍历的所有此类节点中。遍历的类型包括NODE_VISITOR,UPDATE_VISITOR,COLLECT_OCCLUDER_VISITOR和CULL_VISITOR。由于我们还没有讨论场景更新(updating),封闭节点(occluder node)和拣选(culling)的有关内容,因此这里首先介绍NODE_VISITOR(节点访问器)遍历类型。“访问器”同样允许用户指定遍历的模式,可选项包括TRAVERSE_NONE,TRAVERSE_PARENTS,TRAVERSE_ALL_CHILDREN和TRAVERSE_ACTIVE_CHILDREN。这里我们将选择TRAVERSE_ALL_CHILDREN(遍历所有子节点)的模式。 然后,我们需要定义应用到每个节点的函数。这里我们将会针对用户自定义的节点名称进行字符串比较。如果某个节点的名称与指定字符串相符,该节点将被添加到一个节点列表中。遍历过程结束后,列表中将包含所有符合指定的搜索字符串的节点。
为了能够充分利用“访问器”,我们可以从基类osg::NodeVisitor派生一个特定的节点访问器(命名为findNodeVisitor)。这个类需要两个新的数据成员:一个std::string变量,用于和我们搜索的有名节点进行字符串比较;以及一个节点列表变量(std::vector
http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/findNodeVisitor.zip #include
#include
class findNodeVisitor : public osg::NodeVisitor { public:
findNodeVisitor();
findNodeVisitor(const std::string &searchName) ;
共分享92篇相关文档