Reimplement Animation using splines
authorMikko Rasa <tdb@tdb.fi>
Wed, 5 Jun 2019 22:02:41 +0000 (01:02 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 5 Jun 2019 22:10:14 +0000 (01:10 +0300)
Transform interpolation is now performed on individual components rather
than entire matrices.  This produces more accurate rotations and later
will allow animations to only affect some components of the transform.

Currently linear splines are used and the slope parameters are ignored.
Pose matrices are also disabled for now because I don't have any suitable
data for testing them.  The old implementation was broken in various ways
anyway.

source/animation.cpp
source/animation.h

index 6c9e179dc749dc16642b5b755f6afd7e36fecf2b..dbdd05bafd741262910b1832a10a8590dd1ce88b 100644 (file)
@@ -1,6 +1,7 @@
 #include <cmath>
 #include <msp/core/maputils.h>
 #include <msp/datafile/collection.h>
+#include <msp/interpolate/linearspline.h>
 #include "animation.h"
 #include "animationeventobserver.h"
 #include "armature.h"
@@ -58,6 +59,7 @@ void Animation::add_keyframe(const Time::TimeDelta &t, const KeyFrame &kf, float
        RefPtr<const KeyFrame> kfr(&kf);
        kfr.keep();
        add_keyframe(t, kfr, ss, es);
+       create_curves();
 }
 
 void Animation::add_keyframe(const Time::TimeDelta &t, const RefPtr<const KeyFrame> &kf, float ss, float es)
@@ -69,49 +71,87 @@ void Animation::add_keyframe(const Time::TimeDelta &t, const RefPtr<const KeyFra
        if(kf->get_pose() && armature && kf->get_pose()->get_armature()!=armature)
                throw invalid_argument("Animation::add_keyframe");
 
-       bool realloc = (keyframes.size()>=keyframes.capacity());
+       const KeyFrame::UniformMap &kf_uniforms = kf->get_uniforms();
+       for(vector<UniformInfo>::const_iterator i=uniforms.begin(); i!=uniforms.end(); ++i)
+       {
+               KeyFrame::UniformMap::const_iterator j = kf_uniforms.find(i->name);
+               if(j!=kf_uniforms.end() && j->second.size!=i->size)
+                       throw invalid_argument("Animation::add_keyframe");
+       }
 
        if(kf->get_pose() && !armature)
                armature = kf->get_pose()->get_armature();
 
-       keyframes.push_back(TimedKeyFrame());
-       TimedKeyFrame &tkf = keyframes.back();
+       TimedKeyFrame tkf;
        tkf.time = t;
        tkf.start_slope = ss;
        tkf.end_slope = es;
        tkf.keyframe = kf;
 
-       if(realloc)
-       {
-               for(unsigned i=1; i<keyframes.size(); ++i)
-                       if(keyframes[i].prev)
-                               keyframes[i].prev = &keyframes[i-1];
-       }
-       if(keyframes.size()>1 && t>(&tkf-1)->time)
-               tkf.prev = &tkf-1;
+       keyframes.push_back(tkf);
 
-       prepare_keyframe(tkf);
-}
-
-void Animation::prepare_keyframe(TimedKeyFrame &tkf)
-{
-       const KeyFrame::UniformMap &kf_uniforms = tkf.keyframe->get_uniforms();
        for(KeyFrame::UniformMap::const_iterator i=kf_uniforms.begin(); i!=kf_uniforms.end(); ++i)
        {
                bool found = false;
-               for(unsigned j=0; (!found && j<uniforms.size()); ++j)
-                       if(uniforms[j].name==i->first)
-                       {
-                               if(uniforms[j].size!=i->second.size)
-                                       throw invalid_operation("Animation::prepare_keyframe");
-                               found = true;
-                       }
+               for(vector<UniformInfo>::const_iterator j=uniforms.begin(); (!found && j!=uniforms.end()); ++j)
+                       found = (j->name==i->first);
 
                if(!found)
                        uniforms.push_back(UniformInfo(i->first, i->second.size));
        }
+}
+
+void Animation::create_curves()
+{
+       for(vector<Curve *>::iterator i=curves.begin(); i!=curves.end(); ++i)
+               delete *i;
+       curves.clear();
+
+       typedef ValueCurve<3>::Knot Knot;
+       vector<Knot> positions;
+       vector<Knot> eulers;
+       vector<Knot> scales;
+       for(vector<TimedKeyFrame>::const_iterator i=keyframes.begin(); i!=keyframes.end(); ++i)
+       {
+               positions.push_back(Knot(i->time/Time::sec, i->keyframe->get_transform().get_position()));
+               const Transform::AngleVector3 &euler = i->keyframe->get_transform().get_euler();
+               eulers.push_back(Knot(i->time/Time::sec, Vector3(euler.x.radians(), euler.y.radians(), euler.z.radians())));
+               scales.push_back(Knot(i->time/Time::sec, i->keyframe->get_transform().get_scale()));
+       }
+
+       curves.reserve(3+uniforms.size());
+       curves.push_back(new ValueCurve<3>(POSITION, positions));
+       curves.push_back(new ValueCurve<3>(EULER, eulers));
+       curves.push_back(new ValueCurve<3>(SCALE, scales));
+
+       for(vector<UniformInfo>::const_iterator i=uniforms.begin(); i!=uniforms.end(); ++i)
+       {
+               if(i->size==1)
+                       create_uniform_curve<1>(i->name);
+               else if(i->size==2)
+                       create_uniform_curve<2>(i->name);
+               else if(i->size==3)
+                       create_uniform_curve<3>(i->name);
+               else if(i->size==4)
+                       create_uniform_curve<4>(i->name);
+       }
+}
+
+template<unsigned N>
+void Animation::create_uniform_curve(const string &name)
+{
+       typedef typename ValueCurve<N>::Knot Knot;
 
-       tkf.prepare(*this);
+       vector<Knot> knots;
+       for(vector<TimedKeyFrame>::const_iterator i=keyframes.begin(); i!=keyframes.end(); ++i)
+       {
+               const KeyFrame::UniformMap &kf_uniforms = i->keyframe->get_uniforms();
+               const KeyFrame::UniformMap::const_iterator j = kf_uniforms.find(name);
+               if(j!=kf_uniforms.end())
+                       knots.push_back(Knot(i->time/Time::sec, Interpolate::SplineValue<float, N>::make(j->second.values)));
+       }
+
+       curves.push_back(new ValueCurve<N>(UNIFORM, knots));
 }
 
 void Animation::add_event(const Time::TimeDelta &t, const string &n, const Variant &v)
@@ -137,133 +177,48 @@ void Animation::set_looping(bool l)
 }
 
 
