#include "global.h" #include "ModelTypes.h" #include "IniFile.h" #include "RageUtil.h" #include "RageFile.h" #include "RageMath.h" #include "RageTexture.h" #include "RageTextureManager.h" #include "RageLog.h" #include "RageDisplay.h" #include #include #define MS_MAX_NAME 32 AnimatedTexture::AnimatedTexture() { m_iCurState = 0; m_fSecsIntoFrame = 0; m_bSphereMapped = false; m_vTexOffset = RageVector2(0,0); m_vTexVelocity = RageVector2(0,0); m_BlendMode = BLEND_NORMAL; } AnimatedTexture::~AnimatedTexture() { Unload(); } RageVector3 RadianToDegree(RageVector3 radian) { constexpr float RAD_TO_DEG = 180.0f / PI; return radian * RAD_TO_DEG; } void AnimatedTexture::LoadBlank() { AnimatedTextureState state( nullptr, 1, RageVector2(0,0) ); vFrames.push_back( state ); } void AnimatedTexture::Load( const RString &sTexOrIniPath ) { ASSERT( vFrames.empty() ); // don't load more than once m_bSphereMapped = sTexOrIniPath.find("sphere") != RString::npos; if( sTexOrIniPath.find("add") != std::string::npos ) m_BlendMode = BLEND_ADD; else m_BlendMode = BLEND_NORMAL; if( GetExtension(sTexOrIniPath).CompareNoCase("ini")==0 ) { IniFile ini; if( !ini.ReadFile( sTexOrIniPath ) ) RageException::Throw( "Error reading \"%s\": %s", sTexOrIniPath.c_str(), ini.GetError().c_str() ); const XNode* pAnimatedTexture = ini.GetChild("AnimatedTexture"); if( pAnimatedTexture == nullptr ) RageException::Throw( "The animated texture file \"%s\" doesn't contain a section called \"AnimatedTexture\".", sTexOrIniPath.c_str() ); pAnimatedTexture->GetAttrValue( "TexVelocityX", m_vTexVelocity.x ); pAnimatedTexture->GetAttrValue( "TexVelocityY", m_vTexVelocity.y ); pAnimatedTexture->GetAttrValue( "TexOffsetX", m_vTexOffset.x ); pAnimatedTexture->GetAttrValue( "TexOffsetY", m_vTexOffset.y ); for( int i=0; i<1000; i++ ) { RString sFileKey = ssprintf( "Frame%04d", i ); RString sDelayKey = ssprintf( "Delay%04d", i ); RString sFileName; float fDelay = 0; if( pAnimatedTexture->GetAttrValue( sFileKey, sFileName ) && pAnimatedTexture->GetAttrValue( sDelayKey, fDelay ) ) { RString sTranslateXKey = ssprintf( "TranslateX%04d", i ); RString sTranslateYKey = ssprintf( "TranslateY%04d", i ); RageVector2 vOffset(0,0); pAnimatedTexture->GetAttrValue( sTranslateXKey, vOffset.x ); pAnimatedTexture->GetAttrValue( sTranslateYKey, vOffset.y ); RageTextureID ID; ID.filename = Dirname(sTexOrIniPath) + sFileName; ID.bStretch = true; ID.bHotPinkColorKey = true; ID.bMipMaps = true; // use mipmaps in Models AnimatedTextureState state( TEXTUREMAN->LoadTexture( ID ), fDelay, vOffset ); vFrames.push_back( state ); } else { break; } } } else { RageTextureID ID; ID.filename = sTexOrIniPath; ID.bHotPinkColorKey = true; ID.bStretch = true; ID.bMipMaps = true; // use mipmaps in Models AnimatedTextureState state( TEXTUREMAN->LoadTexture( ID ), 1, RageVector2(0,0) ); vFrames.push_back( state ); } } void AnimatedTexture::Update( float fDelta ) { if( vFrames.empty() ) return; ASSERT( m_iCurState < (int)vFrames.size() ); m_fSecsIntoFrame += fDelta; if( m_fSecsIntoFrame > vFrames[m_iCurState].fDelaySecs ) { m_fSecsIntoFrame -= vFrames[m_iCurState].fDelaySecs; m_iCurState = (m_iCurState+1) % vFrames.size(); } } RageTexture* AnimatedTexture::GetCurrentTexture() { if( vFrames.empty() ) return nullptr; 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 ); m_iCurState = iState; } float AnimatedTexture::GetAnimationLengthSeconds() const { return std::accumulate(vFrames.begin(), vFrames.end(), 0.f, [](float total, AnimatedTextureState const &state) { return total + state.fDelaySecs; }); } void AnimatedTexture::SetSecondsIntoAnimation( float fSeconds ) { fSeconds = std::fmod( fSeconds, GetAnimationLengthSeconds() ); m_iCurState = 0; for( unsigned i=0; i= ats.fDelaySecs ) { fSeconds -= ats.fDelaySecs; m_iCurState = i+1; } else { break; } } m_fSecsIntoFrame = fSeconds; // remainder } float AnimatedTexture::GetSecondsIntoAnimation() const { float fSeconds = 0; for( unsigned i=0; i= m_iCurState ) break; fSeconds += ats.fDelaySecs; } fSeconds += m_fSecsIntoFrame; return fSeconds; } void AnimatedTexture::Unload() { for(unsigned i = 0; i < vFrames.size(); ++i) TEXTUREMAN->UnloadTexture(vFrames[i].pTexture); vFrames.clear(); m_iCurState = 0; m_fSecsIntoFrame = 0; } RageVector2 AnimatedTexture::GetTextureTranslate() { float fPercentIntoAnimation = GetSecondsIntoAnimation() / GetAnimationLengthSeconds(); RageVector2 v = m_vTexVelocity * fPercentIntoAnimation + m_vTexOffset; if( vFrames.empty() ) return v; ASSERT( m_iCurState < (int)vFrames.size() ); v += vFrames[m_iCurState].vTranslate; return v; } #define THROW RageException::Throw( "Parse error in \"%s\" at line %d: \"%s\".", sPath.c_str(), iLineNum, sLine.c_str() ) bool msAnimation::LoadMilkshapeAsciiBones( RString sAniName, RString sPath ) { FixSlashesInPlace(sPath); const RString sDir = Dirname( sPath ); RageFile f; if ( !f.Open(sPath) ) RageException::Throw( "Model:: Could not open \"%s\": %s", sPath.c_str(), f.GetError().c_str() ); RString sLine; int iLineNum = 0; msAnimation &Animation = *this; bool bLoaded = false; while( f.GetLine( sLine ) > 0 ) { iLineNum++; if (!strncmp (sLine.c_str(), "//", 2)) continue; // bones int nNumBones = 0; if( sscanf (sLine.c_str(), "Bones: %d", &nNumBones) != 1 ) continue; char szName[MS_MAX_NAME]; Animation.Bones.resize( nNumBones ); for( int i = 0; i < nNumBones; i++ ) { msBone& Bone = Animation.Bones[i]; // name if( f.GetLine( sLine ) <= 0 ) THROW; if (sscanf(sLine.c_str(), "\"%31[^\"]\"", szName) != 1) THROW; Bone.sName = szName; // parent if( f.GetLine( sLine ) <= 0 ) THROW; strcpy(szName, ""); sscanf(sLine.c_str(), "\"%31[^\"]\"", szName); Bone.sParentName = szName; // flags, position, rotation RageVector3 Position, Rotation; if( f.GetLine( sLine ) <= 0 ) THROW; int nFlags; if (sscanf (sLine.c_str(), "%d %f %f %f %f %f %f", &nFlags, &Position[0], &Position[1], &Position[2], &Rotation[0], &Rotation[1], &Rotation[2]) != 7) { THROW; } Rotation = RadianToDegree(Rotation); Bone.nFlags = nFlags; memcpy( &Bone.Position, &Position, sizeof(Bone.Position) ); memcpy( &Bone.Rotation, &Rotation, sizeof(Bone.Rotation) ); // position key count if( f.GetLine( sLine ) <= 0 ) THROW; int nNumPositionKeys = 0; if (sscanf (sLine.c_str(), "%d", &nNumPositionKeys) != 1) THROW; Bone.PositionKeys.resize( nNumPositionKeys ); for( int j = 0; j < nNumPositionKeys; ++j ) { if( f.GetLine( sLine ) <= 0 ) THROW; float fTime; if (sscanf (sLine.c_str(), "%f %f %f %f", &fTime, &Position[0], &Position[1], &Position[2]) != 4) THROW; msPositionKey key = {}; key.fTime = fTime; key.Position = RageVector3( Position[0], Position[1], Position[2] ); Bone.PositionKeys[j] = key; } // rotation key count if( f.GetLine( sLine ) <= 0 ) THROW; int nNumRotationKeys = 0; if (sscanf (sLine.c_str(), "%d", &nNumRotationKeys) != 1) THROW; Bone.RotationKeys.resize( nNumRotationKeys ); for( int j = 0; j < nNumRotationKeys; ++j ) { if( f.GetLine( sLine ) <= 0 ) THROW; float fTime; if (sscanf (sLine.c_str(), "%f %f %f %f", &fTime, &Rotation[0], &Rotation[1], &Rotation[2]) != 4) THROW; Rotation = RadianToDegree(Rotation); msRotationKey key = {}; key.fTime = fTime; Rotation = RageVector3( Rotation[0], Rotation[1], Rotation[2] ); RageQuatFromHPR( &key.Rotation, Rotation ); Bone.RotationKeys[j] = key; } } // Ignore "Frames:" in file. Calculate it ourself Animation.nTotalFrames = 0; for( int i = 0; i < (int)Animation.Bones.size(); i++ ) { msBone& Bone = Animation.Bones[i]; for( unsigned j = 0; j < Bone.PositionKeys.size(); ++j ) Animation.nTotalFrames = std::max( Animation.nTotalFrames, (int)Bone.PositionKeys[j].fTime ); for( unsigned j = 0; j < Bone.RotationKeys.size(); ++j ) Animation.nTotalFrames = std::max( Animation.nTotalFrames, (int)Bone.RotationKeys[j].fTime ); } } return bLoaded; } /* * (c) 2003-2004 Chris Danford * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */