您的位置:首页 > Web前端 > Node.js

osg::Node::accept

2013-08-28 11:05 1176 查看
搜索场景图形中的一个有名节点 ¶
模型文件可能包含了各种不同的节点类型,用户通过对这些节点的使用来更新和表达模型的各个部分。使用osgSim::MultiSwitch多重节点可以在多个模型渲染状态间进行选择。例如,对坦克模型使用多重节点,用户即可自行选择与完整的或者损坏的坦克相关联的几何体以及渲染状态。模型中还可以包含DOF节点,以便清晰表达坦克的某个部分。例如炮塔节点可以旋转,机枪节点可以升高。炮塔旋转时,炮塔体(包括机枪)的航向角(heading)与坦克的航向角相关联,而机枪抬升时,机枪的俯仰角(pitch)与炮塔的俯仰角相关联。

对这些节点进行更新时,我们需要一个指向节点的指针。而我们首先要获取节点的名字,才能得到该节点的指针。而获取节点的名称,主要有这样一些方法:咨询建模人员;使用其它文件浏览器(对于.flt文件,可以使用Creator或者Vega)浏览模型;或者使用OpenSceneGraph。用户可以根据自己的需要自由运用OSG的功能。例如在场景图形中载入flt文件,并且在仿真过程中将整个场景保存成.osg文件。osg文件使用ASCII格式保存,因此用户可以使用各种文本处理软件(写字板,记事本)对其进行编辑。在坦克模型文件中,你可以发现一个名为“sw1”的开关节点,它有两个子节点“good”和“bad”,分别指向坦克未损坏和损坏的状态。坦克模型的.osg文件可以从这里下载:
 http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/T72Tank.osg 
现在我们已经获得了需要控制的开关节点的名称(sw1),亦可获取其指针对象。获取节点指针的方法有两种:一是编写代码遍历整个场景图形;二是使用后面将会介绍的访问器(visitor)。在以前的教程中,我们已经知道如何加载flight文件,将其添加到场景并进入仿真循环的方法。
#include <osg/PositionAttitudeTransform>
#include <osg/Group>
#include <osg/Node>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>

int main()
{
osg::Node* tankNode = NULL;
osg::Group* root = NULL;
osg::Vec3 tankPosit;
osg::PositionAttitudeTransform* tankXform;

tankNode = osgDB::readNodeFile("Models/T72-tank/t72-tank_des.flt");

root = new osg::Group();
tankXform = new osg::PositionAttitudeTransform();

root->addChild(tankXform);
tankXform->addChild(tankNode);

tankPosit.set(5,0,0);
tankXform->setPosition( tankPosit );

osgViewer::Viewer viewer;

viewer.setSceneData( root );

return viewer.run();
}

现在我们需要修改上述代码,以添加查找节点的函数。下面的递归函数有两个参数值:用于搜索的字符串,以及用于指定搜索开始位置的节点。函数的返回值是指定节点子树中,第一个与输入字符串名称相符的节点实例。如果没有找到这样的节点,函数将返回NULL。特别要注意的是,使用访问器将提供更为灵活的节点访问方式。而下面的代码只用于展示如何手动编写场景图形的遍历代码。

osg::Node* findNamedNode(const std::string& searchName,
osg::Node* currNode)
{
osg::Group* currGroup;
osg::Node* foundNode;

// 检查输入的节点是否是合法的,
// 如果输入节点为NULL,则直接返回NULL。
if ( !currNode)
{
return NULL;
}

// 如果输入节点合法,那么先检查该节点是否就是我们想要的结果。
// 如果确为所求,那么直接返回输入节点。
if (currNode->getName() == searchName)
{
return currNode;
}

// 如果输入节点并非所求,那么检查它的子节点(不包括叶节点)情况。
// 如果子节点存在,则使用递归调用来检查每个子节点。
// 如果某一次递归的返回值非空,说明已经找到所求的节点,返回其指针。
// 如果所有的节点都已经遍历过,那么说明不存在所求节点,返回NULL。
currGroup = currNode->asGroup(); // returns NULL if not a group.
if ( currGroup )
{
for (unsigned int i = 0 ; i < currGroup->getNumChildren(); i ++)
{
foundNode = findNamedNode(searchName, currGroup->getChild(i));
if (foundNode)
return foundNode; // 找到所求节点。
}
return NULL; // 遍历结束,不存在所求节点。
}
else
{
return NULL; // 该节点不是组节点,返回NULL
}
}

现在我们可以在代码中添加这个函数,用于查找场景中指定名称的节点并获取其指针。注意这是一种深度优先的算法,它返回第一个符合的节点指针。我们将在设置场景之后,进入仿真循环之前调用该函数。函数返回的开关节点指针可以用于更新开关的状态。下面的代码用于模型载入后,执行查找节点的工作。

