array 发表于 2008-8-22 14:06:25

OSG原创教程:最长的一帧(9)

当前位置:osgViewer/Viewer.cpp第782行,osgViewer::Viewer::eventTraversal()
现在我们遇到的这一段代码专门用来处理OSG的退出事件,对于osgviewer等例子而言,有一个专门的退出键Esc,按下后即可直接退出OSG。

对于很多成型的仿真软件来说,按下Esc就退出程序的做法显然不太现实,此时我们可以使用ViewerBase::setQuitEventSetsDone设置是否允许按下某个键之后直接退出这种做法,同时还可以使用另一个函数ViewerBase::setKeyEventSetsDone来设置自定义的退出键(缺省是GUIEventAdapter::KEY_Escape)。

ViewerBase::setDone函数可以随时帮助我们结束仿真程序,因此不必担心退出键被屏蔽之后会带来什么后果。

下一步我们再次遍历所有得到的事件,这里面可能包括用户的鼠标与键盘操作事件,也可能由系统产生的窗口尺寸变化事件,或者程序的关闭事件。对于遍历过程中得到的每一个GUIEventAdapter事件对象event,把它传递给每一个注册的GUIEventHandler事件处理器:for(EventHandlers::iterator hitr = _eventHandlers.begin();
    hitr != _eventHandlers.end();
    ++hitr)
{
(*hitr)->handleWithCheckAgainstIgnoreHandledEventsMask( *event, *this, 0, 0);
}不必关心那个名字又长又拗口的执行函数,它的实质内容其实一看便知。

当前位置:osgGA/GUIEventHandler第73行,osgGA::GUIEventHandler::handleWithCheckAgainstIgnoreHandledEventsMask()
这个函数的主要工作是执行GUIEventHandler::handle函数,后者在用户自定义的事件处理器中是必须被重写的。当handle函数返回值为true时,表示此事件已经处理完毕,其它事件处理器不会再理会这个事件;返回false则继续交由后继的GUIEventHandler对象处理。没有特殊需要的话,请不要重写handleWithCheckAgainstIgnoreHandledEventsMask函数。

在新的OSG版本中,如果希望某个事件处理器暂时不要处理某一事件,可以使用GUIEventHandler::setIgnoreHandledEventsMask函数,传入GUIEventAdapter::EventType类型的参数(或者通过“或”运算传入多个EventType类型),就可以禁止处理器处理此类事件了。

在事件处理器组_eventHandlers处理过事件之后,场景漫游器_cameraManipulator将再次考察同一事件,TrackballManipulator等漫游器就是在这个时候执行场景的平移、旋转和缩放动作的。因此,对于鼠标/键盘事件,请不要轻易设置handle函数的返回值为true,因为那样将屏蔽漫游器对这些事件的处理。

当前位置:osgViewer/Viewer.cpp第827行,osgViewer::Viewer::eventTraversal()
通过阅读osgcallback例子以及各种相关的教程,我们早已知道OSG中存在着各种各样的回调(Callback),其中以事件回调(EventCallback)和更新回调(UpdateCallback)最为常见。对于场景图形中的任何节点,以及Geode叶节点所包含的任何Drawable几何体,我们都可以使用setEventCallback和setUpdateCallback函数设置它的事件回调与更新回调对象。这些回调对象的调用时机就在eventTraversal函数中。

场景节点的回调对象必须继承自osg::NodeCallback,并重写NodeCallback:: operator()函数以实现回调的具体内容。由此有所不同的是,Drawable对象的事件回调必须继承自Drawable::EventCallback,并具现EventCallback::event函数的内容;其更新回调则必须继承Drawable::UpdateCallback并具现UpdateCallback::update函数。
为了正确地遍历场景的节点和几何体对象,并执行所有可能的事件回调和更新回调,OSG使用访问器(Visitor)机制来处理场景图形的访问工作。这其中,_eventVisitor就是负责管理事件回调的遍历工作的(下一日介绍的_updateVisitor负责更新遍历)。这个变量的值会自动初始化,当然我们也可以用ViewerBase::setEventVisitor加载我们自定义的事件访问器,前提是这个访问器要继承自osgGA::EventVisitor类。

在事件回调的处理函数中(operator()或者event),我们可以通过读取第二个传入参数,并调用EventVisitor::getEvents函数来获取当前发生的事件。所有的交互和系统事件都会一次又一次地触发事件回调,因此编写这个回调的内容时请一定要慎重,不然会大幅度地降低系统的性能。

在遍历场景节点并执行其事件回调之后,OSG还要转至主摄像机_camera和从摄像机组_slaves,再次执行它们的事件回调对象,注意这里的实现代码:osg::NodeVisitor::TraversalMode tm = _eventVisitor->getTraversalMode();
_eventVisitor->setTraversalMode(osg::NodeVisitor::TRAVERSE_NONE);
if (_camera.valid() && _camera->getEventCallback()) _camera->accept(*_eventVisitor);
for(unsigned int i=0; i<getNumSlaves(); ++i)
{
osg::Camera* camera = getSlave(i)._camera.get();
if (camera && camera->getEventCallback()) camera->accept(*_eventVisitor);
}
_eventVisitor->setTraversalMode(tm);依然使用访问器,但是设置访问器不要向下遍历节点(因为Camera同样可以作为场景的一个中间节点),在访问过所有摄像机之后再恢复访问器的原有值。

最后,计算事件遍历的结束时间,将相关的时刻信息保存到记录器中,休息一下,准备向下一个函数updateTraversal迈进。

解读成果:
osgGA::GUIEventHandler,osgViewer::Viewer::eventTraversal()。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?

shengrendan2 发表于 2008-9-27 14:10:16

好;
真乃高手真人司马在世也;

likai11 发表于 2008-10-8 22:06:14

写的很好 赞一个:victory:
页: [1]
查看完整版本: OSG原创教程:最长的一帧(9)