观察MyReadFileCallback结构体的内容,可以发现它继承自osgDB::Registry::ReadFileCallback,并重载了一个函数readNode,分析源代码可知,该函数在osgDB::readNodeFile函数中被调用,即,在加载模型文件时,即会调用 MyReadFileCallback结构体所重载的readNode函数并执行相应的内容。示例程序中该函数的内容如下:
class MyReadFileCallback : public osgDB::Registry::ReadFileCallback
{
public:
virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options)
{
std::cout<<"before readNode"<<std::endl;
// note when calling the Registry to do the read you have to call readNodeImplementation NOT readNode, as this will
// cause on infinite recusive loop.
osgDB::ReaderWriter::ReadResult result = osgDB::Registry::instance()->readNodeImplementation(fileName,options);
std::cout<<"after readNode"<<std::endl;
return result;
}
};
其中较为重要的是readNodeImplementation一行,这是模型文件读取的实际执行函数,如果在回调中对于这一语句的使用有错,那么模型无论怎样都不能被加载。
InsertCallbacksVisitor的内容比较复杂,它继承自osg::NodeVisitor,属于用户自定义的节点访问器。换句话说,当执行到:
InsertCallbacksVisitor icv;
rootnode->accept(icv);
节点访问器将从根节点rootnode开始,遍历所有的子节点并执行相应的操作。
节点访问器InsertCallbacksVisitor的构造函数中,可以传递一个遍历方式的枚举量TraversalMode ,它的取值意义如下:
TRAVERSE_NONE:仅仅传递到当前节点,然后停止传递;
TRAVERSE_PARENTS:传递给当前节点及其父节点,注意,如果当前节点有不止一个父节点,那么访问器将列举出所有的传递路线,其中可能有多个重复出现的节点;
TRAVERSE_ALL_CHILDREN:传递给当前节点及其所有子节点,同样,对于多个子节点的情况,访问器将列举所有的传递路线;
TRAVERSE_ACTIVE_CHILDREN:传递给当前节点及所有被激活的子节点,例如遇到LOD节点和Switch节点时,将不会传递给当前无法显示的子节点。
在节点访问器中,使用apply函数来实现对各种类型的节点的访问。当遍历过程中遇到与某个重载的apply的输入参数相符合的节点时,将执行这一 apply函数的内容。示例代码中重载了Node,Geode和Transform节点的访问函数。在Geode节点的访问函数中,实现了对场景图形结构中每个节点的回调指定:
// 指定Geode节点的更新回调。
geode.setUpdateCallback(new UpdateCallback());
// 指定Geode节点中所有关联几何体(Drawables)的绘制回调,拣选回调和更新回调。
for(unsigned int i=0;i<geode.getNumDrawables();++i)
{
geode.getDrawable(i)->setUpdateCallback(new DrawableUpdateCallback());
geode.getDrawable(i)->setCullCallback(new DrawableCullCallback());
geode.getDrawable(i)->setDrawCallback(new DrawableDrawCallback());
}
在Geode之外的其它节点的apply函数中,往往包含有下面的语句:
traverse(node);
这表示对场景图形节点的遍历将继续进行。换句话说,如果某种类型的节点的apply函数中不包含这一语句,那么场景图形遍历到这一类型的节点时,当前遍历的路线将自动结束,以下的子节点均被忽略。而对Geode节点来说,因为不存在子节点,因此也不必添加这一语句。
使用节点访问器为场景图形节点或几何体添加的回调主要有以下几种:
几何体绘制遍历(DrawableDrawCallback):重载函数drawImplementation,当几何体进行绘制时,函数的内容被调用,注意需要添加Drawable::drawImplementation函数以实现几何体的实际绘制,否则将无法画出当前的几何形状。可以参阅 include/osg/Drawable,draw函数中的调用过程。
几何体拣选遍历(DrawableCullCallback):重载函数cull,当几何体进行拣选优化时,函数的内容被调用。可以参阅src/osgUtil/CullVisitor,apply(Geode&)函数中的调用过程。
几何体更新遍历(DrawableUpdateCallback):重载函数update,当几何体更新时,函数的内容被调用。可以参阅include/osgUtil/UpdateVisitor,handle_geode_callbacks函数中的调用过程。
拣选遍历(CullCallBack):重载操作符operator(),当场景执行拣选优化时,operator()的内容被调用,注意需要添加 traverse函数以实现节点的继续遍历。可以参阅include/osgUtil /CullVisitor,handle_cull_callbacks_and_traverse函数中的调用过程。
更新遍历(UpdateCallBack):重载操作符operator(),当场景更新时,operator()的内容被调用,注意需要添加 traverse函数以实现节点的继续遍历。可以参阅src/osg/StateSet.cpp,runUpdateCallbacks函数中的调用过程。
最后,观察CameraUpdateCallback和CameraEventCallback结构体的内容,可以发现它们均继承自 osg::NodeCallback,并重载了操作符operator(),当执行摄像机更新或者事件回调时,operator()的内容将被调用,添加 traverse函数可以实现节点的继续遍历。
此外,还可以使用Node::setEventCallback来设置节点的事件回调,其输入参数为继承自osg::NodeCallback的用户类,且需要重载操作符operator()以实现回调过程的处理。
综上,可以得出回调的编写方法:
1、编写用户结构体,继承OSG中相应的虚函数结构体,如osg::NodeCallBack;
2、重载回调执行函数,根据回调种类的不同,执行函数的名称也不同,可以参考osgcallback中的设置;
3、注意在回调执行的过程中,有一些必要的系统操作需要交由用户来完成,例如readNodeImplementation,Drawable::drawImplementation,traverse等,否则系统本身的渲染工作可能会不正常。
编译运行osgcallback之后,可以看到控制台不断输出各种回调的执行结果,用户可以根据自己的需要在不同的时间段进入不同的回调来完成所需的工作。