osg::Switch* tankStateSwitch = NULL;
osg::Node* foundNode = NULL;

foundNode = findNamedNode("sw1",root);
tankStateSwitch = (osg::Switch*) foundNode;
if ( !tankStateSwitch)
{
std::cout << "tank state switch node not found, quitting." << std::endl;
return -1;
}

按照“访问器”模式搜索有名节点 ¶
“访问器”的设计允许用户将某个特定节点的指定函数,应用到当前场景遍历的所有此类节点中。遍历的类型包括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),用于保存符合搜索字符串的所有节点。为了实现上述的操作,我们需要重载“apply”方法。基类的“apply”方法已经针对所有类型的节点(所有派生自osg::Node的节点)作了定义。用户可以重载apply方法来操作特定类型的节点。如果我们希望针对所有的节点进行同样的操作,那么可以重载针对osg::Node类型的apply方法。findNodeVisitor的头文件内容在下表中列出,相关的源代码可以在这里下载:
 http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/findNodeVisitor.zip 
#ifndef FIND_NODE_VISITOR_H
#define FIND_NODE_VISITOR_H

#include <osg/NodeVisitor>
#include <osg/Node>

class findNodeVisitor : public osg::NodeVisitor
{
public:

// Default constructor - initialize searchForName to "" and
// set the traversal mode to TRAVERSE_ALL_CHILDREN
findNodeVisitor();

// Constructor that accepts string argument
// Initializes searchForName to user string
// set the traversal mode to TRAVERSE_ALL_CHILDREN
findNodeVisitor(const std::string &searchName);

// The 'apply' method for 'node' type instances.
// Compare the 'searchForName' data member against the node's name.
// If the strings match, add this node to our list
virtual void apply(osg::Node &searchNode);

// Set the searchForName to user-defined string
void setNameToFind(const std::string &searchName);

// Return a pointer to the first node in the list
// with a matching name
osg::Node* getFirst();

// typedef a vector of node pointers for convenience
typedef std::vector<osg::Node*> nodeListType;

// return a reference to the list of nodes we found
nodeListType& getNodeList() { return foundNodeList; }

private:

// the name we are looking for
std::string searchForName;

// List of nodes with names that match the searchForName string
nodeListType foundNodeList;

};

#endif

现在,我们创建的类可以做到:启动一次节点访问遍历,访问指定场景子树的每个子节点,将节点的名称与用户指定的字符串作比较,并建立一个列表用于保存名字与搜索字符串相同的节点。那么如何启动这个过程呢?我们可以使用osg::Node的“accept”方法来实现节点访问器的启动。选择某个执行accept方法的节点,我们就可以控制遍历开始的位置。(遍历的方向是通过选择遍历模式来决定的,而节点类型的区分则是通过重载相应的apply方法来实现)“accpet”方法将响应某一类的遍历请求,并执行用户指定节点的所有子类节点的apply方法。在这里我们将重载一般节点的apply方法,并选择TRAVERSE_ALL_CHILDREN的遍历模式,因此,触发accept方法的场景子树中所有的节点,均会执行这一apply方法。

在这个例子中,我们将读入三种不同状态的坦克。第一个模型没有任何变化,第二个模型将使用多重开关(multSwitch)来关联损坏状态,而第三个模型中,坦克的炮塔将旋转不同的角度,同时枪管也会升高。

下面的代码实现了从文件中读入三个坦克模型并将其添加到场景的过程。其中两个坦克将作为变换节点(PositionAttitudeTransform)的子节点载入,以便将其位置设置到坐标原点之外。

// 定义场景树的根节点,以及三个独立的坦克模型节点
osg::Group* root = new osg::Group();
osg::Group* tankOneGroup = NULL;
osg::Group* tankTwoGroup = NULL;
osg::Group* tankThreeGroup = NULL;

// 从文件中读入坦克模型
tankOneGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("\\Models\\t72-tank\\t72-tank_des.flt"));
tankTwoGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("\\Models\\t72-tank\\t72-tank_des.flt"));
tankThreeGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("\\Models\\t72-tank\\t72-tank_des.flt"));

// 将第一个坦克作为根节点的子节点载入
root->addChild(tankOneGroup);

// 为第二个坦克定义一个位置变换
osg::PositionAttitudeTransform* tankTwoPAT =
new osg::PositionAttitudeTransform();
// 将第二个坦克向右移动5个单位,向前移动5个单位
tankTwoPAT->setPosition( osg::Vec3(5,5,0) );
//  将第二个坦克作为变换节点的子节点载入场景
root->addChild(tankTwoPAT);
tankTwoPAT->addChild(tankTwoGroup);

