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

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

 

osgexample http://dis.dankook.ac.kr/lectures/med08/?page=1

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

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类的子类ShapeDrawable, Geode 是场景图的叶子节点,所有的几何体都包含在 Geode 里,而一个 Geode 里可以有多个Drawable的子类,我们看到的图像真正是Drawable画的,这个后面在讨论。

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
调函数中发布话题,你可以使用一个调函数来接收其他话题的消息,然后在调函数中发布一个新的话题。下面是一个示例代码: ```cpp #include <ros/ros.h> #include <geometry_msgs/PoseStamped.h> void callback(const geometry_msgs::PoseStamped::ConstPtr& msg) { // 创建一个新的Publisher来发布新的PoseStamped消息 ros::NodeHandle nh; ros::Publisher new_pose_pub = nh.advertise<geometry_msgs::PoseStamped>("new_pose_topic", 10); // 创建一个新的PoseStamped消息 geometry_msgs::PoseStamped new_pose_stamped; // 设置新的PoseStamped消息的header部分 new_pose_stamped.header.stamp = ros::Time::now(); new_pose_stamped.header.frame_id = "new_frame"; // 设置新的PoseStamped消息的位姿信息 new_pose_stamped.pose.position.x = msg->pose.position.x + 1.0; new_pose_stamped.pose.position.y = msg->pose.position.y + 1.0; new_pose_stamped.pose.position.z = msg->pose.position.z + 1.0; new_pose_stamped.pose.orientation = msg->pose.orientation; // 发布新的PoseStamped消息 new_pose_pub.publish(new_pose_stamped); } int main(int argc, char** argv) { // 初始化ROS节点 ros::init(argc, argv, "callback_example"); ros::NodeHandle nh; // 创建一个Subscriber来订阅原始的PoseStamped消息 ros::Subscriber pose_sub = nh.subscribe<geometry_msgs::PoseStamped>("pose_stamped_topic", 10, callback); ros::spin(); return 0; } ``` 在这个例子中,我们创建了一个调函数`callback`来接收原始的PoseStamped消息。在调函数中,我们创建了一个新的Publisher`new_pose_pub`来发布新的PoseStamped消息到`new_pose_topic`。在调函数中,我们可以访问原始消息`msg`的内容,并根据需要进行处理。在这个例子中,我们将原始消息的位置坐标加上1,并保持其姿态不变,然后发布新的PoseStamped消息。你可以根据自己的需求修改调函数中的处理逻辑和发布的话题名称。最后,在`main`函数中,我们创建了一个Subscriber来订阅原始的PoseStamped消息,并使用`ros::spin()`来保持节点的运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值