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.
763 lines
19 KiB
763 lines
19 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "MAX.H"
|
||
|
#include "DECOMP.H"
|
||
|
#include "STDMAT.H"
|
||
|
#include "ANIMTBL.H"
|
||
|
#include "istdplug.h"
|
||
|
#include "phyexp.h"
|
||
|
#include "vtaexprc.h"
|
||
|
#include "vtadefs.h"
|
||
|
|
||
|
|
||
|
|
||
|
//===================================================================
|
||
|
// Prototype declarations
|
||
|
//
|
||
|
int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE);
|
||
|
void SetIndexOfINode(INode *pnode, int inode);
|
||
|
BOOL FUndesirableNode(INode *pnode);
|
||
|
BOOL FNodeMarkedToSkip(INode *pnode);
|
||
|
float FlReduceRotation(float fl);
|
||
|
|
||
|
|
||
|
//===================================================================
|
||
|
// Global variable definitions
|
||
|
//
|
||
|
|
||
|
// Save for use with dialogs
|
||
|
static HINSTANCE hInstance;
|
||
|
|
||
|
// We just need one of these to hand off to 3DSMAX.
|
||
|
static VtaExportClassDesc VtaExportCD;
|
||
|
|
||
|
// For OutputDebugString and misc sprintf's
|
||
|
static char st_szDBG[300];
|
||
|
|
||
|
// INode mapping table
|
||
|
static int g_inmMac = 0;
|
||
|
|
||
|
//===================================================================
|
||
|
// Utility functions
|
||
|
//
|
||
|
|
||
|
static int AssertFailedFunc(char *sz)
|
||
|
{
|
||
|
MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK);
|
||
|
int Set_Your_Breakpoint_Here = 1;
|
||
|
return 1;
|
||
|
}
|
||
|
#define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz))
|
||
|
|
||
|
|
||
|
//===================================================================
|
||
|
// Required plug-in export functions
|
||
|
//
|
||
|
BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved)
|
||
|
{
|
||
|
static int fFirstTimeHere = TRUE;
|
||
|
if (fFirstTimeHere)
|
||
|
{
|
||
|
fFirstTimeHere = FALSE;
|
||
|
hInstance = hinstDLL;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
EXPORT_THIS int LibNumberClasses(void)
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass)
|
||
|
{
|
||
|
switch(iWhichClass)
|
||
|
{
|
||
|
case 0: return &VtaExportCD;
|
||
|
default: return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
EXPORT_THIS const TCHAR *LibDescription()
|
||
|
{
|
||
|
return _T("Valve VTA Plug-in.");
|
||
|
}
|
||
|
|
||
|
|
||
|
EXPORT_THIS ULONG LibVersion()
|
||
|
{
|
||
|
return VERSION_3DSMAX;
|
||
|
}
|
||
|
|
||
|
|
||
|
//=====================================================================
|
||
|
// Methods for VtaExportClass
|
||
|
//
|
||
|
|
||
|
CONSTRUCTOR VtaExportClass::VtaExportClass(void)
|
||
|
{
|
||
|
m_rgmaxnode = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
DESTRUCTOR VtaExportClass::~VtaExportClass(void)
|
||
|
{
|
||
|
if (m_rgmaxnode)
|
||
|
delete[] m_rgmaxnode;
|
||
|
}
|
||
|
|
||
|
|
||
|
int VtaExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
|
||
|
{
|
||
|
ExpInterface *pexpiface = ei; // Hungarian
|
||
|
Interface *piface = i; // Hungarian
|
||
|
|
||
|
// Reset the name-map property manager
|
||
|
g_inmMac = 0;
|
||
|
|
||
|
// Break up filename, re-assemble longer versions
|
||
|
TSTR strPath, strFile, strExt;
|
||
|
TCHAR szFile[MAX_PATH];
|
||
|
SplitFilename(TSTR(name), &strPath, &strFile, &strExt);
|
||
|
sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT);
|
||
|
|
||
|
FILE *pFile;
|
||
|
if ((pFile = fopen(szFile, "w")) == NULL)
|
||
|
return FALSE/*failure*/;
|
||
|
|
||
|
fprintf( pFile, "version %d\n", 1 );
|
||
|
|
||
|
// Get animation metrics
|
||
|
m_intervalOfAnimation = piface->GetAnimRange();
|
||
|
m_tvStart = m_intervalOfAnimation.Start();
|
||
|
m_tvEnd = m_intervalOfAnimation.End();
|
||
|
m_tpf = ::GetTicksPerFrame();
|
||
|
|
||
|
// Count nodes, label them, collect into array
|
||
|
if (!CollectNodes(pexpiface))
|
||
|
return 0; /*fail*/
|
||
|
|
||
|
// Output nodes
|
||
|
if (!DumpBones(pFile, pexpiface))
|
||
|
{
|
||
|
fclose( pFile );
|
||
|
return 0; /*fail*/
|
||
|
}
|
||
|
|
||
|
// Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file
|
||
|
DumpRotations(pFile, pexpiface);
|
||
|
|
||
|
// Output triangle meshes (first frame/all frames), if this is the reference frame MAX file
|
||
|
DumpModel(pFile, pexpiface);
|
||
|
|
||
|
// Tell user that exporting is finished (it can take a while with no feedback)
|
||
|
char szExportComplete[300];
|
||
|
sprintf(szExportComplete, "Exported %s.", szFile);
|
||
|
MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK);
|
||
|
|
||
|
fclose( pFile );
|
||
|
|
||
|
return 1/*success*/;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL VtaExportClass::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;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL VtaExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface)
|
||
|
{
|
||
|
// Dump bone names
|
||
|
DumpNodesTEP procDumpNodes;
|
||
|
procDumpNodes.m_pfile = pFile;
|
||
|
procDumpNodes.m_phec = this;
|
||
|
fprintf(pFile, "nodes\n" );
|
||
|
(void) pexpiface->theScene->EnumTree(&procDumpNodes);
|
||
|
fprintf(pFile, "end\n" );
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL VtaExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface)
|
||
|
{
|
||
|
// Dump bone-rotation info, for each frame
|
||
|
// Also dumps root-node translation info (the model's world-position at each frame)
|
||
|
DumpFrameRotationsTEP procDumpFrameRotations;
|
||
|
procDumpFrameRotations.m_pfile = pFile;
|
||
|
procDumpFrameRotations.m_phec = this;
|
||
|
|
||
|
fprintf(pFile, "skeleton\n" );
|
||
|
for (TimeValue tv = m_tvStart; tv <= m_tvEnd; tv += m_tpf)
|
||
|
{
|
||
|
fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() );
|
||
|
procDumpFrameRotations.m_tvToDump = tv;
|
||
|
(void) pexpiface->theScene->EnumTree(&procDumpFrameRotations);
|
||
|
}
|
||
|
fprintf(pFile, "end\n" );
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL VtaExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface)
|
||
|
{
|
||
|
// Dump mesh info: vertices, normals, UV texture map coords, bone assignments
|
||
|
DumpModelTEP procDumpModel;
|
||
|
procDumpModel.m_pfile = pFile;
|
||
|
procDumpModel.m_phec = this;
|
||
|
|
||
|
procDumpModel.m_tvBase = m_tvStart;
|
||
|
|
||
|
fprintf(pFile, "vertexanimation\n" );
|
||
|
procDumpModel.m_baseVert = NULL;
|
||
|
for (TimeValue tv = m_tvStart; tv <= m_tvEnd; tv += m_tpf)
|
||
|
{
|
||
|
fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() );
|
||
|
procDumpModel.m_tvToDump = tv;
|
||
|
procDumpModel.m_baseVertCount = 0;
|
||
|
(void) pexpiface->theScene->EnumTree(&procDumpModel);
|
||
|
}
|
||
|
fprintf(pFile, "end\n" );
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
// TREE-ENUMERATION PROCEDURES
|
||
|
//=============================================================================
|
||
|
|
||
|
#define ASSERT_AND_ABORT(f, sz) \
|
||
|
if (!(f)) \
|
||
|
{ \
|
||
|
ASSERT_MBOX(FALSE, sz); \
|
||
|
/* cleanup( ); */ \
|
||
|
return TREE_ABORT; \
|
||
|
}
|
||
|
|
||
|
|
||
|
//=================================================================
|
||
|
// 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, VtaExportClass::UNDESIRABLE_NODE_MARKER);
|
||
|
return TREE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
// Establish "node index"--just ascending ints
|
||
|
::SetIndexOfINode(pnode, m_cNodes);
|
||
|
|
||
|
m_cNodes++;
|
||
|
|
||
|
return TREE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//=================================================================
|
||
|
// 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 = VtaExportClass::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 ? VtaExportClass::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;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//=================================================================
|
||
|
// 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();
|
||
|
|
||
|
// 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 %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n",
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//=================================================================
|
||
|
// Methods for DumpModelTEP
|
||
|
//
|
||
|
Modifier *FindPhysiqueModifier (INode *nodePtr)
|
||
|
{
|
||
|
// Get object from node. Abort if no object.
|
||
|
Object *ObjectPtr = nodePtr->GetObjectRef();
|
||
|
if (!ObjectPtr) return NULL;
|
||
|
|
||
|
// Is derived object ?
|
||
|
if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID)
|
||
|
{
|
||
|
// Yes -> Cast.
|
||
|
IDerivedObject *DerivedObjectPtr = static_cast<IDerivedObject*>(ObjectPtr);
|
||
|
|
||
|
// Iterate over all entries of the modifier stack.
|
||
|
int ModStackIndex = 0;
|
||
|
while (ModStackIndex < DerivedObjectPtr->NumModifiers())
|
||
|
{
|
||
|
// Get current modifier.
|
||
|
Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
|
||
|
|
||
|
// Is this Physique ?
|
||
|
if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) )
|
||
|
{
|
||
|
// Yes -> Exit.
|
||
|
return ModifierPtr;
|
||
|
}
|
||
|
// Next modifier stack entry.
|
||
|
ModStackIndex++;
|
||
|
}
|
||
|
}
|
||
|
// Not found.
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
// #define DEBUG_MESH_DUMP
|
||
|
|
||
|
//=================================================================
|
||
|
// Methods for DumpModelTEP
|
||
|
//
|
||
|
int DumpModelTEP::callback(INode *pnode)
|
||
|
{
|
||
|
ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");
|
||
|
|
||
|
if (::FNodeMarkedToSkip(pnode))
|
||
|
return TREE_CONTINUE;
|
||
|
|
||
|
if ( !pnode->Selected())
|
||
|
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
|
||
|
Object *pobj = pnode->GetObjectRef();
|
||
|
if (pobj->SuperClassID() == HELPER_CLASS_ID)
|
||
|
return TREE_CONTINUE;
|
||
|
|
||
|
|
||
|
// Get the object's parameter block if it has one
|
||
|
|
||
|
IParamBlock *pb = NULL;
|
||
|
IParamArray *pa = pobj->GetParamBlock();
|
||
|
|
||
|
if (!pa)
|
||
|
{
|
||
|
int i = pobj->NumRefs();
|
||
|
// Search the references looking for a parameter block
|
||
|
for (i = 0; i < pobj->NumRefs(); i++)
|
||
|
{
|
||
|
RefTargetHandle r = pobj->GetReference(i);
|
||
|
if (r)
|
||
|
{
|
||
|
Class_ID x = r->ClassID();
|
||
|
|
||
|
if (r && r->ClassID() == Class_ID(PARAMETER_BLOCK_CLASS_ID,0))
|
||
|
{
|
||
|
pb = (IParamBlock *) r;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// pb = pa->GetParamBlock2();
|
||
|
}
|
||
|
|
||
|
if (pb)
|
||
|
{
|
||
|
// Find out how many _animatable_ parameters there are
|
||
|
int count = pb->NumSubs();
|
||
|
|
||
|
// Display data about each one
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
TSTR name = pb->SubAnimName(i);
|
||
|
|
||
|
int pbIndex = pb->AnimNumToParamNum(i);
|
||
|
|
||
|
TSTR cname;
|
||
|
|
||
|
SClass_ID sc = pb->GetAnimParamControlType(i);
|
||
|
|
||
|
if (sc == CTRL_FLOAT_CLASS_ID)
|
||
|
|
||
|
cname = TSTR(_T("Float"));
|
||
|
|
||
|
else if (sc == CTRL_POINT3_CLASS_ID)
|
||
|
|
||
|
cname = TSTR(_T("Point3"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// 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;
|
||
|
BOOL deleteMesh = (ptriobj != pobj);
|
||
|
|
||
|
// 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);
|
||
|
Matrix3 mat3ObjectNTM = mat3ObjectTM;
|
||
|
mat3ObjectNTM.NoScale( );
|
||
|
|
||
|
int cVerts = pmesh->getNumVerts();
|
||
|
|
||
|
// it would be nice to just evaluate the object for both time periods, but MAX
|
||
|
// may do EvalWorldState in place, so the vertex animations get stomped
|
||
|
if (m_tvToDump == m_tvBase)
|
||
|
{
|
||
|
if (m_baseVert)
|
||
|
m_baseVert = (Point3 *)realloc( m_baseVert, (m_baseVertCount + cVerts) * sizeof( Point3 ) );
|
||
|
else
|
||
|
m_baseVert = (Point3 *)malloc( (m_baseVertCount + cVerts) * sizeof( Point3 ) );
|
||
|
|
||
|
for (int iVert = 0; iVert < cVerts; iVert++)
|
||
|
{
|
||
|
Point3 pt3Vertex1 = pmesh->getVert(iVert);
|
||
|
int iAdjVert = m_baseVertCount + iVert;
|
||
|
|
||
|
m_baseVert[iAdjVert] = pt3Vertex1 * mat3ObjectTM;
|
||
|
|
||
|
Point3 pt3Normal1 = pmesh->getNormal(iVert);
|
||
|
pt3Normal1 = VectorTransform( mat3ObjectNTM, pt3Normal1 );
|
||
|
|
||
|
fprintf(m_pfile, "%5d %8.4f %8.4f %8.4f %9.6f %9.6f %9.6f\n",
|
||
|
iAdjVert, m_baseVert[iAdjVert].x, m_baseVert[iAdjVert].y, m_baseVert[iAdjVert].z,
|
||
|
pt3Normal1.x, pt3Normal1.y, pt3Normal1.z );
|
||
|
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int iVert = 0; iVert < cVerts; iVert++)
|
||
|
{
|
||
|
Point3 pt3Vertex1 = pmesh->getVert(iVert);
|
||
|
Point3 v1 = pt3Vertex1 * mat3ObjectTM;
|
||
|
|
||
|
int iAdjVert = m_baseVertCount + iVert;
|
||
|
|
||
|
if (Length( m_baseVert[iAdjVert] - v1) > 0.01)
|
||
|
{
|
||
|
Point3 pt3Vertex2 = pt3Vertex1;
|
||
|
pt3Vertex2.z = pt3Vertex2.z + 10.0;
|
||
|
pmesh->setVert( iVert, pt3Vertex2 );
|
||
|
|
||
|
Point3 pt3Normal1 = pmesh->getNormal(iVert);
|
||
|
pt3Normal1 = VectorTransform( mat3ObjectNTM, pt3Normal1 );
|
||
|
|
||
|
fprintf(m_pfile, "%5d %8.4f %8.4f %8.4f %9.6f %9.6f %9.6f\n",
|
||
|
iAdjVert, v1.x, v1.y, v1.z,
|
||
|
pt3Normal1.x, pt3Normal1.y, pt3Normal1.z );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
m_baseVertCount += cVerts;
|
||
|
|
||
|
fflush( m_pfile );
|
||
|
|
||
|
/*
|
||
|
if (deleteMesh)
|
||
|
delete pmesh;
|
||
|
*/
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
DumpModelTEP::~DumpModelTEP()
|
||
|
{
|
||
|
if (m_baseVert)
|
||
|
{
|
||
|
delete m_baseVert;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//========================================================================
|
||
|
// Utility functions for getting/setting the personal "node index" property.
|
||
|
// NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I
|
||
|
// NOTE: tried using an integer property.
|
||
|
// FURTHER NOTE: those properties seem to change randomly sometimes, so I'm
|
||
|
// implementing my own.
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
char szNodeName[VtaExportClass::MAX_NAME_CHARS];
|
||
|
int iNode;
|
||
|
} NAMEMAP;
|
||
|
const int MAX_NAMEMAP = 512;
|
||
|
static NAMEMAP g_rgnm[MAX_NAMEMAP];
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
//=============================================================
|
||
|
// Returns TRUE if a node should be ignored during tree traversal.
|
||
|
//
|
||
|
BOOL FUndesirableNode(INode *pnode)
|
||
|
{
|
||
|
// Get Node's underlying object, and object class name
|
||
|
Object *pobj = pnode->GetObjectRef();
|
||
|
|
||
|
// Don't care about lights, dummies, and cameras
|
||
|
if (pobj->SuperClassID() == CAMERA_CLASS_ID)
|
||
|
return TRUE;
|
||
|
if (pobj->SuperClassID() == LIGHT_CLASS_ID)
|
||
|
return TRUE;
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
// Actually, if it's not selected, pretend it doesn't exist!
|
||
|
//if (!pnode->Selected())
|
||
|
// return TRUE;
|
||
|
//return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//=============================================================
|
||
|
// Returns TRUE if a node has been marked as skippable
|
||
|
//
|
||
|
BOOL FNodeMarkedToSkip(INode *pnode)
|
||
|
{
|
||
|
return (::GetIndexOfINode(pnode) == VtaExportClass::UNDESIRABLE_NODE_MARKER);
|
||
|
}
|
||
|
|
||
|
|
||
|
//=============================================================
|
||
|
// Reduces a rotation to within the -2PI..2PI range.
|
||
|
//
|
||
|
static float FlReduceRotation(float fl)
|
||
|
{
|
||
|
while (fl >= TWOPI)
|
||
|
fl -= TWOPI;
|
||
|
while (fl <= -TWOPI)
|
||
|
fl += TWOPI;
|
||
|
return fl;
|
||
|
}
|