//================================================================= // 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; // Dump node description fprintf(m_pfile, "%3d \"%s\" %3d\n", iNode, strNodeName, iNodeParent ); return TREE_CONTINUE; }
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; }
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; }
//================================================================= // 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; }
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; }
//================================================================= // 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; }
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(); } }
//================================================================= // 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; ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); if (::FNodeMarkedToSkip(pnode)) return TREE_CONTINUE; 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); } } } // 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[iFace]; 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) { MtlID mtlidFace = pface->getMatID(); 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; Texmap *ptexmap = pstdmtlFace->GetSubTexmap(ID_DI); // 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 (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; if (m_mcExport) { // The Physique add-in allows vertices to be assigned to bones arbitrarily iNodeV0 = InodeOfPhyVectex( iVertex0 ); iNodeV1 = InodeOfPhyVectex( iVertex1 ); iNodeV2 = InodeOfPhyVectex( iVertex2 ); } else { // 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\n", iNodeV0, v0.x, v0.y, v0.z, pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z, UVvertex0.x, UVvertex0.y); fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n", iNodeV1, v1.x, v1.y, v1.z, pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z, UVvertex1.x, UVvertex1.y); fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n", iNodeV2, v2.x, v2.y, v2.z, pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z, UVvertex2.x, UVvertex2.y); } cleanup( ); return TREE_CONTINUE; }
//================================================================= // Methods for CollectModelTEP // int CollectModelTEP::callback(INode *pnode) { if (::FNodeMarkedToSkip(pnode)) return TREE_CONTINUE; if ( !pnode->Selected()) return TREE_CONTINUE; // clear physique export parameters m_mcExport = NULL; m_phyExport = NULL; m_phyMod = NULL; m_bonesProMod = NULL; ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); 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 Object *pobj = pnode->GetObjectRef(); if (pobj->SuperClassID() == HELPER_CLASS_ID) return TREE_CONTINUE; // 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; // 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); } // convert mesh to triobject if (!pobj->CanConvertToType(triObjectClassID)) return TREE_CONTINUE; TriObject *ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID); if (ptriobj == NULL) return TREE_CONTINUE; Mesh *pmesh = &ptriobj->mesh; // 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 ); } int cVerts = pmesh->getNumVerts(); // Dump the triangle face info int cFaces = pmesh->getNumFaces(); int *iUsed = new int[cVerts]; for (int iVert = 0; iVert < cVerts; iVert++) { iUsed[iVert] = 0; } for (int iFace = 0; iFace < cFaces; iFace++) { if (pmesh->faces[iFace].flags & HAS_TVERTS) { iUsed[pmesh->faces[iFace].getVert(0)] = 1; iUsed[pmesh->faces[iFace].getVert(1)] = 1; iUsed[pmesh->faces[iFace].getVert(2)] = 1; } } for (iVert = 0; iVert < cVerts; iVert++) { MaxVertWeight *pweight = m_phec->m_MaxVertex[m_phec->m_cMaxVertex] = new MaxVertWeight [m_phec->m_cMaxNode]; Point3 pt3Vertex1 = pmesh->getVert(iVert); Point3 v1 = pt3Vertex1 * mat3ObjectTM; GetUnifiedCoord( v1, pweight, m_phec->m_MaxNode, m_phec->m_cMaxNode ); if (CollectWeights( iVert, pweight )) { m_phec->m_cMaxVertex++; } } // fflush( m_pfile ); return TREE_CONTINUE; }