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.

762 lines
19 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 "vtaexprc.h"
  15. #include "vtadefs.h"
  16. //===================================================================
  17. // Prototype declarations
  18. //
  19. int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE);
  20. void SetIndexOfINode(INode *pnode, int inode);
  21. BOOL FUndesirableNode(INode *pnode);
  22. BOOL FNodeMarkedToSkip(INode *pnode);
  23. float FlReduceRotation(float fl);
  24. //===================================================================
  25. // Global variable definitions
  26. //
  27. // Save for use with dialogs
  28. static HINSTANCE hInstance;
  29. // We just need one of these to hand off to 3DSMAX.
  30. static VtaExportClassDesc VtaExportCD;
  31. // For OutputDebugString and misc sprintf's
  32. static char st_szDBG[300];
  33. // INode mapping table
  34. static int g_inmMac = 0;
  35. //===================================================================
  36. // Utility functions
  37. //
  38. static int AssertFailedFunc(char *sz)
  39. {
  40. MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK);
  41. int Set_Your_Breakpoint_Here = 1;
  42. return 1;
  43. }
  44. #define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz))
  45. //===================================================================
  46. // Required plug-in export functions
  47. //
  48. BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved)
  49. {
  50. static int fFirstTimeHere = TRUE;
  51. if (fFirstTimeHere)
  52. {
  53. fFirstTimeHere = FALSE;
  54. hInstance = hinstDLL;
  55. }
  56. return TRUE;
  57. }
  58. EXPORT_THIS int LibNumberClasses(void)
  59. {
  60. return 1;
  61. }
  62. EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass)
  63. {
  64. switch(iWhichClass)
  65. {
  66. case 0: return &VtaExportCD;
  67. default: return 0;
  68. }
  69. }
  70. EXPORT_THIS const TCHAR *LibDescription()
  71. {
  72. return _T("Valve VTA Plug-in.");
  73. }
  74. EXPORT_THIS ULONG LibVersion()
  75. {
  76. return VERSION_3DSMAX;
  77. }
  78. //=====================================================================
  79. // Methods for VtaExportClass
  80. //
  81. CONSTRUCTOR VtaExportClass::VtaExportClass(void)
  82. {
  83. m_rgmaxnode = NULL;
  84. }
  85. DESTRUCTOR VtaExportClass::~VtaExportClass(void)
  86. {
  87. if (m_rgmaxnode)
  88. delete[] m_rgmaxnode;
  89. }
  90. int VtaExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
  91. {
  92. ExpInterface *pexpiface = ei; // Hungarian
  93. Interface *piface = i; // Hungarian
  94. // Reset the name-map property manager
  95. g_inmMac = 0;
  96. // Break up filename, re-assemble longer versions
  97. TSTR strPath, strFile, strExt;
  98. TCHAR szFile[MAX_PATH];
  99. SplitFilename(TSTR(name), &strPath, &strFile, &strExt);
  100. sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT);
  101. FILE *pFile;
  102. if ((pFile = fopen(szFile, "w")) == NULL)
  103. return FALSE/*failure*/;
  104. fprintf( pFile, "version %d\n", 1 );
  105. // Get animation metrics
  106. m_intervalOfAnimation = piface->GetAnimRange();
  107. m_tvStart = m_intervalOfAnimation.Start();
  108. m_tvEnd = m_intervalOfAnimation.End();
  109. m_tpf = ::GetTicksPerFrame();
  110. // Count nodes, label them, collect into array
  111. if (!CollectNodes(pexpiface))
  112. return 0; /*fail*/
  113. // Output nodes
  114. if (!DumpBones(pFile, pexpiface))
  115. {
  116. fclose( pFile );
  117. return 0; /*fail*/
  118. }
  119. // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file
  120. DumpRotations(pFile, pexpiface);
  121. // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file
  122. DumpModel(pFile, pexpiface);
  123. // Tell user that exporting is finished (it can take a while with no feedback)
  124. char szExportComplete[300];
  125. sprintf(szExportComplete, "Exported %s.", szFile);
  126. MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK);
  127. fclose( pFile );
  128. return 1/*success*/;
  129. }
  130. BOOL VtaExportClass::CollectNodes( ExpInterface *pexpiface)
  131. {
  132. // Count total nodes in the model, so I can alloc array
  133. // Also "brands" each node with node index, or with "skip me" marker.
  134. CountNodesTEP procCountNodes;
  135. procCountNodes.m_cNodes = 0;
  136. (void) pexpiface->theScene->EnumTree(&procCountNodes);
  137. ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!");
  138. // Alloc and fill array
  139. m_imaxnodeMac = procCountNodes.m_cNodes;
  140. m_rgmaxnode = new MaxNode[m_imaxnodeMac];
  141. ASSERT_MBOX(m_rgmaxnode != NULL, "new failed");
  142. CollectNodesTEP procCollectNodes;
  143. procCollectNodes.m_phec = this;
  144. (void) pexpiface->theScene->EnumTree(&procCollectNodes);
  145. return TRUE;
  146. }
  147. BOOL VtaExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface)
  148. {
  149. // Dump bone names
  150. DumpNodesTEP procDumpNodes;
  151. procDumpNodes.m_pfile = pFile;
  152. procDumpNodes.m_phec = this;
  153. fprintf(pFile, "nodes\n" );
  154. (void) pexpiface->theScene->EnumTree(&procDumpNodes);
  155. fprintf(pFile, "end\n" );
  156. return TRUE;
  157. }
  158. BOOL VtaExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface)
  159. {
  160. // Dump bone-rotation info, for each frame
  161. // Also dumps root-node translation info (the model's world-position at each frame)
  162. DumpFrameRotationsTEP procDumpFrameRotations;
  163. procDumpFrameRotations.m_pfile = pFile;
  164. procDumpFrameRotations.m_phec = this;
  165. fprintf(pFile, "skeleton\n" );
  166. for (TimeValue tv = m_tvStart; tv <= m_tvEnd; tv += m_tpf)
  167. {
  168. fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() );
  169. procDumpFrameRotations.m_tvToDump = tv;
  170. (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations);
  171. }
  172. fprintf(pFile, "end\n" );
  173. return TRUE;
  174. }
  175. BOOL VtaExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface)
  176. {
  177. // Dump mesh info: vertices, normals, UV texture map coords, bone assignments
  178. DumpModelTEP procDumpModel;
  179. procDumpModel.m_pfile = pFile;
  180. procDumpModel.m_phec = this;
  181. procDumpModel.m_tvBase = m_tvStart;
  182. fprintf(pFile, "vertexanimation\n" );
  183. procDumpModel.m_baseVert = NULL;
  184. for (TimeValue tv = m_tvStart; tv <= m_tvEnd; tv += m_tpf)
  185. {
  186. fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() );
  187. procDumpModel.m_tvToDump = tv;
  188. procDumpModel.m_baseVertCount = 0;
  189. (void) pexpiface->theScene->EnumTree(&procDumpModel);
  190. }
  191. fprintf(pFile, "end\n" );
  192. return TRUE;
  193. }
  194. //=============================================================================
  195. // TREE-ENUMERATION PROCEDURES
  196. //=============================================================================
  197. #define ASSERT_AND_ABORT(f, sz) \
  198. if (!(f)) \
  199. { \
  200. ASSERT_MBOX(FALSE, sz); \
  201. /* cleanup( ); */ \
  202. return TREE_ABORT; \
  203. }
  204. //=================================================================
  205. // Methods for CountNodesTEP
  206. //
  207. int CountNodesTEP::callback( INode *node)
  208. {
  209. INode *pnode = node; // Hungarian
  210. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  211. if (::FUndesirableNode(pnode))
  212. {
  213. // Mark as skippable
  214. ::SetIndexOfINode(pnode, VtaExportClass::UNDESIRABLE_NODE_MARKER);
  215. return TREE_CONTINUE;
  216. }
  217. // Establish "node index"--just ascending ints
  218. ::SetIndexOfINode(pnode, m_cNodes);
  219. m_cNodes++;
  220. return TREE_CONTINUE;
  221. }
  222. //=================================================================
  223. // Methods for CollectNodesTEP
  224. //
  225. int CollectNodesTEP::callback(INode *node)
  226. {
  227. INode *pnode = node; // Hungarian
  228. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  229. if (::FNodeMarkedToSkip(pnode))
  230. return TREE_CONTINUE;
  231. // Get pre-stored "index"
  232. int iNode = ::GetIndexOfINode(pnode);
  233. ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode");
  234. // Get name, store name in array
  235. TSTR strNodeName(pnode->GetName());
  236. strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName);
  237. // Get Node's time-zero Transformation Matrices
  238. m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/);
  239. m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/);
  240. // I'll calculate this later
  241. m_phec->m_rgmaxnode[iNode].imaxnodeParent = VtaExportClass::UNDESIRABLE_NODE_MARKER;
  242. return TREE_CONTINUE;
  243. }
  244. //=================================================================
  245. // Methods for DumpNodesTEP
  246. //
  247. int DumpNodesTEP::callback(INode *pnode)
  248. {
  249. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  250. if (::FNodeMarkedToSkip(pnode))
  251. return TREE_CONTINUE;
  252. // Get node's parent
  253. INode *pnodeParent;
  254. pnodeParent = pnode->GetParentNode();
  255. // The model's root is a child of the real "scene root"
  256. TSTR strNodeName(pnode->GetName());
  257. BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
  258. int iNode = ::GetIndexOfINode(pnode);
  259. int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/);
  260. // Convenient time to cache this
  261. m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? VtaExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent;
  262. // Root node has no parent, thus no translation
  263. if (fNodeIsRoot)
  264. iNodeParent = -1;
  265. // Dump node description
  266. fprintf(m_pfile, "%3d \"%s\" %3d\n",
  267. iNode,
  268. strNodeName,
  269. iNodeParent );
  270. return TREE_CONTINUE;
  271. }
  272. //=================================================================
  273. // Methods for DumpFrameRotationsTEP
  274. //
  275. int DumpFrameRotationsTEP::callback(INode *pnode)
  276. {
  277. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  278. if (::FNodeMarkedToSkip(pnode))
  279. return TREE_CONTINUE;
  280. int iNode = ::GetIndexOfINode(pnode);
  281. TSTR strNodeName(pnode->GetName());
  282. // The model's root is a child of the real "scene root"
  283. INode *pnodeParent = pnode->GetParentNode();
  284. BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
  285. // Get Node's "Local" Transformation Matrix
  286. Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump);
  287. Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump);
  288. mat3NodeTM.NoScale(); // Clear these out because they apparently
  289. mat3ParentTM.NoScale(); // screw up the following calculation.
  290. Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM);
  291. Point3 rowTrans = mat3NodeLocalTM.GetTrans();
  292. // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler)
  293. // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z.
  294. AffineParts affparts;
  295. float rgflXYZRotations[3];
  296. decomp_affine(mat3NodeLocalTM, &affparts);
  297. QuatToEuler(affparts.q, rgflXYZRotations);
  298. float xRot = rgflXYZRotations[0]; // in radians
  299. float yRot = rgflXYZRotations[1]; // in radians
  300. float zRot = rgflXYZRotations[2]; // in radians
  301. // Get rotations in the -2pi...2pi range
  302. xRot = ::FlReduceRotation(xRot);
  303. yRot = ::FlReduceRotation(yRot);
  304. zRot = ::FlReduceRotation(zRot);
  305. // Print rotations
  306. //fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n",
  307. fprintf(m_pfile, "%3d %f %f %f %f %f %f\n",
  308. // Node:%-15s Rotation (x,y,z)\n",
  309. iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot);
  310. return TREE_CONTINUE;
  311. }
  312. //=================================================================
  313. // Methods for DumpModelTEP
  314. //
  315. Modifier *FindPhysiqueModifier (INode *nodePtr)
  316. {
  317. // Get object from node. Abort if no object.
  318. Object *ObjectPtr = nodePtr->GetObjectRef();
  319. if (!ObjectPtr) return NULL;
  320. // Is derived object ?
  321. if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID)
  322. {
  323. // Yes -> Cast.
  324. IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr);
  325. // Iterate over all entries of the modifier stack.
  326. int ModStackIndex = 0;
  327. while (ModStackIndex < DerivedObjectPtr->NumModifiers())
  328. {
  329. // Get current modifier.
  330. Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
  331. // Is this Physique ?
  332. if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) )
  333. {
  334. // Yes -> Exit.
  335. return ModifierPtr;
  336. }
  337. // Next modifier stack entry.
  338. ModStackIndex++;
  339. }
  340. }
  341. // Not found.
  342. return NULL;
  343. }
  344. // #define DEBUG_MESH_DUMP
  345. //=================================================================
  346. // Methods for DumpModelTEP
  347. //
  348. int DumpModelTEP::callback(INode *pnode)
  349. {
  350. ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
  351. if (::FNodeMarkedToSkip(pnode))
  352. return TREE_CONTINUE;
  353. if ( !pnode->Selected())
  354. return TREE_CONTINUE;
  355. int iNode = ::GetIndexOfINode(pnode);
  356. TSTR strNodeName(pnode->GetName());
  357. // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly.
  358. if (FStrEq((char*)strNodeName, "Bip01 Footsteps"))
  359. return TREE_CONTINUE;
  360. // Helper nodes don't have meshes
  361. Object *pobj = pnode->GetObjectRef();
  362. if (pobj->SuperClassID() == HELPER_CLASS_ID)
  363. return TREE_CONTINUE;
  364. // Get the object's parameter block if it has one
  365. IParamBlock *pb = NULL;
  366. IParamArray *pa = pobj->GetParamBlock();
  367. if (!pa)
  368. {
  369. int i = pobj->NumRefs();
  370. // Search the references looking for a parameter block
  371. for (i = 0; i < pobj->NumRefs(); i++)
  372. {
  373. RefTargetHandle r = pobj->GetReference(i);
  374. if (r)
  375. {
  376. Class_ID x = r->ClassID();
  377. if (r && r->ClassID() == Class_ID(PARAMETER_BLOCK_CLASS_ID,0))
  378. {
  379. pb = (IParamBlock *) r;
  380. }
  381. }
  382. }
  383. }
  384. else
  385. {
  386. // pb = pa->GetParamBlock2();
  387. }
  388. if (pb)
  389. {
  390. // Find out how many _animatable_ parameters there are
  391. int count = pb->NumSubs();
  392. // Display data about each one
  393. for (int i = 0; i < count; i++)
  394. {
  395. TSTR name = pb->SubAnimName(i);
  396. int pbIndex = pb->AnimNumToParamNum(i);
  397. TSTR cname;
  398. SClass_ID sc = pb->GetAnimParamControlType(i);
  399. if (sc == CTRL_FLOAT_CLASS_ID)
  400. cname = TSTR(_T("Float"));
  401. else if (sc == CTRL_POINT3_CLASS_ID)
  402. cname = TSTR(_T("Point3"));
  403. }
  404. }
  405. // Get Node's object, convert to a triangle-mesh object, so I can access the Faces
  406. ObjectState os = pnode->EvalWorldState(m_tvToDump);
  407. pobj = os.obj;
  408. // Shouldn't have gotten this far if it's a helper object
  409. if (pobj->SuperClassID() == HELPER_CLASS_ID)
  410. {
  411. sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName);
  412. ASSERT_AND_ABORT(FALSE, st_szDBG);
  413. }
  414. // convert mesh to triobject
  415. if (!pobj->CanConvertToType(triObjectClassID))
  416. return TREE_CONTINUE;
  417. TriObject *ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID);
  418. if (ptriobj == NULL)
  419. return TREE_CONTINUE;
  420. Mesh *pmesh = &ptriobj->mesh;
  421. BOOL deleteMesh = (ptriobj != pobj);
  422. // Ensure that the vertex normals are up-to-date
  423. pmesh->buildNormals();
  424. // We want the vertex coordinates in World-space, not object-space
  425. Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump);
  426. Matrix3 mat3ObjectNTM = mat3ObjectTM;
  427. mat3ObjectNTM.NoScale( );
  428. int cVerts = pmesh->getNumVerts();
  429. // it would be nice to just evaluate the object for both time periods, but MAX
  430. // may do EvalWorldState in place, so the vertex animations get stomped
  431. if (m_tvToDump == m_tvBase)
  432. {
  433. if (m_baseVert)
  434. m_baseVert = (Point3 *)realloc( m_baseVert, (m_baseVertCount + cVerts) * sizeof( Point3 ) );
  435. else
  436. m_baseVert = (Point3 *)malloc( (m_baseVertCount + cVerts) * sizeof( Point3 ) );
  437. for (int iVert = 0; iVert < cVerts; iVert++)
  438. {
  439. Point3 pt3Vertex1 = pmesh->getVert(iVert);
  440. int iAdjVert = m_baseVertCount + iVert;
  441. m_baseVert[iAdjVert] = pt3Vertex1 * mat3ObjectTM;
  442. Point3 pt3Normal1 = pmesh->getNormal(iVert);
  443. pt3Normal1 = VectorTransform( mat3ObjectNTM, pt3Normal1 );
  444. fprintf(m_pfile, "%5d %8.4f %8.4f %8.4f %9.6f %9.6f %9.6f\n",
  445. iAdjVert, m_baseVert[iAdjVert].x, m_baseVert[iAdjVert].y, m_baseVert[iAdjVert].z,
  446. pt3Normal1.x, pt3Normal1.y, pt3Normal1.z );
  447. }
  448. }
  449. else
  450. {
  451. for (int iVert = 0; iVert < cVerts; iVert++)
  452. {
  453. Point3 pt3Vertex1 = pmesh->getVert(iVert);
  454. Point3 v1 = pt3Vertex1 * mat3ObjectTM;
  455. int iAdjVert = m_baseVertCount + iVert;
  456. if (Length( m_baseVert[iAdjVert] - v1) > 0.01)
  457. {
  458. Point3 pt3Vertex2 = pt3Vertex1;
  459. pt3Vertex2.z = pt3Vertex2.z + 10.0;
  460. pmesh->setVert( iVert, pt3Vertex2 );
  461. Point3 pt3Normal1 = pmesh->getNormal(iVert);
  462. pt3Normal1 = VectorTransform( mat3ObjectNTM, pt3Normal1 );
  463. fprintf(m_pfile, "%5d %8.4f %8.4f %8.4f %9.6f %9.6f %9.6f\n",
  464. iAdjVert, v1.x, v1.y, v1.z,
  465. pt3Normal1.x, pt3Normal1.y, pt3Normal1.z );
  466. }
  467. }
  468. }
  469. m_baseVertCount += cVerts;
  470. fflush( m_pfile );
  471. /*
  472. if (deleteMesh)
  473. delete pmesh;
  474. */
  475. return TREE_CONTINUE;
  476. }
  477. Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace)
  478. {
  479. // Lookup the appropriate vertex normal, based on smoothing group.
  480. int cNormals = prvertex->rFlags & NORCT_MASK;
  481. ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) ||
  482. (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX");
  483. if (cNormals == 1)
  484. return prvertex->rn.getNormal();
  485. else
  486. {
  487. for (int irn = 0; irn < cNormals; irn++)
  488. if (prvertex->ern[irn].getSmGroup() & smGroupFace)
  489. break;
  490. if (irn >= cNormals)
  491. {
  492. irn = 0;
  493. // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n");
  494. }
  495. return prvertex->ern[irn].getNormal();
  496. }
  497. }
  498. DumpModelTEP::~DumpModelTEP()
  499. {
  500. if (m_baseVert)
  501. {
  502. delete m_baseVert;
  503. }
  504. }
  505. //========================================================================
  506. // Utility functions for getting/setting the personal "node index" property.
  507. // NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I
  508. // NOTE: tried using an integer property.
  509. // FURTHER NOTE: those properties seem to change randomly sometimes, so I'm
  510. // implementing my own.
  511. typedef struct
  512. {
  513. char szNodeName[VtaExportClass::MAX_NAME_CHARS];
  514. int iNode;
  515. } NAMEMAP;
  516. const int MAX_NAMEMAP = 512;
  517. static NAMEMAP g_rgnm[MAX_NAMEMAP];
  518. int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists)
  519. {
  520. TSTR strNodeName(pnode->GetName());
  521. for (int inm = 0; inm < g_inmMac; inm++)
  522. if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName))
  523. return g_rgnm[inm].iNode;
  524. if (fAssertPropExists)
  525. ASSERT_MBOX(FALSE, "No NODEINDEXSTR property");
  526. return -7777;
  527. }
  528. void SetIndexOfINode(INode *pnode, int inode)
  529. {
  530. TSTR strNodeName(pnode->GetName());
  531. NAMEMAP *pnm;
  532. for (int inm = 0; inm < g_inmMac; inm++)
  533. if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName))
  534. break;
  535. if (inm < g_inmMac)
  536. pnm = &g_rgnm[inm];
  537. else
  538. {
  539. ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full");
  540. pnm = &g_rgnm[g_inmMac++];
  541. strcpy(pnm->szNodeName, (char*)strNodeName);
  542. }
  543. pnm->iNode = inode;
  544. }
  545. //=============================================================
  546. // Returns TRUE if a node should be ignored during tree traversal.
  547. //
  548. BOOL FUndesirableNode(INode *pnode)
  549. {
  550. // Get Node's underlying object, and object class name
  551. Object *pobj = pnode->GetObjectRef();
  552. // Don't care about lights, dummies, and cameras
  553. if (pobj->SuperClassID() == CAMERA_CLASS_ID)
  554. return TRUE;
  555. if (pobj->SuperClassID() == LIGHT_CLASS_ID)
  556. return TRUE;
  557. return FALSE;
  558. // Actually, if it's not selected, pretend it doesn't exist!
  559. //if (!pnode->Selected())
  560. // return TRUE;
  561. //return FALSE;
  562. }
  563. //=============================================================
  564. // Returns TRUE if a node has been marked as skippable
  565. //
  566. BOOL FNodeMarkedToSkip(INode *pnode)
  567. {
  568. return (::GetIndexOfINode(pnode) == VtaExportClass::UNDESIRABLE_NODE_MARKER);
  569. }
  570. //=============================================================
  571. // Reduces a rotation to within the -2PI..2PI range.
  572. //
  573. static float FlReduceRotation(float fl)
  574. {
  575. while (fl >= TWOPI)
  576. fl -= TWOPI;
  577. while (fl <= -TWOPI)
  578. fl += TWOPI;
  579. return fl;
  580. }