//====== Copyright © 1996-2006, Valve Corporation, All rights reserved. =======
//
// Serialize and Unserialize Wavefront OBJ <-> DME Data
//
//=============================================================================


// Valve includes
#include "tier1/characterset.h"
#include "movieobjects/dmedag.h"
#include "movieobjects/dmemesh.h"
#include "movieobjects/dmefaceset.h"
#include "movieobjects/dmematerial.h"
#include "movieobjects/dmobjserializer.h"
#include "movieobjects/dmecombinationoperator.h"
#include "movieobjects/dmemodel.h"
#include "filesystem.h"
#include "tier2/tier2.h"
#include "tier1/UtlStringMap.h"
#include "mathlib/mathlib.h"

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
class CFaceSetData
{
public:
	void Clear();

	inline CUtlVector< int > *GetFaceSetIndices( const char *pFaceSetName )
	{
		return &m_faceSetIndices[ pFaceSetName ];
	}

	void AddToMesh( CDmeMesh *pMesh );
protected:

	CUtlStringMap< CUtlVector< int > > m_faceSetIndices;
};


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CFaceSetData::Clear()
{
	m_faceSetIndices.Clear();
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CFaceSetData::AddToMesh( CDmeMesh *pMesh )
{
	const int nFaceSets( m_faceSetIndices.GetNumStrings() );
	for ( int i( 0 ); i < nFaceSets; ++i )
	{
		const char *pName( m_faceSetIndices.String( i ) );
		CUtlVector< int > &faceSetIndices( m_faceSetIndices[ pName ] );

		if ( faceSetIndices.Count() )
		{
			CDmeFaceSet *pFaceSet = CreateElement< CDmeFaceSet >( pName, pMesh->GetFileId() );

			CDmeMaterial *pMaterial = CreateElement< CDmeMaterial >( pName, pMesh->GetFileId() );
			pMaterial->SetMaterial( pName );

			pFaceSet->AddIndices( faceSetIndices.Count() );
			pFaceSet->SetIndices( 0, faceSetIndices.Count(), faceSetIndices.Base() );

			pFaceSet->SetMaterial( pMaterial );

			pMesh->AddFaceSet( pFaceSet );
		}
	}

	Clear();
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
class CVertexData
{
public:
	void Clear();

	inline void AddPosition( const Vector &p ) { m_positions.AddToTail( p ); }

	inline void AddPositionIndex( int i ) { m_pIndices.AddToTail( i ); }

	inline void AddNormal( const Vector &n ) { m_normals.AddToTail( n ); }

	inline void AddNormalIndex( int i ) { m_nIndices.AddToTail( i ); }

	inline void AddUV( const Vector2D &uv ) { AddUniqueValue( uv, m_uvs, m_uvIndexMap, FLT_EPSILON * 0.1f ); }

	inline void AddUVIndex( int i ) { Assert( i < m_uvIndexMap.Count() ); m_uvIndices.AddToTail( m_uvIndexMap[ i ] ); }

	inline int VertexCount() const { return m_pIndices.Count(); }

	CDmeVertexDataBase *AddToMesh( CDmeMesh *pMesh, bool bAbsolute, const char *pName, bool bDelta );

protected:
	template < class T_t > void AddUniqueValue(
		const T_t &v,
		CUtlVector< T_t > &vs,
		CUtlVector< int > &map,
		float flThresh = FLT_EPSILON )
	{
		const int nVs( vs.Count() );
		for ( int i( 0 ); i < nVs; ++i )
		{
			if ( v.DistToSqr( vs[ i ] ) < flThresh )
			{
				map.AddToTail( i );
				return;
			}
		}

		map.AddToTail( vs.Count() );
		vs.AddToTail( v );
	}

	CDmeVertexDataBase *Add( CDmeMesh *pMesh, const char *pName = "bind" );

	CDmeVertexDeltaData *AddDelta( CDmeMesh *pMesh, bool bAbsolute, const char *pName = "bind" );

	CUtlVector< Vector > m_positions;
	CUtlVector< int > m_pIndices;

	CUtlVector< Vector > m_normals;
	CUtlVector< int > m_nIndices;

	CUtlVector< Vector2D > m_uvs;
	CUtlVector< int > m_uvIndexMap;
	CUtlVector< int > m_uvIndices;
};


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CVertexData::Clear()
{
	m_positions.RemoveAll();
	m_pIndices.RemoveAll();

	m_normals.RemoveAll();
	m_nIndices.RemoveAll();

	m_uvs.RemoveAll();
	m_uvIndexMap.RemoveAll();
	m_uvIndices.RemoveAll();
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CDmeVertexDataBase *CVertexData::AddToMesh( CDmeMesh *pMesh, bool bAbsolute, const char *pName, bool delta )
{
	if ( delta )
		return AddDelta( pMesh, bAbsolute, pName );

	return Add( pMesh, pName );
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CDmeVertexDataBase *CVertexData::Add( CDmeMesh *pMesh, const char *pName )
{
	CDmeVertexDataBase *pVertexData( NULL );

	if ( m_positions.Count() && m_pIndices.Count() )
	{
		{
			CDmeVertexData *pBaseVertexData = pMesh->FindOrCreateBaseState( pName );
			pMesh->SetCurrentBaseState( pName );
			pBaseVertexData->AddVertexIndices( m_pIndices.Count() );
			pVertexData = pBaseVertexData;
		}
		
		pVertexData->FlipVCoordinate( true );

		const FieldIndex_t pIndex( pVertexData->CreateField( CDmeVertexData::FIELD_POSITION ) );
		pVertexData->AddVertexData( pIndex, m_positions.Count() );
		pVertexData->SetVertexData( pIndex, 0, m_positions.Count(), AT_VECTOR3, m_positions.Base() );
		pVertexData->SetVertexIndices( pIndex, 0, m_pIndices.Count(), m_pIndices.Base() );

		if ( pVertexData && m_normals.Count() && m_nIndices.Count() )
		{
			Assert( m_pIndices.Count() == m_nIndices.Count() );

			const FieldIndex_t nIndex( pVertexData->CreateField( CDmeVertexData::FIELD_NORMAL ) );
			pVertexData->AddVertexData( nIndex, m_normals.Count() );
			pVertexData->SetVertexData( nIndex, 0, m_normals.Count(), AT_VECTOR3, m_normals.Base() );
			pVertexData->SetVertexIndices( nIndex, 0, m_nIndices.Count(), m_nIndices.Base() );
		}

		if ( pVertexData && m_uvs.Count() && m_uvIndices.Count() )
		{
			Assert( m_pIndices.Count() == m_uvIndices.Count() );

			const FieldIndex_t uvIndex( pVertexData->CreateField( CDmeVertexData::FIELD_TEXCOORD ) );
			pVertexData->AddVertexData( uvIndex, m_uvs.Count() );
			pVertexData->SetVertexData( uvIndex, 0, m_uvs.Count(), AT_VECTOR2, m_uvs.Base() );
			pVertexData->SetVertexIndices( uvIndex, 0, m_uvIndices.Count(), m_uvIndices.Base() );
		}
	}

	return pVertexData;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CDmeVertexDeltaData *CVertexData::AddDelta( CDmeMesh *pMesh, bool bAbsolute, const char *pName )
{
	CDmeVertexDeltaData *pDelta( NULL );

	if ( m_positions.Count() )
	{
		CDmeVertexData *pBind = pMesh->FindBaseState( "bind" );
		if ( pBind == NULL )
			return NULL;

		const FieldIndex_t pBindIndex( pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) );

		if ( pBindIndex < 0 )
			return NULL;

		CDmrArrayConst< Vector > pBindData( pBind->GetVertexData( pBindIndex ) );

		const int pCount( m_positions.Count() );
		if ( pBindData.Count() != pCount )
			return NULL;

		for ( int i( 0 ); i < pCount; ++i )
		{
			m_positions[ i ] -= pBindData[ i ];
		}

		int *pIndices = reinterpret_cast< int * >( alloca( pCount * sizeof( int ) ) );
		int nNonZero( 0 );
		for ( int i( 0 ); i < pCount; ++i )
		{
			const Vector &v( m_positions[ i ] );
			// Kind of a magic number but it's because of 16 bit compression of the delta values
			if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) )
			{
				m_positions[ nNonZero ] = v;
				pIndices[ nNonZero ] = i;
				++nNonZero;
			}
		}

		pDelta = pMesh->FindOrCreateDeltaState( pName );
		pDelta->FlipVCoordinate( true );
		pDelta->SetValue( "corrected", !bAbsolute );

		const FieldIndex_t pIndex( pDelta->CreateField( CDmeVertexData::FIELD_POSITION ) );
		pDelta->AddVertexData( pIndex, nNonZero );
		pDelta->SetVertexData( pIndex, 0, nNonZero, AT_VECTOR3, m_positions.Base() );
		pDelta->SetVertexIndices( pIndex, 0, nNonZero, pIndices );

		const FieldIndex_t nBindNormalIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_NORMAL );
		if ( nBindNormalIndex >= 0 )
		{
			CDmrArrayConst< Vector > bindNormalData( pBind->GetVertexData( nBindNormalIndex ) );
			const int nNormalCount = m_normals.Count();
			if ( bindNormalData.Count() == nNormalCount )
			{
				for ( int i = 0; i < nNormalCount; ++i )
				{
					m_normals[ i ] -= bindNormalData[ i ];
				}

				int *pNormalIndices = reinterpret_cast< int * >( stackalloc( nNormalCount * sizeof( int ) ) );
				int nNormalDeltaCount = 0;
				for ( int i = 0; i < nNormalCount; ++i )
				{
					const Vector &n = m_normals[ i ];
					// Kind of a magic number but it's because of 16 bit compression of the delta values
					if ( fabs( n.x ) >= ( 1 / 4096.0f ) || fabs( n.y ) >= ( 1 / 4096.0f ) || fabs( n.z ) >= ( 1 / 4096.0f ) )
					{
						m_normals[ nNormalDeltaCount ] = n;
						pNormalIndices[ nNormalDeltaCount ] = i;
						++nNormalDeltaCount;
					}
				}
			}
		}
	}

	return pDelta;
}


//-----------------------------------------------------------------------------
// Convert from DME -> OBJ
//-----------------------------------------------------------------------------
bool CDmObjSerializer::Serialize( CUtlBuffer &buf, CDmElement *pRoot )
{
	return false; // For now
}


//-----------------------------------------------------------------------------
// Convert from OBJ -> DME
//-----------------------------------------------------------------------------
bool CDmObjSerializer::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion,
									const char *pSourceFormatName, int nSourceFormatVersion,
									DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot )
{
	*ppRoot = ReadOBJ( buf, fileid, "bind" );
	return *ppRoot != NULL;
}


