diff options
Diffstat (limited to 'utils/smdlexp')
| -rw-r--r-- | utils/smdlexp/smdlexp.cpp | 1096 | ||||
| -rw-r--r-- | utils/smdlexp/smdlexp.def | 8 | ||||
| -rw-r--r-- | utils/smdlexp/smdlexp.mak | 325 | ||||
| -rw-r--r-- | utils/smdlexp/smdlexp.rc | 147 | ||||
| -rw-r--r-- | utils/smdlexp/smdlexp.vcproj | 179 | ||||
| -rw-r--r-- | utils/smdlexp/smedefs.h | 178 | ||||
| -rw-r--r-- | utils/smdlexp/smexprc.h | 28 |
7 files changed, 1961 insertions, 0 deletions
diff --git a/utils/smdlexp/smdlexp.cpp b/utils/smdlexp/smdlexp.cpp new file mode 100644 index 0000000..4955dd9 --- /dev/null +++ b/utils/smdlexp/smdlexp.cpp @@ -0,0 +1,1096 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "MAX.H" +#include "DECOMP.H" +#include "STDMAT.H" +#include "ANIMTBL.H" +#include "istdplug.h" +#include "phyexp.h" +#include "BonesPro.h" + +#include "smexprc.h" +#include "smedefs.h" + +//=================================================================== +// Prototype declarations +// +int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE); +void SetIndexOfINode(INode *pnode, int inode); +BOOL FUndesirableNode(INode *pnode); +BOOL FNodeMarkedToSkip(INode *pnode); +float FlReduceRotation(float fl); + + +//=================================================================== +// Global variable definitions +// + +// Save for use with dialogs +static HINSTANCE hInstance; + +// We just need one of these to hand off to 3DSMAX. +static SmdExportClassDesc SmdExportCD; + +// For OutputDebugString and misc sprintf's +static char st_szDBG[300]; + +// INode mapping table +static int g_inmMac = 0; + +//=================================================================== +// Utility functions +// + +static int AssertFailedFunc(char *sz) +{ + MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK); + int Set_Your_Breakpoint_Here = 1; + return 1; +} +#define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz)) + + +//=================================================================== +// Required plug-in export functions +// +BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved) +{ + static int fFirstTimeHere = TRUE; + if (fFirstTimeHere) + { + fFirstTimeHere = FALSE; + hInstance = hinstDLL; + } + return TRUE; +} + + +EXPORT_THIS int LibNumberClasses(void) +{ + return 1; +} + + +EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass) +{ + switch(iWhichClass) + { + case 0: return &SmdExportCD; + default: return 0; + } +} + + +EXPORT_THIS const TCHAR *LibDescription() +{ + return _T("Valve SMD Plug-in."); +} + + +EXPORT_THIS ULONG LibVersion() +{ + return VERSION_3DSMAX; +} + + +//===================================================================== +// Methods for SmdExportClass +// + +CONSTRUCTOR SmdExportClass::SmdExportClass(void) +{ + m_rgmaxnode = NULL; +} + + +DESTRUCTOR SmdExportClass::~SmdExportClass(void) +{ + if (m_rgmaxnode) + delete[] m_rgmaxnode; +} + + +int SmdExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options) +{ + ExpInterface *pexpiface = ei; // Hungarian + Interface *piface = i; // Hungarian + + // Reset the name-map property manager + g_inmMac = 0; + + if (!suppressPrompts) + { + // Present the user with the Export Options dialog + if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORTOPTIONS), GetActiveWindow(), + ExportOptionsDlgProc, (LPARAM)this) <= 0) + return 0; // error or cancel + } + else + { + m_fReferenceFrame = 0; + } + + // Break up filename, re-assemble longer versions + TSTR strPath, strFile, strExt; + TCHAR szFile[MAX_PATH]; + SplitFilename(TSTR(name), &strPath, &strFile, &strExt); + sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + + /* + if (m_fReferenceFrame) + sprintf(szFile, "%s\\%s_model.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + */ + + FILE *pFile; + if ((pFile = fopen(szFile, "w")) == NULL) + return FALSE/*failure*/; + + fprintf( pFile, "version %d\n", 1 ); + + // Get animation metrics + m_intervalOfAnimation = piface->GetAnimRange(); + m_tvStart = m_intervalOfAnimation.Start(); + m_tvEnd = m_intervalOfAnimation.End(); + m_tpf = ::GetTicksPerFrame(); + + // Count nodes, label them, collect into array + if (!CollectNodes(pexpiface)) + return 0; /*fail*/ + + // Output nodes + if (!DumpBones(pFile, pexpiface)) + { + fclose( pFile ); + return 0; /*fail*/ + } + + // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file + DumpRotations(pFile, pexpiface); + + // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file + if (m_fReferenceFrame) + { + DumpModel(pFile, pexpiface); + } + + if (!suppressPrompts) + { + // Tell user that exporting is finished (it can take a while with no feedback) + char szExportComplete[300]; + sprintf(szExportComplete, "Exported %s.", szFile); + MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK); + } + + fclose( pFile ); + + return 1/*success*/; +} + + +BOOL SmdExportClass::CollectNodes( ExpInterface *pexpiface) +{ + // Count total nodes in the model, so I can alloc array + // Also "brands" each node with node index, or with "skip me" marker. + CountNodesTEP procCountNodes; + procCountNodes.m_cNodes = 0; + (void) pexpiface->theScene->EnumTree(&procCountNodes); + ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!"); + + // Alloc and fill array + m_imaxnodeMac = procCountNodes.m_cNodes; + m_rgmaxnode = new MaxNode[m_imaxnodeMac]; + ASSERT_MBOX(m_rgmaxnode != NULL, "new failed"); + + + CollectNodesTEP procCollectNodes; + procCollectNodes.m_phec = this; + (void) pexpiface->theScene->EnumTree(&procCollectNodes); + + return TRUE; +} + + +BOOL SmdExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone names + DumpNodesTEP procDumpNodes; + procDumpNodes.m_pfile = pFile; + procDumpNodes.m_phec = this; + fprintf(pFile, "nodes\n" ); + (void) pexpiface->theScene->EnumTree(&procDumpNodes); + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone-rotation info, for each frame + // Also dumps root-node translation info (the model's world-position at each frame) + DumpFrameRotationsTEP procDumpFrameRotations; + procDumpFrameRotations.m_pfile = pFile; + procDumpFrameRotations.m_phec = this; + + TimeValue m_tvTill = (m_fReferenceFrame) ? m_tvStart : m_tvEnd; + + fprintf(pFile, "skeleton\n" ); + for (TimeValue tv = m_tvStart; tv <= m_tvTill; tv += m_tpf) + { + fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() ); + procDumpFrameRotations.m_tvToDump = tv; + (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations); + } + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface) +{ + // Dump mesh info: vertices, normals, UV texture map coords, bone assignments + DumpModelTEP procDumpModel; + procDumpModel.m_pfile = pFile; + procDumpModel.m_phec = this; + fprintf(pFile, "triangles\n" ); + procDumpModel.m_tvToDump = m_tvStart; + (void) pexpiface->theScene->EnumTree(&procDumpModel); + fprintf(pFile, "end\n" ); + return TRUE; +} + + + + +//============================================================================= +// TREE-ENUMERATION PROCEDURES +//============================================================================= + +#define ASSERT_AND_ABORT(f, sz) \ + if (!(f)) \ + { \ + ASSERT_MBOX(FALSE, sz); \ + cleanup( ); \ + return TREE_ABORT; \ + } + + +//================================================================= +// Methods for CountNodesTEP +// +int CountNodesTEP::callback( INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FUndesirableNode(pnode)) + { + // Mark as skippable + ::SetIndexOfINode(pnode, SmdExportClass::UNDESIRABLE_NODE_MARKER); + return TREE_CONTINUE; + } + + // Establish "node index"--just ascending ints + ::SetIndexOfINode(pnode, m_cNodes); + + m_cNodes++; + + return TREE_CONTINUE; +} + + +//================================================================= +// Methods for CollectNodesTEP +// +int CollectNodesTEP::callback(INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get pre-stored "index" + int iNode = ::GetIndexOfINode(pnode); + ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode"); + + // Get name, store name in array + TSTR strNodeName(pnode->GetName()); + strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName); + + // Get Node's time-zero Transformation Matrices + m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/); + m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/); + + // I'll calculate this later + m_phec->m_rgmaxnode[iNode].imaxnodeParent = SmdExportClass::UNDESIRABLE_NODE_MARKER; + + return TREE_CONTINUE; +} + + + + + + +//================================================================= +// Methods for DumpNodesTEP +// +int DumpNodesTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get node's parent + INode *pnodeParent; + pnodeParent = pnode->GetParentNode(); + + // The model's root is a child of the real "scene root" + TSTR strNodeName(pnode->GetName()); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + int iNode = ::GetIndexOfINode(pnode); + int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/); + + // Convenient time to cache this + m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent; + + // Root node has no parent, thus no translation + if (fNodeIsRoot) + iNodeParent = -1; + + // check to see if the matrix isn't right handed + m_phec->m_rgmaxnode[iNode].isMirrored = DotProd( CrossProd( m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(0).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(1).Normalize() ).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(2).Normalize() ) < 0; + + // Dump node description + fprintf(m_pfile, "%3d \"%s\" %3d\n", + iNode, + strNodeName, + iNodeParent ); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpFrameRotationsTEP +// +int DumpFrameRotationsTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + int iNode = ::GetIndexOfINode(pnode); + + TSTR strNodeName(pnode->GetName()); + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get Node's "Local" Transformation Matrix + Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump); + Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump); + mat3NodeTM.NoScale(); // Clear these out because they apparently + mat3ParentTM.NoScale(); // screw up the following calculation. + Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM); + Point3 rowTrans = mat3NodeLocalTM.GetTrans(); + + // check to see if the parent bone was mirrored. If so, mirror invert this bones position + if (m_phec->m_rgmaxnode[iNode].imaxnodeParent >= 0 && m_phec->m_rgmaxnode[m_phec->m_rgmaxnode[iNode].imaxnodeParent].isMirrored) + { + rowTrans = rowTrans * -1.0f; + } + + // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler) + // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z. + AffineParts affparts; + float rgflXYZRotations[3]; + + decomp_affine(mat3NodeLocalTM, &affparts); + QuatToEuler(affparts.q, rgflXYZRotations); + + float xRot = rgflXYZRotations[0]; // in radians + float yRot = rgflXYZRotations[1]; // in radians + float zRot = rgflXYZRotations[2]; // in radians + + // Get rotations in the -2pi...2pi range + xRot = ::FlReduceRotation(xRot); + yRot = ::FlReduceRotation(yRot); + zRot = ::FlReduceRotation(zRot); + + // Print rotations + fprintf(m_pfile, "%3d %f %f %f %f %f %f\n", + // Node:%-15s Rotation (x,y,z)\n", + iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpModelTEP +// +Modifier *FindPhysiqueModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Physique ? + if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +Modifier *FindBonesProModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Bones Pro OSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_OSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Is this Bones Pro WSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_WSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +// #define DEBUG_MESH_DUMP + +//================================================================= +// Methods for DumpModelTEP +// +int DumpModelTEP::callback(INode *pnode) +{ + Object* pobj; + int fHasMat = TRUE; + + // clear physique export parameters + m_mcExport = NULL; + m_phyExport = NULL; + m_phyMod = NULL; + m_bonesProMod = NULL; + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Actually, if it's not selected, skip it! + //if (!pnode->Selected()) + // return TRUE; + + int iNode = ::GetIndexOfINode(pnode); + TSTR strNodeName(pnode->GetName()); + + // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly. + if (FStrEq((char*)strNodeName, "Bip01 Footsteps")) + return TREE_CONTINUE; + + // Helper nodes don't have meshes + pobj = pnode->GetObjectRef(); + if (pobj->SuperClassID() == HELPER_CLASS_ID) + return TREE_CONTINUE; + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get node's material: should be a multi/sub (if it has a material at all) + Mtl *pmtlNode = pnode->GetMtl(); + if (pmtlNode == NULL) + { + return TREE_CONTINUE; + fHasMat = FALSE; + } + else if (!(pmtlNode->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlNode->IsMultiMtl())) + { + // sprintf(st_szDBG, "ERROR--Material on node %s isn't a Multi/Sub-Object", (char*)strNodeName); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + // fHasMat = FALSE; + } + + // Get Node's object, convert to a triangle-mesh object, so I can access the Faces + ObjectState os = pnode->EvalWorldState(m_tvToDump); + pobj = os.obj; + TriObject *ptriobj; + BOOL fConvertedToTriObject = + pobj->CanConvertToType(triObjectClassID) && + (ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID)) != NULL; + if (!fConvertedToTriObject) + return TREE_CONTINUE; + Mesh *pmesh = &ptriobj->mesh; + + // Shouldn't have gotten this far if it's a helper object + if (pobj->SuperClassID() == HELPER_CLASS_ID) + { + sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + // Ensure that the vertex normals are up-to-date + pmesh->buildNormals(); + + // We want the vertex coordinates in World-space, not object-space + Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump); + + + // initialize physique export parameters + m_phyMod = FindPhysiqueModifier(pnode); + if (m_phyMod) + { + // Physique Modifier exists for given Node + m_phyExport = (IPhysiqueExport *)m_phyMod->GetInterface(I_PHYINTERFACE); + + if (m_phyExport) + { + // create a ModContext Export Interface for the specific node of the Physique Modifier + m_mcExport = (IPhyContextExport *)m_phyExport->GetContextInterface(pnode); + + if (m_mcExport) + { + // convert all vertices to Rigid + m_mcExport->ConvertToRigid(TRUE); + } + } + } + + // initialize bones pro export parameters + m_wa = NULL; + m_bonesProMod = FindBonesProModifier(pnode); + if (m_bonesProMod) + { + m_bonesProMod->SetProperty( BP_PROPID_GET_WEIGHTS, &m_wa ); + } + + // Dump the triangle face info + int cFaces = pmesh->getNumFaces(); + for (int iFace = 0; iFace < cFaces; iFace++) + { + Face* pface = &pmesh->faces[iFace]; + TVFace* ptvface = (pmesh->tvFace) ? &pmesh->tvFace[iFace] : NULL; + DWORD smGroupFace = pface->getSmGroup(); + + // Get face's 3 indexes into the Mesh's vertex array(s). + DWORD iVertex0 = pface->getVert(0); + DWORD iVertex1 = pface->getVert(1); + DWORD iVertex2 = pface->getVert(2); + ASSERT_AND_ABORT((int)iVertex0 < pmesh->getNumVerts(), "Bogus Vertex 0 index"); + ASSERT_AND_ABORT((int)iVertex1 < pmesh->getNumVerts(), "Bogus Vertex 1 index"); + ASSERT_AND_ABORT((int)iVertex2 < pmesh->getNumVerts(), "Bogus Vertex 2 index"); + + // Get the 3 Vertex's for this face + Point3 pt3Vertex0 = pmesh->getVert(iVertex0); + Point3 pt3Vertex1 = pmesh->getVert(iVertex1); + Point3 pt3Vertex2 = pmesh->getVert(iVertex2); + + // Get the 3 RVertex's for this face + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + RVertex *prvertex0 = pmesh->getRVertPtr(iVertex0); + RVertex *prvertex1 = pmesh->getRVertPtr(iVertex1); + RVertex *prvertex2 = pmesh->getRVertPtr(iVertex2); + + // Find appropriate normals for each RVertex + // A vertex can be part of multiple faces, so the "smoothing group" + // is used to locate the normal for this face's use of the vertex. + Point3 pt3Vertex0Normal; + Point3 pt3Vertex1Normal; + Point3 pt3Vertex2Normal; + if (smGroupFace) + { + pt3Vertex0Normal = Pt3GetRVertexNormal(prvertex0, smGroupFace); + pt3Vertex1Normal = Pt3GetRVertexNormal(prvertex1, smGroupFace); + pt3Vertex2Normal = Pt3GetRVertexNormal(prvertex2, smGroupFace); + } + else + { + pt3Vertex0Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex1Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex2Normal = pmesh->getFaceNormal( iFace ); + } + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" ); + + // Get Face's sub-material from node's material, to get the bitmap name. + // And no, there isn't a simpler way to get the bitmap name, you have to + // dig down through all these levels. + TCHAR szBitmapName[256] = "null.bmp"; + if (fHasMat) + { + Texmap *ptexmap = NULL; + MtlID mtlidFace = pface->getMatID(); + if (pmtlNode->IsMultiMtl()) + { + if (mtlidFace >= pmtlNode->NumSubMtls()) + { + sprintf(st_szDBG, "ERROR--Bogus sub-material index %d in node %s; highest valid index is %d", + mtlidFace, (char*)strNodeName, pmtlNode->NumSubMtls()-1); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + mtlidFace = 0; + } + Mtl *pmtlFace = pmtlNode->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + + /* + if ((pmtlFace->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlFace->IsMultiMtl())) + { + // it's a sub-sub material. Gads. + pmtlFace = pmtlFace->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + } + */ + + if (!(pmtlFace->ClassID() == Class_ID(DMTL_CLASS_ID, 0))) + { + + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) isn't a 'default/standard' material [%x].", + mtlidFace, (char*)strNodeName, pmtlFace->ClassID()); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + StdMat *pstdmtlFace = (StdMat*)pmtlFace; + ptexmap = pstdmtlFace->GetSubTexmap(ID_DI); + } + else + { + ptexmap = pmtlNode->GetActiveTexmap(); + } + + // ASSERT_AND_ABORT(ptexmap != NULL, "NULL diffuse texture") + if (ptexmap != NULL) + { + if (!(ptexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) + { + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) doesn't have a bitmap as its diffuse texture.", + mtlidFace, (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + BitmapTex *pbmptex = (BitmapTex*)ptexmap; + strcpy(szBitmapName, pbmptex->GetMapName()); + TSTR strPath, strFile; + SplitPathFile(TSTR(szBitmapName), &strPath, &strFile); + strcpy(szBitmapName,strFile); + } + } + + UVVert UVvertex0( 0, 0, 0 ); + UVVert UVvertex1( 1, 0, 0 ); + UVVert UVvertex2( 0, 1, 0 ); + + // All faces must have textures assigned to them + if (ptvface && (pface->flags & HAS_TVERTS)) + { + // Get TVface's 3 indexes into the Mesh's TVertex array(s). + DWORD iTVertex0 = ptvface->getTVert(0); + DWORD iTVertex1 = ptvface->getTVert(1); + DWORD iTVertex2 = ptvface->getTVert(2); + ASSERT_AND_ABORT((int)iTVertex0 < pmesh->getNumTVerts(), "Bogus TVertex 0 index"); + ASSERT_AND_ABORT((int)iTVertex1 < pmesh->getNumTVerts(), "Bogus TVertex 1 index"); + ASSERT_AND_ABORT((int)iTVertex2 < pmesh->getNumTVerts(), "Bogus TVertex 2 index"); + + // Get the 3 TVertex's for this TVFace + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + UVvertex0 = pmesh->getTVert(iTVertex0); + UVvertex1 = pmesh->getTVert(iTVertex1); + UVvertex2 = pmesh->getTVert(iTVertex2); + } + else + { + //sprintf(st_szDBG, "ERROR--Node %s has a textureless face. All faces must have an applied texture.", (char*)strNodeName); + //ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + /* + const char *szExpectedExtension = ".bmp"; + if (stricmp(szBitmapName+strlen(szBitmapName)-strlen(szExpectedExtension), szExpectedExtension) != 0) + { + sprintf(st_szDBG, "Node %s uses %s, which is not a %s file", (char*)strNodeName, szBitmapName, szExpectedExtension); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + */ + + // Determine owning bones for the vertices. + int iNodeV0, iNodeV1, iNodeV2; + // Simple 3dsMax model: the vertices are owned by the object, and hence the node + iNodeV0 = iNode; + iNodeV1 = iNode; + iNodeV2 = iNode; + + // Rotate the face vertices out of object-space, and into world-space space + Point3 v0 = pt3Vertex0 * mat3ObjectTM; + Point3 v1 = pt3Vertex1 * mat3ObjectTM; + Point3 v2 = pt3Vertex2 * mat3ObjectTM; + + + Matrix3 mat3ObjectNTM = mat3ObjectTM; + mat3ObjectNTM.NoScale( ); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus pre normal 0" ); + pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus post normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus pre normal 1" ); + pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus post normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus pre normal 2" ); + pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus post normal 2" ); + + // Finally dump the bitmap name and 3 lines of face info + fprintf(m_pfile, "%s\n", szBitmapName); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV0, v0.x, v0.y, v0.z, + pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z, + UVvertex0.x, UVvertex0.y); + DumpWeights( iVertex0 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV1, v1.x, v1.y, v1.z, + pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z, + UVvertex1.x, UVvertex1.y); + DumpWeights( iVertex1 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV2, v2.x, v2.y, v2.z, + pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z, + UVvertex2.x, UVvertex2.y); + DumpWeights( iVertex2 ); + } + + cleanup( ); + return TREE_CONTINUE; +} + + +#define MAX_BLEND_WEIGHTS 8 + +static struct { + int iNode; + float flWeight; +} aWeights[MAX_BLEND_WEIGHTS+1]; + +int AddWeight( int iCount, int iNode, float flWeight ) +{ + if (flWeight < 0.001) + return iCount; + + for (int i = 0; i < iCount; i++) + { + if (aWeights[i].flWeight < flWeight) + { + for (int j = iCount; j > i; j--) + { + aWeights[j] = aWeights[j-1]; + } + break; + } + } + aWeights[i].iNode = iNode; + aWeights[i].flWeight = flWeight; + + iCount++; + if (iCount > MAX_BLEND_WEIGHTS) + iCount = MAX_BLEND_WEIGHTS; + + return iCount; +} + + +void DumpModelTEP::DumpWeights(int iVertex) +{ + if (m_mcExport) + { + IPhyVertexExport *vtxExport = m_mcExport->GetVertexInterface(iVertex); + + if (vtxExport) + { + if (vtxExport->GetVertexType() & BLENDED_TYPE) + { + IPhyBlendedRigidVertex *pBlend = ((IPhyBlendedRigidVertex *)vtxExport); + int iCount = 0; + + for (int i = 0; i < pBlend->GetNumberNodes(); i++) + { + iCount = AddWeight( iCount, GetIndexOfINode( pBlend->GetNode( i ) ), pBlend->GetWeight( i ) ); + } + + fprintf(m_pfile, " %2d ", iCount ); + for (i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + else + { + INode *Bone = ((IPhyRigidVertex *)vtxExport)->GetNode(); + + fprintf(m_pfile, " 1 %2d 1.000", GetIndexOfINode(Bone) ); + } + m_mcExport->ReleaseVertexInterface(vtxExport); + } + } + else if (m_wa != NULL) + { + int iCount = 0; + + for ( int iBone = 0; iBone < m_wa->nb; iBone++) + { + if (m_wa->w[iVertex * m_wa->nb + iBone] > 0.0) + { + BonesPro_Bone bone; + bone.t = BP_TIME_ATTACHED; + bone.index = iBone; + m_bonesProMod->SetProperty( BP_PROPID_GET_BONE_STAT, &bone ); + if (bone.node != NULL) + { + iCount = AddWeight( iCount, GetIndexOfINode( bone.node ), m_wa->w[iVertex * m_wa->nb + iBone] ); + } + } + } + + fprintf(m_pfile, " %2d ", iCount ); + for (int i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + + fprintf(m_pfile, "\n" ); + fflush( m_pfile ); +} + +void DumpModelTEP::cleanup(void) +{ + if (m_phyMod && m_phyExport) + { + if (m_mcExport) + { + m_phyExport->ReleaseContextInterface(m_mcExport); + m_mcExport = NULL; + } + m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport); + m_phyExport = NULL; + m_phyMod = NULL; + } +} + + +Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace) +{ + // Lookup the appropriate vertex normal, based on smoothing group. + int cNormals = prvertex->rFlags & NORCT_MASK; + + ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) || + (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX"); + + if (cNormals == 1) + return prvertex->rn.getNormal(); + else + { + for (int irn = 0; irn < cNormals; irn++) + if (prvertex->ern[irn].getSmGroup() & smGroupFace) + break; + + if (irn >= cNormals) + { + irn = 0; + // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n"); + } + return prvertex->ern[irn].getNormal(); + } +} + + + + + +//=========================================================== +// Dialog proc for export options +// +static BOOL CALLBACK ExportOptionsDlgProc( + HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static SmdExportClass *pexp; + switch (message) + { + case WM_INITDIALOG: + pexp = (SmdExportClass*) lParam; + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, IDC_CHECK_SKELETAL); + SetFocus(GetDlgItem(hDlg,IDOK)); + return FALSE; + case WM_DESTROY: + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + pexp->m_fReferenceFrame = IsDlgButtonChecked(hDlg, IDC_CHECK_REFFRAME); + EndDialog(hDlg, 1); // 1 indicates "ok to export" + return TRUE; + case IDCANCEL: // 0 indicates "cancel export" + EndDialog(hDlg, 0); + return TRUE; + case IDC_CHECK_SKELETAL: + case IDC_CHECK_REFFRAME: + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, LOWORD(wParam)); + break; + } + } + return FALSE; +} + + + +//======================================================================== +// Utility functions for getting/setting the personal "node index" property. +// NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I +// NOTE: tried using an integer property. +// FURTHER NOTE: those properties seem to change randomly sometimes, so I'm +// implementing my own. + +typedef struct +{ + char szNodeName[SmdExportClass::MAX_NAME_CHARS]; + int iNode; +} NAMEMAP; +const int MAX_NAMEMAP = 512; +static NAMEMAP g_rgnm[MAX_NAMEMAP]; + +int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists) +{ + TSTR strNodeName(pnode->GetName()); + for (int inm = 0; inm < g_inmMac; inm++) + { + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + { + return g_rgnm[inm].iNode; + } + } + + if (fAssertPropExists) + ASSERT_MBOX(FALSE, "No NODEINDEXSTR property"); + return -7777; +} + + +void SetIndexOfINode(INode *pnode, int inode) +{ + TSTR strNodeName(pnode->GetName()); + NAMEMAP *pnm; + for (int inm = 0; inm < g_inmMac; inm++) + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + break; + if (inm < g_inmMac) + pnm = &g_rgnm[inm]; + else + { + ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full"); + pnm = &g_rgnm[g_inmMac++]; + strcpy(pnm->szNodeName, (char*)strNodeName); + } + pnm->iNode = inode; +} + + +//============================================================= +// Returns TRUE if a node should be ignored during tree traversal. +// +BOOL FUndesirableNode(INode *pnode) +{ + // Get Node's underlying object, and object class name + Object *pobj = pnode->GetObjectRef(); + + // Don't care about lights, dummies, and cameras + if (pobj->SuperClassID() == CAMERA_CLASS_ID) + return TRUE; + if (pobj->SuperClassID() == LIGHT_CLASS_ID) + return TRUE; + + return FALSE; +} + + +//============================================================= +// Returns TRUE if a node has been marked as skippable +// +BOOL FNodeMarkedToSkip(INode *pnode) +{ + return (::GetIndexOfINode(pnode) == SmdExportClass::UNDESIRABLE_NODE_MARKER); +} + + +//============================================================= +// Reduces a rotation to within the -2PI..2PI range. +// +static float FlReduceRotation(float fl) +{ + while (fl >= TWOPI) + fl -= TWOPI; + while (fl <= -TWOPI) + fl += TWOPI; + return fl; +} diff --git a/utils/smdlexp/smdlexp.def b/utils/smdlexp/smdlexp.def new file mode 100644 index 0000000..bfb9a88 --- /dev/null +++ b/utils/smdlexp/smdlexp.def @@ -0,0 +1,8 @@ +LIBRARY smdlexp +EXPORTS + LibDescription @1 + LibNumberClasses @2 + LibClassDesc @3 + LibVersion @4 +SECTIONS + .data READ WRITE diff --git a/utils/smdlexp/smdlexp.mak b/utils/smdlexp/smdlexp.mak new file mode 100644 index 0000000..07a171b --- /dev/null +++ b/utils/smdlexp/smdlexp.mak @@ -0,0 +1,325 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +!IF "$(CFG)" == "" +CFG=smdlexp - Win32 Debug +!MESSAGE No configuration specified. Defaulting to smdlexp - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "smdlexp - Win32 Release" && "$(CFG)" !=\ + "smdlexp - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "smdlexp.mak" CFG="smdlexp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "smdlexp - Win32 Release" (based on\ + "Win32 (x86) Dynamic-Link Library") +!MESSAGE "smdlexp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "smdlexp - Win32 Debug" +RSC=rc.exe +MTL=mktyplib.exe +CPP=cl.exe + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + +CLEAN : + -@erase "$(INTDIR)\smdlexp.obj" + -@erase "$(INTDIR)\smdlexp.res" + -@erase "$(OUTDIR)\SMDLEXP.exp" + -@erase "$(OUTDIR)\SMDLEXP.lib" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +CPP_PROJ=/nologo /MT /W3 /GX /O2 /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D\ + "NDEBUG" /D "_WINDOWS" /Fp"$(INTDIR)/smdlexp.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /win32 +MTL_PROJ=/nologo /D "NDEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +RSC_PROJ=/l 0x409 /fo"$(INTDIR)/smdlexp.res" /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/smdlexp.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo /subsystem:windows /dll /machine:I386 /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE" +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo\ + /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)/SMDLEXP.pdb"\ + /machine:I386 /def:".\smdlexp.def" /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE"\ + /implib:"$(OUTDIR)/SMDLEXP.lib" +DEF_FILE= \ + ".\smdlexp.def" +LINK32_OBJS= \ + "$(INTDIR)\smdlexp.obj" \ + "$(INTDIR)\smdlexp.res" \ + "..\..\..\quiver\src\utils\3dsmax\CORE.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\GEOM.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\MESH.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\UTIL.LIB" + +"..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" "$(OUTDIR)\smdlexp.bsc" + +CLEAN : + -@erase "$(INTDIR)\smdlexp.obj" + -@erase "$(INTDIR)\smdlexp.res" + -@erase "$(INTDIR)\smdlexp.sbr" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\smdlexp.bsc" + -@erase "$(OUTDIR)\SMDLEXP.exp" + -@erase "$(OUTDIR)\SMDLEXP.lib" + -@erase "$(OUTDIR)\SMDLEXP.pdb" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.ILK" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /Gm /GX /Zi /Od /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /c +CPP_PROJ=/nologo /MD /W3 /Gm /GX /Zi /Od /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D\ + "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"$(INTDIR)/" /Fp"$(INTDIR)/smdlexp.pch"\ + /YX /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\Debug/ +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /win32 +MTL_PROJ=/nologo /D "_DEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +RSC_PROJ=/l 0x409 /fo"$(INTDIR)/smdlexp.res" /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/smdlexp.bsc" +BSC32_SBRS= \ + "$(INTDIR)\smdlexp.sbr" + +"$(OUTDIR)\smdlexp.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo /subsystem:windows /dll /debug /machine:I386 /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE" +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo\ + /subsystem:windows /dll /incremental:yes /pdb:"$(OUTDIR)/SMDLEXP.pdb" /debug\ + /machine:I386 /def:".\smdlexp.def" /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE"\ + /implib:"$(OUTDIR)/SMDLEXP.lib" +DEF_FILE= \ + ".\smdlexp.def" +LINK32_OBJS= \ + "$(INTDIR)\smdlexp.obj" \ + "$(INTDIR)\smdlexp.res" \ + "..\..\..\quiver\src\utils\3dsmax\CORE.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\GEOM.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\MESH.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\UTIL.LIB" + +"..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +################################################################################ +# Begin Target + +# Name "smdlexp - Win32 Release" +# Name "smdlexp - Win32 Debug" + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.cpp +DEP_CPP_SMDLE=\ + ".\smedefs.h"\ + +NODEP_CPP_SMDLE=\ + ".\ANIMTBL.H"\ + ".\DECOMP.H"\ + ".\istdplug.h"\ + ".\MAX.H"\ + ".\STDMAT.H"\ + + +!IF "$(CFG)" == "smdlexp - Win32 Release" + + +"$(INTDIR)\smdlexp.obj" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + + +"$(INTDIR)\smdlexp.obj" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + +"$(INTDIR)\smdlexp.sbr" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.def + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.rc + +"$(INTDIR)\smdlexp.res" : $(SOURCE) "$(INTDIR)" + $(RSC) $(RSC_PROJ) $(SOURCE) + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\UTIL.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\GEOM.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\MESH.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\CORE.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/utils/smdlexp/smdlexp.rc b/utils/smdlexp/smdlexp.rc new file mode 100644 index 0000000..a723514 --- /dev/null +++ b/utils/smdlexp/smdlexp.rc @@ -0,0 +1,147 @@ +//Microsoft Developer Studio generated resource script. +// +#include "smexprc.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_EXPORTOPTIONS DIALOG DISCARDABLE 0, 0, 186, 42 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "SMD Exporter" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,22,50,14 + CONTROL "Skeletal Animation",IDC_CHECK_SKELETAL,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,15,10,74,10 + CONTROL "Reference Frame",IDC_CHECK_REFFRAME,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,15,24,71,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_EXPORTOPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 36 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "smexprc.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,0,0,1 + PRODUCTVERSION 2,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "Valve LLC\0" + VALUE "FileDescription", "SMD file exporter (3D Studio Max plugin)\0" + VALUE "FileVersion", "2, 0, 0, 1\0" + VALUE "InternalName", "SMDLEXP\0" + VALUE "LegalCopyright", "Copyright � 1998, Valve LLC\0" + VALUE "LegalTrademarks", "The following are registered trademarks of Autodesk, Inc.: 3D Studio MAX. The following are trademarks of Autodesk, Inc.: Kinetix, Kinetix(logo), BIPED, Physique, Character Studio, MAX DWG, DWG Unplugged, Heidi, FLI, FLC, DXF.\0" + VALUE "OriginalFilename", "SMDLEXP.DLE\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Valve LLC SMDLEXP\0" + VALUE "ProductVersion", "2, 0, 0, 1\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/smdlexp/smdlexp.vcproj b/utils/smdlexp/smdlexp.vcproj new file mode 100644 index 0000000..fce2bc7 --- /dev/null +++ b/utils/smdlexp/smdlexp.vcproj @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="smdlexp" + ProjectGUID="{E8699E31-F261-4277-8EBB-3B33F2EFCC89}" + SccProjectName="" + SccLocalPath=""> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="2" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\MAXSDK\INCLUDE" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS" + RuntimeLibrary="2" + UsePrecompiledHeader="2" + PrecompiledHeaderFile=".\Debug/smdlexp.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + BrowseInformation="1" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="maxutil.lib geom.lib mesh.lib core.lib COMCTL32.LIB" + OutputFile="c:\3DSMAX3_1\plugins\SMDLEXP.DLE" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + AdditionalLibraryDirectories="..\..\maxsdk\lib" + ModuleDefinitionFile=".\smdlexp.def" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/SMDLEXP.pdb" + SubSystem="2" + ImportLibrary=".\Debug/SMDLEXP.lib" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="TRUE" + SuppressStartupBanner="TRUE" + TargetEnvironment="1" + TypeLibraryName=".\Debug/smdlexp.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="2" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="\3DSMAX2.5\MAXSDK\INCLUDE,..\..\MAXSDK\INCLUDE" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS" + StringPooling="TRUE" + RuntimeLibrary="0" + EnableFunctionLevelLinking="TRUE" + UsePrecompiledHeader="2" + PrecompiledHeaderFile=".\Release/smdlexp.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="maxutil.lib geom.lib mesh.lib core.lib COMCTL32.LIB" + OutputFile="c:\3DSMAX3_1\plugins\SMDLEXP.DLE" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + AdditionalLibraryDirectories="..\..\maxsdk\lib" + ModuleDefinitionFile=".\smdlexp.def" + ProgramDatabaseFile=".\Release/SMDLEXP.pdb" + SubSystem="2" + ImportLibrary=".\Release/SMDLEXP.lib" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="TRUE" + SuppressStartupBanner="TRUE" + TargetEnvironment="1" + TypeLibraryName=".\Release/smdlexp.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90"> + <File + RelativePath="smdlexp.cpp"> + </File> + <File + RelativePath="smdlexp.def"> + </File> + <File + RelativePath="smdlexp.rc"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;fi;fd"> + <File + RelativePath="smedefs.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe"> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/smdlexp/smedefs.h b/utils/smdlexp/smedefs.h new file mode 100644 index 0000000..4b09d5d --- /dev/null +++ b/utils/smdlexp/smedefs.h @@ -0,0 +1,178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +//=================================================================== +// Useful macros +// +#define CONSTRUCTOR +#define DESTRUCTOR + +#define EXPORT_THIS __declspec(dllexport) + +#define DEFAULT_EXT _T("smd") + +#define FStrEq(sz1, sz2) (strcmp((sz1), (sz2)) == 0) + + +//=================================================================== +// Class that implements the scene-export. +// +class SmdExportClass : public SceneExport +{ + friend BOOL CALLBACK ExportOptionsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + friend class DumpModelTEP; + friend class DumpDeformsTEP; + +public: + CONSTRUCTOR SmdExportClass (void); + DESTRUCTOR ~SmdExportClass (void); + + // Required by classes derived from SceneExport + virtual int ExtCount (void) { return 1; } + virtual const TCHAR* Ext (int i) { return DEFAULT_EXT; } + virtual const TCHAR* LongDesc (void) { return _T("Valve Skeletal Model Exporter for 3D Studio Max"); } + virtual const TCHAR* ShortDesc (void) { return _T("Valve SMD"); } + virtual const TCHAR* AuthorName (void) { return _T("Valve, LLC"); } + virtual const TCHAR* CopyrightMessage(void) { return _T("Copyright (c) 1998, Valve LLC"); } + virtual const TCHAR* OtherMessage1 (void) { return _T(""); } + virtual const TCHAR* OtherMessage2 (void) { return _T(""); } + virtual unsigned int Version (void) { return 201; } + virtual void ShowAbout (HWND hWnd) { return; } + // virtual int DoExport (const TCHAR *name, ExpInterface *ei, Interface *i); + virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE,DWORD options=0); // Export file + + // Integer constants for this class + enum + { + MAX_NAME_CHARS = 70, + UNDESIRABLE_NODE_MARKER = -7777 + }; + + // For keeping info about each (non-ignored) 3dsMax node in the tree + typedef struct + { + char szNodeName[MAX_NAME_CHARS]; // usefull for lookups + Matrix3 mat3NodeTM; // node's transformation matrix (at time zero) + Matrix3 mat3ObjectTM; // object-offset transformation matrix (at time zero) + int imaxnodeParent; // cached index of parent node + float xRotFirstFrame; // 1st frame's X rotation + float yRotFirstFrame; // 1st frame's Y rotation + float zRotFirstFrame; // 1st frame's Z rotation + bool isMirrored; + } MaxNode; + MaxNode *m_rgmaxnode; // array of nodes + long m_imaxnodeMac; // # of nodes + + // Animation metrics (gleaned from 3dsMax and cached for convenience) + Interval m_intervalOfAnimation; + TimeValue m_tvStart; + TimeValue m_tvEnd; + int m_tpf; // ticks-per-frame + +private: + BOOL CollectNodes (ExpInterface *expiface); + BOOL DumpBones (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpRotations (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpModel (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpDeforms (FILE *pFile, ExpInterface *pexpiface); + + // Is this MAX file just the reference frame, or an animation? + // If TRUE, the "bones" and "mesh" files will be created. + // If FALSE, the "rots" file will be created. + BOOL m_fReferenceFrame; +}; + + +//=================================================================== +// Basically just a ClassFactory for communicating with 3DSMAX. +// +class SmdExportClassDesc : public ClassDesc +{ +public: + int IsPublic (void) { return TRUE; } + void * Create (BOOL loading=FALSE) { return new SmdExportClass; } + const TCHAR * ClassName (void) { return _T("SmdExport"); } + SClass_ID SuperClassID (void) { return SCENE_EXPORT_CLASS_ID; } + Class_ID ClassID (void) { return Class_ID(0x774a43fd, 0x794d2210); } + const TCHAR * Category (void) { return _T(""); } +}; + + +//=================================================================== +// Tree Enumeration Callback +// Just counts the nodes in the node tree +// +class CountNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + int m_cNodes; // running count of nodes +}; + + +//=================================================================== +// Tree Enumeration Callback +// Collects the nodes in the tree into the global array +// +class CollectNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the bone offsets to a file. +// +class DumpNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + FILE *m_pfile; // write to this file + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the per-frame bone rotations to a file. +// +class DumpFrameRotationsTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; +}; + +//=================================================================== +// Tree Enumeration Callback +// Dumps the triangle meshes to a file. +// +class DumpModelTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; + IPhyContextExport *m_mcExport; + IPhysiqueExport *m_phyExport; + Modifier *m_phyMod; + Modifier *m_bonesProMod; + BonesPro_WeightArray *m_wa; +private: + Point3 Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace); + void DumpWeights( int iVertex ); +}; + diff --git a/utils/smdlexp/smexprc.h b/utils/smdlexp/smexprc.h new file mode 100644 index 0000000..e6482bf --- /dev/null +++ b/utils/smdlexp/smexprc.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by smdlexp.rc +// +#define IDD_SMDLEXP_UI 101 +#define IDD_EXPORTOPTIONS 101 +#define IDC_CHECK_SKELETAL 1000 +#define IDC_CHECK_DEFORM 1001 +#define IDC_CHECK_REFFRAME 1002 +#define IDC_CHECK_PHYSIQUE 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif |