/* =========================================================================== 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 . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "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; }