-Animation::AxisInterpolation::AxisInterpolation():
-       slope(0),
-       scale(0)
+Animation::Curve::Curve(CurveTarget t):
+       target(t)
 { }
 
-Animation::AxisInterpolation::AxisInterpolation(const float *axis1, const float *axis2)
-{
-       // Compute a normalized vector halfway between the two endpoints
-       float a1_len = 0;
-       float h_len = 0;
-       float cos_half = 0;
-       for(unsigned i=0; i<3; ++i)
-       {
-               float half_i = (axis1[i]+axis2[i])/2;
-               cos_half += axis1[i]*half_i;
-               a1_len += axis1[i]*axis1[i];
-               h_len += half_i*half_i;
-       }
-
-       // Compute correction factors for smooth interpolation
-       cos_half = min(max(cos_half/sqrt(a1_len*h_len), -1.0f), 1.0f);
-       float angle = acos(cos_half);
-       slope = (angle ? angle/tan(angle) : 1);
-       scale = cos_half;
-}
-
 
-Animation::MatrixInterpolation::MatrixInterpolation():
-       matrix1(0),
-       matrix2(0)
+template<unsigned N>
+Animation::ValueCurve<N>::ValueCurve(CurveTarget t, const vector<Knot> &k):
+       Curve(t),
+       spline(Interpolate::LinearSpline<float, N>(k))
 { }
 
