/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see .
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "sys/platform.h"
#include "Maya5.0/maya.h"
//#include "Maya6.0/maya.h" // must also change include directory in project from "MayaImport\Maya4.5\include" to "MayaImport\Maya6.0\include" (requires MSDev 7.1)
#include "MayaImport/exporter.h"
#include "MayaImport/maya_main.h"
idStr errorMessage;
bool initialized = false;
#define DEFAULT_ANIM_EPSILON 0.125f
#define DEFAULT_QUAT_EPSILON ( 1.0f / 8192.0f )
#define SLOP_VERTEX 0.01f // merge xyz coordinates this far apart
#define SLOP_TEXCOORD 0.001f // merge texture coordinates this far apart
const char *componentNames[ 6 ] = { "Tx", "Ty", "Tz", "Qx", "Qy", "Qz" };
idSys * sys = NULL;
idCommon * common = NULL;
idCVarSystem * cvarSystem = NULL;
idCVar * idCVar::staticVars = NULL;
/*
=================
MayaError
=================
*/
void MayaError( const char *fmt, ... ) {
va_list argptr;
char text[ 8192 ];
va_start( argptr, fmt );
idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
va_end( argptr );
throw idException( text );
}
/*
=================
FS_WriteFloatString
=================
*/
#define MAX_PRINT_MSG 4096
static int WriteFloatString( FILE *file, const char *fmt, ... ) {
long i;
unsigned long u;
double f;
char *str;
int index;
idStr tmp, format;
va_list argPtr;
va_start( argPtr, fmt );
index = 0;
while( *fmt ) {
switch( *fmt ) {
case '%':
format = "";
format += *fmt++;
while ( (*fmt >= '0' && *fmt <= '9') ||
*fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') {
format += *fmt++;
}
format += *fmt;
switch( *fmt ) {
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
f = va_arg( argPtr, double );
if ( format.Length() <= 2 ) {
// high precision floating point number without trailing zeros
sprintf( tmp, "%1.10f", f );
tmp.StripTrailing( '0' );
tmp.StripTrailing( '.' );
index += fprintf( file, "%s", tmp.c_str() );
}
else {
index += fprintf( file, format.c_str(), f );
}
break;
case 'd':
case 'i':
i = va_arg( argPtr, long );
index += fprintf( file, format.c_str(), i );
break;
case 'u':
u = va_arg( argPtr, unsigned long );
index += fprintf( file, format.c_str(), u );
break;
case 'o':
u = va_arg( argPtr, unsigned long );
index += fprintf( file, format.c_str(), u );
break;
case 'x':
u = va_arg( argPtr, unsigned long );
index += fprintf( file, format.c_str(), u );
break;
case 'X':
u = va_arg( argPtr, unsigned long );
index += fprintf( file, format.c_str(), u );
break;
case 'c':
i = va_arg( argPtr, long );
index += fprintf( file, format.c_str(), (char) i );
break;
case 's':
str = va_arg( argPtr, char * );
index += fprintf( file, format.c_str(), str );
break;
case '%':
index += fprintf( file, format.c_str() );
break;
default:
MayaError( "WriteFloatString: invalid format %s", format.c_str() );
break;
}
fmt++;
break;
case '\\':
fmt++;
switch( *fmt ) {
case 't':
index += fprintf( file, "\t" );
break;
case 'n':
index += fprintf( file, "\n" );
default:
MayaError( "WriteFloatString: unknown escape character \'%c\'", *fmt );
break;
}
fmt++;
break;
default:
index += fprintf( file, "%c", *fmt );
fmt++;
break;
}
}
va_end( argPtr );
return index;
}
/*
================
OSPathToRelativePath
takes a full OS path, as might be found in data from a media creation
program, and converts it to a qpath by stripping off directories
Returns false if the osPath tree doesn't match any of the existing
search paths.
================
*/
bool OSPathToRelativePath( const char *osPath, idStr &qpath, const char *game ) {
char *s, *base;
// skip a drive letter?
// search for anything with BASE_GAMEDIR in it
// Ase files from max may have the form of:
// "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
// which won't match any of our drive letter based search paths
base = (char *)strstr( osPath, BASE_GAMEDIR );
// _D3XP added mod support
if ( base == NULL && strlen(game) > 0 ) {
base = s = (char *)strstr( osPath, game );
while( s = strstr( s, game ) ) {
s += strlen( game );
if ( s[0] == '/' || s[0] == '\\' ) {
base = s;
}
}
}
if ( base ) {
s = strstr( base, "/" );
if ( !s ) {
s = strstr( base, "\\" );
}
if ( s ) {
qpath = s + 1;
return true;
}
}
common->Printf( "OSPathToRelativePath failed on %s\n", osPath );
qpath = osPath;
return false;
}
/*
===============
ConvertFromIdSpace
===============
*/
idMat3 ConvertFromIdSpace( const idMat3 &idmat ) {
idMat3 mat;
mat[ 0 ][ 0 ] = idmat[ 0 ][ 0 ];
mat[ 0 ][ 2 ] = -idmat[ 0 ][ 1 ];
mat[ 0 ][ 1 ] = idmat[ 0 ][ 2 ];
mat[ 1 ][ 0 ] = idmat[ 1 ][ 0 ];
mat[ 1 ][ 2 ] = -idmat[ 1 ][ 1 ];
mat[ 1 ][ 1 ] = idmat[ 1 ][ 2 ];
mat[ 2 ][ 0 ] = idmat[ 2 ][ 0 ];
mat[ 2 ][ 2 ] = -idmat[ 2 ][ 1 ];
mat[ 2 ][ 1 ] = idmat[ 2 ][ 2 ];
return mat;
}
/*
===============
ConvertFromIdSpace
===============
*/
idVec3 ConvertFromIdSpace( const idVec3 &idpos ) {
idVec3 pos;
pos.x = idpos.x;
pos.z = -idpos.y;
pos.y = idpos.z;
return pos;
}
/*
===============
ConvertToIdSpace
===============
*/
idMat3 ConvertToIdSpace( const idMat3 &mat ) {
idMat3 idmat;
idmat[ 0 ][ 0 ] = mat[ 0 ][ 0 ];
idmat[ 0 ][ 1 ] = -mat[ 0 ][ 2 ];
idmat[ 0 ][ 2 ] = mat[ 0 ][ 1 ];
idmat[ 1 ][ 0 ] = mat[ 1 ][ 0 ];
idmat[ 1 ][ 1 ] = -mat[ 1 ][ 2 ];
idmat[ 1 ][ 2 ] = mat[ 1 ][ 1 ];
idmat[ 2 ][ 0 ] = mat[ 2 ][ 0 ];
idmat[ 2 ][ 1 ] = -mat[ 2 ][ 2 ];
idmat[ 2 ][ 2 ] = mat[ 2 ][ 1 ];
return idmat;
}
/*
===============
ConvertToIdSpace
===============
*/
idVec3 ConvertToIdSpace( const idVec3 &pos ) {
idVec3 idpos;
idpos.x = pos.x;
idpos.y = -pos.z;
idpos.z = pos.y;
return idpos;
}
/*
===============
idVec
===============
*/
idVec3 idVec( const MFloatPoint &point ) {
return idVec3( point[ 0 ], point[ 1 ], point[ 2 ] );
}
/*
===============
idVec
===============
*/
idVec3 idVec( const MMatrix &matrix ) {
return idVec3( matrix[ 3 ][ 0 ], matrix[ 3 ][ 1 ], matrix[ 3 ][ 2 ] );
}
/*
===============
idMat
===============
*/
idMat3 idMat( const MMatrix &matrix ) {
int j, k;
idMat3 mat;
for( j = 0; j < 3; j++ ) {
for( k = 0; k < 3; k++ ) {
mat[ j ][ k ] = matrix[ j ][ k ];
}
}
return mat;
}
/*
===============
GetParent
===============
*/
MFnDagNode *GetParent( MFnDagNode *joint ) {
MStatus status;
MObject parentObject;
parentObject = joint->parent( 0, &status );
if ( !status && status.statusCode() == MStatus::kInvalidParameter ) {
return NULL;
}
while( !parentObject.hasFn( MFn::kTransform ) ) {
MFnDagNode parentNode( parentObject, &status );
if ( !status ) {
return NULL;
}
parentObject = parentNode.parent( 0, &status );
if ( !status && status.statusCode() == MStatus::kInvalidParameter ) {
return NULL;
}
}
MFnDagNode *parentNode;
parentNode = new MFnDagNode( parentObject, &status );
if ( !status ) {
delete parentNode;
return NULL;
}
return parentNode;
}
/*
==============================================================================================
idTokenizer
==============================================================================================
*/
/*
====================
idTokenizer::SetTokens
====================
*/
int idTokenizer::SetTokens( const char *buffer ) {
const char *cmd;
Clear();
// tokenize commandline
cmd = buffer;
while ( *cmd ) {
// skip whitespace
while( *cmd && isspace( *cmd ) ) {
cmd++;
}
if ( !*cmd ) {
break;
}
idStr ¤t = tokens.Alloc();
while( *cmd && !isspace( *cmd ) ) {
current += *cmd;
cmd++;
}
}
return tokens.Num();
}
/*
====================
idTokenizer::NextToken
====================
*/
const char *idTokenizer::NextToken( const char *errorstring ) {
if ( currentToken < tokens.Num() ) {
return tokens[ currentToken++ ];
}
if ( errorstring ) {
MayaError( "Error: %s", errorstring );
}
return NULL;
}
/*
==============================================================================================
idExportOptions
==============================================================================================
*/
/*
====================
idExportOptions::Reset
====================
*/
void idExportOptions::Reset( const char *commandline ) {
scale = 1.0f;
type = WRITE_MESH;
startframe = -1;
endframe = -1;
ignoreMeshes = false;
clearOrigin = false;
clearOriginAxis = false;
framerate = 24;
align = "";
rotate = 0.0f;
commandLine = commandline;
prefix = "";
jointThreshold = 0.05f;
ignoreScale = false;
xyzPrecision = DEFAULT_ANIM_EPSILON;
quatPrecision = DEFAULT_QUAT_EPSILON;
cycleStart = -1;
src.Clear();
dest.Clear();
tokens.SetTokens( commandline );
keepjoints.Clear();
renamejoints.Clear();
remapjoints.Clear();
exportgroups.Clear();
skipmeshes.Clear();
keepmeshes.Clear();
groups.Clear();
}
/*
====================
idExportOptions::idExportOptions
====================
*/
idExportOptions::idExportOptions( const char *commandline, const char *ospath ) {
idStr token;
idNamePair joints;
int i;
idAnimGroup *group;
idStr sourceDir;
idStr destDir;
Reset( commandline );
token = tokens.NextToken( "Missing export command" );
if ( token == "mesh" ) {
type = WRITE_MESH;
} else if ( token == "anim" ) {
type = WRITE_ANIM;
} else if ( token == "camera" ) {
type = WRITE_CAMERA;
} else {
MayaError( "Unknown export command '%s'", token.c_str() );
}
src = tokens.NextToken( "Missing source filename" );
dest = src;
for( token = tokens.NextToken(); token != ""; token = tokens.NextToken() ) {
if ( token == "-force" ) {
// skip
} else if ( token == "-game" ) {
// parse game name
game = tokens.NextToken( "Expecting game name after -game" );
} else if ( token == "-rename" ) {
// parse joint to rename
joints.from = tokens.NextToken( "Missing joint name for -rename. Usage: -rename [joint name] [new name]" );
joints.to = tokens.NextToken( "Missing new name for -rename. Usage: -rename [joint name] [new name]" );
renamejoints.Append( joints );
} else if ( token == "-prefix" ) {
prefix = tokens.NextToken( "Missing name for -prefix. Usage: -prefix [joint prefix]" );
} else if ( token == "-parent" ) {
// parse joint to reparent
joints.from = tokens.NextToken( "Missing joint name for -parent. Usage: -parent [joint name] [new parent]" );
joints.to = tokens.NextToken( "Missing new parent for -parent. Usage: -parent [joint name] [new parent]" );
remapjoints.Append( joints );
} else if ( !token.Icmp( "-sourcedir" ) ) {
// parse source directory
sourceDir = tokens.NextToken( "Missing filename for -sourcedir. Usage: -sourcedir [directory]" );
} else if ( !token.Icmp( "-destdir" ) ) {
// parse destination directory
destDir = tokens.NextToken( "Missing filename for -destdir. Usage: -destdir [directory]" );
} else if ( token == "-dest" ) {
// parse destination filename
dest = tokens.NextToken( "Missing filename for -dest. Usage: -dest [filename]" );
} else if ( token == "-range" ) {
// parse frame range to export
token = tokens.NextToken( "Missing start frame for -range. Usage: -range [start frame] [end frame]" );
startframe = atoi( token );
token = tokens.NextToken( "Missing end frame for -range. Usage: -range [start frame] [end frame]" );
endframe = atoi( token );
if ( startframe > endframe ) {
MayaError( "Start frame is greater than end frame." );
}
} else if ( !token.Icmp( "-cycleStart" ) ) {
// parse start frame of cycle
token = tokens.NextToken( "Missing cycle start frame for -cycleStart. Usage: -cycleStart [first frame of cycle]" );
cycleStart = atoi( token );
} else if ( token == "-scale" ) {
// parse scale
token = tokens.NextToken( "Missing scale amount for -scale. Usage: -scale [scale amount]" );
scale = atof( token );
} else if ( token == "-align" ) {
// parse align joint
align = tokens.NextToken( "Missing joint name for -align. Usage: -align [joint name]" );
} else if ( token == "-rotate" ) {
// parse angle rotation
token = tokens.NextToken( "Missing value for -rotate. Usage: -rotate [yaw]" );
rotate = -atof( token );
} else if ( token == "-nomesh" ) {
ignoreMeshes = true;
} else if ( token == "-clearorigin" ) {
clearOrigin = true;
clearOriginAxis = true;
} else if ( token == "-clearoriginaxis" ) {
clearOriginAxis = true;
} else if ( token == "-ignorescale" ) {
ignoreScale = true;
} else if ( token == "-xyzprecision" ) {
// parse quaternion precision
token = tokens.NextToken( "Missing value for -xyzprecision. Usage: -xyzprecision [precision]" );
xyzPrecision = atof( token );
if ( xyzPrecision < 0.0f ) {
MayaError( "Invalid value for -xyzprecision. Must be >= 0" );
}
} else if ( token == "-quatprecision" ) {
// parse quaternion precision
token = tokens.NextToken( "Missing value for -quatprecision. Usage: -quatprecision [precision]" );
quatPrecision = atof( token );
if ( quatPrecision < 0.0f ) {
MayaError( "Invalid value for -quatprecision. Must be >= 0" );
}
} else if ( token == "-jointthreshold" ) {
// parse joint threshold
token = tokens.NextToken( "Missing weight for -jointthreshold. Usage: -jointthreshold [minimum joint weight]" );
jointThreshold = atof( token );
} else if ( token == "-skipmesh" ) {
token = tokens.NextToken( "Missing name for -skipmesh. Usage: -skipmesh [name of mesh to skip]" );
skipmeshes.AddUnique( token );
} else if ( token == "-keepmesh" ) {
token = tokens.NextToken( "Missing name for -keepmesh. Usage: -keepmesh [name of mesh to keep]" );
keepmeshes.AddUnique( token );
} else if ( token == "-jointgroup" ) {
token = tokens.NextToken( "Missing name for -jointgroup. Usage: -jointgroup [group name] [joint1] [joint2]...[joint n]" );
group = groups.Ptr();
for( i = 0; i < groups.Num(); i++, group++ ) {
if ( group->name == token ) {
break;
}
}
if ( i >= groups.Num() ) {
// create a new group
group = &groups.Alloc();
group->name = token;
}
while( tokens.TokenAvailable() ) {
token = tokens.NextToken();
if ( token[ 0 ] == '-' ) {
tokens.UnGetToken();
break;
}
group->joints.AddUnique( token );
}
} else if ( token == "-group" ) {
// add the list of groups to export (these don't affect the hierarchy)
while( tokens.TokenAvailable() ) {
token = tokens.NextToken();
if ( token[ 0 ] == '-' ) {
tokens.UnGetToken();
break;
}
group = groups.Ptr();
for( i = 0; i < groups.Num(); i++, group++ ) {
if ( group->name == token ) {
break;
}
}
if ( i >= groups.Num() ) {
MayaError( "Unknown group '%s'", token.c_str() );
}
exportgroups.AddUnique( group );
}
} else if ( token == "-keep" ) {
// add joints that are kept whether they're used by a mesh or not
while( tokens.TokenAvailable() ) {
token = tokens.NextToken();
if ( token[ 0 ] == '-' ) {
tokens.UnGetToken();
break;
}
keepjoints.AddUnique( token );
}
} else {
MayaError( "Unknown option '%s'", token.c_str() );
}
}
token = src;
src = ospath;
src.BackSlashesToSlashes();
src.AppendPath( sourceDir );
src.AppendPath( token );
token = dest;
dest = ospath;
dest.BackSlashesToSlashes();
dest.AppendPath( destDir );
dest.AppendPath( token );
// Maya only accepts unix style path separators
src.BackSlashesToSlashes();
dest.BackSlashesToSlashes();
if ( skipmeshes.Num() && keepmeshes.Num() ) {
MayaError( "Can't use -keepmesh and -skipmesh together." );
}
}
/*
====================
idExportOptions::jointInExportGroup
====================
*/
bool idExportOptions::jointInExportGroup( const char *jointname ) {
int i;
int j;
idAnimGroup *group;
if ( !exportgroups.Num() ) {
// if we don't have any groups specified as export then export every joint
return true;
}
// search through all exported groups to see if this joint is exported
for( i = 0; i < exportgroups.Num(); i++ ) {
group = exportgroups[ i ];
for( j = 0; j < group->joints.Num(); j++ ) {
if ( group->joints[ j ] == jointname ) {
return true;
}
}
}
return false;
}
/*
==============================================================================
idExportJoint
==============================================================================
*/
idExportJoint::idExportJoint() {
index = 0;
exportNum = 0;
mayaNode.SetOwner( this );
exportNode.SetOwner( this );
dagnode = NULL;
t = vec3_zero;
wm = mat3_default;
bindpos = vec3_zero;
bindmat = mat3_default;
keep = false;
scale = 1.0f;
invscale = 1.0f;
animBits = 0;
firstComponent = 0;
baseFrame.q.Set( 0.0f, 0.0f, 0.0f );
baseFrame.t.Zero();
}
idExportJoint &idExportJoint::operator=( const idExportJoint &other ) {
name = other.name;
realname = other.realname;
longname = other.longname;
index = other.index;
exportNum = other.exportNum;
keep = other.keep;
scale = other.scale;
invscale = other.invscale;
dagnode = other.dagnode;
mayaNode = other.mayaNode;
exportNode = other.exportNode;
t = other.t;
idt = other.idt;
wm = other.wm;
idwm = other.idwm;
bindpos = other.bindpos;
bindmat = other.bindmat;
animBits = other.animBits;
firstComponent = other.firstComponent;
baseFrame = other.baseFrame;
mayaNode.SetOwner( this );
exportNode.SetOwner( this );
return *this;
}
/*
==============================================================================
idExportMesh
==============================================================================
*/
void idExportMesh::ShareVerts( void ) {
int i, j, k;
exportVertex_t vert;
idList v;
v = verts;
verts.Clear();
for( i = 0; i < tris.Num(); i++ ) {
for( j = 0; j < 3; j++ ) {
vert = v[ tris[ i ].indexes[ j ] ];
vert.texCoords[ 0 ] = uv[ i ].uv[ j ][ 0 ];
vert.texCoords[ 1 ] = 1.0f - uv[ i ].uv[ j ][ 1 ];
for( k = 0; k < verts.Num(); k++ ) {
if ( vert.numWeights != verts[ k ].numWeights ) {
continue;
}
if ( vert.startweight != verts[ k ].startweight ) {
continue;
}
if ( !vert.pos.Compare( verts[ k ].pos, SLOP_VERTEX ) ) {
continue;
}
if ( !vert.texCoords.Compare( verts[ k ].texCoords, SLOP_TEXCOORD ) ) {
continue;
}
break;
}
if ( k < verts.Num() ) {
tris[ i ].indexes[ j ] = k;
} else {
tris[ i ].indexes[ j ] = verts.Append( vert );
}
}
}
}
void idExportMesh::Merge( idExportMesh *mesh ) {
int i;
int numverts;
int numtris;
int numweights;
int numuvs;
// merge name
sprintf( name, "%s, %s", name.c_str(), mesh->name.c_str() );
// merge verts
numverts = verts.Num();
verts.SetNum( numverts + mesh->verts.Num() );
for( i = 0; i < mesh->verts.Num(); i++ ) {
verts[ numverts + i ] = mesh->verts[ i ];
verts[ numverts + i ].startweight += weights.Num();
}
// merge triangles
numtris = tris.Num();
tris.SetNum( numtris + mesh->tris.Num() );
for( i = 0; i < mesh->tris.Num(); i++ ) {
tris[ numtris + i ].indexes[ 0 ] = mesh->tris[ i ].indexes[ 0 ] + numverts;
tris[ numtris + i ].indexes[ 1 ] = mesh->tris[ i ].indexes[ 1 ] + numverts;
tris[ numtris + i ].indexes[ 2 ] = mesh->tris[ i ].indexes[ 2 ] + numverts;
}
// merge weights
numweights = weights.Num();
weights.SetNum( numweights + mesh->weights.Num() );
for( i = 0; i < mesh->weights.Num(); i++ ) {
weights[ numweights + i ] = mesh->weights[ i ];
}
// merge uvs
numuvs = uv.Num();
uv .SetNum( numuvs + mesh->uv.Num() );
for( i = 0; i < mesh->uv.Num(); i++ ) {
uv[ numuvs + i ] = mesh->uv[ i ];
}
}
void idExportMesh::GetBounds( idBounds &bounds ) const {
int i;
int j;
idVec3 pos;
const exportWeight_t *weight;
const exportVertex_t *vert;
bounds.Clear();
weight = weights.Ptr();
vert = verts.Ptr();
for( i = 0; i < verts.Num(); i++, vert++ ) {
pos.Zero();
weight = &weights[ vert->startweight ];
for( j = 0; j < vert->numWeights; j++, weight++ ) {
pos += weight->jointWeight * ( weight->joint->idwm * weight->offset + weight->joint->idt );
}
bounds.AddPoint( pos );
}
}
/*
==============================================================================
idExportModel
==============================================================================
*/
/*
====================
idExportModel::idExportModel
====================
*/
ID_INLINE idExportModel::idExportModel() {
export_joints = 0;
skipjoints = 0;
frameRate = 24;
numFrames = 0;
exportOrigin = NULL;
}
/*
====================
idExportModel::~idExportModel
====================
*/
ID_INLINE idExportModel::~idExportModel() {
meshes.DeleteContents( true );
}
idExportJoint *idExportModel::FindJointReal( const char *name ) {
idExportJoint *joint;
int i;
joint = joints.Ptr();
for( i = 0; i < joints.Num(); i++, joint++ ) {
if ( joint->realname == name ) {
return joint;
}
}
return NULL;
}
idExportJoint *idExportModel::FindJoint( const char *name ) {
idExportJoint *joint;
int i;
joint = joints.Ptr();
for( i = 0; i < joints.Num(); i++, joint++ ) {
if ( joint->name == name ) {
return joint;
}
}
return NULL;
}
bool idExportModel::WriteMesh( const char *filename, idExportOptions &options ) {
int i, j;
int numMeshes;
idExportMesh *mesh;
idExportJoint *joint;
idExportJoint *parent;
idExportJoint *sibling;
FILE *file;
const char *parentName;
int parentNum;
idList jointList;
file = fopen( filename, "w" );
if ( !file ) {
return false;
}
for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
jointList.Append( joint );
}
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
sibling = joint->exportNode.GetSibling();
while( sibling ) {
if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) {
joint->exportNode.MakeSiblingAfter( sibling->exportNode );
sibling = joint->exportNode.GetSibling();
} else {
sibling = sibling->exportNode.GetSibling();
}
}
}
jointList.Clear();
for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
joint->exportNum = jointList.Append( joint );
}
numMeshes = 0;
if ( !options.ignoreMeshes ) {
for( i = 0; i < meshes.Num(); i++ ) {
if ( meshes[ i ]->keep ) {
numMeshes++;
}
}
}
// write version info
WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
// write joints
WriteFloatString( file, "numJoints %d\n", jointList.Num() );
WriteFloatString( file, "numMeshes %d\n\n", numMeshes );
WriteFloatString( file, "joints {\n" );
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
parent = joint->exportNode.GetParent();
if ( parent ) {
parentNum = parent->exportNum;
parentName = parent->name.c_str();
} else {
parentNum = -1;
parentName = "";
}
idCQuat bindQuat = joint->bindmat.ToQuat().ToCQuat();
WriteFloatString( file, "\t\"%s\"\t%d ( %f %f %f ) ( %f %f %f )\t\t// %s\n", joint->name.c_str(), parentNum,
joint->bindpos.x, joint->bindpos.y, joint->bindpos.z, bindQuat[ 0 ], bindQuat[ 1 ], bindQuat[ 2 ], parentName );
}
WriteFloatString( file, "}\n" );
// write meshes
for( i = 0; i < meshes.Num(); i++ ) {
mesh = meshes[ i ];
if ( !mesh->keep ) {
continue;
}
WriteFloatString( file, "\nmesh {\n" );
WriteFloatString( file, "\t// meshes: %s\n", mesh->name.c_str() );
WriteFloatString( file, "\tshader \"%s\"\n", mesh->shader.c_str() );
WriteFloatString( file, "\n\tnumverts %d\n", mesh->verts.Num() );
for( j = 0; j < mesh->verts.Num(); j++ ) {
WriteFloatString( file, "\tvert %d ( %f %f ) %d %d\n", j, mesh->verts[ j ].texCoords[ 0 ], mesh->verts[ j ].texCoords[ 1 ],
mesh->verts[ j ].startweight, mesh->verts[ j ].numWeights );
}
WriteFloatString( file, "\n\tnumtris %d\n", mesh->tris.Num() );
for( j = 0; j < mesh->tris.Num(); j++ ) {
WriteFloatString( file, "\ttri %d %d %d %d\n", j, mesh->tris[ j ].indexes[ 2 ], mesh->tris[ j ].indexes[ 1 ], mesh->tris[ j ].indexes[ 0 ] );
}
WriteFloatString( file, "\n\tnumweights %d\n", mesh->weights.Num() );
for( j = 0; j < mesh->weights.Num(); j++ ) {
exportWeight_t *weight;
weight = &mesh->weights[ j ];
WriteFloatString( file, "\tweight %d %d %f ( %f %f %f )\n", j,
weight->joint->exportNum, weight->jointWeight, weight->offset.x, weight->offset.y, weight->offset.z );
}
WriteFloatString( file, "}\n" );
}
fclose( file );
return true;
}
bool idExportModel::WriteAnim( const char *filename, idExportOptions &options ) {
int i, j;
idExportJoint *joint;
idExportJoint *parent;
idExportJoint *sibling;
jointFrame_t *frame;
FILE *file;
int numAnimatedComponents;
idList jointList;
file = fopen( filename, "w" );
if ( !file ) {
return false;
}
for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
jointList.Append( joint );
}
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
sibling = joint->exportNode.GetSibling();
while( sibling ) {
if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) {
joint->exportNode.MakeSiblingAfter( sibling->exportNode );
sibling = joint->exportNode.GetSibling();
} else {
sibling = sibling->exportNode.GetSibling();
}
}
}
jointList.Clear();
for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
joint->exportNum = jointList.Append( joint );
}
numAnimatedComponents = 0;
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
joint->exportNum = i;
joint->baseFrame = frames[ 0 ][ joint->index ];
joint->animBits = 0;
for( j = 1; j < numFrames; j++ ) {
frame = &frames[ j ][ joint->index ];
if ( fabs( frame->t[ 0 ] - joint->baseFrame.t[ 0 ] ) > options.xyzPrecision ) {
joint->animBits |= ANIM_TX;
}
if ( fabs( frame->t[ 1 ] - joint->baseFrame.t[ 1 ] ) > options.xyzPrecision ) {
joint->animBits |= ANIM_TY;
}
if ( fabs( frame->t[ 2 ] - joint->baseFrame.t[ 2 ] ) > options.xyzPrecision ) {
joint->animBits |= ANIM_TZ;
}
if ( fabs( frame->q[ 0 ] - joint->baseFrame.q[ 0 ] ) > options.quatPrecision ) {
joint->animBits |= ANIM_QX;
}
if ( fabs( frame->q[ 1 ] - joint->baseFrame.q[ 1 ] ) > options.quatPrecision ) {
joint->animBits |= ANIM_QY;
}
if ( fabs( frame->q[ 2 ] - joint->baseFrame.q[ 2 ] ) > options.quatPrecision ) {
joint->animBits |= ANIM_QZ;
}
if ( ( joint->animBits & 63 ) == 63 ) {
break;
}
}
if ( joint->animBits ) {
joint->firstComponent = numAnimatedComponents;
for( j = 0; j < 6; j++ ) {
if ( joint->animBits & BIT( j ) ) {
numAnimatedComponents++;
}
}
}
}
// write version info
WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
WriteFloatString( file, "numFrames %d\n", numFrames );
WriteFloatString( file, "numJoints %d\n", jointList.Num() );
WriteFloatString( file, "frameRate %d\n", frameRate );
WriteFloatString( file, "numAnimatedComponents %d\n", numAnimatedComponents );
// write out the hierarchy
WriteFloatString( file, "\nhierarchy {\n" );
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
parent = joint->exportNode.GetParent();
if ( parent ) {
WriteFloatString( file, "\t\"%s\"\t%d %d %d\t// %s", joint->name.c_str(), parent->exportNum, joint->animBits, joint->firstComponent, parent->name.c_str() );
} else {
WriteFloatString( file, "\t\"%s\"\t-1 %d %d\t//", joint->name.c_str(), joint->animBits, joint->firstComponent );
}
if ( !joint->animBits ) {
WriteFloatString( file, "\n" );
} else {
WriteFloatString( file, " ( " );
for( j = 0; j < 6; j++ ) {
if ( joint->animBits & BIT( j ) ) {
WriteFloatString( file, "%s ", componentNames[ j ] );
}
}
WriteFloatString( file, ")\n" );
}
}
WriteFloatString( file, "}\n" );
// write the frame bounds
WriteFloatString( file, "\nbounds {\n" );
for( i = 0; i < numFrames; i++ ) {
WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", bounds[ i ][ 0 ].x, bounds[ i ][ 0 ].y, bounds[ i ][ 0 ].z, bounds[ i ][ 1 ].x, bounds[ i ][ 1 ].y, bounds[ i ][ 1 ].z );
}
WriteFloatString( file, "}\n" );
// write the base frame
WriteFloatString( file, "\nbaseframe {\n" );
for( i = 0; i < jointList.Num(); i++ ) {
joint = jointList[ i ];
WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", joint->baseFrame.t[ 0 ], joint->baseFrame.t[ 1 ], joint->baseFrame.t[ 2 ],
joint->baseFrame.q[ 0 ], joint->baseFrame.q[ 1 ], joint->baseFrame.q[ 2 ] );
}
WriteFloatString( file, "}\n" );
// write the frames
for( i = 0; i < numFrames; i++ ) {
WriteFloatString( file, "\nframe %d {\n", i );
for( j = 0; j < jointList.Num(); j++ ) {
joint = jointList[ j ];
frame = &frames[ i ][ joint->index ];
if ( joint->animBits ) {
WriteFloatString( file, "\t" );
if ( joint->animBits & ANIM_TX ) {
WriteFloatString( file, " %f", frame->t[ 0 ] );
}
if ( joint->animBits & ANIM_TY ) {
WriteFloatString( file, " %f", frame->t[ 1 ] );
}
if ( joint->animBits & ANIM_TZ ) {
WriteFloatString( file, " %f", frame->t[ 2 ] );
}
if ( joint->animBits & ANIM_QX ) {
WriteFloatString( file, " %f", frame->q[ 0 ] );
}
if ( joint->animBits & ANIM_QY ) {
WriteFloatString( file, " %f", frame->q[ 1 ] );
}
if ( joint->animBits & ANIM_QZ ) {
WriteFloatString( file, " %f", frame->q[ 2 ] );
}
WriteFloatString( file, "\n" );
}
}
WriteFloatString( file, "}\n" );
}
fclose( file );
return true;
}
bool idExportModel::WriteCamera( const char *filename, idExportOptions &options ) {
int i;
FILE *file;
file = fopen( filename, "w" );
if ( !file ) {
return false;
}
// write version info
WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
WriteFloatString( file, "numFrames %d\n", camera.Num() );
WriteFloatString( file, "frameRate %d\n", frameRate );
WriteFloatString( file, "numCuts %d\n", cameraCuts.Num() );
// write out the cuts
WriteFloatString( file, "\ncuts {\n" );
for( i = 0; i < cameraCuts.Num(); i++ ) {
WriteFloatString( file, "\t%d\n", cameraCuts[ i ] );
}
WriteFloatString( file, "}\n" );
// write out the frames
WriteFloatString( file, "\ncamera {\n" );
cameraFrame_t *frame = camera.Ptr();
for( i = 0; i < camera.Num(); i++, frame++ ) {
WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f ) %f\n", frame->t.x, frame->t.y, frame->t.z, frame->q[ 0 ], frame->q[ 1 ], frame->q[ 2 ], frame->fov );
}
WriteFloatString( file, "}\n" );
fclose( file );
return true;
}
/*
==============================================================================
Maya
==============================================================================
*/
/*
===============
idMayaExport::~idMayaExport
===============
*/
idMayaExport::~idMayaExport() {
FreeDagNodes();
// free up the file in Maya
MFileIO::newFile( true );
}
/*
===============
idMayaExport::TimeForFrame
===============
*/
float idMayaExport::TimeForFrame( int num ) const {
MTime time;
// set time unit to 24 frames per second
time.setUnit( MTime::kFilm );
time.setValue( num );
return time.as( MTime::kSeconds );
}
/*
===============
idMayaExport::GetMayaFrameNum
===============
*/
int idMayaExport::GetMayaFrameNum( int num ) const {
int frameNum;
if ( options.cycleStart > options.startframe ) {
// in cycles, the last frame is a duplicate of the first frame, so with cycleStart we need to
// duplicate one of the interior frames instead and chop off the first frame.
frameNum = options.cycleStart + num;
if ( frameNum > options.endframe ) {
frameNum -= options.endframe - options.startframe;
}
if ( frameNum < options.startframe ) {
frameNum = options.startframe + 1;
}
} else {
frameNum = options.startframe + num;
if ( frameNum > options.endframe ) {
frameNum -= options.endframe + 1 - options.startframe;
}
if ( frameNum < options.startframe ) {
frameNum = options.startframe;
}
}
return frameNum;
}
/*
===============
idMayaExport::SetFrame
===============
*/
void idMayaExport::SetFrame( int num ) {
MTime time;
int frameNum;
frameNum = GetMayaFrameNum( num );
// set time unit to 24 frames per second
time.setUnit( MTime::kFilm );
time.setValue( frameNum );
MGlobal::viewFrame( time );
}
/*
===============
idMayaExport::PruneJoints
===============
*/
void idMayaExport::PruneJoints( idStrList &keepjoints, idStr &prefix ) {
int i;
int j;
idExportMesh *mesh;
idExportJoint *joint;
idExportJoint *joint2;
idExportJoint *parent;
int num_weights;
// if we don't have any joints specified to keep, mark the ones used by the meshes as keep
if ( !keepjoints.Num() && !prefix.Length() ) {
if ( !model.meshes.Num() || options.ignoreMeshes ) {
// export all joints
joint = model.joints.Ptr();
for( i = 0; i < model.joints.Num(); i++, joint++ ) {
joint->keep = true;
}
} else {
for( i = 0; i < model.meshes.Num(); i++, mesh++ ) {
mesh = model.meshes[ i ];
for( j = 0; j < mesh->weights.Num(); j++ ) {
mesh->weights[ j ].joint->keep = true;
}
}
}
} else {
// mark the joints to keep
for( i = 0; i < keepjoints.Num(); i++ ) {
joint = model.FindJoint( keepjoints[ i ] );
if ( joint ) {
joint->keep = true;
}
}
// count valid meshes
for( i = 0; i < model.meshes.Num(); i++ ) {
mesh = model.meshes[ i ];
num_weights = 0;
for( j = 0; j < mesh->weights.Num(); j++ ) {
if ( mesh->weights[ j ].joint->keep ) {
num_weights++;
} else if ( prefix.Length() && !mesh->weights[ j ].joint->realname.Cmpn( prefix, prefix.Length() ) ) {
// keep the joint if it's used by the mesh and it has the right prefix
mesh->weights[ j ].joint->keep = true;
num_weights++;
}
}
if ( num_weights != mesh->weights.Num() ) {
mesh->keep = false;
}
}
}
// find all joints aren't exported and reparent joint's children
model.export_joints = 0;
joint = model.joints.Ptr();
for( i = 0; i < model.joints.Num(); i++, joint++ ) {
if ( !joint->keep ) {
joint->exportNode.RemoveFromHierarchy();
} else {
joint->index = model.export_joints;
model.export_joints++;
// make sure we are parented to an exported joint
for( parent = joint->exportNode.GetParent(); parent != NULL; parent = parent->exportNode.GetParent() ) {
if ( parent->keep ) {
break;
}
}
if ( parent != NULL ) {
joint->exportNode.ParentTo( parent->exportNode );
} else {
joint->exportNode.ParentTo( model.exportHead );
}
}
}
// check if we have any duplicate joint names
for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
if ( !joint->keep ) {
MayaError( "Non-kept joint in export tree ('%s')", joint->name.c_str() );
}
for( joint2 = model.exportHead.GetNext(); joint2 != NULL; joint2 = joint2->exportNode.GetNext() ) {
if ( ( joint2 != joint ) && ( joint2->name == joint->name ) ) {
MayaError( "Two joints found with the same name ('%s')", joint->name.c_str() );
}
}
}
}
/*
===============
idMayaExport::FreeDagNodes
===============
*/
void idMayaExport::FreeDagNodes( void ) {
int i;
for( i = 0; i < model.joints.Num(); i++ ) {
delete model.joints[ i ].dagnode;
model.joints[ i ].dagnode = NULL;
}
}
/*
===============
idMayaExport::GetBindPose
===============
*/
void idMayaExport::GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ) {
MStatus status;
MFnDependencyNode fnJoint( jointNode );
MObject aBindPose = fnJoint.attribute( "bindPose", &status );
joint->bindpos = vec3_zero;
joint->bindmat = mat3_default;
if ( MS::kSuccess == status ) {
unsigned ii;
unsigned jointIndex;
unsigned connLength;
MPlugArray connPlugs;
MPlug pBindPose( jointNode, aBindPose );
pBindPose.connectedTo( connPlugs, false, true );
connLength = connPlugs.length();
for( ii = 0; ii < connLength; ++ii ) {
if ( connPlugs[ ii ].node().apiType() == MFn::kDagPose ) {
MObject aMember = connPlugs[ ii ].attribute();
MFnAttribute fnAttr( aMember );
if ( fnAttr.name() == "worldMatrix" ) {
jointIndex = connPlugs[ ii ].logicalIndex();
MFnDependencyNode nDagPose( connPlugs[ ii ].node() );
// construct plugs for this joint's world matrix
MObject aWorldMatrix = nDagPose.attribute( "worldMatrix" );
MPlug pWorldMatrix( connPlugs[ ii ].node(), aWorldMatrix );
pWorldMatrix.selectAncestorLogicalIndex( jointIndex, aWorldMatrix );
// get the world matrix data
MObject worldMatrix;
MStatus status = pWorldMatrix.getValue( worldMatrix );
if ( MS::kSuccess != status ) {
// Problem retrieving world matrix
return;
}
MFnMatrixData dMatrix( worldMatrix );
MMatrix wMatrix = dMatrix.matrix( &status );
joint->bindmat = ConvertToIdSpace( idMat( wMatrix ) );
joint->bindpos = ConvertToIdSpace( idVec( wMatrix ) ) * scale;
if ( !options.ignoreScale ) {
joint->bindpos *= joint->scale;
} else {
joint->bindmat[ 0 ].Normalize();
joint->bindmat[ 1 ].Normalize();
joint->bindmat[ 2 ].Normalize();
}
return;
}
}
}
}
}
/*
===============
idMayaExport::GetLocalTransform
===============
*/
void idMayaExport::GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ) {
MStatus status;
MDagPath dagPath;
pos.Zero();
mat.Identity();
if ( !joint->dagnode ) {
return;
}
status = joint->dagnode->getPath( dagPath );
if ( !status ) {
return;
}
MObject transformNode = dagPath.transform( &status );
if ( !status && ( status.statusCode () == MStatus::kInvalidParameter ) ) {
return;
}
MFnDagNode transform( transformNode, &status );
if ( !status ) {
return;
}
pos = idVec( transform.transformationMatrix() );
mat = idMat( transform.transformationMatrix() );
}
/*
===============
idMayaExport::GetWorldTransform
===============
*/
void idMayaExport::GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ) {
idExportJoint *parent;
GetLocalTransform( joint, pos, mat );
mat.OrthoNormalizeSelf();
pos *= scale;
parent = joint->mayaNode.GetParent();
if ( parent ) {
idVec3 parentpos;
idMat3 parentmat;
GetWorldTransform( parent, parentpos, parentmat, scale );
pos = parentpos + ( parentmat * ( pos * parent->scale ) );
mat = mat * parentmat;
}
}
/*
===============
idMayaExport::CreateJoints
===============
*/
void idMayaExport::CreateJoints( float scale ) {
int i;
int j;
idExportJoint *joint;
idExportJoint *parent;
MStatus status;
MDagPath dagPath;
MFnDagNode *parentNode;
SetFrame( 0 );
// create an initial list with all of the transformable nodes in the scene
MItDag dagIterator( MItDag::kDepthFirst, MFn::kTransform, &status );
for ( ; !dagIterator.isDone(); dagIterator.next() ) {
status = dagIterator.getPath( dagPath );
if ( !status ) {
MayaError( "CreateJoints: MItDag::getPath failed (%s)", status.errorString().asChar() );
continue;
}
joint = &model.joints.Alloc();
joint->index = model.joints.Num() - 1;
joint->dagnode = new MFnDagNode( dagPath, &status );
if ( !status ) {
MayaError( "CreateJoints: MFnDagNode constructor failed (%s)", status.errorString().asChar() );
continue;
}
joint->name = joint->dagnode->name().asChar();
joint->realname = joint->name;
}
// allocate an extra joint in case we need to add an origin later
model.exportOrigin = &model.joints.Alloc();
model.exportOrigin->index = model.joints.Num() - 1;
// create scene hierarchy
joint = model.joints.Ptr();
for( i = 0; i < model.joints.Num(); i++, joint++ ) {
if ( !joint->dagnode ) {
continue;
}
joint->mayaNode.ParentTo( model.mayaHead );
joint->exportNode.ParentTo( model.exportHead );
parentNode = GetParent( joint->dagnode );
if ( parentNode ) {
// find the parent joint in our jointlist
for( j = 0; j < model.joints.Num(); j++ ) {
if ( !model.joints[ j ].dagnode ) {
continue;
}
if ( model.joints[ j ].dagnode->name() == parentNode->name() ) {
joint->mayaNode.ParentTo( model.joints[ j ].mayaNode );
joint->exportNode.ParentTo( model.joints[ j ].exportNode );
break;
}
}
delete parentNode;
}
// create long name
parent = joint->mayaNode.GetParent();
if ( parent ) {
joint->longname = parent->longname + "/" + joint->name;
} else {
joint->longname = joint->name;
}
// get the joint's scale
GetLocalTransform( &model.joints[ i ], joint->t, joint->wm );
joint->scale = joint->wm[ 0 ].Length();
if ( parent ) {
joint->scale *= parent->scale;
if ( joint->scale != 0 ) {
joint->invscale = 1.0f / joint->scale;
} else {
joint->invscale = 0;
}
}
joint->dagnode->getPath( dagPath );
GetBindPose( dagPath.node( &status ), joint, scale );
}
}
/*
===============
idMayaExport::RenameJoints
===============
*/
void idMayaExport::RenameJoints( idList &renamejoints, idStr &prefix ) {
int i;
idExportJoint *joint;
// rename joints that match the prefix
if ( prefix.Length() ) {
joint = model.joints.Ptr();
for( i = 0; i < model.joints.Num(); i++, joint++ ) {
if ( !joint->name.Cmpn( prefix, prefix.Length() ) ) {
// remove the prefix from the name
joint->name = joint->name.Right( joint->name.Length() - prefix.Length() );
}
}
}
// rename joints if necessary
for( i = 0; i < renamejoints.Num(); i++ ) {
joint = model.FindJoint( renamejoints[ i ].from );
if ( joint ) {
joint->name = renamejoints[ i ].to;
}
}
}
/*
===============
idMayaExport::RemapParents
===============
*/
bool idMayaExport::RemapParents( idList &remapjoints ) {
int i;
idExportJoint *joint;
idExportJoint *parent;
idExportJoint *origin;
idExportJoint *sibling;
for( i = 0; i < remapjoints.Num(); i++ ) {
// find joint to reparent
joint = model.FindJoint( remapjoints[ i ].from );
if ( !joint ) {
// couldn't find joint, fail
MayaError( "Couldn't find joint '%s' to reparent\n", remapjoints[ i ].from.c_str() );
}
// find new parent joint
parent = model.FindJoint( remapjoints[ i ].to );
if ( !parent ) {
// couldn't find parent, fail
MayaError( "Couldn't find joint '%s' to be new parent for '%s'\n", remapjoints[ i ].to.c_str(), remapjoints[ i ].from.c_str() );
}
if ( parent->exportNode.ParentedBy( joint->exportNode ) ) {
MayaError( "Joint '%s' is a child of joint '%s' and can't become the parent.", joint->name.c_str(), parent->name.c_str() );
}
joint->exportNode.ParentTo( parent->exportNode );
}
// if we have an origin, make it the first node in the export list, otherwise add one
origin = model.FindJoint( "origin" );
if ( !origin ) {
origin = model.exportOrigin;
origin->dagnode = NULL;
origin->name = "origin";
origin->realname = "origin";
origin->bindmat.Identity();
origin->bindpos.Zero();
}
origin->exportNode.ParentTo( model.exportHead );
// force the joint to be kept
origin->keep = true;
// make all root joints children of the origin joint
joint = model.exportHead.GetChild();
while( joint ) {
sibling = joint->exportNode.GetSibling();
if ( joint != origin ) {
joint->exportNode.ParentTo( origin->exportNode );
}
joint = sibling;
}
return true;
}
/*
===============
idMayaExport::FindShader
Find the shading node for the given shading group set node.
===============
*/
MObject idMayaExport::FindShader( MObject& setNode ) {
MStatus status;
MFnDependencyNode fnNode( setNode );
MPlug shaderPlug;
shaderPlug = fnNode.findPlug( "surfaceShader" );
if ( !shaderPlug.isNull() ) {
MPlugArray connectedPlugs;
bool asSrc = false;
bool asDst = true;
shaderPlug.connectedTo( connectedPlugs, asDst, asSrc, &status );
if ( connectedPlugs.length() != 1 ) {
MayaError( "FindShader: Error getting shader (%s)", status.errorString().asChar() );
} else {
return connectedPlugs[ 0 ].node();
}
}
return MObject::kNullObj;
}
/*
===============
idMayaExport::GetTextureForMesh
Find the texture files that apply to the color of each polygon of
a selected shape if the shape has its polygons organized into sets.
===============
*/
void idMayaExport::GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ) {
MStatus status;
MDagPath path;
int i;
int instanceNum;
status = dagNode.getPath( path );
if ( !status ) {
return;
}
path.extendToShape();
// If the shape is instanced then we need to determine which
// instance this path refers to.
//
instanceNum = 0;
if ( path.isInstanced() ) {
instanceNum = path.instanceNumber();
}
// Get a list of all sets pertaining to the selected shape and the
// members of those sets.
//
MFnMesh fnMesh( path );
MObjectArray sets;
MObjectArray comps;
status = fnMesh.getConnectedSetsAndMembers( instanceNum, sets, comps, true );
if ( !status ) {
MayaError( "GetTextureForMesh: MFnMesh::getConnectedSetsAndMembers failed (%s)", status.errorString().asChar() );
}
// Loop through all the sets. If the set is a polygonal set, find the
// shader attached to the and print out the texture file name for the
// set along with the polygons in the set.
//
for ( i = 0; i < ( int )sets.length(); i++ ) {
MObject set = sets[i];
MObject comp = comps[i];
MFnSet fnSet( set, &status );
if ( status == MS::kFailure ) {
MayaError( "GetTextureForMesh: MFnSet constructor failed (%s)", status.errorString().asChar() );
continue;
}
// Make sure the set is a polygonal set. If not, continue.
MItMeshPolygon piter(path, comp, &status);
if (status == MS::kFailure) {
continue;
}
// Find the texture that is applied to this set. First, get the
// shading node connected to the set. Then, if there is an input
// attribute called "color", search upstream from it for a texture
// file node.
//
MObject shaderNode = FindShader( set );
if ( shaderNode == MObject::kNullObj ) {
continue;
}
MPlug colorPlug = MFnDependencyNode(shaderNode).findPlug("color", &status);
if ( status == MS::kFailure ) {
continue;
}
MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture,
MItDependencyGraph::kUpstream,
MItDependencyGraph::kBreadthFirst,
MItDependencyGraph::kNodeLevel,
&status);
if ( status == MS::kFailure ) {
continue;
}
dgIt.disablePruningOnFilter();
// If no texture file node was found, just continue.
//
if ( dgIt.isDone() ) {
continue;
}
// Print out the texture node name and texture file that it references.
//
MObject textureNode = dgIt.thisNode();
MPlug filenamePlug = MFnDependencyNode( textureNode ).findPlug( "fileTextureName" );
MString textureName;
filenamePlug.getValue( textureName );
// remove the OS path and save it in the mesh
OSPathToRelativePath( textureName.asChar(), mesh->shader, options.game );
mesh->shader.StripFileExtension();
return;
}
}
/*
===============
idMayaExport::CopyMesh
===============
*/
idExportMesh *idMayaExport::CopyMesh( MFnSkinCluster &skinCluster, float scale ) {
MStatus status;
MObjectArray objarray;
MObjectArray outputarray;
int nGeom;
int i, j, k;
idExportMesh *mesh;
float uv_u, uv_v;
idStr name, altname;
int pos;
status = skinCluster.getInputGeometry( objarray );
if ( !status ) {
MayaError( "CopyMesh: Error getting input geometry (%s)", status.errorString().asChar() );
return NULL;
}
nGeom = objarray.length();
for( i = 0; i < nGeom; i++ ) {
MFnDagNode dagNode( objarray[ i ], &status );
if ( !status ) {
common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() );
continue;
}
MFnMesh fnmesh( objarray[ i ], &status );
if ( !status ) {
// object isn't an MFnMesh
continue;
}
status = skinCluster.getOutputGeometry( outputarray );
if ( !status ) {
common->Printf( "CopyMesh: Error getting output geometry (%s)", status.errorString().asChar() );
return NULL;
}
if ( outputarray.length() < 1 ) {
return NULL;
}
name = fnmesh.name().asChar();
if ( options.prefix.Length() ) {
if ( !name.Cmpn( options.prefix, options.prefix.Length() ) ) {
// remove the prefix from the name
name = name.Right( name.Length() - options.prefix.Length() );
} else {
// name doesn't match prefix, so don't use this mesh
//return NULL;
}
}
pos = name.Find( "ShapeOrig" );
if ( pos >= 0 ) {
name.CapLength( pos );
}
MFnDagNode dagNode2( outputarray[ 0 ], &status );
if ( !status ) {
common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() );
continue;
}
altname = name;
MObject parent = fnmesh.parent( 0, &status );
if ( status ) {
MFnDagNode parentNode( parent, &status );
if ( status ) {
altname = parentNode.name().asChar();
}
}
name.StripLeadingOnce( options.prefix );
altname.StripLeadingOnce( options.prefix );
if ( options.keepmeshes.Num() ) {
if ( !options.keepmeshes.Find( name ) && !options.keepmeshes.Find( altname ) ) {
if ( altname != name ) {
common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() );
} else {
common->Printf( "Skipping mesh '%s'\n", name.c_str() );
}
return NULL;
}
}
if ( options.skipmeshes.Find( name ) || options.skipmeshes.Find( altname ) ) {
common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() );
return NULL;
}
mesh = new idExportMesh();
model.meshes.Append( mesh );
if ( altname.Length() ) {
mesh->name = altname;
} else {
mesh->name = name;
}
GetTextureForMesh( mesh, dagNode2 );
int v = fnmesh.numVertices( &status );
mesh->verts.SetNum( v );
MFloatPointArray vertexArray;
fnmesh.getPoints( vertexArray, MSpace::kPreTransform );
for( j = 0; j < v; j++ ) {
memset( &mesh->verts[ j ], 0, sizeof( mesh->verts[ j ] ) );
mesh->verts[ j ].pos = ConvertToIdSpace( idVec( vertexArray[ j ] ) ) * scale;
}
MIntArray vertexList;
int p;
p = fnmesh.numPolygons( &status );
mesh->tris.SetNum( p );
mesh->uv.SetNum( p );
MString setName;
status = fnmesh.getCurrentUVSetName( setName );
if ( !status ) {
MayaError( "CopyMesh: MFnMesh::getCurrentUVSetName failed (%s)", status.errorString().asChar() );
}
for( j = 0; j < p; j++ ) {
fnmesh.getPolygonVertices( j, vertexList );
if ( vertexList.length() != 3 ) {
MayaError( "CopyMesh: Too many vertices on a face (%d)\n", vertexList.length() );
}
for( k = 0; k < 3; k++ ) {
mesh->tris[ j ].indexes[ k ] = vertexList[ k ];
status = fnmesh.getPolygonUV( j, k, uv_u, uv_v, &setName );
if ( !status ) {
MayaError( "CopyMesh: MFnMesh::getPolygonUV failed (%s)", status.errorString().asChar() );
}
mesh->uv[ j ].uv[ k ][ 0 ] = uv_u;
mesh->uv[ j ].uv[ k ][ 1 ] = uv_v;
}
}
return mesh;
}
return NULL;
}
/*
===============
idMayaExport::CreateMesh
===============
*/
void idMayaExport::CreateMesh( float scale ) {
size_t count;
idExportMesh *mesh;
MStatus status;
exportWeight_t weight;
unsigned int nGeoms;
// Iterate through graph and search for skinCluster nodes
MItDependencyNodes iter( MFn::kSkinClusterFilter );
count = 0;
for ( ; !iter.isDone(); iter.next() ) {
MObject object = iter.item();
count++;
// For each skinCluster node, get the list of influence objects
MFnSkinCluster skinCluster( object, &status );
if ( !status ) {
MayaError( "%s: Error getting skin cluster (%s)", object.apiTypeStr(), status.errorString().asChar() );
}
mesh = CopyMesh( skinCluster, scale );
if ( !mesh ) {
continue;
}
MDagPathArray infs;
unsigned int nInfs = skinCluster.influenceObjects(infs, &status);
if ( !status ) {
MayaError( "Mesh '%s': Error getting influence objects (%s)", mesh->name.c_str(), status.errorString().asChar() );
}
if ( 0 == nInfs ) {
MayaError( "Mesh '%s': No influence objects found", mesh->name.c_str() );
}
// loop through the geometries affected by this cluster
nGeoms = skinCluster.numOutputConnections();
for (size_t ii = 0; ii < nGeoms; ++ii) {
unsigned int index = skinCluster.indexForOutputConnection(ii,&status);
if ( !status ) {
MayaError( "Mesh '%s': Error getting geometry index (%s)", mesh->name.c_str(), status.errorString().asChar() );
}
// get the dag path of the ii'th geometry
MDagPath skinPath;
status = skinCluster.getPathAtIndex(index,skinPath);
if ( !status ) {
MayaError( "Mesh '%s': Error getting geometry path (%s)", mesh->name.c_str(), status.errorString().asChar() );
}
// iterate through the components of this geometry
MItGeometry gIter( skinPath );
// print out the influence objects
idList joints;
idExportJoint *joint;
exportVertex_t *vert;
joints.Resize( nInfs );
for (size_t kk = 0; kk < nInfs; ++kk) {
const char *c;
MString s;
s = infs[kk].partialPathName();
c = s.asChar();
joint = model.FindJointReal( c );
if ( !joint ) {
MayaError( "Mesh '%s': joint %s not found", mesh->name.c_str(), c );
}
joints.Append( joint );
}
for ( /* nothing */ ; !gIter.isDone(); gIter.next() ) {
MObject comp = gIter.component( &status );
if ( !status ) {
MayaError( "Mesh '%s': Error getting component (%s)", mesh->name.c_str(), status.errorString().asChar() );
}
// Get the weights for this vertex (one per influence object)
MFloatArray wts;
unsigned infCount;
status = skinCluster.getWeights(skinPath,comp,wts,infCount);
if ( !status ) {
MayaError( "Mesh '%s': Error getting weights (%s)", mesh->name.c_str(), status.errorString().asChar() );
}
if (0 == infCount) {
MayaError( "Mesh '%s': Error: 0 influence objects.", mesh->name.c_str() );
}
int num = gIter.index();
vert = &mesh->verts[ num ];
vert->startweight = mesh->weights.Num();
float totalweight = 0.0f;
// copy the weight data for this vertex
int numNonZeroWeights = 0;
int jj;
for ( jj = 0; jj < (int)infCount ; ++jj ) {
float w = ( float )wts[ jj ];
if ( w > 0.0f ) {
numNonZeroWeights++;
}
if ( w > options.jointThreshold ) {
weight.joint = joints[ jj ];
weight.jointWeight = wts[ jj ];
if ( !options.ignoreScale ) {
weight.joint->bindmat.ProjectVector( vert->pos - ( weight.joint->bindpos * weight.joint->invscale ), weight.offset );
weight.offset *= weight.joint->scale;
} else {
weight.joint->bindmat.ProjectVector( vert->pos - weight.joint->bindpos, weight.offset );
}
mesh->weights.Append( weight );
totalweight += weight.jointWeight;
}
}
vert->numWeights = mesh->weights.Num() - vert->startweight;
if ( !vert->numWeights ) {
if ( numNonZeroWeights ) {
MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights exceeding jointThreshold (%f).", mesh->name.c_str(), num, options.jointThreshold );
} else {
MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights.", mesh->name.c_str(), num );
}
} else if ( !totalweight ) {
MayaError( "Error on mesh '%s': Combined weight of 0 on vertex %d.", mesh->name.c_str(), num );
}
//if ( numNonZeroWeights ) {
// common->Printf( "Mesh '%s': skipped %d out of %d weights on vertex %d\n", mesh->name.c_str(), numNonZeroWeights, numNonZeroWeights + vert->numWeights, num );
//}
// normalize the joint weights
for( jj = 0; jj < vert->numWeights; jj++ ) {
mesh->weights[ vert->startweight + jj ].jointWeight /= totalweight;
}
}
break;
}
}
if ( !count && !options.ignoreMeshes ) {
MayaError( "CreateMesh: No skinClusters found in this scene.\n" );
}
}
/*
===============
idMayaExport::CombineMeshes
combine surfaces with the same shader.
===============
*/
void idMayaExport::CombineMeshes( void ) {
int i, j;
int count;
idExportMesh *mesh;
idExportMesh *combine;
idList oldmeshes;
oldmeshes = model.meshes;
model.meshes.Clear();
count = 0;
for( i = 0; i < oldmeshes.Num(); i++ ) {
mesh = oldmeshes[ i ];
if ( !mesh->keep ) {
delete mesh;
continue;
}
combine = NULL;
for( j = 0; j < model.meshes.Num(); j++ ) {
if ( model.meshes[ j ]->shader == mesh->shader ) {
combine = model.meshes[ j ];
break;
}
}
if ( combine ) {
combine->Merge( mesh );
delete mesh;
count++;
} else {
model.meshes.Append( mesh );
}
}
// share verts
for( i = 0; i < model.meshes.Num(); i++ ) {
model.meshes[ i ]->ShareVerts();
}
common->Printf( "Merged %d meshes\n", count );
}
/*
===============
idMayaExport::GetAlignment
===============
*/
void idMayaExport::GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ) {
idVec3 pos;
idExportJoint *joint;
idAngles ang( 0, rotate, 0 );
idMat3 mat;
align.Identity();
if ( alignName.Length() ) {
SetFrame( 0 );
joint = model.FindJoint( alignName );
if ( !joint ) {
MayaError( "could not find joint '%s' to align model to.\n", alignName.c_str() );
}
// found it
GetWorldTransform( joint, pos, mat, 1.0f );
align[ 0 ][ 0 ] = mat[ 2 ][ 0 ];
align[ 0 ][ 1 ] = -mat[ 2 ][ 2 ];
align[ 0 ][ 2 ] = mat[ 2 ][ 1 ];
align[ 1 ][ 0 ] = mat[ 0 ][ 0 ];
align[ 1 ][ 1 ] = -mat[ 0 ][ 2 ];
align[ 1 ][ 2 ] = mat[ 0 ][ 1 ];
align[ 2 ][ 0 ] = mat[ 1 ][ 0 ];
align[ 2 ][ 1 ] = -mat[ 1 ][ 2 ];
align[ 2 ][ 2 ] = mat[ 1 ][ 1 ];
if ( rotate ) {
align *= ang.ToMat3();
}
} else if ( rotate ) {
align = ang.ToMat3();
}
align.TransposeSelf();
}
/*
===============
idMayaExport::GetObjectType
return the type of the object
===============
*/
const char *idMayaExport::GetObjectType( MObject object ) {
if( object.isNull() ) {
return "(Null)";
}
MStatus stat;
MFnDependencyNode dgNode;
MString typeName;
stat = dgNode.setObject( object );
typeName = dgNode.typeName( &stat );
if( MS::kSuccess != stat ) {
// can not get the type name of this object
return "(Unknown)";
}
return typeName.asChar();
}
/*
===============
idMayaExport::GetCameraFov
===============
*/
float idMayaExport::GetCameraFov( idExportJoint *joint ) {
int childCount;
int j;
double horiz;
double focal;
MStatus status;
const char *n1, *n2;
MFnDagNode *dagnode;
float fov;
dagnode = joint->dagnode;
MObject cameraNode = dagnode->object();
childCount = dagnode->childCount();
fov = 90.0f;
for( j = 0; j < childCount; j++ ) {
MObject childNode = dagnode->child( j );
n1 = GetObjectType( cameraNode );
n2 = GetObjectType( childNode );
if ( ( !strcmp( "transform", n1 ) ) && ( !strcmp( "camera", n2 ) ) ) {
MFnCamera camera( childNode );
focal = camera.focalLength();
horiz = camera.horizontalFilmAperture();
fov = RAD2DEG( 2 * atan( ( horiz * 0.5 ) / ( focal / 25.4 ) ) );
break;
}
}
return fov;
}
/*
===============
idMayaExport::GetCameraFrame
===============
*/
void idMayaExport::GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ) {
idMat3 mat;
idMat3 axis;
idVec3 pos;
// get the worldspace positions of the joint
GetWorldTransform( camera, pos, axis, 1.0f );
// convert to id coordinates
cam->t = ConvertToIdSpace( pos ) * align;
// correct the orientation for the game
axis = ConvertToIdSpace( axis ) * align;
mat[ 0 ] = -axis[ 2 ];
mat[ 1 ] = -axis[ 0 ];
mat[ 2 ] = axis[ 1 ];
cam->q = mat.ToQuat().ToCQuat();
// get it's fov
cam->fov = GetCameraFov( camera );
}
/*
===============
idMayaExport::CreateCameraAnim
===============
*/
void idMayaExport::CreateCameraAnim( idMat3 &align ) {
float start, end;
MDagPath dagPath;
int frameNum;
short v;
MStatus status;
cameraFrame_t cam;
idExportJoint *refCam;
idExportJoint *camJoint;
idStr currentCam;
idStr newCam;
MPlug plug;
MFnEnumAttribute cameraAttribute;
start = TimeForFrame( options.startframe );
end = TimeForFrame( options.endframe );
#if 0
options.framerate = 60;
model.numFrames = ( int )( ( end - start ) * ( float )options.framerate ) + 1;
model.frameRate = options.framerate;
#else
model.numFrames = options.endframe + 1 - options.startframe;
model.frameRate = options.framerate;
#endif
common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe );
common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start );
if ( start > end ) {
MayaError( "Start frame is greater than end frame." );
}
refCam = model.FindJoint( "refcam" );
if ( refCam == NULL ) {
currentCam = MAYA_DEFAULT_CAMERA;
} else {
MObject cameraNode = refCam->dagnode->object();
MFnDependencyNode cameraDG( cameraNode, &status );
if( MS::kSuccess != status ) {
MayaError( "Can't find 'refcam' dependency node." );
return;
}
MObject attr = cameraDG.attribute( MString( "Camera" ), &status );
if( MS::kSuccess != status ) {
MayaError( "Can't find 'Camera' attribute on 'refcam'." );
return;
}
plug = MPlug( cameraNode, attr );
status = cameraAttribute.setObject( attr );
if( MS::kSuccess != status ) {
MayaError( "Bad 'Camera' attribute on 'refcam'." );
return;
}
model.camera.Clear();
model.cameraCuts.Clear();
SetFrame( 0 );
status = plug.getValue( v );
currentCam = cameraAttribute.fieldName( v, &status ).asChar();
if( MS::kSuccess != status ) {
MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( 0 ) );
}
}
camJoint = model.FindJoint( currentCam );
if ( !camJoint ) {
MayaError( "Couldn't find camera '%s'", currentCam.c_str() );
}
for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe );
#if 0
MTime time;
time.setUnit( MTime::kSeconds );
time.setValue( start + ( ( float )frameNum / ( float )options.framerate ) );
MGlobal::viewFrame( time );
#else
SetFrame( frameNum );
#endif
// get the position for this frame
GetCameraFrame( camJoint, align, &model.camera.Alloc() );
if ( refCam != NULL ) {
status = plug.getValue( v );
newCam = cameraAttribute.fieldName( v, &status ).asChar();
if( MS::kSuccess != status ) {
MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( frameNum ) );
}
if ( newCam != currentCam ) {
// place a cut at our last frame
model.cameraCuts.Append( model.camera.Num() - 1 );
currentCam = newCam;
camJoint = model.FindJoint( currentCam );
if ( !camJoint ) {
MayaError( "Couldn't find camera '%s'", currentCam.c_str() );
}
// get the position for this frame
GetCameraFrame( camJoint, align, &model.camera.Alloc() );
}
}
}
common->Printf( "\n" );
}
/*
===============
idMayaExport::GetDefaultPose
===============
*/
void idMayaExport::GetDefaultPose( idMat3 &align ) {
float start;
MDagPath dagPath;
idMat3 jointaxis;
idVec3 jointpos;
idExportJoint *joint, *parent;
idBounds bnds;
idBounds meshBounds;
idList frame;
start = TimeForFrame( options.startframe );
common->Printf( "default pose frame = %d\n", options.startframe );
common->Printf( " default pose time = %f\n", start );
frame.SetNum( model.joints.Num() );
SetFrame( 0 );
// convert joints into local coordinates and save in channels
for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
if ( !joint->dagnode ) {
// custom origin joint
joint->idwm.Identity();
joint->idt.Zero();
frame[ joint->index ].t.Zero();
frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f );
continue;
}
// get the worldspace positions of the joint
GetWorldTransform( joint, jointpos, jointaxis, options.scale );
// convert to id coordinates
jointaxis = ConvertToIdSpace( jointaxis ) * align;
jointpos = ConvertToIdSpace( jointpos ) * align;
// save worldspace position of joint for children
joint->idwm = jointaxis;
joint->idt = jointpos;
parent = joint->exportNode.GetParent();
if ( parent ) {
// convert to local coordinates
jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose();
jointaxis = jointaxis * parent->idwm.Transpose();
} else if ( joint->name == "origin" ) {
if ( options.clearOrigin ) {
jointpos.Zero();
}
if ( options.clearOriginAxis ) {
jointaxis.Identity();
}
}
frame[ joint->index ].t = jointpos;
frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat();
}
// relocate origin to start at 0, 0, 0 for first frame
joint = model.FindJoint( "origin" );
if ( joint ) {
frame[ joint->index ].t.Zero();
}
// transform the hierarchy
for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
jointpos = frame[ joint->index ].t;
jointaxis = frame[ joint->index ].q.ToQuat().ToMat3();
parent = joint->exportNode.GetParent();
if ( parent ) {
joint->idwm = jointaxis * parent->idwm;
joint->idt = parent->idt + jointpos * parent->idwm;
} else {
joint->idwm = jointaxis;
joint->idt = jointpos;
}
joint->bindmat = joint->idwm;
joint->bindpos = joint->idt;
}
common->Printf( "\n" );
}
/*
===============
idMayaExport::CreateAnimation
===============
*/
void idMayaExport::CreateAnimation( idMat3 &align ) {
int i;
float start, end;
MDagPath dagPath;
idMat3 jointaxis;
idVec3 jointpos;
int frameNum;
idExportJoint *joint, *parent;
idBounds bnds;
idBounds meshBounds;
jointFrame_t *frame;
int cycleStart;
idVec3 totalDelta;
idList copyFrames;
start = TimeForFrame( options.startframe );
end = TimeForFrame( options.endframe );
model.numFrames = options.endframe + 1 - options.startframe;
model.frameRate = options.framerate;
common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe );
common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start );
if ( start > end ) {
MayaError( "Start frame is greater than end frame." );
}
model.bounds.SetNum( model.numFrames );
model.jointFrames.SetNum( model.numFrames * model.joints.Num() );
model.frames.SetNum( model.numFrames );
for( i = 0; i < model.numFrames; i++ ) {
model.frames[ i ] = &model.jointFrames[ model.joints.Num() * i ];
}
// *sigh*. cyclestart doesn't work nicely with the anims.
// may just want to not do it in SetTime anymore.
cycleStart = options.cycleStart;
options.cycleStart = options.startframe;
for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe );
frame = model.frames[ frameNum ];
SetFrame( frameNum );
// convert joints into local coordinates and save in channels
for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
if ( !joint->dagnode ) {
// custom origin joint
joint->idwm.Identity();
joint->idt.Zero();
frame[ joint->index ].t.Zero();
frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f );
continue;
}
// get the worldspace positions of the joint
GetWorldTransform( joint, jointpos, jointaxis, options.scale );
// convert to id coordinates
jointaxis = ConvertToIdSpace( jointaxis ) * align;
jointpos = ConvertToIdSpace( jointpos ) * align;
// save worldspace position of joint for children
joint->idwm = jointaxis;
joint->idt = jointpos;
parent = joint->exportNode.GetParent();
if ( parent ) {
// convert to local coordinates
jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose();
jointaxis = jointaxis * parent->idwm.Transpose();
} else if ( joint->name == "origin" ) {
if ( options.clearOrigin ) {
jointpos.Zero();
}
if ( options.clearOriginAxis ) {
jointaxis.Identity();
}
}
frame[ joint->index ].t = jointpos;
frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat();
}
}
options.cycleStart = cycleStart;
totalDelta.Zero();
joint = model.FindJoint( "origin" );
if ( joint ) {
frame = model.frames[ 0 ];
idVec3 origin = frame[ joint->index ].t;
frame = model.frames[ model.numFrames - 1 ];
totalDelta = frame[ joint->index ].t - origin;
}
// shift the frames when cycleStart is used
if ( options.cycleStart > options.startframe ) {
copyFrames = model.jointFrames;
for( i = 0; i < model.numFrames; i++ ) {
bool shiftorigin = false;
frameNum = i + ( options.cycleStart - options.startframe );
if ( frameNum >= model.numFrames ) {
// wrap around, skipping the first frame, since it's a dupe of the last frame
frameNum -= model.numFrames - 1;
shiftorigin = true;
}
memcpy( &model.jointFrames[ model.joints.Num() * i ], ©Frames[ model.joints.Num() * frameNum ], model.joints.Num() * sizeof( copyFrames[ 0 ] ) );
if ( joint && shiftorigin ) {
model.frames[ i ][ joint->index ].t += totalDelta;
}
}
}
if ( joint ) {
// relocate origin to start at 0, 0, 0 for first frame
frame = model.frames[ 0 ];
idVec3 origin = frame[ joint->index ].t;
for( i = 0; i < model.numFrames; i++ ) {
frame = model.frames[ i ];
frame[ joint->index ].t -= origin;
}
}
// get the bounds for each frame
for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
frame = model.frames[ frameNum ];
// transform the hierarchy
for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
jointpos = frame[ joint->index ].t;
jointaxis = frame[ joint->index ].q.ToQuat().ToMat3();
parent = joint->exportNode.GetParent();
if ( parent ) {
joint->idwm = jointaxis * parent->idwm;
joint->idt = parent->idt + jointpos * parent->idwm;
} else {
joint->idwm = jointaxis;
joint->idt = jointpos;
}
}
// get bounds for this frame
bnds.Clear();
for( i = 0; i < model.meshes.Num(); i++ ) {
if ( model.meshes[ i ]->keep ) {
model.meshes[ i ]->GetBounds( meshBounds );
bnds.AddBounds( meshBounds );
}
}
model.bounds[ frameNum ][ 0 ] = bnds[ 0 ];
model.bounds[ frameNum ][ 1 ] = bnds[ 1 ];
}
common->Printf( "\n" );
}
/*
===============
idMayaExport::ConvertModel
===============
*/
void idMayaExport::ConvertModel( void ) {
MStatus status;
idMat3 align;
common->Printf( "Converting %s to %s...\n", options.src.c_str(), options.dest.c_str() );
// see if the destination file exists
FILE *file = fopen( options.dest, "r" );
if ( file ) {
fclose( file );
// make sure we can write to the destination
FILE *file = fopen( options.dest, "r+" );
if ( !file ) {
MayaError( "Unable to write to the file '%s'", options.dest.c_str() );
}
fclose( file );
}
MString filename( options.src );
MFileIO::newFile( true );
// Load the file into Maya
common->Printf( "Loading file...\n" );
status = MFileIO::open( filename, NULL, true );
if ( !status ) {
MayaError( "Error loading '%s': '%s'\n", filename.asChar(), status.errorString().asChar() );
}
// force Maya to update the frame. When using references, sometimes
// the model is posed the way it is in the source. Since Maya only
// updates it when the frame time changes to a value other than the
// current, just setting the time to the min time doesn't guarantee
// that the model gets updated.
MGlobal::viewFrame( MAnimControl::maxTime() );
MGlobal::viewFrame( MAnimControl::minTime() );
if ( options.startframe < 0 ) {
options.startframe = MAnimControl::minTime().as( MTime::kFilm );
}
if ( options.endframe < 0 ) {
options.endframe = MAnimControl::maxTime().as( MTime::kFilm );
}
if ( options.cycleStart < 0 ) {
options.cycleStart = options.startframe;
} else if ( ( options.cycleStart < options.startframe ) || ( options.cycleStart > options.endframe ) ) {
MayaError( "cycleStart (%d) out of frame range (%d to %d)\n", options.cycleStart, options.startframe, options.endframe );
} else if ( options.cycleStart == options.endframe ) {
// end frame is a duplicate of the first frame in cycles, so just disable cycleStart
options.cycleStart = options.startframe;
}
// create a list of the transform nodes that will make up our heirarchy
common->Printf( "Creating joints...\n" );
CreateJoints( options.scale );
if ( options.type != WRITE_CAMERA ) {
common->Printf( "Creating meshes...\n" );
CreateMesh( options.scale );
common->Printf( "Renaming joints...\n" );
RenameJoints( options.renamejoints, options.prefix );
common->Printf( "Remapping parents...\n" );
RemapParents( options.remapjoints );
common->Printf( "Pruning joints...\n" );
PruneJoints( options.keepjoints, options.prefix );
common->Printf( "Combining meshes...\n" );
CombineMeshes();
}
common->Printf( "Align model...\n" );
GetAlignment( options.align, align, options.rotate, 0 );
switch( options.type ) {
case WRITE_MESH :
common->Printf( "Grabbing default pose:\n" );
GetDefaultPose( align );
common->Printf( "Writing file...\n" );
if ( !model.WriteMesh( options.dest, options ) ) {
MayaError( "error writing to '%s'", options.dest.c_str() );
}
break;
case WRITE_ANIM :
common->Printf( "Creating animation frames:\n" );
CreateAnimation( align );
common->Printf( "Writing file...\n" );
if ( !model.WriteAnim( options.dest, options ) ) {
MayaError( "error writing to '%s'", options.dest.c_str() );
}
break;
case WRITE_CAMERA :
common->Printf( "Creating camera frames:\n" );
CreateCameraAnim( align );
common->Printf( "Writing file...\n" );
if ( !model.WriteCamera( options.dest, options ) ) {
MayaError( "error writing to '%s'", options.dest.c_str() );
}
break;
}
common->Printf( "done\n\n" );
}
/*
===============
idMayaExport::ConvertToMD3
===============
*/
void idMayaExport::ConvertToMD3( void ) {
#if 0
int i, j;
md3Header_t *pinmodel;
md3Frame_t *frame;
md3Surface_t *surf;
md3Shader_t *shader;
md3Triangle_t *tri;
md3St_t *st;
md3XyzNormal_t *xyz;
md3Tag_t *tag;
int version;
int size;
//model_t *mod, int lod, void *buffer, const char *mod_name
pinmodel = (md3Header_t *)buffer;
version = LittleLong (pinmodel->version);
if (version != MD3_VERSION) {
common->Printf( "R_LoadMD3: %s has wrong version (%i should be %i)\n",
mod_name, version, MD3_VERSION);
return qfalse;
}
mod->type = MOD_MESH;
size = LittleLong(pinmodel->ofsEnd);
mod->dataSize += size;
mod->md3[lod] = ri.Hunk_Alloc( size );
memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) );
LL(mod->md3[lod]->ident);
LL(mod->md3[lod]->version);
LL(mod->md3[lod]->numFrames);
LL(mod->md3[lod]->numTags);
LL(mod->md3[lod]->numSurfaces);
LL(mod->md3[lod]->ofsFrames);
LL(mod->md3[lod]->ofsTags);
LL(mod->md3[lod]->ofsSurfaces);
LL(mod->md3[lod]->ofsEnd);
if ( mod->md3[lod]->numFrames < 1 ) {
common->Printf( "R_LoadMD3: %s has no frames\n", mod_name );
return qfalse;
}
// swap all the frames
frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames );
for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) {
frame->radius = LittleFloat( frame->radius );
for ( j = 0 ; j < 3 ; j++ ) {
frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
}
}
// swap all the tags
tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags );
for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) {
for ( j = 0 ; j < 3 ; j++ ) {
tag->origin[j] = LittleFloat( tag->origin[j] );
tag->axis[0][j] = LittleFloat( tag->axis[0][j] );
tag->axis[1][j] = LittleFloat( tag->axis[1][j] );
tag->axis[2][j] = LittleFloat( tag->axis[2][j] );
}
}
// swap all the surfaces
surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces );
for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) {
LL(surf->ident);
LL(surf->flags);
LL(surf->numFrames);
LL(surf->numShaders);
LL(surf->numTriangles);
LL(surf->ofsTriangles);
LL(surf->numVerts);
LL(surf->ofsShaders);
LL(surf->ofsSt);
LL(surf->ofsXyzNormals);
LL(surf->ofsEnd);
if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
}
if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
}
// change to surface identifier
surf->ident = SF_MD3;
// lowercase the surface name so skin compares are faster
Q_strlwr( surf->name );
// strip off a trailing _1 or _2
// this is a crutch for q3data being a mess
j = strlen( surf->name );
if ( j > 2 && surf->name[j-2] == '_' ) {
surf->name[j-2] = 0;
}
// register the shaders
shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders );
for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) {
shader_t *sh;
sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue );
if ( sh->defaultShader ) {
shader->shaderIndex = 0;
} else {
shader->shaderIndex = sh->index;
}
}
// swap all the triangles
tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
LL(tri->indexes[0]);
LL(tri->indexes[1]);
LL(tri->indexes[2]);
}
// swap all the ST
st = (md3St_t *) ( (byte *)surf + surf->ofsSt );
for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
st->st[0] = LittleFloat( st->st[0] );
st->st[1] = LittleFloat( st->st[1] );
}
// swap all the XyzNormals
xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals );
for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ )
{
xyz->xyz[0] = LittleShort( xyz->xyz[0] );
xyz->xyz[1] = LittleShort( xyz->xyz[1] );
xyz->xyz[2] = LittleShort( xyz->xyz[2] );
xyz->normal = LittleShort( xyz->normal );
}
// find the next surface
surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd );
}
return true;
#endif
}
/*
==============================================================================
dll setup
==============================================================================
*/
/*
===============
Maya_Shutdown
===============
*/
void Maya_Shutdown( void ) {
if ( initialized ) {
errorMessage.Clear();
initialized = false;
// This shuts down the entire app somehow, so just ignore it.
//MLibrary::cleanup();
}
}
/*
===============
Maya_ConvertModel
===============
*/
const char *Maya_ConvertModel( const char *ospath, const char *commandline ) {
errorMessage = "Ok";
try {
idExportOptions options( commandline, ospath );
idMayaExport exportM( options );
exportM.ConvertModel();
}
catch( idException &exception ) {
errorMessage = exception.error;
}
return errorMessage;
}
/*
===============
dllEntry
===============
*/
bool dllEntry( int version, idCommon *common, idSys *sys ) {
if ( !common || !sys ) {
return false;
}
::common = common;
::sys = sys;
::cvarSystem = NULL;
idLib::sys = sys;
idLib::common = common;
idLib::cvarSystem = NULL;
idLib::fileSystem = NULL;
idLib::Init();
if ( version != MD5_VERSION ) {
common->Printf( "Error initializing Maya exporter: DLL version %d different from .exe version %d\n", MD5_VERSION, version );
return false;
}
if ( !initialized ) {
MStatus status;
status = MLibrary::initialize( GAME_NAME, true );
if ( !status ) {
common->Printf( "Error calling MLibrary::initialize (%s)\n", status.errorString().asChar() );
return false;
}
initialized = true;
}
return true;
}
// Force type checking on the interface functions to help ensure that they match the ones in the .exe
const exporterDLLEntry_t ValidateEntry = &dllEntry;
const exporterInterface_t ValidateConvert = &Maya_ConvertModel;
const exporterShutdown_t ValidateShutdown = &Maya_Shutdown;