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