场景交互与场景漫游-osgGA库(5)
osgGA库
osgGA库是OSG的一个附加的工具库,它为用户提供各种事件处理及操作处理。通过osgGA库读者可以像控制Windows窗口一样来处理各种事件
osgGA的事件处理器主要由两大部分组成,即事件适配器和动作适配器。osgGA:GUIEventHandler类主要提供了窗口系统的GUI事件接口它使用osgGA::GUIEventAdapter 实例来接收更新,使用osgGA::GUIActionAdapter 实例向系统发出请求
在osgGA::GUIEventHandler 中包含了一系列的枚举事件类型,如NONE、PUSH、RELEASE、DOUBLECLICK2、DRAG、MOVE、KEYDOWN 和KEYUP 等。但osgGA::GUIEventHandler 会根据不同的事件类型实现一些相关的操作,主要包括下面的类型:
NONE
PUSH//鼠标事件
RELEASE//松开
DOUBLECLICK//双击
DRAG//拖动
MOVE//移动
KEYDOWN//按键
KEYUP//松开
FRAME//每一帧
RESIZE//大小缩放
SCROLL//滚轮
PEN_PRESSURE/鼠标笔压力
PEN_ORIENTATION//鼠标笔的方位
PEN_PROXIMITY_ENTER //开始鼠标笔
PEN_PROXIMITY_LEAVE标笔离开
CLOSE_WINDOW//关闭窗口
QUIT_APPLICATIONG//退出程序
USER
这些事件类型与Windows的窗口事件非常类似。但在使用这些事件类型时,需要注意,有些OSG并没有实现好,如鼠标滑轮的事件。如果读者有兴趣实现的话,可以向 OSG 官方提供读者的实现代码,读者的大名将会出现在贡献人之列。
相对而言,osgGA::GUIActionAdapter 类就简单多了,它主要包含系统执行的动作,如重新绘制和光标重置之类的请求。
osgGA库还包含很多操作器,主要包含一个基类osgGA::MatrixManipulator和它的一些派生类osgGA::MatrixManipulator类是一个基类,提供了各种接口,这也是漫游会用到的一个基类。它提供的接口包括矩阵变换和事件处理,编写自己的操作器需要通过继承osgGA::MatrixManipulator 来实现,需要重载里面的矩阵变换函数和时间处理函数。这里只是简单介绍,后面会有详细说明。
从osgGA::MatrixManipulator 类派生的子类就是OSG中默认的操作器,主要包括
osgGA::AnimalionPathManipulator//动画路径操作器
osgGA::DriveManipulator//车行操作器
osgGA::FlightManipulator//飞行操作器
osgGA::KeySwitchMatrixManipulator//按键切换操作器
osgGA::NodeTrackerManipulator//节点跟踪操作器
osgGA::TerrainManipulator//地形操作器
osgGA::TrackballManipulator//跟踪球操作器
osgGA::UFOManipulator//UFO操作器
在默认情况下,OSG通常使用的是osgGA::TrackballManipulator 操作器。在前面很多程序中,都是直接使用 viewer->run(),其实,使用的操作器就是该跟踪球操作器。当然,读者可以将其设置为其他的操作器来处理,例如:
viewer->setCameraManipulator(new osgGA::TerrainManipulator());
OSG中预设了很多操作器,读者可以在适当的时候选择适当的操作器来完成相应的功能,这里不再赘述,因为不可能把所有操作器的源代码都拿来讲解,在实际应用中,更偏向于编写自己的操作器。
键盘事件消息处理
在osgGA库中,存在一个事件处理器 osgGA::GUIEventHandler,可以提供窗口系统的GUI事件接口。在OSG中,默认有很多已经写好的事件处理器。在osgViewer 应用程序中添加了很多在OSG中预定义的事件处理器,实现的功能有显示帧率、操作器切换和帮助等,这些事件处理器主要包括
class HelpHandler //帮助事件处理器
class StatsHandler //状态事件处理器
class WindowSizeHandler //窗口大小事件处理器
class ThreadingHandler //线程模型设置事件处理器
class RecordCameraPathHandler //动画记录事件处理器
class LODScaleHandler//LOD缩放事件处理器
class ScrccnCaptureHandler //截屏事件处理器
这些事件处理器的源代码可以在osgViewer目录下找到,有兴趣的读者可以去研究一下,或者改写为自己的事件处理器。
在实际过程中,编写自己的事件处理器是一件不可避免的事情,这也是交互的重要部分之一。如果一个平台没有交互,那就只是给客户放电影,就没有必要用到3D这样充满挑战的技术。
编写一个自己的事件处理器是非常简单的,主要操作步骤如下:
<1> 编写一个新类,继承自osgGA:GUIEventHandler类。
<2> 重载成员函数handle(),在osgGA::GUIEventHandler中,该函数原型如下,是一个虚函数:
virtual bool handle(const GUIEventAdapter &, GUIActionAdapter &)
这一步是非常关键的,也是十分重要的。通过重载该函数,可以根据事件的类型执行自定义动作(可参看第8.2.3节的示例)。
<3> 将事件处理器压入处理器列表。这一步是非常必要的,如果没有这一步,所写的事件处理器将不会执行,这相当于启动该事件处理器的作用,在使用时一定要注意。
在编写自己的事件处理器时,在事件处理函数中,事件处理函数的返回值与事件处理器列表中的当前处理器的键盘和鼠标时间相关,并保持一一对应关系。如果返回值为true,则系统会认为该事件已经处理,就不再传递给下一个事件处理器。如果返回值是false,那么它会继续传递给下一个事件处理器,执行对应事件的动作。在用户的应用程序中,可以包含多个事件处理器,在编写时要注意事件不要重复,否则可能会造成有些事件没有响应,有些事件却重复响应。
在第8.2.3节将演示一个抓图的示例。抓图本身比较简单,可以通过下面的代码实现:
// 读取像素信息抓图
image->readPixels(0, 0, width, height, GL_RGB,GL_UNSIGNED_BYTE);
但在OSG2x系列以后,这样简单的执行会出现问题,读者会发现使用上面代码根本抓不到图片因为此时并没有得到当前的 RC 设备,这个问题曾经在邮件列表上也讨论过,最好的解决方案就是使用osg::Camera::DrawCallback,这样可以确保在当前RC设备下执行 readPixels(),从而可以保证抓取到图片。这时写一个简单的类,继承自 osg:Camera::DrawCallback类,在开始渲染前就执行绘制回调可以很容易抓到图片。
抓图示例的代码如程序如下所示。
/******************************************* 键盘事件-抓图示例 *************************************/
osg::ref_ptr<osg::Image> image_c = new osg::Image();// 定义一个全局变量
// 得到抓图
struct CaptureDrawCallback :public osg::Camera::DrawCallback
{
CaptureDrawCallback(osg::ref_ptr<osg::Image>image)
{
_image = image;
}
~CaptureDrawCallback(){}
virtual void operator()(const osg::Camera &camera) const
{
// 得到窗口系统接口
osg::ref_ptr<osg::GraphicsContext::WindowingSystemInterface> wsi = osg::GraphicsContext::getWindowingSystemInterface();
unsigned int width, height;
// 得到分辨率
wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);
// 分配一个image
_image->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);
// 读取像素信息抓图
_image->readPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE);
}
osg::ref_ptr<osg::Image> _image;
};
// 抓图时间处理器
class ImageHandle :public osgGA::GUIEventHandler
{
public:
ImageHandle()
{
}
~ImageHandle()
{
}
// 重置handle()函数
bool ImageHandle::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
osg::ref_ptr<osgViewer::Viewer> viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
if (viewer == nullptr)
{
return false;
}
// 定义一个静态变量
static int _screenCaptureSequence = 0;
switch (ea.getEventType())
{
case osgGA::GUIEventAdapter::KEYDOWN:// 按键
{
int iValue = ea.getKey();
if (ea.getKey() == 'c' || ea.getKey() == 'C')
{
char filename[128];
// 确定一个合理的文件名,以保证能够连续抓图
sprintf_s(filename, "D:\\ScreenShot%04d.bmp", _screenCaptureSequence);
++_screenCaptureSequence;
// 写入文件
osgDB::writeImageFile(*(image_c.get()), filename);
}
break;
}
default:
{
return false;
}
}
return true;
}
};
// 键盘事件之抓图示例
void CaptureImage_8_7(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
// 读取模型
string strDataPath = strDataFolder + "cow.osg";
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
// 设置绘制回调
viewer->getCamera()->setPostDrawCallback(new CaptureDrawCallback(image_c.get()));
viewer->setSceneData(root.get());
// 添加抓图时间
viewer->addEventHandler(new ImageHandle());
viewer->realize();
viewer->run();
}
图8-16 抓图示例截图