mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-29 23:51:49 +00:00
279a40a981
This is based on https://github.com/dhewm/dhewm3/pull/500 by https://github.com/jayaddison See also https://github.com/blendogames/quadrilateralcowboy/pull/4
1113 lines
28 KiB
C++
1113 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.
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
|
|
|
|
// 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()) );
|
|
}
|
|
|
|
|
|
//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; // XXX: +1?
|
|
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()));
|
|
}
|
|
//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()));
|
|
}
|
|
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()));
|
|
}
|
|
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()));
|
|
}
|
|
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++) {
|
|
int idx = (int)pMesh->vertTransforms[i].w;
|
|
if(idx < 0 || idx >= pMesh->numVertexes)
|
|
{
|
|
// this happens with d3xp/models/david/hell_h7.ma in the d3xp hell level
|
|
// TODO: if it happens for other models, too, maybe it's intended and the .ma parsing is broken
|
|
common->Warning( "Model %s tried to set an out-of-bounds vertex transform (%d, but max vert. index is %d)!",
|
|
parser.GetFileName(), idx, pMesh->numVertexes-1 );
|
|
continue;
|
|
}
|
|
pMesh->vertexes[idx] += pMesh->vertTransforms[i].ToVec3();
|
|
}
|
|
|
|
if(maGlobal.verbose) {
|
|
common->Printf("MESH %s - parent %s\n", header.name, header.parent);
|
|
common->Printf("\tverts:%d\n",maGlobal.currentObject->mesh.numVertexes);
|
|
common->Printf("\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()));
|
|
}
|
|
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()));
|
|
}
|
|
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;
|
|
}
|