OSG中对于Node、NodeVisitor、Callback三者关系与的梳理

前言

学习osg以来对节点的遍历、回调没有一个清晰的认识,这次希望通过写博客的方式让自己好好梳理一下。

首先画了一下NodeVisitoor、Node、Callback这3者之间的主要关系, 可以看到是你中有我,我中有你,并且都有一个名叫traverse的函数, 这也是让我感到混乱的地方。下面就对着源码一一分析下这套流程是如何进行的。

一 Node

  //Node.h   
/** Visitor Pattern : calls the apply method of a NodeVisitor with this node's type.*/
virtual void accept(NodeVisitor& nv)
{
     if (nv.validNodeMask(*this))
    {
        nv.pushOntoNodePath(this);
        nv.apply(*this);
        nv.popFromNodePath();
    }
}

/** Traverse upwards : calls parents' accept method with NodeVisitor.*/
virtual void ascend(NodeVisitor& nv)
{
    std::for_each(_parents.begin(),_parents.end(),NodeAcceptOp(nv));
}

/** Traverse downwards : calls children's accept method with NodeVisitor.*/
virtual void traverse(NodeVisitor& /*nv*/) {}

1)accept函数中,主要就是调用了NodeVisitor的apply函数, 并把自己作为参数传入,这样就可以在NodeVisitor的派生类中重写apply做一些想要对该节点进行的操作了。

2)ascend函数主要就是遍历父节点并依次执行accept操作。

3)traverse函数在Node节点中是一个空的虚函数,毕竟叶子节点也是继承自Node的,叶子节点没有子节点,那么在Node中同样也不能traverse,很容易知道,真正具有traverse功能的代码在Group节点中,简单看一下:

void Group::traverse(NodeVisitor& nv)
{
    for(NodeList::iterator itr=_children.begin();
        itr!=_children.end();
        ++itr)
    {
        (*itr)->accept(nv);
    }
}

其实也是在遍历子节点,然后让它们执行accept函数。 

可以看出osg中是以深度优先遍历的整个节点树。

二 NodeVisitor

inline void traverse(Node& node)
{
   if (_traversalMode==TRAVERSE_PARENTS) node.ascend(*this);
    else if (_traversalMode!=TRAVERSE_NONE) node.traverse(*this);
}

首先看traverse函数,其实就是根据_traversalMode调用Node的ascend函数或者traverse函数,如果是TRAVERSE_NONE, NodeVisitor的这次遍历也就到此为止了。

void NodeVisitor::apply(Node& node)
{
    traverse(node);
}

在NodeVisitor的apply(Node&) 中调用了traverse函数, 就这样当Node accept了一个NodeVisitor之后,首先会以自己为参数调用NodeVisitor的apply函数, 然后在NodeVisitor执行完apply函数后,又会以自己为参数调用Node的traverse函数(这里以TRAVERSE_ALL_CHILDREN为例)。

这样来回调用,就能让NodeVisitor访问到节点树中的每一个节点。

比如我们继承了NodeVisitor,并重写apply(Group& group), 在执行完自定义逻辑以后, 为了让本次traverse能够持续下去,就该主动调用一下父类的traverse函数。

三 Callback

“OSG 中的节点主要使用回调(Callback)来完成用户临时定义的、需要每帧执行的工作。这是一种方便的扩展节点功能的方式。但是对于功能要求比较复杂的用户节点,重构Node::traverse()函数并编写自定义的实现代码,应当是一种结构更为清晰的方式. ”

以上是摘自《OpenSceneGraph三维渲染引擎设计与实践》中对于callback的描述, 对于“但是对于功能要求比较复杂的用户节点,重构Node::traverse()函数并编写自定义的实现代码,应当是一种结构更为清晰的方式” 这句不是太理解,如果有小伙伴有自己的见解或者例子可以在评论中分享一下。

osg中有默认的UpdateVisitor与EventVisitor,在渲染每一帧之前都会先进行这两次visitor:

void ViewerBase::frame(double simulationTime)
{
    if (_done) return;

    // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl;

    if (_firstFrame)
    {
        viewerInit();

        if (!isRealized())
        {
            realize();
        }

        _firstFrame = false;
    }
    advance(simulationTime);

    eventTraversal();
    updateTraversal();
    renderingTraversals();
}

具体细节先不深究,比如在updataVisitor的apply函数中,就会去检测节点是否具有updateCallback, 如果有则会调用其run函数, 并将自己(updataVisitor)与Node作为参数传入其中,用户可以重写run函数来完成额外功能。

 /** During traversal each type of node calls its callbacks and its children traversed. */
 virtual void apply(osg::Node& node) 
{ 
    handle_callbacks_and_traverse(node); 
}

inline void handle_callbacks_and_traverse(osg::Node& node)
{
    handle_callbacks(node.getStateSet());

    osg::Callback* callback = node.getUpdateCallback();
    if (callback) callback->run(&node,this);
    else if (node.getNumChildrenRequiringUpdateTraversal()>0) traverse(node);
}

在callback中,包含了一个callback的指针,也就是说其实是一个单向链表,那么它的traverse函数其实就是去遍历这个链表,并执行它的run函数,直到没有下一个callback, 则执行NodeVisitortraverse函数,继续遍历节点树。

bool Callback::traverse(Object* object, Object* data)
{
    if (_nestedCallback.valid()) return _nestedCallback->run(object, data);
    else
    {
        osg::Node* node = object ? object->asNode() : 0;
        osg::NodeVisitor* nv = data ? data->asNodeVisitor() : 0;
        if (node && nv)
        {
            nv->traverse(*node);
            return true;
        }
        else return false;
    }
}

画图大概示意:

四 总结

综上所述,其实NodeVisitor与Node之间就已经能完成对节点树的遍历,并在NodeVisitor的apply函数中完成一些自定义的操作。当然还可以在NodeVisitor的apply函数中去检查Node是否具有一些类型的callback,如果有的话执行callback,完成功能。 osg自带的UpdateVisitor与EventVisitor就是这么做的, 用户可以很方便的继承callback,并将它设置为一个节点的updateCallback,在每一帧绘制前完成一些动作(比如旋转这个节点)。

  • 37
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值