//-----------------------------------------------------------------------------
// Convert from OBJ -> DME
// If mesh is not NULL, the OBJ is added as a delta state to the mesh
//-----------------------------------------------------------------------------
CDmElement *CDmObjSerializer::ReadOBJ(
	const char *pFilename,
	CDmeMesh **ppCreatedMesh,
	bool bLoadAllDeltas /* = true */,
	bool bAbsolute /* = true */ )
{
	char filename[ MAX_PATH ];
	Q_strncpy( filename, pFilename, sizeof( filename ) );
	Q_FixSlashes( filename );

	CUtlBuffer utlBuf;
	if ( !g_pFullFileSystem->ReadFile( filename, NULL, utlBuf ) )
		return NULL;

	char baseFile[ MAX_PATH ];
	Q_FileBase( filename, baseFile, sizeof( baseFile ) );

	CDmeMesh *pMesh( NULL );

	CDmElement *pRoot = ReadOBJ( utlBuf, DMFILEID_INVALID, baseFile, filename, NULL, &pMesh, bAbsolute );
	if ( pRoot && pMesh )
	{
		if ( ppCreatedMesh )
		{
			*ppCreatedMesh = pMesh;
		}

		CDmeCombinationOperator *pCombo( NULL );

		// Check if there are deltas in the directory with the same prefix
		// But only if the rest of the file is <prefix>=<suffix>.obj or is <prefix>_zero.obj

		char *pSuffix = Q_strrchr( baseFile, '=' );
		if ( !pSuffix || !*pSuffix )
		{
			pSuffix = Q_strrchr( baseFile, '_' );
			if ( !pSuffix || !*pSuffix )
				return pRoot;

			if ( Q_stricmp( pSuffix, "_zero" ) )
				return pRoot;
		}

		char findGlob[ MAX_PATH ];
		Q_strncpy( findGlob, baseFile, sizeof( findGlob ) );
		pSuffix = findGlob + ( pSuffix - baseFile );
		*( pSuffix + 0 ) = '_';	// Just in case it was <prefix>=<suffix>.obj
		*( pSuffix + 1 ) = '*';
		*( pSuffix + 2 ) = '.';
		Q_strncpy( pSuffix + 3, Q_GetFileExtension( filename ), sizeof( findGlob ) - ( pSuffix - findGlob + 3 ) );

		char path[ MAX_PATH ];
		Q_ExtractFilePath( filename, path, sizeof( path ) );

		m_objDirectory = path;

		char findPath[ MAX_PATH ];
		Q_ComposeFileName( path, findGlob, findPath, sizeof( findPath ) );

		FileFindHandle_t hFind;

		char deltaFile[ MAX_PATH ];
		char deltaPath[ MAX_PATH ];

		for ( const char *pFindFile( g_pFullFileSystem->FindFirst( findPath, &hFind ) ); pFindFile && *pFindFile; pFindFile = g_pFullFileSystem->FindNext( hFind ) )
		{
			Q_FileBase( pFindFile, deltaFile, sizeof( deltaFile ) );

			if ( Q_stricmp( baseFile, deltaFile ) )
			{
				Q_ComposeFileName( path, pFindFile, deltaPath, sizeof( deltaPath ) );

				if ( !g_pFullFileSystem->FileExists( deltaPath ) )
					continue;

				char *pControlName = strchr( deltaFile, '_' );
				if ( pControlName && *( pControlName + 1 ) )
				{
					++pControlName;
					char *pDeltaName( pControlName );
					for ( char *pPlus( strchr( pDeltaName, '+' ) ); pPlus; pPlus = strchr( pPlus, '+' ) )
					{
						*pPlus = '_';
					}
				}

				if ( !strchr( pControlName, '_' ) )
				{
					if ( pCombo == NULL )
					{
						pCombo = CreateElement< CDmeCombinationOperator >( "combinationOperator", pRoot->GetFileId() );
						pRoot->SetValue( "combinationOperator", pCombo );
					}
				}

				DeltaInfo_t &deltaInfo = m_deltas[ pControlName ];
				deltaInfo.m_filename = pFindFile;
				deltaInfo.m_pMesh = pMesh;
				deltaInfo.m_pComboOp = pCombo;

				if ( bLoadAllDeltas )
				{
					GetDelta( pControlName, bAbsolute );
				}
			}
		}

		g_pFullFileSystem->FindClose( hFind );

		if ( pCombo )
		{
			pCombo->AddTarget( pMesh );
			pMesh->ComputeAllCorrectedPositionsFromActualPositions();
		}
	}

	return pRoot;
}


