/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition 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 BFG Edition 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. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" #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() ) ); } //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 ), TAG_MODEL ); 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, TAG_MODEL ); } //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, TAG_MODEL ); 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, TAG_MODEL ); } //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, TAG_MODEL ); } //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, TAG_MODEL ); } //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(); uvstIndex; 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, TAG_MODEL ); } //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, TAG_MODEL ); } //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 ), TAG_MODEL ); 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 ), TAG_MODEL ); 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 ), TAG_MODEL ); 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 ), TAG_MODEL ); 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( TAG_MODEL ) 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.GetError() ); 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; }