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
This commit is contained in:
hikerstk 2012-04-18 13:14:18 +00:00
parent 22e0f0f9c0
commit d0fb8aa78a
21 changed files with 549 additions and 293 deletions

View File

@ -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<Vec3>& 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

View File

@ -27,11 +27,13 @@
#include <vector>
// Note that ipo.hpp is included here in order that PtrVector<Ipo> 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<Ipo> 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();

View File

@ -20,12 +20,20 @@
#include "io/xml_node.hpp"
#include <string.h>
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; i<IPO_MAX; i++)
{
if(m_all_channel_names[i]==channel) m_channel=(IpoChannelType)i;
if(m_all_channel_names[i]==channel)
{
m_channel=(IpoChannelType)i;
break;
}
}
if(m_channel==IPO_MAX)
{
fprintf(stderr, "Unknown animation channel: '%s' - aborting.\n", channel.c_str());
fprintf(stderr, "Unknown animation channel: '%s' - aborting.\n",
channel.c_str());
exit(-1);
}
@ -52,6 +65,23 @@ Ipo::Ipo(const XMLNode &curve, float fps)
else if(interp=="linear") m_interpolation = IP_LINEAR;
else m_interpolation = IP_BEZIER;
if(m_channel==IPO_LOCXYZ)
readCurve(curve);
else
readIPO(curve, fps);
// ATM no other extends are supported, so hardcode the only one
// that works!
m_extend = ET_CYCLIC;
} // IpoData
// ----------------------------------------------------------------------------
/** Reads a blender IPO curve, which constists of a frame number and a control
* point. This only handles a single axis.
* \param node The root node with all curve data points.
*/
void Ipo::IpoData::readIPO(const XMLNode &curve, float fps)
{
m_start_time = 999999.9f;
m_end_time = -999999.9f;
for(unsigned int i=0; i<curve.getNumNodes(); i++)
@ -61,30 +91,303 @@ Ipo::Ipo(const XMLNode &curve, float fps)
node->get("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<getNumNodes()
} // IpoData::readIPO
// ATM no other extends are supported, so hardcode the only one
// that works!
m_extend = ET_CYCLIC;
// ----------------------------------------------------------------------------
/** Reads in 3 dimensional curve data - i.e. the xml file contains xyz, but no
* time. If the curve is using bezier interpolation, the curve is
* approximated by piecewise linear functions. Reason is that bezier curves
* can not (easily) be used for smooth (i.e. constant speed) driving:
* A linear time variation in [0, 1] will result in non-linear distances
* for the bezier function, which is a 3rd degree polynomial (--> 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; i<curve.getNumNodes(); i++)
{
const XMLNode *node = curve.getNode(i);
Vec3 point;
node->get("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 i<getNumNodes()
// The handles of a bezier curve are not needed anymore and can be
// removed now (since the bezier funciton has been replaced with a
// piecewise linear
if(m_interpolation==IP_BEZIER)
{
m_handle1.clear();
m_handle2.clear();
m_interpolation = IP_LINEAR;
}
if(m_points.size()==0) return;
// Compute the time for each segment based on the speed and
// store it in the W component.
m_points[0].setW(0);
for(unsigned int i=1; i<m_points.size(); i++)
{
m_points[i].setW( (m_points[i]-m_points[i-1]).length()/speed
+ m_points[i-1].getW() );
}
m_end_time = m_points.back().getW();
} // IpoData::readCurve
// ----------------------------------------------------------------------------
/** This function approximates a bezier curve by piecewise linear functions.
* It uses quite primitive approximations: if the estimated distance of
* the bezier curve at between t=t0 and t=t1 is greater than 2, it
* inserts one point at (t0+t1)/2, and recursively splits the two intervals
* further. End condition is either a maximum recursion depth of 6 or
* an estimated curve length of less than 2. It does not add any points
* at t=t0 or t=t1, only between this interval.
* \param t0, t1 The interval which is approximated.
* \param p0, p1, h0, h1: The bezier parameters.
* \param rec_level The recursion level to avoid creating too many points.
*/
void Ipo::IpoData::approximateBezier(float t0, float t1,
const Vec3 &p0, const Vec3 &p1,
const Vec3 &h0, const Vec3 &h1,
unsigned int rec_level)
{
// Limit the granularity by limiting the recursion depth
if(rec_level>6)
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_start_time)
{
switch(m_extend)
{
case IpoData::ET_CYCLIC:
time = m_start_time + fmodf(time, m_end_time-m_start_time); break;
case ET_CONST:
time = m_start_time; break;
default:
// FIXME: ET_CYCLIC_EXTRAP and ET_EXTRAP missing
assert(false);
} // switch m_extend
} // if time < m_start_time
else 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_start_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_start_time; break;
default:
// FIXME: ET_CYCLIC_EXTRAP and ET_EXTRAP missing
assert(false);
} // switch m_extend
} // if time < m_start_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
// 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.size()-1 && time >=m_points[m_next_n].getX())
while(m_next_n<m_ipo_data->m_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

View File

@ -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<Vec3> m_points;
/** The actual control points. */
std::vector<Vec3> m_points;
/** Only used for bezier curves: the two handles. */
std::vector<Vec3> m_handle1, m_handle2;
/** Only used for bezier curves: the two handles. */
std::vector<Vec3> 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<Vec3>& getPoints() const { return m_points; }
const std::vector<Vec3>& 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

View File

@ -20,7 +20,6 @@
#include <stdio.h>
#include "animations/ipo.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"

View File

@ -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.

View File

@ -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; }

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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_over_min_height &&
new_pos.getY()-m_min_height>-m_under_min_height;

View File

@ -19,8 +19,9 @@
#ifndef HEADER_CHECK_LINE_HPP
#define HEADER_CHECK_LINE_HPP
#include <line2d.h>
#include <IMeshSceneNode.h>
#include <line2d.h>
#include <vector2d.h>
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

View File

@ -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
{

View File

@ -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.

View File

@ -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();

View File

@ -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"

View File

@ -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) {}