/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech 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.
IceTech 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 IceTech Source Code. If not, see .
In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#undef DotProduct
#define FLT_EPSILON 1.19209290e-07F
#include
// exporter.h was originally authored beside the Maya SDK and stores an MFnDagNode*
// in idExportJoint. For this FBX-backed build, make that member an FbxNode*.
class MObject;
class MFnSkinCluster;
#define MFnDagNode FbxNode
#include "exporter.h"
#undef MFnDagNode
#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 / idMat FBX helpers
===============
*/
static idVec3 idVec(const FbxVector4& point) {
return idVec3((float)point[0], (float)point[1], (float)point[2]);
}
static idVec3 idVec(const FbxDouble3& point) {
return idVec3((float)point[0], (float)point[1], (float)point[2]);
}
static idMat3 idMat(const FbxAMatrix& matrix) {
int j, k;
idMat3 mat;
for (j = 0; j < 3; j++) {
for (k = 0; k < 3; k++) {
mat[j][k] = (float)matrix.Get(j, k);
}
}
mat.OrthoNormalizeSelf();
return mat;
}
static FbxAMatrix FbxGetGeometryTransform(const FbxNode* node) {
FbxAMatrix geo;
geo.SetIdentity();
if (node == NULL) {
return geo;
}
FbxVector4 t = node->GetGeometricTranslation(FbxNode::eSourcePivot);
FbxVector4 r = node->GetGeometricRotation(FbxNode::eSourcePivot);
FbxVector4 s = node->GetGeometricScaling(FbxNode::eSourcePivot);
geo.SetTRS(t, r, s);
return geo;
}
/*
==============================================================================================
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& current = 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 == "md3") {
// MD3 uses the mesh command path internally, but dispatches to WriteMD3 below.
type = WRITE_MESH;
}
else if (token == "anim") {
type = WRITE_ANIM;
}
else if (token == "camera") {
type = WRITE_CAMERA;
}
else {
MayaError("Unknown export command '%s'", token.c_str());
}
src = tokens.NextToken("Missing source filename");
dest = src;
for (token = tokens.NextToken(); token != ""; token = tokens.NextToken()) {
if (token == "-force") {
// skip
}
else if (token == "-game") {
// parse game name
game = tokens.NextToken("Expecting game name after -game");
}
else if (token == "-rename") {
// parse joint to rename
joints.from = tokens.NextToken("Missing joint name for -rename. Usage: -rename [joint name] [new name]");
joints.to = tokens.NextToken("Missing new name for -rename. Usage: -rename [joint name] [new name]");
renamejoints.Append(joints);
}
else if (token == "-prefix") {
prefix = tokens.NextToken("Missing name for -prefix. Usage: -prefix [joint prefix]");
}
else if (token == "-parent") {
// parse joint to reparent
joints.from = tokens.NextToken("Missing joint name for -parent. Usage: -parent [joint name] [new parent]");
joints.to = tokens.NextToken("Missing new parent for -parent. Usage: -parent [joint name] [new parent]");
remapjoints.Append(joints);
}
else if (!token.Icmp("-sourcedir")) {
// parse source directory
sourceDir = tokens.NextToken("Missing filename for -sourcedir. Usage: -sourcedir [directory]");
}
else if (!token.Icmp("-destdir")) {
// parse destination directory
destDir = tokens.NextToken("Missing filename for -destdir. Usage: -destdir [directory]");
}
else if (token == "-dest") {
// parse destination filename
dest = tokens.NextToken("Missing filename for -dest. Usage: -dest [filename]");
}
else if (token == "-range") {
// parse frame range to export
token = tokens.NextToken("Missing start frame for -range. Usage: -range [start frame] [end frame]");
startframe = atoi(token);
token = tokens.NextToken("Missing end frame for -range. Usage: -range [start frame] [end frame]");
endframe = atoi(token);
if (startframe > endframe) {
MayaError("Start frame is greater than end frame.");
}
}
else if (!token.Icmp("-cycleStart")) {
// parse start frame of cycle
token = tokens.NextToken("Missing cycle start frame for -cycleStart. Usage: -cycleStart [first frame of cycle]");
cycleStart = atoi(token);
}
else if (token == "-scale") {
// parse scale
token = tokens.NextToken("Missing scale amount for -scale. Usage: -scale [scale amount]");
scale = atof(token);
}
else if (token == "-align") {
// parse align joint
align = tokens.NextToken("Missing joint name for -align. Usage: -align [joint name]");
}
else if (token == "-rotate") {
// parse angle rotation
token = tokens.NextToken("Missing value for -rotate. Usage: -rotate [yaw]");
rotate = -atof(token);
}
else if (token == "-nomesh") {
ignoreMeshes = true;
}
else if (token == "-clearorigin") {
clearOrigin = true;
clearOriginAxis = true;
}
else if (token == "-clearoriginaxis") {
clearOriginAxis = true;
}
else if (token == "-ignorescale") {
ignoreScale = true;
}
else if (token == "-xyzprecision") {
// parse quaternion precision
token = tokens.NextToken("Missing value for -xyzprecision. Usage: -xyzprecision [precision]");
xyzPrecision = atof(token);
if (xyzPrecision < 0.0f) {
MayaError("Invalid value for -xyzprecision. Must be >= 0");
}
}
else if (token == "-quatprecision") {
// parse quaternion precision
token = tokens.NextToken("Missing value for -quatprecision. Usage: -quatprecision [precision]");
quatPrecision = atof(token);
if (quatPrecision < 0.0f) {
MayaError("Invalid value for -quatprecision. Must be >= 0");
}
}
else if (token == "-jointthreshold") {
// parse joint threshold
token = tokens.NextToken("Missing weight for -jointthreshold. Usage: -jointthreshold [minimum joint weight]");
jointThreshold = atof(token);
}
else if (token == "-skipmesh") {
token = tokens.NextToken("Missing name for -skipmesh. Usage: -skipmesh [name of mesh to skip]");
skipmeshes.AddUnique(token);
}
else if (token == "-keepmesh") {
token = tokens.NextToken("Missing name for -keepmesh. Usage: -keepmesh [name of mesh to keep]");
keepmeshes.AddUnique(token);
}
else if (token == "-jointgroup") {
token = tokens.NextToken("Missing name for -jointgroup. Usage: -jointgroup [group name] [joint1] [joint2]...[joint n]");
group = groups.Ptr();
for (i = 0; i < groups.Num(); i++, group++) {
if (group->name == token) {
break;
}
}
if (i >= groups.Num()) {
// create a new group
group = &groups.Alloc();
group->name = token;
}
while (tokens.TokenAvailable()) {
token = tokens.NextToken();
if (token[0] == '-') {
tokens.UnGetToken();
break;
}
group->joints.AddUnique(token);
}
}
else if (token == "-group") {
// add the list of groups to export (these don't affect the hierarchy)
while (tokens.TokenAvailable()) {
token = tokens.NextToken();
if (token[0] == '-') {
tokens.UnGetToken();
break;
}
group = groups.Ptr();
for (i = 0; i < groups.Num(); i++, group++) {
if (group->name == token) {
break;
}
}
if (i >= groups.Num()) {
MayaError("Unknown group '%s'", token.c_str());
}
exportgroups.AddUnique(group);
}
}
else if (token == "-keep") {
// add joints that are kept whether they're used by a mesh or not
while (tokens.TokenAvailable()) {
token = tokens.NextToken();
if (token[0] == '-') {
tokens.UnGetToken();
break;
}
keepjoints.AddUnique(token);
}
}
else {
MayaError("Unknown option '%s'", token.c_str());
}
}
token = src;
src = ospath;
src.BackSlashesToSlashes();
src.AppendPath(sourceDir);
src.AppendPath(token);
token = dest;
dest = ospath;
dest.BackSlashesToSlashes();
dest.AppendPath(destDir);
dest.AppendPath(token);
// Maya only accepts unix style path separators
src.BackSlashesToSlashes();
dest.BackSlashesToSlashes();
if (skipmeshes.Num() && keepmeshes.Num()) {
MayaError("Can't use -keepmesh and -skipmesh together.");
}
}
/*
====================
idExportOptions::jointInExportGroup
====================
*/
bool idExportOptions::jointInExportGroup(const char* jointname) {
int i;
int j;
idAnimGroup* group;
if (!exportgroups.Num()) {
// if we don't have any groups specified as export then export every joint
return true;
}
// search through all exported groups to see if this joint is exported
for (i = 0; i < exportgroups.Num(); i++) {
group = exportgroups[i];
for (j = 0; j < group->joints.Num(); j++) {
if (group->joints[j] == jointname) {
return true;
}
}
}
return false;
}
/*
==============================================================================
idExportJoint
==============================================================================
*/
idExportJoint::idExportJoint() {
index = 0;
exportNum = 0;
mayaNode.SetOwner(this);
exportNode.SetOwner(this);
dagnode = NULL;
t = vec3_zero;
wm = mat3_default;
bindpos = vec3_zero;
bindmat = mat3_default;
keep = false;
scale = 1.0f;
invscale = 1.0f;
animBits = 0;
firstComponent = 0;
baseFrame.q.Set(0.0f, 0.0f, 0.0f);
baseFrame.t.Zero();
}
idExportJoint& idExportJoint::operator=(const idExportJoint& other) {
name = other.name;
realname = other.realname;
longname = other.longname;
index = other.index;
exportNum = other.exportNum;
keep = other.keep;
scale = other.scale;
invscale = other.invscale;
dagnode = other.dagnode;
mayaNode = other.mayaNode;
exportNode = other.exportNode;
t = other.t;
idt = other.idt;
wm = other.wm;
idwm = other.idwm;
bindpos = other.bindpos;
bindmat = other.bindmat;
animBits = other.animBits;
firstComponent = other.firstComponent;
baseFrame = other.baseFrame;
mayaNode.SetOwner(this);
exportNode.SetOwner(this);
return *this;
}
/*
==============================================================================
idExportMesh
==============================================================================
*/
void idExportMesh::ShareVerts(void) {
int i, j, k;
exportVertex_t vert;
idList v;
v = verts;
verts.Clear();
for (i = 0; i < tris.Num(); i++) {
for (j = 0; j < 3; j++) {
vert = v[tris[i].indexes[j]];
vert.texCoords[0] = uv[i].uv[j][0];
vert.texCoords[1] = 1.0f - uv[i].uv[j][1];
for (k = 0; k < verts.Num(); k++) {
if (vert.numWeights != verts[k].numWeights) {
continue;
}
if (vert.startweight != verts[k].startweight) {
continue;
}
if (!vert.pos.Compare(verts[k].pos, SLOP_VERTEX)) {
continue;
}
if (!vert.texCoords.Compare(verts[k].texCoords, SLOP_TEXCOORD)) {
continue;
}
break;
}
if (k < verts.Num()) {
tris[i].indexes[j] = k;
}
else {
tris[i].indexes[j] = verts.Append(vert);
}
}
}
}
void idExportMesh::Merge(idExportMesh* mesh) {
int i;
int numverts;
int numtris;
int numweights;
int numuvs;
// merge name
sprintf(name, "%s, %s", name.c_str(), mesh->name.c_str());
// merge verts
numverts = verts.Num();
verts.SetNum(numverts + mesh->verts.Num());
for (i = 0; i < mesh->verts.Num(); i++) {
verts[numverts + i] = mesh->verts[i];
verts[numverts + i].startweight += weights.Num();
}
// merge triangles
numtris = tris.Num();
tris.SetNum(numtris + mesh->tris.Num());
for (i = 0; i < mesh->tris.Num(); i++) {
tris[numtris + i].indexes[0] = mesh->tris[i].indexes[0] + numverts;
tris[numtris + i].indexes[1] = mesh->tris[i].indexes[1] + numverts;
tris[numtris + i].indexes[2] = mesh->tris[i].indexes[2] + numverts;
}
// merge weights
numweights = weights.Num();
weights.SetNum(numweights + mesh->weights.Num());
for (i = 0; i < mesh->weights.Num(); i++) {
weights[numweights + i] = mesh->weights[i];
}
// merge uvs
numuvs = uv.Num();
uv.SetNum(numuvs + mesh->uv.Num());
for (i = 0; i < mesh->uv.Num(); i++) {
uv[numuvs + i] = mesh->uv[i];
}
}
void idExportMesh::GetBounds(idBounds& bounds) const {
int i;
int j;
idVec3 pos;
const exportWeight_t* weight;
const exportVertex_t* vert;
bounds.Clear();
weight = weights.Ptr();
vert = verts.Ptr();
for (i = 0; i < verts.Num(); i++, vert++) {
pos.Zero();
weight = &weights[vert->startweight];
for (j = 0; j < vert->numWeights; j++, weight++) {
pos += weight->jointWeight * (weight->joint->idwm * weight->offset + weight->joint->idt);
}
bounds.AddPoint(pos);
}
}
/*
==============================================================================
idExportModel
==============================================================================
*/
/*
====================
idExportModel::idExportModel
====================
*/
ID_INLINE idExportModel::idExportModel() {
export_joints = 0;
skipjoints = 0;
frameRate = 24;
numFrames = 0;
exportOrigin = NULL;
}
/*
====================
idExportModel::~idExportModel
====================
*/
ID_INLINE idExportModel::~idExportModel() {
meshes.DeleteContents(true);
}
idExportJoint* idExportModel::FindJointReal(const char* name) {
idExportJoint* joint;
int i;
joint = joints.Ptr();
for (i = 0; i < joints.Num(); i++, joint++) {
if (joint->realname == name) {
return joint;
}
}
return NULL;
}
idExportJoint* idExportModel::FindJoint(const char* name) {
idExportJoint* joint;
int i;
joint = joints.Ptr();
for (i = 0; i < joints.Num(); i++, joint++) {
if (joint->name == name) {
return joint;
}
}
return NULL;
}
bool idExportModel::WriteMesh(const char* filename, idExportOptions& options) {
int i, j;
int numMeshes;
idExportMesh* mesh;
idExportJoint* joint;
idExportJoint* parent;
idExportJoint* sibling;
FILE* file;
const char* parentName;
int parentNum;
idList jointList;
file = fopen(filename, "w");
if (!file) {
return false;
}
for (joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
jointList.Append(joint);
}
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
sibling = joint->exportNode.GetSibling();
while (sibling) {
if (idStr::Cmp(joint->name, sibling->name) > 0) {
joint->exportNode.MakeSiblingAfter(sibling->exportNode);
sibling = joint->exportNode.GetSibling();
}
else {
sibling = sibling->exportNode.GetSibling();
}
}
}
jointList.Clear();
for (joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
joint->exportNum = jointList.Append(joint);
}
numMeshes = 0;
if (!options.ignoreMeshes) {
for (i = 0; i < meshes.Num(); i++) {
if (meshes[i]->keep) {
numMeshes++;
}
}
}
// write version info
WriteFloatString(file, MD5_VERSION_STRING " %d\n", MD5_VERSION);
WriteFloatString(file, "commandline \"%s\"\n\n", options.commandLine.c_str());
// write joints
WriteFloatString(file, "numJoints %d\n", jointList.Num());
WriteFloatString(file, "numMeshes %d\n\n", numMeshes);
WriteFloatString(file, "joints {\n");
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
parent = joint->exportNode.GetParent();
if (parent) {
parentNum = parent->exportNum;
parentName = parent->name.c_str();
}
else {
parentNum = -1;
parentName = "";
}
idCQuat bindQuat = joint->bindmat.ToQuat().ToCQuat();
WriteFloatString(file, "\t\"%s\"\t%d ( %f %f %f ) ( %f %f %f )\t\t// %s\n", joint->name.c_str(), parentNum,
joint->bindpos.x, joint->bindpos.y, joint->bindpos.z, bindQuat[0], bindQuat[1], bindQuat[2], parentName);
}
WriteFloatString(file, "}\n");
// write meshes
for (i = 0; i < meshes.Num(); i++) {
mesh = meshes[i];
if (!mesh->keep) {
continue;
}
WriteFloatString(file, "\nmesh {\n");
WriteFloatString(file, "\t// meshes: %s\n", mesh->name.c_str());
WriteFloatString(file, "\tshader \"%s\"\n", mesh->shader.c_str());
WriteFloatString(file, "\n\tnumverts %d\n", mesh->verts.Num());
for (j = 0; j < mesh->verts.Num(); j++) {
WriteFloatString(file, "\tvert %d ( %f %f ) %d %d\n", j, mesh->verts[j].texCoords[0], mesh->verts[j].texCoords[1],
mesh->verts[j].startweight, mesh->verts[j].numWeights);
}
WriteFloatString(file, "\n\tnumtris %d\n", mesh->tris.Num());
for (j = 0; j < mesh->tris.Num(); j++) {
WriteFloatString(file, "\ttri %d %d %d %d\n", j, mesh->tris[j].indexes[2], mesh->tris[j].indexes[1], mesh->tris[j].indexes[0]);
}
WriteFloatString(file, "\n\tnumweights %d\n", mesh->weights.Num());
for (j = 0; j < mesh->weights.Num(); j++) {
exportWeight_t* weight;
weight = &mesh->weights[j];
WriteFloatString(file, "\tweight %d %d %f ( %f %f %f )\n", j,
weight->joint->exportNum, weight->jointWeight, weight->offset.x, weight->offset.y, weight->offset.z);
}
WriteFloatString(file, "}\n");
}
fclose(file);
return true;
}
bool idExportModel::WriteAnim(const char* filename, idExportOptions& options) {
int i, j;
idExportJoint* joint;
idExportJoint* parent;
idExportJoint* sibling;
jointFrame_t* frame;
FILE* file;
int numAnimatedComponents;
idList jointList;
file = fopen(filename, "w");
if (!file) {
return false;
}
for (joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
jointList.Append(joint);
}
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
sibling = joint->exportNode.GetSibling();
while (sibling) {
if (idStr::Cmp(joint->name, sibling->name) > 0) {
joint->exportNode.MakeSiblingAfter(sibling->exportNode);
sibling = joint->exportNode.GetSibling();
}
else {
sibling = sibling->exportNode.GetSibling();
}
}
}
jointList.Clear();
for (joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
joint->exportNum = jointList.Append(joint);
}
numAnimatedComponents = 0;
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
joint->exportNum = i;
joint->baseFrame = frames[0][joint->index];
joint->animBits = 0;
for (j = 1; j < numFrames; j++) {
frame = &frames[j][joint->index];
if (fabs(frame->t[0] - joint->baseFrame.t[0]) > options.xyzPrecision) {
joint->animBits |= ANIM_TX;
}
if (fabs(frame->t[1] - joint->baseFrame.t[1]) > options.xyzPrecision) {
joint->animBits |= ANIM_TY;
}
if (fabs(frame->t[2] - joint->baseFrame.t[2]) > options.xyzPrecision) {
joint->animBits |= ANIM_TZ;
}
if (fabs(frame->q[0] - joint->baseFrame.q[0]) > options.quatPrecision) {
joint->animBits |= ANIM_QX;
}
if (fabs(frame->q[1] - joint->baseFrame.q[1]) > options.quatPrecision) {
joint->animBits |= ANIM_QY;
}
if (fabs(frame->q[2] - joint->baseFrame.q[2]) > options.quatPrecision) {
joint->animBits |= ANIM_QZ;
}
if ((joint->animBits & 63) == 63) {
break;
}
}
if (joint->animBits) {
joint->firstComponent = numAnimatedComponents;
for (j = 0; j < 6; j++) {
if (joint->animBits & BIT(j)) {
numAnimatedComponents++;
}
}
}
}
// write version info
WriteFloatString(file, MD5_VERSION_STRING " %d\n", MD5_VERSION);
WriteFloatString(file, "commandline \"%s\"\n\n", options.commandLine.c_str());
WriteFloatString(file, "numFrames %d\n", numFrames);
WriteFloatString(file, "numJoints %d\n", jointList.Num());
WriteFloatString(file, "frameRate %d\n", frameRate);
WriteFloatString(file, "numAnimatedComponents %d\n", numAnimatedComponents);
// write out the hierarchy
WriteFloatString(file, "\nhierarchy {\n");
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
parent = joint->exportNode.GetParent();
if (parent) {
WriteFloatString(file, "\t\"%s\"\t%d %d %d\t// %s", joint->name.c_str(), parent->exportNum, joint->animBits, joint->firstComponent, parent->name.c_str());
}
else {
WriteFloatString(file, "\t\"%s\"\t-1 %d %d\t//", joint->name.c_str(), joint->animBits, joint->firstComponent);
}
if (!joint->animBits) {
WriteFloatString(file, "\n");
}
else {
WriteFloatString(file, " ( ");
for (j = 0; j < 6; j++) {
if (joint->animBits & BIT(j)) {
WriteFloatString(file, "%s ", componentNames[j]);
}
}
WriteFloatString(file, ")\n");
}
}
WriteFloatString(file, "}\n");
// write the frame bounds
WriteFloatString(file, "\nbounds {\n");
for (i = 0; i < numFrames; i++) {
WriteFloatString(file, "\t( %f %f %f ) ( %f %f %f )\n", bounds[i][0].x, bounds[i][0].y, bounds[i][0].z, bounds[i][1].x, bounds[i][1].y, bounds[i][1].z);
}
WriteFloatString(file, "}\n");
// write the base frame
WriteFloatString(file, "\nbaseframe {\n");
for (i = 0; i < jointList.Num(); i++) {
joint = jointList[i];
WriteFloatString(file, "\t( %f %f %f ) ( %f %f %f )\n", joint->baseFrame.t[0], joint->baseFrame.t[1], joint->baseFrame.t[2],
joint->baseFrame.q[0], joint->baseFrame.q[1], joint->baseFrame.q[2]);
}
WriteFloatString(file, "}\n");
// write the frames
for (i = 0; i < numFrames; i++) {
WriteFloatString(file, "\nframe %d {\n", i);
for (j = 0; j < jointList.Num(); j++) {
joint = jointList[j];
frame = &frames[i][joint->index];
if (joint->animBits) {
WriteFloatString(file, "\t");
if (joint->animBits & ANIM_TX) {
WriteFloatString(file, " %f", frame->t[0]);
}
if (joint->animBits & ANIM_TY) {
WriteFloatString(file, " %f", frame->t[1]);
}
if (joint->animBits & ANIM_TZ) {
WriteFloatString(file, " %f", frame->t[2]);
}
if (joint->animBits & ANIM_QX) {
WriteFloatString(file, " %f", frame->q[0]);
}
if (joint->animBits & ANIM_QY) {
WriteFloatString(file, " %f", frame->q[1]);
}
if (joint->animBits & ANIM_QZ) {
WriteFloatString(file, " %f", frame->q[2]);
}
WriteFloatString(file, "\n");
}
}
WriteFloatString(file, "}\n");
}
fclose(file);
return true;
}
bool idExportModel::WriteCamera(const char* filename, idExportOptions& options) {
int i;
FILE* file;
file = fopen(filename, "w");
if (!file) {
return false;
}
// write version info
WriteFloatString(file, MD5_VERSION_STRING " %d\n", MD5_VERSION);
WriteFloatString(file, "commandline \"%s\"\n\n", options.commandLine.c_str());
WriteFloatString(file, "numFrames %d\n", camera.Num());
WriteFloatString(file, "frameRate %d\n", frameRate);
WriteFloatString(file, "numCuts %d\n", cameraCuts.Num());
// write out the cuts
WriteFloatString(file, "\ncuts {\n");
for (i = 0; i < cameraCuts.Num(); i++) {
WriteFloatString(file, "\t%d\n", cameraCuts[i]);
}
WriteFloatString(file, "}\n");
// write out the frames
WriteFloatString(file, "\ncamera {\n");
cameraFrame_t* frame = camera.Ptr();
for (i = 0; i < camera.Num(); i++, frame++) {
WriteFloatString(file, "\t( %f %f %f ) ( %f %f %f ) %f\n", frame->t.x, frame->t.y, frame->t.z, frame->q[0], frame->q[1], frame->q[2], frame->fov);
}
WriteFloatString(file, "}\n");
fclose(file);
return true;
}
/*
==============================================================================
FBX scene extraction
==============================================================================
*/
static bool IsMD3Export(idExportOptions& options) {
idTokenizer tokens;
tokens.SetTokens(options.commandLine.c_str());
idStr first = tokens.NextToken();
if (first == "md3") {
return true;
}
if (options.dest.Length() >= 4) {
idStr ext = options.dest.Right(4);
if (!ext.Icmp(".md3")) {
return true;
}
}
return false;
}
static const char* FbxSafeName(const char* name) {
return (name && name[0]) ? name : "unnamed";
}
static void MD3_CopyString(char* dst, int dstSize, const char* src) {
memset(dst, 0, dstSize);
if (src == NULL) {
return;
}
strncpy(dst, src, dstSize - 1);
}
static int MD3_ClampShort(int v) {
if (v < -32768) {
return -32768;
}
if (v > 32767) {
return 32767;
}
return v;
}
static short MD3_EncodeNormal(const idVec3& normal) {
const float twoPi = 6.28318530717958647692f;
idVec3 n = normal;
if (n.Normalize() == 0.0f) {
return 0;
}
int lat = (int)(atan2(n.y, n.x) * 255.0f / twoPi);
int lng = (int)(acos(n.z) * 255.0f / twoPi);
return (short)(((lat & 0xff) << 8) | (lng & 0xff));
}
static void MD3_BuildWorldPose(idExportModel& model, jointFrame_t* frame) {
idExportJoint* joint;
idExportJoint* parent;
idVec3 jointpos;
idMat3 jointaxis;
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;
}
}
}
static void MD3_BuildMeshPositions(idExportMesh* mesh, idList& positions) {
int i, j;
exportVertex_t* vert;
exportWeight_t* weight;
idVec3 pos;
positions.SetNum(mesh->verts.Num());
for (i = 0; i < mesh->verts.Num(); i++) {
vert = &mesh->verts[i];
pos.Zero();
weight = &mesh->weights[vert->startweight];
for (j = 0; j < vert->numWeights; j++, weight++) {
pos += weight->jointWeight * (weight->joint->idwm * weight->offset + weight->joint->idt);
}
positions[i] = pos;
}
}
static void MD3_BuildMeshNormals(idExportMesh* mesh, const idList& positions, idList& normals) {
int i, j;
idVec3 a, b, n;
int indexes[3];
normals.SetNum(mesh->verts.Num());
for (i = 0; i < normals.Num(); i++) {
normals[i].Zero();
}
for (i = 0; i < mesh->tris.Num(); i++) {
indexes[0] = mesh->tris[i].indexes[2];
indexes[1] = mesh->tris[i].indexes[1];
indexes[2] = mesh->tris[i].indexes[0];
a = positions[indexes[1]] - positions[indexes[0]];
b = positions[indexes[2]] - positions[indexes[0]];
n = a.Cross(b);
n.Normalize();
for (j = 0; j < 3; j++) {
normals[indexes[j]] += n;
}
}
for (i = 0; i < normals.Num(); i++) {
normals[i].Normalize();
}
}
static void MD3_BuildTagList(idExportModel& model, idList& tags) {
idExportJoint* joint;
tags.Clear();
for (joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
if (!joint->name.Icmpn("tag_", 4)) {
if (tags.Num() >= MD3_MAX_TAGS) {
MayaError("MD3 export supports only %d tags", MD3_MAX_TAGS);
}
tags.Append(joint);
}
}
}
static bool WriteMD3Model(idExportModel& model, const char* filename, idExportOptions& options) {
int i, j, frameNum;
int numSurfaces;
idBounds bounds;
idList tags;
idList md3Frames;
idList positions;
idList normals;
idExportMesh* mesh;
FILE* file;
MD3_BuildTagList(model, tags);
if (model.numFrames < 1 || model.numFrames > MD3_MAX_FRAMES) {
MayaError("MD3 export requires 1..%d frames, got %d", MD3_MAX_FRAMES, model.numFrames);
}
numSurfaces = 0;
for (i = 0; i < model.meshes.Num(); i++) {
if (model.meshes[i]->keep) {
numSurfaces++;
}
}
if (numSurfaces < 1) {
MayaError("MD3 export has no mesh surfaces");
}
if (numSurfaces > MD3_MAX_SURFACES) {
MayaError("MD3 export supports only %d surfaces, got %d", MD3_MAX_SURFACES, numSurfaces);
}
md3Frames.SetNum(model.numFrames);
for (frameNum = 0; frameNum < model.numFrames; frameNum++) {
MD3_BuildWorldPose(model, model.frames[frameNum]);
bounds.Clear();
for (i = 0; i < model.meshes.Num(); i++) {
mesh = model.meshes[i];
if (!mesh->keep) {
continue;
}
MD3_BuildMeshPositions(mesh, positions);
for (j = 0; j < positions.Num(); j++) {
bounds.AddPoint(positions[j]);
}
}
memset(&md3Frames[frameNum], 0, sizeof(md3Frames[frameNum]));
md3Frames[frameNum].bounds[0] = bounds[0];
md3Frames[frameNum].bounds[1] = bounds[1];
md3Frames[frameNum].localOrigin = (bounds[0] + bounds[1]) * 0.5f;
md3Frames[frameNum].radius = 0.0f;
for (i = 0; i < 8; i++) {
idVec3 corner;
corner.x = (i & 1) ? bounds[1].x : bounds[0].x;
corner.y = (i & 2) ? bounds[1].y : bounds[0].y;
corner.z = (i & 4) ? bounds[1].z : bounds[0].z;
float radius = (corner - md3Frames[frameNum].localOrigin).Length();
if (radius > md3Frames[frameNum].radius) {
md3Frames[frameNum].radius = radius;
}
}
sprintf(md3Frames[frameNum].name, "frame%d", frameNum);
}
file = fopen(filename, "wb");
if (!file) {
return false;
}
md3Header_t header;
memset(&header, 0, sizeof(header));
header.ident = MD3_IDENT;
header.version = MD3_VERSION;
MD3_CopyString(header.name, sizeof(header.name), filename);
header.numFrames = model.numFrames;
header.numTags = tags.Num();
header.numSurfaces = numSurfaces;
header.numSkins = 0;
header.ofsFrames = sizeof(md3Header_t);
header.ofsTags = header.ofsFrames + sizeof(md3Frame_t) * header.numFrames;
header.ofsSurfaces = header.ofsTags + sizeof(md3Tag_t) * header.numFrames * header.numTags;
header.ofsEnd = header.ofsSurfaces;
for (i = 0; i < model.meshes.Num(); i++) {
mesh = model.meshes[i];
if (!mesh->keep) {
continue;
}
if (mesh->verts.Num() > MD3_MAX_VERTS) {
MayaError("MD3 surface '%s' has %d verts, max is %d", mesh->name.c_str(), mesh->verts.Num(), MD3_MAX_VERTS);
}
if (mesh->tris.Num() > MD3_MAX_TRIANGLES) {
MayaError("MD3 surface '%s' has %d tris, max is %d", mesh->name.c_str(), mesh->tris.Num(), MD3_MAX_TRIANGLES);
}
header.ofsEnd += sizeof(md3Surface_t) + sizeof(md3Shader_t) + sizeof(md3Triangle_t) * mesh->tris.Num() + sizeof(md3St_t) * mesh->verts.Num() + sizeof(md3XyzNormal_t) * mesh->verts.Num() * model.numFrames;
}
fwrite(&header, sizeof(header), 1, file);
fwrite(md3Frames.Ptr(), sizeof(md3Frame_t), md3Frames.Num(), file);
for (frameNum = 0; frameNum < model.numFrames; frameNum++) {
MD3_BuildWorldPose(model, model.frames[frameNum]);
for (i = 0; i < tags.Num(); i++) {
md3Tag_t tag;
memset(&tag, 0, sizeof(tag));
MD3_CopyString(tag.name, sizeof(tag.name), tags[i]->name.c_str());
tag.origin = tags[i]->idt;
tag.axis[0] = tags[i]->idwm[0];
tag.axis[1] = tags[i]->idwm[1];
tag.axis[2] = tags[i]->idwm[2];
fwrite(&tag, sizeof(tag), 1, file);
}
}
for (i = 0; i < model.meshes.Num(); i++) {
mesh = model.meshes[i];
if (!mesh->keep) {
continue;
}
md3Surface_t surface;
memset(&surface, 0, sizeof(surface));
surface.ident = MD3_IDENT;
MD3_CopyString(surface.name, sizeof(surface.name), mesh->name.c_str());
surface.flags = 0;
surface.numFrames = model.numFrames;
surface.numShaders = 1;
surface.numVerts = mesh->verts.Num();
surface.numTriangles = mesh->tris.Num();
surface.ofsShaders = sizeof(md3Surface_t);
surface.ofsTriangles = surface.ofsShaders + sizeof(md3Shader_t) * surface.numShaders;
surface.ofsSt = surface.ofsTriangles + sizeof(md3Triangle_t) * surface.numTriangles;
surface.ofsXyzNormals = surface.ofsSt + sizeof(md3St_t) * surface.numVerts;
surface.ofsEnd = surface.ofsXyzNormals + sizeof(md3XyzNormal_t) * surface.numVerts * surface.numFrames;
fwrite(&surface, sizeof(surface), 1, file);
md3Shader_t shader;
memset(&shader, 0, sizeof(shader));
if (mesh->shader.Length()) {
MD3_CopyString(shader.name, sizeof(shader.name), mesh->shader.c_str());
}
else {
MD3_CopyString(shader.name, sizeof(shader.name), mesh->name.c_str());
}
shader.shaderIndex = 0;
fwrite(&shader, sizeof(shader), 1, file);
for (j = 0; j < mesh->tris.Num(); j++) {
md3Triangle_t tri;
tri.indexes[0] = mesh->tris[j].indexes[2];
tri.indexes[1] = mesh->tris[j].indexes[1];
tri.indexes[2] = mesh->tris[j].indexes[0];
fwrite(&tri, sizeof(tri), 1, file);
}
for (j = 0; j < mesh->verts.Num(); j++) {
md3St_t st;
st.st[0] = mesh->verts[j].texCoords[0];
st.st[1] = mesh->verts[j].texCoords[1];
fwrite(&st, sizeof(st), 1, file);
}
for (frameNum = 0; frameNum < model.numFrames; frameNum++) {
MD3_BuildWorldPose(model, model.frames[frameNum]);
MD3_BuildMeshPositions(mesh, positions);
MD3_BuildMeshNormals(mesh, positions, normals);
for (j = 0; j < positions.Num(); j++) {
md3XyzNormal_t xyz;
xyz.xyz[0] = (short)MD3_ClampShort((int)floor(positions[j].x / MD3_XYZ_SCALE + 0.5f));
xyz.xyz[1] = (short)MD3_ClampShort((int)floor(positions[j].y / MD3_XYZ_SCALE + 0.5f));
xyz.xyz[2] = (short)MD3_ClampShort((int)floor(positions[j].z / MD3_XYZ_SCALE + 0.5f));
xyz.normal = MD3_EncodeNormal(normals[j]);
fwrite(&xyz, sizeof(xyz), 1, file);
}
}
}
fclose(file);
return true;
}
class idFbxExport {
private:
idExportModel model;
idExportOptions& options;
FbxManager* sdkManager;
FbxScene* scene;
FbxAnimStack* animStack;
FbxTime currentTime;
void LoadScene(void);
void SetDefaultFrameRange(void);
float TimeForFrame(int num) const;
int GetFbxFrameNum(int num) const;
FbxTime TimeForFbxFrame(int num) const;
void SetFrame(int num);
void AddNodesRecursive(FbxNode* node);
void CreateJoints(float scale);
idExportJoint* FindJointForNode(FbxNode* node);
void SetJointBindPose(idExportJoint* joint, const FbxAMatrix& matrix, float scale);
void GetWorldTransform(idExportJoint* joint, idVec3& pos, idMat3& mat, float scale);
void PruneJoints(idStrList& keepjoints, idStr& prefix);
void RenameJoints(idList& renamejoints, idStr& prefix);
bool RemapParents(idList& remapjoints);
void GetTextureForMesh(idExportMesh* mesh, FbxNode* node);
idExportMesh* CopyMesh(FbxNode* node, FbxMesh* fbxMesh, float scale);
void CreateMeshRecursive(FbxNode* node, int& count);
void CreateMesh(float scale);
void CombineMeshes(void);
void GetAlignment(idStr& alignName, idMat3& align, float rotate, int startframe);
idExportJoint* FindFirstCamera(void);
float GetCameraFov(idExportJoint* joint);
void GetCameraFrame(idExportJoint* camera, idMat3& align, cameraFrame_t* cam);
void CreateCameraAnim(idMat3& align);
void GetDefaultPose(idMat3& align);
void CreateAnimation(idMat3& align);
public:
idFbxExport(idExportOptions& exportOptions) : options(exportOptions) {
sdkManager = NULL;
scene = NULL;
animStack = NULL;
currentTime.SetFrame(0, FbxTime::eFrames24);
};
~idFbxExport() {
if (sdkManager != NULL) {
sdkManager->Destroy();
sdkManager = NULL;
scene = NULL;
animStack = NULL;
}
};
void ConvertModel(void);
};
void idFbxExport::LoadScene(void) {
sdkManager = FbxManager::Create();
if (sdkManager == NULL) {
MayaError("FBX SDK manager creation failed");
}
FbxIOSettings* ios = FbxIOSettings::Create(sdkManager, IOSROOT);
sdkManager->SetIOSettings(ios);
FbxImporter* importer = FbxImporter::Create(sdkManager, "");
if (importer == NULL) {
MayaError("FBX importer creation failed");
}
if (!importer->Initialize(options.src.c_str(), -1, sdkManager->GetIOSettings())) {
const char* err = importer->GetStatus().GetErrorString();
importer->Destroy();
MayaError("Error opening FBX '%s': '%s'", options.src.c_str(), err ? err : "unknown error");
}
scene = FbxScene::Create(sdkManager, "doom3_fbx_scene");
if (scene == NULL) {
importer->Destroy();
MayaError("FBX scene creation failed");
}
if (!importer->Import(scene)) {
const char* err = importer->GetStatus().GetErrorString();
importer->Destroy();
MayaError("Error importing FBX '%s': '%s'", options.src.c_str(), err ? err : "unknown error");
}
importer->Destroy();
// Match the old Maya exporter assumptions before applying ConvertToIdSpace().
FbxAxisSystem::MayaYUp.ConvertScene(scene);
FbxSystemUnit::cm.ConvertScene(scene);
FbxGeometryConverter converter(sdkManager);
converter.Triangulate(scene, true);
animStack = scene->GetSrcObject(0);
if (animStack != NULL) {
scene->SetCurrentAnimationStack(animStack);
}
}
FbxTime idFbxExport::TimeForFbxFrame(int num) const {
FbxTime time;
time.SetFrame(num, FbxTime::eFrames24);
return time;
}
float idFbxExport::TimeForFrame(int num) const {
return (float)TimeForFbxFrame(num).GetSecondDouble();
}
void idFbxExport::SetDefaultFrameRange(void) {
if (options.startframe < 0 || options.endframe < 0) {
FbxTime start;
FbxTime stop;
start.SetFrame(0, FbxTime::eFrames24);
stop.SetFrame(0, FbxTime::eFrames24);
if (animStack != NULL) {
FbxTimeSpan span = animStack->GetLocalTimeSpan();
start = span.GetStart();
stop = span.GetStop();
}
else if (scene != NULL && scene->GetRootNode() != NULL) {
FbxTimeSpan span;
if (scene->GetRootNode()->GetAnimationInterval(span, animStack)) {
start = span.GetStart();
stop = span.GetStop();
}
}
if (options.startframe < 0) {
options.startframe = (int)start.GetFrameCount(FbxTime::eFrames24);
}
if (options.endframe < 0) {
options.endframe = (int)stop.GetFrameCount(FbxTime::eFrames24);
}
}
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) {
options.cycleStart = options.startframe;
}
}
int idFbxExport::GetFbxFrameNum(int num) const {
int frameNum;
if (options.cycleStart > options.startframe) {
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;
}
void idFbxExport::SetFrame(int num) {
currentTime = TimeForFbxFrame(GetFbxFrameNum(num));
}
void idFbxExport::AddNodesRecursive(FbxNode* node) {
int i;
idExportJoint* joint;
if (node == NULL) {
return;
}
if (node != scene->GetRootNode()) {
joint = &model.joints.Alloc();
joint->index = model.joints.Num() - 1;
joint->dagnode = node;
joint->name = FbxSafeName(node->GetName());
joint->realname = joint->name;
}
for (i = 0; i < node->GetChildCount(); i++) {
AddNodesRecursive(node->GetChild(i));
}
}
idExportJoint* idFbxExport::FindJointForNode(FbxNode* node) {
int i;
if (node == NULL) {
return NULL;
}
for (i = 0; i < model.joints.Num(); i++) {
if (model.joints[i].dagnode == node) {
return &model.joints[i];
}
}
return NULL;
}
void idFbxExport::SetJointBindPose(idExportJoint* joint, const FbxAMatrix& matrix, float scale) {
if (joint == NULL) {
return;
}
joint->bindmat = ConvertToIdSpace(idMat(matrix));
joint->bindmat.OrthoNormalizeSelf();
joint->bindpos = ConvertToIdSpace(idVec(matrix.GetT())) * scale;
joint->scale = 1.0f;
joint->invscale = 1.0f;
}
void idFbxExport::CreateJoints(float scale) {
int i, j;
idExportJoint* joint;
idExportJoint* parent;
SetFrame(0);
AddNodesRecursive(scene->GetRootNode());
model.exportOrigin = &model.joints.Alloc();
model.exportOrigin->index = model.joints.Num() - 1;
model.exportOrigin->dagnode = NULL;
for (i = 0; i < model.joints.Num(); i++) {
joint = &model.joints[i];
if (!joint->dagnode) {
continue;
}
joint->mayaNode.ParentTo(model.mayaHead);
joint->exportNode.ParentTo(model.exportHead);
FbxNode* parentNode = joint->dagnode->GetParent();
while (parentNode != NULL && parentNode != scene->GetRootNode()) {
for (j = 0; j < model.joints.Num(); j++) {
if (model.joints[j].dagnode == parentNode) {
joint->mayaNode.ParentTo(model.joints[j].mayaNode);
joint->exportNode.ParentTo(model.joints[j].exportNode);
break;
}
}
if (j < model.joints.Num()) {
break;
}
parentNode = parentNode->GetParent();
}
parent = joint->mayaNode.GetParent();
if (parent) {
joint->longname = parent->longname + "/" + joint->name;
}
else {
joint->longname = joint->name;
}
SetJointBindPose(joint, joint->dagnode->EvaluateGlobalTransform(currentTime), scale);
}
}
void idFbxExport::GetWorldTransform(idExportJoint* joint, idVec3& pos, idMat3& mat, float scale) {
pos.Zero();
mat.Identity();
if (joint == NULL || joint->dagnode == NULL) {
return;
}
FbxAMatrix global = joint->dagnode->EvaluateGlobalTransform(currentTime, FbxNode::eSourcePivot, false, true);
pos = idVec(global.GetT()) * scale;
mat = idMat(global);
mat.OrthoNormalizeSelf();
}
void idFbxExport::PruneJoints(idStrList& keepjoints, idStr& prefix) {
int i;
int j;
idExportMesh* mesh;
idExportJoint* joint;
idExportJoint* joint2;
idExportJoint* parent;
int num_weights;
if (!keepjoints.Num() && !prefix.Length()) {
if (!model.meshes.Num() || options.ignoreMeshes) {
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 = model.meshes[i];
for (j = 0; j < mesh->weights.Num(); j++) {
mesh->weights[j].joint->keep = true;
}
}
}
}
else {
for (i = 0; i < keepjoints.Num(); i++) {
joint = model.FindJoint(keepjoints[i]);
if (joint) {
joint->keep = true;
}
}
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())) {
mesh->weights[j].joint->keep = true;
num_weights++;
}
}
if (num_weights != mesh->weights.Num()) {
mesh->keep = false;
}
}
}
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++;
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);
}
}
}
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());
}
}
}
}
void idFbxExport::RenameJoints(idList& renamejoints, idStr& prefix) {
int i;
idExportJoint* joint;
if (prefix.Length()) {
joint = model.joints.Ptr();
for (i = 0; i < model.joints.Num(); i++, joint++) {
if (!joint->name.Cmpn(prefix, prefix.Length())) {
joint->name = joint->name.Right(joint->name.Length() - prefix.Length());
}
}
}
for (i = 0; i < renamejoints.Num(); i++) {
joint = model.FindJoint(renamejoints[i].from);
if (joint) {
joint->name = renamejoints[i].to;
}
}
}
bool idFbxExport::RemapParents(idList& remapjoints) {
int i;
idExportJoint* joint;
idExportJoint* parent;
idExportJoint* origin;
idExportJoint* sibling;
for (i = 0; i < remapjoints.Num(); i++) {
joint = model.FindJoint(remapjoints[i].from);
if (!joint) {
MayaError("Couldn't find joint '%s' to reparent\n", remapjoints[i].from.c_str());
}
parent = model.FindJoint(remapjoints[i].to);
if (!parent) {
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);
}
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);
origin->keep = true;
joint = model.exportHead.GetChild();
while (joint) {
sibling = joint->exportNode.GetSibling();
if (joint != origin) {
joint->exportNode.ParentTo(origin->exportNode);
}
joint = sibling;
}
return true;
}
void idFbxExport::GetTextureForMesh(idExportMesh* mesh, FbxNode* node) {
int i;
FbxSurfaceMaterial* material;
FbxProperty prop;
mesh->shader = "";
if (node == NULL) {
return;
}
for (i = 0; i < node->GetMaterialCount(); i++) {
material = node->GetMaterial(i);
if (material == NULL) {
continue;
}
prop = material->FindProperty(FbxSurfaceMaterial::sDiffuse);
if (prop.IsValid()) {
int textureCount = prop.GetSrcObjectCount();
if (textureCount > 0) {
FbxFileTexture* texture = prop.GetSrcObject(0);
if (texture != NULL && texture->GetFileName() != NULL && texture->GetFileName()[0]) {
OSPathToRelativePath(texture->GetFileName(), mesh->shader, options.game);
mesh->shader.StripFileExtension();
return;
}
}
}
if (material->GetName() && material->GetName()[0]) {
mesh->shader = material->GetName();
return;
}
}
}
idExportMesh* idFbxExport::CopyMesh(FbxNode* node, FbxMesh* fbxMesh, float scale) {
int i, j, k;
int cpCount;
int polygonCount;
idStr name, altname;
idExportMesh* mesh;
bool allowUnskinned;
bool hasSkin;
FbxAMatrix meshBindMatrix;
FbxAMatrix geometryMatrix;
FbxStringList uvSets;
const char* uvSetName;
if (node == NULL || fbxMesh == NULL) {
return NULL;
}
name = FbxSafeName(fbxMesh->GetName());
altname = FbxSafeName(node->GetName());
if (!name.Length() || name == "unnamed") {
name = altname;
}
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;
}
allowUnskinned = IsMD3Export(options);
hasSkin = fbxMesh->GetDeformerCount(FbxDeformer::eSkin) > 0;
if (!hasSkin && !allowUnskinned) {
MayaError("Mesh '%s': No skin deformers found. MD5 export expects a skinned FBX mesh.", altname.c_str());
}
mesh = new idExportMesh();
model.meshes.Append(mesh);
mesh->name = altname.Length() ? altname : name;
GetTextureForMesh(mesh, node);
cpCount = fbxMesh->GetControlPointsCount();
mesh->verts.SetNum(cpCount);
geometryMatrix = FbxGetGeometryTransform(node);
meshBindMatrix = node->EvaluateGlobalTransform(currentTime) * geometryMatrix;
for (i = 0; i < cpCount; i++) {
memset(&mesh->verts[i], 0, sizeof(mesh->verts[i]));
FbxVector4 p = meshBindMatrix.MultT(fbxMesh->GetControlPointAt(i));
mesh->verts[i].pos = ConvertToIdSpace(idVec(p)) * scale;
}
polygonCount = fbxMesh->GetPolygonCount();
mesh->tris.SetNum(polygonCount);
mesh->uv.SetNum(polygonCount);
fbxMesh->GetUVSetNames(uvSets);
uvSetName = (uvSets.GetCount() > 0) ? uvSets[0] : NULL;
for (i = 0; i < polygonCount; i++) {
if (fbxMesh->GetPolygonSize(i) != 3) {
MayaError("CopyMesh: FBX triangulation failed; polygon %d on '%s' has %d vertices", i, mesh->name.c_str(), fbxMesh->GetPolygonSize(i));
}
for (j = 0; j < 3; j++) {
mesh->tris[i].indexes[j] = fbxMesh->GetPolygonVertex(i, j);
mesh->uv[i].uv[j][0] = 0.0f;
mesh->uv[i].uv[j][1] = 0.0f;
if (uvSetName != NULL) {
FbxVector2 uv;
bool unmapped = false;
if (fbxMesh->GetPolygonVertexUV(i, j, uvSetName, uv, unmapped) && !unmapped) {
mesh->uv[i].uv[j][0] = (float)uv[0];
mesh->uv[i].uv[j][1] = (float)uv[1];
}
}
}
}
idList< idList > cpWeights;
cpWeights.SetNum(cpCount);
if (hasSkin) {
bool meshBindSet = false;
for (i = 0; i < fbxMesh->GetDeformerCount(FbxDeformer::eSkin); i++) {
FbxSkin* skin = (FbxSkin*)fbxMesh->GetDeformer(i, FbxDeformer::eSkin);
if (skin == NULL) {
continue;
}
for (j = 0; j < skin->GetClusterCount(); j++) {
FbxCluster* cluster = skin->GetCluster(j);
if (cluster == NULL || cluster->GetLink() == NULL) {
continue;
}
idExportJoint* joint = FindJointForNode(cluster->GetLink());
if (joint == NULL) {
MayaError("Mesh '%s': joint '%s' not found", mesh->name.c_str(), FbxSafeName(cluster->GetLink()->GetName()));
}
FbxAMatrix linkMatrix;
cluster->GetTransformLinkMatrix(linkMatrix);
SetJointBindPose(joint, linkMatrix, scale);
if (!meshBindSet) {
cluster->GetTransformMatrix(meshBindMatrix);
meshBindMatrix = meshBindMatrix * geometryMatrix;
for (k = 0; k < cpCount; k++) {
FbxVector4 p = meshBindMatrix.MultT(fbxMesh->GetControlPointAt(k));
mesh->verts[k].pos = ConvertToIdSpace(idVec(p)) * scale;
}
meshBindSet = true;
}
int* indices = cluster->GetControlPointIndices();
double* weights = cluster->GetControlPointWeights();
int weightCount = cluster->GetControlPointIndicesCount();
for (k = 0; k < weightCount; k++) {
int cpIndex = indices[k];
if (cpIndex < 0 || cpIndex >= cpCount) {
continue;
}
if (weights[k] <= 0.0) {
continue;
}
exportWeight_t weight;
weight.joint = joint;
weight.jointWeight = (float)weights[k];
weight.offset.Zero();
cpWeights[cpIndex].Append(weight);
}
}
}
}
else {
idExportJoint* joint = FindJointForNode(node);
if (joint == NULL) {
joint = model.exportOrigin;
joint->name = "origin";
joint->realname = "origin";
joint->bindmat.Identity();
joint->bindpos.Zero();
joint->keep = true;
}
else {
SetJointBindPose(joint, node->EvaluateGlobalTransform(currentTime), scale);
}
for (i = 0; i < cpCount; i++) {
exportWeight_t weight;
weight.joint = joint;
weight.jointWeight = 1.0f;
weight.offset.Zero();
cpWeights[i].Append(weight);
}
}
for (i = 0; i < cpCount; i++) {
exportVertex_t* vert = &mesh->verts[i];
vert->startweight = mesh->weights.Num();
float totalWeight = 0.0f;
int nonZeroWeights = 0;
for (j = 0; j < cpWeights[i].Num(); j++) {
if (cpWeights[i][j].jointWeight > 0.0f) {
nonZeroWeights++;
}
if (cpWeights[i][j].jointWeight <= options.jointThreshold) {
continue;
}
exportWeight_t weight = cpWeights[i][j];
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 (nonZeroWeights) {
MayaError("Error on mesh '%s': Vertex %d doesn't have any joint weights exceeding jointThreshold (%f).", mesh->name.c_str(), i, options.jointThreshold);
}
else {
MayaError("Error on mesh '%s': Vertex %d doesn't have any joint weights.", mesh->name.c_str(), i);
}
}
else if (!totalWeight) {
MayaError("Error on mesh '%s': Combined weight of 0 on vertex %d.", mesh->name.c_str(), i);
}
for (j = 0; j < vert->numWeights; j++) {
mesh->weights[vert->startweight + j].jointWeight /= totalWeight;
}
}
return mesh;
}
void idFbxExport::CreateMeshRecursive(FbxNode* node, int& count) {
if (node == NULL) {
return;
}
FbxMesh* mesh = node->GetMesh();
if (mesh != NULL) {
if (CopyMesh(node, mesh, options.scale) != NULL) {
count++;
}
}
for (int i = 0; i < node->GetChildCount(); i++) {
CreateMeshRecursive(node->GetChild(i), count);
}
}
void idFbxExport::CreateMesh(float scale) {
int count = 0;
CreateMeshRecursive(scene->GetRootNode(), count);
if (!count && !options.ignoreMeshes) {
MayaError("CreateMesh: No FBX meshes found in this scene.\n");
}
}
void idFbxExport::CombineMeshes(void) {
int i, j;
int count;
idExportMesh* mesh;
idExportMesh* combine;
idList oldmeshes;
oldmeshes = model.meshes;
model.meshes.Clear();
count = 0;
for (i = 0; i < oldmeshes.Num(); i++) {
mesh = oldmeshes[i];
if (!mesh->keep) {
delete mesh;
continue;
}
combine = NULL;
for (j = 0; j < model.meshes.Num(); j++) {
if (model.meshes[j]->shader == mesh->shader) {
combine = model.meshes[j];
break;
}
}
if (combine) {
combine->Merge(mesh);
delete mesh;
count++;
}
else {
model.meshes.Append(mesh);
}
}
for (i = 0; i < model.meshes.Num(); i++) {
model.meshes[i]->ShareVerts();
}
common->Printf("Merged %d meshes\n", count);
}
void idFbxExport::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());
}
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();
}
idExportJoint* idFbxExport::FindFirstCamera(void) {
int i;
idExportJoint* joint;
joint = model.FindJoint(MAYA_DEFAULT_CAMERA);
if (joint && joint->dagnode && joint->dagnode->GetCamera()) {
return joint;
}
for (i = 0; i < model.joints.Num(); i++) {
joint = &model.joints[i];
if (joint->dagnode && joint->dagnode->GetCamera()) {
return joint;
}
}
return NULL;
}
float idFbxExport::GetCameraFov(idExportJoint* joint) {
if (joint == NULL || joint->dagnode == NULL || joint->dagnode->GetCamera() == NULL) {
return 90.0f;
}
FbxCamera* camera = joint->dagnode->GetCamera();
if (camera->GetApertureMode() == FbxCamera::eFocalLength) {
return (float)camera->ComputeFieldOfView(camera->FocalLength.Get());
}
if (camera->GetApertureMode() == FbxCamera::eHorizAndVert) {
return (float)camera->FieldOfViewX.Get();
}
return (float)camera->FieldOfView.Get();
}
void idFbxExport::GetCameraFrame(idExportJoint* camera, idMat3& align, cameraFrame_t* cam) {
idMat3 mat;
idMat3 axis;
idVec3 pos;
GetWorldTransform(camera, pos, axis, 1.0f);
cam->t = ConvertToIdSpace(pos) * align;
axis = ConvertToIdSpace(axis) * align;
mat[0] = -axis[2];
mat[1] = -axis[0];
mat[2] = axis[1];
cam->q = mat.ToQuat().ToCQuat();
cam->fov = GetCameraFov(camera);
}
void idFbxExport::CreateCameraAnim(idMat3& align) {
float start, end;
int frameNum;
idExportJoint* camJoint;
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.camera.Clear();
model.cameraCuts.Clear();
camJoint = FindFirstCamera();
if (camJoint == NULL) {
MayaError("Couldn't find an FBX camera node");
}
for (frameNum = 0; frameNum < model.numFrames; frameNum++) {
common->Printf("\rFrame %d/%d...", options.startframe + frameNum, options.endframe);
SetFrame(frameNum);
GetCameraFrame(camJoint, align, &model.camera.Alloc());
}
common->Printf("\n");
}
void idFbxExport::GetDefaultPose(idMat3& align) {
float start;
idMat3 jointaxis;
idVec3 jointpos;
idExportJoint* joint, * parent;
idList frame;
start = TimeForFrame(options.startframe);
common->Printf("default pose frame = %d\n", options.startframe);
common->Printf(" default pose time = %f\n", start);
frame.SetNum(model.joints.Num());
SetFrame(0);
for (joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
if (!joint->dagnode) {
joint->idwm.Identity();
joint->idt.Zero();
frame[joint->index].t.Zero();
frame[joint->index].q.Set(0.0f, 0.0f, 0.0f);
continue;
}
GetWorldTransform(joint, jointpos, jointaxis, options.scale);
jointaxis = ConvertToIdSpace(jointaxis) * align;
jointpos = ConvertToIdSpace(jointpos) * align;
joint->idwm = jointaxis;
joint->idt = jointpos;
parent = joint->exportNode.GetParent();
if (parent) {
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();
}
joint = model.FindJoint("origin");
if (joint) {
frame[joint->index].t.Zero();
}
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");
}
void idFbxExport::CreateAnimation(idMat3& align) {
int i;
float start, end;
idMat3 jointaxis;
idVec3 jointpos;
int frameNum;
idExportJoint* joint, * parent;
idBounds bnds;
idBounds meshBounds;
jointFrame_t* frame;
int cycleStart;
idVec3 totalDelta;
idList copyFrames;
start = TimeForFrame(options.startframe);
end = TimeForFrame(options.endframe);
model.numFrames = options.endframe + 1 - options.startframe;
model.frameRate = options.framerate;
common->Printf("start frame = %d\n end frame = %d\n", options.startframe, options.endframe);
common->Printf(" start time = %f\n end time = %f\n total time = %f\n", start, end, end - start);
if (start > end) {
MayaError("Start frame is greater than end frame.");
}
model.bounds.SetNum(model.numFrames);
model.jointFrames.SetNum(model.numFrames * model.joints.Num());
model.frames.SetNum(model.numFrames);
for (i = 0; i < model.numFrames; i++) {
model.frames[i] = &model.jointFrames[model.joints.Num() * i];
}
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);
for (joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext()) {
if (!joint->dagnode) {
joint->idwm.Identity();
joint->idt.Zero();
frame[joint->index].t.Zero();
frame[joint->index].q.Set(0.0f, 0.0f, 0.0f);
continue;
}
GetWorldTransform(joint, jointpos, jointaxis, options.scale);
jointaxis = ConvertToIdSpace(jointaxis) * align;
jointpos = ConvertToIdSpace(jointpos) * align;
joint->idwm = jointaxis;
joint->idt = jointpos;
parent = joint->exportNode.GetParent();
if (parent) {
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;
}
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) {
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) {
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;
}
}
for (frameNum = 0; frameNum < model.numFrames; frameNum++) {
frame = model.frames[frameNum];
MD3_BuildWorldPose(model, 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");
}
void idFbxExport::ConvertModel(void) {
idMat3 align;
common->Printf("Converting %s to %s...\n", options.src.c_str(), options.dest.c_str());
FILE* file = fopen(options.dest, "r");
if (file) {
fclose(file);
FILE* file = fopen(options.dest, "r+");
if (!file) {
MayaError("Unable to write to the file '%s'", options.dest.c_str());
}
fclose(file);
}
common->Printf("Loading FBX file...\n");
LoadScene();
SetDefaultFrameRange();
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);
if (IsMD3Export(options)) {
common->Printf("Creating MD3 vertex frames:\n");
CreateAnimation(align);
common->Printf("Writing MD3 file...\n");
if (!WriteMD3Model(model, options.dest, options)) {
MayaError("error writing to '%s'", options.dest.c_str());
}
common->Printf("done\n\n");
return;
}
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");
}
/*
==============================================================================
dll setup
==============================================================================
*/
/*
===============
Maya_Shutdown
===============
*/
void Maya_Shutdown(void) {
if (initialized) {
errorMessage.Clear();
initialized = false;
// FbxManager instances are owned per conversion and destroyed by idFbxExport.
}
}
/*
===============
Maya_ConvertModel
===============
*/
const char* Maya_ConvertModel(const char* ospath, const char* commandline) {
errorMessage = "Ok";
try {
idExportOptions options(commandline, ospath);
idFbxExport 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 FBX exporter: DLL version %d different from .exe version %d\n", MD5_VERSION, version);
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;