/* Copyright (C) 2010 Matthew Baranowski, Sander van Rossen & Raven software. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "system.h" #include "ndictionary.h" #include "md3gl.h" #include "md3view.h" #include "oddbits.h" #include #include "animation.h" using namespace std; int Model_EnsureCurrentFrameLegal(gl_model* model, int iNewFrame); double getDoubleTime (void); /* sets all model frames to 0 */ void rewindAnim() { NodePosition pos; gl_model *model; for (pos=mdview.modelList->first() ; pos!=NULL ; pos=mdview.modelList->after(pos)) { model = (gl_model *)pos->element(); model->currentFrame = 0; model->currentFrame = Model_EnsureCurrentFrameLegal(model, model->currentFrame); } // for (unsigned int i=0; iHeader.Mesh_num ; i++) { // mdview.frames[i]=0; // } } /* exectues every frame */ void animation_loop() { NodePosition pos; gl_model *model; if (mdview.modelList->isEmpty()) return; bool bModelUpdated = false; // // sanity check, legalise any locked frames in case they were missed elsewhere (dunno why/how that occurs, but...) // for (pos=mdview.modelList->first() ; pos!=NULL ; pos=mdview.modelList->after(pos)) { model = (gl_model *)pos->element(); int iFrame = Model_EnsureCurrentFrameLegal(model, model->currentFrame); if (model->currentFrame != iFrame) { model->currentFrame = iFrame; bModelUpdated = true; } } if (!mdview.animate) { if (mdview.interpolate) mdview.frameFrac = 0.5f; else mdview.frameFrac = 0; //if (bModelUpdated) { render_mdview(); swap_buffers(); } return; } double timeStamp2 = getDoubleTime(); mdview.frameFrac = (float)((timeStamp2 - mdview.timeStamp1) / mdview.animSpeed); if (mdview.frameFrac > 1.f) { mdview.frameFrac = 0; mdview.timeStamp1 = timeStamp2; for (pos=mdview.modelList->first() ; pos!=NULL ; pos=mdview.modelList->after(pos)) { model = (gl_model *)pos->element(); model->currentFrame = GetNextFrame_MultiLocked(model, model->currentFrame, 1); // note legalise before generic wrap, otherwise last sequence won't loop (if relevant) // model->currentFrame = Model_EnsureCurrentFrameLegal(model, model->currentFrame); if (model->currentFrame == model->iNumFrames) // itu? model->currentFrame = 0; } render_mdview(); swap_buffers(); } else if (mdview.interpolate || bModelUpdated) { render_mdview(); swap_buffers(); } } // returns index else -1 for not frame-not-found-in-sequences... // int GetMultiLockedSequenceIndexFromFrame(int iFrame, bool bIsUpper ) { MultiSequenceLock_t* pMultiLock = (bIsUpper)?&MultiSequenceLock_Upper:&MultiSequenceLock_Lower; MultiSequenceLock_t::iterator it; int iIndex=0; for (it = pMultiLock->begin(); it != pMultiLock->end(); ++it, iIndex++) { int iSeqIndex = *it; Sequence_t* pSeq = (bIsUpper)?Animation_GetUpperSequence(iSeqIndex):Animation_GetLowerSequence(iSeqIndex); assert(pSeq); if (pSeq) { int iSeqFrameFirst = pSeq->iTargetFrame; int iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1; if (iFrame >= iSeqFrameFirst && iFrame <= iSeqFrameLast) return iIndex; } } return -1; } Sequence_t* GetMultiLockedSequenceFromFrame(int iFrame, bool bIsUpper ) { int iMultiLockSequenceIndex = GetMultiLockedSequenceIndexFromFrame( iFrame, bIsUpper ); if (iMultiLockSequenceIndex == -1) return NULL; MultiSequenceLock_t* pMultiLock = (bIsUpper)?&MultiSequenceLock_Upper:&MultiSequenceLock_Lower; int iIndex = pMultiLock->at(iMultiLockSequenceIndex); return bIsUpper?Animation_GetUpperSequence(iIndex):Animation_GetLowerSequence(iIndex); } Sequence_t *GetLockedSequence_Upper(gl_model* model) { if (model && (model == pModel_Upper || model->pModel_LOD0 == pModel_Upper) && iAnimLockNumber_Upper) { return Animation_GetUpperSequence( iAnimLockNumber_Upper-1 ); } return NULL; } Sequence_t *GetLockedSequence_Lower(gl_model* model) { if (model && (model == pModel_Lower || model->pModel_LOD0 == pModel_Lower) && iAnimLockNumber_Lower) { return Animation_GetLowerSequence( iAnimLockNumber_Lower-1 ); } return NULL; } // Another uberhack, sigh.... // // gets the next frame in a multilocked sequence, where iStepVal can be +ve or -ve, (though usually just -1/0/1) // // (this isn't staggeringly fast at certain points, but 99% of the time it executes quickly) // int GetNextFrame_MultiLocked(gl_model* pModel, int iFrame, int iStepVal) { Sequence_t* pSeq = NULL; bool bIsUpper = false; if ((pSeq = GetLockedSequence_Upper(pModel)) != NULL) bIsUpper = true; else if ((pSeq = GetLockedSequence_Lower(pModel)) != NULL) bIsUpper = false; if (pSeq && pSeq->bMultiSeq) { int iNewFrame = iFrame; int iStep = (iStepVal<0)?-1:1; for (int iStepValRemaining = iStepVal; iStepValRemaining!=0; iStepValRemaining += -iStep) { int iThisFrame = iNewFrame; int iNextFrame = iNewFrame + iStep; int iMultiLockSequenceIndex = GetMultiLockedSequenceIndexFromFrame( iThisFrame, bIsUpper ); if (iMultiLockSequenceIndex != -1) { MultiSequenceLock_t* pMultiLock = (bIsUpper)?&MultiSequenceLock_Upper:&MultiSequenceLock_Lower; int iIndex = pMultiLock->at(iMultiLockSequenceIndex); pSeq = bIsUpper?Animation_GetUpperSequence(iIndex):Animation_GetLowerSequence(iIndex); assert(pSeq); // if (pSeq) // itu? { // this is the sequence we're currently in, does the proposed next frame also sit within this one?... // int iSeqFrameFirst = pSeq->iTargetFrame; int iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1; if (iNextFrame >= iSeqFrameFirst && iNextFrame <= iSeqFrameLast) { // yes, so adopt it... // iNewFrame = iNextFrame; } else { // new frame is outside of current group, so get the index of the sequence after (or before) this one, // and adopt the first or last frame of it... // int iNumAvailableSequences = pMultiLock->size(); iMultiLockSequenceIndex += iStep; if (iMultiLockSequenceIndex >= iNumAvailableSequences) iMultiLockSequenceIndex = 0; if (iMultiLockSequenceIndex < 0) iMultiLockSequenceIndex = iNumAvailableSequences-1; iIndex = pMultiLock->at(iMultiLockSequenceIndex); Sequence_t* pSeq = bIsUpper?Animation_GetUpperSequence(iIndex):Animation_GetLowerSequence(iIndex); assert(pSeq); // if (pSeq) // itu? { iSeqFrameFirst = pSeq->iTargetFrame; iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1; iNewFrame = (iStep>0)?iSeqFrameFirst:iSeqFrameLast; } else { return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning } } } else { // I don't think we'll ever get here, but I've kind of stopped caring... // return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning } } else { // err... return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning } } return iNewFrame; } else { return iFrame+iStepVal; } } // this only returns the legalised frame, it does NOT set it... // int Model_EnsureCurrentFrameLegal(gl_model* model, int iFrame) { Sequence_t *pSeq = NULL; bool bIsUpper = false; if ((pSeq = GetLockedSequence_Upper(model)) != NULL) bIsUpper = true; else if ((pSeq = GetLockedSequence_Lower(model)) != NULL) bIsUpper = false; if (pSeq) { if (pSeq->bMultiSeq) { // multi-seq locks are a special case, the wrap logic can be pretty freaky, so... // if (GetMultiLockedSequenceFromFrame(iFrame, bIsUpper )) return iFrame; // frame is ok // if we got here then we're outside all current multi-seqs, unfortunately because the specified sequences // can be at any position within the master list order I can't just work out which 2 sequences I'm between // and jump to the start of the higher one, so working on the principle that 99% of the time this code is // used when lerping between frame & frame+1 I'll see if I can find iFrame-1 anywhere... // iFrame--; MultiSequenceLock_t* pMultiLock = (bIsUpper)?&MultiSequenceLock_Upper:&MultiSequenceLock_Lower; MultiSequenceLock_t::iterator it; for (it = pMultiLock->begin(); it != pMultiLock->end(); ++it) { int iSeqIndex = *it; Sequence_t* pSeq = (bIsUpper)?Animation_GetUpperSequence(iSeqIndex):Animation_GetLowerSequence(iSeqIndex); assert(pSeq); if (pSeq) { int iSeqFrameFirst = pSeq->iTargetFrame; int iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1; if (iFrame >= iSeqFrameFirst && iFrame <= iSeqFrameLast) { // got it, so is there another sequence after this? // if (++it != pMultiLock->end()) { iSeqIndex = *it; pSeq = (bIsUpper)?Animation_GetUpperSequence(iSeqIndex):Animation_GetLowerSequence(iSeqIndex); assert(pSeq); if (pSeq) return pSeq->iTargetFrame; // return first frame of next seq in list } else { // reached end of list... // break; } } } } // sod it, whatever happened, let's start at the beginning of the list again... // it = pMultiLock->begin(); int iSeqIndex = *it; pSeq = (bIsUpper)?Animation_GetUpperSequence(iSeqIndex):Animation_GetLowerSequence(iSeqIndex); assert(pSeq); if (pSeq) iFrame = pSeq->iTargetFrame; } else { if (iFrame > (pSeq->iTargetFrame+pSeq->iFrameCount)-1) { // OOR above, loop or wrap?... // if (pSeq->iLoopFrame != -1) { // account for loop frame... // iFrame = pSeq->iTargetFrame + pSeq->iLoopFrame; } else { // no loop, straight wrap... // // iFrame = pSeq->iTargetFrame; // wrap iFrame =(pSeq->iTargetFrame+pSeq->iFrameCount)-1; // stop at end } } else if (iFrame < pSeq->iTargetFrame) { // OOR below, wrap only... // iFrame = pSeq->iTargetFrame; } } } //sanity if (iFrame > (int)model->iNumFrames) { iFrame = model->iNumFrames; } return iFrame; } void SetLODLevel(int iLOD) { mdview.iLODLevel = iLOD; } void FrameAdvanceAnim(int iStepVal) // basically I only use this as +/-1, but other vals should be ok { NodePosition pos; gl_model *model; for (pos=mdview.modelList->first() ; pos!=NULL ; pos=mdview.modelList->after(pos)) { model = (gl_model *)pos->element(); model->currentFrame = GetNextFrame_MultiLocked(model, model->currentFrame, iStepVal); model->currentFrame = Model_EnsureCurrentFrameLegal(model, model->currentFrame); // handle range locking before generic wrap check int iFrame = model->currentFrame; // saves messing with unsigned/signed range conflicts // technically this isn't 100% correct if you were stepping by other values than +/- 1 because I'm // just wrapping to the other end of the range, and not taking into account the amount that you // overflowed it by. If you want to alter this then help yourself. // if (iFrame >= (int)(model->iNumFrames)) { iFrame = 0; } if (iFrame <0) // fix for unsigned { iFrame = model->iNumFrames-1; } model->currentFrame = iFrame; } mdview.animate = false; } vector < Sequence_t > Sequences_LowerAnims; vector < Sequence_t > Sequences_UpperAnims; MultiSequenceLock_t MultiSequenceLock_Upper; MultiSequenceLock_t MultiSequenceLock_Lower; Sequence_t Sequence_Upper_Fake; Sequence_t Sequence_Lower_Fake; int iAnimLockNumber_Upper = 0; int iAnimLockNumber_Lower = 0; int iAnimDisplayNumber_Upper = 0; int iAnimDisplayNumber_Lower = 0; int iAnimLockLongestString= 0; // for aesthetics gl_model* pModel_Lower = NULL; gl_model* pModel_Upper = NULL; int Animation_GetNumLowerSequences(void) { return Sequences_LowerAnims.size(); } int Animation_GetNumUpperSequences(void) { return Sequences_UpperAnims.size(); } // iIndex either = 0-based index if +ve, or -2 (because of blah-1 before call) // Sequence_t* Animation_GetLowerSequence(int iIndex) { if (iIndex<0) // bleh... return &Sequence_Lower_Fake; return &Sequences_LowerAnims[iIndex]; } // iIndex either = 0-based index if +ve, or -2 (because of blah-1 before call) // Sequence_t* Animation_GetUpperSequence(int iIndex) { if (iIndex<0) // bleh... return &Sequence_Upper_Fake; return &Sequences_UpperAnims[iIndex]; } // to do a request by Shubes, this works out which sequence you would be in on this frame if you were in a lock... // Sequence_t* Animation_FromUpperFrame( int iFrame ) { int iLoopCount = Animation_GetNumUpperSequences(); for (int i=0; i= pSeq->iTargetFrame && iFrame < pSeq->iTargetFrame + pSeq->iFrameCount ) { return pSeq; } } return NULL; } Sequence_t* Animation_FromLowerFrame( int iFrame ) { int iLoopCount = Animation_GetNumLowerSequences(); for (int i=0; i= pSeq->iTargetFrame && iFrame < pSeq->iTargetFrame + pSeq->iFrameCount ) { return pSeq; } } return NULL; } void ClearAnimationCFG() { Sequences_LowerAnims.clear(); Sequences_UpperAnims.clear(); Menu_UpperAnims_Clear(); Menu_LowerAnims_Clear(); Sequence_Upper_Fake.bMultiSeq = true; Sequence_Lower_Fake.bMultiSeq = true; iAnimLockNumber_Upper = 0; iAnimLockNumber_Lower = 0; iAnimDisplayNumber_Upper = 0; iAnimDisplayNumber_Lower = 0; iAnimLockLongestString= 0; pModel_Lower = NULL; pModel_Upper = NULL; mdview.xPos = 0.0f; mdview.yPos = 0.0f; mdview.rotAngleX = mdview.rotAngleY = 0.0f; mdview.rotAngleZ = -90.0f; mdview.bAnimCFGLoaded = false; mdview.bAnimIsMultiPlayerFormat = false; } void ReportFrameMismatches(Sequence_t* pSeq, gl_model* pModel) { if (pSeq) { int iModelAnims = pModel->iNumFrames; int iCFGAnims = pSeq->iTargetFrame + pSeq->iFrameCount; // no need to -1 because of 0-based indexing if (iModelAnims > iCFGAnims) { WarningBox(va("Model : \"%s\"\n\n... has %d frames, but animations for it only use %d frames\n\n( ... so %d frame(s) are currently just wasting space )",pModel->sMDXFullPathname,iModelAnims,iCFGAnims, iModelAnims-iCFGAnims )); } else if (iModelAnims < iCFGAnims) { ErrorBox(va("Model : \"%s\"\n\n... has %d frames, but animations for it assume %d frames\n\n( ... so you'll get errors accessing the last %d frame(s)! )",pModel->sMDXFullPathname,iModelAnims,iCFGAnims, iCFGAnims-iModelAnims)); } } } // this has now been re-written to only add to pulldown menus when all menus have been scanned, this way // I can strcat frame info to the seq names while keeping a smooth tabbing line... // // Note that this function can automatically read either ID format or Raven format files transparently... // void LoadAnimationCFG(LPCSTR psFullPath, HDC hDC) // hDC for text metrics { int iLongestTextMetric = 0; SIZE Size; bool bOk = false; FILE *handle = fopen(psFullPath,"rt"); if (handle) { mdview.bAnimCFGLoaded = true; static char sLineBuffer[2048]; int iFirstFrameAfterBoth = -1; // stuff I need to do for ID's non-folded frame numbers int iFirstFrameAfterTorso= -1; while (1) { ZEROMEM(sLineBuffer); if (!fgets(sLineBuffer,sizeof(sLineBuffer),handle)) { if (ferror(handle)) { ErrorBox(va("Error while reading \"%s\"!",psFullPath)); ClearAnimationCFG(); } break; // whether error or EOF } char sComment[2048] = {0}; // keep comments now because of the way ID cfg files work // zap any comments... // char *p = strstr(sLineBuffer,"//"); if (p) { strcpy(sComment,p+2); *p=0; } // update, to read ID cfg files, we need to skip over some stuff that Raven ones don't have... // // our cfg files don't have "sex" (how depressingly apt...) // if (strnicmp(sLineBuffer,"sex",3)==0) continue; // // or this other crap either... // if (strnicmp(sLineBuffer,"footsteps",9)==0) continue; if (strnicmp(sLineBuffer,"headoffset",10)==0) continue; if (strnicmp(sLineBuffer,"soundpath",9)==0) continue; Sequence_t seq; memset(&seq,0,sizeof(seq)); char sLine[2048]; int iElementsDone = sscanf( sLineBuffer, "%s %d %d %d %d", &sLine, &seq.iTargetFrame, &seq.iFrameCount, &seq.iLoopFrame, &seq.iFrameSpeed ); if (iElementsDone == EOF) continue; // probably skipping over a comment line bool bElementsScannedOk = false; if (iElementsDone == 5) { // then it must be a Raven line... // bElementsScannedOk = true; mdview.bAnimIsMultiPlayerFormat = false; } else { // try scanning it as an ID line... // iElementsDone = sscanf( sLineBuffer, "%d %d %d %d", &seq.iTargetFrame, &seq.iFrameCount, &seq.iLoopFrame, &seq.iFrameSpeed ); if (iElementsDone == 4) { mdview.bAnimIsMultiPlayerFormat = true; // scanned an ID line in ok, now convert it to Raven format... // iElementsDone = sscanf( sComment, "%s", &sLine ); // grab anim name from original saved comment if (iElementsDone == 1) { // ... and convert their loop format to ours... // if (seq.iLoopFrame == 0) { seq.iLoopFrame = -1; } else { seq.iLoopFrame = seq.iFrameCount - seq.iLoopFrame; } // now do the folding number stuff since ID don't do it in their files... // if ( !strnicmp(sLine,"TORSO_",6) && iFirstFrameAfterBoth == -1) { iFirstFrameAfterBoth = seq.iTargetFrame; } if ( !strnicmp(sLine,"LEGS_",5)) { if (iFirstFrameAfterTorso == -1) { iFirstFrameAfterTorso = seq.iTargetFrame; } // now correct the leg framenumber... // if (iFirstFrameAfterBoth != -1) // if it did, then there'd be no torso frames, so no adj nec. { seq.iTargetFrame -= (iFirstFrameAfterTorso - iFirstFrameAfterBoth); } } bElementsScannedOk = true; } } } if (bElementsScannedOk) { seq.sName = sLine; // // this line seems to be ok... // // OutputDebugString(va("%s %d %d %d %d\n",seq.sName.c_str(), seq.iTargetFrame, seq.iFrameCount, seq.iLoopFrame, seq.iFrameSpeed )); // "both" or "torso" get added to 'upper' menu... // if ( (!strnicmp(seq.sName.c_str(),"BOTH_",5)) || (!strnicmp(seq.sName.c_str(),"TORSO_",6)) ) { Sequences_UpperAnims.push_back(seq); if (iAnimLockLongestString < (int)strlen(seq.sName.c_str())) iAnimLockLongestString = strlen(seq.sName.c_str()); if (GetTextExtentPoint( hDC, // HDC hdc, // handle to device context seq.sName.c_str(), // LPCTSTR lpString, // pointer to text string strlen(seq.sName.c_str()), // int cbString, // number of characters in string &Size // LPSIZE lpSize // pointer to structure for string size ) ) { if (iLongestTextMetric < Size.cx) iLongestTextMetric = Size.cx; } // Menu_UpperAnims_AddItem(seq.sName.c_str()); } // "both" or "legs" get added to 'lower' menu... // if ( (!strnicmp(seq.sName.c_str(),"BOTH_",5)) || (!strnicmp(seq.sName.c_str(),"LEGS_",5)) ) { Sequences_LowerAnims.push_back(seq); if (iAnimLockLongestString < (int)strlen(seq.sName.c_str())) iAnimLockLongestString = strlen(seq.sName.c_str()); if (GetTextExtentPoint( hDC, // HDC hdc, // handle to device context seq.sName.c_str(), // LPCTSTR lpString, // pointer to text string strlen(seq.sName.c_str()), // int cbString, // number of characters in string &Size // LPSIZE lpSize // pointer to structure for string size ) ) { if (iLongestTextMetric < Size.cx) iLongestTextMetric = Size.cx; } // Menu_LowerAnims_AddItem(seq.sName.c_str()); } } else { // so do we report this as an error or what? // ErrorBox(sLineBuffer); } } fclose(handle); // now add to menus... (this code is awful, it was simple at first then mutated with feature-add) // char sLine[2048]; vector< Sequence_t >::iterator it; for (it=Sequences_UpperAnims.begin(); it!=Sequences_UpperAnims.end(); it++) { sprintf(sLine,(*it).sName.c_str()); while (1) { GetTextExtentPoint( hDC, // HDC hdc, // handle to device context sLine, // LPCTSTR lpString, // pointer to text string strlen(sLine), // int cbString, // number of characters in string &Size // LPSIZE lpSize // pointer to structure for string size ); if (Size.cx >= iLongestTextMetric) break; strcat(sLine," "); } Menu_UpperAnims_AddItem(va("%s (%d...%d)%s",sLine,(*it).iTargetFrame,((*it).iTargetFrame+(*it).iFrameCount)-1,((*it).iLoopFrame==-1)?"":va(" Loop %d",(*it).iTargetFrame+(*it).iLoopFrame))); } for (it=Sequences_LowerAnims.begin(); it!=Sequences_LowerAnims.end(); it++) { sprintf(sLine,(*it).sName.c_str()); while (1) { GetTextExtentPoint( hDC, // HDC hdc, // handle to device context sLine, // LPCTSTR lpString, // pointer to text string strlen(sLine), // int cbString, // number of characters in string &Size // LPSIZE lpSize // pointer to structure for string size ); if (Size.cx >= iLongestTextMetric) break; strcat(sLine," "); } Menu_LowerAnims_AddItem(va("%s (%d...%d)%s",sLine,(*it).iTargetFrame,((*it).iTargetFrame+(*it).iFrameCount)-1,((*it).iLoopFrame==-1)?"":va(" Loop %d",(*it).iTargetFrame+(*it).iLoopFrame))); } // a bit of sanity checking, to cope with something Bob tried to do... :-) // Sequence_t* pSeq = NULL; gl_model* pModel; if ((pModel = pModel_Lower)!=0) { pSeq = Animation_GetLowerSequence(Animation_GetNumLowerSequences()-1); ReportFrameMismatches(pSeq,pModel); } if ((pModel = pModel_Upper)!=0) { pSeq = Animation_GetUpperSequence(Animation_GetNumUpperSequences()-1); ReportFrameMismatches(pSeq,pModel); } } }