add support for texture coordinate animation in AnimatedTexture

This commit is contained in:
Chris Danford
2004-08-09 00:46:42 +00:00
parent 3f927e6280
commit 1d376f5795
14 changed files with 224 additions and 101 deletions
+7 -3
View File
@@ -305,9 +305,13 @@ public:
virtual void HandleCommand( const ParsedCommand &command ); // derivable
static float GetCommandLength( CString command );
virtual void SetState( int iNewState ) {};
virtual void SetSecondsIntoAnimation( float fSeconds ) {};
virtual int GetNumStates() { return 1; };
//
// Animation
//
virtual int GetNumStates() const { return 1; }
virtual void SetState( int iNewState ) {}
virtual float GetAnimationLengthSeconds() const { return 0; }
virtual void SetSecondsIntoAnimation( float fSeconds ) {}
//
// BGAnimation stuff
+51 -26
View File
@@ -11,6 +11,7 @@
#include "ActorUtil.h"
#include <cerrno>
#include "ModelManager.h"
#include "Foreach.h"
const float FRAMES_PER_SECOND = 30;
const CString DEFAULT_ANIMATION_NAME = "default";
@@ -466,6 +467,8 @@ void Model::DrawPrimitives()
RageMatrix &mat = m_vpBones[pMesh->nBoneIndex].mFinal;
DISPLAY->PreMultMatrix( mat );
}
DISPLAY->TexturePushMatrix();
if( pMesh->nMaterialIndex != -1 ) // has a material
{
@@ -482,15 +485,25 @@ void Model::DrawPrimitives()
DISPLAY->SetMaterial( Emissive, Ambient, Diffuse, mat.Specular, mat.fShininess );
// render the first pass with texture 1
DISPLAY->SetTexture( 0, mat.diffuse.ani.GetCurrentTexture() );
DISPLAY->SetSphereEnironmentMapping( mat.diffuse.bSphereMapped );
// render the second pass with texture 2
if( mat.alpha.ani.GetCurrentTexture() )
float fScrollX = 0;
float fScrollY = 0;
if( mat.diffuse.m_fTexVelocityX != 0 || mat.diffuse.m_fTexVelocityY != 0 )
{
DISPLAY->SetTexture( 1, mat.alpha.ani.GetCurrentTexture() );
DISPLAY->SetSphereEnironmentMapping( mat.alpha.bSphereMapped );
fScrollX = mat.diffuse.m_fTexVelocityX * mat.diffuse.GetSecondsIntoAnimation() / mat.diffuse.GetAnimationLengthSeconds();
fScrollY = mat.diffuse.m_fTexVelocityY * mat.diffuse.GetSecondsIntoAnimation() / mat.diffuse.GetAnimationLengthSeconds();
DISPLAY->SetTextureWrapping( true );
}
DISPLAY->TextureTranslate( fScrollX, fScrollY, 0 );
// render the first pass with texture 1
DISPLAY->SetTexture( 0, mat.diffuse.GetCurrentTexture() );
DISPLAY->SetSphereEnironmentMapping( mat.diffuse.m_bSphereMapped );
// render the second pass with texture 2
if( mat.alpha.GetCurrentTexture() )
{
DISPLAY->SetTexture( 1, mat.alpha.GetCurrentTexture() );
DISPLAY->SetSphereEnironmentMapping( mat.alpha.m_bSphereMapped );
// UGLY: This overrides the Actor's BlendMode
DISPLAY->SetTextureModeAdd();
DISPLAY->SetTextureFiltering( true );
@@ -512,6 +525,8 @@ void Model::DrawPrimitives()
DISPLAY->DrawCompiledGeometry( TempGeometry, i, m_pGeometry->m_Meshes );
DISPLAY->SetSphereEnironmentMapping( false );
DISPLAY->TexturePopMatrix();
if( pMesh->nBoneIndex != -1 )
{
DISPLAY->PopMatrix();
@@ -549,7 +564,7 @@ void Model::DrawPrimitives()
if( pMesh->nMaterialIndex != -1 )
{
msMaterial& mat = m_Materials[ pMesh->nMaterialIndex ];
DISPLAY->SetTexture( 0, mat.diffuse.ani.GetCurrentTexture() );
DISPLAY->SetTexture( 0, mat.diffuse.GetCurrentTexture() );
}
else
{
@@ -661,13 +676,6 @@ void Model::PlayAnimation( CString sAniName, float fPlayRate )
AdvanceFrame( 0.0f );
}
float Model::GetCurFrame() { return m_fCurrFrame; };
void Model::SetFrame( float fNewFrame )
{
m_fCurrFrame = fNewFrame;
}
void Model::AdvanceFrame (float dt)
{
if( m_pGeometry == NULL ||
@@ -807,8 +815,8 @@ void Model::Update( float fDelta )
for( int i=0; i<(int)m_Materials.size(); i++ )
{
m_Materials[i].diffuse.ani.Update( fDelta );
m_Materials[i].alpha.ani.Update( fDelta );
m_Materials[i].diffuse.Update( fDelta );
m_Materials[i].alpha.Update( fDelta );
}
//
@@ -853,21 +861,38 @@ void Model::Update( float fDelta )
}
}
int Model::GetNumStates() const
{
int iMaxStates = 0;
FOREACH_CONST( msMaterial, m_Materials, m )
iMaxStates = max( iMaxStates, m->diffuse.GetNumStates() );
return iMaxStates;
}
void Model::SetState( int iNewState )
{
for( int i=0; i<(int)m_Materials.size(); i++ )
FOREACH( msMaterial, m_Materials, m )
{
m_Materials[i].diffuse.ani.SetState( iNewState );
m_Materials[i].alpha.ani.SetState( iNewState );
m->diffuse.SetState( iNewState );
m->alpha.SetState( iNewState );
}
}
int Model::GetNumStates()
float Model::GetAnimationLengthSeconds() const
{
int iMaxStates = 0;
for( int i=0; i<(int)m_Materials.size(); i++ )
iMaxStates = max( iMaxStates, m_Materials[i].diffuse.ani.GetNumStates() );
return iMaxStates;
float fSeconds = 0;
FOREACH_CONST( msMaterial, m_Materials, m )
fSeconds = max( fSeconds, m->diffuse.GetAnimationLengthSeconds() );
return fSeconds;
}
void Model::SetSecondsIntoAnimation( float fSeconds )
{
FOREACH( msMaterial, m_Materials, m )
{
m->diffuse.SetSecondsIntoAnimation( fSeconds );
m->alpha.SetSecondsIntoAnimation( fSeconds );
}
}
void Model::HandleCommand( const ParsedCommand &command )
+4 -3
View File
@@ -37,10 +37,11 @@ public:
void AdvanceFrame (float dt);
void DrawCelShaded();
virtual int GetNumStates() const;
virtual void SetState( int iNewState );
float GetCurFrame();
void SetFrame( float fNewFrame );
virtual int GetNumStates();
virtual float GetAnimationLengthSeconds() const;
virtual void SetSecondsIntoAnimation( float fSeconds );
CString GetDefaultAnimation() { return m_sDefaultAnimation; };
void SetDefaultAnimation( CString sAnimation, float fPlayRate = 1 );
bool m_bRevertToDefaultAnimation;
+71 -16
View File
@@ -6,11 +6,16 @@
#include "RageTextureManager.h"
#include "RageLog.h"
#include "RageDisplay.h"
#include "Foreach.h"
AnimatedTexture::AnimatedTexture()
{
iCurState = 0;
fSecsIntoFrame = 0;
m_iCurState = 0;
m_fSecsIntoFrame = 0;
m_bSphereMapped = false;
m_fTexVelocityX = 0;
m_fTexVelocityY = 0;
m_BlendMode = BLEND_NORMAL;
}
AnimatedTexture::~AnimatedTexture()
@@ -22,6 +27,12 @@ void AnimatedTexture::Load( CString sTexOrIniPath )
{
ASSERT( vFrames.empty() ); // don't load more than once
m_bSphereMapped = sTexOrIniPath.Find("sphere") != -1;
if( sTexOrIniPath.Find("add") != -1 )
m_BlendMode = BLEND_ADD;
else
m_BlendMode = BLEND_NORMAL;
if( GetExtension(sTexOrIniPath).CompareNoCase("ini")==0 )
{
IniFile ini;
@@ -30,6 +41,10 @@ void AnimatedTexture::Load( CString sTexOrIniPath )
if( !ini.GetKey("AnimatedTexture") )
RageException::Throw( "The animated texture file '%s' doesn't contain a section called 'AnimatedTexture'.", sTexOrIniPath.c_str() );
ini.GetValue( "AnimatedTexture", "TexVelocityX", m_fTexVelocityX );
ini.GetValue( "AnimatedTexture", "TexVelocityY", m_fTexVelocityY );
for( int i=0; i<1000; i++ )
{
CString sFileKey = ssprintf( "Frame%04d", i );
@@ -64,7 +79,7 @@ void AnimatedTexture::Load( CString sTexOrIniPath )
ID.bMipMaps = true; // use mipmaps in Models
AnimatedTextureState state = {
TEXTUREMAN->LoadTexture( ID ),
10
1
};
vFrames.push_back( state );
}
@@ -75,12 +90,12 @@ void AnimatedTexture::Update( float fDelta )
{
if( vFrames.empty() )
return;
ASSERT( iCurState < (int)vFrames.size() );
fSecsIntoFrame += fDelta;
if( fSecsIntoFrame > vFrames[iCurState].fDelaySecs )
ASSERT( m_iCurState < (int)vFrames.size() );
m_fSecsIntoFrame += fDelta;
if( m_fSecsIntoFrame > vFrames[m_iCurState].fDelaySecs )
{
fSecsIntoFrame -= vFrames[iCurState].fDelaySecs;
iCurState = (iCurState+1) % vFrames.size();
m_fSecsIntoFrame -= vFrames[m_iCurState].fDelaySecs;
m_iCurState = (m_iCurState+1) % vFrames.size();
}
}
@@ -88,19 +103,60 @@ RageTexture* AnimatedTexture::GetCurrentTexture()
{
if( vFrames.empty() )
return NULL;
ASSERT( iCurState < (int)vFrames.size() );
return vFrames[iCurState].pTexture;
ASSERT( m_iCurState < (int)vFrames.size() );
return vFrames[m_iCurState].pTexture;
}
int AnimatedTexture::GetNumStates() const
{
return vFrames.size();
}
void AnimatedTexture::SetState( int iState )
{
CLAMP( iState, 0, GetNumStates()-1 );
iCurState = iState;
m_iCurState = iState;
}
int AnimatedTexture::GetNumStates()
float AnimatedTexture::GetAnimationLengthSeconds() const
{
return vFrames.size();
float fTotalSeconds = 0;
FOREACH_CONST( AnimatedTextureState, vFrames, ats )
fTotalSeconds += ats->fDelaySecs;
return fTotalSeconds;
}
void AnimatedTexture::SetSecondsIntoAnimation( float fSeconds )
{
fSeconds = fmodf( fSeconds, GetAnimationLengthSeconds() );
m_iCurState = 0;
for( unsigned i=0; i<vFrames.size(); i++ )
{
AnimatedTextureState& ats = vFrames[i];
if( fSeconds > ats.fDelaySecs )
{
fSeconds -= ats.fDelaySecs;
m_iCurState = i+1;
}
}
m_fSecsIntoFrame = fSeconds; // remainder
}
float AnimatedTexture::GetSecondsIntoAnimation() const
{
float fSeconds = 0;
for( unsigned i=0; i<vFrames.size(); i++ )
{
const AnimatedTextureState& ats = vFrames[i];
if( i >= m_iCurState )
break;
fSeconds += ats.fDelaySecs;
}
fSeconds += m_fSecsIntoFrame;
return fSeconds;
}
void AnimatedTexture::Unload()
@@ -108,11 +164,10 @@ void AnimatedTexture::Unload()
for(unsigned i = 0; i < vFrames.size(); ++i)
TEXTUREMAN->UnloadTexture(vFrames[i].pTexture);
vFrames.clear();
iCurState = 0;
fSecsIntoFrame = 0;
m_iCurState = 0;
m_fSecsIntoFrame = 0;
}
msMesh::msMesh()
{
ZERO( szName );
+18 -26
View File
@@ -71,8 +71,9 @@ typedef struct msMesh
class RageTexture;
// merge this into Sprite?
struct AnimatedTexture
class AnimatedTexture
{
public:
AnimatedTexture();
~AnimatedTexture();
@@ -82,11 +83,21 @@ struct AnimatedTexture
RageTexture* GetCurrentTexture();
void SetState( int iState );
int GetNumStates();
int GetNumStates() const;
void SetState( int iNewState );
float GetAnimationLengthSeconds() const;
void SetSecondsIntoAnimation( float fSeconds );
float GetSecondsIntoAnimation() const;
int iCurState;
float fSecsIntoFrame;
bool m_bSphereMapped;
float m_fTexVelocityX;
float m_fTexVelocityY;
BlendMode m_BlendMode;
private:
int m_iCurState;
float m_fSecsIntoFrame;
struct AnimatedTextureState
{
RageTexture* pTexture;
@@ -109,27 +120,8 @@ typedef struct msMaterial
char szAlphaTexture[MS_MAX_PATH]; // not used in SM. Use alpha in diffuse texture instead
int nName; // not used in SM. What is this for anyway?
struct Texture
{
Texture()
{
bSphereMapped = false;
blendMode = BLEND_NORMAL;
}
void Load( CString sFile )
{
ani.Load( sFile );
bSphereMapped = sFile.Find("sphere") != -1;
if( sFile.Find("add") != -1 )
blendMode = BLEND_ADD;
else
blendMode = BLEND_NORMAL;
};
AnimatedTexture ani;
bool bSphereMapped; // true of "sphere" appears in the material name
BlendMode blendMode;
} diffuse, alpha;
AnimatedTexture diffuse;
AnimatedTexture alpha;
} msMaterial;
/* msPositionKey */
+5 -11
View File
@@ -251,31 +251,25 @@ void NoteDisplay::Update( float fDeltaTime )
void NoteDisplay::SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLengthInBeats, bool bVivid, bool bNoteColor )
{
const int iNumFrames = actorToSet.GetNumStates();
if( iNumFrames == 0 ) // Model with no textures
return;
float fSongBeat = GAMESTATE->m_fSongBeat;
float fPrecentIntoAnimation = fmodf(fSongBeat,fAnimationLengthInBeats) / fAnimationLengthInBeats;
float fPercentIntoAnimation = fmodf(fSongBeat,fAnimationLengthInBeats) / fAnimationLengthInBeats;
float fNoteBeatFraction = fmodf( fNoteBeat, 1.0f );
int iFrameNo = (int)(fPrecentIntoAnimation*iNumFrames);
if( bVivid )
{
// changed to deal with the minor complaint that the color cycling is
// one tick off in general
const float fFraction = fNoteBeatFraction - 0.25f/fAnimationLengthInBeats;
const float fInterval = 1.f / fAnimationLengthInBeats;
iFrameNo += int( froundf(fFraction,fInterval)*iNumFrames );
fPercentIntoAnimation += froundf(fFraction,fInterval);
}
// just in case somehow we're majorly negative with the subtraction
iFrameNo += (iNumFrames * 2);
iFrameNo %= iNumFrames;
wrap( fPercentIntoAnimation, 1.f );
ASSERT( iFrameNo>=0 && iFrameNo<iNumFrames );
actorToSet.SetState( iFrameNo );
float fLengthSeconds = actorToSet.GetAnimationLengthSeconds();
actorToSet.SetSecondsIntoAnimation( fPercentIntoAnimation*fLengthSeconds );
}
Actor * NoteDisplay::GetTapNoteActor( float fNoteBeat )
+23
View File
@@ -350,6 +350,7 @@ public:
MatrixStack g_ProjectionStack;
MatrixStack g_ViewStack;
MatrixStack g_WorldStack;
MatrixStack g_TextureStack;
const RageMatrix* RageDisplay::GetProjectionTop()
{
@@ -366,6 +367,11 @@ const RageMatrix* RageDisplay::GetWorldTop()
return g_WorldStack.GetTop();
}
const RageMatrix* RageDisplay::GetTextureTop()
{
return g_TextureStack.GetTop();
}
void RageDisplay::PushMatrix()
{
g_WorldStack.Push();
@@ -421,6 +427,23 @@ void RageDisplay::LoadIdentity()
g_WorldStack.LoadIdentity();
}
void RageDisplay::TexturePushMatrix()
{
g_TextureStack.Push();
}
void RageDisplay::TexturePopMatrix()
{
g_TextureStack.Pop();
}
void RageDisplay::TextureTranslate( float x, float y, float z )
{
g_TextureStack.TranslateLocal(x, y, z);
}
void RageDisplay::LoadMenuPerspective( float fovDegrees, float fVanishPointX, float fVanishPointY )
{
/* fovDegrees == 0 looks the same as an ortho projection. However,
+6
View File
@@ -293,6 +293,11 @@ public:
void PreMultMatrix( const RageMatrix &f );
void LoadIdentity();
/* Texture matrix functions */
void TexturePushMatrix();
void TexturePopMatrix();
void TextureTranslate( float x, float y, float z );
/* Projection and View matrix stack functions. */
void CameraPushMatrix();
void CameraPopMatrix();
@@ -322,6 +327,7 @@ protected:
const RageMatrix* GetProjectionTop();
const RageMatrix* GetViewTop();
const RageMatrix* GetWorldTop();
const RageMatrix* GetTextureTop();
};
+17 -15
View File
@@ -737,15 +737,17 @@ RageSurface* RageDisplay_D3D::CreateScreenshot()
RageDisplay::VideoModeParams RageDisplay_D3D::GetVideoModeParams() const { return g_CurrentParams; }
#define SEND_CURRENT_MATRICES \
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, (D3DMATRIX*)GetProjectionTop() ); \
g_pd3dDevice->SetTransform( D3DTS_VIEW, (D3DMATRIX*)GetViewTop() ); \
RageMatrix m; \
/* Convert to OpenGL-style "pixel-centered" coords */ \
RageMatrixTranslation( &m, -0.5f, -0.5f, 0 ); \
RageMatrixMultiply( &m, &m, GetWorldTop() ); \
void RageDisplay_D3D::SendCurrentMatrices()
{
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, (D3DMATRIX*)GetProjectionTop() );
g_pd3dDevice->SetTransform( D3DTS_VIEW, (D3DMATRIX*)GetViewTop() );
RageMatrix m;
/* Convert to OpenGL-style "pixel-centered" coords */
RageMatrixTranslation( &m, -0.5f, -0.5f, 0 );
RageMatrixMultiply( &m, &m, GetWorldTop() );
g_pd3dDevice->SetTransform( D3DTS_WORLD, (D3DMATRIX*)&m );
g_pd3dDevice->SetTransform( D3DTS_TEXTURE0, (D3DMATRIX*)GetTextureTop() );
}
class RageCompiledGeometrySWD3D : public RageCompiledGeometry
{
@@ -831,7 +833,7 @@ void RageDisplay_D3D::DrawQuadsInternal( const RageSpriteVertex v[], int iNumVer
}
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawIndexedPrimitiveUP(
D3DPT_TRIANGLELIST, // PrimitiveType
0, // MinIndex
@@ -867,7 +869,7 @@ void RageDisplay_D3D::DrawQuadStripInternal( const RageSpriteVertex v[], int iNu
}
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawIndexedPrimitiveUP(
D3DPT_TRIANGLELIST, // PrimitiveType
0, // MinIndex
@@ -883,7 +885,7 @@ void RageDisplay_D3D::DrawQuadStripInternal( const RageSpriteVertex v[], int iNu
void RageDisplay_D3D::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts )
{
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawPrimitiveUP(
D3DPT_TRIANGLEFAN, // PrimitiveType
iNumVerts-2, // PrimitiveCount,
@@ -895,7 +897,7 @@ void RageDisplay_D3D::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts
void RageDisplay_D3D::DrawStripInternal( const RageSpriteVertex v[], int iNumVerts )
{
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawPrimitiveUP(
D3DPT_TRIANGLESTRIP, // PrimitiveType
iNumVerts-2, // PrimitiveCount,
@@ -907,7 +909,7 @@ void RageDisplay_D3D::DrawStripInternal( const RageSpriteVertex v[], int iNumVer
void RageDisplay_D3D::DrawTrianglesInternal( const RageSpriteVertex v[], int iNumVerts )
{
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawPrimitiveUP(
D3DPT_TRIANGLELIST, // PrimitiveType
iNumVerts/3, // PrimitiveCount,
@@ -918,7 +920,7 @@ void RageDisplay_D3D::DrawTrianglesInternal( const RageSpriteVertex v[], int iNu
void RageDisplay_D3D::DrawCompiledGeometryInternal( const RageCompiledGeometry *p, int iMeshIndex )
{
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
p->Draw( iMeshIndex );
}
@@ -931,7 +933,7 @@ void RageDisplay_D3D::DrawLineStrip( const RageSpriteVertex v[], int iNumVerts,
ASSERT( iNumVerts >= 2 );
g_pd3dDevice->SetRenderState( D3DRS_POINTSIZE, *((DWORD*)&LineWidth) ); // funky cast. See D3DRENDERSTATETYPE doc
g_pd3dDevice->SetVertexShader( D3DFVF_RageSpriteVertex );
SEND_CURRENT_MATRICES;
SendCurrentMatrices();
g_pd3dDevice->DrawPrimitiveUP(
D3DPT_LINESTRIP, // PrimitiveType
iNumVerts-1, // PrimitiveCount,
+2
View File
@@ -89,6 +89,8 @@ protected:
RageSurface* CreateScreenshot();
void SetViewport(int shift_left, int shift_down);
RageMatrix GetOrthoMatrix( float l, float r, float b, float t, float zn, float zf );
void SendCurrentMatrices();
};
#endif
+2
View File
@@ -899,6 +899,8 @@ void RageDisplay_OGL::SendCurrentMatrices()
RageMatrixMultiply( &modelView, &modelView, GetWorldTop() );
glMatrixMode( GL_MODELVIEW );
glLoadMatrixf( (const float*)&modelView );
glMatrixMode( GL_TEXTURE );
glLoadMatrixf( (const float*)GetTextureTop() );
}
class RageCompiledGeometrySWOGL : public RageCompiledGeometry
+1
View File
@@ -86,6 +86,7 @@ protected:
RageMatrix GetOrthoMatrix( float l, float r, float b, float t, float zn, float zf );
PixelFormat GetImgPixelFormat( RageSurface* &img, bool &FreeImg, int width, int height );
bool SupportsSurfaceFormat( PixelFormat pixfmt );
void SendCurrentMatrices();
};
+14
View File
@@ -11,6 +11,7 @@
#include "RageTexture.h"
#include "ActorUtil.h"
#include "arch/Dialog/Dialog.h"
#include "Foreach.h"
Sprite::Sprite()
{
@@ -588,6 +589,11 @@ void Sprite::DrawPrimitives()
}
int Sprite::GetNumStates() const
{
return m_States.size();
}
void Sprite::SetState( int iNewState )
{
// This assert will likely trigger if the "missing" theme element graphic
@@ -610,6 +616,14 @@ void Sprite::SetState( int iNewState )
m_fSecsIntoState = 0.0;
}
float Sprite::GetAnimationLengthSeconds() const
{
float fTotal = 0;
FOREACH_CONST( State, m_States, s )
fTotal += s->fDelay;
return fTotal;
}
void Sprite::SetSecondsIntoAnimation( float fSeconds )
{
SetState( 0 ); // rewind to the first state
+3 -1
View File
@@ -34,10 +34,12 @@ public:
RageTexture* GetTexture() { return m_pTexture; };
virtual void EnableAnimation( bool bEnable );
virtual int GetNumStates() const;
virtual void SetState( int iNewState );
virtual float GetAnimationLengthSeconds() const;
virtual void SetSecondsIntoAnimation( float fSeconds );
virtual int GetNumStates() { return m_States.size(); };
CString GetTexturePath() const;
void SetCustomTextureRect( const RectF &new_texcoord_frect );