]> git.tdb.fi Git - libs/gl.git/blob - source/animation.cpp
Basic animation support
[libs/gl.git] / source / animation.cpp
1 #include <cmath>
2 #include <msp/datafile/collection.h>
3 #include <msp/time/units.h>
4 #include "animation.h"
5 #include "keyframe.h"
6
7 using namespace std;
8
9 #include <msp/io/print.h>
10
11 namespace Msp {
12 namespace GL {
13
14 Animation::Animation():
15         looping(false)
16 { }
17
18 void Animation::add_keyframe(const Time::TimeDelta &t, const KeyFrame &kf)
19 {
20         if(!keyframes.empty() && t<keyframes.back().time)
21                 throw invalid_argument("Animation::add_keyframe");
22
23         TimedKeyFrame tkf;
24         tkf.time = t;
25         tkf.keyframe = &kf;
26         tkf.keyframe.keep();
27         prepare_keyframe(tkf);
28         keyframes.push_back(tkf);
29 }
30
31 void Animation::prepare_keyframe(TimedKeyFrame &tkf)
32 {
33         tkf.prev = (keyframes.empty() ? 0 : &keyframes.back());
34         if(!tkf.prev)
35                 return;
36
37         tkf.delta_t = tkf.time-tkf.prev->time;
38
39         const double *m1_data = tkf.prev->keyframe->get_matrix().data();
40         const double *m2_data = tkf.keyframe->get_matrix().data();
41         for(unsigned i=0; i<3; ++i)
42         {
43                 const double *m1_col = m1_data+i*4;
44                 const double *m2_col = m2_data+i*4;
45
46                 // Compute a normalized vector halfway between the two endpoints
47                 double half[3];
48                 double len = 0;
49                 for(unsigned j=0; j<3; ++j)
50                 {
51                         half[j] = (m1_col[j]+m2_col[j])/2;
52                         len += half[j]*half[j];
53                 }
54                 len = sqrt(len);
55                 for(unsigned j=0; j<3; ++j)
56                         half[j] /= len;
57
58                 // Compute correction factors for smooth interpolation
59                 double cos_half = m1_col[0]*half[0]+m1_col[1]*half[1]+m1_col[2]*half[2];
60                 double angle = acos(cos_half);
61                 tkf.axes[i].slope = (angle ? angle/tan(angle) : 1);
62                 tkf.axes[i].scale = cos_half;
63         }
64 }
65
66 Matrix Animation::compute_matrix(const TimedKeyFrame &tkf, const Time::TimeDelta &dt) const
67 {
68         if(!dt)
69                 return tkf.keyframe->get_matrix();
70         if(!tkf.prev)
71                 throw invalid_argument("Animation::compute_matrix");
72         const TimedKeyFrame &prev = *tkf.prev;
73
74         float t = dt/tkf.delta_t;
75         float u = t*2.0f-1.0f;
76
77         double matrix[16];
78         const double *m1_data = prev.keyframe->get_matrix().data();
79         const double *m2_data = tkf.keyframe->get_matrix().data();
80         for(unsigned i=0; i<4; ++i)
81         {
82                 const double *m1_col = m1_data+i*4;
83                 const double *m2_col = m2_data+i*4;
84                 double *out_col = matrix+i*4;
85
86                 if(i<3)
87                 {
88                         /* Linear interpolation will produce vectors that fall on the line
89                         between the two endpoints, and has a higher angular velocity near the
90                         middle.  We compensate for the velocity by interpolating the angle
91                         around the halfway point and computing its tangent.  This is
92                         approximated by a third degree polynomial, scaled so that the result
93                         will be in the range [-1, 1]. */
94                         float w = (tkf.axes[i].slope+(1-tkf.axes[i].slope)*u*u)*u*0.5f+0.5f;
95
96                         /* The interpolate vectors will also be shorter than unit length.  At
97                         the halfway point the length will be equal to the cosine of half the
98                         angle, which was computed earlier.  Use a second degree polynomial to
99                         approximate. */
100                         float n = (tkf.axes[i].scale+(1-tkf.axes[i].scale)*u*u);
101
102                         for(unsigned j=0; j<3; ++j)
103                                 out_col[j] = ((1-w)*m1_col[j]+w*m2_col[j])/n;
104                 }
105                 else
106                 {
107                         for(unsigned j=0; j<3; ++j)
108                                 out_col[j] = (1-t)*m1_col[j]+t*m2_col[j];
109                 }
110         }
111
112         matrix[3] = 0;
113         matrix[7] = 0;
114         matrix[11] = 0;
115         matrix[15] = 1;
116
117         return matrix;
118 }
119
120
121 Animation::AxisInterpolation::AxisInterpolation():
122         slope(0),
123         scale(0)
124 { }
125
126
127 Animation::Iterator::Iterator(const Animation &a):
128         animation(a),
129         iter(animation.keyframes.begin()),
130         end(false)
131 { }
132
133 Animation::Iterator &Animation::Iterator::operator+=(const Time::TimeDelta &t)
134 {
135         time_since_keyframe += t;
136         while(time_since_keyframe>iter->delta_t)
137         {
138                 KeyFrameList::const_iterator next = iter;
139                 ++next;
140                 if(next==animation.keyframes.end())
141                 {
142                         if(animation.looping)
143                                 next = animation.keyframes.begin();
144                         else
145                         {
146                                 end = true;
147                                 time_since_keyframe = iter->delta_t;
148                                 break;
149                         }
150                 }
151
152                 time_since_keyframe -= iter->delta_t;
153                 iter = next;
154         }
155
156         return *this;
157 }
158
159 Matrix Animation::Iterator::get_matrix() const
160 {
161         return animation.compute_matrix(*iter, time_since_keyframe);
162 }
163
164
165 Animation::Loader::Loader(Animation &a):
166         DataFile::CollectionObjectLoader<Animation>(a, 0)
167 {
168         init();
169 }
170
171 Animation::Loader::Loader(Animation &a, Collection &c):
172         DataFile::CollectionObjectLoader<Animation>(a, &c)
173 {
174         init();
175 }
176
177 void Animation::Loader::init()
178 {
179         add("interval", &Loader::interval);
180         add("keyframe", &Loader::keyframe);
181         add("keyframe", &Loader::keyframe_inline);
182         add("looping", &Animation::looping);
183 }
184
185 void Animation::Loader::keyframe(const string &n)
186 {
187         obj.add_keyframe(current_time, get_collection().get<KeyFrame>(n));
188 }
189
190 void Animation::Loader::keyframe_inline()
191 {
192         RefPtr<KeyFrame> kf = new KeyFrame;
193         load_sub(*kf);
194
195         TimedKeyFrame tkf;
196         tkf.time = current_time;
197         tkf.keyframe = kf;
198         obj.prepare_keyframe(tkf);
199         obj.keyframes.push_back(tkf);
200 }
201
202 void Animation::Loader::interval(float t)
203 {
204         current_time += t*Time::sec;
205 }
206
207 } // namespace GL
208 } // namespace Msp