目录
本功功能
本节我们在场景中创建了3个关键点,连成一条线,一个小人会从第一个点,跑到最后一个点,如图所示:
本节的内容在网盘中,目录为/osgChina站长文集/文件中的附件/, 有附件的会根据节的编号存放在该目录:
请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743:
链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
提取码:xrf5
实现要点
路径生成
OSG用类osg::AnimationPath,其有一个方法叫做insert:
animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
- 第一个参数 t 代表路径经过当前点的时间
- 第二个参数ControlPoint包含两个量,一个是位置一个是朝向
这两个参数合在一起就是在某个时间,经过了某个点朝向某个方向。时间很好求,两个关键点的距离除以速度就可以了,经们在此实现了一个函数,叫做:
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)
它的入参osg::Vec3Array,里面放的是一系列的点,speed代表这条路径的速度。
现在要注意两个问题,一个问题是朝向怎么计算,当前场景ceep.ive是沿着XY平面展开的,所以朝向的计算主要是沿着Z轴转动的角度。默认情况下我们是头顶向Z轴,朝向Y轴正方向的,所以我们计算这个角度用了一个简单的函数:
Quqt.makeRotate(osg::Y_AXIS, keyPoint->at(i+1) - keyPoint->at(i));
因为我们知道keyPoint中的点是沿XY平面展开的,所以它的向量与Y轴的夹角就是其旋转的角度。
第二个问题是朝向的插值,
如图所示,蓝色的是压入的关键点和朝向,我们一看是合理的,在起点朝向第二个点,第二个点朝向第三个点,但是其中间的值就未必合理了,对于位置来说是插值形成的,从第一个点到第二个点中间的位置跟据时间插值来取,但是朝向也是的,如其中黄色的箭 头,它从起点的朝向,在两点之间平均的过渡到了第二个点的朝向,这个效果未必是我们想要的,也许我们想要他快跑到跟前的时候再转向。这就需要在计算路径的时候人为在转向的地方插值一个关键点。这也是我们经常做的操作。
人物加载
这个人物的动画控制是在第36.2节 动画-骨骼动画的控制,我们取其中第22个动作进行播放就得到了跑动的人,但是人的默认朝向并不是在头朝Z,看向Y,而且也太大了,我们就对其进行修改,先缩放个0.02倍,再沿X轴转90度,转完后再沿Z轴转180度,再向Z轴正方向抬高4米,才把这个人物放在地面上,而且头朝Z轴,看向Y轴的正方向,就保证了他沿着路径是在奔跑,而不是在倒着跑,头朝下跑或横着跑。
以上就是所有的关键点。
以下是所有代码
#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>
//给定顶点,和行进的速度,创建一个路径
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));
//循环结束
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_STRIP, 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;
}
//添加运动的小人,沿着animation运动
osg::Node* loadPersion(osg::AnimationPath* animation)
{
//用于旋转小人,使其默认朝向正确
osg::MatrixTransform* mt = new osg::MatrixTransform;
osg::Node* persion = osgDB::readNodeFile("Naruto.fbx");
//因为建模的时候,小人的朝向是不定的,我们要看其加载到OSG的朝向,然后把它调整到朝向Y轴,头顶Z轴,
//这是我们建立这个路径时候,计算夹角是以头顶Z轴,朝向Y轴为0度
//先缩小0.02倍,再给小人沿x轴旋转90度,再给小人沿Z轴转180度,这样其朝向就是朝向Y轴正方向了,再沿Z轴正方向提4米
mt->setMatrix(osg::Matrix::scale(0.02, 0.02, 0.02)*osg::Matrix::rotate(osg::inDegrees(90.0), osg::X_AXIS) * osg::Matrix::rotate(osg::inDegrees(180.0), osg::Z_AXIS)*osg::Matrix::translate(osg::Vec3(0.0, 0.0, 4.0)));
mt->addChild(persion);
//播放其第22个动作,是奔跑
osgAnimation::BasicAnimationManager* apc = dynamic_cast<osgAnimation::BasicAnimationManager*>(persion->getUpdateCallback());
apc->playAnimation(apc->getAnimationList().at(22));
//实际的路径控制
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(-28.22, -63.42, 1.0));
keyPoint->push_back(osg::Vec3(3.38, -32, 1.0));
keyPoint->push_back(osg::Vec3(40.34, -38.40, 1.0));
osg::Node* ceep = osgDB::readNodeFile("ceep.ive");
//场景ceep
osg::Group* root = new osg::Group;
root->addChild(ceep);
//关键点和路径的标记
root->addChild(createMark(keyPoint));
//添加运动的小人
root->addChild(loadPersion(CreateAnimate(keyPoint, 2.5)));
viewer.setSceneData(root);
return viewer.run();
}