本节功能
本节创建了一个高高低低的三维的路径,在楼顶和地面之间穿梭,一个飞机沿着这个路径进行飞行。如下图所示:
请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743:
【击此打开网盘资源链接】
关键点
在上一节的基础上:第36.3节 动画-路径动画生成要点,我们很容易得出结论,其中关于路径两点之间的角度计算是个问题。
如果像上一节,是二维的,那么只需要在一个平面内计算角度就可以了,如果是三维的呢?想一想是不是就有点复杂。二维的情况下,模型是绕着Z轴旋转。而三维的情况下,可以认为是绕着任意轴旋转,大家可以看到本节的关于计算角度的代码其实并没有改变,还是这个:
osg::Quat rotate;
rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i + 1) - keyPoint->at(i));
也就是仍然的飞机默认朝向Y轴正方向,我们将其旋转到路径上的两点朝向的向量。只是这个向量并不是指向XY平面的,而是任意的指向。一个向量旋转到另一个向量,其实是:向量沿任意轴的旋转问题。我们想象有一个向量垂直于当前向量,和旋转后的向量,那么就可以认为这个垂直的向量是轴,当前向量就是绕着这个轴向量旋转到达旋转后向量的。
绕任意轴的旋转其实还真不是个容易的命题,我在这里有一篇文章大家可以详细看一下计算公式:【数学】三维向量绕任意轴的旋转公式
可以看到,旋转之后,飞机的翅膀始终是水平的。这也许不是我们想要的,我们看osganimate这个例子(只需要命令行中输入osganimate就可以运行),它的飞机翅膀有一定的角度。假如说我们要飞机沿着某个方向要旋转着前进,那么我们就要在关键点处用自己的算法来指定旋转量,而不是由上面的makeRotate公式来计算。
现实中,常常的,飞机或其它航天器的轨迹点除了位置以外,还会包含yaw, pitch, roll这三个量,其实就是旋转的量,这样值就给定了,就不需要我们计算了。
在第18.4节 OE3.1实例-导弹在地球上沿路径飞行中,我使用了较为笨拙的方法严丝合缝的实现了一个叫做GetFlyPosture的方法,可以根据起点和终点,返回旋转的三个分量pitch, yaw, roll,大家需要也可以参考一下:
void GetFlyPosture(osg::Vec3d First, osg::Vec3d Second, double& PitchAngle, double& yAngleHengGun, double& YawAngle)
所有代码
#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/NodeVisitor>
#include <osg/Transform>
#include <osg/AnimationPath>
#include <osgGA/GUIEventHandler>
#include <osgUtil/LineSegmentIntersector>
#include <osg/ShapeDrawable>
#include <osg/LineWidth>
#include <osgAnimation/BasicAnimationManager>
#include <osg/MatrixTransform>
#include <osg/io_utils>
//给定顶点,和行进的速度,创建一个路径
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)
{
//只有1个点,是形成不了路径的
if (keyPoint->size() <= 1)
{
return NULL;
}
osg::AnimationPath* animationPath = new osg::AnimationPath;
animationPath->setLoopMode(osg::AnimationPath::LOOP);
//关键点对应的时间
float t = 0.0;
//朝向
osg::Quat rotate;
for (int i = 0; i < keyPoint->size(); i++)
{
//最后一个点,就把第一个点也压进去,形成环路
if ((keyPoint->size() - 1) == i)
{
//最后一个点的朝向不计算了,就用上一个点的朝向
animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
//计算最后一个点的时间
t += ((keyPoint->at(0) - keyPoint->at(i)).length() / speed);
//第一个点的朝向还是指向第二个点
rotate.makeRotate(osg::Y_AXIS, keyPoint->at(1) - keyPoint->at(0));
animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(0), rotate));
//循环结束
break;
}
else
{
//其它点
//如果是第一个点,时间上是t=0.0, 朝向上由第一个点和第二个点的朝向决定
//计算当前点的朝向,因为我们的朝向只在一个轴上水平的变化,所以可以用简单的方法
//视口默认朝Y轴,我们要将其转到由第一个点看向第二个点的向量
rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i + 1) - keyPoint->at(i));
animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
}
//两点间的距离除以速度
t += ((keyPoint->at(i + 1) - keyPoint->at(i)).length() / speed);
}
return animationPath;
}
//创建一个红球做标志,给定圆心和半径
osg::Node* CreateSphere(osg::Vec3 center, float r)
{
osg::Geode* gnode = new osg::Geode;
osg::ShapeDrawable* sd = new osg::ShapeDrawable(new osg::Sphere(center, r));
sd->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));//红色
gnode->addDrawable(sd);
return gnode;
}
//创建线,给定顶点和红宽,
osg::Node* CreateLine(osg::Vec3Array* vertex, float lineWidth)
{
osg::Geode* gnode = new osg::Geode;
osg::Geometry* geom = new osg::Geometry;
gnode->addDrawable(geom);
//设置顶点
geom->setVertexArray(vertex);
//设置颜色
osg::Vec4Array* color = new osg::Vec4Array();
color->push_back(osg::Vec4(1.0, 0.0, 0.0, 1.0));
geom->setColorArray(color, osg::Array::BIND_OVERALL);
//设置线宽
osg::LineWidth* lw = new osg::LineWidth(lineWidth);
geom->getOrCreateStateSet()->setAttribute(lw, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
//关闭灯光显得亮一点
geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);
//设置添加为线
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, 0, vertex->size()));
return gnode;
}
osg::Group* createMark(osg::Vec3Array* keyPoint)
{
osg::Group* markGroup = new osg::Group();
//添加红球做关键点
for (int i = 0; i < keyPoint->size(); i++)
{
markGroup->addChild(CreateSphere(keyPoint->at(i), 1.0));
}
//添加线
markGroup->addChild(CreateLine(keyPoint, 1.0));
return markGroup;
}
//添加运动的飞机
osg::Node* loadFly(osg::AnimationPath* animation)
{
//用于旋转飞机,使其朝向合适
osg::MatrixTransform* mt = new osg::MatrixTransform;
osg::Node* glider = osgDB::readNodeFile("glider.osg");
glider->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
//飞机默认朝X轴负方向,我们绕Z轴转-90度,使其朝Y轴正方向
mt->setMatrix(osg::Matrix::scale(5.0, 5.0, 5.0) * osg::Matrix::rotate(osg::inDegrees(-90.0), osg::Z_AXIS));
mt->addChild(glider);
//实际的路径控制
osg::MatrixTransform* persionMT = new osg::MatrixTransform;
persionMT->setUpdateCallback(new osg::AnimationPathCallback(animation));
persionMT->addChild(mt);
return persionMT;
}
int main()
{
osgViewer::Viewer viewer;
//里面存放的是路径上的关键点
osg::Vec3Array* keyPoint = new osg::Vec3Array;
keyPoint->push_back(osg::Vec3(-5.15, -88.89, 8.0));
keyPoint->push_back(osg::Vec3(50.1688, -62.9621, 39.0));
keyPoint->push_back(osg::Vec3(79.1318, -46.6088, 15.0));
keyPoint->push_back(osg::Vec3(80.03, -26.95, 29.0));
keyPoint->push_back(osg::Vec3(81.41, -6.42, 12.0));
keyPoint->push_back(osg::Vec3(70.11, 12.47, 29.0));
keyPoint->push_back(osg::Vec3(57.51, 28.49, 12.0));
keyPoint->push_back(osg::Vec3(38.83, 45.23, 29.0));
keyPoint->push_back(osg::Vec3(6.98, 61.87, 12.0));
keyPoint->push_back(osg::Vec3(-23.46, 43.42, 29.0));
keyPoint->push_back(osg::Vec3(-36.27, 22.40, 15.0));
keyPoint->push_back(osg::Vec3(-42.94, -13.22, 29.0));
keyPoint->push_back(osg::Vec3(-60.78, -41.55, 8.0));
keyPoint->push_back(osg::Vec3(-41.95, -49.65, 29.0));
osg::Node* ceep = osgDB::readNodeFile("ceep.ive");
//场景ceep
osg::Group* root = new osg::Group;
root->addChild(ceep);
//关键点和路径的标记
root->addChild(createMark(keyPoint));
//添加运动的小人
root->addChild(loadFly(CreateAnimate(keyPoint, 3.0)));
viewer.setSceneData(root);
//viewer.addEventHandler(new MyEvent);
return viewer.run();
}