Bootstrap

通过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

参考与感谢

;