From: Mikko Rasa Date: Sun, 5 Aug 2012 12:26:23 +0000 (+0300) Subject: Basic animation support X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=4c5ba8f7d3bc755d6256cb6bf75907a1b10fc290;p=libs%2Fgl.git Basic animation support --- diff --git a/source/animatedobject.cpp b/source/animatedobject.cpp new file mode 100644 index 00000000..df0217ae --- /dev/null +++ b/source/animatedobject.cpp @@ -0,0 +1,22 @@ +#include "animatedobject.h" +#include "renderer.h" + +namespace Msp { +namespace GL { + +AnimatedObject::AnimatedObject(const Object &o): + ObjectInstance(o) +{ } + +void AnimatedObject::set_matrix(const Matrix &m) +{ + matrix = m; +} + +void AnimatedObject::setup_render(Renderer &renderer, const Tag &) const +{ + renderer.matrix_stack() *= matrix; +} + +} // namespace GL +} // namespace Msp diff --git a/source/animatedobject.h b/source/animatedobject.h new file mode 100644 index 00000000..4f6085ff --- /dev/null +++ b/source/animatedobject.h @@ -0,0 +1,30 @@ +#ifndef MSP_GL_ANIMATEDOBJECT_H_ +#define MSP_GL_ANIMATEDOBJECT_H_ + +#include "matrix.h" +#include "objectinstance.h" + +namespace Msp { +namespace GL { + +/** +An object instance that can be animated. Despite the name, this can also be +useful for displaying objects at a static position. +*/ +class AnimatedObject: public ObjectInstance +{ +private: + Matrix matrix; + +public: + AnimatedObject(const Object &); + + void set_matrix(const Matrix &); + + virtual void setup_render(Renderer &, const Tag &) const; +}; + +} // namespace GL +} // namespace Msp + +#endif diff --git a/source/animation.cpp b/source/animation.cpp new file mode 100644 index 00000000..eb22ab29 --- /dev/null +++ b/source/animation.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include "animation.h" +#include "keyframe.h" + +using namespace std; + +#include + +namespace Msp { +namespace GL { + +Animation::Animation(): + looping(false) +{ } + +void Animation::add_keyframe(const Time::TimeDelta &t, const KeyFrame &kf) +{ + if(!keyframes.empty() && ttime; + + const double *m1_data = tkf.prev->keyframe->get_matrix().data(); + const double *m2_data = tkf.keyframe->get_matrix().data(); + 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; + } +} + +Matrix Animation::compute_matrix(const TimedKeyFrame &tkf, const Time::TimeDelta &dt) const +{ + if(!dt) + return tkf.keyframe->get_matrix(); + if(!tkf.prev) + throw invalid_argument("Animation::compute_matrix"); + const TimedKeyFrame &prev = *tkf.prev; + + float t = dt/tkf.delta_t; + 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; + double *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 = (tkf.axes[i].slope+(1-tkf.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); + + 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[3] = 0; + matrix[7] = 0; + matrix[11] = 0; + matrix[15] = 1; + + return matrix; +} + + +Animation::AxisInterpolation::AxisInterpolation(): + slope(0), + scale(0) +{ } + + +Animation::Iterator::Iterator(const Animation &a): + animation(a), + iter(animation.keyframes.begin()), + end(false) +{ } + +Animation::Iterator &Animation::Iterator::operator+=(const Time::TimeDelta &t) +{ + time_since_keyframe += t; + while(time_since_keyframe>iter->delta_t) + { + KeyFrameList::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; + } + + return *this; +} + +Matrix Animation::Iterator::get_matrix() const +{ + return animation.compute_matrix(*iter, time_since_keyframe); +} + + +Animation::Loader::Loader(Animation &a): + DataFile::CollectionObjectLoader(a, 0) +{ + init(); +} + +Animation::Loader::Loader(Animation &a, Collection &c): + DataFile::CollectionObjectLoader(a, &c) +{ + init(); +} + +void Animation::Loader::init() +{ + add("interval", &Loader::interval); + add("keyframe", &Loader::keyframe); + add("keyframe", &Loader::keyframe_inline); + add("looping", &Animation::looping); +} + +void Animation::Loader::keyframe(const string &n) +{ + obj.add_keyframe(current_time, get_collection().get(n)); +} + +void Animation::Loader::keyframe_inline() +{ + RefPtr kf = new KeyFrame; + load_sub(*kf); + + TimedKeyFrame tkf; + tkf.time = current_time; + tkf.keyframe = kf; + obj.prepare_keyframe(tkf); + obj.keyframes.push_back(tkf); +} + +void Animation::Loader::interval(float t) +{ + current_time += t*Time::sec; +} + +} // namespace GL +} // namespace Msp diff --git a/source/animation.h b/source/animation.h new file mode 100644 index 00000000..1460c944 --- /dev/null +++ b/source/animation.h @@ -0,0 +1,93 @@ +#ifndef MSP_GL_ANIMATION_H_ +#define MSP_GL_ANIMATION_H_ + +#include +#include +#include + +namespace Msp { +namespace GL { + +class KeyFrame; +class Matrix; + +/** +An Animation is a sequence of KeyFrames combined with timing information. The +state at any point in the animation can be interpolated from the keyframes. +*/ +class Animation +{ +public: + class Loader: public DataFile::CollectionObjectLoader + { + private: + Time::TimeDelta current_time; + + public: + Loader(Animation &); + Loader(Animation &, Collection &); + private: + void init(); + + void keyframe(const std::string &); + void keyframe_inline(); + void interval(float); + }; + +private: + struct AxisInterpolation + { + float slope; + float scale; + + AxisInterpolation(); + }; + + struct TimedKeyFrame + { + const TimedKeyFrame *prev; + Time::TimeDelta time; + Time::TimeDelta delta_t; + RefPtr keyframe; + AxisInterpolation axes[3]; + }; + + typedef std::list KeyFrameList; + +public: + class Iterator + { + private: + const Animation &animation; + KeyFrameList::const_iterator iter; + Time::TimeDelta time_since_keyframe; + bool end; + + public: + Iterator(const Animation &); + + Iterator &operator+=(const Time::TimeDelta &); + + bool is_end() const { return end; } + Matrix get_matrix() const; + }; + +private: + KeyFrameList keyframes; + bool looping; + +public: + Animation(); + + void add_keyframe(const Time::TimeDelta &, const KeyFrame &); + void set_looping(bool); +private: + void prepare_keyframe(TimedKeyFrame &); + + Matrix compute_matrix(const TimedKeyFrame &, const Time::TimeDelta &) const; +}; + +} // namespace GL +} // namespace Msp + +#endif diff --git a/source/animationplayer.cpp b/source/animationplayer.cpp new file mode 100644 index 00000000..9537960c --- /dev/null +++ b/source/animationplayer.cpp @@ -0,0 +1,36 @@ +#include "animatedobject.h" +#include "animationplayer.h" + +using namespace std; + +namespace Msp { +namespace GL { + +void AnimationPlayer::play(AnimatedObject &obj, const Animation &anim) +{ + slots.push_back(Slot(obj, anim)); +} + +void AnimationPlayer::tick(const Time::TimeDelta &dt) +{ + for(list::iterator i=slots.begin(); i!=slots.end(); ) + { + i->iterator += dt; + i->object.set_matrix(i->iterator.get_matrix()); + + if(i->iterator.is_end()) + slots.erase(i++); + else + ++i; + } +} + + +AnimationPlayer::Slot::Slot(AnimatedObject &o, const Animation &a): + object(o), + animation(a), + iterator(animation) +{ } + +} // namespace GL +} // namespace Msp diff --git a/source/animationplayer.h b/source/animationplayer.h new file mode 100644 index 00000000..fe53a359 --- /dev/null +++ b/source/animationplayer.h @@ -0,0 +1,42 @@ +#ifndef MSP_GL_ANIMATIONPLAYER_H_ +#define MSP_GL_ANIMATIONPLAYER_H_ + +#include +#include "animation.h" + +namespace Msp { +namespace GL { + +class AnimatedObject; + +/** +The bridge between Animations and AnimatedObjects. A single AnimationPlayer +can handle an arbitrary number of animations simultaneously. +*/ +class AnimationPlayer +{ +private: + struct Slot + { + AnimatedObject &object; + const Animation &animation; + Animation::Iterator iterator; + + Slot(AnimatedObject &, const Animation &); + }; + + std::list slots; + +public: + /** Plays an animation on an object. */ + void play(AnimatedObject &, const Animation &); + + /** Advances all playing animations. Should be called in a regular manner, + preferably just before rendering. */ + void tick(const Time::TimeDelta &); +}; + +} // namespace GL +} // namespace Msp + +#endif diff --git a/source/keyframe.cpp b/source/keyframe.cpp new file mode 100644 index 00000000..7310c239 --- /dev/null +++ b/source/keyframe.cpp @@ -0,0 +1,36 @@ +#include "keyframe.h" + +namespace Msp { +namespace GL { + +KeyFrame::Loader::Loader(KeyFrame &k): + DataFile::ObjectLoader(k) +{ + add("position", &Loader::position); + add("rotation", &Loader::rotation); + add("scaling", &Loader::scaling_uniform); + add("scaling", &Loader::scaling); +} + +void KeyFrame::Loader::position(float x, float y, float z) +{ + obj.matrix.translate(x, y, z); +} + +void KeyFrame::Loader::rotation(float a, float x, float y, float z) +{ + obj.matrix.rotate_deg(a, x, y, z); +} + +void KeyFrame::Loader::scaling_uniform(float s) +{ + obj.matrix.scale(s); +} + +void KeyFrame::Loader::scaling(float x, float y, float z) +{ + obj.matrix.scale(x, y, z); +} + +} // namespace GL +} // namespace Msp diff --git a/source/keyframe.h b/source/keyframe.h new file mode 100644 index 00000000..4b0fa5bd --- /dev/null +++ b/source/keyframe.h @@ -0,0 +1,37 @@ +#ifndef MSP_GL_KEYFRAME_H_ +#define MSP_GL_KEYFRAME_H_ + +#include +#include "matrix.h" + +namespace Msp { +namespace GL { + +/** +Keyframes are used to encapsulate object state for animation. +*/ +class KeyFrame +{ +public: + class Loader: public DataFile::ObjectLoader + { + public: + Loader(KeyFrame &); + private: + void position(float, float, float); + void rotation(float, float, float, float); + void scaling_uniform(float); + void scaling(float, float, float); + }; + +private: + Matrix matrix; + +public: + const Matrix &get_matrix() const { return matrix; } +}; + +} // namespace GL +} // namespace Msp + +#endif