Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1096 lines
30 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "MAX.H"
  9. #include "DECOMP.H"
  10. #include "STDMAT.H"
  11. #include "ANIMTBL.H"
  12. #include "istdplug.h"
  13. #include "phyexp.h"
  14. #include "BonesPro.h"
  15. #include "smexprc.h"
  16. #include "smedefs.h"
  17. //===================================================================
  18. // Prototype declarations
  19. //
  20. int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE);
  21. void SetIndexOfINode(INode *pnode, int inode);
  22. BOOL FUndesirableNode(INode *pnode);
  23. BOOL FNodeMarkedToSkip(INode *pnode);
  24. float FlReduceRotation(float fl);
  25. //===================================================================
  26. // Global variable definitions
  27. //
  28. // Save for use with dialogs
  29. static HINSTANCE hInstance;
  30. // We just need one of these to hand off to 3DSMAX.
  31. static SmdExportClassDesc SmdExportCD;
  32. // For OutputDebugString and misc sprintf's
  33. static char st_szDBG[300];
  34. // INode mapping table
  35. static int g_inmMac = 0;
  36. //===================================================================
  37. // Utility functions
  38. //
  39. static int AssertFailedFunc(char *sz)
  40. {
  41. MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK);
  42. int Set_Your_Breakpoint_Here = 1;
  43. return 1;
  44. }
  45. #define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz))
  46. //===================================================================
  47. // Required plug-in export functions
  48. //
  49. BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved)
  50. {
  51. static int fFirstTimeHere = TRUE;
  52. if (fFirstTimeHere)
  53. {
  54. fFirstTimeHere = FALSE;
  55. hInstance = hinstDLL;
  56. }
  57. return TRUE;
  58. }
  59. EXPORT_THIS int LibNumberClasses(void)
  60. {
  61. return 1;
  62. }
  63. EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass)
  64. {
  65. switch(iWhichClass)
  66. {
  67. case 0: return &SmdExportCD;
  68. default: return 0;
  69. }
  70. }
  71. EXPORT_THIS const TCHAR *LibDescription()
  72. {
  73. return _T("Valve SMD Plug-in.");
  74. }
  75. EXPORT_THIS ULONG LibVersion()
  76. {
  77. return VERSION_3DSMAX;
  78. }
  79. //=====================================================================
  80. // Methods for SmdExportClass
  81. //
  82. CONSTRUCTOR SmdExportClass::SmdExportClass(void)
  83. {
  84. m_rgmaxnode = NULL;
  85. }
  86. DESTRUCTOR SmdExportClass::~SmdExportClass(void)
  87. {
  88. if (m_rgmaxnode)
  89. delete[] m_rgmaxnode;
  90. }
  91. int SmdExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
  92. {
  93. ExpInterface *pexpiface = ei; // Hungarian
  94. Interface *piface = i; // Hungarian
  95. // Reset the name-map property manager
  96. g_inmMac = 0;
  97. if (!suppressPrompts)
  98. {
  99. // Present the user with the Export Options dialog
  100. if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORTOPTIONS), GetActiveWindow(),
  101. ExportOptionsDlgProc, (LPARAM)this) <= 0)
  102. return 0; // error or cancel
  103. }
  104. else
  105. {
  106. m_fReferenceFrame = 0;
  107. }
  108. // Break up filename, re-assemble longer versions
  109. TSTR strPath, strFile, strExt;
  110. TCHAR szFile[MAX_PATH];
  111. SplitFilename(TSTR(name), &strPath, &strFile, &strExt);
  112. sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT);
  113. /*
  114. if (m_fReferenceFrame)
  115. sprintf(szFile, "%s\\%s_model.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT);
  116. */
  117. FILE *pFile;
  118. if ((pFile = fopen(szFile, "w")) == NULL)
  119. return FALSE/*failure*/;
  120. fprintf( pFile, "version %d\n", 1 );
  121. // Get animation metrics
  122. m_intervalOfAnimation = piface->GetAnimRange();
  123. m_tvStart = m_intervalOfAnimation.Start();
  124. m_tvEnd = m_intervalOfAnimation.End();
  125. m_tpf = ::GetTicksPerFrame();
  126. // Count nodes, label them, collect into array
  127. if (!CollectNodes(pexpiface))
  128. return 0; /*fail*/
  129. // Output nodes
  130. if (!DumpBones(pFile, pexpiface))
  131. {
  132. fclose( pFile );
  133. return 0; /*fail*/
  134. }
  135. // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file
  136. DumpRotations(pFile, pexpiface);
  137. // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file
  138. if (m_fReferenceFrame)
  139. {
  140. DumpModel(pFile, pexpiface);
  141. }
  142. if (!suppressPrompts)
  143. {
  144. // Tell user that exporting is finished (it can take a while with no feedback)
  145. char szExportComplete[300];
  146. sprintf(szExportComplete, "Exported %s.", szFile);
  147. MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK);
  148. }
  149. fclose( pFile );
  150. return 1/*success*/;
  151. }
  152. BOOL SmdExportClass::CollectNodes( ExpInterface *pexpiface)
  153. {
  154. // Count total nodes in the model, so I can alloc array
  155. // Also "brands" each node with node index, or with "skip me" marker.
  156. CountNodesTEP procCountNodes;
  157. procCountNodes.m_cNodes = 0;
  158. (void) pexpiface->theScene->EnumTree(&procCountNodes);
  159. ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!");
  160. // Alloc and fill array
  161. m_imaxnodeMac = procCountNodes.m_cNodes;
  162. m_rgmaxnode = new MaxNode[m_imaxnodeMac];
  163. ASSERT_MBOX(m_rgmaxnode != NULL, "new failed");
  164. CollectNodesTEP procCollectNodes;
  165. procCollectNodes.m_phec = this;
  166. (void) pexpiface->theScene->EnumTree(&procCollectNodes);
  167. return TRUE;
  168. }
  169. BOOL SmdExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface)
  170. {
  171. // Dump bone names
  172. DumpNodesTEP procDumpNodes;
  173. procDumpNodes.m_pfile = pFile;
  174. procDumpNodes.m_phec = this;
  175. fprintf(pFile, "nodes\n" );
  176. (void) pexpiface->theScene->EnumTree(&procDumpNodes);
  177. fprintf(pFile, "end\n" );
  178. return TRUE;
  179. }
  180. BOOL SmdExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface)
  181. {
  182. // Dump bone-rotation info, for each frame
  183. // Also dumps root-node translation info (the model's world-position at each frame)
  184. DumpFrameRotationsTEP procDumpFrameRotations;
  185. procDumpFrameRotations.m_pfile = pFile;
  186. procDumpFrameRotations.m_phec = this;
  187. TimeValue m_tvTill = (m_fReferenceFrame) ? m_tvStart : m_tvEnd;
  188. fprintf(pFile, "skeleton\n" );
  189. for (TimeValue tv = m_tvStart; tv <= m_tvTill; tv += m_tpf)
  190. {
  191. fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() );
  192. procDumpFrameRotations.m_tvToDump = tv;
  193. (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations);
  194. }
  195. fprintf(pFile, "end\n" );
  196. return TRUE;
  197. }
  198. BOOL SmdExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface)
  199. {
  200. // Dump mesh info: vertices, normals, UV texture map coords, bone assignments
  201. DumpModelTEP procDumpModel;
  202. procDumpModel.m_pfile = pFile;
  203. procDumpModel.m_phec = this;
  204. fprintf(pFile, "triangles\n" );
  205. procDumpModel.m_tvToDump = m_tvStart;
  206. (void) pexpiface->theScene->EnumTree(&procDumpModel);
  207. fprintf(pFile, "end\n" );
  208. return TRUE;
  209. }
  210. //=============================================================================
  211. // TREE-ENUMERATION PROCEDURES
  212. //=============================================================================
  213. #define ASSERT_AND_ABORT(f, sz) \
  214. if (!(f)) \
  215. { \
  216. ASSERT_MBOX(FALSE, sz); \
  217. cleanup( ); \
  218. return TREE_ABORT; \
  219. }
  220. //=================================================================
  221. // Methods for CountNodesTEP
  222. //
  223. int CountNodesTEP::callback( INode *node)
  224. {
  225. INode *pnode = node; // Hungarian
  226. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  227. if (::FUndesirableNode(pnode))
  228. {
  229. // Mark as skippable
  230. ::SetIndexOfINode(pnode, SmdExportClass::UNDESIRABLE_NODE_MARKER);
  231. return TREE_CONTINUE;
  232. }
  233. // Establish "node index"--just ascending ints
  234. ::SetIndexOfINode(pnode, m_cNodes);
  235. m_cNodes++;
  236. return TREE_CONTINUE;
  237. }
  238. //=================================================================
  239. // Methods for CollectNodesTEP
  240. //
  241. int CollectNodesTEP::callback(INode *node)
  242. {
  243. INode *pnode = node; // Hungarian
  244. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  245. if (::FNodeMarkedToSkip(pnode))
  246. return TREE_CONTINUE;
  247. // Get pre-stored "index"
  248. int iNode = ::GetIndexOfINode(pnode);
  249. ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode");
  250. // Get name, store name in array
  251. TSTR strNodeName(pnode->GetName());
  252. strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName);
  253. // Get Node's time-zero Transformation Matrices
  254. m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/);
  255. m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/);
  256. // I'll calculate this later
  257. m_phec->m_rgmaxnode[iNode].imaxnodeParent = SmdExportClass::UNDESIRABLE_NODE_MARKER;
  258. return TREE_CONTINUE;
  259. }
  260. //=================================================================
  261. // Methods for DumpNodesTEP
  262. //
  263. int DumpNodesTEP::callback(INode *pnode)
  264. {
  265. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  266. if (::FNodeMarkedToSkip(pnode))
  267. return TREE_CONTINUE;
  268. // Get node's parent
  269. INode *pnodeParent;
  270. pnodeParent = pnode->GetParentNode();
  271. // The model's root is a child of the real "scene root"
  272. TSTR strNodeName(pnode->GetName());
  273. BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
  274. int iNode = ::GetIndexOfINode(pnode);
  275. int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/);
  276. // Convenient time to cache this
  277. m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent;
  278. // Root node has no parent, thus no translation
  279. if (fNodeIsRoot)
  280. iNodeParent = -1;
  281. // check to see if the matrix isn't right handed
  282. 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;
  283. // Dump node description
  284. fprintf(m_pfile, "%3d \"%s\" %3d\n",
  285. iNode,
  286. strNodeName,
  287. iNodeParent );
  288. return TREE_CONTINUE;
  289. }
  290. //=================================================================
  291. // Methods for DumpFrameRotationsTEP
  292. //
  293. int DumpFrameRotationsTEP::callback(INode *pnode)
  294. {
  295. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  296. if (::FNodeMarkedToSkip(pnode))
  297. return TREE_CONTINUE;
  298. int iNode = ::GetIndexOfINode(pnode);
  299. TSTR strNodeName(pnode->GetName());
  300. // The model's root is a child of the real "scene root"
  301. INode *pnodeParent = pnode->GetParentNode();
  302. BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
  303. // Get Node's "Local" Transformation Matrix
  304. Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump);
  305. Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump);
  306. mat3NodeTM.NoScale(); // Clear these out because they apparently
  307. mat3ParentTM.NoScale(); // screw up the following calculation.
  308. Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM);
  309. Point3 rowTrans = mat3NodeLocalTM.GetTrans();
  310. // check to see if the parent bone was mirrored. If so, mirror invert this bones position
  311. if (m_phec->m_rgmaxnode[iNode].imaxnodeParent >= 0 && m_phec->m_rgmaxnode[m_phec->m_rgmaxnode[iNode].imaxnodeParent].isMirrored)
  312. {
  313. rowTrans = rowTrans * -1.0f;
  314. }
  315. // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler)
  316. // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z.
  317. AffineParts affparts;
  318. float rgflXYZRotations[3];
  319. decomp_affine(mat3NodeLocalTM, &affparts);
  320. QuatToEuler(affparts.q, rgflXYZRotations);
  321. float xRot = rgflXYZRotations[0]; // in radians
  322. float yRot = rgflXYZRotations[1]; // in radians
  323. float zRot = rgflXYZRotations[2]; // in radians
  324. // Get rotations in the -2pi...2pi range
  325. xRot = ::FlReduceRotation(xRot);
  326. yRot = ::FlReduceRotation(yRot);
  327. zRot = ::FlReduceRotation(zRot);
  328. // Print rotations
  329. fprintf(m_pfile, "%3d %f %f %f %f %f %f\n",
  330. // Node:%-15s Rotation (x,y,z)\n",
  331. iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot);
  332. return TREE_CONTINUE;
  333. }
  334. //=================================================================
  335. // Methods for DumpModelTEP
  336. //
  337. Modifier *FindPhysiqueModifier (INode *nodePtr)
  338. {
  339. // Get object from node. Abort if no object.
  340. Object *ObjectPtr = nodePtr->GetObjectRef();
  341. if (!ObjectPtr) return NULL;
  342. // Is derived object ?
  343. if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID)
  344. {
  345. // Yes -> Cast.
  346. IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr);
  347. // Iterate over all entries of the modifier stack.
  348. int ModStackIndex = 0;
  349. while (ModStackIndex < DerivedObjectPtr->NumModifiers())
  350. {
  351. // Get current modifier.
  352. Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
  353. // Is this Physique ?
  354. if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) )
  355. {
  356. // Yes -> Exit.
  357. return ModifierPtr;
  358. }
  359. // Next modifier stack entry.
  360. ModStackIndex++;
  361. }
  362. }
  363. // Not found.
  364. return NULL;
  365. }
  366. Modifier *FindBonesProModifier (INode *nodePtr)
  367. {
  368. // Get object from node. Abort if no object.
  369. Object *ObjectPtr = nodePtr->GetObjectRef();
  370. if (!ObjectPtr) return NULL;
  371. // Is derived object ?
  372. if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID)
  373. {
  374. // Yes -> Cast.
  375. IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr);
  376. // Iterate over all entries of the modifier stack.
  377. int ModStackIndex = 0;
  378. while (ModStackIndex < DerivedObjectPtr->NumModifiers())
  379. {
  380. // Get current modifier.
  381. Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
  382. // Is this Bones Pro OSM?
  383. if (ModifierPtr->ClassID() == BP_CLASS_ID_OSM )
  384. {
  385. // Yes -> Exit.
  386. return ModifierPtr;
  387. }
  388. // Is this Bones Pro WSM?
  389. if (ModifierPtr->ClassID() == BP_CLASS_ID_WSM )
  390. {
  391. // Yes -> Exit.
  392. return ModifierPtr;
  393. }
  394. // Next modifier stack entry.
  395. ModStackIndex++;
  396. }
  397. }
  398. // Not found.
  399. return NULL;
  400. }
  401. // #define DEBUG_MESH_DUMP
  402. //=================================================================
  403. // Methods for DumpModelTEP
  404. //
  405. int DumpModelTEP::callback(INode *pnode)
  406. {
  407. Object* pobj;
  408. int fHasMat = TRUE;
  409. // clear physique export parameters
  410. m_mcExport = NULL;
  411. m_phyExport = NULL;
  412. m_phyMod = NULL;
  413. m_bonesProMod = NULL;
  414. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  415. if (::FNodeMarkedToSkip(pnode))
  416. return TREE_CONTINUE;
  417. // Actually, if it's not selected, skip it!
  418. //if (!pnode->Selected())
  419. // return TRUE;
  420. int iNode = ::GetIndexOfINode(pnode);
  421. TSTR strNodeName(pnode->GetName());
  422. // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly.
  423. if (FStrEq((char*)strNodeName, "Bip01 Footsteps"))
  424. return TREE_CONTINUE;
  425. // Helper nodes don't have meshes
  426. pobj = pnode->GetObjectRef();
  427. if (pobj->SuperClassID() == HELPER_CLASS_ID)
  428. return TREE_CONTINUE;
  429. // The model's root is a child of the real "scene root"
  430. INode *pnodeParent = pnode->GetParentNode();
  431. BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
  432. // Get node's material: should be a multi/sub (if it has a material at all)
  433. Mtl *pmtlNode = pnode->GetMtl();
  434. if (pmtlNode == NULL)
  435. {
  436. return TREE_CONTINUE;
  437. fHasMat = FALSE;
  438. }
  439. else if (!(pmtlNode->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlNode->IsMultiMtl()))
  440. {
  441. // sprintf(st_szDBG, "ERROR--Material on node %s isn't a Multi/Sub-Object", (char*)strNodeName);
  442. // ASSERT_AND_ABORT(FALSE, st_szDBG);
  443. // fHasMat = FALSE;
  444. }
  445. // Get Node's object, convert to a triangle-mesh object, so I can access the Faces
  446. ObjectState os = pnode->EvalWorldState(m_tvToDump);
  447. pobj = os.obj;
  448. TriObject *ptriobj;
  449. BOOL fConvertedToTriObject =
  450. pobj->CanConvertToType(triObjectClassID) &&
  451. (ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID)) != NULL;
  452. if (!fConvertedToTriObject)
  453. return TREE_CONTINUE;
  454. Mesh *pmesh = &ptriobj->mesh;
  455. // Shouldn't have gotten this far if it's a helper object
  456. if (pobj->SuperClassID() == HELPER_CLASS_ID)
  457. {
  458. sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName);
  459. ASSERT_AND_ABORT(FALSE, st_szDBG);
  460. }
  461. // Ensure that the vertex normals are up-to-date
  462. pmesh->buildNormals();
  463. // We want the vertex coordinates in World-space, not object-space
  464. Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump);
  465. // initialize physique export parameters
  466. m_phyMod = FindPhysiqueModifier(pnode);
  467. if (m_phyMod)
  468. {
  469. // Physique Modifier exists for given Node
  470. m_phyExport = (IPhysiqueExport *)m_phyMod->GetInterface(I_PHYINTERFACE);
  471. if (m_phyExport)
  472. {
  473. // create a ModContext Export Interface for the specific node of the Physique Modifier
  474. m_mcExport = (IPhyContextExport *)m_phyExport->GetContextInterface(pnode);
  475. if (m_mcExport)
  476. {
  477. // convert all vertices to Rigid
  478. m_mcExport->ConvertToRigid(TRUE);
  479. }
  480. }
  481. }
  482. // initialize bones pro export parameters
  483. m_wa = NULL;
  484. m_bonesProMod = FindBonesProModifier(pnode);
  485. if (m_bonesProMod)
  486. {
  487. m_bonesProMod->SetProperty( BP_PROPID_GET_WEIGHTS, &m_wa );
  488. }
  489. // Dump the triangle face info
  490. int cFaces = pmesh->getNumFaces();
  491. for (int iFace = 0; iFace < cFaces; iFace++)
  492. {
  493. Face* pface = &pmesh->faces[iFace];
  494. TVFace* ptvface = (pmesh->tvFace) ? &pmesh->tvFace[iFace] : NULL;
  495. DWORD smGroupFace = pface->getSmGroup();
  496. // Get face's 3 indexes into the Mesh's vertex array(s).
  497. DWORD iVertex0 = pface->getVert(0);
  498. DWORD iVertex1 = pface->getVert(1);
  499. DWORD iVertex2 = pface->getVert(2);
  500. ASSERT_AND_ABORT((int)iVertex0 < pmesh->getNumVerts(), "Bogus Vertex 0 index");
  501. ASSERT_AND_ABORT((int)iVertex1 < pmesh->getNumVerts(), "Bogus Vertex 1 index");
  502. ASSERT_AND_ABORT((int)iVertex2 < pmesh->getNumVerts(), "Bogus Vertex 2 index");
  503. // Get the 3 Vertex's for this face
  504. Point3 pt3Vertex0 = pmesh->getVert(iVertex0);
  505. Point3 pt3Vertex1 = pmesh->getVert(iVertex1);
  506. Point3 pt3Vertex2 = pmesh->getVert(iVertex2);
  507. // Get the 3 RVertex's for this face
  508. // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug
  509. RVertex *prvertex0 = pmesh->getRVertPtr(iVertex0);
  510. RVertex *prvertex1 = pmesh->getRVertPtr(iVertex1);
  511. RVertex *prvertex2 = pmesh->getRVertPtr(iVertex2);
  512. // Find appropriate normals for each RVertex
  513. // A vertex can be part of multiple faces, so the "smoothing group"
  514. // is used to locate the normal for this face's use of the vertex.
  515. Point3 pt3Vertex0Normal;
  516. Point3 pt3Vertex1Normal;
  517. Point3 pt3Vertex2Normal;
  518. if (smGroupFace)
  519. {
  520. pt3Vertex0Normal = Pt3GetRVertexNormal(prvertex0, smGroupFace);
  521. pt3Vertex1Normal = Pt3GetRVertexNormal(prvertex1, smGroupFace);
  522. pt3Vertex2Normal = Pt3GetRVertexNormal(prvertex2, smGroupFace);
  523. }
  524. else
  525. {
  526. pt3Vertex0Normal = pmesh->getFaceNormal( iFace );
  527. pt3Vertex1Normal = pmesh->getFaceNormal( iFace );
  528. pt3Vertex2Normal = pmesh->getFaceNormal( iFace );
  529. }
  530. ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" );
  531. ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" );
  532. ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" );
  533. // Get Face's sub-material from node's material, to get the bitmap name.
  534. // And no, there isn't a simpler way to get the bitmap name, you have to
  535. // dig down through all these levels.
  536. TCHAR szBitmapName[256] = "null.bmp";
  537. if (fHasMat)
  538. {
  539. Texmap *ptexmap = NULL;
  540. MtlID mtlidFace = pface->getMatID();
  541. if (pmtlNode->IsMultiMtl())
  542. {
  543. if (mtlidFace >= pmtlNode->NumSubMtls())
  544. {
  545. sprintf(st_szDBG, "ERROR--Bogus sub-material index %d in node %s; highest valid index is %d",
  546. mtlidFace, (char*)strNodeName, pmtlNode->NumSubMtls()-1);
  547. // ASSERT_AND_ABORT(FALSE, st_szDBG);
  548. mtlidFace = 0;
  549. }
  550. Mtl *pmtlFace = pmtlNode->GetSubMtl(mtlidFace);
  551. ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned");
  552. /*
  553. if ((pmtlFace->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlFace->IsMultiMtl()))
  554. {
  555. // it's a sub-sub material. Gads.
  556. pmtlFace = pmtlFace->GetSubMtl(mtlidFace);
  557. ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned");
  558. }
  559. */
  560. if (!(pmtlFace->ClassID() == Class_ID(DMTL_CLASS_ID, 0)))
  561. {
  562. sprintf(st_szDBG,
  563. "ERROR--Sub-material with index %d (used in node %s) isn't a 'default/standard' material [%x].",
  564. mtlidFace, (char*)strNodeName, pmtlFace->ClassID());
  565. ASSERT_AND_ABORT(FALSE, st_szDBG);
  566. }
  567. StdMat *pstdmtlFace = (StdMat*)pmtlFace;
  568. ptexmap = pstdmtlFace->GetSubTexmap(ID_DI);
  569. }
  570. else
  571. {
  572. ptexmap = pmtlNode->GetActiveTexmap();
  573. }
  574. // ASSERT_AND_ABORT(ptexmap != NULL, "NULL diffuse texture")
  575. if (ptexmap != NULL)
  576. {
  577. if (!(ptexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)))
  578. {
  579. sprintf(st_szDBG,
  580. "ERROR--Sub-material with index %d (used in node %s) doesn't have a bitmap as its diffuse texture.",
  581. mtlidFace, (char*)strNodeName);
  582. ASSERT_AND_ABORT(FALSE, st_szDBG);
  583. }
  584. BitmapTex *pbmptex = (BitmapTex*)ptexmap;
  585. strcpy(szBitmapName, pbmptex->GetMapName());
  586. TSTR strPath, strFile;
  587. SplitPathFile(TSTR(szBitmapName), &strPath, &strFile);
  588. strcpy(szBitmapName,strFile);
  589. }
  590. }
  591. UVVert UVvertex0( 0, 0, 0 );
  592. UVVert UVvertex1( 1, 0, 0 );
  593. UVVert UVvertex2( 0, 1, 0 );
  594. // All faces must have textures assigned to them
  595. if (ptvface && (pface->flags & HAS_TVERTS))
  596. {
  597. // Get TVface's 3 indexes into the Mesh's TVertex array(s).
  598. DWORD iTVertex0 = ptvface->getTVert(0);
  599. DWORD iTVertex1 = ptvface->getTVert(1);
  600. DWORD iTVertex2 = ptvface->getTVert(2);
  601. ASSERT_AND_ABORT((int)iTVertex0 < pmesh->getNumTVerts(), "Bogus TVertex 0 index");
  602. ASSERT_AND_ABORT((int)iTVertex1 < pmesh->getNumTVerts(), "Bogus TVertex 1 index");
  603. ASSERT_AND_ABORT((int)iTVertex2 < pmesh->getNumTVerts(), "Bogus TVertex 2 index");
  604. // Get the 3 TVertex's for this TVFace
  605. // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug
  606. UVvertex0 = pmesh->getTVert(iTVertex0);
  607. UVvertex1 = pmesh->getTVert(iTVertex1);
  608. UVvertex2 = pmesh->getTVert(iTVertex2);
  609. }
  610. else
  611. {
  612. //sprintf(st_szDBG, "ERROR--Node %s has a textureless face. All faces must have an applied texture.", (char*)strNodeName);
  613. //ASSERT_AND_ABORT(FALSE, st_szDBG);
  614. }
  615. /*
  616. const char *szExpectedExtension = ".bmp";
  617. if (stricmp(szBitmapName+strlen(szBitmapName)-strlen(szExpectedExtension), szExpectedExtension) != 0)
  618. {
  619. sprintf(st_szDBG, "Node %s uses %s, which is not a %s file", (char*)strNodeName, szBitmapName, szExpectedExtension);
  620. ASSERT_AND_ABORT(FALSE, st_szDBG);
  621. }
  622. */
  623. // Determine owning bones for the vertices.
  624. int iNodeV0, iNodeV1, iNodeV2;
  625. // Simple 3dsMax model: the vertices are owned by the object, and hence the node
  626. iNodeV0 = iNode;
  627. iNodeV1 = iNode;
  628. iNodeV2 = iNode;
  629. // Rotate the face vertices out of object-space, and into world-space space
  630. Point3 v0 = pt3Vertex0 * mat3ObjectTM;
  631. Point3 v1 = pt3Vertex1 * mat3ObjectTM;
  632. Point3 v2 = pt3Vertex2 * mat3ObjectTM;
  633. Matrix3 mat3ObjectNTM = mat3ObjectTM;
  634. mat3ObjectNTM.NoScale( );
  635. ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus pre normal 0" );
  636. pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal);
  637. ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus post normal 0" );
  638. ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus pre normal 1" );
  639. pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal);
  640. ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus post normal 1" );
  641. ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus pre normal 2" );
  642. pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal);
  643. ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus post normal 2" );
  644. // Finally dump the bitmap name and 3 lines of face info
  645. fprintf(m_pfile, "%s\n", szBitmapName);
  646. fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f",
  647. iNodeV0, v0.x, v0.y, v0.z,
  648. pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z,
  649. UVvertex0.x, UVvertex0.y);
  650. DumpWeights( iVertex0 );
  651. fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f",
  652. iNodeV1, v1.x, v1.y, v1.z,
  653. pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z,
  654. UVvertex1.x, UVvertex1.y);
  655. DumpWeights( iVertex1 );
  656. fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f",
  657. iNodeV2, v2.x, v2.y, v2.z,
  658. pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z,
  659. UVvertex2.x, UVvertex2.y);
  660. DumpWeights( iVertex2 );
  661. }
  662. cleanup( );
  663. return TREE_CONTINUE;
  664. }
  665. #define MAX_BLEND_WEIGHTS 8
  666. static struct {
  667. int iNode;
  668. float flWeight;
  669. } aWeights[MAX_BLEND_WEIGHTS+1];
  670. int AddWeight( int iCount, int iNode, float flWeight )
  671. {
  672. if (flWeight < 0.001)
  673. return iCount;
  674. for (int i = 0; i < iCount; i++)
  675. {
  676. if (aWeights[i].flWeight < flWeight)
  677. {
  678. for (int j = iCount; j > i; j--)
  679. {
  680. aWeights[j] = aWeights[j-1];
  681. }
  682. break;
  683. }
  684. }
  685. aWeights[i].iNode = iNode;
  686. aWeights[i].flWeight = flWeight;
  687. iCount++;
  688. if (iCount > MAX_BLEND_WEIGHTS)
  689. iCount = MAX_BLEND_WEIGHTS;
  690. return iCount;
  691. }
  692. void DumpModelTEP::DumpWeights(int iVertex)
  693. {
  694. if (m_mcExport)
  695. {
  696. IPhyVertexExport *vtxExport = m_mcExport->GetVertexInterface(iVertex);
  697. if (vtxExport)
  698. {
  699. if (vtxExport->GetVertexType() & BLENDED_TYPE)
  700. {
  701. IPhyBlendedRigidVertex *pBlend = ((IPhyBlendedRigidVertex *)vtxExport);
  702. int iCount = 0;
  703. for (int i = 0; i < pBlend->GetNumberNodes(); i++)
  704. {
  705. iCount = AddWeight( iCount, GetIndexOfINode( pBlend->GetNode( i ) ), pBlend->GetWeight( i ) );
  706. }
  707. fprintf(m_pfile, " %2d ", iCount );
  708. for (i = 0; i < iCount; i++)
  709. {
  710. fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight );
  711. }
  712. }
  713. else
  714. {
  715. INode *Bone = ((IPhyRigidVertex *)vtxExport)->GetNode();
  716. fprintf(m_pfile, " 1 %2d 1.000", GetIndexOfINode(Bone) );
  717. }
  718. m_mcExport->ReleaseVertexInterface(vtxExport);
  719. }
  720. }
  721. else if (m_wa != NULL)
  722. {
  723. int iCount = 0;
  724. for ( int iBone = 0; iBone < m_wa->nb; iBone++)
  725. {
  726. if (m_wa->w[iVertex * m_wa->nb + iBone] > 0.0)
  727. {
  728. BonesPro_Bone bone;
  729. bone.t = BP_TIME_ATTACHED;
  730. bone.index = iBone;
  731. m_bonesProMod->SetProperty( BP_PROPID_GET_BONE_STAT, &bone );
  732. if (bone.node != NULL)
  733. {
  734. iCount = AddWeight( iCount, GetIndexOfINode( bone.node ), m_wa->w[iVertex * m_wa->nb + iBone] );
  735. }
  736. }
  737. }
  738. fprintf(m_pfile, " %2d ", iCount );
  739. for (int i = 0; i < iCount; i++)
  740. {
  741. fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight );
  742. }
  743. }
  744. fprintf(m_pfile, "\n" );
  745. fflush( m_pfile );
  746. }
  747. void DumpModelTEP::cleanup(void)
  748. {
  749. if (m_phyMod && m_phyExport)
  750. {
  751. if (m_mcExport)
  752. {
  753. m_phyExport->ReleaseContextInterface(m_mcExport);
  754. m_mcExport = NULL;
  755. }
  756. m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport);
  757. m_phyExport = NULL;
  758. m_phyMod = NULL;
  759. }
  760. }
  761. Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace)
  762. {
  763. // Lookup the appropriate vertex normal, based on smoothing group.
  764. int cNormals = prvertex->rFlags & NORCT_MASK;
  765. ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) ||
  766. (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX");
  767. if (cNormals == 1)
  768. return prvertex->rn.getNormal();
  769. else
  770. {
  771. for (int irn = 0; irn < cNormals; irn++)
  772. if (prvertex->ern[irn].getSmGroup() & smGroupFace)
  773. break;
  774. if (irn >= cNormals)
  775. {
  776. irn = 0;
  777. // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n");
  778. }
  779. return prvertex->ern[irn].getNormal();
  780. }
  781. }
  782. //===========================================================
  783. // Dialog proc for export options
  784. //
  785. static BOOL CALLBACK ExportOptionsDlgProc(
  786. HWND hDlg,
  787. UINT message,
  788. WPARAM wParam,
  789. LPARAM lParam)
  790. {
  791. static SmdExportClass *pexp;
  792. switch (message)
  793. {
  794. case WM_INITDIALOG:
  795. pexp = (SmdExportClass*) lParam;
  796. CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, IDC_CHECK_SKELETAL);
  797. SetFocus(GetDlgItem(hDlg,IDOK));
  798. return FALSE;
  799. case WM_DESTROY:
  800. return FALSE;
  801. case WM_COMMAND:
  802. switch (LOWORD(wParam))
  803. {
  804. case IDOK:
  805. pexp->m_fReferenceFrame = IsDlgButtonChecked(hDlg, IDC_CHECK_REFFRAME);
  806. EndDialog(hDlg, 1); // 1 indicates "ok to export"
  807. return TRUE;
  808. case IDCANCEL: // 0 indicates "cancel export"
  809. EndDialog(hDlg, 0);
  810. return TRUE;
  811. case IDC_CHECK_SKELETAL:
  812. case IDC_CHECK_REFFRAME:
  813. CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, LOWORD(wParam));
  814. break;
  815. }
  816. }
  817. return FALSE;
  818. }
  819. //========================================================================
  820. // Utility functions for getting/setting the personal "node index" property.
  821. // NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I
  822. // NOTE: tried using an integer property.
  823. // FURTHER NOTE: those properties seem to change randomly sometimes, so I'm
  824. // implementing my own.
  825. typedef struct
  826. {
  827. char szNodeName[SmdExportClass::MAX_NAME_CHARS];
  828. int iNode;
  829. } NAMEMAP;
  830. const int MAX_NAMEMAP = 512;
  831. static NAMEMAP g_rgnm[MAX_NAMEMAP];
  832. int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists)
  833. {
  834. TSTR strNodeName(pnode->GetName());
  835. for (int inm = 0; inm < g_inmMac; inm++)
  836. {
  837. if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName))
  838. {
  839. return g_rgnm[inm].iNode;
  840. }
  841. }
  842. if (fAssertPropExists)
  843. ASSERT_MBOX(FALSE, "No NODEINDEXSTR property");
  844. return -7777;
  845. }
  846. void SetIndexOfINode(INode *pnode, int inode)
  847. {
  848. TSTR strNodeName(pnode->GetName());
  849. NAMEMAP *pnm;
  850. for (int inm = 0; inm < g_inmMac; inm++)
  851. if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName))
  852. break;
  853. if (inm < g_inmMac)
  854. pnm = &g_rgnm[inm];
  855. else
  856. {
  857. ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full");
  858. pnm = &g_rgnm[g_inmMac++];
  859. strcpy(pnm->szNodeName, (char*)strNodeName);
  860. }
  861. pnm->iNode = inode;
  862. }
  863. //=============================================================
  864. // Returns TRUE if a node should be ignored during tree traversal.
  865. //
  866. BOOL FUndesirableNode(INode *pnode)
  867. {
  868. // Get Node's underlying object, and object class name
  869. Object *pobj = pnode->GetObjectRef();
  870. // Don't care about lights, dummies, and cameras
  871. if (pobj->SuperClassID() == CAMERA_CLASS_ID)
  872. return TRUE;
  873. if (pobj->SuperClassID() == LIGHT_CLASS_ID)
  874. return TRUE;
  875. return FALSE;
  876. }
  877. //=============================================================
  878. // Returns TRUE if a node has been marked as skippable
  879. //
  880. BOOL FNodeMarkedToSkip(INode *pnode)
  881. {
  882. return (::GetIndexOfINode(pnode) == SmdExportClass::UNDESIRABLE_NODE_MARKER);
  883. }
  884. //=============================================================
  885. // Reduces a rotation to within the -2PI..2PI range.
  886. //
  887. static float FlReduceRotation(float fl)
  888. {
  889. while (fl >= TWOPI)
  890. fl -= TWOPI;
  891. while (fl <= -TWOPI)
  892. fl += TWOPI;
  893. return fl;
  894. }