-Animation::MatrixInterpolation::MatrixInterpolation(const Matrix &m1, const Matrix &m2):
-       matrix1(&m1),
-       matrix2(&m2)
+template<unsigned N>
+void Animation::ValueCurve<N>::apply(float, Matrix &) const
 {
-       const float *m1_data = matrix1->data();
-       const float *m2_data = matrix2->data();
-       for(unsigned i=0; i<3; ++i)
-               axes[i] = AxisInterpolation(m1_data+i*4, m2_data+i*4);
+       throw invalid_operation("ValueCurve::apply");
 }
 
-Matrix Animation::MatrixInterpolation::get(float t) const
+template<>
+void Animation::ValueCurve<3>::apply(float x, Matrix &matrix) const
 {
-       float u = t*2.0f-1.0f;
-
-       float matrix[16];
-       for(unsigned i=0; i<4; ++i)
+       Vector3 value = spline(x);
+       if(target==POSITION)
+               matrix.translate(value);
+       else if(target==EULER)
        {
-               const float *m1_col = matrix1->data()+i*4;
-               const float *m2_col = matrix2->data()+i*4;
-               float *out_col = matrix+i*4;
-
-               if(i<3)
-               {
-                       /* Linear interpolation will produce vectors that fall on the line
-                       between the two endpoints, and has a higher angular velocity near the
-                       middle.  We compensate for the velocity by interpolating the angle
-                       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 = (axes[i].slope+(1-axes[i].slope)*u*u)*u*0.5f+0.5f;
-
-                       /* The interpolated 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 = (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;
-               }
-               else
-               {
-                       for(unsigned j=0; j<3; ++j)
-                               out_col[j] = (1-t)*m1_col[j]+t*m2_col[j];
-               }
+               matrix.rotate(value.z, Vector3(0, 0, 1));
+               matrix.rotate(value.y, Vector3(0, 1, 0));
+               matrix.rotate(value.x, Vector3(1, 0, 0));
        }
-
-       matrix[3] = 0;
-       matrix[7] = 0;
-       matrix[11] = 0;
-       matrix[15] = 1;
-
-       return matrix;
+       else if(target==SCALE)
+               matrix.scale(value);
+       else
+               throw invalid_operation("ValueCurve::apply");
 }
 
-
-Animation::TimedKeyFrame::TimedKeyFrame():
-       prev(0),
-       start_slope(1),
-       end_slope(1)
-{ }
-
-void Animation::TimedKeyFrame::prepare(const Animation &animation)
+template<unsigned N>
+void Animation::ValueCurve<N>::apply(float x, KeyFrame::AnimatedUniform &uni) const
 {
-       const KeyFrame::UniformMap &kf_uniforms = keyframe->get_uniforms();
-       for(KeyFrame::UniformMap::const_iterator i=kf_uniforms.begin(); i!=kf_uniforms.end(); ++i)
-       {
-               unsigned j = animation.get_slot_for_uniform(i->first);
-               uniforms.reserve(j+1);
-               for(unsigned k=uniforms.size(); k<=j; ++k)
-                       uniforms.push_back(KeyFrame::AnimatedUniform(animation.uniforms[k].size, 0.0f));
-
-               uniforms[j] = i->second;
-       }
-
-       if(!prev)
-               return;
-
-       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);
-               }
-       }
+       uni.size = N;
+       typename Interpolate::Spline<float, 3, N>::Value value = spline(x);
+       for(unsigned i=0; i<N; ++i)
+               uni.values[i] = Interpolate::SplineValue<float, N>::get(value, i);
 }
 
 
@@ -275,85 +230,54 @@ Animation::UniformInfo::UniformInfo(const string &n, unsigned s):
 
 Animation::Iterator::Iterator(const Animation &a):
        animation(&a),
-       iter(animation->keyframes.begin()),
        event_iter(animation->events.begin()),
-       x(0),
        end(false)
 {
-       if(iter==animation->keyframes.end())
-               throw invalid_argument("Animation::Iterator::Iterator");
 }
 
 Animation::Iterator &Animation::Iterator::operator+=(const Time::TimeDelta &t)
 {
-       time_since_keyframe += t;
-       while(time_since_keyframe>iter->delta_t)
+       const Time::TimeDelta &duration = animation->get_duration();
+       if(!duration)
+               return *this;
+
+       elapsed += t;
+       if(animation->looping)
        {
-               vector<TimedKeyFrame>::const_iterator next = iter;
-               ++next;
-               if(next==animation->keyframes.end())
-               {
-                       if(animation->looping)
-                               next = animation->keyframes.begin();
-                       else
-                       {
-                               end = true;
-                               time_since_keyframe = iter->delta_t;
-                               break;
-                       }
-               }
-
-               time_since_keyframe -= iter->delta_t;
-               iter = next;
+               while(elapsed>=duration)
+                       elapsed -= duration;
+       }
+       else if(elapsed>=duration)
+       {
+               end = true;
+               elapsed = duration;
        }
-
-       x = time_since_keyframe/iter->delta_t;
-       x += (iter->start_slope-1)*((x-2)*x+1)*x + (1-iter->end_slope)*(1-x)*x*x;
 
        return *this;
 }
 
 void Animation::Iterator::dispatch_events(AnimationEventObserver &observer)
 {
-       vector<Event>::const_iterator events_end = animation->events.end();
-       if(end)
-       {
-               for(; event_iter!=events_end; ++event_iter)
-                       observer.animation_event(0, event_iter->name, event_iter->value);
-       }
-       else if(event_iter!=events_end)
-       {
-               Time::TimeDelta t = time_since_keyframe;
-               if(iter->prev)
-                       t += iter->prev->time;
-               for(; (event_iter!=events_end && event_iter->time<=t); ++event_iter)
-                       observer.animation_event(0, event_iter->name, event_iter->value);
-       }
+       for(; (event_iter!=animation->events.end() && event_iter->time<=elapsed); ++event_iter)
+               observer.animation_event(0, event_iter->name, event_iter->value);
 }
 
 Matrix Animation::Iterator::get_matrix() const
 {
-       if(!iter->prev)
-               return iter->keyframe->get_matrix();
-
-       return iter->matrix.get(x);
+       Matrix matrix;
+       for(unsigned i=0; i<3; ++i)
+               animation->curves[i]->apply(elapsed/Time::sec, matrix);
+       return matrix;
 }
 
 KeyFrame::AnimatedUniform Animation::Iterator::get_uniform(unsigned i) const
 {
-       if(!iter->prev)
-       {
-               if(iter->uniforms.size()>i)
-                       return iter->uniforms[i];
-               else
-                       return KeyFrame::AnimatedUniform(animation->uniforms[i].size, 0.0f);
-       }
+       if(i>=animation->uniforms.size())
+               throw out_of_range("Animation::Iterator::get_uniform");
 
-       unsigned size = animation->uniforms[i].size;
-       KeyFrame::AnimatedUniform result(size, 0.0f);
-       for(unsigned j=0; j<size; ++j)
-               result.values[j] = iter->prev->uniforms[i].values[j]*(1-x)+iter->uniforms[i].values[j]*x;
-       return result;
+       KeyFrame::AnimatedUniform uni(animation->uniforms[i].size, 0.0f);
+       animation->curves[3+i]->apply(elapsed/Time::sec, uni);
+       return uni;
 }
 
 Matrix Animation::Iterator::get_pose_matrix(unsigned link) const
@@ -363,21 +287,7 @@ Matrix Animation::Iterator::get_pose_matrix(unsigned link) const
        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 it off
-       // XXX This should probably be done on local matrices
-       Matrix result = iter->pose_matrices[link].get(x);
-       const Vector3 &base = animation->armature->get_link(link).get_base();
-       Vector3 new_base = result*base;
-       result = Matrix::translation(base-new_base)*result;
-       return result;
+       throw logic_error("pose animations are currently unimplemented");
 }
 
 
@@ -411,6 +321,11 @@ void Animation::Loader::init()
        add("slopes", &Loader::slopes);
 }
 
+void Animation::Loader::finish()
+{
+       obj.create_curves();
+}
+
 void Animation::Loader::event(const string &n)
 {
        obj.add_event(current_time, n);
index b9e9fe1f3d1bd25e7a10efd3c0d7648eadd75000..83b369bfca1f1e975df889de113d9655ca77db80 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <msp/core/refptr.h>
 #include <msp/datafile/objectloader.h>
+#include <msp/interpolate/spline.h>
 #include <msp/time/timedelta.h>
 #include "keyframe.h"
 
@@ -33,6 +34,7 @@ public:
                Loader(Animation &, Collection &);
        private:
                void init();
+               virtual void finish();
 
                void event(const std::string &);
                void event1i(const std::string &, int);
@@ -47,41 +49,49 @@ public:
        };
 
 private:
-       struct AxisInterpolation
+       enum CurveTarget
        {
-               float slope;
-               float scale;
+               POSITION,
+               EULER,
+               SCALE,
+               UNIFORM
+       };
+
+       class Curve
+       {
+       protected:
+               CurveTarget target;
+
+               Curve(CurveTarget);
+       public:
+               virtual ~Curve() { }
 
-               AxisInterpolation();
-               AxisInterpolation(const float *, const float *);
+               virtual void apply(float, Matrix &) const = 0;
+               virtual void apply(float, KeyFrame::AnimatedUniform &) const = 0;
        };
 
-       struct MatrixInterpolation
+       template<unsigned N>
+       class ValueCurve: public Curve
        {
-               const Matrix *matrix1;
-               const Matrix *matrix2;
-               AxisInterpolation axes[3];
+       public:
+               typedef typename Interpolate::SplineKnot<float, N> Knot;
 
-               MatrixInterpolation();
-               MatrixInterpolation(const Matrix &, const Matrix &);
+       private:
+               Interpolate::Spline<float, 1, N> spline;
 
-               Matrix get(float) const;
+       public:
+               ValueCurve(CurveTarget, const std::vector<Knot> &);
+
+               virtual void apply(float, Matrix &) const;
+               virtual void apply(float, KeyFrame::AnimatedUniform &) const;
        };
 
        struct TimedKeyFrame
        {
-               const TimedKeyFrame *prev;
                Time::TimeDelta time;
-               Time::TimeDelta delta_t;
                float start_slope;
                float end_slope;
                RefPtr<const KeyFrame> keyframe;
-               MatrixInterpolation matrix;
-               std::vector<KeyFrame::AnimatedUniform> uniforms;
-               std::vector<MatrixInterpolation> pose_matrices;
-
-               TimedKeyFrame();
-               void prepare(const Animation &);
        };
 
        struct Event
@@ -104,10 +114,8 @@ public:
        {
        private:
                const Animation *animation;
-               std::vector<TimedKeyFrame>::const_iterator iter;
+               Time::TimeDelta elapsed;
                std::vector<Event>::const_iterator event_iter;
-               Time::TimeDelta time_since_keyframe;
-               float x;
                bool end;
 
        public:
@@ -128,6 +136,7 @@ private:
        std::vector<Event> events;
        bool looping;
        std::vector<UniformInfo> uniforms;
+       std::vector<Curve *> curves;
 
 public:
        Animation();
@@ -146,6 +155,9 @@ public:
 private:
        void add_keyframe(const Time::TimeDelta &, const RefPtr<const KeyFrame> &, float, float);
        void prepare_keyframe(TimedKeyFrame &);
+       void create_curves();
+       template<unsigned N>
+       void create_uniform_curve(const std::string &);
 public:
        void add_event(const Time::TimeDelta &, const std::string &, const Variant & = Variant());