// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #ifndef NL_TRACK_H #error "internal file included from track.h" #endif // *************************************************************************** // *************************************************************************** // TCB Keyframes. // *************************************************************************** // *************************************************************************** // *************************************************************************** /** * TCB Track tools (for both normal TCB, and quat TCB). internal use. * * \author Cyril 'Hulud' Corvazier * \author Nevrax France * \date 2001 */ template class CTCBTools { protected: typedef typename TMapTimeCKey::iterator TMapTimeCKeyIterator; /// compute TCB ease information. void compileTCBEase(TMapTimeCKey &mapKey, bool loopMode) { TMapTimeCKeyIterator it= mapKey.begin(); for(;it!=mapKey.end();it++) { TMapTimeCKeyIterator next= it; next++; // loop mgt. must compute ease from last to first (useful if _RangeLock is false). if(next==mapKey.end() && loopMode && mapKey.size()>1) next= mapKey.begin(); // Ease Precompute. //================= CKeyT &key= it->second; if(next!=mapKey.end()) { float e0= it->second.EaseFrom; float e1= next->second.EaseTo; float s = e0 + e1; // "normalize". if (s > 1.0f) { e0 = e0/s; e1 = e1/s; } // precalc ease factors. key.Ease0= e0; key.Ease1= e1; key.EaseK= 1/(2.0f - e0 - e1); if(e0) key.EaseKOverEase0= key.EaseK / e0; if(e1) key.EaseKOverEase1= key.EaseK / e1; } else { // force ease() to just return d (see ease()). key.EaseK = 0.5f; } } } // ease time. float ease(const CKeyT *key, float d) { if (d==0.0f || d==1.0f) return d; // k==0.5f <=> e0+e1 == 0. if (key->EaseK == 0.5f) return d; if (d < key->Ease0) return key->EaseKOverEase0 * d*d; else if (d < 1.0f - key->Ease1) return key->EaseK * (2.0f*d - key->Ease0); else { d = 1.0f - d; return 1.0f - key->EaseKOverEase1 * d*d; } } // compute hermite factors. void computeHermiteBasis(float d, float hb[4]) { float d2,d3,a; d2 = d*d; d3 = d2*d; a = 3.0f*d2 - 2.0f*d3; hb[0] = 1.0f - a; hb[1] = a; hb[2] = d3 - 2.0f*d2 + d; hb[3] = d3 - d2; } // compute TCB tangents factors. void computeTCBFactors(const CKeyT &key, float timeBefore, float time, float timeAfter, float rangeDelta, bool firstKey, bool endKey, bool isLoop, float &ksm, float &ksp, float &kdm, float &kdp) { float fp,fn; if(isLoop || (!firstKey && !endKey)) { float dtm; // Compute Time deltas. if (firstKey) { dtm = 0.5f * (rangeDelta + timeAfter - time); fp = rangeDelta / dtm; fn = (timeAfter - time) / dtm; } else if (endKey) { dtm = 0.5f * (rangeDelta + time - timeBefore); fp = rangeDelta / dtm; fn = (time - timeBefore) / dtm; } else { dtm = 0.5f * (timeAfter - timeBefore); fp = (time - timeBefore) / dtm; fn = (timeAfter - time) / dtm; } float c= (float)fabs( key.Continuity ); fp = fp + c - c * fp; fn = fn + c - c * fn; } else { // firstkey and lastkey of not loop track. fp = 1.0f; fn = 1.0f; } // Compute tangents factors. float tm,cm,cp,bm,bp,tmcm,tmcp; cm = 1.0f - key.Continuity; tm = 0.5f * ( 1.0f - key.Tension ); cp = 2.0f - cm; bm = 1.0f - key.Bias; bp = 2.0f - bm; tmcm = tm*cm; tmcp = tm*cp; // tgts factors. ksm = tmcm*bp*fp; ksp = tmcp*bm*fp; kdm = tmcp*bp*fn; kdp = tmcm*bm*fn; } }; // *************************************************************************** /** * ITrack implementation for TCB keyframer. * * \author Cyril 'Hulud' Corvazier * \author Nevrax France * \date 2001 */ template class CTrackKeyFramerTCB : public ITrackKeyFramer, private CTCBTools > { public: protected: typedef typename CKeyT::TValueType TKeyValueType; /// \name From ITrackKeyFramer // @{ /// evalKey (runtime). virtual void evalKey ( const CKeyT* previous, const CKeyT* next, TAnimationTime datePrevious, TAnimationTime /* dateNext */, TAnimationTime date, IAnimatedValue &result ) { CAnimatedValueBlendable &resultVal= static_cast&>(result); if(previous && next) { // lerp from previous to cur. date-= datePrevious; date*= previous->OODeltaTime; NLMISC::clamp(date, 0,1); date = this->ease(previous, date); float hb[4]; this->computeHermiteBasis(date, hb); copyToValue(resultVal.Value, previous->Value*hb[0] + next->Value*hb[1] + previous->TanFrom*hb[2] + next->TanTo*hb[3]); } else { if (previous) copyToValue(resultVal.Value, previous->Value); else if (next) copyToValue(resultVal.Value, next->Value); } } /// compile (precalc). virtual void compile() { ITrackKeyFramer::compile(); // Ease Precompute. this->compileTCBEase(this->_MapKey, this->getLoopMode()); // Tangents Precompute. sint nKeys= (sint)this->_MapKey.size(); if(nKeys<=1) return; typename std::map::iterator it= this->_MapKey.begin(); // first key. typename std::map::iterator itNext= it; itNext++; // second key. typename std::map::iterator itPrev= this->_MapKey.end(); itPrev--; // last key. if(nKeys==2 && !this->getLoopMode()) { computeTCBKeyLinear( it->second, itNext->second ); } else { // rangeDelta is the length of effective Range - length of LastKey-FirstKey. // NB: if RangeLock, rangeDelta==0. float rangeDelta; // NB: _RangeDelta has just been compiled in ITrackKeyFramer::compile(). rangeDelta= this->getCompiledRangeDelta(); // Compute all middle keys. for(;it!=this->_MapKey.end();) { // Do the first key and the last key only in LoopMode. // NB: we are the last if itNext==_MapKey.begin(). if(this->getLoopMode() || (it!=this->_MapKey.begin() && itNext!=this->_MapKey.begin()) ) { computeTCBKey(itPrev->second, it->second, itNext->second, itPrev->first, it->first, itNext->first, rangeDelta, it==this->_MapKey.begin(), itNext==this->_MapKey.begin(), this->getLoopMode()); } // Next key!! itPrev= it; it++; itNext++; // loop. if(itNext==this->_MapKey.end()) itNext= this->_MapKey.begin(); } // In not loop mode, compute first and last key, AFTER middle keys computed. if(!this->getLoopMode()) { typename std::map::iterator it0= this->_MapKey.begin(); // first key. typename std::map::iterator it1= it0; it1++; // second key. typename std::map::iterator itLast= this->_MapKey.end();itLast--; // last key. typename std::map::iterator itLastPrev= itLast;itLastPrev--; // prev of last key. computeFirstKey(it0->second, it1->second); computeLastKey(itLast->second, itLastPrev->second); } } } // @} // ***************** private: void computeTCBKey(CKeyT &keyBefore, CKeyT &key, CKeyT &keyAfter, float timeBefore, float time, float timeAfter, float rangeDelta, bool firstKey, bool endKey, bool isLoop) { float ksm,ksp,kdm,kdp; // compute tangents factors. this->computeTCBFactors(key, timeBefore, time, timeAfter, rangeDelta, firstKey, endKey, isLoop, ksm,ksp,kdm,kdp); // Delta. TKeyValueType delm, delp; delm = key.Value - keyBefore.Value; delp = keyAfter.Value - key.Value; // Tangents. key.TanTo = delm*ksm + delp*ksp; key.TanFrom= delm*kdm + delp*kdp; } // compute 2 TCB keys for a not-loop track => "linear". void computeTCBKeyLinear(CKeyT &key0, CKeyT &key1) { float f0, f1; TKeyValueType dv; f0 = 1.0f - key0.Tension; f1 = 1.0f - key1.Tension; dv = key1.Value - key0.Value; key0.TanFrom= dv * f0; key1.TanTo= dv * f1; } // compute this AFTER computing key1. void computeFirstKey(CKeyT &keyFirst, CKeyT &keyAfter) { float tm; tm = 0.5f * (1.0f - keyFirst.Tension); keyFirst.TanFrom= tm * ((keyAfter.Value - keyFirst.Value) * 3.0f - keyAfter.TanTo); } // compute this AFTER computing key(n-2). void computeLastKey(CKeyT &keyLast, CKeyT &keyBefore) { float tm; tm = 0.5f * (1.0f - keyLast.Tension); keyLast.TanTo= tm * ((keyLast.Value - keyBefore.Value) * 3.0f - keyBefore.TanFrom); } }; // *************************************************************************** /** * ITrack implementation for CQuat TCB keyframer. * * \author Lionel Berenguier * \author Nevrax France * \date 2001 */ template<> class CTrackKeyFramerTCB : public ITrackKeyFramer, private CTCBTools > { public: /// \name From ITrackKeyFramer // @{ /// evalKey (runtime). virtual void evalKey ( const CKeyTCBQuat* previous, const CKeyTCBQuat* next, TAnimationTime datePrevious, TAnimationTime /* dateNext */, TAnimationTime date, IAnimatedValue &result ) { CAnimatedValueQuat &resultVal= static_cast(result); if(previous && next) { // lerp from previous to cur. date-= datePrevious; date*= previous->OODeltaTime; NLMISC::clamp(date, 0,1); // ease. date = ease(previous, date); // quad slerp. resultVal.Value= CQuat::squadrev(next->LocalAngleAxis, previous->Quat, previous->A, next->B, next->Quat, date); } else { if (previous) resultVal.Value= previous->Quat; else if (next) resultVal.Value= next->Quat; } } /// compile (precalc). virtual void compile() { ITrackKeyFramer::compile(); // Ease Precompute. this->compileTCBEase(_MapKey, getLoopMode()); TMapTimeCKey::iterator it; TMapTimeCKey::iterator itNext; TMapTimeCKey::iterator itPrev; // Compute absolute quaternions. for (it= _MapKey.begin();it!=_MapKey.end();) { CKeyTCBQuat &key= it->second; // Compute Local AngleAxis. if(it!= _MapKey.begin()) { NLMISC::CMatrix mat; mat.setRot(itPrev->second.Quat); mat.invert(); key.LocalAngleAxis.Axis= mat*key.Value.Axis; key.LocalAngleAxis.Angle= key.Value.Angle; } else key.LocalAngleAxis= key.Value; key.LocalAngleAxis.Axis.normalize(); // make angle positive. if(key.LocalAngleAxis.Angle<0.f) { key.LocalAngleAxis.Axis= -key.LocalAngleAxis.Axis; key.LocalAngleAxis.Angle= -key.LocalAngleAxis.Angle; } // relative quat key.Quat.setAngleAxis(key.LocalAngleAxis); // absolute quat if (it!= _MapKey.begin()) key.Quat = itPrev->second.Quat * key.Quat; // next key. itPrev= it; it++; } // Tangents Precompute. sint nKeys= (sint)_MapKey.size(); if(nKeys<=1) return; // rangeDelta is the length of effective Range - length of LastKey-FirstKey. // NB: if RangeLock, rangeDelta==0. float rangeDelta; // NB: _RangeDelta has just been compiled in ITrackKeyFramer::compile(). rangeDelta= getCompiledRangeDelta(); it= _MapKey.begin(); // first key. itNext= it; itNext++; // second key. itPrev= _MapKey.end(); itPrev--; // last key. // Compute all keys. for(;it!=_MapKey.end();) { // NB: we are the last key if itNext==_MapKey.begin(). computeTCBKey(itPrev->second, it->second, itNext->second, itPrev->first, it->first, itNext->first, rangeDelta, it==_MapKey.begin(), itNext==_MapKey.begin(), getLoopMode()); // Next key!! itPrev= it; it++; itNext++; // loop. if(itNext==_MapKey.end()) itNext= _MapKey.begin(); } } // @} // ***************** private: void computeTCBKey(CKeyTCBQuat &keyBefore, CKeyTCBQuat &key, CKeyTCBQuat &keyAfter, float timeBefore, float time, float timeAfter, float rangeDelta, bool firstKey, bool endKey, bool isLoop) { CQuat qp, qm; // compute qm. if (!firstKey || isLoop) { float angle= key.LocalAngleAxis.Angle; CVector &axis= key.LocalAngleAxis.Axis; if (angle > 2*NLMISC::Pi- NLMISC::QuatEpsilon) { qm.set(axis.x, axis.y, axis.z, 0.0f); qm = qm.log(); } else { CQuat qprev= keyBefore.Quat; qprev.makeClosest(key.Quat); qm = CQuat::lnDif(qprev, key.Quat); } } // compute qp. if (!endKey || isLoop) { float angle= keyAfter.LocalAngleAxis.Angle; CVector &axis= keyAfter.LocalAngleAxis.Axis; if (angle > 2*NLMISC::Pi- NLMISC::QuatEpsilon) { qp.set(axis.x, axis.y, axis.z, 0.0f); qp = qp.log(); } else { CQuat qnext= keyAfter.Quat; qnext.makeClosest(key.Quat); qp = CQuat::lnDif(key.Quat, qnext); } } // not loop mgt. if (firstKey && !isLoop) qm = qp; if (endKey && !isLoop) qp = qm; // compute tangents factors. float ksm, ksp, kdm, kdp; computeTCBFactors(key, timeBefore, time, timeAfter, rangeDelta, firstKey, endKey, isLoop, ksm,ksp,kdm,kdp); // A/B. CQuat qa, qb; qb= (qm * (1.0f-ksm) + qp * (-ksp) ) * 0.5f; qa= (qm * kdm + qp * (kdp-1.0f) ) * 0.5f; qa = qa.exp(); qb = qb.exp(); key.A = key.Quat * qa; key.B = key.Quat * qb; } };