You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6209 lines
173 KiB
6209 lines
173 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
#include "movieobjects/dmelog.h" |
|
#include "datamodel/dmelementfactoryhelper.h" |
|
#include "datamodel/dmehandle.h" |
|
#include "vstdlib/random.h" |
|
|
|
#include "tier0/dbg.h" |
|
|
|
#include <limits.h> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
LayerSelectionData_t::DataLayer_t::DataLayer_t( float frac, CDmeLogLayer *layer ) : |
|
m_flStartFraction( frac ) |
|
{ |
|
m_hData = layer; |
|
} |
|
|
|
LayerSelectionData_t::LayerSelectionData_t() : |
|
m_DataType( AT_UNKNOWN ), |
|
m_nDuration( 0 ), |
|
m_tStartOffset( DMETIME_ZERO ) |
|
{ |
|
m_nHoldTimes[ 0 ] = m_nHoldTimes[ 1 ] = 0; |
|
} |
|
|
|
void LayerSelectionData_t::Release() |
|
{ |
|
for ( int i = 0; i < m_vecData.Count(); ++i ) |
|
{ |
|
DataLayer_t *dl = &m_vecData[ i ]; |
|
if ( dl->m_hData.Get() ) |
|
{ |
|
g_pDataModel->DestroyElement( dl->m_hData->GetHandle() ); |
|
} |
|
} |
|
m_vecData.Purge(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Interpolatable types |
|
//----------------------------------------------------------------------------- |
|
inline bool IsInterpolableType( DmAttributeType_t type ) |
|
{ |
|
return ( type == AT_FLOAT ) || |
|
( type == AT_COLOR ) || |
|
( type == AT_VECTOR2 ) || |
|
( type == AT_VECTOR3 ) || |
|
( type == AT_QANGLE ) || |
|
( type == AT_QUATERNION ); |
|
} |
|
|
|
static Vector s_pInterolationPoints[ 4 ] = |
|
{ |
|
Vector( 0.0f, 0.0f, 0.0f ), |
|
Vector( 0.0f, 0.0f, 0.0f ), |
|
Vector( 1.0f, 1.0f, 0.0f ), |
|
Vector( 1.0f, 1.0f, 0.0f ) |
|
}; |
|
|
|
static inline float ComputeInterpolationFactor( float flFactor, int nInterpolatorType ) |
|
{ |
|
Vector out; |
|
Interpolator_CurveInterpolate |
|
( |
|
nInterpolatorType, |
|
s_pInterolationPoints[ 0 ], // unused |
|
s_pInterolationPoints[ 1 ], |
|
s_pInterolationPoints[ 2 ], |
|
s_pInterolationPoints[ 3 ], // unused |
|
flFactor, |
|
out |
|
); |
|
return out.y; // clamp( out.y, 0.0f, 1.0f ); |
|
} |
|
|
|
float DmeLog_TimeSelection_t::AdjustFactorForInterpolatorType( float flFactor, int nSide ) const |
|
{ |
|
return ComputeInterpolationFactor( flFactor, m_nFalloffInterpolatorTypes[ nSide ] ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// NOTE: See DmeTimeSelectionTimes_t for return values, -1 means before, TS_TIME_COUNT means after |
|
//----------------------------------------------------------------------------- |
|
static inline int ComputeRegionForTime( DmeTime_t t, const DmeTime_t *pRegionTimes ) |
|
{ |
|
if ( t >= pRegionTimes[TS_LEFT_HOLD] ) |
|
{ |
|
if ( t <= pRegionTimes[TS_RIGHT_HOLD] ) |
|
return 2; |
|
return ( t <= pRegionTimes[TS_RIGHT_FALLOFF] ) ? 3 : 4; |
|
} |
|
return ( t >= pRegionTimes[TS_LEFT_FALLOFF] ) ? 1 : 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// NOTE: See DmeTimeSelectionTimes_t for return values, -1 means before, TS_TIME_COUNT means after |
|
//----------------------------------------------------------------------------- |
|
int DmeLog_TimeSelection_t::ComputeRegionForTime( DmeTime_t curtime ) const |
|
{ |
|
return ::ComputeRegionForTime( curtime, m_nTimes ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// per-type averaging methods |
|
//----------------------------------------------------------------------------- |
|
float DmeLog_TimeSelection_t::GetAmountForTime( DmeTime_t dmetime ) const |
|
{ |
|
float minfrac = 0.0f; |
|
|
|
float t = dmetime.GetSeconds(); |
|
|
|
// FIXME, this is slow, we should cache this maybe? |
|
COMPILE_TIME_ASSERT( TS_TIME_COUNT == 4 ); |
|
float times[ TS_TIME_COUNT ]; |
|
times[ 0 ] = m_nTimes[ 0 ].GetSeconds(); |
|
times[ 1 ] = m_nTimes[ 1 ].GetSeconds(); |
|
times[ 2 ] = m_nTimes[ 2 ].GetSeconds(); |
|
times[ 3 ] = m_nTimes[ 3 ].GetSeconds(); |
|
|
|
float dt1, dt2; |
|
dt1 = times[ 1 ] - times[ 0 ]; |
|
dt2 = times[ 3 ] - times[ 2 ]; |
|
|
|
if ( dt1 > 0.0f && t >= times[ 0 ] && t < times[ 1 ] ) |
|
{ |
|
float f = ( t - times[ 0 ] ) / dt1; |
|
|
|
Vector out; |
|
|
|
Interpolator_CurveInterpolate |
|
( |
|
m_nFalloffInterpolatorTypes[ 0 ], |
|
s_pInterolationPoints[ 0 ], // unused |
|
s_pInterolationPoints[ 1 ], |
|
s_pInterolationPoints[ 2 ], |
|
s_pInterolationPoints[ 3 ], // unused |
|
f, |
|
out |
|
); |
|
return clamp( out.y, minfrac, 1.0f ); |
|
} |
|
|
|
if ( t >= times[ 1 ] && t <= times[ 2 ] ) |
|
return 1.0f; |
|
|
|
if ( dt2 > 0.0f && t > times[ 2 ] && t <= times[ 3 ] ) |
|
{ |
|
float f = ( times[ 3 ] - t ) / dt2; |
|
|
|
Vector out; |
|
|
|
Interpolator_CurveInterpolate |
|
( |
|
m_nFalloffInterpolatorTypes[ 1 ], |
|
s_pInterolationPoints[ 0 ], // unused |
|
s_pInterolationPoints[ 1 ], |
|
s_pInterolationPoints[ 2 ], |
|
s_pInterolationPoints[ 3 ], // unused |
|
f, |
|
out |
|
); |
|
return clamp( out.y, minfrac, 1.0f ); |
|
} |
|
|
|
return minfrac; |
|
} |
|
|
|
// catch-all for non-interpolable types - just holds first value |
|
template < class T > |
|
T Average( const T *pValues, int nValues) |
|
{ |
|
if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) |
|
{ |
|
static bool first = true; |
|
if ( first ) |
|
{ |
|
first = false; |
|
Warning( "CDmeLog: interpolable type %s doesn't have an averaging function!", CDmAttributeInfo< T >::AttributeTypeName() ); |
|
} |
|
} |
|
|
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return T(); // uninitialized for most value classes!!! |
|
|
|
return pValues[ 0 ]; |
|
} |
|
|
|
// float version |
|
template <> |
|
float Average( const float *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return 0.0f; |
|
|
|
float sum = 0.0f; |
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
sum += pValues[ i ]; |
|
} |
|
return sum / nValues; |
|
} |
|
|
|
// Color version |
|
template <> |
|
Color Average( const Color *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return Color( 0, 0, 0, 0 ); |
|
|
|
float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f; |
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
r += pValues[ i ].r(); |
|
g += pValues[ i ].g(); |
|
b += pValues[ i ].b(); |
|
a += pValues[ i ].a(); |
|
} |
|
float inv = nValues; |
|
return Color( r * inv, g * inv, b * inv, a * inv ); |
|
} |
|
|
|
// Vector2 version |
|
template <> |
|
Vector2D Average( const Vector2D *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return Vector2D( 0.0f, 0.0f ); |
|
|
|
Vector2D sum( 0.0f, 0.0f ); |
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
sum += pValues[ i ]; |
|
} |
|
return sum / nValues; |
|
} |
|
|
|
// Vector3 version |
|
template <> |
|
Vector Average( const Vector *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return Vector( 0.0f, 0.0f, 0.0f ); |
|
|
|
Vector sum( 0.0f, 0.0f, 0.0f ); |
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
sum += pValues[ i ]; |
|
} |
|
return sum / nValues; |
|
} |
|
|
|
// QAngle version |
|
template <> |
|
QAngle Average( const QAngle *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return QAngle( 0.0f, 0.0f, 0.0f ); |
|
|
|
Quaternion ave; |
|
AngleQuaternion( pValues[ 0 ], ave ); |
|
|
|
// this is calculating the average by slerping with decreasing weights |
|
// for example: ave = 1/3 * q2 + 2/3 ( 1/2 * q1 + 1/2 * q0 ) |
|
for ( int i = 1; i < nValues; ++i ) |
|
{ |
|
Quaternion quat; |
|
AngleQuaternion( pValues[ i ], quat ); |
|
QuaternionSlerp( ave, quat, 1 / float( i + 1 ), ave ); |
|
} |
|
|
|
QAngle qangle; |
|
QuaternionAngles( ave, qangle ); |
|
return qangle; |
|
} |
|
|
|
// Quaternion version |
|
template <> |
|
Quaternion Average( const Quaternion *pValues, int nValues ) |
|
{ |
|
Assert( nValues > 0 ); |
|
if ( nValues <= 0 ) |
|
return Quaternion( 0.0f, 0.0f, 0.0f, 1.0f ); |
|
|
|
Quaternion ave = pValues[ 0 ]; |
|
|
|
// this is calculating the average by slerping with decreasing weights |
|
// for example: ave = 1/3 * q2 + 2/3 ( 1/2 * q1 + 1/2 * q0 ) |
|
for ( int i = 1; i < nValues; ++i ) |
|
{ |
|
QuaternionSlerp( ave, pValues[ i ], 1 / float( i + 1 ), ave ); |
|
} |
|
|
|
return ave; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// per-type interpolation methods |
|
//----------------------------------------------------------------------------- |
|
|
|
// catch-all for non-interpolable types - just holds first value |
|
template < class T > |
|
T Interpolate( float t, const T& ti, const T& tj ) |
|
{ |
|
if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) |
|
{ |
|
static bool first = true; |
|
if ( first ) |
|
{ |
|
first = false; |
|
Warning( "CDmeLog: interpolable type %s doesn't have an interpolation function!", CDmAttributeInfo< T >::AttributeTypeName() ); |
|
} |
|
} |
|
|
|
return ti; |
|
} |
|
|
|
// float version |
|
template <> |
|
float Interpolate( float t, const float& ti, const float& tj ) |
|
{ |
|
return t * tj + (1.0f - t) * ti; |
|
} |
|
|
|
// Color version |
|
template <> |
|
Color Interpolate( float t, const Color& ti, const Color& tj ) |
|
{ |
|
int ri, gi, bi, ai; |
|
int rj, gj, bj, aj; |
|
|
|
ti.GetColor( ri, gi, bi, ai ); |
|
tj.GetColor( rj, gj, bj, aj ); |
|
|
|
return Color( t * rj + (1.0f - t) * ri, |
|
t * gj + (1.0f - t) * gi, |
|
t * bj + (1.0f - t) * bi, |
|
t * aj + (1.0f - t) * ai); |
|
} |
|
|
|
// Vector2 version |
|
template <> |
|
Vector2D Interpolate( float t, const Vector2D& ti, const Vector2D& tj ) |
|
{ |
|
return t * tj + (1.0f - t) * ti; |
|
} |
|
|
|
// Vector3 version |
|
template <> |
|
Vector Interpolate( float t, const Vector& ti, const Vector& tj ) |
|
{ |
|
return t * tj + (1.0f - t) * ti; |
|
} |
|
|
|
// QAngle version |
|
template <> |
|
QAngle Interpolate( float t, const QAngle& ti, const QAngle& tj ) |
|
{ |
|
QAngle qaResult; |
|
Quaternion q, qi, qj; // Some Quaternion temps for doing the slerp |
|
|
|
AngleQuaternion( ti, qi ); // Convert QAngles to Quaternions |
|
AngleQuaternion( tj, qj ); |
|
QuaternionSlerp( qi, qj, t, q ); // Do a slerp as Quaternions |
|
QuaternionAngles( q, qaResult ); // Convert back to QAngles |
|
return qaResult; |
|
} |
|
|
|
// Quaternion version |
|
template <> |
|
Quaternion Interpolate( float t, const Quaternion& ti, const Quaternion& tj ) |
|
{ |
|
static Quaternion s_value; |
|
QuaternionSlerp( ti, tj, t, s_value ); |
|
return s_value; |
|
} |
|
|
|
// catch-all for non-interpolable types - just holds first value |
|
template < class T > |
|
T Curve_Interpolate( float t, DmeTime_t times[ 4 ], const T values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) |
|
{ |
|
if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) |
|
{ |
|
static bool first = true; |
|
if ( first ) |
|
{ |
|
first = false; |
|
Warning( "CDmeLog: interpolable type %s doesn't have an interpolation function!", CDmAttributeInfo< T >::AttributeTypeName() ); |
|
} |
|
} |
|
|
|
return t; |
|
} |
|
|
|
// float version |
|
template <> |
|
float Curve_Interpolate( float t, DmeTime_t times[ 4 ], const float values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) |
|
{ |
|
Vector args[ 4 ]; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
args[ i ].Init( times[ i ].GetSeconds(), values[ i ], 0.0f ); |
|
} |
|
|
|
Vector vOut; |
|
int dummy; |
|
int earlypart, laterpart; |
|
|
|
// Not holding out value of previous curve... |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); |
|
|
|
if ( earlypart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
VectorLerp( args[ 1 ], args[ 2 ], t, vOut ); |
|
vOut.y = args[ 1 ].y; |
|
} |
|
else if ( laterpart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
VectorLerp( args[ 1 ], args[ 2 ], t, vOut ); |
|
vOut.y = args[ 2 ].y; |
|
} |
|
else |
|
{ |
|
bool sameCurveType = earlypart == laterpart ? true : false; |
|
if ( sameCurveType ) |
|
{ |
|
Interpolator_CurveInterpolate( laterpart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut ); |
|
} |
|
else // curves differ, sigh |
|
{ |
|
Vector vOut1, vOut2; |
|
|
|
Interpolator_CurveInterpolate( earlypart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut1 ); |
|
Interpolator_CurveInterpolate( laterpart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut2 ); |
|
|
|
VectorLerp( vOut1, vOut2, t, vOut ); |
|
} |
|
} |
|
|
|
// FIXME: This means we can only work with curves that range from 0.0 to 1.0f!!! |
|
float retval = clamp( vOut.y, fmin, fmax ); |
|
return retval; |
|
} |
|
|
|
// Vector version |
|
template <> |
|
Vector Curve_Interpolate( float t, DmeTime_t times[ 4 ], const Vector values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) |
|
{ |
|
Vector vOut; |
|
int dummy; |
|
int earlypart, laterpart; |
|
|
|
// Not holding out value of previous curve... |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); |
|
|
|
if ( earlypart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
vOut = values[ 1 ]; |
|
} |
|
else if ( laterpart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
vOut = values[ 2 ]; |
|
} |
|
else |
|
{ |
|
bool sameCurveType = earlypart == laterpart; |
|
if ( sameCurveType ) |
|
{ |
|
Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut ); |
|
} |
|
else // curves differ, sigh |
|
{ |
|
Vector vOut1, vOut2; |
|
|
|
Interpolator_CurveInterpolate_NonNormalized( earlypart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut1 ); |
|
Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut2 ); |
|
|
|
VectorLerp( vOut1, vOut2, t, vOut ); |
|
} |
|
} |
|
|
|
return vOut; |
|
} |
|
|
|
// Quaternion version |
|
template <> |
|
Quaternion Curve_Interpolate( float t, DmeTime_t times[ 4 ], const Quaternion values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) |
|
{ |
|
Quaternion vOut; |
|
int dummy; |
|
int earlypart, laterpart; |
|
|
|
// Not holding out value of previous curve... |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); |
|
Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); |
|
|
|
if ( earlypart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
vOut = values[ 1 ]; |
|
} |
|
else if ( laterpart == INTERPOLATE_HOLD ) |
|
{ |
|
// Hold "out" of previous sample (can cause a discontinuity) |
|
vOut = values[ 2 ]; |
|
} |
|
else |
|
{ |
|
bool sameCurveType = ( earlypart == laterpart ) ? true : false; |
|
if ( sameCurveType ) |
|
{ |
|
Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut ); |
|
} |
|
else // curves differ, sigh |
|
{ |
|
Quaternion vOut1, vOut2; |
|
|
|
Interpolator_CurveInterpolate_NonNormalized( earlypart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut1 ); |
|
Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut2 ); |
|
|
|
QuaternionSlerp( vOut1, vOut2, t, vOut ); |
|
} |
|
} |
|
|
|
return vOut; |
|
} |
|
|
|
|
|
template< class T > |
|
T ScaleValue( const T& value, float scale ) |
|
{ |
|
return value * scale; |
|
} |
|
template<> |
|
bool ScaleValue( const bool& value, float scale ) |
|
{ |
|
Assert( 0 ); |
|
return value; |
|
} |
|
template<> |
|
Color ScaleValue( const Color& value, float scale ) |
|
{ |
|
Assert( 0 ); |
|
return value; |
|
} |
|
template<> |
|
Vector4D ScaleValue( const Vector4D& value, float scale ) |
|
{ |
|
return Vector4D( value.x * scale, value.y * scale, value.z * scale, value.w * scale ); |
|
} |
|
|
|
template<> |
|
Quaternion ScaleValue( const Quaternion& value, float scale ) |
|
{ |
|
return Quaternion( value.x * scale, value.y * scale, value.z * scale, value.w * scale ); |
|
} |
|
|
|
template<> |
|
VMatrix ScaleValue( const VMatrix& value, float scale ) |
|
{ |
|
Assert( 0 ); |
|
return value; |
|
} |
|
|
|
template<> |
|
CUtlString ScaleValue( const CUtlString& value, float scale ) |
|
{ |
|
Assert( 0 ); |
|
return value; |
|
} |
|
|
|
template< class T > |
|
float LengthOf( const T& value ) |
|
{ |
|
return value; |
|
} |
|
|
|
template<> |
|
float LengthOf( const bool& value ) |
|
{ |
|
if ( value ) |
|
return 1.0f; |
|
return 0.0f; |
|
} |
|
template<> |
|
float LengthOf( const Color& value ) |
|
{ |
|
return (float)sqrt( (float)( value.r() * value.r() + |
|
value.g() * value.g() + |
|
value.b() * value.b() + |
|
value.a() * value.a()) ); |
|
} |
|
template<> |
|
float LengthOf( const Vector4D& value ) |
|
{ |
|
return sqrt( value.x * value.x + |
|
value.y * value.y + |
|
value.z * value.z + |
|
value.w * value.w ); |
|
} |
|
|
|
template<> |
|
float LengthOf( const Quaternion& value ) |
|
{ |
|
return sqrt( value.x * value.x + |
|
value.y * value.y + |
|
value.z * value.z + |
|
value.w * value.w ); |
|
} |
|
|
|
template<> |
|
float LengthOf( const VMatrix& value ) |
|
{ |
|
return 0.0f; |
|
} |
|
|
|
template<> |
|
float LengthOf( const CUtlString& value ) |
|
{ |
|
return 0.0f; |
|
} |
|
|
|
template<> |
|
float LengthOf( const Vector2D& value ) |
|
{ |
|
return value.Length(); |
|
} |
|
|
|
template<> |
|
float LengthOf( const Vector& value ) |
|
{ |
|
return value.Length(); |
|
} |
|
|
|
template<> |
|
float LengthOf( const QAngle& value ) |
|
{ |
|
return value.Length(); |
|
} |
|
|
|
template< class T > |
|
T Subtract( const T& v1, const T& v2 ) |
|
{ |
|
return v1 - v2; |
|
} |
|
|
|
template<> |
|
bool Subtract( const bool& v1, const bool& v2 ) |
|
{ |
|
return v1; |
|
} |
|
|
|
template<> |
|
CUtlString Subtract( const CUtlString& v1, const CUtlString& v2 ) |
|
{ |
|
return v1; |
|
} |
|
|
|
template<> |
|
Color Subtract( const Color& v1, const Color& v2 ) |
|
{ |
|
Color ret; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
ret[ i ] = clamp( v1[ i ] - v2[ i ], 0, 255 ); |
|
} |
|
return ret; |
|
} |
|
|
|
template<> |
|
Vector4D Subtract( const Vector4D& v1, const Vector4D& v2 ) |
|
{ |
|
Vector4D ret; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
ret[ i ] = v1[ i ] - v2[ i ]; |
|
} |
|
return ret; |
|
} |
|
|
|
template<> |
|
Quaternion Subtract( const Quaternion& v1, const Quaternion& v2 ) |
|
{ |
|
Quaternion ret; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
ret[ i ] = v1[ i ]; |
|
} |
|
return ret; |
|
} |
|
|
|
template< class T > |
|
T Add( const T& v1, const T& v2 ) |
|
{ |
|
return v1 + v2; |
|
} |
|
|
|
template<> |
|
bool Add( const bool& v1, const bool& v2 ) |
|
{ |
|
return v1; |
|
} |
|
|
|
template<> |
|
CUtlString Add( const CUtlString& v1, const CUtlString& v2 ) |
|
{ |
|
return v1; |
|
} |
|
|
|
|
|
template<> |
|
Color Add( const Color& v1, const Color& v2 ) |
|
{ |
|
Color ret; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
ret[ i ] = clamp( v1[ i ] + v2[ i ], 0, 255 ); |
|
} |
|
return ret; |
|
} |
|
|
|
template<> |
|
Vector4D Add( const Vector4D& v1, const Vector4D& v2 ) |
|
{ |
|
Vector4D ret; |
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
ret[ i ] = v1[ i ] + v2[ i ]; |
|
} |
|
return ret; |
|
} |
|
|
|
template<> |
|
Quaternion Add( const Quaternion& v1, const Quaternion& v2 ) |
|
{ |
|
return v1; |
|
} |
|
|
|
IMPLEMENT_ABSTRACT_ELEMENT( DmeLogLayer, CDmeLogLayer ); |
|
|
|
IMPLEMENT_ELEMENT_FACTORY( DmeIntLogLayer, CDmeIntLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeFloatLogLayer, CDmeFloatLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeBoolLogLayer, CDmeBoolLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeColorLogLayer, CDmeColorLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector2LogLayer, CDmeVector2LogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector3LogLayer, CDmeVector3LogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector4LogLayer, CDmeVector4LogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQAngleLogLayer, CDmeQAngleLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionLogLayer, CDmeQuaternionLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixLogLayer, CDmeVMatrixLogLayer ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeStringLogLayer, CDmeStringLogLayer ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// explicit template instantiation |
|
//----------------------------------------------------------------------------- |
|
template class CDmeTypedLogLayer<int>; |
|
template class CDmeTypedLogLayer<float>; |
|
template class CDmeTypedLogLayer<bool>; |
|
template class CDmeTypedLogLayer<Color>; |
|
template class CDmeTypedLogLayer<Vector2D>; |
|
template class CDmeTypedLogLayer<Vector>; |
|
template class CDmeTypedLogLayer<Vector4D>; |
|
template class CDmeTypedLogLayer<QAngle>; |
|
template class CDmeTypedLogLayer<Quaternion>; |
|
template class CDmeTypedLogLayer<VMatrix>; |
|
template class CDmeTypedLogLayer<CUtlString>; |
|
|
|
|
|
IMPLEMENT_ABSTRACT_ELEMENT( DmeCurveInfo, CDmeCurveInfo ); |
|
|
|
IMPLEMENT_ELEMENT_FACTORY( DmeIntCurveInfo, CDmeIntCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeFloatCurveInfo, CDmeFloatCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeBoolCurveInfo, CDmeBoolCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeColorCurveInfo, CDmeColorCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector2CurveInfo, CDmeVector2CurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector3CurveInfo, CDmeVector3CurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector4CurveInfo, CDmeVector4CurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQAngleCurveInfo, CDmeQAngleCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionCurveInfo, CDmeQuaternionCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixCurveInfo, CDmeVMatrixCurveInfo ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeStringCurveInfo, CDmeStringCurveInfo ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// explicit template instantiation |
|
//----------------------------------------------------------------------------- |
|
template class CDmeTypedCurveInfo<int>; |
|
template class CDmeTypedCurveInfo<float>; |
|
template class CDmeTypedCurveInfo<bool>; |
|
template class CDmeTypedCurveInfo<Color>; |
|
template class CDmeTypedCurveInfo<Vector2D>; |
|
template class CDmeTypedCurveInfo<Vector>; |
|
template class CDmeTypedCurveInfo<Vector4D>; |
|
template class CDmeTypedCurveInfo<QAngle>; |
|
template class CDmeTypedCurveInfo<Quaternion>; |
|
template class CDmeTypedCurveInfo<VMatrix>; |
|
template class CDmeTypedCurveInfo<CUtlString>; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Class factory |
|
//----------------------------------------------------------------------------- |
|
IMPLEMENT_ABSTRACT_ELEMENT( DmeLog, CDmeLog ); |
|
|
|
IMPLEMENT_ELEMENT_FACTORY( DmeIntLog, CDmeIntLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeFloatLog, CDmeFloatLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeBoolLog, CDmeBoolLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeColorLog, CDmeColorLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector2Log, CDmeVector2Log ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector3Log, CDmeVector3Log ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVector4Log, CDmeVector4Log ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQAngleLog, CDmeQAngleLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionLog, CDmeQuaternionLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixLog, CDmeVMatrixLog ); |
|
IMPLEMENT_ELEMENT_FACTORY( DmeStringLog, CDmeStringLog ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// explicit template instantiation |
|
//----------------------------------------------------------------------------- |
|
template class CDmeTypedLog<int>; |
|
template class CDmeTypedLog<float>; |
|
template class CDmeTypedLog<bool>; |
|
template class CDmeTypedLog<Color>; |
|
template class CDmeTypedLog<Vector2D>; |
|
template class CDmeTypedLog<Vector>; |
|
template class CDmeTypedLog<Vector4D>; |
|
template class CDmeTypedLog<QAngle>; |
|
template class CDmeTypedLog<Quaternion>; |
|
template class CDmeTypedLog<VMatrix>; |
|
template class CDmeTypedLog<CUtlString>; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// instantiate and initialize static vars |
|
//----------------------------------------------------------------------------- |
|
float CDmeIntLog::s_defaultThreshold = 0.0f; |
|
float CDmeFloatLog::s_defaultThreshold = 0.0f; |
|
float CDmeBoolLog::s_defaultThreshold = 0.0f; |
|
float CDmeColorLog::s_defaultThreshold = 0.0f; |
|
float CDmeVector2Log::s_defaultThreshold = 0.0f; |
|
float CDmeVector3Log::s_defaultThreshold = 0.0f; |
|
float CDmeVector4Log::s_defaultThreshold = 0.0f; |
|
float CDmeQAngleLog::s_defaultThreshold = 0.0f; |
|
float CDmeQuaternionLog::s_defaultThreshold = 0.0f; |
|
float CDmeVMatrixLog::s_defaultThreshold = 0.0f; |
|
float CDmeStringLog::s_defaultThreshold = 0.0f; |
|
|
|
|
|
void CDmeLogLayer::OnConstruction() |
|
{ |
|
m_pOwnerLog = NULL; |
|
m_lastKey = 0; |
|
m_times.Init( this, "times" ); |
|
m_CurveTypes.Init( this, "curvetypes" ); |
|
} |
|
|
|
void CDmeLogLayer::OnDestruction() |
|
{ |
|
} |
|
|
|
CDmeLog *CDmeLogLayer::GetOwnerLog() |
|
{ |
|
return m_pOwnerLog; |
|
} |
|
|
|
const CDmeLog *CDmeLogLayer::GetOwnerLog() const |
|
{ |
|
return m_pOwnerLog; |
|
} |
|
|
|
DmeTime_t CDmeLogLayer::GetBeginTime() const |
|
{ |
|
if ( m_times.Count() == 0 ) |
|
return DmeTime_t::MinTime(); |
|
|
|
return DmeTime_t( m_times[ 0 ] ); |
|
} |
|
|
|
DmeTime_t CDmeLogLayer::GetEndTime() const |
|
{ |
|
uint tn = m_times.Count(); |
|
if ( tn == 0 ) |
|
return DmeTime_t::MaxTime(); |
|
|
|
return DmeTime_t( m_times[ tn - 1 ] ); |
|
} |
|
|
|
|
|
// Validates that all keys are correctly sorted in time |
|
bool CDmeLogLayer::ValidateKeys() const |
|
{ |
|
int nCount = m_times.Count(); |
|
for ( int i = 1; i < nCount; ++i ) |
|
{ |
|
if ( m_times[i] <= m_times[i-1] ) |
|
{ |
|
Warning( "Error in log %s! Key times are out of order [keys %d->%d: %d->%d]!\n", |
|
GetName(), i-1, i, m_times[i-1], m_times[i] ); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
int CDmeLogLayer::FindKey( DmeTime_t time ) const |
|
{ |
|
int tn = m_times.Count(); |
|
if ( m_lastKey >= 0 && m_lastKey < tn ) |
|
{ |
|
if ( time >= DmeTime_t( m_times[ m_lastKey ] ) ) |
|
{ |
|
// common case - playing forward |
|
for ( ; m_lastKey < tn - 1; ++m_lastKey ) |
|
{ |
|
if ( time < DmeTime_t( m_times[ m_lastKey + 1 ] ) ) |
|
return m_lastKey; |
|
} |
|
|
|
// if time past the end, return the last key |
|
return m_lastKey; |
|
} |
|
else |
|
{ |
|
tn = m_lastKey; |
|
} |
|
} |
|
|
|
for ( int ti = tn - 1; ti >= 0; --ti ) |
|
{ |
|
if ( time >= DmeTime_t( m_times[ ti ] ) ) |
|
{ |
|
m_lastKey = ti; |
|
return ti; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the number of keys |
|
//----------------------------------------------------------------------------- |
|
int CDmeLogLayer::GetKeyCount() const |
|
{ |
|
return m_times.Count(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : nKeyIndex - |
|
// keyTime - |
|
//----------------------------------------------------------------------------- |
|
void CDmeLogLayer::SetKeyTime( int nKeyIndex, DmeTime_t keyTime ) |
|
{ |
|
m_times.Set( nKeyIndex, keyTime.GetTenthsOfMS() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a specific key's value |
|
//----------------------------------------------------------------------------- |
|
DmeTime_t CDmeLogLayer::GetKeyTime( int nKeyIndex ) const |
|
{ |
|
return DmeTime_t( m_times[ nKeyIndex ] ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scale + bias key times |
|
//----------------------------------------------------------------------------- |
|
void CDmeLogLayer::ScaleBiasKeyTimes( double flScale, DmeTime_t nBias ) |
|
{ |
|
// Don't waste time on the identity transform |
|
if ( ( nBias == DMETIME_ZERO ) && ( fabs( flScale - 1.0 ) < 1e-5 ) ) |
|
return; |
|
|
|
int nCount = GetKeyCount(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
DmeTime_t t = GetKeyTime( i ); |
|
t.SetSeconds( t.GetSeconds() * flScale ); |
|
t += nBias; |
|
SetKeyTime( i, t ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the index of a particular key |
|
//----------------------------------------------------------------------------- |
|
int CDmeLogLayer::FindKeyWithinTolerance( DmeTime_t nTime, DmeTime_t nTolerance ) |
|
{ |
|
int nClosest = -1; |
|
DmeTime_t nClosestTolerance = DmeTime_t::MaxTime(); |
|
DmeTime_t nCurrTolerance; |
|
int start = 0, end = GetKeyCount() - 1; |
|
while ( start <= end ) |
|
{ |
|
int mid = (start + end) >> 1; |
|
DmeTime_t nDelta = nTime - DmeTime_t( m_times[mid] ); |
|
if ( nDelta > DmeTime_t( 0 ) ) |
|
{ |
|
nCurrTolerance = nDelta; |
|
start = mid + 1; |
|
} |
|
else if ( nDelta < DmeTime_t( 0 ) ) |
|
{ |
|
nCurrTolerance = -nDelta; |
|
end = mid - 1; |
|
} |
|
else |
|
{ |
|
return mid; |
|
} |
|
|
|
if ( nCurrTolerance < nClosestTolerance ) |
|
{ |
|
nClosest = mid; |
|
nClosestTolerance = nCurrTolerance; |
|
} |
|
} |
|
if ( nClosestTolerance > nTolerance ) |
|
return -1; |
|
return nClosest; |
|
} |
|
|
|
void CDmeLogLayer::OnUsingCurveTypesChanged() |
|
{ |
|
if ( g_pDataModel->IsUnserializing() ) |
|
return; |
|
|
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.RemoveAll(); |
|
} |
|
else |
|
{ |
|
m_CurveTypes.RemoveAll(); |
|
// Fill in an array with the default curve type for |
|
int c = m_times.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
m_CurveTypes.AddToTail( GetDefaultCurveType() ); |
|
} |
|
} |
|
} |
|
|
|
bool CDmeLogLayer::IsUsingCurveTypes() const |
|
{ |
|
return GetOwnerLog() ? GetOwnerLog()->IsUsingCurveTypes() : false; |
|
} |
|
|
|
int CDmeLogLayer::GetDefaultCurveType() const |
|
{ |
|
return GetOwnerLog()->GetDefaultCurveType(); |
|
} |
|
|
|
void CDmeLogLayer::SetKeyCurveType( int nKeyIndex, int curveType ) |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
return; |
|
|
|
Assert( GetOwnerLog()->IsUsingCurveTypes() ); |
|
Assert( m_CurveTypes.IsValidIndex( nKeyIndex ) ); |
|
if ( !m_CurveTypes.IsValidIndex( nKeyIndex ) ) |
|
return; |
|
|
|
m_CurveTypes.Set( nKeyIndex, curveType ); |
|
} |
|
|
|
int CDmeLogLayer::GetKeyCurveType( int nKeyIndex ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
return CURVE_DEFAULT; |
|
|
|
Assert( GetOwnerLog()->IsUsingCurveTypes() ); |
|
Assert( m_CurveTypes.IsValidIndex( nKeyIndex ) ); |
|
if ( !m_CurveTypes.IsValidIndex( nKeyIndex ) ) |
|
return GetOwnerLog()->GetDefaultCurveType(); |
|
|
|
return m_CurveTypes[ nKeyIndex ]; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removes all keys outside the specified time range |
|
//----------------------------------------------------------------------------- |
|
void CDmeLogLayer::RemoveKeysOutsideRange( DmeTime_t tStart, DmeTime_t tEnd ) |
|
{ |
|
int i; |
|
int nKeysToRemove = 0; |
|
int nKeyCount = m_times.Count(); |
|
for ( i = 0; i < nKeyCount; ++i, ++nKeysToRemove ) |
|
{ |
|
if ( m_times[i] >= tStart.GetTenthsOfMS() ) |
|
break; |
|
} |
|
|
|
if ( nKeysToRemove ) |
|
{ |
|
RemoveKey( 0, nKeysToRemove ); |
|
} |
|
|
|
nKeyCount = m_times.Count(); |
|
for ( i = 0; i < nKeyCount; ++i ) |
|
{ |
|
if ( m_times[i] > tEnd.GetTenthsOfMS() ) |
|
break; |
|
} |
|
nKeysToRemove = nKeyCount - i; |
|
if ( nKeysToRemove ) |
|
{ |
|
RemoveKey( i, nKeysToRemove ); |
|
} |
|
} |
|
|
|
|
|
template < class T > |
|
class CUndoLayerAdded : public CUndoElement |
|
{ |
|
typedef CUndoElement BaseClass; |
|
|
|
public: |
|
CUndoLayerAdded( const char *desc, CDmeLog *pLog ) : |
|
BaseClass( desc ), |
|
m_bNeedsCleanup( false ), |
|
m_hLog( pLog ) |
|
{ |
|
Assert( pLog && pLog->GetFileId() != DMFILEID_INVALID ); |
|
} |
|
|
|
virtual ~CUndoLayerAdded() |
|
{ |
|
if ( m_bNeedsCleanup ) |
|
{ |
|
g_pDataModel->DestroyElement( m_hLayer ); |
|
} |
|
} |
|
|
|
virtual void Undo() |
|
{ |
|
m_bNeedsCleanup = true; |
|
m_hLayer = m_hLog->RemoveLayerFromTail()->GetHandle(); |
|
g_pDataModel->MarkHandleInvalid( m_hLayer ); |
|
} |
|
|
|
virtual void Redo() |
|
{ |
|
m_bNeedsCleanup = false; |
|
g_pDataModel->MarkHandleValid( m_hLayer ); |
|
m_hLog->AddLayerToTail( GetElement< CDmeTypedLogLayer< T > >( m_hLayer ) ); |
|
} |
|
|
|
virtual const char *GetDesc() |
|
{ |
|
static char sz[ 512 ]; |
|
int iLayer = m_hLog->GetTopmostLayer(); |
|
if ( iLayer >= 0 ) |
|
{ |
|
CDmeLogLayer *layer = m_hLog->GetLayer( iLayer ); |
|
Q_snprintf( sz, sizeof( sz ), "addlayer: log %p lc[%d], layer %p", |
|
m_hLog.Get(), m_hLog->GetNumLayers(), layer ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( sz, sizeof( sz ), "addlayer: log %p lc[%d], layer NULL", |
|
m_hLog.Get(), m_hLog->GetNumLayers() ); |
|
} |
|
return sz; |
|
} |
|
|
|
private: |
|
CDmeHandle< CDmeLog > m_hLog; |
|
bool m_bNeedsCleanup; |
|
CDmeCountedHandle m_hLayer; |
|
}; |
|
|
|
template < class T > |
|
class CUndoFlattenLayers : public CUndoElement |
|
{ |
|
typedef CUndoElement BaseClass; |
|
|
|
public: |
|
|
|
CUndoFlattenLayers( const char *desc, CDmeTypedLog< T > *pLog, float threshold, int flags ) : |
|
BaseClass( desc ), |
|
m_bNeedsCleanup( true ), |
|
m_hLog( pLog ), |
|
m_nFlags( flags ), |
|
m_flThreshold( threshold ) |
|
{ |
|
Assert( pLog && pLog->GetFileId() != DMFILEID_INVALID ); |
|
LatchCurrentLayers(); |
|
} |
|
|
|
virtual ~CUndoFlattenLayers() |
|
{ |
|
if ( m_bNeedsCleanup ) |
|
{ |
|
for ( int i = 0; i < m_hLayers.Count(); ++i ) |
|
{ |
|
m_hLayers[ i ] = DMELEMENT_HANDLE_INVALID; |
|
#ifdef _DEBUG |
|
CDmElement *pElement = g_pDataModel->GetElement( m_hLayers[ i ] ); |
|
Assert( !pElement || pElement->IsStronglyReferenced() ); |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
virtual void Undo() |
|
{ |
|
m_bNeedsCleanup = false; |
|
|
|
for ( int i = 0; i < m_hLayers.Count(); ++i ) |
|
{ |
|
if ( i == 0 ) |
|
{ |
|
// Copy base layer in place so handles to the base layer remain valid |
|
CDmeTypedLogLayer< T > *base = m_hLog->GetLayer( i ); |
|
base->CopyLayer( GetElement< CDmeTypedLogLayer< T > >( m_hLayers[ i ] ) ); |
|
// Release it since we didn't txfer it over |
|
g_pDataModel->DestroyElement( m_hLayers[ i ] ); |
|
} |
|
else |
|
{ |
|
// This transfers ownership, so no Release needed |
|
m_hLog->AddLayerToTail( GetElement< CDmeTypedLogLayer< T > >( m_hLayers[ i ] ) ); |
|
} |
|
} |
|
|
|
m_hLayers.RemoveAll(); |
|
} |
|
|
|
virtual void Redo() |
|
{ |
|
m_bNeedsCleanup = true; |
|
Assert( m_hLayers.Count() == 0 ); |
|
|
|
LatchCurrentLayers(); |
|
// Flatten them again (won't create undo records since we're in undo already) |
|
|
|
m_hLog->FlattenLayers( m_flThreshold, m_nFlags ); |
|
} |
|
|
|
virtual const char *GetDesc() |
|
{ |
|
static char sz[ 512 ]; |
|
Q_snprintf( sz, sizeof( sz ), "flatten log %p lc[%d]", |
|
m_hLog.Get(), m_hLayers.Count() ); |
|
return sz; |
|
} |
|
|
|
private: |
|
|
|
void LatchCurrentLayers() |
|
{ |
|
CDisableUndoScopeGuard guard; |
|
Assert( m_hLayers.Count() == 0 ); |
|
Assert( m_hLog->GetNumLayers() >= 1 ); |
|
|
|
// Entry 0 is the original "base" layer |
|
for ( int i = 0; i < m_hLog->GetNumLayers(); ++i ) |
|
{ |
|
CDmeTypedLogLayer< T > *pLayer = CastElement< CDmeTypedLogLayer< T > >( CreateLayer< T >( m_hLog ) ); |
|
pLayer->CopyLayer( m_hLog->GetLayer( i ) ); |
|
m_hLayers.AddToTail( pLayer->GetHandle() ); |
|
} |
|
} |
|
|
|
CDmeHandle< CDmeTypedLog< T > > m_hLog; |
|
bool m_bNeedsCleanup; |
|
CUtlVector< CDmeCountedHandle > m_hLayers; |
|
int m_nFlags; |
|
float m_flThreshold; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// CDmeTypedLogLayer - a generic typed layer used by a log |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLogLayer< T >::OnConstruction() |
|
{ |
|
// m_times.Init( this, "times" ); |
|
// m_CurveTypes.Init( this, "curvetypes" ); |
|
m_values.Init( this, "values" ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::SetOwnerLog( CDmeLog *owner ) |
|
{ |
|
Assert( owner ); |
|
Assert( assert_cast< CDmeTypedLog< T > * >( owner ) ); |
|
m_pOwnerLog = owner; |
|
} |
|
|
|
template< class T > |
|
CDmeTypedLog< T > *CDmeTypedLogLayer< T >::GetTypedOwnerLog() |
|
{ |
|
return assert_cast< CDmeTypedLog< T > * >( m_pOwnerLog ); |
|
} |
|
|
|
template< class T > |
|
const CDmeTypedLog< T > *CDmeTypedLogLayer< T >::GetTypedOwnerLog() const |
|
{ |
|
return assert_cast< CDmeTypedLog< T > * >( m_pOwnerLog ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::OnDestruction() |
|
{ |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::RemoveKeys( DmeTime_t starttime ) |
|
{ |
|
int ti = FindKey( starttime ); |
|
if ( ti < 0 ) |
|
return; |
|
|
|
if ( starttime > DmeTime_t( m_times[ ti ] ) ) |
|
++ti; |
|
|
|
int nKeys = m_times.Count() - ti; |
|
if ( nKeys == 0 ) |
|
return; |
|
|
|
m_times.RemoveMultiple( ti, nKeys ); |
|
m_values.RemoveMultiple( ti, nKeys ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.RemoveMultiple( ti, nKeys ); |
|
} |
|
|
|
if ( m_lastKey >= ti && m_lastKey < ti + nKeys ) |
|
{ |
|
m_lastKey = ( ti > 0 ) ? ti - 1 : 0; |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::ClearKeys() |
|
{ |
|
m_times.RemoveAll(); |
|
m_values.RemoveAll(); |
|
m_CurveTypes.RemoveAll(); |
|
m_lastKey = 0; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::RemoveKey( int nKeyIndex, int nNumKeysToRemove /*= 1*/ ) |
|
{ |
|
m_times.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); |
|
m_values.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets a key, removes all keys after this time |
|
// FIXME: This needs to account for interpolation!!! |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLogLayer< T >::SetKey( DmeTime_t time, const T& value, int curveType /*=CURVE_DEFAULT*/) |
|
{ |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
|
|
// Remove all keys after this time |
|
RemoveKeys( time ); |
|
|
|
// Add the key and then check to see if the penultimate key is still necessary |
|
m_times.AddToTail( time.GetTenthsOfMS() ); |
|
m_values.AddToTail( value ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.AddToTail( curveType ); |
|
} |
|
|
|
int nKeys = m_values.Count(); |
|
if ( ( nKeys < 3 ) || |
|
( IsUsingCurveTypes() && ( curveType != m_CurveTypes[ nKeys -1 ] || ( curveType != m_CurveTypes[ nKeys - 2 ] ) ) ) |
|
) |
|
{ |
|
return; |
|
} |
|
|
|
// If adding the new means that the penultimate key's value was unneeded, then we will remove the penultimate key value |
|
T check = GetValueSkippingKey( nKeys - 2 ); |
|
T oldPenultimateValue = m_values[ nKeys - 2 ]; |
|
if ( GetTypedOwnerLog()->ValuesDiffer( oldPenultimateValue, check ) ) |
|
{ |
|
return; |
|
} |
|
|
|
// Remove penultimate, it's not needed |
|
m_times.Remove( nKeys - 2 ); |
|
m_values.Remove( nKeys - 2 ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.Remove( nKeys - 2 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a key within tolerance, or adds one |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
int CDmeTypedLogLayer< T >::FindOrAddKey( DmeTime_t nTime, DmeTime_t nTolerance, const T& value, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
|
|
// NOTE: This math must occur in 64bits because the max delta nDelta |
|
// can be 33 bits large. Bleah. |
|
int nClosest = -1; |
|
int64 nClosestTolerance = DmeTime_t::MinTime().GetTenthsOfMS(); |
|
int64 nCurrTolerance; |
|
int start = 0, end = GetKeyCount() - 1; |
|
while ( start <= end ) |
|
{ |
|
int mid = (start + end) >> 1; |
|
int64 nDelta = (int64)nTime.GetTenthsOfMS() - (int64)m_times[mid]; |
|
if ( nDelta > 0 ) |
|
{ |
|
nCurrTolerance = nDelta; |
|
start = mid + 1; |
|
} |
|
else if ( nDelta < 0 ) |
|
{ |
|
nCurrTolerance = -nDelta; |
|
end = mid - 1; |
|
} |
|
else |
|
{ |
|
nClosest = end = mid; |
|
nClosestTolerance = 0; |
|
break; |
|
} |
|
|
|
if ( nCurrTolerance < nClosestTolerance ) |
|
{ |
|
nClosest = mid; |
|
nClosestTolerance = nCurrTolerance; |
|
} |
|
} |
|
|
|
// At this point, end is the entry less than or equal to the entry |
|
if ( nClosest == -1 || nTolerance.GetTenthsOfMS() < nClosestTolerance ) |
|
{ |
|
++end; |
|
nClosest = m_times.InsertBefore( end, nTime.GetTenthsOfMS() ); |
|
m_values.InsertBefore( end, value ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.InsertBefore( end, curveType ); |
|
} |
|
} |
|
return nClosest; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This inserts a key. Unlike SetKey, this will *not* delete keys after the specified time |
|
//----------------------------------------------------------------------------- |
|
template < class T > |
|
int CDmeTypedLogLayer< T >::InsertKey( DmeTime_t nTime, const T& value, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
int idx = FindOrAddKey( nTime, DmeTime_t( 0 ), value ); |
|
m_times .Set( idx, nTime.GetTenthsOfMS() ); |
|
m_values.Set( idx, value ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.Set( idx, curveType ); |
|
} |
|
return idx; |
|
} |
|
|
|
template< class T > |
|
int CDmeTypedLogLayer< T >::InsertKeyAtTime( DmeTime_t nTime, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
T curVal = GetValue( nTime ); |
|
return InsertKey( nTime, curVal, curveType ); |
|
} |
|
|
|
static bool CanInterpolateType( DmAttributeType_t attType ) |
|
{ |
|
switch ( attType ) |
|
{ |
|
default: |
|
return false; |
|
case AT_FLOAT: |
|
case AT_VECTOR3: |
|
case AT_QUATERNION: |
|
break; |
|
} |
|
return true; |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLogLayer< T >::GetValue( DmeTime_t time ) const |
|
{ |
|
// Curve Interpolation only for 1-D float data right now!!! |
|
if ( IsUsingCurveTypes() && |
|
CanInterpolateType( GetDataType() ) ) |
|
{ |
|
static T out; |
|
GetValueUsingCurveInfo( time, out ); |
|
return out; |
|
} |
|
|
|
int tc = m_times.Count(); |
|
|
|
Assert( m_values.Count() == tc ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == tc ) ); |
|
|
|
int ti = FindKey( time ); |
|
if ( ti < 0 ) |
|
{ |
|
if ( tc > 0 ) |
|
return m_values[ 0 ]; |
|
|
|
const CDmeTypedLog< T > *pOwner = GetTypedOwnerLog(); |
|
if ( pOwner->HasDefaultValue() ) |
|
return pOwner->GetDefaultValue(); |
|
|
|
static T s_value; |
|
CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return s_value; |
|
} |
|
|
|
// Early out if we're at the end |
|
if ( ti >= tc - 1 ) |
|
return m_values[ ti ]; |
|
|
|
if ( !IsInterpolableType( GetDataType() ) ) |
|
return m_values[ ti ]; |
|
|
|
// Figure out the lerp factor |
|
float t = GetFractionOfTimeBetween( time, DmeTime_t( m_times[ti] ), DmeTime_t( m_times[ti+1] ) ); |
|
static T s_value; |
|
s_value = Interpolate( t, m_values[ti], m_values[ti+1] ); // Compute the lerp between ti and ti+1 |
|
return s_value; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::SetKey( DmeTime_t time, const CDmAttribute *pAttr, uint index, int curveType /*= CURVE_DEFAULT*/ ) |
|
{ |
|
DmAttributeType_t type = pAttr->GetType(); |
|
if ( IsValueType( type ) ) |
|
{ |
|
Assert( pAttr->GetType() == GetDataType() ); |
|
SetKey( time, pAttr->GetValue< T >(), curveType ); |
|
} |
|
else if ( IsArrayType( type ) ) |
|
{ |
|
Assert( ArrayTypeToValueType( type ) == GetDataType() ); |
|
CDmrArrayConst<T> array( pAttr ); |
|
SetKey( time, array[ index ], curveType ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLogLayer< T >::SetDuplicateKeyAtTime( DmeTime_t time ) |
|
{ |
|
int nKeys = m_times.Count(); |
|
if ( nKeys == 0 || DmeTime_t( m_times[ nKeys - 1 ] ) == time ) |
|
return false; |
|
|
|
T value = GetValue( time ); |
|
// these two calls need to be separated (and we need to make an extra copy here) because |
|
// CUtlVector has an assert to try to safeguard against inserting an existing value |
|
// therefore, m_values.AddToTail( m_values[ i ] ) is illegal (or at least, triggers the assert) |
|
SetKey( time, value ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a specific key's value |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
const T& CDmeTypedLogLayer< T >::GetKeyValue( int nKeyIndex ) const |
|
{ |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
|
|
return m_values[ nKeyIndex ]; |
|
} |
|
|
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::GetValue( DmeTime_t time, CDmAttribute *pAttr, uint index ) const |
|
{ |
|
DmAttributeType_t attrtype = pAttr->GetType(); |
|
if ( IsValueType( attrtype ) ) |
|
{ |
|
Assert( attrtype == GetDataType() ); |
|
pAttr->SetValue( GetValue( time ) ); |
|
} |
|
else if ( IsArrayType( attrtype ) ) |
|
{ |
|
Assert( ArrayTypeToValueType( attrtype ) == GetDataType() ); |
|
CDmrArray<T> array( pAttr ); |
|
array.Set( index, GetValue( time ) ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
template< class T > |
|
float CDmeTypedLogLayer< T >::GetComponent( DmeTime_t time, int componentIndex ) const |
|
{ |
|
return ::GetComponent( GetValue( time ), componentIndex ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::SetKeyValue( int nKey, const T& value ) |
|
{ |
|
Assert( nKey >= 0 ); |
|
Assert( nKey < m_values.Count() ); |
|
|
|
m_values.Set( nKey, value ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// resampling and filtering |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLogLayer< T >::Resample( DmeFramerate_t samplerate ) |
|
{ |
|
// FIXME: Might have to revisit how to determine "curve types" for "resampled points... |
|
Assert( !IsUsingCurveTypes() ); |
|
|
|
// make sure we resample to include _at_least_ the existing time range |
|
DmeTime_t begin = GetBeginTime(); |
|
DmeTime_t end = GetEndTime(); |
|
int nSamples = 2 + FrameForTime( end - begin, samplerate ); |
|
|
|
CUtlVector< int > resampledTimes; |
|
CUtlVector< T > resampledValues; |
|
CUtlVector< int > resampledCurveTypes; |
|
|
|
resampledValues.EnsureCapacity( nSamples ); |
|
resampledTimes.EnsureCapacity( nSamples ); |
|
|
|
DmeTime_t time( begin ); |
|
for ( int i = 0; i < nSamples; ++i ) |
|
{ |
|
resampledTimes.AddToTail( time.GetTenthsOfMS() ); |
|
resampledValues.AddToTail( GetValue( time ) ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
resampledCurveTypes.AddToTail( CURVE_DEFAULT ); |
|
} |
|
|
|
time = time.TimeAtNextFrame( samplerate ); |
|
} |
|
|
|
m_times.SwapArray( resampledTimes ); |
|
m_values.SwapArray( resampledValues ); |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
m_CurveTypes.SwapArray( resampledCurveTypes ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::Filter( int nSampleRadius ) |
|
{ |
|
// Doesn't mess with curvetypes!!! |
|
|
|
const CUtlVector< T > &values = m_values.Get(); |
|
CUtlVector< T > filteredValues; |
|
|
|
int nValues = values.Count(); |
|
filteredValues.EnsureCapacity( nValues ); |
|
|
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
int nSamples = min( nSampleRadius, min( i, nValues - i - 1 ) ); |
|
filteredValues.AddToTail( Average( values.Base() + i - nSamples, 2 * nSamples + 1 ) ); |
|
} |
|
|
|
m_values.SwapArray( filteredValues ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::Filter2( DmeTime_t sampleRadius ) |
|
{ |
|
// Doesn't mess with curvetypes!!! |
|
|
|
const CUtlVector< T > &values = m_values.Get(); |
|
CUtlVector< T > filteredValues; |
|
|
|
int nValues = values.Count(); |
|
filteredValues.EnsureCapacity( nValues ); |
|
|
|
DmeTime_t earliest = DMETIME_ZERO; |
|
if ( nValues > 0 ) |
|
{ |
|
earliest = DmeTime_t( m_times[ 0 ] ); |
|
} |
|
for ( int i = 0; i < nValues; ++i ) |
|
{ |
|
T vals[ 3 ]; |
|
DmeTime_t t = GetKeyTime( i ); |
|
DmeTime_t t0 = t - sampleRadius; |
|
DmeTime_t t1 = t + sampleRadius; |
|
|
|
if ( t0 >= earliest ) |
|
{ |
|
vals[ 0 ] = GetValue( t0 ); |
|
} |
|
else |
|
{ |
|
vals[ 0 ] = m_values[ 0 ]; |
|
} |
|
vals[ 1 ] = GetValue( t ); |
|
vals[ 2 ] = GetValue( t1 ); |
|
|
|
if ( i == 0 || i == nValues - 1 ) |
|
{ |
|
filteredValues.AddToTail( values[ i ] ); |
|
} |
|
else |
|
{ |
|
filteredValues.AddToTail( Average( vals, 3 ) ); |
|
} |
|
} |
|
|
|
m_values.SwapArray( filteredValues ); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLogLayer< T >::GetValueSkippingKey( int nKeyToSkip ) const |
|
{ |
|
// Curve Interpolation only for 1-D float data right now!!! |
|
if ( IsUsingCurveTypes() && CanInterpolateType( GetDataType() ) ) |
|
{ |
|
static T out; |
|
GetValueUsingCurveInfoSkippingKey( nKeyToSkip, out ); |
|
return out; |
|
} |
|
|
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
|
|
DmeTime_t time = GetKeyTime( nKeyToSkip ); |
|
|
|
int prevKey = nKeyToSkip - 1; |
|
int nextKey = nKeyToSkip + 1; |
|
|
|
DmeTime_t prevTime; |
|
T prevValue; |
|
int prevCurveType; |
|
DmeTime_t nextTime; |
|
T nextValue; |
|
int nextCurveType; |
|
|
|
GetBoundedSample( prevKey, prevTime, prevValue, prevCurveType ); |
|
GetBoundedSample( nextKey, nextTime, nextValue, nextCurveType ); |
|
|
|
// Figure out the lerp factor |
|
float t = GetFractionOfTimeBetween( time, prevTime, nextTime ); |
|
|
|
static T s_value; |
|
s_value = Interpolate( t, prevValue, nextValue ); |
|
return s_value; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog<T>::RemoveRedundantKeys( float threshold ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->RemoveRedundantKeys( threshold ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer<T>::RemoveRedundantKeys( float threshold ) |
|
{ |
|
Assert( GetTypedOwnerLog() ); |
|
if ( !GetTypedOwnerLog() ) |
|
return; |
|
|
|
float saveThreshold; |
|
{ |
|
CDisableUndoScopeGuard sg; |
|
saveThreshold = GetTypedOwnerLog()->GetValueThreshold(); |
|
GetTypedOwnerLog()->SetValueThreshold( threshold ); |
|
} |
|
|
|
RemoveRedundantKeys(); |
|
|
|
{ |
|
CDisableUndoScopeGuard sg; |
|
GetTypedOwnerLog()->SetValueThreshold( saveThreshold ); |
|
} |
|
|
|
} |
|
|
|
// Implementation of Douglas-Peucker curve simplification routine (hacked to only care about error against original curve (sort of 1D) |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< T > *output ) |
|
{ |
|
if ( endPoint <= startPoint + 1 ) |
|
{ |
|
return; |
|
} |
|
|
|
int maxPoint = startPoint; |
|
float maxDistanceSqr = 0.0f; |
|
|
|
for ( int i = startPoint + 1 ; i < endPoint; ++i ) |
|
{ |
|
DmeTime_t keyTime = GetKeyTime( i ); |
|
T check = GetKeyValue( i ); |
|
T check2 = output->GetValue( keyTime ); |
|
T dist = Subtract( check, check2 ); |
|
float distSqr = LengthOf( dist ) * LengthOf( dist ); |
|
|
|
if ( distSqr < maxDistanceSqr ) |
|
continue; |
|
|
|
maxPoint = i; |
|
maxDistanceSqr = distSqr; |
|
} |
|
|
|
if ( maxDistanceSqr > thresholdSqr ) |
|
{ |
|
output->InsertKey( GetKeyTime( maxPoint ), GetKeyValue( maxPoint ) ); |
|
CurveSimplify_R( thresholdSqr, startPoint, maxPoint, output ); |
|
CurveSimplify_R( thresholdSqr, maxPoint, endPoint, output ); |
|
} |
|
} |
|
|
|
template<> void CDmeTypedLogLayer< bool >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< bool > *output ) {}; |
|
template<> void CDmeTypedLogLayer< int >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< int > *output ) {}; |
|
template<> void CDmeTypedLogLayer< Color >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< Color > *output ) {}; |
|
template<> void CDmeTypedLogLayer< Quaternion >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< Quaternion > *output ) {}; |
|
template<> void CDmeTypedLogLayer< VMatrix >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< VMatrix > *output ) {}; |
|
|
|
// We can't just walk the keys linearly since it'll accumulate too much error and give us a bad curve after simplification. We do a recursive subdivide which has a worst case of O(n^2) but |
|
// probably is better than that in most cases. |
|
template< class T > |
|
void CDmeTypedLogLayer<T>::RemoveRedundantKeys() |
|
{ |
|
CDmeTypedLog< T > *pOwner = GetTypedOwnerLog(); |
|
if ( !pOwner ) |
|
return; |
|
|
|
int nKeys = GetKeyCount(); |
|
if ( nKeys <= 2 ) |
|
return; |
|
|
|
float thresh = pOwner->GetValueThreshold(); |
|
if ( thresh < 0.0f ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *save = 0; |
|
{ |
|
CDisableUndoScopeGuard guard; |
|
save = CastElement< CDmeTypedLogLayer< T > >( CreateLayer< T >( pOwner ) ); |
|
Assert( save ); |
|
|
|
save->m_times.EnsureCapacity( nKeys ); |
|
save->m_values.EnsureCapacity( nKeys ); |
|
|
|
// Insert start and end points as first "guess" at simplified curve |
|
// Skip preceeding and ending keys that have the same value |
|
int nFirstKey, nLastKey; |
|
for ( nFirstKey = 1; nFirstKey < nKeys; ++nFirstKey ) |
|
{ |
|
// FIXME: Should we use a tolerance check here? |
|
if ( GetKeyValue( nFirstKey ) != GetKeyValue( nFirstKey - 1 ) ) |
|
break; |
|
} |
|
--nFirstKey; |
|
|
|
for ( nLastKey = nKeys; --nLastKey >= 1; ) |
|
{ |
|
// FIXME: Should we use a tolerance check here? |
|
if ( GetKeyValue( nLastKey ) != GetKeyValue( nLastKey - 1 ) ) |
|
break; |
|
} |
|
|
|
if ( nLastKey <= nFirstKey ) |
|
{ |
|
save->InsertKey( GetKeyTime( 0 ), GetKeyValue( 0 ) ); |
|
} |
|
else |
|
{ |
|
if ( GetDataType() == AT_FLOAT ) |
|
{ |
|
save->InsertKey( GetKeyTime( nFirstKey ), GetKeyValue( nFirstKey ) ); |
|
save->InsertKey( GetKeyTime( nLastKey ), GetKeyValue( nLastKey ) ); |
|
|
|
// Recursively finds the point with the largest error from the "simplified curve" and subdivides the problem on both sides until the largest delta from the simplified |
|
// curve is less than the tolerance (squared) |
|
CurveSimplify_R( thresh * thresh, nFirstKey, nLastKey, save ); |
|
} |
|
else |
|
{ |
|
save->InsertKey( GetKeyTime( nFirstKey ), GetKeyValue( nFirstKey ) ); |
|
|
|
// copy over keys that differ from their prior or next keys - this keeps the first and last key of a run of same-valued keys |
|
for ( int i = nFirstKey + 1; i < nLastKey; ++i ) |
|
{ |
|
// prev is from the saved log to allow deleting runs of same-valued keys |
|
const T &prev = save->GetKeyValue( save->GetKeyCount() - 1 ); |
|
const T &curr = GetKeyValue( i ); |
|
const T &next = GetKeyValue( i + 1 ); |
|
if ( pOwner->ValuesDiffer( prev, curr ) || pOwner->ValuesDiffer( curr, next ) ) |
|
{ |
|
save->InsertKey( GetKeyTime( i ), curr ); |
|
} |
|
} |
|
|
|
save->InsertKey( GetKeyTime( nLastKey ), GetKeyValue( nLastKey ) ); |
|
} |
|
} |
|
} |
|
|
|
// This operation is undoable |
|
CopyLayer( save ); |
|
|
|
{ |
|
CDisableUndoScopeGuard guard; |
|
g_pDataModel->DestroyElement( save->GetHandle() ); |
|
} |
|
} |
|
|
|
// curve info helpers |
|
template< class T > |
|
const CDmeTypedCurveInfo< T > *CDmeTypedLogLayer<T>::GetTypedCurveInfo() const |
|
{ |
|
Assert( GetTypedOwnerLog() ); |
|
return GetTypedOwnerLog()->GetTypedCurveInfo(); |
|
} |
|
|
|
template< class T > |
|
CDmeTypedCurveInfo< T > *CDmeTypedLogLayer<T>::GetTypedCurveInfo() |
|
{ |
|
Assert( GetTypedOwnerLog() ); |
|
return GetTypedOwnerLog()->GetTypedCurveInfo(); |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLogLayer< T >::IsUsingEdgeInfo() const |
|
{ |
|
return GetTypedOwnerLog()->IsUsingEdgeInfo(); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLogLayer< T >::GetDefaultEdgeZeroValue() const |
|
{ |
|
return GetTypedOwnerLog()->GetDefaultEdgeZeroValue(); |
|
} |
|
|
|
template< class T > |
|
DmeTime_t CDmeTypedLogLayer< T >::GetRightEdgeTime() const |
|
{ |
|
return GetTypedOwnerLog()->GetRightEdgeTime(); |
|
} |
|
|
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const |
|
{ |
|
GetTypedOwnerLog()->GetEdgeInfo( edge, active, val, curveType ); |
|
} |
|
|
|
template< class T > |
|
int CDmeTypedLogLayer< T >::GetEdgeCurveType( int edge ) const |
|
{ |
|
return GetTypedOwnerLog()->GetEdgeCurveType( edge ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::GetZeroValue( int side, T& val ) const |
|
{ |
|
return GetTypedOwnerLog()->GetZeroValue( side, val ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::GetBoundedSample( int keyindex, DmeTime_t& time, T& val, int& curveType ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
time = DmeTime_t( 0 ); |
|
CDmAttributeInfo< T >::SetDefaultValue( val ); |
|
curveType = CURVE_DEFAULT; |
|
return; |
|
} |
|
|
|
if ( keyindex < 0 ) |
|
{ |
|
time = DmeTime_t( 0 ); |
|
GetZeroValue( 0, val ); |
|
curveType = GetEdgeCurveType( 0 ); |
|
return; |
|
} |
|
else if ( keyindex >= m_times.Count() ) |
|
{ |
|
time = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( time == DmeTime_t( 0 ) && m_times.Count() > 0 ) |
|
{ |
|
// Push it one msec past the final end time |
|
time = DmeTime_t( m_times[ m_times.Count() - 1 ] ) + DmeTime_t( 1 ); |
|
} |
|
GetTypedOwnerLog()->GetZeroValue( 1, val ); |
|
curveType = GetTypedOwnerLog()->GetEdgeCurveType( 1 ); |
|
return; |
|
} |
|
|
|
time = DmeTime_t( m_times[ keyindex ] ); |
|
val = m_values[ keyindex ]; |
|
if ( IsUsingCurveTypes() ) |
|
{ |
|
curveType = m_CurveTypes[ keyindex ]; |
|
if ( curveType == CURVE_DEFAULT ) |
|
{ |
|
curveType = GetTypedOwnerLog()->GetDefaultCurveType(); |
|
} |
|
} |
|
} |
|
|
|
template<> |
|
void CDmeTypedLogLayer< float >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, float& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
out = 0.0f; |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
float v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = nKeyToSkip; |
|
DmeTime_t time = GetKeyTime( nKeyToSkip ); |
|
|
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< float >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
GetZeroValue( 1, out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
template<> |
|
void CDmeTypedLogLayer< Vector >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, Vector& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
Vector v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = nKeyToSkip; |
|
DmeTime_t time = GetKeyTime( nKeyToSkip ); |
|
|
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
template<> |
|
void CDmeTypedLogLayer< Quaternion >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, Quaternion& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
Quaternion v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = nKeyToSkip; |
|
DmeTime_t time = GetKeyTime( nKeyToSkip ); |
|
|
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti + 1 ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
|
|
template<> |
|
void CDmeTypedLogLayer< float >::GetValueUsingCurveInfo( DmeTime_t time, float& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
out = 0.0f; |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
float v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = FindKey( time ); |
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< float >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
GetZeroValue( 1, out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
template<> |
|
void CDmeTypedLogLayer< Vector >::GetValueUsingCurveInfo( DmeTime_t time, Vector& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
Vector v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = FindKey( time ); |
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
CDmAttributeInfo< Vector >::SetDefaultValue( out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
template<> |
|
void CDmeTypedLogLayer< Quaternion >::GetValueUsingCurveInfo( DmeTime_t time, Quaternion& out ) const |
|
{ |
|
Assert( GetOwnerLog() ); |
|
if ( !GetOwnerLog() ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); |
|
return; |
|
} |
|
|
|
Assert( CanInterpolateType( GetDataType() ) ); |
|
Assert( m_values.Count() == m_times.Count() ); |
|
Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); |
|
Assert( IsInterpolableType( GetDataType() ) ); |
|
|
|
Quaternion v[ 4 ]; |
|
DmeTime_t t[ 4 ]; |
|
int curvetypes[ 4 ]; |
|
int ti = FindKey( time ); |
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
if ( ti < 0 ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return; |
|
} |
|
else if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); |
|
if ( finalTime != DmeTime_t( 0 ) ) |
|
{ |
|
if ( time > finalTime ) |
|
{ |
|
CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
if ( ti >= m_times.Count() - 1 ) |
|
{ |
|
out = m_values[ ti ]; |
|
return; |
|
} |
|
} |
|
|
|
GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); |
|
GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); |
|
GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); |
|
GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); |
|
|
|
float frac = 0.0f; |
|
if ( t[2] > t[ 1 ] ) |
|
{ |
|
frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); |
|
} |
|
|
|
// Compute the lerp between ti and ti+1 |
|
out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::CopyLayer( const CDmeLogLayer *src ) |
|
{ |
|
const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); |
|
m_times = pSrc->m_times; |
|
m_lastKey = pSrc->m_lastKey; |
|
m_values = pSrc->m_values; |
|
m_CurveTypes = pSrc->m_CurveTypes; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::InsertKeyFromLayer( DmeTime_t keyTime, const CDmeLogLayer *src, DmeTime_t srcKeyTime ) |
|
{ |
|
const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); |
|
Assert( pSrc ); |
|
|
|
// NOTE: This copy is necessary if src == this |
|
T value = pSrc->GetValue( srcKeyTime ); |
|
InsertKey( keyTime, value ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::ExplodeLayer( const CDmeLogLayer *src, DmeTime_t startTime, DmeTime_t endTime, bool bRebaseTimestamps, DmeTime_t tResampleInterval ) |
|
{ |
|
const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); |
|
Assert( pSrc ); |
|
|
|
DmeTime_t tTimeOffset = DMETIME_ZERO; |
|
if ( bRebaseTimestamps ) |
|
{ |
|
tTimeOffset = -startTime; |
|
} |
|
|
|
m_times.RemoveAll(); |
|
m_values.RemoveAll(); |
|
m_CurveTypes.RemoveAll(); |
|
|
|
bool usecurvetypes = pSrc->IsUsingCurveTypes(); |
|
|
|
// Now copy the data for the later |
|
for ( DmeTime_t t = startTime ; t + tResampleInterval < endTime; t += tResampleInterval ) |
|
{ |
|
DmeTime_t keyTime = DmeTime_t( t ); |
|
if ( keyTime > endTime ) |
|
{ |
|
keyTime = endTime; |
|
} |
|
|
|
T val = pSrc->GetValue( keyTime ); |
|
|
|
keyTime += tTimeOffset; |
|
|
|
InsertKey( keyTime, val, usecurvetypes ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
|
|
m_lastKey = m_times.Count() - 1; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLogLayer< T >::CopyPartialLayer( const CDmeLogLayer *src, DmeTime_t startTime, DmeTime_t endTime, bool bRebaseTimestamps ) |
|
{ |
|
const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); |
|
Assert( pSrc ); |
|
|
|
int nTimeOffset = 0; |
|
if ( bRebaseTimestamps ) |
|
{ |
|
nTimeOffset = -startTime.GetTenthsOfMS(); |
|
} |
|
|
|
m_times.RemoveAll(); |
|
m_values.RemoveAll(); |
|
m_CurveTypes.RemoveAll(); |
|
|
|
bool usecurvetypes = pSrc->IsUsingCurveTypes(); |
|
|
|
// Now copy the data for the later |
|
int c = pSrc->m_times.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
DmeTime_t keyTime = DmeTime_t( pSrc->m_times[ i ] ); |
|
if ( keyTime < startTime || keyTime > endTime ) |
|
continue; |
|
|
|
m_times.AddToTail( pSrc->m_times[ i ] + nTimeOffset ); |
|
m_values.AddToTail( pSrc->m_values[ i ] ); |
|
if ( usecurvetypes ) |
|
{ |
|
m_CurveTypes.AddToTail( pSrc->m_CurveTypes[ i ] ); |
|
} |
|
} |
|
|
|
m_lastKey = m_times.Count() - 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a log of a specific type |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
CDmeLogLayer *CreateLayer< T >( CDmeTypedLog< T > *pOwnerLog ) |
|
{ |
|
DmFileId_t fileid = pOwnerLog ? pOwnerLog->GetFileId() : DMFILEID_INVALID; |
|
CDmeLogLayer *layer = NULL; |
|
|
|
switch ( CDmAttributeInfo<T>::AttributeType() ) |
|
{ |
|
case AT_INT: |
|
case AT_INT_ARRAY: |
|
layer = CreateElement< CDmeIntLogLayer >( "int log", fileid ); |
|
break; |
|
case AT_FLOAT: |
|
case AT_FLOAT_ARRAY: |
|
layer = CreateElement< CDmeFloatLogLayer >( "float log", fileid ); |
|
break; |
|
case AT_BOOL: |
|
case AT_BOOL_ARRAY: |
|
layer = CreateElement< CDmeBoolLogLayer >( "bool log", fileid ); |
|
break; |
|
case AT_COLOR: |
|
case AT_COLOR_ARRAY: |
|
layer = CreateElement< CDmeColorLogLayer >( "color log", fileid ); |
|
break; |
|
case AT_VECTOR2: |
|
case AT_VECTOR2_ARRAY: |
|
layer = CreateElement< CDmeVector2LogLayer >( "vector2 log", fileid ); |
|
break; |
|
case AT_VECTOR3: |
|
case AT_VECTOR3_ARRAY: |
|
layer = CreateElement< CDmeVector3LogLayer >( "vector3 log", fileid ); |
|
break; |
|
case AT_VECTOR4: |
|
case AT_VECTOR4_ARRAY: |
|
layer = CreateElement< CDmeVector4LogLayer >( "vector4 log", fileid ); |
|
break; |
|
case AT_QANGLE: |
|
case AT_QANGLE_ARRAY: |
|
layer = CreateElement< CDmeQAngleLogLayer >( "qangle log", fileid ); |
|
break; |
|
case AT_QUATERNION: |
|
case AT_QUATERNION_ARRAY: |
|
layer = CreateElement< CDmeQuaternionLogLayer >( "quaternion log", fileid ); |
|
break; |
|
case AT_VMATRIX: |
|
case AT_VMATRIX_ARRAY: |
|
layer = CreateElement< CDmeVMatrixLogLayer >( "vmatrix log", fileid ); |
|
break; |
|
case AT_STRING: |
|
case AT_STRING_ARRAY: |
|
layer = CreateElement< CDmeStringLogLayer >( "string log", fileid ); |
|
break; |
|
} |
|
|
|
if ( layer ) |
|
{ |
|
layer->SetOwnerLog( pOwnerLog ); |
|
} |
|
return layer; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// CDmeCurveInfo - abstract base class |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmeCurveInfo::OnConstruction() |
|
{ |
|
m_DefaultCurveType.Init( this, "defaultCurveType" ); |
|
|
|
m_MinValue.InitAndSet( this, "minvalue", 0.0f ); |
|
m_MaxValue.InitAndSet( this, "maxvalue", 1.0f ); |
|
} |
|
|
|
void CDmeCurveInfo::OnDestruction() |
|
{ |
|
} |
|
|
|
// Global override for all keys unless overriden by specific key |
|
void CDmeCurveInfo::SetDefaultCurveType( int curveType ) |
|
{ |
|
m_DefaultCurveType = curveType; |
|
} |
|
|
|
int CDmeCurveInfo::GetDefaultCurveType() const |
|
{ |
|
return m_DefaultCurveType.Get(); |
|
} |
|
|
|
void CDmeCurveInfo::SetMinValue( float val ) |
|
{ |
|
m_MinValue = val; |
|
} |
|
|
|
float CDmeCurveInfo::GetMinValue() const |
|
{ |
|
return m_MinValue; |
|
} |
|
|
|
void CDmeCurveInfo::SetMaxValue( float val ) |
|
{ |
|
m_MaxValue = val; |
|
} |
|
|
|
float CDmeCurveInfo::GetMaxValue() const |
|
{ |
|
return m_MaxValue; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// CDmeTypedCurveInfo - implementation class for all logs |
|
// |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::OnConstruction() |
|
{ |
|
m_bUseEdgeInfo.Init( this, "useEdgeInfo" ); |
|
m_DefaultEdgeValue.Init( this, "defaultEdgeZeroValue" ); |
|
m_RightEdgeTime.Init( this, "rightEdgeTime" ); |
|
|
|
for ( int i = 0; i < 2; ++i ) |
|
{ |
|
char edgename[ 32 ]; |
|
Q_snprintf( edgename, sizeof( edgename ), "%s", i == 0 ? "left" : "right" ); |
|
char name[ 32 ]; |
|
Q_snprintf( name, sizeof( name ), "%sEdgeActive", edgename ); |
|
m_bEdgeActive[ i ].Init( this, name ); |
|
Q_snprintf( name, sizeof( name ), "%sEdgeValue", edgename ); |
|
m_EdgeValue[ i ].Init( this, name ); |
|
Q_snprintf( name, sizeof( name ), "%sEdgeCurveType", edgename ); |
|
m_EdgeCurveType[ i ].Init( this, name ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::OnDestruction() |
|
{ |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::SetUseEdgeInfo( bool state ) |
|
{ |
|
m_bUseEdgeInfo = state; |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedCurveInfo< T >::IsUsingEdgeInfo() const |
|
{ |
|
return m_bUseEdgeInfo; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::SetEdgeInfo( int edge, bool active, const T& val, int curveType ) |
|
{ |
|
SetUseEdgeInfo( true ); |
|
|
|
Assert( edge == 0 || edge == 1 ); |
|
|
|
m_bEdgeActive[ edge ] = active; |
|
m_EdgeValue[ edge ] = val; |
|
m_EdgeCurveType[ edge ] = curveType; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::SetDefaultEdgeZeroValue( const T& val ) |
|
{ |
|
m_DefaultEdgeValue = val; |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedCurveInfo< T >::GetDefaultEdgeZeroValue() const |
|
{ |
|
return m_DefaultEdgeValue; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::SetRightEdgeTime( DmeTime_t time ) |
|
{ |
|
m_RightEdgeTime = time.GetTenthsOfMS(); |
|
} |
|
|
|
template< class T > |
|
DmeTime_t CDmeTypedCurveInfo< T >::GetRightEdgeTime() const |
|
{ |
|
return DmeTime_t( m_RightEdgeTime ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedCurveInfo< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const |
|
{ |
|
Assert( IsUsingEdgeInfo() ); |
|
|
|
Assert( edge == 0 || edge == 1 ); |
|
|
|
active = m_bEdgeActive[ edge ]; |
|
val = m_EdgeValue[ edge ]; |
|
curveType = m_EdgeCurveType[ edge ]; |
|
} |
|
|
|
template< class T > |
|
int CDmeTypedCurveInfo< T >::GetEdgeCurveType( int edge ) const |
|
{ |
|
Assert( edge == 0 || edge == 1 ); |
|
|
|
if ( !m_bEdgeActive[ edge ] ) |
|
{ |
|
return m_DefaultCurveType; |
|
} |
|
|
|
if ( m_EdgeCurveType[ edge ] == CURVE_DEFAULT ) |
|
{ |
|
return m_DefaultCurveType; |
|
} |
|
|
|
return m_EdgeCurveType[ edge ]; |
|
} |
|
|
|
template<> |
|
void CDmeTypedCurveInfo<float>::GetZeroValue( int side, float& val ) const |
|
{ |
|
if ( !m_bUseEdgeInfo ) |
|
{ |
|
val = 0.0f; |
|
return; |
|
} |
|
|
|
if ( m_bEdgeActive[ side ] ) |
|
{ |
|
val = m_EdgeValue[ side ]; |
|
return; |
|
} |
|
|
|
val = m_DefaultEdgeValue; |
|
} |
|
|
|
template<> |
|
bool CDmeTypedCurveInfo<float>::IsEdgeActive( int edge ) const |
|
{ |
|
return m_bEdgeActive[ edge ]; |
|
} |
|
|
|
template<> |
|
void CDmeTypedCurveInfo<float>::GetEdgeValue( int edge, float& value ) const |
|
{ |
|
value = m_EdgeValue[ edge ]; |
|
} |
|
|
|
template<> |
|
void CDmeTypedCurveInfo<Vector>::GetZeroValue( int side, Vector& val ) const |
|
{ |
|
if ( !m_bUseEdgeInfo ) |
|
{ |
|
val = vec3_origin; |
|
return; |
|
} |
|
|
|
if ( m_bEdgeActive[ side ] ) |
|
{ |
|
val = m_EdgeValue[ side ]; |
|
return; |
|
} |
|
|
|
val = m_DefaultEdgeValue; |
|
} |
|
|
|
template<> |
|
void CDmeTypedCurveInfo<Quaternion>::GetZeroValue( int side, Quaternion& val ) const |
|
{ |
|
if ( !m_bUseEdgeInfo ) |
|
{ |
|
val.Init(); |
|
return; |
|
} |
|
|
|
if ( m_bEdgeActive[ side ] ) |
|
{ |
|
val = m_EdgeValue[ side ]; |
|
return; |
|
} |
|
|
|
val = m_DefaultEdgeValue; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// CDmeLog - abstract base class |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmeLog::OnConstruction() |
|
{ |
|
m_Layers.Init( this, "layers", FATTRIB_MUSTCOPY | FATTRIB_HAS_ARRAY_CALLBACK ); |
|
m_CurveInfo.Init( this, "curveinfo", FATTRIB_MUSTCOPY | FATTRIB_HAS_CALLBACK ); |
|
} |
|
|
|
void CDmeLog::OnDestruction() |
|
{ |
|
} |
|
|
|
int CDmeLog::GetTopmostLayer() const |
|
{ |
|
return m_Layers.Count() - 1; |
|
} |
|
|
|
int CDmeLog::GetNumLayers() const |
|
{ |
|
return m_Layers.Count(); |
|
} |
|
|
|
CDmeLogLayer *CDmeLog::GetLayer( int index ) |
|
{ |
|
return m_Layers[ index ]; |
|
} |
|
|
|
const CDmeLogLayer *CDmeLog::GetLayer( int index ) const |
|
{ |
|
return m_Layers[ index ]; |
|
} |
|
|
|
bool CDmeLog::IsEmpty() const |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
if ( layer->GetKeyCount() > 0 ) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
void CDmeLog::FindLayersForTime( DmeTime_t time, CUtlVector< int >& list ) const |
|
{ |
|
list.RemoveAll(); |
|
int c = m_Layers.Count(); |
|
// The base layer is always available!!! |
|
if ( c > 0 ) |
|
{ |
|
list.AddToTail( 0 ); |
|
} |
|
for ( int i = 1; i < c; ++i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
DmeTime_t layerStart = layer->GetBeginTime(); |
|
if ( layerStart == DmeTime_t::MinTime() ) |
|
continue; |
|
DmeTime_t layerEnd = layer->GetEndTime(); |
|
if ( layerEnd == DmeTime_t::MaxTime() ) |
|
continue; |
|
|
|
if ( time >= layerStart && time <= layerEnd ) |
|
{ |
|
list.AddToTail( i ); |
|
} |
|
} |
|
} |
|
|
|
int CDmeLog::FindLayerForTimeSkippingTopmost( DmeTime_t time ) const |
|
{ |
|
int c = m_Layers.Count() - 1; // This makes it never consider the topmost layer!!! |
|
for ( int i = c - 1; i >= 0; --i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
DmeTime_t layerStart = layer->GetBeginTime(); |
|
if ( layerStart == DmeTime_t::MinTime() ) |
|
continue; |
|
DmeTime_t layerEnd = layer->GetEndTime(); |
|
if ( layerEnd == DmeTime_t::MaxTime() ) |
|
continue; |
|
|
|
if ( time >= layerStart && time <= layerEnd ) |
|
return i; |
|
} |
|
return ( c > 0 ) ? 0 : -1; |
|
} |
|
|
|
int CDmeLog::FindLayerForTime( DmeTime_t time ) const |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = c - 1; i >= 0; --i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
DmeTime_t layerStart = layer->GetBeginTime(); |
|
if ( layerStart == DmeTime_t::MinTime() ) |
|
continue; |
|
DmeTime_t layerEnd = layer->GetEndTime(); |
|
if ( layerEnd == DmeTime_t::MaxTime() ) |
|
continue; |
|
|
|
if ( time >= layerStart && time <= layerEnd ) |
|
return i; |
|
} |
|
return ( c > 0 ) ? 0 : -1; |
|
} |
|
|
|
DmeTime_t CDmeLog::GetBeginTime() const |
|
{ |
|
int c = m_Layers.Count(); |
|
if ( c == 0 ) |
|
return DmeTime_t::MinTime(); |
|
|
|
DmeTime_t bestMin = DmeTime_t::MinTime(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
DmeTime_t layerStart = layer->GetBeginTime(); |
|
if ( layerStart == DmeTime_t::MinTime() ) |
|
continue; |
|
|
|
if ( bestMin == DmeTime_t::MinTime() ) |
|
{ |
|
bestMin = layerStart; |
|
} |
|
else if ( layerStart < bestMin ) |
|
{ |
|
bestMin = layerStart; |
|
} |
|
} |
|
|
|
return bestMin; |
|
} |
|
|
|
DmeTime_t CDmeLog::GetEndTime() const |
|
{ |
|
int c = m_Layers.Count(); |
|
if ( c == 0 ) |
|
return DmeTime_t::MaxTime(); |
|
|
|
DmeTime_t bestMax = DmeTime_t::MaxTime(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CDmeLogLayer *layer = m_Layers[ i ]; |
|
DmeTime_t layerEnd = layer->GetEndTime(); |
|
if ( layerEnd == DmeTime_t::MaxTime() ) |
|
continue; |
|
if ( bestMax == DmeTime_t::MaxTime() ) |
|
{ |
|
bestMax = layerEnd; |
|
} |
|
else if ( layerEnd > bestMax ) |
|
{ |
|
bestMax = layerEnd; |
|
} |
|
} |
|
|
|
return bestMax; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the number of keys |
|
//----------------------------------------------------------------------------- |
|
int CDmeLog::GetKeyCount() const |
|
{ |
|
int count = 0; |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
int timecount = layer->GetKeyCount(); |
|
count += timecount; |
|
} |
|
return count; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scale + bias key times |
|
//----------------------------------------------------------------------------- |
|
void CDmeLog::ScaleBiasKeyTimes( double flScale, DmeTime_t nBias ) |
|
{ |
|
// Don't waste time on the identity transform |
|
if ( ( nBias == DMETIME_ZERO ) && ( fabs( flScale - 1.0 ) < 1e-5 ) ) |
|
return; |
|
|
|
int nCount = GetNumLayers(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
CDmeLogLayer *pLayer = GetLayer( i ); |
|
pLayer->ScaleBiasKeyTimes( flScale, nBias ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Resolve - keeps non-attribute data in sync with attribute data |
|
//----------------------------------------------------------------------------- |
|
void CDmeLog::Resolve() |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CDmeLogLayer* layer = m_Layers[ i ]; |
|
layer->SetOwnerLog( this ); |
|
} |
|
} |
|
|
|
void CDmeLog::OnAttributeChanged( CDmAttribute *pAttribute ) |
|
{ |
|
if ( pAttribute == m_CurveInfo.GetAttribute() ) |
|
{ |
|
OnUsingCurveTypesChanged(); |
|
} |
|
} |
|
|
|
void CDmeLog::OnUsingCurveTypesChanged() |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
GetLayer( i )->OnUsingCurveTypesChanged(); |
|
} |
|
} |
|
|
|
// curve info helpers |
|
bool CDmeLog::IsUsingCurveTypes() const |
|
{ |
|
return m_CurveInfo.GetElement() != NULL; |
|
} |
|
|
|
const CDmeCurveInfo *CDmeLog::GetCurveInfo() const |
|
{ |
|
return m_CurveInfo.GetElement(); |
|
} |
|
|
|
CDmeCurveInfo *CDmeLog::GetCurveInfo() |
|
{ |
|
return m_CurveInfo.GetElement(); |
|
} |
|
|
|
// accessors for CurveInfo data |
|
int CDmeLog::GetDefaultCurveType() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return m_CurveInfo->GetDefaultCurveType(); |
|
} |
|
|
|
// min/max accessors |
|
float CDmeLog::GetMinValue() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return m_CurveInfo->GetMinValue(); |
|
} |
|
|
|
void CDmeLog::SetMinValue( float val ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
m_CurveInfo->SetMinValue( val ); |
|
} |
|
|
|
float CDmeLog::GetMaxValue() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return m_CurveInfo->GetMaxValue(); |
|
} |
|
|
|
void CDmeLog::SetMaxValue( float val ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
m_CurveInfo->SetMaxValue( val ); |
|
} |
|
|
|
void CDmeLog::SetKeyCurveType( int nKeyIndex, int curveType ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->SetKeyCurveType( nKeyIndex, curveType ); |
|
} |
|
|
|
int CDmeLog::GetKeyCurveType( int nKeyIndex ) const |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return CURVE_DEFAULT; |
|
|
|
return GetLayer( bestLayer )->GetKeyCurveType( nKeyIndex ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removes all keys in a certain time interval |
|
//----------------------------------------------------------------------------- |
|
bool CDmeLog::RemoveKeys( DmeTime_t tStartTime, DmeTime_t tEndTime ) |
|
{ |
|
CDmeLogLayer *pLayer = GetLayer( GetTopmostLayer() ); |
|
|
|
int nKeyCount = pLayer->GetKeyCount(); |
|
int nFirstRemove = -1; |
|
int nLastRemove = -1; |
|
for ( int nKey = 0; nKey < nKeyCount; ++nKey ) |
|
{ |
|
DmeTime_t tKeyTime = pLayer->GetKeyTime( nKey ); |
|
if ( tKeyTime < tStartTime ) |
|
continue; |
|
if ( tKeyTime > tEndTime ) |
|
break; |
|
if ( nFirstRemove == -1 ) |
|
{ |
|
nFirstRemove = nKey; |
|
} |
|
nLastRemove = nKey; |
|
} |
|
|
|
if ( nFirstRemove != -1 ) |
|
{ |
|
int nRemoveCount = nLastRemove - nFirstRemove + 1; |
|
pLayer->RemoveKey( nFirstRemove, nRemoveCount ); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// CDmeTypedLog - implementation class for all logs |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::OnConstruction() |
|
{ |
|
if ( !g_pDataModel->IsUnserializing() ) |
|
{ |
|
// Add the default layer!!! |
|
AddNewLayer(); |
|
Assert( m_Layers.Count() == 1 ); |
|
} |
|
|
|
m_threshold = s_defaultThreshold; |
|
|
|
m_UseDefaultValue.InitAndSet( this, "usedefaultvalue", false ); |
|
m_DefaultValue.Init( this, "defaultvalue" ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::OnDestruction() |
|
{ |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetDefaultValue( const T& value ) |
|
{ |
|
m_UseDefaultValue = true; |
|
m_DefaultValue.Set( value ); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLog< T >::GetDefaultValue() const |
|
{ |
|
Assert( (bool)m_UseDefaultValue ); |
|
return m_DefaultValue; |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLog< T >::HasDefaultValue() const |
|
{ |
|
return m_UseDefaultValue; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::ClearDefaultValue() |
|
{ |
|
m_UseDefaultValue = false; |
|
T out; |
|
CDmAttributeInfo< T >::SetDefaultValue( out ); |
|
m_DefaultValue.Set( out ); |
|
} |
|
|
|
// Only used by undo system!!! |
|
template< class T > |
|
void CDmeTypedLog< T >::AddLayerToTail( CDmeLogLayer *layer ) |
|
{ |
|
Assert( layer ); |
|
Assert( (static_cast< CDmeTypedLogLayer< T > * >( layer ))->GetTypedOwnerLog() == this ); |
|
m_Layers.AddToTail( layer ); |
|
} |
|
|
|
template< class T > |
|
CDmeLogLayer *CDmeTypedLog< T >::RemoveLayerFromTail() |
|
{ |
|
Assert( m_Layers.Count() >= 1 ); |
|
CDmeLogLayer *layer = m_Layers[ m_Layers.Count() -1 ]; |
|
m_Layers.Remove( m_Layers.Count() - 1 ); |
|
return layer; |
|
} |
|
|
|
template< class T > |
|
CDmeLogLayer *CDmeTypedLog< T >::RemoveLayer( int iLayer ) |
|
{ |
|
Assert( m_Layers.IsValidIndex( iLayer ) ); |
|
CDmeLogLayer *layer = m_Layers[ iLayer ]; |
|
m_Layers.Remove( iLayer ); |
|
return layer; |
|
} |
|
|
|
|
|
template< class T > |
|
CDmeLogLayer *CDmeTypedLog< T >::AddNewLayer() |
|
{ |
|
if ( g_pDataModel->UndoEnabledForElement( this ) ) |
|
{ |
|
CUndoLayerAdded<T> *pUndo = new CUndoLayerAdded<T>( "AddNewLayer", this ); |
|
g_pDataModel->AddUndoElement( pUndo ); |
|
} |
|
|
|
CDisableUndoScopeGuard guard; |
|
|
|
// Now add the layer to the stack!!! |
|
CDmeTypedLogLayer< T > *layer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer<T>( this ) ); |
|
if ( layer ) |
|
{ |
|
layer->SetOwnerLog( this ); |
|
m_Layers.AddToTail( layer ); |
|
} |
|
|
|
return layer; |
|
} |
|
|
|
// curve info helpers |
|
template< class T > |
|
const CDmeTypedCurveInfo< T > *CDmeTypedLog<T>::GetTypedCurveInfo() const |
|
{ |
|
Assert( !m_CurveInfo.GetElement() || dynamic_cast< const CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ) ); |
|
return static_cast< const CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ); |
|
} |
|
|
|
template< class T > |
|
CDmeTypedCurveInfo< T > *CDmeTypedLog<T>::GetTypedCurveInfo() |
|
{ |
|
Assert( !m_CurveInfo.GetElement() || dynamic_cast< CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ) ); |
|
return static_cast< CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog<T>::SetCurveInfo( CDmeCurveInfo *pCurveInfo ) |
|
{ |
|
Assert( !pCurveInfo || dynamic_cast< CDmeTypedCurveInfo< T > * >( pCurveInfo ) ); |
|
m_CurveInfo = pCurveInfo; |
|
OnUsingCurveTypesChanged(); // FIXME: Is this really necessary? OnAttributeChanged should have already called this! |
|
} |
|
|
|
template< class T > |
|
CDmeCurveInfo *CDmeTypedLog<T>::GetOrCreateCurveInfo() |
|
{ |
|
CDmeCurveInfo *pCurveInfo = m_CurveInfo.GetElement(); |
|
if ( pCurveInfo ) |
|
return pCurveInfo; |
|
|
|
SetCurveInfo( CreateElement< CDmeTypedCurveInfo< T > >( "curveinfo", GetFileId() ) ); |
|
return m_CurveInfo.GetElement(); |
|
} |
|
|
|
|
|
|
|
template < class T > |
|
struct ActiveLayer_t |
|
{ |
|
ActiveLayer_t() : |
|
bValid( false ), |
|
priority( 0 ), |
|
firstTime( 0 ), |
|
lastTime( 0 ), |
|
layer( NULL ) |
|
{ |
|
} |
|
|
|
static bool PriorityLessFunc( ActiveLayer_t< T > * const & lhs, ActiveLayer_t< T > * const & rhs ) |
|
{ |
|
return lhs->priority < rhs->priority; |
|
} |
|
|
|
int priority; // higher wins |
|
|
|
bool bValid; |
|
DmeTime_t firstTime; |
|
DmeTime_t lastTime; |
|
|
|
CDmeTypedLogLayer< T > *layer; |
|
}; |
|
|
|
template < class T > |
|
struct LayerEvent_t |
|
{ |
|
enum EventType_t |
|
{ |
|
LE_START = 0, |
|
LE_END |
|
}; |
|
|
|
LayerEvent_t() : m_pList( NULL ), m_Type( LE_START ), m_nLayer( 0 ), m_Time( 0 ) |
|
{ |
|
} |
|
|
|
static bool LessFunc( const LayerEvent_t& lhs, const LayerEvent_t& rhs ) |
|
{ |
|
return lhs.m_Time < rhs.m_Time; |
|
} |
|
|
|
CUtlVector< ActiveLayer_t< T > > *m_pList; |
|
EventType_t m_Type; |
|
int m_nLayer; |
|
DmeTime_t m_Time; |
|
T m_NeighborValue; |
|
}; |
|
|
|
template< class T > |
|
static const T& GetActiveLayerValue( CUtlVector< ActiveLayer_t< T > > &layerlist, DmeTime_t t, int nTopmostLayer ) |
|
{ |
|
int nCount = layerlist.Count(); |
|
#ifdef _DEBUG |
|
Assert( nCount >= nTopmostLayer ); |
|
#endif |
|
|
|
for ( int i = nTopmostLayer; i >= 0; --i ) |
|
{ |
|
ActiveLayer_t< T > &layer = layerlist[i]; |
|
if ( layer.firstTime > t || layer.lastTime < t ) |
|
continue; |
|
|
|
return layer.layer->GetValue( t ); |
|
} |
|
|
|
if ( nCount != 0 ) |
|
{ |
|
const CDmeTypedLog< T > *pOwner = layerlist[0].layer->GetTypedOwnerLog(); |
|
if ( pOwner->HasDefaultValue() ) |
|
return pOwner->GetDefaultValue(); |
|
} |
|
|
|
static T defaultVal; |
|
CDmAttributeInfo<T>::SetDefaultValue( defaultVal ); |
|
return defaultVal; |
|
} |
|
|
|
|
|
template< class T > |
|
static void SpewEvents( CUtlRBTree< LayerEvent_t< T > > &events ) |
|
{ |
|
for ( unsigned short idx = events.FirstInorder(); idx != events.InvalidIndex(); idx = events.NextInorder( idx ) ) |
|
{ |
|
LayerEvent_t< T > *pEvent = &events[ idx ]; |
|
Msg( "Event %u layer %i at time %i type %s\n", |
|
(unsigned)idx, pEvent->m_nLayer, pEvent->m_Time.GetTenthsOfMS(), pEvent->m_Type == LayerEvent_t< T >::LE_START ? "start" : "end" ); |
|
} |
|
} |
|
|
|
template< class T > |
|
inline void SpewKey( const T& ) |
|
{ |
|
Msg( "GenericType" ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<float>( const float& val ) |
|
{ |
|
Msg( "%f", val ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<int>( const int& val ) |
|
{ |
|
Msg( "%d", val ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<Vector2D>( const Vector2D& val ) |
|
{ |
|
Msg( "%f,%f", val.x, val.y ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<Vector4D>( const Vector4D& val ) |
|
{ |
|
Msg( "%f,%f,%f,%f", val.x, val.y, val.z, val.w ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<DmeTime_t>( const DmeTime_t& val ) |
|
{ |
|
Msg( "%d", val.GetTenthsOfMS() ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<bool>( const bool& val ) |
|
{ |
|
Msg( "%s", val ? "true" : "false" ); |
|
} |
|
|
|
template<> |
|
inline void SpewKey<Color>( const Color& val ) |
|
{ |
|
Msg( "%08x", val.GetRawColor() ); |
|
} |
|
|
|
template< > |
|
inline void SpewKey( const Vector& val ) |
|
{ |
|
Msg( "[%f %f %f]", val.x, val.y, val.z ); |
|
} |
|
|
|
template< > |
|
inline void SpewKey( const Quaternion& val ) |
|
{ |
|
Msg( "[%f %f %f %f]", val.x, val.y, val.z, val.w ); |
|
} |
|
|
|
template< class T > |
|
static void SpewFlattenedKey( CDmeTypedLogLayer< T > *pLogLayer, ActiveLayer_t< T > *pActiveLayer, DmeTime_t t, const T& val ) |
|
{ |
|
Msg( "Layer %d: adding key at time %f [%d -> %d], value ", |
|
pActiveLayer->priority, t.GetSeconds(), pActiveLayer->firstTime.GetTenthsOfMS(), pActiveLayer->lastTime.GetTenthsOfMS() ); |
|
SpewKey( val ); |
|
Msg( "\n" ); |
|
} |
|
|
|
template< class T > |
|
static void ComputeLayerEvents( CDmeTypedLog< T >* pLog, |
|
CUtlVector< ActiveLayer_t< T > > &layerlist, |
|
CUtlRBTree< LayerEvent_t< T > > &events ) |
|
{ |
|
// Build a list of all known layers and a sorted list of layer "transitions" |
|
for ( int i = 0; i < pLog->GetNumLayers(); ++i ) |
|
{ |
|
ActiveLayer_t< T > layer; |
|
layer.priority = i; |
|
layer.layer = static_cast< CDmeTypedLogLayer< T > * >( pLog->GetLayer( i ) ); |
|
layer.firstTime = layer.layer->GetBeginTime(); |
|
layer.lastTime = layer.layer->GetEndTime(); |
|
layer.bValid = true; |
|
|
|
if ( ( layer.firstTime == DMETIME_MINTIME || layer.lastTime == DMETIME_MAXTIME ) && ( i > 0 ) ) // Base layer is always valid |
|
{ |
|
layer.bValid = false; |
|
} |
|
|
|
// Skip invalid layers |
|
if ( !layer.bValid ) |
|
continue; |
|
|
|
// Layer zero can capture everything from above... |
|
if ( i == 0 ) |
|
{ |
|
layer.firstTime = DmeTime_t::MinTime(); |
|
layer.lastTime = DmeTime_t::MaxTime(); |
|
} |
|
|
|
// Add layer to global list |
|
int nIndex = layerlist.AddToTail( layer ); |
|
|
|
// Add layer start/end events |
|
DmeTime_t tNeighbor = ( layer.firstTime != DMETIME_MINTIME ) ? ( layer.firstTime - DMETIME_MINDELTA ) : DMETIME_MINTIME; |
|
LayerEvent_t< T > start; |
|
start.m_pList = &layerlist; |
|
start.m_nLayer = nIndex; |
|
start.m_Type = LayerEvent_t< T >::LE_START; |
|
start.m_Time = layer.firstTime; |
|
start.m_NeighborValue = GetActiveLayerValue( layerlist, tNeighbor, nIndex - 1 ); |
|
events.Insert( start ); |
|
|
|
tNeighbor = ( layer.lastTime != DMETIME_MAXTIME ) ? ( layer.lastTime + DMETIME_MINDELTA ) : DMETIME_MAXTIME; |
|
LayerEvent_t< T > end; |
|
end.m_pList = &layerlist; |
|
end.m_nLayer = nIndex; |
|
end.m_Type = LayerEvent_t< T >::LE_END; |
|
end.m_Time = layer.lastTime; |
|
end.m_NeighborValue = GetActiveLayerValue( layerlist, tNeighbor, nIndex - 1 ); |
|
events.Insert( end ); |
|
} |
|
} |
|
|
|
|
|
template< class T > |
|
static void AddDiscontinitySample( CDmeTypedLogLayer< T > *pTargetLayer, CDmeTypedLog< T > *pLog, DmeTime_t tKeyTime, const T& val, const char *pSpewLabel ) |
|
{ |
|
// Finally, add a helper key |
|
if ( pLog->IsUsingCurveTypes() ) |
|
{ |
|
if ( pSpewLabel ) |
|
{ |
|
Msg( "Adding %s helper key at %d value ", pSpewLabel, tKeyTime.GetTenthsOfMS() ); |
|
SpewKey( val ); |
|
Msg( " [curvetype %s]\n", Interpolator_NameForCurveType( pLog->GetDefaultCurveType(), false ) ); |
|
} |
|
pTargetLayer->SetKey( tKeyTime, val, pLog->GetDefaultCurveType() ); |
|
} |
|
else |
|
{ |
|
if ( pSpewLabel ) |
|
{ |
|
Msg( "Adding %s helper key at %d value ", pSpewLabel, tKeyTime.GetTenthsOfMS() ); |
|
SpewKey( val ); |
|
Msg( "\n" ); |
|
} |
|
pTargetLayer->SetKey( tKeyTime, val ); |
|
} |
|
} |
|
|
|
|
|
template< class T > |
|
static DmeTime_t ProcessStartLayerStartEvent( |
|
bool bSpew, |
|
bool bFixupDiscontinuities, |
|
CDmeTypedLog< T > *pLog, |
|
LayerEvent_t< T > *pEvent, |
|
CUtlVector< ActiveLayer_t< T > > &layerlist, |
|
CUtlRBTree< ActiveLayer_t< T > * > &active, |
|
CDmeTypedLogLayer< T > *flattenedlayer ) |
|
{ |
|
Assert( pEvent->m_Type == LayerEvent_t< T >::LE_START ); |
|
|
|
// Push it onto the active stack if it's not already on the stack |
|
if ( active.Find( &layerlist[ pEvent->m_nLayer ] ) != active.InvalidIndex() ) |
|
return pEvent->m_Time; |
|
|
|
if ( bSpew ) |
|
{ |
|
Msg( "adding layer %d to stack\n", layerlist[ pEvent->m_nLayer ].priority ); |
|
} |
|
active.Insert( &layerlist[ pEvent->m_nLayer ] ); |
|
|
|
if ( !bFixupDiscontinuities || ( pEvent->m_Time == DMETIME_MINTIME ) ) |
|
return pEvent->m_Time; |
|
|
|
// We'll need to add 2 new "discontinuity" fixup samples. |
|
// 1) A sample from the base layer @ start time - .1 msec |
|
// 2) A sample from the new layer @ start time |
|
int nActiveCount = active.Count(); |
|
if ( nActiveCount >= 2 ) |
|
{ |
|
DmeTime_t tKeyTime = pEvent->m_Time - DmeTime_t( 1 ); |
|
AddDiscontinitySample( flattenedlayer, pLog, tKeyTime, pEvent->m_NeighborValue, bSpew ? "start" : NULL ); |
|
} |
|
AddDiscontinitySample( flattenedlayer, pLog, pEvent->m_Time, GetActiveLayerValue( layerlist, pEvent->m_Time, pEvent->m_nLayer ), bSpew ? "start" : NULL ); |
|
return pEvent->m_Time; |
|
} |
|
|
|
template< class T > |
|
static DmeTime_t ProcessStartLayerEndEvent( |
|
bool bSpew, |
|
bool bFixupDiscontinuities, |
|
CDmeTypedLog< T > *pLog, |
|
LayerEvent_t< T > *pEvent, |
|
CUtlVector< ActiveLayer_t< T > > &layerlist, |
|
CUtlRBTree< ActiveLayer_t< T > * > &active, |
|
CDmeTypedLogLayer< T > *pBaseLayer ) |
|
{ |
|
Assert( pEvent->m_Type == LayerEvent_t< T >::LE_END ); |
|
|
|
// Push it onto the active stack if it's not already on the stack |
|
if ( bSpew ) |
|
{ |
|
Msg( "removing layer %d from stack\n", layerlist[ pEvent->m_nLayer ].priority ); |
|
} |
|
|
|
// We'll need to add a "discontinuity" fixup sample from the |
|
// 1) A sample from the ending layer @ start time |
|
// 2) A sample from the new layer @ start time + .1 msec |
|
// NOTE: This will cause problems if there are non-default value keys at max time |
|
Assert( active.Count() >= 1 ); |
|
if ( bFixupDiscontinuities && ( pEvent->m_Time != DMETIME_MAXTIME ) ) |
|
{ |
|
AddDiscontinitySample( pBaseLayer, pLog, pEvent->m_Time, GetActiveLayerValue( layerlist, pEvent->m_Time, pEvent->m_nLayer ), bSpew ? "end" : NULL ); |
|
if ( active.Count() >= 2 ) |
|
{ |
|
DmeTime_t keyTime = pEvent->m_Time + DmeTime_t( 1 ); |
|
AddDiscontinitySample( pBaseLayer, pLog, keyTime, pEvent->m_NeighborValue, bSpew ? "end" : NULL ); |
|
} |
|
} |
|
|
|
active.Remove( &layerlist[ pEvent->m_nLayer ] ); |
|
return ( active.Count() >= 2 ) ? pEvent->m_Time + DmeTime_t( 1 ) : pEvent->m_Time; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::FlattenLayers( float threshold, int flags ) |
|
{ |
|
// Already flattened |
|
if ( m_Layers.Count() <= 1 ) |
|
return; |
|
|
|
if ( g_pDataModel->UndoEnabledForElement( this ) ) |
|
{ |
|
CUndoFlattenLayers<T> *pUndo = new CUndoFlattenLayers<T>( "FlattenLayers", this, threshold, flags ); |
|
g_pDataModel->AddUndoElement( pUndo ); |
|
} |
|
|
|
bool bSpew = ( flags & FLATTEN_SPEW ) != 0; |
|
bool bFixupDiscontinuities = true; //( flags & FLATTEN_NODISCONTINUITY_FIXUP ) == 0; |
|
|
|
// NOTE: UNDO IS DISABLED FOR THE REST OF THIS OPERATION (the above function does what we need to preserve the layers) |
|
CDisableUndoScopeGuard guard; |
|
|
|
CDmeTypedLogLayer< T > *flattenedlayer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer< T >( this ) ); |
|
flattenedlayer->SetOwnerLog( this ); |
|
|
|
// Global list of layers |
|
CUtlVector< ActiveLayer_t< T > > layerlist; |
|
// List of all start/end layer events, sorted by the time at which the event occurs ( we walk this list in order ) |
|
CUtlRBTree< LayerEvent_t< T > > events( 0, 0, LayerEvent_t< T >::LessFunc ); |
|
// Stack of active events, sorted by event "priority", which means last item is the one writing data into the new base layer |
|
CUtlRBTree< ActiveLayer_t< T > * > active( 0, 0, ActiveLayer_t< T >::PriorityLessFunc ); |
|
|
|
// Build layer list and list of start/end events and times |
|
ComputeLayerEvents( this, layerlist, events ); |
|
|
|
// Debuggins |
|
if ( bSpew ) |
|
{ |
|
SpewEvents( events ); |
|
} |
|
|
|
// Now walk from the earliest time in any layer until the latest time, going key by key and checking if the active layer should change as we go |
|
|
|
DmeTime_t iCurrentKeyTime = DmeTime_t::MinTime(); |
|
unsigned short idx = events.FirstInorder(); |
|
while ( 1 ) |
|
{ |
|
if ( idx == events.InvalidIndex() ) |
|
break; |
|
|
|
LayerEvent_t< T > *pEvent = &events[ idx ]; |
|
|
|
switch ( pEvent->m_Type ) |
|
{ |
|
default: |
|
iCurrentKeyTime = pEvent->m_Time; |
|
Assert( 0 ); |
|
break; |
|
case LayerEvent_t< T >::LE_START: |
|
iCurrentKeyTime = ProcessStartLayerStartEvent( bSpew, bFixupDiscontinuities, this, pEvent, layerlist, active, flattenedlayer ); |
|
break; |
|
|
|
case LayerEvent_t< T >::LE_END: |
|
iCurrentKeyTime = ProcessStartLayerEndEvent( bSpew, bFixupDiscontinuities, this, pEvent, layerlist, active, flattenedlayer ); |
|
break; |
|
} |
|
|
|
int nNextIndex = events.NextInorder( idx ); |
|
|
|
// We popped the last item off the stack |
|
if ( nNextIndex == events.InvalidIndex() ) |
|
{ |
|
Assert( active.Count() == 0 ); |
|
break; |
|
} |
|
|
|
// Walk from current time up to the time of the next relevant event |
|
LayerEvent_t< T > *nextevent = &events[ nNextIndex ]; |
|
DmeTime_t layerFinishTime = nextevent->m_Time; |
|
|
|
// The topmost layer is the active layer |
|
int layernum = active.LastInorder(); |
|
if ( layernum == active.InvalidIndex() ) |
|
break; |
|
|
|
ActiveLayer_t< T > *activeLayer = active[ layernum ]; |
|
CDmeTypedLogLayer< T > *loglayer = activeLayer->layer; |
|
|
|
// Splat all keys betweeen the current head position and the next event time (layerFinishTime) into the flattened layer |
|
int keyCount = loglayer->GetKeyCount(); |
|
for ( int j = 0; j < keyCount; ++j ) |
|
{ |
|
DmeTime_t keyTime = loglayer->GetKeyTime( j ); |
|
// Key is too early, skip |
|
if ( keyTime < iCurrentKeyTime ) |
|
continue; |
|
|
|
// Done with this layer, set time exactly equal to end time so next layer can take over |
|
// at the correct spot |
|
if ( keyTime >= layerFinishTime ) |
|
{ |
|
iCurrentKeyTime = layerFinishTime; |
|
break; |
|
} |
|
|
|
// Advance the head position |
|
iCurrentKeyTime = keyTime; |
|
|
|
// Because it's a key, the interpolated value should == the actual value (not true for certain 4 point curve types, but we shouldn't support them |
|
// for this type of operation anyway) |
|
const T& val = loglayer->GetKeyValue( j ); |
|
|
|
// Debugging spew |
|
if ( bSpew ) |
|
{ |
|
SpewFlattenedKey( loglayer, activeLayer, iCurrentKeyTime, val ); |
|
} |
|
|
|
// Now set the key into the flattened layer |
|
flattenedlayer->SetKey( iCurrentKeyTime, val, loglayer->IsUsingCurveTypes() ? loglayer->GetKeyCurveType( j ) : CURVE_DEFAULT ); |
|
} |
|
idx = nNextIndex; |
|
} |
|
|
|
// Blow away all of the existing layers except the original base layer |
|
while ( GetNumLayers() > 1 ) |
|
{ |
|
CDmeTypedLogLayer< T > *layer = static_cast< CDmeTypedLogLayer< T > * >( RemoveLayerFromTail() ); |
|
g_pDataModel->DestroyElement( layer->GetHandle() ); |
|
} |
|
|
|
// Compress the flattened layer |
|
flattenedlayer->RemoveRedundantKeys( threshold ); |
|
|
|
// Copy the flattened layer over the existing base layer |
|
GetLayer( 0 )->CopyLayer( flattenedlayer ); |
|
|
|
g_pDataModel->DestroyElement( flattenedlayer->GetHandle() ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::StampKeyAtHead( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) |
|
{ |
|
DmAttributeType_t type = pAttr->GetType(); |
|
if ( IsValueType( type ) ) |
|
{ |
|
Assert( pAttr->GetType() == GetDataType() ); |
|
StampKeyAtHead( tHeadPosition, tPreviousHeadPosition, params, pAttr->GetValue< T >() ); |
|
} |
|
else if ( IsArrayType( type ) ) |
|
{ |
|
Assert( ArrayTypeToValueType( type ) == GetDataType() ); |
|
CDmrArrayConst<T> array( pAttr ); |
|
StampKeyAtHead( tHeadPosition, tPreviousHeadPosition, params, array[ index ] ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::FinishTimeSelection( DmeTime_t tHeadPosition, DmeLog_TimeSelection_t& params ) |
|
{ |
|
bool bWasAdvancing = params.IsTimeAdvancing(); |
|
params.ResetTimeAdvancing(); |
|
|
|
if ( !params.m_bAttachedMode ) |
|
return; |
|
|
|
if ( !bWasAdvancing ) |
|
return; |
|
|
|
// Should be in "layer recording" mode!!! |
|
Assert( GetNumLayers() >= 2 ); |
|
int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( nBestLayer < 1 ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); |
|
Assert( pWriteLayer ); |
|
if ( !pWriteLayer ) |
|
return; |
|
|
|
int nKeyCount = pWriteLayer->GetKeyCount(); |
|
if ( nKeyCount <= 0 ) |
|
return; |
|
|
|
// The head is considered to be at the "last" value |
|
T headValue = pWriteLayer->GetKeyValue( nKeyCount - 1 ); |
|
_StampKeyAtHeadResample( tHeadPosition, params, headValue, true, false ); |
|
} |
|
|
|
template< > |
|
float CDmeTypedLog< float >::ClampValue( const float& value ) |
|
{ |
|
float retval; |
|
if ( !IsUsingCurveTypes() ) |
|
{ |
|
retval = clamp( value, 0.0f, 1.0f ); |
|
} |
|
else |
|
{ |
|
retval = clamp( value, GetMinValue(), GetMaxValue() ); |
|
} |
|
return retval; |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::StampKeyAtHead( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t& params, const T& value ) |
|
{ |
|
//T useValue = ClampValue( value ); |
|
|
|
// This gets set if time ever starts moving (even if the user pauses time while still holding a slider) |
|
if ( params.IsTimeAdvancing() ) |
|
{ |
|
// This uses the time selection as a "filter" to decide whether to stamp a new key at the current position |
|
_StampKeyAtHeadFilteredByTimeSelection( tHeadPosition, tPreviousHeadPosition, params, value ); |
|
} |
|
else |
|
{ |
|
Assert( params.m_bResampleMode ); |
|
_StampKeyAtHeadResample( tHeadPosition, params, value, false, true ); |
|
} |
|
} |
|
|
|
/* |
|
template<> |
|
void CDmeTypedLog< float >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const float& value ); |
|
template<> |
|
void CDmeTypedLog< bool >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const bool& value ); |
|
template<> |
|
void CDmeTypedLog< Color >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Color& value ); |
|
template<> |
|
void CDmeTypedLog< Vector4D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector4D& value ); |
|
template<> |
|
void CDmeTypedLog< Vector2D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector2D& value ); |
|
template<> |
|
void CDmeTypedLog< VMatrix >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const VMatrix& value ); |
|
template<> |
|
void CDmeTypedLog< Quaternion >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Quaternion& value ); |
|
template<> |
|
void CDmeTypedLog< QAngle >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const QAngle& value ); |
|
*/ |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Helper class used to compute falloff blend factors |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
struct LogClampHelper_t |
|
{ |
|
public: |
|
LogClampHelper_t() : m_tLastTime( DMETIME_MINTIME ) {} |
|
|
|
DmeTime_t m_tLastTime; |
|
T m_LastUnclampedValue; |
|
}; |
|
|
|
|
|
template< class T > |
|
class CLogFalloffBlend |
|
{ |
|
public: |
|
void Init( CDmeTypedLog<T> *pLog, DmeTime_t tFalloff, DmeTime_t tHold, bool bLeftFalloff, int nInterpolatorType, const T& delta ); |
|
void Init( CDmeTypedLog<T> *pLog, const T& delta ); |
|
float ComputeBlendFactor( DmeTime_t tTime, const T& oldVal, bool bUsingInterpolation ) const; |
|
const T& GetDelta() const; |
|
void StampKey( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &helper, bool bSpew, const T* pInterpTarget ); |
|
void UpdateClampHelper( DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &helper, const T* pInterpTarget ); |
|
|
|
private: |
|
void ComputeDelta( CDmeTypedLog<T> *pLog, const T& delta, const T& holdValue ); |
|
void InsertClampTransitionPoints( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, LogClampHelper_t<T> &clampHelper, const T& val, bool bSpew ); |
|
void ComputeBounds( CDmeTypedLog<T> *pLog ); |
|
|
|
T m_BaseValue; |
|
T m_Delta; |
|
DmeTime_t m_tBaseTime; |
|
DmeTime_t m_tHoldTime; |
|
float m_flOOTime; |
|
int m_nTestSign; |
|
int m_nInterpolatorType; |
|
int m_nCurveType; |
|
T m_MinValue; |
|
T m_MaxValue; |
|
bool m_bHold; |
|
}; |
|
|
|
template< class T > |
|
void CLogFalloffBlend< T >::Init( CDmeTypedLog<T> *pLog, DmeTime_t tFalloffTime, DmeTime_t tHoldTime, bool bLeftFalloff, int nInterpolatorType, const T& delta ) |
|
{ |
|
m_tBaseTime = tFalloffTime; |
|
m_tHoldTime = tHoldTime; |
|
m_BaseValue = pLog->GetValueSkippingTopmostLayer( tFalloffTime ); |
|
T holdValue = pLog->GetValueSkippingTopmostLayer( tHoldTime ); |
|
m_nTestSign = bLeftFalloff ? 1 : -1; |
|
m_nInterpolatorType = nInterpolatorType; |
|
m_bHold = false; |
|
m_nCurveType = pLog->IsUsingCurveTypes() ? pLog->GetDefaultCurveType() : CURVE_DEFAULT; |
|
|
|
float flDuration = tHoldTime.GetSeconds() - tFalloffTime.GetSeconds(); |
|
m_flOOTime = ( flDuration != 0.0f ) ? 1.0f / flDuration : 0.0f; |
|
ComputeBounds( pLog ); |
|
ComputeDelta( pLog, delta, holdValue ); |
|
} |
|
|
|
template< class T > |
|
void CLogFalloffBlend< T >::Init( CDmeTypedLog<T> *pLog, const T& delta ) |
|
{ |
|
m_nTestSign = 0; |
|
m_nInterpolatorType = INTERPOLATE_DEFAULT; |
|
m_bHold = true; |
|
m_nCurveType = pLog->IsUsingCurveTypes() ? pLog->GetDefaultCurveType() : CURVE_DEFAULT; |
|
m_Delta = delta; |
|
ComputeBounds( pLog ); |
|
} |
|
|
|
template< class T > |
|
void CLogFalloffBlend< T >::ComputeBounds( CDmeTypedLog<T> *pLog ) |
|
{ |
|
} |
|
|
|
template<> |
|
void CLogFalloffBlend< float >::ComputeBounds( CDmeTypedLog<float> *pLog ) |
|
{ |
|
m_MinValue = pLog->IsUsingCurveTypes() ? pLog->GetMinValue() : 0.0f; |
|
m_MaxValue = pLog->IsUsingCurveTypes() ? pLog->GetMaxValue() : 1.0f; |
|
} |
|
|
|
template< class T > |
|
void CLogFalloffBlend< T >::ComputeDelta( CDmeTypedLog<T> *pLog, const T& delta, const T& holdValue ) |
|
{ |
|
// By default, no clamping |
|
m_Delta = delta; |
|
} |
|
|
|
template<> |
|
void CLogFalloffBlend< float >::ComputeDelta( CDmeTypedLog<float> *pLog, const float& delta, const float& holdValue ) |
|
{ |
|
if ( LengthOf( delta ) > 0.0f ) |
|
{ |
|
m_Delta = min( delta, m_MaxValue - holdValue ); // Max amount we can move up... |
|
} |
|
else |
|
{ |
|
m_Delta = max( delta, m_MinValue - holdValue ); // Amount we can move down... |
|
} |
|
} |
|
|
|
|
|
template< class T > |
|
float CLogFalloffBlend< T >::ComputeBlendFactor( DmeTime_t tTime, const T& oldVal, bool bUsingInterpolation ) const |
|
{ |
|
if ( m_bHold ) |
|
return 1.0f; |
|
|
|
// Clamp inside region; hold time beats base time (for zero width regions) |
|
if ( ( tTime - m_tHoldTime ) * m_nTestSign >= DMETIME_ZERO ) |
|
return 1.0f; |
|
|
|
if ( ( tTime - m_tBaseTime ) * m_nTestSign <= DMETIME_ZERO ) |
|
return 0.0f; |
|
|
|
float flFactor = ( tTime.GetSeconds() - m_tBaseTime.GetSeconds() ) * m_flOOTime; |
|
return ComputeInterpolationFactor( flFactor, m_nInterpolatorType ); |
|
} |
|
|
|
template< class T > |
|
const T& CLogFalloffBlend< T >::GetDelta( ) const |
|
{ |
|
return m_Delta; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Insert points where clamping begins or ends |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CLogFalloffBlend< T >::InsertClampTransitionPoints( CDmeTypedLogLayer<T>* pWriteLayer, |
|
DmeTime_t t, LogClampHelper_t<T> &clampHelper, const T& val, bool bSpew ) |
|
{ |
|
// NOTE: By default, nothing clamps, so no transition points are needed |
|
} |
|
|
|
template<> |
|
void CLogFalloffBlend< float >::InsertClampTransitionPoints( CDmeTypedLogLayer<float>* pWriteLayer, |
|
DmeTime_t t, LogClampHelper_t<float> &clampHelper, const float& val, bool bSpew ) |
|
{ |
|
bool bLastLess, bLastGreater, bCurrLess, bCurrGreater; |
|
DmeTime_t tCrossing, tDuration; |
|
double flOODv; |
|
|
|
// First time through? cache last values. |
|
if ( clampHelper.m_tLastTime == DMETIME_MINTIME ) |
|
goto cacheLastValues; |
|
|
|
bLastLess = clampHelper.m_LastUnclampedValue < m_MinValue; |
|
bLastGreater = clampHelper.m_LastUnclampedValue > m_MaxValue; |
|
bCurrLess = val < m_MinValue; |
|
bCurrGreater = val > m_MaxValue; |
|
if ( bLastLess == bCurrLess && bLastGreater == bCurrGreater ) |
|
goto cacheLastValues; |
|
|
|
// NOTE: The check above means val != m_LastUnclampedValue |
|
flOODv = 1.0 / ( val - clampHelper.m_LastUnclampedValue ); |
|
tDuration = t - clampHelper.m_tLastTime; |
|
|
|
// NOTE: Clamp semantics here favor keeping the non-clamped value |
|
// That's why when we start outside + end inside, we never overwrite the dest |
|
// and why when we start inside + end outside, we never overwrite the start |
|
// These two checks deal with starting outside + heading inside |
|
if ( bLastLess && !bCurrLess ) |
|
{ |
|
// Insert at min crossing |
|
double flFactor = ( m_MinValue - clampHelper.m_LastUnclampedValue ) * flOODv; |
|
tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; |
|
tCrossing.Clamp( clampHelper.m_tLastTime, t - DMETIME_MINDELTA ); |
|
pWriteLayer->InsertKey( tCrossing, m_MinValue, m_nCurveType ); |
|
if ( bSpew ) |
|
{ |
|
Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MinValue ); |
|
} |
|
} |
|
else if ( bLastGreater && !bCurrGreater ) |
|
{ |
|
// Insert at max crossing |
|
double flFactor = ( m_MaxValue - clampHelper.m_LastUnclampedValue ) * flOODv; |
|
tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; |
|
tCrossing.Clamp( clampHelper.m_tLastTime, t - DMETIME_MINDELTA ); |
|
pWriteLayer->InsertKey( tCrossing, m_MaxValue, m_nCurveType ); |
|
if ( bSpew ) |
|
{ |
|
Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MaxValue ); |
|
} |
|
} |
|
|
|
// These two checks deal with starting inside + heading outside |
|
if ( !bLastLess && bCurrLess ) |
|
{ |
|
// Insert at min crossing |
|
// NOTE: Clamp semantics here favor keeping the non-clamped value |
|
double flFactor = ( m_MinValue - clampHelper.m_LastUnclampedValue ) * flOODv; |
|
tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; |
|
tCrossing.Clamp( clampHelper.m_tLastTime + DMETIME_MINDELTA, t ); |
|
pWriteLayer->InsertKey( tCrossing, m_MinValue, m_nCurveType ); |
|
if ( bSpew ) |
|
{ |
|
Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MinValue ); |
|
} |
|
} |
|
else if ( !bLastGreater && bCurrGreater ) |
|
{ |
|
// Insert at max crossing |
|
double flFactor = ( m_MaxValue - clampHelper.m_LastUnclampedValue ) * flOODv; |
|
tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; |
|
tCrossing.Clamp( clampHelper.m_tLastTime + DMETIME_MINDELTA, t ); |
|
pWriteLayer->InsertKey( tCrossing, m_MaxValue, m_nCurveType ); |
|
if ( bSpew ) |
|
{ |
|
Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MaxValue ); |
|
} |
|
} |
|
|
|
// Cache off the last values |
|
cacheLastValues: |
|
clampHelper.m_tLastTime = t; |
|
clampHelper.m_LastUnclampedValue = val; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stamp the key at the specified time |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CLogFalloffBlend< T >::StampKey( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, |
|
const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &clampHelper, bool bSpew, const T* pInterpTarget ) |
|
{ |
|
// Stamp the key at the current time |
|
T oldVal = pReadLayer->GetValue( t ); |
|
|
|
// In the falloff area |
|
float flFactor = ComputeBlendFactor( t, oldVal, ( pInterpTarget != NULL ) ); |
|
flFactor *= flIntensity; |
|
|
|
T newVal; |
|
if ( !pInterpTarget ) |
|
{ |
|
newVal = ScaleValue( m_Delta, flFactor ); |
|
newVal = Add( oldVal, newVal ); |
|
} |
|
else |
|
{ |
|
newVal = Interpolate( flFactor, oldVal, *pInterpTarget ); |
|
} |
|
|
|
InsertClampTransitionPoints( pWriteLayer, t, clampHelper, newVal, bSpew ); |
|
|
|
T clampedVal = pWriteLayer->GetTypedOwnerLog()->ClampValue( newVal ); |
|
|
|
// Add a key to the new "layer" at this time with this value |
|
pWriteLayer->InsertKey( t, clampedVal, m_nCurveType ); |
|
|
|
if ( bSpew ) |
|
{ |
|
Msg(" Key: %d ", t.GetTenthsOfMS() ); |
|
SpewKey( clampedVal ); |
|
Msg(" [" ); |
|
SpewKey( newVal ); |
|
Msg( "]\n" ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stamp the key at the specified time |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CLogFalloffBlend< T >::UpdateClampHelper( DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, |
|
float flIntensity, LogClampHelper_t<T> &clampHelper, const T* pInterpTarget ) |
|
{ |
|
// Stamp the key at the current time |
|
T oldVal = pReadLayer->GetValue( t ); |
|
|
|
// In the falloff area |
|
float flFactor = ComputeBlendFactor( t, oldVal, ( pInterpTarget != NULL ) ); |
|
flFactor *= flIntensity; |
|
|
|
T val; |
|
if ( !pInterpTarget ) |
|
{ |
|
val = ScaleValue( m_Delta, flFactor ); |
|
val = Add( oldVal, val ); |
|
} |
|
else |
|
{ |
|
val = Interpolate( flFactor, oldVal, *pInterpTarget ); |
|
} |
|
|
|
clampHelper.m_tLastTime = t; |
|
clampHelper.m_LastUnclampedValue = val; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This is used to modify the entire portion of the curve under the time selection |
|
//----------------------------------------------------------------------------- |
|
static inline DmeTime_t ComputeResampleStartTime( const DmeLog_TimeSelection_t ¶ms, int nSide ) |
|
{ |
|
// NOTE: This logic will place the resampled points centered in the falloff regions |
|
DmeTimeSelectionTimes_t start = ( nSide == 0 ) ? TS_LEFT_FALLOFF : TS_RIGHT_HOLD; |
|
DmeTimeSelectionTimes_t end = ( nSide == 0 ) ? TS_LEFT_HOLD : TS_RIGHT_FALLOFF; |
|
|
|
if ( params.m_nFalloffInterpolatorTypes[nSide] != INTERPOLATE_LINEAR_INTERP ) |
|
{ |
|
DmeTime_t tDuration = params.m_nTimes[end] - params.m_nTimes[start]; |
|
if ( tDuration > params.m_nResampleInterval ) |
|
{ |
|
int nFactor = tDuration.GetTenthsOfMS() / params.m_nResampleInterval.GetTenthsOfMS(); |
|
tDuration -= params.m_nResampleInterval * nFactor; |
|
tDuration /= 2; |
|
return params.m_nTimes[start] + tDuration; |
|
} |
|
} |
|
return DMETIME_MAXTIME; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This is used to modify the entire portion of the curve under the time selection |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::_StampKeyAtHeadResample( DmeTime_t tHeadPosition, const DmeLog_TimeSelection_t& params, const T& value, bool bSkipToHead, bool bClearPreviousKeys ) |
|
{ |
|
Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); |
|
if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) |
|
return; |
|
|
|
// Should be in "layer recording" mode!!! |
|
Assert( GetNumLayers() >= 2 ); |
|
int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( nBestLayer < 1 ) |
|
return; |
|
CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); |
|
Assert( pWriteLayer ); |
|
if ( !pWriteLayer ) |
|
return; |
|
|
|
if ( bClearPreviousKeys ) |
|
{ |
|
pWriteLayer->ClearKeys(); |
|
} |
|
|
|
bool bSpew = false; |
|
|
|
// NOTE: The headDelta is only used when not blending toward a preset |
|
// When not blending toward a preset, just add the head delta onto everything. |
|
// When blending toward a preset, lerp towards the preset. |
|
T oldHeadValue = GetValueSkippingTopmostLayer( tHeadPosition ); |
|
T headDelta = Subtract( value, oldHeadValue ); |
|
|
|
// When dragging preset fader, eveything get's blended in by the amount of the preset being applied |
|
bool bUsePresetRules = ( RECORD_PRESET == params.GetRecordingMode() ); |
|
bool bIsStampingQuaternions = ( CDmAttributeInfo<T>::ATTRIBUTE_TYPE == AT_QUATERNION ); |
|
bool bPerformInterpolation = bUsePresetRules || bIsStampingQuaternions; |
|
|
|
// FIXME: Preset value should never be NULL. We need to grab it from the attribute |
|
bool bUsePresetValue = bUsePresetRules && params.m_pPresetValue && params.m_pPresetValue->GetType() == CDmAttributeInfo<T>::ATTRIBUTE_TYPE; |
|
const T& interpTarget = bUsePresetValue ? params.m_pPresetValue->GetValue<T>() : value; |
|
|
|
// Compute falloff region blend factors |
|
CLogFalloffBlend< T > blend[ 3 ]; |
|
blend[0].Init( this, params.m_nTimes[ TS_FALLOFF(0) ], params.m_nTimes[ TS_HOLD(0) ], true, params.m_nFalloffInterpolatorTypes[0], headDelta ); |
|
blend[1].Init( this, headDelta ); |
|
blend[2].Init( this, params.m_nTimes[ TS_FALLOFF(1) ], params.m_nTimes[ TS_HOLD(1) ], false, params.m_nFalloffInterpolatorTypes[1], headDelta ); |
|
|
|
// The algorithm we're going to use is to add samples in the following places: |
|
// 1) At each time selection transition point (start, end of falloff regions) |
|
// NOTE: If a falloff region has 0 size, we'll add points right outside the transition |
|
// 2) At the resample point (we're going to base this so the resamples always occur at the same spots) |
|
// 3) At any existing sample position |
|
// 4) Any time we switch from clamped to not clamped |
|
// By doing this, we will guarantee no bogus slope changes |
|
|
|
// First, compute times for transition regions |
|
DmeTime_t tTransitionTimes[TS_TIME_COUNT]; |
|
memcpy( &tTransitionTimes, ¶ms.m_nTimes, sizeof(params.m_nTimes) ); |
|
if ( tTransitionTimes[TS_LEFT_FALLOFF] == tTransitionTimes[TS_LEFT_HOLD] ) |
|
{ |
|
tTransitionTimes[TS_LEFT_FALLOFF] -= DMETIME_MINDELTA; |
|
} |
|
if ( tTransitionTimes[TS_RIGHT_FALLOFF] == tTransitionTimes[TS_RIGHT_HOLD] ) |
|
{ |
|
tTransitionTimes[TS_RIGHT_FALLOFF] += DMETIME_MINDELTA; |
|
} |
|
|
|
DmeTime_t tStartTime = params.m_nTimes[ TS_LEFT_FALLOFF ]; |
|
|
|
// Next, compute the first resample time for each region |
|
DmeTime_t tResampleStartTime[TS_TIME_COUNT]; |
|
tResampleStartTime[TS_LEFT_FALLOFF] = DMETIME_MAXTIME; |
|
tResampleStartTime[TS_LEFT_HOLD] = ComputeResampleStartTime( params, 0 ); |
|
tResampleStartTime[TS_RIGHT_HOLD] = DMETIME_MAXTIME; |
|
tResampleStartTime[TS_RIGHT_FALLOFF] = ComputeResampleStartTime( params, 1 ); |
|
|
|
// Finally, figure out which layer we're reading from, |
|
// where the next key is, and when we must stop reading from it |
|
int nReadLayer = FindLayerForTimeSkippingTopmost( tStartTime ); |
|
CDmeTypedLogLayer< T > *pReadLayer = GetLayer( nReadLayer ); |
|
int nLayerSampleIndex = pReadLayer->FindKey( tStartTime ) + 1; |
|
DmeTime_t tLayerEndTime = pReadLayer->GetEndTime(); |
|
// NOTE: This can happen after reading off the end of layer 0 |
|
if ( tLayerEndTime <= tStartTime ) |
|
{ |
|
tLayerEndTime = DMETIME_MAXTIME; |
|
} |
|
DmeTime_t tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); |
|
if ( tNextSampleTime > tLayerEndTime ) |
|
{ |
|
tNextSampleTime = tLayerEndTime; |
|
} |
|
|
|
// Now keep going until we've hit the end point |
|
// NOTE: We use tTransitionTimes, *not* params.m_nTimes, so that we can get a single |
|
// sample before zero-width left falloff regions |
|
DmeTime_t tCurrent = tTransitionTimes[TS_LEFT_FALLOFF]; |
|
int nNextTransition = TS_LEFT_HOLD; |
|
DmeTime_t tResampleTime = tResampleStartTime[nNextTransition]; |
|
|
|
const T* pInterpTarget = bPerformInterpolation ? &interpTarget : NULL; |
|
|
|
if ( bSpew ) |
|
{ |
|
Msg( "Stamp key at head resample: %s\n", GetName() ); |
|
} |
|
|
|
LogClampHelper_t<T> clampHelper; |
|
while( nNextTransition < TS_TIME_COUNT ) |
|
{ |
|
// Stamp the key at the current time |
|
if ( !bSkipToHead || ( tCurrent >= tHeadPosition ) ) |
|
{ |
|
blend[nNextTransition-1].StampKey( pWriteLayer, tCurrent, pReadLayer, params.m_flIntensity, clampHelper, bSpew, pInterpTarget ); |
|
} |
|
|
|
// Update the read layer sample |
|
if ( tCurrent == tNextSampleTime ) |
|
{ |
|
++nLayerSampleIndex; |
|
tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); |
|
} |
|
|
|
// Update the read layer |
|
if ( tCurrent == tLayerEndTime ) |
|
{ |
|
nReadLayer = FindLayerForTimeSkippingTopmost( tCurrent + DMETIME_MINDELTA ); |
|
pReadLayer = GetLayer( nReadLayer ); |
|
nLayerSampleIndex = pReadLayer->FindKey( tCurrent ) + 1; |
|
tLayerEndTime = pReadLayer->GetEndTime(); |
|
|
|
// NOTE: This can happen after reading off the end of layer 0 |
|
if ( tLayerEndTime <= tCurrent ) |
|
{ |
|
tLayerEndTime = DMETIME_MAXTIME; |
|
} |
|
|
|
tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); |
|
if ( tNextSampleTime > tLayerEndTime ) |
|
{ |
|
tNextSampleTime = tLayerEndTime; |
|
} |
|
} |
|
|
|
// Update the transition time |
|
if ( tCurrent == tTransitionTimes[nNextTransition] ) |
|
{ |
|
// NOTE: This is necessary because each blend region has different 'deltas' |
|
// to avoid overdriving in the falloff regions. Therefore, the 'previous value' |
|
// used in the clamping operation will be different |
|
if ( nNextTransition < ARRAYSIZE(blend) ) |
|
{ |
|
blend[nNextTransition].UpdateClampHelper( tCurrent, pReadLayer, params.m_flIntensity, clampHelper, pInterpTarget ); |
|
} |
|
|
|
// Also need to update the 'previous' value stored in the |
|
++nNextTransition; |
|
if ( nNextTransition >= ARRAYSIZE(tResampleStartTime) ) |
|
break; |
|
|
|
// Update the first resample time |
|
tResampleTime = tResampleStartTime[nNextTransition]; |
|
|
|
if ( bSpew ) |
|
{ |
|
Msg( " Entering region %d\n", nNextTransition-1 ); |
|
} |
|
} |
|
|
|
// Update the resample time |
|
if ( tCurrent == tResampleTime ) |
|
{ |
|
tResampleTime += params.m_nResampleInterval; |
|
} |
|
|
|
// Now that the key is stamped, update current time. |
|
tCurrent = tTransitionTimes[nNextTransition]; |
|
if ( tResampleTime < tCurrent ) |
|
{ |
|
tCurrent = tResampleTime; |
|
} |
|
if ( tNextSampleTime < tCurrent ) |
|
{ |
|
tCurrent = tNextSampleTime; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// In this case, we actually stamp a key right at the head position unlike the above method |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::_StampKeyFilteredByTimeSelection( CDmeTypedLogLayer< T > *pWriteLayer, DmeTime_t t, const DmeLog_TimeSelection_t ¶ms, const T& value, bool bForce ) |
|
{ |
|
// Found a key which needs to be modulated upward |
|
float flFraction = params.GetAmountForTime( t ) * params.m_flIntensity; |
|
if ( flFraction <= 0.0f && !bForce ) |
|
return; |
|
|
|
// When dragging preset fader, eveything get's blended in by the amount of the preset being applied |
|
bool bUsePresetRules = ( RECORD_PRESET == params.GetRecordingMode() ); |
|
|
|
// FIXME: Preset value should never be NULL. We need to grab it from the attribute |
|
const T& interpTarget = ( bUsePresetRules && params.m_pPresetValue ) ? params.m_pPresetValue->GetValue<T>() : value; |
|
T oldVal = GetValueSkippingTopmostLayer( t ); |
|
T newVal = Interpolate( flFraction, oldVal, interpTarget ); |
|
T writeVal = ClampValue( newVal ); |
|
pWriteLayer->InsertKey( t, writeVal, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// In this case, we actually stamp a key right at the head position unlike the above method |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::_StampKeyAtHeadFilteredByTimeSelection( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t ¶ms, const T& value ) |
|
{ |
|
// Should be in "layer recording" mode!!! |
|
Assert( GetNumLayers() >= 2 ); |
|
int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( nBestLayer < 1 ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); |
|
Assert( pWriteLayer ); |
|
if ( !pWriteLayer ) |
|
return; |
|
|
|
// NOTE: This little trickery is necessary to generate samples right outside the |
|
// transition region in the case of zero length falloff regions |
|
DmeLog_TimeSelection_t tempParams = params; |
|
if ( tempParams.m_nTimes[TS_LEFT_FALLOFF] == tempParams.m_nTimes[TS_LEFT_HOLD] ) |
|
{ |
|
tempParams.m_nTimes[TS_LEFT_FALLOFF] -= DMETIME_MINDELTA; |
|
} |
|
if ( tempParams.m_nTimes[TS_RIGHT_FALLOFF] == tempParams.m_nTimes[TS_RIGHT_HOLD] ) |
|
{ |
|
tempParams.m_nTimes[TS_RIGHT_FALLOFF] += DMETIME_MINDELTA; |
|
} |
|
|
|
int nPrevRegion = tempParams.ComputeRegionForTime( tPreviousHeadPosition ); |
|
int nCurrRegion = tempParams.ComputeRegionForTime( tHeadPosition ); |
|
|
|
// Test for backward performance! |
|
if ( nCurrRegion < nPrevRegion ) |
|
{ |
|
V_swap( nCurrRegion, nPrevRegion ); |
|
} |
|
|
|
// Insert samples at each transition point we skipped over |
|
for ( int i = nPrevRegion; i < nCurrRegion; ++i ) |
|
{ |
|
_StampKeyFilteredByTimeSelection( pWriteLayer, tempParams.m_nTimes[i], params, value, true ); |
|
} |
|
|
|
_StampKeyFilteredByTimeSelection( pWriteLayer, tHeadPosition, params, value ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::RemoveKeys( DmeTime_t starttime ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->RemoveKeys( starttime ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::RemoveKey( int nKeyIndex, int nNumKeysToRemove /*= 1*/ ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->RemoveKey( nKeyIndex, nNumKeysToRemove ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::ClearKeys() |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->ClearKeys(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a specific key's value |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
DmeTime_t CDmeTypedLog< T >::GetKeyTime( int nKeyIndex ) const |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return DmeTime_t::MinTime(); |
|
return GetLayer( bestLayer )->GetKeyTime( nKeyIndex ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetKeyTime( int nKeyIndex, DmeTime_t keyTime ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
return GetLayer( bestLayer )->SetKeyTime( nKeyIndex, keyTime ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the index of a particular key |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
int CDmeTypedLog< T >::FindKeyWithinTolerance( DmeTime_t nTime, DmeTime_t nTolerance ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return -1; |
|
|
|
return GetLayer( bestLayer )->FindKeyWithinTolerance( nTime, nTolerance ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// tests whether two values differ by more than the threshold |
|
//----------------------------------------------------------------------------- |
|
template<> |
|
bool CDmeTypedLog< Vector >::ValuesDiffer( const Vector& a, const Vector& b ) const |
|
{ |
|
return a.DistToSqr( b ) > m_threshold * m_threshold; |
|
} |
|
|
|
template<> |
|
bool CDmeTypedLog< QAngle >::ValuesDiffer( const QAngle& a, const QAngle& b ) const |
|
{ |
|
return ( a - b ).LengthSqr() > m_threshold * m_threshold; |
|
} |
|
|
|
template<> |
|
bool CDmeTypedLog< Quaternion >::ValuesDiffer( const Quaternion& a, const Quaternion& b ) const |
|
{ |
|
return QuaternionAngleDiff( a, b ) > m_threshold; |
|
} |
|
|
|
template<> |
|
bool CDmeTypedLog< float >::ValuesDiffer( const float& a, const float& b ) const |
|
{ |
|
return fabs( a - b ) > m_threshold; |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLog< T >::ValuesDiffer( const T& a, const T& b ) const |
|
{ |
|
return a != b; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets a key, removes all keys after this time |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::SetKey( DmeTime_t time, const T& value, int curveType /*=CURVE_DEFAULT*/) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer < 0 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->SetKey( time, value, curveType ); |
|
} |
|
|
|
template< class T > |
|
CDmeTypedLogLayer< T > *CDmeTypedLog< T >::GetLayer( int index ) |
|
{ |
|
if ( index < 0 ) |
|
return NULL; |
|
|
|
return static_cast< CDmeTypedLogLayer< T > * >( m_Layers[ index ] ); |
|
} |
|
|
|
template< class T > |
|
const CDmeTypedLogLayer< T > *CDmeTypedLog< T >::GetLayer( int index ) const |
|
{ |
|
if ( index < 0 ) |
|
return NULL; |
|
|
|
return static_cast< CDmeTypedLogLayer< T > * >( m_Layers[ index ] ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a key within tolerance, or adds one |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
int CDmeTypedLog< T >::FindOrAddKey( DmeTime_t nTime, DmeTime_t nTolerance, const T& value, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
return -1; |
|
|
|
return GetLayer( bestLayer )->FindOrAddKey( nTime, nTolerance, value, curveType ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This inserts a key. Unlike SetKey, this will *not* delete keys after the specified time |
|
//----------------------------------------------------------------------------- |
|
template < class T > |
|
int CDmeTypedLog< T >::InsertKey( DmeTime_t nTime, const T& value, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
return -1; |
|
|
|
return GetLayer( bestLayer )->InsertKey( nTime, value, curveType ); |
|
} |
|
|
|
template < class T > |
|
int CDmeTypedLog< T >::InsertKeyAtTime( DmeTime_t nTime, int curveType /*=CURVE_DEFAULT*/ ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
return -1; |
|
|
|
return GetLayer( bestLayer )->InsertKeyAtTime( nTime, curveType ); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLog< T >::GetValue( DmeTime_t time ) const |
|
{ |
|
int bestLayer = FindLayerForTime( time ); |
|
if ( bestLayer < 0 ) |
|
{ |
|
static T s_value; |
|
CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return s_value; |
|
} |
|
|
|
return GetLayer( bestLayer )->GetValue( time ); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLog< T >::GetValueSkippingTopmostLayer( DmeTime_t time ) const |
|
{ |
|
int nLayer = FindLayerForTimeSkippingTopmost( time ); |
|
if ( nLayer < 0 ) |
|
return GetValue( time ); |
|
return GetLayer( nLayer )->GetValue( time ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetKey( DmeTime_t time, const CDmAttribute *pAttr, uint index, int curveType /*= CURVE_DEFAULT*/ ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
return; |
|
|
|
GetLayer( bestLayer )->SetKey( time, pAttr, index, curveType ); |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLog< T >::SetDuplicateKeyAtTime( DmeTime_t time ) |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
return false; |
|
|
|
return GetLayer( bestLayer )->SetDuplicateKeyAtTime( time ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a specific key's value |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
const T& CDmeTypedLog< T >::GetKeyValue( int nKeyIndex ) const |
|
{ |
|
int bestLayer = GetTopmostLayer(); |
|
if ( bestLayer == -1 ) |
|
{ |
|
static T s_value; |
|
CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
return s_value; |
|
} |
|
|
|
return GetLayer( bestLayer )->GetKeyValue( nKeyIndex ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::GetValue( DmeTime_t time, CDmAttribute *pAttr, uint index ) const |
|
{ |
|
int bestLayer = FindLayerForTime( time ); |
|
if ( bestLayer < 0 ) |
|
{ |
|
T value; |
|
CDmAttributeInfo< T >::SetDefaultValue( value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time |
|
pAttr->SetValue( CDmAttributeInfo< T >::AttributeType(), &value ); |
|
} |
|
|
|
return GetLayer( bestLayer )->GetValue( time, pAttr, index ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::GetValueSkippingTopmostLayer( DmeTime_t time, CDmAttribute *pAttr, uint index = 0 ) const |
|
{ |
|
CUtlVector< int > layers; |
|
FindLayersForTime( time, layers ); |
|
int layerCount = layers.Count(); |
|
if ( layerCount <= 1 ) |
|
{ |
|
return GetValue( time, pAttr, index ); |
|
} |
|
|
|
int topMostLayer = GetTopmostLayer(); |
|
int useLayer = layers[ layerCount - 1 ]; |
|
if ( topMostLayer == useLayer ) |
|
{ |
|
useLayer = layers[ layerCount - 2 ]; |
|
} |
|
Assert( useLayer >= 0 ); |
|
return GetLayer( useLayer )->GetValue( time, pAttr, index ); |
|
} |
|
|
|
template< class T > |
|
float CDmeTypedLog< T >::GetComponent( DmeTime_t time, int componentIndex ) const |
|
{ |
|
return ::GetComponent( GetValue( time ), componentIndex ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// resampling and filtering |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
void CDmeTypedLog< T >::Resample( DmeFramerate_t samplerate ) |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
GetLayer( i )->Resample( samplerate ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::Filter( int nSampleRadius ) |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
GetLayer( i )->Filter( nSampleRadius ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::Filter2( DmeTime_t sampleRadius ) |
|
{ |
|
int c = m_Layers.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
GetLayer( i )->Filter2( sampleRadius ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::OnAttributeArrayElementAdded( CDmAttribute *pAttribute, int nFirstElem, int nLastElem ) |
|
{ |
|
BaseClass::OnAttributeArrayElementAdded( pAttribute, nFirstElem, nLastElem ); |
|
if ( pAttribute == m_Layers.GetAttribute() ) |
|
{ |
|
for ( int i = nFirstElem; i <= nLastElem; ++i ) |
|
{ |
|
m_Layers[i]->SetOwnerLog( this ); |
|
} |
|
return; |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetUseEdgeInfo( bool state ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->SetUseEdgeInfo( state ); |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLog< T >::IsUsingEdgeInfo() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return GetTypedCurveInfo()->IsUsingEdgeInfo(); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetEdgeInfo( int edge, bool active, const T& val, int curveType ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->SetEdgeInfo( edge, active, val, curveType ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetDefaultEdgeZeroValue( const T& val ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->SetDefaultEdgeZeroValue( val ); |
|
} |
|
|
|
template< class T > |
|
const T& CDmeTypedLog< T >::GetDefaultEdgeZeroValue() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return GetTypedCurveInfo()->GetDefaultEdgeZeroValue(); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::SetRightEdgeTime( DmeTime_t time ) |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->SetRightEdgeTime( time ); |
|
} |
|
|
|
template< class T > |
|
DmeTime_t CDmeTypedLog< T >::GetRightEdgeTime() const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return GetTypedCurveInfo()->GetRightEdgeTime(); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->GetEdgeInfo( edge, active, val, curveType ); |
|
} |
|
|
|
template< class T > |
|
int CDmeTypedLog< T >::GetEdgeCurveType( int edge ) const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return GetTypedCurveInfo()->GetEdgeCurveType( edge ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::GetZeroValue( int side, T& val ) const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->GetZeroValue( side, val ); |
|
} |
|
|
|
template< class T > |
|
bool CDmeTypedLog< T >::IsEdgeActive( int edge ) const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
return GetTypedCurveInfo()->IsEdgeActive( edge ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::GetEdgeValue( int edge, T& val ) const |
|
{ |
|
Assert( IsUsingCurveTypes() ); |
|
GetTypedCurveInfo()->GetEdgeValue( edge, val ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::BlendTimesUsingTimeSelection( const CDmeLogLayer *firstLayer, const CDmeLogLayer *secondLayer, CDmeLogLayer *outputLayer, const DmeLog_TimeSelection_t ¶ms, DmeTime_t tStartOffset ) |
|
{ |
|
const CDmeTypedLogLayer< T > *topLayer = static_cast< const CDmeTypedLogLayer< T > * >( secondLayer ); |
|
if ( !topLayer ) |
|
return; |
|
|
|
const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( firstLayer ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( outputLayer ); |
|
if ( !newLayer ) |
|
return; |
|
|
|
Assert( topLayer->GetKeyCount() == baseLayer->GetKeyCount() ); |
|
|
|
int i; |
|
// Resample everything in the base layer first |
|
int kc = baseLayer->GetKeyCount(); |
|
|
|
newLayer->ClearKeys(); |
|
|
|
for ( i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t baseKeyTime = baseLayer->GetKeyTime( i ); |
|
DmeTime_t checkTime = baseKeyTime + tStartOffset; |
|
if ( checkTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
if ( checkTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
break; |
|
float frac = params.GetAmountForTime( checkTime ); |
|
float frac2 = params.m_flIntensity; |
|
float flInterp = frac2 * frac; |
|
|
|
DmeTime_t targetKeyTime = topLayer->GetKeyTime( i ); |
|
|
|
DmeTime_t blendedKeyTime = Lerp( flInterp, baseKeyTime, targetKeyTime ) + tStartOffset; |
|
|
|
T baseVal = baseLayer->GetKeyValue( i ); |
|
|
|
newLayer->InsertKey( blendedKeyTime, baseVal ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::BlendLayersUsingTimeSelection( const CDmeLogLayer *firstLayer, const CDmeLogLayer *secondLayer, CDmeLogLayer *outputLayer, const DmeLog_TimeSelection_t ¶ms, bool bUseBaseLayerSamples, DmeTime_t tStartOffset ) |
|
{ |
|
const CDmeTypedLogLayer< T > *topLayer = static_cast< const CDmeTypedLogLayer< T > * >( secondLayer ); |
|
if ( !topLayer ) |
|
return; |
|
|
|
const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( firstLayer ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( outputLayer ); |
|
if ( !newLayer ) |
|
return; |
|
|
|
int i; |
|
// Resample everything in the base layer first |
|
int kc = baseLayer->GetKeyCount(); |
|
if ( bUseBaseLayerSamples ) |
|
{ |
|
for ( i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
if ( keyTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
if ( keyTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
break; |
|
|
|
float frac = params.GetAmountForTime( keyTime ); |
|
float frac2 = params.m_flIntensity; |
|
|
|
T baseVal = baseLayer->GetKeyValue( i ); |
|
T newVal = topLayer->GetValue( keyTime ); |
|
T blended = Interpolate( frac2 * frac, baseVal, newVal ); |
|
|
|
newLayer->SetKey( keyTime + tStartOffset, blended ); |
|
} |
|
} |
|
|
|
kc = topLayer->GetKeyCount(); |
|
for ( i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = topLayer->GetKeyTime( i ); |
|
DmeTime_t finalKeyTime = keyTime + tStartOffset; |
|
if ( finalKeyTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
if ( finalKeyTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
break; |
|
float frac = params.GetAmountForTime( finalKeyTime ); |
|
float frac2 = params.m_flIntensity; |
|
|
|
T baseVal = baseLayer->GetValue( keyTime ); |
|
T newVal = topLayer->GetKeyValue( i ); |
|
T blended = Interpolate( frac2 *frac, baseVal, newVal ); |
|
|
|
newLayer->InsertKey( finalKeyTime, blended ); |
|
} |
|
|
|
if ( g_pDmElementFramework->GetPhase() == PH_EDIT ) |
|
{ |
|
newLayer->RemoveRedundantKeys( params.m_flThreshold ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::BlendLayersUsingTimeSelection( const DmeLog_TimeSelection_t ¶ms ) |
|
{ |
|
Assert( GetNumLayers() >= 2 ); |
|
int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( bestLayer <= 0 ) |
|
return; |
|
|
|
Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); |
|
if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *topLayer = GetLayer( bestLayer ); |
|
Assert( topLayer ); |
|
if ( !topLayer ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *baseLayer = GetLayer( 0 ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer< T >( this ) ); |
|
if ( !newLayer ) |
|
return; |
|
|
|
BlendLayersUsingTimeSelection( baseLayer, topLayer, newLayer, params, true, DMETIME_ZERO ); |
|
|
|
// Store it back into the new topmost layer |
|
topLayer->CopyLayer( newLayer ); |
|
|
|
g_pDataModel->DestroyElement( newLayer->GetHandle() ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::RevealUsingTimeSelection( const DmeLog_TimeSelection_t ¶ms, CDmeLogLayer *savedLayer ) |
|
{ |
|
CDmeTypedLogLayer< T > *saved = static_cast< CDmeTypedLogLayer< T > * >( savedLayer ); |
|
if ( !saved ) |
|
return; |
|
|
|
Assert( GetNumLayers() >= 2 ); |
|
int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( bestLayer <= 0 ) |
|
return; |
|
|
|
Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); |
|
if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( GetLayer( bestLayer ) ); |
|
Assert( writeLayer ); |
|
if ( !writeLayer ) |
|
return; |
|
|
|
CDmeLogLayer *baseLayer = GetLayer( 0 ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
DmeTime_t resample = 0.5f * params.m_nResampleInterval; |
|
|
|
// Do a second pass where we bis the keys in the falloff area back toward the original value |
|
for ( int t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) |
|
{ |
|
DmeTime_t curtime = DmeTime_t( t ); |
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; |
|
|
|
float frac = params.GetAmountForTime( curtime ); |
|
frac *= params.m_flIntensity; |
|
|
|
if ( frac <= 0.0f ) |
|
continue; |
|
|
|
// Get current value in layer |
|
T curValue = GetValueSkippingTopmostLayer( curtime ); |
|
T revealValue = saved->GetValue( curtime ); |
|
|
|
T newValue = Interpolate( frac, curValue, revealValue ); |
|
|
|
// Overwrite key |
|
writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
|
|
if ( g_pDmElementFramework->GetPhase() == PH_EDIT ) |
|
{ |
|
writeLayer->RemoveRedundantKeys( params.m_flThreshold ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void RandomValue( const T& average, const T& oldValue, T& newValue ) |
|
{ |
|
newValue = oldValue; |
|
} |
|
|
|
template<> void RandomValue( const Vector& average, const Vector& oldValue, Vector& newValue ) |
|
{ |
|
newValue = oldValue; |
|
|
|
for ( int i = 0; i < 3; ++i ) |
|
{ |
|
newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); |
|
} |
|
} |
|
|
|
template<> void RandomValue( const Quaternion& average, const Quaternion& oldValue, Quaternion& newValue ) |
|
{ |
|
newValue = oldValue; |
|
|
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); |
|
} |
|
} |
|
|
|
template<> void RandomValue( const Vector4D& average, const Vector4D& oldValue, Vector4D& newValue ) |
|
{ |
|
newValue = oldValue; |
|
|
|
for ( int i = 0; i < 4; ++i ) |
|
{ |
|
newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); |
|
} |
|
} |
|
|
|
template<> void RandomValue( const Vector2D& average, const Vector2D& oldValue, Vector2D& newValue ) |
|
{ |
|
newValue = oldValue; |
|
|
|
for ( int i = 0; i < 2; ++i ) |
|
{ |
|
newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); |
|
} |
|
} |
|
|
|
template<> void RandomValue( const float& average, const float& oldValue, float& newValue ) |
|
{ |
|
newValue = oldValue + RandomFloat( -average, average ); |
|
} |
|
|
|
template<> void RandomValue( const int& average, const int& oldValue, int& newValue ) |
|
{ |
|
newValue = oldValue + RandomInt( -average, average ); |
|
} |
|
|
|
// Builds a layer with samples matching the times in reference layer, from the data in pDataLayer, putting the resulting keys into pOutputLayer |
|
template< class T > |
|
void CDmeTypedLog< T >::BuildCorrespondingLayer( const CDmeLogLayer *pReferenceLayer, const CDmeLogLayer *pDataLayer, CDmeLogLayer *pOutputLayer ) |
|
{ |
|
const CDmeTypedLogLayer< T > *ref = static_cast< const CDmeTypedLogLayer< T > * >( pReferenceLayer ); |
|
const CDmeTypedLogLayer< T > *data = static_cast< const CDmeTypedLogLayer< T > * >( pDataLayer ); |
|
CDmeTypedLogLayer< T > *out = static_cast< CDmeTypedLogLayer< T > * >( pOutputLayer ); |
|
|
|
if ( !ref || !data || !out ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
bool usecurvetypes = ref->IsUsingCurveTypes(); |
|
|
|
out->ClearKeys(); |
|
int kc = ref->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = ref->GetKeyTime( i ); |
|
T value = data->GetValue( keyTime ); |
|
|
|
out->InsertKey( keyTime, value, usecurvetypes ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::StaggerUsingTimeSelection( const DmeLog_TimeSelection_t& params, DmeTime_t tStaggerAmount, const CDmeLogLayer *pBaseLayer, CDmeLogLayer *pWriteLayer ) |
|
{ |
|
CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( pWriteLayer ); |
|
Assert( writeLayer ); |
|
if ( !writeLayer ) |
|
return; |
|
|
|
const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( pBaseLayer ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
writeLayer->ClearKeys(); |
|
|
|
DmeLog_TimeSelection_t newParams; |
|
newParams = params; |
|
|
|
// Move the hold area by the stagger amount |
|
float flScaleFactor[ 2 ] = { 1.0f, 1.0f }; |
|
|
|
newParams.m_nTimes[ TS_LEFT_HOLD ] += tStaggerAmount; |
|
newParams.m_nTimes[ TS_RIGHT_HOLD ] += tStaggerAmount; |
|
|
|
for ( int i = 0; i < 2 ; ++i ) |
|
{ |
|
DmeTime_t dt = params.m_nTimes[ 2 * i + 1 ] - params.m_nTimes[ 2 * i ]; |
|
if ( dt > DMETIME_ZERO ) |
|
{ |
|
DmeTime_t newDt = newParams.m_nTimes[ 2 * i + 1 ] - newParams.m_nTimes[ 2 * i ]; |
|
flScaleFactor[ i ] = newDt / dt; |
|
} |
|
} |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t curtime = baseLayer->GetKeyTime( i ); |
|
T oldValue = baseLayer->GetKeyValue( i ); |
|
|
|
// Classify time |
|
if ( curtime <= params.m_nTimes[ TS_LEFT_HOLD ] ) |
|
{ |
|
curtime = curtime * flScaleFactor[ 0 ]; |
|
} |
|
else if ( curtime >= params.m_nTimes[ TS_RIGHT_HOLD ] ) |
|
{ |
|
curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ] - ( params.m_nTimes[ TS_RIGHT_FALLOFF ] - curtime ) * flScaleFactor[ 1 ]; |
|
} |
|
else |
|
{ |
|
curtime += tStaggerAmount; |
|
} |
|
|
|
writeLayer->InsertKey( curtime, oldValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::FilterUsingTimeSelection( IUniformRandomStream *random, const DmeLog_TimeSelection_t& params, int filterType, bool bResample, bool bApplyFalloff ) |
|
{ |
|
Assert( GetNumLayers() >= 2 ); |
|
int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) |
|
if ( bestLayer <= 0 ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *writeLayer = GetLayer( bestLayer ); |
|
Assert( writeLayer ); |
|
if ( !writeLayer ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *baseLayer = GetLayer( 0 ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
FilterUsingTimeSelection( random, 1.0f, params, filterType, bResample, bApplyFalloff, baseLayer, writeLayer ); |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::FilterUsingTimeSelection( IUniformRandomStream *random, float flScale, const DmeLog_TimeSelection_t& params, int filterType, bool bResample, bool bApplyFalloff, const CDmeLogLayer *pBaseLayer, CDmeLogLayer *pWriteLayer ) |
|
{ |
|
Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); |
|
if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( pWriteLayer ); |
|
Assert( writeLayer ); |
|
if ( !writeLayer ) |
|
return; |
|
|
|
const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( pBaseLayer ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
writeLayer->ClearKeys(); |
|
|
|
DmeTime_t resample = 0.5f * params.m_nResampleInterval; |
|
|
|
switch ( filterType ) |
|
{ |
|
default: |
|
case FILTER_SMOOTH: |
|
{ |
|
int t; |
|
if ( bResample ) |
|
{ |
|
for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) |
|
{ |
|
DmeTime_t curtime = DmeTime_t( t ); |
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; |
|
|
|
T curValue = baseLayer->GetValue( curtime ); |
|
writeLayer->SetKey( curtime, curValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
else |
|
{ |
|
// Do a second pass where we bias the keys in the falloff area back toward the original value |
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t curtime = baseLayer->GetKeyTime( i ); |
|
if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
|
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
continue; |
|
|
|
T oldValue = baseLayer->GetKeyValue( i ); |
|
writeLayer->InsertKey( curtime, oldValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
|
|
writeLayer->Filter2( params.m_nResampleInterval * 0.95f * flScale ); |
|
|
|
if ( bApplyFalloff ) |
|
{ |
|
if ( bResample ) |
|
{ |
|
// Do a second pass where we bias the keys in the falloff area back toward the original value |
|
for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) |
|
{ |
|
DmeTime_t curtime = DmeTime_t( t ); |
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; |
|
|
|
T oldValue = baseLayer->GetValue( curtime ); |
|
|
|
if ( curtime >= params.m_nTimes[ TS_LEFT_HOLD ] && curtime <= params.m_nTimes[ TS_RIGHT_HOLD ] ) |
|
continue; |
|
|
|
// Modulate these keys back down toward the original value |
|
T newValue = writeLayer->GetValue( curtime ); |
|
|
|
float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; |
|
|
|
newValue = Interpolate( frac, oldValue, newValue ); |
|
|
|
// Overwrite key |
|
writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
else |
|
{ |
|
// Do a second pass where we bias the keys in the falloff area back toward the original value |
|
int kc = writeLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t curtime = writeLayer->GetKeyTime( i ); |
|
if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
|
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
continue; |
|
|
|
if ( curtime >= params.m_nTimes[ TS_LEFT_HOLD ] && curtime <= params.m_nTimes[ TS_RIGHT_HOLD ] ) |
|
continue; |
|
|
|
T oldValue = baseLayer->GetValue( curtime ); |
|
|
|
// Modulate these keys back down toward the original value |
|
T newValue = writeLayer->GetValue( curtime ); |
|
|
|
float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; |
|
|
|
newValue = Interpolate( frac, oldValue, newValue ); |
|
|
|
// Overwrite key |
|
writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
} |
|
|
|
if ( bResample ) |
|
{ |
|
writeLayer->RemoveRedundantKeys( params.m_flThreshold ); |
|
} |
|
} |
|
break; |
|
case FILTER_JITTER: |
|
{ |
|
// Compute average value in entire log |
|
|
|
T average = Average( baseLayer->m_values.Base(), baseLayer->m_values.Count() ); |
|
average = ScaleValue( average, 0.05f * flScale ); |
|
|
|
if ( bResample ) |
|
{ |
|
int t; |
|
for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) |
|
{ |
|
DmeTime_t curtime = DmeTime_t( t ); |
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; |
|
|
|
float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; |
|
|
|
T oldValue = baseLayer->GetValue( curtime ); |
|
|
|
T newValue; |
|
RandomValue( average, oldValue, newValue ); |
|
|
|
newValue = Interpolate( frac, oldValue, newValue ); |
|
|
|
writeLayer->SetKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
|
|
} |
|
else |
|
{ |
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t curtime = baseLayer->GetKeyTime( i ); |
|
if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
|
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
continue; |
|
|
|
float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; |
|
|
|
T oldValue = baseLayer->GetValue( curtime ); |
|
|
|
T newValue; |
|
RandomValue( average, oldValue, newValue ); |
|
|
|
newValue = Interpolate( frac, oldValue, newValue ); |
|
|
|
writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
} |
|
break; |
|
case FILTER_SHARPEN: |
|
case FILTER_SOFTEN: |
|
{ |
|
writeLayer->ClearKeys(); |
|
|
|
bool bSharpen = filterType == FILTER_SHARPEN; |
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t curtime = baseLayer->GetKeyTime( i ); |
|
if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) |
|
continue; |
|
|
|
if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) |
|
continue; |
|
|
|
float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; |
|
|
|
T oldValue = baseLayer->GetValue( curtime ); |
|
|
|
T newValue = oldValue; |
|
if ( frac != 1.0f ) |
|
{ |
|
T crossingValue[ 2 ] = { oldValue, oldValue }; |
|
if ( curtime <= params.m_nTimes[ TS_LEFT_HOLD ] ) |
|
{ |
|
// Get the value at the crossing point (either green edge for sharpen, or left edge for soften...) |
|
crossingValue[ 0 ] = baseLayer->GetValue( params.m_nTimes[ TS_LEFT_FALLOFF ] ); |
|
crossingValue[ 1 ] = baseLayer->GetValue( params.m_nTimes[ TS_LEFT_HOLD ] ); |
|
} |
|
else if ( curtime >= params.m_nTimes[ TS_RIGHT_HOLD ] ) |
|
{ |
|
crossingValue[ 0 ] = baseLayer->GetValue( params.m_nTimes[ TS_RIGHT_FALLOFF ] ); |
|
crossingValue[ 1 ] = baseLayer->GetValue( params.m_nTimes[ TS_RIGHT_HOLD ] ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
} |
|
|
|
T dynamicRange = Subtract( crossingValue[ 1 ], crossingValue[ 0 ] ); |
|
|
|
int iType = bSharpen ? INTERPOLATE_EASE_IN : INTERPOLATE_EASE_OUT; |
|
|
|
Vector points[ 4 ]; |
|
points[ 0 ].Init(); |
|
points[ 1 ].Init( 0.0, 0.0, 0.0f ); |
|
points[ 2 ].Init( 1.0f, 1.0f, 0.0f ); |
|
points[ 3 ].Init(); |
|
|
|
Vector out; |
|
|
|
Interpolator_CurveInterpolate |
|
( |
|
iType, |
|
points[ 0 ], // unused |
|
points[ 1 ], |
|
points[ 2 ], |
|
points[ 3 ], // unused |
|
frac, |
|
out |
|
); |
|
|
|
float flBias = clamp( out.y, 0.0f, 1.0f ); |
|
float dFrac = flScale * ( frac - flBias ); |
|
|
|
newValue = Add( oldValue, ScaleValue( dynamicRange, dFrac ) ); |
|
} |
|
|
|
writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
enum PasteState_t |
|
{ |
|
PASTE_STATE_BEFORE = -1, |
|
|
|
PASTE_STATE_RAMP_IN = 0, |
|
PASTE_STATE_HOLD, |
|
PASTE_STATE_RAMP_OUT, |
|
|
|
PASTE_STATE_COUNT, |
|
PASTE_STATE_AFTER = PASTE_STATE_COUNT, |
|
}; |
|
|
|
template<class T > |
|
static void CountClipboardSamples( int *pCount, CDmeTypedLogLayer< T > *pClipboard, const DmeLog_TimeSelection_t ¶ms ) |
|
{ |
|
pCount[0] = pCount[1] = pCount[2] = 0; |
|
|
|
int nKeyCount = pClipboard->GetKeyCount(); |
|
for ( int i = 0; i < nKeyCount; ++i ) |
|
{ |
|
DmeTime_t tKeyTime = pClipboard->GetKeyTime( i ); |
|
int nIndex = params.ComputeRegionForTime( tKeyTime ) - 1; |
|
if ( nIndex < 0 || nIndex > 2 ) |
|
continue; |
|
|
|
// Only count interstitial samples.. don't count ones that land exactly on boundaries |
|
if ( tKeyTime != params.m_nTimes[nIndex] && tKeyTime != params.m_nTimes[nIndex+1] ) |
|
{ |
|
pCount[nIndex]++; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used by PasteAndRescaleSamples to determine if it should skip a transition or not |
|
//----------------------------------------------------------------------------- |
|
static inline bool ShouldSkipTransition( int nTransition, int nZeroField ) |
|
{ |
|
// NOTE: This is pretty tricky. The bits of the 'zero field' are set to true |
|
// for each region whose source + dest region size is exactly 0 seconds. |
|
// Here's the table this logic is reproducing: |
|
// 0,1,2,3 are the time selection m_nTimes, and A,B,C are the regions |
|
// 0 1 2 3 |
|
// | A | B | C | |
|
// |
|
// nZeroField bits |
|
// C B A Skip transitions |
|
// 0 0 0 none |
|
// 0 0 1 2 |
|
// 0 1 0 2 |
|
// 0 1 1 1, 2 |
|
// 1 0 0 1 |
|
// 1 0 1 1, 2 |
|
// 1 1 0 1, 2 |
|
// 1 1 1 1, 2, 3 |
|
switch( nTransition ) |
|
{ |
|
default: case 0: return false; |
|
case 1: return ( (( nZeroField & 0x1 ) != 0 ) || nZeroField >= 5 ); |
|
case 2: return ( nZeroField >= 2 ); |
|
case 3: return ( nZeroField == 7 ); |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::PasteAndRescaleSamples( |
|
const CDmeLogLayer *pBase, |
|
const CDmeLogLayer *pDataLayer, |
|
CDmeLogLayer *pOutputLayer, |
|
const DmeLog_TimeSelection_t& srcParams, |
|
const DmeLog_TimeSelection_t& destParams, |
|
bool bBlendAreaInFalloffRegion ) |
|
{ |
|
Assert( GetNumLayers() >= 2 ); |
|
if ( GetNumLayers() < 2 ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *pClipboard = CastElement< CDmeTypedLogLayer< T > >( const_cast< CDmeLogLayer * >( pDataLayer ) ); |
|
|
|
// Could have passed in layer with wrong attribute type?! |
|
Assert( pClipboard ); |
|
if ( !pClipboard ) |
|
return; |
|
|
|
CDmeTypedLogLayer< T > *pBaseLayer = CastElement< CDmeTypedLogLayer< T > >( const_cast< CDmeLogLayer * >( pBase ) ); |
|
CDmeTypedLogLayer< T > *pWriteLayer = CastElement< CDmeTypedLogLayer< T > >( pOutputLayer ); |
|
Assert( pBaseLayer ); |
|
Assert( pWriteLayer ); |
|
|
|
// NOTE: Array index 0 is src (pClipboard), index 1 is dest (pWriteLayer) |
|
DmeTime_t tStartTime[ PASTE_STATE_COUNT+1 ][ 2 ] = |
|
{ |
|
{ DmeTime_t( srcParams.m_nTimes[ 0 ] ), DmeTime_t( destParams.m_nTimes[ 0 ] ) }, |
|
{ DmeTime_t( srcParams.m_nTimes[ 1 ] ), DmeTime_t( destParams.m_nTimes[ 1 ] ) }, |
|
{ DmeTime_t( srcParams.m_nTimes[ 2 ] ), DmeTime_t( destParams.m_nTimes[ 2 ] ) }, |
|
{ DmeTime_t( srcParams.m_nTimes[ 3 ] ), DmeTime_t( destParams.m_nTimes[ 3 ] ) }, |
|
}; |
|
|
|
// compute rescaling factors |
|
int pDuration[ PASTE_STATE_COUNT ][ 2 ]; |
|
double pScaleFactor[ PASTE_STATE_COUNT ]; |
|
int nZeroField = 0; |
|
for ( int i = 0; i < PASTE_STATE_COUNT; ++i ) |
|
{ |
|
for ( int s = 0; s < 2; ++s ) |
|
{ |
|
pDuration[ i ][ s ] = tStartTime[ i+1 ][ s ].GetTenthsOfMS() - tStartTime[ i ][ s ].GetTenthsOfMS(); |
|
} |
|
|
|
// We're building up a bitfield to find which regions have src + dest durations of 0 |
|
// for use in determining which regions to completely skip processing |
|
if ( pDuration[i][0] == 0 && pDuration[i][1] == 0 ) |
|
{ |
|
nZeroField |= ( 1 << i ); |
|
} |
|
|
|
pScaleFactor[i] = 1.0; |
|
if ( pDuration[ i ][ 0 ] > 0 ) |
|
{ |
|
pScaleFactor[i] = 1.0 / ( double )pDuration[ i ][ 0 ]; |
|
} |
|
} |
|
|
|
// Compute values used to paste into selection state transitions |
|
T pStartValue[ PASTE_STATE_COUNT + 1 ] = |
|
{ |
|
bBlendAreaInFalloffRegion ? pBaseLayer->GetValue( tStartTime[ PASTE_STATE_RAMP_IN ][ 1 ] ) : pClipboard->GetValue( tStartTime[ PASTE_STATE_RAMP_IN ][ 0 ] ), |
|
pClipboard->GetValue( tStartTime[ PASTE_STATE_HOLD ][ 0 ] ), |
|
pClipboard->GetValue( tStartTime[ PASTE_STATE_RAMP_OUT ][ 0 ] ), |
|
bBlendAreaInFalloffRegion ? pBaseLayer->GetValue( tStartTime[ PASTE_STATE_AFTER ][ 1 ] ) : pClipboard->GetValue( tStartTime[ PASTE_STATE_AFTER ][ 0 ] ) |
|
}; |
|
|
|
// Compute state necessary to blend in the ramp in + ramp out regions |
|
// NOTE: These computations are only used if bBlendAreaInFalloffRegion is true |
|
T pBlendBase[ 2 ]; |
|
float pOOBlendLength[ 2 ]; |
|
DmeTime_t pBlendTime[ 2 ]; |
|
for ( int s = 0; s < 2; ++s ) |
|
{ |
|
pBlendTime[ s ] = destParams.m_nTimes[ TS_FALLOFF(s) ]; |
|
pBlendBase[ s ] = pBaseLayer->GetValue( pBlendTime[ s ] ); |
|
T holdValue = pBaseLayer->GetValue( destParams.m_nTimes[ TS_HOLD(s) ] ); |
|
|
|
Vector2D vec; |
|
vec.x = destParams.m_nTimes[ TS_HOLD(s) ].GetSeconds() - pBlendTime[ s ].GetSeconds(); |
|
vec.y = LengthOf( Subtract( holdValue, pBlendBase[ s ] ) ); |
|
pOOBlendLength[ s ] = vec.Length(); |
|
if ( pOOBlendLength[ s ] != 0.0f ) |
|
{ |
|
pOOBlendLength[ s ] = 1.0f / pOOBlendLength[ s ]; |
|
} |
|
} |
|
|
|
// Count the number of samples on the clipboard in the various regions |
|
int pKeyCount[PASTE_STATE_COUNT]; |
|
CountClipboardSamples( pKeyCount, pClipboard, srcParams ); |
|
|
|
// Walk the samples in the clipboard |
|
int nKeyCount = pClipboard->GetKeyCount(); |
|
int nPrevState = PASTE_STATE_BEFORE; |
|
DmeTime_t tLastWrittenTime = DMETIME_MINTIME; |
|
DmeTime_t tMaxKeyTime = DMETIME_MAXTIME; |
|
bool bCollapseSamples = false; |
|
for ( int j = 0 ; j < nKeyCount; ++j ) |
|
{ |
|
DmeTime_t tKeyTime = pClipboard->GetKeyTime( j ); |
|
T val = pClipboard->GetKeyValue( j ); |
|
|
|
// Determine which state we're in |
|
// NOTE: Don't use ComputeRegionForTime here because it includes |
|
// the endpoint of the hold region into the hold region. |
|
int nState; |
|
for ( nState = nPrevState; nState < PASTE_STATE_COUNT; ++nState ) |
|
{ |
|
if ( tKeyTime < tStartTime[ nState + 1 ][ 0 ] ) |
|
break; |
|
} |
|
|
|
// This logic inserts a key if there is no sample in the clipboard at the transition time |
|
bool bForceKey = false; |
|
if ( nPrevState < nState ) |
|
{ |
|
nState = ++nPrevState; |
|
|
|
// This logic will prevent samples at the hold start + end if |
|
// the source + dest regions are 0 width and will only do the first and last |
|
// if we're squeezing the entire time selection down to a single point. |
|
bForceKey = true; |
|
|
|
if ( nState != PASTE_STATE_AFTER ) |
|
{ |
|
bCollapseSamples = ( pKeyCount[nState] >= pDuration[nState][1] ); |
|
tMaxKeyTime = bCollapseSamples ? tStartTime[ nState ][ 1 ] : ( tStartTime[ nState+1 ][ 1 ] - DmeTime_t( pKeyCount[nState] + 1 ) ); |
|
} |
|
else |
|
{ |
|
bCollapseSamples = false; |
|
tMaxKeyTime = DMETIME_MAXTIME; |
|
} |
|
|
|
// NOTE: This has to occur after collapse samples + max key time has been set |
|
if ( ShouldSkipTransition( nState, nZeroField ) ) |
|
{ |
|
--j; |
|
continue; |
|
} |
|
|
|
// Don't insert an extra key if the current one we're looking at is right at that point |
|
if ( tKeyTime != tStartTime[ nPrevState ][ 0 ] ) |
|
{ |
|
tKeyTime = tStartTime[ nPrevState ][ 0 ]; |
|
val = pStartValue[nPrevState]; |
|
|
|
// We want to re-do this key, since we inserted a key beforehand |
|
--j; |
|
} |
|
} |
|
|
|
if ( nState == PASTE_STATE_BEFORE ) |
|
continue; |
|
|
|
if ( nState == PASTE_STATE_AFTER && !bForceKey ) |
|
return; |
|
|
|
// Compute destination time based on scale + offset |
|
double flFactor = ( tKeyTime - tStartTime[ nState ][ 0 ] ).GetTenthsOfMS() * pScaleFactor[ nState ]; |
|
|
|
// FIXME: Fix the algorithm, then uncomment to get time-scaled falloff regions |
|
// if ( nState == PASTE_STATE_RAMP_IN || nState == PASTE_STATE_RAMP_OUT ) |
|
// { |
|
// int s = ( nState == PASTE_STATE_RAMP_IN ) ? 0 : 1; |
|
// flFactor = ComputeInterpolationFactor( flFactor, destParams.m_nFalloffInterpolatorTypes[s] ); |
|
// } |
|
double flTempTime = flFactor * pDuration[ nState ][ 1 ]; |
|
DmeTime_t tDestTime( (int)( flTempTime + 0.5 ) ); |
|
tDestTime += tStartTime[ nState ][ 1 ]; |
|
|
|
// Clamp necessary to not lose samples |
|
// NOTE: The !bForceKey check here makes it so we don't clamp points |
|
// in time corresponding to transitions of the time selection |
|
if ( !bForceKey && ( tDestTime > tMaxKeyTime ) ) |
|
{ |
|
tDestTime = tMaxKeyTime; |
|
} |
|
if ( tMaxKeyTime != DMETIME_MAXTIME ) |
|
{ |
|
tMaxKeyTime += DMETIME_MINDELTA; |
|
} |
|
|
|
// This logic will cause *all* samples to appear if we have enough room for them |
|
if ( !bCollapseSamples ) |
|
{ |
|
bForceKey = true; |
|
} |
|
|
|
// If we'd go outside our region and we're not forcing the key, then skip |
|
if ( !bForceKey && tDestTime >= tStartTime[ nState+1 ][ 1 ] ) |
|
continue; |
|
|
|
// Perform blending on ramp in + ramp out regions |
|
if ( bBlendAreaInFalloffRegion && ( nState != PASTE_STATE_HOLD ) ) |
|
{ |
|
int nBlendIndex = ( nState < PASTE_STATE_HOLD ) ? 0 : 1; |
|
T baseValue = pBaseLayer->GetValue( tDestTime ); |
|
|
|
Vector2D oldDist; |
|
oldDist.x = tDestTime.GetSeconds() - pBlendTime[ nBlendIndex ].GetSeconds(); |
|
oldDist.y = LengthOf( Subtract( baseValue, pBlendBase[ nBlendIndex ] ) ); |
|
|
|
float flDistance = oldDist.Length(); |
|
float flFactorBlend = flDistance * pOOBlendLength[ nBlendIndex ]; |
|
flFactorBlend = destParams.AdjustFactorForInterpolatorType( flFactorBlend, nBlendIndex ); |
|
val = Interpolate( flFactorBlend, baseValue, val ); |
|
} |
|
|
|
// Force key insertion when we transition between states |
|
if ( bForceKey && ( tLastWrittenTime >= tDestTime ) ) |
|
{ |
|
tDestTime = tLastWrittenTime + DMETIME_MINDELTA; |
|
} |
|
|
|
// Insert the key into the log |
|
if ( tLastWrittenTime < tDestTime ) |
|
{ |
|
pWriteLayer->InsertKey( tDestTime, val ); |
|
tLastWrittenTime = tDestTime; |
|
} |
|
} |
|
} |
|
|
|
template< class T > |
|
void CDmeTypedLog< T >::PasteAndRescaleSamples( |
|
const CDmeLogLayer *src, // clipboard data |
|
const DmeLog_TimeSelection_t& srcParams, // clipboard time selection |
|
const DmeLog_TimeSelection_t& destParams, // current time selection |
|
bool bBlendAreaInFalloffRegion ) // blending behavior in falloff area of current time selection |
|
{ |
|
CDmeLogLayer *pBaseLayer = GetLayer( 0 ); |
|
CDmeLogLayer *pWriteLayer = GetLayer( GetTopmostLayer() ); |
|
PasteAndRescaleSamples( pBaseLayer, src, pWriteLayer, srcParams, destParams, bBlendAreaInFalloffRegion ); |
|
} |
|
|
|
template<> |
|
void CDmeTypedLog< Vector >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) |
|
{ |
|
Assert( target ); |
|
Assert( GetDataType() != AT_FLOAT ); |
|
|
|
CDmeTypedLogLayer< Vector > *baseLayer = static_cast< CDmeTypedLogLayer< Vector > * >( GetLayer( 0 ) ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
float flMin = FLT_MAX; |
|
float flMax = FLT_MIN; |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
Vector keyValue = baseLayer->GetKeyValue( i ); |
|
|
|
float len = keyValue.Length(); |
|
if ( len < flMin ) |
|
{ |
|
flMin = len; |
|
} |
|
if ( len > flMax ) |
|
{ |
|
flMax = len; |
|
} |
|
|
|
target->InsertKey( keyTime, len ); |
|
} |
|
|
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
float keyValue = target->GetKeyValue( i ); |
|
float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); |
|
target->SetKeyValue( i, normalized ); |
|
} |
|
|
|
if ( HasDefaultValue() ) |
|
{ |
|
target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); |
|
} |
|
} |
|
|
|
template<> |
|
void CDmeTypedLog< Vector2D >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) |
|
{ |
|
Assert( target ); |
|
Assert( GetDataType() != AT_FLOAT ); |
|
|
|
CDmeTypedLogLayer< Vector2D > *baseLayer = static_cast< CDmeTypedLogLayer< Vector2D > * >( GetLayer( 0 ) ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
float flMin = FLT_MAX; |
|
float flMax = FLT_MIN; |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
Vector2D keyValue = baseLayer->GetKeyValue( i ); |
|
|
|
float len = keyValue.Length(); |
|
|
|
if ( len < flMin ) |
|
{ |
|
flMin = len; |
|
} |
|
if ( len > flMax ) |
|
{ |
|
flMax = len; |
|
} |
|
|
|
target->InsertKey( keyTime, len ); |
|
} |
|
|
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
float keyValue = target->GetKeyValue( i ); |
|
float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); |
|
target->SetKeyValue( i, normalized ); |
|
} |
|
|
|
if ( HasDefaultValue() ) |
|
{ |
|
target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); |
|
} |
|
} |
|
|
|
template<> |
|
void CDmeTypedLog< Vector4D >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) |
|
{ |
|
Assert( target ); |
|
Assert( GetDataType() != AT_FLOAT ); |
|
|
|
CDmeTypedLogLayer< Vector4D > *baseLayer = static_cast< CDmeTypedLogLayer< Vector4D > * >( GetLayer( 0 ) ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
float flMin = FLT_MAX; |
|
float flMax = FLT_MIN; |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
Vector4D keyValue = baseLayer->GetKeyValue( i ); |
|
|
|
float len = keyValue.Length(); |
|
|
|
if ( len < flMin ) |
|
{ |
|
flMin = len; |
|
} |
|
if ( len > flMax ) |
|
{ |
|
flMax = len; |
|
} |
|
|
|
target->InsertKey( keyTime, len ); |
|
} |
|
|
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
float keyValue = target->GetKeyValue( i ); |
|
float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); |
|
target->SetKeyValue( i, normalized ); |
|
} |
|
|
|
if ( HasDefaultValue() ) |
|
{ |
|
target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); |
|
} |
|
} |
|
|
|
template<> |
|
void CDmeTypedLog< int >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) |
|
{ |
|
Assert( target ); |
|
Assert( GetDataType() != AT_FLOAT ); |
|
|
|
CDmeTypedLogLayer< int > *baseLayer = static_cast< CDmeTypedLogLayer< int > * >( GetLayer( 0 ) ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
float flMin = FLT_MAX; |
|
float flMax = FLT_MIN; |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
int keyValue = baseLayer->GetKeyValue( i ); |
|
|
|
float len = (float)keyValue; |
|
|
|
if ( len < flMin ) |
|
{ |
|
flMin = len; |
|
} |
|
if ( len > flMax ) |
|
{ |
|
flMax = len; |
|
} |
|
|
|
target->InsertKey( keyTime, len ); |
|
} |
|
|
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
float keyValue = target->GetKeyValue( i ); |
|
float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); |
|
target->SetKeyValue( i, normalized ); |
|
} |
|
|
|
if ( HasDefaultValue() ) |
|
{ |
|
target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue(), flMin, flMax, 0.0f, 1.0f ) ); |
|
} |
|
} |
|
|
|
template<> |
|
void CDmeTypedLog< float >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) |
|
{ |
|
Assert( target ); |
|
Assert( GetDataType() != AT_FLOAT ); |
|
|
|
CDmeTypedLogLayer< float > *baseLayer = static_cast< CDmeTypedLogLayer< float > * >( GetLayer( 0 ) ); |
|
if ( !baseLayer ) |
|
return; |
|
|
|
float flMin = FLT_MAX; |
|
float flMax = FLT_MIN; |
|
|
|
int kc = baseLayer->GetKeyCount(); |
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
DmeTime_t keyTime = baseLayer->GetKeyTime( i ); |
|
int keyValue = baseLayer->GetKeyValue( i ); |
|
|
|
float len = (float)keyValue; |
|
|
|
if ( len < flMin ) |
|
{ |
|
flMin = len; |
|
} |
|
if ( len > flMax ) |
|
{ |
|
flMax = len; |
|
} |
|
|
|
target->InsertKey( keyTime, len ); |
|
} |
|
|
|
for ( int i = 0; i < kc; ++i ) |
|
{ |
|
float keyValue = target->GetKeyValue( i ); |
|
float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); |
|
target->SetKeyValue( i, normalized ); |
|
} |
|
|
|
if ( HasDefaultValue() ) |
|
{ |
|
target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue(), flMin, flMax, 0.0f, 1.0f ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a log of a specific type |
|
//----------------------------------------------------------------------------- |
|
CDmeLog *CDmeLog::CreateLog( DmAttributeType_t type, DmFileId_t fileid ) |
|
{ |
|
switch ( type ) |
|
{ |
|
case AT_INT: |
|
case AT_INT_ARRAY: |
|
return CreateElement< CDmeIntLog >( "int log", fileid ); |
|
case AT_FLOAT: |
|
case AT_FLOAT_ARRAY: |
|
return CreateElement< CDmeFloatLog >( "float log", fileid ); |
|
case AT_BOOL: |
|
case AT_BOOL_ARRAY: |
|
return CreateElement< CDmeBoolLog >( "bool log", fileid ); |
|
case AT_COLOR: |
|
case AT_COLOR_ARRAY: |
|
return CreateElement< CDmeColorLog >( "color log", fileid ); |
|
case AT_VECTOR2: |
|
case AT_VECTOR2_ARRAY: |
|
return CreateElement< CDmeVector2Log >( "vector2 log", fileid ); |
|
case AT_VECTOR3: |
|
case AT_VECTOR3_ARRAY: |
|
return CreateElement< CDmeVector3Log >( "vector3 log", fileid ); |
|
case AT_VECTOR4: |
|
case AT_VECTOR4_ARRAY: |
|
return CreateElement< CDmeVector4Log >( "vector4 log", fileid ); |
|
case AT_QANGLE: |
|
case AT_QANGLE_ARRAY: |
|
return CreateElement< CDmeQAngleLog >( "qangle log", fileid ); |
|
case AT_QUATERNION: |
|
case AT_QUATERNION_ARRAY: |
|
return CreateElement< CDmeQuaternionLog >( "quaternion log", fileid ); |
|
case AT_VMATRIX: |
|
case AT_VMATRIX_ARRAY: |
|
return CreateElement< CDmeVMatrixLog >( "vmatrix log", fileid ); |
|
case AT_STRING: |
|
case AT_STRING_ARRAY: |
|
return CreateElement< CDmeStringLog >( "string log", fileid ); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// Disallowed methods for types |
|
//template<> void CDmeTypedLog< bool >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const bool& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< bool >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const bool& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< bool >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< bool >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
// |
|
//template<> void CDmeTypedLog< Color >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Color& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Color >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Color& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Color >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Color >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
// |
|
//template<> void CDmeTypedLog< Vector4D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector4D& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector4D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector4D& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector4D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector4D >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
// |
|
//template<> void CDmeTypedLog< Vector2D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector2D& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector2D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector2D& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector2D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector2D >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
|
|
//template<> void CDmeTypedLog< Vector >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Vector >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
|
|
//template<> void CDmeTypedLog< VMatrix >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const VMatrix& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< VMatrix >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const VMatrix& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< VMatrix >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< VMatrix >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
// |
|
//template<> void CDmeTypedLog< Quaternion >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Quaternion& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Quaternion >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Quaternion& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Quaternion >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< Quaternion >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
// |
|
//template<> void CDmeTypedLog< QAngle >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const QAngle& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< QAngle >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const QAngle& value ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< QAngle >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } |
|
//template<> void CDmeTypedLog< QAngle >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Helpers for particular types of log layers |
|
//----------------------------------------------------------------------------- |
|
void GenerateRotationLog( CDmeQuaternionLogLayer *pLayer, const Vector &vecAxis, DmeTime_t pTime[4], float pRevolutionsPerSec[4] ) |
|
{ |
|
for ( int i = 1; i < 4; ++i ) |
|
{ |
|
if ( pTime[i] < pTime[i-1] ) |
|
{ |
|
Warning( "Bogus times passed into GenerateRotationLog\n" ); |
|
return; |
|
} |
|
} |
|
|
|
// Gets the initial value |
|
matrix3x4_t initial; |
|
Quaternion q = pLayer->GetValue( pTime[0] ); |
|
QuaternionMatrix( q, initial ); |
|
|
|
// Find the max rps, and compute the total rotation in degrees |
|
// by the time we reach the transition points. The total rotation = |
|
// integral from 0 to t of 360 * ( rate[i] - rate[i-1] ) t / tl + rate[i-1] ) |
|
// == 360 * ( ( rate[i] - rate[i-1] ) t^2 / 2 + rate[i-1] t ) |
|
float pTotalRotation[4]; |
|
float flMaxRPS = pRevolutionsPerSec[0]; |
|
pTotalRotation[0] = 0.0f; |
|
for ( int i = 1; i < 4; ++i ) |
|
{ |
|
if ( pRevolutionsPerSec[i] > flMaxRPS ) |
|
{ |
|
flMaxRPS = pRevolutionsPerSec[i]; |
|
} |
|
float dt = pTime[i].GetSeconds() - pTime[i-1].GetSeconds(); |
|
float dRot = pRevolutionsPerSec[i] - pRevolutionsPerSec[i-1]; |
|
pTotalRotation[i] = 360.0f * ( dRot * dt * 0.5 + pRevolutionsPerSec[i-1] * dt ) + pTotalRotation[i-1]; |
|
} |
|
|
|
// We need to compute how long a single rotation takes, then create samples |
|
// at 1/4 the frequency of that amount of time |
|
VMatrix rot; |
|
matrix3x4_t total; |
|
QAngle angles; |
|
float flMaxRotationTime = (flMaxRPS != 0.0f) ? ( 0.125f / flMaxRPS ) : ( pTime[3].GetSeconds() - pTime[0].GetSeconds() ); |
|
DmeTime_t dt( flMaxRotationTime ); |
|
for ( DmeTime_t t = pTime[0]; t <= pTime[3]; t += dt ) |
|
{ |
|
int i = ( t < pTime[1] ) ? 1 : ( ( t < pTime[2] ) ? 2 : 3 ); |
|
float flInterval = t.GetSeconds() - pTime[i-1].GetSeconds(); |
|
float flOOSegmentDur = pTime[i].GetSeconds() - pTime[i-1].GetSeconds(); |
|
if ( flOOSegmentDur == 0.0f ) |
|
{ |
|
Assert( flInterval == 0.0f ); |
|
flOOSegmentDur = 1.0f; |
|
} |
|
else |
|
{ |
|
flOOSegmentDur = 1.0f / flOOSegmentDur; |
|
} |
|
float dRot = pRevolutionsPerSec[i] - pRevolutionsPerSec[i-1]; |
|
float flRotation = 360.0f * ( dRot * flInterval * flInterval * 0.5f * flOOSegmentDur + pRevolutionsPerSec[i-1] * flInterval ) + pTotalRotation[i-1]; |
|
|
|
MatrixBuildRotationAboutAxis( rot, vecAxis, flRotation ); |
|
ConcatTransforms( initial, rot.As3x4(), total ); |
|
MatrixToAngles( total, angles ); |
|
AngleQuaternion( angles, q ); |
|
pLayer->SetKey( t, q ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Transforms a position log |
|
//----------------------------------------------------------------------------- |
|
void RotatePositionLog( CDmeVector3LogLayer *pPositionLog, const matrix3x4_t& matrix ) |
|
{ |
|
Assert( fabs( matrix[0][3] ) < 1e-3 && fabs( matrix[1][3] ) < 1e-3 && fabs( matrix[2][3] ) < 1e-3 ); |
|
Vector position; |
|
int nCount = pPositionLog->GetKeyCount(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
const Vector &srcPosition = pPositionLog->GetKeyValue( i ); |
|
VectorTransform( srcPosition, matrix, position ); |
|
pPositionLog->SetKeyValue( i, position ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Transforms a orientation log |
|
//----------------------------------------------------------------------------- |
|
void RotateOrientationLog( CDmeQuaternionLogLayer *pOrientationLog, const matrix3x4_t& matrix, bool bPreMultiply = false ) |
|
{ |
|
Assert( fabs( matrix[0][3] ) < 1e-3 && fabs( matrix[1][3] ) < 1e-3 && fabs( matrix[2][3] ) < 1e-3 ); |
|
matrix3x4_t orientation, newOrientation; |
|
Quaternion q; |
|
int nCount = pOrientationLog->GetKeyCount(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
const Quaternion &srcQuat = pOrientationLog->GetKeyValue( i ); |
|
QuaternionMatrix( srcQuat, orientation ); |
|
if ( bPreMultiply ) |
|
{ |
|
ConcatTransforms( matrix, orientation, newOrientation ); |
|
} |
|
else |
|
{ |
|
ConcatTransforms( orientation, matrix, newOrientation ); |
|
} |
|
MatrixQuaternion( newOrientation, q ); |
|
pOrientationLog->SetKeyValue( i, q ); |
|
} |
|
}
|
|
|