Serious-Engine/Sources/Engine/Ska/ModelInstance.cpp
Ryan C. Gordon 24cb244d43 First attempt to hand-merge Ryan's Linux and Mac OS X port.
This was a _ton_ of changes, made 15 years ago, so there are probably some
problems to work out still.

Among others: Engine/Base/Stream.* was mostly abandoned and will need to be
re-ported.

Still, this is a pretty good start, and probably holds a world record for
lines of changes or something.  :)
2016-03-28 23:46:13 -04:00

1094 lines
31 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
#include <Engine/StdH.h>
#include <Engine/Ska/ModelInstance.h>
#include <Engine/Ska/Skeleton.h>
#include <Engine/Ska/Render.h>
#include <Engine/Ska/StringTable.h>
#include <Engine/Ska/ParsingSmbs.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Console.h>
#include <Engine/Math/Quaternion.h>
#include <Engine/Templates/DynamicStackArray.cpp>
#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/Stock_CMesh.h>
#include <Engine/Templates/Stock_CSkeleton.h>
#include <Engine/Templates/Stock_CAnimSet.h>
#include <Engine/Templates/Stock_CTextureData.h>
#include <Engine/Templates/Stock_CShader.h>
// does parser remember smc source files?
BOOL bRememberSourceFN = FALSE;
// pointer to model instance for parser
CModelInstance *_yy_mi = NULL;
// calculate fade factor of animation list in animqueue
FLOAT CalculateFadeFactor(AnimList &alList)
{
if(alList.al_fFadeTime==0) {
return 1.0f;
}
FLOAT fFadeFactor = (_pTimer->GetLerpedCurrentTick() - alList.al_fStartTime) / alList.al_fFadeTime;
return Clamp(fFadeFactor,0.0f,1.0f);
}
// create model instance
CModelInstance *CreateModelInstance(CTString strName)
{
CModelInstance *pmi = new CModelInstance;
pmi->SetName(strName);
return pmi;
}
void DeleteModelInstance(CModelInstance *pmi)
{
ASSERT(pmi!=NULL);
// if model instance is valid
if(pmi!=NULL) {
// Clear model instance
pmi->Clear();
// Delete model instance
delete pmi;
pmi = NULL;
}
}
// Parse smc file in existing model instance
void ParseSmcFile_t(CModelInstance &mi, const CTString &fnSmcFile)
{
// Clear given model instance before parsing
mi.Clear();
CTFileName fnFileName = fnSmcFile;
try {
fnFileName.RemoveApplicationPath_t();
} catch (char *) {
}
CTString strIncludeFile;
strIncludeFile.Load_t(fnFileName);
_yy_mi = &mi;
SMCPushBuffer(fnFileName, strIncludeFile, TRUE);
syyparse();
}
// Create model instance and parse smc file in it
CModelInstance *ParseSmcFile_t(const CTString &fnSmcFile)
{
_yy_mi = NULL;
// Create new model instance for parser
_yy_mi = CreateModelInstance("Temp");
// Parse given smc file
ParseSmcFile_t(*_yy_mi,fnSmcFile);
return _yy_mi;
}
CModelInstance::CModelInstance()
{
mi_psklSkeleton = NULL;
mi_iParentBoneID = -1;
mi_colModelColor = 0;
mi_vStretch = FLOAT3D(1,1,1);
mi_colModelColor = 0xFFFFFFFF;
mi_aqAnims.aq_Lists.SetAllocationStep(1);
mi_cmiChildren.SetAllocationStep(1);
memset(&mi_qvOffset,0,sizeof(QVect));
mi_qvOffset.qRot.q_w = 1;
mi_iCurentBBox = -1;
// set default all frames bbox
// mi_cbAllFramesBBox.SetName("All Frames Bounding box");
mi_cbAllFramesBBox.SetMin(FLOAT3D(-0.5,0,-0.5));
mi_cbAllFramesBBox.SetMax(FLOAT3D(0.5,2,0.5));
// Set default model instance name
// SetName("Noname");
}
CModelInstance::~CModelInstance()
{
}
// copy constructor
CModelInstance::CModelInstance(CModelInstance &miOther)
{
// Forbiden
ASSERT(FALSE);
}
void CModelInstance::operator=(CModelInstance &miOther)
{
// Forbiden
ASSERT(FALSE);
}
void CModelInstance::GetAllFramesBBox(FLOATaabbox3D &aabbox)
{
aabbox = FLOATaabbox3D(mi_cbAllFramesBBox.Min(),mi_cbAllFramesBBox.Max());
}
// fills curent colision box info
void CModelInstance::GetCurrentColisionBox(FLOATaabbox3D &aabbox)
{
ColisionBox &cb = GetCurrentColisionBox();
aabbox = FLOATaabbox3D(cb.Min(),cb.Max());
}
ColisionBox &CModelInstance::GetCurrentColisionBox()
{
ASSERT(mi_iCurentBBox>=0);
ASSERT(mi_iCurentBBox<mi_cbAABox.Count());
ASSERT(mi_cbAABox.Count()>0);
return mi_cbAABox[mi_iCurentBBox];
}
INDEX CModelInstance::GetColisionBoxIndex(INDEX iBoxID)
{
INDEX ctcb = mi_cbAABox.Count();
// for each existing box
for(SLONG icb=0;icb<ctcb;icb++) {
ColisionBox &cb = mi_cbAABox[icb];
// if this is searched box
if(cb.GetID() == iBoxID) {
// return index of box
return icb;
}
}
// colision box was not found, return default (0)
SKAASSERT(FALSE);
return 0;
}
ColisionBox &CModelInstance::GetColisionBox(INDEX icb)
{
ASSERT(icb>=0);
ASSERT(icb<mi_cbAABox.Count());
return mi_cbAABox[icb];
}
FLOAT3D CModelInstance::GetCollisionBoxMin(INDEX iCollisionBox/*=0*/)
{
INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0L, mi_cbAABox.Count()-1L);
FLOAT3D vMin = mi_cbAABox[ iCollisionBoxClamped].Min();
return vMin;
};
FLOAT3D CModelInstance::GetCollisionBoxMax(INDEX iCollisionBox/*=0*/)
{
INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0L, mi_cbAABox.Count()-1L);
FLOAT3D vMax = mi_cbAABox[ iCollisionBoxClamped].Max();
return vMax;
};
// returns HEIGHT_EQ_WIDTH, LENGHT_EQ_WIDTH or LENGHT_EQ_HEIGHT
INDEX CModelInstance::GetCollisionBoxDimensionEquality(INDEX iCollisionBox/*=0*/)
{
// if colision box does not exists
if(iCollisionBox>=mi_cbAABox.Count()) {
// give last colision box
iCollisionBox = mi_cbAABox.Count()-1;
}
// check if error is fixed
ASSERT(mi_cbAABox.Count()>iCollisionBox);
ColisionBox &cb = this->mi_cbAABox[iCollisionBox];
FLOAT fWeigth = cb.Max()(1) - cb.Min()(1);
FLOAT fHeight = cb.Max()(2) - cb.Min()(2);
FLOAT fLength = cb.Max()(3) - cb.Min()(3);
if(fLength == fHeight) {
return SKA_LENGTH_EQ_HEIGHT;
} else if(fHeight == fWeigth) {
return SKA_HEIGHT_EQ_WIDTH;
// default fLength == fWeight
} else {
return SKA_LENGTH_EQ_WIDTH;
}
};
// add colision box to model instance
void CModelInstance::AddColisionBox(CTString strName,FLOAT3D vMin,FLOAT3D vMax)
{
INDEX ctcb = mi_cbAABox.Count();
mi_cbAABox.Expand(ctcb+1);
ColisionBox &cb = mi_cbAABox[ctcb];
cb.SetName(strName);
cb.SetMin(vMin);
cb.SetMax(vMax);
mi_iCurentBBox = 0;
}
// remove colision box from model instance
void CModelInstance::RemoveColisionBox(INDEX iIndex)
{
INDEX ctcb = mi_cbAABox.Count();
INDEX icbNew = 0;
CStaticArray<struct ColisionBox> aColisionBoxesTemp;
aColisionBoxesTemp.New(ctcb-1);
for(INDEX icb=0;icb<ctcb;icb++) {
if(iIndex != icb) {
aColisionBoxesTemp[icbNew] = mi_cbAABox[icb];
icbNew++;
}
}
mi_cbAABox = aColisionBoxesTemp;
}
// add child to modelinstance
void CModelInstance::AddChild(CModelInstance *pmi, INDEX iParentBoneID /* = -1 */)
{
SKAASSERT(pmi!=NULL);
if(pmi==NULL) return;
mi_cmiChildren.Add(pmi);
if (iParentBoneID>0) {
pmi->SetParentBone(iParentBoneID);
}
}
// remove model instance child
void CModelInstance::RemoveChild(CModelInstance *pmi)
{
ASSERT(pmi!=this);
SKAASSERT(pmi!=NULL);
// aditional check
if(pmi==NULL) return;
if(pmi==this) return;
mi_cmiChildren.Remove(pmi);
}
// set new parent bone index
void CModelInstance::SetParentBone(INDEX iParentBoneID)
{
mi_iParentBoneID = iParentBoneID;
}
// Model instance offsets from parent model
void CModelInstance::SetOffset(FLOAT fOffset[6])
{
FLOAT3D fRot(fOffset[3],fOffset[4],fOffset[5]);
mi_qvOffset.qRot.FromEuler(fRot);
mi_qvOffset.vPos = FLOAT3D(fOffset[0],fOffset[1],fOffset[2]);
}
void CModelInstance::SetOffsetPos(FLOAT3D vPos)
{
mi_qvOffset.vPos = vPos;
}
void CModelInstance::SetOffsetRot(ANGLE3D aRot)
{
mi_qvOffset.qRot.FromEuler(aRot);
}
FLOAT3D CModelInstance::GetOffsetPos()
{
return mi_qvOffset.vPos;
}
ANGLE3D CModelInstance::GetOffsetRot()
{
ANGLE3D aRot;
FLOATmatrix3D mat;
mi_qvOffset.qRot.ToMatrix(mat);
DecomposeRotationMatrix(aRot,mat);
return aRot;
}
// Stretch model instance
void CModelInstance::StretchModel(const FLOAT3D &vStretch)
{
mi_vStretch = vStretch;
}
// Stretch model instance without attachments
void CModelInstance::StretchSingleModel(const FLOAT3D &vStretch)
{
mi_vStretch = vStretch;
// for each child of model instance
INDEX ctch = mi_cmiChildren.Count();
for(INDEX ich=0;ich<ctch;ich++) {
// set new stretch of model instance
CModelInstance &chmi = mi_cmiChildren[ich];
chmi.StretchSingleModel(FLOAT3D(1/vStretch(1),1/vStretch(2),1/vStretch(3)));
}
}
// Add mesh to ModelInstance
void CModelInstance::AddMesh_t(CTFileName fnMesh)
{
int ctMeshInst = mi_aMeshInst.Count();
mi_aMeshInst.Expand(ctMeshInst+1);
memset(&mi_aMeshInst[ctMeshInst],0,sizeof(mi_aMeshInst[ctMeshInst]));
mi_aMeshInst[ctMeshInst].mi_pMesh = _pMeshStock->Obtain_t(fnMesh);
}
// Add skeleton to ModelInstance
void CModelInstance::AddSkeleton_t(CTFileName fnSkeleton)
{
mi_psklSkeleton = _pSkeletonStock->Obtain_t(fnSkeleton);
}
// Add AnimSet to ModelInstance
void CModelInstance::AddAnimSet_t(CTFileName fnAnimSet)
{
CAnimSet *Anim = _pAnimSetStock->Obtain_t(fnAnimSet);
mi_aAnimSet.Add(Anim);
}
// Add texture to ModelInstance (if no mesh instance given, add texture to last mesh instance)
void CModelInstance::AddTexture_t(CTFileName fnTexture, CTString strTexID,MeshInstance *pmshi)
{
if(pmshi == NULL) {
INDEX ctMeshInst = mi_aMeshInst.Count();
if(ctMeshInst<=0) throw("Error adding texture\nMesh instance does not exists");
pmshi = &mi_aMeshInst[ctMeshInst-1];
}
INDEX ctTextInst = pmshi->mi_tiTextures.Count();
pmshi->mi_tiTextures.Expand(ctTextInst+1);
pmshi->mi_tiTextures[ctTextInst].ti_toTexture.SetData_t(fnTexture);
pmshi->mi_tiTextures[ctTextInst].SetName(strTexID);
}
// Remove one texture from model instance
void CModelInstance::RemoveTexture(TextureInstance *ptiRemove,MeshInstance *pmshi)
{
ASSERT(pmshi!=NULL);
CStaticArray<struct TextureInstance> atiTextures;
INDEX ctti=pmshi->mi_tiTextures.Count();
atiTextures.New(ctti-1);
// for each texture instance in mesh instance
INDEX iIndexSrc=0;
for(INDEX iti=0;iti<ctti;iti++)
{
TextureInstance *pti = &pmshi->mi_tiTextures[iti];
// if texture instance is different from selected one
if(pti != ptiRemove) {
// copy it to new array of texture isntances
atiTextures[iIndexSrc] = pmshi->mi_tiTextures[iti];
iIndexSrc++;
}
}
// copy new texture instances array in mesh instance
pmshi->mi_tiTextures.CopyArray(atiTextures);
// clear temp texture isntances array
atiTextures.Clear();
}
// Find texture instance in all mesh instances in model instance
TextureInstance *CModelInstance::FindTexureInstance(INDEX iTexID)
{
// for each mesh instance
INDEX ctmshi = mi_aMeshInst.Count();
for(INDEX imshi=0;imshi<ctmshi;imshi++) {
MeshInstance &mshi = mi_aMeshInst[imshi];
// for each texture instance in meshinstance
INDEX ctti = mshi.mi_tiTextures.Count();
for(INDEX iti=0;iti<ctti;iti++) {
TextureInstance &ti = mshi.mi_tiTextures[iti];
// if this is texinstance that is beeing serched for
if(ti.GetID() == iTexID) {
// return it
return &ti;
}
}
}
// texture instance wasn't found
return NULL;
}
// Find texture instance in given mesh instance
TextureInstance *CModelInstance::FindTexureInstance(INDEX iTexID, MeshInstance &mshi)
{
// for each texture instance in given mesh instance
INDEX ctti = mshi.mi_tiTextures.Count();
for(INDEX iti=0;iti<ctti;iti++) {
TextureInstance &ti = mshi.mi_tiTextures[iti];
// if this is texinstance that is beeing serched for
if(ti.GetID() == iTexID) {
// return it
return &ti;
}
}
// texture instance wasn't found
return NULL;
}
// change parent of model instance
void CModelInstance::ChangeParent(CModelInstance *pmiOldParent, CModelInstance *pmiNewParent)
{
SKAASSERT(pmiOldParent!=NULL);
SKAASSERT(pmiNewParent!=NULL);
if(pmiOldParent == NULL) {
CPrintF("Model Instance doesn't have a parent\n");
return;
}
if(pmiNewParent == NULL) {
CPrintF("New parent of model instance is NULL\n");
return;
}
pmiOldParent->mi_cmiChildren.Remove(this);
pmiNewParent->mi_cmiChildren.Add(this);
}
// return parent of this model instance
// must suply first model instance in hierarchy cos model instance does not have its parent remembered
CModelInstance *CModelInstance::GetParent(CModelInstance *pmiStartFrom)
{
ASSERT(pmiStartFrom!=NULL);
// aditional check
if(pmiStartFrom==NULL) return NULL;
// if 'this' is member of pmiStartFrom return it
if(pmiStartFrom->mi_cmiChildren.IsMember(this)) {
return pmiStartFrom;
}
// count childrent of pmiStartFrom
INDEX ctcmi = pmiStartFrom->mi_cmiChildren.Count();
// for each child of pmiStartFrom
for(INDEX icmi=0;icmi<ctcmi;icmi++) {
// if some of children have 'this' as member return them as parent
CModelInstance *pmiReturned = GetParent(&pmiStartFrom->mi_cmiChildren[icmi]);
if(pmiReturned != NULL) {
return pmiReturned;
}
}
return NULL;
}
// returns child with specified id
/*CModelInstance *CModelInstance::GetChild(INDEX iChildID, BOOL bRecursive)
{
INDEX ctcmi = mi_cmiChildren.Count();
for(INDEX icmi=0;icmi<ctcmi;icmi++) {
CModelInstance *pmi = &mi_cmiChildren[icmi];
if(pmi->mi_iModelID == iChildID) {
return pmi;
}
}
return NULL;
}*/
CModelInstance *CModelInstance::GetChild(INDEX iChildID, BOOL bRecursive/*=FALSE*/)
{
INDEX ctcmi = mi_cmiChildren.Count();
for(INDEX icmi=0;icmi<ctcmi;icmi++) {
CModelInstance *pmi = &mi_cmiChildren[icmi];
if(pmi->mi_iModelID == iChildID) {
return pmi;
}
// if child has own children, go recursive
if(bRecursive && pmi->mi_cmiChildren.Count()>0) {
pmi = pmi->GetChild(iChildID, TRUE);
if (pmi!=NULL) return pmi;
}
}
return NULL;
}
// returns parent that is not included in his parents smc file
CModelInstance *CModelInstance::GetFirstNonReferencedParent(CModelInstance *pmiRoot)
{
ASSERT(this!=NULL);
ASSERT(pmiRoot!=NULL);
CModelInstance *pmiParent = this->GetParent(pmiRoot);
CModelInstance *pmiLast = this;
while(pmiParent != NULL)
{
if(pmiParent->mi_fnSourceFile != mi_fnSourceFile)
{
return pmiLast;
}
pmiLast = pmiParent;
pmiParent = pmiParent->GetParent(pmiRoot);
}
return NULL;//return pmiRoot
}
// add animation to ModelInstance
void CModelInstance::AddAnimation(INDEX iAnimID, ULONG ulFlags, FLOAT fStrength, INDEX iGroupID, FLOAT fSpeedMul/*=1.0f*/)
{
#ifdef SKADEBUG
// see whether this animation even exists in the current skeleton
INDEX iDummy1, iDummy2;
if (!FindAnimationByID(iAnimID, &iDummy1, &iDummy2)) {
/*this ModelInstance does not contain the required animation!!!*/
SKAASSERT(FALSE);
}
#endif
fSpeedMul = 1/fSpeedMul;
// if no restart flag was set
if(ulFlags&AN_NORESTART) {
// if given animtion is allready playing
if(IsAnimationPlaying(iAnimID)) {
if(ulFlags&AN_LOOPING) {
AddFlagsToPlayingAnim(iAnimID,AN_LOOPING);
}
// return without adding animtion
return;
}
}
// if flag for new cleared state is set
if(ulFlags&AN_CLEAR) {
// do new clear state with default length
NewClearState(CLEAR_STATE_LENGTH);
// if flag for new cloned state is set
} else if(ulFlags&AN_CLONE) {
// do new clear state with default length
NewClonedState(CLONED_STATE_LENGTH);
}
// if anim queue is empty
INDEX ctal = mi_aqAnims.aq_Lists.Count();
if(ctal == 0) {
// add new clear state
NewClearState(0);
}
ctal = mi_aqAnims.aq_Lists.Count();
AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
// if flag is set not to sort anims
if(ulFlags&AN_NOGROUP_SORT) {
// just add new animations to end of list
PlayedAnim &plAnim = alList.al_PlayedAnims.Push();
plAnim.pa_iAnimID = iAnimID;
plAnim.pa_fSpeedMul = fSpeedMul;
plAnim.pa_fStartTime = _pTimer->CurrentTick();
plAnim.pa_Strength = fStrength;
plAnim.pa_ulFlags = ulFlags;
plAnim.pa_GroupID = iGroupID;
// no flag set, sort animation by groupID
} else {
// add one animation to anim list
alList.al_PlayedAnims.Push();
INDEX ctpa = alList.al_PlayedAnims.Count();
INDEX ipa=ctpa-1;
if(ipa>0) {
// for each old animation from last to first
for(;ipa>0;ipa--) {
PlayedAnim &pa = alList.al_PlayedAnims[ipa-1];
PlayedAnim &paNext = alList.al_PlayedAnims[ipa];
// if anim group id is larger than new group id
if(pa.pa_GroupID>iGroupID) {
// move animation in array to right
paNext = pa;
} else break;
}
}
// set new animation as current index in anim list
PlayedAnim &plAnim = alList.al_PlayedAnims[ipa];
plAnim.pa_iAnimID = iAnimID;
plAnim.pa_fSpeedMul = fSpeedMul;
plAnim.pa_fStartTime = _pTimer->CurrentTick();
plAnim.pa_Strength = fStrength;
plAnim.pa_ulFlags = ulFlags;
plAnim.pa_GroupID = iGroupID;
}
}
// remove played anim from stack
void CModelInstance::RemAnimation(INDEX iAnimID)
{
INDEX ctal = mi_aqAnims.aq_Lists.Count();
// if anim queue is empty
if(ctal < 1) {
SKAASSERT(FALSE);
// no anim to remove
return;
}
// get last anim list in queue
AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
// count played anims in anim list
INDEX ctpa = alList.al_PlayedAnims.Count();
// loop each played anim in anim list
for(int ipa=0;ipa<ctpa;ipa++) {
PlayedAnim &paAnim = alList.al_PlayedAnims[ipa];
// remove if same ID
if(paAnim.pa_iAnimID == iAnimID) {
// copy all latter anims over this one
for(int ira=ipa;ira<ctpa-1;ira++) {
alList.al_PlayedAnims[ira] = alList.al_PlayedAnims[ira+1];
}
// decrease played anims count
ctpa--;
// remove last anim
alList.al_PlayedAnims.Pop();
}
}
}
// Remove all anims with GroupID
void CModelInstance::RemAnimsWithID(INDEX iGroupID)
{
INDEX ctal = mi_aqAnims.aq_Lists.Count();
// if anim queue is empty
if(ctal < 1) {
SKAASSERT(FALSE);
// no anim to remove
return;
}
// get last anim list in queue
AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
// count played anims in anim list
INDEX ctpa = alList.al_PlayedAnims.Count();
// loop each played anim in anim list
int ipa;
for(ipa=0;ipa<ctpa;ipa++) {
PlayedAnim &paAnim = alList.al_PlayedAnims[ipa];
// remove if same Group ID
if(paAnim.pa_GroupID == iGroupID) {
// copy all latter anims over this one
for(int ira=ipa;ira<ctpa-1;ira++) {
alList.al_PlayedAnims[ira] = alList.al_PlayedAnims[ira+1];
}
// decrease played anims count
ctpa--;
// remove last anim
alList.al_PlayedAnims.Pop();
}
}
}
// remove unused anims from queue
void CModelInstance::RemovePassedAnimsFromQueue()
{
// count AnimLists
INDEX ctal = mi_aqAnims.aq_Lists.Count();
// find newes animlist that has fully faded in
INDEX iFirstAnimList = -1;
// for each anim list from last to first
INDEX ial;
for(ial=ctal-1;ial>=0;ial--)
{
AnimList &alList = mi_aqAnims.aq_Lists[ial];
// calculate fade factor for this animlist
FLOAT fFadeFactor = CalculateFadeFactor(alList);
// if factor is 1 remove all animlists before this one
if(fFadeFactor >= 1.0f) {
iFirstAnimList = ial;
break;
}
}
if(iFirstAnimList <= 0) return;
// move later anim lists to first pos
for(ial=iFirstAnimList;ial<ctal;ial++)
{
mi_aqAnims.aq_Lists[ial-iFirstAnimList] = mi_aqAnims.aq_Lists[ial];
mi_aqAnims.aq_Lists[ial].al_PlayedAnims.PopAll();
}
// remove all Anim list before iFirstAnimList
mi_aqAnims.aq_Lists.PopUntil(ctal-iFirstAnimList-1);
}
// create new state, copy last state in it and give it a fade time
void CModelInstance::NewClonedState(FLOAT fFadeTime)
{
RemovePassedAnimsFromQueue();
INDEX ctal = mi_aqAnims.aq_Lists.Count();
if(ctal == 0)
{
// if anim queue is empty add new clear state
NewClearState(fFadeTime);
ctal = 1;
}
// create new Anim list
AnimList &alNewList = mi_aqAnims.aq_Lists.Push();
alNewList.al_PlayedAnims.SetAllocationStep(1);
AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
// copy anims to new List
alNewList.al_PlayedAnims = alList.al_PlayedAnims;
alNewList.al_fFadeTime = fFadeTime;
alNewList.al_fStartTime = _pTimer->CurrentTick();
}
// create new cleared state and give it a fade time
void CModelInstance::NewClearState(FLOAT fFadeTime)
{
RemovePassedAnimsFromQueue();
// add new empty list
AnimList &alNewList = mi_aqAnims.aq_Lists.Push();
alNewList.al_PlayedAnims.SetAllocationStep(1);
alNewList.al_fFadeTime = fFadeTime;
alNewList.al_fStartTime = _pTimer->CurrentTick();
alNewList.al_PlayedAnims.PopAll();
}
// stop all animations in this model instance and its children
void CModelInstance::StopAllAnimations(FLOAT fFadeTime)
{
INDEX ctmi = mi_cmiChildren.Count();
for(INDEX imi=0;imi<ctmi;imi++)
{
CModelInstance &cmi = mi_cmiChildren[imi];
cmi.StopAllAnimations(fFadeTime);
}
NewClearState(fFadeTime);
}
// Offset all animations in anim queue
void CModelInstance::OffSetAnimationQueue(TIME fOffsetTime)
{
// for each anim list in anim queue
INDEX ctal = mi_aqAnims.aq_Lists.Count();
for(INDEX ial=0;ial<ctal;ial++) {
AnimList &al = mi_aqAnims.aq_Lists[ial];
// Modify anim list start time
al.al_fStartTime +=fOffsetTime;
}
}
// Find animation by ID
BOOL CModelInstance::FindAnimationByID(int iAnimID,INDEX *piAnimSetIndex,INDEX *piAnimIndex)
{
INDEX ctas = mi_aAnimSet.Count();
if (ctas<=0) return FALSE;
// for each animset
for(int ias=ctas-1;ias>=0;ias--) {
CAnimSet &asAnimSet = mi_aAnimSet[ias];
INDEX ctan = asAnimSet.as_Anims.Count();
// for each animation
for(int ian=0;ian<ctan;ian++) {
Animation &an = asAnimSet.as_Anims[ian];
// if this is animation to find
if(an.an_iID == iAnimID) {
// set pointers of indices to animset and animation
*piAnimSetIndex = ias;
*piAnimIndex = ian;
// retrun succesfully
return TRUE;
}
}
}
// animation was't found
return FALSE;
}
// Find animation by ID
INDEX CModelInstance::FindFirstAnimationID()
{
INDEX ctas = mi_aAnimSet.Count();
// for each animset
for(int ias=0; ias<ctas; ias--) {
CAnimSet &asAnimSet = mi_aAnimSet[ias];
INDEX ctan = asAnimSet.as_Anims.Count();
// for each animation
for(int ian=0;ian<ctan;ian++) {
Animation &an = asAnimSet.as_Anims[ian];
return an.an_iID;
}
}
// never should get here
return -1;
}
// get animation length
FLOAT CModelInstance::GetAnimLength(INDEX iAnimID)
{
INDEX iAnimSetIndex,iAnimIndex;
FindAnimationByID(iAnimID,&iAnimSetIndex,&iAnimIndex);
CAnimSet &as = mi_aAnimSet[iAnimSetIndex];
Animation &an = as.as_Anims[iAnimIndex];
return an.an_fSecPerFrame * an.an_iFrames;
}
// Check if given animation is currently playing
BOOL CModelInstance::IsAnimationPlaying(INDEX iAnimID)
{
// check last anim list if animation iAnimID exists in it
INDEX ctal = mi_aqAnims.aq_Lists.Count();
// if there are anim lists in animqueue
if(ctal>0) {
// check last one
AnimList &al = mi_aqAnims.aq_Lists[ctal-1];
INDEX ctpa = al.al_PlayedAnims.Count();
for(INDEX ipa=0;ipa<ctpa;ipa++) {
PlayedAnim &pa = al.al_PlayedAnims[ipa];
if(pa.pa_iAnimID == iAnimID) {
// found it
return TRUE;
}
}
}
// this animation is currently not playing
return FALSE;
}
// Add flags to animation playing in anim queue
BOOL CModelInstance::AddFlagsToPlayingAnim(INDEX iAnimID, ULONG ulFlags)
{
// check last anim list if animation iAnimID exists in it
INDEX ctal = mi_aqAnims.aq_Lists.Count();
// if there are anim lists in animqueue
if(ctal>0) {
// check last one
AnimList &al = mi_aqAnims.aq_Lists[ctal-1];
INDEX ctpa = al.al_PlayedAnims.Count();
for(INDEX ipa=0;ipa<ctpa;ipa++) {
PlayedAnim &pa = al.al_PlayedAnims[ipa];
if(pa.pa_iAnimID == iAnimID) {
pa.pa_ulFlags |= ulFlags;
// found it
return TRUE;
}
}
}
// this animation is currently not playing
return FALSE;
}
// Sets name of model instance
void CModelInstance::SetName(CTString strName)
{
mi_strName = strName;
mi_iModelID = ska_GetIDFromStringTable(strName);
}
// Gets name of model instance
const CTString &CModelInstance::GetName()
{
return mi_strName;
}
// Gets id of model instance
const INDEX &CModelInstance::GetID()
{
return mi_iModelID;
}
// Get vertex positions in absolute space
void CModelInstance::GetModelVertices( CStaticStackArray<FLOAT3D> &avVertices, FLOATmatrix3D &mRotation,
FLOAT3D &vPosition, FLOAT fNormalOffset, FLOAT fDistance)
{
RM_SetObjectPlacement(mRotation,vPosition);
RM_GetModelVertices(*this,avVertices, mRotation, vPosition, fNormalOffset, fDistance);
}
// Model color
COLOR &CModelInstance::GetModelColor(void)
{
return mi_colModelColor;
}
void CModelInstance::SetModelColor(COLOR colNewColor)
{
mi_colModelColor = colNewColor;
INDEX ctch = mi_cmiChildren.Count();
// for each child
for(INDEX ich=0;ich<ctch;ich++) {
CModelInstance &cmi = mi_cmiChildren[ich];
// set child model color
cmi.SetModelColor(colNewColor);
}
}
// test it the model has alpha blending
BOOL CModelInstance::HasAlpha(void)
{
return (GetModelColor()&0xFF)!=0xFF;
}
BOOL CModelInstance::IsModelVisible( FLOAT fMipFactor)
{
#pragma message(">> IsModelVisible")
return TRUE;
}
BOOL CModelInstance::HasShadow(FLOAT fMipFactor)
{
#pragma message(">> HasShadow")
return TRUE;
}
// simple shadow rendering
void CModelInstance::AddSimpleShadow( const FLOAT fIntensity, const FLOATplane3D &plShadowPlane)
{
// if shadows are not rendered for current mip, model is half/full face-forward,
// intensitiy is too low or projection is not perspective - do nothing!
//if( !HasShadow(1) || fIntensity<0.01f || !_aprProjection.IsPerspective()
// || (rm.rm_pmdModelData->md_Flags&(MF_FACE_FORWARD|MF_HALF_FACE_FORWARD))) return;
// ASSERT( _iRenderingType==1);
ASSERT( fIntensity>0 && fIntensity<=1);
// do some rendering
// _pfModelProfile.StartTimer( CModelProfile::PTI_RENDERSIMPLESHADOW);
// _pfModelProfile.IncrementTimerAveragingCounter( CModelProfile::PTI_RENDERSIMPLESHADOW);
// _sfStats.IncrementCounter( CStatForm::SCI_MODELSHADOWS);
// calculate projection model bounding box in object space (if needed)
// CalculateBoundingBox( this, rm);
// add one simple shadow to batch list
RM_SetObjectMatrices(*this);
RM_AddSimpleShadow_View(*this, fIntensity, plShadowPlane);
// all done
// _pfModelProfile.StopTimer( CModelProfile::PTI_RENDERSIMPLESHADOW);
}
// Copy mesh instance for other model instance
void CModelInstance::CopyMeshInstance(CModelInstance &miOther)
{
INDEX ctmshi = miOther.mi_aMeshInst.Count();
// for each mesh insntace
for(INDEX imshi=0;imshi<ctmshi;imshi++) {
MeshInstance &mshiOther = miOther.mi_aMeshInst[imshi];
// add its mesh
AddMesh_t(mshiOther.mi_pMesh->GetName());
MeshInstance *pmshi = &mi_aMeshInst[imshi];
INDEX ctti = mshiOther.mi_tiTextures.Count();
// for each texture in mesh instance
for(INDEX iti=0;iti<ctti;iti++) {
TextureInstance &tiOther = mshiOther.mi_tiTextures[iti];
CTString strTexID = ska_GetStringFromTable(tiOther.GetID());
// CTextureData *ptd = (CTextureData*)tiOther.ti_toTexture.GetData();
AddTexture_t(tiOther.ti_toTexture.GetName(),strTexID,pmshi);
}
}
}
// copy from another object of same class
void CModelInstance::Copy(CModelInstance &miOther)
{
// clear this instance - otherwise it won't work
Clear();
mi_aqAnims.aq_Lists = miOther.mi_aqAnims.aq_Lists;
mi_iCurentBBox = miOther.mi_iCurentBBox;
mi_colModelColor = miOther.mi_colModelColor;
mi_iParentBoneID = miOther.mi_iParentBoneID;
mi_qvOffset = miOther.mi_qvOffset;
mi_strName = miOther.mi_strName;
mi_iModelID = miOther.mi_iModelID;
mi_cbAABox = miOther.mi_cbAABox;
mi_fnSourceFile = miOther.mi_fnSourceFile;
mi_vStretch = miOther.mi_vStretch;
// copt mesh instance
CopyMeshInstance(miOther);
// if skeleton exists
if(miOther.mi_psklSkeleton!=NULL) {
// copy skeleton
AddSkeleton_t(miOther.mi_psklSkeleton->GetName());
}
// copy animsets
INDEX ctas = miOther.mi_aAnimSet.Count();
// for each animset
for(INDEX ias=0;ias<ctas;ias++) {
// add animset to this model instance
CAnimSet &asOther = miOther.mi_aAnimSet[ias];
AddAnimSet_t(asOther.GetName());
}
// copy children
INDEX ctch = miOther.mi_cmiChildren.Count();
// for each child in other model instance
for(INDEX ich=0;ich<ctch;ich++) {
CModelInstance &chmiOther = miOther.mi_cmiChildren[ich];
CModelInstance *pchmi = CreateModelInstance("Temp");
pchmi->Copy(chmiOther);
AddChild(pchmi);
}
}
// Synchronize with another model (copy animations/attachments positions etc from there)
void CModelInstance::Synchronize(CModelInstance &miOther)
{
// Sync animations
mi_aqAnims.aq_Lists = miOther.mi_aqAnims.aq_Lists;
// Sync misc params
mi_qvOffset = miOther.mi_qvOffset;
mi_iParentBoneID = miOther.mi_iParentBoneID;
mi_colModelColor = miOther.mi_colModelColor;
mi_vStretch = miOther.mi_vStretch;
// for each child in model instance
INDEX ctchmi=mi_cmiChildren.Count();
for(INDEX ichmi=0;ichmi<ctchmi;ichmi++) {
CModelInstance &chmi = mi_cmiChildren[ichmi];
CModelInstance *pchmiOther = miOther.GetChild(chmi.GetID(),FALSE);
// if both model instance have this child
if(pchmiOther!=NULL) {
// sync child
chmi.Synchronize(*pchmiOther);
}
}
}
// clear model instance
void CModelInstance::Clear(void)
{
// for each child of this model instance
INDEX ctcmi = mi_cmiChildren.Count();
for(INDEX icmi=0; icmi<ctcmi; icmi++) {
// delete child
CModelInstance *pcmi = &mi_cmiChildren[0];
mi_cmiChildren.Remove(pcmi);
DeleteModelInstance(pcmi);
}
// release all meshes in stock used by mi
INDEX ctmshi = mi_aMeshInst.Count();
for(INDEX imshi=0; imshi<ctmshi; imshi++) {
MeshInstance &mshi = mi_aMeshInst[imshi];
CMesh *pmesh = mshi.mi_pMesh;
if(pmesh != NULL) {
_pMeshStock->Release(pmesh);
}
// release all textures in stock used by mesh
INDEX ctti = mshi.mi_tiTextures.Count();
for(INDEX iti=0;iti<ctti;iti++) {
TextureInstance &ti = mshi.mi_tiTextures[iti];
ti.ti_toTexture.SetData(NULL);
}
}
mi_aMeshInst.Clear();
// release skeleton in stock used by mi(if it exist)
if(mi_psklSkeleton != NULL) {
_pSkeletonStock->Release(mi_psklSkeleton);
mi_psklSkeleton = NULL;
}
// release all animsets in stock used by mi
INDEX ctas = mi_aAnimSet.Count();
for(INDEX ias=0;ias<ctas;ias++) {
_pAnimSetStock->Release(&mi_aAnimSet[ias]);
}
mi_aAnimSet.Clear();
// clear all colision boxes
mi_cbAABox.Clear();
// clear anim list
mi_aqAnims.aq_Lists.Clear();
}
// Count used memory
SLONG CModelInstance::GetUsedMemory(void)
{
SLONG slMemoryUsed = sizeof(*this);
// Count mesh instances
INDEX ctmshi = mi_aMeshInst.Count();
for(INDEX imshi=0;imshi<ctmshi;imshi++) {
MeshInstance &mshi = mi_aMeshInst[imshi];
slMemoryUsed += mshi.mi_tiTextures.Count() * sizeof(TextureInstance);
}
slMemoryUsed += mi_aMeshInst.Count() * sizeof(MeshInstance);
// Count bounding boxes
slMemoryUsed += mi_cbAABox.Count() * sizeof(ColisionBox);
// Cound child model instances
INDEX ctcmi = mi_cmiChildren.Count();
for(INDEX icmi=0;icmi<ctcmi;icmi++) {
CModelInstance &cmi = mi_cmiChildren[icmi];
slMemoryUsed += cmi.GetUsedMemory();
}
return slMemoryUsed;
}
// set parser to remember file names for modelinstances
void CModelInstance::EnableSrcRememberFN(BOOL bEnable)
{
bRememberSourceFN = bEnable;
}