From 025016d7628be9c43b20999325dcbaae5cb3c3b8 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 6 Jun 2019 01:02:41 +0300 Subject: [PATCH] Reimplement Animation using splines 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 | 331 ++++++++++++++++--------------------------- source/animation.h | 58 +++++--- 2 files changed, 158 insertions(+), 231 deletions(-) diff --git a/source/animation.cpp b/source/animation.cpp index 6c9e179d..dbdd05ba 100644 --- a/source/animation.cpp +++ b/source/animation.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #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 kfr(&kf); kfr.keep(); add_keyframe(t, kfr, ss, es); + create_curves(); } void Animation::add_keyframe(const Time::TimeDelta &t, const RefPtr &kf, float ss, float es) @@ -69,49 +71,87 @@ void Animation::add_keyframe(const Time::TimeDelta &t, const RefPtrget_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::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; i1 && 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 && jfirst) - { - if(uniforms[j].size!=i->second.size) - throw invalid_operation("Animation::prepare_keyframe"); - found = true; - } + for(vector::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::iterator i=curves.begin(); i!=curves.end(); ++i) + delete *i; + curves.clear(); + + typedef ValueCurve<3>::Knot Knot; + vector positions; + vector eulers; + vector scales; + for(vector::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::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 +void Animation::create_uniform_curve(const string &name) +{ + typedef typename ValueCurve::Knot Knot; - tkf.prepare(*this); + vector knots; + for(vector::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::make(j->second.values))); + } + + curves.push_back(new ValueCurve(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 +Animation::ValueCurve::ValueCurve(CurveTarget t, const vector &k): + Curve(t), + spline(Interpolate::LinearSpline(k)) { } -Animation::MatrixInterpolation::MatrixInterpolation(const Matrix &m1, const Matrix &m2): - matrix1(&m1), - matrix2(&m2) +template +void Animation::ValueCurve::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 +void Animation::ValueCurve::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::Value value = spline(x); + for(unsigned i=0; i::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::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::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; jprev->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); diff --git a/source/animation.h b/source/animation.h index b9e9fe1f..83b369bf 100644 --- a/source/animation.h +++ b/source/animation.h @@ -3,6 +3,7 @@ #include #include +#include #include #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 + class ValueCurve: public Curve { - const Matrix *matrix1; - const Matrix *matrix2; - AxisInterpolation axes[3]; + public: + typedef typename Interpolate::SplineKnot Knot; - MatrixInterpolation(); - MatrixInterpolation(const Matrix &, const Matrix &); + private: + Interpolate::Spline spline; - Matrix get(float) const; + public: + ValueCurve(CurveTarget, const std::vector &); + + 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 keyframe; - MatrixInterpolation matrix; - std::vector uniforms; - std::vector pose_matrices; - - TimedKeyFrame(); - void prepare(const Animation &); }; struct Event @@ -104,10 +114,8 @@ public: { private: const Animation *animation; - std::vector::const_iterator iter; + Time::TimeDelta elapsed; std::vector::const_iterator event_iter; - Time::TimeDelta time_since_keyframe; - float x; bool end; public: @@ -128,6 +136,7 @@ private: std::vector events; bool looping; std::vector uniforms; + std::vector curves; public: Animation(); @@ -146,6 +155,9 @@ public: private: void add_keyframe(const Time::TimeDelta &, const RefPtr &, float, float); void prepare_keyframe(TimedKeyFrame &); + void create_curves(); + template + void create_uniform_curve(const std::string &); public: void add_event(const Time::TimeDelta &, const std::string &, const Variant & = Variant()); -- 2.43.0