//========= Copyright 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( 0, 0, CUtlBuffer::TEXT_BUFFER ); if ( !g_pFullFileSystem->ReadFile( filename, NULL, utlBuf ) ) return NULL; char baseFile[ MAX_PATH ]; Q_FileBase( filename, baseFile, sizeof( baseFile ) ); CDmeMesh *pMesh( NULL ); DmFileId_t nFileId = g_pDataModel->FindOrCreateFileId( pFilename ); CDmElement *pRoot = ReadOBJ( utlBuf, nFileId, 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 =.obj or is _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 =.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( 0, 0, CUtlBuffer::TEXT_BUFFER ); 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 nPosIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); if ( nPosIndex < 0 ) return; int nPositionCount = 0; int nTextureCount = 0; int nNormalCount = 0; b << "g " << pMesh->GetName() << "\n"; CDmrArrayConst< Vector > pArray( pBase->GetVertexData( nPosIndex ) ); const CUtlVector< int > *ppIndices( &pBase->GetVertexIndexData( nPosIndex ) ); 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 *pnFaceSetIndex( pFaceSet->GetIndices() ); const int *const pEnd( pnFaceSetIndex + 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 > &nvIndices( *pnIndices ); while ( pnFaceSetIndex < pEnd ) { fIndex = *pnFaceSetIndex++; if ( fIndex < 0 ) { b << "\n"; pFaceSep = pFaceStart; continue; } b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << '/' << ( uvIndices[ fIndex ] + m_nTextureOffset ) << '/' << ( nvIndices[ fIndex ] + m_nNormalOffset ); pFaceSep = pFaceNext; } } else if ( pCount == uvCount ) { const CUtlVector< int > &pIndices( *ppIndices ); const CUtlVector< int > &uvIndices( *puvIndices ); while ( pnFaceSetIndex < pEnd ) { fIndex = *pnFaceSetIndex++; 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 > &nvIndices( *pnIndices ); while ( pnFaceSetIndex < pEnd ) { fIndex = *pnFaceSetIndex++; if ( fIndex < 0 ) { b << "\n"; pFaceSep = pFaceStart; continue; } b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << "//" << ( nvIndices[ fIndex ] + m_nNormalOffset ); pFaceSep = pFaceNext; } } else { const CUtlVector< int > &pIndices( *ppIndices ); while ( pnFaceSetIndex < pEnd ) { fIndex = *pnFaceSetIndex++; 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 = mtlName; } 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 ); } } } } */