/*
Damper_impl.h
A C++ implementation of a Damper.
Author:
Herbert Stocker
Underlying Paper:
"Linear Filters - Animating Objects in a
Flexible and Pleasing Way" by Herbert Stocker
HomePage:
http://www.hersto.net/Followers
(various resources there)
License:
You may use this file in any project and
modify it as long as you keep this comment
block unchanged.
Accompanying Files:
Damper.h
Damper.cpp
Required Files:
Follower_Trait.h
Follower_Trait.cpp
Follower_Trait_impl.h
TimeDependent.h
TimeDependent.cpp
Documentation:
See http://www.hersto.net/Followers
*/
#include <exception>
template<class Type>
CTDamper<Type>::CTDamper(double Tau, int Order, bool bStartWithFirstInput)
: mTau(Tau)
, mOrder(Order)
, mEps(.001)
, mbNeedTick(false)
, mbNeedToTakeFirstInput(bStartWithFirstInput)
, mpvRcvContext(NULL)
, mpfValueReceiver(NULL)
, mpfIsActiveReceiver(NULL)
{
if(Order < 0 || Order > 5 || Tau < 0)
throw std::exception(); // TBD: range exception.
}
template<class Type>
CTDamper<Type>::CTDamper(double Tau, bool bStartWithFirstInput)
: mTau(Tau)
, mOrder(1)
, mEps(.001)
, mbNeedTick(false)
, mbNeedToTakeFirstInput(bStartWithFirstInput)
, mpvRcvContext(NULL)
, mpfValueReceiver(NULL)
, mpfIsActiveReceiver(NULL)
{
if(Order < 0 || Order > 5 || Tau < 0)
throw std::exception(); // TBD: range exception.
}
template<class Type>
CTDamper<Type>::CTDamper(double Tau, int Order, cType& InitialVal, cType& InitialDest)
: mTau(Tau)
, mOrder(Order)
, mEps(.001)
, mbNeedTick(InitialVal != InitialDest)
, mbNeedToTakeFirstInput(false)
, mpvRcvContext(NULL)
, mpfValueReceiver(NULL)
, mpfIsActiveReceiver(NULL)
, mValue0(InitialDest)
, mValue1(InitialVal)
, mValue2(InitialVal)
, mValue3(InitialVal)
, mValue4(InitialVal)
, mValue5(InitialVal)
{
if(Order < 0 || Order > 5 || Tau < 0)
throw std::exception(); // TBD: range exception.
if(mbNeedTick) mfCallIsActiveRcv(true);
}
template<class Type>
CTDamper<Type>::~CTDamper()
{}
template<class Type>
void CTDamper<Type>::set_destination(cType& Dest)
{
if(mbNeedToTakeFirstInput)
{
mbNeedToTakeFirstInput= false;
mValue5=
mValue4=
mValue3=
mValue2=
mValue1=
mValue0= Dest;
mfCallValueRcv(Dest);
return;
}
if(CTFollowerVarTrait<Type>(Dest) != mValue0)
{
mValue0= Dest;
mfSetNeedTick(true);
}
}
template<class Type>
void CTDamper<Type>::set_value(cType& Val)
{
mbNeedToTakeFirstInput= false;
mValue5=
mValue4=
mValue3=
mValue2=
mValue1= Val;
mfCallValueRcv(Val);
mfUpdateReached();
if(mValue1 != mValue0) // we use mValue1 as this has already been assigned Val, and thus we don't need to convert Val to CTFollowerVarTrait, which probably involves at least a copy operation)
mfSetNeedTick(true);
}
template<class Type>
const Type& CTDamper<Type>::get_destination()
{
return mValue0.get_Value();
}
template<class Type>
const Type& CTDamper<Type>::get_value()
{
switch(mOrder)
{
case 0:
return mValue0.get_Value();
case 1:
return mValue1.get_Value();
case 2:
return mValue2.get_Value();
case 3:
return mValue3.get_Value();
case 4:
return mValue4.get_Value();
case 5:
return mValue5.get_Value();
default:
throw std::exception()/*range exception*/;
}
}
template<class Type>
void CTDamper<Type>::set_tau(double Tau)
{
mTau= Tau;
}
template<class Type>
double CTDamper<Type>::get_tau()
{
return mTau;
}
template<class Type>
int CTDamper<Type>::get_order()
{
return mOrder;
}
template<class Type>
void CTDamper<Type>::set_eps(double Eps)
{
mEps= Eps;
}
template<class Type>
double CTDamper<Type>::get_eps()
{
return mEps;
}
template<class Type>
bool CTDamper<Type>::get_isActive()
{
return mbNeedTick;
}
template<class Type>
void CTDamper<Type>::SetReceivers( void* pvContext
, void (*pfValueReceiver)( void* pvContext, cType& Value)
, void (*pfIsActiveReceiver)(void* pvContext, bool bIsActive)
)
{
mpvContext= pvContext;
mpfValueReceiver= pfValueReceiver;
mpfIsActiveReceiver= pfIsActiveReceiver;
}
template<class Type>
void CTDamper<Type>::Tick(double DeltaT, double Now)
{
if(!mbNeedTick)
return;
if(mbNeedToTakeFirstInput)
return;
if(mTau)
{
double Exp(double X); // we declare a wrapper function to exp(.), which we implement in Damper.cpp, so that
// we do not have to pull in math.h in this header file.
double Alpha= 1 - Exp(-DeltaT / mTau); // TBD: does exp work correctly with mTau == 0? (but how about a DeltaT of 0 then?)
if(mOrder > 0)
mValue1= mValue1.Interpolate(mValue0, Alpha);
if(mOrder > 1)
mValue2= mValue2.Interpolate(mValue1, Alpha);
if(mOrder > 2)
mValue3= mValue3.Interpolate(mValue2, Alpha);
if(mOrder > 3)
mValue4= mValue4.Interpolate(mValue3, Alpha);
if(mOrder > 4)
mValue5= mValue5.Interpolate(mValue4, Alpha);
}else
{
if(mOrder > 0)
mValue1= mValue0;
if(mOrder > 1)
mValue2= mValue1;
if(mOrder > 2)
mValue3= mValue2;
if(mOrder > 3)
mValue4= mValue3;
if(mOrder > 4)
mValue5= mValue4;
}
double Dist= mfGetDist();
if(Dist < mEps)
{
mValue1=
mValue2=
mValue3=
mValue4=
mValue5= mValue0;
mfCallValueRcv(get_value());
mfUpdateReached(Dist);
mfSetNeedTick(false);
return;
}
mfCallValueRcv(get_value());
mfUpdateReached(Dist);
}
template<class Type>
double CTDamper<Type>::mfGetDist()
{
double Dist= mOrder > 0
? mValue0.Distance(mValue1)
: 0
;
if(mOrder > 1)
{
double Dist2= mValue1.Distance(mValue2);
if(Dist2 > Dist) Dist= Dist2;
}
if(mOrder > 2)
{
double Dist2= mValue2.Distance(mValue3);
if(Dist2 > Dist) Dist= Dist2;
}
if(mOrder > 3)
{
double Dist2= mValue3.Distance(mValue4);
if(Dist2 > Dist) Dist= Dist2;
}
if(mOrder > 4)
{
double Dist2= mValue4.Distance(mValue5);
if(Dist2 > Dist) Dist= Dist2;
}
return Dist;
}
template<class Type>
void CTDamper<Type>::mfSetNeedTick(bool bNeedTick)
{
if(bNeedTick == mbNeedTick)
return;
if(bNeedTick)
{
mbNeedTick= true;
if(mpfNeedTickCB)
{
mpfNeedTickCB(mpvNeedTickContext, this, true);
}
}else
{
mbNeedTick= false;
if(mpfNeedTickCB)
{
mpfNeedTickCB(mpvNeedTickContext, this, false);
}
}
}
template<class Type>
void CTDamper<Type>::SetNeedTickCB(void* pvContext, double (*pfNeedTickCB) (void* pvContext, CTimeDependent* pCaller, bool bNeedTick))
{
bool bHadBefore= !!mpfNeedTickCB;
CTimeDependent::SetNeedTickCB(pvContext, pfNeedTickCB);
if(!bHadBefore && !!mpfNeedTickCB)
{
mpfNeedTickCB(mpvNeedTickContext, this, mbNeedTick);
}
}
/*
If the need-tick call-back function receives true, some timer should start calling Tick(.) frequently.
if it receives false, the timer should stop doing so. (It can continue calling Tick(.) but this will
have no effect, because it is not required anyway.)
If the need-tick call-back function receives true, it must return the current time value.
if it receives false, its return value is ignored.
The need-tick call-back function should not call any other function on the Damper, e.g. set_destination()
as this is not defined and may cause confusion in the internal state of the Damper, or may cause recursion.
*/
/*
SetNeedTickCB(.) can be used to send the Damper Tick(.) calls only when required.
A typical usage is to have a timer object that maintains a list of objects that have a
Tick(.) function, and when the need-tick callback is called with a parameter of true,
it adds the calling object to the list, and removes it from the list.
*/
|