osg操控器之动画路径操控器osgGA::AnimationPathManipulator分析
目录
1. 前言
2. 示例代码
3. 动画路径操控器源码分析
3.1. 构造函数
3.2. home函数
3.3. handle函数
3.3.1 帧事件处理
3.3.2. 按键事件处理
4. 主要接口说明
1. 前言
osg官方提供了很多操控器,在源码目录下的src\osgGA目录下,cpp文件名含有Manipulator的都是操控器,每个这样的cpp表示一种类型的操控器。其中AnimationPathManipulator.cpp文件实现了动画路径操控器。
所谓操控器是指:操控相机运动,从而实现场景视图矩阵变化,反映到观察场景用户的感官上就是:人眼感觉场景变化了,但现实中的物体是没变化的,只是相机方位或距离远近在改变,从而给人视觉上的感觉而已。
其它操控器参见如下博文:
osg操控器之键盘切换操控器osgGA::KeySwitchMatrixManipulator。
2. 示例代码
如下用动画路径操控器写的例子,实现奶牛根据预先设定好的路径进行动画动作:
#include<osgViewer/Viewer>
#include<osgGA/AnimationPathManipulator>
#include<osgDB/readFile>
int main(int argc, char* argv[])
{
osg::ArgumentParser arguments(&argc, argv);
osgViewer::Viewer viewer(arguments);
auto pNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)");
if (nullptr == pNode)
{
OSG_WARN << "file not exist!\r\n";
return 1;
}
auto pAnimationMani = new osgGA::AnimationPathManipulator("AnimationPath.txt");
viewer.setCameraManipulator(pAnimationMani);
viewer.setSceneData(pNode);
return viewer.run();
}
结果如下:
图1
按键盘p键,停止动画;再按p键又开启动画。按键盘(键,则减慢动画速率到原速率的1.1倍、按键盘)键,则加快动画速率到原速率的1.1倍。
3. 动画路径操控器源码分析
3.1. 构造函数
osg动画路径操控器是osgGA::AnimationPathManipulator类实现的。该类有两个构造函数,分别如下:
AnimationPathManipulator::AnimationPathManipulator(osg::AnimationPath* animationPath)
{
..... // 为节省篇幅,代码略。
}
AnimationPathManipulator::AnimationPathManipulator( const std::string& filename )
{
..... // 为节省篇幅,其它代码略。
_animationPath->read(in);
}
第1个构造函数参数是指向动画路径对象的指针。动画路径用osg::AnimationPath类表达,该类封装时间变化和模型变换的对应关系,被用来更新相机位置和模型位置。
第2个构造函数参数是指向包含动画路径对象参数的文件名。动画路径对象参数文件的格式是一般的ASCII文本文件,每行8个float数据,每个数据以空格隔开即可,这8个数值按如下格式组织:
time px py pz ax ay az aw
其中time表示动画帧所在的时刻,以秒为单位,该时刻是距离动画开始时逝去的秒数;px、 py、 pz分别表示模型在笛卡尔坐标系下的世界坐标; ax、ay、az、aw以四元组形式表示模型方向、姿态。本文第2节中的AnimationPath.txt是一个自己制作的动画路径对象参数文件,内容如下:
0.2 1.0 1.0 1.0 0.5 1.0 1.5 2.0
0.5 2.0 2.0 2.0 1.0 1.5 2.0 2.5
0.8 3.0 3.0 3.0 1.5 2.0 2.5 3.0
1.2 4.0 4.0 4.0 2.0 2.5 3.0 3.5
1.6 5.0 5.0 5.0 2.5 3.0 3.5 4.0
2.0 6.0 6.0 6.0 3.0 3.5 4.0 4.5
当调用第2个构造函数时,会通过调用如下代码:
_animationPath->read(in);
将上述文件每行中的8个数值会成为动画路径对象的ControlPoint点(动画帧控制点),read实现如下:
void AnimationPath::read(std::istream& in)
{
while (!in.eof())
{
double time;
osg::Vec3d position;
osg::Quat rotation;
in >> time >> position.x() >> position.y() >> position.z() >> rotation.x() >> rotation.y() >> rotation.z() >> rotation.w();
if(!in.eof())
insert(time,osg::AnimationPath::ControlPoint(position,rotation));
}
}
void AnimationPath::insert(double time,const ControlPoint& controlPoint)
{
_timeControlPointMap[time] = controlPoint;
}
可以看到,将每行8个点读入后存入到map中(_timeControlPointMap是std::map类型),map的key是time,value是ControlPoint点(动画帧控制点)。
3.2. home函数
该类中home函数有两个,这里只讲如下home函数:
void AnimationPathManipulator::home(double currentTime)
{
if (_animationPath.valid())
{
_timeOffset = _animationPath->getFirstTime()-currentTime;
}
// reset the timing of the animation.
_numOfFramesSinceStartOfTimedPeriod=-1;
}
当启动2节的例子时,osg外层框架会调用该函数。因为事件才刚开始,所以参数currentTime一般为0。home函数的作用是:记录动画第一帧离起始时间currentTime的偏移量,将该时间偏移量保存在成员变量_timeOffset中;_numOfFramesSinceStartOfTimedPeriod表示动画运行到当前时刻的帧索引(0表示第1帧)。因为执行home时,动画还未开启,故这里将_numOfFramesSinceStartOfTimedPeriod置为-1,为后续动画的开始计数作准备。
3.3. handle函数
3.3.1 帧事件处理
所谓帧事件是每帧都会处理的事件,其用GUIEventAdapter::FRAME表示,其在handleFrame函数实现,代码如下:
void AnimationPathManipulator::handleFrame( double time )
{
osg::AnimationPath::ControlPoint cp;
double animTime = (time+_timeOffset)*_timeScale;
_animationPath->getInterpolatedControlPoint( animTime, cp );
if (_numOfFramesSinceStartOfTimedPeriod==-1)
{
_realStartOfTimedPeriod = time;
_animStartOfTimedPeriod = animTime;
}
++_numOfFramesSinceStartOfTimedPeriod;
double animDelta = (animTime-_animStartOfTimedPeriod);
if (animDelta>=_animationPath->getPeriod())
{
if (_animationCompletedCallback.valid())
{
_animationCompletedCallback->completed(this);
}
if (_printOutTimingInfo)
{
double delta = time-_realStartOfTimedPeriod;
double frameRate = (double)_numOfFramesSinceStartOfTimedPeriod/delta;
OSG_NOTICE <<"AnimatonPath completed in "<<delta<<" seconds, completing "<<_numOfFramesSinceStartOfTimedPeriod<<" frames,"<<std::endl;
OSG_NOTICE <<" average frame rate = "<<frameRate<<std::endl;
}
// reset counters for next loop.
_realStartOfTimedPeriod = time;
_animStartOfTimedPeriod = animTime;
_numOfFramesSinceStartOfTimedPeriod = 0;
}
cp.getMatrix( _matrix );
}
要理解上述代码,需借助如下的时间流逝图:
图2
其中紫色时间线的time表示handle调用以来即当前帧经过的总时间,其是以时刻0为参考点的。而实际中帧的起始时间参考点是_timeOffset,故在_timeScale为1时(即没有调节速率),当前帧时刻为:
_timeOffset + time
而帧速率可以通过按下键盘(、)键或通过接口setTimeScale还变快或变慢,故当前帧时刻还需乘以_timeScale,即为:
(time+_timeOffset)*_timeScale
if语句判断如果发现是第1帧,则记录当前实际时间(基于0时刻)和动画帧基于_timeOffset为基准的时刻,便于后面计算动画总时长。之后将帧索引加1。一旦发现当前帧和基准时间大于等于动画总周期(总时长),则表示动画完成,如果安装的动画完成回调对象有效,则调用动画完成回调对象的completed函数,则该函数可以打印出一些信息等。
3.3.2. 按键事件处理
里面的代码很简单,结合图2的时间线理解不难。需要注意是:当按p暂停后再按p继续动画时,_timeOffset的值要从当前时刻减去暂停时刻,因为这段时间内,动画根本没动。
4. 主要接口说明
如果需要调用osgGA::AnimationPathManipulator类,经常用到的接口说明如下:
void setTimeScale(double s);
double getTimeScale() const;
设置、获取时间缩放因子,即上面提到的_timeScale,该值越大,动画越快;否则越慢。
void setAnimationCompletedCallback(AnimationCompletedCallback* acc);
AnimationCompletedCallback* getAnimationCompletedCallback();
const AnimationCompletedCallback* getAnimationCompletedCallback() const ;
设置、获取动画完成后的回调对象,用于动画完成时,通知外层调用方,外层调用方收到这个通知后,可以进行处理一些事情。注意:自定义的动画回调类必须从下面AnimationCompletedCallback 继承并实现completed方法,该方法的参数为AnimationPathManipulator类对象,一般情况下为动画路径对象本身。
struct AnimationCompletedCallback : public virtual osg::Referenced
{
virtual void completed(const AnimationPathManipulator* apm) = 0;
};
可以通过如下函数:
void setPrintOutTimingInfo(bool printOutTimingInfo);
bool getPrintOutTimingInfo() const;
获取或设置是否需要输出动画的一些时间线信息,如:动画帧总数、动画平均帧率、动画总时长等。