通过NodeVisitor遍历OSG节点

通过NodeVisitor遍历OSG节点

遍历场景中的节点,可用于在场景渲染的时候找到需要的节点

原理概述

OSG::Node节点中有Node::accept(NodeVisitor& nv)方法,NodeVisitor中有nv.apply()方法,根据不同节点类型重载apply()方法,实现对不同类型节点的操作,可以实现对当前节点/当前节点的父节点/当前节点的子节点的访问。
OSG中的NodeVisitor实际上采用观察者模式(Observer Pattern)
方式实现。

观察者模式

意图: 对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
编码关键:

  • 在抽象类里有一个ArrayList存放观察者们;
  • 建立一套触发机制。
  • 避免循环引用
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

优点: 观察者和被观察者是抽象耦合的。
缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
    观察者UML图

OSG源码阅读

NodeVisitor 文件
class OSG_EXPORT NodeVisitor : public virtual Object
{
    public:
        enum TraversalMode //遍历方式
        {
            TRAVERSE_NONE,
            TRAVERSE_PARENTS,//访问父节点
            TRAVERSE_ALL_CHILDREN,//访问子节点
            TRAVERSE_ACTIVE_CHILDREN//访问激活子节点
        };

		virtual void apply(Drawable& drawable);
		virtual void apply(Geometry& geometry);
		virtual void apply(Node& node);
		virtual void apply(Geode& node);
		virtual void apply(Billboard& node);
		...

		void NodeVisitor::apply(Node& node)
		{
		    traverse(node);
		}
		void NodeVisitor::apply(Geode& node)
		{
		    apply(static_cast<Group&>(node));
		}

        /** Method for handling traversal of a nodes.
            If you intend to use the visitor for actively traversing
            the scene graph then make sure the accept() methods call
            this method unless they handle traversal directly.*/
        // 访问者主动遍历时调用该方法 实现迭代遍历
        inline void traverse(Node& node)
        {
        	if (_traversalMode==TRAVERSE_PARENTS)
	        	node.ascend(*this);
            else if (_traversalMode!=TRAVERSE_NONE)
            	node.traverse(*this);
        }

//



}

Node 文件
typedef std::vector< Node* > NodePath;

class OSG_EXPORT Node : public Object
{
    public:
	virtual void accept(NodeVisitor& nv);
	
	inline void pushOntoNodePath(Node* node) 
	{ 
		if (_traversalMode!=TRAVERSE_PARENTS)
			_nodePath.push_back(node);
		else
			_nodePath.insert(_nodePath.begin(),node);
	}

	inline void popFromNodePath()
	{
		 if (_traversalMode!=TRAVERSE_PARENTS)
		 	_nodePath.pop_back(); 
		 else 
		 	_nodePath.erase(_nodePath.begin());
	}
	
        /** Traverse upwards : calls parents' accept method with NodeVisitor.*/
        virtual void ascend(NodeVisitor& nv);
        /** Traverse downwards : calls children's accept method with NodeVisitor.*/
        virtual void traverse(NodeVisitor& /*nv*/) {}
}

Node.cpp 文件
void Node::accept(NodeVisitor& nv)
{
    if (nv.validNodeMask(*this))
    {
        nv.pushOntoNodePath(this);
        nv.apply(*this);
        nv.popFromNodePath();
    }
}

Group 文件
class OSG_EXPORT Group : public Node
{
    public :
		virtual void traverse(NodeVisitor& nv);
		void Group::traverse(NodeVisitor& nv)
		{
		    for(NodeList::iterator itr=_children.begin();
		        itr!=_children.end();
		        ++itr)
		    {
		        (*itr)->accept(nv);
		    }
		}
}


//Switch 文件
class OSG_EXPORT Switch : public Group
{
    public :
		virtual void traverse(NodeVisitor& nv);
		void Switch::traverse(NodeVisitor& nv)
		{
    		if (nv.getTraversalMode()==NodeVisitor::TRAVERSE_ACTIVE_CHILDREN)
    		{
        		for(unsigned int pos=0;pos<_children.size();++pos)
        		{
            		if (_values[pos]) _children[pos]->accept(nv);
        		}
    		}
    		else
    		{
        		Group::traverse(nv);
    		}
		}
}


遍历OSG节点示例

示例1

class VisitorNodePath : public osg::NodeVisitor
{
public:
	VisitorNodePath() :osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
	
	//注意Node是Group的父类,若不重载Group的apply方法,则也会进入Node的apply方法
	void apply(osg::Node& node)
	{
		std::cout << "Apply node: " << node.getName() << std::endl;
		if (node.getName() == "glider")//通过名字确定节点
		{
			//Do something
		}
		traverse(node);
	}
}

