Code: Select all
//********************************************************************************************
class TCBKey
{
public:
int time;
int flags;
float tension;
float continuity;
float bias;
float easeIn;
float easeOut;
};
//********************************************************************************************
class TCBVector3dKey : public TCBKey
{
public:
vector3df value;
};
//********************************************************************************************
// compute hermite spline params
void Hermite(float t, float& h1, float& h2, float& h3, float& h4)
{
float t2, t3;
t2 = t * t;
t3 = t * t2;
h2 = 3.0 * t2 - t3 - t3;
h1 = 1.0 - h2;
h4 = t3 - t2;
h3 = h4 - t2 + t;
}
//********************************************************************************************
// Handles 3ds max TCB easeFrom easeTo params
float TCB_EaseFromEaseTo(float t, float easefrom, float easeto)
{
float span = easefrom + easeto;
if(span == 0)
return t;
if(span > 1.0f)
{
easefrom /= span;
easeto /= span;
}
float c = 1.0/(2.0 - easefrom - easeto);
if( t < easefrom)
return c/easefrom * t * t;
else if( t > 1.0 - easeto)
return 1.0 - c/easeto * (t * (t - 2.0) + 1.0);
else
return c * (2.0 * t - easefrom);
}
//********************************************************************************************
void TCBInTangent(TCBVector3dKey* key, TCBVector3dKey* prev, TCBVector3dKey* next, vector3df& tangent)
{
vector3df a = (key->value - prev->value) * (1 - key->tension) * (1 - key->continuity) * (1 + key->bias);
vector3df b = (next->value - key->value) * (1 - key->tension) * (1 + key->continuity) * (1 - key->bias);
float t = (float)(key->time - prev->time)/(float)(next->time - prev->time);
tangent = (b + a) * lerp(t,0.5f,fabs(key->continuity));
}
//********************************************************************************************
void TCBOutTangent(TCBVector3dKey* key, TCBVector3dKey* prev, TCBVector3dKey* next, vector3df& tangent)
{
vector3df a = (key->value - prev->value) * (1 - key->tension) * (1 + key->continuity) * (1 + key->bias);
vector3df b = (next->value - key->value) * (1 - key->tension) * (1 - key->continuity) * (1 - key->bias);
float t = (float)(next->time - key->time)/(float)(next->time - prev->time);
tangent = (b + a) * lerp(t, 0.5f,fabs(key->continuity));
}
//********************************************************************************************
class CTCBPositionAnim : public ISceneNodeAnimator
{
short framerate;
short numkeys;
short frame;
short key;
vector3df outtangent;
vector3df intangent;
tcbposkeybuf pos;
int oldtime;
bool play;
bool reverse;
bool bounce;
bool loop;
bool new_segment;
public:
CTCBPositionAnim(short nkeys, TCBVector3dKey* pkeys)
: numkeys(nkeys), pos(nkeys), oldtime(0), framerate(30), key(0),
frame(-1), reverse(false), bounce(false), loop(false), play(true), new_segment(true)
{
memmove(&pos[0], pkeys, nkeys * sizeof(TCBVector3dKey));
}
void Loop() { loop = true; bounce = false;}
void Bounce() { loop = false; bounce = true;}
void Play() { play = true; }
void Stop() { play = false; }
void Reverse()
{
reverse = true;
key = numkeys - 1;
frame = pos[key].time + 1;
}
void Fowards()
{
reverse = false;
frame = -1;
key = 0;
}
virtual void animateNode(ISceneNode* node, u32 timeMs)
{
if(!play)
return;
if(numkeys == 1)
{
node->setPosition(pos[0].value);
return;
}
if((timeMs - oldtime) * framerate >= MS_PER_SEC)
{
oldtime = timeMs;
if(reverse)
{
frame--;
if(frame <= pos[key - 1].time)
{
new_segment = true;
key--;
if(key <= 0)
{
if(bounce)
{
reverse = false;
frame++;
key++;
}
else if(loop)
{
key = numkeys - 1;
frame = pos[key].time;
}
else
{
frame = 0;
key = 0;
play = false;
}
}
}
}
else
{
frame++;
if(frame > pos[key].time)
{
new_segment = true;
key++;
if(key > numkeys - 1)
{
if(bounce)
{
reverse = true;
frame--;
key--;
new_segment = false;
}
else if(loop)
{
key = 0;
frame = 0;
}
else
{
frame--;
key--;
play = false;
}
}
}
}
if(frame == 0)
node->setPosition(pos[0].value);
else
{
short outkey = key - 1;
if(new_segment) // in a new segment so compute new in and out tangents
{
TCBVector3dKey* next, *prev;
prev = key == 0 ? &pos[0] : &pos[outkey];
next = key == numkeys - 1 ? &pos[numkeys - 1] : &pos[key + 1];
TCBInTangent(&pos[key], prev, next, intangent);
prev = outkey == 0 ? &pos[0] : &pos[outkey - 1];
TCBOutTangent(&pos[outkey], prev, &pos[key], outtangent);
// handle the end cases
if(key == 1)
outtangent = outtangent * 1.5 - intangent * 0.5 * (1.0f - pos[0].tension);
if(key == numkeys - 1)
intangent = intangent * 1.5 - outtangent * 0.5 * (1.0f - pos[numkeys - 1].tension);
new_segment = false;
}
float h1, h2, h3, h4;
// interpolation time
float t = (float)(frame - pos[outkey].time)/(float)(pos[key].time - pos[outkey].time);
// adjust for the ease
t = TCB_EaseFromEaseTo(t, pos[outkey].easeOut, pos[key].easeIn);
// hermite time
Hermite(t,h1,h2,h3,h4);
// interpolate
node->setPosition(h1 * pos[outkey].value + h2 * pos[key].value + h3 * outtangent + h4 * intangent);
}
}
}
virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const {}
virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) {}
virtual ESCENE_NODE_ANIMATOR_TYPE getType() const { return ESNAT_UNKNOWN; }
virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0)
{ return new CTCBPositionAnim(numkeys, &pos[0] ); }
};
//********************************************************************************************
//********************************************************************************************
edit: correct some error on the start and end keys, also worth noting that bias and continuity should always be zero for start and end frames