jedioutcast/utils/Assimilate/Model.cpp
2013-04-04 13:07:40 -05:00

1652 lines
39 KiB
C++

// Model.cpp
#include "StdAfx.h"
#include "Includes.h"
#include <assert.h>
#define sANIMATION_CFG_NAME "animation.cfg"
#define sANIMATION_PRE_NAME "animation.pre"
bool gbReportMissingASEs = true;
int giFixUpdatedASEFrameCounts = YES;
CModel::CModel()
{
m_bCurrentUserSelection = false;
}
CModel::~CModel()
{
}
void CModel::Delete()
{
while(m_comments != NULL)
{
CComment* curComment = m_comments;
m_comments = curComment->GetNext();
curComment->Delete();
}
while(m_sequences != NULL)
{
CSequence* curSequence = m_sequences;
m_sequences = curSequence->GetNext();
curSequence->Delete();
}
if (m_name != NULL)
{
free(m_name);
m_name = NULL;
}
if (m_path != NULL)
{
free(m_path);
m_path = NULL;
}
if (m_psSkelPath != NULL)
{
free(m_psSkelPath);
m_psSkelPath = NULL;
}
if (m_psMakeSkelPath != NULL)
{
free(m_psMakeSkelPath);
m_psMakeSkelPath = NULL;
}
m_curSequence = NULL;
PCJList_Clear(); // not really necessary, but useful reminder
delete this;
}
CModel* CModel::Create(CComment* comments)
{
CModel* retval = new CModel();
retval->Init(comments);
return retval;
}
bool CModel::DoProperties()
{
bool dirty = false;
// if (IsGhoul2())
// {
// InfoBox("This properties page is for params not relevant for Ghoul2 models");
// }
// else
{
CPropertySheet* propSheet = new CPropertySheet(m_name);
CModelPropPage* propPage = new CModelPropPage();
propPage->m_model = this;
propPage->m_soilFlag = &dirty;
propSheet->AddPage(propPage);
for (int i=0; i<PCJList_GetEntries(); i++)
{
propPage->AddPCJEntry(PCJList_GetEntry(i));
}
propSheet->DoModal();
delete propPage;
delete propSheet;
}
return dirty;
}
void CModel::SetNext(CModel* next)
{
m_next= next;
}
CModel* CModel::GetNext()
{
return m_next;
}
bool CModel::ContainsFile(LPCSTR psFilename)
{
if (m_sequences == NULL)
{
return false;
}
else
{
CSequence* curSequence = m_sequences;
while(curSequence)
{
if (stricmp(curSequence->GetPath(),psFilename)==0)
return true;
curSequence = curSequence->GetNext();
}
}
return false;
}
void CModel::AddSequence(CSequence* sequence)
{
if (m_sequences == NULL)
{
m_sequences = sequence;
}
else
{
CSequence* curSequence = m_sequences;
while(curSequence->GetNext() != NULL)
{
curSequence = curSequence->GetNext();
}
curSequence->SetNext(sequence);
}
}
void CModel::DeleteSequence(CSequence* deleteSequence)
{
// linklist is only 1-way, so we need to find the stage previous to this (if any)...
//
CSequence* prevSequence = NULL;
CSequence* scanSequence = GetFirstSequence();
while (scanSequence && scanSequence != deleteSequence)
{
prevSequence = scanSequence;
scanSequence = scanSequence->GetNext();
}
if (scanSequence == deleteSequence)
{
// we found it, so was this the first sequence in the list?
//
if (prevSequence)
{
prevSequence->SetNext(scanSequence->GetNext()); // ...no
}
else
{
m_sequences = scanSequence->GetNext(); // ...yes
}
scanSequence->Delete();
}
}
// func for qsort to callback, returns:
//
// <0 elem1 less than elem2
// 0 elem1 equivalent to elem2
// >0 elem1 greater than elem2
//
int ModelSequenceCompareFunc( const void *arg1, const void *arg2 )
{
CSequence *seq1 = (CSequence *) *(CSequence **)arg1;
CSequence *seq2 = (CSequence *) *(CSequence **)arg2;
return (seq1->m_iSequenceNumber - seq2->m_iSequenceNumber);
/* if (seq1->m_iSequenceNumber < seq2->m_iSequenceNumber)
return -1;
if (seq1->m_iSequenceNumber > seq2->m_iSequenceNumber)
return 1;
return 0;
*/
}
// change the sequences around in the model till each one is in the position specified by it's member: m_iSequenceNumber
//
void CModel::ReOrderSequences()
{
typedef vector<CSequence*> sequences_t; sequences_t sequences;
// add sequences to list...
//
CSequence *curSequence = m_sequences;
while (curSequence)
{
sequences.push_back(curSequence);
curSequence = curSequence->GetNext();
}
// re-order sequences...
//
qsort( (void *)&sequences[0], (size_t)(sequences.size()), sizeof(CSequence *), ModelSequenceCompareFunc );
// now rebuild links...
//
int iTotMasterSequences = GetTotMasterSequences(); // this needs to be eval'd here, you can't do it in the for-next below
m_sequences = NULL;
for (int i=0; i<iTotMasterSequences; i++)
{
curSequence = sequences[i];
curSequence->SetNext(NULL);
AddSequence(curSequence);
}
Resequence();
}
// a niceness feature so the popup anim enum dialog picker can ask which enums are already in use...
//
// (now updated to return a int instead of a bool, therefore can be used to check for duplicates)
//
int CModel::AnimEnumInUse(LPCSTR psAnimEnum)
{
int iCount = 0;
if (strlen(psAnimEnum)) // added for G2 models, which don't necessarily use enums yet
{
CSequence *curSequence = GetFirstSequence();
while (curSequence)
{
if (!strcmp(psAnimEnum,curSequence->GetEnum()))
{
iCount++;// return true;
}
// new bit, ask the additional sequences as well...
//
for (int i=0; i<MAX_ADDITIONAL_SEQUENCES; i++)
{
if (!strcmp(psAnimEnum,curSequence->AdditionalSeqs[i]->GetEnum()))
{
iCount++;// return true;
}
}
curSequence = curSequence->GetNext();
}
}
return iCount;//false;
}
CSequence* CModel::GetFirstSequence()
{
return m_sequences;
}
int CModel::GetTotMasterSequences()
{
int tot = 0;
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
tot++;
curSequence = curSequence->GetNext();
}
return tot;
}
int CModel::GetTotSequences()
{
int tot = 0;
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
tot++;
for (int i=0; i<MAX_ADDITIONAL_SEQUENCES; i++)
{
if (curSequence->AdditionalSeqs[i]->AdditionalSequenceIsValid())
tot++;
}
curSequence = curSequence->GetNext();
}
return tot;
}
void CModel::Resequence(bool bReScanASEFiles /* = false */)
{
CWaitCursor wait;
CRect Rect;
// ((CAssimilateApp*)AfxGetApp())->m_pMainWnd->GetWindowRect(&Rect);
// CPoint Point = Rect.CenterPoint();
CProgressCtrl *pProgress = NULL;
if (bReScanASEFiles && ((CAssimilateApp*)AfxGetApp())->m_pMainWnd)
{
pProgress = new CProgressCtrl;
bool bOK = !!pProgress->Create( WS_CHILD|WS_VISIBLE|PBS_SMOOTH, // DWORD dwStyle,
CRect(100,100,200,200), // const RECT& rect,
((CAssimilateApp*)AfxGetApp())->m_pMainWnd, // CWnd* pParentWnd,
1 // UINT nID
);
if (!bOK)
{
delete pProgress;
pProgress = NULL;
}
}
int iTotMasterSequences = GetTotMasterSequences();
if (pProgress)
{
pProgress->SetRange(0,iTotMasterSequences);
}
int iSequenceNumber=0;
int curFrame = 0;
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
if (pProgress)
{
pProgress->SetPos(iSequenceNumber++);
// pProgress->SetWindowText(va("Sequence %d/%d (%s)",iSequenceNumber,iTotMasterSequences,curSequence->GetPath()));
wait.Restore();
}
// mark current enums as valid or not...
//
curSequence->SetValidEnum(((CAssimilateApp*)AfxGetApp())->ValidEnum(curSequence->GetEnum()));
//--------
for (int _i=0; _i<MAX_ADDITIONAL_SEQUENCES; _i++)
{
CSequence *additionalSeq = curSequence->AdditionalSeqs[_i];
additionalSeq->SetValidEnum(((CAssimilateApp*)AfxGetApp())->ValidEnum(additionalSeq->GetEnum()));
}
//---------
if ( bReScanASEFiles )
{
// new code, first of all check for changed framecounts (ie updated ASE file), and update sequence if nec...
//
CString nameASE = ((CAssimilateApp*)AfxGetApp())->GetQuakeDir();
nameASE+= curSequence->GetPath();
if (!FileExists(nameASE))
{
if (gbCarWash_DoingScan)
{
strCarWashErrors += va("Model file missing: \"%s\"\n",nameASE);
}
else
{
if ( gbReportMissingASEs )
{
gbReportMissingASEs = GetYesNo(va("Model file missing: \"%s\"\n\nContinue recieving this message?",nameASE));
}
}
}
else
{
int iStartFrame, iFrameCount, iFrameSpeed;
iFrameCount = curSequence->GetFrameCount(); // default it in case we skip an XSI read
iFrameSpeed = curSequence->GetFrameSpeed(); // default it in case we cache this file
curSequence->ReadASEHeader( nameASE, iStartFrame, iFrameCount, iFrameSpeed, true); // true = can skip XSI read
if ( iFrameCount != curSequence->GetFrameCount() )
{
if (gbCarWash_DoingScan)
{
strCarWashErrors += va("file: \"%s\" has a framecount of %d, but .CAR file says %d\n",nameASE,iFrameCount,curSequence->GetFrameCount());
}
else
{
// don't mention it if the current count is zero, it's probably a new anim we've just added...
//
if ( curSequence->GetFrameCount() )
{
if (giFixUpdatedASEFrameCounts == YES || giFixUpdatedASEFrameCounts == NO)
{
CYesNoYesAllNoAll query( va("Model file: \"%s\"",nameASE),
"",
va("... has a framecount of %d instead of %d as the QDT/CAR file says",iFrameCount, curSequence->GetFrameCount()),
"",
"",
"Do you want me to fix this?"
);
giFixUpdatedASEFrameCounts = query.DoModal();
//gbReportUpdatedASEFrameCounts = GetYesNo(va("Model file: \"%s\"\n\n... has a framecount of %d instead of %d as the QDT/CAR file says (so I'll update it).\n\nContinue recieving this message?",nameASE, iFrameCount, curSequence->GetFrameCount()));
}
}
// update the sequence?...
//
if (giFixUpdatedASEFrameCounts == YES || giFixUpdatedASEFrameCounts == YES_ALL
|| !curSequence->GetFrameCount() // update: I think this should be here?
)
{
curSequence->SetFrameCount( iFrameCount );
}
}
}
}
// findmeste: this no longer seems to do anything under JK2, presumablt EF1-only?
#if 0
// now try to do any auto-associate between the ASE filename base and the existing enums,
// so if we find (eg) /...../...../CROUCH.ASE and we have BOTH_CROUCH then auto-set the enum to BOTH_CROUCH
//
CString stringASEName = nameASE;
Filename_BaseOnly(stringASEName); // now = (eg) "falldeath" or "injured" etc
for (int i=0; ; i++)
{
LPCSTR p = ((CAssimilateApp*)AfxGetApp())->GetEnumEntry(i);
if (!p) // EOS?
break;
CString stringEnum = p;
// note, I could check stuff like "IsEnumSeperator(LPCSTR lpString)" on <p>, but you'd never
// have one of those enums assigned to a sequence anyway.
char *psEnumPosAfterUnderScore = strchr(stringEnum,'_');
if (psEnumPosAfterUnderScore++) // check it, and skip to next char
{
// does this enum match the ASE name?
//
if ( !stricmp( psEnumPosAfterUnderScore, stringASEName ) )
{
// ok, we've found a good candidate, so set it... (no need for query-prev code, but I wanted to)
//
if ( strcmp( curSequence->GetEnum(), stringEnum))
{
// InfoBox( va("(temp notify box)\n\nEnum auto-assign of \"%s\" to ASE \"%s\" (prev enum was \"%s\")",stringEnum,nameASE,curSequence->GetEnum()));
curSequence->SetEnum(stringEnum);
}
}
}
else
{
// this should never happen...
//
if (gbCarWash_DoingScan)
{
strCarWashErrors += va("found an anim enum with no underscore: \"%s\"\n",stringEnum);
}
else
{
ASSERT(0);
ErrorBox(va("Error! Somehow I found an anim enum with no underscore: \"%s\"",stringEnum));
}
}
}
#endif
}
// More bollox for Gummelt... :-)
// now do the other freaky trick (you'd better be grateful for all this Mike!!! <g>), which is:
//
// If you find the substring DEATH in this (master) sequence's enum, then ensure that the first *additional*
// sequence of it is set to be the corresponding DEAD enum, but using the last frame only (and non-looping)
//
// (... or something...)
//
{ // keep scope local for neatness
if ( strstr (curSequence->GetEnum(), "DEATH") )
{
// scan this sequence's additional sequences for a DEAD of the same basic type...
//
CString stringEnumDEAD = curSequence->GetEnum();
ASSERT(!IsEnumSeperator(stringEnumDEAD));
stringEnumDEAD.Replace("DEATH","DEAD");
// 1st, is there even a corresponding DEAD enum in the global enum table that we can look for...
//
CString stringEnum;
bool bEnumFound = false;
for (int iEnumEntry=0; !bEnumFound; iEnumEntry++)
{
LPCSTR p = ((CAssimilateApp*)AfxGetApp())->GetEnumEntry(iEnumEntry);
if (!p) // EOS?
break;
stringEnum = p;
// note, I could check stuff like "IsEnumSeperator(LPCSTR lpString)" on <p>, but you'd never
// have one of those enums assigned to a sequence anyway.
// does this enum match the one we've built?
//
if ( !stricmp( stringEnum, stringEnumDEAD ) )
{
bEnumFound = true;
}
}
if ( bEnumFound )
{
// ok, there *is* one of these, so let's scan this sequence's additional sequences to see if we've
// got it...
//
CSequence *additionalSeq; // outside FOR scope
for (int i=0; i<MAX_ADDITIONAL_SEQUENCES; i++)
{
additionalSeq = curSequence->AdditionalSeqs[i];
if (additionalSeq->AdditionalSequenceIsValid())
{
if (!strcmp(additionalSeq->GetEnum(),stringEnum))
{
break; // we've found one!
}
}
}
// if we didn't find one, NULL the ptr
//
if ( i == MAX_ADDITIONAL_SEQUENCES)
{
additionalSeq = NULL;
}
// did we find one? (or did it have the wrong info in?)
//
if ( additionalSeq == NULL // didn't find one
|| additionalSeq->GetFrameCount()!=1
|| additionalSeq->GetLoopFrame() !=-1
|| additionalSeq->GetStartFrame()!= curSequence->GetFrameCount()-1
|| additionalSeq->GetFrameSpeed()!= curSequence->GetFrameSpeed()
)
{
// find a slot to add this new sequence to, or use the faulty one...
//
if (additionalSeq == NULL)
{
for (int i=0; i<MAX_ADDITIONAL_SEQUENCES; i++)
{
additionalSeq = curSequence->AdditionalSeqs[i];
if (!additionalSeq->AdditionalSequenceIsValid())
{
break; // found an unused slot
}
}
}
// so have we got a slot to work with?
//
if ( additionalSeq == NULL )
{
if (gbCarWash_DoingScan)
{
strCarWashErrors += va( "Fuck!!!: I need an 'additional sequence' slot free in the entry: \"%s\" to generate a DEAD seq, but there isn't one spare. Edit this yourself later.\n",curSequence->GetPath());
}
else
{
ErrorBox( va( "Fuck!!!\n\nI need an 'additional sequence' slot free in the ASE:\n\n\"%s\"\n\n... to generate a DEAD seq, but there isn't one spare. Edit this yourself later.",curSequence->GetPath()));
}
}
else
{
additionalSeq->SetStartFrame( curSequence->GetFrameCount()-1 );
additionalSeq->SetFrameCount( 1 );
additionalSeq->SetLoopFrame (-1 );
additionalSeq->SetFrameSpeed( curSequence->GetFrameSpeed() );
additionalSeq->SetEnum ( stringEnumDEAD );
}
}
}
}
}
curSequence->SetTargetFrame(curFrame + curSequence->GetStartFrame()); // slightly more legal than just (curFrame)
// update: now set any additional sequences within it...
//
for (int i=0; i<MAX_ADDITIONAL_SEQUENCES; i++)
{
curSequence->AdditionalSeqs[i]->SetTargetFrame(curFrame + curSequence->AdditionalSeqs[i]->GetStartFrame());
}
curFrame += curSequence->GetFrameCount();
curFrame += curSequence->GetGenLoopFrame()?1:0; // findme: is this right? I hate this system
curSequence = curSequence->GetNext();
}
m_totFrames = curFrame;
ghAssimilateView->GetDocument()->SetModifiedFlag();
// (ghAssimilateView->GetDocument()->IsModified())?OutputDebugString("modified\n"):OutputDebugString("same\n");
if (pProgress)
{
delete pProgress;
pProgress = 0;
}
}
void CModel::AddComment(CComment* comment)
{
if (m_curSequence != NULL)
{
m_curSequence->AddComment(comment);
return;
}
if (m_comments == NULL)
{
m_comments = comment;
}
else
{
CComment* curComment = m_comments;
while (curComment->GetNext() != NULL)
{
curComment = curComment->GetNext();
}
curComment->SetNext(comment);
}
}
CComment* CModel::GetFirstComment()
{
return m_comments;
}
CComment* CModel::ExtractComments()
{
CComment* retval = m_comments;
m_comments = NULL;
return retval;
}
void CModel::SetName(LPCTSTR name)
{
if (m_name != NULL)
{
free(m_name);
}
if (name == NULL)
{
m_name = NULL;
}
else
{
m_name = (char*)malloc(strlen(name) + 1);
strcpy(m_name, name);
}
}
LPCTSTR CModel::GetName()
{
return m_name;
}
void CModel::SetPath(LPCTSTR name)
{
if (m_path != NULL)
{
free(m_path);
}
if (name == NULL)
{
m_path = NULL;
}
else
{
m_path = (char*)malloc(strlen(name) + 1);
strcpy(m_path, name);
}
}
LPCTSTR CModel::GetPath()
{
return m_path;
}
void CModel::DeriveName(LPCTSTR fromname)
{
SetPath(fromname);
if (fromname == NULL)
{
if (m_name != NULL)
{
free(m_name);
m_name = NULL;
}
return;
}
CString name = fromname;
int loc = name.ReverseFind('/');
if (loc > -1)
{
name = name.Left(loc);
}
loc = name.ReverseFind('/');
if (loc > -1)
{
name = name.Right(name.GetLength() - loc - 1);
}
SetName(name);
}
void CModel::Init(CComment* comments)
{
m_comments = comments;
m_next = NULL;
m_sequences = NULL;
m_curSequence = NULL;
m_name = NULL;
m_path = NULL;
m_psSkelPath = NULL;
m_psMakeSkelPath = NULL;
SetOrigin(0, 0, 0);
SetParms(-1, -1, 0, 1);
m_iType = TK_AS_CONVERTMDX_NOASK;
m_bSmooth = false;
m_bLoseDupVerts = false;
m_bMakeSkin = false;
m_fScale = 1.0f;
m_bIgnoreBaseDeviations = false;
m_bSkew90 = false;
m_bNoSkew90 = false;
m_bKeepMotion = false;
m_bPreQuat = false;
PCJList_Clear();
}
void CModel::SetConvertType(int iType)
{
m_iType = iType;
}
int CModel::GetConvertType(void)
{
return m_iType;
}
bool CModel::IsGhoul2(void)
{
if (GetConvertType() == TK_AS_CONVERTMDX ||
GetConvertType() == TK_AS_CONVERTMDX_NOASK
)
{
return true;
}
return false;
}
void CModel::SetMakeSkin(bool bMakeSkin)
{
m_bMakeSkin = bMakeSkin;
}
bool CModel::GetMakeSkin(void)
{
return m_bMakeSkin;
}
void CModel::SetSmooth(bool bSmooth)
{
m_bSmooth = bSmooth;
}
bool CModel::GetSmooth(void)
{
return m_bSmooth;
}
void CModel::SetKeepMotion(bool bKeepMotion)
{
m_bKeepMotion = bKeepMotion;
}
bool CModel::GetKeepMotion(void)
{
return m_bKeepMotion;
}
void CModel::SetLoseDupVerts(bool bLoseDupVerts)
{
m_bLoseDupVerts = bLoseDupVerts;
}
bool CModel::GetLoseDupVerts(void)
{
return m_bLoseDupVerts;
}
void CModel::SetScale(float fScale)
{
m_fScale = fScale;
}
float CModel::GetScale(void)
{
return m_fScale;
}
bool CModel::GetPreQuat(void)
{
return m_bPreQuat;
}
void CModel::SetPreQuat(bool bPreQuat)
{
m_bPreQuat = bPreQuat;
}
void CModel::PCJList_Clear()
{
m_vPCJList.clear();
}
void CModel::PCJList_AddEntry(LPCSTR psEntry)
{
m_vPCJList.push_back(psEntry);
}
int CModel::PCJList_GetEntries()
{
return m_vPCJList.size();
}
LPCSTR CModel::PCJList_GetEntry(int iIndex)
{
if (iIndex < PCJList_GetEntries())
{
return m_vPCJList[iIndex].c_str();
}
assert( iIndex < PCJList_GetEntries() );
return "";
}
// temporary!!!!!!!
void CModel::SetIgnoreBaseDeviations(bool bIgnore)
{
m_bIgnoreBaseDeviations = bIgnore;
}
bool CModel::GetIgnoreBaseDeviations(void)
{
return m_bIgnoreBaseDeviations;
}
void CModel::SetSkew90(bool bSkew90)
{
m_bSkew90 = bSkew90;
}
bool CModel::GetSkew90(void)
{
return m_bSkew90;
}
void CModel::SetNoSkew90(bool bNoSkew90)
{
m_bNoSkew90 = bNoSkew90;
}
bool CModel::GetNoSkew90(void)
{
return m_bNoSkew90;
}
/*
void CModel::SetSkelPath(LPCSTR psPath)
{
if (m_psSkelPath != NULL)
{
free(m_psSkelPath);
}
if (psPath == NULL)
{
m_psSkelPath = NULL;
}
else
{
m_psSkelPath = (char*) malloc (strlen(psPath)+1);
strcpy(m_psSkelPath, psPath);
}
}
LPCSTR CModel::GetSkelPath(void)
{
return m_psSkelPath; // warning, may be NULL or blank
}
*/
void CModel::SetMakeSkelPath(LPCSTR psPath)
{
if (m_psMakeSkelPath != NULL)
{
free(m_psMakeSkelPath);
}
if (psPath == NULL)
{
m_psMakeSkelPath = NULL;
}
else
{
m_psMakeSkelPath = (char*) malloc (strlen(psPath)+1);
strcpy(m_psMakeSkelPath, psPath);
strlwr(m_psMakeSkelPath);
}
}
LPCSTR CModel::GetMakeSkelPath(void)
{
return m_psMakeSkelPath; // warning, may be NULL or blank
}
void CModel::SetOrigin(int x, int y, int z)
{
SetOriginX(x);
SetOriginY(y);
SetOriginZ(z);
}
void CModel::SetOriginX(int x)
{
m_originx = x;
}
void CModel::SetOriginY(int y)
{
m_originy = y;
}
void CModel::SetOriginZ(int z)
{
m_originz = z;
}
int CModel::GetOriginX()
{
return m_originx;
}
int CModel::GetOriginY()
{
return m_originy;
}
int CModel::GetOriginZ()
{
return m_originz;
}
void CModel::SetParms(int skipStart, int skipEnd, int totFrames, int headFrames)
{
SetTotFrames(totFrames);
}
void CModel::SetTotFrames(int value)
{
m_totFrames = value;
}
int CModel::GetTotFrames()
{
return m_totFrames;
}
void CModel::SetUserSelectionBool(bool bSelected)
{
m_bCurrentUserSelection = bSelected;
}
bool CModel::GetUserSelectionBool()
{
return m_bCurrentUserSelection;
}
bool CModel::WriteExternal(bool bPromptForNames, bool& bCFGWritten)
{
bCFGWritten = false;
CString filename;
if (bPromptForNames)
{
//XXXXXXXXXXXXXX
// CFileDialog dialog(FALSE, ".cfg", /*m_name*/"D:\\Source\\StarTrek\\Code-DM\\baseef\\models\\players2\\imperial\\animation_new.cfg", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Config Data Files (*.cfg)|*.cfg|All Files (*.*)|*.*||", NULL);
CString strInitialPrompt(ghAssimilateView->GetDocument()->GetPathName());
Filename_RemoveFilename(strInitialPrompt);
strInitialPrompt.Replace("/","\\");
strInitialPrompt += "\\";
strInitialPrompt += sANIMATION_CFG_NAME;
CFileDialog dialog(FALSE, ".cfg", strInitialPrompt, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Config Data Files (*.cfg)|*.cfg|All Files (*.*)|*.*||", NULL);
if (dialog.DoModal() != IDOK)
{
return false;
}
filename = dialog.GetPathName(); // eg. {"Q:\quake\baseq3\models\players\ste_assimilate_test\ste_assimilate_test.cfg"}
}
else
{
filename = ((CAssimilateApp*)AfxGetApp())->GetQuakeDir();
filename+= GetPath();
filename.MakeLower();
filename.Replace('\\', '/');
int loc = filename.Find(m_name);//"/root");
if (loc>=0)
{
filename = filename.Left(loc+strlen(m_name));
}
// dup the dirname to use as the model name... (eg "/.../.../klingon" becomes "/.../.../klingon/klingon"
//loc = filename.ReverseFind('/');
//filename += filename.Mid(loc);
//filename += ".cfg";
filename += "/"; filename += sANIMATION_CFG_NAME;
}
CTxtFile* file = CTxtFile::Create(filename);
if (file == NULL || !file->IsValid())
{
ErrorBox(va("Error creating file \"%s\"!",filename));
return false;
}
// new bit, check for the existance of an animation.pre file, which means export this in Q3 format (rather than trek)
//
CString strQ3FormatCheckName(filename);
Filename_RemoveFilename(strQ3FormatCheckName);
strQ3FormatCheckName += "\\";
strQ3FormatCheckName += sANIMATION_PRE_NAME;
strQ3FormatCheckName.Replace("/","\\");
bool bExportFormatIsQuake3Multiplayer = //FileExists(strQ3FormatCheckName);
((CAssimilateApp*)AfxGetApp())->GetMultiPlayerMode();
CString strPrePend;
if (bExportFormatIsQuake3Multiplayer)
{
// multi-player format, check for optional animation.pre file...
//
FILE *fhPRE = fopen(strQ3FormatCheckName, "rt");
if (fhPRE)
{
//
// read all the lines in this file and just write them straight to the output file...
//
char sLine[16384];
char *psLine;
CString strTrimmed;
while ((psLine = fgets( sLine, sizeof(sLine), fhPRE ))!=NULL)
{
strTrimmed = psLine;
strTrimmed.Replace("\n","");
strTrimmed.TrimRight();
strTrimmed.TrimLeft();
file->Writeln(strTrimmed);
}
if (ferror(fhPRE))
{
ErrorBox(va("Error during reading of file \"%s\"!\n\n( this shouldn't happen )",(LPCSTR)strQ3FormatCheckName));
}
fclose(fhPRE);
}
file->Writeln("");
file->Writeln("//");
file->Writeln("// Format: targetFrame, frameCount, loopFrame, frameSpeed");
file->Writeln("//");
}
else
{
// single-player format...
//
CString commentLine;
CTime time = CTime::GetCurrentTime();
commentLine.Format("// %s %d frames; %d sequences; updated %s", filename, m_totFrames, GetTotSequences(), time.Format("%H:%M %A, %B %d, %Y"));
file->Writeln(commentLine);
//
// the Writeln functions I have to call don't handle "\n" chars properly because of being opened in binary mode
// (sigh), so I have to explicitly call the Writeln() functions to output CRs... :-(
//
file->Writeln("//");
file->Writeln("// Format: enum, targetFrame, frameCount, loopFrame, frameSpeed");
file->Writeln("//");
}
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
curSequence->WriteExternal(this, file, bExportFormatIsQuake3Multiplayer);
curSequence = curSequence->GetNext();
}
file->Delete();
if (HasGLA())
{
unlink(filename); // zap it, since it's meaningless here (only has one seq/enum: the whole GLA)
}
else
{
bCFGWritten = true;
}
return true;
}
bool CModel::HasGLA()
{
CSequence* curSequence = GetFirstSequence();
while (curSequence)
{
if (curSequence->IsGLA())
{
return true;
}
curSequence = curSequence->GetNext();
}
return false;
}
// returns NULL else name string
//
LPCSTR CModel::GLAName(void)
{
CSequence* curSequence = GetFirstSequence();
while (curSequence)
{
if (curSequence->IsGLA())
{
return curSequence->GetName();
}
curSequence = curSequence->GetNext();
}
return NULL;
}
// should only be called once model is known to be in a sorted state or results are meaningless.
//
// either param can be NULL if not interested in them...
//
void CModel::GetMasterEnumBoundaryFrameNumbers(int *piFirstFrameAfterBOTH, int *piFirstFrameAfterTORSO)
{
ENUMTYPE prevET = ET_INVALID;
int iFirstFrameAfterBOTH = 0;
int iFirstFrameAfterTORSO= 0;
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
ENUMTYPE thisET = curSequence->GetEnumType();
// update any frame markers first...
//
if (prevET == ET_BOTH && thisET != ET_BOTH)
{
iFirstFrameAfterBOTH = curSequence->GetTargetFrame();
iFirstFrameAfterTORSO= curSequence->GetTargetFrame(); // set this as well in case there are no TORSOs at all
}
if (prevET == ET_TORSO && thisET != ET_TORSO)
{
iFirstFrameAfterTORSO= curSequence->GetTargetFrame();
}
prevET = thisET;
curSequence = curSequence->GetNext();
}
// bug fix, if there are no leg frames at all, then we need to check if the ...AfterTORSO marker needs moving...
//
if (prevET == ET_BOTH)
{
iFirstFrameAfterBOTH = GetTotFrames();
iFirstFrameAfterTORSO= GetTotFrames();
}
if (prevET == ET_TORSO)
{
iFirstFrameAfterTORSO = GetTotFrames();
}
if (piFirstFrameAfterBOTH)
{
*piFirstFrameAfterBOTH = iFirstFrameAfterBOTH;
}
if (piFirstFrameAfterTORSO)
{
*piFirstFrameAfterTORSO= iFirstFrameAfterTORSO;
}
}
void CModel::Write(CTxtFile* file)
{
file->Write("$");
file->Writeln(CAssimilateDoc::GetKeyword(TK_AS_GRABINIT, TABLE_QDT));
if (!HasGLA())
{
if (GetScale() != 1.0f)
{
file->Write("$");
file->Write(CAssimilateDoc::GetKeyword(TK_AS_SCALE, TABLE_QDT)," ");
file->Writeln(va("%g",GetScale()));
}
if (GetKeepMotion())
{
file->Write("$");
file->Writeln(CAssimilateDoc::GetKeyword(TK_AS_KEEPMOTION, TABLE_QDT));
}
for (int iPCJ=0; iPCJ < PCJList_GetEntries(); iPCJ++)
{
file->Write("$");
file->Write(CAssimilateDoc::GetKeyword(TK_AS_PCJ, TABLE_QDT)," ");
file->Writeln( PCJList_GetEntry(iPCJ) );
}
}
CComment* curComment = m_comments;
while(curComment != NULL)
{
curComment->Write(file);
curComment = curComment->GetNext();
}
bool bFirstSeqWritten = false;
CSequence* curSequence = m_sequences;
while(curSequence != NULL)
{
curSequence->Write(file, !bFirstSeqWritten && GetPreQuat());
curSequence = curSequence->GetNext();
bFirstSeqWritten = true;
}
file->Writeln("$", CAssimilateDoc::GetKeyword(TK_AS_GRABFINALIZE, TABLE_QDT));
if (m_path != NULL)
{
file->Write("$", CAssimilateDoc::GetKeyword(GetConvertType(), TABLE_QDT));
CString path = m_path;
int loc = path.Find("/base");
if (loc > -1)
{
path = path.Right(path.GetLength() - loc - 5);
loc = path.Find("/");
path = path.Right(path.GetLength() - loc - 1);
}
if (!path.GetLength()) // check that some dopey artist hasn't use the name "base" on the right hand side
{
path = m_path;
}
file->Write(" ", path, " ");
// params stuff...
//
if (IsGhoul2())
{
if (GetMakeSkin())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_MAKESKIN, TABLE_CONVERT), " ");
}
if (GetSmooth())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_SMOOTH, TABLE_CONVERT), " ");
}
if (GetLoseDupVerts())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_LOSEDUPVERTS, TABLE_CONVERT), " ");
}
if (GetIgnoreBaseDeviations())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_IGNOREBASEDEVIATIONS, TABLE_CONVERT), " ");
}
if (GetSkew90())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_SKEW90, TABLE_CONVERT), " ");
}
if (GetNoSkew90())
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_NOSKEW90, TABLE_CONVERT), " ");
}
/*
if (GetSkelPath() && strlen(GetSkelPath()))
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_SKEL, TABLE_CONVERT), " ");
file->Write(GetSkelPath(), " ");
}
*/
if (GetMakeSkelPath() && strlen(GetMakeSkelPath()))
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_MAKESKEL, TABLE_CONVERT), " ");
file->Write(GetMakeSkelPath(), " ");
}
// params below not used for ghoul2
}
else
{
if (giLODLevelOverride)
{
file->Write(va("-lod %d ",giLODLevelOverride));
}
//xxxxxxxxxxxxxxxxxxxxxxxxxxx
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_PLAYERPARMS, TABLE_CONVERT), " ");
file->Write(0);//m_skipStart+1); // ignore these, but don't want to update parser and have invalid prev files
file->Space();
// file->Write(m_skipEnd); // this param no longer used
// file->Space();
file->Write(0);//m_skipEnd);//max upper frames);
file->Space();
// file->Write(m_headFrames); // this param no longer used
// file->Space();
}
//xxxxxxxxxxxxxxxxxxxxxx
if (m_originx || m_originy || m_originz)
{
file->Write("-", CAssimilateDoc::GetKeyword(TK_AS_ORIGIN, TABLE_CONVERT), " ");
file->Write(m_originx);
file->Space();
file->Write(m_originy);
file->Space();
file->Write(m_originz);
}
file->Writeln();
}
}
/////////////////////////////////////////////////////////////////////////////
// CModelPropPage property page
IMPLEMENT_DYNCREATE(CModelPropPage, CPropertyPage)
CModelPropPage::CModelPropPage() : CPropertyPage(CModelPropPage::IDD)
{
//{{AFX_DATA_INIT(CModelPropPage)
m_bSkew90 = FALSE;
m_bSmooth = FALSE;
m_strSkelPath = _T("");
m_iOriginX = 0;
m_iOriginY = 0;
m_iOriginZ = 0;
m_fScale = 0.0f;
m_bMakeSkin = FALSE;
m_bLoseDupVerts = FALSE;
m_bMakeSkel = FALSE;
m_strNewPCJ = _T("");
m_bKeepMotion = FALSE;
m_bPreQuat = FALSE;
//}}AFX_DATA_INIT
m_PCJList.clear();
}
CModelPropPage::~CModelPropPage()
{
}
void CModelPropPage::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CModelPropPage)
DDX_Check(pDX, IDC_CHECK_SKEW90, m_bSkew90);
DDX_Check(pDX, IDC_CHECK_SMOOTH_ALL, m_bSmooth);
DDX_Text(pDX, IDC_EDIT_SKELPATH, m_strSkelPath);
DDX_Text(pDX, IDC_EDIT_ORIGINX, m_iOriginX);
DDX_Text(pDX, IDC_EDIT_ORIGINY, m_iOriginY);
DDX_Text(pDX, IDC_EDIT_ORIGINZ, m_iOriginZ);
DDX_Text(pDX, IDC_EDIT_SCALE, m_fScale);
DDX_Check(pDX, IDC_CHECK_MAKESKIN, m_bMakeSkin);
DDX_Check(pDX, IDC_CHECK_LOSEDUPVERTS, m_bLoseDupVerts);
DDX_Check(pDX, IDC_CHECK_MAKESKEL, m_bMakeSkel);
DDX_Text(pDX, IDC_EDIT_PCJ, m_strNewPCJ);
DDX_Check(pDX, IDC_CHECK_KEEPMOTIONBONE, m_bKeepMotion);
DDX_Check(pDX, IDC_CHECK_PREQUAT, m_bPreQuat);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CModelPropPage, CPropertyPage)
//{{AFX_MSG_MAP(CModelPropPage)
ON_BN_CLICKED(IDC_CHECK_MAKESKEL, OnCheckMakeskel)
ON_BN_CLICKED(IDC_BUTTON_DELPCJ, OnButtonDelpcj)
ON_BN_CLICKED(IDC_BUTTON_PCJ, OnButtonPcj)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CModelPropPage message handlers
void CModelPropPage::OnOK()
{
if (!m_bMakeSkel && !m_strSkelPath.IsEmpty())
{
// actually, this SHOULD just cancel the OnOK and stay in the dialog (according to the windoze docs,
// but of course being microsoft, it doesn't bloody work, and just cancels, so I adapted the message...
//
if (!GetYesNo("Warning, you have a make-skeleton path entered, but 'makeskel' is OFF, this will lose that path info when this dialog closes.\n\nProceed? ('NO' is the same as clicking 'CANCEL')"))
return;
}
/*
// these are all irrelevant now...
//
m_model->SetHeadFrames(m_headFrames);
m_model->SetSkipEnd(m_skipEnd);
m_model->SetSkipStart(m_skipStart);
*/
m_model->SetOriginX(m_iOriginX);
m_model->SetOriginY(m_iOriginY);
m_model->SetOriginZ(m_iOriginZ);
m_model->SetSkew90(!!m_bSkew90);
m_model->SetSmooth(!!m_bSmooth);
m_model->SetLoseDupVerts(!!m_bLoseDupVerts);
m_model->SetMakeSkin(!!m_bMakeSkin);
// m_model->SetSkelPath(m_bMakeSkel?(LPCSTR)m_strSkelPath:"");
m_model->SetMakeSkelPath(m_bMakeSkel?(LPCSTR)m_strSkelPath:"");
m_model->SetScale(m_fScale);
m_model->SetKeepMotion(!!m_bKeepMotion);
m_model->SetPreQuat(!!m_bPreQuat);
m_model->PCJList_Clear();
for (int i=0; i<GetPCJEntries(); i++)
{
m_model->PCJList_AddEntry(GetPCJEntry(i));
}
*m_soilFlag = true;
CPropertyPage::OnOK();
}
void CModelPropPage::PopulatePCJList(void)
{
CListBox *pListBox = (CListBox *) GetDlgItem(IDC_LIST_PCJ);
if (pListBox)
{
pListBox->ResetContent();
for (int i = 0; i<m_PCJList.size(); i++)
{
pListBox->InsertString(-1, (LPCSTR) m_PCJList[i]);
}
}
}
BOOL CModelPropPage::OnInitDialog()
{
CPropertyPage::OnInitDialog();
/*
// these are all irrelevant now...
//
m_headFrames = m_model->GetHeadFrames();
m_skipEnd = m_model->GetSkipEnd();
m_skipStart = m_model->GetSkipStart();
m_totFrames.Format("%d", m_model->GetTotFrames());
m_totSequences.Format("%d", m_model->GetTotSequences());
*/
m_iOriginX = m_model->GetOriginX();
m_iOriginY = m_model->GetOriginY();
m_iOriginZ = m_model->GetOriginZ();
m_bSkew90 = m_model->GetSkew90();
m_bSmooth = m_model->GetSmooth();
m_bLoseDupVerts = m_model->GetLoseDupVerts();
m_bMakeSkin = m_model->GetMakeSkin();
m_strSkelPath = m_model->GetMakeSkelPath();
if (m_strSkelPath.IsEmpty())
{
// m_strSkelPath = m_model->GetSkelPath();
}
m_bMakeSkel = (m_model->GetMakeSkelPath() && strlen(m_model->GetMakeSkelPath()))?TRUE:FALSE;
m_fScale = m_model->GetScale();
m_bKeepMotion = m_model->GetKeepMotion();
m_bPreQuat = m_model->GetPreQuat();
PopulatePCJList();
UpdateData(DATA_TO_DIALOG);
HandleItemGreying();
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CModelPropPage::DelPCJEntry(int iIndex)
{
if (iIndex < m_PCJList.size())
{
m_PCJList.erase(m_PCJList.begin() + iIndex);
}
}
void CModelPropPage::AddPCJEntry(LPCSTR psPCJName)
{
CString strTemp(psPCJName);
strTemp.Replace(" ","");
strTemp.Replace("\t","");
m_PCJList.push_back(strTemp);
}
int CModelPropPage::GetPCJEntries(void)
{
return m_PCJList.size();
}
LPCSTR CModelPropPage::GetPCJEntry(int iIndex)
{
if (iIndex < m_PCJList.size())
{
return (LPCSTR) m_PCJList[iIndex];
}
assert(0);
return NULL;
}
void CModelPropPage::HandleItemGreying(void)
{
UpdateData(DIALOG_TO_DATA);
GetDlgItem(IDC_EDIT_SCALE)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_EDIT_SKELPATH)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_STATIC_SKELPATH)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_PCJ_STATIC)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_LIST_PCJ)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_EDIT_PCJ)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_BUTTON_PCJ)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_BUTTON_DELPCJ)->EnableWindow(m_bMakeSkel);
GetDlgItem(IDC_CHECK_KEEPMOTIONBONE)->EnableWindow(m_bMakeSkel);
Invalidate();
}
void CModelPropPage::OnCheckMakeskel()
{
UpdateData(DIALOG_TO_DATA);
// first time we turn it on, we should make up a reasonable default name...
//
if (m_bMakeSkel && m_strSkelPath.IsEmpty())
{
// basically I'm just going to use the dir name as the GLA name base as well...
//
CString strSuggestedPath(m_model->GetPath()); // eg. "models/players/blah/root"
int iLoc = strSuggestedPath.ReverseFind('/');
if (iLoc>=0)
{
strSuggestedPath = strSuggestedPath.Left(iLoc); // eg. "models/players/blah"
iLoc = strSuggestedPath.ReverseFind('/');
if (iLoc >= 0)
{
CString strDir(strSuggestedPath.Mid(iLoc+1)); // eg. "blah"
strSuggestedPath += "/";
strSuggestedPath += strDir; // eg. "models/players/blah/blah"
m_strSkelPath = strSuggestedPath;
UpdateData(DATA_TO_DIALOG);
}
}
}
HandleItemGreying();
}
void CModelPropPage::OnButtonPcj()
{
UpdateData(DIALOG_TO_DATA);
if (!m_strNewPCJ.IsEmpty())
{
AddPCJEntry(m_strNewPCJ);
PopulatePCJList();
UpdateData(DIALOG_TO_DATA);
m_strNewPCJ = "";
UpdateData(DATA_TO_DIALOG);
}
}
void CModelPropPage::OnButtonDelpcj()
{
CListBox *pListBox = (CListBox *) GetDlgItem(IDC_LIST_PCJ);
if (pListBox)
{
int iCurSel = pListBox->GetCurSel();
if (iCurSel != LB_ERR)
{
DelPCJEntry(iCurSel);
PopulatePCJList();
}
}
}