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.
1110 lines
28 KiB
C++
1110 lines
28 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 "idlib/Parser.h"
|
|
#include "framework/Common.h"
|
|
#include "framework/FileSystem.h"
|
|
#include "framework/Session.h"
|
|
|
|
#include "renderer/Model_ma.h"
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
Parses Maya ASCII files.
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
|
|
#define MA_VERBOSE( x ) { if ( maGlobal.verbose ) { common->Printf x ; } }
|
|
|
|
// working variables used during parsing
|
|
typedef struct {
|
|
bool verbose;
|
|
maModel_t *model;
|
|
maObject_t *currentObject;
|
|
} ma_t;
|
|
|
|
static ma_t maGlobal;
|
|
|
|
|
|
void MA_ParseNodeHeader(idParser& parser, maNodeHeader_t* header) {
|
|
|
|
memset(header, 0, sizeof(maNodeHeader_t));
|
|
|
|
idToken token;
|
|
while(parser.ReadToken(&token)) {
|
|
if(!token.Icmp("-")) {
|
|
parser.ReadToken(&token);
|
|
if (!token.Icmp("n")) {
|
|
parser.ReadToken(&token);
|
|
strcpy(header->name, token.c_str());
|
|
} else if (!token.Icmp("p")) {
|
|
parser.ReadToken(&token);
|
|
strcpy(header->parent, token.c_str());
|
|
}
|
|
} else if (!token.Icmp(";")) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MA_ParseHeaderIndex(maAttribHeader_t* header, int& minIndex, int& maxIndex, const char* headerType, const char* skipString) {
|
|
|
|
idParser miniParse;
|
|
idToken token;
|
|
|
|
miniParse.LoadMemory(header->name, strlen(header->name), headerType);
|
|
if(skipString) {
|
|
miniParse.SkipUntilString(skipString);
|
|
}
|
|
|
|
if(!miniParse.SkipUntilString("[")) {
|
|
//This was just a header
|
|
return false;
|
|
}
|
|
minIndex = miniParse.ParseInt();
|
|
miniParse.ReadToken(&token);
|
|
if(!token.Icmp("]")) {
|
|
maxIndex = minIndex;
|
|
} else {
|
|
maxIndex = miniParse.ParseInt();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseAttribHeader(idParser &parser, maAttribHeader_t* header) {
|
|
|
|
idToken token;
|
|
|
|
memset(header, 0, sizeof(maAttribHeader_t));
|
|
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp("-")) {
|
|
parser.ReadToken(&token);
|
|
if (!token.Icmp("s")) {
|
|
header->size = parser.ParseInt();
|
|
parser.ReadToken(&token);
|
|
}
|
|
}
|
|
strcpy(header->name, token.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool MA_ReadVec3(idParser& parser, idVec3& vec) {
|
|
idToken token;
|
|
if(!parser.SkipUntilString("double3")) {
|
|
throw idException( va("Maya Loader '%s': Invalid Vec3", parser.GetFileName()) );
|
|
return false;
|
|
}
|
|
|
|
|
|
//We need to flip y and z because of the maya coordinate system
|
|
vec.x = parser.ParseFloat();
|
|
vec.z = parser.ParseFloat();
|
|
vec.y = parser.ParseFloat();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsNodeComplete(idToken& token) {
|
|
if(!token.Icmp("createNode") || !token.Icmp("connectAttr") || !token.Icmp("select")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MA_ParseTransform(idParser& parser) {
|
|
|
|
maNodeHeader_t header;
|
|
maTransform_t* transform;
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
//Allocate room for the transform
|
|
transform = (maTransform_t *)Mem_Alloc( sizeof( maTransform_t ) );
|
|
memset(transform, 0, sizeof(maTransform_t));
|
|
transform->scale.x = transform->scale.y = transform->scale.z = 1;
|
|
|
|
//Get the header info from the transform
|
|
MA_ParseNodeHeader(parser, &header);
|
|
|
|
//Read the transform attributes
|
|
idToken token;
|
|
while(parser.ReadToken(&token)) {
|
|
if(IsNodeComplete(token)) {
|
|
parser.UnreadToken(&token);
|
|
break;
|
|
}
|
|
if(!token.Icmp("setAttr")) {
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp(".t")) {
|
|
if(!MA_ReadVec3(parser, transform->translate)) {
|
|
return false;
|
|
}
|
|
transform->translate.y *= -1;
|
|
} else if (!token.Icmp(".r")) {
|
|
if(!MA_ReadVec3(parser, transform->rotate)) {
|
|
return false;
|
|
}
|
|
} else if (!token.Icmp(".s")) {
|
|
if(!MA_ReadVec3(parser, transform->scale)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
parser.SkipRestOfLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(header.parent[0] != 0) {
|
|
//Find the parent
|
|
maTransform_t** parent;
|
|
maGlobal.model->transforms.Get(header.parent, &parent);
|
|
if(parent) {
|
|
transform->parent = *parent;
|
|
}
|
|
}
|
|
|
|
//Add this transform to the list
|
|
maGlobal.model->transforms.Set(header.name, transform);
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseVertex(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->vertexes) {
|
|
pMesh->numVertexes = header->size;
|
|
pMesh->vertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numVertexes );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
//Read each vert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->vertexes[i].x = parser.ParseFloat();
|
|
pMesh->vertexes[i].z = parser.ParseFloat();
|
|
pMesh->vertexes[i].y = -parser.ParseFloat();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseVertexTransforms(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->vertTransforms) {
|
|
if(header->size == 0) {
|
|
header->size = 1;
|
|
}
|
|
|
|
pMesh->numVertTransforms = header->size;
|
|
pMesh->vertTransforms = (idVec4 *)Mem_Alloc( sizeof( idVec4 ) * pMesh->numVertTransforms );
|
|
pMesh->nextVertTransformIndex = 0;
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexTransformHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp("-")) {
|
|
idToken tk2;
|
|
parser.ReadToken(&tk2);
|
|
if(!tk2.Icmp("type")) {
|
|
parser.SkipUntilString("float3");
|
|
} else {
|
|
parser.UnreadToken(&tk2);
|
|
parser.UnreadToken(&token);
|
|
}
|
|
} else {
|
|
parser.UnreadToken(&token);
|
|
}
|
|
|
|
//Read each vert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->vertTransforms[pMesh->nextVertTransformIndex].x = parser.ParseFloat();
|
|
pMesh->vertTransforms[pMesh->nextVertTransformIndex].z = parser.ParseFloat();
|
|
pMesh->vertTransforms[pMesh->nextVertTransformIndex].y = -parser.ParseFloat();
|
|
|
|
//w hold the vert index
|
|
pMesh->vertTransforms[pMesh->nextVertTransformIndex].w = i;
|
|
|
|
pMesh->nextVertTransformIndex++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseEdge(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->edges) {
|
|
pMesh->numEdges = header->size;
|
|
pMesh->edges = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numEdges );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "EdgeHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
//Read each vert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->edges[i].x = parser.ParseFloat();
|
|
pMesh->edges[i].y = parser.ParseFloat();
|
|
pMesh->edges[i].z = parser.ParseFloat();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseNormal(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->normals) {
|
|
pMesh->numNormals = header->size;
|
|
pMesh->normals = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numNormals );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "NormalHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp("-")) {
|
|
idToken tk2;
|
|
parser.ReadToken(&tk2);
|
|
if(!tk2.Icmp("type")) {
|
|
parser.SkipUntilString("float3");
|
|
} else {
|
|
parser.UnreadToken(&tk2);
|
|
parser.UnreadToken(&token);
|
|
}
|
|
} else {
|
|
parser.UnreadToken(&token);
|
|
}
|
|
|
|
|
|
//Read each vert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->normals[i].x = parser.ParseFloat();
|
|
|
|
//Adjust the normals for the change in coordinate systems
|
|
pMesh->normals[i].z = parser.ParseFloat();
|
|
pMesh->normals[i].y = -parser.ParseFloat();
|
|
|
|
pMesh->normals[i].Normalize();
|
|
|
|
}
|
|
|
|
pMesh->normalsParsed = true;
|
|
pMesh->nextNormal = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool MA_ParseFace(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->faces) {
|
|
pMesh->numFaces = header->size;
|
|
pMesh->faces = (maFace_t *)Mem_Alloc( sizeof( maFace_t ) * pMesh->numFaces );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "FaceHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
//Read the face data
|
|
int currentFace = minIndex-1;
|
|
while(parser.ReadToken(&token)) {
|
|
if(IsNodeComplete(token)) {
|
|
parser.UnreadToken(&token);
|
|
break;
|
|
}
|
|
|
|
if(!token.Icmp("f")) {
|
|
int count = parser.ParseInt();
|
|
if(count != 3) {
|
|
throw idException(va("Maya Loader '%s': Face is not a triangle.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
//Increment the face number because a new face always starts with an "f" token
|
|
currentFace++;
|
|
|
|
//We cannot reorder edges until later because the normal processing
|
|
//assumes the edges are in the original order
|
|
pMesh->faces[currentFace].edge[0] = parser.ParseInt();
|
|
pMesh->faces[currentFace].edge[1] = parser.ParseInt();
|
|
pMesh->faces[currentFace].edge[2] = parser.ParseInt();
|
|
|
|
//Some more init stuff
|
|
pMesh->faces[currentFace].vertexColors[0] = pMesh->faces[currentFace].vertexColors[1] = pMesh->faces[currentFace].vertexColors[2] = -1;
|
|
|
|
} else if(!token.Icmp("mu")) {
|
|
/* int uvstIndex = */ parser.ParseInt();
|
|
int count = parser.ParseInt();
|
|
if(count != 3) {
|
|
throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt();
|
|
pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt();
|
|
pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt();
|
|
|
|
} else if(!token.Icmp("mf")) {
|
|
int count = parser.ParseInt();
|
|
if(count != 3) {
|
|
throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt();
|
|
pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt();
|
|
pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt();
|
|
|
|
} else if(!token.Icmp("fc")) {
|
|
|
|
int count = parser.ParseInt();
|
|
if(count != 3) {
|
|
throw idException(va("Maya Loader '%s': Invalid vertex color.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
pMesh->faces[currentFace].vertexColors[0] = parser.ParseInt();
|
|
pMesh->faces[currentFace].vertexColors[1] = parser.ParseInt();
|
|
pMesh->faces[currentFace].vertexColors[2] = parser.ParseInt();
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseColor(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//Allocate enough space for all the verts if this is the first attribute for verticies
|
|
if(!pMesh->colors) {
|
|
pMesh->numColors = header->size;
|
|
pMesh->colors = (byte *)Mem_Alloc( sizeof( byte ) * pMesh->numColors * 4 );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "ColorHeader", NULL)) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
//Read each vert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->colors[i*4] = parser.ParseFloat() * 255;
|
|
pMesh->colors[i*4+1] = parser.ParseFloat() * 255;
|
|
pMesh->colors[i*4+2] = parser.ParseFloat() * 255;
|
|
pMesh->colors[i*4+3] = parser.ParseFloat() * 255;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MA_ParseTVert(idParser& parser, maAttribHeader_t* header) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
idToken token;
|
|
|
|
//This is not the texture coordinates. It is just the name so ignore it
|
|
if(strstr(header->name, "uvsn")) {
|
|
return true;
|
|
}
|
|
|
|
//Allocate enough space for all the data
|
|
if(!pMesh->tvertexes) {
|
|
pMesh->numTVertexes = header->size;
|
|
pMesh->tvertexes = (idVec2 *)Mem_Alloc( sizeof( idVec2 ) * pMesh->numTVertexes );
|
|
}
|
|
|
|
//Get the start and end index for this attribute
|
|
int minIndex, maxIndex;
|
|
if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "TextureCoordHeader", "uvsp")) {
|
|
//This was just a header
|
|
return true;
|
|
}
|
|
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp("-")) {
|
|
idToken tk2;
|
|
parser.ReadToken(&tk2);
|
|
if(!tk2.Icmp("type")) {
|
|
parser.SkipUntilString("float2");
|
|
} else {
|
|
parser.UnreadToken(&tk2);
|
|
parser.UnreadToken(&token);
|
|
}
|
|
} else {
|
|
parser.UnreadToken(&token);
|
|
}
|
|
|
|
//Read each tvert
|
|
for(int i = minIndex; i <= maxIndex; i++) {
|
|
pMesh->tvertexes[i].x = parser.ParseFloat();
|
|
pMesh->tvertexes[i].y = 1.0f - parser.ParseFloat();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Quick check to see if the vert participates in a shared normal
|
|
*/
|
|
bool MA_QuickIsVertShared(int faceIndex, int vertIndex) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex];
|
|
|
|
for( int i = 0; i < 3; i++) {
|
|
int edge = pMesh->faces[faceIndex].edge[i];
|
|
if(edge < 0) {
|
|
edge = idMath::Fabs(edge)-1;
|
|
}
|
|
if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MA_GetSharedFace(int faceIndex, int vertIndex, int& sharedFace, int& sharedVert) {
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex];
|
|
|
|
sharedFace = -1;
|
|
sharedVert = -1;
|
|
|
|
//Find a shared edge on this face that contains the specified vert
|
|
for(int edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
|
|
|
|
int edge = pMesh->faces[faceIndex].edge[edgeIndex];
|
|
if(edge < 0) {
|
|
edge = idMath::Fabs(edge)-1;
|
|
}
|
|
|
|
if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) {
|
|
|
|
for(int i = 0; i < faceIndex; i++) {
|
|
|
|
for(int j = 0; j < 3; j++) {
|
|
if(pMesh->faces[i].vertexNum[j] == vertNum) {
|
|
sharedFace = i;
|
|
sharedVert = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(sharedFace != -1)
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
void MA_ParseMesh(idParser& parser) {
|
|
|
|
maObject_t *object;
|
|
object = (maObject_t *)Mem_Alloc( sizeof( maObject_t ) );
|
|
memset( object, 0, sizeof( maObject_t ) );
|
|
maGlobal.model->objects.Append( object );
|
|
maGlobal.currentObject = object;
|
|
object->materialRef = -1;
|
|
|
|
|
|
//Get the header info from the mesh
|
|
maNodeHeader_t header;
|
|
MA_ParseNodeHeader(parser, &header);
|
|
|
|
//Find my parent
|
|
if(header.parent[0] != 0) {
|
|
//Find the parent
|
|
maTransform_t** parent;
|
|
maGlobal.model->transforms.Get(header.parent, &parent);
|
|
if(parent) {
|
|
maGlobal.currentObject->mesh.transform = *parent;
|
|
}
|
|
}
|
|
|
|
strcpy(object->name, header.name);
|
|
|
|
//Read the transform attributes
|
|
idToken token;
|
|
while(parser.ReadToken(&token)) {
|
|
if(IsNodeComplete(token)) {
|
|
parser.UnreadToken(&token);
|
|
break;
|
|
}
|
|
if(!token.Icmp("setAttr")) {
|
|
maAttribHeader_t header;
|
|
MA_ParseAttribHeader(parser, &header);
|
|
|
|
if(strstr(header.name, ".vt")) {
|
|
MA_ParseVertex(parser, &header);
|
|
} else if (strstr(header.name, ".ed")) {
|
|
MA_ParseEdge(parser, &header);
|
|
} else if (strstr(header.name, ".pt")) {
|
|
MA_ParseVertexTransforms(parser, &header);
|
|
} else if (strstr(header.name, ".n")) {
|
|
MA_ParseNormal(parser, &header);
|
|
} else if (strstr(header.name, ".fc")) {
|
|
MA_ParseFace(parser, &header);
|
|
} else if (strstr(header.name, ".clr")) {
|
|
MA_ParseColor(parser, &header);
|
|
} else if (strstr(header.name, ".uvst")) {
|
|
MA_ParseTVert(parser, &header);
|
|
} else {
|
|
parser.SkipRestOfLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
maMesh_t* pMesh = &maGlobal.currentObject->mesh;
|
|
|
|
//Get the verts from the edge
|
|
for(int i = 0; i < pMesh->numFaces; i++) {
|
|
for(int j = 0; j < 3; j++) {
|
|
int edge = pMesh->faces[i].edge[j];
|
|
if(edge < 0) {
|
|
edge = idMath::Fabs(edge)-1;
|
|
pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].y;
|
|
} else {
|
|
pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].x;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Get the normals
|
|
if(pMesh->normalsParsed) {
|
|
for(int i = 0; i < pMesh->numFaces; i++) {
|
|
for(int j = 0; j < 3; j++) {
|
|
|
|
//Is this vertex shared
|
|
int sharedFace = -1;
|
|
int sharedVert = -1;
|
|
|
|
if(MA_QuickIsVertShared(i, j)) {
|
|
MA_GetSharedFace(i, j, sharedFace, sharedVert);
|
|
}
|
|
|
|
if(sharedFace != -1) {
|
|
//Get the normal from the share
|
|
pMesh->faces[i].vertexNormals[j] = pMesh->faces[sharedFace].vertexNormals[sharedVert];
|
|
|
|
} else {
|
|
//The vertex is not shared so get the next normal
|
|
if(pMesh->nextNormal >= pMesh->numNormals) {
|
|
//We are using more normals than exist
|
|
throw idException(va("Maya Loader '%s': Invalid Normals Index.", parser.GetFileName()));
|
|
}
|
|
pMesh->faces[i].vertexNormals[j] = pMesh->normals[pMesh->nextNormal];
|
|
pMesh->nextNormal++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Now that the normals are good...lets reorder the verts to make the tris face the right way
|
|
for(int i = 0; i < pMesh->numFaces; i++) {
|
|
int tmp = pMesh->faces[i].vertexNum[1];
|
|
pMesh->faces[i].vertexNum[1] = pMesh->faces[i].vertexNum[2];
|
|
pMesh->faces[i].vertexNum[2] = tmp;
|
|
|
|
idVec3 tmpVec = pMesh->faces[i].vertexNormals[1];
|
|
pMesh->faces[i].vertexNormals[1] = pMesh->faces[i].vertexNormals[2];
|
|
pMesh->faces[i].vertexNormals[2] = tmpVec;
|
|
|
|
tmp = pMesh->faces[i].tVertexNum[1];
|
|
pMesh->faces[i].tVertexNum[1] = pMesh->faces[i].tVertexNum[2];
|
|
pMesh->faces[i].tVertexNum[2] = tmp;
|
|
|
|
tmp = pMesh->faces[i].vertexColors[1];
|
|
pMesh->faces[i].vertexColors[1] = pMesh->faces[i].vertexColors[2];
|
|
pMesh->faces[i].vertexColors[2] = tmp;
|
|
}
|
|
|
|
//Now apply the pt transformations
|
|
for(int i = 0; i < pMesh->numVertTransforms; i++) {
|
|
pMesh->vertexes[(int)pMesh->vertTransforms[i].w] += pMesh->vertTransforms[i].ToVec3();
|
|
}
|
|
|
|
MA_VERBOSE((va("MESH %s - parent %s\n", header.name, header.parent)));
|
|
MA_VERBOSE((va("\tverts:%d\n",maGlobal.currentObject->mesh.numVertexes)));
|
|
MA_VERBOSE((va("\tfaces:%d\n",maGlobal.currentObject->mesh.numFaces)));
|
|
}
|
|
|
|
void MA_ParseFileNode(idParser& parser) {
|
|
|
|
//Get the header info from the node
|
|
maNodeHeader_t header;
|
|
MA_ParseNodeHeader(parser, &header);
|
|
|
|
//Read the transform attributes
|
|
idToken token;
|
|
while(parser.ReadToken(&token)) {
|
|
if(IsNodeComplete(token)) {
|
|
parser.UnreadToken(&token);
|
|
break;
|
|
}
|
|
if(!token.Icmp("setAttr")) {
|
|
maAttribHeader_t attribHeader;
|
|
MA_ParseAttribHeader(parser, &attribHeader);
|
|
|
|
if(strstr(attribHeader.name, ".ftn")) {
|
|
parser.SkipUntilString("string");
|
|
parser.ReadToken(&token);
|
|
if(!token.Icmp("(")) {
|
|
parser.ReadToken(&token);
|
|
}
|
|
|
|
maFileNode_t* fileNode;
|
|
fileNode = (maFileNode_t*)Mem_Alloc( sizeof( maFileNode_t ) );
|
|
strcpy(fileNode->name, header.name);
|
|
strcpy(fileNode->path, token.c_str());
|
|
|
|
maGlobal.model->fileNodes.Set(fileNode->name, fileNode);
|
|
} else {
|
|
parser.SkipRestOfLine();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MA_ParseMaterialNode(idParser& parser) {
|
|
|
|
//Get the header info from the node
|
|
maNodeHeader_t header;
|
|
MA_ParseNodeHeader(parser, &header);
|
|
|
|
maMaterialNode_t* matNode;
|
|
matNode = (maMaterialNode_t*)Mem_Alloc( sizeof( maMaterialNode_t ) );
|
|
memset(matNode, 0, sizeof(maMaterialNode_t));
|
|
|
|
strcpy(matNode->name, header.name);
|
|
|
|
maGlobal.model->materialNodes.Set(matNode->name, matNode);
|
|
}
|
|
|
|
void MA_ParseCreateNode(idParser& parser) {
|
|
|
|
idToken token;
|
|
parser.ReadToken(&token);
|
|
|
|
if(!token.Icmp("transform")) {
|
|
MA_ParseTransform(parser);
|
|
} else if(!token.Icmp("mesh")) {
|
|
MA_ParseMesh(parser);
|
|
} else if(!token.Icmp("file")) {
|
|
MA_ParseFileNode(parser);
|
|
} else if(!token.Icmp("shadingEngine") || !token.Icmp("lambert") || !token.Icmp("phong") || !token.Icmp("blinn") ) {
|
|
MA_ParseMaterialNode(parser);
|
|
}
|
|
}
|
|
|
|
|
|
int MA_AddMaterial(const char* materialName) {
|
|
|
|
|
|
maMaterialNode_t** destNode;
|
|
maGlobal.model->materialNodes.Get(materialName, &destNode);
|
|
if(destNode) {
|
|
maMaterialNode_t* matNode = *destNode;
|
|
|
|
//Iterate down the tree until we get a file
|
|
while(matNode && !matNode->file) {
|
|
matNode = matNode->child;
|
|
}
|
|
if(matNode && matNode->file) {
|
|
|
|
//Got the file
|
|
maMaterial_t *material;
|
|
material = (maMaterial_t *)Mem_Alloc( sizeof( maMaterial_t ) );
|
|
memset( material, 0, sizeof( maMaterial_t ) );
|
|
|
|
//Remove the OS stuff
|
|
idStr qPath;
|
|
qPath = fileSystem->OSPathToRelativePath( matNode->file->path );
|
|
|
|
strcpy(material->name, qPath.c_str());
|
|
|
|
maGlobal.model->materials.Append( material );
|
|
return maGlobal.model->materials.Num()-1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool MA_ParseConnectAttr(idParser& parser) {
|
|
|
|
idStr temp;
|
|
idStr srcName;
|
|
idStr srcType;
|
|
idStr destName;
|
|
idStr destType;
|
|
|
|
idToken token;
|
|
parser.ReadToken(&token);
|
|
temp = token;
|
|
int dot = temp.Find(".");
|
|
if(dot == -1) {
|
|
throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
srcName = temp.Left(dot);
|
|
srcType = temp.Right(temp.Length()-dot-1);
|
|
|
|
parser.ReadToken(&token);
|
|
temp = token;
|
|
dot = temp.Find(".");
|
|
if(dot == -1) {
|
|
throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName()));
|
|
return false;
|
|
}
|
|
destName = temp.Left(dot);
|
|
destType = temp.Right(temp.Length()-dot-1);
|
|
|
|
if(srcType.Find("oc") != -1) {
|
|
|
|
//Is this attribute a material node attribute
|
|
maMaterialNode_t** matNode;
|
|
maGlobal.model->materialNodes.Get(srcName, &matNode);
|
|
if(matNode) {
|
|
maMaterialNode_t** destNode;
|
|
maGlobal.model->materialNodes.Get(destName, &destNode);
|
|
if(destNode) {
|
|
(*destNode)->child = *matNode;
|
|
}
|
|
}
|
|
|
|
//Is this attribute a file node
|
|
maFileNode_t** fileNode;
|
|
maGlobal.model->fileNodes.Get(srcName, &fileNode);
|
|
if(fileNode) {
|
|
maMaterialNode_t** destNode;
|
|
maGlobal.model->materialNodes.Get(destName, &destNode);
|
|
if(destNode) {
|
|
(*destNode)->file = *fileNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(srcType.Find("iog") != -1) {
|
|
//Is this an attribute for one of our meshes
|
|
for(int i = 0; i < maGlobal.model->objects.Num(); i++) {
|
|
if(!strcmp(maGlobal.model->objects[i]->name, srcName)) {
|
|
//maGlobal.model->objects[i]->materialRef = MA_AddMaterial(destName);
|
|
strcpy(maGlobal.model->objects[i]->materialName, destName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void MA_BuildScale(idMat4& mat, float x, float y, float z) {
|
|
mat.Identity();
|
|
mat[0][0] = x;
|
|
mat[1][1] = y;
|
|
mat[2][2] = z;
|
|
}
|
|
|
|
void MA_BuildAxisRotation(idMat4& mat, float ang, int axis) {
|
|
|
|
float sinAng = idMath::Sin(ang);
|
|
float cosAng = idMath::Cos(ang);
|
|
|
|
mat.Identity();
|
|
switch(axis) {
|
|
case 0: //x
|
|
mat[1][1] = cosAng;
|
|
mat[1][2] = sinAng;
|
|
mat[2][1] = -sinAng;
|
|
mat[2][2] = cosAng;
|
|
break;
|
|
case 1: //y
|
|
mat[0][0] = cosAng;
|
|
mat[0][2] = -sinAng;
|
|
mat[2][0] = sinAng;
|
|
mat[2][2] = cosAng;
|
|
break;
|
|
case 2://z
|
|
mat[0][0] = cosAng;
|
|
mat[0][1] = sinAng;
|
|
mat[1][0] = -sinAng;
|
|
mat[1][1] = cosAng;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MA_ApplyTransformation(maModel_t *model) {
|
|
|
|
for(int i = 0; i < model->objects.Num(); i++) {
|
|
maMesh_t* mesh = &model->objects[i]->mesh;
|
|
maTransform_t* transform = mesh->transform;
|
|
|
|
|
|
|
|
while(transform) {
|
|
|
|
idMat4 rotx, roty, rotz;
|
|
idMat4 scale;
|
|
|
|
rotx.Identity();
|
|
roty.Identity();
|
|
rotz.Identity();
|
|
|
|
if(fabs(transform->rotate.x) > 0.0f) {
|
|
MA_BuildAxisRotation(rotx, DEG2RAD(-transform->rotate.x), 0);
|
|
}
|
|
if(fabs(transform->rotate.y) > 0.0f) {
|
|
MA_BuildAxisRotation(roty, DEG2RAD(transform->rotate.y), 1);
|
|
}
|
|
if(fabs(transform->rotate.z) > 0.0f) {
|
|
MA_BuildAxisRotation(rotz, DEG2RAD(-transform->rotate.z), 2);
|
|
}
|
|
|
|
MA_BuildScale(scale, transform->scale.x, transform->scale.y, transform->scale.z);
|
|
|
|
//Apply the transformation to each vert
|
|
for(int j = 0; j < mesh->numVertexes; j++) {
|
|
mesh->vertexes[j] = scale * mesh->vertexes[j];
|
|
|
|
mesh->vertexes[j] = rotx * mesh->vertexes[j];
|
|
mesh->vertexes[j] = rotz * mesh->vertexes[j];
|
|
mesh->vertexes[j] = roty * mesh->vertexes[j];
|
|
|
|
mesh->vertexes[j] = mesh->vertexes[j] + transform->translate;
|
|
}
|
|
|
|
transform = transform->parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
MA_Parse
|
|
=================
|
|
*/
|
|
maModel_t *MA_Parse( const char *buffer, const char* filename, bool verbose ) {
|
|
memset( &maGlobal, 0, sizeof( maGlobal ) );
|
|
|
|
maGlobal.verbose = verbose;
|
|
|
|
|
|
|
|
|
|
maGlobal.currentObject = NULL;
|
|
|
|
// NOTE: using new operator because aseModel_t contains idList class objects
|
|
maGlobal.model = new maModel_t;
|
|
maGlobal.model->objects.Resize( 32, 32 );
|
|
maGlobal.model->materials.Resize( 32, 32 );
|
|
|
|
|
|
idParser parser;
|
|
parser.SetFlags(LEXFL_NOSTRINGCONCAT);
|
|
parser.LoadMemory(buffer, strlen(buffer), filename);
|
|
|
|
idToken token;
|
|
while(parser.ReadToken(&token)) {
|
|
|
|
if(!token.Icmp("createNode")) {
|
|
MA_ParseCreateNode(parser);
|
|
} else if(!token.Icmp("connectAttr")) {
|
|
MA_ParseConnectAttr(parser);
|
|
}
|
|
}
|
|
|
|
//Resolve The Materials
|
|
for(int i = 0; i < maGlobal.model->objects.Num(); i++) {
|
|
maGlobal.model->objects[i]->materialRef = MA_AddMaterial(maGlobal.model->objects[i]->materialName);
|
|
}
|
|
|
|
|
|
|
|
//Apply Transformation
|
|
MA_ApplyTransformation(maGlobal.model);
|
|
|
|
return maGlobal.model;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
MA_Load
|
|
=================
|
|
*/
|
|
maModel_t *MA_Load( const char *fileName ) {
|
|
char *buf;
|
|
ID_TIME_T timeStamp;
|
|
maModel_t *ma;
|
|
|
|
fileSystem->ReadFile( fileName, (void **)&buf, &timeStamp );
|
|
if ( !buf ) {
|
|
return NULL;
|
|
}
|
|
|
|
try {
|
|
ma = MA_Parse( buf, fileName, false );
|
|
ma->timeStamp = timeStamp;
|
|
} catch( idException &e ) {
|
|
common->Warning("%s", e.error);
|
|
if(maGlobal.model) {
|
|
MA_Free(maGlobal.model);
|
|
}
|
|
ma = NULL;
|
|
}
|
|
|
|
fileSystem->FreeFile( buf );
|
|
|
|
return ma;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
MA_Free
|
|
=================
|
|
*/
|
|
void MA_Free( maModel_t *ma ) {
|
|
int i;
|
|
maObject_t *obj;
|
|
maMesh_t *mesh;
|
|
maMaterial_t *material;
|
|
|
|
if ( !ma ) {
|
|
return;
|
|
}
|
|
for ( i = 0; i < ma->objects.Num(); i++ ) {
|
|
obj = ma->objects[i];
|
|
|
|
// free the base nesh
|
|
mesh = &obj->mesh;
|
|
|
|
if ( mesh->vertexes ) {
|
|
Mem_Free( mesh->vertexes );
|
|
}
|
|
if ( mesh->vertTransforms ) {
|
|
Mem_Free( mesh->vertTransforms );
|
|
}
|
|
if ( mesh->normals ) {
|
|
Mem_Free( mesh->normals );
|
|
}
|
|
if ( mesh->tvertexes ) {
|
|
Mem_Free( mesh->tvertexes );
|
|
}
|
|
if ( mesh->edges ) {
|
|
Mem_Free( mesh->edges );
|
|
}
|
|
if ( mesh->colors ) {
|
|
Mem_Free( mesh->colors );
|
|
}
|
|
if ( mesh->faces ) {
|
|
Mem_Free( mesh->faces );
|
|
}
|
|
Mem_Free( obj );
|
|
}
|
|
ma->objects.Clear();
|
|
|
|
for ( i = 0; i < ma->materials.Num(); i++ ) {
|
|
material = ma->materials[i];
|
|
Mem_Free( material );
|
|
}
|
|
ma->materials.Clear();
|
|
|
|
maTransform_t** trans;
|
|
for ( i = 0; i < ma->transforms.Num(); i++ ) {
|
|
trans = ma->transforms.GetIndex(i);
|
|
Mem_Free( *trans );
|
|
}
|
|
ma->transforms.Clear();
|
|
|
|
|
|
maFileNode_t** fileNode;
|
|
for ( i = 0; i < ma->fileNodes.Num(); i++ ) {
|
|
fileNode = ma->fileNodes.GetIndex(i);
|
|
Mem_Free( *fileNode );
|
|
}
|
|
ma->fileNodes.Clear();
|
|
|
|
maMaterialNode_t** matNode;
|
|
for ( i = 0; i < ma->materialNodes.Num(); i++ ) {
|
|
matNode = ma->materialNodes.GetIndex(i);
|
|
Mem_Free( *matNode );
|
|
}
|
|
ma->materialNodes.Clear();
|
|
delete ma;
|
|
}
|