二、节点的更新回调和位置姿态控制

(最近更新:2006314)

原文地址:http://www.flmnware.com/


目的:通过本教程,学会将自定义节点添加入场景图,并控制节点在每一帧的运动。





1、添加自定义节点

OpenSceneGraph提供的节点类(Node及其子类)实现了通用的场景图结构,但是不可能提供每一个用户需要的类,比如我要做一个飞行模拟程序,OpenSceneGraph不会给我提供一个Aircraft类。所以要实现自己的应用程序逻辑,就要自定义自己的类,这个类以OpenSceneGraph提供的类为基类,并能添加到场景图中,实现自己的特定功能。

一个飞机的运动,最简单情况是我们要考虑飞机的位置和姿态,而要想在OpenSceneGraph里控制一个模型的位置、旋转或缩放,需要在这个节点上面添加一个Transform的子类,这要用到矩阵运算,矩阵运算非常复杂,对于控制位置姿态这个经常用到的功能,OpenSceneGraph提供了类PositionAttitudeTransform,通过它可以方便控制位置姿态而不用理会复杂的矩阵运算。

飞机需要导入一个模型,在场景图中,我们就把这个模型加为这个类实例的儿子,这样,这个类可以这样定义,飞机类CCessna


class CCessna :

public osg::PositionAttitudeTransform

{

public:

CCessna(void);

~CCessna(void);

private:

osg::ref_ptr<osg::Node> _Model;

};

class CCessna :

public osg::PositionAttitudeTransform

{

public:

CCessna(void);

~CCessna(void);

private:

osg::ref_ptr<osg::Node> _Model;

};

class CCessna :

public osg::PositionAttitudeTransform

{

public:

CCessna(void);

~CCessna(void);

private:

osg::ref_ptr<osg::Node> _Model;

};

class CCessna :

public osg::PositionAttitudeTransform

{

public:

CCessna(void);

~CCessna(void);

private:

osg::ref_ptr<osg::Node> _Model;

};



成员变量_Model是保存模型的指针。

这样,在构造函数里,将_Model加为儿子:


CCessna::CCessna(void)

{

_Model = osgDB::readNodeFile("cessna.osg");

this->addChild(_Model.get());

}

CCessna::CCessna(void)

{

_Model = osgDB::readNodeFile("cessna.osg");

this->addChild(_Model.get());

}

CCessna::CCessna(void)

{

_Model = osgDB::readNodeFile("cessna.osg");

this->addChild(_Model.get());

}

CCessna::CCessna(void)

{

_Model = osgDB::readNodeFile("cessna.osg");

this->addChild(_Model.get());

}



继续使用第一个教程的源码,将这一句:


osg::Node* node = osgDB::readNodeFile("cessna.osg");

viewer.setSceneData(node);

osg::Node* node = osgDB::readNodeFile("cessna.osg");

viewer.setSceneData(node);

osg::Node* node = osgDB::readNodeFile("cessna.osg");

viewer.setSceneData(node);

osg::Node* node = osgDB::readNodeFile("cessna.osg");

viewer.setSceneData(node);



改为:


CCessna* cessna = new Ccessna();

viewer.setSceneData(cessna);

CCessna* cessna = new Ccessna();

viewer.setSceneData(cessna);

CCessna* cessna = new Ccessna();

viewer.setSceneData(cessna);

CCessna* cessna = new Ccessna();

viewer.setSceneData(cessna);



就能看到效果了。


2、给节点添加更新回调并控制节点运动

为了控制飞机的运动,我们应该在每一帧根据速度等条件计算飞机的位置,并更新飞机节点,但是我们的更新代码应该加在哪里呢?

在程序的主循环里,可以看到这句:


viewer.update();

viewer.update();

viewer.update();

viewer.update();



在这个方法里,OpenSceneGraph会遍历场景图,调用每个节点的更新回调,我们只要把自己代码放在更新回调里,就能保证每帧代码得到运行,也就能实现我们要求的功能了。

OpenSceneGraph中,每个回调都是类NodeCallback的子类,我们只需要继承它,就能实现功能了。为了保证飞机更新的时候能得到需要的足够信息,又不让飞机的内部实现暴露于外,我们让更新回调只进行一个转手的操作,而实际的更新函数放在CCessna类里。

更新回调代码如下:


class CCessnaUpdateCallback :

public osg::NodeCallback

{

virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)

{

CCessna* cessna = dynamic_cast<CCessna*>(node);

if(cessna != NULL)

{

cessna->update();

}

}

};

class CCessnaUpdateCallback :

public osg::NodeCallback

{

virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)

{

CCessna* cessna = dynamic_cast<CCessna*>(node);

if(cessna != NULL)

{

cessna->update();

}

}

};

class CCessnaUpdateCallback :

public osg::NodeCallback

{

virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)

{

CCessna* cessna = dynamic_cast<CCessna*>(node);

if(cessna != NULL)

{

cessna->update();

}

}

};

class CCessnaUpdateCallback :

public osg::NodeCallback

