From d0fb8aa78a2a85e6ba2346c54c674ed64b461a12 Mon Sep 17 00:00:00 2001 From: hikerstk Date: Wed, 18 Apr 2012 13:14:18 +0000 Subject: [PATCH] A first version of the cannon that uses linear interpolation for the bezier curves to allow smooth (i.e. constant speed) travel along the curve. This is WIP. Known bug: the kart jumps at the beginning and end of the cannon animation. git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@11122 178a84e3-b1eb-0310-8ba1-8eac791a3b58 --- src/animations/animation_base.cpp | 67 +-- src/animations/animation_base.hpp | 14 +- src/animations/ipo.cpp | 518 ++++++++++++------ src/animations/ipo.hpp | 104 ++-- src/animations/three_d_animation.cpp | 1 - src/karts/abstract_kart_animation.cpp | 17 + src/karts/abstract_kart_animation.hpp | 2 +- src/karts/cannon_animation.cpp | 43 +- src/karts/cannon_animation.hpp | 17 +- .../controller/default_ai_controller.cpp | 6 +- src/karts/explosion_animation.cpp | 2 - src/karts/rescue_animation.cpp | 2 - src/tracks/check_cannon.cpp | 10 +- src/tracks/check_cannon.hpp | 3 +- src/tracks/check_line.cpp | 6 +- src/tracks/check_line.hpp | 13 +- src/tracks/check_structure.cpp | 3 +- src/tracks/track_object.cpp | 8 + src/tracks/track_object.hpp | 1 + src/tracks/track_object_manager.cpp | 1 + src/utils/vec3.hpp | 4 + 21 files changed, 549 insertions(+), 293 deletions(-) diff --git a/src/animations/animation_base.cpp b/src/animations/animation_base.cpp index ab3e47582..0bcfb978b 100644 --- a/src/animations/animation_base.cpp +++ b/src/animations/animation_base.cpp @@ -36,7 +36,8 @@ AnimationBase::AnimationBase(const XMLNode &node) Ipo *ipo = new Ipo(*node.getNode(i), fps); m_all_ipos.push_back(ipo); } - m_playing = true; + m_playing = true; + m_anim_type = ATT_CYCLIC; #ifdef DEBUG if(m_all_ipos.size()==0) { @@ -47,10 +48,15 @@ AnimationBase::AnimationBase(const XMLNode &node) } // AnimationBase // ---------------------------------------------------------------------------- - -AnimationBase::~AnimationBase() +/** Special constructor which takes one IPO (or curve). This is used by the + */ +AnimationBase::AnimationBase(Ipo *ipo) : TrackObject() { -} // ~AnimationBase + m_anim_type = ATT_CYCLIC_ONCE; + m_playing = true; + m_all_ipos.push_back(ipo); + reset(); +} // AnimationBase(Ipo) // ---------------------------------------------------------------------------- /** Stores the initial transform (in the IPOs actually). This is necessary @@ -93,57 +99,10 @@ void AnimationBase::update(float dt, Vec3 *xyz, Vec3 *hpr, Vec3 *scale) if(!m_playing) return; m_current_time += dt; - if ( UserConfigParams::m_graphical_effects ) + Ipo* curr; + for_in (curr, m_all_ipos) { - Ipo* curr; - for_in (curr, m_all_ipos) - { - curr->update(m_current_time, xyz, hpr, scale); - } + curr->update(m_current_time, xyz, hpr, scale); } } // 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; - float max_time = 0; - for_in (curr, m_all_ipos) - { - const std::vector& points = curr->getPoints(); - max_points = std::max(max_points, (unsigned int) points.size()); - max_time = std::max(max_time, curr->getEndTime()); - } - - // 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 dx = max_time / STEPS ; - float distance = 0; - - // Initialise xyz_old with the point at time 0 - Vec3 xyz_old(0,0,0), hpr, scale; - for_in(curr, m_all_ipos) - { - curr->update(/*time*/0, &xyz_old, &hpr, &scale); - } - - for(unsigned int i=1; i<(unsigned int)STEPS; i++) - { - // Interpolations always start at time 0 - float x = dx * i; - Vec3 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).length(); - xyz_old = xyz; - } // for i in m_points -} // computeLengths diff --git a/src/animations/animation_base.hpp b/src/animations/animation_base.hpp index 733245f62..6a14dc1ef 100644 --- a/src/animations/animation_base.hpp +++ b/src/animations/animation_base.hpp @@ -27,11 +27,13 @@ #include +// Note that ipo.hpp is included here in order that PtrVector can call +// the proper destructor! +#include "animations/ipo.hpp" #include "tracks/track_object.hpp" #include "utils/ptr_vector.hpp" class XMLNode; -class Ipo; /** * \brief A base class for all animations. @@ -59,19 +61,19 @@ private: /** The initial rotation of this object. */ Vec3 m_initial_hpr; - void computeLengths(); - protected: /** All IPOs for this animation. */ PtrVector m_all_ipos; public: AnimationBase(const XMLNode &node); - virtual ~AnimationBase(); + AnimationBase(Ipo *ipo); virtual void update(float dt, Vec3 *xyz, Vec3 *hpr, Vec3 *scale); /** This needs to be implemented by the inheriting classes. It is called - * once per frame from the track. */ - virtual void update(float dt) = 0; + * once per frame from the track. It has a dummy implementation that + * just asserts so that this class can be instantiated in + * CannonAnimation. */ + virtual void update(float dt) {assert(false); }; void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr); void reset(); diff --git a/src/animations/ipo.cpp b/src/animations/ipo.cpp index 606187006..25f7b18ad 100644 --- a/src/animations/ipo.cpp +++ b/src/animations/ipo.cpp @@ -20,12 +20,20 @@ #include "io/xml_node.hpp" +#include + const std::string Ipo::m_all_channel_names[IPO_MAX] = {"LocX", "LocY", "LocZ", "LocXYZ", "RotX", "RotY", "RotZ", "ScaleX", "ScaleY", "ScaleZ" }; -Ipo::Ipo(const XMLNode &curve, float fps) +// ---------------------------------------------------------------------------- +/** Initialise the Ipo from the specifications in the XML file. + * \param curve The XML node with the IPO data. + * \param fps Frames per second value, necessary to convert frame values + * into time. + */ +Ipo::IpoData::IpoData(const XMLNode &curve, float fps) { if(curve.getName()!="curve") { @@ -38,11 +46,16 @@ Ipo::Ipo(const XMLNode &curve, float fps) m_channel=IPO_MAX; for(unsigned int i=IPO_LOCX; iget("c", &xy); // Convert blender's frame number (1 ...) into time (0 ...) float t = (xy.X-1)/fps; - - Vec3 point(t, xy.Y, 0); + Vec3 point(xy.Y, 0, 0, t); + m_points.push_back(point); m_start_time = std::min(m_start_time, t); m_end_time = std::max(m_end_time, t); - m_points.push_back(point); if(m_interpolation==IP_BEZIER) { + Vec3 handle1, handle2; core::vector2df handle; node->get("h1", &handle); - handle.X = (xy.X-1)/fps; - m_handle1.push_back(Vec3(handle.X, handle.Y, 0)); + handle1.setW((xy.X-1)/fps); + handle1.setX(handle.Y); node->get("h2", &handle); - handle.X = (xy.X-1)/fps; - m_handle2.push_back(Vec3(handle.X, handle.Y, 0)); + handle2.setW((xy.X-1)/fps); + handle2.setX(handle.Y); + m_handle1.push_back(handle1); + m_handle2.push_back(handle2); } } // for i the speed + * which is the deviation of this function is a 2nd degree polynomial, and + * therefore not constant! + * \param node The root node with all curve data points. + */ +void Ipo::IpoData::readCurve(const XMLNode &curve) +{ + m_start_time = 0; + m_end_time = -999999.9f; + float speed = 30.0f; + curve.get("speed", &speed); + for(unsigned int i=0; iget("c", &point); + + if(m_interpolation==IP_BEZIER) + { + Vec3 handle; + node->get("h1", &handle); + m_handle1.push_back(handle); + node->get("h2", &handle); + m_handle2.push_back(handle); + if(i>0) + { + // We have to take a copy of the end point, since otherwise + // it can happen that as more points are added to m_points + // in the approximateBezier function, the data gets + // reallocated and then the reference to the original point + // is not correct anymore. + Vec3 end_point = m_points[m_points.size()-1]; + approximateBezier(0.0f, 1.0f, end_point, point, + m_handle2[i-1], m_handle1[i]); + } + } + m_points.push_back(point); + } // for i6) + return; + + float distance = approximateLength(t0, t1, p0, p1, h0, h1); + // A more sophisticated estimation might be useful (e.g. taking the + // difference between a linear approximation and the actual bezier + // curve into accound. + if(distance<=2.0f) + return; + + // Insert one point at (t0+t1)/2. First split the left part of + // the interval by a recursive call, then insert the point at + // (t0+t1)/2, then approximate the right part of the interval. + approximateBezier(t0, (t0+t1)*0.5f, p0, p1, h0, h1, rec_level + 1); + Vec3 middle; + for(unsigned int j=0; j<3; j++) + middle[j] = getCubicBezier((t0+t1)*0.5f, p0[j], h0[j], h1[j], p1[j]); + m_points.push_back(middle); + approximateBezier((t0+t1)*0.5f, t1, p0, p1, h0, h1, rec_level + 1); + +} // approximateBezier + +// ---------------------------------------------------------------------------- +/** Approximates the length of a bezier curve using a simple Euler + * approximation by dividing the interval [t0, t1] into 10 pieces. Good enough + * for our needs in STK. + * \param t0, t1 Approximate for t in [t0, t1]. + * \param p0, p1 The start and end point of the curve. + * \param h0, h1 The control points for the corresponding points. + */ +float Ipo::IpoData::approximateLength(float t0, float t1, + const Vec3 &p0, const Vec3 &p1, + const Vec3 &h0, const Vec3 &h1) +{ + assert(m_interpolation == IP_BEZIER); + + float distance=0; + const unsigned int NUM_STEPS=10; + float delta = (t1-t0)/NUM_STEPS; + Vec3 prev_point; + for(unsigned int j=0; j<3; j++) + prev_point[j] = getCubicBezier(t0, p0[j], h0[j], h1[j], p1[j]); + for(unsigned int i=1; i<=NUM_STEPS; i++) + { + float t = t0 + i * delta; + Vec3 next_point; + // Interpolate all three axis + for(unsigned j=0; j<3; j++) + { + next_point[j] = getCubicBezier(t, p0[j], h0[j], h1[j], p1[j]); + } + distance += (next_point - prev_point).length(); + prev_point = next_point; + } // for i< NUM_STEPS + + return distance; +} // IpoData::approximateLength + +// ---------------------------------------------------------------------------- +/** Adjusts the time so that it is between start and end of this Ipo. This + * takes the extend type into account, e.g. cyclic animations will just + * use a modulo operation, while constant extends will return start or + * end time directly. + * \param time The time to adjust. + */ +float Ipo::IpoData::adjustTime(float time) +{ + if(time m_end_time) + { + switch(m_extend) + { + case ET_CYCLIC: + time = m_start_time + fmodf(time, m_end_time-m_start_time); break; + case ET_CONST: + time = m_end_time; break; + default: + // FIXME: ET_CYCLIC_EXTRAP and ET_EXTRAP missing + assert(false); + } // switch m_extend + } // if time > m_end_time + return time; +} // adjustTime + +// ---------------------------------------------------------------------------- +float Ipo::IpoData::get(float time, unsigned int index, unsigned int n) +{ + switch(m_interpolation) + { + case IP_CONST : return m_points[n][index]; + case IP_LINEAR : { + float t = time-m_points[n].getW(); + return m_points[n][index] + + t*(m_points[n+1][index]-m_points[n][index]) / + (m_points[n+1].getW()-m_points[n].getW()); + } + case IP_BEZIER: { if(n==m_points.size()-1) + { + // FIXME: only const implemented atm. + return m_points[n][index]; + } + float t = (time-m_points[n].getW()) + / (m_points[n+1].getW()-m_points[n].getW()); + return getCubicBezier(t, + m_points [n ][index], + m_handle2[n ][index], + m_handle1[n+1][index], + m_points [n+1][index]); + } + } // switch + // Keep the compiler happy: + return 0; +} // get + +// ---------------------------------------------------------------------------- +/** Computes a cubic bezier curve for a given t in [0,1] and four control + * points. The curve will go through p0 (t=0), p3 (t=1). + * \param t The parameter for the bezier curve, must be in [0,1]. + * \param p0, p1, p2, p3 The four control points. + */ +float Ipo::IpoData::getCubicBezier(float t, float p0, float p1, + float p2, float p3) const +{ + float c = 3.0f*(p1-p0); + float b = 3.0f*(p2-p1)-c; + float a = p3 - p0 - c - b; + return ((a*t+b)*t+c)*t+p0; +} // bezier + +// ============================================================================ +/** The Ipo constructor. Ipos can share the actual data to interpolate, which + * is stored in a separate IpoData object, see Ipo(const Ipo *ipo) + * constructor. This is used for cannons: the actual check line stores the + * 'master' Ipo, and each actual IPO that animate a kart just use a copy + * of this read-only data. + * \param curve The XML data for this curve. + * \param fps Frames per second, used to convert all frame based value + * in the xml file into seconds. + */ +Ipo::Ipo(const XMLNode &curve, float fps) +{ + m_ipo_data = new IpoData(curve, fps); + m_own_ipo_data = true; reset(); } // Ipo +// ---------------------------------------------------------------------------- +/** A copy constructor. It shares the read-only data with the source Ipo + * \param ipo The ipo to copy from. + */ +Ipo::Ipo(const Ipo *ipo) +{ + // Share the read-only data + m_ipo_data = ipo->m_ipo_data; + m_own_ipo_data = false; + reset(); +} // Ipo(Ipo*) + +// ---------------------------------------------------------------------------- +/** Creates a copy of this object (the copy constructor is disabled in order + * to avoid implicit copies happening). + */ +Ipo *Ipo::clone() +{ + return new Ipo(this); +} // clone + +// ---------------------------------------------------------------------------- +/** The destructor only frees IpoData if it was created by this instance (and + * not if this instance was copied, therefore sharing the IpoData). + */ +Ipo::~Ipo() +{ + if(m_own_ipo_data) + delete m_ipo_data; +} // ~Ipo + // ---------------------------------------------------------------------------- /** Stores the initial transform. This is necessary for relative IPOs. * \param xyz Position of the object. @@ -93,8 +396,8 @@ Ipo::Ipo(const XMLNode &curve, float fps) void Ipo::setInitialTransform(const Vec3 &xyz, const Vec3 &hpr) { - m_initial_xyz = xyz; - m_initial_hpr = hpr; + m_ipo_data->m_initial_xyz = xyz; + m_ipo_data->m_initial_hpr = hpr; } // setInitialTransform // ---------------------------------------------------------------------------- @@ -114,17 +417,24 @@ void Ipo::reset() */ void Ipo::update(float time, Vec3 *xyz, Vec3 *hpr,Vec3 *scale) { - switch(m_channel) + switch(m_ipo_data->m_channel) { - case Ipo::IPO_LOCX : xyz ->setX(get(time)); break; - case Ipo::IPO_LOCY : xyz ->setY(get(time)); break; - case Ipo::IPO_LOCZ : xyz ->setZ(get(time)); break; - case Ipo::IPO_ROTX : hpr ->setX(get(time)); break; - case Ipo::IPO_ROTY : hpr ->setY(get(time)); break; - case Ipo::IPO_ROTZ : hpr ->setZ(get(time)); break; - case Ipo::IPO_SCALEX : scale->setX(get(time)); break; - case Ipo::IPO_SCALEY : scale->setY(get(time)); break; - case Ipo::IPO_SCALEZ : scale->setZ(get(time)); break; + case Ipo::IPO_LOCX : xyz ->setX(get(time, 0)); break; + case Ipo::IPO_LOCY : xyz ->setY(get(time, 0)); break; + case Ipo::IPO_LOCZ : xyz ->setZ(get(time, 0)); break; + case Ipo::IPO_ROTX : hpr ->setX(get(time, 0)); break; + case Ipo::IPO_ROTY : hpr ->setY(get(time, 0)); break; + case Ipo::IPO_ROTZ : hpr ->setZ(get(time, 0)); break; + case Ipo::IPO_SCALEX : scale->setX(get(time, 0)); break; + case Ipo::IPO_SCALEY : scale->setY(get(time, 0)); break; + case Ipo::IPO_SCALEZ : scale->setZ(get(time, 0)); break; + case Ipo::IPO_LOCXYZ : + { + for(unsigned int j=0; j<3; j++) + (*xyz)[j] = get(time, j); + break; + } + default: assert(false); // shut up compiler warning } // switch @@ -135,162 +445,22 @@ void Ipo::update(float time, Vec3 *xyz, Vec3 *hpr,Vec3 *scale) * keeps track of). * \param time The time for which the interpolated value should be computed. */ -float Ipo::get(float time) const +float Ipo::get(float time, unsigned int index) const { - if(time m_end_time) - { - switch(m_extend) - { - case ET_CYCLIC: - time = m_start_time + fmodf(time, m_end_time-m_start_time); break; - case ET_CONST: - time = m_end_time; break; - default: - // FIXME: ET_CYCLIC_EXTRAP and ET_EXTRAP missing - assert(false); - } // switch m_extend - } // if time > m_end_time - // Avoid crash in case that only one point is given for this IPO. if(m_next_n==0) - return m_points[0].getY(); + return m_ipo_data->m_points[0][index]; +float orig_t=time; + time = m_ipo_data->adjustTime(time); // Time was reset since the last cached value for n, // reset n to start from the beginning again. - if(time < m_points[m_next_n-1].getX()) + if(time < m_ipo_data->m_points[m_next_n-1].getW()) m_next_n = 1; // Search for the first point in the (sorted) array which is greater or equal // to the current time. - while(m_next_n=m_points[m_next_n].getX()) + while(m_next_nm_points.size()-1 && + time >=m_ipo_data->m_points[m_next_n].getW()) m_next_n++; - int n = m_next_n - 1; - switch(m_interpolation) - { - case IP_CONST : return m_points[n].getY(); - case IP_LINEAR : { - float t = time-m_points[n].getX(); - return m_points[n].getY() - + t*(m_points[n+1].getY()-m_points[n].getY()) / - (m_points[n+1].getX()-m_points[n].getX()); - } - case IP_BEZIER: { - if(n==(int)m_points.size()-1) - { - // FIXME: only const implemented atm. - return m_points[n].getY(); - } - float t = (time-m_points[n].getX()) - / (m_points[n+1].getX()-m_points[n].getX()); - return getCubicBezier(t, - m_points[n].getY(), - m_handle2[n].getY(), - m_handle1[n+1].getY(), - m_points[n+1].getY()); - } - } // switch - // Keep the compiler happy: - return 0; -} // get - -// ---------------------------------------------------------------------------- -/** Computes a cubic bezier curve for a given t in [0,1] and four control - * points. The curve will go through p0 (t=0), p3 (t=1). - * \param t The parameter for the bezier curve, must be in [0,1]. - * \param p0, p1, p2, p3 The four control points. - */ -float Ipo::getCubicBezier(float t, float p0, float p1, - float p2, float p3) const -{ - float c = 3.0f*(p1-p0); - float b = 3.0f*(p2-p1)-c; - float a = p3 - p0 - c - b; - return ((a*t+b)*t+c)*t+p0; -} // bezier -// ---------------------------------------------------------------------------- -/** 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].getX() > 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].getX() < x); - extend(x, m_points.size()-1); -} // extendEnd - -// ---------------------------------------------------------------------------- -/** 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: - { - Vec3 new_point(x, m_points[n].getY(), 0); - if(n==0) - m_points.insert(m_points.begin(), new_point); - else - m_points.push_back( new_point); - break; - } - case IP_LINEAR: - { - Vec3 new_point(x, m_points[n].getY(), 0); - 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 - Vec3 new_h1 = m_handle1[n] + Vec3(x - m_points[n].getX() ,0, 0); - Vec3 new_h2 = m_handle2[n] + Vec3(x - m_points[n].getX() ,0, 0); - Vec3 new_p(x, m_points[n].getY()); - 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; - } - } -} // extend - + return m_ipo_data->get(time, index, m_next_n-1); +} // get \ No newline at end of file diff --git a/src/animations/ipo.hpp b/src/animations/ipo.hpp index d45e1da57..51aef376a 100644 --- a/src/animations/ipo.hpp +++ b/src/animations/ipo.hpp @@ -47,61 +47,89 @@ public: IPO_MAX}; static const std::string m_all_channel_names[IPO_MAX]; private: - /** The type of this IPO. */ - IpoChannelType m_channel; + /** This object stores the read-only data of an IPO. It might be + * shared among several instances of an Ipo (e.g. in a cannon + * animation). */ + class IpoData + { + public: + /** The type of this IPO. */ + IpoChannelType m_channel; - /** The three interpolations defined by blender. */ - enum {IP_CONST, IP_LINEAR, IP_BEZIER} m_interpolation; - /** The four extend types. */ - enum {ET_CONST, ET_EXTRAP, ET_CYCLIC_EXTRAP, ET_CYCLIC} m_extend; + /** The three interpolations defined by blender. */ + enum {IP_CONST, IP_LINEAR, IP_BEZIER} m_interpolation; + /** The four extend types. */ + enum {ET_CONST, ET_EXTRAP, ET_CYCLIC_EXTRAP, ET_CYCLIC} m_extend; - /** The actual control points. */ - std::vector m_points; + /** The actual control points. */ + std::vector m_points; - /** Only used for bezier curves: the two handles. */ - std::vector m_handle1, m_handle2; + /** Only used for bezier curves: the two handles. */ + std::vector m_handle1, m_handle2; - /** Frames per second for this animation. */ - float m_fps; + /** Time of the first control point. */ + float m_start_time; - /** Time of the first control point. */ - float m_start_time; + /** Time of the last control point. */ + float m_end_time; - /** Time of the last control point. */ - float m_end_time; + /** Stores the inital position of the object. */ + Vec3 m_initial_xyz; + + /** Stores the inital rotation of the object. */ + Vec3 m_initial_hpr; + private: + float getCubicBezier(float t, float p0, float p1, + float p2, float p3) const; + void approximateBezier(float t0, float t1, + const Vec3 &p0, const Vec3 &p1, + const Vec3 &h0, const Vec3 &h2, + unsigned int rec_level = 0); + public: + IpoData(const XMLNode &curve, float fps); + void readCurve(const XMLNode &node); + void readIPO(const XMLNode &node, float fps); + float approximateLength(float t0, float t1, + const Vec3 &p0, const Vec3 &p1, + const Vec3 &h1, const Vec3 &h2); + float adjustTime(float time); + float get(float time, unsigned int index, unsigned int n); + + }; // IpoData + // ------------------------------------------------------------------------ + /** The actual data of the IPO. This can be shared between Ipo (e.g. each + * cannon animation will use the same IpoData block, but its own instance + * of Ipo, since data like m_next_n should not be shared). */ + IpoData *m_ipo_data; + + /** True if m_ipo_data is 'owned' by this object and therefore needs to be + * freed. If an Ipo is cloned, it will share a reference to m_ipo_data, + * and must therefore not free it. */ + bool m_own_ipo_data; /** 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). */ + * 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. */ - Vec3 m_initial_xyz; - - /** Stores the inital rotation of the object. */ - Vec3 m_initial_hpr; - - void extend(float x, unsigned int n); - float getCubicBezier(float t, float p0, float p1, - float p2, float p3) const; + Ipo(const Ipo *ipo); public: - Ipo(const XMLNode &curve, float fps); - void update(float time, Vec3 *xyz, Vec3 *hpr, Vec3 *scale); - float get(float time) const; - void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr); - void reset(); - - void extendStart(float x); - void extendEnd(float x); + Ipo(const XMLNode &curve, float fps=25); + virtual ~Ipo(); + Ipo *clone(); + void update(float time, Vec3 *xyz, Vec3 *hpr, Vec3 *scale); + float get(float time, unsigned int index) const; + void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr); + void reset(); // ------------------------------------------------------------------------ /** Returns the raw data points for this IPO. */ - const std::vector& getPoints() const { return m_points; } + const std::vector& getPoints() const { return m_ipo_data->m_points; } // ------------------------------------------------------------------------ /** Returns the last specified time (i.e. not considering any extend * types). */ - float getEndTime() const { return m_end_time; } + float getEndTime() const { return m_ipo_data->m_end_time; } }; // Ipo #endif diff --git a/src/animations/three_d_animation.cpp b/src/animations/three_d_animation.cpp index d5dddf364..c0c686e44 100644 --- a/src/animations/three_d_animation.cpp +++ b/src/animations/three_d_animation.cpp @@ -20,7 +20,6 @@ #include -#include "animations/ipo.hpp" #include "graphics/irr_driver.hpp" #include "graphics/material.hpp" #include "graphics/material_manager.hpp" diff --git a/src/karts/abstract_kart_animation.cpp b/src/karts/abstract_kart_animation.cpp index 760d546c4..3932a1e60 100644 --- a/src/karts/abstract_kart_animation.cpp +++ b/src/karts/abstract_kart_animation.cpp @@ -18,6 +18,7 @@ #include "karts/abstract_kart.hpp" #include "karts/abstract_kart_animation.hpp" +#include "modes/world.hpp" AbstractKartAnimation::AbstractKartAnimation(AbstractKart *kart, const std::string &name) @@ -42,8 +43,24 @@ AbstractKartAnimation::AbstractKartAnimation(AbstractKart *kart, // Register this animation with the kart (which will free it // later). kart->setKartAnimation(this); + World::getWorld()->getPhysics()->removeKart(m_kart); } // AbstractKartAnimation +// ---------------------------------------------------------------------------- +AbstractKartAnimation::~AbstractKartAnimation() +{ + // If m_timer >=0, this object is deleted because the kart + // is deleted (at the end of a race), which means that + // world is in the process of being deleted. In this case + // we can't call getPhysics() anymore. + if(m_timer < 0) + { + //m_kart->getBody()->setLinearVelocity(btVector3(0,0,0)); + m_kart->getBody()->setAngularVelocity(btVector3(0,0,0)); + World::getWorld()->getPhysics()->addKart(m_kart); + } +} // ~AbstractKartAnimation + // ---------------------------------------------------------------------------- /** Updates the timer, and if it expires (<0), the kart animation will be * removed from the kart and this object will be deleted. diff --git a/src/karts/abstract_kart_animation.hpp b/src/karts/abstract_kart_animation.hpp index 5133cd148..ccbe03ac6 100644 --- a/src/karts/abstract_kart_animation.hpp +++ b/src/karts/abstract_kart_animation.hpp @@ -50,7 +50,7 @@ protected: public: AbstractKartAnimation(AbstractKart *kart, const std::string &name); - virtual ~AbstractKartAnimation() {} + virtual ~AbstractKartAnimation(); virtual void update(float dt); // ------------------------------------------------------------------------ virtual float getAnimationTimer() const { return m_timer; } diff --git a/src/karts/cannon_animation.cpp b/src/karts/cannon_animation.cpp index e690e08f3..799b8d026 100644 --- a/src/karts/cannon_animation.cpp +++ b/src/karts/cannon_animation.cpp @@ -19,19 +19,42 @@ #include "karts/cannon_animation.hpp" #include "animations/animation_base.hpp" +#include "animations/ipo.hpp" +#include "animations/three_d_animation.hpp" #include "karts/abstract_kart.hpp" +#include "karts/kart_properties.hpp" #include "modes/world.hpp" #include "LinearMath/btTransform.h" -CannonAnimation::CannonAnimation(AbstractKart *kart, AnimationBase *ab) +CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo, + const Vec3 &delta) : AbstractKartAnimation(kart, "CannonAnimation") { + m_curve = new AnimationBase(ipo); + m_timer = ipo->getEndTime(); + Vec3 xyz = m_kart->getXYZ(); + Vec3 hpr, scale; + // Get the curve position at t=0 + m_curve->update(0, &xyz, &hpr, &scale); + m_offset = m_kart->getXYZ() - xyz-delta; + m_delta = delta; } // CannonAnimation // ---------------------------------------------------------------------------- CannonAnimation::~CannonAnimation() { + delete m_curve; + float epsilon = 0.5f * m_kart->getKartHeight(); + + btTransform pos; + pos.setOrigin(m_kart->getXYZ()+btVector3(0, m_kart->getKartHeight() + epsilon, + 0)); + pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f), m_kart->getHeading())); + + m_kart->getBody()->setCenterOfMassTransform(pos); + Vec3 v(0, 0, m_kart->getKartProperties()->getMaxSpeed()); + m_kart->setVelocity(pos.getBasis()*v); } // ~CannonAnimation // ---------------------------------------------------------------------------- @@ -41,5 +64,23 @@ CannonAnimation::~CannonAnimation() */ void CannonAnimation::update(float dt) { + if(m_timer < dt) + { + AbstractKartAnimation::update(dt); + return; + } + Vec3 xyz = m_kart->getXYZ(); + core::vector3df old_xyz = xyz.toIrrVector(); + Vec3 hpr, scale; + m_curve->update(dt, &xyz, &hpr, &scale); + + Vec3 rotated_delta = m_kart->getTrans().getBasis()*m_delta; + rotated_delta = Vec3(0,0,0); + Vec3 new_xyz = xyz+rotated_delta+m_offset; + m_kart->setXYZ(new_xyz); + + core::vector3df rot = (new_xyz.toIrrVector()-old_xyz).getHorizontalAngle(); + btQuaternion q(Vec3(0,1,0),rot.Y*DEGREE_TO_RAD); + m_kart->setRotation(q); AbstractKartAnimation::update(dt); } // update diff --git a/src/karts/cannon_animation.hpp b/src/karts/cannon_animation.hpp index d33246c45..f0d6fdf76 100644 --- a/src/karts/cannon_animation.hpp +++ b/src/karts/cannon_animation.hpp @@ -29,17 +29,26 @@ class AbstractKart; class AnimationBase; +class Ipo; class CannonAnimation: public AbstractKartAnimation { protected: - /** The offset between the point where the check line was originially - * crossed and the origin of the curve. */ - Vec3 delta; + /** The offset between the origin of the curve (relative to which + * all points are interpolated) and the position of the kart. */ + Vec3 m_offset; + /** An offset that is rotated with the kart and is added to the + * interpolated point. This basically shifts the curve (usually) + * to the left/right to be aligned with the crossing point of the + * kart. */ + Vec3 m_delta; + /** Stores the curve interpolation for the cannon. */ + AnimationBase *m_curve; + public: - CannonAnimation(AbstractKart *kart, AnimationBase *ab); + CannonAnimation(AbstractKart *kart, Ipo *ipo, const Vec3 &delta); virtual ~CannonAnimation(); virtual void update(float dt); diff --git a/src/karts/controller/default_ai_controller.cpp b/src/karts/controller/default_ai_controller.cpp index 775c9359e..a24d88179 100644 --- a/src/karts/controller/default_ai_controller.cpp +++ b/src/karts/controller/default_ai_controller.cpp @@ -176,6 +176,10 @@ void DefaultAIController::update(float dt) m_controls->m_look_back = false; m_controls->m_nitro = false; + // Don't do anything if there is currently a kart animations shown. + if(m_kart->getKartAnimation()) + return; + // Having a non-moving AI can be useful for debugging, e.g. aiming // or slipstreaming. #undef AI_DOES_NOT_MOVE_FOR_DEBUGGING @@ -193,7 +197,7 @@ void DefaultAIController::update(float dt) } // If the kart needs to be rescued, do it now (and nothing else) - if(isStuck()) + if(isStuck() && !m_kart->getKartAnimation()) { new RescueAnimation(m_kart); AIBaseController::update(dt); diff --git a/src/karts/explosion_animation.cpp b/src/karts/explosion_animation.cpp index 9e7a7af9f..a14e82bbb 100644 --- a/src/karts/explosion_animation.cpp +++ b/src/karts/explosion_animation.cpp @@ -77,7 +77,6 @@ ExplosionAnimation::ExplosionAnimation(AbstractKart *kart, // the right initial velocity for a kart to land back after // the specified time. m_velocity = 0.5f * m_timer * World::getWorld()->getTrack()->getGravity(); - World::getWorld()->getPhysics()->removeKart(m_kart); m_curr_rotation.setHeading(m_kart->getHeading()); m_curr_rotation.setPitch(m_kart->getPitch()); @@ -114,7 +113,6 @@ ExplosionAnimation::~ExplosionAnimation() { m_kart->getBody()->setLinearVelocity(btVector3(0,0,0)); m_kart->getBody()->setAngularVelocity(btVector3(0,0,0)); - World::getWorld()->getPhysics()->addKart(m_kart); if(m_kart->getCamera() && m_kart->getCamera()->getMode() != Camera::CM_FINAL) m_kart->getCamera()->setMode(Camera::CM_NORMAL); diff --git a/src/karts/rescue_animation.cpp b/src/karts/rescue_animation.cpp index 6bcb97127..dd96bc80c 100644 --- a/src/karts/rescue_animation.cpp +++ b/src/karts/rescue_animation.cpp @@ -49,8 +49,6 @@ RescueAnimation::RescueAnimation(AbstractKart *kart, bool is_auto_rescue) m_add_rotation = -m_curr_rotation/m_timer; m_curr_rotation.setHeading(m_kart->getHeading()); - World::getWorld()->getPhysics()->removeKart(m_kart); - // Add a hit unless it was auto-rescue if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES && !is_auto_rescue) diff --git a/src/tracks/check_cannon.cpp b/src/tracks/check_cannon.cpp index 8da866bad..668444492 100644 --- a/src/tracks/check_cannon.cpp +++ b/src/tracks/check_cannon.cpp @@ -19,6 +19,7 @@ #include "tracks/check_cannon.hpp" #include "animations/animation_base.hpp" +#include "animations/ipo.hpp" #include "io/xml_node.hpp" #include "karts/abstract_kart.hpp" #include "karts/cannon_animation.hpp" @@ -54,7 +55,7 @@ CheckCannon::CheckCannon(const XMLNode &node, unsigned int index) exit(-1); } m_target.setLine(p1, p2); - m_curve = new CannonCurve(node); + m_curve = new Ipo(*(node.getNode("curve"))); } // CheckCannon // ---------------------------------------------------------------------------- @@ -68,5 +69,10 @@ void CheckCannon::trigger(unsigned int kart_index) { Vec3 target(m_target.getMiddle()); AbstractKart *kart = World::getWorld()->getKart(kart_index); - new CannonAnimation(kart, m_curve); + if(kart->getKartAnimation()) return; + + const core::vector2df &cross = getCrossPoint(); + const core::line2df &line = getLine2D(); + Vec3 delta = Vec3(1,0,0) * (line.start-cross).getLength(); + new CannonAnimation(kart, m_curve->clone(), delta); } // CheckCannon diff --git a/src/tracks/check_cannon.hpp b/src/tracks/check_cannon.hpp index eecc60b81..5f69b147b 100644 --- a/src/tracks/check_cannon.hpp +++ b/src/tracks/check_cannon.hpp @@ -23,6 +23,7 @@ #include "tracks/check_line.hpp" class CheckManager; +class Ipo; class XMLNode; /** @@ -52,7 +53,7 @@ protected: // ------------------------------------------------------------------------ /** Stores the cannon curve data. */ - CannonCurve *m_curve; + Ipo *m_curve; public: CheckCannon(const XMLNode &node, unsigned int index); diff --git a/src/tracks/check_line.cpp b/src/tracks/check_line.cpp index 289270724..b38db4ea9 100644 --- a/src/tracks/check_line.cpp +++ b/src/tracks/check_line.cpp @@ -132,15 +132,15 @@ bool CheckLine::isTriggered(const Vec3 &old_pos, const Vec3 &new_pos, int indx) bool result=sign!=m_previous_sign[indx]; // If the sign has changed, i.e. the infinite line was crossed somewhere, // check if the finite line was actually crossed: - core::vector2df out; if(sign!=m_previous_sign[indx] && m_line.intersectWith(core::line2df(old_pos.toIrrVector2d(), - new_pos.toIrrVector2d()), out) ) + new_pos.toIrrVector2d()), + m_cross_point) ) { // Now check the minimum height: the kart position must be within a // reasonable distance in the Z axis - 'reasonable' for now to be // between -1 and 4 units (negative numbers are unlikely, but help - // in case that there is 'somewhat' inside of the track, or the + // in case that the kart is 'somewhat' inside of the track, or the // checklines are a bit off in Z direction. result = new_pos.getY()-m_min_height-m_under_min_height; diff --git a/src/tracks/check_line.hpp b/src/tracks/check_line.hpp index ad158f51b..893417365 100644 --- a/src/tracks/check_line.hpp +++ b/src/tracks/check_line.hpp @@ -19,8 +19,9 @@ #ifndef HEADER_CHECK_LINE_HPP #define HEADER_CHECK_LINE_HPP -#include #include +#include +#include using namespace irr; #include "tracks/check_structure.hpp" @@ -45,6 +46,8 @@ private: /** The line that is tested for being crossed. */ core::line2df m_line; + core::vector2df m_cross_point; + /** The minimum height of the checkline. */ float m_min_height; @@ -66,11 +69,17 @@ private: public: CheckLine(const XMLNode &node, unsigned int index); virtual ~CheckLine(); - virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos, int indx); + virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos, + int indx); virtual void reset(const Track &track); virtual void changeDebugColor(bool is_active); /** Returns the actual line data for this checkpoint. */ const core::line2df &getLine2D() const {return m_line;} + // ------------------------------------------------------------------------ + /** Returns the 2d point at which the line was crossed. Note that this + * value is ONLY valid after isTriggered is called and inside of + * trigger(). */ + const core::vector2df &getCrossPoint() const { return m_cross_point; } }; // CheckLine #endif diff --git a/src/tracks/check_structure.cpp b/src/tracks/check_structure.cpp index 26d063679..2749a95a4 100644 --- a/src/tracks/check_structure.cpp +++ b/src/tracks/check_structure.cpp @@ -48,7 +48,8 @@ CheckStructure::CheckStructure(const XMLNode &node, unsigned int index) m_check_type = CT_TOGGLE; else if(kind=="ambient-light") m_check_type = CT_AMBIENT_SPHERE; - else if(kind=="cannon") + // Cannons don't have a kind specified, so test for the name in this case + else if(node.getName()=="cannon") m_check_type = CT_CANNON; else { diff --git a/src/tracks/track_object.cpp b/src/tracks/track_object.cpp index cf31dda8b..e20720598 100644 --- a/src/tracks/track_object.cpp +++ b/src/tracks/track_object.cpp @@ -235,6 +235,14 @@ TrackObject::TrackObject(const core::vector3df& pos, const core::vector3df& hpr, reset(); } // TrackObject +// ---------------------------------------------------------------------------- +TrackObject::TrackObject() +{ + m_node = NULL; + m_mesh = NULL; + m_sound = NULL; +} // TrackObject() + // ---------------------------------------------------------------------------- /** Destructor. Removes the node from the scene graph, and also * drops the textures of the mesh. Sound buffers are also freed. diff --git a/src/tracks/track_object.hpp b/src/tracks/track_object.hpp index 6e9c22888..41a7ba8cd 100644 --- a/src/tracks/track_object.hpp +++ b/src/tracks/track_object.hpp @@ -95,6 +95,7 @@ protected: public: TrackObject(const XMLNode &xml_node); + TrackObject(); TrackObject(const core::vector3df& pos, const core::vector3df& hpr, const core::vector3df& scale, const std::string& model); ~TrackObject(); diff --git a/src/tracks/track_object_manager.cpp b/src/tracks/track_object_manager.cpp index b9f84d153..a5ffaa319 100644 --- a/src/tracks/track_object_manager.cpp +++ b/src/tracks/track_object_manager.cpp @@ -18,6 +18,7 @@ #include "tracks/track_object_manager.hpp" +#include "animations/ipo.hpp" #include "config/user_config.hpp" #include "animations/billboard_animation.hpp" #include "animations/three_d_animation.hpp" diff --git a/src/utils/vec3.hpp b/src/utils/vec3.hpp index e22fbd503..956b0ed17 100644 --- a/src/utils/vec3.hpp +++ b/src/utils/vec3.hpp @@ -56,6 +56,10 @@ public: /** Creates a 3d vector from three scalars. */ inline Vec3(float x, float y, float z) : btVector3(x,y,z) {} // ------------------------------------------------------------------------ + /** Creates a 3d vector from three scalars. */ + inline Vec3(float x, float y, float z, float w) : btVector3(x,y,z) + { setW(w); } + // ------------------------------------------------------------------------ /** Initialises a 3d vector from one scalar value, which is used to * initialise all components. */ inline Vec3(float x) : btVector3(x,x,x) {}