一个非常绚丽和真实的动画会用一个引擎让人刮目相看。可以这么说,一个好的动画引擎集成了前面讲到的所有东西,在游戏或一些演示中,动画的渲染会非常重要,但也会添加更多的娱乐与互动。目前,骨骼动画是一项比较高级的动画技术,它的显示效果也非常逼真。同样,三维声音同样可以产生非常强悍的立体效果。
本章将主要介绍OSG本身所包含的路径动画及帧动画,以及由第三方库osgCal支持的Cal3D动画。同时,也会对三维声音进行介绍,会用到目前效果非常不错的封装OpenAL的第三方库osgAL。
路径动画
如果读者耐心地读到这里,已经对路径动画不陌生了。因为前面很多处已经涉及路径动画,在很多示例中也用到了路径动画。路径动画就是按一定的插值方式生成路径,物体对象按照生成的路径或预先指定的路径来完成相应的动作的动画。
在OSG中管理路径动画的核心类主要有osg::AnimationPath类和osg::AnimationPathCallback类下面分别进行详细说明。
osg::AnimationPath类
osg::AnimationPath 类直接继承自 osg::Object基类,继承关系图如图10-1所示
图10-1 osg::AnimationPath 的继承关系图
osg::AnimationPath类封装了对动画路径的一系列操作,如动画模式、动画路径的导入与导出动画的时间、动画的关键点及插值生成路径等。
路径动画默认有以下3种动画式:
- enum LoopMode
- {
- SWING, // 单摆
- LOOP, // 循环
- NO_LOOPING// 非循环
- }
可以调用下面的方式来得到或设置动画模式
- void setLoopMode(LoopMode lm);
- LoopMode getLoopMode()const;
路径动画的时间获取是必要的,因为动画的结束可能会触发一个新的动作或场景。在OSG中并没有设置判断动画结束的函数,但可以设计一个计时器,从动画开始起开始计时,根据动画持续的时间长短来判断动画是否结束。得到动画持续的时间函数为:
- double getFirstTime() const;
- double getLastTime() const;
有时需要在场景中显示当前路径物体动画所在点的位置,这时可以通过设置一个位置变换节点来获取位置。其实,在路径里面也有相关的函数,例如:
- virtual bool getInterpolatedControlPoint(double time,ControlPoint &controlPoint)const;
通过上面函数可以得到在路径动画的任意时刻当前路径所在的点的位置的坐标。
在实际应用中,路径的导入或导出都是非常重要的。因为很多时候都是预先定义好路径,然后导出应用程序再执行路径。在类的成员函数中,有路径导入和导出的函数,导入或导出使用的是 C++的操作:
- void read(std::istream &in);
- void write(std::ostream &out);
- void write(TimeControlPointMap::const_iterator itr,std::ostream &out)const;
当读者打开一个.path 文件时,会发现其中主要有 7列,依次为时间、坐标位置和旋转矩阵读取动画路径的源代码如下:
- void AnimationPath::read(std::istream &in)
- {
- while (!in.efo())
- {
- // 时间
- 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::write(std::ostream &fout)const
- {
- // 设置精度
- int prec = fout.precision();
- fout.precision(15);
- // 得到时间控制点列表
- const TimeControlPointMap &tcpm = getTimeControlPointMap();
- for (TimeControlPointMap::const_iterator tcpmitr = tcpm.begin(); tcpmitr != tcpm.end(); ++tcpmitr)
- {
- // 输出时间与控制点列表
- write(tcpmitr, Font);
- }
- // 恢复精度
- font.precision(prec);
- }
通过上面简单源代码的阅读,对于路径动画,相信读者已经能够理解了。值得注意的一个问题是:写入时要尽可能达到高精度,如果精度不够,可能会造成不连贯的现象。
osg::AnimationPathCallback类
osg::AnimationPathCallback类继承自osg::NodeCallback类,继承关系图如图10-2所示:
图10-2 osg::AnimationPathCallback的继承关系图
从继承关系图中可以看出,osg::AnimationPathCallback 继承自osgNodeCallback 类,所以它有普通回调的基本方法,同时启用回调可以用 setUpdateCallback()函数来实现。osg;:AnimationPathCallback类封装了对osg::AnimationPath的管理,如时间偏移、动画执行度和染暂停状态等。
动画的执行速度通过下面的函数来控制或得到:
- void setTimeMultiplier(double multiplier);
- double getTimeMultiplier()const;
其中,默认的速度为 1.0,即为正常的执行速度.
动画的控制还包括暂停和重置等状态,由下面的函数控制。
- void reset();
- void setPause(bool pause);
- bool getPause()const;;
默认情况下,pause 为 false 状态。通过上面的函数,再加上简单的事件处理器,就可以实现对个路径动画的控制。
路径动画控制及显示示例
路径动画控制及显示示例的代码如程序清单10-1所示:
1. /* 路径动画控制事件 */
2. class AnimationEventHander :public osgGA::GUIEventHandler
3. {
4. public:
5. AnimationEventHander(osgViewer::Viewer &vr) : viewer(vr)
6. {
7.
8. }
9.
10. // 事件处理
11. virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
12. {
13. // 创建动画更新回调对象
14. osg::ref_ptr<osg::AnimationPathCallback> animationPathCallback = new osg::AnimationPathCallback();
15.
16. osg::ref_ptr<osg::Group> group = dynamic_cast<osg::Group*>(viewer.getSceneData());
17.
18. // 取得节点的动画属性
19. animationPathCallback = dynamic_cast<osg::AnimationPathCallback*>(group->getChild(0)->getUpdateCallback());
20.
21. switch (ea.getEventType())
22. {
23. case(osgGA::GUIEventAdapter::KEYDOWN):
24. {
25. if (ea.getKey() == 'p')
26. {
27. // 暂停
28. animationPathCallback->setPause(true);
29.
30. return true;
31. }
32.
33. if (ea.getKey() == 's')
34. {
35. // 开始
36. animationPathCallback->setPause(false);
37.
38. return true;
39. }
40.
41. if (ea.getKey() == 'r')
42. {
43. // 重新开始
44. animationPathCallback->reset();
45.
46. return true;
47. }
48.
49. break;
50. }
51. default:
52. break;
53. }
54.
55. return false;
56. }
57.
58. osgViewer::Viewer &viewer;
59. };
60.
61. // 创建路径
62. osg::ref_ptr<osg::AnimationPath> createAnimationPath(osg::Vec3 ¢er,
63. float radius, float looptime)
64. {
65. // 创建一个Path对象
66. osg::ref_ptr<osg::AnimationPath> animationPath = new osg::AnimationPath();
67.
68. // 设置动画模式为循环(LOOP),LOOP,循环,SWING;单摆,NO_LOOPING,不循环
69. animationPath->setLoopMode(osg::AnimationPath::LOOP);
70.
71. // 关键点数
72. int numPoint = 60;
73.
74. // 每次偏移角度
75. float yaw = 0.0;
76. float yaw_delta = 2.0f * osg::PI / (numPoint - 1.0);
77.
78. // 倾斜角度
79. float roll = osg::inDegrees(45.0);
80.
81. // 时间偏移
82. float time = 0.0;
83. float time_delta = looptime/(float(numPoint));
84.
85. for (int i = 0; i < numPoint; ++i)
86. {
87. // 关键点位置
88. osg::Vec3 position(center + osg::Vec3(sinf(yaw) * radius, cosf(yaw) * radius, 0.0f));
89.
90. // 关键点角度
91. osg::Quat rotation(osg::Quat(roll, osg::Vec3(0.0, 1.0, 0.0)) * osg::Quat(-(yaw + osg::inDegrees(90.0)),osg::Vec3(0.0,0.0,1.0)));
92.
93. // 插入Path,把关键点与时间压入星辰Path
94. animationPath->insert(time, osg::AnimationPath::ControlPoint(position, rotation));
95.
96. yaw += yaw_delta;
97. time += time_delta;
98. }
99.
100. // 返回Path
101. return animationPath.get();
102. }
103.
104. void animationPath_10_1(const string &strDataFolder);
105. void animationPath_10_1(const string &strDataFolder)
106. {
107. osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
108. osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
109. traits->x = 40;
110. traits->y = 40;
111. traits->width = 600;
112. traits->height = 480;
113. traits->windowDecoration = true;
114. traits->doubleBuffer = true;
115. traits->sharedContext = 0;
116.
117. osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
118.
119. osg::ref_ptr<osg::Camera> camera = viewer->getCamera();
120. camera->setGraphicsContext(gc.get());
121. camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
122. GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
123. camera->setDrawBuffer(buffer);
124. camera->setReadBuffer(buffer);
125.
126. osg::ref_ptr<osg::Group> root = new osg::Group();
127.
128. // 读取飞机模型
129. string strDataPath = strDataFolder + "cessna.osg";
130. osg::ref_ptr<osg::Node> cessna = osgDB::readNodeFile(strDataPath);
131.
132. strDataPath = strDataFolder + "lz.osg";
133. osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
134.
135. // 得到包围盒,来确定动画旋转中心
136. const osg::BoundingSphere &bs = cessna->getBound();
137. osg::Vec3 position = bs.center() + osg::Vec3(0.0, 0.0, 200.0);
138.
139. // 缩放比例,如果比例不当,模型会看不见
140. float size = 100.0 / bs.radius() * 0.3;
141.
142. // 创建路径
143. osg::ref_ptr<osg::AnimationPath> animationPath = new osg::AnimationPath();
144. animationPath = createAnimationPath(position, 200.0, 10.0);
145. osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform();
146.
147. // OSG确保只有STATIC数据可以进行图形渲染
148. mt->setDataVariance(osg::Object::STATIC);
149.
150. // 进行适当的变换(平移、缩放以及旋转)
151. mt->setMatrix(osg::Matrix::translate(-bs.center()) * osg::Matrix::scale(size, size, size) *
152. osg::Matrix::rotate(osg::inDegrees(-180.0), 0.0, 0.0, 1.0));
153. mt->addChild(cessna.get());
154. osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform();
155.
156. // 设置更新回调
157. pat->setUpdateCallback(new osg::AnimationPathCallback(animationPath.get(), 0.0, 1.0));
158. pat->addChild(mt.get());
159.
160. root->addChild(pat.get());
161. root->addChild(node.get());
162.
163. // 优化场景数据
164. osgUtil::Optimizer optimizer;
165. optimizer.optimize(root.get());
166.
167. viewer->setSceneData(root.get());
168. viewer->addEventHandler(new AnimationEventHander(*(viewer.get())));
169. viewer->realize();
170. viewer->run();
171. }
运行程序,截图如图10-3所示
图10-3 路径动画控制及显示示例截图