osgGA库中所有漫游器的基类是CameraManipulator,本文就对这个类进行一个完整的解析。通过之前的分析(详细请参考这一些列的第一篇),可以知道为了实现一个自定义的漫游器,我们至少需要实现以下几个纯虚函数:
- virtual void setByMatrix(const osg::Matrixd& matrix) = 0;
- virtual void setByInverseMatrix(const osg::Matrixd& matrix) = 0;
- virtual osg::Matrixd getMatrix() const = 0;
- virtual osg::Matrixd getInverseMatrix() const = 0;
这其中最重要的是getInverseMatrix的实现,因为它会在更新遍历中被场景相机调用。
下面我们看一看CameraManipulator的数据成员:
- unsigned int _intersectTraversalMask;
- bool _autoComputeHomePosition;
- osg::Vec3d _homeEye;
- osg::Vec3d _homeCenter;
- osg::Vec3d _homeUp;
- osg::ref_ptr<CoordinateFrameCallback> _coordinateFrameCallback;
从中可以看出CameraManipulator的一个主要功能是记录漫游器在场景中的初始姿态,这个功能可以通过重写漫游器的home方法实现,在CameraManipulator中提供了它计算初始位置的一种方法,通过节点的包围盒(或包围球)来计算漫游器的位置。(在TrackballManipulator中也是用了这种方式,这样保证场景加载之后所有的场景对象都可见)具体的计算代码在computeHomePosition函数中
- void CameraManipulator::computeHomePosition(const osg::Camera *camera, bool useBoundingBox)
- {
- if (getNode())
- {
- osg::BoundingSphere boundingSphere;
- if (useBoundingBox)
- {
- osg::ComputeBoundsVisitor cbVisitor;
- getNode()->accept(cbVisitor);
- osg::BoundingBox &bb = cbVisitor.getBoundingBox();
-
- if (bb.valid()) boundingSphere.expandBy(bb);
- else boundingSphere = getNode()->getBound();
- }
- else
- {
- boundingSphere = getNode()->getBound();
- }
- double dist = 3.5f * boundingSphere.radius();
-
- if (camera)
- {
- double left,right,bottom,top,zNear,zFar;
- if (camera->getProjectionMatrixAsFrustum(left,right,bottom,top,zNear,zFar))
- {
- double vertical2 = fabs(right - left) / zNear / 2.;
- double horizontal2 = fabs(top - bottom) / zNear / 2.;
- double dim = horizontal2 < vertical2 ? horizontal2 : vertical2;
- double viewAngle = atan2(dim,1.);
- dist = boundingSphere.radius() / sin(viewAngle);
- }
- else
- {
- if (camera->getProjectionMatrixAsOrtho(left,right,bottom,top,zNear,zFar))
- {
- dist = fabs(zFar - zNear) / 2.;
- }
- }
- }
-
- setHomePosition(boundingSphere.center() + osg::Vec3d(0.0,-dist,0.0f),
- boundingSphere.center(),
- osg::Vec3d(0.0f,0.0f,1.0f),
- _autoComputeHomePosition);
- }
- }
从代码中可以看出,设置相机的观察矩阵是由场景的包围球得到的,如果未设置相机参数,那么视点到观察点的距离直接设置为包围球的3.5倍,否则按平行投影还是透视投影进行计算。平行投影的计算比较简单,去远平面和近平面差值的1/2,透视投影计算方式如下:
该图是一个截面的效果,代码中比较了水平和垂直方向的值,以较小的值计算来保证整个包围盒位于平截体之中,否则会导致水平或者垂直方向有一部分与包围球相交而不是相切或者相离。
最后设置了相机的观察矩阵,通过视点、中心点和向上的向量确定,通过设置可以知道,视点是位于y轴负方向,并且朝向y轴正向。
关于osg的坐标系统有必要解释一下:
OpenGL的世界坐标轴向可以看做是:x轴向右,y轴向上,z轴向屏幕外。在osg中实际上也是一样的,只不过漫游器在设置视点时把视点设置在了y轴负方向并朝向y轴正向,导致这二者看起来坐标系统不一致。 感觉像是OpenGL坐标系统沿着x轴逆时针翻转90度。并且osg提供的模型数据的顶点坐标也都遵循这一原则,最终让使用者感觉osg的坐标系统是 x轴向右,y轴向屏幕里,z轴朝上
osgGA::CameraManipulator中提到了另一个函数getCoordinateFrame,这个函数是用来获取相机坐标系框架,通过它可以得到当前相机坐标系的坐标轴向。通过osgGA::CameraManipulator提供的另外三个函数:
- osg::Vec3d getSideVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(0,0),cf(0,1),cf(0,2)); }
- osg::Vec3d getFrontVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(1,0),cf(1,1),cf(1,2)); }
- osg::Vec3d getUpVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(2,0),cf(2,1),cf(2,2)); }
可以得到当前相机坐标的右、前、上的方向,可以方便地计算角色向前移动、向右移动、旋转等操作。