本章教程将继续使用回调和节点路径(NodePath)来检索节点的世界坐标。
本章目标:
在一个典型的仿真过程中,用户可能需要从场景中的各种车辆和人物里选择一个进行跟随。本章将介绍一种将摄像机“依附”到场景图形节点的方法。此时视口的摄像机将跟随节点的世界坐标进行放置。
----------------------------------------------------------------------
概述:
视口类包括了一系列的矩阵控制器(osgGA::MatrixManipulator)。因而提供了“驱动控制(Drive)”,“轨迹球 (Trackball)”,“飞行(Fly)”等交互方法。矩阵控制器类用于更新摄像机位置矩阵。它通常用于回应GUI事件(鼠标点击,拖动,按键,等 等)。本文所述的功能需要依赖于相机位置矩阵,并参照场景图形节点的世界坐标。这样的话,相机就可以跟随场景图形中的节点进行运动了。
为了获得场景图形中节点的世界坐标,我们需要使用节点访问器的节点路径功能来具现一个新的类。这个类将提供一种方法将自己的实例关联到场景图形,并因此提
供访问任意节点世界坐标的方法。此坐标矩阵(场景中任意节点的世界坐标)将作为相机位置的矩阵,由osgGA::MatrixManipulator实例
使用。
实现:
首先我们创建一个类,计算场景图形中的多个变换矩阵的累加结果。很显然,所有的节点访问器都会访问当前的节点路径。节点路径本质上是根节点到当前节点的所有节点列表。有了节点路径的实例之后,我们就可以使用场景图形的方法computeWorldToLocal(
osg::NodePath)来获取表达节点世界坐标的矩阵了。
这个类的核心是使用更新回调来获取某个给定节点之前所有节点的矩阵和。整个类的定义如下:
struct updateAccumulatedMatrix :
public osg::NodeCallback
{
virtual void
operator()(osg::Node* node, osg::NodeVisitor* nv)
{
matrix = osg::computeWorldToLocal(nv->getNodePath() );
traverse(node,nv);
}
osg::Matrix matrix;
};
下一步,我们需要在场景图形的更新遍历中启动回调类。因此,我们将创建一个类,其中包括一个osg::Node实例作为数据成员。此节点数据成员的更新回
调是上述updateAccumulatedMatrix类的实例,同时此节点也将设置为场景的一部分。为了读取用于描绘节点世界坐标的矩阵(该矩阵与节
点实例相关联),我们需要为矩阵提供一个“get”方法。我们还需要提供添加节点到场景图形的方法。我们需要注意的是,用户应如何将节点关联到场景中。此
节点应当有且只有一个父节点。因此,为了保证这个类的实例只有一个相关联的节点,我们还需要记录这个类的父节点。类的定义如下面的代码所示:
struct transformAccumulator
{
public:
transformAccumulator();
bool attachToGroup(osg::Group*
g);
osg::Matrix getMatrix();
protected:
osg::ref_ptr parent;
osg::Node* node;
updateAccumulatedMatrix*
mpcb;
};
类的实现代码如下所示:
transformAccumulator::transformAccumulator()
{
parent = NULL;
node = new osg::Node;
mpcb = new
updateAccumulatedMatrix();
node->setUpdateCallback(mpcb);
}
osg::Matrix
transformAccumulator::getMatrix()
{
return mpcb->matrix;
}
bool
transformAccumulator::attachToGroup(osg::Group* g)
// 注意不要在回调中调用这个函数。
{
bool success = false;
if (parent != NULL)
{
int n = parent->getNumChildren();
for (int i = 0; i < n; i++)
{
if (node == parent->getChild(i) )
{
parent->removeChild(i,1);
success = true;
}
}
if (! success)
{
return success;
}
}
g->addChild(node);
return true;
}
现在,我们已经提供了类和方法来获取场景中节点的世界坐标矩阵,我们所需的只是学习如何使用这个矩阵来变换相机的位置。
osgGA::MatrixManipulator类即可提供一种更新相机位置矩阵的方法。我们可以从MatrixManipulator继承一个新的
类,以实现利用场景中某个节点的世界坐标矩阵来改变相机的位置。为了实现这一目的,这个类需要提供一个数据成员,作为上述的
accumulateTransform实例的句柄。新建类同时还需要保存相机位置矩阵的相应数据。
MatrixManipulator类的核心是“handle”方法。这个方法用于检查选中的GUI事件并作出响应。对我们的类而言,唯一需要响应的
GUI事件就是“FRAME”事件。在每一个“帧事件”中,我们都需要设置相机位置矩阵与transformAccumulator矩阵的数值相等。我们
可以在类的成员中创建一个简单的updateMatrix方法来实现这一操作。由于我们使用了虚基类,因此某些方法必须在这里进行定义(矩阵的设置及读
取,以及反转)。综上所述,类的实现代码如下所示:
class followNodeMatrixManipulator :
public osgGA::MatrixManipulator
{
public:
followNodeMatrixManipulator(
transformAccumulator* ta);
bool handle (const
osgGA::GUIEventAdapter&ea,
osgGA::GUIActionAdapter&aa);
void updateTheMatrix();
virtual void setByMatrix(const
osg::Matrixd& mat) {theMatrix = mat;}
virtual void
setByInverseMatrix(const osg::Matrixd&mat) {}
virtual osg::Matrixd
getInverseMatrix() const;
virtual osg::Matrixd
getMatrix() const;
protected:
~followNodeMatrixManipulator()
{}
transformAccumulator*
worldCoordinatesOfNode;
osg::Matrixd theMatrix;
};
The class implementation is as
follows:
followNodeMatrixManipulator::followNodeMatrixManipulator(
transformAccumulator* ta)
{
worldCoordinatesOfNode = ta;
theMatrix = osg::Matrixd::identity();
}
void followNodeMatrixManipulator::updateTheMatrix()
{
theMatrix =
worldCoordinatesOfNode->getMatrix();
}
osg::Matrixd followNodeMatrixManipulator::getMatrix() const
{
return theMatrix;
}
osg::Matrixd followNodeMatrixManipulator::getInverseMatrix()
const
{
// 将矩阵从Y轴向上旋转到Z轴向上
osg::Matrixd m;
m = theMatrix *
osg::Matrixd::rotate(-M_PI/2.0, osg::Vec3(1,0,0) );
return m;
}
void followNodeMatrixManipulator::setByMatrix(const
osg::Matrixd& mat)
{
theMatrix = mat;
}
void followNodeMatrixManipulator::setByInverseMatrix(const
osg::Matrixd& mat)
{
theMatrix =
mat.inverse();
}
bool
followNodeMatrixManipulator::handle
(const osgGA::GUIEventAdapter&ea,
osgGA::GUIActionAdapter&aa)
{
switch(ea.getEventType())
{
case (osgGA::GUIEventAdapter::FRAME):
{
updateTheMatrix();
return false;
}
}
return false;
}
上述的所有类都定义完毕之后,我们即可直接对其进行使用。我们需要声明一个transformAccumulator类的实例。该实例应当与场景图形中的某个节点相关联。然后,我们需要声明nodeFollowerMatrixManipulator类的实例。此操纵器类的构造函数将获取transformAccumulator实例的指针。最后,将新的矩阵操纵器添加到视口操控器列表中。上述步骤的实现如下:
//
设置场景和视口(包括tankTransform节点的添加)……
transformAccumulator*
tankWorldCoords = new transformAccumulator();
tankWorldCoords->attachToGroup(tankTransform);
followNodeMatrixManipulator* followTank =
new
followNodeMatrixManipulator(tankWorldCoords);
osgGA::KeySwitchMatrixManipulator *ksmm = new
osgGA::KeySwitchMatrixManipulator();
if (!ksmm)