//************************************************************************** //* MeshAnimout.cpp - Virtools File Exporter //* //* Romain Sididris - Copyright (c) Virtools 2000 //* //* (Based on Ascii File Exporter source code //* By Christer Janson //* Kinetix Development //* Copyright (c) Kinetix 1997, All Rights Reserved. ) //* //* This module handles animated mesh output(sampling to morph or conversion to skin) //* //*************************************************************************** #include "Precomp.h" #include "Max2Nmo.h" #include "modstack.h" extern BOOL g_CharacterStudio312; // helper class to automatically deactivate morpher modifier on a mesh, and restore it at // dtor time class CMorpherModifierDeactivate { public: CMorpherModifierDeactivate(INode* node) { if (!node) return; /// Get object from node. Abort if no object. Object* pObj = node->GetObjectRef(); if (pObj) { // Is derived object ? SClass_ID sid = pObj->SuperClassID(); while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID) { // Yes -> Cast. IDerivedObject* pDerObj = static_cast(pObj); // Iterate over all entries of the modifier stack. int ModStackIndex = 0; while (ModStackIndex < pDerObj->NumModifiers()) { // Get current modifier. Modifier* mod = pDerObj->GetModifier(ModStackIndex); // Is this Skin ? if (mod->ClassID() == MR3_CLASS_ID) { if (mod->IsEnabled()) { m_MorphModifier.PushBack(mod); mod->DisableMod(); } } // Next modifier stack entry. ModStackIndex++; } pObj = pDerObj->GetObjRef(); } } } ~CMorpherModifierDeactivate() { for (int i = 0; i < m_MorphModifier.Size(); ++i) { m_MorphModifier[i]->EnableMod(); } } private: XArray m_MorphModifier; }; /**************************************************************************** Mesh Animation output ****************************************************************************/ void Max2Nmo::ExportAnimMesh(INode* node) { BOOL blendShapesOn = this->bExportBlendShapes || this->bExportBlendShapeAnims; //-- Check if a morpher modifier exists on this node, and temporarily disable it if needed CMorpherModifierDeactivate mmd(blendShapesOn || !this->bIncludeBlendShapesInMorphController ? node : NULL); ObjectState os = node->EvalWorldState(GetStaticFrame()); if (!os.obj) return; CK3dEntity* entity = VirtoolsExporter->GetEntityByKey(node); if (!entity) return; //-- Check if a skin modifier exists on this node if (blendShapesOn || !GetForceSkinToMorph()) if (ExportSkinnedMesh(node,entity)) return; //-- Check if a physique modifier exists on this node if (blendShapesOn || GetConvertPhysiqueToSkin()) if (ExportPhysiqueMesh(node,entity)) return; os = node->EvalWorldState(GetStaticFrame()); //-- Otherwise check if the object is animated to create a morph animation //-- we will output the entire mesh definition for every specified frame. Interval animRange = ip->GetAnimRange(); Interval objRange = os.obj->ObjectValidity(GetStaticFrame()); //if (objRange.Start()==0 && objRange.End()==0) return; //-- If the animation range is not fully included in the validity //-- interval of the object, then we're animated. if (!objRange.InInterval(animRange)) { TimeValue t = animRange.Start(); CKObjectAnimation* anim = entity->GetObjectAnimationCount() ? entity->GetObjectAnimation(0) : NULL; CKMorphController* mctrl = NULL; while (1) { // This may seem strange, but the object in the pipeline // might not be valid anymore. os = node->EvalWorldState(t); objRange = os.obj->ObjectValidity(t); TimeValue t2 = objRange.Start() < animRange.Start() ? animRange.Start() : objRange.Start(); if (t2AddObjectAnimation(entity,(const char *)entity->GetName(),node); anim->SetLength(FrameTime(m_EndFrame)); } if (!mctrl) { mctrl = (CKMorphController*)anim->CreateController(CKANIMATION_MORPH_CONTROL); mctrl->SetLength(FrameTime(m_EndFrame)); } if (os.obj->ClassID() == Class_ID(PATCHOBJ_CLASS_ID, 0)) DumpPatchMeshMorphKey(mctrl,node,t); else DumpMeshMorphKey(mctrl,node,t); //------ if (objRange.End() >= animRange.End()) { break; } else { t2 = (objRange.End()/GetTicksPerFrame()+GetMeshFrameStep()) * GetTicksPerFrame(); if (t2<=t) break; t = t2; } } if (mctrl && (mctrl->GetKeyCount()<2)) { anim->DeleteController(CKANIMATION_MORPH_CONTROL); mctrl=NULL; } if (mctrl) { Report(REPORT_HLEVEL,"%s : Sampling to morph animation : %d keys\r\n",node->GetName(),mctrl->GetKeyCount()); } } } BOOL Max2Nmo::ExportSkinnedMesh(INode* node,CK3dEntity* ent) { if (!ent) return FALSE; Modifier *mod = FindSkinModifier(node); if(!mod) return FALSE; ISkin *iskin = (ISkin *) mod->GetInterface(I_SKIN); if (!iskin) return FALSE; if (!iskin->GetNumBones()) return FALSE; ISkinContextData *iskinMC = iskin->GetContextInterface(node); if (!iskinMC) return FALSE; CKMesh* mesh=ent->GetCurrentMesh(); if (!mesh) return FALSE; Report(REPORT_HLEVEL,"%s : Saving Skin Data\r\n",node->GetName()); Matrix3 InitTM; VxMatrix Mat; VirtoolsTransitionMesh* tmp_mesh = VirtoolsExporter->GetTransitionMesh(mesh); CKSkin* skin = ent->CreateSkin(); int VertexCount = mesh->GetModifierVertexCount(); BOOL DoNormal = (mesh->GetClassID() != CKCID_PATCHMESH); skin->SetBoneCount(iskin->GetNumBones()); skin->SetVertexCount(VertexCount); if (DoNormal) { skin->SetNormalCount(VertexCount); } for(int j = 0 ; j < iskin->GetNumBones() ; j++) { CK3dEntity* bone = VirtoolsExporter->GetEntityByKey(iskin->GetBone(j)); CKSkinBoneData* bonedata = skin->GetBoneData(j); bonedata->SetBone(bone); if (bone) { Matrix3 MaxBoneMat; VxMatrix InvBoneMat,BoneMat; /* // Do not use GetBoneInitTM from skin since // we cannot have the vertex original position if (iskin->GetBoneInitTM(iskin->GetBone(j),MaxBoneMat)) { ConvertMaxMatrix2Virtools(MaxBoneMat, BoneMat); Vx3DInverseMatrix(InvBoneMat,BoneMat); bonedata->SetBoneInitialInverseMatrix(InvBoneMat); } else { */ bonedata->SetBoneInitialInverseMatrix(bone->GetInverseWorldMatrix()); // } } } if(iskin->GetSkinInitTM(node, InitTM)) { ConvertMaxMatrix2Virtools(InitTM, Mat); skin->SetObjectInitMatrix(Mat); } CKDWORD Stride=0; BYTE* pts=mesh->GetModifierVertices(&Stride); CKDWORD NStride=0; BYTE* norms=(BYTE*)mesh->GetNormalsPtr(&NStride); for(int j = 0 ; j < VertexCount ; j++,pts+=Stride,norms+=NStride ) { int MaxVertexIndex = tmp_mesh ? tmp_mesh->m_VirtoolsVertices[j].OriginalPosIndex : j; CKSkinVertexData* vertexdata = skin->GetVertexData(j); if (DoNormal) { skin->SetNormal(j,*(VxVector *)norms); } vertexdata->SetInitialPos(*(VxVector *)pts); //int numPoint=iskinMC->GetNumPoints(); //if (MaxVertexIndex>=numPoint) MaxVertexIndex = numPoint-1; vertexdata->SetBoneCount(iskinMC->GetNumAssignedBones(MaxVertexIndex)); int boneindex=0; for(int k= 0 ; k < vertexdata->GetBoneCount(); ++k) { boneindex = iskinMC->GetAssignedBone(MaxVertexIndex,k); if (boneindex < 0) { Report(REPORT_HLEVEL,"WARNING: Invalid bone index in vertex bone list. Setting this bone to index 0\r\n"); vertexdata->SetBone(k,0); //Do we need to do something on the weight if the bone is invalid ? } else vertexdata->SetBone(k,boneindex); vertexdata->SetWeight(k,iskinMC->GetBoneWeight(MaxVertexIndex,k)); } } // iskin->ReleaseContextInterface(iskinMC); return TRUE; } int AddBoneToArray(XVoidArray* bones,void* bone) { int pos=bones->GetPosition(bone); if (pos>=0) return pos; bones->PushBack(bone); return bones->Size()-1; } BOOL Max2Nmo::ExportPhysiqueMesh(INode* node,CK3dEntity* ent) { #ifndef MAX42 if (g_CharacterStudio312) { return ExportPhysiqueMeshCS312(node,ent); } else #endif { return ExportPhysiqueMeshCS300(node,ent); } } /******************************************************************** Adds a Morph Key for a mesh at the specified time t. For this we evaluate the node at the specified time value and convert its vertices and normals to Virtools format. If the vertices have not moved the key is not stored. ********************************************************************/ void Max2Nmo::DumpMeshMorphKey(CKMorphController* ctrl,INode* node,TimeValue t) { Matrix3 tm = node->GetNodeTM(t); int i; // Since referential is not the same in Virtools indices 1 & 2 are swapped int vx1 = 0, vx2 = 2, vx3 = 1; ObjectState os = node->EvalWorldState(t); if (!os.obj || os.obj->SuperClassID()!=GEOMOBJECT_CLASS_ID) { return; // Safety net. This shouldn't happen. } BOOL needDel; TriObject* tri = GetTriObjectFromNode(node, t, needDel); if (!tri) return; Mesh* mesh = &tri->GetMesh(); mesh->buildNormals(); int numVtx = mesh->getNumVerts(); // Vertex Count int numFaces = mesh->getNumFaces(); // Face Count if (!numVtx || !numFaces) return; int numCVx = mesh->numCVerts; // Vertex color Count BOOL UseVertexColor = node->GetCVertMode() && numCVx; //-- Get back the transition mesh, it will be used to store temporary vertices and normals VirtoolsTransitionMesh* tmp_mesh = VirtoolsExporter->GetTransitionMesh(node); if (!tmp_mesh) return; //-- Test invalid number of vertices or faces (topology may have changed !!!) if (tmp_mesh->m_Vertices.Size() != numVtx) return ; if (tmp_mesh->m_Faces.Size() != numFaces) return ; //-------------- Update the vertices pos -------------------------- // Max Vertices are given in the ObjectTM referential and we need them in NodeTM referential // So transform them using OffsetTM Matrix3 OffsetTM = GetNodeOffsetTM(node); // (transform them and invert Y & Z for (i=0; iverts[i]); tmp_mesh->m_Vertices[i]=VxVector(tmp.x,tmp.z,tmp.y); } //---------- Vertex normals. -------------------------------- // In MAX a vertex can have more than one normal (but doesn't always have it). // This is depending on the face you are accessing the vertex through. // To get all information we need to export all three vertex normals // for every face. if (!UseVertexColor) { int Nindex=0; for (i=0; ifaces[i]; Point3 vn; vn = GetVertexNormal(mesh, i, mesh->getRVertPtr(f->getVert(vx1))); vn = OffsetTM.VectorTransform(vn); tmp_mesh->m_Normals[Nindex]=VxVector(vn.x,vn.z,vn.y); vn = GetVertexNormal(mesh, i, mesh->getRVertPtr(f->getVert(vx2))); vn = OffsetTM.VectorTransform(vn); tmp_mesh->m_Normals[Nindex+1]=VxVector(vn.x,vn.z,vn.y); vn = GetVertexNormal(mesh, i, mesh->getRVertPtr(f->getVert(vx3))); vn = OffsetTM.VectorTransform(vn); tmp_mesh->m_Normals[Nindex+2]=VxVector(vn.x,vn.z,vn.y); } } //-------- And refresh Virtools Data tmp_mesh->RefreshVirtoolsVerticesPosition(); int VirtoolsVertexCount = tmp_mesh->m_VirtoolsVertices.Size(); ctrl->SetMorphVertexCount(VirtoolsVertexCount); //-- We compare if there were changes in geometry between two keys //-- No need to have two similar keys BOOL Same = TRUE; if (ctrl->GetKeyCount()>0) { CKMorphKey* PrevKey = (CKMorphKey*)ctrl->GetKey(ctrl->GetKeyCount()-1); for (int i=0; i< VirtoolsVertexCount; ++i) { if (PrevKey->PosArray[i]!=tmp_mesh->m_VirtoolsVertices[i].Pos) { Same = FALSE; break; } } } else Same = FALSE; if (!Same) { int index = ctrl->AddKey(FrameTime(t),!UseVertexColor); CKMorphKey* key = (CKMorphKey*)ctrl->GetKey(index); for (i = 0 ; i< VirtoolsVertexCount; ++i) { key->PosArray[i] = tmp_mesh->m_VirtoolsVertices[i].Pos; } if (!UseVertexColor) for (i = 0 ; i< VirtoolsVertexCount; ++i) { key->NormArray[i] = tmp_mesh->m_VirtoolsVertices[i].Norm; } } if (needDel) { delete tri; } } /******************************************************************** Adds a Morph Key for a patch mesh. Same function as above.. ********************************************************************/ void Max2Nmo::DumpPatchMeshMorphKey(CKMorphController* ctrl,INode* node,TimeValue t) { Mtl* nodeMtl = node->GetMtl(); Matrix3 tm = node->GetNodeTM(t); int channel = 0; ObjectState os = node->EvalWorldState(t); if (!os.obj || os.obj->SuperClassID()!=GEOMOBJECT_CLASS_ID) { return; // Safety net. This shouldn't happen. } BOOL needDel; PatchObject* patch = GetPatchObjectFromNode(node, t, needDel); if (!patch) return; PatchMesh* pmesh = &patch->patch; //-------------- Export the patch vertices and tangent vectors -------------------------- // Max Vertices are given in the ObjectTM referential and we need them in NodeTM referential // So transform them using OffsetTM Matrix3 OffsetTM = GetNodeOffsetTM(node); // (transform them and invert Y & Z int VirtoolsVertexCount = pmesh->numVerts + pmesh->numVecs; if (! VirtoolsVertexCount) return; ctrl->SetMorphVertexCount(VirtoolsVertexCount); CKMorphKey key; key.NormArray = NULL; key.TimeStep = FrameTime(t); key.PosArray = new VxVector[VirtoolsVertexCount]; for (int i=0; inumVerts; i++) { Point3 tmp = OffsetTM.PointTransform(pmesh->verts[i].p); key.PosArray[i] = VxVector(tmp.x,tmp.z,tmp.y); } for (int i=0; inumVecs; i++) { Point3 tmp = OffsetTM.PointTransform(pmesh->vecs[i].p); key.PosArray[i+pmesh->numVerts] = VxVector(tmp.x,tmp.z,tmp.y); } // Compare if there was changes in geometry between two keys // No need to have two similar keys BOOL Same = TRUE; if (ctrl->GetKeyCount()>0) { CKMorphKey* PrevKey = (CKMorphKey*)ctrl->GetKey(ctrl->GetKeyCount()-1); for (int i=0; i< VirtoolsVertexCount; ++i) { if (PrevKey->PosArray[i]!=key.PosArray[i]) { Same = FALSE; break; } } } else Same = FALSE; if (!Same) ctrl->AddKey(&key,FALSE); delete[] key.PosArray; if (needDel) { delete patch; } }