{

virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)

{

CCessna* cessna = dynamic_cast<CCessna*>(node);

if(cessna != NULL)

{

cessna->update();

}

}

};



这个类覆盖了基类对操作符()的重载,所以说它实际上是一个函数对象,因为这个重载是通用的,在函数里我们要先动态转换成 CCessna 类的指针,然后调用她的更新成员函数。

为了让这个回调起作用,不要忘了在 CCessna 类的构造函数里设置更新回调:


this->setUpdateCallback(new CCessnaUpdateCallback());

this->setUpdateCallback(new CCessnaUpdateCallback());

this->setUpdateCallback(new CCessnaUpdateCallback());

this->setUpdateCallback(new CCessnaUpdateCallback());



下面设计让飞机绕圈飞行,同时让飞机横滚,代码都是写计算操作,需要注意的是PositionAttitudeTransform类提供的设置姿态的方法不是使用通常习惯的HPR为参数,而是一个四元数,关于四元数数学请参考相关书籍,但是HPR向四元数的转换还是很直接的,这就是我们的setRotation方法:


void

CCessna::setRotation(osg::Vec3& rot)

{

osg::Quat q(rot.x(), osg::Vec3(1.0f, 0.0f, 0.0f),

rot.y(), osg::Vec3(0.0f, 1.0f, 0.0f),

rot.z(), osg::Vec3(0.0f, 0.0f, 1.0f));

this->setAttitude(q);

}

void

CCessna::setRotation(osg::Vec3& rot)

{

osg::Quat q(rot.x(), osg::Vec3(1.0f, 0.0f, 0.0f),

rot.y(), osg::Vec3(0.0f, 1.0f, 0.0f),

rot.z(), osg::Vec3(0.0f, 0.0f, 1.0f));

this->setAttitude(q);

}

void

CCessna::setRotation(osg::Vec3& rot)

{

osg::Quat q(rot.x(), osg::Vec3(1.0f, 0.0f, 0.0f),

rot.y(), osg::Vec3(0.0f, 1.0f, 0.0f),

rot.z(), osg::Vec3(0.0f, 0.0f, 1.0f));

this->setAttitude(q);

}

void

CCessna::setRotation(osg::Vec3& rot)

{

osg::Quat q(rot.x(), osg::Vec3(1.0f, 0.0f, 0.0f),

rot.y(), osg::Vec3(0.0f, 1.0f, 0.0f),

rot.z(), osg::Vec3(0.0f, 0.0f, 1.0f));

this->setAttitude(q);

}



update方法里,通过设置位置和旋转来控制飞机运动,具体请查看代码。


3、完善程序

由于Viewer类在程序开始会根据模型的包围盒调整视点初始位置,这样能保证程序开始能看到模型,但是因为我们的模型是运动的,运行一点之后飞机会飞出视线,所以我们给飞机提供一个场地,这个场地就是一个正方体,代码如下:


osg::Group* root = new osg::Group();

osg::Geode* geode = new osg::Geode();

geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0f, 0.0f, -20.0f), 200.0f, 200.0f, 2.0f)));

root->addChild(geode);

CCessna* cessna = new CCessna();

root->addChild(cessna);

viewer.setSceneData(root);

osg::Group* root = new osg::Group();

osg::Geode* geode = new osg::Geode();

geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0f, 0.0f, -20.0f), 200.0f, 200.0f, 2.0f)));

root->addChild(geode);

CCessna* cessna = new CCessna();

root->addChild(cessna);

viewer.setSceneData(root);

osg::Group* root = new osg::Group();

osg::Geode* geode = new osg::Geode();

geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0f, 0.0f, -20.0f), 200.0f, 200.0f, 2.0f)));

root->addChild(geode);

CCessna* cessna = new CCessna();

root->addChild(cessna);

viewer.setSceneData(root);

osg::Group* root = new osg::Group();

osg::Geode* geode = new osg::Geode();

geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0f, 0.0f, -20.0f), 200.0f, 200.0f, 2.0f)));

root->addChild(geode);

CCessna* cessna = new CCessna();

root->addChild(cessna);

viewer.setSceneData(root);



这里用到了Geode 类和Drawable类的子类ShapeDrawableGeode 是场景图的叶子节点,所有的几何体都包含在 Geode 里,而一个 Geode 里可以有多个Drawable的子类,我们看到的图像真正是Drawable画的,这个后面在讨论。



总结:要想实现自己的功能,加入自己的代码,就要定义自己的节点类,由于OpenSceneGraph使用了组合设计模式,我们自己定义的类在OpenSceneGraph看来和它自己的类是一样的,OpenSceneGraph的这种场景图结构,基本上是业界的一种标准,很多程序都实现了类似的结构。



作者:flmn

网站:http://www.flmnware.com

作者:flmn

网站:http://www.flmnware.com

作者:flmn

网站:http://www.flmnware.com

作者:flmn

网站:http://www.flmnware.com



评论

该日志第一篇评论

发表评论

评论也有版权!