diff --git a/src/animations/animation_base.cpp b/src/animations/animation_base.cpp index 2cadf604d..6d995592e 100644 --- a/src/animations/animation_base.cpp +++ b/src/animations/animation_base.cpp @@ -36,32 +36,34 @@ AnimationBase::AnimationBase(const XMLNode &node) Ipo *ipo = new Ipo(*node.getNode(i), fps); m_all_ipos.push_back(ipo); } + if(m_all_ipos.size()==0) + { + printf("Warning: empty animation curve.\n"); + exit(-1); + } - - // extend all IPOs to add at the same point - float last_x = -1; + // Determine start and end X values for this curve. + m_start_time = m_all_ipos[0].getPoints()[0].X; + m_end_time = m_start_time; Ipo* curr; for_in (curr, m_all_ipos) { const std::vector& points = curr->getPoints(); - last_x = std::max(last_x, points[points.size() - 1].X); + m_start_time = std::min(m_start_time, points[0].X ); + m_end_time = std::max(m_end_time, points[points.size() - 1].X); } - if (last_x > -1) + // Make sure all individual IPOs start and end at the same X value. + for_in (curr, m_all_ipos) { - for_in (curr, m_all_ipos) - { - const std::vector& points = curr->getPoints(); - if (points[points.size() - 1].X < last_x) - { - curr->extendTo(last_x); - } - } + const std::vector& points = curr->getPoints(); + if (points[points.size() - 1].X < m_end_time) + curr->extendEnd(m_end_time); + if(points[0].X > m_start_time) + curr->extendStart(m_start_time); } - - - m_playing = true; + m_playing = true; } // AnimationBase // ---------------------------------------------------------------------------- @@ -90,6 +92,7 @@ void AnimationBase::setInitialTransform(const core::vector3df &xyz, */ void AnimationBase::reset() { + m_current_time = m_start_time; Ipo* curr; for_in (curr, m_all_ipos) { @@ -106,12 +109,58 @@ void AnimationBase::reset() void AnimationBase::update(float dt, core::vector3df *xyz, core::vector3df *hpr, core::vector3df *scale) { + m_current_time += dt; + + while(m_current_time > m_end_time) + m_current_time -= (m_end_time - m_start_time); if ( UserConfigParams::m_graphical_effects ) { Ipo* curr; for_in (curr, m_all_ipos) { - curr->update(dt, xyz, hpr, scale); + curr->update(m_current_time, xyz, hpr, scale); } } -} // float dt +} // update + +// ---------------------------------------------------------------------------- +/** Approximates the overall length of each segment (curve between two points) + * and the overall length of the curve. + */ +void AnimationBase::computeLengths() +{ + Ipo* curr; + // First determine the maximum number of points among all IPOs + unsigned int max_points=0; + for_in (curr, m_all_ipos) + { + const std::vector& points = curr->getPoints(); + max_points = std::max(max_points, points.size()); + } + + // Divide (on average) each segment into STEPS points, and use + // a simple linear approximation for this part of the curve + const float STEPS = 100.0f * (max_points-1); + float x = m_start_time; + float dx = (m_end_time - m_start_time) / STEPS ; + float distance = 0; + + core::vector3df xyz_old(0,0,0), hpr, scale; + for_in(curr, m_all_ipos) + { + curr->update(m_start_time, &xyz_old, &hpr, &scale); + } + + for(unsigned int i=1; i<(unsigned int)STEPS; i++) + { + float x = m_start_time + dx * i; + core::vector3df xyz(0,0,0); + for_in(curr, m_all_ipos) + { + // hpr is not needed, so just reuse old variable + curr->update(x, &xyz, &hpr, &scale); + } // for curr in m_all_ipos + distance += (xyz-xyz_old).getLength(); + xyz_old = xyz; + } // for i in m_points +} // computeLengths \ No newline at end of file diff --git a/src/animations/animation_base.hpp b/src/animations/animation_base.hpp index 55ca4eb07..ea0820912 100644 --- a/src/animations/animation_base.hpp +++ b/src/animations/animation_base.hpp @@ -26,7 +26,6 @@ */ #include - #include using namespace irr; @@ -50,20 +49,26 @@ private: /** True if the animation is currently playing. */ bool m_playing; - /** For one time animations: start time. */ - float m_start; + /** The smallest time for which this animation is defined - usually 0.*/ + float m_start_time; + + /** The end time for this animation. */ + float m_end_time; + + /** The current time used in the IPOs. */ + float m_current_time; /** For cyclic animations: duration of the cycle. */ float m_cycle_length; - /** The current time in the cycle of a cyclic animation. */ - float m_current_time; - /** The inital position of this object. */ core::vector3df m_initial_xyz; /** The initial rotation of this object. */ core::vector3df m_initial_hpr; + + void computeLengths(); + protected: /** All IPOs for this animation. */ PtrVector m_all_ipos; diff --git a/src/animations/ipo.cpp b/src/animations/ipo.cpp index 341b6929a..57510453b 100644 --- a/src/animations/ipo.cpp +++ b/src/animations/ipo.cpp @@ -52,8 +52,6 @@ Ipo::Ipo(const XMLNode &curve, float fps) else if(interp=="linear") m_interpolation = IP_LINEAR; else m_interpolation = IP_BEZIER; - m_min_time = 999999; - m_max_time = -1; for(unsigned int i=0; im_max_time) m_max_time = t; xy.X = t; m_points.push_back(xy); if(m_interpolation==IP_BEZIER) @@ -102,33 +98,30 @@ void Ipo::setInitialTransform(const core::vector3df &xyz, */ void Ipo::reset() { - m_time = 0; + m_next_n = 1; } // reset // ---------------------------------------------------------------------------- /** Updates the time of this ipo and interpolates the new position and * rotation (taking the cycle length etc. into account). - * \param dt Time since last call. + * \param time Current time for which to determine the interpolation. * \param xyz The position that needs to be updated. * \param hpr The rotation that needs to be updated. */ -void Ipo::update(float dt, core::vector3df *xyz, core::vector3df *hpr, +void Ipo::update(float time, core::vector3df *xyz, core::vector3df *hpr, core::vector3df *scale) -{ - m_time += dt; - if(m_extend!=ET_CONST && m_time>m_max_time) m_time = 0; - +{ switch(m_channel) { - case Ipo::IPO_LOCX : xyz->X = get(); break; - case Ipo::IPO_LOCY : xyz->Y = get(); break; - case Ipo::IPO_LOCZ : xyz->Z = get(); break; - case Ipo::IPO_ROTX : hpr->X = get(); break; - case Ipo::IPO_ROTY : hpr->Y = get(); break; - case Ipo::IPO_ROTZ : hpr->Z = get(); break; - case Ipo::IPO_SCALEX : scale->X = get(); break; - case Ipo::IPO_SCALEY : scale->Y = get(); break; - case Ipo::IPO_SCALEZ : scale->Z = get(); break; + case Ipo::IPO_LOCX : xyz->X = get(time); break; + case Ipo::IPO_LOCY : xyz->Y = get(time); break; + case Ipo::IPO_LOCZ : xyz->Z = get(time); break; + case Ipo::IPO_ROTX : hpr->X = get(time); break; + case Ipo::IPO_ROTY : hpr->Y = get(time); break; + case Ipo::IPO_ROTZ : hpr->Z = get(time); break; + case Ipo::IPO_SCALEX : scale->X = get(time); break; + case Ipo::IPO_SCALEY : scale->Y = get(time); break; + case Ipo::IPO_SCALEZ : scale->Z = get(time); break; default: assert(false); // shut up compiler warning } // switch @@ -137,29 +130,28 @@ void Ipo::update(float dt, core::vector3df *xyz, core::vector3df *hpr, // ---------------------------------------------------------------------------- /** Returns the interpolated value at the current time (which this objects * keeps track of). + * \param time The time for which the interpolated value should be computed. */ -float Ipo::get() const +float Ipo::get(float time) const { - if(m_time=m_points[n].X) - n++; - // Avoid crash in case that only one point is given for this IPO. - if(n==0) - return m_points[0].Y; - n--; + while(m_next_n=m_points[m_next_n].X) + m_next_n++; + int n = m_next_n - 1; switch(m_interpolation) { case IP_CONST : return m_points[n].Y; case IP_LINEAR : { - float t = m_time-m_points[n].X; + float t = time-m_points[n].X; return m_points[n].Y + t*(m_points[n+1].Y-m_points[n].Y) / (m_points[n+1].X-m_points[n].X); } @@ -172,7 +164,7 @@ float Ipo::get() const core::vector2df c = 3.0f*(m_handle2[n]-m_points[n]); core::vector2df b = 3.0f*(m_handle1[n+1]-m_handle2[n])-c; core::vector2df a = m_points[n+1] - m_points[n] - c - b; - float t = (m_time-m_points[n].X)/(m_points[n+1].X-m_points[n].X); + float t = (time-m_points[n].X)/(m_points[n+1].X-m_points[n].X); core::vector2df r = ((a*t+b)*t+c)*t+m_points[n]; return r.Y; } @@ -182,31 +174,77 @@ float Ipo::get() const } // get // ---------------------------------------------------------------------------- +/** Inserts a new start point at the beginning of the IPO to make sure that + * this IPO starts with X. + * \param x The minimum value for which this IPO should be defined. + */ +void Ipo::extendStart(float x) +{ + assert(m_points[0].X > x); + extend(x, 0); +} // extendStart +// ---------------------------------------------------------------------------- +/** Inserts an additional point at the end of the IPO to make sure that this + * IPO ends with X. + * \param x The maximum value for which this IPO should be defined. + */ +void Ipo::extendEnd(float x) +{ + assert(m_points[m_points.size()-1].X < x); + extend(x, m_points.size()-1); +} // extendEnd -/** Extends the IPO to end at the given x time coordinate */ -void Ipo::extendTo(float x) +// ---------------------------------------------------------------------------- +/** Extends the IPO either at the beginning (n=0) or at the end (n=size()-1). + * This is used by AnimationBase to make sure all IPOs of one curve have the + * same cycle. + * \param x The X value to which the IPO must be extended. + * \param n The index at (before/after) which to extend. + */ +void Ipo::extend(float x, unsigned int n) { switch (m_interpolation) { case IP_CONST: { - m_points.push_back( core::vector2df(x, m_points[m_points.size()-1].Y) ); + core::vector2df new_point(x, m_points[n].Y); + if(n==0) + m_points.insert(m_points.begin(), new_point); + else + m_points.push_back( new_point); break; } case IP_LINEAR: { - m_points.push_back( core::vector2df(x, m_points[m_points.size()-1].Y) ); + core::vector2df new_point(x, m_points[n].Y); + if(n=0) + m_points.insert(m_points.begin(), new_point); + else + m_points.push_back(new_point); break; } case IP_BEZIER: { // FIXME: I'm somewhat dubious this is the correct way to extend handles - m_handle1.push_back( m_handle1[m_handle1.size() - 1] + core::vector2df(x - m_points[m_points.size()-1].X ,0) ); - m_handle2.push_back( m_handle2[m_handle2.size() - 1] + core::vector2df(x - m_points[m_points.size()-1].X ,0) ); - - m_points.push_back( core::vector2df(x, m_points[m_points.size()-1].Y) ); + core::vector2df new_h1 = m_handle1[n] + + core::vector2df(x - m_points[n].X ,0); + core::vector2df new_h2 = m_handle2[n] + + core::vector2df(x - m_points[n].X ,0); + core::vector2df new_p(x, m_points[n].Y); + if(n==0) + { + m_handle1.insert(m_handle1.begin(), new_h1); + m_handle2.insert(m_handle2.begin(), new_h2); + m_points.insert(m_points.begin(), new_p); + } + else + { + m_handle1.push_back(new_h1); + m_handle2.push_back(new_h2); + m_points.push_back(new_p); + } break; } } - m_max_time = x; -} +} // extend + diff --git a/src/animations/ipo.hpp b/src/animations/ipo.hpp index 9c8a57a9e..e1f3b97bc 100644 --- a/src/animations/ipo.hpp +++ b/src/animations/ipo.hpp @@ -54,39 +54,39 @@ private: /** The actual control points. */ std::vector m_points; + /** Only used for bezier curves: the two handles. */ std::vector m_handle1, m_handle2; - /** Current time in cycle. */ - float m_time; - - /** Minium time when this animations starts, usually 0. */ - float m_min_time; - - /** Time this animation finishes (or cycles). */ - float m_max_time; - /** Frames per second for this animation. */ float m_fps; + /** Which control points will be the next one (so m_next_n-1 and + * m_next_n are the control points to use now). This just reduces + * lookup time in get(t). To allow modifying this in get() const, + * it is declared mutable). */ + mutable unsigned int m_next_n; + /** Stores the inital position of the object. */ core::vector3df m_initial_xyz; + /** Stores the inital rotation of the object. */ core::vector3df m_initial_hpr; -public: - Ipo(const XMLNode &curve, float fps); - void update(float dt, core::vector3df *xyz, core::vector3df *hpr, - core::vector3df *scale); - float get() const; - void setInitialTransform(const core::vector3df &xyz, - const core::vector3df &hpr); - void reset(); - - void extendTo(float x); - - const std::vector& getPoints() const { return m_points; } - + void extend(float x, unsigned int n); +public: + Ipo(const XMLNode &curve, float fps); + void update(float time, core::vector3df *xyz, core::vector3df *hpr, + core::vector3df *scale); + float get(float time) const; + void setInitialTransform(const core::vector3df &xyz, + const core::vector3df &hpr); + void reset(); + + void extendStart(float x); + void extendEnd(float x); + + const std::vector& getPoints() const { return m_points; } }; // Ipo #endif diff --git a/src/karts/cannon_animation.cpp b/src/karts/cannon_animation.cpp index fa23c90ba..357ca2c5d 100644 --- a/src/karts/cannon_animation.cpp +++ b/src/karts/cannon_animation.cpp @@ -18,40 +18,20 @@ #include "karts/cannon_animation.hpp" +#include "animations/animation_base.hpp" #include "karts/abstract_kart.hpp" #include "modes/world.hpp" #include "LinearMath/btTransform.h" -CannonAnimation::CannonAnimation(AbstractKart *kart, const Vec3 &target, - float speed) +CannonAnimation::CannonAnimation(AbstractKart *kart, AnimationBase *ab) : AbstractKartAnimation(kart) { - m_xyz = m_kart->getXYZ(); - assert(speed>0); - Vec3 delta = target-m_kart->getXYZ(); - m_timer = delta.length()/speed; - m_velocity = delta/m_timer; - - World::getWorld()->getPhysics()->removeKart(m_kart); - - m_curr_rotation.setHeading(m_kart->getHeading()); - m_curr_rotation.setPitch(m_kart->getPitch()); - m_curr_rotation.setRoll(m_kart->getRoll()); - - m_add_rotation.setHeading(0); - m_add_rotation.setPitch( 0); - m_add_rotation.setRoll( 0); } // CannonAnimation // ---------------------------------------------------------------------------- CannonAnimation::~CannonAnimation() { - btTransform trans = m_kart->getTrans(); - trans.setOrigin(m_xyz); - m_kart->setTrans(trans); - m_kart->getBody()->setCenterOfMassTransform(trans); - World::getWorld()->getPhysics()->addKart(m_kart); } // ~CannonAnimation // ---------------------------------------------------------------------------- @@ -61,12 +41,5 @@ CannonAnimation::~CannonAnimation() */ void CannonAnimation::update(float dt) { - m_xyz += dt*m_velocity; - m_kart->setXYZ(m_xyz); - m_curr_rotation += dt*m_add_rotation; - btQuaternion q(m_curr_rotation.getHeading(), m_curr_rotation.getPitch(), - m_curr_rotation.getRoll()); - m_kart->setRotation(q); - AbstractKartAnimation::update(dt); } // update diff --git a/src/karts/cannon_animation.hpp b/src/karts/cannon_animation.hpp index 69988e915..a413485a0 100644 --- a/src/karts/cannon_animation.hpp +++ b/src/karts/cannon_animation.hpp @@ -28,31 +28,18 @@ */ class AbstractKart; +class AnimationBase; class CannonAnimation: public AbstractKartAnimation { protected: - /** The coordinates where the kart was hit originally. */ - Vec3 m_xyz; + /** The offset between the point where the check line was originially + * crossed and the origin of the curve. */ + Vec3 delta; - /** The kart's current rotation. */ - Vec3 m_curr_rotation; - - /** The artificial rotation to toss the kart around. It's in units - * of rotation per second. */ - Vec3 m_add_rotation; - - /** The velocity with which the kart is moved. */ - Vec3 m_velocity; - - /** Duration for this explosion. This can potentially be set - * with different values for different karts, or depending - * on difficulty (so that on easy you can drive again earlier. */ - float m_duration; public: - CannonAnimation(AbstractKart *kart, const Vec3 &target, - float speed); + CannonAnimation(AbstractKart *kart, AnimationBase *ab); virtual ~CannonAnimation(); virtual void update(float dt); virtual const std::string getName() const {return "Cannon";} diff --git a/src/tracks/check_cannon.cpp b/src/tracks/check_cannon.cpp index 02b2af232..8da866bad 100644 --- a/src/tracks/check_cannon.cpp +++ b/src/tracks/check_cannon.cpp @@ -18,11 +18,27 @@ #include "tracks/check_cannon.hpp" +#include "animations/animation_base.hpp" #include "io/xml_node.hpp" #include "karts/abstract_kart.hpp" #include "karts/cannon_animation.hpp" #include "modes/world.hpp" + +CheckCannon::CannonCurve::CannonCurve(const XMLNode &node) + : AnimationBase(node) +{ + m_speed = -1; + node.get("speed", &m_speed); +} // CannonCurve + +// ------------------------------------------------------------------------ +void CheckCannon::CannonCurve::update(float dt) +{ +} // update + +// ============================================================================ + /** Constructor for a check cannon. * \param node XML node containing the parameters for this checkline. * \param index Index of this check structure in the check manager. @@ -38,14 +54,19 @@ CheckCannon::CheckCannon(const XMLNode &node, unsigned int index) exit(-1); } m_target.setLine(p1, p2); - m_speed = -1; - node.get("speed", &m_speed); + m_curve = new CannonCurve(node); } // CheckCannon +// ---------------------------------------------------------------------------- +CheckCannon::~CheckCannon() +{ + delete m_curve; +} // ~CheckCannon + // ---------------------------------------------------------------------------- void CheckCannon::trigger(unsigned int kart_index) { Vec3 target(m_target.getMiddle()); AbstractKart *kart = World::getWorld()->getKart(kart_index); - new CannonAnimation(kart, target, m_speed); + new CannonAnimation(kart, m_curve); } // CheckCannon diff --git a/src/tracks/check_cannon.hpp b/src/tracks/check_cannon.hpp index 2db209932..eecc60b81 100644 --- a/src/tracks/check_cannon.hpp +++ b/src/tracks/check_cannon.hpp @@ -19,10 +19,11 @@ #ifndef HEADER_CHECK_CANNON_HPP #define HEADER_CHECK_CANNON_HPP +#include "animations/animation_base.hpp" #include "tracks/check_line.hpp" -class XMLNode; class CheckManager; +class XMLNode; /** * \brief Implements a simple checkline that will cause a kart to be @@ -36,11 +37,26 @@ private: /** The target point the kart will fly to. */ core::line3df m_target; - /** The speed with which the kart moves. */ - float m_speed; + + // ------------------------------------------------------------------------ +protected: + class CannonCurve : public AnimationBase + { + private: + /** The speed with which the kart moves. */ + float m_speed; + public: + CannonCurve(const XMLNode &node); + void update(float dt); + }; // CannonCurve + + // ------------------------------------------------------------------------ + /** Stores the cannon curve data. */ + CannonCurve *m_curve; public: - CheckCannon(const XMLNode &node, unsigned int index); + CheckCannon(const XMLNode &node, unsigned int index); + virtual ~CheckCannon(); virtual void trigger(unsigned int kart_index); }; // CheckLine