home *** CD-ROM | disk | FTP | other *** search
/ Isometric Game Programming with DirectX 7.0 / Isometric Game Programming.iso / directx / dxf / extras / direct3d / tools / 3dsmax3 / exportxfile.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2000-09-22  |  104.4 KB  |  3,589 lines

  1. //-----------------------------------------------------------------------------
  2. // File: ExportXFile.cpp
  3. //
  4. // Desc: Functions used to export max data to an X File
  5. //
  6. // Copyright (C) 1998-2000 Microsoft Corporation. All Rights Reserved.
  7. //-----------------------------------------------------------------------------
  8.  
  9. #include "pch.h"
  10. #include "XSkinExp.h"
  11. #include "MeshData.h"
  12.  
  13. #include <initguid.h>
  14.  
  15. #include "XSkinExpTemplates.h"
  16. #include "dxfile.h"
  17. #include "rmxfguid.h"
  18. #include "rmxftmpl.h"
  19.  
  20. #define POWER_DEFAULT   0.0
  21.  
  22. struct SDialogOptions
  23. {
  24.     BOOL                        m_bProceedWithExport;
  25.     DXFILEFORMAT                m_xFormat;
  26.     DWORD                       m_cMaxBonesPerVertex;
  27.     DWORD                       m_cMaxBonesPerFace;
  28.  
  29.     BOOL                        m_bSavePatchData;
  30.     BOOL                        m_bSaveAnimationData;
  31.     DWORD                       m_iAnimSamplingRate;
  32. };
  33.  
  34. struct SBoneInfo
  35. {
  36.     INode                       *m_pBoneNode;
  37.     DWORD                       m_cVertices;
  38. };
  39.  
  40. // structure used to map an mesh to a bone info structure
  41. struct SSkinMap
  42. {
  43.     SSkinMap()
  44.         :m_pMeshNode(NULL), m_rgbiBones(NULL), m_cbiBones(0), m_cbiBonesMax(0) {}
  45.     ~SSkinMap()
  46.         { delete []m_rgbiBones; }
  47.  
  48.     INode                       *m_pMeshNode;
  49.  
  50.     SBoneInfo                   *m_rgbiBones;
  51.     DWORD                       m_cbiBones;
  52.     DWORD                       m_cbiBonesMax;
  53.  
  54.     SBoneInfo *FindBone(INode *pBoneNode)
  55.     {
  56.         SBoneInfo *pbi = NULL;
  57.         DWORD iBone;
  58.  
  59.         for (iBone = 0; iBone < m_cbiBones; iBone++)
  60.         {
  61.             if (pBoneNode == m_rgbiBones[iBone].m_pBoneNode)
  62.             {
  63.                 pbi = &m_rgbiBones[iBone];
  64.                 break;
  65.             }
  66.         }
  67.  
  68.         return pbi;
  69.     }
  70.  
  71.     HRESULT AddBone(INode *pBoneNode, SBoneInfo **ppbiBoneInfo)
  72.     {
  73.         HRESULT hr = S_OK;
  74.         SBoneInfo *rgbiTemp;
  75.  
  76.         // reallocate if neccessary
  77.         if (m_cbiBones == m_cbiBonesMax)
  78.         {
  79.             m_cbiBonesMax = max(1, m_cbiBonesMax);
  80.             m_cbiBonesMax *= 2;
  81.  
  82.             rgbiTemp = m_rgbiBones;
  83.             m_rgbiBones = new SBoneInfo[m_cbiBonesMax];
  84.             if (m_rgbiBones == NULL)
  85.             {
  86.                 m_rgbiBones = rgbiTemp;
  87.                 hr = E_OUTOFMEMORY;
  88.                 goto e_Exit;
  89.             }
  90.  
  91.             if (m_cbiBones > 0)
  92.             {
  93.                 memcpy(m_rgbiBones, rgbiTemp, m_cbiBones * sizeof(SBoneInfo));
  94.             }
  95.  
  96.             delete []rgbiTemp;
  97.         }
  98.  
  99.         // not initialize the next bone in the array and return a pointer to it
  100.  
  101.         m_rgbiBones[m_cbiBones].m_cVertices = 0;
  102.         m_rgbiBones[m_cbiBones].m_pBoneNode = pBoneNode;
  103.  
  104.         *ppbiBoneInfo = &m_rgbiBones[m_cbiBones];
  105.  
  106.         m_cbiBones += 1;
  107.  
  108. e_Exit:
  109.         return hr;
  110.     }
  111. };
  112.  
  113. struct SPreprocessContext
  114. {
  115.     SPreprocessContext()
  116.         :m_rgpsmSkinMaps(NULL), 
  117.          m_cpsmSkinMaps(NULL), 
  118.          m_cpsmSkinMapsMax(0), 
  119.          m_cMaxWeightsPerVertex(0), 
  120.          m_cMaxWeightsPerFace(0),
  121.          m_cNodes(0) {}
  122.  
  123.     ~SPreprocessContext()
  124.         { delete []m_rgpsmSkinMaps; }
  125.  
  126.     BOOL                        m_bSaveSelection;
  127.  
  128.     SSkinMap                    **m_rgpsmSkinMaps;
  129.     DWORD                       m_cpsmSkinMaps;
  130.     DWORD                       m_cpsmSkinMapsMax;
  131.  
  132.     DWORD                       m_cMaxWeightsPerVertex;
  133.     DWORD                       m_cMaxWeightsPerFace;
  134.  
  135.     DWORD                       m_cNodes;
  136. };
  137.  
  138. const int x_cbStringBufferMax = 4088;
  139.  
  140. struct SStringBlock
  141. {
  142.     SStringBlock()
  143.         :m_psbNext(NULL), m_cbData(0) {}
  144.     ~SStringBlock()
  145.     {
  146.         delete m_psbNext;
  147.     }
  148.  
  149.     SStringBlock                *m_psbNext;
  150.     DWORD                        m_cbData;
  151.  
  152.     TCHAR                        szData[x_cbStringBufferMax];
  153. };
  154.  
  155. class CStringTable
  156. {
  157. public:
  158.     CStringTable()
  159.         :m_psbHead(NULL) {}
  160.  
  161.     ~CStringTable()
  162.     {
  163.         delete m_psbHead;
  164.     }
  165.  
  166.     // allocate a string out of the data blocks to be free'd later, and make it a valid
  167.     //   x-file name at the same time
  168.     TCHAR *CreateNiceString(TCHAR *szString)
  169.     {
  170.         TCHAR* szNewString = NULL;
  171.         BOOL bFirstCharIsDigit;
  172.         DWORD cbLength;
  173.         SStringBlock *psbNew;
  174.  
  175.         if (szString == NULL)
  176.             return NULL;
  177.  
  178.         cbLength = _tcslen(szString) + 1;
  179.  
  180.         bFirstCharIsDigit = _istdigit(*szString);
  181.         if (bFirstCharIsDigit)
  182.         {
  183.             cbLength += 1;
  184.         }
  185.  
  186.         // if no string blocks or the current doesn't have enough space, then allocate one
  187.         if ((m_psbHead == NULL) || ((x_cbStringBufferMax - m_psbHead->m_cbData) < cbLength))
  188.         {
  189.             psbNew = new SStringBlock();
  190.             if (psbNew == NULL)
  191.                 return NULL;
  192.  
  193.             psbNew->m_psbNext = m_psbHead;
  194.             m_psbHead = psbNew;
  195.         }
  196.  
  197.         // allocate a string out of the data block
  198.         szNewString = m_psbHead->szData + m_psbHead->m_cbData;
  199.         m_psbHead->m_cbData += cbLength;
  200.  
  201.         // deal with the fact that the string can't start with digits
  202.         *szNewString = _T('\0');
  203.         if( bFirstCharIsDigit ) 
  204.         {
  205.             _tcscat(szNewString, _T("_"));
  206.         }
  207.  
  208.         _tcscat(szNewString, szString);
  209.  
  210.         TCHAR* pchCur = szNewString;
  211.         while( NULL != *pchCur )
  212.         {
  213.             if( *pchCur != _T('_') && !_istalnum(*pchCur) )
  214.             {
  215.                 *pchCur = _T('_');
  216.             }
  217.             pchCur++;
  218.         }
  219.         return szNewString;
  220.     }
  221.  
  222.     // Allocate a new string with '\\' in place of '\' characters
  223.     TCHAR* CreateNiceFilename(TCHAR *szString)
  224.     {
  225.         TCHAR* szNewString = NULL;
  226.         DWORD cbNameLength;
  227.         DWORD cbLength;
  228.         TCHAR* pchCur;
  229.         TCHAR* pchOrig;
  230.         SStringBlock *psbNew;
  231.  
  232.         if( NULL == szString )
  233.         {
  234.             return NULL;
  235.         }
  236.  
  237.         cbNameLength = _tcslen(szString);
  238.         cbLength = cbNameLength*2 + 1;
  239.  
  240.  
  241.         // if no string blocks or the current doesn't have enough space, then allocate one
  242.         if ((m_psbHead == NULL) || ((x_cbStringBufferMax - m_psbHead->m_cbData) < cbLength))
  243.         {
  244.             psbNew = new SStringBlock();
  245.             if (psbNew == NULL)
  246.                 return NULL;
  247.  
  248.             psbNew->m_psbNext = m_psbHead;
  249.             m_psbHead = psbNew;
  250.         }
  251.  
  252.         // allocate a string out of the data block
  253.         szNewString = m_psbHead->szData + m_psbHead->m_cbData;
  254.         m_psbHead->m_cbData += cbLength;
  255.  
  256.         pchCur = szNewString;
  257.         pchOrig = szString;
  258.         while (NULL != *pchOrig)
  259.         {
  260.             if( _T('\\') == *pchOrig )
  261.             {
  262.                 *(pchCur++) = _T('\\');
  263.                 *(pchCur++) = _T('\\');
  264.             }
  265.             else
  266.             {
  267.                 *(pchCur++) = *pchOrig;
  268.             }
  269.             pchOrig++;
  270.         }
  271.         *pchCur = _T('\0');
  272.  
  273.         return szNewString;
  274.     }
  275. private:
  276.     SStringBlock *m_psbHead;
  277. };
  278.  
  279. struct SSaveContext
  280. {
  281.     SSaveContext()
  282.         :m_rgpsmSkinMaps(NULL), m_pAnimationSet(NULL) {}
  283.  
  284.     ~SSaveContext()
  285.         { delete []m_rgpsmSkinMaps; }
  286.  
  287.     LPDIRECTXFILESAVEOBJECT     m_pxofsave;
  288.     DXFILEFORMAT                m_xFormat;
  289.     DWORD                       m_iTime;
  290.     BOOL                        m_bSaveSelection;
  291.     BOOL                        m_bSavePatchData;
  292.     BOOL                        m_bSaveAnimationData;
  293.     DWORD                       m_iAnimSamplingRate;
  294.     DWORD                       m_cMaxWeightsPerVertex;
  295.     DWORD                       m_cMaxWeightsPerFace;
  296.  
  297.     SSkinMap                    **m_rgpsmSkinMaps;
  298.     DWORD                       m_cpsmSkinMaps;
  299.  
  300.     LPDIRECTXFILEDATA           m_pAnimationSet;
  301.     Interface                   *m_pInterface;
  302.  
  303.     DWORD                       m_cNodes;
  304.     DWORD                       m_cNodesCur;
  305.     INode                       **m_rgpnNodes;
  306.  
  307.     CStringTable                m_stStrings;
  308.  
  309.     SSkinMap *GetSkinMap(INode *pMeshNode)
  310.     {
  311.         SSkinMap *psm = NULL;
  312.         DWORD iMesh;
  313.  
  314.         for (iMesh = 0; iMesh < m_cpsmSkinMaps; iMesh++)
  315.         {
  316.             if (pMeshNode == m_rgpsmSkinMaps[iMesh]->m_pMeshNode)
  317.             {
  318.                 psm = m_rgpsmSkinMaps[iMesh];
  319.                 break;
  320.             }
  321.         }
  322.  
  323.         return psm;
  324.     }
  325.  
  326. };
  327.  
  328.  
  329. // Macros for saving data to memory at DWORD* pbCur (this pointer is incremented)
  330. #define WRITE_PTCHAR(pbCur, ptchar) {TCHAR** __pptchar = (TCHAR**)pbCur; *(__pptchar++) = ptchar;\
  331.                              pbCur = (PBYTE)__pptchar;}
  332.  
  333. #define WRITE_STRING(pbCur, pstring) {TCHAR* __pCurrDestChar = (TCHAR*)pbCur; TCHAR* __pCurrOrgChar = pstring;\
  334.                                 while(NULL != *__pCurrOrgChar) { *(__pCurrDestChar++) = *(__pCurrOrgChar++); }\
  335.                                 *(__pCurrDestChar++) = _T('\0'); pbCur = __pCurrDestChar;}\
  336.  
  337. #define WRITE_WORD(pbCur, word) {WORD* __pword = (WORD*)pbCur; *(__pword++) = word;\
  338.                              pbCur = (PBYTE)__pword;}
  339.  
  340. #define WRITE_DWORD(pbCur, dword) {DWORD* __pdword = (DWORD*)pbCur; *(__pdword++) = dword;\
  341.                              pbCur = (PBYTE)__pdword;}
  342.  
  343. #define WRITE_FLOAT(pbCur, _float) {float* __pfloat = (float*)pbCur; *(__pfloat++) = _float;\
  344.                              pbCur = (PBYTE)__pfloat;}
  345.  
  346. #define WRITE_POINT3(pbCur, _point3) {Point3 _temp = (Point3)_point3; float __tempVal;\
  347.                                __tempVal = _temp[0]; WRITE_FLOAT(pbCur, __tempVal);\
  348.                                __tempVal = _temp[1]; WRITE_FLOAT(pbCur, __tempVal);\
  349.                                __tempVal = _temp[2]; WRITE_FLOAT(pbCur, __tempVal);}
  350.  
  351. #define WRITE_COLOR(pbCur, _color) {D3DXCOLOR _temp = (D3DXCOLOR)_color; float __tempVal;\
  352.                                __tempVal = _temp.r; WRITE_FLOAT(pbCur, __tempVal);\
  353.                                __tempVal = _temp.g; WRITE_FLOAT(pbCur, __tempVal);\
  354.                                __tempVal = _temp.b; WRITE_FLOAT(pbCur, __tempVal);\
  355.                                __tempVal = _temp.a; WRITE_FLOAT(pbCur, __tempVal);}
  356.  
  357. #define WRITE_MATRIX4_FROM_MATRIX3(pbCur, _matrix3) {Point3 __tempRow = ((Matrix3)_matrix3).GetRow(0);\
  358.                                 WRITE_POINT3(pbCur, __tempRow); WRITE_FLOAT(pbCur, 0);\
  359.                                 __tempRow = _matrix3.GetRow(1); WRITE_POINT3(pbCur, __tempRow); WRITE_FLOAT(pbCur, 0);\
  360.                                 __tempRow = _matrix3.GetRow(2); WRITE_POINT3(pbCur, __tempRow); WRITE_FLOAT(pbCur, 0);\
  361.                                 __tempRow = _matrix3.GetRow(3); WRITE_POINT3(pbCur, __tempRow); WRITE_FLOAT(pbCur, 1);}
  362.  
  363. #define WRITE_MATRIX(pbCur, mat) { *(D3DXMATRIX*)pbCur = mat;\
  364.                                pbCur = (PBYTE)pbCur + sizeof(D3DXMATRIX);}
  365.  
  366. const GUID* aIds[] = {&DXFILEOBJ_XSkinMeshHeader,
  367.                 &DXFILEOBJ_VertexDuplicationIndices,
  368.                 &DXFILEOBJ_SkinWeights};
  369.  
  370.  
  371.  
  372.  
  373. BOOL CALLBACK XSkinExpOptionsDlgProc
  374.     (
  375.     HWND hWnd,
  376.     UINT message,
  377.     WPARAM wParam,
  378.     LPARAM lParam
  379.     ) 
  380. {
  381.     static SDialogOptions *pDialogOptions = NULL;
  382.     TCHAR buff[8];
  383.  
  384.     switch(message) 
  385.     {
  386.         case WM_INITDIALOG:
  387.             pDialogOptions = (SDialogOptions *)lParam;
  388.  
  389.             CenterWindow(hWnd,GetParent(hWnd));
  390.  
  391.             CheckDlgButton(hWnd, IDC_ANIMATION, 
  392.                     pDialogOptions->m_bSaveAnimationData ? BST_CHECKED : BST_UNCHECKED);
  393.  
  394.             CheckDlgButton(hWnd, IDC_PATCHDATA, 
  395.                     pDialogOptions->m_bSavePatchData ? BST_CHECKED : BST_UNCHECKED);
  396.  
  397.             if (pDialogOptions == NULL)
  398.                 return FALSE;
  399.  
  400.             switch (pDialogOptions->m_xFormat)
  401.             {
  402.                 case DXFILEFORMAT_BINARY:
  403.                     CheckRadioButton(hWnd,IDC_TEXT,IDC_BINARYCOMPRESSED,IDC_BINARY);
  404.                     break;
  405.  
  406.                 case DXFILEFORMAT_TEXT:
  407.                     CheckRadioButton(hWnd,IDC_TEXT,IDC_BINARYCOMPRESSED,IDC_TEXT);
  408.                     break;
  409.  
  410.                 case DXFILEFORMAT_BINARY | DXFILEFORMAT_COMPRESSED:
  411.                     CheckRadioButton(hWnd,IDC_TEXT,IDC_BINARYCOMPRESSED,IDC_BINARYCOMPRESSED);
  412.                     break;
  413.             }
  414.  
  415.             _stprintf(buff,_T("%d"),pDialogOptions->m_cMaxBonesPerVertex);
  416.             SetDlgItemText(hWnd,IDC_MAX_BONES_PER_VERTEX,buff);
  417.  
  418.             _stprintf(buff,_T("%d"),pDialogOptions->m_cMaxBonesPerFace);
  419.             SetDlgItemText(hWnd,IDC_MAX_BONES_PER_FACE,buff);
  420.  
  421.             _stprintf(buff,_T("%d"),pDialogOptions->m_iAnimSamplingRate);
  422.             SetDlgItemText(hWnd,IDC_SAMPLERATE,buff);
  423.  
  424.             return TRUE;
  425.  
  426.         case WM_CLOSE:
  427.             pDialogOptions->m_bProceedWithExport = FALSE;
  428.  
  429.             EndDialog(hWnd, 0);
  430.             return TRUE;
  431.  
  432.         case WM_COMMAND:
  433.             switch(LOWORD(wParam))
  434.             {
  435.             case IDC_GO:
  436.                 pDialogOptions->m_bProceedWithExport = TRUE;
  437.  
  438.                 EndDialog(hWnd,0);
  439.                 break;
  440.  
  441.             case IDC_CANCEL:
  442.                 pDialogOptions->m_bProceedWithExport = FALSE;
  443.  
  444.                 EndDialog(hWnd,0);
  445.                 break;
  446.  
  447.             case IDC_TEXT:
  448.                 pDialogOptions->m_xFormat = DXFILEFORMAT_TEXT;
  449.                 break;
  450.  
  451.             case IDC_BINARY:
  452.                 pDialogOptions->m_xFormat = DXFILEFORMAT_BINARY;
  453.                 break;
  454.  
  455.             case IDC_BINARYCOMPRESSED:
  456.                 pDialogOptions->m_xFormat = DXFILEFORMAT_BINARY | DXFILEFORMAT_COMPRESSED;
  457.                 break;
  458.  
  459.             case IDC_ANIMATION:
  460.                 pDialogOptions->m_bSaveAnimationData = !pDialogOptions->m_bSaveAnimationData;
  461.                 break;
  462.  
  463.             case IDC_PATCHDATA:
  464.                 pDialogOptions->m_bSavePatchData = !pDialogOptions->m_bSavePatchData;
  465.                 break;
  466.  
  467.             case IDC_SAMPLERATE:
  468.                 GetDlgItemText(hWnd,IDC_SAMPLERATE,buff,sizeof(buff) / sizeof(TCHAR));
  469.  
  470.                 pDialogOptions->m_iAnimSamplingRate = _ttoi(buff);
  471.                 break;
  472.  
  473.             default:
  474.                 break;
  475.             }
  476.     }
  477.     return FALSE;
  478. }
  479.  
  480. // ================================================== FindPhysiqueModifier()
  481. // Find if a given node contains a Physique Modifier
  482. // DerivedObjectPtr requires you include "modstack.h" from the MAX SDK
  483. Modifier* FindPhysiqueModifier (INode* nodePtr)
  484. {
  485.     // Get object from node. Abort if no object.
  486.     Object* ObjectPtr = nodePtr->GetObjectRef();
  487.     
  488.  
  489.     if ( NULL == ObjectPtr)
  490.     {
  491.         return NULL;
  492.     }
  493.  
  494.     // Is derived object ?
  495.     if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID)
  496.     {
  497.         // Yes -> Cast.
  498.         IDerivedObject* DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr);
  499.  
  500.         // Iterate over all entries of the modifier stack.
  501.         int ModStackIndex = 0;
  502.         while (ModStackIndex < DerivedObjectPtr->NumModifiers())
  503.         {
  504.             // Get current modifier.
  505.             Modifier* ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
  506.             Class_ID clsid = ModifierPtr->ClassID();
  507.             // Is this Physique ?
  508.             if (ModifierPtr->ClassID() == Class_ID(PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B))
  509.             {
  510.                 // Yes -> Exit.
  511.                 return ModifierPtr;
  512.             }
  513.  
  514.             // Next modifier stack entry.
  515.             ModStackIndex++;
  516.         }
  517.     }
  518.  
  519.     // Not found.
  520.     return NULL;
  521. }
  522.  
  523. // ================================================== GetTriObjectFromObjRef
  524. // Return a pointer to a TriObject given an object reference or return NULL
  525. // if the node cannot be converted to a TriObject
  526. TriObject *GetTriObjectFromObjRef
  527.     (
  528.     Object* pObj, 
  529.     BOOL *pbDeleteIt
  530.     ) 
  531. {
  532.     TriObject *pTri;
  533.  
  534.     assert(pObj != NULL);
  535.     assert(pbDeleteIt != NULL);
  536.  
  537.     *pbDeleteIt = FALSE;
  538.  
  539.     if (pObj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) 
  540.     { 
  541.         pTri = (TriObject *) pObj->ConvertToType(0, Class_ID(TRIOBJ_CLASS_ID, 0));
  542.  
  543.         // Note that the TriObject should only be deleted
  544.         // if the pointer to it is not equal to the object
  545.         // pointer that called ConvertToType()
  546.         if (pObj != pTri) 
  547.             *pbDeleteIt = TRUE;
  548.  
  549.         return pTri;
  550.     } 
  551.     else 
  552.     {
  553.         return NULL;
  554.     }
  555. }
  556.  
  557. // ================================================== GetTriObjectFromObjRef
  558. // Return a pointer to a TriObject given an object reference or return NULL
  559. // if the node cannot be converted to a TriObject
  560. PatchObject *GetPatchObjectFromObjRef
  561.     (
  562.     Object* pObj, 
  563.     BOOL *pbDeleteIt
  564.     ) 
  565. {
  566.     PatchObject *pPatchObject;
  567.  
  568.     assert(pObj != NULL);
  569.     assert(pbDeleteIt != NULL);
  570.  
  571.     *pbDeleteIt = FALSE;
  572.  
  573.     if (pObj->CanConvertToType(Class_ID(PATCHOBJ_CLASS_ID, 0))) 
  574.     { 
  575.         pPatchObject = (PatchObject *) pObj->ConvertToType(0, Class_ID(PATCHOBJ_CLASS_ID, 0));
  576.  
  577.         // Note that the PatchObject should only be deleted
  578.         // if the pointer to it is not equal to the object
  579.         // pointer that called ConvertToType()
  580.         if (pObj != pPatchObject) 
  581.             *pbDeleteIt = TRUE;
  582.  
  583.         return pPatchObject;
  584.     } 
  585.     else 
  586.     {
  587.         return NULL;
  588.     }
  589. }
  590.  
  591. // ================================================== IsExportableMesh()
  592. BOOL IsExportableMesh(INode* pNode, Object* &pObj)
  593. {
  594.     ULONG superClassID;
  595.  
  596.     assert(pNode);
  597.     pObj = pNode->GetObjectRef();
  598.  
  599.     if (pObj == NULL)
  600.     {
  601.         return FALSE;
  602.     }
  603.  
  604.     superClassID = pObj->SuperClassID();
  605.     //find out if mesh is renderable
  606.  
  607.     if( !pNode->Renderable() || pNode->IsNodeHidden())
  608.     {
  609.         return FALSE;
  610.     }
  611.  
  612.     BOOL bFoundGeomObject = FALSE;
  613.     //find out if mesh is renderable (more)
  614.     switch(superClassID)
  615.     {
  616.     case GEN_DERIVOB_CLASS_ID:
  617.  
  618.         do
  619.         {
  620.             pObj = ((IDerivedObject*)pObj)->GetObjRef();
  621.             superClassID = pObj->SuperClassID();
  622.         }
  623.         while( superClassID == GEN_DERIVOB_CLASS_ID );
  624.  
  625.         switch(superClassID)
  626.         {
  627.         case GEOMOBJECT_CLASS_ID:
  628.             bFoundGeomObject = TRUE;
  629.             break;
  630.         }
  631.         break;
  632.  
  633.     case GEOMOBJECT_CLASS_ID:
  634.         bFoundGeomObject = TRUE;
  635.  
  636.     default:
  637.         break;
  638.     }
  639.  
  640.     return bFoundGeomObject;
  641. }
  642.  
  643. // ================================================== IsExportablePatchMesh()
  644. BOOL IsExportablePatchMesh(INode* pNode, Object* &pObj)
  645. {
  646.     ULONG superClassID;
  647.     Object *pObjBase;
  648.  
  649.     assert(pNode != NULL);
  650.  
  651.     pObj = pNode->GetObjectRef();
  652.     if (pObj == NULL)
  653.         return FALSE;
  654.  
  655.     if( !pNode->Renderable() || pNode->IsNodeHidden())
  656.         return FALSE;
  657.  
  658.  
  659.     pObjBase = pObj;
  660.  
  661.     superClassID = pObjBase->SuperClassID();
  662.     switch(superClassID)
  663.     {
  664.     case GEN_DERIVOB_CLASS_ID:
  665.  
  666.         do
  667.         {
  668.             pObjBase = ((IDerivedObject*)pObjBase)->GetObjRef();
  669.             superClassID = pObjBase->SuperClassID();
  670.         }
  671.         while( superClassID == GEN_DERIVOB_CLASS_ID );
  672.  
  673.         switch(superClassID)
  674.         {
  675.         case GEOMOBJECT_CLASS_ID:
  676.             break;
  677.         }
  678.         break;
  679.  
  680.     case GEOMOBJECT_CLASS_ID:
  681.         break;
  682.  
  683.     default:
  684.         break;
  685.     }
  686.  
  687.     pObj = pObjBase;
  688.  
  689.     // if the base object is a patch based object
  690.     if ((pObjBase->ClassID() == Class_ID(PATCHOBJ_CLASS_ID, 0))
  691.         || (pObjBase->ClassID() == Class_ID(PATCHGRID_CLASS_ID, 0)))
  692.     {
  693.         // then check to make sure that the intervening steps don't disallow
  694.         //   a patch object to be converted to
  695.         return pObjBase->CanConvertToType(Class_ID(PATCHOBJ_CLASS_ID, 0));
  696.     }
  697.     else
  698.         return FALSE;
  699. }
  700.  
  701. // ================================================== dummyFn()
  702. // used by 3DS progress bar
  703. DWORD WINAPI dummyFn(LPVOID arg)
  704. {
  705.     return(0);
  706. }
  707.  
  708. // -------------------------------------------------------------------------------
  709. //   class      CFindRootNode
  710. //
  711. //   devnote    Helper class for FindRootNode, used to implement callback function
  712. //                  for scene traversal.  Finds the root node by aborting the search
  713. //                  after the first node is returned.  It takes the first node and
  714. //                  walks to the root from there.
  715. //
  716. class CFindRootNode : public ITreeEnumProc
  717. {
  718. public:
  719.     CFindRootNode()
  720.         :m_pNodeRoot(NULL) {}
  721.  
  722.     virtual int callback(INode *pNode)
  723.     {
  724.         INode *pNodeCur = pNode;
  725.  
  726.         while (!pNodeCur->IsRootNode())
  727.         {
  728.             pNodeCur = pNodeCur->GetParentNode();
  729.         }
  730.         m_pNodeRoot = pNodeCur;
  731.  
  732.         return TREE_ABORT;
  733.     }
  734.  
  735.     INode *m_pNodeRoot;
  736. };
  737.  
  738. // -------------------------------------------------------------------------------
  739. //  function    FindRootNode
  740. //
  741. //   devnote    Finds the root node of the scene by aborting a tree walk after the first node
  742. //
  743. //   returns    S_OK if a node is found, E_FAIL if not
  744. //
  745. HRESULT FindRootNode
  746.     (
  747.     IScene *pScene, 
  748.     INode **ppNode
  749.     )
  750. {
  751.     CFindRootNode RootNode;
  752.  
  753.     // grab the first node of the scene graph
  754.     pScene->EnumTree(&RootNode);
  755.  
  756.     *ppNode = RootNode.m_pNodeRoot;
  757.  
  758.     return RootNode.m_pNodeRoot != NULL ? S_OK : E_FAIL;
  759. }
  760.  
  761. HRESULT AddNormals
  762.     (
  763.     SSaveContext *psc,
  764.     SMeshData *pMeshData, 
  765.     BOOL bSwapTriOrder,
  766.     LPDIRECTXFILEDATA pParent
  767.     )
  768. {
  769.     HRESULT hr = S_OK;
  770.     LPDIRECTXFILEDATA pDataObject = NULL;
  771.     PBYTE         pbData = NULL;
  772.     PBYTE         pbCur;        
  773.     DWORD          cbSize;
  774.     DWORD cNormals;
  775.     DWORD cFaces;
  776.     DWORD iFace;
  777.     DWORD iVertex;
  778.  
  779.     assert(psc != NULL);
  780.     assert(pParent != NULL);
  781.     assert(pMeshData != NULL);
  782.  
  783.     cNormals = pMeshData->m_cVertices;
  784.     cFaces = pMeshData->m_cFaces;
  785.  
  786.     cbSize = sizeof(DWORD) // nNormals
  787.              + 3*sizeof(float)*cNormals // normals
  788.              + sizeof(DWORD) // nFaces
  789.              + cFaces* // MeshFace array
  790.                 (sizeof(DWORD) //nFaceVertexIndices (number of normal indices)
  791.                  + 3*sizeof(DWORD)); // faceVertexIndices (normal indices)
  792.  
  793.     pbCur = pbData = new BYTE[cbSize];
  794.     if (pbData == NULL)
  795.     {
  796.         hr = E_OUTOFMEMORY;
  797.         goto e_Exit;
  798.     }
  799.  
  800.     // nNormals
  801.     WRITE_DWORD(pbCur, cNormals);
  802.  
  803.     // normals
  804.     for (iVertex = 0; iVertex < pMeshData->m_cVertices; iVertex++)
  805.     {
  806.         WRITE_POINT3(pbCur, pMeshData->m_rgVertices[iVertex].vNormal);
  807.     }
  808.  
  809.     // nFaces
  810.     WRITE_DWORD(pbCur, cFaces);
  811.  
  812.  
  813.     // MeshFace array
  814.     for( iFace = 0; iFace < cFaces; iFace++ )
  815.     {
  816.         WRITE_DWORD(pbCur, 3); // nFaceVertexIndices (number of normal indices)
  817.  
  818.         // faceVertexIndices (indices to normals - same as indices to vertices for us)
  819.         if( bSwapTriOrder )
  820.         {
  821.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[0]);
  822.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[2]);
  823.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[1]);
  824.         }
  825.         else
  826.         {
  827.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[0]);
  828.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[1]);
  829.             WRITE_DWORD(pbCur, pMeshData->m_rgFaces[iFace].index[2]);
  830.         }
  831.     }
  832.  
  833.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMeshNormals,
  834.                                     NULL,
  835.                                     NULL,
  836.                                     cbSize,
  837.                                     pbData,
  838.                                     &pDataObject
  839.                                     );
  840.     if (FAILED(hr))
  841.     {
  842.         OutputDebugString("Failed to create x file data object!");
  843.         goto e_Exit;
  844.     }
  845.  
  846.     hr = pParent->AddDataObject(pDataObject);
  847.     if (FAILED(hr))
  848.     {
  849.         OutputDebugString("Failed to add x file data object!");
  850.         goto e_Exit;
  851.     }
  852.  
  853.     // falling through
  854. e_Exit:
  855.     RELEASE(pDataObject);
  856.     delete []pbData;
  857.  
  858.     return hr;
  859. }
  860.  
  861. HRESULT AddTextureCoordinates
  862.     (
  863.     SSaveContext *psc,
  864.     SMeshData* pMeshData, 
  865.     Mesh* pMesh,
  866.     LPDIRECTXFILEDATA pParent
  867.     )
  868. {
  869.     LPDIRECTXFILEDATA pDataObject = NULL;
  870.     PBYTE         pbData = NULL;
  871.     PBYTE         pbCur = NULL;
  872.     DWORD         cbSize;
  873.     DWORD         cTexCoords;
  874.     DWORD         iVertex;
  875.     DWORD         iTextureIndex;
  876.     HRESULT    hr = S_OK;
  877.  
  878.     assert( psc );
  879.     assert( pParent );
  880.     assert( pMesh );
  881.     assert( pMeshData );
  882.  
  883.     // if no tex coords, then don't add them
  884.     if( !pMeshData->m_bTexCoordsPresent )
  885.         goto e_Exit;
  886.  
  887.     cTexCoords = pMeshData->m_cVertices;
  888.  
  889.     cbSize = sizeof(DWORD) //nTextureCoords
  890.              + cTexCoords*2*sizeof(float); //texture coords
  891.  
  892.     pbCur = pbData = new BYTE[cbSize];
  893.     if (pbData == NULL)
  894.     {
  895.         hr = E_OUTOFMEMORY;
  896.         goto e_Exit;
  897.     }
  898.  
  899.     WRITE_DWORD(pbCur, cTexCoords); //nTextureCoords
  900.  
  901.     for (iVertex = 0; iVertex < pMeshData->m_cVertices; iVertex++)
  902.     {
  903.         iTextureIndex  = pMeshData->m_rgVertices[iVertex].iTextureIndex;
  904.         if( iTextureIndex == 0xFFFFFFFF ) // none present, or bad data
  905.         {
  906.             WRITE_FLOAT(pbCur, 0); //u
  907.             WRITE_FLOAT(pbCur, 0); //v
  908.         }
  909.         else
  910.         {
  911.             WRITE_FLOAT(pbCur, pMesh->tVerts[iTextureIndex].x); //u
  912.             WRITE_FLOAT(pbCur, pMesh->tVerts[iTextureIndex].y * -1); //v
  913.         }
  914.     }
  915.  
  916.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMeshTextureCoords,
  917.                                     NULL,
  918.                                     NULL,
  919.                                     cbSize,
  920.                                     pbData,
  921.                                     &pDataObject
  922.                                     );
  923.     if (FAILED(hr))
  924.     {
  925.         OutputDebugString("Failed to create x file data object!");
  926.         goto e_Exit;
  927.     }
  928.  
  929.     hr = pParent->AddDataObject(pDataObject);
  930.     if (FAILED(hr))
  931.     {
  932.         OutputDebugString("Failed to add x file data object!");
  933.         goto e_Exit;
  934.     }
  935.  
  936.     // falling through
  937. e_Exit:
  938.     RELEASE(pDataObject);
  939.  
  940.     delete []pbData;
  941.     return hr;
  942. }
  943.  
  944. HRESULT AddPatchTextureCoordinates
  945.     (
  946.     SSaveContext *psc,
  947.     SPatchMeshData* pPatchMeshData, 
  948.     PatchMesh* pPatchMesh,
  949.     LPDIRECTXFILEDATA pParent
  950.     )
  951. {
  952.     LPDIRECTXFILEDATA pDataObject = NULL;
  953.     PBYTE         pbData = NULL;
  954.     PBYTE         pbCur = NULL;
  955.     DWORD         cbSize;
  956.     DWORD         cTexCoords;
  957.     DWORD         iVertex;
  958.     DWORD         iTextureIndex;
  959.     HRESULT    hr = S_OK;
  960.  
  961.     assert( psc );
  962.     assert( pParent );
  963.     assert( pPatchMesh );
  964.     assert( pPatchMeshData );
  965.  
  966.     // if no tex coords, then don't add them
  967.     if( !pPatchMeshData->m_bTexCoordsPresent )
  968.         goto e_Exit;
  969.  
  970.     cTexCoords = pPatchMeshData->m_cVertices;
  971.  
  972.     cbSize = sizeof(DWORD) //nTextureCoords
  973.              + cTexCoords*2*sizeof(float); //texture coords
  974.  
  975.     pbCur = pbData = new BYTE[cbSize];
  976.     if (pbData == NULL)
  977.     {
  978.         hr = E_OUTOFMEMORY;
  979.         goto e_Exit;
  980.     }
  981.  
  982.     WRITE_DWORD(pbCur, cTexCoords); //nTextureCoords
  983.  
  984.     for (iVertex = 0; iVertex < pPatchMeshData->m_cVertices; iVertex++)
  985.     {
  986.         iTextureIndex  = pPatchMeshData->m_rgVertices[iVertex].iTextureIndex;
  987.         if( iTextureIndex == 0xFFFFFFFF ) // none present, or bad data
  988.         {
  989.             WRITE_FLOAT(pbCur, 0); //u
  990.             WRITE_FLOAT(pbCur, 0); //v
  991.         }
  992.         else
  993.         {
  994.             WRITE_FLOAT(pbCur, pPatchMesh->tVerts[1][iTextureIndex].x); //u
  995.             WRITE_FLOAT(pbCur, pPatchMesh->tVerts[1][iTextureIndex].y * -1); //v
  996.         }
  997.     }
  998.  
  999.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMeshTextureCoords,
  1000.                                     NULL,
  1001.                                     NULL,
  1002.                                     cbSize,
  1003.                                     pbData,
  1004.                                     &pDataObject
  1005.                                     );
  1006.     if (FAILED(hr))
  1007.     {
  1008.         OutputDebugString("Failed to create x file data object!");
  1009.         goto e_Exit;
  1010.     }
  1011.  
  1012.     hr = pParent->AddDataObject(pDataObject);
  1013.     if (FAILED(hr))
  1014.     {
  1015.         OutputDebugString("Failed to add x file data object!");
  1016.         goto e_Exit;
  1017.     }
  1018.  
  1019.     // falling through
  1020. e_Exit:
  1021.     RELEASE(pDataObject);
  1022.  
  1023.     delete []pbData;
  1024.     return hr;
  1025. }
  1026.  
  1027. HRESULT AddVertexDuplicationIndices
  1028.     (
  1029.     SSaveContext *psc,
  1030.     SMeshData *pMeshData, 
  1031.     LPDIRECTXFILEDATA pParent
  1032.     )
  1033. {
  1034.     HRESULT    hr = S_OK;
  1035.     LPDIRECTXFILEDATA pDataObject = NULL;
  1036.     PBYTE         pbData = NULL;
  1037.     PBYTE         pbCur = NULL;
  1038.     DWORD         cbSize;
  1039.     DWORD         cIndices;
  1040.     DWORD         cVerticesBeforeDuplication;
  1041.     DWORD         iVertex;
  1042.  
  1043.     assert(psc != NULL);
  1044.     assert(pParent != NULL);
  1045.     assert(pMeshData != NULL);
  1046.  
  1047.     cIndices = pMeshData->m_cVertices;
  1048.     cVerticesBeforeDuplication = pMeshData->m_cVerticesBeforeDuplication;
  1049.  
  1050.     // if no new vertices, then don't add a record to the file
  1051.     if (pMeshData->m_cVerticesBeforeDuplication == pMeshData->m_cVertices)
  1052.         goto e_Exit;
  1053.  
  1054.     cbSize = sizeof(DWORD) //nIndices
  1055.              + sizeof(DWORD) //nVerticesBeforeDuplication
  1056.              + cIndices*sizeof(DWORD); // array of indices
  1057.  
  1058.     pbCur = pbData = new BYTE[cbSize];
  1059.     if (pbData == NULL)
  1060.     {
  1061.         hr = E_OUTOFMEMORY;
  1062.         goto e_Exit;
  1063.     }
  1064.  
  1065.     WRITE_DWORD(pbCur, cIndices) // nIndices;
  1066.     WRITE_DWORD(pbCur, cVerticesBeforeDuplication) // nVerticesBeforeDuplication
  1067.     
  1068.     for (iVertex = 0; iVertex < cIndices; iVertex++)
  1069.     {
  1070.         WRITE_DWORD(pbCur, pMeshData->m_rgVertices[iVertex].iPointRep); // index to original vertex without duplication.
  1071.     }
  1072.  
  1073.     hr = psc->m_pxofsave->CreateDataObject(DXFILEOBJ_VertexDuplicationIndices,
  1074.                                     NULL,
  1075.                                     NULL,
  1076.                                     cbSize,
  1077.                                     pbData,
  1078.                                     &pDataObject
  1079.                                     );
  1080.     if (FAILED(hr))
  1081.     {
  1082.         OutputDebugString("Failed to create x file data object!");
  1083.         goto e_Exit;
  1084.     }
  1085.  
  1086.     hr = pParent->AddDataObject(pDataObject);
  1087.     if (FAILED(hr))
  1088.     {
  1089.         OutputDebugString("Failed to add x file data object!");
  1090.         goto e_Exit;
  1091.     }
  1092.  
  1093.     // falling through
  1094. e_Exit:
  1095.     RELEASE(pDataObject);
  1096.  
  1097.     delete []pbData;
  1098.     return hr;
  1099. }
  1100.  
  1101. HRESULT AddPatchVertexDuplicationIndices
  1102.     (
  1103.     SSaveContext *psc,
  1104.     SPatchMeshData *pPatchMeshData, 
  1105.     LPDIRECTXFILEDATA pParent
  1106.     )
  1107. {
  1108.     HRESULT    hr = S_OK;
  1109.     LPDIRECTXFILEDATA pDataObject = NULL;
  1110.     PBYTE         pbData = NULL;
  1111.     PBYTE         pbCur = NULL;
  1112.     DWORD         cbSize;
  1113.     DWORD         cIndices;
  1114.     DWORD         cVerticesBeforeDuplication;
  1115.     DWORD         iVertex;
  1116.  
  1117.     assert(psc != NULL);
  1118.     assert(pParent != NULL);
  1119.     assert(pPatchMeshData != NULL);
  1120.  
  1121.     cIndices = pPatchMeshData->m_cVertices;
  1122.     cVerticesBeforeDuplication = pPatchMeshData->m_cVerticesBeforeDuplication;
  1123.  
  1124.     // if no new vertices, then don't add a record to the file
  1125.     if (pPatchMeshData->m_cVerticesBeforeDuplication == pPatchMeshData->m_cVertices)
  1126.         goto e_Exit;
  1127.  
  1128.     cbSize = sizeof(DWORD) //nIndices
  1129.              + sizeof(DWORD) //nVerticesBeforeDuplication
  1130.              + cIndices*sizeof(DWORD); // array of indices
  1131.  
  1132.     pbCur = pbData = new BYTE[cbSize];
  1133.     if (pbData == NULL)
  1134.     {
  1135.         hr = E_OUTOFMEMORY;
  1136.         goto e_Exit;
  1137.     }
  1138.  
  1139.     WRITE_DWORD(pbCur, cIndices) // nIndices;
  1140.     WRITE_DWORD(pbCur, cVerticesBeforeDuplication) // nVerticesBeforeDuplication
  1141.     
  1142.     for (iVertex = 0; iVertex < cIndices; iVertex++)
  1143.     {
  1144.         WRITE_DWORD(pbCur, pPatchMeshData->m_rgVertices[iVertex].iPointRep); // index to original vertex without duplication.
  1145.     }
  1146.  
  1147.     hr = psc->m_pxofsave->CreateDataObject(DXFILEOBJ_VertexDuplicationIndices,
  1148.                                     NULL,
  1149.                                     NULL,
  1150.                                     cbSize,
  1151.                                     pbData,
  1152.                                     &pDataObject
  1153.                                     );
  1154.     if (FAILED(hr))
  1155.     {
  1156.         OutputDebugString("Failed to create x file data object!");
  1157.         goto e_Exit;
  1158.     }
  1159.  
  1160.     hr = pParent->AddDataObject(pDataObject);
  1161.     if (FAILED(hr))
  1162.     {
  1163.         OutputDebugString("Failed to add x file data object!");
  1164.         goto e_Exit;
  1165.     }
  1166.  
  1167.     // falling through
  1168. e_Exit:
  1169.     RELEASE(pDataObject);
  1170.  
  1171.     delete []pbData;
  1172.     return hr;
  1173. }
  1174.  
  1175. HRESULT
  1176. AddWireframeMaterial(
  1177.     SSaveContext *psc,
  1178.     INode *pNode,
  1179.     LPDIRECTXFILEDATA pParent
  1180.     )
  1181. {
  1182.     HRESULT hr = S_OK;
  1183.     DWORD cbSize;
  1184.     LPDIRECTXFILEDATA pDataObject = NULL;
  1185.     PBYTE pbData = NULL;
  1186.     PBYTE pbCur;
  1187.     DWORD dwWFColor;
  1188.     D3DXCOLOR colorWF;
  1189.  
  1190.     cbSize = 4*sizeof(float) // colorRGBA
  1191.              + sizeof(float) //power
  1192.              + 3*sizeof(float) //specularColor
  1193.              + 3*sizeof(float); //emissiveColor
  1194.  
  1195.     pbData = pbCur = new BYTE[cbSize];
  1196.     if (pbCur == NULL)
  1197.     {
  1198.         hr = E_OUTOFMEMORY;
  1199.         goto e_Exit;
  1200.     }
  1201.     
  1202.     // get the wireframe color
  1203.     dwWFColor = pNode->GetWireColor();
  1204.     dwWFColor |= 0xff000000;  // set alpha to fully opaque
  1205.  
  1206.     // convert to floating point
  1207.     colorWF = D3DXCOLOR(dwWFColor);
  1208.  
  1209.     //RGBA
  1210.     WRITE_COLOR(pbCur, colorWF);
  1211.  
  1212.     // Wireframe doesn't have an explicit specular power, so output our default.
  1213.     WRITE_FLOAT(pbCur, POWER_DEFAULT);
  1214.  
  1215.     // Set the specular color identical to diffuse, as recommended in 3DSMax docs.
  1216.     WRITE_FLOAT(pbCur, colorWF.r);
  1217.     WRITE_FLOAT(pbCur, colorWF.g);
  1218.     WRITE_FLOAT(pbCur, colorWF.b);
  1219.  
  1220.     // Set the luminence to 0: the material isn't glowing.
  1221.     WRITE_FLOAT(pbCur, 0.0f);
  1222.     WRITE_FLOAT(pbCur, 0.0f);
  1223.     WRITE_FLOAT(pbCur, 0.0f);
  1224.  
  1225.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMaterial,
  1226.                                     NULL,
  1227.                                     NULL,
  1228.                                     cbSize,
  1229.                                     pbData,
  1230.                                     &pDataObject
  1231.                                     );
  1232.  
  1233.     hr = pParent->AddDataObject(pDataObject);
  1234.     if (FAILED(hr))
  1235.         goto e_Exit;
  1236.  
  1237. e_Exit:
  1238.     delete []pbData;
  1239.     RELEASE(pDataObject);
  1240.  
  1241.     return hr;
  1242. }
  1243.  
  1244. HRESULT
  1245. AddTextureFilename(
  1246.     SSaveContext *psc,
  1247.     TCHAR *szName,
  1248.     LPDIRECTXFILEDATA pParent
  1249.     )
  1250. {
  1251.     HRESULT hr = S_OK;
  1252.     LPDIRECTXFILEDATA pDataObject = NULL;
  1253.     DWORD cbSize;
  1254.  
  1255.     cbSize = sizeof(TCHAR**);
  1256.  
  1257.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMTextureFilename,
  1258.                                     NULL,
  1259.                                     NULL,
  1260.                                     cbSize,
  1261.                                     &szName,
  1262.                                     &pDataObject
  1263.                                     );
  1264.     if (FAILED(hr))
  1265.         goto e_Exit;
  1266.  
  1267.     hr = pParent->AddDataObject(pDataObject);
  1268.     if (FAILED(hr))
  1269.         goto e_Exit;
  1270.  
  1271. e_Exit:
  1272.     RELEASE(pDataObject);
  1273.  
  1274.     return hr;
  1275. }
  1276.  
  1277. // ================================================== FindTextureFileName
  1278. // callback called by 3DSMAX
  1279. class FindTextureFilename : public NameEnumCallback
  1280. {
  1281. public:
  1282.     FindTextureFilename()
  1283.         :m_szTextureName(NULL) {}
  1284.  
  1285.     virtual void RecordName(TCHAR *szName)
  1286.     {
  1287.         m_szTextureName = szName;
  1288.     }
  1289.  
  1290.     TCHAR *m_szTextureName;    
  1291. };
  1292.  
  1293. HRESULT
  1294. AddMaterial(
  1295.     SSaveContext *psc,
  1296.     Mtl *pMtl,
  1297.     LPDIRECTXFILEDATA pParent
  1298.     )
  1299. {
  1300.     HRESULT hr = S_OK;
  1301.     LPDIRECTXFILEDATA pDataObject = NULL;
  1302.     PBYTE pbData = NULL;
  1303.     PBYTE pbCur;
  1304.     DWORD cbSize;
  1305.     TCHAR *szNiceFilename;
  1306.     Texmap *pTexMap;
  1307.     FindTextureFilename findTextureFilename;
  1308.     BOOL bDetailMap = FALSE;
  1309.  
  1310.     cbSize = 4*sizeof(float) // colorRGBA
  1311.              + sizeof(float) //power
  1312.              + 3*sizeof(float) //specularColor
  1313.              + 3*sizeof(float); //emissiveColor
  1314.  
  1315.     pbData = pbCur = new BYTE[cbSize];
  1316.     if (pbCur == NULL)
  1317.     {
  1318.         hr = E_OUTOFMEMORY;
  1319.         goto e_Exit;
  1320.     }
  1321.  
  1322.     pTexMap = pMtl->GetActiveTexmap();
  1323.     if ((pTexMap != NULL) && (pTexMap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0x00)))
  1324.     {
  1325.         bDetailMap = TRUE;
  1326.     }
  1327.  
  1328.     
  1329.     // 'Ambient color' is specified in Max docs as "the color of the object in shadows," and 
  1330.     //                 is usually a darker version of the diffuse color.
  1331.     // 'Diffuse color' is specified in the Max docs as "the color of the object in 
  1332.     //                  good lighting," usually referred to as the objects' color.
  1333.     // 'Specular color' is specified as the color of reflection highlights in direct lighting,
  1334.     //                  and according to Max docs is usually the same as diffuse.
  1335.  
  1336.     if (!bDetailMap)
  1337.     {
  1338.         // We're going to use the 'Diffuse' color as the object color for DirectX
  1339.         WRITE_POINT3(pbCur, pMtl->GetDiffuse());
  1340.         //Alpha
  1341.         WRITE_FLOAT(pbCur, 1 - pMtl->GetXParency());
  1342.  
  1343.         // 3DSMax specular power is comprised of shininess, and shininess strength.
  1344.         // TODO - figure out a mapping from shininess to power
  1345.         WRITE_FLOAT(pbCur, POWER_DEFAULT);
  1346.  
  1347.         // Specular
  1348.         WRITE_POINT3(pbCur, pMtl->GetSpecular());
  1349.  
  1350.         // Emmissive
  1351.         WRITE_POINT3(pbCur, pMtl->GetSelfIllumColor());
  1352.  
  1353.     }
  1354.     else  // if a detail map, then don't write the color
  1355.     {
  1356.         // diffuse - write white RGBA
  1357.         WRITE_FLOAT(pbCur, 1.0f);
  1358.         WRITE_FLOAT(pbCur, 1.0f);
  1359.         WRITE_FLOAT(pbCur, 1.0f);
  1360.         WRITE_FLOAT(pbCur, 1.0f);
  1361.  
  1362.         // specular power
  1363.         WRITE_FLOAT(pbCur, POWER_DEFAULT);
  1364.  
  1365.         // specular - write white RGBA
  1366.         WRITE_FLOAT(pbCur, 1.0f);
  1367.         WRITE_FLOAT(pbCur, 1.0f);
  1368.         WRITE_FLOAT(pbCur, 1.0f);
  1369.  
  1370.         // emmissive - write white RGBA
  1371.         WRITE_FLOAT(pbCur, 0.0f);
  1372.         WRITE_FLOAT(pbCur, 0.0f);
  1373.         WRITE_FLOAT(pbCur, 0.0f);
  1374.     }
  1375.  
  1376.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMaterial,
  1377.                                     NULL,
  1378.                                     NULL,
  1379.                                     cbSize,
  1380.                                     pbData,
  1381.                                     &pDataObject
  1382.                                     );
  1383.     if (FAILED(hr))
  1384.         goto e_Exit;
  1385.  
  1386.     // See if there is a valid bitmap texture
  1387.     if (bDetailMap)
  1388.     {
  1389.         pTexMap->EnumAuxFiles(findTextureFilename, FILE_ENUM_ALL);
  1390.  
  1391.         if (findTextureFilename.m_szTextureName == NULL)
  1392.         {
  1393.             OutputDebugString("AddMaterial: Bitmap texture found, but no texture name\n");
  1394.             hr = E_FAIL;
  1395.             goto e_Exit;
  1396.         }
  1397.  
  1398.         if (psc->m_xFormat == DXFILEFORMAT_TEXT)
  1399.         {
  1400.             szNiceFilename = psc->m_stStrings.CreateNiceFilename(findTextureFilename.m_szTextureName); /*expand \ char to \\ */
  1401.             if (szNiceFilename == NULL)
  1402.             {
  1403.                 hr = E_OUTOFMEMORY;
  1404.                 goto e_Exit;
  1405.             }
  1406.  
  1407.  
  1408.             hr = AddTextureFilename(psc, szNiceFilename, pDataObject);
  1409.             if (FAILED(hr))
  1410.                 goto e_Exit;
  1411.         }
  1412.         else
  1413.         {
  1414.             hr = AddTextureFilename(psc, findTextureFilename.m_szTextureName, pDataObject);
  1415.             if (FAILED(hr))
  1416.                 goto e_Exit;
  1417.         }
  1418.     }
  1419.  
  1420.     hr = pParent->AddDataObject(pDataObject);
  1421.     if (FAILED(hr))
  1422.         goto e_Exit;
  1423.  
  1424. e_Exit:
  1425.     delete []pbData;
  1426.     RELEASE(pDataObject);
  1427.  
  1428.     return hr;
  1429. }
  1430.  
  1431. HRESULT
  1432. AddMeshMaterials(
  1433.     SSaveContext *psc,
  1434.     INode *pNode,
  1435.     Mesh *pMesh,
  1436.     LPDIRECTXFILEDATA pParent
  1437.     )
  1438. {
  1439.     HRESULT hr = S_OK;
  1440.     LPDIRECTXFILEDATA pDataObject = NULL;
  1441.     Mtl *pMtlMain;
  1442.     Mtl *pMtlCur;
  1443.     DWORD cSubMaterials;
  1444.     DWORD iFace;
  1445.     DWORD cFaces;
  1446.     BOOL bNoSubMaterials;
  1447.     BOOL bWireframeColor;
  1448.     PBYTE pbCur;
  1449.     PBYTE pbData = NULL;
  1450.     DWORD cbSize;
  1451.     DWORD iCurMatID;
  1452.     DWORD iCurMaterial;
  1453.  
  1454.     pMtlMain = pNode->GetMtl();
  1455.     cFaces = pMesh->getNumFaces();
  1456.     cSubMaterials = 0;
  1457.     bNoSubMaterials = FALSE;
  1458.     bWireframeColor = FALSE;
  1459.  
  1460.     // The color of a given mesh can be provided by three different sources:
  1461.     //   1) applied texture maps, as part of a material
  1462.     //   2) explicitly defined & applied materials without texture maps
  1463.     //   3) a 'wireframe' color.
  1464.     
  1465.     // For our purposes, we will output these in this order of preference, ignoring
  1466.     // processing past the first one we find.
  1467.  
  1468.     cbSize = sizeof(DWORD) // nMaterials
  1469.                 + sizeof(DWORD) // nFaceIndexes
  1470.                 + cFaces*sizeof(DWORD); // face indexes
  1471.  
  1472.     pbData = pbCur = new BYTE[cbSize];
  1473.     if (pbCur == NULL)
  1474.     {
  1475.         hr = E_OUTOFMEMORY;
  1476.         goto e_Exit;
  1477.     }
  1478.     
  1479.     if (pMtlMain != NULL)
  1480.     {
  1481.         // There is at least one material. We're in case 1) or 2)
  1482.  
  1483.         cSubMaterials = pMtlMain->NumSubMtls();
  1484.         if (cSubMaterials < 1)
  1485.         {
  1486.             // Count the material itself as a submaterial.
  1487.             cSubMaterials = 1;
  1488.             bNoSubMaterials = TRUE;
  1489.         }
  1490.     }
  1491.     else  // no material, then we'll create a material corresponding to the default wire color.
  1492.     {
  1493.         bWireframeColor = TRUE;
  1494.         cSubMaterials = 1;
  1495.     }
  1496.  
  1497.     WRITE_DWORD(pbCur, cSubMaterials);
  1498.     WRITE_DWORD(pbCur, cFaces);
  1499.  
  1500.  
  1501.     // For each face, output the index of the material which applies to it, 
  1502.     // starting from  0
  1503.  
  1504.     for (iFace=0; iFace < cFaces; iFace++)
  1505.     {
  1506.  
  1507.         if (bWireframeColor || bNoSubMaterials) 
  1508.         {
  1509.             // If we're using wireframe color, it's our only material
  1510.             WRITE_DWORD(pbCur, 0);
  1511.         }
  1512.         else
  1513.         {
  1514.             // Otherwise we have at least one material to process.
  1515.  
  1516.             iCurMatID = pMesh->faces[iFace].getMatID();
  1517.  
  1518.             if (cSubMaterials == 1)
  1519.             {
  1520.                 iCurMatID = 0;
  1521.             }
  1522.             else
  1523.             {
  1524.                 // SDK recommends mod'ing the material ID by the valid # of materials, 
  1525.                 // as sometimes a material number that's too high is returned.
  1526.                 iCurMatID %= cSubMaterials;
  1527.             }
  1528.  
  1529.             // output the appropriate material color
  1530.  
  1531.             WRITE_DWORD(pbCur, iCurMatID);
  1532.  
  1533.         } 
  1534.  
  1535.     } 
  1536.  
  1537.     // now finally create the mesh material list
  1538.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMeshMaterialList,
  1539.                                     NULL,
  1540.                                     NULL,
  1541.                                     cbSize,
  1542.                                     pbData,
  1543.                                     &pDataObject
  1544.                                     );
  1545.     if (FAILED(hr))
  1546.         goto e_Exit;
  1547.  
  1548.     hr = pParent->AddDataObject(pDataObject);
  1549.     if (FAILED(hr))
  1550.         goto e_Exit;
  1551.  
  1552.     // 3DSMax uses wireframe color as its default material for a mesh.
  1553.     // Output the wireframe color as the material if there are no explicit materials.
  1554.  
  1555.     if (bWireframeColor)
  1556.     {
  1557.         AddWireframeMaterial(psc, pNode, pDataObject);
  1558.     } 
  1559.     else
  1560.     {
  1561.         // 3DSMax allows multiple materials to be used on a single mesh via
  1562.         // 'submaterials'. When the first submaterial is defined, the main material
  1563.         // is copied into submaterial #1, and the new submaterial because submaterial #2.
  1564.         // 
  1565.         // We have to catch the case where there's a material, but no submaterials. For this
  1566.         // case, set NumSubMaterials to 1 which would never happen otherwise. It's imperative
  1567.         // that the first material be set to MtlMain, rather than trying to GetSubMtl() to 
  1568.         // allow this logic to work.
  1569.  
  1570.         // Loop through the materials (if any) and output them.
  1571.  
  1572.         for (iCurMaterial = 0; (iCurMaterial < cSubMaterials); iCurMaterial++)
  1573.         {
  1574.             if (bNoSubMaterials)
  1575.             {
  1576.                 // We're on the parent material, possibly the ONLY material.
  1577.                 // We won't be able to get it with GetSubMtl() if it's the only one, and
  1578.                 // the data in the first submaterial is identical to the main material,
  1579.                 // so just use the main material.
  1580.  
  1581.                 pMtlCur = pMtlMain;
  1582.             }
  1583.             else
  1584.             {
  1585.                 // We're into the true submaterials. Safe to get with 'GetSubMtl'
  1586.  
  1587.                 pMtlCur = pMtlMain->GetSubMtl(iCurMaterial);
  1588.             }
  1589.  
  1590.             hr = AddMaterial(psc, pMtlCur, pDataObject);
  1591.             if (FAILED(hr))
  1592.                 goto e_Exit;
  1593.         } 
  1594.     }
  1595.  
  1596. e_Exit:
  1597.     delete []pbData;
  1598.     RELEASE(pDataObject);
  1599.  
  1600.  
  1601.     return hr;
  1602.  
  1603. // structe used by AddSkinData to convert skin data from per vertex to per bone
  1604. struct SBoneState
  1605. {
  1606.     SBoneState()
  1607.         :m_pbData(NULL), m_szBoneName(NULL) {}
  1608.     ~SBoneState()
  1609.         { 
  1610.             delete []m_pbData; 
  1611.  
  1612.             // Bone name is allocated out of a string pool because it has to be deleted after
  1613.             //   the data has been saved to disk
  1614.             //delete []m_szBoneName; 
  1615.         }
  1616.  
  1617.     // info computed up front (note m_rgdwIndices, m_rgfWeights and m_pmatOFfset point into pbData)
  1618.     INode *m_pBoneNode;
  1619.     DWORD m_cVertices;
  1620.     DWORD m_cbData;
  1621.     PBYTE m_pbData;
  1622.     char *m_szBoneName;
  1623.     DWORD *m_rgdwIndices;
  1624.     float *m_rgfWeights;
  1625.     D3DXMATRIX *m_pmatOffset;
  1626.  
  1627.     // current index of last added vertex index
  1628.     DWORD m_iCurIndex;
  1629. };
  1630.  
  1631. SBoneState *FindBoneData
  1632.     (
  1633.     INode *pBoneNode, 
  1634.     SBoneState *rgbsBoneData, 
  1635.     DWORD cBones
  1636.     )
  1637. {
  1638.     DWORD iBone;
  1639.     SBoneState *pbs = NULL;
  1640.  
  1641.     for (iBone = 0; iBone < cBones; iBone++)
  1642.     {
  1643.         if (rgbsBoneData[iBone].m_pBoneNode == pBoneNode)
  1644.         {
  1645.             pbs = &rgbsBoneData[iBone];
  1646.             break;
  1647.         }
  1648.     }
  1649.  
  1650.     return pbs;
  1651. }
  1652.  
  1653. HRESULT AddSkinData
  1654.     (
  1655.     SSaveContext *psc,
  1656.     INode *pNode,
  1657.     SMeshData *pMeshData,
  1658.     LPDIRECTXFILEDATA pParent
  1659.     )
  1660. {
  1661.     HRESULT hr = S_OK;
  1662.     IPhyVertexExport *pVertexExport;
  1663.     IPhyRigidVertex* pRigidVertexExport;
  1664.     IPhyBlendedRigidVertex *pBlendedRigidVertexExport;
  1665.     INode* pBoneNode;
  1666.     SSkinMap *psmSkinMap = NULL;
  1667.     Modifier* pPhyMod = NULL;
  1668.     IPhysiqueExport* pPhyExport = NULL;
  1669.     IPhyContextExport* pPhyContextExport = NULL;
  1670.     SBoneState *rgbsBoneData = NULL;
  1671.     SBoneState *rgbsBoneDataDups = NULL;
  1672.     SBoneState *pbsCurData = NULL;
  1673.     SBoneState *pbsOldData = NULL;
  1674.     DWORD iVertex;
  1675.     DWORD cVertices;
  1676.     DWORD iVertexType;
  1677.     DWORD cTotalBones;
  1678.     DWORD iBone;
  1679.     PBYTE pbCur;
  1680.     float fWeight;
  1681.     LPDIRECTXFILEDATA pDataObject = NULL;
  1682.     DWORD iIndex;
  1683.     DWORD iNextWedge;
  1684.     Matrix3 matMesh;
  1685.     Matrix3 matOffset;
  1686.     Matrix3 matZero;
  1687.     PBYTE pbHeaderData = NULL;
  1688.     DWORD cbHeaderData;
  1689.     static bool shownError = false;
  1690.  
  1691.     matZero.Zero();
  1692.     pPhyMod = FindPhysiqueModifier(pNode);
  1693.     if (pPhyMod != NULL)
  1694.     {
  1695.         // Get a Physique Export interface
  1696.         pPhyExport = (IPhysiqueExport *)pPhyMod->GetInterface(I_PHYINTERFACE);
  1697.         if (pPhyExport == NULL)
  1698.         {   // not all interfaces present, so ignore
  1699.             goto e_NoPhysique;
  1700.         }
  1701.  
  1702.         // get the init matrix for the mesh (used to mult by the bone matrices)
  1703.         int rval = pPhyExport->GetInitNodeTM(pNode, matMesh);
  1704.         if (rval || matMesh.Equals(matZero, 0.0))
  1705.         {
  1706.             if (!shownError)
  1707.             {
  1708.                 shownError = true;
  1709.                 MessageBox(NULL, "This mesh was skinned using an older version of Character Studio. Mesh may not be exported correctly",
  1710.                            "X File export Warning", MB_OK);
  1711.             }
  1712.         }
  1713.  
  1714.         // For a given Object's INode get a
  1715.         // ModContext Interface from the Physique Export Interface:
  1716.         pPhyContextExport = (IPhyContextExport *)pPhyExport->GetContextInterface(pNode);
  1717.         if(pPhyContextExport == NULL)
  1718.         {
  1719.             // not all interfaces present, so ignore
  1720.             goto e_NoPhysique;
  1721.         }
  1722.  
  1723.         // should already have been converted to rigid with blending by Preprocess
  1724.             // convert to rigid with blending
  1725.             pPhyContextExport->ConvertToRigid(TRUE);
  1726.             pPhyContextExport->AllowBlending(TRUE);
  1727.  
  1728.         psmSkinMap = psc->GetSkinMap(pNode);
  1729.         if (psmSkinMap == NULL)
  1730.         {
  1731.             OutputDebugString("AddSkinData: Found physique info at save time, but not preprocess\n");
  1732.             hr = E_FAIL;
  1733.             goto e_Exit;
  1734.         }
  1735.  
  1736.         // now setup state used to fill arrays for output 
  1737.         rgbsBoneData = new SBoneState[psmSkinMap->m_cbiBones];
  1738.         if (rgbsBoneData == NULL)
  1739.         {
  1740.             hr = E_OUTOFMEMORY;
  1741.             goto e_Exit;
  1742.         }
  1743.  
  1744.         // intialize each of the bone structures 
  1745.         for (iBone = 0; iBone < psmSkinMap->m_cbiBones; iBone++)
  1746.         {
  1747.             pbsCurData = &rgbsBoneData[iBone];
  1748.  
  1749.             pbsCurData->m_iCurIndex = 0;
  1750.             pbsCurData->m_cVertices = psmSkinMap->m_rgbiBones[iBone].m_cVertices;
  1751.             pbsCurData->m_pBoneNode = psmSkinMap->m_rgbiBones[iBone].m_pBoneNode;
  1752.  
  1753.             // allocate memory to pass to D3DXOF lib
  1754.             pbsCurData->m_cbData = sizeof(TCHAR*)
  1755.                                     + sizeof(DWORD) // numWeights
  1756.                                     + sizeof(DWORD) * pbsCurData->m_cVertices // array of vertex indices
  1757.                                     + sizeof(float) * pbsCurData->m_cVertices // array of weights
  1758.                                     + sizeof(float) * 16; // offset matrix
  1759.  
  1760.             pbCur = pbsCurData->m_pbData = new BYTE[pbsCurData->m_cbData];
  1761.             if (pbsCurData->m_pbData == NULL)
  1762.             {
  1763.                 hr = E_OUTOFMEMORY;
  1764.                 goto e_Exit;
  1765.             }
  1766.  
  1767.             // fill first few entries, and remember pointers to the other arrays/matrix
  1768.             WRITE_PTCHAR(pbCur, pbsCurData->m_szBoneName);
  1769.             WRITE_DWORD(pbCur, pbsCurData->m_cVertices);
  1770.  
  1771.             pbsCurData->m_rgdwIndices = (DWORD*)pbCur;
  1772.             pbCur += sizeof(DWORD) * pbsCurData->m_cVertices;
  1773.  
  1774.             pbsCurData->m_rgfWeights = (float*)pbCur;
  1775.             pbCur += sizeof(float) * pbsCurData->m_cVertices;
  1776.  
  1777.             pbsCurData->m_pmatOffset = (D3DXMATRIX*)pbCur;
  1778.  
  1779.             // compute the mat offset
  1780.             int rval = pPhyExport->GetInitNodeTM(pbsCurData->m_pBoneNode, matOffset);
  1781.             if (rval)
  1782.             {
  1783.                 MessageBox(NULL, "This mesh was skinned using an older version of Character Studio. Mesh may not be exported correctly",
  1784.                            "X File export Warning", MB_OK);
  1785.             }
  1786.             matOffset.Invert();
  1787.             matOffset = matMesh * matOffset;
  1788.  
  1789.             WRITE_MATRIX4_FROM_MATRIX3(pbCur, matOffset);
  1790.         }
  1791.  
  1792.         cVertices = pPhyContextExport->GetNumberVertices();
  1793.         for (iVertex = 0; iVertex < cVertices; iVertex++ )
  1794.         {
  1795.             pVertexExport = (IPhyVertexExport *)pPhyContextExport->GetVertexInterface(iVertex);    
  1796.             if (pVertexExport == NULL)
  1797.             {
  1798.                 OutputDebugString("Couldn't get export interface!");
  1799.                 hr = E_FAIL;
  1800.                 goto e_Exit;
  1801.             }
  1802.         
  1803.             // What kind of vertices are these?
  1804.             iVertexType = pVertexExport->GetVertexType();
  1805.  
  1806.             pPhyContextExport->ReleaseVertexInterface( pVertexExport );    
  1807.  
  1808.             if( iVertexType == RIGID_TYPE )
  1809.             {
  1810.                 pRigidVertexExport = (IPhyRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  1811.                 if (pRigidVertexExport == NULL)
  1812.                 {
  1813.                     OutputDebugString("Couldn't get rigid vertex export interface!");
  1814.                     hr = E_FAIL;
  1815.                     goto e_Exit;
  1816.                 }
  1817.                 // Get the vertex info!
  1818.             
  1819.                 pBoneNode = pRigidVertexExport->GetNode();
  1820.  
  1821.                 pbsCurData = FindBoneData(pBoneNode, rgbsBoneData, psmSkinMap->m_cbiBones);
  1822.                 if (pbsCurData == NULL)
  1823.                 {
  1824.                     assert(0);
  1825.                     OutputDebugString("AddSkinData: Bone node not found on second pass\n");
  1826.                     hr = E_FAIL;
  1827.                     goto e_Exit;
  1828.                 }
  1829.  
  1830.                 pbsCurData->m_rgdwIndices[pbsCurData->m_iCurIndex] = iVertex;
  1831.                 pbsCurData->m_rgfWeights[pbsCurData->m_iCurIndex] = 1.0f;
  1832.                 pbsCurData->m_iCurIndex += 1;
  1833.  
  1834.                 pPhyContextExport->ReleaseVertexInterface( pRigidVertexExport);
  1835.             }
  1836.             else
  1837.             {
  1838.                 assert( iVertexType == RIGID_BLENDED_TYPE );
  1839.  
  1840.                 pBlendedRigidVertexExport = (IPhyBlendedRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  1841.                 if (pBlendedRigidVertexExport == NULL)
  1842.                 {
  1843.                     OutputDebugString("Couldn't get blended rigid vertex export interface!");
  1844.                     hr = E_FAIL;
  1845.                     goto e_Exit;
  1846.                 }
  1847.  
  1848.                 // How many nodes affect his vertex?
  1849.                 cTotalBones = pBlendedRigidVertexExport->GetNumberNodes();
  1850.                 for (iBone = 0; iBone < cTotalBones; iBone++ )
  1851.                 {
  1852.                     pBoneNode = pBlendedRigidVertexExport->GetNode(iBone);
  1853.  
  1854.                     pbsCurData = FindBoneData(pBoneNode, rgbsBoneData, psmSkinMap->m_cbiBones);
  1855.                     if (pbsCurData == NULL)
  1856.                     {
  1857.                         assert(0);
  1858.                         OutputDebugString("AddSkinData: Bone node not found on second pass\n");
  1859.                         hr = E_FAIL;
  1860.                         goto e_Exit;
  1861.                     }
  1862.  
  1863.                     fWeight = pBlendedRigidVertexExport->GetWeight(iBone);
  1864.  
  1865.                     // first see if it is a repeat weight, is so just add to previous
  1866.                     if ((pbsCurData->m_iCurIndex > 0) && (pbsCurData->m_rgdwIndices[pbsCurData->m_iCurIndex-1] == iVertex))
  1867.                     {
  1868.                         pbsCurData->m_rgfWeights[pbsCurData->m_iCurIndex-1] += fWeight;
  1869.                     }
  1870.                     else
  1871.                     {
  1872.                         pbsCurData->m_rgdwIndices[pbsCurData->m_iCurIndex] = iVertex;
  1873.                         pbsCurData->m_rgfWeights[pbsCurData->m_iCurIndex] = fWeight;
  1874.                         pbsCurData->m_iCurIndex += 1;
  1875.                     }                    
  1876.                 }
  1877.  
  1878.                 pPhyContextExport->ReleaseVertexInterface( pBlendedRigidVertexExport);
  1879.             }
  1880.         }
  1881.     }
  1882. e_NoPhysique:
  1883.  
  1884.  
  1885.  
  1886.     if (rgbsBoneData != NULL)
  1887.     {
  1888.         assert(psmSkinMap != NULL);
  1889.  
  1890. // now deal with the wonderful world of duplicated indices
  1891.         rgbsBoneDataDups = new SBoneState[psmSkinMap->m_cbiBones];
  1892.         if (rgbsBoneDataDups == NULL)
  1893.         {
  1894.             hr = E_OUTOFMEMORY;
  1895.             goto e_Exit;
  1896.         }
  1897.  
  1898.         // first calculate new lengths for the bone weight arrays
  1899.         for (iBone = 0; iBone < psmSkinMap->m_cbiBones; iBone++)
  1900.         {
  1901.             pbsOldData = &rgbsBoneData[iBone];
  1902.             pbsCurData = &rgbsBoneDataDups[iBone];
  1903.  
  1904.             pbsCurData->m_cVertices = pbsOldData->m_cVertices;
  1905.             for (iIndex = 0; iIndex < pbsOldData->m_cVertices; iIndex++)
  1906.             {
  1907.                 iVertex = pbsOldData->m_rgdwIndices[iIndex];
  1908.  
  1909.                 iNextWedge = pMeshData->m_rgVertices[iVertex].iWedgeList;
  1910.                 while (iVertex != iNextWedge)
  1911.                 {
  1912.                     pbsCurData->m_cVertices += 1;
  1913.  
  1914.                     iNextWedge = pMeshData->m_rgVertices[iNextWedge].iWedgeList;
  1915.                 }
  1916.             }
  1917.         }
  1918.  
  1919.         // next build 
  1920.         for (iBone = 0; iBone < psmSkinMap->m_cbiBones; iBone++)
  1921.         {
  1922.             pbsOldData = &rgbsBoneData[iBone];
  1923.             pbsCurData = &rgbsBoneDataDups[iBone];
  1924.  
  1925.             pbsCurData->m_pBoneNode = pbsOldData->m_pBoneNode;
  1926.             pbsCurData->m_iCurIndex = 0;
  1927.  
  1928.             // allocate memory to pass to D3DXOF lib
  1929.             pbsCurData->m_cbData = sizeof(TCHAR*)
  1930.                                     + sizeof(DWORD) // numWeights
  1931.                                     + sizeof(DWORD) * pbsCurData->m_cVertices // array of vertex indices
  1932.                                     + sizeof(float) * pbsCurData->m_cVertices // array of weights
  1933.                                     + sizeof(float) * 16; // offset matrix
  1934.  
  1935.             pbCur = pbsCurData->m_pbData = new BYTE[pbsCurData->m_cbData];
  1936.             pbsCurData->m_szBoneName = psc->m_stStrings.CreateNiceString(pbsCurData->m_pBoneNode->GetName());
  1937.             if ((pbsCurData->m_pbData == NULL) || (pbsCurData->m_szBoneName == NULL))
  1938.             {
  1939.                 hr = E_OUTOFMEMORY;
  1940.                 goto e_Exit;
  1941.             }
  1942.  
  1943.             // fill first few entries, and remember pointers to the other arrays/matrix
  1944.             WRITE_PTCHAR(pbCur, pbsCurData->m_szBoneName);
  1945.             WRITE_DWORD(pbCur, pbsCurData->m_cVertices);
  1946.  
  1947.             pbsCurData->m_rgdwIndices = (DWORD*)pbCur;
  1948.             pbCur += sizeof(DWORD) * pbsCurData->m_cVertices;
  1949.  
  1950.             pbsCurData->m_rgfWeights = (float*)pbCur;
  1951.             pbCur += sizeof(float) * pbsCurData->m_cVertices;
  1952.  
  1953.             pbsCurData->m_pmatOffset = (D3DXMATRIX*)pbCur;
  1954.  
  1955.             // already loaded above, copy from the original state data
  1956.             *pbsCurData->m_pmatOffset = *pbsOldData->m_pmatOffset;
  1957.  
  1958.  
  1959.             // now that we have the new states set up, copy the data from the old states
  1960.             for (iIndex = 0; iIndex < pbsOldData->m_cVertices; iIndex++)
  1961.             {
  1962.                 iVertex = pbsOldData->m_rgdwIndices[iIndex];
  1963.  
  1964.                 pbsCurData->m_rgdwIndices[pbsCurData->m_iCurIndex] = iVertex;
  1965.                 pbsCurData->m_rgfWeights[pbsCurData->m_iCurIndex] = pbsOldData->m_rgfWeights[iIndex];
  1966.                 pbsCurData->m_iCurIndex += 1;
  1967.  
  1968.                 iNextWedge = pMeshData->m_rgVertices[iVertex].iWedgeList;
  1969.                 while (iVertex != iNextWedge)
  1970.                 {
  1971.                     pbsCurData->m_rgdwIndices[pbsCurData->m_iCurIndex] = iNextWedge;
  1972.                     pbsCurData->m_rgfWeights[pbsCurData->m_iCurIndex] = pbsOldData->m_rgfWeights[iIndex];
  1973.                     pbsCurData->m_iCurIndex += 1;
  1974.  
  1975.                     iNextWedge = pMeshData->m_rgVertices[iNextWedge].iWedgeList;
  1976.                 }
  1977.             }
  1978.         }
  1979.  
  1980.         // now that we do have skin data to put in the file, add a skinning header record
  1981.         cbHeaderData = sizeof(DWORD) * 3;
  1982.         pbCur = pbHeaderData = new BYTE[cbHeaderData];
  1983.         if (pbHeaderData == NULL)
  1984.         {
  1985.             hr = E_OUTOFMEMORY;
  1986.             goto e_Exit;
  1987.         }
  1988.  
  1989.         WRITE_WORD(pbCur, psc->m_cMaxWeightsPerVertex);
  1990.         WRITE_WORD(pbCur, psc->m_cMaxWeightsPerFace);
  1991.         WRITE_WORD(pbCur, psmSkinMap->m_cbiBones);
  1992.  
  1993.         hr = psc->m_pxofsave->CreateDataObject(DXFILEOBJ_XSkinMeshHeader,
  1994.                                         NULL,
  1995.                                         NULL,
  1996.                                         cbHeaderData,
  1997.                                         pbHeaderData,
  1998.                                         &pDataObject
  1999.                                         );
  2000.         hr = pParent->AddDataObject(pDataObject);
  2001.         if (FAILED(hr))
  2002.             goto e_Exit;
  2003.  
  2004.         RELEASE(pDataObject);
  2005.  
  2006. // now actually output the prepared buffers
  2007.         for (iBone = 0; iBone < psmSkinMap->m_cbiBones; iBone++)
  2008.         {
  2009.             assert(rgbsBoneData[iBone].m_iCurIndex == rgbsBoneData[iBone].m_cVertices);
  2010.             assert(rgbsBoneDataDups[iBone].m_iCurIndex == rgbsBoneDataDups[iBone].m_cVertices);
  2011.  
  2012.             hr = psc->m_pxofsave->CreateDataObject(DXFILEOBJ_SkinWeights,
  2013.                                             NULL,
  2014.                                             NULL,
  2015.                                             rgbsBoneDataDups[iBone].m_cbData,
  2016.                                             rgbsBoneDataDups[iBone].m_pbData,
  2017.                                             &pDataObject
  2018.                                             );
  2019.             if (FAILED(hr))
  2020.                 goto e_Exit;
  2021.  
  2022.             hr = pParent->AddDataObject(pDataObject);
  2023.             if (FAILED(hr))
  2024.                 goto e_Exit;
  2025.  
  2026.             RELEASE(pDataObject);
  2027.         }
  2028.     }
  2029.  
  2030. e_Exit:
  2031.     delete []pbHeaderData;
  2032.     delete []rgbsBoneData;
  2033.     delete []rgbsBoneDataDups;
  2034.     return hr;
  2035. }
  2036.  
  2037. HRESULT AddMesh
  2038.     (
  2039.     SSaveContext *psc,
  2040.     INode *pNode, 
  2041.     Object* pObj,
  2042.     LPDIRECTXFILEDATA pParent
  2043.     )
  2044. {
  2045.     HRESULT hr = S_OK;
  2046.     BOOL bDeleteTriObject = false;
  2047.     TriObject *pTriObject = NULL;
  2048.     Mesh *pMesh;
  2049.     BOOL bSwapTriOrder;
  2050.     PBYTE pbData = NULL;
  2051.     PBYTE pbCur;
  2052.     DWORD cbSize;
  2053.     DWORD cVertices;
  2054.     DWORD cFaces;
  2055.     DWORD iFace;
  2056.     Matrix3 matNodeTM;
  2057.     SMeshData MeshData;
  2058.     LPDIRECTXFILEDATA pDataObject = NULL;
  2059.     DWORD iVertex;
  2060.  
  2061.     // Retrieve the TriObject from the node
  2062.  
  2063.     pTriObject = GetTriObjectFromObjRef(pObj, &bDeleteTriObject);
  2064.  
  2065.     // If no TriObject then skip
  2066.     if (pTriObject == NULL) 
  2067.         goto e_Exit;
  2068.  
  2069.     pMesh = &(pTriObject->mesh);
  2070.     pMesh->checkNormals(TRUE); // TODO: Is this necessary?
  2071.     matNodeTM = pNode->GetNodeTM(psc->m_iTime);
  2072.     bSwapTriOrder = matNodeTM.Parity();
  2073.  
  2074.     hr = GenerateMeshData(pMesh, &MeshData);
  2075.     if (FAILED(hr))
  2076.         goto e_Exit;
  2077.  
  2078.     cVertices = MeshData.m_cVertices;
  2079.     cFaces = MeshData.m_cFaces;
  2080.     cbSize = sizeof(DWORD) // nVertices
  2081.              + cVertices*sizeof(float)*3 // vertices
  2082.              + sizeof(DWORD) // nFaces
  2083.              + cFaces*(sizeof(DWORD) /*nFaceVertexIndices*/ 
  2084.                             + sizeof(DWORD)*3 /*faceVertexIndices*/); // faces
  2085.  
  2086.     pbCur = pbData = new BYTE[cbSize];
  2087.     if (pbData == NULL)
  2088.     {
  2089.         hr = E_OUTOFMEMORY;
  2090.         goto e_Exit;
  2091.     }
  2092.  
  2093.     // write nVertices
  2094.     WRITE_DWORD(pbCur, cVertices);
  2095.  
  2096.     // write vertices
  2097.     for (iVertex = 0; iVertex < MeshData.m_cVertices; iVertex++)
  2098.     {
  2099.         WRITE_POINT3(pbCur, pMesh->verts[MeshData.m_rgVertices[iVertex].iPointRep]);
  2100.     }
  2101.  
  2102.     // write nFaces
  2103.     WRITE_DWORD(pbCur, cFaces);
  2104.     
  2105.     // write faces
  2106.     for( iFace = 0; iFace < cFaces; iFace++ )
  2107.     {
  2108.         WRITE_DWORD(pbCur, 3); //nFaceVertexIndices
  2109.  
  2110.         // face vertex indices
  2111.         if( bSwapTriOrder )
  2112.         {
  2113.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[0]);
  2114.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[2]);
  2115.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[1]);
  2116.         }
  2117.         else
  2118.         {
  2119.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[0]);
  2120.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[1]);
  2121.             WRITE_DWORD(pbCur, MeshData.m_rgFaces[iFace].index[2]);
  2122.         }
  2123.     }
  2124.  
  2125.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMesh,
  2126.                                     NULL,
  2127.                                     NULL,
  2128.                                     cbSize,
  2129.                                     pbData,
  2130.                                     &pDataObject
  2131.                                     );
  2132.     if (FAILED(hr))
  2133.         goto e_Exit;
  2134.  
  2135.     // add the normals to the file
  2136.     hr = AddNormals(psc, &MeshData, bSwapTriOrder, pDataObject);
  2137.     if (FAILED(hr))
  2138.         goto e_Exit;
  2139.  
  2140.     // write texture coordinates
  2141.     hr = AddTextureCoordinates(psc, &MeshData, pMesh, pDataObject);
  2142.     if (FAILED(hr))
  2143.         goto e_Exit;
  2144.  
  2145.     hr = AddVertexDuplicationIndices(psc, &MeshData, pDataObject);
  2146.     if (FAILED(hr))
  2147.         goto e_Exit;
  2148.  
  2149.     hr = AddMeshMaterials(psc, pNode, pMesh, pDataObject);
  2150.     if (FAILED(hr))
  2151.         goto e_Exit;
  2152.  
  2153.     hr = AddSkinData(psc, pNode, &MeshData, pDataObject);
  2154.     if (FAILED(hr))
  2155.         goto e_Exit;
  2156.  
  2157.     hr = pParent->AddDataObject(pDataObject);
  2158.     if (FAILED(hr))
  2159.         goto e_Exit;
  2160.  
  2161. e_Exit:
  2162.     if (bDeleteTriObject)
  2163.     {
  2164.         delete pTriObject;
  2165.     }
  2166.  
  2167.     RELEASE(pDataObject);
  2168.     return hr;
  2169. }
  2170.  
  2171. HRESULT
  2172. AddPatchMeshMaterials(
  2173.     SSaveContext *psc,
  2174.     INode *pNode,
  2175.     PatchMesh *pPatchMesh,
  2176.     LPDIRECTXFILEDATA pParent
  2177.     )
  2178. {
  2179.     HRESULT hr = S_OK;
  2180.     LPDIRECTXFILEDATA pDataObject = NULL;
  2181.     Mtl *pMtlMain;
  2182.     Mtl *pMtlCur;
  2183.     DWORD cSubMaterials;
  2184.     DWORD iPatch;
  2185.     DWORD cPatches;
  2186.     BOOL bNoSubMaterials;
  2187.     BOOL bWireframeColor;
  2188.     PBYTE pbCur;
  2189.     PBYTE pbData = NULL;
  2190.     DWORD cbSize;
  2191.     DWORD iCurMatID;
  2192.     DWORD iCurMaterial;
  2193.  
  2194.     pMtlMain = pNode->GetMtl();
  2195.     cPatches = pPatchMesh->getNumPatches();
  2196.     cSubMaterials = 0;
  2197.     bNoSubMaterials = FALSE;
  2198.     bWireframeColor = FALSE;
  2199.  
  2200.     // The color of a given mesh can be provided by three different sources:
  2201.     //   1) applied texture maps, as part of a material
  2202.     //   2) explicitly defined & applied materials without texture maps
  2203.     //   3) a 'wireframe' color.
  2204.     
  2205.     // For our purposes, we will output these in this order of preference, ignoring
  2206.     // processing past the first one we find.
  2207.  
  2208.     cbSize = sizeof(DWORD) // nMaterials
  2209.                 + sizeof(DWORD) // nFaceIndexes
  2210.                 + cPatches*sizeof(DWORD); // face indexes
  2211.  
  2212.     pbData = pbCur = new BYTE[cbSize];
  2213.     if (pbCur == NULL)
  2214.     {
  2215.         hr = E_OUTOFMEMORY;
  2216.         goto e_Exit;
  2217.     }
  2218.     
  2219.     if (pMtlMain != NULL)
  2220.     {
  2221.         // There is at least one material. We're in case 1) or 2)
  2222.  
  2223.         cSubMaterials = pMtlMain->NumSubMtls();
  2224.         if (cSubMaterials < 1)
  2225.         {
  2226.             // Count the material itself as a submaterial.
  2227.             cSubMaterials = 1;
  2228.             bNoSubMaterials = TRUE;
  2229.         }
  2230.     }
  2231.     else  // no material, then we'll create a material corresponding to the default wire color.
  2232.     {
  2233.         bWireframeColor = TRUE;
  2234.         cSubMaterials = 1;
  2235.     }
  2236.  
  2237.     WRITE_DWORD(pbCur, cSubMaterials);
  2238.     WRITE_DWORD(pbCur, cPatches);
  2239.  
  2240.  
  2241.     // For each face, output the index of the material which applies to it, 
  2242.     // starting from  0
  2243.  
  2244.     for (iPatch=0; iPatch < cPatches; iPatch++)
  2245.     {
  2246.  
  2247.         if (bWireframeColor || bNoSubMaterials) 
  2248.         {
  2249.             // If we're using wireframe color, it's our only material
  2250.             WRITE_DWORD(pbCur, 0);
  2251.         }
  2252.         else
  2253.         {
  2254.             // Otherwise we have at least one material to process.
  2255.  
  2256.             iCurMatID = pPatchMesh->getPatchMtlIndex(iPatch);
  2257.  
  2258.             if (cSubMaterials == 1)
  2259.             {
  2260.                 iCurMatID = 0;
  2261.             }
  2262.             else
  2263.             {
  2264.                 // SDK recommends mod'ing the material ID by the valid # of materials, 
  2265.                 // as sometimes a material number that's too high is returned.
  2266.                 iCurMatID %= cSubMaterials;
  2267.             }
  2268.  
  2269.             // output the appropriate material color
  2270.  
  2271.             WRITE_DWORD(pbCur, iCurMatID);
  2272.  
  2273.         } 
  2274.  
  2275.     } 
  2276.  
  2277.     // now finally create the mesh material list
  2278.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMMeshMaterialList,
  2279.                                     NULL,
  2280.                                     NULL,
  2281.                                     cbSize,
  2282.                                     pbData,
  2283.                                     &pDataObject
  2284.                                     );
  2285.     if (FAILED(hr))
  2286.         goto e_Exit;
  2287.  
  2288.     hr = pParent->AddDataObject(pDataObject);
  2289.     if (FAILED(hr))
  2290.         goto e_Exit;
  2291.  
  2292.     // 3DSMax uses wireframe color as its default material for a mesh.
  2293.     // Output the wireframe color as the material if there are no explicit materials.
  2294.  
  2295.     if (bWireframeColor)
  2296.     {
  2297.         AddWireframeMaterial(psc, pNode, pDataObject);
  2298.     } 
  2299.     else
  2300.     {
  2301.         // 3DSMax allows multiple materials to be used on a single mesh via
  2302.         // 'submaterials'. When the first submaterial is defined, the main material
  2303.         // is copied into submaterial #1, and the new submaterial because submaterial #2.
  2304.         // 
  2305.         // We have to catch the case where there's a material, but no submaterials. For this
  2306.         // case, set NumSubMaterials to 1 which would never happen otherwise. It's imperative
  2307.         // that the first material be set to MtlMain, rather than trying to GetSubMtl() to 
  2308.         // allow this logic to work.
  2309.  
  2310.         // Loop through the materials (if any) and output them.
  2311.  
  2312.         for (iCurMaterial = 0; (iCurMaterial < cSubMaterials); iCurMaterial++)
  2313.         {
  2314.             if (bNoSubMaterials)
  2315.             {
  2316.                 // We're on the parent material, possibly the ONLY material.
  2317.                 // We won't be able to get it with GetSubMtl() if it's the only one, and
  2318.                 // the data in the first submaterial is identical to the main material,
  2319.                 // so just use the main material.
  2320.  
  2321.                 pMtlCur = pMtlMain;
  2322.             }
  2323.             else
  2324.             {
  2325.                 // We're into the true submaterials. Safe to get with 'GetSubMtl'
  2326.  
  2327.                 pMtlCur = pMtlMain->GetSubMtl(iCurMaterial);
  2328.             }
  2329.  
  2330.             hr = AddMaterial(psc, pMtlCur, pDataObject);
  2331.             if (FAILED(hr))
  2332.                 goto e_Exit;
  2333.         } 
  2334.     }
  2335.  
  2336. e_Exit:
  2337.     delete []pbData;
  2338.     RELEASE(pDataObject);
  2339.  
  2340.  
  2341.     return hr;
  2342.  
  2343. HRESULT AddPatchMesh
  2344.     (
  2345.     SSaveContext *psc,
  2346.     INode *pNode, 
  2347.     Object* pObj,
  2348.     LPDIRECTXFILEDATA pParent
  2349.     )
  2350. {
  2351.     HRESULT    hr = S_OK;
  2352.     LPDIRECTXFILEDATA pDataObject = NULL;
  2353.     PBYTE         pbData = NULL;
  2354.     PBYTE          pbCur = NULL;        
  2355.     DWORD          cbSize;
  2356.     DWORD iVertex;
  2357.     DWORD iPatch;
  2358.     Point3 *pvVertices;
  2359.     PatchMesh *pPatchMesh;
  2360.     PatchObject *pPatchObject = NULL;
  2361.     BOOL bDeletePatchObject;
  2362.     DWORD iControl;
  2363.     DWORD cPatchIndices;
  2364.     SPatchMeshData PatchMeshData;
  2365.  
  2366.     assert(psc != NULL);
  2367.     assert(pNode != NULL);
  2368.     assert(pObj != NULL);
  2369.     assert(pParent != NULL);
  2370.  
  2371.     pPatchObject = GetPatchObjectFromObjRef(pObj, &bDeletePatchObject);
  2372.  
  2373.     // If no PatchObject then skip
  2374.     if (pPatchObject == NULL) 
  2375.         goto e_Exit;
  2376.  
  2377.     pPatchMesh = &pPatchObject->patch;
  2378.  
  2379.     // massage the patch data into an outputable format (including texture coord handling)
  2380.     hr = GeneratePatchMeshData(pPatchMesh, &PatchMeshData);
  2381.     if (FAILED(hr))
  2382.         goto e_Exit;
  2383.  
  2384.     // figure out the total number of control indices
  2385.     cPatchIndices = 0;
  2386.     for (iPatch = 0; iPatch < PatchMeshData.m_cPatches; iPatch++)
  2387.     {
  2388.         cPatchIndices += PatchMeshData.m_rgPatches[iPatch].m_cControl;
  2389.     }
  2390.  
  2391.     cbSize = sizeof(DWORD) // nVertices
  2392.              + PatchMeshData.m_cVertices * sizeof(float)*3 // vertices
  2393.              + sizeof(DWORD) // nPatches
  2394.              + PatchMeshData.m_cPatches * sizeof(DWORD) /*nControlIndices*/ 
  2395.              + cPatchIndices * sizeof(DWORD) /*controlIndices*/; // patches
  2396.  
  2397.     pbCur = pbData = new BYTE[cbSize];
  2398.     if (pbData == NULL)
  2399.     {
  2400.         hr = E_OUTOFMEMORY;
  2401.         goto e_Exit;
  2402.     }
  2403.  
  2404.     // write the number of vertices
  2405.     WRITE_DWORD(pbCur, PatchMeshData.m_cVertices);
  2406.  
  2407.     // first write all the vertices
  2408.     pvVertices = (Point3*)pbCur;
  2409.     for (iVertex = 0; iVertex < PatchMeshData.m_cVertices; iVertex++)
  2410.     {
  2411.         pvVertices[iVertex] = PatchMeshData.m_rgVertices[iVertex].vPosition;
  2412.     }
  2413.  
  2414.     // skip over the vertices
  2415.     pbCur = pbData + sizeof(DWORD) + sizeof(float)*3*PatchMeshData.m_cVertices;
  2416.  
  2417.     // write the number of patches
  2418.     WRITE_DWORD(pbCur, PatchMeshData.m_cPatches);
  2419.  
  2420.     // now write all the patch data
  2421.     for (iPatch = 0; iPatch < PatchMeshData.m_cPatches; iPatch++)
  2422.     {
  2423.         WRITE_DWORD(pbCur, PatchMeshData.m_rgPatches[iPatch].m_cControl);
  2424.         for (iControl = 0; iControl < PatchMeshData.m_rgPatches[iPatch].m_cControl; iControl++)
  2425.         {
  2426.             WRITE_DWORD(pbCur, PatchMeshData.m_rgPatches[iPatch].m_rgdwControl[iControl]);
  2427.         }
  2428.     }
  2429.  
  2430.     hr = psc->m_pxofsave->CreateDataObject(DXFILEOBJ_PatchMesh,
  2431.                                     NULL,
  2432.                                     NULL,
  2433.                                     cbSize,
  2434.                                     pbData,
  2435.                                     &pDataObject
  2436.                                     );
  2437.     if (FAILED(hr))
  2438.         goto e_Exit;
  2439.  
  2440.     hr = AddPatchTextureCoordinates(psc, &PatchMeshData, pPatchMesh, pDataObject);
  2441.     if (FAILED(hr))
  2442.         goto e_Exit;
  2443.  
  2444.     hr = AddPatchVertexDuplicationIndices(psc, &PatchMeshData, pDataObject);
  2445.     if (FAILED(hr))
  2446.         goto e_Exit;
  2447.  
  2448.     hr = AddPatchMeshMaterials(psc, pNode, pPatchMesh, pDataObject);
  2449.     if (FAILED(hr))
  2450.         goto e_Exit;
  2451.  
  2452.     hr = pParent->AddDataObject(pDataObject);
  2453.     if (FAILED(hr))
  2454.         goto e_Exit;
  2455.  
  2456. e_Exit:
  2457.     delete []pbData;
  2458.     RELEASE(pDataObject);
  2459.  
  2460.     if (bDeletePatchObject)
  2461.     {
  2462.         delete pPatchObject;
  2463.     }
  2464.  
  2465.     return hr;
  2466. }
  2467.  
  2468. HRESULT AddTransform
  2469.     (
  2470.     SSaveContext *psc,
  2471.     INode *pNode, 
  2472.     LPDIRECTXFILEDATA pParent
  2473.     )
  2474. {
  2475.     HRESULT hr = S_OK;
  2476.     PBYTE pbData = NULL;
  2477.     PBYTE pbCur;
  2478.     DWORD cbSize;
  2479.     Matrix3 matNodeTM;
  2480.     Matrix3 matRelativeTM;
  2481.     LPDIRECTXFILEDATA pDataObject = NULL;
  2482.  
  2483.     cbSize = 16*sizeof(float);
  2484.  
  2485.     pbCur = pbData = new BYTE[cbSize];
  2486.     if (pbData == NULL)
  2487.     {
  2488.         hr = E_OUTOFMEMORY;
  2489.         goto e_Exit;
  2490.     }
  2491.  
  2492.     // Node transform
  2493.     matNodeTM = pNode->GetNodeTM(psc->m_iTime);
  2494.     if( pNode->IsRootNode() )
  2495.     {
  2496.         // scene root
  2497.         matRelativeTM = matNodeTM;
  2498.     }
  2499.     else
  2500.     {
  2501.         Matrix3 matParentTM = pNode->GetParentTM(psc->m_iTime);
  2502.         if( memcmp(&matNodeTM,&matParentTM,12*sizeof(float)) == 0 )
  2503.         {
  2504.             matRelativeTM.IdentityMatrix();
  2505.         }
  2506.         else
  2507.         {
  2508.             matRelativeTM = matNodeTM * Inverse(matParentTM);
  2509.         }
  2510.     }
  2511.  
  2512.     WRITE_MATRIX4_FROM_MATRIX3(pbCur, matRelativeTM);
  2513.  
  2514.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMFrameTransformMatrix,
  2515.                                     NULL,
  2516.                                     NULL,
  2517.                                     cbSize,
  2518.                                     pbData,
  2519.                                     &pDataObject
  2520.                                     );
  2521.     if (FAILED(hr))
  2522.         goto e_Exit;
  2523.  
  2524.     hr = pParent->AddDataObject(pDataObject);
  2525.     if (FAILED(hr))
  2526.         goto e_Exit;
  2527.  
  2528. e_Exit:
  2529.     delete []pbData;
  2530.     RELEASE(pDataObject);
  2531.  
  2532.     return hr;
  2533. }
  2534.  
  2535. HRESULT AddObjectOffsetTransform
  2536.     (
  2537.     SSaveContext *psc,
  2538.     INode *pNode,
  2539.     LPDIRECTXFILEDATA pParent,
  2540.     LPDIRECTXFILEDATA *ppMeshParent
  2541.     )
  2542. {
  2543.     HRESULT hr = S_OK;
  2544.     PBYTE pbData = NULL;
  2545.     PBYTE pbCur;
  2546.     DWORD cbSize;
  2547.     LPDIRECTXFILEDATA pDataObject = NULL;
  2548.     Matrix3 matNodeTM;
  2549.     Matrix3 matObjTMAfterWSM;
  2550.     Matrix3 matObjectOffset;
  2551.     LPDIRECTXFILEDATA pObjectOffset = NULL;
  2552.  
  2553.     // check to see if the node has an object offset matrix
  2554.     matNodeTM = pNode->GetNodeTM(psc->m_iTime);
  2555.     matObjTMAfterWSM = pNode->GetObjTMAfterWSM(psc->m_iTime);
  2556.     if (memcmp(&matObjTMAfterWSM, &matNodeTM,12*sizeof(Matrix3)) != 0)
  2557.     {
  2558.         // the mesh is positioned offset from the node, so add another
  2559.         //   frame (unnamed) to offset the mesh without affecting the node's children
  2560.         //   and/or animation attached to the node
  2561.         hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMFrame,
  2562.                                         NULL,
  2563.                                         NULL,
  2564.                                         0,
  2565.                                         NULL,
  2566.                                         &pObjectOffset
  2567.                                         );
  2568.  
  2569.         matObjectOffset = matObjTMAfterWSM * Inverse(matNodeTM);
  2570.  
  2571.  
  2572.         cbSize = 16*sizeof(float);
  2573.  
  2574.         pbCur = pbData = new BYTE[cbSize];
  2575.         if (pbData == NULL)
  2576.         {
  2577.             hr = E_OUTOFMEMORY;
  2578.             goto e_Exit;
  2579.         }
  2580.  
  2581.         WRITE_MATRIX4_FROM_MATRIX3(pbCur, matObjectOffset);
  2582.  
  2583.         hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMFrameTransformMatrix,
  2584.                                         NULL,
  2585.                                         NULL,
  2586.                                         cbSize,
  2587.                                         pbData,
  2588.                                         &pDataObject
  2589.                                         );
  2590.         if (FAILED(hr))
  2591.             goto e_Exit;
  2592.  
  2593.         hr = pObjectOffset->AddDataObject(pDataObject);
  2594.         if (FAILED(hr))
  2595.             goto e_Exit;
  2596.  
  2597.         hr = pParent->AddDataObject(pObjectOffset);
  2598.         if (FAILED(hr))
  2599.             goto e_Exit;
  2600.  
  2601.  
  2602.         *ppMeshParent = pObjectOffset;
  2603.     }
  2604.     else  // identity object offset, mesh should use node as parent
  2605.     {
  2606.         *ppMeshParent = pParent;
  2607.     }
  2608.  
  2609. e_Exit:
  2610.     delete []pbData;
  2611.     RELEASE(pDataObject);
  2612.     RELEASE(pObjectOffset);
  2613.  
  2614.     return hr;
  2615. }
  2616.  
  2617. HRESULT EnumTree
  2618.     (
  2619.     SSaveContext *psc,
  2620.     INode *pNode, 
  2621.     LPDIRECTXFILEDATA *ppDataObjectNew
  2622.     )
  2623. {
  2624.     HRESULT hr = S_OK;
  2625.     DWORD cChildren;
  2626.     DWORD iChild;
  2627.     LPDIRECTXFILEDATA pChildDataObject;
  2628.     LPDIRECTXFILEDATA pDataObject = NULL;
  2629.     LPDIRECTXFILEDATA pMeshParent;
  2630.     Object* pObj;
  2631.     char *szName = NULL;
  2632.     
  2633.     szName = psc->m_stStrings.CreateNiceString(pNode->GetName());
  2634.     if (szName == NULL)
  2635.     {
  2636.         hr = E_OUTOFMEMORY;
  2637.         goto e_Exit;
  2638.     }
  2639.  
  2640.     OutputDebugString(szName);
  2641.     OutputDebugString("\n");
  2642.  
  2643.     // add the node to the array for anim purposes
  2644.     assert( psc->m_cNodesCur < psc->m_cNodes );
  2645.     psc->m_rgpnNodes[psc->m_cNodesCur] = pNode;
  2646.     psc->m_cNodesCur += 1;
  2647.  
  2648.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMFrame,
  2649.                                     szName,
  2650.                                     NULL,
  2651.                                     0,
  2652.                                     NULL,
  2653.                                     &pDataObject
  2654.                                     );
  2655.  
  2656.     hr = AddTransform(psc, pNode, pDataObject);
  2657.     if (FAILED(hr))
  2658.         goto e_Exit;
  2659.  
  2660.     if (psc->m_bSavePatchData && IsExportablePatchMesh(pNode, pObj) && !(psc->m_bSaveSelection && !pNode->Selected()))
  2661.     {
  2662.         hr = AddObjectOffsetTransform(psc, pNode, pDataObject, &pMeshParent);
  2663.         if (FAILED(hr))
  2664.             goto e_Exit;
  2665.  
  2666.         hr = AddPatchMesh(psc, pNode, pObj, pMeshParent);
  2667.         if (FAILED(hr))
  2668.             goto e_Exit;
  2669.     }
  2670.     else if (IsExportableMesh(pNode, pObj) && !(psc->m_bSaveSelection && !pNode->Selected()))
  2671.     {
  2672.         hr = AddObjectOffsetTransform(psc, pNode, pDataObject, &pMeshParent);
  2673.         if (FAILED(hr))
  2674.             goto e_Exit;
  2675.         
  2676.         hr = AddMesh(psc, pNode, pObj, pMeshParent);
  2677.         if (FAILED(hr))
  2678.             goto e_Exit;
  2679.     }
  2680.  
  2681.     cChildren = pNode->NumberOfChildren();
  2682.     for (iChild = 0; iChild < cChildren; iChild++)
  2683.     {
  2684.         // enumerate the child
  2685.         hr = EnumTree(psc, pNode->GetChildNode(iChild), &pChildDataObject);
  2686.         if (FAILED(hr))
  2687.             goto e_Exit;
  2688.         
  2689.         hr = pDataObject->AddDataObject(pChildDataObject);
  2690.         if (FAILED(hr))
  2691.             goto e_Exit;
  2692.  
  2693.         RELEASE(pChildDataObject);
  2694.     }
  2695.  
  2696.     *ppDataObjectNew = pDataObject;
  2697. e_Exit:
  2698.     return hr;
  2699. }
  2700.  
  2701.  
  2702. HRESULT Preprocess
  2703.     (
  2704.     SPreprocessContext *ppc,
  2705.     INode *pNode
  2706.     )
  2707. {
  2708.     HRESULT hr = S_OK;
  2709.     DWORD cChildren;
  2710.     DWORD iChild;
  2711.     Object* pObj;
  2712.     SSkinMap **rgpsmTemp = NULL;
  2713.     IPhyVertexExport *pVertexExport;
  2714.     IPhyRigidVertex* pRigidVertexExport;
  2715.     IPhyBlendedRigidVertex *pBlendedRigidVertexExport;
  2716.     INode* pBoneNode;
  2717.     SSkinMap *psmSkinMap;
  2718.     Modifier* pPhyMod = NULL;
  2719.     IPhysiqueExport* pPhyExport = NULL;
  2720.     IPhyContextExport* pPhyContextExport = NULL;
  2721.     DWORD iVertex;
  2722.     DWORD cVertices;
  2723.     DWORD iVertexType;
  2724.     SBoneInfo *pbi;
  2725.     DWORD cTotalBones;
  2726.     DWORD iBone;
  2727.     DWORD cpnBonesSeen;
  2728.     DWORD cpnBonesSeenMax;
  2729.     INode **rgpnBonesSeen = NULL;
  2730.     INode **rgpnTemp;
  2731.     BOOL bBoneSeen;
  2732.     DWORD iBoneSeen;
  2733.     BOOL bDeleteTriObject = false;
  2734.     TriObject *pTriObject = NULL;
  2735.     Mesh *pMesh;
  2736.     DWORD iFace;
  2737.     DWORD iPoint;
  2738.  
  2739.     ppc->m_cNodes += 1;
  2740.  
  2741.     if (IsExportableMesh(pNode,pObj) && !(ppc->m_bSaveSelection && !pNode->Selected()))
  2742.     {
  2743.         // first see if physique is present
  2744.         pPhyMod = FindPhysiqueModifier(pNode);
  2745.         if (pPhyMod != NULL)
  2746.         {
  2747.             // Get a Physique Export interface
  2748.             pPhyExport = (IPhysiqueExport *)pPhyMod->GetInterface(I_PHYINTERFACE);
  2749.             if (pPhyExport == NULL)
  2750.             {   // not all interfaces present, so ignore
  2751.                 goto e_NoPhysique;
  2752.             }
  2753.             // For a given Object's INode get a
  2754.             // ModContext Interface from the Physique Export Interface:
  2755.             pPhyContextExport = (IPhyContextExport *)pPhyExport->GetContextInterface(pNode);
  2756.             if(pPhyContextExport == NULL)
  2757.             {
  2758.                 // not all interfaces present, so ignore
  2759.                 goto e_NoPhysique;
  2760.             }
  2761.  
  2762.             // convert to rigid with blending
  2763.             pPhyContextExport->ConvertToRigid(TRUE);
  2764.             pPhyContextExport->AllowBlending(TRUE);
  2765.  
  2766.             psmSkinMap = new SSkinMap;
  2767.             if (psmSkinMap == NULL)
  2768.             {
  2769.                 hr = E_OUTOFMEMORY;
  2770.                 goto e_Exit;
  2771.             }
  2772.  
  2773.             // realloc if necessary
  2774.             if (ppc->m_cpsmSkinMaps == ppc->m_cpsmSkinMapsMax)
  2775.             {
  2776.                 rgpsmTemp = ppc->m_rgpsmSkinMaps;
  2777.  
  2778.                 ppc->m_cpsmSkinMapsMax = max(1, ppc->m_cpsmSkinMapsMax);
  2779.                 ppc->m_cpsmSkinMapsMax = ppc->m_cpsmSkinMapsMax * 2;
  2780.                 ppc->m_rgpsmSkinMaps = new SSkinMap*[ppc->m_cpsmSkinMapsMax];
  2781.                 if (ppc->m_rgpsmSkinMaps == NULL)
  2782.                 {
  2783.                     ppc->m_rgpsmSkinMaps = rgpsmTemp;
  2784.                     hr = E_OUTOFMEMORY;
  2785.                     goto e_Exit;
  2786.                 }
  2787.  
  2788.                 if (ppc->m_cpsmSkinMaps > 0)
  2789.                 {
  2790.                     memcpy(ppc->m_rgpsmSkinMaps, rgpsmTemp, sizeof(SSkinMap*) * ppc->m_cpsmSkinMaps);
  2791.                 }
  2792.  
  2793.                 delete []rgpsmTemp;
  2794.             }
  2795.             ppc->m_rgpsmSkinMaps[ppc->m_cpsmSkinMaps] = psmSkinMap;
  2796.             ppc->m_cpsmSkinMaps += 1;
  2797.  
  2798.             // init the map
  2799.             psmSkinMap->m_pMeshNode = pNode;
  2800.             psmSkinMap->m_cbiBonesMax = 30;
  2801.             psmSkinMap->m_rgbiBones = new SBoneInfo[psmSkinMap->m_cbiBonesMax];
  2802.             if (psmSkinMap->m_rgbiBones == NULL)
  2803.             {
  2804.                 hr = E_OUTOFMEMORY;
  2805.                 goto e_Exit;
  2806.             }
  2807.  
  2808.             // init duplication removal for redundant weights
  2809.             cpnBonesSeenMax = 30;
  2810.             cpnBonesSeen = 0;
  2811.             rgpnBonesSeen = new INode*[cpnBonesSeenMax];
  2812.             if (rgpnBonesSeen == NULL)
  2813.             {
  2814.                 hr = E_OUTOFMEMORY;
  2815.                 goto e_Exit;
  2816.             }
  2817.  
  2818.             cVertices = pPhyContextExport->GetNumberVertices();
  2819.             for (iVertex = 0; iVertex < cVertices; iVertex++ )
  2820.             {
  2821.                 pVertexExport = (IPhyVertexExport *)pPhyContextExport->GetVertexInterface(iVertex);    
  2822.                 if (pVertexExport == NULL)
  2823.                 {
  2824.                     OutputDebugString("Couldn't get export interface!");
  2825.                     hr = E_FAIL;
  2826.                     goto e_Exit;
  2827.                 }
  2828.             
  2829.                 // What kind of vertices are these?
  2830.                 iVertexType = pVertexExport->GetVertexType();
  2831.  
  2832.                 pPhyContextExport->ReleaseVertexInterface( pVertexExport );    
  2833.  
  2834.                 if( iVertexType == RIGID_TYPE )
  2835.                 {
  2836.                     pRigidVertexExport = (IPhyRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  2837.                     if (pRigidVertexExport == NULL)
  2838.                     {
  2839.                         OutputDebugString("Couldn't get rigid vertex export interface!");
  2840.                         hr = E_FAIL;
  2841.                         goto e_Exit;
  2842.                     }
  2843.                     // Get the vertex info!
  2844.                 
  2845.                     pBoneNode = pRigidVertexExport->GetNode();
  2846.  
  2847.                     pbi = psmSkinMap->FindBone(pBoneNode);
  2848.                     if (pbi == NULL)
  2849.                     {
  2850.                         hr = psmSkinMap->AddBone(pBoneNode, &pbi);
  2851.                         if (FAILED(hr))
  2852.                             goto e_Exit;
  2853.                     }
  2854.  
  2855.                     pbi->m_cVertices += 1;
  2856.  
  2857.                     ppc->m_cMaxWeightsPerVertex = max(1, ppc->m_cMaxWeightsPerVertex);
  2858.  
  2859.                     pPhyContextExport->ReleaseVertexInterface( pRigidVertexExport);
  2860.                 }
  2861.                 else
  2862.                 {
  2863.                     assert( iVertexType == RIGID_BLENDED_TYPE );
  2864.  
  2865.                     pBlendedRigidVertexExport = (IPhyBlendedRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  2866.                     if (pBlendedRigidVertexExport == NULL)
  2867.                     {
  2868.                         OutputDebugString("Couldn't get blended rigid vertex export interface!");
  2869.                         hr = E_FAIL;
  2870.                         goto e_Exit;
  2871.                     }
  2872.  
  2873.                     // How many nodes affect his vertex?
  2874.                     cTotalBones = pBlendedRigidVertexExport->GetNumberNodes();
  2875.                     cpnBonesSeen = 0;
  2876.                     for (iBone = 0; iBone < cTotalBones; iBone++ )
  2877.                     {
  2878.                         pBoneNode = pBlendedRigidVertexExport->GetNode(iBone);
  2879.  
  2880.                         // first see if the bone has already been seen
  2881.                         bBoneSeen = FALSE;
  2882.                         for (iBoneSeen = 0; iBoneSeen < cpnBonesSeen; iBoneSeen++)
  2883.                         {
  2884.                             if (rgpnBonesSeen[iBoneSeen] == pBoneNode)
  2885.                             {
  2886.                                 bBoneSeen = TRUE;
  2887.                                 break;
  2888.                             }
  2889.                         }
  2890.                         
  2891.                         // if not seen, collect stats and add to already seen
  2892.                         if (!bBoneSeen)
  2893.                         {
  2894.                             pbi = psmSkinMap->FindBone(pBoneNode);
  2895.                             if (pbi == NULL)
  2896.                             {
  2897.                                 hr = psmSkinMap->AddBone(pBoneNode, &pbi);
  2898.                                 if (FAILED(hr))
  2899.                                     goto e_Exit;
  2900.                             }
  2901.                             pbi->m_cVertices += 1;
  2902.  
  2903.                             if (cpnBonesSeen >= cpnBonesSeenMax)
  2904.                             {
  2905.                                 rgpnTemp = rgpnBonesSeen;
  2906.                                 cpnBonesSeenMax *= 2;
  2907.  
  2908.                                 rgpnBonesSeen = new INode*[cpnBonesSeenMax];
  2909.                                 if (rgpnBonesSeen == NULL)
  2910.                                 {
  2911.                                     rgpnBonesSeen = rgpnTemp;
  2912.                                     hr = E_OUTOFMEMORY;
  2913.                                     goto e_Exit;
  2914.                                 }
  2915.  
  2916.                                 memcpy(rgpnBonesSeen, rgpnTemp, cpnBonesSeen * sizeof(INode*));
  2917.                                 delete []rgpnTemp;
  2918.                             }
  2919.  
  2920.                             rgpnBonesSeen[cpnBonesSeen] = pBoneNode;
  2921.                             cpnBonesSeen += 1;
  2922.                         }
  2923.                     }
  2924.  
  2925.                     // actualNum... accounts for duplicated weights to same transform node
  2926.                     // that are combined automatically above
  2927.                     ppc->m_cMaxWeightsPerVertex = max(cpnBonesSeen, ppc->m_cMaxWeightsPerVertex);
  2928.             
  2929.                     pPhyContextExport->ReleaseVertexInterface( pBlendedRigidVertexExport);
  2930.                 }
  2931.             }
  2932.  
  2933.  
  2934.         // now figure out the max number of weights per face
  2935.  
  2936.             pTriObject = GetTriObjectFromObjRef(pObj, &bDeleteTriObject);
  2937.             if (pTriObject == NULL) 
  2938.             {
  2939.                 OutputDebugString("Physique info, but no mesh");
  2940.                 hr = E_FAIL;
  2941.                 goto e_Exit;
  2942.             }
  2943.  
  2944.             pMesh = &(pTriObject->mesh);
  2945.  
  2946.             for (iFace = 0; iFace < pMesh->numFaces; iFace++)
  2947.             {
  2948.                 cpnBonesSeen = 0;
  2949.                 for (iPoint = 0; iPoint < 3; iPoint++ )
  2950.                 {
  2951.                     iVertex = pMesh->faces[iFace].v[iPoint];
  2952.  
  2953.                     pVertexExport = (IPhyVertexExport *)pPhyContextExport->GetVertexInterface(iVertex);    
  2954.                     if (pVertexExport == NULL)
  2955.                     {
  2956.                         OutputDebugString("Couldn't get export interface!");
  2957.                         hr = E_FAIL;
  2958.                         goto e_Exit;
  2959.                     }
  2960.             
  2961.                     // What kind of vertices are these?
  2962.                     iVertexType = pVertexExport->GetVertexType();
  2963.  
  2964.                     pPhyContextExport->ReleaseVertexInterface( pVertexExport );    
  2965.  
  2966.                     if( iVertexType == RIGID_TYPE )
  2967.                     {
  2968.                         pRigidVertexExport = (IPhyRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  2969.                         if (pRigidVertexExport == NULL)
  2970.                         {
  2971.                             OutputDebugString("Couldn't get rigid vertex export interface!");
  2972.                             hr = E_FAIL;
  2973.                             goto e_Exit;
  2974.                         }
  2975.                         // Get the vertex info!
  2976.                 
  2977.                         pBoneNode = pRigidVertexExport->GetNode();
  2978.  
  2979.                         pbi = psmSkinMap->FindBone(pBoneNode);
  2980.                         if (pbi == NULL)
  2981.                         {
  2982.                             hr = psmSkinMap->AddBone(pBoneNode, &pbi);
  2983.                             if (FAILED(hr))
  2984.                                 goto e_Exit;
  2985.                         }
  2986.  
  2987.                         // first see if the bone has already been seen
  2988.                         bBoneSeen = FALSE;
  2989.                         for (iBoneSeen = 0; iBoneSeen < cpnBonesSeen; iBoneSeen++)
  2990.                         {
  2991.                             if (rgpnBonesSeen[iBoneSeen] == pBoneNode)
  2992.                             {
  2993.                                 bBoneSeen = TRUE;
  2994.                                 break;
  2995.                             }
  2996.                         }
  2997.                     
  2998.                         // if not seen, collect stats and add to already seen
  2999.                         if (!bBoneSeen)
  3000.                         {
  3001.                             if (cpnBonesSeen >= cpnBonesSeenMax)
  3002.                             {
  3003.                                 rgpnTemp = rgpnBonesSeen;
  3004.                                 cpnBonesSeenMax *= 2;
  3005.  
  3006.                                 rgpnBonesSeen = new INode*[cpnBonesSeenMax];
  3007.                                 if (rgpnBonesSeen == NULL)
  3008.                                 {
  3009.                                     rgpnBonesSeen = rgpnTemp;
  3010.                                     hr = E_OUTOFMEMORY;
  3011.                                     goto e_Exit;
  3012.                                 }
  3013.  
  3014.                                 memcpy(rgpnBonesSeen, rgpnTemp, cpnBonesSeen * sizeof(INode*));
  3015.                                 delete []rgpnTemp;
  3016.                             }
  3017.  
  3018.                             rgpnBonesSeen[cpnBonesSeen] = pBoneNode;
  3019.                             cpnBonesSeen += 1;
  3020.                         }
  3021.  
  3022.                         pPhyContextExport->ReleaseVertexInterface( pRigidVertexExport);
  3023.                     }
  3024.                     else
  3025.                     {
  3026.                         assert( iVertexType == RIGID_BLENDED_TYPE );
  3027.  
  3028.                         pBlendedRigidVertexExport = (IPhyBlendedRigidVertex *)pPhyContextExport->GetVertexInterface(iVertex);
  3029.                         if (pBlendedRigidVertexExport == NULL)
  3030.                         {
  3031.                             OutputDebugString("Couldn't get blended rigid vertex export interface!");
  3032.                             hr = E_FAIL;
  3033.                             goto e_Exit;
  3034.                         }
  3035.  
  3036.                         // How many nodes affect his vertex?
  3037.                         cTotalBones = pBlendedRigidVertexExport->GetNumberNodes();
  3038.                         for (iBone = 0; iBone < cTotalBones; iBone++ )
  3039.                         {
  3040.                             pBoneNode = pBlendedRigidVertexExport->GetNode(iBone);
  3041.  
  3042.                             // first see if the bone has already been seen
  3043.                             bBoneSeen = FALSE;
  3044.                             for (iBoneSeen = 0; iBoneSeen < cpnBonesSeen; iBoneSeen++)
  3045.                             {
  3046.                                 if (rgpnBonesSeen[iBoneSeen] == pBoneNode)
  3047.                                 {
  3048.                                     bBoneSeen = TRUE;
  3049.                                     break;
  3050.                                 }
  3051.                             }
  3052.                         
  3053.                             // if not seen, collect stats and add to already seen
  3054.                             if (!bBoneSeen)
  3055.                             {
  3056.                                 if (cpnBonesSeen >= cpnBonesSeenMax)
  3057.                                 {
  3058.                                     rgpnTemp = rgpnBonesSeen;
  3059.                                     cpnBonesSeenMax *= 2;
  3060.  
  3061.                                     rgpnBonesSeen = new INode*[cpnBonesSeenMax];
  3062.                                     if (rgpnBonesSeen == NULL)
  3063.                                     {
  3064.                                         rgpnBonesSeen = rgpnTemp;
  3065.                                         hr = E_OUTOFMEMORY;
  3066.                                         goto e_Exit;
  3067.                                     }
  3068.  
  3069.                                     memcpy(rgpnBonesSeen, rgpnTemp, cpnBonesSeen * sizeof(INode*));
  3070.                                     delete []rgpnTemp;
  3071.                                 }
  3072.  
  3073.                                 rgpnBonesSeen[cpnBonesSeen] = pBoneNode;
  3074.                                 cpnBonesSeen += 1;
  3075.                             }
  3076.                         }
  3077.  
  3078.                         pPhyContextExport->ReleaseVertexInterface( pBlendedRigidVertexExport);
  3079.                     }
  3080.                 }
  3081.  
  3082.                 ppc->m_cMaxWeightsPerFace = max(cpnBonesSeen, ppc->m_cMaxWeightsPerFace);
  3083.             }
  3084.         }
  3085.  
  3086. e_NoPhysique:;
  3087.     }
  3088.  
  3089.     cChildren = pNode->NumberOfChildren();
  3090.     for (iChild = 0; iChild < cChildren; iChild++)
  3091.     {
  3092.         // enumerate the child
  3093.         hr = Preprocess(ppc, pNode->GetChildNode(iChild));
  3094.         if (FAILED(hr))
  3095.             goto e_Exit;        
  3096.     }
  3097.  
  3098. e_Exit:
  3099.     if (bDeleteTriObject)
  3100.     {
  3101.         delete pTriObject;
  3102.     }
  3103.  
  3104.     delete []rgpnBonesSeen;
  3105.     return hr;
  3106. }
  3107.  
  3108.  
  3109. struct SAnimInfo
  3110. {
  3111.     DWORD dwTime;
  3112.     DWORD dwNumValues;
  3113.     float rgfValues[16];
  3114. };
  3115.  
  3116. BOOL BFloatsEqual
  3117.     (
  3118.     float f1,
  3119.     float f2
  3120.     )
  3121. {
  3122.     // first do a bitwise compare
  3123.     if ((*(DWORD*)&f1) == (*(DWORD*)&f2))
  3124.         return TRUE;
  3125.  
  3126.     // next do an epsilon compare
  3127.     float fDiff = (f1 - f2);
  3128.     return (fDiff <= 1e-6f) && (fDiff >= -1e-6f);
  3129. }
  3130.  
  3131. BOOL BEqualMatrices(float *rgfMat1, float *rgfMat2)
  3132. {
  3133.     DWORD iFloat;
  3134.  
  3135.     for (iFloat = 0; iFloat < 16; iFloat++)
  3136.     {
  3137.         if (!BFloatsEqual(rgfMat1[iFloat], rgfMat2[iFloat]))
  3138.             return FALSE;
  3139.     }
  3140.  
  3141.     return TRUE;
  3142. }
  3143.  
  3144. HRESULT GenerateAnimationSet
  3145.     (
  3146.     SSaveContext *psc
  3147.     )
  3148. {
  3149.     HRESULT hr = S_OK;
  3150.     DWORD cKeys;
  3151.     PBYTE pbData = NULL;
  3152.     PBYTE pbCur;
  3153.     DWORD cTicksPerFrame;
  3154.     DWORD iCurTime;
  3155.     DWORD iCurKey;
  3156.     Matrix3 matFirstTM;
  3157.     Matrix3 matTM;
  3158.     Matrix3 matRelativeTM;
  3159.     Matrix3 matParentTM;
  3160.     Interval interval;
  3161.     DWORD iNode;
  3162.     INode *pNode;
  3163.     DWORD cbSize;
  3164.     DWORD iKey;
  3165.     DWORD cCurrentKeys;
  3166.     SAnimInfo *rgaiAnimData = NULL;
  3167.     SAnimInfo *rgaiAnimDataCur;
  3168.     LPDIRECTXFILEDATA pAnimation = NULL;
  3169.     LPDIRECTXFILEDATA pAnimationKeys = NULL;
  3170.     char *szName;
  3171.     BOOL bAddEndKey = FALSE;
  3172.     DWORD iInterval;
  3173.     DWORD iStartTime;
  3174.     DWORD iEndTime;
  3175.     DWORD cFrameRate;
  3176.  
  3177.     // find the animation info from the interface
  3178.     interval = psc->m_pInterface->GetAnimRange();
  3179.     cTicksPerFrame = GetTicksPerFrame();
  3180.     cFrameRate = GetFrameRate();
  3181.     iStartTime = interval.Start();
  3182.     iEndTime = interval.End();
  3183.  
  3184.     iInterval = (cTicksPerFrame * cFrameRate) / psc->m_iAnimSamplingRate;
  3185.  
  3186.     cKeys = (iEndTime - iStartTime) / iInterval;
  3187.  
  3188.     // if the sampling frequency doesn't end directly on 
  3189.     //   on the end time, then add a partial end key
  3190.     if (((iEndTime - iStartTime) % iInterval) != 0)
  3191.     {
  3192.         bAddEndKey = TRUE;
  3193.         cKeys += 1;
  3194.     }
  3195.  
  3196.     // add one more key for looping
  3197.     cKeys += 1;
  3198.  
  3199.     rgaiAnimData = new SAnimInfo[psc->m_cNodes*cKeys];
  3200.     if (rgaiAnimData == NULL)
  3201.     {
  3202.         hr = E_OUTOFMEMORY;
  3203.         goto e_Exit;
  3204.     }
  3205.  
  3206.     for (iCurKey = 0, iCurTime = iStartTime; iCurTime <= iEndTime; iCurTime += iInterval, iCurKey++ )
  3207.     {
  3208.         for (iNode = 0; iNode < psc->m_cNodes; iNode++)
  3209.         {
  3210.             pNode = psc->m_rgpnNodes[iNode];
  3211.  
  3212.             //iCurTime = iFrame * (cTicksPerFrame / 2);
  3213.             matTM = pNode->GetNodeTM(iCurTime);
  3214.  
  3215.             if (pNode->GetParentNode() == NULL)
  3216.             {
  3217.                 matRelativeTM = matTM;
  3218.             }
  3219.             else
  3220.             {
  3221.                 matParentTM = pNode->GetParentNode()->GetNodeTM(iCurTime);
  3222.                 if( memcmp(&matTM,&matParentTM,12*sizeof(float)) == 0 )
  3223.                 {
  3224.                     matRelativeTM.IdentityMatrix();
  3225.                 }
  3226.                 else
  3227.                 {
  3228.                     matRelativeTM = matTM * Inverse(matParentTM);
  3229.                 }
  3230.             }
  3231.  
  3232.             // set the current pointer to the correct buffer position
  3233.             pbCur = (PBYTE)&rgaiAnimData[iNode*cKeys + iCurKey];
  3234.  
  3235.             // first write the time and dword count            
  3236.             WRITE_DWORD(pbCur, iCurTime);
  3237.             WRITE_DWORD(pbCur, 16);
  3238.  
  3239.             // next write the matrix
  3240.             WRITE_MATRIX4_FROM_MATRIX3(pbCur, matRelativeTM);
  3241.  
  3242.         }
  3243.     }
  3244.  
  3245.     // if the sampling rate doesn't evenly end on the last frame, add a partial key frame
  3246.     if (bAddEndKey)
  3247.     {
  3248.         assert(((iEndTime - iStartTime) % iInterval) != 0);
  3249.  
  3250.         // just add the end time as a key frame
  3251.         for (iNode = 0; iNode < psc->m_cNodes; iNode++)
  3252.         {
  3253.             // set the current pointer to the correct buffer position
  3254.             pbCur = (PBYTE)&rgaiAnimData[iCurKey];
  3255.  
  3256.             matTM = pNode->GetNodeTM(iEndTime);
  3257.             if (pNode->GetParentNode() == NULL)
  3258.             {
  3259.                 matRelativeTM = matTM;
  3260.             }
  3261.             else
  3262.             {
  3263.                 matParentTM = pNode->GetParentNode()->GetNodeTM(iEndTime);
  3264.                 if( memcmp(&matTM,&matParentTM,12*sizeof(float)) == 0 )
  3265.                 {
  3266.                     matRelativeTM.IdentityMatrix();
  3267.                 }
  3268.                 else
  3269.                 {
  3270.                     matRelativeTM = matTM * Inverse(matParentTM);
  3271.                 }
  3272.             }
  3273.  
  3274.             WRITE_DWORD(pbCur, iEndTime);
  3275.             WRITE_DWORD(pbCur, 16);
  3276.  
  3277.             // next write the matrix
  3278.             WRITE_MATRIX4_FROM_MATRIX3(pbCur, matRelativeTM);
  3279.         }
  3280.     }
  3281.  
  3282.     // loop the anim
  3283.     for (iNode = 0; iNode < psc->m_cNodes; iNode++)
  3284.     {
  3285.         // set the current pointer to the correct buffer position
  3286.         pbCur = (PBYTE)&rgaiAnimData[iNode*cKeys + (cKeys-1)];
  3287.  
  3288.         WRITE_DWORD(pbCur, iEndTime + iInterval);
  3289.         WRITE_DWORD(pbCur, 16);
  3290.  
  3291.         // next write the matrix
  3292.         memcpy(pbCur, rgaiAnimData[iNode*cKeys].rgfValues, sizeof(float)*16);
  3293.     }
  3294.  
  3295.     // allocate memory to send to D3DXOF lib
  3296.     cbSize = sizeof(DWORD) + sizeof(DWORD) +
  3297.             (sizeof(DWORD) //time
  3298.                 + sizeof(DWORD) //nValues
  3299.                 + sizeof(FLOAT)*16) // x, y, z
  3300.                * cKeys; // number of keys
  3301.  
  3302.     pbData = new BYTE[cbSize];
  3303.     if (pbData == NULL)
  3304.     {
  3305.         hr = E_OUTOFMEMORY;
  3306.         goto e_Exit;
  3307.     }
  3308.  
  3309.     hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMAnimationSet,
  3310.                                     NULL,
  3311.                                     NULL,
  3312.                                     0,
  3313.                                     NULL,
  3314.                                     &psc->m_pAnimationSet
  3315.                                     );
  3316.     if (FAILED(hr))
  3317.         goto e_Exit;
  3318.  
  3319.     for (iNode = 0; iNode < psc->m_cNodes; iNode++)
  3320.     {
  3321.         pbCur = pbData;
  3322.  
  3323.         szName = psc->m_stStrings.CreateNiceString(psc->m_rgpnNodes[iNode]->GetName());
  3324.         if (szName == NULL)
  3325.         {
  3326.             hr = E_OUTOFMEMORY;
  3327.             goto e_Exit;
  3328.         }
  3329.  
  3330.         // write the key type
  3331.         WRITE_DWORD(pbCur, 4);
  3332.  
  3333.         // write the number of keys
  3334.         WRITE_DWORD(pbCur, cKeys);
  3335.  
  3336.         rgaiAnimDataCur = &rgaiAnimData[iNode*cKeys];
  3337.         cCurrentKeys = 0;
  3338.         for (iKey = 0; iKey < cKeys; iKey++)
  3339.         {
  3340.             memcpy(pbCur, &rgaiAnimDataCur[iKey], sizeof(SAnimInfo));
  3341.             pbCur += sizeof(SAnimInfo);
  3342.             cCurrentKeys += 1;
  3343.  
  3344.             // if not last key, then check for start of a run of identical keys
  3345.             if (iKey < (cKeys-1))
  3346.             {
  3347.                 // if equal to next, check for a run of equal matrices
  3348.                 if (BEqualMatrices(rgaiAnimDataCur[iKey].rgfValues, rgaiAnimDataCur[iKey+1].rgfValues))
  3349.                 {
  3350.                     // move to the next key, and skip all equal matrices
  3351.                     iKey += 1;
  3352.                     while ((iKey < (cKeys-1)) && BEqualMatrices(rgaiAnimDataCur[iKey].rgfValues, rgaiAnimDataCur[iKey+1].rgfValues))
  3353.                     {
  3354.                         iKey += 1;
  3355.                     }
  3356.  
  3357.                     memcpy(pbCur, &rgaiAnimDataCur[iKey], sizeof(SAnimInfo));
  3358.                     pbCur += sizeof(SAnimInfo);
  3359.                     cCurrentKeys += 1;
  3360.                 }
  3361.             }
  3362.         }
  3363.  
  3364.         // update to current count of keys
  3365.         ((DWORD*)pbData)[1] = cCurrentKeys;
  3366.  
  3367.         hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMAnimation,
  3368.                                         NULL,
  3369.                                         NULL,
  3370.                                         0,
  3371.                                         NULL,
  3372.                                         &pAnimation
  3373.                                         );
  3374.  
  3375.     // add the data to the file
  3376.         hr = psc->m_pxofsave->CreateDataObject(TID_D3DRMAnimationKey,
  3377.                                         NULL,
  3378.                                         NULL,
  3379.                                         cbSize,
  3380.                                         pbData,
  3381.                                         &pAnimationKeys
  3382.                                         );
  3383.         if (FAILED(hr))
  3384.             goto e_Exit;
  3385.  
  3386.         // add to the animation set
  3387.         hr = pAnimation->AddDataObject(pAnimationKeys);
  3388.         if (FAILED(hr))
  3389.             goto e_Exit;
  3390.  
  3391.         hr = pAnimation->AddDataReference(szName, NULL);
  3392.         if (FAILED(hr))
  3393.             goto e_Exit;
  3394.  
  3395.         hr = psc->m_pAnimationSet->AddDataObject(pAnimation);
  3396.         if (FAILED(hr))
  3397.             goto e_Exit;
  3398.  
  3399.         RELEASE(pAnimation);
  3400.         RELEASE(pAnimationKeys);
  3401.     }
  3402.  
  3403. e_Exit:
  3404.     delete []rgaiAnimData;
  3405.     delete []pbData;
  3406.     RELEASE(pAnimation);
  3407.     RELEASE(pAnimationKeys);
  3408.  
  3409.     return hr;
  3410. }
  3411.  
  3412.  
  3413. // ================================================== CDataSave::CDataSave()
  3414. HRESULT ExportXFile
  3415.     (
  3416.     const TCHAR *szFilename,
  3417.     ExpInterface *pExportInterface,
  3418.     Interface *pInterface, 
  3419.     BOOL bSuppressPrompts,
  3420.     BOOL bSaveSelection,
  3421.     HWND hwndParent
  3422.     ) 
  3423. {
  3424.     LPDIRECTXFILE pxofapi = NULL;
  3425.     LPDIRECTXFILEDATA pRootData = NULL;
  3426.     LPDIRECTXFILESAVEOBJECT pxofsave = NULL; 
  3427.     HRESULT hr = S_OK;
  3428.     INode *pRootNode;
  3429.     SSaveContext sc;
  3430.     SPreprocessContext pc;
  3431.     SDialogOptions DlgOptions;
  3432.  
  3433.     assert(szFilename && pExportInterface && pInterface);
  3434.  
  3435.     // Extract scene information
  3436.     pInterface->ProgressStart(_T("Extracting skinning data"),TRUE,dummyFn,NULL);
  3437.     pInterface->ProgressUpdate(0);
  3438.     //pInterface->ProgressUpdate(100);
  3439.  
  3440.     // first find the root node
  3441.     hr = FindRootNode(pExportInterface->theScene, &pRootNode);
  3442.     if (FAILED(hr))
  3443.         goto e_Exit;
  3444.  
  3445.     // setup options for Preprocess stage
  3446.     pc.m_bSaveSelection = bSaveSelection;
  3447.  
  3448.     // figure out bone counts, etc.
  3449.     hr = Preprocess(&pc, pRootNode);
  3450.     if (FAILED(hr))
  3451.         goto e_Exit;
  3452.  
  3453.     // move the skin map data from the preprocess context to the save context
  3454.     sc.m_cpsmSkinMaps = pc.m_cpsmSkinMaps;
  3455.     sc.m_rgpsmSkinMaps = pc.m_rgpsmSkinMaps;
  3456.     pc.m_rgpsmSkinMaps = NULL;
  3457.  
  3458.     pInterface->ProgressUpdate(25);
  3459.  
  3460.     // setup dialog options
  3461.     DlgOptions.m_xFormat = DXFILEFORMAT_BINARY;
  3462.     DlgOptions.m_bSavePatchData = FALSE;
  3463.     DlgOptions.m_bSaveAnimationData = TRUE;
  3464.     DlgOptions.m_iAnimSamplingRate = 30;
  3465.     DlgOptions.m_cMaxBonesPerVertex = pc.m_cMaxWeightsPerVertex;
  3466.     DlgOptions.m_cMaxBonesPerFace = pc.m_cMaxWeightsPerFace;
  3467.  
  3468.     // if prompts not suppressed, then check with the user on options
  3469.     if (!bSuppressPrompts)
  3470.     {
  3471.         DialogBoxParam(g_hInstance, 
  3472.                        MAKEINTRESOURCE(IDD_PANEL), 
  3473.                         hwndParent, 
  3474.                         XSkinExpOptionsDlgProc, 
  3475.                         (LPARAM)&DlgOptions);
  3476.  
  3477.         if (!DlgOptions.m_bProceedWithExport)
  3478.             goto e_Exit;
  3479.     }
  3480.  
  3481.     pInterface->ProgressStart(_T("Exporting data"),TRUE,dummyFn,NULL);
  3482.     pInterface->ProgressUpdate(25);
  3483.  
  3484.     // Create xofapi object.
  3485.     hr = DirectXFileCreate(&pxofapi);
  3486.     if (FAILED(hr))
  3487.         goto e_Exit;
  3488.  
  3489.     // Register templates for d3drm.
  3490.     hr = pxofapi->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES,
  3491.                                     D3DRM_XTEMPLATE_BYTES);
  3492.     if (FAILED(hr))
  3493.         goto e_Exit;
  3494.  
  3495.     hr = pxofapi->RegisterTemplates((LPVOID)XSKINEXP_TEMPLATES,
  3496.                                     strlen(XSKINEXP_TEMPLATES));
  3497.     if (FAILED(hr))
  3498.         goto e_Exit;
  3499.  
  3500.     // Create save object.
  3501.     hr = pxofapi->CreateSaveObject(szFilename,    // filename
  3502.                                    DlgOptions.m_xFormat,  // binary or text
  3503.                                    &pxofsave);
  3504.     if (FAILED(hr))
  3505.         goto e_Exit;
  3506.  
  3507.     hr = pxofsave->SaveTemplates(3, aIds);
  3508.     if (FAILED(hr))
  3509.         goto e_Exit;
  3510.  
  3511.     sc.m_pxofsave = pxofsave;
  3512.     sc.m_xFormat = DlgOptions.m_xFormat;
  3513.     sc.m_bSaveAnimationData = DlgOptions.m_bSaveAnimationData;
  3514.     sc.m_iAnimSamplingRate = DlgOptions.m_iAnimSamplingRate;
  3515.     sc.m_bSavePatchData = DlgOptions.m_bSavePatchData;
  3516.     sc.m_iTime = pInterface->GetTime();
  3517.     sc.m_pInterface = pInterface;
  3518.     sc.m_bSaveSelection = bSaveSelection;
  3519.     sc.m_cMaxWeightsPerVertex = pc.m_cMaxWeightsPerVertex;
  3520.     sc.m_cMaxWeightsPerFace = pc.m_cMaxWeightsPerFace;
  3521.  
  3522.     sc.m_cNodes = pc.m_cNodes;
  3523.     sc.m_cNodesCur = 0;
  3524.     sc.m_rgpnNodes = new INode*[sc.m_cNodes];
  3525.     if (sc.m_rgpnNodes == NULL)
  3526.     {
  3527.         hr = E_OUTOFMEMORY;
  3528.         goto e_Exit;
  3529.     }
  3530.  
  3531.     // then write the whole tree out into file data's
  3532.     hr = EnumTree(&sc, pRootNode, &pRootData);
  3533.     if (FAILED(hr))
  3534.         goto e_Exit;
  3535.  
  3536.     pInterface->ProgressStart(_T("Saving data to file"),TRUE,dummyFn,NULL);
  3537.     pInterface->ProgressUpdate(50);
  3538.  
  3539.     // now save that file data to the file
  3540.     hr = pxofsave->SaveData(pRootData);
  3541.     if (FAILED(hr))
  3542.         goto e_Exit;
  3543.  
  3544.     pInterface->ProgressUpdate(75);
  3545.  
  3546.     if (DlgOptions.m_bSaveAnimationData)
  3547.     {
  3548.         pInterface->ProgressStart(_T("Saving animation data to file"),TRUE,dummyFn,NULL);
  3549.         pInterface->ProgressUpdate(75);
  3550.  
  3551.         hr = GenerateAnimationSet(&sc);
  3552.         if (FAILED(hr))
  3553.             goto e_Exit;
  3554.  
  3555.         hr = pxofsave->SaveData(sc.m_pAnimationSet);
  3556.         if (FAILED(hr))
  3557.         {
  3558.             OutputDebugString("Failed to add animation set to X File\n");
  3559.             goto e_Exit;
  3560.         }            
  3561.     }
  3562.  
  3563. e_Exit:
  3564.     if (FAILED(hr))
  3565.     {
  3566.         OutputDebugString("File was not successfully exported.");
  3567.         {
  3568.             TCHAR errstr[500 + _MAX_PATH];
  3569.             _stprintf(errstr,"Could not write to file: %s.\n"\
  3570.                 "Try checking the file's permissions, or if it is currently open.",szFilename);
  3571.             MessageBox(hwndParent,errstr,_T("Error!"),MB_OK);
  3572.         }
  3573.     }
  3574.  
  3575.     // falling through
  3576.     // Free up outstanding interfaces
  3577.     RELEASE(pxofsave);
  3578.     RELEASE(pRootData);
  3579.     RELEASE(pxofapi);
  3580.  
  3581.     pInterface->ProgressUpdate(100);
  3582.     pInterface->ProgressEnd();
  3583.  
  3584.     return hr;
  3585. }
  3586.  
  3587.