diff --git a/src/animations/animation_base.cpp b/src/animations/animation_base.cpp index ff10089e0..029273cf7 100644 --- a/src/animations/animation_base.cpp +++ b/src/animations/animation_base.cpp @@ -107,3 +107,36 @@ void AnimationBase::update(float dt, Vec3 *xyz, Vec3 *hpr, Vec3 *scale) } } // update +// ---------------------------------------------------------------------------- +/** Return the time, position and rotation at the specified time. It does not + * update the internal timer as update() does. + * \param dt Time since last call. + * \param xyz Position to be updated. + * \param hpr Rotation to be updated. + */ +void AnimationBase::getAt(float time, Vec3 *xyz, Vec3 *hpr, Vec3 *scale) +{ + assert(!std::isnan(time)); + + // Don't do anything if the animation is disabled + if (!m_playing) return; + + for_var_in(Ipo*, curr, m_all_ipos) + { + curr->update(time, xyz, hpr, scale); + } +} // getAt + +// ---------------------------------------------------------------------------- +/** Returns the derivative at the specified point. + * \param time The time for which to determine the derivative. + * \param xyz Float pointer to store the result. + */ +void AnimationBase::getDerivativeAt(float time, Vec3 *xyz) +{ + for_var_in(Ipo*, curr, m_all_ipos) + { + curr->getDerivative(time, xyz); + } + xyz->normalize(); +} diff --git a/src/animations/animation_base.hpp b/src/animations/animation_base.hpp index 98e30f4ca..8f5430542 100644 --- a/src/animations/animation_base.hpp +++ b/src/animations/animation_base.hpp @@ -67,8 +67,11 @@ public: AnimationBase(const XMLNode &node); AnimationBase(Ipo *ipo); virtual ~AnimationBase() {} - virtual void update(float dt, Vec3 *xyz=NULL, Vec3 *hpr=NULL, - Vec3 *scale=NULL); + virtual void update(float dt, Vec3 *xyz=NULL, Vec3 *hpr=NULL, + Vec3 *scale=NULL); + virtual void getAt(float time, Vec3 *xyz = NULL, Vec3 *hpr = NULL, + Vec3 *scale = NULL); + virtual void getDerivativeAt(float time, Vec3 *xyz); /** This needs to be implemented by the inheriting classes. It is called * once per frame from the track. It has a dummy implementation that * just asserts so that this class can be instantiated in diff --git a/src/animations/ipo.cpp b/src/animations/ipo.cpp index 139378710..2338a4ec1 100644 --- a/src/animations/ipo.cpp +++ b/src/animations/ipo.cpp @@ -346,7 +346,7 @@ float Ipo::IpoData::get(float time, unsigned int index, unsigned int n) } // switch // Keep the compiler happy: return 0; -} // get +} // IpoData::get // ---------------------------------------------------------------------------- /** Computes a cubic bezier curve for a given t in [0,1] and four control @@ -361,39 +361,65 @@ float Ipo::IpoData::getCubicBezier(float t, float p0, float p1, float b = 3.0f*(p2-p1)-c; float a = p3 - p0 - c - b; return ((a*t+b)*t+c)*t+p0; -} // bezier +} // getCubicBezier // ---------------------------------------------------------------------------- -/** Determines the rotation between the start and end of this curve. +/** Determines the derivative of a IPO at a given point. + * \param time At what time value the derivative is to be computed. + * \param index IpoData is based on 3d data. The index specified which + * value to use (0=x, 1=y, 2=z). + * \param n Curve segment to be used for the computation. It must be correct + * for the specified time value. */ -btQuaternion Ipo::IpoData::getOverallRotation() +float Ipo::IpoData::getDerivative(float time, unsigned int index, + unsigned int n) { - // Vectors at start and end of curve - Vec3 start, end; - - if (m_interpolation == IP_BEZIER) + switch (m_interpolation) { - // In case of Bezier use the handles to get initial and final - // orientation - start = m_handle2[0] - m_handle1[0]; - end = *m_handle2.back() - *m_handle1.back(); - } - else // Const or linear - { - // In this case determine the start vector by selecting using the - // beginning and a second point a bit further on the curve - start.setX(get(m_start_time + 0.1f, 0, 0) - m_points[0].getX()); - start.setY(get(m_start_time + 0.1f, 1, 0) - m_points[0].getY()); - start.setZ(get(m_start_time + 0.1f, 2, 0) - m_points[0].getZ()); - int n = m_points.size() - 2; - end. setX(get(m_end_time - 0.1f, 0, n) - m_points[n].getX()); - end. setY(get(m_end_time - 0.1f, 1, n) - m_points[n].getY()); - end. setZ(get(m_end_time - 0.1f, 2, n) - m_points[n].getZ()); + case IP_CONST: return 0; // Const --> Derivative is 0 + case IP_LINEAR: { + return (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) + { + // Only const, so derivative is 0 + return 0; + } + float t = (time - m_points[n].getW()) + / (m_points[n + 1].getW() - m_points[n].getW()); + return getCubicBezierDerivative(t, + m_points [n ][index], + m_handle2[n ][index], + m_handle1[n + 1][index], + m_points [n + 1][index] ); + } // case IPBEZIER + default: + Log::warn("Ipo::IpoData", "Incorrect interpolation %d", + m_interpolation); + } // switch + return 0; +} // IpoData::getDerivative - btQuaternion q = shortestArcQuatNormalize2(start, end); - return q; -} // IpData::getOverallRoation + +// ---------------------------------------------------------------------------- +/** Returns the derivative of a cubic bezier curve for a given t in [0,1] and + * four control points. The curve will go through p0 (t=0). + * \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::getCubicBezierDerivative(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; + // f(t) = ((a*t + b)*t + c)*t + p0; + // = a*t^3 +b*t^2 + c*t + p0 + // --> f'(t) = 3*a*t^2 + 2*b*t + c + return (3*a * t + 2*b) * t + c; +} // bezier // ============================================================================ /** The Ipo constructor. Ipos can share the actual data to interpolate, which @@ -503,6 +529,28 @@ void Ipo::update(float time, Vec3 *xyz, Vec3 *hpr,Vec3 *scale) } // update +// ---------------------------------------------------------------------------- +/** Updates the value of m_next_n to point to the right ipo segment based on + * the time. + * \param t Time for which m_next_n needs to be updated. + */ +void Ipo::updateNextN(float time) const +{ + 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_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_ipo_data->m_points.size() - 1 && + time >= m_ipo_data->m_points[m_next_n].getW()) + { + m_next_n++; + } // while +} // updateNextN + // ---------------------------------------------------------------------------- /** Returns the interpolated value at the current time (which this objects * keeps track of). @@ -516,34 +564,50 @@ float Ipo::get(float time, unsigned int index) const if(m_next_n==0) return m_ipo_data->m_points[0][index]; - time = m_ipo_data->adjustTime(time); + updateNextN(time); - // Time was reset since the last cached value for n, - // reset n to start from the beginning again. - 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_nm_points.size()-1 && - time >=m_ipo_data->m_points[m_next_n].getW()) - m_next_n++; float rval = m_ipo_data->get(time, index, m_next_n-1); assert(!std::isnan(rval)); return rval; } // get // ---------------------------------------------------------------------------- -/** Return the quaternion that rotates an object form the start of the IPO - * to the end. +/** Returns the derivative for any location based curves. + * \param time Time for which the derivative is being computed. + * \param xyz Pointer where the results should be stored. */ -btQuaternion Ipo::getOverallRotation() +void Ipo::getDerivative(float time, Vec3 *xyz) { - // In case of a single point only: + // Avoid crash in case that only one point is given for this IPO. if (m_next_n == 0) { - // Return a unit quaternion - btQuaternion q(0, 0, 0, 1); - return q; + // Derivative has no real meaning in case of a single point. + // So just return a dummy value. + xyz->setValue(1, 0, 0); + return; } - return m_ipo_data->getOverallRotation(); -} // getOverallRoation \ No newline at end of file + + updateNextN(time); + switch (m_ipo_data->m_channel) + { + case Ipo::IPO_LOCX: xyz->setX(m_ipo_data->getDerivative(time, m_next_n, 0)); break; + case Ipo::IPO_LOCY: xyz->setY(m_ipo_data->getDerivative(time, m_next_n, 0)); break; + case Ipo::IPO_LOCZ: xyz->setZ(m_ipo_data->getDerivative(time, m_next_n, 0)); break; + case Ipo::IPO_LOCXYZ: + { + if (xyz) + { + for (unsigned int j = 0; j < 3; j++) + (*xyz)[j] = m_ipo_data->getDerivative(time, j, m_next_n-1); + } + break; + } + default: Log::warn("IPO", "Unexpected channel %d for derivate.", + m_ipo_data->m_channel ); + xyz->setValue(1, 0, 0); + break; + } // switch + + +} // getDerivative + diff --git a/src/animations/ipo.hpp b/src/animations/ipo.hpp index 39eda9816..f1b83a3e9 100644 --- a/src/animations/ipo.hpp +++ b/src/animations/ipo.hpp @@ -81,6 +81,8 @@ private: private: float getCubicBezier(float t, float p0, float p1, float p2, float p3) const; + float getCubicBezierDerivative(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, @@ -94,7 +96,7 @@ private: const Vec3 &h1, const Vec3 &h2); float adjustTime(float time); float get(float time, unsigned int index, unsigned int n); - btQuaternion getOverallRotation(); + float getDerivative(float time, unsigned int index, unsigned int n); }; // IpoData // ------------------------------------------------------------------------ @@ -114,6 +116,8 @@ private: * it is declared mutable). */ mutable unsigned int m_next_n; + void updateNextN(float time) const; + Ipo(const Ipo *ipo); public: Ipo(const XMLNode &curve, float fps=25, bool reverse=false); @@ -121,10 +125,10 @@ public: Ipo *clone(); void update(float time, Vec3 *xyz=NULL, Vec3 *hpr=NULL, Vec3 *scale=NULL); + void getDerivative(float time, Vec3 *xyz); float get(float time, unsigned int index) const; void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr); void reset(); - btQuaternion getOverallRotation(); // ------------------------------------------------------------------------ /** Returns the raw data points for this IPO. */ const std::vector& getPoints() const { return m_ipo_data->m_points; } diff --git a/src/karts/cannon_animation.cpp b/src/karts/cannon_animation.cpp index 5f0666b50..10e7ec328 100644 --- a/src/karts/cannon_animation.cpp +++ b/src/karts/cannon_animation.cpp @@ -35,26 +35,90 @@ CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo, m_curve = new AnimationBase(ipo); m_timer = ipo->getEndTime(); + float kw2 = m_kart->getKartModel()->getWidth()*0.5f; + + // First make sure that left and right points are indeed correct + Vec3 my_start_left = start_left; + Vec3 my_start_right = start_right; + Vec3 p0, p1; + m_curve->getAt(0, &p0); + m_curve->getAt(0.1f, &p1); + Vec3 p2 = 0.5f*(p0 + p1) + m_kart->getNormal(); + if (start_left.sideofPlane(p0, p1, p2) < 0) + { + my_start_left = start_right; + my_start_right = start_left; + } + // First adjust start and end points to take on each side half the kart + // width into account: + Vec3 direction = my_start_right - my_start_left; + direction.normalize(); + + Vec3 adj_start_left = my_start_left + kw2 * direction; + Vec3 adj_start_right = my_start_right - kw2 * direction; + + // Same adjustments for end points + float t = m_curve->getAnimationDuration(); + Vec3 my_end_left = end_left; + Vec3 my_end_right = end_right; + m_curve->getAt(t-0.1f, &p0); + m_curve->getAt(t, &p1); + p2 = 0.5f*(p0 + p1) + m_kart->getNormal(); + if (end_left.sideofPlane(p0, p1, p2) < 0) + { + my_end_left = end_right; + my_end_right = end_left; + } + // Left and right end points are sometimes swapped + direction = my_end_right - my_end_left; + direction.normalize(); + + Vec3 adj_end_left = my_end_left + kw2 * direction; + Vec3 adj_end_right = my_end_right - kw2 * direction; + + // The kart position is divided into three components: + // 1) The point at the curve at t=0. + // 2) A component parallel to the start line. This component is scaled + // depending on time, length of start- and end-line (e.g. if the + // end line is twice as long as the start line, this will make sure + // that a kart starting at the very left of the start line will end + // up at the very left of the end line. This component can also be + // adjusted by steering while in the air. This is done by modifying + // m_fraction_of_line, which is multiplied with the current width + // vector. + // 3) The rest, i.e. the amoount that the kart is ahead and above the + // start line. This is stored in m_delta. + // // Compute the delta between the kart position and the start of the curve. // This delta is rotated with the kart and added to the interpolated curve // position to get the actual kart position during the animation. - m_curve->update(0, &m_previous_orig_xyz); - m_delta = kart->getXYZ() - m_previous_orig_xyz; - Vec3 v1 = start_left - start_right; - Vec3 v2 = end_left - end_right; - m_delta_rotation = shortestArcQuatNormalize2(v1, v2 ); + Vec3 curve_xyz; + m_curve->update(0, &curve_xyz); + m_delta = kart->getXYZ() - curve_xyz; + m_start_line = 0.5f*(adj_start_right - adj_start_left); + m_end_line = 0.5f*(adj_end_right - adj_end_left ); + Vec3 v = adj_start_left - adj_start_right; + float l = v.length(); + v /= l; - // Now the delta vector needs to be rotated back, so that it will point - // in the right direction when it is (in update) rotated to be the same - // as the kart's heading. To estimate the angle at the start, use the - // interpolated value at t=dt: - const float dt = 0.1f; - Vec3 xyz1; - m_curve->update(dt, &xyz1); + // Compute on which fraction of the start line the kart is + float f = v.dot(adj_start_left - kart->getXYZ()); + if (f <= 0) + f = 0; + else if (f >= l) + f = l; + else + f = f / l; + // Now f is in [0,1]. Convert it to [-1,1] assuming that the + // ipo for the cannon is in the middle of the start and end line + m_fraction_of_line = 2.0f*f - 1.0f; + Vec3 delta = m_start_line * m_fraction_of_line; + m_delta = m_delta - delta; + // The previous call to m_curve->update will set the internal timer // of the curve to dt. Reset it to 0 to make sure the timer is in // synch with the timer of the CanonAnimation @@ -68,8 +132,9 @@ CannonAnimation::~CannonAnimation() btTransform pos; pos.setOrigin(m_kart->getXYZ()); - pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f), - m_kart->getHeading() )); + //pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f), + // m_kart->getHeading() )); + pos.setRotation(m_kart->getRotation()); m_kart->getBody()->setCenterOfMassTransform(pos); Vec3 v(0, 0, m_kart->getKartProperties()->getEngineMaxSpeed()); @@ -89,30 +154,39 @@ void CannonAnimation::update(float dt) return; } + // Adjust the horizontal location based on steering + m_fraction_of_line += m_kart->getSteerPercent()*dt*2.0f; + + // The timer count backwards, so the fraction goes from 1 to 0 + float f = m_timer / m_curve->getAnimationDuration(); + + btClamp(m_fraction_of_line, -1.0f, 1.0f); + Vec3 current_width = m_start_line * f + m_end_line * (1.0f - f); + + // Get the tangent = derivative at the current point to compute the + // orientation of the kart + Vec3 tangent; + m_curve->getDerivativeAt(m_curve->getAnimationDuration() - m_timer, + &tangent); + + Vec3 forward = m_kart->getTrans().getBasis().getColumn(2); + forward.normalize(); + + // Only adjust the heading. I tried to also adjust pitch, + // but that adds a strong roll to the kart on some cannons + Vec3 v1(tangent), v2(forward); + v1.setY(0); v2.setY(0); + btQuaternion q = m_kart->getRotation()*shortestArcQuatNormalize2(v2, v1); + + m_kart->setRotation(q); + Vec3 xyz; m_curve->update(dt, &xyz); - // It can happen that the same position is returned, e.g. if the end of - // the curve is reached, but due to floating point differences the - // end is not detected in the above test. To avoid that the kart then - // rotates to a heading of 0, do not rotate in this case at all, i.e. - // the previous rotation is kept. - if(xyz!=m_previous_orig_xyz) - { - btQuaternion prev_rot = m_kart->getRotation(); - core::vector3df rot = (xyz-m_previous_orig_xyz).toIrrVector() - .getHorizontalAngle(); - btQuaternion q(Vec3(0,1,0),rot.Y*DEGREE_TO_RAD); - m_kart->setRotation(prev_rot.slerp(q,0.1f)); - } - m_previous_orig_xyz = xyz; + Vec3 rotated_delta = quatRotate(q, m_delta ) + current_width * m_fraction_of_line; + m_kart->setXYZ(xyz+rotated_delta); + // m_kart->setXYZ(xyz); - btQuaternion zero(0, 0, 0, 1); - // The timer count backwards, so the fraction goes from 1 to 0 - float f = m_timer / m_curve->getAnimationDuration(); - btQuaternion current_rot = m_delta_rotation.slerp(zero, f); - Vec3 rotated_delta = quatRotate(current_rot, m_delta); - m_kart->setXYZ(xyz + rotated_delta); AbstractKartAnimation::update(dt); } // update diff --git a/src/karts/cannon_animation.hpp b/src/karts/cannon_animation.hpp index 44019efea..74673396a 100644 --- a/src/karts/cannon_animation.hpp +++ b/src/karts/cannon_animation.hpp @@ -43,16 +43,18 @@ protected: * kart position (so the kart moves relative to the curve). */ Vec3 m_delta; - /** The amount of rotation to be applied to m_delta so that it keeps - * being on the 'right' side of the curve. */ - btQuaternion m_delta_rotation; - /** Stores the curve interpolation for the cannon. */ AnimationBase *m_curve; - /** This stores the original (unmodified) interpolated curve value. THis - * is used to determine the orientation of the kart. */ - Vec3 m_previous_orig_xyz; + /** The original checkline at start. */ + Vec3 m_start_line; + + /** The original checkline at end. */ + Vec3 m_end_line; + + /** Stores the position of the kart relative to the line width + * at the current location. */ + float m_fraction_of_line; public: CannonAnimation(AbstractKart *kart, Ipo *ipo,