summaryrefslogtreecommitdiff
path: root/utils/smdlexp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/smdlexp')
-rw-r--r--utils/smdlexp/smdlexp.cpp1096
-rw-r--r--utils/smdlexp/smdlexp.def8
-rw-r--r--utils/smdlexp/smdlexp.mak325
-rw-r--r--utils/smdlexp/smdlexp.rc147
-rw-r--r--utils/smdlexp/smdlexp.vcproj179
-rw-r--r--utils/smdlexp/smedefs.h178
-rw-r--r--utils/smdlexp/smexprc.h28
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