1) Changed base data types in IPO to be based on Vec3 instead

of irr::core::vector2df (in preparation to support curves).
2) Re-added support for individual start and end time for
   each individual curve of an IPO (and not only for all curves
   together, which results in incorrect behaviour if not all
   individual curves have the same length).


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@11099 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk
2012-04-09 22:17:51 +00:00
parent cb7492c9a8
commit c0f5484c9c
7 changed files with 149 additions and 119 deletions

View File

@@ -39,32 +39,9 @@ AnimationBase::AnimationBase(const XMLNode &node)
if(m_all_ipos.size()==0)
{
printf("Warning: empty animation curve.\n");
m_start_time = 0.0f;
m_end_time = 1.0f;
return;
//exit(-1);
}
// Determine start and end X values for this curve.
m_start_time = m_all_ipos[0].getPoints()[0].X;
m_end_time = m_start_time;
Ipo* curr;
for_in (curr, m_all_ipos)
{
const std::vector<core::vector2df>& points = curr->getPoints();
m_start_time = std::min(m_start_time, points[0].X );
m_end_time = std::max(m_end_time, points[points.size() - 1].X);
}
// Make sure all individual IPOs start and end at the same X value.
for_in (curr, m_all_ipos)
{
const std::vector<core::vector2df>& points = curr->getPoints();
if (points[points.size() - 1].X < m_end_time)
curr->extendEnd(m_end_time);
if(points[0].X > m_start_time)
curr->extendStart(m_start_time);
}
m_playing = true;
} // AnimationBase
@@ -80,8 +57,8 @@ AnimationBase::~AnimationBase()
* \param xyz Position of the object.
* \param hpr Rotation of the object.
*/
void AnimationBase::setInitialTransform(const core::vector3df &xyz,
const core::vector3df &hpr)
void AnimationBase::setInitialTransform(const Vec3 &xyz,
const Vec3 &hpr)
{
Ipo* curr;
for_in (curr, m_all_ipos)
@@ -95,7 +72,7 @@ void AnimationBase::setInitialTransform(const core::vector3df &xyz,
*/
void AnimationBase::reset()
{
m_current_time = m_start_time;
m_current_time = 0;
Ipo* curr;
for_in (curr, m_all_ipos)
{
@@ -109,13 +86,10 @@ void AnimationBase::reset()
* \param xyz Position to be updated.
* \param hpr Rotation to be updated.
*/
void AnimationBase::update(float dt, core::vector3df *xyz,
core::vector3df *hpr, core::vector3df *scale)
void AnimationBase::update(float dt, Vec3 *xyz, Vec3 *hpr, Vec3 *scale)
{
m_current_time += dt;
while(m_current_time > m_end_time)
m_current_time -= (m_end_time - m_start_time);
if ( UserConfigParams::m_graphical_effects )
{
Ipo* curr;
@@ -134,36 +108,40 @@ void AnimationBase::computeLengths()
{
Ipo* curr;
// First determine the maximum number of points among all IPOs
unsigned int max_points=0;
unsigned int max_points =0;
float max_time = 0;
for_in (curr, m_all_ipos)
{
const std::vector<core::vector2df>& points = curr->getPoints();
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 x = m_start_time;
float dx = (m_end_time - m_start_time) / STEPS ;
float x = 0;
float dx = max_time / STEPS ;
float distance = 0;
core::vector3df xyz_old(0,0,0), hpr, scale;
// 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(m_start_time, &xyz_old, &hpr, &scale);
curr->update(/*time*/0, &xyz_old, &hpr, &scale);
}
for(unsigned int i=1; i<(unsigned int)STEPS; i++)
{
float x = m_start_time + dx * i;
core::vector3df xyz(0,0,0);
// 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).getLength();
distance += (xyz-xyz_old).length();
xyz_old = xyz;
} // for i in m_points
} // computeLengths

View File

@@ -26,8 +26,6 @@
*/
#include <vector>
#include <vector3d.h>
using namespace irr;
#include "tracks/track_object.hpp"
#include "utils/ptr_vector.hpp"
@@ -49,12 +47,6 @@ private:
/** True if the animation is currently playing. */
bool m_playing;
/** The smallest time for which this animation is defined - usually 0.*/
float m_start_time;
/** The end time for this animation. */
float m_end_time;
/** The current time used in the IPOs. */
float m_current_time;
@@ -62,10 +54,10 @@ private:
float m_cycle_length;
/** The inital position of this object. */
core::vector3df m_initial_xyz;
Vec3 m_initial_xyz;
/** The initial rotation of this object. */
core::vector3df m_initial_hpr;
Vec3 m_initial_hpr;
void computeLengths();
@@ -76,13 +68,12 @@ protected:
public:
AnimationBase(const XMLNode &node);
virtual ~AnimationBase();
virtual void update(float dt, core::vector3df *xyz, core::vector3df *hpr,
core::vector3df *scale);
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;
void setInitialTransform(const core::vector3df &xyz,
const core::vector3df &hpr);
void setInitialTransform(const Vec3 &xyz,
const Vec3 &hpr);
void reset();
}; // AnimationBase

View File

@@ -67,14 +67,14 @@ void BillboardAnimation::update(float dt)
{
if ( UserConfigParams::m_graphical_effects )
{
core::vector3df xyz=m_node->getPosition();
Vec3 xyz(m_node->getPosition());
// Rotation doesn't make too much sense for a billboard,
// so just set it to 0
core::vector3df hpr(0, 0, 0);
core::vector3df scale = m_node->getScale();
Vec3 hpr(0, 0, 0);
Vec3 scale = m_node->getScale();
AnimationBase::update(dt, &xyz, &hpr, &scale);
m_node->setPosition(xyz);
m_node->setScale(scale);
m_node->setPosition(xyz.toIrrVector());
m_node->setScale(scale.toIrrVector());
// Setting rotation doesn't make sense
}

View File

@@ -21,7 +21,7 @@
#include "io/xml_node.hpp"
const std::string Ipo::m_all_channel_names[IPO_MAX] =
{"LocX", "LocY", "LocZ",
{"LocX", "LocY", "LocZ", "LocXYZ",
"RotX", "RotY", "RotZ",
"ScaleX", "ScaleY", "ScaleZ" };
@@ -52,6 +52,8 @@ Ipo::Ipo(const XMLNode &curve, float fps)
else if(interp=="linear") m_interpolation = IP_LINEAR;
else m_interpolation = IP_BEZIER;
m_start_time = 999999.9f;
m_end_time = -999999.9f;
for(unsigned int i=0; i<curve.getNumNodes(); i++)
{
const XMLNode *node = curve.getNode(i);
@@ -60,17 +62,19 @@ Ipo::Ipo(const XMLNode &curve, float fps)
// Convert blender's frame number (1 ...) into time (0 ...)
float t = (xy.X-1)/fps;
xy.X = t;
m_points.push_back(xy);
Vec3 point(t, xy.Y, 0);
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)
{
core::vector2df handle;
node->get("h1", &handle);
handle.X = (xy.X-1)/fps;
m_handle1.push_back(handle);
m_handle1.push_back(Vec3(handle.X, handle.Y, 0));
node->get("h2", &handle);
handle.X = (xy.X-1)/fps;
m_handle2.push_back(handle);
m_handle2.push_back(Vec3(handle.X, handle.Y, 0));
}
} // for i<getNumNodes()
@@ -86,8 +90,8 @@ Ipo::Ipo(const XMLNode &curve, float fps)
* \param xyz Position of the object.
* \param hpr Rotation of the object.
*/
void Ipo::setInitialTransform(const core::vector3df &xyz,
const core::vector3df &hpr)
void Ipo::setInitialTransform(const Vec3 &xyz,
const Vec3 &hpr)
{
m_initial_xyz = xyz;
m_initial_hpr = hpr;
@@ -108,20 +112,19 @@ void Ipo::reset()
* \param xyz The position that needs to be updated.
* \param hpr The rotation that needs to be updated.
*/
void Ipo::update(float time, core::vector3df *xyz, core::vector3df *hpr,
core::vector3df *scale)
void Ipo::update(float time, Vec3 *xyz, Vec3 *hpr,Vec3 *scale)
{
switch(m_channel)
{
case Ipo::IPO_LOCX : xyz->X = get(time); break;
case Ipo::IPO_LOCY : xyz->Y = get(time); break;
case Ipo::IPO_LOCZ : xyz->Z = get(time); break;
case Ipo::IPO_ROTX : hpr->X = get(time); break;
case Ipo::IPO_ROTY : hpr->Y = get(time); break;
case Ipo::IPO_ROTZ : hpr->Z = get(time); break;
case Ipo::IPO_SCALEX : scale->X = get(time); break;
case Ipo::IPO_SCALEY : scale->Y = get(time); break;
case Ipo::IPO_SCALEZ : scale->Z = get(time); break;
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;
default: assert(false); // shut up compiler warning
} // switch
@@ -134,45 +137,89 @@ void Ipo::update(float time, core::vector3df *xyz, core::vector3df *hpr,
*/
float Ipo::get(float time) 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].Y;
return m_points[0].getY();
// 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].X)
if(time < m_points[m_next_n-1].getX())
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].X)
while(m_next_n<m_points.size()-1 && time >=m_points[m_next_n].getX())
m_next_n++;
int n = m_next_n - 1;
switch(m_interpolation)
{
case IP_CONST : return m_points[n].Y;
case IP_CONST : return m_points[n].getY();
case IP_LINEAR : {
float t = time-m_points[n].X;
return m_points[n].Y + t*(m_points[n+1].Y-m_points[n].Y) /
(m_points[n+1].X-m_points[n].X);
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==m_points.size()-1)
{
// FIXME: only const implemented atm.
return m_points[n].Y;
return m_points[n].getY();
}
core::vector2df c = 3.0f*(m_handle2[n]-m_points[n]);
core::vector2df b = 3.0f*(m_handle1[n+1]-m_handle2[n])-c;
core::vector2df a = m_points[n+1] - m_points[n] - c - b;
float t = (time-m_points[n].X)/(m_points[n+1].X-m_points[n].X);
core::vector2df r = ((a*t+b)*t+c)*t+m_points[n];
return r.Y;
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.
@@ -180,7 +227,7 @@ float Ipo::get(float time) const
*/
void Ipo::extendStart(float x)
{
assert(m_points[0].X > x);
assert(m_points[0].getX() > x);
extend(x, 0);
} // extendStart
// ----------------------------------------------------------------------------
@@ -190,7 +237,7 @@ void Ipo::extendStart(float x)
*/
void Ipo::extendEnd(float x)
{
assert(m_points[m_points.size()-1].X < x);
assert(m_points[m_points.size()-1].getX() < x);
extend(x, m_points.size()-1);
} // extendEnd
@@ -207,7 +254,7 @@ void Ipo::extend(float x, unsigned int n)
{
case IP_CONST:
{
core::vector2df new_point(x, m_points[n].Y);
Vec3 new_point(x, m_points[n].getY(), 0);
if(n==0)
m_points.insert(m_points.begin(), new_point);
else
@@ -216,7 +263,7 @@ void Ipo::extend(float x, unsigned int n)
}
case IP_LINEAR:
{
core::vector2df new_point(x, m_points[n].Y);
Vec3 new_point(x, m_points[n].getY(), 0);
if(n=0)
m_points.insert(m_points.begin(), new_point);
else
@@ -225,12 +272,11 @@ void Ipo::extend(float x, unsigned int n)
}
case IP_BEZIER:
{
// FIXME: I'm somewhat dubious this is the correct way to extend handles
core::vector2df new_h1 = m_handle1[n] +
core::vector2df(x - m_points[n].X ,0);
core::vector2df new_h2 = m_handle2[n] +
core::vector2df(x - m_points[n].X ,0);
core::vector2df new_p(x, m_points[n].Y);
// 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);

View File

@@ -37,8 +37,11 @@ class XMLNode;
class Ipo : public NoCopy
{
public:
/** All supported ipo types. */
enum IpoChannelType {IPO_LOCX, IPO_LOCY, IPO_LOCZ,
/** All supported ipo types. LOCXYZ is basically a curve, the
* IPO is actually a 3d curve, without a time axis, only the
* actual data points. */
enum IpoChannelType {IPO_LOCX, IPO_LOCY, IPO_LOCZ,
IPO_LOCXYZ,
IPO_ROTX, IPO_ROTY, IPO_ROTZ,
IPO_SCALEX, IPO_SCALEY, IPO_SCALEZ,
IPO_MAX};
@@ -53,14 +56,20 @@ private:
enum {ET_CONST, ET_EXTRAP, ET_CYCLIC_EXTRAP, ET_CYCLIC} m_extend;
/** The actual control points. */
std::vector<core::vector2df> m_points;
std::vector<Vec3> m_points;
/** Only used for bezier curves: the two handles. */
std::vector<core::vector2df> m_handle1, m_handle2;
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 last control point. */
float m_end_time;
/** 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,
@@ -68,25 +77,31 @@ private:
mutable unsigned int m_next_n;
/** Stores the inital position of the object. */
core::vector3df m_initial_xyz;
Vec3 m_initial_xyz;
/** Stores the inital rotation of the object. */
core::vector3df m_initial_hpr;
Vec3 m_initial_hpr;
void extend(float x, unsigned int n);
float Ipo::getCubicBezier(float t, float p0, float p1,
float p2, float p3) const;
public:
Ipo(const XMLNode &curve, float fps);
void update(float time, core::vector3df *xyz, core::vector3df *hpr,
core::vector3df *scale);
void update(float time, Vec3 *xyz, Vec3 *hpr, Vec3 *scale);
float get(float time) const;
void setInitialTransform(const core::vector3df &xyz,
const core::vector3df &hpr);
void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr);
void reset();
void extendStart(float x);
void extendEnd(float x);
const std::vector<core::vector2df>& getPoints() const { return m_points; }
// ------------------------------------------------------------------------
/** Returns the raw data points for this IPO. */
const std::vector<Vec3>& getPoints() const { return m_points; }
// ------------------------------------------------------------------------
/** Returns the last specified time (i.e. not considering any extend
* types). */
float getEndTime() const { return m_end_time; }
}; // Ipo
#endif

View File

@@ -285,11 +285,11 @@ void ThreeDAnimation::update(float dt)
{
if ( UserConfigParams::m_graphical_effects )
{
core::vector3df xyz = m_node->getPosition();
core::vector3df scale = m_node->getScale();
Vec3 xyz = m_node->getPosition();
Vec3 scale = m_node->getScale();
AnimationBase::update(dt, &xyz, &m_hpr, &scale); //updates all IPOs
m_node->setPosition(xyz);
m_node->setScale(scale);
m_node->setPosition(xyz.toIrrVector());
m_node->setScale(scale.toIrrVector());
// Note that the rotation order of irrlicht is different from the one
// in blender. So in order to reproduce the blender IPO rotations
// correctly, we have to get the rotations around each axis and combine
@@ -297,11 +297,11 @@ void ThreeDAnimation::update(float dt)
core::matrix4 m;
m.makeIdentity();
core::matrix4 mx;
mx.setRotationDegrees(core::vector3df(m_hpr.X, 0, 0));
mx.setRotationDegrees(core::vector3df(m_hpr.getX(), 0, 0));
core::matrix4 my;
my.setRotationDegrees(core::vector3df(0, m_hpr.Y, 0));
my.setRotationDegrees(core::vector3df(0, m_hpr.getY(), 0));
core::matrix4 mz;
mz.setRotationDegrees(core::vector3df(0, 0, m_hpr.Z));
mz.setRotationDegrees(core::vector3df(0, 0, m_hpr.getZ()));
m = my*mz*mx;
core::vector3df hpr = m.getRotationDegrees();
m_node->setRotation(hpr);

View File

@@ -60,7 +60,7 @@ private:
/** We have to store the rotation value as computed in blender, since
* irrlicht uses a different order, so for rotation animations we
* can not use the value returned by getRotation from a scene node. */
core::vector3df m_hpr;
Vec3 m_hpr;
void createPhysicsBody(const std::string &shape);