#include <msp/datafile/collection.h>
#include <msp/time/units.h>
#include "animation.h"
+#include "armature.h"
+#include "error.h"
#include "keyframe.h"
+#include "pose.h"
using namespace std;
-#include <msp/io/print.h>
-
namespace Msp {
namespace GL {
Animation::Animation():
+ armature(0),
looping(false)
{ }
+// Avoid synthesizing ~RefPtr in files including animation.h
+Animation::~Animation()
+{ }
+
+void Animation::set_armature(const Armature &a)
+{
+ armature = &a;
+}
+
void Animation::add_keyframe(const Time::TimeDelta &t, const KeyFrame &kf)
{
if(!keyframes.empty() && t<keyframes.back().time)
throw invalid_argument("Animation::add_keyframe");
- TimedKeyFrame tkf;
+ TimedKeyFrame tkf(*this);
tkf.time = t;
tkf.keyframe = &kf;
tkf.keyframe.keep();
keyframes.push_back(tkf);
}
+void Animation::set_looping(bool l)
+{
+ looping = l;
+}
+
void Animation::prepare_keyframe(TimedKeyFrame &tkf)
{
tkf.prev = (keyframes.empty() ? 0 : &keyframes.back());
if(!tkf.prev)
return;
- tkf.delta_t = tkf.time-tkf.prev->time;
+ tkf.prepare();
+}
+
+
+Animation::AxisInterpolation::AxisInterpolation():
+ slope(0),
+ scale(0)
+{ }
- const double *m1_data = tkf.prev->keyframe->get_matrix().data();
- const double *m2_data = tkf.keyframe->get_matrix().data();
+Animation::AxisInterpolation::AxisInterpolation(const double *axis1, const double *axis2)
+{
+ // Compute a normalized vector halfway between the two endpoints
+ double half[3];
+ double a1_len = 0;
+ double h_len = 0;
for(unsigned i=0; i<3; ++i)
{
- const double *m1_col = m1_data+i*4;
- const double *m2_col = m2_data+i*4;
-
- // Compute a normalized vector halfway between the two endpoints
- double half[3];
- double len = 0;
- for(unsigned j=0; j<3; ++j)
- {
- half[j] = (m1_col[j]+m2_col[j])/2;
- len += half[j]*half[j];
- }
- len = sqrt(len);
- for(unsigned j=0; j<3; ++j)
- half[j] /= len;
-
- // Compute correction factors for smooth interpolation
- double cos_half = m1_col[0]*half[0]+m1_col[1]*half[1]+m1_col[2]*half[2];
- double angle = acos(cos_half);
- tkf.axes[i].slope = (angle ? angle/tan(angle) : 1);
- tkf.axes[i].scale = cos_half;
+ half[i] = (axis1[i]+axis2[i])/2;
+ a1_len += axis1[i]*axis1[i];
+ h_len += half[i]*half[i];
}
+
+ // Compute correction factors for smooth interpolation
+ double cos_half = (axis1[0]*half[0]+axis1[1]*half[1]+axis1[2]*half[2])/sqrt(a1_len*h_len);
+ double angle = acos(cos_half);
+ slope = (angle ? angle/tan(angle) : 1);
+ scale = cos_half;
}
-Matrix Animation::compute_matrix(const TimedKeyFrame &tkf, const Time::TimeDelta &dt) const
+
+Animation::MatrixInterpolation::MatrixInterpolation():
+ matrix1(0),
+ matrix2(0)
+{ }
+
+Animation::MatrixInterpolation::MatrixInterpolation(const Matrix &m1, const Matrix &m2):
+ matrix1(&m1),
+ matrix2(&m2)
{
- if(!dt)
- return tkf.keyframe->get_matrix();
- if(!tkf.prev)
- throw invalid_argument("Animation::compute_matrix");
- const TimedKeyFrame &prev = *tkf.prev;
+ const double *m1_data = matrix1->data();
+ const double *m2_data = matrix2->data();
+ for(unsigned i=0; i<3; ++i)
+ axes[i] = AxisInterpolation(m1_data+i*4, m2_data+i*4);
+}
- float t = dt/tkf.delta_t;
+Matrix Animation::MatrixInterpolation::get(float t) const
+{
float u = t*2.0f-1.0f;
double matrix[16];
- const double *m1_data = prev.keyframe->get_matrix().data();
- const double *m2_data = tkf.keyframe->get_matrix().data();
for(unsigned i=0; i<4; ++i)
{
- const double *m1_col = m1_data+i*4;
- const double *m2_col = m2_data+i*4;
+ const double *m1_col = matrix1->data()+i*4;
+ const double *m2_col = matrix2->data()+i*4;
double *out_col = matrix+i*4;
if(i<3)
around the halfway point and computing its tangent. This is
approximated by a third degree polynomial, scaled so that the result
will be in the range [-1, 1]. */
- float w = (tkf.axes[i].slope+(1-tkf.axes[i].slope)*u*u)*u*0.5f+0.5f;
+ float w = (axes[i].slope+(1-axes[i].slope)*u*u)*u*0.5f+0.5f;
/* The interpolate vectors will also be shorter than unit length. At
the halfway point the length will be equal to the cosine of half the
angle, which was computed earlier. Use a second degree polynomial to
approximate. */
- float n = (tkf.axes[i].scale+(1-tkf.axes[i].scale)*u*u);
+ float n = (axes[i].scale+(1-axes[i].scale)*u*u);
for(unsigned j=0; j<3; ++j)
out_col[j] = ((1-w)*m1_col[j]+w*m2_col[j])/n;
}
-Animation::AxisInterpolation::AxisInterpolation():
- slope(0),
- scale(0)
+Animation::TimedKeyFrame::TimedKeyFrame(const Animation &a):
+ animation(a),
+ prev(0)
{ }
+void Animation::TimedKeyFrame::prepare()
+{
+ delta_t = time-prev->time;
+ matrix = MatrixInterpolation(prev->keyframe->get_matrix(), keyframe->get_matrix());
+ if(animation.armature)
+ {
+ unsigned max_index = animation.armature->get_max_link_index();
+ pose_matrices.resize(max_index+1);
+ const Pose *pose1 = prev->keyframe->get_pose();
+ const Pose *pose2 = keyframe->get_pose();
+ static Matrix identity;
+ for(unsigned i=0; i<=max_index; ++i)
+ {
+ const Matrix &matrix1 = (pose1 ? pose1->get_link_matrix(i) : identity);
+ const Matrix &matrix2 = (pose2 ? pose2->get_link_matrix(i) : identity);
+ pose_matrices[i] = MatrixInterpolation(matrix1, matrix2);
+ }
+ }
+}
+
Animation::Iterator::Iterator(const Animation &a):
animation(a),
Matrix Animation::Iterator::get_matrix() const
{
- return animation.compute_matrix(*iter, time_since_keyframe);
+ if(!iter->prev)
+ return iter->keyframe->get_matrix();
+
+ return iter->matrix.get(time_since_keyframe/iter->delta_t);
+}
+
+Matrix Animation::Iterator::get_pose_matrix(unsigned link) const
+{
+ if(!animation.armature)
+ throw invalid_operation("Animation::Iterator::get_pose_matrix");
+ if(link>animation.armature->get_max_link_index())
+ throw out_of_range("Animation::Iterator::get_pose_matrix");
+
+ if(!iter->prev)
+ {
+ if(const Pose *pose = iter->keyframe->get_pose())
+ return pose->get_link_matrix(link);
+ else
+ return Matrix();
+ }
+
+ // We must redo the base point correction since interpolation throws if off
+ Matrix result = iter->pose_matrices[link].get(time_since_keyframe/iter->delta_t);
+ const Vector3 &base = animation.armature->get_link(link).get_base();
+ Vector3 new_base = result*base;
+ result = Matrix::translation(base.x-new_base.x, base.y-new_base.y, base.z-new_base.z)*result;
+ return result;
}
void Animation::Loader::init()
{
+ add("armature", &Animation::armature);
add("interval", &Loader::interval);
add("keyframe", &Loader::keyframe);
add("keyframe", &Loader::keyframe_inline);
void Animation::Loader::keyframe_inline()
{
RefPtr<KeyFrame> kf = new KeyFrame;
- load_sub(*kf);
+ if(coll)
+ load_sub(*kf, get_collection());
+ else
+ load_sub(*kf);
- TimedKeyFrame tkf;
+ TimedKeyFrame tkf(obj);
tkf.time = current_time;
tkf.keyframe = kf;
obj.prepare_keyframe(tkf);