int main()
{
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
	viewer->addEventHandler(new ChangeWindow());

	osg::ref_ptr<osg::Group> root = new osg::Group;
	osg::ref_ptr<osg::Node> glider = osgDB::readNodeFile("glider.osg");
	root->setName("root");//添加名字方便遍历
	glider->setName("glider");
	root->addChild(glider);

	viewer->setSceneData(root);
	VisitorNodePath vn;
	root->accept(vn);

	return viewer->run();
}





示例2

示例3

参考与感谢

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要利用键盘操作器(`osgGA::GUIEventHandler`)来对从 `NodeVisitor` 遍历得到的节点进行旋转、平移等操作,您可以使用 OpenSceneGraph (OSG) 提供的事件处理机制。以下是一个示例代码片段,展示了如何实现这样的操作: ```cpp #include <osg/NodeVisitor> #include <osg/MatrixTransform> #include <osgGA/GUIEventHandler> #include <osgViewer/Viewer> // 自定义的 NodeVisitor 子类 class MyVisitor : public osg::NodeVisitor { public: MyVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} // 重写 apply 方法,在遍历每个节点时执行操作 virtual void apply(osg::Node& node) { // 在这里添加旋转和平移的代码 osg::MatrixTransform* transform = dynamic_cast<osg::MatrixTransform*>(&node); if (transform) { // 旋转节点 transform->setMatrix(osg::Matrix::rotate(rotationAngle_, osg::Vec3(0, 0, 1))); // 平移节点 transform->setMatrix(transform->getMatrix() * osg::Matrix::translate(translationOffset_)); } // 继续遍历节点 traverse(node); } void setRotationAngle(float angle) { rotationAngle_ = angle; } void setTranslationOffset(const osg::Vec3& offset) { translationOffset_ = offset; } private: float rotationAngle_ = 0.0f; osg::Vec3 translationOffset_; }; // 自定义的 GUIEventHandler 子类 class MyEventHandler : public osgGA::GUIEventHandler { public: MyEventHandler(MyVisitor* visitor) : visitor_(visitor) {} // 重写 handle 方法,在接收到按键事件时执行操作 virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN) { switch (ea.getKey()) { case osgGA::GUIEventAdapter::KEY_Left: visitor_->setRotationAngle(-0.1f); break; case osgGA::GUIEventAdapter::KEY_Right: visitor_->setRotationAngle(0.1f); break; case osgGA::GUIEventAdapter::KEY_Up: visitor_->setTranslationOffset(osg::Vec3(0, 0.1f, 0)); break; case osgGA::GUIEventAdapter::KEY_Down: visitor_->setTranslationOffset(osg::Vec3(0, -0.1f, 0)); break; } return true; } return false; } private: MyVisitor* visitor_; }; int main() { // 创建场景图(省略加载场景文件的步骤) // 创建 MyVisitor 对象并应用于场景节点 MyVisitor visitor; scene->accept(visitor); // 创建查看器并设置场景数据 osgViewer::Viewer viewer; viewer.setSceneData(scene); // 创建事件处理器并绑定到查看器 MyEventHandler eventHandler(&visitor); viewer.addEventHandler(&eventHandler); // 运行场景图的渲染循环 viewer.run(); return 0; } ``` 在上面的示例中,我们创建了一个名为 `MyVisitor` 的自定义 `NodeVisitor` 子类,在 `apply` 方法中实现了旋转和平移操作。我们还创建了一个名为 `MyEventHandler` 的自定义 `GUIEventHandler` 子类,用于处理键盘事件。 在 `main` 函数中,我们创建了 `MyVisitor` 对象并将其应用于场景节点。然后,我们创建了一个 `osgViewer::Viewer` 对象,并将场景数据设置为场景图。接下来,我们创建了一个 `MyEventHandler` 对象,并将其绑定到查看器上。 最后,我们运行场景图的渲染循环。当用户按下左、右、上、下方向键时,`MyEventHandler` 类的 `handle` 方法会被调用,根据按键事件设置旋转角度和平移偏移量。然后,在每次遍历节点时,`MyVisitor` 类会根据这些设置执行相应的旋转和平移操作。 请注意,上述示例中的旋转和平移操作仅作为演示。您可以根据实际需求修改旋转角度和平移偏移量的设置。同时,您可能需要添加其他按键事件和相应的操作。 希望对您有所帮助!如果有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周陽讀書

周陽也想繼往聖之絕學呀~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值