mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
3148 lines
80 KiB
C++
3148 lines
80 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 "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<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;
|