//====== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======= // // Functions which do things to a DmeMesh // //============================================================================= // Valve includes #include "dmeutils/dmmeshutils.h" #include "movieobjects/dmeanimationset.h" #include "movieobjects/dmecombinationoperator.h" #include "movieobjects/dmemodel.h" #include "movieobjects/dmedag.h" #include "movieobjects/dmemesh.h" #include "movieobjects/dmefaceset.h" #include "movieobjects/dmematerial.h" #include "movieobjects/dmevertexdata.h" #include "tier1/utlstack.h" #include "tier2/p4helpers.h" #include "tier1/utlstring.h" #include "tier1/utlstringmap.h" #include "tier1/utlbuffer.h" #include "tier1/fmtstr.h" #include "filesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_MESHUTILS, "MeshUtils" ); #ifndef LOG_COLOR_RED #define LOG_COLOR_RED Color( 255, 0, 0, 255 ) #endif // #ifndef LOG_COLOR_RED #ifndef LOG_COLOR_YELLOW #define LOG_COLOR_YELLOW Color( 255, 255, 0, 255 ) #endif // #ifndef LOG_COLOR_YELLOW //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::RemoveLargeAxisAlignedPlanarFaces( CDmeMesh *pMesh ) { CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); if ( !pBase ) return false; const int posIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); if ( posIndex < 0 ) return false; const CUtlVector< Vector > &posData( CDmrArrayConst< Vector >( pBase->GetVertexData( posIndex ) ).Get() ); if ( posData.Count() <= 0 ) return false; const CUtlVector< int > &posIndices( CDmrArrayConst< int >( pBase->GetIndexData( posIndex ) ).Get() ); if ( posIndices.Count() <= 0 ) return false; bool bMeshChanged = false; CUtlVector< int > emptyFaceSets; int faceStartIndex = 0; int faceCurrentIndex = 0; int faceVertexCount = 0; bool bPlanarX = true; bool bPlanarY = true; bool bPlanarZ = true; Vector p; CUtlVector< int > removeStart; CUtlVector< int > removeCount; const int nFaceSets = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSets; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); const int nFaceIndices = pFaceSet->NumIndices(); if ( nFaceIndices <= 0 ) continue; faceStartIndex = 0; faceCurrentIndex = pFaceSet->GetIndex( 0 ); if ( faceCurrentIndex < 0 ) continue; faceVertexCount = 0; bPlanarX = true; bPlanarY = true; bPlanarZ = true; removeStart.RemoveAll(); removeCount.RemoveAll(); p = posData[ posIndices[ faceCurrentIndex ] ]; for ( int j = 1; j < nFaceIndices; ++j ) { faceCurrentIndex = pFaceSet->GetIndex( j ); if ( faceCurrentIndex < 0 ) { // End of a face if ( faceVertexCount > 4 && ( bPlanarX || bPlanarY || bPlanarZ ) ) { removeStart.AddToTail( faceStartIndex ); removeCount.AddToTail( j - faceStartIndex + 1 ); } faceStartIndex = j + 1; if ( faceStartIndex < nFaceIndices ) { p = posData[ posIndices[ pFaceSet->GetIndex( faceStartIndex ) ] ]; } faceVertexCount = 0; bPlanarX = true; bPlanarY = true; bPlanarZ = true; continue; } Assert( faceCurrentIndex < posIndices.Count() ); Assert( posIndices[ faceCurrentIndex ] < posData.Count() ); const Vector &vPos = posData[ posIndices[ faceCurrentIndex ] ]; if ( vPos.x != p.x ) bPlanarX = false; if ( vPos.y != p.y ) bPlanarY = false; if ( vPos.z != p.z ) bPlanarZ = false; ++faceVertexCount; } Assert( removeStart.Count() == removeCount.Count() ); for ( int j = removeStart.Count() - 1; j >= 0; --j ) { pFaceSet->RemoveMultiple( removeStart[ j ], removeCount[ j ] ); bMeshChanged = true; } if ( pFaceSet->GetIndexCount() == 0 ) { emptyFaceSets.AddToTail( i ); } } for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) { pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); bMeshChanged = true; } if ( bMeshChanged ) { PurgeUnusedData( pMesh ); return true; } return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::RemoveFacesWithMaterial( CDmeMesh *pMesh, const char *pMaterialName ) { bool bMeshChanged = false; CUtlVector< int > emptyFaceSets; const int nFaceSets = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSets; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); if ( !Q_strcmp( pFaceSet->GetMaterial()->GetMaterialName(), pMaterialName ) ) { emptyFaceSets.AddToTail( i ); bMeshChanged = true; } } for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) { pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); bMeshChanged = true; } if ( bMeshChanged ) { PurgeUnusedData( pMesh ); return true; } return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::RemoveFacesWithMoreThanNVerts( CDmeMesh *pMesh, const int nVertexCount ) { CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); if ( !pBase ) return false; bool bMeshChanged = false; CUtlVector< int > emptyFaceSets; int faceStartIndex = 0; int faceCurrentIndex = 0; int faceVertexCount = 0; CUtlVector< int > removeStart; CUtlVector< int > removeCount; const int nFaceSets = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSets; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); const int nFaceIndices = pFaceSet->NumIndices(); if ( nFaceIndices <= 0 ) continue; faceStartIndex = 0; faceCurrentIndex = pFaceSet->GetIndex( 0 ); if ( faceCurrentIndex < 0 ) continue; faceVertexCount = 0; removeStart.RemoveAll(); removeCount.RemoveAll(); for ( int j = 1; j < nFaceIndices; ++j ) { faceCurrentIndex = pFaceSet->GetIndex( j ); if ( faceCurrentIndex < 0 ) { // End of a face if ( faceVertexCount > nVertexCount ) { removeStart.AddToTail( faceStartIndex ); removeCount.AddToTail( j - faceStartIndex + 1 ); } faceStartIndex = j + 1; faceVertexCount = 0; continue; } ++faceVertexCount; } Assert( removeStart.Count() == removeCount.Count() ); for ( int j = removeStart.Count() - 1; j >= 0; --j ) { pFaceSet->RemoveMultiple( removeStart[ j ], removeCount[ j ] ); bMeshChanged = true; } if ( pFaceSet->GetIndexCount() == 0 ) { emptyFaceSets.AddToTail( i ); } } for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) { pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); bMeshChanged = true; } if ( bMeshChanged ) { PurgeUnusedData( pMesh ); return true; } // Nothing remove return false; } //----------------------------------------------------------------------------- // Figures out which vertexIndices are missing // Returned list will be in sorted order //----------------------------------------------------------------------------- void ComputeVertexIndexMap( CDmeMesh *pMesh, int nMaxVertexCount, CUtlVector< int > &vertexIndexMap ) { bool *pVertexFound = reinterpret_cast< bool * >( alloca( nMaxVertexCount * sizeof( bool ) ) ); memset( pVertexFound, 0, nMaxVertexCount * sizeof( bool ) ); // Loop through all the face sets to find out the highest vertex index const int nFaceSetCount = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSetCount; ++i ) { const CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); const int nFaceSetIndices = pFaceSet->NumIndices(); for ( int j = 0; j < nFaceSetIndices; ++j ) { const int &nIndex = pFaceSet->GetIndex( j ); if ( nIndex >= 0 ) { Assert( nIndex < nMaxVertexCount ); pVertexFound[ nIndex ] = true; } } } int nMissingCount = 0; for ( int i = 0; i < nMaxVertexCount; ++i ) { if ( !pVertexFound[ i ] ) { ++nMissingCount; } } vertexIndexMap.SetSize( nMaxVertexCount ); for ( int i = 0; i < nMaxVertexCount; ++i ) { vertexIndexMap[ i ] = i; } for ( int i = nMaxVertexCount - 1; i >= 0; --i ) { if ( !pVertexFound[ i ] ) { vertexIndexMap.Remove( i ); } } // Build up the reverse map int *pReverseVertexIndexMap = reinterpret_cast< int * >( alloca( nMaxVertexCount * sizeof( int ) ) ); for ( int i = 0; i < nFaceSetCount; ++i ) { pReverseVertexIndexMap[ i ] = -1; } for ( int i = vertexIndexMap.Count() - 1; i >= 0; --i ) { pReverseVertexIndexMap[ vertexIndexMap[ i ] ] = i; } // Fix up the face set indices to compensate for the ones which are going to be removed for ( int i = 0; i < nFaceSetCount; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); const int nFaceSetIndices = pFaceSet->NumIndices(); for ( int j = 0; j < nFaceSetIndices; ++j ) { const int &nIndex = pFaceSet->GetIndex( j ); if ( nIndex >= 0 ) { Assert( pReverseVertexIndexMap[ nIndex ] >= 0 ); pFaceSet->SetIndex( j, pReverseVertexIndexMap[ nIndex ] ); } } } } //----------------------------------------------------------------------------- // Returns the highest vertex index used by the face sets of the mesh + 1 //----------------------------------------------------------------------------- int GetMaxVertexCount( const CDmeMesh *pMesh ) { int nMaxVertexIndex = 0; // Loop through all the face sets to find out the highest vertex index const int nFaceSetCount = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSetCount; ++i ) { const CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); const int nFaceSetIndices = pFaceSet->NumIndices(); for ( int j = 0; j < nFaceSetIndices; ++j ) { const int &nIndex = pFaceSet->GetIndex( j ); if ( nIndex > nMaxVertexIndex ) { nMaxVertexIndex = nIndex; } } } return nMaxVertexIndex + 1; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < class T_t > void RemapData( CDmrArray< T_t > data, const CUtlVector< int > &newToOldMap ) { const int nNewToOldMapCount = newToOldMap.Count(); T_t *pNewData = reinterpret_cast< T_t * >( alloca( nNewToOldMapCount * sizeof( T_t ) ) ); for ( int i = 0; i < nNewToOldMapCount; ++i ) { pNewData[ i ] = data.Get( newToOldMap[ i ] ); } data.RemoveMultiple( nNewToOldMapCount, data.Count() - nNewToOldMapCount ); data.SetMultiple( 0, nNewToOldMapCount, pNewData ); } //----------------------------------------------------------------------------- // Computes the map of new data indices to old data indices //----------------------------------------------------------------------------- void RemoveUnusedData( CDmeMesh *pMesh, CDmeVertexData *pVertexData, bool bBind, const char *pFieldName, int *pIndices, int nIndicesCount, CDmrGenericArray &data ) { const int nDataCount = data.Count(); bool *pDataIndexFound = reinterpret_cast< bool * >( alloca( nDataCount * sizeof( bool ) ) ); memset( pDataIndexFound, 0, nDataCount * sizeof( bool ) ); // Figure out which data is used for ( int i = 0; i < nIndicesCount; ++i ) { Assert( pIndices[ i ] >= 0 && pIndices[ i ] < nDataCount ); pDataIndexFound[ pIndices[ i ] ] = true; } int nMissingCount = 0; for ( int i = 0; i < nDataCount; ++i ) { if ( !pDataIndexFound[ i ] ) { ++nMissingCount; } } // Compute the New to Old data map CUtlVector< int > newToOldDataMap; newToOldDataMap.SetSize( nDataCount ); for ( int i = 0; i < nDataCount; ++i ) { newToOldDataMap[ i ] = i; } for ( int i = nDataCount - 1; i >= 0; --i ) { if ( !pDataIndexFound[ i ] ) { newToOldDataMap.Remove( i ); } } // Fix up the data CDmAttribute *pDataAttr = data.GetAttribute(); const DmAttributeType_t dataAttrType = pDataAttr->GetType(); switch ( dataAttrType ) { case AT_FLOAT_ARRAY: RemapData( CDmrArray< float >( pDataAttr ), newToOldDataMap ); break; case AT_VECTOR2_ARRAY: RemapData( CDmrArray< Vector2D >( pDataAttr ), newToOldDataMap ); break; case AT_VECTOR3_ARRAY: RemapData( CDmrArray< Vector >( pDataAttr ), newToOldDataMap ); break; case AT_VECTOR4_ARRAY: RemapData( CDmrArray< Vector4D >( pDataAttr ), newToOldDataMap ); break; case AT_QUATERNION_ARRAY: RemapData( CDmrArray< Quaternion >( pDataAttr ), newToOldDataMap ); break; case AT_COLOR_ARRAY: RemapData( CDmrArray< Color >( pDataAttr ), newToOldDataMap ); break; default: Assert( 0 ); break; } // Compute Old To New Data Map int *pOldToNewDataMap = reinterpret_cast< int * >( alloca( nDataCount * sizeof( int ) ) ); for ( int i = 0; i < nDataCount; ++i ) { pOldToNewDataMap[ i ] = -1; } for ( int i = newToOldDataMap.Count() - 1; i >= 0; --i ) { pOldToNewDataMap[ newToOldDataMap[ i ] ] = i; } // Fix up the indices for ( int i = 0; i < nIndicesCount; ++i ) { pIndices[ i ] = pOldToNewDataMap[ pIndices[ i ] ]; } // TODO: Fix up "jointWeight & "jointIndices" if this is "position" if ( !Q_strcmp( pFieldName, "position" ) ) { const int nFields = pVertexData->FieldCount(); for ( int i = 0; i < nFields; ++i ) { } } // If this is the bind state then fix up any delta states if ( !bBind ) return; // Fix up any Delta states const int nDeltaStateCount = pMesh->DeltaStateCount(); for ( int i = 0; i < nDeltaStateCount; ++i ) { CDmeVertexDeltaData *pDelta = pMesh->GetDeltaState( i ); const int nDeltaFieldCount = pDelta->FieldCount(); for ( int j = 0; j < nDeltaFieldCount; ++j ) { if ( !Q_strcmp( pFieldName, pDelta->FieldName( j ) ) ) { CDmrArray< int > deltaIndices = pDelta->GetIndexData( j ); CDmrGenericArray deltaData = pDelta->GetVertexData( j ); Assert( deltaIndices.Count() == deltaData.Count() ); for ( int k = deltaIndices.Count() - 1; k >= 0; --k ) { const int oldIndex = deltaIndices.Get( k ); const int &newIndex = pOldToNewDataMap[ oldIndex ]; if ( newIndex < 0 ) { deltaIndices.Remove( k ); deltaData.Remove( k ); } else if ( newIndex != oldIndex ) { deltaIndices.Set( k, newIndex ); } } } } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void RemoveUnusedVerticesFromBaseState( CDmeMesh *pMesh, CDmeVertexData *pVertexData, const CUtlVector< int > &newToOldIndexMap ) { const int nNewToOldIndexMapCount = newToOldIndexMap.Count(); int *pNewVertexIndices = reinterpret_cast< int * >( alloca( nNewToOldIndexMapCount * sizeof( int ) ) ); // See if this is the bind state for the mesh const bool bBind = !Q_strcmp( pVertexData->GetName(), "bind" ); const int nFieldCount = pVertexData->FieldCount(); for ( int i = 0; i < nFieldCount; ++i ) { const char *pFieldName = pVertexData->FieldName( i ); // TODO: Checking by name is lame... should be a lookup to map fieldIndex to a standard field index if ( !Q_strcmp( pFieldName, "jointWeights" ) || !Q_strcmp( pFieldName, "jointIndices" ) ) { // TODO: Handle when positions are Remapped continue; } CDmrArray< int > indices = pVertexData->GetIndexData( i ); // Create the new index array accounting for missing indices for ( int j = 0; j < nNewToOldIndexMapCount; ++j ) { Assert( newToOldIndexMap[ j ] < indices.Count() ); pNewVertexIndices[ j ] = indices.Get( newToOldIndexMap[ j ] ); } CDmrGenericArray data = pVertexData->GetVertexData( i ); // This will also update pNewVertexIndices RemoveUnusedData( pMesh, pVertexData, bBind, pFieldName, pNewVertexIndices, nNewToOldIndexMapCount, CDmrGenericArray( pVertexData->GetVertexData( i ) ) ); // Shrink the indices array indices.RemoveMultiple( nNewToOldIndexMapCount, indices.Count() - nNewToOldIndexMapCount ); // Set the new index values indices.SetMultiple( 0, nNewToOldIndexMapCount, pNewVertexIndices ); } // Update the vertex count pVertexData->Resolve(); } //----------------------------------------------------------------------------- // Removes unused data from the mesh // Unused means a 'vertex' that isn't referred to by any face // Once all unused vertices are removed, unused data is removed from each // bit of data // TODO: Also loop through each field of data, see which ones are no longer // being referred to and then purge the data as well // Would also have to purge delta data at the same time // Would also have to purge joints at the same time (for position) //----------------------------------------------------------------------------- bool CDmMeshUtils::PurgeUnusedData( CDmeMesh *pMesh ) { // Get the maximum vertex index of the mesh const int nMaxVertexCount = GetMaxVertexCount( pMesh ); // Now find any missing indices CUtlVector< int > vertexIndexMap; ComputeVertexIndexMap( pMesh, nMaxVertexCount, vertexIndexMap ); // Remove the redundant vertices from all base states for ( int i = pMesh->BaseStateCount() - 1; i >= 0; --i ) { RemoveUnusedVerticesFromBaseState( pMesh, pMesh->GetBaseState( i ), vertexIndexMap ); } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::Mirror( CDmeMesh *pMesh, int axis /*= kXAxis */ ) { CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); if ( !pBase ) return false; CUtlVector< int > mirrorMap; if ( !MirrorVertices( pMesh, pBase, axis, mirrorMap ) ) return false; int vertexIndex; int faceStart = 0; CUtlVector< int > newFaceIndices; const int nFaceSets = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSets; ++i ) { CDmeFaceSet *pSrcFaceSet = pMesh->GetFaceSet( i ); const int nFaceSetIndices = pSrcFaceSet->NumIndices(); if ( nFaceSetIndices <= 0 ) continue; CDmeFaceSet *pDstFaceSet = pSrcFaceSet; // See if a new face set needs to be created CDmeMaterial *pSrcMaterial = pSrcFaceSet->GetMaterial(); const char *pSrcMaterialName = pSrcMaterial->GetMaterialName(); const int nNameLen = Q_strlen( pSrcMaterialName ); if ( nNameLen >= 2 ) { CUtlString materialName; if ( !Q_stricmp( pSrcMaterialName + nNameLen - 2, "_l" ) ) { materialName = pSrcMaterialName; materialName.SetLength( nNameLen - 2 ); materialName += "_r"; } else if ( !Q_stricmp( pSrcMaterialName + nNameLen - 2, "_r" ) ) { materialName = pSrcMaterialName; materialName.SetLength( nNameLen - 2 ); materialName += "_l"; } else if ( nNameLen >= 5 && !Q_stricmp( pSrcMaterialName + nNameLen - 5, "_left" ) ) { materialName = pSrcMaterialName; materialName.SetLength( nNameLen - 5 ); materialName += "_right"; } else if ( nNameLen >= 6 && !Q_stricmp( pSrcMaterialName + nNameLen - 6, "_right" ) ) { materialName = pSrcMaterialName; materialName.SetLength( nNameLen - 6 ); materialName += "_left"; } if ( materialName.Length() ) { pDstFaceSet = CreateElement< CDmeFaceSet >( materialName, pMesh->GetFileId() ); CDmeMaterial *pDstMaterial = CreateElement< CDmeMaterial >( materialName, pDstFaceSet->GetFileId() ); pDstMaterial->SetMaterial( materialName ); pDstFaceSet->SetMaterial( pDstMaterial ); pMesh->AddFaceSet( pDstFaceSet ); } } faceStart = 0; for ( int j = 0; j < nFaceSetIndices; ++j ) { vertexIndex = pSrcFaceSet->GetIndex( j ); if ( vertexIndex < 0 ) { newFaceIndices.RemoveAll(); for ( int k = j - 1; k >= faceStart; --k ) { newFaceIndices.AddToTail( mirrorMap[ pSrcFaceSet->GetIndex( k ) ] ); } newFaceIndices.AddToTail( -1 ); const int oldNumIndices = pDstFaceSet->NumIndices(); pDstFaceSet->AddIndices( newFaceIndices.Count() ); pDstFaceSet->SetIndices( oldNumIndices, newFaceIndices.Count(), newFaceIndices.Base() ); // End of face faceStart = j + 1; continue; } } } return true; } //----------------------------------------------------------------------------- // Initializes the CUtlVector to a linear ramp where utlVector[ i ] == i //----------------------------------------------------------------------------- template < typename T_t > void RampInit( CUtlVector< T_t > &utlVector, const int nCount ) { utlVector.SetCount( nCount ); for ( int i = 0; i < nCount; ++i ) { utlVector[ i ] = i; } } //----------------------------------------------------------------------------- // Build Data Mirror Map // Returns a pointer to the memory holding the indices for the map or NULL //----------------------------------------------------------------------------- const int *CDmMeshUtils::BuildDataMirrorMap( CDmeVertexData *pBase, int axis, CDmeVertexData::StandardFields_t standardField, CUtlVector< int > &dataMirrorMap ) { const FieldIndex_t fieldIndex = pBase->FindFieldIndex( standardField ); if ( fieldIndex < 0 ) return NULL; const CUtlVector< int > &indices( CDmrArrayConst< int >( pBase->GetIndexData( fieldIndex ) ).Get() ); CDmAttribute *pData = pBase->GetVertexData( fieldIndex ); if ( standardField == CDmeVertexData::FIELD_POSITION || standardField == CDmeVertexData::FIELD_NORMAL ) { const Vector mirrorOrigin( 0.0f, 0.0f, 0.0f ); const float mirrorAxisVal = mirrorOrigin[ axis ]; CDmrArray< Vector > data( pBase->GetVertexData( fieldIndex ) ); Vector v; const int nDataCount = data.Count(); dataMirrorMap.SetCount( nDataCount ); int nMirrorDataCount = nDataCount; for ( int i = 0; i < nDataCount; ++i ) { if ( fabs( data[ i ][ axis ] - mirrorAxisVal ) > FLT_EPSILON * 1000.0f ) { dataMirrorMap[ i ] = nMirrorDataCount++; } else { dataMirrorMap[ i ] = i; v = data[ i ]; v[ axis ] = mirrorOrigin[ axis ]; data.Set( i, v ); } } } else if ( standardField == CDmeVertexData::FIELD_TEXCOORD ) { const Vector2D mirrorOrigin( 0.5f, 0.5f ); const float mirrorAxisVal = mirrorOrigin[ axis % 2 ]; const CUtlVector< Vector2D > &data( CDmrArrayConst< Vector2D >( pBase->GetVertexData( fieldIndex ) ).Get() ); const int nDataCount = data.Count(); dataMirrorMap.SetCount( nDataCount ); int nMirrorDataCount = nDataCount; for ( int i = 0; i < nDataCount; ++i ) { if ( fabs( data[ i ][ axis ] - mirrorAxisVal ) > FLT_EPSILON * 1000.0f ) { dataMirrorMap[ i ] = nMirrorDataCount++; } else { dataMirrorMap[ i ] = i; } } } else { RampInit( dataMirrorMap, CDmrGenericArrayConst( pData ).Count() ); } return indices.Base(); } //----------------------------------------------------------------------------- // y = mirrorMap[ x ] means that if y < 0 then original position x is not // mirrored. Otherwise y is the index into the vertex indices of the mirrored // version of vertex //----------------------------------------------------------------------------- bool CDmMeshUtils::MirrorVertices( CDmeMesh *pMesh, CDmeVertexData *pBase, int axis, CUtlVector< int > &mirrorMap ) { mirrorMap.RemoveAll(); if ( !pMesh || !pBase || axis < kXAxis || axis > kZAxis ) return false; const int posIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); if ( posIndex < 0 ) return false; const CUtlVector< int > &posIndices( CDmrArrayConst< int >( pBase->GetIndexData( posIndex ) ).Get() ); const int nIndices = posIndices.Count(); Assert( nIndices == pBase->VertexCount() ); CUtlVector< int > posMirrorMap; if ( !BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_POSITION, posMirrorMap ) ) return false; CUtlVector< int > normalMirrorMap; const int *pNormalIndices = BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_NORMAL, normalMirrorMap ); CUtlVector< int > uvMirrorMap; const int *pUVIndices = BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_TEXCOORD, uvMirrorMap ); RampInit( mirrorMap, nIndices ); int mirrorCount = 0; { bool mirror; Vector tmpVec; Vector2D tmpVec2D; for ( int i = 0; i < nIndices; ++i ) { mirror = false; if ( posMirrorMap[ posIndices[ i ] ] != posIndices[ i ] ) { mirror = true; } if ( pNormalIndices && normalMirrorMap[ pNormalIndices[ i ] ] != pNormalIndices[ i ] ) { mirror = true; } if ( pUVIndices && uvMirrorMap[ pUVIndices[ i ] ] != pUVIndices[ i ] ) { mirror = true; } if ( mirror ) { mirrorMap[ i ] = nIndices + mirrorCount; ++mirrorCount; } } } const int nBaseState = pMesh->BaseStateCount(); for ( int i = 0; i < nBaseState; ++i ) { CDmeVertexData *pBase = pMesh->GetBaseState( i ); const int nVertexCount = pBase->VertexCount(); MirrorVertices( pBase, axis, nVertexCount, mirrorCount, mirrorMap, posMirrorMap, normalMirrorMap, uvMirrorMap ); } const int nDeltaState = pMesh->DeltaStateCount(); for ( int i = 0; i < nDeltaState; ++i ) { CDmeVertexDeltaData *pDelta = pMesh->GetDeltaState( i ); MirrorDelta( pDelta, axis, posMirrorMap, normalMirrorMap, uvMirrorMap ); } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline void MirrorData( Vector &d, const int &axis ) { d[ axis ] *= -1.0f; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline void MirrorData( Vector2D &d, const int &axis ) { d[ axis ] = ( d[ axis ] - 0.5f ) * -1.0f + 0.5f; } //----------------------------------------------------------------------------- // Mirror 3D things like positions & normals //----------------------------------------------------------------------------- template < class T_t > void MirrorVertexData( CDmeVertexData *pBase, FieldIndex_t fieldIndex, int axis, int nOrigVertexCount, int nMirrorCount, const CDmrArrayConst< T_t > &origData, const CUtlVector< int > &origIndices, const CUtlVector< int > &mirrorMap, const CUtlVector< int > &dataMirrorMap ) { if ( nMirrorCount <= 0 ) return; Assert( origIndices.Count() == nOrigVertexCount + nMirrorCount ); Assert( mirrorMap.Count() == nOrigVertexCount ); Assert( dataMirrorMap.Count() == origData.Count() ); const int nData = origData.Count(); T_t *pMirrorData = reinterpret_cast< T_t * >( alloca( nMirrorCount * sizeof( T_t ) ) ); int *pMirrorIndices = reinterpret_cast< int * >( alloca( nMirrorCount * sizeof( int ) ) ); T_t mirrorData; int nMirrorIndex = 0; int nMirrorDataCount = -1; for ( int i = 0; i < nOrigVertexCount; ++i ) { if ( mirrorMap[ i ] != i ) { // Vertex must be mirrored if ( dataMirrorMap[ origIndices[ i ] ] != origIndices[ i ] ) { // Data referred to by vertex i must be mirror (this may be done a redundant number of times) const T_t &origData( origData[ origIndices[ i ] ] ); mirrorData = origData; MirrorData( mirrorData, axis ); pMirrorData[ dataMirrorMap[ origIndices[ i ] ] - nData ] = mirrorData; if ( ( dataMirrorMap[ origIndices[ i ] ] - nData ) > nMirrorDataCount ) { nMirrorDataCount = dataMirrorMap[ origIndices[ i ] ] - nData; } pMirrorIndices[ nMirrorIndex ] = dataMirrorMap[ origIndices[ i ] ]; } else { // The data does not need to be mirrored pMirrorIndices[ nMirrorIndex ] = origIndices[ i ]; } ++nMirrorIndex; } else { Assert( dataMirrorMap[ origIndices[ i ] ] == origIndices[ i ] ); } } ++nMirrorDataCount; Assert( nMirrorCount == nMirrorIndex ); Assert( nMirrorDataCount <= nMirrorCount ); const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( origData.GetAttribute()->GetType() ); pBase->AddVertexData( fieldIndex, nMirrorDataCount ); pBase->SetVertexData( fieldIndex, nData, nMirrorDataCount, dmAttributeType, pMirrorData ); pBase->SetVertexIndices( fieldIndex, nOrigVertexCount, nMirrorCount, pMirrorIndices ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::MirrorVertices( CDmeVertexData *pBase, int axis, int nOldVertexCount, int nMirrorCount, const CUtlVector< int > &mirrorMap, const CUtlVector< int > &posMirrorMap, const CUtlVector< int > &normalMirrorMap, const CUtlVector< int > &uvMirrorMap ) { if ( !pBase || axis < kXAxis || axis > kZAxis ) return false; pBase->AddVertexIndices( nMirrorCount ); const int posFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const int normalFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); const int uvFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); const int nFields = pBase->FieldCount(); for ( int i = 0; i < nFields; ++i ) { CDmAttribute *pBaseData( pBase->GetVertexData( i ) ); const CUtlVector< int > &baseIndices( pBase->GetVertexIndexData( i ) ); Assert( baseIndices.Count() == nOldVertexCount + nMirrorCount ); Assert( mirrorMap.Count() == nOldVertexCount ); switch ( pBaseData->GetType() ) { case AT_VECTOR2_ARRAY: if ( i == uvFieldIndex ) { MirrorVertexData( pBase, i, axis % 2, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector2D >( pBaseData ), baseIndices, mirrorMap, uvMirrorMap ); continue; } break; case AT_VECTOR3_ARRAY: if ( i == posFieldIndex ) { MirrorVertexData( pBase, i, axis, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector >( pBaseData ), baseIndices, mirrorMap, posMirrorMap ); continue; } else if ( i == normalFieldIndex ) { MirrorVertexData( pBase, i, axis, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector >( pBaseData ), baseIndices, mirrorMap, normalMirrorMap ); continue; } break; default: break; } MirrorVertices( pBase, i, nOldVertexCount, nMirrorCount, baseIndices, mirrorMap ); } return true; } //----------------------------------------------------------------------------- // This does the default case of mirroring which is no mirroring at all! // No data is changed, the extra indices are added to the index //----------------------------------------------------------------------------- void CDmMeshUtils::MirrorVertices( CDmeVertexData *pBase, FieldIndex_t fieldIndex, int nOldVertexCount, int nMirrorCount, const CUtlVector< int > &baseIndices, const CUtlVector< int > &mirrorMap ) { if ( nMirrorCount <= 0 ) return; Assert( baseIndices.Count() == nOldVertexCount + nMirrorCount ); Assert( mirrorMap.Count() == nOldVertexCount ); int *pIndices = reinterpret_cast< int * >( alloca( nMirrorCount * sizeof( int ) ) ); { int pIndex = 0; for ( int i = 0; i < nOldVertexCount; ++i ) { if ( mirrorMap[ i ] != i ) { pIndices[ pIndex ] = baseIndices[ mirrorMap[ i ] - nOldVertexCount ]; ++pIndex; } } Assert( pIndex == nMirrorCount ); } pBase->SetVertexIndices( fieldIndex, nOldVertexCount, nMirrorCount, pIndices ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < class T_t > void MirrorDeltaData( CDmeVertexDeltaData *pDelta, FieldIndex_t fieldIndex, int axis, const CDmrArrayConst< T_t > &origData, const CUtlVector< int > &origIndices, const CUtlVector< int > &dataMap ) { Assert( origData.Count() == origIndices.Count() ); const int nOrigDataCount = origData.Count(); T_t *pMirrorData = reinterpret_cast< T_t * >( alloca( nOrigDataCount * sizeof( T_t ) ) ); int *pMirrorIndices = reinterpret_cast< int * >( alloca( nOrigDataCount * sizeof( int ) ) ); int nMirrorDataCount = 0; for ( int i = 0; i < nOrigDataCount; ++i ) { if ( dataMap[ origIndices[ i ] ] != origIndices[ i ] ) { pMirrorData[ nMirrorDataCount ] = origData[ i ]; MirrorData( pMirrorData[ nMirrorDataCount ], axis ); pMirrorIndices[ nMirrorDataCount ] = dataMap[ origIndices[ i ] ]; ++nMirrorDataCount; } } const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( origData.GetAttribute()->GetType() ); pDelta->AddVertexData( fieldIndex, nMirrorDataCount ); pDelta->SetVertexData( fieldIndex, nOrigDataCount, nMirrorDataCount, dmAttributeType, pMirrorData ); pDelta->SetVertexIndices( fieldIndex, nOrigDataCount, nMirrorDataCount, pMirrorIndices ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::MirrorDelta( CDmeVertexDeltaData *pDelta, int axis, const CUtlVector< int > &posMirrorMap, const CUtlVector< int > &normalMirrorMap, const CUtlVector< int > &uvMirrorMap ) { if ( !pDelta || axis < kXAxis || axis > kZAxis ) return false; const int posFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const int normalFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); const int uvFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); const int nFields = pDelta->FieldCount(); for ( int i = 0; i < nFields; ++i ) { CDmAttribute *pDeltaData( pDelta->GetVertexData( i ) ); const CUtlVector< int > &deltaIndices( pDelta->GetVertexIndexData( i ) ); switch ( pDeltaData->GetType() ) { case AT_VECTOR2_ARRAY: if ( i == uvFieldIndex ) { MirrorDeltaData( pDelta, i, axis % 2, CDmrArrayConst< Vector2D >( pDeltaData ), deltaIndices, uvMirrorMap ); continue; } break; case AT_VECTOR3_ARRAY: if ( i == posFieldIndex ) { MirrorDeltaData( pDelta, i, axis, CDmrArrayConst< Vector >( pDeltaData ), deltaIndices, posMirrorMap ); continue; } else if ( i == normalFieldIndex ) { MirrorDeltaData( pDelta, i, axis, CDmrArrayConst< Vector >( pDeltaData ), deltaIndices, normalMirrorMap ); continue; } break; default: break; } } return true; } //----------------------------------------------------------------------------- // Finds all materials bound to the mesh and replaces ones which match the // source name with the destination name //----------------------------------------------------------------------------- bool CDmMeshUtils::RemapMaterial( CDmeMesh *pMesh, const CUtlString &src, const CUtlString &dst ) { bool retVal = false; char srcName[ MAX_PATH ]; char matName[ MAX_PATH ]; char dstName[ MAX_PATH ]; Q_StripExtension( src.Get(), srcName, sizeof( srcName ) ); Q_FixSlashes( srcName, '/' ); Q_strncpy( dstName, dst.Get(), sizeof( dstName ) ); Q_FixSlashes( dstName, '/' ); const int nFaceSets = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSets; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); if ( !pFaceSet ) continue; CDmeMaterial *pMaterial = pFaceSet->GetMaterial(); if ( !pMaterial ) continue; const char *pMaterialName = pMaterial->GetMaterialName(); Q_StripExtension( pMaterialName, matName, sizeof( matName ) ); Q_FixSlashes( matName, '/' ); // TODO: Regular expressions or at least glob style matching would be cool if ( !Q_stricmp( srcName, matName ) ) { pMaterial->SetMaterial( dstName ); pMaterial->SetName( dstName ); retVal = true; } } return retVal; } //----------------------------------------------------------------------------- // Replaces the nth material found with the specified material name //----------------------------------------------------------------------------- bool CDmMeshUtils::RemapMaterial( CDmeMesh *pMesh, const int nMaterialIndex, const CUtlString &dst ) { const int nFaceSets = pMesh->FaceSetCount(); if ( nMaterialIndex >= nFaceSets ) return false; CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( nMaterialIndex ); if ( !pFaceSet ) return false; CDmeMaterial *pMaterial = pFaceSet->GetMaterial(); if ( !pMaterial ) return false; pMaterial->SetMaterial( dst ); pMaterial->SetName( dst ); return true; } //----------------------------------------------------------------------------- // Finds the "socket" on which to base the mesh merge // This is defined as the vertices along the two meshes // Returns the index into srcBorderEdgesList of the edge list that is found // -1 if not found //----------------------------------------------------------------------------- int CDmMeshUtils::FindMergeSocket( const CUtlVector< CUtlVector< CDmMeshComp::CEdge * > > &srcBorderEdgesList, CDmeMesh *pDstMesh ) { CDmMeshComp dstComp( pDstMesh ); const CUtlVector< CDmMeshComp::CEdge * > &edgeList = dstComp.m_edges; for ( int i = srcBorderEdgesList.Count() - 1; i >= 0; --i ) { const CUtlVector< CDmMeshComp::CEdge * > &srcBorderEdges = srcBorderEdgesList[ i ]; int nEdgeMatch = 0; for ( int j = 0; j != edgeList.Count(); j++ ) { const CDmMeshComp::CEdge &e = *edgeList[ j ]; for ( int k = srcBorderEdges.Count() - 1; k >= 0; --k ) { if ( e == *srcBorderEdges[ k ] ) { ++nEdgeMatch; break; } } } if ( nEdgeMatch == srcBorderEdges.Count() ) { return i; } } return -1; } //----------------------------------------------------------------------------- // Merge by finding the two meshes in the scene which are joined at a socket // A socket being defined as a group of border edges that match exactly // between two meshes //----------------------------------------------------------------------------- bool CDmMeshUtils::Merge( CDmeMesh *pSrcMesh, CDmElement *pRoot ) { CDmMeshComp srcComp( pSrcMesh ); CUtlVector< CUtlVector< CDmMeshComp::CEdge * > > srcBorderEdgesList; if ( srcComp.GetBorderEdges( srcBorderEdgesList ) == 0 ) return false; CDmeMesh *pDstMesh = NULL; // Find each mesh under pRoot CDmeDag *pModel = pRoot->GetValueElement< CDmeDag >( "model" ); if ( !pModel ) return false; CUtlStack< CDmeDag * > traverseStack; traverseStack.Push( pModel ); CDmeDag *pDag; CDmeMesh *pMesh; Vector srcCenter; float srcRadius; Vector dstCenter; float dstRadius; float sqDist = FLT_MAX; pSrcMesh->GetBoundingSphere( srcCenter, srcRadius ); int nEdgeListIndex = -1; while ( traverseStack.Count() ) { traverseStack.Pop( pDag ); if ( !pDag ) continue; // Push all children onto stack in reverse order for ( int nChildIndex = pDag->GetChildCount() - 1; nChildIndex >= 0; --nChildIndex ) { traverseStack.Push( pDag->GetChild( nChildIndex ) ); } // See if there's a mesh associated with this dag pMesh = CastElement< CDmeMesh >( pDag->GetShape() ); if ( !pMesh ) continue; int eli = FindMergeSocket( srcBorderEdgesList, pMesh ); if ( eli < 0 ) continue; pMesh->GetBoundingSphere( dstCenter, dstRadius ); dstRadius = dstCenter.DistToSqr( srcCenter ); if ( dstRadius < sqDist ) { sqDist = dstRadius; pDstMesh = pMesh; nEdgeListIndex = eli; } } if ( pDstMesh ) { return Merge( srcComp, srcBorderEdgesList[ nEdgeListIndex ], pDstMesh ); } Msg( "Error: Merge() - No Merge Socket Found - i.e. A Set Of Border Edges On The Source Model That Are Found On The Merge Model" ); return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < class T_t > void AppendData( const CDmrArrayConst< T_t > &srcData, CDmrArray< T_t > &dstData, const matrix3x4_t *pMat = NULL ) { const int nSrcCount = srcData.Count(); const int nDstCount = dstData.Count(); dstData.AddMultipleToTail( nSrcCount ); dstData.SetMultiple( nDstCount, nSrcCount, srcData.Base() ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void AppendData( const CDmrArrayConst< Vector > &srcData, CDmrArray< Vector > &dstData, const matrix3x4_t *pMat ) { const int nSrcCount = srcData.Count(); const int nDstCount = dstData.Count(); dstData.AddMultipleToTail( nSrcCount ); if ( pMat ) { Vector v; for ( int i = 0; i < nSrcCount; ++i ) { v = srcData.Get( i ); VectorTransform( srcData.Get( i ), *pMat, v ); dstData.Set( nDstCount + i, v ); } } else { dstData.SetMultiple( nDstCount, nSrcCount, srcData.Base() ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static void MergeJointData( CDmeVertexData *pDstBase, CDmeVertexData *pSrcBase, const CUtlMap< int, int > &jointMap ) { if ( !pSrcBase || !pDstBase ) return; const int nSrcPositionIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const int nSrcJointWeightsIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); const int nSrcJointIndicesIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); const int nDstJointWeightsIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); const int nDstJointIndicesIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); if ( nSrcPositionIndex < 0 || nSrcJointWeightsIndex < 0 || nSrcJointIndicesIndex < 0 || nDstJointWeightsIndex < 0 || nDstJointIndicesIndex < 0 ) return; const int nSrcJointCount = pSrcBase->JointCount(); const int nDstJointCount = pDstBase->JointCount(); if ( nSrcJointCount <= 0 || nDstJointCount <= 0 ) return; const int nSrcPosCount = CDmrGenericArray( pSrcBase->GetVertexData( nSrcPositionIndex ) ).Count(); const CDmrArrayConst< float > srcWeights( pSrcBase->GetVertexData( nSrcJointWeightsIndex ) ); const CDmrArrayConst< int > srcIndices( pSrcBase->GetVertexData( nSrcJointIndicesIndex ) ); CDmrArray< float > dstWeights( pDstBase->GetVertexData( nDstJointWeightsIndex ) ); CDmrArray< int > dstIndices( pDstBase->GetVertexData( nDstJointIndicesIndex ) ); int nDstIndex = dstWeights.Count(); int nSrcIndex = 0; dstWeights.AddMultipleToTail( nSrcPosCount * nDstJointCount ); dstIndices.AddMultipleToTail( nSrcPosCount * nDstJointCount ); while ( nDstIndex < dstWeights.Count() ) { float flTotalWeight = 0.0f; for ( int i = 0; i < nDstJointCount; ++i ) { if ( i < nSrcJointCount ) { Assert( nSrcIndex < srcWeights.Count() ); const CUtlMap< int, int >::IndexType_t nJointMapIndex = jointMap.Find( srcIndices[ nSrcIndex ] ); if ( jointMap.IsValidIndex( nJointMapIndex ) ) { dstWeights.Set( nDstIndex, srcWeights[ nSrcIndex ] ); dstIndices.Set( nDstIndex, jointMap.Element( nJointMapIndex ) ); flTotalWeight += dstWeights[ nDstIndex ]; } else { Warning( "Can't Joint Index %d On Src Isn't Mapped To Dst\n", srcIndices[ nSrcIndex ] ); dstWeights.Set( nDstIndex, 0.0f ); dstIndices.Set( nDstIndex, -1 ); } ++nSrcIndex; } else { dstWeights.Set( nDstIndex, 0.0f ); dstIndices.Set( nDstIndex, -1 ); } ++nDstIndex; } // Renormalize if weight didn't equal 1 if ( flTotalWeight > 0.0f && FloatMakePositive( flTotalWeight - 1.0f ) > FLT_EPSILON * 100.0 ) { for ( int i = nDstIndex - nDstJointCount; i < nDstIndex; ++i ) { dstWeights.Set( i, dstWeights.Element( i ) / flTotalWeight ); } } } } //----------------------------------------------------------------------------- // Merge data from a base state on one DmeMesh into another DmeMesh // Preserve positions and normals by transforming them with the // positionMatrix & normalMatrix // // Return the number of new vertices in the mesh //----------------------------------------------------------------------------- int MergeBaseState( CDmeVertexData *pSrcBase, CDmeVertexData *pDstBase, const matrix3x4_t &pMat, const matrix3x4_t &nMat, int nSkinningJointIndex, int &nPositionOffset, int &nNormalOffset, int &nWrinkleOffset, CUtlMap< int, int > *pSkinMap /* = NULL */ ) { int nRetVal = -1; const int nSrcPositionIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const int nSrcNormalIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); const int nSrcWrinkleIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_WRINKLE ); const int nSrcJointWeightsIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); const int nSrcJointIndicesIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); const int nDstJointWeightsIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); const int nDstJointIndicesIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); // Handle skinning the new mesh data to a single joint if the destination mesh // is already skinned. If the destination mesh is skinned but there is no // specific joint specified to skin to, the first joint is used and a warning issued if ( nDstJointWeightsIndex >= 0 && nDstJointIndicesIndex >= 0 ) { if ( pSkinMap && nSrcJointWeightsIndex >= 0 && nSrcJointIndicesIndex >= 0 ) { MergeJointData( pDstBase, pSrcBase, *pSkinMap ); } else { if ( nSkinningJointIndex < 0 ) { Msg( "Warning: Destination mesh is skinned but no valid joint specified to skin to, using first joint\n" ); nSkinningJointIndex = 0; } const int nJointCount = pDstBase->JointCount(); CDmrGenericArray srcPos( pSrcBase->GetVertexData( nSrcPositionIndex ) ); const int nSrcPosCount = srcPos.Count(); CDmrArray< float > dstWeights( pDstBase->GetVertexData( nDstJointWeightsIndex ) ); CDmrArray< int > dstIndices( pDstBase->GetVertexData( nDstJointIndicesIndex ) ); const int nDstCount = dstWeights.Count(); Assert( nDstCount == dstIndices.Count() ); dstWeights.AddMultipleToTail( nSrcPosCount * nJointCount ); dstIndices.AddMultipleToTail( nSrcPosCount * nJointCount ); // Since there can be more than 1 joint per vertex, specify 1 // for the first joint and 0 for the rest but use the same joint const int nEnd = nDstCount + nSrcPosCount * nJointCount; for ( int i = nDstCount; i < nEnd; i += nJointCount ) { dstWeights.Set( i, 1.0f ); dstIndices.Set( i, nSkinningJointIndex ); } for ( int i = 1; i < nJointCount; ++i ) { for ( int j = nDstCount + i; j < nEnd; j += nJointCount ) { dstWeights.Set( j, 0.0f ); dstIndices.Set( j, nSkinningJointIndex ); } } } } // Handling merging all fields that match int nIndexPadCount = -1; for ( int i = 0; i < pSrcBase->FieldCount(); ++i ) { bool bMerged = false; for ( int j = 0; j < pDstBase->FieldCount(); ++j ) { if ( V_strcmp( pSrcBase->FieldName( i ), pDstBase->FieldName( j ) ) ) continue; bMerged = true; if ( i == nSrcJointWeightsIndex || i == nSrcJointIndicesIndex ) continue; CDmAttribute *pSrcData = pSrcBase->GetVertexData( i ); CDmAttribute *pDstData = pDstBase->GetVertexData( j ); const int nOffset = CDmrGenericArray( pDstData ).Count(); switch ( pSrcData->GetType() ) { case AT_FLOAT_ARRAY: AppendData( CDmrArrayConst< float >( pSrcData ), CDmrArray< float >( pDstData ) ); break; case AT_VECTOR2_ARRAY: AppendData( CDmrArrayConst< Vector2D >( pSrcData ), CDmrArray< Vector2D >( pDstData ) ); break; case AT_VECTOR3_ARRAY: if ( i == nSrcPositionIndex ) { AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ), &pMat ); } else if ( i == nSrcNormalIndex ) { AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ), &nMat ); } else { AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ) ); } break; case AT_VECTOR4_ARRAY: AppendData( CDmrArrayConst< Vector4D >( pSrcData ), CDmrArray< Vector4D >( pDstData ) ); break; case AT_QUATERNION_ARRAY: AppendData( CDmrArrayConst< Quaternion >( pSrcData ), CDmrArray< Quaternion >( pDstData ) ); break; case AT_COLOR_ARRAY: AppendData( CDmrArrayConst< Color >( pSrcData ), CDmrArray< Color >( pDstData ) ); break; default: Assert( 0 ); break; } CDmrArray< int > srcIndices( pSrcBase->GetIndexData( i ) ); CDmrArray< int > dstIndices( pDstBase->GetIndexData( j ) ); const int nSrcIndexCount = srcIndices.Count(); const int nDstIndexCount = dstIndices.Count(); if ( nRetVal < 0 ) { nRetVal = nDstIndexCount; } Assert( nRetVal == nDstIndexCount ); dstIndices.AddMultipleToTail( nSrcIndexCount ); if ( nIndexPadCount < 0 ) { nIndexPadCount = nSrcIndexCount; } Assert( nIndexPadCount == nSrcIndexCount ); for ( int k = 0; k < nSrcIndexCount; ++k ) { dstIndices.Set( nDstIndexCount + k, srcIndices.Get( k ) + nOffset ); } if ( i == nSrcPositionIndex ) { nPositionOffset = nOffset; } else if ( i == nSrcNormalIndex ) { nNormalOffset = nOffset; } else if ( i == nSrcWrinkleIndex ) { nWrinkleOffset = nOffset; } } if ( !bMerged ) { Msg( "Warning: Not merging base data %s\n", pSrcBase->FieldName( i ) ); } } const int nDstSpeedIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ); // Handle all fields on the destination mesh that weren't on the source mesh for ( int i = 0; i < pDstBase->FieldCount(); ++i ) { bool bFound = false; if ( i == nDstJointWeightsIndex || i == nDstJointIndicesIndex ) continue; for ( int j = 0; j < pSrcBase->FieldCount(); ++j ) { if ( Q_strcmp( pDstBase->FieldName( i ), pSrcBase->FieldName( j ) ) ) continue; bFound = true; break; } if ( !bFound ) { int nDstIndex = -1; if ( i == nDstSpeedIndex ) { // Pad data with a 1 nDstIndex = CDmrArray< float >( pDstBase->GetVertexData( i ) ).AddToTail( 1.0f ); } else { // Pad data with a 0 nDstIndex = CDmrGenericArray( pDstBase->GetVertexData( i ) ).AddToTail(); } // Pad data indices with index to that extra data value CDmrArray< int > dstIndices( pDstBase->GetIndexData( i ) ); const int nStart = dstIndices.Count(); const int nEnd = dstIndices.Count() + nIndexPadCount; dstIndices.AddMultipleToTail( nIndexPadCount ); for ( int k = nStart; k < nEnd; ++k ) { dstIndices.Set( k, nDstIndex ); } } } pDstBase->Resolve(); return nRetVal; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void MergeDeltaState( CDmeMesh *pDmeMesh, CDmeVertexDeltaData *pSrcDelta, CDmeVertexDeltaData *pDstDelta, int &nPositionOffset, int &nNormalOffset, int &nWrinkleOffset ) { if ( !pDstDelta ) { // No destination delta... copy it pDstDelta = pDmeMesh->FindOrCreateDeltaState( pSrcDelta->GetName() ); if ( !pDstDelta ) return; } for ( int i = 0; i < pSrcDelta->FieldCount(); ++i ) { bool bFound = false; for ( int j = 0; j < pDstDelta->FieldCount(); ++j ) { if ( Q_strcmp( pSrcDelta->FieldName( i ), pDstDelta->FieldName( j ) ) ) continue; bFound = true; break; } if ( !bFound ) { // Make an empty one, data will be added below CDmAttribute *pSrcData = pSrcDelta->GetVertexData( i ); pDstDelta->CreateField( pSrcDelta->FieldName( i ), pSrcData->GetType() ); } } const int nSrcPositionIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const int nSrcNormalIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); const int nSrcWrinkleIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_WRINKLE ); for ( int i = 0; i < pSrcDelta->FieldCount(); ++i ) { int nOffset = 0; if ( i == nSrcPositionIndex ) { nOffset = nPositionOffset; } else if ( i == nSrcNormalIndex ) { nOffset = nNormalOffset; } else if ( i == nSrcWrinkleIndex ) { nOffset = nWrinkleOffset; } if ( nOffset < 0 ) { nOffset = 0; } for ( int j = 0; j < pDstDelta->FieldCount(); ++j ) { if ( Q_strcmp( pSrcDelta->FieldName( i ), pDstDelta->FieldName( j ) ) ) continue; CDmAttribute *pSrcData = pSrcDelta->GetVertexData( i ); CDmAttribute *pDstData = pDstDelta->GetVertexData( j ); switch ( pSrcData->GetType() ) { case AT_FLOAT_ARRAY: AppendData( CDmrArrayConst< float >( pSrcData ), CDmrArray< float >( pDstData ) ); break; case AT_VECTOR2_ARRAY: AppendData( CDmrArrayConst< Vector2D >( pSrcData ), CDmrArray< Vector2D >( pDstData ) ); break; case AT_VECTOR3_ARRAY: AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ) ); break; case AT_VECTOR4_ARRAY: AppendData( CDmrArrayConst< Vector4D >( pSrcData ), CDmrArray< Vector4D >( pDstData ) ); break; case AT_QUATERNION_ARRAY: AppendData( CDmrArrayConst< Quaternion >( pSrcData ), CDmrArray< Quaternion >( pDstData ) ); break; case AT_COLOR_ARRAY: AppendData( CDmrArrayConst< Color >( pSrcData ), CDmrArray< Color >( pDstData ) ); break; default: Assert( 0 ); break; } CDmrArray< int > srcIndices( pSrcDelta->GetIndexData( i ) ); CDmrArray< int > dstIndices( pDstDelta->GetIndexData( j ) ); const int nSrcIndexCount = srcIndices.Count(); const int nDstIndexCount = dstIndices.Count(); dstIndices.AddMultipleToTail( nSrcIndexCount ); for ( int k = 0; k < nSrcIndexCount; ++k ) { dstIndices.Set( nDstIndexCount + k, srcIndices.Get( k ) + nOffset ); } break; } } // TODO: Centralize all of the '_' for corrector business... const char *pszDeltaName = pDstDelta->GetName(); if ( strchr( pszDeltaName, '_' ) ) return; // No controls for deltas with '_''s if ( !pDmeMesh ) return; CDmeCombinationOperator *pDmeCombo = FindReferringElement< CDmeCombinationOperator >( pDmeMesh, "targets" ); if ( !pDmeCombo ) return; if ( pDmeCombo->HasRawControl( pszDeltaName ) ) return; pDmeCombo->FindOrCreateControl( pDstDelta->GetName(), false, true ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::Merge( CDmeMesh *pSrcMesh, CDmeMesh *pDstMesh, int nSkinningJointIndex, CUtlMap< int, int > *pJointMap /* = NULL */ ) { if ( !pSrcMesh || !pDstMesh ) return false; CDmeDag *pSrcDag = FindReferringElement< CDmeDag >( pSrcMesh, "shape", true ); CDmeDag *pDstDag = FindReferringElement< CDmeDag >( pDstMesh, "shape", true ); if ( !pSrcDag || !pDstDag ) return false; matrix3x4_t nMat; pSrcDag->GetAbsTransform( nMat ); matrix3x4_t pMat; pDstDag->GetAbsTransform( pMat ); matrix3x4_t dMatInv; MatrixInvert( pMat, dMatInv ); MatrixMultiply( dMatInv, nMat, pMat ); MatrixInverseTranspose( pMat, nMat ); int nPositionOffset = -1; int nNormalOffset = -1; int nWrinkleOffset = -1; int nVertexOffset = -1; for ( int i = 0; i < pSrcMesh->BaseStateCount(); ++i ) { CDmeVertexData *pSrcBase = pSrcMesh->GetBaseState( i ); bool bMerged = false; for ( int j = 0; j < pDstMesh->BaseStateCount(); ++j ) { CDmeVertexData *pDstBase = pDstMesh->GetBaseState( j ); if ( Q_strcmp( pSrcBase->GetName(), pDstBase->GetName() ) ) continue; bMerged = true; const int nTmpVertexOffset = MergeBaseState( pSrcBase, pDstBase, pMat, nMat, nSkinningJointIndex, nPositionOffset, nNormalOffset, nWrinkleOffset, pJointMap ); if ( nVertexOffset < 0 ) { nVertexOffset = nTmpVertexOffset; } Assert( nVertexOffset == nTmpVertexOffset ); } if ( !bMerged ) { Msg( "Error: Merge( %s, %s ) - Can't Find Base State %s On %s\n", pSrcMesh->GetName(), pDstMesh->GetName(), pSrcBase->GetName(), pDstMesh->GetName() ); } } // Merge Face Sets int nFaceSetIndex; for ( int i = 0; i < pSrcMesh->FaceSetCount(); ++i ) { CDmeFaceSet *pFaceSet = pSrcMesh->GetFaceSet( i )->Copy(); pFaceSet->SetFileId( pDstMesh->GetFileId(), TD_DEEP ); const int nFaceSetIndexCount = pFaceSet->NumIndices(); for ( int j = 0; j < nFaceSetIndexCount; ++j ) { nFaceSetIndex = pFaceSet->GetIndex( j ); if ( nFaceSetIndex >= 0 ) { pFaceSet->SetIndex( j, nFaceSetIndex + nVertexOffset ); } } pDstMesh->AddFaceSet( pFaceSet ); } // Merge Deltas for ( int i = 0; i < pSrcMesh->DeltaStateCount(); ++i ) { CDmeVertexDeltaData *pSrcDelta = pSrcMesh->GetDeltaState( i ); CDmeVertexDeltaData *pDstDelta = pDstMesh->FindDeltaState( pSrcDelta->GetName() ); MergeDeltaState( pDstMesh, pSrcDelta, pDstDelta, nPositionOffset, nNormalOffset, nWrinkleOffset ); } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- struct VertexWeightMap_s { struct VertexWeight_s { int m_vertexDataIndex; // Index into the CDmeVertexData data (only used for joint weights & indices) const CUtlVector< int > *m_pVertexIndices; // Index into the CDmeVertexData vertex indices float m_vertexWeight; }; int m_nVertexWeights; VertexWeight_s m_vertexWeights[ 5 ]; }; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CopyJointWeights( CDmeVertexData *pSrcData, CDmeVertexData *pDstData, const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) { const int nJointCount = pSrcData->GetValue< int >( "jointCount" ); const FieldIndex_t nSrcJointWeightsField = pSrcData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); const FieldIndex_t nSrcJointIndicesField = pSrcData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); if ( nJointCount <= 0 || nSrcJointWeightsField < 0 || nSrcJointIndicesField < 0 ) return false; const CUtlVector< float > &srcJointWeights = CDmrArrayConst< float >( pSrcData->GetVertexData( nSrcJointWeightsField ) ).Get(); const float *const pSrcJointWeights = srcJointWeights.Base(); const CUtlVector< int > &srcJointIndices = CDmrArrayConst< int >( pSrcData->GetVertexData( nSrcJointIndicesField ) ).Get(); const int *const pSrcJointIndices = srcJointIndices.Base(); FieldIndex_t nDstJointWeightsField; FieldIndex_t nDstJointIndicesField; pDstData->CreateJointWeightsAndIndices( nJointCount, &nDstJointWeightsField, &nDstJointIndicesField ); const int nDstCount = vertexWeightMap.Count(); float *pDstJointWeights = reinterpret_cast< float * >( alloca( nDstCount * nJointCount * sizeof( float ) ) ); memset( pDstJointWeights, 0, nDstCount * nJointCount ); int *pDstJointIndices = reinterpret_cast< int * >( alloca( nDstCount * nJointCount * sizeof( int ) ) ); memset( pDstJointIndices, 0, nDstCount * nJointCount ); for ( int i = 0; i < nDstCount; ++i ) { const VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; const int nVertexWeights = vertexWeight.m_nVertexWeights; if ( nVertexWeights > 0 ) { // TODO: Find the best weights to use! For now, use the first one int nMatchIndex = vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex; memcpy( pDstJointWeights + i * nJointCount, pSrcJointWeights + nMatchIndex * nJointCount, nJointCount * sizeof( float ) ); memcpy( pDstJointIndices + i * nJointCount, pSrcJointIndices + nMatchIndex * nJointCount, nJointCount * sizeof( int ) ); } } pDstData->AddVertexData( nDstJointIndicesField, nDstCount * nJointCount ); pDstData->SetVertexData( nDstJointIndicesField, 0, nDstCount * nJointCount, AT_INT, pDstJointIndices ); pDstData->AddVertexData( nDstJointWeightsField, nDstCount * nJointCount ); pDstData->SetVertexData( nDstJointWeightsField, 0, nDstCount * nJointCount, AT_FLOAT, pDstJointWeights ); return true; } //----------------------------------------------------------------------------- // Replaces the DstMesh with the SrcMesh //----------------------------------------------------------------------------- CDmeMesh *ReplaceMesh( CDmeMesh *pSrcMesh, CDmeMesh *pDstMesh ) { if ( !pSrcMesh || !pDstMesh ) return NULL; CDmeDag *pSrcDag = pSrcMesh->GetParent(); CDmeDag *pDstDag = pDstMesh->GetParent(); if ( !pSrcDag || !pDstDag ) return NULL; // Fix up the transform matrix3x4_t inclusiveMat; matrix3x4_t localMat; pDstDag->GetShapeToWorldTransform( inclusiveMat ); pDstDag->GetTransform()->GetTransform( localMat ); matrix3x4_t inverseMat; MatrixInvert( localMat, inverseMat ); matrix3x4_t exclusiveMat; MatrixMultiply( inclusiveMat, inverseMat, exclusiveMat ); MatrixInvert( exclusiveMat, inverseMat ); pSrcDag->GetShapeToWorldTransform( inclusiveMat ); MatrixMultiply( inverseMat, inclusiveMat, localMat ); pDstDag->GetTransform()->SetTransform( localMat ); // Duplicate the mesh CDmeMesh *pNewMesh = pSrcMesh->Copy(); pNewMesh->SetFileId( pDstMesh->GetFileId(), TD_DEEP ); // A bit of cleanup pNewMesh->RemoveAttribute( "selection" ); pNewMesh->SetCurrentBaseState( "bind" ); pNewMesh->DeleteBaseState( "__dmxEdit_work" ); // Replace the DstMesh with the SrcMesh pDstDag->SetShape( pNewMesh ); // Replace the combination operators, if applicable CDmeCombinationOperator *pSrcComboOp = FindReferringElement< CDmeCombinationOperator >( pSrcMesh, "targets" ); if ( pSrcComboOp ) { CDmeCombinationOperator *pDstComboOp = FindReferringElement< CDmeCombinationOperator >( pDstMesh, "targets" ); CDmElement *pDstRoot = NULL; if ( pDstComboOp ) { // Find the root the easy way pDstRoot = FindReferringElement< CDmElement >( pDstComboOp, "combinationOperator" ); // Delete the old busted combination operator g_pDataModel->DestroyElement( pDstComboOp->GetHandle() ); } else { // Find the root the hard way CDmeDag *pDmeDag = pDstDag; for ( ;; ) { // Walk backwards via "children" attribute CDmeDag *pNextDag = FindReferringElement< CDmeDag >( pDmeDag, "children" ); if ( pNextDag ) { pDmeDag = pNextDag; } else { // Can't find anyone referring to this via "children" so, hopefully it's the DmeModel referred to by "model" pDstRoot = FindReferringElement< CDmElement >( pDmeDag, "model" ); break; } } } if ( pDstRoot ) { // Install the shiny new combination operator CDmeCombinationOperator *pNewComboOp = pSrcComboOp->Copy(); pNewComboOp->SetFileId( pDstRoot->GetFileId(), TD_DEEP ); pDstRoot->SetValue( "combinationOperator", pNewComboOp ); pNewComboOp->RemoveAllTargets(); pNewComboOp->AddTarget( pNewMesh ); pNewComboOp->GenerateWrinkleDeltas( false ); } } return pNewMesh; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < class T_t > void CopyFieldData( const CDmrArrayConst< T_t > &srcData, const CUtlVector< int > &srcIndices, CDmeVertexData *pDstVertexData, FieldIndex_t dstFieldIndex, const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) { const int nDstData = vertexWeightMap.Count(); T_t sum; T_t *pDstData = reinterpret_cast< T_t * >( alloca( nDstData * sizeof( T_t ) ) ); for ( int i = 0; i < nDstData; ++i ) { CDmAttributeInfo< T_t >::SetDefaultValue( pDstData[ i ] ); const VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; for ( int j = 0; j < vertexWeight.m_nVertexWeights; ++j ) { const VertexWeightMap_s::VertexWeight_s &vWeight = vertexWeight.m_vertexWeights[ j ]; CDmAttributeInfo< T_t >::SetDefaultValue( sum ); const CUtlVector< int > &vertexList = *vWeight.m_pVertexIndices; for ( int k = 0; k < vertexList.Count(); ++k ) { sum += srcData[ srcIndices[ vertexList[ k ] ] ]; } sum /= static_cast< float >( vertexList.Count() ); pDstData[ i ] += sum * vWeight.m_vertexWeight; } } const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( srcData.GetAttribute()->GetType() ); CDmrArray< T_t > dstData( pDstVertexData->GetVertexData( dstFieldIndex ) ); dstData.EnsureCount( nDstData ); pDstVertexData->SetVertexData( dstFieldIndex, 0, nDstData, dmAttributeType, pDstData ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CopyField( CDmeVertexData::StandardFields_t field, CDmeVertexData *pSrcData, CDmeVertexData *pDstData, const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) { FieldIndex_t srcFieldIndex = pSrcData->FindFieldIndex( field ); if ( srcFieldIndex < 0 ) return false; FieldIndex_t dstFieldIndex = pDstData->CreateField( field ); if ( dstFieldIndex < 0 ) return false; CDmAttribute *pSrcVertexData = pSrcData->GetVertexData( srcFieldIndex ); const CUtlVector< int > &srcIndices = pSrcData->GetVertexIndexData( srcFieldIndex ); // Everything on dst has to be indexed the same as position const CUtlVector< int > &dstPosIndices = pDstData->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ); CDmrArray< int > dstIndices( pDstData->GetIndexData( dstFieldIndex ) ); dstIndices.EnsureCount( dstPosIndices.Count() ); pDstData->SetVertexIndices( dstFieldIndex, 0, dstPosIndices.Count(), dstPosIndices.Base() ); switch ( pSrcVertexData->GetType() ) { case AT_FLOAT_ARRAY: CopyFieldData( CDmrArrayConst< float >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); break; case AT_VECTOR2_ARRAY: CopyFieldData( CDmrArrayConst< Vector2D >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); break; case AT_VECTOR3_ARRAY: CopyFieldData( CDmrArrayConst< Vector >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); break; default: break; } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline bool IsEqual( const float &flA, const float &flB, float flTolerance ) { return ( FloatMakePositive( flA - flB ) <= flTolerance ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline bool IsEqual( const Vector &vA, const Vector &vB, float flTolerance ) { return VectorsAreEqual( vA, vB, flTolerance ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline bool IsEqual( const Vector2D &vA, const Vector2D &vB, float flTolerance ) { if ( FloatMakePositive( vA.x - vB.x ) > flTolerance ) return false; return ( FloatMakePositive( vA.y - vB.y ) <= flTolerance ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < class T > bool MergeField( CDmeVertexData *pDstVertexData, FieldIndex_t nDstFieldIndex, CDmAttribute *pDstAttr, CDmeVertexData *pSrcVertexData, FieldIndex_t nSrcFieldIndex, CDmAttribute *pSrcAttr, int nIndexOffset, float flTolerance ) { CUtlMap< int, int > srcToDstMap( CDefOps< int >::LessFunc ); if ( !pDstVertexData || !pDstAttr || !pSrcAttr ) return false; const CDmrArrayConst< T > dstData( pDstAttr ); const CDmrArrayConst< T > srcData( pSrcAttr ); if ( dstData.Count() <= 0 || srcData.Count() <= 0 ) return false; CUtlVector< T > newDstData; for ( int i = 0; i < srcData.Count(); ++i ) { int nDstIndex = -1; const T &srcVal = srcData[i]; for ( int j = 0; j < dstData.Count(); ++j ) { const T &dstVal = dstData[j]; if ( IsEqual( srcVal, dstVal, flTolerance ) ) { nDstIndex = j; break; } } if ( nDstIndex < 0 ) { nDstIndex = dstData.Count() + newDstData.AddToTail( srcVal ); } Assert( nDstIndex >= 0 ); srcToDstMap.InsertOrReplace( i, nDstIndex ); } CUtlVector< int > newIndices; const CUtlVector< int > &srcIndexData = pSrcVertexData->GetVertexIndexData( nSrcFieldIndex ); Assert( srcIndexData.Count() > 0 ); for ( int i = 0; i < srcIndexData.Count(); ++i ) { const CUtlMap< int, int >::IndexType_t nFindIndex = srcToDstMap.Find( srcIndexData[i] ); Assert( srcToDstMap.IsValidIndex( nFindIndex ) ); newIndices.AddToTail( srcToDstMap.Element( nFindIndex ) ); } Assert( pDstVertexData->VertexCount() == nIndexOffset + newIndices.Count() ); pDstVertexData->SetVertexIndices( nDstFieldIndex, nIndexOffset, newIndices.Count(), newIndices.Base() ); if ( newDstData.Count() > 0 ) { const int nStart = dstData.Count(); pDstVertexData->AddVertexData( nDstFieldIndex, newDstData.Count() ); pDstVertexData->SetVertexData( nDstFieldIndex, nStart, newDstData.Count(), ArrayTypeToValueType( pDstAttr->GetType() ), newDstData.Base() ); } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::Merge( CDmMeshComp &srcComp, const CUtlVector< CDmMeshComp::CEdge * > &edgeList, CDmeMesh *pDstMesh ) { CDmeMesh *pSrcMesh = srcComp.m_pMesh; if ( !pSrcMesh || !pDstMesh ) return false; CDmeVertexData *pSrcData = pSrcMesh->FindBaseState( "bind" ); CDmeVertexData *pDstData = pDstMesh->FindBaseState( "bind" ); if ( !pSrcData || !pDstData ) return false; const CUtlVector< Vector > &srcPosData = pSrcData->GetPositionData(); const int nSrcCount = srcPosData.Count(); const CUtlVector< Vector > &dstPosData = pDstData->GetPositionData(); const int nDstCount = dstPosData.Count(); if ( nSrcCount <= 0 || nDstCount <= 0 ) return false; CUtlVector< VertexWeightMap_s > vertexWeightMap; vertexWeightMap.SetSize( nSrcCount ); for ( int i = 0; i < nSrcCount; ++i ) { int nClosestIndex = -1; float closest = FLT_MAX; VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; vertexWeight.m_nVertexWeights = 0; const Vector &vSrc = srcPosData[ i ]; for ( int j = 0; j < nDstCount; ++j ) { const Vector &vDst = dstPosData[ j ]; if ( vSrc.DistToSqr( vDst ) < FLT_EPSILON * 10.0f ) { vertexWeight.m_nVertexWeights = 1; vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex = j; vertexWeight.m_vertexWeights[ 0 ].m_vertexWeight = 1.0f; vertexWeight.m_vertexWeights[ 0 ].m_pVertexIndices = &pDstData->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, j ); break; } float distance = vSrc.DistToSqr( vDst ); if ( distance < closest ) { closest = distance; nClosestIndex = j; } } if ( vertexWeight.m_nVertexWeights == 0 ) { Warning( "Warning: Merge() - No Match For Src Vertex: %f %f %f, Using Closest: %f %f %f\n", vSrc.x, vSrc.y, vSrc.z, dstPosData[ nClosestIndex ].x, dstPosData[ nClosestIndex ].y, dstPosData[ nClosestIndex ].z ); // TODO: Loop through and find up to n closest vertices by position vertexWeight.m_nVertexWeights = 1; vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex = nClosestIndex; vertexWeight.m_vertexWeights[ 0 ].m_vertexWeight = 1.0f; vertexWeight.m_vertexWeights[ 0 ].m_pVertexIndices = &pDstData->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, nClosestIndex ); // Assert( vertexWeight.m_nVertexWeights ); // return false; } } CDmeMesh *pNewMesh = ReplaceMesh( pSrcMesh, pDstMesh ); if ( !pNewMesh ) { Error( "Error: Merge() - Couldn't Replace Mesh %s With %s\n", pDstMesh->GetName(), pSrcMesh->GetName() ); return false; } CDmeVertexData *pNewData = pNewMesh->FindBaseState( "bind" ); if ( pNewData ) { CopyJointWeights( pDstData, pNewData, vertexWeightMap ); CopyField( CDmeVertexData::FIELD_BALANCE, pDstData, pNewData, vertexWeightMap ); CopyField( CDmeVertexData::FIELD_MORPH_SPEED, pDstData, pNewData, vertexWeightMap ); if ( pNewData->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ) >= 0 ) { CDmeCombinationOperator *pComboOp( FindReferringElement< CDmeCombinationOperator >( pNewMesh, "targets" ) ); if ( pComboOp ) { pComboOp->UsingLaggedData( true ); } } } // Destroy the old busted mesh g_pDataModel->DestroyElement( pDstMesh->GetHandle() ); return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::NewMerge( CDmeMesh *pDstMesh, CDmeMesh *pSrcMesh, const CUtlVector< CDmMeshComp::CEdge * > &edgeList, float flTolerance, float flTexCoordTolerance ) { if ( !pSrcMesh || !pDstMesh ) return false; CDmeVertexData *pSrcData = pSrcMesh->FindBaseState( "bind" ); CDmeVertexData *pDstData = pDstMesh->FindBaseState( "bind" ); if ( !pSrcData || !pDstData ) return false; int nDstIndexCount = pDstData->VertexCount(); int nDstIndexOffset = nDstIndexCount; if ( nDstIndexCount <= 0 ) return false; // Merge or add face sets for ( int i = 0; i < pSrcMesh->FaceSetCount(); ++i ) { CDmeFaceSet *pSrcFaceSet = pSrcMesh->GetFaceSet( i ); if ( !pSrcFaceSet ) continue; const int nSrcIndexCount = pSrcFaceSet->GetIndexCount(); if ( nSrcIndexCount <= 0 ) continue; CDmeMaterial *pSrcMat = pSrcFaceSet->GetMaterial(); const char *pszSrcMatName = pSrcMat ? pSrcMat->GetMaterialName() : NULL; bool bCopy = true; if ( pszSrcMatName ) { for ( int j = 0; j < pDstMesh->FaceSetCount(); ++j ) { CDmeFaceSet *pDstFaceSet = pDstMesh->GetFaceSet( j ); if ( !pDstFaceSet ) continue; CDmeMaterial *pDstMat = pDstFaceSet->GetMaterial(); if ( !pDstMat ) continue; if ( !V_strcmp( pDstMat->GetMaterialName(), pszSrcMatName ) ) { bCopy = false; // Merge pDstData->AddVertexIndices( nSrcIndexCount ); for ( int k = 0; k < pSrcFaceSet->NumIndices(); ++k ) { const int nFaceSetIndex = pSrcFaceSet->GetIndex( k ); const int nToSetIndex = pDstFaceSet->NumIndices(); pDstFaceSet->AddIndices( 1 ); if ( nFaceSetIndex < 0 ) { pDstFaceSet->SetIndex( nToSetIndex, -1 ); } else { pDstFaceSet->SetIndex( nToSetIndex, nFaceSetIndex + nDstIndexCount ); } } nDstIndexCount += nSrcIndexCount; break; } } } if ( bCopy ) { CDmeFaceSet *pDmeFaceSet = pSrcMesh->GetFaceSet( i )->Copy(); pDmeFaceSet->SetFileId( pDstMesh->GetFileId(), TD_DEEP ); pDstData->AddVertexIndices( nSrcIndexCount ); for ( int j = 0; j < pDmeFaceSet->NumIndices(); ++j ) { int nFaceSetIndex = pDmeFaceSet->GetIndex( j ); if ( nFaceSetIndex >= 0 ) { pDmeFaceSet->SetIndex( j, nFaceSetIndex + nDstIndexCount ); } } nDstIndexCount += nSrcIndexCount; pDstMesh->AddFaceSet( pDmeFaceSet ); } } for ( int nSrcFieldIndex = 0; nSrcFieldIndex < pSrcData->FieldCount(); ++nSrcFieldIndex ) { CDmAttribute *pSrcAttr = pSrcData->GetVertexData( nSrcFieldIndex ); const char *pszSrcFieldName = pSrcData->FieldName( nSrcFieldIndex ); if ( !pSrcAttr || !pszSrcFieldName || V_strlen( pszSrcFieldName ) <= 0 ) continue; const FieldIndex_t nDstFieldIndex = pDstData->FindFieldIndex( pszSrcFieldName ); if ( nDstFieldIndex < 0 ) { Log_Error( LOG_MESHUTILS, LOG_COLOR_RED, "ERROR: Merge( \"%s\", \"%s\" ): Cannot Find Field \"%s\" On Dst Mesh: \"%s\"\n", pDstMesh->GetName(), pSrcMesh->GetName(), pszSrcFieldName, pDstMesh->GetName() ); continue; } CDmAttribute *pDstAttr = pDstData->GetVertexData( nDstFieldIndex ); if ( !pDstAttr ) { Log_Error( LOG_MESHUTILS, LOG_COLOR_RED, "ERROR: Merge( \"%s\", \"%s\" ): Cannot Find Field \"%s\" On Dst Mesh: \"%s\"\n", pDstMesh->GetName(), pSrcMesh->GetName(), pszSrcFieldName, pDstMesh->GetName() ); continue; } if ( pSrcAttr->GetType() != pDstAttr->GetType() ) { Log_Error( LOG_MESHUTILS, LOG_COLOR_RED, "ERROR: Merge( \"%s\", \"%s\" ): Field \"%s\" Type Mismatch: Src %s vs Dst %s\n", pDstMesh->GetName(), pSrcMesh->GetName(), pszSrcFieldName, pSrcAttr->GetTypeString(), pDstAttr->GetTypeString() ); continue; } bool bOk = false; const DmAttributeType_t nAttrType = pSrcAttr->GetType(); if ( nAttrType == AT_FLOAT_ARRAY ) { bOk = MergeField< float >( pDstData, nDstFieldIndex, pDstAttr, pSrcData, nSrcFieldIndex, pSrcAttr, nDstIndexOffset, flTolerance ); } else if ( nAttrType == AT_VECTOR2_ARRAY ) { bOk = MergeField< Vector2D >( pDstData, nDstFieldIndex, pDstAttr, pSrcData, nSrcFieldIndex, pSrcAttr, nDstIndexOffset, flTexCoordTolerance ); } else if ( nAttrType == AT_VECTOR3_ARRAY ) { bOk = MergeField< Vector >( pDstData, nDstFieldIndex, pDstAttr, pSrcData, nSrcFieldIndex, pSrcAttr, nDstIndexOffset, flTolerance ); } if ( !bOk ) { Log_Warning( LOG_MESHUTILS, LOG_COLOR_YELLOW, "WARNING: Merge( \"%s\", \"%s\" ): Field \"%s\" Not Merged\n", pDstMesh->GetName(), pSrcMesh->GetName(), pszSrcFieldName ); } } pDstData->Resolve(); pDstMesh->Resolve(); return true; } //----------------------------------------------------------------------------- // Returns a guaranteed unique DmFileId_t //----------------------------------------------------------------------------- DmFileId_t CreateUniqueFileId() { DmFileId_t fileId = DMFILEID_INVALID; UniqueId_t uniqueId; char fileIdBuf[ MAX_PATH ]; do { CreateUniqueId( &uniqueId ); UniqueIdToString( uniqueId, fileIdBuf, sizeof( fileIdBuf ) ); fileId = g_pDataModel->GetFileId( fileIdBuf ); } while( fileId != DMFILEID_INVALID ); return g_pDataModel->FindOrCreateFileId( fileIdBuf ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CreateExpressionFile( const char *pExpressionFile, const CUtlVector< CUtlString > *pPurgeAllButThese, CDmeCombinationOperator *pComboOp, CDmePresetGroup *pPresetGroup ) { if ( !pPresetGroup ) return false; Assert( pExpressionFile && pComboOp ); const int nControlCount = pComboOp->GetControlCount(); const CDmaElementArray< CDmePreset > &presets = pPresetGroup->GetPresets(); const int nPresetsCount = presets.Count(); if ( nControlCount <= 0 || nPresetsCount <= 0 ) return false; char expName[ MAX_PATH ]; Q_FileBase( pExpressionFile, expName, sizeof( expName ) ); CDmePresetGroup *pDstPresetGroup = CreateElement< CDmePresetGroup >( expName, CreateUniqueFileId() ); if ( !pDstPresetGroup ) return false; for ( int i = 0; i < nPresetsCount; ++i ) { CDmePreset *pPreset = presets[ i ]; Assert( !pPreset->IsAnimated() ); // deal with this after GDC if ( pPreset->IsAnimated() ) continue; const char *pPresetName = pPreset->GetName(); CDmePreset *pDstPreset = pDstPresetGroup->FindOrAddPreset( pPresetName ); const CDmaElementArray< CDmElement > &controlValues = pPreset->GetControlValues(); const int nControlValueCount = controlValues.Count(); for ( int j = 0; j < nControlCount; ++j ) { // Figure out if this preset is used bool bFound = false; // Used for two things const char *pControlName = pComboOp->GetControlName( j ); if ( pPurgeAllButThese ) { for ( int k = 0; k < pPurgeAllButThese->Count(); ++k ) { if ( !Q_strcmp( pControlName, pPurgeAllButThese->Element( k ).Get() ) ) { bFound = true; break; } } } if ( !bFound && pPresetGroup->FindPreset( pControlName ) ) bFound = true; if ( !bFound ) continue; CDmElement *pDstControlValue = NULL; const bool bStereo = pComboOp->IsStereoControl( j ); const bool bMulti = pComboOp->IsMultiControl( j ); if ( !Q_strcmp( pControlName, pPresetName ) ) { pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); // These shouldn't really happen because these are presets which were made // into deltas so they are never stereo nor multi-controls if ( bStereo ) { pDstControlValue->SetValue( "leftValue", 1.0f ); pDstControlValue->SetValue( "rightValue", 1.0f ); } else { pDstControlValue->SetValue( "value", 1.0f ); } if ( bMulti ) { CFmtStr multiControlName( "%s_multi", pControlName ); CDmElement *pDstMultiControlValue = pDstPreset->FindOrAddControlValue( multiControlName ); pDstMultiControlValue->SetValue( "value", 0.5f ); } continue; } for ( int k = 0; k < nControlValueCount; ++k ) { CDmElement *pControlPreset = controlValues[ k ]; if ( !pControlPreset ) continue; if ( !Q_strcmp( pControlName, pControlPreset->GetName() ) ) { pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); if ( bStereo ) { pDstControlValue->SetValue( "leftValue", pControlPreset->GetValue( "leftValue", 0.0f ) ); pDstControlValue->SetValue( "rightValue", pControlPreset->GetValue( "rightValue", 0.0f ) ); } else { pDstControlValue->SetValue( "value", pControlPreset->GetValue( "value", 0.0f ) ); } if ( bMulti ) { CFmtStr multiControlName( "%s_multi", pControlName ); CDmElement *pMultiControlPreset = pPreset->FindControlValue( multiControlName ); CDmElement *pDstMultiControlValue = pDstPreset->FindOrAddControlValue( multiControlName ); pDstMultiControlValue->SetValue( "value", pMultiControlPreset ? pMultiControlPreset->GetValue( "value", 0.5f ) : 0.5f ); } break; } } if ( !pDstControlValue ) { pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); if ( bStereo ) { float flValue = pComboOp->GetControlDefaultValue( j ); pDstControlValue->SetValue( "leftValue", flValue ); pDstControlValue->SetValue( "rightValue", flValue ); } else { pDstControlValue->SetValue( "value", pComboOp->GetControlDefaultValue( j ) ); } if ( bMulti ) { CFmtStr multiControlName( "%s_multi", pControlName ); CDmElement *pDstMultiControlValue = pDstPreset->FindOrAddControlValue( multiControlName ); pDstMultiControlValue->SetValue( "value", 0.5f ); } } } } char buf[ MAX_PATH ]; char buf1[ MAX_PATH ]; Q_strncpy( buf, pExpressionFile, sizeof( buf ) ); Q_SetExtension( buf, ".txt", sizeof( buf ) ); Q_ExtractFilePath( buf, buf1, sizeof( buf1 ) ); Q_FixSlashes( buf1 ); g_pFullFileSystem->CreateDirHierarchy( buf1 ); if ( !g_p4factory->AccessFile( buf )->Edit() ) { g_p4factory->AccessFile( buf )->Add(); } pDstPresetGroup->ExportToTXT( buf, NULL, pComboOp ); Q_SetExtension( buf, ".vfe", sizeof( buf ) ); Q_ExtractFilePath( buf, buf1, sizeof( buf1 ) ); Q_FixSlashes( buf1 ); g_pFullFileSystem->CreateDirHierarchy( buf1 ); if ( !g_p4factory->AccessFile( buf )->Edit() ) { g_p4factory->AccessFile( buf )->Add(); } pDstPresetGroup->ExportToVFE( buf, NULL, pComboOp ); g_pDataModel->UnloadFile( pDstPresetGroup->GetFileId() ); return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::CreateDeltasFromPresets( CDmeMesh *pMesh, CDmeVertexData *pPassedDst, const CUtlStringMap< CUtlString > &presetExpressionMap, bool bPurge, const CUtlVector< CUtlString > *pPurgeAllButThese /*= NULL */ ) { if ( !pMesh ) return false; CDisableUndoScopeGuard sgDisableUndo; CUtlStringMap< CDmePreset * > presetMap; CUtlStringMap< CUtlString > conflictingNames; CDmeVertexData *pDst = pPassedDst ? pPassedDst : pMesh->GetCurrentBaseState(); CDmeVertexData *pBind = pMesh->FindBaseState( "bind" ); if ( !pDst || !pBind || pDst == pBind ) return false; CDmeCombinationOperator *pComboOp = FindReferringElement< CDmeCombinationOperator >( pMesh, "targets" ); if ( !pComboOp ) return false; const bool bSavedUsingLagged = pComboOp->IsUsingLaggedData(); CUtlVector< CDmePresetGroup * > presetGroups; for ( int i = 0; i < presetExpressionMap.GetNumStrings(); ++i ) { const char *pPresetFilename = presetExpressionMap.String( i ); // Load the preset file CDmElement *pRoot = NULL; g_p4factory->AccessFile( pPresetFilename )->Add(); g_pDataModel->RestoreFromFile( pPresetFilename, NULL, NULL, &pRoot ); CDmePresetGroup *pPresetGroup = CastElement< CDmePresetGroup >( pRoot ); presetGroups.AddToTail( pPresetGroup ); if ( !pPresetGroup ) continue; CreateDeltasFromPresetGroup( pPresetGroup, pComboOp, pPurgeAllButThese, pMesh, pDst, conflictingNames, presetMap ); } if ( bPurge ) { PurgeUnreferencedDeltas( pMesh, presetMap, pPurgeAllButThese, pComboOp ); } for ( int i = 0; i < presetMap.GetNumStrings(); ++i ) { const char *pPresetName = presetMap[ i ]->GetName(); const int nControlIndex = pComboOp->FindControlIndex( pPresetName ); if ( nControlIndex < 0 ) { pComboOp->FindOrCreateControl( pPresetName, false, true ); } else { bool bFound = false; if ( bPurge ) { pComboOp->RemoveAllRawControls( nControlIndex ); } else { const int nRawControls = pComboOp->GetRawControlCount( nControlIndex ); for ( int j = 0; j < nRawControls; ++j ) { if ( !Q_strcmp( pComboOp->GetRawControlName( nControlIndex, j ), pPresetName ) ) { bFound = true; break; } } } if ( !bFound ) { pComboOp->AddRawControl( nControlIndex, pPresetName ); } } } pComboOp->UsingLaggedData( bSavedUsingLagged ); pComboOp->SetToDefault(); for ( int i = 0; i < presetExpressionMap.GetNumStrings(); ++i ) { const CUtlString &expressionFile = presetExpressionMap[ i ]; if ( expressionFile.IsEmpty() ) continue; CreateExpressionFile( expressionFile.Get(), pPurgeAllButThese, pComboOp, presetGroups[ i ] ); } for ( int i = 0; i < presetGroups.Count(); ++i ) { CDmePresetGroup *pPresetGroup = presetGroups[ i ]; if ( !pPresetGroup ) continue; g_pDataModel->UnloadFile( pPresetGroup->GetFileId() ); } return true; } //----------------------------------------------------------------------------- // Removes any deltas from the specified mesh which are not referred to by // any rule of the combination operator driving the mesh //----------------------------------------------------------------------------- bool CDmMeshUtils::PurgeUnusedDeltas( CDmeMesh *pMesh ) { // Disable for now // This code will delete all corrective delta states, i.e. deltas named A_B return true; if ( !pMesh ) return false; CDmeCombinationOperator *pCombo = FindReferringElement< CDmeCombinationOperator >( pMesh, "targets" ); if ( !pCombo ) return false; const int nControlCount = pCombo->GetControlCount(); CUtlVector< CDmeMesh::DeltaComputation_t > compList; pMesh->ComputeDependentDeltaStateList( compList ); const int nDeltaCount = compList.Count(); Assert( nDeltaCount == pMesh->DeltaStateCount() ); CUtlVector< bool > deltasToKeep; deltasToKeep.EnsureCount( nDeltaCount ); memset( deltasToKeep.Base(), 0, sizeof( bool ) * nDeltaCount ); for ( int i = 0; i < nControlCount; ++i ) { const int nRawControlCount = pCombo->GetRawControlCount( i ); for ( int j = 0; j < nRawControlCount; ++j ) { const int nDeltaIndex = pMesh->FindDeltaStateIndex( pCombo->GetRawControlName( i, j ) ); const CDmeMesh::DeltaComputation_t &deltaComp = compList[ nDeltaIndex ]; deltasToKeep[ deltaComp.m_nDeltaIndex ] = true; for ( int k = 0; k < deltaComp.m_DependentDeltas.Count(); ++k ) { deltasToKeep[ deltaComp.m_DependentDeltas[ k ] ] = true; } } } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::CreateWrinkleDeltaFromBaseState( CDmeVertexDeltaData *pDelta, float flScale /* = 1.0f */, WrinkleOp wrinkleOp /* = kReplace */, CDmeMesh *pPassedMesh /* = NULL */, CDmeVertexData *pPassedBind /* = NULL */, CDmeVertexData *pPassedCurrent /* = NULL */, bool bUseNormalForSign /* = false */ ) { CDmeVertexData *pBind = pPassedBind ? pPassedBind : pPassedMesh ? pPassedMesh->GetBindBaseState() : NULL; CDmeVertexData *pCurr = pPassedCurrent ? pPassedCurrent : pPassedMesh ? pPassedMesh->GetCurrentBaseState() : NULL; const CDmeMesh *pMesh = pPassedMesh ? pPassedMesh : pBind ? FindReferringElement< CDmeMesh >( pBind, "baseStates" ) : NULL; const CDmeMesh *pBindMesh = pBind ? FindReferringElement< CDmeMesh >( pBind, "baseStates" ) : NULL; const CDmeMesh *pCurrMesh = pCurr ? FindReferringElement< CDmeMesh >( pCurr, "baseStates", false ) : NULL; const CDmeMesh *pDeltaMesh = pDelta ? FindReferringElement< CDmeMesh >( pDelta, "deltaStates" ) : NULL; if ( !pDelta || !pBind || !pCurr || pBind == pCurr || !pMesh || pMesh != pBindMesh || pMesh != pCurrMesh || pMesh != pDeltaMesh ) { return false; } const FieldIndex_t nBindPosIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); const FieldIndex_t nBindTexIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); const FieldIndex_t nCurrPosIndex = pCurr->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); if ( nBindPosIndex < 0 || nBindTexIndex < 0 || nCurrPosIndex < 0 ) return false; const CUtlVector< Vector > &bindPos = CDmrArrayConst< Vector >( pBind->GetVertexData( nBindPosIndex ) ).Get(); const CUtlVector< Vector > &currPos = CDmrArrayConst< Vector >( pCurr->GetVertexData( nCurrPosIndex ) ).Get(); const CUtlVector< int > &baseTexCoordIndices = pBind->GetVertexIndexData( nBindTexIndex ); const int nPosCount = bindPos.Count(); if ( nPosCount != currPos.Count() ) return false; const CDmrArrayConst< Vector2D > texData( pBind->GetVertexData( nBindTexIndex ) ); const int nBaseTexCoordCount = texData.Count(); FieldIndex_t nWrinkleIndex = pDelta->FindFieldIndex( CDmeVertexDeltaData::FIELD_WRINKLE ); if ( nWrinkleIndex < 0 ) { nWrinkleIndex = pDelta->CreateField( CDmeVertexDeltaData::FIELD_WRINKLE ); } float *pOldWrinkleData = NULL; if ( wrinkleOp == kAdd ) { // Copy the old wrinkle data CDmAttribute *pWrinkleDeltaAttr = pDelta->GetVertexData( nWrinkleIndex ); if ( pWrinkleDeltaAttr ) { CDmrArrayConst< float > wrinkleDeltaArray( pWrinkleDeltaAttr ); if ( wrinkleDeltaArray.Count() ) { const CUtlVector< int > &wrinkleDeltaIndices = pDelta->GetVertexIndexData( nWrinkleIndex ); Assert( wrinkleDeltaIndices.Count() == wrinkleDeltaArray.Count() ); pOldWrinkleData = reinterpret_cast< float * >( alloca( nBaseTexCoordCount * sizeof( float ) ) ); memset( pOldWrinkleData, 0, nBaseTexCoordCount * sizeof( float ) ); int nWrinkleIndex; for ( int i = 0; i < wrinkleDeltaIndices.Count(); ++i ) { nWrinkleIndex = wrinkleDeltaIndices[ i ]; if ( i < nPosCount ) { *( pOldWrinkleData + nWrinkleIndex ) = wrinkleDeltaArray[ i ]; } } } } } pDelta->RemoveAllVertexData( nWrinkleIndex ); if ( flScale == 0.0f && wrinkleOp != kAdd ) return true; float flMaxDeflection = 0.0f; int *pWrinkleIndices = reinterpret_cast< int * >( alloca( nPosCount * sizeof( int ) ) ); float *pWrinkleDelta = reinterpret_cast< float * >( alloca( nPosCount * sizeof( float ) ) ); int nWrinkleCount = 0; float flDelta; Vector v; const int *pNormalIndices = NULL; const Vector *pNormals = NULL; if ( bUseNormalForSign ) { const CUtlVector &normalIndices = pBind->GetVertexIndexData( CDmeVertexData::FIELD_NORMAL ); const CUtlVector< Vector > &normals = pBind->GetNormalData(); if ( normalIndices.Count() > 0 && normals.Count() > 0 ) { pNormalIndices = normalIndices.Base(); pNormals = normals.Base(); } else { Warning( "ComputeNormalWrinkle called but no normals on Mesh: %s\n", pDeltaMesh->GetName() ); bUseNormalForSign = false; } } if ( pOldWrinkleData || bUseNormalForSign ) { for ( int i = 0; i < nPosCount; ++i ) { v = bindPos[ i ] - currPos[ i ]; // Figure out the texture indices for this position index const CUtlVector< int > &baseVerts = pBind->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, i ); for ( int j = 0; j < baseVerts.Count(); ++j ) { // See if we have a delta for this texcoord... const int nTexCoordIndex = baseTexCoordIndices[ baseVerts[ j ] ]; if ( ( pOldWrinkleData && fabs( pOldWrinkleData[ nTexCoordIndex ] ) > 0.0001 ) || fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) { flDelta = v.NormalizeInPlace(); if ( flDelta > flMaxDeflection ) { flMaxDeflection = flDelta; } if ( bUseNormalForSign ) { Vector vNormal = pNormals[ pNormalIndices[ baseVerts[ j ] ] ]; vNormal.NormalizeInPlace(); if ( DotProduct( v, vNormal ) < 0 ) { flDelta = -flDelta; } } pWrinkleDelta[ nWrinkleCount ] = flDelta; pWrinkleIndices[ nWrinkleCount ] = i; ++nWrinkleCount; break; } } } } else { for ( int i = 0; i < nPosCount; ++i ) { v = bindPos[ i ] - currPos[ i ]; if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) { flDelta = v.Length(); if ( flDelta > flMaxDeflection ) { flMaxDeflection = flDelta; } pWrinkleDelta[ nWrinkleCount ] = flDelta; pWrinkleIndices[ nWrinkleCount ] = i; ++nWrinkleCount; } } } if ( flMaxDeflection == 0.0f ) return true; const double scaledInverseMaxDeflection = static_cast< double >( flScale ) / static_cast< double >( flMaxDeflection ); const int nBufSize = ( ( nBaseTexCoordCount + 7 ) >> 3 ); unsigned char * const pUsedBits = reinterpret_cast< unsigned char* >( alloca( nBufSize * sizeof( unsigned char ) ) ); memset( pUsedBits, 0, nBufSize ); for ( int i = 0; i < nWrinkleCount; ++i ) { float flWrinkleDelta = static_cast< float >( static_cast< double >( pWrinkleDelta[ i ] ) * scaledInverseMaxDeflection ); Assert( fabs( flWrinkleDelta ) <= fabs( flScale ) ); // NOTE: This will produce bad behavior in cases where two positions share the // same texcoord, which shouldn't theoretically happen. const CUtlVector< int > &baseVerts = pBind->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, pWrinkleIndices[ i ] ); const int nBaseVertCount = baseVerts.Count(); for ( int j = 0; j < nBaseVertCount; ++j ) { // See if we have a delta for this texcoord... int nTexCoordIndex = baseTexCoordIndices[ baseVerts[j] ]; if ( pUsedBits[ nTexCoordIndex >> 3 ] & ( 1 << ( nTexCoordIndex & 0x7 ) ) ) continue; pUsedBits[ nTexCoordIndex >> 3 ] |= 1 << ( nTexCoordIndex & 0x7 ); if ( pOldWrinkleData ) { flWrinkleDelta += pOldWrinkleData[ nTexCoordIndex ]; } int nDeltaIndex = pDelta->AddVertexData( nWrinkleIndex, 1 ); pDelta->SetVertexIndices( nWrinkleIndex, nDeltaIndex, 1, &nTexCoordIndex ); pDelta->SetVertexData( nWrinkleIndex, nDeltaIndex, 1, AT_FLOAT, &flWrinkleDelta ); } } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmMeshFaceIt::CDmMeshFaceIt( const CDmeMesh *pMesh, const CDmeVertexData *pVertexData /* = NULL */ ) { Reset( pMesh, pVertexData ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::Reset( const CDmeMesh *pMesh, const CDmeVertexData *pVertexData /* = NULL */ ) { m_nFaceIndex = 0; if ( pMesh ) { m_pMesh = pMesh; m_pVertexData = pVertexData ? pVertexData : m_pVertexData ? m_pVertexData : m_pMesh->GetCurrentBaseState(); m_nFaceSetCount = 0; m_nFaceSetIndex = 0; m_pFaceSet = NULL; m_nFaceSetIndexCount = 0; m_nFaceSetIndexIndex = 0; m_nFaceCount = 0; // Get number of face sets in current mesh m_nFaceSetCount = m_pMesh->FaceSetCount(); if ( m_nFaceSetCount <= 0 ) return false; // Get number of faces in current mesh for ( m_nFaceSetIndex = 0; m_nFaceSetIndex < m_nFaceSetCount; ++m_nFaceSetIndex ) { const CDmeFaceSet *pFaceSet = m_pMesh->GetFaceSet( m_nFaceSetIndex ); m_nFaceCount += pFaceSet->GetFaceCount(); } } else if ( !m_pMesh ) { return false; } // Set indices to point to first index of first face of first face set, accounting for // NULL face sets and NULL faces for ( m_nFaceSetIndex = 0; m_nFaceSetIndex < m_nFaceSetCount; ++m_nFaceSetIndex ) { if ( SetFaceSet() ) return true; } // All face sets were empty or full of nothing but -1's Assert( m_nFaceSetIndex == m_nFaceSetCount ); Assert( m_nFaceCount == 0 ); m_pFaceSet = NULL; m_nFaceSetIndexCount = 0; m_nFaceSetIndexIndex = 0; return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CDmMeshFaceIt::Count() const { return m_nFaceCount; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CDmMeshFaceIt::VertexCount() const { if ( IsDone() ) return 0; return m_pFaceSet->GetNextPolygonVertexCount( m_nFaceSetIndexIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::IsDone() const { if ( m_nFaceIndex < m_nFaceCount ) { Assert( m_nFaceSetIndex < m_nFaceSetCount ); Assert( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ); } else { Assert( m_nFaceSetIndex >= m_nFaceSetCount ); Assert( m_nFaceSetIndexIndex >= m_nFaceSetIndexCount ); } return m_nFaceIndex >= m_nFaceCount; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::Next() { // Set indices to point to first index of first face of first face set, accounting for // NULL face sets and NULL faces while ( m_nFaceSetIndex < m_nFaceSetCount ) { // Skip to next -1 face delimiter while ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) { if ( m_pFaceSet->GetIndex( m_nFaceSetIndexIndex ) >= 0 ) break; ++m_nFaceSetIndexIndex; } // Skip to next face index while ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) { if ( m_pFaceSet->GetIndex( m_nFaceSetIndexIndex ) < 0 ) break; ++m_nFaceSetIndexIndex; } if ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) { ++m_nFaceIndex; Assert( m_nFaceIndex < m_nFaceCount ); return true; } // Must increment the face set ++m_nFaceSetIndex; SetFaceSet(); } // At the end of the iteration Assert( IsDone() ); return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::SetFaceSet() { if ( !m_pMesh ) { m_pFaceSet = NULL; m_nFaceSetIndexCount = 0; m_nFaceSetIndexIndex = 0; return false; } if ( m_nFaceSetIndex >= m_nFaceSetCount ) { m_pFaceSet = NULL; m_nFaceSetIndexCount = 0; m_nFaceSetIndexIndex = 0; return false; } m_pFaceSet = m_pMesh->GetFaceSet( m_nFaceSetIndex ); m_nFaceSetIndexCount = m_pFaceSet->NumIndices(); m_nFaceSetIndexIndex = 0; // Skip to the first valid face index for ( m_nFaceSetIndexIndex = 0; m_nFaceSetIndexIndex < m_nFaceSetIndexCount; ++m_nFaceSetIndexIndex ) { if ( m_pFaceSet->GetIndex( m_nFaceSetIndex ) >= 0 ) return true; } return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::GetVertexIndices( int *pIndices, int nIndices ) const { if ( IsDone() || nIndices != VertexCount() ) { memset( pIndices, 0, nIndices * sizeof( int ) ); return false; } int vertexIndex; for ( int i = m_nFaceSetIndexIndex; i < m_nFaceSetIndexCount; ++i ) { vertexIndex = m_pFaceSet->GetIndex( i ); if ( vertexIndex < 0 ) { Assert( i == m_nFaceSetIndexIndex + VertexCount() ); return true; } Assert( i < m_nFaceSetIndexIndex + VertexCount() ); *pIndices = vertexIndex; ++pIndices; } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshFaceIt::GetVertexIndices( CUtlVector< int > &vertexIndices ) const { vertexIndices.SetCount( VertexCount() ); if ( IsDone() ) { memset( vertexIndices.Base(), 0, vertexIndices.Count() * sizeof( int ) ); return false; } return GetVertexIndices( vertexIndices.Base(), vertexIndices.Count() ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CDmMeshFaceIt::GetVertexIndex( int nFaceRelativeVertexIndex ) const { if ( IsDone() ) return -1; const int nVertexCount = VertexCount(); if ( nVertexCount <= 0 || nFaceRelativeVertexIndex < 0 || nFaceRelativeVertexIndex >= nVertexCount ) return -1; int *pVertexIndices = reinterpret_cast< int * >( alloca( nVertexCount * sizeof( int ) ) ); if ( !GetVertexIndices( pVertexIndices, nVertexCount ) ) return -1; return pVertexIndices[ nFaceRelativeVertexIndex ]; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmMeshUtils::CreateDeltasFromPresetGroup( CDmePresetGroup *pPresetGroup, CDmeCombinationOperator * pComboOp, const CUtlVector< CUtlString > *pPurgeAllButThese, CDmeMesh *pMesh, CDmeVertexData *pDst, CUtlStringMap< CUtlString > &conflictingNames, CUtlStringMap< CDmePreset * > &presetMap ) { const CDmaElementArray< CDmePreset > &presets = pPresetGroup->GetPresets(); const int nPresetsCount = presets.Count(); if ( nPresetsCount <= 0 ) return; for ( int i = 0; i < nPresetsCount; ++i ) { pComboOp->SetToBase(); CDmePreset *pPreset = presets[ i ]; Assert( !pPreset->IsAnimated() ); // deal with this after GDC if ( pPreset->IsAnimated() ) continue; CDmaElementArray< CDmElement > &controlValues = pPreset->GetControlValues(); const int nControlValues = controlValues.Count(); for ( int j = 0; j < nControlValues; ++j ) { CDmElement *pControlPreset = controlValues[ j ]; bool bIsMulti; const ControlIndex_t nControlIndex = FindComboOpControlIndexForAnimSetControl( pComboOp, pControlPreset->GetName(), &bIsMulti ); if ( nControlIndex < 0 ) continue; bool bSkip = false; if ( pPurgeAllButThese ) { for ( int k = 0; k < pPurgeAllButThese->Count(); ++k ) { if ( !Q_strcmp( pControlPreset->GetName(), pPurgeAllButThese->Element( k ).Get() ) ) { bSkip = true; } } } if ( bSkip ) continue; if ( pComboOp->IsStereoControl( nControlIndex ) ) { pComboOp->SetControlValue( nControlIndex, pControlPreset->GetValue( "leftValue", 0.0f ), pControlPreset->GetValue( "rightValue", 0.0f ) ); } else { pComboOp->SetControlValue( nControlIndex, pControlPreset->GetValue< float >( "value", 0.0 ) ); } if ( bIsMulti ) { pComboOp->SetMultiControlLevel( nControlIndex, pControlPreset->GetValue< float >( "value", 0.5 ) ); } } // Pass the control data from the DmeCombinationOperator into the mesh pComboOp->Resolve(); pComboOp->Operate(); pMesh->Resolve(); pMesh->SetBaseStateToDeltas( pDst ); CUtlString presetName = pPreset->GetName(); // Look for any conflicting pre-existing names for ( int presetSuffix = 1; pComboOp->FindControlIndex( presetName ) >= 0 || pMesh->FindDeltaState( presetName ) != NULL || conflictingNames.Defined( presetName ) || presetMap.Defined( presetName ); ++presetSuffix ) { presetName = pPreset->GetName(); presetName += presetSuffix; } if ( Q_strcmp( pPreset->GetName(), presetName ) ) { // Had to rename preset... save name for later renaming back conflictingNames[ presetName ] = pPreset->GetName(); } presetMap[ presetName ] = pPreset; pMesh->ModifyOrCreateDeltaStateFromBaseState( presetName, pDst, true ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmMeshUtils::PurgeUnreferencedDeltas( CDmeMesh *pMesh, CUtlStringMap< CDmePreset * > &presetMap, const CUtlVector< CUtlString > *pPurgeAllButThese, CDmeCombinationOperator *pComboOp ) { // Loop because deleting changes indexing bool bDeleted = false; do { bDeleted = false; for ( int i = 0; i < pMesh->DeltaStateCount(); ++i ) { const char *pDeltaStateName = pMesh->GetDeltaState( i )->GetName(); if ( presetMap.Defined( pDeltaStateName ) ) continue; bool bDelete = true; if ( pPurgeAllButThese ) { for ( int j = 0; j < pPurgeAllButThese->Count(); ++j ) { if ( !Q_strcmp( pDeltaStateName, pPurgeAllButThese->Element( j ).Get() ) ) { bDelete = false; break; } const ControlIndex_t nControlIndex = pComboOp->FindControlIndex( pPurgeAllButThese->Element( j ) ); if ( nControlIndex < 0 ) continue; for ( int k = 0; k < pComboOp->GetRawControlCount( nControlIndex ); ++k ) { if ( !Q_strcmp( pDeltaStateName, pComboOp->GetRawControlName( nControlIndex, k ) ) ) { bDelete = false; break; } } } } if ( bDelete ) { pMesh->DeleteDeltaState( pDeltaStateName ); bDeleted = true; break; } } } while( bDeleted ); // Loop because deleting changes indexing do { bDeleted = false; for ( int i = 0; i < pComboOp->GetControlCount(); ++i ) { const char *pControlName = pComboOp->GetControlName( i ); if ( presetMap.Defined( pControlName ) ) continue; bool bDelete = true; if ( pPurgeAllButThese ) { for ( int j = 0; j < pPurgeAllButThese->Count(); ++j ) { if ( !Q_strcmp( pControlName, pPurgeAllButThese->Element( j ) ) ) { bDelete = false; break; } } } if ( bDelete ) { pComboOp->RemoveControl( pControlName ); bDeleted = true; break; } } } while( bDeleted ); // Rename any that can be renamed... which should be all of them for ( int i = 0; i < presetMap.GetNumStrings(); ++i ) { const char *pPresetName = presetMap.String( i ); CDmePreset *pPreset = presetMap[ i ]; if ( Q_strcmp( pPreset->GetName(), pPresetName ) ) { const ControlIndex_t nOrigIndex = pComboOp->FindControlIndex( pPreset->GetName() ); const ControlIndex_t nRenamedIndex = pComboOp->FindControlIndex( pPresetName ); CDmeVertexDeltaData *pOrigDelta = pMesh->FindDeltaState( pPreset->GetName() ); CDmeVertexDeltaData *pRenamedDelta = pMesh->FindDeltaState( pPresetName ); if ( nOrigIndex < 0 && nRenamedIndex >= 0 && pOrigDelta == NULL && pRenamedDelta != NULL ) { pComboOp->RemoveControl( pPresetName ); pRenamedDelta->SetName( pPreset->GetName() ); } } } } //----------------------------------------------------------------------------- // Returns the first DmeModel which is an ancestor of the specified DmeMesh //----------------------------------------------------------------------------- CDmeModel *CDmMeshUtils::GetDmeModelFromMesh( CDmeMesh *pDmeMesh ) { if ( !pDmeMesh ) return NULL; CUtlStack< CDmeDag * > parentWalk; CDmeDag *pDmeDag; for ( int i = pDmeMesh->GetParentCount() - 1; i >= 0; --i ) { pDmeDag = pDmeMesh->GetParent( i ); if ( !pDmeDag ) continue; parentWalk.Push( pDmeDag ); } while ( parentWalk.Count() ) { parentWalk.Pop( pDmeDag ); if ( !pDmeDag ) continue; if ( CastElement< CDmeModel >( pDmeDag ) ) return CastElement< CDmeModel >( pDmeDag ); parentWalk.Push( pDmeDag->GetParent() ); } return NULL; } //----------------------------------------------------------------------------- // Does a depth first walk of the DmeDag hierarchy specified until a DmeDag with the // requested name is found, returns that dag if found, NULL if not found or on error //----------------------------------------------------------------------------- CDmeDag *CDmMeshUtils::FindDagByNameInHierarchy( CDmeDag *pSearchDag, const char *pszSearchName ) { if ( !pSearchDag || !pszSearchName || V_strlen( pszSearchName ) <= 0 ) return NULL; CUtlStack< CDmeDag * > depthFirstStack; depthFirstStack.Push( pSearchDag ); CDmeDag *pDmeDag; while ( depthFirstStack.Count() > 0 ) { depthFirstStack.Pop( pDmeDag ); if ( !pDmeDag ) continue; if ( !V_strcmp( pDmeDag->GetName(), pszSearchName ) ) return pDmeDag; for ( int i = pDmeDag->GetChildCount() - 1; i >= 0; --i ) { depthFirstStack.Push( pDmeDag->GetChild( i ) ); } } return NULL; } //----------------------------------------------------------------------------- // Adds the joint to the specified model by finding the common ancestor // Duplicates the required hierarchy of the source joint //----------------------------------------------------------------------------- CDmeDag *CDmMeshUtils::AddJointToModel( CDmeModel *pDstModel, CDmeDag *pSrcJoint ) { if ( !pDstModel || !pSrcJoint || CastElement< CDmeModel >( pSrcJoint ) ) return NULL; // See if the dag is already part of the DmeModel if ( pDstModel->GetJointIndex( pSrcJoint ) >= 0 ) return pSrcJoint; // Find it by name in the DmeModel hierarchy CDmeDag *pDstDag = FindDagByNameInHierarchy( pDstModel, pSrcJoint->GetName() ); if ( pDstDag ) { // Same named joint was already in hierarchy just not in model // TODO: Warn if joint is not equal to pSrcJoint pDstModel->AddJoint( pDstDag ); // TODO: This is a big hammer, make 'AddJoint' add the base state for just the added joint pDstModel->CaptureJointsToBaseState( "bind" ); pDstModel->Resolve(); return pDstDag; } // Walk backwards until we find an ancestor of pSrcJoint that exists in the DstModel // or until we get to the DmeModel ancestor of the pSrcJoint CDmeDag *pDstParent = pDstModel; CUtlStack< CDmeDag * > dupeList; dupeList.Push( pSrcJoint ); for ( CDmeDag *pSrcParent = pSrcJoint->GetParent(); pSrcParent; pSrcParent = pSrcParent->GetParent() ) { // If we've gotten to a CDmeModel then leave the pDstParent as the pDstModel if ( CastElement< CDmeModel >( pSrcParent ) ) break; CDmeDag *pTmpDag = FindDagByNameInHierarchy( pDstModel, pSrcParent->GetName() ); if ( pTmpDag ) { pDstParent = pTmpDag; break; } dupeList.Push( pSrcParent ); } CDmeDag *pSrcDag = NULL; matrix3x4a_t mAbs; while ( dupeList.Count() ) { dupeList.Pop( pSrcDag ); if ( !pSrcDag ) { Error( "NULL DmeDag in AddJoint\n" ); break; } CDmeDag *pDstDag = pDstModel->AddJoint( pSrcDag->GetName(), pDstParent ); if ( !pDstDag ) { Error( "Couldn't AddJoint( %s )\n", pSrcDag->GetName() ); break; } // Only settings the Abs transform... not copyting any other dynamic attributes pSrcDag->GetAbsTransform( mAbs ); pDstDag->SetAbsTransform( mAbs ); pDstParent = pDstDag; } pDstModel->CaptureJointsToBaseState( "bind" ); return pDstParent; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmMeshUtils::MergeMeshAndSkeleton( CDmeMesh *pDstMesh, CDmeMesh *pSrcMesh ) { if ( !pDstMesh || !pSrcMesh ) return false; CDmeModel *pDstModel = GetDmeModelFromMesh( pDstMesh ); CDmeModel *pSrcModel = GetDmeModelFromMesh( pSrcMesh ); CDmeVertexData *pSrcBind = pSrcMesh->GetBindBaseState(); if ( !pDstModel || !pSrcModel || !pSrcBind || !pSrcBind->HasSkinningData() ) return false; CUtlMap< int, int > jointRemap( CDefOps< int >::LessFunc ); const FieldIndex_t nSrcJointIndex = pSrcBind->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); if ( nSrcJointIndex < 0 ) { Warning( "No Joints!\n" ); return false; } const CDmrArrayConst< int > srcJointIndices = pSrcBind->GetVertexData( nSrcJointIndex ); for ( int i = 0; i < srcJointIndices.Count(); ++i ) { const int nSrcIndex = srcJointIndices[i]; if ( jointRemap.IsValidIndex( jointRemap.Find( nSrcIndex ) ) ) continue; CDmeDag *pSrcJoint = pSrcModel->GetJoint( nSrcIndex ); if ( !pSrcJoint ) continue; CDmeDag *pDstJoint = pDstModel->GetJoint( pSrcJoint->GetName() ); if ( !pDstJoint ) { pDstJoint = AddJointToModel( pDstModel, pSrcJoint ); } if ( pDstJoint ) { const int nDstIndex = pDstModel->GetJointIndex( pDstJoint ); Assert( nDstIndex >= 0 ); jointRemap.InsertOrReplace( nSrcIndex, nDstIndex ); continue; } else { Error( "Can't Add Joint: %s\n", pSrcJoint->GetName() ); return false; } } Merge( pSrcMesh, pDstMesh, -1, &jointRemap ); // If there are any other base states on the mesh, just copy the bind base state overtop CDmeVertexData *pBindState = pDstMesh->GetBindBaseState(); if ( pBindState ) { const int nBaseStateCount = pDstMesh->BaseStateCount(); for ( int i = 0; i < nBaseStateCount; ++i ) { CDmeVertexData *pBaseState = pDstMesh->GetBaseState( i ); if ( pBindState != pBaseState ) { pBindState->CopyTo( pBaseState ); } } } return true; }