Files
FC1/CryAnimation/CryModelAnimationContainer.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

570 lines
20 KiB
C++

#include "stdafx.h"
#include <CryCompiledFile.h>
#include <stlutils.h>
//#include "CryAnimation.h"
#include "ControllerManager.h"
#include "CryModelAnimationContainer.h"
#include "CVars.h"
#include "CryBoneInfo.h"
#include "StringUtils.h"
#include "CryAnimationInfo.h"
#include "CryGeomMorphTarget.h"
#include "CrySkinMorph.h"
using namespace CryStringUtils;
CryModelAnimationContainer::CryModelAnimationContainer (CControllerManager * pControllerManager):
m_pControllerManager(pControllerManager),
m_arrBones ("CryModelAnimationContainer.BoneInfo"),
m_arrMorphTargets ("CryModelAnimationContainer.MorphTargets")
{
m_pControllerManager->Register(this);
}
CryModelAnimationContainer::~CryModelAnimationContainer()
{
// before releasing the animations, we should release the controllers from bones
m_arrBones.clear();
// now there are no controllers referred to by this object, we can release the animations
for (AnimationArray::iterator it = m_arrAnimations.begin(); it != m_arrAnimations.end(); ++it)
m_pControllerManager->AnimationRelease(it->nGlobalAnimId, this);
m_pControllerManager->Unregister(this);
}
void CryModelAnimationContainer::OnAnimationGlobalUnload(int nGlobalAnimId)
{
std::vector<LocalAnimId>::iterator it = std::lower_bound (m_arrAnimByGlobalId.begin(), m_arrAnimByGlobalId.end(), nGlobalAnimId, AnimationGlobIdPred(m_arrAnimations));
assert (it == m_arrAnimByGlobalId.begin() || m_arrAnimations[(it-1)->nAnimId].nGlobalAnimId < nGlobalAnimId);
for (;it != m_arrAnimByGlobalId.end() && m_arrAnimations[it->nAnimId].nGlobalAnimId == nGlobalAnimId; ++it)
{
AUTO_PROFILE_SECTION(g_dTimeAnimBindControllers);
// bind loaded controllers to the bones
for (unsigned nBone = 0; nBone < numBoneInfos(); ++nBone)
{
CryBoneInfo& rBoneInfo = getBoneInfo(nBone);
rBoneInfo.UnbindController (it->nAnimId);
}
}
}
void CryModelAnimationContainer::OnAnimationGlobalLoad (int nGlobalAnimId)
{
std::vector<LocalAnimId>::iterator it = std::lower_bound (m_arrAnimByGlobalId.begin(), m_arrAnimByGlobalId.end(), nGlobalAnimId, AnimationGlobIdPred(m_arrAnimations));
assert (it == m_arrAnimByGlobalId.begin() || m_arrAnimations[(it-1)->nAnimId].nGlobalAnimId < nGlobalAnimId);
// scan through the sequence of animations with the given Global AnimId
// and bind each of them to the bones
if (it != m_arrAnimByGlobalId.end() && m_arrAnimations[it->nAnimId].nGlobalAnimId == nGlobalAnimId)
{
AUTO_PROFILE_SECTION(g_dTimeAnimBindControllers);
GlobalAnimation& GlobalAnim = m_pControllerManager->GetAnimation (nGlobalAnimId);
m_fTicksPerFrame = (float)GlobalAnim.nTicksPerFrame;
m_fSecsPerTick = (float)GlobalAnim.fSecsPerTick;
float fSecsPerFrame = m_fSecsPerTick * m_fTicksPerFrame;
float fStart = GlobalAnim.rangeGlobal.start * fSecsPerFrame;
float fStop = GlobalAnim.rangeGlobal.end * fSecsPerFrame;
do {
AnimData &LocalAnim = m_arrAnimations[it->nAnimId];
assert (LocalAnim.nGlobalAnimId == nGlobalAnimId);
LocalAnim.fStart = fStart;
LocalAnim.fStop = fStop;
if (GlobalAnim.IsLoaded())
// bind loaded controllers (if any) to the bones
for (unsigned nBone = 0; nBone < numBoneInfos(); ++nBone)
{
CryBoneInfo& rBoneInfo = getBoneInfo(nBone);
rBoneInfo.BindController (GlobalAnim, it->nAnimId);
}
} while(++it != m_arrAnimByGlobalId.end() && m_arrAnimations[it->nAnimId].nGlobalAnimId == nGlobalAnimId);
}
}
// adds an animation record to the animation set
void CryModelAnimationContainer::RegisterAnimation(const char * szFileName, int nGlobalAnimId, const char* szAnimName)
{
const CControllerManager::Animation& GlobalAnim = m_pControllerManager->GetAnimation (nGlobalAnimId);
m_fTicksPerFrame = (float)GlobalAnim.nTicksPerFrame;
m_fSecsPerTick = (float)GlobalAnim.fSecsPerTick;
float fSecsPerFrame = m_fSecsPerTick * m_fTicksPerFrame;
float fStart = GlobalAnim.rangeGlobal.start * fSecsPerFrame;
float fStop = GlobalAnim.rangeGlobal.end * fSecsPerFrame;
AnimData LocalAnim;
LocalAnim.fStart = fStart;
LocalAnim.fStop = fStop;
LocalAnim.bLoop = stristr(szFileName, "loop") != NULL;
LocalAnim.nGlobalAnimId = nGlobalAnimId;
LocalAnim.strName = szAnimName;
// hack to fix random goto default pose problem
if(LocalAnim.fStop <= LocalAnim.fStart)
LocalAnim.fStop = LocalAnim.fStart+1.0f/30.0f;
if(LocalAnim.fStart > LocalAnim.fStop)
{
g_GetLog()->LogToFile (" Invalid start(%g) > stop(%g) values in animation: %s", LocalAnim.fStart, LocalAnim.fStop, LocalAnim.strName.c_str());
return;
}
int nLocalAnimId = m_arrAnimations.size();
m_arrAnimations.push_back(LocalAnim);
m_arrAnimByGlobalId.insert (std::lower_bound(m_arrAnimByGlobalId.begin(), m_arrAnimByGlobalId.end(), nGlobalAnimId, AnimationGlobIdPred(m_arrAnimations)), LocalAnimId(nLocalAnimId));
m_arrAnimByLocalName.insert (std::lower_bound(m_arrAnimByLocalName.begin(), m_arrAnimByLocalName.end(), szAnimName, AnimationNamePred(m_arrAnimations)), nLocalAnimId);
selfValidate();
// The caller MUST TAKE CARE to bind the animation if it's already loaded before it has registered itself within this manager
m_pControllerManager->AnimationAddRef(nGlobalAnimId, this);
}
//////////////////////////////////////////////////////////////////////////
// Loads animation file. Returns the global anim id of the file, or -1 if error
// SIDE EFFECT NOTES:
// THis function does not put up a warning in the case the animation couldn't be loaded.
// It returns an error (false) and the caller must process it.
int CryModelAnimationContainer::LoadCAF(const char * szFileName, float fScale, int nAnimID, const char * szAnimName, unsigned nGlobalAnimFlags)
{
int nGlobalAnimID;
int nLocalAnimId = findAnimation (szAnimName);
if (nLocalAnimId == -1)
{
AUTO_PROFILE_SECTION(g_dTimeAnimLoadFile);
// find already loaded controllers
nGlobalAnimID = m_pControllerManager->StartLoadAnimation (szFileName, fScale, nGlobalAnimFlags);
if (nGlobalAnimID < 0)
return nGlobalAnimID;
m_pControllerManager->GetAnimation(nGlobalAnimID).nFlags |= nGlobalAnimFlags;
RegisterAnimation(szFileName, nGlobalAnimID, szAnimName);
if (!g_GetCVars()->ca_AnimationDeferredLoad() || (nGlobalAnimFlags&GlobalAnimation::FLAGS_DISABLE_DELAY_LOAD))
{
if (g_GetCVars()->ca_Debug() && (g_GetCVars()->ca_AnimationUnloadDelay()>0 || !(nGlobalAnimFlags&GlobalAnimation::FLAGS_DISABLE_AUTO_UNLOAD)))
g_GetLog()->LogWarning ("\003Performance penalty: animation deferred load is disabled, but automatic animation unload is enabled. This will most surely lead to poor performance upon loading the level (because everything is loaded, more memory is used and massive precalculations are needed) and even some time after (because of animation controllers trashing) (animation %s)", szAnimName);
// deferred load is disabled, try to load this animation immediately
m_pControllerManager->LoadAnimation (nGlobalAnimID);
}
}
else
{
nGlobalAnimID = m_arrAnimations[nLocalAnimId].nGlobalAnimId;
g_GetLog()->LogError ("\003Trying to load animation with alias \"%s\" from file \"%s\" into the animation container. Such animation alias already exists and uses file \"%s\". Please use another animation alias.",
szAnimName,
szFileName,
m_pControllerManager->GetAnimation(nGlobalAnimID).strFileName.c_str());
}
return nGlobalAnimID;
}
// Returns the index of the animation in the set, -1 if there's no such animation
int CryModelAnimationContainer::Find (const char* szAnimationName)
{
if (szAnimationName[0] == '#')
return int(m_arrAnimations.size() + findMorphTarget (szAnimationName));
else
return findAnimation(szAnimationName);
}
//! Returns the index of the morph target in the set, -1 if there's no such morph target
int CryModelAnimationContainer::FindMorphTarget (const char* szMorphTarget)
{
return findMorphTarget(szMorphTarget);
}
// returns the index of the animation
int CryModelAnimationContainer::findAnimation (const char*szAnimationName)
{
int nResult = -1;
std::vector<int>::iterator it = std::lower_bound(m_arrAnimByLocalName.begin(), m_arrAnimByLocalName.end(), szAnimationName, AnimationNamePred(m_arrAnimations));
if (it != m_arrAnimByLocalName.end() && !stricmp(m_arrAnimations[*it].strName.c_str(),szAnimationName))
nResult = *it;
#ifdef _DEBUG
int nTestResult = -1;
AnimationArray::iterator itTest = m_arrAnimations.begin(), itTestEnd = m_arrAnimations.end();
for (; itTest != itTestEnd; ++itTest)
if(!stricmp(szAnimationName, itTest->strName.c_str()))
{
nTestResult = itTest - m_arrAnimations.begin();
break;
}
assert (nTestResult == nResult);
#endif
return nResult;
}
// returns the index of the morph target, in the indexation of the array of morph targets
int CryModelAnimationContainer::findMorphTarget (const char* szMorphTargetName)
{
for (int i = 0; i < (int)m_arrMorphTargets.size(); ++i)
if (!stricmp(m_arrMorphTargets[i].getNameCStr(), szMorphTargetName))
return i;
return -1;
}
// Returns the number of animations in this set
int CryModelAnimationContainer::Count()
{
return (int)(m_arrAnimations.size() + m_arrMorphTargets.size());
}
//! Returns the number of morph targets in the set
int CryModelAnimationContainer::CountMorphTargets()
{
return (int)m_arrMorphTargets.size();
}
// Returns the given animation length, in seconds
float CryModelAnimationContainer::GetLength (int nAnimationId)
{
if ((unsigned)nAnimationId < numAnimations())
{
m_pControllerManager->LoadAnimationInfo(m_arrAnimations[nAnimationId].nGlobalAnimId);
return m_arrAnimations[nAnimationId].getLength();
}
else
return 0;
}
//! Returns the given animation's start, in seconds; 0 if the id is invalid
float CryModelAnimationContainer::GetStart (int nAnimationId)
{
if ((unsigned)nAnimationId < numAnimations())
{
m_pControllerManager->LoadAnimationInfo(m_arrAnimations[nAnimationId].nGlobalAnimId);
return m_arrAnimations[nAnimationId].fStart;
}
else
return 0;
}
// Returns the given animation name
const char* CryModelAnimationContainer::GetName (int nAnimationId)
{
if (nAnimationId >=0)
{
if (nAnimationId < (int)m_arrAnimations.size())
return m_arrAnimations[nAnimationId].strName.c_str();
nAnimationId -= (int)m_arrAnimations.size();
if (nAnimationId < (int)m_arrMorphTargets.size())
return m_arrMorphTargets[nAnimationId].getNameCStr();
return "!ANIMATION ID OUT OF RANGE!";
}
else
return "!NEGATIVE ANIMATION ID!";
}
//! Returns the name of the morph target
const char* CryModelAnimationContainer::GetNameMorphTarget (int nMorphTargetId)
{
if (nMorphTargetId< 0)
return "!NEGATIVE MORPH TARGET ID!";
if ((unsigned)nMorphTargetId >= m_arrMorphTargets.size())
return "!MORPH TARGET ID OUT OF RANGE!";
return m_arrMorphTargets[nMorphTargetId].getNameCStr();
}
// Retrieves the animation loop flag
bool CryModelAnimationContainer::IsLoop (int nAnimationId)
{
if ((unsigned)nAnimationId < numAnimations())
{
m_pControllerManager->LoadAnimationInfo(m_arrAnimations[nAnimationId].nGlobalAnimId);
return m_arrAnimations[nAnimationId].bLoop;
}
else
return false;
}
// updates the physics info of the given lod from the given bone animation descriptor
void CryModelAnimationContainer::UpdateRootBonePhysics (const BONEANIM_CHUNK_DESC* pChunk, unsigned nChunkSize, int nLodLevel)
{
if (numBoneInfos())
getRootBoneInfo().UpdateHierarchyPhysics (pChunk, nChunkSize, nLodLevel);
}
// finds the bone by its name; returns the index of the bone, or -1 if not found
// TODO: for performance, we could once build a map name->index
int CryModelAnimationContainer::findBone (const char* szName)const
{
#ifdef _DEBUG
int nTestResult = -1;
TFixedArray<CryBoneInfo>::const_iterator it = m_arrBones.begin(), itEnd = it + m_arrBones.size();
for (; it != itEnd; ++it)
{
if (!strcmp(it->getNameCStr(), szName))
{
nTestResult = it - m_arrBones.begin();
break;
}
}
#endif
int nResult = find_in_map(m_mapBoneNameIndex, szName, -1);
#ifdef _DEBUG
assert (nResult == nTestResult);
#endif
return nResult;
}
void CryModelAnimationContainer::onBonesChanged()
{
m_mapBoneNameIndex.clear();
for (unsigned i = 0; i < m_arrBones.size(); ++i)
m_mapBoneNameIndex[m_arrBones[i].getNameCStr()] = i;
}
void CryModelAnimationContainer::onBonePhysicsChanged()
{
for (unsigned i = 0; i < m_arrBones.size(); ++i)
getBoneInfo(i).PostInitialize();
}
//! Performs post-initialization. This step is requred to initialize the pPhysGeom of the bones
//! After the bone has been loaded but before it is first used. When the bone is first loaded, pPhysGeom
//! is set to the value equal to the chunk id in the file where the physical geometry (BoneMesh) chunk is kept.
//! After those chunks are loaded, and chunk ids are mapped to the registered physical geometry objects,
//! call this function to replace pPhysGeom chunk ids with the actual physical geometry object pointers.
//! NOTE:
//! The entries of the map that were used are deleted upon exit
bool CryModelAnimationContainer::PostInitBonePhysGeom (ChunkIdToPhysGeomMap& mapChunkIdToPhysGeom, int nLodLevel)
{
// map each bone,
// and set the hasphysics to 1 if at least one bone has recognized and mapped its physical geometry
bool bHasPhysics = false;
for (unsigned i=0; i < numBoneInfos(); ++i)
{
if (getBoneInfo(i).PostInitPhysGeom (mapChunkIdToPhysGeom, nLodLevel))
// yes, this model has the physics
bHasPhysics = true;
}
return bHasPhysics;
}
//! Modifies the animation loop flag
void CryModelAnimationContainer::SetLoop (int nAnimationId, bool bIsLooped)
{
if ((unsigned)nAnimationId < numAnimations())
{
m_pControllerManager->LoadAnimationInfo(m_arrAnimations[nAnimationId].nGlobalAnimId);
m_arrAnimations[nAnimationId].bLoop = bIsLooped;
}
}
// returns the reference to the given animation , or the default animation, if the animation
// id is out of range
const AnimData &CryModelAnimationContainer::getAnimation (int nAnimationId) const
{
if ((size_t)nAnimationId < m_arrAnimations.size())
return m_arrAnimations[nAnimationId];
else
{
static const AnimData DefaultAnimation;
return DefaultAnimation;
}
}
// records statistics about the given animation (the id is the same as for getAnimation()):
// should be called upon ticking the animation
void CryModelAnimationContainer::OnAnimationTick(int nAnimationId)
{
m_pControllerManager->OnTickAnimation(getAnimation(nAnimationId).nGlobalAnimId);
}
void CryModelAnimationContainer::OnAnimationApply(int nAnimationId)
{
m_pControllerManager->OnApplyAnimation(getAnimation(nAnimationId).nGlobalAnimId);
}
// records statistics about the given animation (the id is the same as for getAnimation()):
// should be called upon starting the animation
void CryModelAnimationContainer::OnAnimationStart(int nAnimationId)
{
m_pControllerManager->OnStartAnimation(getAnimation(nAnimationId).nGlobalAnimId);
}
// returns the reference to the given morph target, or the default morph target if the
// morph target id is out of range
const CryGeomMorphTarget& CryModelAnimationContainer::getMorphTarget (int nMorphTargetId) const
{
if ((size_t)nMorphTargetId < m_arrMorphTargets.size())
return m_arrMorphTargets[nMorphTargetId];
else
{
assert (0);// this will lead to a memory leak, if it is called
static const CryGeomMorphTarget DefaultMorphTarget;
return DefaultMorphTarget;
}
}
// returns the number of morph targets
unsigned CryModelAnimationContainer::numMorphTargets() const
{
return (unsigned)m_arrMorphTargets.size();
}
// resets the morph target array, and allocates the given number of targets, with the given number of LODs each
void CryModelAnimationContainer::reinitMorphTargets (unsigned numMorphTargets, unsigned numLODs)
{
m_arrMorphTargets.reinit(numMorphTargets);
}
// prepares to load the specified number of CAFs by reserving the space for the controller pointers
void CryModelAnimationContainer::prepareLoadCAFs (unsigned nReserveAnimations)
{
nReserveAnimations += (unsigned)m_arrAnimations.size();
for (unsigned i = 0; i < numBoneInfos(); ++i)
{
CryBoneInfo::ControllerArray& arrControllers = getBoneInfo(i).m_arrControllers;
arrControllers.reserve (nReserveAnimations);
}
m_arrAnimations.reserve (nReserveAnimations);
m_arrAnimByGlobalId.reserve (nReserveAnimations);
m_arrAnimByLocalName.reserve (nReserveAnimations);
}
//! Deserializes the bones from the CCF chunk using serialization function from CryBoneInfo
//! THe serialized data follows the given header; the total size (including the header) is passed
bool CryModelAnimationContainer::loadCCGBones (const CCFBoneDescArrayHeader*pHeader, unsigned nSize)
{
unsigned nBone, numBones = pHeader->numBones;
if (numBones > 1000)
return false; // too many bones
m_arrBones.reinit (numBones);
const char* pBoneData = (const char*)(pHeader+1);
const char* pDataEnd = (const char*)pHeader+ nSize;
for (nBone = 0; nBone < numBones; ++nBone)
{
if (pBoneData >= pDataEnd)
return false;
CryBoneInfo& rBone = m_arrBones[nBone];
unsigned nReadBytes = rBone.Serialize(false, (void*)pBoneData, pDataEnd - pBoneData);
// we don't use the information from these pointers right now, because we bind physical geometry using bone id
rBone.m_PhysInfo[0].pPhysGeom = rBone.m_PhysInfo[1].pPhysGeom = NULL;
// we need the bone name to be in the lookup map
m_mapBoneNameIndex[rBone.getNameCStr()] = nBone;
if (!nReadBytes)
return false;
pBoneData += nReadBytes;
if (pBoneData > pDataEnd)
return false;
}
if (pBoneData != pDataEnd)
g_GetLog()->LogWarning ("\003CryModelAnimationContainer::deserializeBones: %d bytes left unread in the bone chunk", pDataEnd - pBoneData);
return true;
}
// scales the skeleton (its initial pose)
void CryModelAnimationContainer::scaleBones (float fScale)
{
for (unsigned nBone = 0; nBone < numBoneInfos(); ++nBone)
m_arrBones[nBone].scale (fScale);
}
void CryModelAnimationContainer::selfValidate()
{
#ifdef _DEBUG
assert (m_arrAnimByGlobalId.size() == m_arrAnimations.size());
assert (m_arrAnimByLocalName.size() == m_arrAnimations.size());
for (unsigned i = 0; i < m_arrAnimByGlobalId.size()-1; ++i)
{
assert (m_arrAnimations[m_arrAnimByGlobalId[i].nAnimId].nGlobalAnimId <= m_arrAnimations[m_arrAnimByGlobalId[i+1].nAnimId].nGlobalAnimId);
assert (stricmp(m_arrAnimations[m_arrAnimByLocalName[i]].strName.c_str(), m_arrAnimations[m_arrAnimByLocalName[i+1]].strName.c_str())<0);
}
#endif
}
size_t CryModelAnimationContainer::sizeofThis()const
{
size_t nSize = sizeof(*this);
for (AnimationArray::const_iterator itAnim = m_arrAnimations.begin(); itAnim != m_arrAnimations.end(); ++itAnim)
nSize += itAnim->sizeofThis();
nSize += sizeofArray(m_arrAnimByGlobalId);
nSize += sizeofArray(m_arrAnimByLocalName);
for (unsigned nBone = 0; nBone < m_arrBones.size(); ++nBone)
nSize += m_arrBones[nBone].sizeofThis();
for (unsigned nMorphSkin = 0; nMorphSkin < m_arrMorphSkins.size(); ++nMorphSkin)
nSize += m_arrMorphSkins[nMorphSkin].sizeofThis();
for (unsigned nMorphTarget = 0; nMorphTarget < m_arrMorphTargets.size(); ++nMorphTarget)
nSize += m_arrMorphTargets[nMorphTarget].sizeofThis();
nSize += sizeofArray(m_arrTempBoneIdToIndex);
for (std::set<string>::const_iterator it = m_setDummyAnimations.begin(); it != m_setDummyAnimations.end(); ++it)
nSize += it->capacity() + sizeof(*it);
return nSize;
}
// returns the number of animations that aren't shared
unsigned CryModelAnimationContainer::numUniqueAnimations()
{
unsigned numResult =0;
for (AnimationArray::const_iterator it = m_arrAnimations.begin(); it != m_arrAnimations.end(); ++it)
if (m_pControllerManager->GetAnimation(it->nGlobalAnimId).nRefCount == 1)
++numResult;
return numResult;
}
const AnimData* CryModelAnimationContainer::getAnimationInfo(unsigned i)
{
if(m_pControllerManager->LoadAnimationInfo(m_arrAnimations[i].nGlobalAnimId))
return &m_arrAnimations[i];
else
return NULL;
}
//! Unloads animation from memory
//! The client must take into account that the animation can be shared and, if unloaded, the other
//! character models (animation sets) will have to load it back to use.
void CryModelAnimationContainer::UnloadAnimation (int nAnimId)
{
if ((unsigned)nAnimId < m_arrAnimations.size())
{
m_pControllerManager->UnloadAnimation(m_arrAnimations[nAnimId].nGlobalAnimId);
}
}
//! Loads the animation data in memory. fWhenRequired is the timeout in seconds from current moment when
//! the animation data will actually be required
void CryModelAnimationContainer::StartLoadAnimation (int nAnimId, float fWhenRequired)
{
if ((unsigned)nAnimId < m_arrAnimations.size())
{
m_pControllerManager->LoadAnimation(m_arrAnimations[nAnimId].nGlobalAnimId);
}
}