转载自:http://lzchenheng.blog.163.com/blog/static/8383353620106710534514/
osg操纵器是我们实现场景漫游的主要手段,osg提供了驾驶操纵器、飞行操纵器、轨迹球操纵器等,这些操纵器的基类为osgGA::MatrixManipulator,有时为了漫游的需要,我们需要编写自己的操纵器,要想编写好自己的操纵器,必须对osg操纵器的实现方式进行剖分,知道其实现的机理,才能有的放矢,编写一个“听话”的操纵器,下面我们从源代码入手,对操纵器的实现进行剖分。
设置操纵器的代码为:viewer->setCameraManipulator(),其中setCameraManipulator()的代码如下:
void View::setCameraManipulator(osgGA::MatrixManipulator* manipulator)
{
_cameraManipulator = manipulator;
if (_cameraManipulator.valid())
{
_cameraManipulator->setCoordinateFrameCallback(new ViewerCoordinateFrameCallback(this));
//得到场景的根节点
if (getSceneData()) _cameraManipulator->setNode(getSceneData());
//得到当前事件适配器
osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent();
//执行home
_cameraManipulator->home(*dummyEvent, *this);
}
}
在这段代码中,首先得到根节点,调用setNode()方法给操纵器设置根节点,然后调用操纵器本身的home方法,osgGA::MatrixManipulator类的home()和setNode()方法为一个虚方法,其实现在其派生类中,我们看一下TrackballManipulator类的实现方式。
我们先来看一下setNode()方法的代码:
void TrackballManipulator::setNode(osg::Node* node)
{
_node = node;
if (_node.get())
{
//得到根节点包围球的范围
const osg::BoundingSphere& boundingSphere=_node->getBound();
_modelScale = boundingSphere._radius;
}
//是否自动计算视点的位置
if (getAutoComputeHomePosition()) computeHomePosition();
}
该方法主要得到根节点的包围球范围,然后计算视点的位置。
那么我们来看一下home方法在做什么
void TrackballManipulator::home(double /*currentTime*/)
{
//自动计算
if (getAutoComputeHomePosition()) computeHomePosition();
//计算视点的位置
computePosition(_homeEye, _homeCenter, _homeUp);
_thrown = false;
}
在osgGA::MatrixManipulator类中,相关的参数如下:
MatrixManipulator::MatrixManipulator()
{
_minimumDistance = 0.001;
_intersectTraversalMask = 0xffffffff;
//自动计算
_autoComputeHomePosition = true;
//视点的默认位置
_homeEye.set(0.0,-1.0,0.0);
_homeCenter.set(0.0,0.0,0.0);
_homeUp.set(0.0,0.0,1.0);
}
由于_autoComputeHomePosition = true,所以,首先执行 computeHomePosition()方法,该方法在MatrixManipulator类中实现如下:
virtual void computeHomePosition()
{
if(getNode()) //如果根节点存在
{
//计算根节点的包围球
const osg::BoundingSphere& boundingSphere=getNode()->getBound();
//根据根节点包围球的大小,设置视点位置
setHomePosition(boundingSphere._center+osg::Vec3( 0.0,-3.5f * boundingSphere._radius,0.0f),
boundingSphere._center,
osg::Vec3(0.0f,0.0f,1.0f),
_autoComputeHomePosition);
}
}
setHomePosition()方法的实现如下:
virtual void setHomePosition(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up,
bool autoComputeHomePosition=false)
{
setAutoComputeHomePosition(autoComputeHomePosition);
_homeEye = eye;
_homeCenter = center;
_homeUp = up;
}
下面我们再看一下computePosition()方法的实现:
void TrackballManipulator::computePosition(const osg::Vec3& eye,const osg::Vec3& center,const osg::Vec3& up)
{
//生成观察坐标系
osg::Vec3 lv(center-eye);
osg::Vec3 f(lv);
f.normalize();
osg::Vec3 s(f^up);
s.normalize();
osg::Vec3 u(s^f);
u.normalize();
//建立观察旋转矩阵
osg::Matrix rotation_matrix( s[0], u[0], -f[0], 0.0f,
s[1], u[1], -f[1], 0.0f,
s[2], u[2], -f[2], 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
_center = center;
_distance = lv.length();
_rotation = rotation_matrix.getRotate().inverse();
}
从以上的代码可以看出,要编写一个好的操纵器,必须首先重载setNode()和home()方法,根据根节点的包围球,确定视点的初始位置,然后,根据视点的初始位置和用户的操作(移动、旋转等操作),重载getInverseMatrix()和getMatrix()方法,构建观察矩阵或物体的位置姿态矩阵,这两个矩阵互为逆矩阵。
场景在更新遍历时,不再调用操纵器的setCameraManipulator()方法,该方法只能在初始时设置,场景在每次更新遍历时,只调用getInverseMatrix()方法获取观察矩阵,更新相机的姿态、位置,实现场景的漫游,如下代码所示:
void Viewer::updateTraversal()
{
...........
if (_cameraManipulator.valid())
{
setFusionDistance( getCameraManipulator()->getFusionDistanceMode(),
getCameraManipulator()->getFusionDistanceValue() );
//得到操纵器的观察矩阵,更新相机
_camera->setViewMatrix(_cameraManipulator->getInverseMatrix());
}
...............................
}