// 为第三个坦克定义一个位置变换
osg::PositionAttitudeTransform* tankThreePAT =
new osg::PositionAttitudeTransform();
// 将第二个坦克向右移动10个单位
tankThreePAT->setPosition( osg::Vec3(10,0,0) );
// 将坦克模型向左旋转22.5度(为此,炮塔的旋转应当与坦克的头部关联)
tankThreePAT->setAttitude( osg::Quat(3.14159/8.0, osg::Vec3(0,0,1) ));
// 将第三个坦克作为变换节点的子节点载入场景
root->addChild(tankThreePAT);
tankThreePAT->addChild(tankThreeGroup);

我们准备将第二个模型设置为损坏的状态,因此我们使用findNodeVisitor类获取控制状态的多重开关(multiSwitch)的句柄。这个节点访问器需要从包含了第二个坦克的组节点开始执行。下面的代码演示了声明和初始化一个findNodeVisitor实例并执行场景遍历的方法。遍历完成之后,我们即可得到节点列表中符合搜索字符串的第一个节点的句柄。这也就是我们准备使用multiSwitch来进行控制的节点句柄。

// 声明一个findNodeVisitor类的实例,设置搜索字符串为“sw1” "sw1"
findNodeVisitor findNode("sw1");

//开始执行访问器实例的遍历过程,起点是tankTwoGroup,搜索它所有的子节点并创建一个列表,用于保存所有符合搜索条件的节点
tankTwoGroup->accept(findNode);

// 声明一个开关类型,并将其关联给搜索结果列表中的第一个节点。
osgSim::MultiSwitch* tankSwitch = NULL;
tankSwitch = dynamic_cast <osgSim::MultiSwitch*> (findNode.getFirst());

更新开关节点 ¶
当我们获取了一个合法的开关节点句柄后,下一步就是从一个模型状态变换到另一个状态。我们可以使用setSingleChildOn方法来实现这个操作。setSingleChildOn()方法包括两个参数:第一个无符号整型量相当于多重开关组(switchSet)的索引号;第二个无符号整型量相当于开关的位置。在这个例子中,我们只有一个多重开关,其值可以设置为未损坏状态或者损坏状态,如下所示:

// 首先确认节点是否合法,然后设置其中的第一个(也是唯一的)多重开关
if (tankSwitch)
{
//tankSwitch->setSingleChildOn(0,false); // 未损坏的模型
tankSwitch->setSingleChildOn(0,true); // 损坏的模型
}

更新DOF节点 ¶
坦克模型还包括了两个DOF(自由度)节点“turret”和“gun”。这两个节点的句柄也可以使用上文所述的findNodeVisitor来获取。(此时,访问器的场景遍历应当从包含第三个模型的组节点处开始执行)一旦我们获取了某个DOF节点的合法句柄之后,即可使用setCurrentHPR方法来更新与这些节点相关的变换矩阵。setCurrentHPR方法只有一个参数:这个osg::Vec3量相当于三个欧拉角heading,pitch和roll的弧度值。(如果要使用角度来描述这个值,可以使用osg::DegreesToRadians方法)

//  声明一个findNodeVisitor实例,设置搜索字符串为“turret”
//"turret"
findNodeVisitor findTurretNode("turret");

// 遍历将从包含第三个坦克模型的组节点处开始执行
tankThreeGroup->accept(findTurretNode);

// 确认我们找到了正确类型的节点
osgSim::DOFTransform * turretDOF =
dynamic_cast<osgSim::DOFTransform *> (findTurretNode.getFirst());

// 如果节点句柄合法,则设置炮塔的航向角为向右22.5度。
if (turretDOF)
{
turretDOF->setCurrentHPR( osg::Vec3(-3.14159/4.0,0.0,0.0) );
}

同理,机枪的自由度也可以如下设置:

//声明一个findNodeVisitor实例,设置搜索字符串为“gun”
findNodeVisitor findGunNode("gun");
//遍历将从包含第三个坦克模型的组节点处开始执行
tankThreeGroup->accept(findGunNode);
//确认我们找到了正确类型的节点
osgSim::DOFTransform * gunDOF =
dynamic_cast<osgSim::DOFTransform *> (findGunNode.getFirst()) ;
//如果节点句柄合法,则设置机枪的俯仰角为向上22.5度。
if (gunDOF)
{
gunDOF->setCurrentHPR( osg::Vec3(0.0,3.14159/8.0,0.0) );
}

就是这样了!然后再编写仿真循环的代码即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: