3149 lines
81 KiB
C++
3149 lines
81 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
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 "../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#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 "exporter.h"
|
|
#include "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<exportVertex_t> 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<idExportJoint *> 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<idExportJoint *> 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<idNamePair> &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<idNamePair> &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<idExportJoint *> 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<idExportMesh *> 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<jointFrame_t> 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<jointFrame_t> 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;
|