//-----------------------------------------------------------------------------
// Common function both ReadOBJ & Unserialize can call
//-----------------------------------------------------------------------------
CDmElement *CDmObjSerializer::ReadOBJ( CUtlBuffer &buf,
	DmFileId_t dmFileId,
	const char *pName,
	const char *pFilename /* = NULL */,
	CDmeMesh *pBaseMesh /* = NULL */,
	CDmeMesh **ppCreatedMesh /* = NULL */,
	bool bAbsolute /* = true */ )
{
	CDmElement *pRoot( NULL );
	CDmeModel *pModel( NULL );

	if ( !pBaseMesh )
	{
		pRoot = CreateElement< CDmElement >( "root", dmFileId );
		pModel = CreateElement< CDmeModel >( "model", dmFileId );

		pRoot->SetValue( "skeleton", pModel );
		pRoot->SetValue( "model", pModel );
	}

	m_mtlLib.RemoveAll();

	char tmpBuf0[ 4096 ];
	char tmpBuf1[ 4096 ];

	characterset_t breakSet;
	CharacterSetBuild( &breakSet, "/\\" );

	const char *pBuf;

	Vector p;
	Vector2D uv;

	CVertexData vertexData;
	CFaceSetData faceSetData;

	CUtlString groupName;

	CDmeDag *pDmeDag( NULL );
	CDmeMesh *pDmeMesh( NULL );
	CUtlVector< int > *pFaceIndices( NULL );

	while ( buf.IsValid() )
	{
		buf.GetLine( tmpBuf0, sizeof( tmpBuf0 ) );

		pBuf = SkipSpace( tmpBuf0 );

		if ( sscanf( tmpBuf0, "v %f %f %f", &p.x, &p.y, &p.z ) == 3 )
		{
			if ( pDmeDag )
			{
				vertexData.AddToMesh( pDmeMesh, bAbsolute, "bind", false );
				faceSetData.AddToMesh( pDmeMesh );

				pDmeDag = NULL;
				pDmeMesh = NULL;
				pFaceIndices = NULL;
			}

			vertexData.AddPosition( p );

			continue;
		}

		if ( sscanf( pBuf, "vn %f %f %f", &p.x, &p.y, &p.z ) == 3 )
		{
			vertexData.AddNormal( p );
			continue;
		}

		if ( !pBaseMesh )
		{
			if ( sscanf( pBuf, "vt %f %f", &uv.x, &uv.y ) == 2 )
			{
				vertexData.AddUV( uv );
				continue;
			}

			if ( pFilename && sscanf( pBuf, "mtllib %4096s", tmpBuf1 ) == 1 )
			{
				CUtlString mtlLib( tmpBuf1 );

				Q_strncpy( tmpBuf0, pFilename, sizeof( tmpBuf0 ) );
				Q_FixSlashes( tmpBuf0 );
				Q_StripFilename( tmpBuf0 );

				char mtlLibPath[ MAX_PATH ];

				Q_ComposeFileName( tmpBuf0, tmpBuf1, mtlLibPath, sizeof( mtlLibPath ) );
				CUtlBuffer utlBuf;

				if ( g_pFullFileSystem->ReadFile( mtlLibPath, NULL, utlBuf ) )
				{
					ParseMtlLib( utlBuf );
				}

				continue;
			}

			if ( sscanf( pBuf, "usemtl %4096s", tmpBuf1 ) == 1 )
			{
				// Remove any 'SG' suffix from the material
				const uint sLen = Q_strlen( tmpBuf1 );
				if ( sLen && !Q_strcmp( tmpBuf1 + sLen - 2, "SG" ) )
				{
					tmpBuf1[ sLen - 2 ] = '\0';
				}

				const char *pTexture( FindMtlEntry( tmpBuf1 ) );
				if ( pTexture )
				{
					pFaceIndices = faceSetData.GetFaceSetIndices( pTexture );
				}
				else
				{
					pFaceIndices = faceSetData.GetFaceSetIndices( tmpBuf1 );
				}

				continue;
			}

			if ( sscanf( pBuf, "g %4096s", tmpBuf1 ) == 1 )
			{
				groupName = tmpBuf1;
				if ( pFaceIndices == NULL )
				{
					pFaceIndices = faceSetData.GetFaceSetIndices( tmpBuf1 );
				}
				continue;
			}
			if ( *pBuf == 'f' && ( *( pBuf + 1 ) == ' ' || *( pBuf + 1 ) == '\t' ) )
			{
				if ( pDmeDag == NULL )
				{
					pDmeDag = CreateElement< CDmeDag >( pName ? pName : ( groupName.IsEmpty() ? "obj" : groupName.Get() ), pRoot->GetFileId() );
					Assert( pDmeDag );
					pDmeMesh = CreateElement< CDmeMesh >( pName ? pName : ( groupName.IsEmpty() ? "obj" : groupName.Get() ), pRoot->GetFileId() );
					if ( ppCreatedMesh && *ppCreatedMesh == NULL )
					{
						// Only the first mesh created...
						*ppCreatedMesh = pDmeMesh;
					}
					pDmeDag->SetShape( pDmeMesh );
					if ( pModel )
					{
						pModel->AddJoint( pDmeDag );
						pModel->AddChild( pDmeDag );
					}
				}

				if ( pFaceIndices == NULL )
				{
					pFaceIndices = faceSetData.GetFaceSetIndices( "facetSet" );
				}

				int v;
				int t;
				int n;

				pBuf = SkipSpace( pBuf + 1 );
				int nLen = Q_strlen( pBuf );

				CUtlBuffer bufParse( pBuf, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );

				while ( bufParse.IsValid() )
				{
					if ( !ParseVertex( bufParse, breakSet, v, t, n ) )
						break;

					pFaceIndices->AddToTail( vertexData.VertexCount() );
					if ( v > 0 )
					{
						vertexData.AddPositionIndex( v - 1 );
					}

					if ( n > 0 )
					{
						vertexData.AddNormalIndex( n - 1 );
					}

					if ( t > 0 )
					{
						vertexData.AddUVIndex( t - 1 );
					}
				}

				pFaceIndices->AddToTail( -1 );
				continue;
			}
		}
	}

	CDmeVertexDataBase *pVertexData( NULL );

	if ( pBaseMesh )
	{
		pVertexData = vertexData.AddToMesh( pBaseMesh, bAbsolute, pName, true );
	}
	else
	{
		pVertexData = vertexData.AddToMesh( pDmeMesh, bAbsolute, "bind", false );
		faceSetData.AddToMesh( pDmeMesh );
	}

	if ( pModel )
	{
		pModel->CaptureJointsToBaseState( "bind" );
	}

	if ( pBaseMesh )
		return pVertexData;

	return pRoot;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CDmObjSerializer::OutputVectors(
	CUtlBuffer &b,
	const char *pPrefix,
	const CUtlVector< Vector > &vData,
	const matrix3x4_t &matrix )
{
	Vector v;

	const int nv( vData.Count() );

	for ( int i( 0 ); i < nv; ++i )
	{
		VectorTransform( vData[ i ], matrix, v );
		b << pPrefix << v << "\n";
	}

	return nv;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CDmObjSerializer::OutputVectors(
	CUtlBuffer &b,
	const char *pPrefix,
	const CUtlVector< Vector2D > &vData )
{
	Vector v;

	const int nv( vData.Count() );

	for ( int i( 0 ); i < nv; ++i )
	{
		b << pPrefix << vData[ i ] << "\n";
	}

	return nv;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CDmObjSerializer::MeshToObj(
	CUtlBuffer &b,
	const matrix3x4_t &parentWorldMatrix,
	CDmeMesh *pMesh,
	const char *pDeltaName,
	bool absolute )
{
	CUtlVector< CDmeMesh::DeltaComputation_t > compList;

	if ( pDeltaName )
	{
		pMesh->ComputeDependentDeltaStateList( compList );
	}

	const int nCompList( compList.Count() );

	CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) );
	if ( !pBase )
		return;

	const FieldIndex_t pIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) );
	if ( pIndex < 0 )
		return;

	int nPositionCount = 0;
	int nTextureCount = 0;
	int nNormalCount = 0;

	b << "g " << pMesh->GetName() << "\n";

	CDmrArrayConst< Vector > pArray( pBase->GetVertexData( pIndex ) );
	const CUtlVector< int > *ppIndices( &pBase->GetVertexIndexData( pIndex ) );
	const CUtlVector< Vector > &pConstData( pArray.Get() );

	if ( nCompList )
	{
		CUtlVector< Vector > pData;
		pData.CopyArray( pConstData.Base(), pConstData.Count() );

		if ( absolute )
		{
			for ( int i ( 0 ); i < nCompList; ++i )
			{
				CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) );
				if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) )
					continue;

				b << "# Delta: " << pTmpDeltaState->GetName() << "\n";
				pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f );

				const CUtlVector< int > &depDeltas( compList[ i ].m_DependentDeltas );
				const int nDepDeltas( depDeltas.Count() );
				for ( int j( 0 ); j < nDepDeltas; ++j )
				{
					pTmpDeltaState = pMesh->GetDeltaState( depDeltas[ j ] );
					b << "# Dependent Delta: " << pTmpDeltaState->GetName() << "\n";
					pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f );
				}
			}
		}
		else
		{
			for ( int i ( 0 ); i < nCompList; ++i )
			{
				CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) );
				if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) )
					continue;

				b << "# Delta: " << pTmpDeltaState->GetName() << "\n";
				pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f );
			}
		}

		nPositionCount = OutputVectors( b, "v ", pData, parentWorldMatrix );
	}
	else
	{
		nPositionCount = OutputVectors( b, "v ", pConstData, parentWorldMatrix );
	}

	const CUtlVector< int > *puvIndices( NULL );
	const FieldIndex_t uvIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ) );
	if ( uvIndex >= 0 )
	{
		CDmrArrayConst< Vector2D > uvArray( pBase->GetVertexData( uvIndex ) );
		const CUtlVector< Vector2D > &uvData( uvArray.Get() );
		puvIndices = &pBase->GetVertexIndexData( uvIndex );

		nTextureCount = OutputVectors( b, "vt ", uvData );
	}

	const CUtlVector< int > *pnIndices( NULL );
	const FieldIndex_t nIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ) );
	if ( nIndex >= 0 )
	{
		matrix3x4_t normalMatrix;
		MatrixInverseTranspose( parentWorldMatrix, normalMatrix );

		CDmrArrayConst< Vector > nArray( pBase->GetVertexData( nIndex ) );
		const CUtlVector< Vector > &nConstData( nArray.Get() );
		pnIndices = &pBase->GetVertexIndexData( nIndex );

		if ( nCompList )
		{
			CUtlVector< Vector > nData;
			nData.CopyArray( nConstData.Base(), nConstData.Count() );

			if ( absolute )
			{
				for ( int i ( 0 ); i < nCompList; ++i )
				{
					CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) );
					if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) )
						continue;

					b << "# Delta: " << pTmpDeltaState->GetName() << "\n";
					pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f );

					const CUtlVector< int > &depDeltas( compList[ i ].m_DependentDeltas );
					const int nDepDeltas( depDeltas.Count() );
					for ( int j( 0 ); j < nDepDeltas; ++j )
					{
						pTmpDeltaState = pMesh->GetDeltaState( depDeltas[ j ] );
						b << "# Dependent Delta: " << pTmpDeltaState->GetName() << "\n";
						pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f );
					}
				}
			}
			else
			{
				for ( int i ( 0 ); i < nCompList; ++i )
				{
					CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) );
					if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) )
						continue;

					b << "# Delta: " << pTmpDeltaState->GetName() << "\n";
					pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f );
				}
			}

			nNormalCount = OutputVectors( b, "vn ", nData, normalMatrix );
		}
		else
		{
			nNormalCount = OutputVectors( b, "vn ", nConstData, normalMatrix );
		}
	}

	const int pCount( ppIndices->Count() );
	const int uvCount( puvIndices ? puvIndices->Count() : 0 );
	const int nCount( pnIndices ? pnIndices->Count() : 0 );

	const int nFaceSets( pMesh->FaceSetCount() );
	for ( int i( 0 ); i < nFaceSets; ++i )
	{
		CDmeFaceSet *pFaceSet( pMesh->GetFaceSet( i ) );
		CDmeMaterial *pMaterial( pFaceSet->GetMaterial() );
		if ( pMaterial )
		{
			b << "usemtl " << pMaterial->GetMaterialName() << "\n";
		}

		const int nIndices( pFaceSet->NumIndices() );
		const int *pIndex( pFaceSet->GetIndices() );
		const int *const pEnd( pIndex + nIndices );
		int fIndex;

		const char *const pFaceStart( "f " );
		const char *const pFaceNext( " " );
		const char *pFaceSep( pFaceStart );

		if ( pCount == uvCount && pCount == nCount )
		{
			const CUtlVector< int > &pIndices( *ppIndices );
			const CUtlVector< int > &uvIndices( *puvIndices );
			const CUtlVector< int > &nIndices( *pnIndices );

			while ( pIndex < pEnd )
			{
				fIndex = *pIndex++;
				if ( fIndex < 0 )
				{
					b << "\n";
					pFaceSep = pFaceStart;
					continue;
				}

				b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << '/' << ( uvIndices[ fIndex ] + m_nTextureOffset ) << '/' << ( nIndices[ fIndex ] + m_nNormalOffset );
				pFaceSep = pFaceNext;
			}
		}
		else if ( pCount == uvCount )
		{
			const CUtlVector< int > &pIndices( *ppIndices );
			const CUtlVector< int > &uvIndices( *puvIndices );

			while ( pIndex < pEnd )
			{
				fIndex = *pIndex++;
				if ( fIndex < 0 )
				{
					b << "\n";
					pFaceSep = pFaceStart;
					continue;
				}

				b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << '/' << ( uvIndices[ fIndex ] + m_nTextureOffset ) << '/';
				pFaceSep = pFaceNext;
			}
		}
		else if ( pCount == nCount )
		{
			const CUtlVector< int > &pIndices( *ppIndices );
			const CUtlVector< int > &nIndices( *pnIndices );

			while ( pIndex < pEnd )
			{
				fIndex = *pIndex++;
				if ( fIndex < 0 )
				{
					b << "\n";
					pFaceSep = pFaceStart;
					continue;
				}

				b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << "//" << ( nIndices[ fIndex ] + m_nNormalOffset );
				pFaceSep = pFaceNext;
			}
		}
		else
		{
			const CUtlVector< int > &pIndices( *ppIndices );

			while ( pIndex < pEnd )
			{
				fIndex = *pIndex++;
				if ( fIndex < 0 )
				{
					b << "\n";
					pFaceSep = pFaceStart;
					continue;
				}

				b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << "//";
				pFaceSep = pFaceNext;
			}
		}
	}

	m_nPositionOffset += nPositionCount;
	m_nTextureOffset += nTextureCount;
	m_nNormalOffset += nNormalCount;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CDmObjSerializer::DagToObj(
	CUtlBuffer &b,
	const matrix3x4_t &parentWorldMatrix,
	CDmeDag *pDag,
	const char *pDeltaName,
	bool absolute )
{
	matrix3x4_t inclusiveMatrix;
	pDag->GetTransform()->GetTransform( inclusiveMatrix );

	ConcatTransforms( parentWorldMatrix, inclusiveMatrix, inclusiveMatrix );

	CDmeMesh *pMesh( CastElement< CDmeMesh >( pDag->GetShape() ) );
	if ( pMesh )
	{
		MeshToObj( b, inclusiveMatrix, pMesh, pDeltaName, absolute );
	}

	const int nChildren( pDag->GetChildCount() );
	for ( int i( 0 ); i < nChildren; ++i )
	{
		DagToObj( b, inclusiveMatrix, pDag->GetChild( i ), pDeltaName, absolute );
	}
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CDmObjSerializer::FindDeltaMeshes( CDmeDag *pDag, CUtlVector< CDmeMesh * > &meshes )
{
	CDmeMesh *pMesh( CastElement< CDmeMesh >( pDag->GetShape() ) );
	if ( pMesh && pMesh->DeltaStateCount() )
	{
		meshes.AddToTail( pMesh );
	}

	const int nChildren( pDag->GetChildCount() );
	for ( int i( 0 ); i < nChildren; ++i )
	{
		FindDeltaMeshes( pDag->GetChild( i ), meshes );
	}
}


//-----------------------------------------------------------------------------
// Convert from OBJ -> DME
//-----------------------------------------------------------------------------
bool CDmObjSerializer::WriteOBJ( const char *pFilename, CDmElement *pRoot, bool bWriteOBJs, const char *pDeltaName, bool absolute )
{
	CDmeDag *pModel = pRoot->GetValueElement< CDmeDag >( "model" );

	if ( !pModel )
		return false;

	matrix3x4_t identityMatrix;
	SetIdentityMatrix( identityMatrix );

	if ( !pDeltaName )
	{
		CUtlBuffer b( 0, 0, CUtlBuffer::TEXT_BUFFER );

		b << "# OBJ\n";
		b << "#\n";

		m_nPositionOffset = 1;	// OBJs start indexing at 1
		m_nTextureOffset = 1;
		m_nNormalOffset = 1;

		DagToObj( b, identityMatrix, pModel, pDeltaName, absolute );

		g_pFullFileSystem->WriteFile( pFilename, NULL, b );

		// Filesystem is silly
		// On WIN32 filesystem changes all of the characters to lowercase grrrr.....
		rename( pFilename, pFilename );
	}

	if ( !bWriteOBJs )
		return true;

	CUtlVector< CDmeMesh * > deltaMeshes;
	FindDeltaMeshes( pModel, deltaMeshes );

	if ( deltaMeshes.Count() )
	{
		char base[ MAX_PATH ];
		Q_FileBase( pFilename, base, sizeof( base ) );

		char path[ MAX_PATH ];
		Q_ExtractFilePath( pFilename, path, sizeof( path ) );

		char *pSuffix = strchr( base, '='	 );
		if ( !pSuffix )
		{
			pSuffix = strchr( base, '_' );
		}

		if ( pSuffix )
		{
			*( pSuffix + 0 ) = '_';
			*( pSuffix + 1 ) = '\0';
		}

		char filename[ MAX_PATH ];

		const int nDeltaMeshes( deltaMeshes.Count() );
		for ( int i( 0 ); i < nDeltaMeshes; ++i )
		{
			CDmeMesh *pDeltaMesh( deltaMeshes[ i ] );
			const int nDeltas( pDeltaMesh->DeltaStateCount() );
			for ( int j( 0 ); j < nDeltas; ++j )
			{
				CDmeVertexDeltaData *pDelta( pDeltaMesh->GetDeltaState( j ) );

				if ( !pDeltaName || !Q_strcmp( pDeltaName, pDelta->GetName() ) )
				{
					CUtlBuffer b( 0, 0, CUtlBuffer::TEXT_BUFFER );

					b << "# Delta OBJ: " << pDelta->GetName() << "\n";
					b << "#\n";

					Q_strncpy( filename, pDelta->GetName(), sizeof( filename ) );
					// Change _ to +
					const char *const pEnd( filename + sizeof( filename ) );
					for ( char *pChar = filename; *pChar && pChar < pEnd; ++pChar )
					{
						if ( *pChar == '_' )
						{
							*pChar = '+';
						}
					}

					CUtlString deltaFile( base );
					deltaFile += filename;
					deltaFile += ".obj";

					m_nPositionOffset = 1;	// OBJs use 1 based indexes
					m_nTextureOffset = 1;
					m_nNormalOffset = 1;

					DagToObj( b, identityMatrix, pModel, pDelta->GetName(), absolute );

					Q_ComposeFileName( path, deltaFile.Get(), filename, sizeof( filename ) );
					g_pFullFileSystem->WriteFile( filename, NULL, b );
					// On WIN32 filesystem changes all of the characters to lowercase grrrr.....
					rename( filename, filename );
				}
			}
		}
	}

	return true;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CDmObjSerializer::ParseMtlLib( CUtlBuffer &buf )
{
	char tmpBuf0[ 4096 ];

	int nCurrentMtl = -1;
	while ( buf.IsValid() )
	{
		buf.GetLine( tmpBuf0, sizeof(tmpBuf0) );

		if ( StringHasPrefix( tmpBuf0, "newmtl " ) )
		{
			char mtlName[1024];
			if ( sscanf( tmpBuf0, "newmtl %s", mtlName ) == 1 )
			{
				// Remove any 'SG' suffix from the material
				const uint sLen = Q_strlen( mtlName );
				if ( sLen > 2 && !Q_strcmp( mtlName + sLen - 2, "SG" ) )
				{
					mtlName[ sLen - 2 ] = '\0';
				}

				nCurrentMtl = m_mtlLib.AddToTail( );
				m_mtlLib[nCurrentMtl].m_MtlName = mtlName;
				m_mtlLib[nCurrentMtl].m_TgaName = "debugempty";
			}
			continue;
		}

		if ( StringHasPrefix( tmpBuf0, "map_Kd " ) )
		{
			if ( nCurrentMtl < 0 )
				continue;

			char tgaPath[MAX_PATH];
			char tgaName[1024];
			if ( sscanf( tmpBuf0, "map_Kd %s", tgaPath ) == 1 )
			{
				// Try a cheesy hack - look for /materialsrc/ and set the material name off the entire path minus extension
				Q_strncpy( tmpBuf0, tgaPath, sizeof( tmpBuf0 ) );
				Q_FixSlashes( tmpBuf0, '/' );
				const char *pMaterialSrc = Q_strstr( tmpBuf0, "/materialsrc/" );
				if ( pMaterialSrc )
				{
					pMaterialSrc += Q_strlen( "/materialsrc/" );
					Q_StripExtension( pMaterialSrc, tgaName, sizeof( tgaName) );
					m_mtlLib[ nCurrentMtl ].m_TgaName = tgaName;
				}
				else
				{
					Q_FileBase( tgaPath, tgaName, sizeof(tgaName) );
					m_mtlLib[nCurrentMtl].m_TgaName = tgaName;
				}
			}
			continue;
		}
	}
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
const char *CDmObjSerializer::FindMtlEntry( const char *pTgaName )
{
	int nCount = m_mtlLib.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		if ( !Q_stricmp( m_mtlLib[i].m_MtlName, pTgaName ) )
			return m_mtlLib[i].m_TgaName;
	}
	return pTgaName;
}									 


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CDmObjSerializer::ParseVertex( CUtlBuffer& bufParse, characterset_t &breakSet, int &v, int &t, int &n )
{
	char	cmd[1024];
	int nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
	if ( nLen <= 0 )
		return false;

	v = atoi( cmd );
	n = 0;
	t = 0;

	char c = *(char*)bufParse.PeekGet();
	bool bHasTexCoord = IN_CHARACTERSET( breakSet, c ) != 0;
	bool bHasNormal = false;
	if ( bHasTexCoord )
	{
		// Snag the '/'
		nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
		Assert( nLen == 1 );

		c = *(char*)bufParse.PeekGet();
		if ( !IN_CHARACTERSET( breakSet, c ) )
		{
			nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
			Assert( nLen > 0 );
			t = atoi( cmd );

			c = *(char*)bufParse.PeekGet();
			bHasNormal = IN_CHARACTERSET( breakSet, c ) != 0;
		}
		else
		{
			bHasNormal = true;
			bHasTexCoord = false;
		}

		if ( bHasNormal )
		{
			// Snag the '/'
			nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
			Assert( nLen == 1 );

			nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
			Assert( nLen > 0 );
			n = atoi( cmd );
		}
	}
	return true;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
const char *CDmObjSerializer::SkipSpace(
	const char *pBuf )
{
	while ( *pBuf == ' ' || *pBuf == '\t' )
		++pBuf;

	return pBuf;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CDmeVertexDeltaData *CDmObjSerializer::GetDelta( const char *pDeltaName, bool bAbsolute )
{
	if ( !m_deltas.Defined( pDeltaName ) )
		return NULL;

	DeltaInfo_t &deltaInfo( m_deltas[ pDeltaName ] );

	if ( deltaInfo.m_pDeltaData )
		return deltaInfo.m_pDeltaData;

	if ( !LoadDependentDeltas( pDeltaName ) )
		return NULL;

	CUtlBuffer utlBuf;

	char deltaPath[ MAX_PATH ];
	Q_ComposeFileName( m_objDirectory, deltaInfo.m_filename, deltaPath, sizeof( deltaPath ) );
	Q_FixSlashes( deltaPath );

	if ( !g_pFullFileSystem->ReadFile( deltaPath, NULL, utlBuf ) )
		return NULL;

	if ( deltaInfo.m_pComboOp && !strchr( pDeltaName, '_' ) )
	{
		deltaInfo.m_pComboOp->FindOrCreateControl( pDeltaName, false, true );
	}

	deltaInfo.m_pDeltaData = CastElement< CDmeVertexDeltaData >( ReadOBJ( utlBuf, deltaInfo.m_pMesh->GetFileId(), pDeltaName, deltaPath, deltaInfo.m_pMesh, NULL, bAbsolute ) );

	return deltaInfo.m_pDeltaData;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CDmObjSerializer::LoadDependentDeltas( const char *pDeltaName )
{
	// TODO: Load Dependent Deltas
	return true;
}


//-----------------------------------------------------------------------------
// Counts the number of _'s in a string
//-----------------------------------------------------------------------------
int ComputeDimensionality( const char *pDeltaName )
{
	const char *pUnderBar = pDeltaName;
	int nDimensions = 0;

	while ( pUnderBar )
	{
		++nDimensions;
		pUnderBar = strchr( pUnderBar, '_' );
		if ( pUnderBar )
		{
			++pUnderBar;
		}
	}

	return nDimensions;
}

/*
//-----------------------------------------------------------------------------
// Generates a sorted list in order of dimensionality of the delta states
// NOTE: This assumes a naming scheme where delta state names have _ that separate control names
//-----------------------------------------------------------------------------
void CDmObjSerializer::ComputeDeltaStateComputationList( CUtlVector< CUtlVector< int > > &dependentDeltaList )
{
	// Do all combinations in order of dimensionality, lowest dimension first

	for ( int i = 0; i < nDeltas; ++i )
	{
		compList[i].m_nDeltaIndex = i;
		compList[i].m_nDimensionality = ComputeDeltaStateDimensionality( i );
	}
	qsort( compList.Base(), nCount, sizeof(DeltaComputation_t), DeltaStateLessFunc );
}



{
	CUtlVector< CUtlString > atomicControls;
	deltaStateUsage.SetCount( nCount );

	// Build a list of atomic controls
	int nCurrentDelta;
	for ( nCurrentDelta = 0; nCurrentDelta < nCount; ++nCurrentDelta ) 
	{
		if ( pInfo[nCurrentDelta].m_nDimensionality != 1 )
			break;
		int j = atomicControls.AddToTail( GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex )->GetName() );
		deltaStateUsage[ nCurrentDelta ].AddToTail( j );
	}

	for ( ; nCurrentDelta < nCount; ++nCurrentDelta )
	{
		CDmeVertexDeltaData *pDeltaState = GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex );
		int nLen = Q_strlen( pDeltaState->GetName() );
		char *pTempBuf = (char*)_alloca( nLen + 1 );
		memcpy( pTempBuf, pDeltaState->GetName(), nLen+1 );
		char *pNext;
		for ( char *pUnderBar = pTempBuf; pUnderBar; pUnderBar = pNext )
		{
			pNext = strchr( pUnderBar, '_' );
			if ( pNext )
			{
				*pNext = 0;
				++pNext;
			}

			// Find this name in the list of strings
			int j;
			int nControlCount = atomicControls.Count();
			for ( j = 0; j < nControlCount; ++j )
			{
				if ( !Q_stricmp( pUnderBar, atomicControls[j] ) )
					break;
			}
			if ( j == nControlCount )
			{
				j = atomicControls.AddToTail( pUnderBar );
			}
			deltaStateUsage[ nCurrentDelta ].AddToTail( j );
		}
		deltaStateUsage[ nCurrentDelta ].Sort( DeltaStateUsageLessFunc );
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CDmObjSerializer::ComputeDependentUsage( CUtlVector< CUtlVector< int > > &deltaUsage )
{
	const int nDeltas = m_deltas.GetNumStrings();
	compList.EnsureCount( nDeltas );

	CUtlVector< CUtlVector< int > > deltaStateUsage;
	const int nCount( compList.Count() );
	BuildAtomicControlLists( nCount, compList.Base(), deltaStateUsage );

	// Now build up a list of dependent delta states based on usage
	// NOTE: Usage is sorted in ascending order.
	for ( int i = 1; i < nCount; ++i )
	{
		int nUsageCount1 = deltaStateUsage[i].Count();
		for ( int j = 0; j < i; ++j )
		{
			// At the point they have the same dimensionality, no more need to check
			if ( compList[j].m_nDimensionality == compList[i].m_nDimensionality )
				break;

			int ii = 0;
			bool bSubsetFound = true;
			int nUsageCount2 = deltaStateUsage[j].Count();
			for ( int ji = 0; ji < nUsageCount2; ++ji )
			{
				for ( bSubsetFound = false; ii < nUsageCount1; ++ii )
				{
					if ( deltaStateUsage[j][ji] == deltaStateUsage[i][ii] )
					{
						++ii;
						bSubsetFound = true;
						break;
					}

					if ( deltaStateUsage[j][ji] < deltaStateUsage[i][ii] )
						break;
				}

				if ( !bSubsetFound )
					break;
			}

			if ( bSubsetFound )
			{
				compList[i].m_DependentDeltas.AddToTail( compList[j].m_nDeltaIndex );
			}
		}
	}
}
*/