458 lines
14 KiB
C++
458 lines
14 KiB
C++
//**************************************************************************
|
|
//* 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<IDerivedObject*>(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<Modifier *> 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 (t2<t) break;
|
|
t = t2;
|
|
|
|
|
|
//----- Export the mesh definition at this frame
|
|
if (!anim) {
|
|
anim = VirtoolsExporter->AddObjectAnimation(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; i<numVtx; i++) {
|
|
Point3 tmp = OffsetTM.PointTransform(mesh->verts[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; i<numFaces; i++,Nindex+=3) {
|
|
Face* f = &mesh->faces[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; i<pmesh->numVerts; i++) {
|
|
Point3 tmp = OffsetTM.PointTransform(pmesh->verts[i].p);
|
|
key.PosArray[i] = VxVector(tmp.x,tmp.z,tmp.y);
|
|
}
|
|
for (int i=0; i<pmesh->numVecs; 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;
|
|
}
|
|
}
|