Viewer在其内部创建了一个osg::Camera摄像机对象来管理osg的模型—视图矩阵。用户可以通过以下的两个方法来控制Camera对象。
①将一个摄像机控制器对象关联到Viewer中。如果你的程序不需要这么做,那么Viewer.run()将自动创建一个osgGA::TrackballManipulator对象来控制摄像机的工作。osgGA库定义了一些常用的控制器类。用户可以调用Viewer::setCameraManipulator来指定一个期望的控制器。
②设置Camera对象的投影矩阵和观察矩阵为自定义的矩阵值。这样也可以保证用户程序能够完全控制视口的浏览动作。
osgViewer::setViewMatrix()方法来设置视口矩阵。参数是一个osg::Matrix矩阵, osgViewer::setViewMatrixAsLookat():参数和gluLookAt类似,三个向量:eye,center,up。 设置清屏颜色:
Camera::setClearColor()用来设置清屏颜色,缺省情况下会清除深度和颜色缓存,可以使用Camera::setClearMask(GL_CLOLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)来设置清除属性。
3.1.2 SimpleViewer 和 CompositeViewer
osgViewer::SimpleViewer类不会主动的创建窗口或者设备上下文,它需要依赖用户程序来创建窗口、上下文,并将其激活为当前设备,如果操作正确的话,SimpleViewer::frame()将会在用户给定的窗口中渲染。
osgViewer::CompositeViewer:Viewer类只能在一个场景中添加一个视口,而CompositeViewer类可以支持一个或多个场景的多个视口显示,并允许用户程序指定其渲染顺序。CompositeViewer还支持渲染到纹理(RTT)的操作,即,允许用户程序将一个视口中渲染的图像作为另一个视口的纹理贴图。
二.动态更改
Osg允许用户动态的修改场景图形并因而改变每一帧的显示。用户可以更改几何数据,渲染状态参数,Switch节点设置,以及任何场景图形的结构。OsgViewer库支持多线程模式,每一个线程均独立的运行挑选及绘制遍历,但osg并没有为了线程的安全性增设内存锁,而是要求用户程序只可在挑选及绘制遍历的时域之外修改场景图形。
确保用户的修改不会与挑选及绘制线程发生冲突,一个简单的方案是,在Viewer::frame()的调用之外进行场景图形的修改,这需要在主渲染循环中添加额外的代码。但如果用户希望自己的程序更加整洁及规范的话,可以选择在更新遍历中进行场景的修改操作。
一些与场景图形动态更改相关的基本技术:
1. 处于性能优化和线程安全性的考虑,用户需要通知osg,场景图形的哪些部分是可能要进行修改的。用户可以通过设置Object对象(Node,Drawable,StateSet等)的数据变度(data variance)属性来完成这一工作。
2. osh允许用户为Node和Drawable设置回调(callback)。Osg将在特定遍历中执行这些回调, 3. 用户程序有时不能预知场景图形的哪一部分需要修改。这是需要搜索整个场景图形来查找特定的节点,或者由用户使用鼠标等输入设备来选择一个节点。
3.2.1 数据边度
OsgViewer支持的多线程模型允许主循环不必等到绘制遍历结束就可以继续运行,也就是Viewer::frame()方法在绘制遍历仍未结束的时候就可以返回。换句话说,上一帧的绘制遍历可以与下一帧的更新遍历产生重叠。Osg提供了osg::object::DataVariance()方法解决绘制遍历与多线程的更新操作的冲突。(Frame():Render a complete new frame)。用于处理绘制遍历与其他遍历的同步性。
3.2.2 回调
Osg允许用户设置Node和Drawable对象的回调类。Node可以在执行更新和挑选时进行回调,Drawable可以在挑选和绘制遍历时进行回调。
使用NodeCallback,用户程序所需要执行的步骤: 1. 从NodeCallback继承一个新的类。
2. 重载NodeCallback::operator()方法,实现场景图形的动态更改。
3. 实例化继承而来的类,然后使用Node::setUpdateCallback()方法关联到将要修改的Node。 NodeCallback::operator()两参数:第一个是回调类所关联的Node节点地址,第二个参数是osg::NodeVisitor对象的地址。
NodeVisitor::traverse():这个方法允许osg访问其他的节点并执行其回调。 3.2.3 NodeVisitor类
这是osg对于访问器(visitor)设计思想的实现。从本质上说,NodeVisitor类遍历了一个场景图形并为每一个被访问节点调用特定的函数。这一简单的技术却是许多osg操作的基类,如osgUtil::Optimizer,osgUtil的几何体处理类。Osg使用osgUtil::UpdateVisitor类(继承自NodeVisitor)来实现更新遍历。
NodeVisitor是一个基类,用户程序无法直接将其实例化。但用户程序可以使用osg提供的任何NodeVisitor派生类,也可以自己编写继承自NodeVisitor类的代码。NodeVisitor包含了一些经过重载的apply()方法,其输入参数涵盖了大部分osg的节点类型。当一个NodeVisitor对象遍历整个场景图形时,它将会为每个被访问的节点调用其相应的apply()方法。
使用特定的名称来搜索节点也是一种简单而实用的操作。 允许NodeVisitor遍历:
缺省情况下,NodeVisitor类禁止执行遍历。因此在你的派生类中,需要使用枚举量NodeVisitor::TRAVERSE_ALL_CHILDREN来初始化基类,以允许执行遍历,否则osg将不会调用apply()方法。
如果要使用NodeVisitor来遍历整个场景图形,可以将NodeVisitor作为Node::accept()的输入参数传递。你可以在任何一个节点上调用accept(),NodeVisitor将从那个节点开始遍历整个场景图形,如果要搜索整个场景图形的话,可以从根节点开始调用accept()。
3.2.4 用户选择
从本质上讲,osg程序通过两个步骤来实现用户选择:
1. 接受鼠标事件osgGA提供了允许程序接受鼠标事件的事件类,它具备设备平台无关特性。 2. 判断场景图形的哪个部分被鼠标光标覆盖。osgUtil提供了一种相交集类(Intersection),可以在鼠标xy坐标的周围创建包围盒,并判段包围盒与场景图形的相交情况,osgUtil将按照由前至后的顺序返回与包围盒相交的节点列表。
捕捉鼠标事件
osgGA::TrackballManipulator将鼠标事件作为输入,并修改用于控制用户视口的osg::Camera视口矩阵。
TrackballManipulator派生自osgGA::GUIEventHandler类,是虚基类,无法实例化,但用户可以从GUIEventHandler派生自己的类,以实现各种基于GUI事件的操作。要实现鼠标控制的选择操作,可以从GUIEventHandler派生新的类,并重载
GUIEventHandler::Handle()方法,以接收鼠标事件。然后用户可以创建新类的实例并将其关联到应用程序的观察视口。
Virtual bool GUIEventHandler::Handle(const osgGA::GUIEventAdapter & ea,osgGA::GUIActionAdapter & aa);用户实现的Handle()方法可以接收来自GUIEventAdapter的各种GUI事件,包括鼠标事件。GUIEventAdapter类的头文件中定义了枚举类型EventType,用户程序可以从中选择自己需要的GUI事件。使用GUIEventAdapter.getEventType()方法可以得到当前的事件类型。
GUIActionAdapter是用户返回给GUI系统的程序接口。当遇到鼠标选择的操作时,用户将用于选择事件的GUIEventHandler关联到视口类,则视口类就是一个GUIActionAdapter。用户需要使用他来实现当前视口与场景之间的交互。
在渲染视口之前,用户往往会创建一个GUIEventHandler派生类的实例,并使用Viewer::addEventHandler()将其关联到视口。一个视口可以有多个事件处理器,Viewer将事件处理对象添加到一个事件处理列表中。运行时Viewer将调用每个GUI事件的handle()函数,直到其中一个的handle()函数返回true为止。
要实现接收鼠标事件并实现用户选择的功能,需要经过以下几个步骤: 1. 从GUIEventHandler继承新的类,重载handler()方法。
2. 在handler()方法中检查GUIEventAdapter参数传递的事件类型,并针对需要的事件类型执行相应的操纵。方法返回true时将阻止其他事件处理器继续接受事件消息。
3. 在渲染之前,创建事件处理器的实例,并使用addEventHandler()方法添加到视口中。Osg将会把视口作为GUIActionAdapter参数传递给handler()方法。
交集
Osg使用一种多胞体(polytope)的金字塔形包围盒代替射线。这种金字塔的顶峰位于视点,其中心轴直接穿过鼠标(光标)的位置。它距离视点的宽度是由视场和程序控制的宽度参数决定的。
osgUtil::IntersectionVisitor类继承自NodeVisitor,它可以监测每个定点的包围盒与交集包围盒的关系,并允许在某个子图形不可能存在有交集的子节点时,跳过该子图形的遍历。
用户可以设置IntersectionVisitor类并使用几种不同的几何结构进行交集检测。其构造函数使用osgUtil::Intersector作为输入参数,Intersector定义了选择操作的几何体并执行实际的交集操作。Intersector是一个纯虚基类,osgUtil库从中派生了许多代表不同几何结构的新类。
有些程序需要拾取单独的顶点和多边形,而有些程序只需要简单的获取那些包含了被选节点的Group或Transform父节点。IntersectionVisitor返回osg::NodePath对象来满足这些要求。NodePath是一个std::vector
要实现osg中的鼠标选择操作,需要按照如下的步骤编写代码:
1. 创建并设置PolytopeIntersector,其中的鼠标位置应当使用GUIEventAdapter中经过归一化的数据。
2. 创建IntersectionVisitor对象,并将PolytopeIntersector作为其构造函数的输入参数。 3. 由场景图形的根节点加载IntersectionVisitor,一般来说是通过Viewer中的Camera对象, 如下面的代码:
iv是一个IntersectionVisitor对象 viewer.getCamera()->accept(iv);
4. 如果PolytopeIntersector的返回值包含了交集,那么可以获取返回的NodePath并搜索符合要求的节点。