/* = NULL */ // //============================================================================= // Valve includes #include "filesystem.h" #include "fbxsystem/ifbxsystem.h" #include "fbxutils/dmfbxserializer.h" #include "movieobjects/dmedccmakefile.h" #include "movieobjects/dmeanimationlist.h" #include "movieobjects/dmeaxissystem.h" #include "movieobjects/dmechannel.h" #include "movieobjects/dmeclip.h" #include "movieobjects/dmecombinationoperator.h" #include "movieobjects/dmedag.h" #include "movieobjects/dmeexporttags.h" #include "movieobjects/dmefaceset.h" #include "movieobjects/dmelog.h" #include "movieobjects/dmematerial.h" #include "movieobjects/dmemesh.h" #include "movieobjects/dmemodel.h" #include "resourcefile/resourcedictionary.h" #ifdef SOURCE2 #include "resourcesystem/resourcehandletypes.h" #endif #include "tier1/fmtstr.h" // Last include #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- class CDmeFbxAxisSystem : public FbxAxisSystem { public: CDmeFbxAxisSystem( CDmeAxisSystem::Axis_t eUpAxis, CDmeAxisSystem::ForwardParity_t eForwardParity, CDmeAxisSystem::CoordSys_t eCoordSys ) : FbxAxisSystem( static_cast< EUpVector >( eUpAxis ), static_cast< EFrontVector >( eForwardParity ), static_cast< ECoordSystem >( eCoordSys ) ) { } CDmeFbxAxisSystem( EUpVector eUpVector, EFrontVector eFrontVector, ECoordSystem eCoordSystem ) : FbxAxisSystem( eUpVector, eFrontVector, eCoordSystem ) { } CDmeFbxAxisSystem( const FbxAxisSystem &rhs ) { FbxAxisSystem::operator=( rhs ); } CDmeFbxAxisSystem &operator=( const FbxAxisSystem &rhs ) { FbxAxisSystem::operator=( rhs ); return *this; } CUtlString GetAxes() const { int nUSign = 0; const int nU = GetUpAxis( nUSign ); int nFSign = 0; const int nF = GetFrontAxis( nFSign ); int nLSign = 0; const int nL = GetLeftAxis( nLSign ); const char *szAxis[] = { "X", "Y", "Z" }; return CUtlString( CFmtStr( "U: %s%s F: %s%s L: %s%s", nUSign < 0 ? "-" : " ", szAxis[nU], nFSign < 0 ? "-" : " ", szAxis[nF], nLSign < 0 ? "-" : " ", szAxis[nL] ).Get() ); } CUtlString PrintRot( const FbxAxisSystem &from ) const { FbxMatrix mFbx; GetConversionMatrix( from, mFbx ); FbxVector4 vT; FbxQuaternion qR; FbxVector4 vSh; FbxVector4 vSc; double flSign; mFbx.GetElements( vT, qR, vSh, vSc, flSign ); FbxVector4 vR = qR.DecomposeSphericalXYZ(); mFbx = mFbx.Inverse(); mFbx.GetElements( vT, qR, vSh, vSc, flSign ); FbxVector4 vI = qR.DecomposeSphericalXYZ(); return CUtlString( CFmtStr( "F < %6.2f %6.2f %6.2f > I < %6.2f %6.2f %6.2f >\n", RAD2DEG( vR[0] ), RAD2DEG( vR[1] ), RAD2DEG( vR[2] ), RAD2DEG( vI[0] ), RAD2DEG( vI[1] ), RAD2DEG( vI[2] ) ).Get() ); } void GetConversionRotation( RadianEuler &e, const FbxAxisSystem &from ) { FbxMatrix mFbx; GetConversionMatrix( from, mFbx ); mFbx = mFbx.Inverse(); FbxVector4 vT; FbxQuaternion qR; FbxVector4 vSh; FbxVector4 vSc; double flSign; mFbx.GetElements( vT, qR, vSh, vSc, flSign ); FbxVector4 vR = qR.DecomposeSphericalXYZ(); e.x = vR[0]; e.y = vR[1]; e.z = vR[2]; } int GetUpAxis( int &nSign ) const { nSign = mUpVector.mSign; return mUpVector.mAxis; }; int GetFrontAxis( int &nSign ) const { nSign = mFrontVector.mSign; return mFrontVector.mAxis; }; int GetLeftAxis( int &nSign ) const { nSign = mCoorSystem.mSign; return mCoorSystem.mAxis; }; // static void Validate(); }; #if 0 //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmeFbxAxisSystem::Validate() { const int fbxUp[] = { -FbxAxisSystem::eXAxis, -FbxAxisSystem::eYAxis, -FbxAxisSystem::eZAxis, FbxAxisSystem::eXAxis, FbxAxisSystem::eYAxis, FbxAxisSystem::eZAxis }; const int fbxParity[] = { -FbxAxisSystem::eParityEven, -FbxAxisSystem::eParityOdd, FbxAxisSystem::eParityEven, FbxAxisSystem::eParityOdd }; const int dmxUp[] = { CDmeAxisSystem::AS_AXIS_NX, CDmeAxisSystem::AS_AXIS_NY, CDmeAxisSystem::AS_AXIS_NZ, CDmeAxisSystem::AS_AXIS_X, CDmeAxisSystem::AS_AXIS_Y, CDmeAxisSystem::AS_AXIS_Z }; const int dmxParity[] = { CDmeAxisSystem::AS_PARITY_NEVEN, CDmeAxisSystem::AS_PARITY_NODD, CDmeAxisSystem::AS_PARITY_EVEN, CDmeAxisSystem::AS_PARITY_ODD }; COMPILE_TIME_ASSERT( ARRAYSIZE( fbxUp ) == ARRAYSIZE( dmxUp ) ); COMPILE_TIME_ASSERT( ARRAYSIZE( fbxParity ) == ARRAYSIZE( dmxParity ) ); int nOkCount = 0; int nBadCount = 0; for ( int i = 0; i < ARRAYSIZE( fbxUp ); ++i ) { const int nFbxAU = fbxUp[i]; const int nDmxAU = dmxUp[i]; Assert( nFbxAU == nDmxAU ); for ( int j = 0; j < ARRAYSIZE( fbxParity ); ++j ) { const int nFbxAP = fbxParity[j]; const int nDmxAP = dmxParity[j]; Assert( nFbxAP == nDmxAP ); // A = FROM CDmeFbxAxisSystem fA( static_cast< FbxAxisSystem::EUpVector >( nFbxAU ), static_cast< FbxAxisSystem::EFrontVector >( nFbxAP ), FbxAxisSystem::eRightHanded ); const CDmeAxisSystem::Axis_t eAU = static_cast< CDmeAxisSystem::Axis_t >( nDmxAU ); const CDmeAxisSystem::ForwardParity_t eAF = static_cast< CDmeAxisSystem::ForwardParity_t >( nDmxAP ); const CDmeAxisSystem::CoordSys_t eAC = CDmeAxisSystem::AS_RIGHT_HANDED; for ( int k = 0; k < ARRAYSIZE( fbxUp ); ++k ) { const int nFbxBU = fbxUp[k]; const int nDmxBU = dmxUp[k]; Assert( nFbxBU == nDmxBU ); for ( int l = 0; l < ARRAYSIZE( fbxParity ); ++l ) { const int nFbxBP = fbxParity[l]; const int nDmxBP = dmxParity[l]; Assert( nFbxBP == nDmxBP ); // B = TO CDmeFbxAxisSystem fB( static_cast< FbxAxisSystem::EUpVector >( nFbxBU ), static_cast< FbxAxisSystem::EFrontVector >( nFbxBP ), FbxAxisSystem::eRightHanded ); const CDmeAxisSystem::Axis_t eBU = static_cast< CDmeAxisSystem::Axis_t >( nDmxBU ); const CDmeAxisSystem::ForwardParity_t eBF = static_cast< CDmeAxisSystem::ForwardParity_t >( nDmxBP ); const CDmeAxisSystem::CoordSys_t eBC = CDmeAxisSystem::AS_RIGHT_HANDED; RadianEuler eFbx; fB.GetConversionRotation( eFbx, fA ); if ( i == k && j == l ) { Assert( RadianEulersAreEqual( eFbx, RadianEuler( 0.0f, 0.0f, 0.0f ), 1.0e-6 ) ); } matrix3x4a_t mDmx; CDmeAxisSystem::GetConversionMatrix( mDmx, eBU, eBF, eBC, eAU, eAF, eAC ); RadianEuler eDmx; MatrixAngles( mDmx, eDmx ); if ( i == k && j == l ) { Assert( RadianEulersAreEqual( eDmx, RadianEuler( 0.0f, 0.0f, 0.0f ), 1.0e-6 ) ); } // Account for FBX/DMX differences in converting matrix to Euler Quaternion qFbx = eFbx; QuaternionNormalize( qFbx ); Quaternion qDmx = eDmx; QuaternionNormalize( qDmx ); eFbx = qFbx; eDmx = qDmx; if ( !QuaternionsAreEqual( qFbx, qDmx, 1.0e-6 ) ) { Msg( " * FBX \"%s\" -> \"%s\": %6.2f %6.2f %6.2f\n", fA.GetAxes().Get(), fB.GetAxes().Get(), RAD2DEG( eFbx.x ), RAD2DEG( eFbx.y ), RAD2DEG( eFbx.z ) ); Msg( " * DMX \"%s\" -> \"%s\": %6.2f %6.2f %6.2f\n", CDmeAxisSystem::GetAxisString( eAU, eAF, eAC ).Get(), CDmeAxisSystem::GetAxisString( eBU, eBF, eBC ).Get(), RAD2DEG( eDmx.x ), RAD2DEG( eDmx.y ), RAD2DEG( eDmx.z ) ); Msg( "\n" ); ++nBadCount; } else { ++nOkCount; } } } } } Msg( " * OK: %4d Bad: %d\n", nOkCount, nBadCount ); } #endif // 0 //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- const char *FbxImporterGetErrorString( FbxImporter *pFbxImporter ) { #if FBXSDK_VERSION_MAJOR >= 2014 return pFbxImporter->GetStatus().GetErrorString(); #else // FBXSDK_VERSION_MAJOR return pFbxImporter->GetLastErrorString(); #endif // FBXSDK_VERSION_MAJOR } //============================================================================= // // CDmFbxSerializer // //============================================================================= //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmFbxSerializer::CDmFbxSerializer() : m_nOptVerbosity( 0 ) , m_bOptUnderscoreForCorrectors( false ) , m_bAnimation( false ) , m_bReturnDmeModel( false ) , m_flOptScale( 1.0f ) { // Initialize Axis System To Maya Y Up CDmeAxisSystem::GetPredefinedAxisSystem( m_eOptUpAxis, m_eOptForwardParity, m_eCoordSys, CDmeAxisSystem::AS_MAYA_YUP ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmFbxSerializer::~CDmFbxSerializer() { } //----------------------------------------------------------------------------- // Common function both ReadFBX & Unserialize can call //----------------------------------------------------------------------------- CDmElement *CDmFbxSerializer::ReadFBX( const char *pszFilename ) { FbxManager *pFbxManager = GetFbxManager(); if ( !pFbxManager ) return NULL; DmFileId_t nDmFileId = g_pDataModel->FindOrCreateFileId( pszFilename ); if ( nDmFileId == DMFILEID_INVALID ) { Warning( "Warning! Couldn't create DmFileId_t for \"%s\"\n", pszFilename ); return NULL; } FbxTime::EMode eFbxTimeMode = FbxTime::eFrames30; FbxScene *pFbxScene = LoadFbxScene( eFbxTimeMode, pszFilename ); if ( !pFbxScene ) return NULL; if ( !FloatsAreEqual( m_flOptScale, 1.0f, 1.0e-4 ) ) { FbxSystemUnit fbxSystemUnit( 1.0 / m_flOptScale ); fbxSystemUnit.ConvertScene( pFbxScene ); } CDmeFbxAxisSystem fromAxisSystem = pFbxScene->GetGlobalSettings().GetAxisSystem(); CDmeFbxAxisSystem toAxisSystem( m_eOptUpAxis, m_eOptForwardParity, m_eCoordSys ); toAxisSystem.ConvertScene( pFbxScene ); CDmeAxisSystem::Axis_t eUpAxis; CDmeAxisSystem::ForwardParity_t eForwardParity; CDmeAxisSystem::CoordSys_t eCoordSys; { // nU [ -1, 1 ], eU [ 1, 2, 3 ] int nU = 0; const int eU = fromAxisSystem.GetUpVector( nU ); eUpAxis = static_cast< CDmeAxisSystem::Axis_t >( nU * eU ); // nF [ -1, 1 ], eF [ 1, 2 ] int nF = 0; const int eF = fromAxisSystem.GetFrontVector( nF ); eForwardParity = static_cast< CDmeAxisSystem::ForwardParity_t >( nF * eF ); // GetCoordSys() [ 0, 1 ] eCoordSys = static_cast< CDmeAxisSystem::CoordSys_t >( fromAxisSystem.GetCoorSystem() ); } char szFileBase[ MAX_PATH ] = ""; V_FileBase( pszFilename, szFileBase, ARRAYSIZE( szFileBase ) ); CDmElement *pDmeRoot = NULL; CDmeModel *pDmeModel = CreateElement< CDmeModel >( szFileBase, nDmFileId ); #ifdef SOURCE2 pDmeModel->SetAxisSystem( m_eOptUpAxis, m_eOptForwardParity, m_eCoordSys ); #endif if ( !m_bAnimation && m_bReturnDmeModel ) { pDmeRoot = pDmeModel; } else { pDmeRoot = CreateElement< CDmElement >( "root", nDmFileId ); pDmeRoot->SetValue( "skeleton", pDmeModel ); if ( !m_bAnimation ) { // Don't set "model" if animation only... Studiomdl can't handle it pDmeRoot->SetValue( "model", pDmeModel ); } } g_pDataModel->SetFileRoot( nDmFileId, pDmeRoot->GetHandle() ); CDmeDCCMakefile *pDmeMakefile = CreateElement< CDmeDCCMakefile >( "makefile", nDmFileId ); pDmeRoot->SetValue( pDmeMakefile->GetName(), pDmeMakefile ); // DMX sources are relative to the DMX file char szFullPath[ MAX_PATH ]; if ( g_pFullFileSystem->RelativePathToFullPath( pszFilename, NULL, szFullPath, ARRAYSIZE( szFullPath ) ) ) { pDmeMakefile->AddSource< CDmeSource >( szFullPath ); } else { pDmeMakefile->AddSource< CDmeSource >( pszFilename ); } FbxDocumentInfo *pFbxDocumentInfo = pFbxScene->GetSceneInfo(); if ( pFbxDocumentInfo ) { FbxString sUrl = pFbxDocumentInfo->LastSavedUrl.Get(); FbxString sOrigFilename = pFbxDocumentInfo->Original_FileName.Get(); char szUrl[MAX_PATH] = {}; char szOrigFilename[MAX_PATH] = {}; V_FixupPathName( szUrl, ARRAYSIZE( szUrl ), sUrl.Buffer() ); V_FixSlashes( szUrl, '/' ); V_FixupPathName( szOrigFilename, ARRAYSIZE( szOrigFilename ), sOrigFilename.Buffer() ); V_FixSlashes( szOrigFilename, '/' ); if ( V_strcmp( szUrl, szOrigFilename ) != 0 ) { #ifdef SOURCE2 char szRelativePath[MAX_PATH] = {}; GenerateResourceNameFromFileName( sOrigFilename.Buffer(), szRelativePath, ARRAYSIZE( szRelativePath ) ); if ( V_strlen( szRelativePath ) > 0 && GenerateStandardFullPathForResourceName( szRelativePath, RESOURCE_PATH_CONTENT, szFullPath, ARRAYSIZE( szFullPath ) ) ) { pDmeMakefile->AddSource< CDmeSource >( szFullPath ); } else #endif { pDmeMakefile->AddSource< CDmeSource >( sOrigFilename.Buffer() ); } } } CDmeExportTags *pDmeExportTags = CreateElement< CDmeExportTags >( "exportTags", nDmFileId ); pDmeExportTags->Init( "fbx2dmx", g_pFbx->GetFbxManager()->GetVersion() ); pDmeExportTags->SetValue( "cmdLine", CommandLine()->GetCmdLine() ); { char szCurrentDirectory[ MAX_PATH ]; Plat_getwd( szCurrentDirectory, ARRAYSIZE( szCurrentDirectory ) ); V_FixSlashes( szCurrentDirectory, '/' ); pDmeExportTags->SetValue( "pwd", szCurrentDirectory ); } pDmeRoot->SetValue( pDmeExportTags->GetName(), pDmeExportTags ); CDmAttribute *pRootAttr = pDmeModel->AddAttribute( "__rootElement", AT_ELEMENT ); if ( pRootAttr ) { pRootAttr->AddFlag( FATTRIB_DONTSAVE ); pRootAttr->SetValue( pDmeRoot ); } FbxToDmxMap_t fbxToDmxMap( CDefOps< FbxToDmxMap_t::KeyType_t >::LessFunc ); // Don't create a DmeDag for the root node FbxNode *pFbxRootNode = pFbxScene->GetRootNode(); if ( Verbose2() ) { Msg( " * Skeleton\n" ); } for ( int i = 0; i < pFbxRootNode->GetChildCount(); ++i ) { LoadModelAndSkeleton_R( fbxToDmxMap, pDmeModel, pDmeModel, pFbxRootNode->GetChild( i ), m_bAnimation, 0 ); } pDmeModel->CaptureJointsToBaseState( "bind" ); if ( m_bAnimation ) { LoadAnimation( pDmeRoot, pDmeModel, fbxToDmxMap, pFbxScene, pFbxRootNode, eFbxTimeMode ); } else { for ( int i = 0; i < pFbxRootNode->GetChildCount(); ++i ) { SkinMeshes_R( fbxToDmxMap, pDmeModel, pFbxRootNode->GetChild( i ) ); AddBlendShapes_R( fbxToDmxMap, pDmeRoot, pFbxRootNode->GetChild( i ) ); } } pFbxScene->Destroy(); pDmeModel->ConvertToAxisSystem( CDmeAxisSystem::AS_VALVE_ENGINE ); return pDmeRoot; } //----------------------------------------------------------------------------- // Feed the CDmElement returned by ReadFBX to see if there were non-fatal conversion errors which the user should be informed about //----------------------------------------------------------------------------- bool CDmFbxSerializer::HasConversionErrors( CDmElement *pDmRoot ) { if ( !pDmRoot ) return false; CDmAttribute *pConversionErrorsAttr = pDmRoot->GetAttribute( "conversionErrors", AT_STRING_ARRAY ); if ( !pConversionErrorsAttr ) return false; return ( CDmrStringArrayConst( pConversionErrorsAttr ).Count() > 0 ); } //----------------------------------------------------------------------------- // Feed the CDmElement returned by ReadFBX to see if there were non-fatal conversion errors which the user should be informed about, they are stored in pConversionErrors, if no errors, pConversionErrors is not touched //----------------------------------------------------------------------------- void CDmFbxSerializer::GetConversionErrors( CDmElement *pDmRoot, CUtlVector< CUtlString > *pConversionErrors ) { if ( !pDmRoot || !pConversionErrors || !HasConversionErrors( pDmRoot ) ) return; CDmAttribute *pConversionErrorsAttr = pDmRoot->GetAttribute( "conversionErrors", AT_STRING_ARRAY ); if ( !pConversionErrorsAttr ) return; CDmrStringArrayConst conversionErrors( pConversionErrorsAttr ); for ( int i = 0; i < conversionErrors.Count(); ++i ) { pConversionErrors->AddToTail( conversionErrors[i] ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- FbxScene *CDmFbxSerializer::LoadFbxScene( FbxTime::EMode &eFbxTimeMode, const char *pszFilename ) { FbxManager *pFbxManager = GetFbxManager(); if ( !pFbxManager ) return NULL; if ( Verbose1() ) { Msg( "Reading FBX: %s\n", pszFilename ); } // Get the file version number generate by the FBX SDK. int nSDKMajor = 0; int nSDKMinor = 0; int nSDKRevision = 0; FbxManager::GetFileFormatVersion( nSDKMajor, nSDKMinor, nSDKRevision ); if ( Verbose2() ) { Msg( "FBX file version %d.%d.%d - SDK\n", nSDKMajor, nSDKMinor, nSDKRevision ); } FbxIOSettings *pFbxIOSettings = FbxIOSettings::Create( pFbxManager, IOSROOT ); FbxImporter *pFbxImporter = FbxImporter::Create( pFbxManager, "" ); pFbxImporter->ParseForGlobalSettings( true ); // Initialize the importer by providing a filename. const bool bImportStatus = pFbxImporter->Initialize( pszFilename, -1, pFbxIOSettings ); int nFileMajor = 0; int nFileMinor = 0; int nFileRevision = 0; pFbxImporter->GetFileVersion( nFileMajor, nFileMinor, nFileRevision ); if ( !bImportStatus ) { Warning( "Warning! Couldn't import specified file as FBX \"%s\": %s\n", pszFilename, pFbxImporter->GetStatus().GetErrorString() ); if ( pFbxImporter->GetStatus().GetCode() == FbxStatus::eInvalidFileVersion ) { Warning( "Warning! FBX file version mismatch SDK %d.%d.%d vs File %d.%d.%d\n", nSDKMajor, nSDKMinor, nSDKRevision, nFileMajor, nFileMinor, nFileRevision ); } return NULL; } if ( pFbxImporter->IsFBX() ) { if ( Verbose2() ) { Msg( "FBX file version %d.%d.%d - %s\n", nFileMajor, nFileMinor, nFileRevision, pszFilename ); // From this point, it is possible to access animation stack information without // the expense of loading the entire file. Msg( "Animation Stack Information\n"); const int nAnimStackCount = pFbxImporter->GetAnimStackCount(); Msg( " Number of Animation Stacks: %d\n", nAnimStackCount ); Msg( " Current Animation Stack: \"%s\"\n", pFbxImporter->GetActiveAnimStackName().Buffer() ); Msg( "\n" ); for ( int i = 0; i < nAnimStackCount; ++i ) { FbxTakeInfo *pFbxTakeInfo = pFbxImporter->GetTakeInfo( i ); Msg( " Animation Stack %d\n", i ); Msg( " Name: \"%s\"\n", pFbxTakeInfo->mName.Buffer() ); Msg( " Description: \"%s\"\n", pFbxTakeInfo->mDescription.Buffer() ); // Change the value of the import name if the animation stack should be imported // under a different name. Msg( " Import Name: \"%s\"\n", pFbxTakeInfo->mImportName.Buffer() ); // Set the value of the import state to false if the animation stack should be not // be imported. Msg( " Import State: %s\n", pFbxTakeInfo->mSelect ? "true" : "false" ); Msg( "\n"); } } // Set the import states. By default, the import states are always set to // true. The code below shows how to change these states. pFbxIOSettings->SetBoolProp( IMP_FBX_MATERIAL, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_TEXTURE, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_LINK, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_SHAPE, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_GOBO, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_ANIMATION, true ); pFbxIOSettings->SetBoolProp( IMP_FBX_GLOBAL_SETTINGS, true ); } FbxScene *pFbxScene = FbxScene::Create( pFbxManager, "" ); if ( pFbxScene ) { // Import the scene. bool bStatus = pFbxImporter->Import( pFbxScene ); if ( bStatus == false && pFbxImporter->GetStatus().GetCode() == FbxStatus::ePasswordError ) { Warning( "Warning! Password protected FBX files unsupported\n" ); pFbxScene->Destroy(); pFbxScene = NULL; /* TODO: Handle password protected FBX files... Msg( "Please enter password: "); char szPassword[1024]; szPassword[0] = '\0'; FBXSDK_CRT_SECURE_NO_WARNING_BEGIN scanf( "%s", szPassword ); FBXSDK_CRT_SECURE_NO_WARNING_END FbxString lString(szPassword); pFbxIOSettings->SetStringProp( IMP_FBX_PASSWORD, lString ); pFbxIOSettings->SetBoolProp( IMP_FBX_PASSWORD_ENABLE, true ); bStatus = pFbxImporter->Import( pFbxScene ); if ( bStatus == false && pFbxImporter->GetLastErrorID() == FbxIOBase::ePasswordError ) { Msg( "\nPassword is wrong, import aborted.\n" ); pFbxScene->Destroy(); pFbxScene = NULL; } */ } eFbxTimeMode = FbxTime::eFrames30; if ( !pFbxImporter->GetFrameRate( eFbxTimeMode ) ) { eFbxTimeMode = FbxTime::eFrames30; } // Destroy the importer. pFbxImporter->Destroy(); } else { Warning( "Warning! Couldn't create FbxScene\n" ); } return pFbxScene; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::LoadModelAndSkeleton_R( FbxToDmxMap_t &fbxToDmxMap, CDmeModel *pDmeModel, CDmeDag *pDmeDagParent, FbxNode *pFbxNode, bool bAnimation, int nDepth ) const { FbxString sIndent; for ( int i = 0; i < nDepth; ++i ) { sIndent += " "; } CDmeDag *pDmeDag = NULL; const char *pszFbxType = NULL; const char *pszDmeType = NULL; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( pFbxNodeAttribute ) { const FbxNodeAttribute::EType nAttributeType = pFbxNodeAttribute->GetAttributeType(); switch ( nAttributeType ) { case FbxNodeAttribute::eNull: pszFbxType = "Transform"; if ( pFbxNode->GetDstObjectCount( FbxCluster::ClassId ) > 0 ) { pDmeDag = FbxNodeToDmeDag( pDmeDagParent, pFbxNode, "DmeJoint" ); } else { pDmeDag = FbxNodeToDmeDag( pDmeDagParent, pFbxNode, "DmeDag" ); } break; case FbxNodeAttribute::eSkeleton: pszFbxType = "Skeleton"; pDmeDag = FbxNodeToDmeDag( pDmeDagParent, pFbxNode, "DmeJoint" ); break; case FbxNodeAttribute::eMesh: { pszFbxType = "Mesh"; pszDmeType = "DmeMesh"; FbxMatrix mScale; pDmeDag = FbxNodeToDmeDag( pDmeDagParent, pFbxNode, "DmeDag", &mScale ); if ( !bAnimation ) { FbxShapeToDmeMesh( pDmeDag, pFbxNode, mScale ); } } break; default: Warning( "Warning! Ignoring Unsupported FBX Node Attribute Type: %s.%s(%s)\n", pFbxNode->GetName(), pFbxNodeAttribute->GetName(), pFbxNodeAttribute->GetTypeName() ); break; } } if ( Verbose2() && pDmeDag && pszFbxType ) { Msg( "%s + %-8s %s\n", sIndent.Buffer(), pszDmeType ? pszDmeType : pDmeDag->GetTypeString(), pDmeDag->GetName() ); } if ( pDmeDag ) { pDmeModel->AddJoint( pDmeDag ); fbxToDmxMap.Insert( pFbxNode, pDmeDag ); for ( int i = 0; i < pFbxNode->GetChildCount(); ++i ) { LoadModelAndSkeleton_R( fbxToDmxMap, pDmeModel, pDmeDag, pFbxNode->GetChild( i ), bAnimation, nDepth + 1 ); } } } //----------------------------------------------------------------------------- // Converts an FbxMatrix to a Valve matrix3x4_t (transpose) //----------------------------------------------------------------------------- inline void MatrixFbxToValve( matrix3x4_t &mValve, const FbxMatrix &mFbx ) { mValve[0][0] = mFbx[0][0]; mValve[0][1] = mFbx[1][0]; mValve[0][2] = mFbx[2][0]; mValve[0][3] = mFbx[3][0]; mValve[1][0] = mFbx[0][1]; mValve[1][1] = mFbx[1][1]; mValve[1][2] = mFbx[2][1]; mValve[1][3] = mFbx[3][1]; mValve[2][0] = mFbx[0][2]; mValve[2][1] = mFbx[1][2]; mValve[2][2] = mFbx[2][2]; mValve[2][3] = mFbx[3][2]; } //----------------------------------------------------------------------------- // Converts a Valve matrix3x4_t to an FbxMatrix (transpose) //----------------------------------------------------------------------------- inline void MatrixValveToFbx( FbxMatrix &mFbx, const matrix3x4_t &mValve ) { mFbx[0][0] = mValve[0][0]; mFbx[0][1] = mValve[1][0]; mFbx[0][2] = mValve[2][0]; mFbx[3][0] = 0.0; mFbx[1][0] = mValve[0][1]; mFbx[1][1] = mValve[1][1]; mFbx[1][2] = mValve[2][1]; mFbx[3][1] = 0.0; mFbx[2][0] = mValve[0][2]; mFbx[2][1] = mValve[1][2]; mFbx[2][2] = mValve[2][2]; mFbx[3][2] = 0.0; mFbx[3][0] = mValve[0][3]; mFbx[3][1] = mValve[1][3]; mFbx[3][2] = mValve[2][3]; mFbx[3][3] = 1.0; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmeDag *CDmFbxSerializer::FbxNodeToDmeDag( CDmeDag *pDmeDagParent, FbxNode *pFbxNode, const char *pszDmeType, FbxMatrix *pmOutScale /* = NULL */ ) const { const bool bRoot = pDmeDagParent->IsA( CDmeModel::GetStaticTypeSymbol() ); const FbxAMatrix &mFbxWorld = pFbxNode->EvaluateGlobalTransform(); const FbxVector4 vFbxTranslate = mFbxWorld.GetT(); const FbxQuaternion qFbxRotate = mFbxWorld.GetQ(); Assert( vFbxTranslate[3] == 0.0 || vFbxTranslate[3] == 1.0 ); CUtlString sName; GetName( sName, pFbxNode ); Vector vDmeTranslate( vFbxTranslate[0], vFbxTranslate[1], vFbxTranslate[2] ); Quaternion qDmeRotate( qFbxRotate[0], qFbxRotate[1], qFbxRotate[2], qFbxRotate[3] ); DmElementHandle_t hElement = g_pDataModel->CreateElement( pszDmeType, sName.String(), pDmeDagParent->GetFileId() ); CDmeDag *pDmeDag = CastElement< CDmeDag >( g_pDataModel->GetElement( hElement ) ); CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); pDmeTransform->SetName( pDmeDag->GetName() ); if ( !bRoot ) { matrix3x4_t mDmeParentWorld; pDmeDagParent->GetAbsTransform( mDmeParentWorld ); matrix3x4_t mDmeParentWorldInverse; MatrixInvert( mDmeParentWorld, mDmeParentWorldInverse ); matrix3x4_t mDmeWorld; AngleMatrix( RadianEuler( qDmeRotate ), vDmeTranslate, mDmeWorld ); matrix3x4_t mDmeLocal; MatrixMultiply( mDmeParentWorldInverse, mDmeWorld, mDmeLocal ); MatrixAngles( mDmeLocal, qDmeRotate, vDmeTranslate ); } else { CDmAttribute *pDmeRootNodeAttr = pDmeDag->AddAttribute( "__rootNode", AT_BOOL ); pDmeRootNodeAttr->AddFlag( FATTRIB_DONTSAVE ); pDmeRootNodeAttr->SetValue( bRoot ); } pDmeTransform->SetOrientation( qDmeRotate ); pDmeTransform->SetPosition( vDmeTranslate ); pDmeDagParent->AddChild( pDmeDag ); if ( pmOutScale ) { FbxMatrix mfWf = mFbxWorld; FbxMatrix mfWv; { matrix3x4_t mvWv; // mvWv = World Matrix3x4 Valve pDmeDag->GetAbsTransform( mvWv ); MatrixValveToFbx( mfWv, mvWv ); } const FbxMatrix mfWv_ = mfWv.Inverse(); *pmOutScale = mfWv_ * mfWf; } return pDmeDag; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool AddPositionData( CDmeVertexData *pDmeVertexData, const CUtlVector< int > &nIndices, const FbxMesh *pFbxMesh, const FbxMatrix &mScale ) { if ( !pFbxMesh || nIndices.Count() <= 0 ) return false; const FbxVector4 *pvFbxData = pFbxMesh->GetControlPoints(); const int nDataCount = pFbxMesh->GetControlPointsCount(); CUtlVector< Vector > dmxData; dmxData.SetCount( nDataCount ); for ( int i = 0; i < dmxData.Count(); ++i ) { Vector &vDmxData = dmxData[i]; const FbxVector4 vFbxData = mScale.MultNormalize( pvFbxData[i] ); vDmxData.x = vFbxData[0]; vDmxData.y = vFbxData[1]; vDmxData.z = vFbxData[2]; Assert( vFbxData[3] == 0 || vFbxData[3] == 1 ); } const FieldIndex_t nFieldIndex = pDmeVertexData->CreateField( CDmeVertexDataBase::FIELD_POSITION ); pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), AT_VECTOR3, dmxData.Base() ); pDmeVertexData->AddVertexIndices( nIndices.Count() ); pDmeVertexData->SetVertexIndices( nFieldIndex, 0, nIndices.Count(), nIndices.Base() ); return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void AddUVData( CDmeVertexData *pDmeVertexData, const CUtlVector< int > &nIndices, const FbxGeometryElementUV *pFbxElement, int nSemanticIndex ) { if ( !pFbxElement || nIndices.Count() <= 0 ) return; const FbxLayerElementArrayTemplate< FbxVector2 > &fbxData = pFbxElement->GetDirectArray(); const int nDataCount = fbxData.GetCount(); CUtlVector< Vector2D > dmxData; dmxData.SetCount( nDataCount ); for ( int i = 0; i < dmxData.Count(); ++i ) { Vector2D &vDmxData = dmxData[i]; const FbxVector2 vFbxData = fbxData[i]; vDmxData.x = vFbxData[0]; vDmxData.y = vFbxData[1]; } FieldIndex_t nFieldIndex = -1; if ( nSemanticIndex == 0 ) { nFieldIndex = pDmeVertexData->CreateField( CDmeVertexDataBase::FIELD_TEXCOORD ); } #ifdef SOURCE2 else if ( nSemanticIndex == 1 ) { nFieldIndex = pDmeVertexData->CreateField( CDmeVertexDataBase::FIELD_TEXCOORD2 ); } #endif else { // No FIELD_TEXCOORD3, no method to get the standard field name without the semantic index // so magically we know it will be "texcoord$#" where # is the semantic index nFieldIndex = pDmeVertexData->CreateField< Vector2D >( CFmtStr( "texcoord$%d", nSemanticIndex ).Get() ); } pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), AT_VECTOR2, dmxData.Base() ); pDmeVertexData->SetVertexIndices( nFieldIndex, 0, nIndices.Count(), nIndices.Base() ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void AddNormalData( CDmeVertexData *pDmeVertexData, const CUtlVector< int > &nIndices, const FbxGeometryElementNormal *pFbxElement, const FbxMatrix &mScale ) { if ( !pFbxElement || nIndices.Count() <= 0 ) return; // Normals being FbxVector4's are a little weird but just using x, y, z is fine. // There seems to be breakage when loading an FBX 2014 file in FBX 2013 SDK because they // added a NormalsW layer which isn't actually put into the W component but this gets the // right x, y, z values regardless if w is just ignored const FbxLayerElementArrayTemplate< FbxVector4 > &fbxData = pFbxElement->GetDirectArray(); const int nDataCount = fbxData.GetCount(); CUtlVector< Vector > dmxData; dmxData.SetCount( nDataCount ); if ( mScale == FbxMatrix() ) { // If the scale is the identity matrix for ( int i = 0; i < dmxData.Count(); ++i ) { Vector &vDmxData = dmxData[i]; const FbxVector4 vFbxData = fbxData[i]; vDmxData.x = vFbxData[0]; vDmxData.y = vFbxData[1]; vDmxData.z = vFbxData[2]; } } else { const FbxMatrix mInvTranspose = mScale.Inverse().Transpose(); for ( int i = 0; i < dmxData.Count(); ++i ) { Vector &vDmxData = dmxData[i]; FbxVector4 vFbxData = mInvTranspose.MultNormalize( fbxData[i] ); vFbxData.Normalize(); vDmxData.x = vFbxData[0]; vDmxData.y = vFbxData[1]; vDmxData.z = vFbxData[2]; } } const FieldIndex_t nFieldIndex = pDmeVertexData->CreateField( CDmeVertexDataBase::FIELD_NORMAL ); pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), AT_VECTOR3, dmxData.Base() ); pDmeVertexData->SetVertexIndices( nFieldIndex, 0, nIndices.Count(), nIndices.Base() ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::AddColorData( CDmeVertexData *pDmeVertexData, const CUtlVector< int > &nIndices, const FbxGeometryElementVertexColor *pFbxElement ) const { if ( !pFbxElement || nIndices.Count() <= 0 ) return; const FbxLayerElementArrayTemplate< FbxColor > &fbxData = pFbxElement->GetDirectArray(); const int nDataCount = fbxData.GetCount(); if ( nDataCount <= 0 ) return; const char *pszName = pFbxElement->GetName(); const char *pszChannelName = pszName; bool bUseChannelName = false; DmAttributeType_t dmAttributeType = AT_COLOR; if ( StringHasPrefix( pszName, "export_" ) ) { pszChannelName = StringAfterPrefix( pszName, "export_" ); if ( V_strlen( pszChannelName ) > 0 ) { bUseChannelName = true; // Currently "foliageanimation" is a special case which gets converted to a Vector3 if ( !V_stricmp( pszChannelName, "foliageanimation" ) ) { dmAttributeType = AT_VECTOR3; pszChannelName = "foliageanimation"; // Make it lowercase for certain } } else { pszChannelName = pszName; } } FieldIndex_t nFieldIndex = -1; if ( bUseChannelName ) { nFieldIndex = pDmeVertexData->CreateField( pszChannelName, ValueTypeToArrayType( dmAttributeType ) ); } else { CFmtStr sFieldName; int nSemanticIndex = 0; for ( ;; ) { sFieldName.sprintf( "color$%d", nSemanticIndex ); FieldIndex_t nTmpField = pDmeVertexData->FindFieldIndex( sFieldName.Get() ); if ( nTmpField < 0 ) break; ++nSemanticIndex; } nFieldIndex = pDmeVertexData->CreateField( sFieldName.Get(), ValueTypeToArrayType( dmAttributeType ) ); } if ( nFieldIndex < 0 ) { AddConversionError( pDmeVertexData->GetFileId(), CFmtStr( "Couldn't convert color field: %s\n", pszName ).Get() ); return; } if ( dmAttributeType == AT_VECTOR3 ) { CUtlVector< Vector > dmxData; dmxData.SetCount( nDataCount ); for ( int i = 0; i < dmxData.Count(); ++i ) { Vector &vDmxData = dmxData[i]; const FbxColor vFbxData = fbxData[i]; vDmxData[0] = vFbxData[0]; vDmxData[1] = vFbxData[1]; vDmxData[2] = vFbxData[2]; // Alpha gets discarded when converting to Vector3, all FBX color channels are RGBA } pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), dmAttributeType, dmxData.Base() ); } else { Assert( dmAttributeType == AT_COLOR ); CUtlVector< Color > dmxData; dmxData.SetCount( nDataCount ); for ( int i = 0; i < dmxData.Count(); ++i ) { Color &vDmxData = dmxData[i]; const FbxColor vFbxData = fbxData[i]; vDmxData.SetColor( clamp( static_cast< uint8 >( floor( vFbxData[0] * 255.0 ) ), 0, 255 ), clamp( static_cast< uint8 >( floor( vFbxData[1] * 255.0 ) ), 0, 255 ), clamp( static_cast< uint8 >( floor( vFbxData[2] * 255.0 ) ), 0, 255 ), clamp( static_cast< uint8 >( floor( vFbxData[3] * 255.0 ) ), 0, 255 ) ); } pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), dmAttributeType, dmxData.Base() ); } pDmeVertexData->SetVertexIndices( nFieldIndex, 0, nIndices.Count(), nIndices.Base() ); } //----------------------------------------------------------------------------- // UV Color data is a special case for 3DSMax //----------------------------------------------------------------------------- void CDmFbxSerializer::AddUVColorData( CDmeVertexData *pDmeVertexData, const UVColorChannelData_t &uvColorData ) const { // Normally UVColor data is exported as a VECTOR3 data in DMX, set this to true to also export as a color map const bool bExportColor = false; for ( int i = 0; i < ARRAYSIZE( uvColorData.m_uvChannelData ); ++i ) { if ( !uvColorData.m_uvChannelData[i].m_pFbxElementUV || uvColorData.m_uvChannelData[i].m_nIndices.IsEmpty() ) return; } CUtlVector< Color > dmxColorData; CUtlVector< Vector > dmxData; CUtlVector< int > indices; for ( int i = 0; i < ARRAYSIZE( uvColorData.m_uvChannelData ); ++i ) { const CUtlVector< int > &srcIndices = uvColorData.m_uvChannelData[i].m_nIndices; if ( indices.IsEmpty() ) { const int nIndexCount = srcIndices.Count(); indices.SetCount( nIndexCount ); dmxColorData.SetCount( nIndexCount ); dmxData.SetCount( nIndexCount ); for ( int j = 0; j < nIndexCount; ++j ) { indices[j] = j; } if ( bExportColor ) { for ( int j = 0; j < nIndexCount; ++j ) { dmxColorData[j] = Color( 0, 0, 0, 255 ); } } } else { if ( indices.Count() != srcIndices.Count() ) { Warning( "Warning! Fbx 3DS Max index mismatch for channel \"%s\", got %d expected %d\n", uvColorData.m_uvChannelData[i].m_pFbxElementUV->GetName(), srcIndices.Count(), indices.Count() ); return; } } } for ( int i = 0; i < ARRAYSIZE( uvColorData.m_uvChannelData ); ++i ) { const FbxLayerElementArrayTemplate< FbxVector2 > &fbxData = uvColorData.m_uvChannelData[i].m_pFbxElementUV->GetDirectArray(); const int nDataCount = fbxData.GetCount(); if ( nDataCount <= 0 ) return; const CUtlVector< int > &fbxIndices = uvColorData.m_uvChannelData[i].m_nIndices; for ( int j = 0; j < indices.Count(); ++j ) { const int nDmxDataIndex = indices[j]; Assert( nDmxDataIndex == j ); const int nFbxDataIndex = fbxIndices[nDmxDataIndex]; const FbxVector2 &vFbxData = fbxData[nFbxDataIndex]; Vector &vDmxData = dmxData[nDmxDataIndex]; vDmxData[i] = vFbxData[0]; // Always use value 0 if ( bExportColor ) { Color &vDmxColorData = dmxColorData[nDmxDataIndex]; vDmxColorData[i] = clamp( static_cast< uint8 >( floor( vFbxData[0] * 255.0 ) ), 0, 255 ); // Always use value 0 } } } { const DmAttributeType_t dmAttributeType = AT_VECTOR3; const FieldIndex_t nFieldIndex = pDmeVertexData->CreateField( uvColorData.m_sChannelName.Get(), ValueTypeToArrayType( dmAttributeType ) ); pDmeVertexData->AddVertexData( nFieldIndex, dmxData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxData.Count(), dmAttributeType, dmxData.Base() ); pDmeVertexData->SetVertexIndices( nFieldIndex, 0, indices.Count(), indices.Base() ); pDmeVertexData->Resolve(); } if ( bExportColor ) { CFmtStr sFieldName; int nSemanticIndex = 0; for ( ;; ) { sFieldName.sprintf( "color$%d", nSemanticIndex ); FieldIndex_t nTmpField = pDmeVertexData->FindFieldIndex( sFieldName.Get() ); if ( nTmpField < 0 ) break; ++nSemanticIndex; } const DmAttributeType_t dmAttributeType = AT_COLOR; const FieldIndex_t nFieldIndex = pDmeVertexData->CreateField( sFieldName.Get(), ValueTypeToArrayType( dmAttributeType ) ); pDmeVertexData->AddVertexData( nFieldIndex, dmxColorData.Count() ); pDmeVertexData->SetVertexData( nFieldIndex, 0, dmxColorData.Count(), dmAttributeType, dmxColorData.Base() ); pDmeVertexData->SetVertexIndices( nFieldIndex, 0, indices.Count(), indices.Base() ); pDmeVertexData->Resolve(); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < FbxLayerElement::EMappingMode M, FbxLayerElement::EReferenceMode R > void HandleUV( CUtlVector< int > &uvIndices, FbxMesh *pFbxMesh, FbxGeometryElementUV *peUV, int nControlPointIndex, int nPolygonVertexIndex ) { // Do Nothing } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleUV< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >( CUtlVector< int > &uvIndices, FbxMesh *pFbxMesh, FbxGeometryElementUV *peUV, int nControlPointIndex, int nPolygonVertexIndex ) { uvIndices.AddToTail( nControlPointIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleUV< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eIndexToDirect >( CUtlVector< int > &uvIndices, FbxMesh *pFbxMesh, FbxGeometryElementUV *peUV, int nControlPointIndex, int nPolygonVertexIndex ) { const int nUVId = peUV->GetIndexArray().GetAt( nControlPointIndex ); uvIndices.AddToTail( nUVId ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleUV< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >( CUtlVector< int > &uvIndices, FbxMesh *pFbxMesh, FbxGeometryElementUV *peUV, int nControlPointIndex, int nPolygonVertexIndex ) { uvIndices.AddToTail( nPolygonVertexIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleUV< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect >( CUtlVector< int > &uvIndices, FbxMesh *pFbxMesh, FbxGeometryElementUV *peUV, int nControlPointIndex, int nPolygonVertexIndex ) { const int nUVId = peUV->GetIndexArray().GetAt( nPolygonVertexIndex ); uvIndices.AddToTail( nUVId ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmFbxSerializer::HandleUVFunc_t GetHandleUVFunc( FbxGeometryElementUV *pFbxElementUV ) { if ( pFbxElementUV ) { const FbxLayerElement::EMappingMode nMappingMode = pFbxElementUV->GetMappingMode(); const FbxLayerElement::EReferenceMode nReferenceMode = pFbxElementUV->GetReferenceMode(); if ( nMappingMode == FbxGeometryElement::eByControlPoint && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleUV< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByControlPoint && nReferenceMode == FbxGeometryElement::eIndexToDirect ) { return HandleUV< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eIndexToDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleUV< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eIndexToDirect ) { return HandleUV < FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect > ; } else { Warning( "Warning! Unsupported FBX UV Mapping/Reference Mode %d/%d\n", nMappingMode, nReferenceMode ); } } return HandleUV< FbxGeometryElement::eNone, FbxGeometryElement::eDirect >; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < FbxLayerElement::EMappingMode M, FbxLayerElement::EReferenceMode R > void HandleNormal( CUtlVector< int > &nNormalIndices, FbxMesh *pFbxMesh, FbxGeometryElementNormal *pFbxElementNormal, int nControlPointIndex, int nPolygonVertexIndex ) { // Do Nothing } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleNormal< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >( CUtlVector< int > &nNormalIndices, FbxMesh *pFbxMesh, FbxGeometryElementNormal *pFbxElementNormal, int nControlPointIndex, int nPolygonVertexIndex ) { nNormalIndices.AddToTail( nControlPointIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleNormal< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >( CUtlVector< int > &nNormalIndices, FbxMesh *pFbxMesh, FbxGeometryElementNormal *pFbxElementNormal, int nControlPointIndex, int nPolygonVertexIndex ) { nNormalIndices.AddToTail( nPolygonVertexIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleNormal< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect >( CUtlVector< int > &nNormalIndices, FbxMesh *pFbxMesh, FbxGeometryElementNormal *pFbxElementNormal, int nControlPointIndex, int nPolygonVertexIndex ) { const int nNormalIndex = pFbxElementNormal->GetIndexArray().GetAt( nPolygonVertexIndex ); nNormalIndices.AddToTail( nNormalIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- typedef void (*HandleNormalFunc_t)( CUtlVector< int > &, FbxMesh *, FbxGeometryElementNormal *, int, int ); //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- HandleNormalFunc_t GetHandleNormalFunc( FbxGeometryElementNormal *pFbxElementNormal ) { if ( pFbxElementNormal ) { const FbxLayerElement::EMappingMode nMappingMode = pFbxElementNormal->GetMappingMode(); const FbxLayerElement::EReferenceMode nReferenceMode = pFbxElementNormal->GetReferenceMode(); if ( nMappingMode == FbxGeometryElement::eByControlPoint && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleNormal< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleNormal< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eIndexToDirect ) { return HandleNormal< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect >; } else { Warning( "Warning! Unsupported FBX Normal Mapping/Reference Mode %d/%d\n", nMappingMode, nReferenceMode ); } } return HandleNormal< FbxGeometryElement::eNone, FbxGeometryElement::eDirect >; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template < FbxLayerElement::EMappingMode M, FbxLayerElement::EReferenceMode R > void HandleVertexColor( CUtlVector< int > &vertexColorIndices, FbxMesh *pFbxMesh, FbxGeometryElementVertexColor *peVertexColor, int nControlPointIndex, int nPolygonVertexIndex ) { // Do Nothing } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleVertexColor< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >( CUtlVector< int > &vertexColorIndices, FbxMesh *pFbxMesh, FbxGeometryElementVertexColor *peVertexColor, int nControlPointIndex, int nPolygonVertexIndex ) { vertexColorIndices.AddToTail( nControlPointIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleVertexColor< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eIndexToDirect >( CUtlVector< int > &vertexColorIndices, FbxMesh *pFbxMesh, FbxGeometryElementVertexColor *peVertexColor, int nControlPointIndex, int nPolygonVertexIndex ) { const int nVertexColorId = peVertexColor->GetIndexArray().GetAt( nControlPointIndex ); vertexColorIndices.AddToTail( nVertexColorId ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleVertexColor< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >( CUtlVector< int > &vertexColorIndices, FbxMesh *pFbxMesh, FbxGeometryElementVertexColor *peVertexColor, int nControlPointIndex, int nPolygonVertexIndex ) { vertexColorIndices.AddToTail( nPolygonVertexIndex ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- template <> void HandleVertexColor< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect >( CUtlVector< int > &vertexColorIndices, FbxMesh *pFbxMesh, FbxGeometryElementVertexColor *peVertexColor, int nControlPointIndex, int nPolygonVertexIndex ) { const int nVertexColorId = peVertexColor->GetIndexArray().GetAt( nPolygonVertexIndex ); vertexColorIndices.AddToTail( nVertexColorId ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- typedef void (*HandleVertexColorFunc_t)( CUtlVector< int > &, FbxMesh *, FbxGeometryElementVertexColor *, int, int ); //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- HandleVertexColorFunc_t GetHandleVertexColorFunc( FbxGeometryElementVertexColor *pFbxElementVertexColor ) { if ( pFbxElementVertexColor ) { const FbxLayerElement::EMappingMode nMappingMode = pFbxElementVertexColor->GetMappingMode(); const FbxLayerElement::EReferenceMode nReferenceMode = pFbxElementVertexColor->GetReferenceMode(); if ( nMappingMode == FbxGeometryElement::eByControlPoint && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleVertexColor< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByControlPoint && nReferenceMode == FbxGeometryElement::eIndexToDirect ) { return HandleVertexColor< FbxGeometryElement::eByControlPoint, FbxGeometryElement::eIndexToDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eDirect ) { return HandleVertexColor< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eDirect >; } else if ( nMappingMode == FbxGeometryElement::eByPolygonVertex && nReferenceMode == FbxGeometryElement::eIndexToDirect ) { return HandleVertexColor< FbxGeometryElement::eByPolygonVertex, FbxGeometryElement::eIndexToDirect >; } else { Warning( "Warning! Unsupported FBX VertexColor Mapping/Reference Mode %d/%d\n", nMappingMode, nReferenceMode ); } } return HandleVertexColor< FbxGeometryElement::eNone, FbxGeometryElement::eDirect >; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- struct ColorChannelData_t { public: ColorChannelData_t() : m_pFbxElementVertexColor( NULL ) , m_pFunc( NULL ) { } FbxGeometryElementVertexColor *m_pFbxElementVertexColor; HandleVertexColorFunc_t m_pFunc; CUtlVector< int > m_nIndices; }; //----------------------------------------------------------------------------- // // This converts an FbxMesh into a DmeMesh where the vertex data indices for // the DmeMesh will be full expanded to be per polygon per vertex values // //----------------------------------------------------------------------------- CDmeMesh *CDmFbxSerializer::FbxShapeToDmeMesh( CDmeDag *pDmeDag, FbxNode *pFbxNode, const FbxMatrix &mScale ) const { bool bReverse = false; { FbxVector4 vTTmp; FbxQuaternion qRTmp; FbxVector4 vShTmp; FbxVector4 vScTmp; double flSign = 1.0; // Sign of the determinant of the scale matrix, if negative, odd number of scales and faces need reversing mScale.GetElements( vTTmp, qRTmp, vShTmp, vScTmp, flSign ); bReverse = ( flSign < 0.0 ); } if ( !pDmeDag || !pFbxNode ) return NULL; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( !pFbxNodeAttribute || pFbxNodeAttribute->GetAttributeType() != FbxNodeAttribute::eMesh ) return NULL; FbxMesh *pFbxMesh = reinterpret_cast< FbxMesh * >( pFbxNodeAttribute ); if ( !pFbxMesh ) return NULL; CUtlString sName; GetName( sName, pFbxNode ); CDmeMesh *pDmeMesh = CreateElement< CDmeMesh >( sName.String(), pDmeDag->GetFileId() ); if ( !pDmeMesh ) { Warning( "Warning! Couldn't create DmeMesh for FbxMesh \"%s\"\n", pFbxMesh->GetName() ); return NULL; } CUtlVector< int > nPolygonToFaceSetMap; FbxMeshToDmeFaceSets( pDmeDag, pDmeMesh, pFbxMesh, nPolygonToFaceSetMap ); const int nNormalCount = pFbxMesh->GetElementNormalCount(); FbxGeometryElementNormal *pFbxElementNormal = nNormalCount > 0 ? pFbxMesh->GetElementNormal( 0 ) : NULL; HandleNormalFunc_t pHandleNormalFunc = GetHandleNormalFunc( pFbxElementNormal ); CUtlVector< int > nPositionIndices; CUtlVector< int > nNormalIndices; CUtlVector< ColorChannelData_t > colorChannelDataList; const int nColorCount = pFbxMesh->GetElementVertexColorCount(); for ( int nColor = 0; nColor < nColorCount; ++nColor ) { FbxGeometryElementVertexColor *pFbxElementVertexColor = pFbxMesh->GetElementVertexColor( nColor ); if ( !pFbxElementVertexColor ) continue; ColorChannelData_t &colorChannelData = colorChannelDataList[colorChannelDataList.AddToTail()]; colorChannelData.m_pFbxElementVertexColor = pFbxElementVertexColor; colorChannelData.m_pFunc = GetHandleVertexColorFunc( pFbxElementVertexColor ); } CUtlVector< UVColorChannelData_t > uvColorChannelDataList; int nUVElementCount = pFbxMesh->GetElementUVCount(); CUtlVector< UVChannelData_t > uvChannelList; if ( nUVElementCount > 0 ) { CUtlVector< FbxGeometryElementUV * > uvElementList; uvElementList.SetCount( nUVElementCount ); for ( int nUV = 0; nUV < nUVElementCount; ++nUV ) { uvElementList[nUV] = pFbxMesh->GetElementUV( nUV ); } if ( nUVElementCount >= 4 ) { // For 3DS Max, there seems to be a bug in how 3DS Max exports extra vertex color information, it's sticking it into UVs // See if there are three UV channels: // "export_foliageanimation_r", // "export_foliageanimation_g", // "export_foliageanimation_b" const char *szChannelNames[3] = { "r", "g", "b" }; FbxGeometryElementUV *pUVs[3] = { NULL, NULL, NULL }; int nUVFoundCount = 0; bool bValid = true; COMPILE_TIME_ASSERT( ARRAYSIZE( szChannelNames ) == ARRAYSIZE( pUVs ) ); const char *pszPrefix = "export_foliageanimation_"; const char *pszChannelName = "foliageanimation"; for ( int nUV = 0; bValid && nUV < nUVElementCount; ++nUV ) { FbxGeometryElementUV *pUV = pFbxMesh->GetElementUV( nUV ); const char *pszUVName = pUV->GetName(); if ( StringHasPrefix( pszUVName, pszPrefix ) ) { const char *pszChannel = StringAfterPrefix( pszUVName, pszPrefix ); bool bUVFound = false; for ( int i = 0; i < ARRAYSIZE( szChannelNames ); ++i ) { if ( !V_stricmp( pszChannel, szChannelNames[i] ) ) { bUVFound = true; if ( pUVs[i] == NULL ) { pUVs[i] = pUV; ++nUVFoundCount; } else { Warning( "Warning! Fbx 3DSMax UV encoded VertexPaint channel \"%s\", specified multiple times on mesh \"%s\"\n", pszUVName, pFbxMesh->GetName() ); } break; } } if ( bValid && !bUVFound ) { Warning( "Warning! Fbx 3DSMax UV encoded VertexPaint channel \"%s\", unexpected, expecting ending of [ r, g or b ], on mesh \"%s\"\n", pszUVName, pFbxMesh->GetName() ); } } } if ( bValid && nUVFoundCount == 3 ) { UVColorChannelData_t &uvEncodedColorChannelData = uvColorChannelDataList[uvColorChannelDataList.AddToTail()]; uvEncodedColorChannelData.m_sChannelName = pszChannelName; for ( int i = 0; i < ARRAYSIZE( pUVs ); ++i ) { uvEncodedColorChannelData.m_uvChannelData[i].m_pFbxElementUV = pUVs[i]; uvEncodedColorChannelData.m_uvChannelData[i].m_pFunc = GetHandleUVFunc( pUVs[i] ); uvElementList.FindAndRemove( pUVs[i] ); // Remove these funky 3ds max channels from the normal UV channels } } else if ( nUVFoundCount > 0 ) { Warning( "Warning! Fbx 3DSMax UV encoded VertexPaint \"%s\" invalid, expected %llu maps, found %d, on mesh \"%s\"\n", pszPrefix, static_cast< uint64 >( ARRAYSIZE( szChannelNames ) ), nUVFoundCount, pFbxMesh->GetName() ); } } // Add the non-3DsMax UV encoded color channels to the actual UV channel list nUVElementCount = uvElementList.Count(); for ( int nUV = 0; nUV < nUVElementCount; ++nUV ) { FbxGeometryElementUV *pFbxElementUV = pFbxMesh->GetElementUV( nUV ); if ( !pFbxElementUV ) continue; HandleUVFunc_t pHandleUVFunc = GetHandleUVFunc( pFbxElementUV ); if ( !pHandleUVFunc ) continue; UVChannelData_t &uvChannelData = uvChannelList[uvChannelList.AddToTail()]; uvChannelData.m_pFbxElementUV = pFbxElementUV; uvChannelData.m_pFunc = pHandleUVFunc; } nUVElementCount = uvChannelList.Count(); } const int nUVColorCount = uvColorChannelDataList.Count(); CUtlVector< int > nFaceSetIndices; const int nPolygonCount = pFbxMesh->GetPolygonCount(); int nPolygonVertexIndex = 0; for ( int nPolygon = 0; nPolygon < nPolygonCount; ++nPolygon ) { const int nFaceSet = nPolygonToFaceSetMap[nPolygon]; CDmeFaceSet *pDmeFaceSet = pDmeMesh->GetFaceSet( nFaceSet ); const int nPolygonVertCount = pFbxMesh->GetPolygonSize( nPolygon ); nFaceSetIndices.SetCount( nPolygonVertCount + 1 ); for ( int nPolygonVert = 0; nPolygonVert < nPolygonVertCount; ++nPolygonVert ) { const int nControlPointIndex = pFbxMesh->GetPolygonVertex( nPolygon, nPolygonVert ); nFaceSetIndices[nPolygonVert] = nPositionIndices.Count(); nPositionIndices.AddToTail( nControlPointIndex ); for ( int nUV = 0; nUV < nUVElementCount; ++nUV ) { UVChannelData_t &uvChannelData = uvChannelList[nUV]; ( *( uvChannelData.m_pFunc ) )( uvChannelData.m_nIndices, pFbxMesh, uvChannelData.m_pFbxElementUV, nControlPointIndex, nPolygonVertexIndex ); } ( *pHandleNormalFunc )( nNormalIndices, pFbxMesh, pFbxElementNormal, nControlPointIndex, nPolygonVertexIndex ); for ( int nColor = 0; nColor < nColorCount; ++nColor ) { ColorChannelData_t &colorChannelData = colorChannelDataList[nColor]; (*colorChannelData.m_pFunc)( colorChannelData.m_nIndices, pFbxMesh, colorChannelData.m_pFbxElementVertexColor, nControlPointIndex, nPolygonVertexIndex ); } for ( int nUVColor = 0; nUVColor < nUVColorCount; ++nUVColor ) { UVColorChannelData_t &uvColorChannelData = uvColorChannelDataList[nUVColor]; for ( int nUVChannel = 0; nUVChannel < ARRAYSIZE( uvColorChannelData.m_uvChannelData ); ++nUVChannel ) { ( *( uvColorChannelData.m_uvChannelData[nUVChannel].m_pFunc ) )( uvColorChannelData.m_uvChannelData[nUVChannel].m_nIndices, pFbxMesh, uvColorChannelData.m_uvChannelData[nUVChannel].m_pFbxElementUV, nControlPointIndex, nPolygonVertexIndex ); } } ++nPolygonVertexIndex; } nFaceSetIndices[nPolygonVertCount] = -1; const int nStartIndex = pDmeFaceSet->NumIndices(); pDmeFaceSet->AddIndices( nFaceSetIndices.Count() ); if ( bReverse ) { int nCurrentIndex = nStartIndex; // Add indices for this face in reverse order, leaving the -1 as the last index (face terminator) for ( int nReverseIndex = nFaceSetIndices.Count() - 2; nReverseIndex >= 0; --nReverseIndex ) { pDmeFaceSet->SetIndex( nCurrentIndex, nFaceSetIndices[nReverseIndex] ); ++nCurrentIndex; } pDmeFaceSet->SetIndex( nCurrentIndex, nFaceSetIndices.Tail() ); // Add the -1 } else { pDmeFaceSet->SetIndices( nStartIndex, nFaceSetIndices.Count(), nFaceSetIndices.Base() ); } } CDmeVertexData *pDmeVertexData = pDmeMesh->FindOrCreateBaseState( "bind" ); pDmeVertexData->FlipVCoordinate( true ); pDmeMesh->SetBindBaseState( pDmeVertexData ); pDmeMesh->SetCurrentBaseState( "bind" ); if ( !AddPositionData( pDmeVertexData, nPositionIndices, pFbxMesh, mScale ) ) { Warning( "Warning! Couldn't convert FbxMesh \"%s\"\n", pFbxMesh->GetName() ); g_pDataModel->DestroyElement( pDmeMesh->GetHandle() ); return NULL; } for ( int nUV = 0; nUV < nUVElementCount; ++nUV ) { const UVChannelData_t &uvChannelData = uvChannelList[nUV]; AddUVData( pDmeVertexData, uvChannelData.m_nIndices, uvChannelData.m_pFbxElementUV, nUV ); } AddNormalData( pDmeVertexData, nNormalIndices, pFbxElementNormal, mScale ); for ( int nColor = 0; nColor < nColorCount; ++nColor ) { ColorChannelData_t &colorChannelData = colorChannelDataList[nColor]; AddColorData( pDmeVertexData, colorChannelData.m_nIndices, colorChannelData.m_pFbxElementVertexColor ); } for ( int nUVColor = 0; nUVColor < uvColorChannelDataList.Count(); ++nUVColor ) { UVColorChannelData_t &uvColorChannelData = uvColorChannelDataList[nUVColor]; AddUVColorData( pDmeVertexData, uvColorChannelData ); } pDmeDag->SetShape( pDmeMesh ); return pDmeMesh; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmFbxSerializer::FbxMeshToDmeFaceSets( CDmeDag *pDmeDag, CDmeMesh *pDmeMesh, FbxMesh *pFbxMesh, CUtlVector< int > &nPolygonToFaceSetMap ) const { const int nPolygonCount = pFbxMesh->GetPolygonCount(); const int nMaterialCount = pFbxMesh->GetNode()->GetMaterialCount(); const int nElementMaterialCount = pFbxMesh->GetElementMaterialCount(); FbxGeometryElementMaterial *pMatElement = NULL; if ( nMaterialCount > 0 && nElementMaterialCount > 0 ) { pMatElement = pFbxMesh->GetElementMaterial( 0 ); } CUtlString sFallbackMaterialPath; #ifdef SOURCE2 { // Generate a fallback material path based on the mesh name, only used if material cannot be located char szResourceName[MAX_PATH] = {}; if ( !FixupResourceName( pDmeMesh->GetName(), RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ) ) { SetResourceFileNameExtension( pDmeMesh->GetName(), RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); } sFallbackMaterialPath = szResourceName; } #endif CUtlString sMaterialPath = sFallbackMaterialPath; // Looking for UserData on a mesh ending in .vmat static const char szSuffix[] = "_vmat"; const int nSuffixCount = ARRAYSIZE( szSuffix ) - 1; COMPILE_TIME_ASSERT( nSuffixCount == 5 ); typedef FbxMap< FbxString, FbxLayerElementArrayTemplate< void * > * > HoudiniMatDataMap_t; // data to figure out material assignments from houdini HoudiniMatDataMap_t houdiniMatData; for ( int i = 0; i < pFbxMesh->GetElementUserDataCount(); ++i ) { FbxGeometryElementUserData *pFbxUserData = pFbxMesh->GetElementUserData( i );; for ( int j = 0; j < pFbxUserData->GetDirectArrayCount(); ++j ) { if ( pFbxUserData->GetDataType( j ).GetType() == eFbxInt ) { const char *pszDataName = pFbxUserData->GetDataName( j ); FbxString sDataName( pszDataName ); // Look if string ends in "_vmat" if ( sDataName.FindAndReplace( szSuffix, ".vmat", sDataName.GetLen() - nSuffixCount ) ) { bool bArray = false; FbxLayerElementArrayTemplate< void * > *pDirectArrayVoid = pFbxUserData->GetDirectArrayVoid( j, &bArray ); if ( bArray && nPolygonCount == pDirectArrayVoid->GetCount() ) { houdiniMatData.Insert( sDataName, pDirectArrayVoid ); } } } } } if ( pMatElement ) { if ( pMatElement->GetMappingMode() == FbxGeometryElement::eByPolygon ) { nPolygonToFaceSetMap.RemoveAll(); nPolygonToFaceSetMap.EnsureCapacity( nPolygonCount ); CUtlVector< FbxString > materialList; materialList.SetCount( nMaterialCount ); if ( houdiniMatData.Empty() ) { for ( int i = 0; i < nPolygonCount; ++i ) { nPolygonToFaceSetMap.AddToTail( pMatElement->GetIndexArray().GetAt( i ) ); } } else { for ( int i = 0; i < nPolygonCount; ++i ) { const int nPolygonMaterialIndex = pMatElement->GetIndexArray().GetAt( i ); nPolygonToFaceSetMap.AddToTail( nPolygonMaterialIndex ); if ( materialList[nPolygonMaterialIndex].IsEmpty() ) { bool bFoundMat = false; for ( HoudiniMatDataMap_t::Iterator hmdIt = houdiniMatData.Begin(); hmdIt != houdiniMatData.End(); ++hmdIt ) { FbxLayerElementArrayTemplate< void * > *pDirectArrayVoid = hmdIt->GetValue(); int *pDirectArrayInt = NULL; pDirectArrayInt = pDirectArrayVoid->GetLocked( pDirectArrayInt, FbxLayerElementArray::eReadLock ); if ( pDirectArrayInt[i] != 0 ) { materialList[nPolygonMaterialIndex] = hmdIt->GetKey(); bFoundMat = true; } pDirectArrayVoid->Release( reinterpret_cast< void ** >( &pDirectArrayInt ) ); if ( bFoundMat ) break; } } } } for ( int i = 0; i < nMaterialCount; ++i ) { CUtlString sByPolygonMaterialPath; CUtlVector< CUtlString > materialSearchErrorList; if ( materialList[i].IsEmpty() ) { FbxSurfaceMaterial *pFbxMaterial = pFbxMesh->GetNode()->GetMaterial( i ); // If there's actually a material, change the fallback to the material name instead of the mesh name if ( pFbxMaterial ) { sByPolygonMaterialPath = pFbxMaterial->GetName(); } else { sByPolygonMaterialPath = sFallbackMaterialPath; } if ( !GetFbxMaterialPath( sByPolygonMaterialPath, pFbxMaterial, materialSearchErrorList ) ) { #ifdef SOURCE2 AddConversionError( pDmeMesh->GetFileId(), CFmtStrMax( "GetFbxMaterialPath Failed! FbxMesh: %s FbxMaterial[%d]: %s, using: %s", pDmeMesh->GetName(), i, pFbxMaterial ? pFbxMaterial->GetName() : "nil", sByPolygonMaterialPath.Get() ).Get() ); if ( !materialSearchErrorList.IsEmpty() ) { AddConversionError( pDmeMesh->GetFileId(), " + Searched:" ); for ( int e = 0; e < materialSearchErrorList.Count(); ++e ) { AddConversionError( pDmeMesh->GetFileId(), CFmtStr( " + %s", materialSearchErrorList[e].Get() ).Get() ); } } #endif } } else { sByPolygonMaterialPath = materialList[i].Buffer(); } const CUtlString sByPolygonMaterialName = sByPolygonMaterialPath.GetBaseFilename(); CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( sByPolygonMaterialName.String(), pDmeMesh->GetFileId() ); CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( sByPolygonMaterialName.String(), pDmeMesh->GetFileId() ); pDmeMaterial->SetMaterial( sByPolygonMaterialPath.Get() ); pDmeFaceSet->SetMaterial( pDmeMaterial ); pDmeMesh->AddFaceSet( pDmeFaceSet ); } return true; } else if ( pMatElement->GetMappingMode() == FbxGeometryElement::eAllSame ) { CUtlVector< CUtlString > materialSearchErrorList; FbxSurfaceMaterial *pFbxMaterial = pFbxMesh->GetNode()->GetMaterial( 0 ); if ( pFbxMaterial ) { // If there's actually a material, change the fallback to the material name instead of the mesh name sMaterialPath = pFbxMaterial->GetName(); } if ( !GetFbxMaterialPath( sMaterialPath, pFbxMaterial, materialSearchErrorList ) ) { #ifdef SOURCE2 AddConversionError( pDmeMesh->GetFileId(), CFmtStrMax( "GetFbxMaterialPath Failed! FbxMesh: %s FbxMaterial: %s, using: %s", pDmeMesh->GetName(), pFbxMaterial ? pFbxMaterial->GetName() : "nil", sMaterialPath.Get() ).Get() ); if ( !materialSearchErrorList.IsEmpty() ) { AddConversionError( pDmeMesh->GetFileId(), " + Searched:" ); for ( int e = 0; e < materialSearchErrorList.Count(); ++e ) { AddConversionError( pDmeMesh->GetFileId(), CFmtStr( " + %s", materialSearchErrorList[e].Get() ).Get() ); } } #endif } if ( houdiniMatData.GetSize() == 1 ) { sMaterialPath = houdiniMatData.Begin()->GetKey(); } } else { AddConversionError( pDmeMesh->GetFileId(), CFmtStrMax( "GetFbxMaterialPath Failed! FbxMesh: %s, Unsupported material mapping mode, using: %s", pDmeMesh->GetName(), sMaterialPath.Get() ).Get() ); } } else { AddConversionError( pDmeMesh->GetFileId(), CFmtStrMax( "GetFbxMaterialPath Failed! FbxMesh: %s, No FbxMaterial assigned, using: %s", pDmeMesh->GetName(), sMaterialPath.Get() ).Get() ); } // Either a single material or no material on the mesh static const int nZero = 0; nPolygonToFaceSetMap.SetCount( nPolygonCount ); nPolygonToFaceSetMap.FillWithValue( nZero ); const CUtlString sMaterialName = sMaterialPath.GetBaseFilename(); CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( sMaterialName.String(), pDmeMesh->GetFileId() ); CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( sMaterialName.String(), pDmeMesh->GetFileId() ); pDmeMaterial->SetMaterial( sMaterialPath.String() ); pDmeFaceSet->SetMaterial( pDmeMaterial ); pDmeMesh->AddFaceSet( pDmeFaceSet ); return true; } //----------------------------------------------------------------------------- // // Search for a material by name by generating resource names and seeing if // the resource exists on disk in content and then game and then on user // specified material search paths // // If a material file cannot be found, the original name is copied and false // is returned // //----------------------------------------------------------------------------- bool CDmFbxSerializer::FindMaterialResource( CUtlString &sOutMaterialPath, const char *pszInMaterialName, CUtlVector< CUtlString > &materialSearchErrorList ) const { #ifdef SOURCE2 char szResourceName[MAX_PATH] = { 0 }; char szResourceFullPath[MAX_PATH] = { 0 }; char szMaterialPath[MAX_PATH] = { 0 }; ResourcePathGenerationType_t pSearchPaths[] = { RESOURCE_PATH_CONTENT, RESOURCE_PATH_GAME }; const char *szSearchPaths[] = { "CONTENT", "GAME" }; COMPILE_TIME_ASSERT( ARRAYSIZE( pSearchPaths ) == ARRAYSIZE( szSearchPaths ) ); FixupResourceName( pszInMaterialName, RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); for ( int s = 0; s < ARRAYSIZE( pSearchPaths ); ++s ) { // Check if current material is valid if ( GenerateStandardFullPathForResourceName( szResourceName, pSearchPaths[s], szResourceFullPath, ARRAYSIZE( szResourceFullPath ) ) ) { sOutMaterialPath = szResourceName; return true; } else { materialSearchErrorList.AddToTail( CFmtStr( "%-7s %s", szSearchPaths[s], szResourceName ).Get() ); } } // Loop through material search paths and try to find the material for ( int s = 0; s < ARRAYSIZE( pSearchPaths ); s++ ) { for ( int i = 0; i < m_sOptMaterialSearchPathList.Count(); ++i ) { V_ComposeFileName( m_sOptMaterialSearchPathList[i].Get(), pszInMaterialName, szMaterialPath, ARRAYSIZE( szMaterialPath ) ); FixupResourceName( szMaterialPath, RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); if ( GenerateStandardFullPathForResourceName( szResourceName, pSearchPaths[s], szResourceFullPath, ARRAYSIZE( szResourceFullPath ) ) ) { sOutMaterialPath = szResourceName; return true; } else { materialSearchErrorList.AddToTail( CFmtStr( "%-7s %s", szSearchPaths[s], szResourceName ).Get() ); } } } return false; #else //sOutMaterialPath = CUtlString( pszInMaterialName ).UnqualifiedFilename(); return false; #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmFbxSerializer::GetFbxMaterialPathFromFbxFileTexture( CUtlString &sMaterialPath, FbxFileTexture *pFileTexture, CUtlVector< CUtlString > &materialSearchErrorList ) const { const char *pszFileName = pFileTexture->GetFileName(); if ( !pszFileName || *pszFileName == '\0' ) return false; #ifdef SOURCE2 char szResourceName[ MAX_PATH ]; GenerateResourceNameFromFileName( pszFileName, RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); #else sMaterialPath = CUtlString( pszFileName ).UnqualifiedFilename(); return true; const char *szResourceName; szResourceName = pszFileName; #endif if ( FindMaterialResource( sMaterialPath, szResourceName, materialSearchErrorList ) ) return true; // See if the texture ends in any well known suffixes const char *szSuffixes[] = { "_color." }; FbxString sTmpResourceName = szResourceName; for ( int i = 0; i < ARRAYSIZE( szSuffixes ); ++i ) { if ( sTmpResourceName.FindAndReplace( szSuffixes[ i ], "." ) ) { if ( FindMaterialResource( sMaterialPath, sTmpResourceName.Buffer(), materialSearchErrorList ) ) return true; if ( !V_strcmp( szSuffixes[i], "_color." ) ) { // If we got here, probably just use this one, likely the file wasn't put onto disk yet sMaterialPath = sTmpResourceName.Buffer(); } } } return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmFbxSerializer::GetFbxMaterialPath( CUtlString &sMaterialPath, FbxSurfaceMaterial *pFbxMat, CUtlVector< CUtlString > &materialSearchErrorList ) const { if ( !pFbxMat ) { materialSearchErrorList.AddToTail( CFmtStr( "GetFbxMaterialPath Failed: No Fbx material" ).Get() ); return false; } // See if FBX_vmatPath is explicitly set, if it is, then just use it and return static const char *szExplitPaths[] = { "vmatPath", "FBX_vmatPath" }; for ( int i = 0; i < ARRAYSIZE( szExplitPaths ); ++i ) { const char *pszExplicitPath = szExplitPaths[i]; FbxProperty fbx_vmatPath = pFbxMat->FindProperty( pszExplicitPath ); if ( fbx_vmatPath.IsValid() ) { const EFbxType eFbxType = fbx_vmatPath.GetPropertyDataType().GetType(); if ( eFbxType == eFbxString ) { const FbxString sVMatPath = fbx_vmatPath.Get< FbxString >(); if ( !sVMatPath.IsEmpty() ) { sMaterialPath = sVMatPath.Buffer(); return true; } else { materialSearchErrorList.AddToTail( CFmtStr( "FBXSurfaceMaterial( %s ).FBX_vmatPath found but empty", pFbxMat->GetName() ).Get() ); } } else { materialSearchErrorList.AddToTail( CFmtStr( "FBXSurfaceMaterial( %s ).FBX_vmatPath found but not a string attribute", pFbxMat->GetName() ).Get() ); } } } // See if the name of the material maps directly to a material file if ( FindMaterialResource( sMaterialPath, pFbxMat->GetNameOnly(), materialSearchErrorList ) ) return true; // Look to see if a texture can map to a .vmat FbxProperty fbxProperty = pFbxMat->FindProperty( FbxSurfaceMaterial::sDiffuse ); const int nLayeredTextureCount = fbxProperty.GetSrcObjectCount( FbxLayeredTexture::ClassId ); if ( nLayeredTextureCount > 0 ) { for ( int j = 0; j < nLayeredTextureCount; ++j ) { FbxLayeredTexture *pLayeredTexture = FbxCast< FbxLayeredTexture >( fbxProperty.GetSrcObject( FbxLayeredTexture::ClassId, j ) ); const int nTexCount = pLayeredTexture->GetSrcObjectCount( FbxTexture::ClassId ); for ( int k = 0; k < nTexCount; ++k ) { FbxFileTexture *pFileTexture = FbxCast< FbxFileTexture >( pLayeredTexture->GetSrcObject( FbxTexture::ClassId, k ) ); if ( pFileTexture ) { if ( GetFbxMaterialPathFromFbxFileTexture( sMaterialPath, pFileTexture, materialSearchErrorList ) ) return true; } } } } else { //no layered texture simply get on the property const int nTexCount = fbxProperty.GetSrcObjectCount( FbxTexture::ClassId ); if ( nTexCount > 0 ) { for ( int j = 0; j < nTexCount; ++j ) { FbxFileTexture *pFileTexture = FbxCast< FbxFileTexture >( fbxProperty.GetSrcObject( FbxTexture::ClassId, j ) ); if ( pFileTexture ) { if ( GetFbxMaterialPathFromFbxFileTexture( sMaterialPath, pFileTexture, materialSearchErrorList ) ) return true; } } } } return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::SkinMeshes_R( const FbxToDmxMap_t &fbxToDmxMap, CDmeModel *pDmeModel, FbxNode *pFbxNode ) const { FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( pFbxNodeAttribute ) { const FbxNodeAttribute::EType nAttributeType = pFbxNodeAttribute->GetAttributeType(); if ( nAttributeType == FbxNodeAttribute::eMesh ) { FbxToDmxMap_t::IndexType_t nDmxIndex = fbxToDmxMap.Find( pFbxNode ); if ( fbxToDmxMap.IsValidIndex( nDmxIndex ) ) { SkinMesh( fbxToDmxMap.Element( nDmxIndex ), fbxToDmxMap, pDmeModel, pFbxNode ); } } } for ( int i = 0; i < pFbxNode->GetChildCount(); ++i ) { SkinMeshes_R( fbxToDmxMap, pDmeModel, pFbxNode->GetChild( i ) ); } } //============================================================================= // //============================================================================= class CVertexSkin { public: enum { kMaxWeights = 3 }; enum SkinError_t { kSkinErrorNone = 0, kSkinErrorTooManyWeights, kSkinErrorZeroWeight, kSkinErrorNegativeWeight }; bool AddWeight( int nJointIndex, float flJointWeight ) { // Ignore 0 weights if ( flJointWeight < m_flEps ) return false; IndexWeightPair_t &indexWeightPair = m_weights[ m_weights.AddToTail() ]; indexWeightPair.m_nIndex = nJointIndex; indexWeightPair.m_flWeight = flJointWeight; return true; } int GetIndex( int nIndex ) const { if ( nIndex >= kMaxWeights || nIndex >= m_weights.Count() ) return -1; return m_weights[nIndex].m_nIndex; } float GetWeight( int nIndex ) const { if ( nIndex >= kMaxWeights || nIndex >= m_weights.Count() ) return 0.0f; return m_weights[nIndex].m_flWeight; } SkinError_t Renormalize(); protected: static int VertexWeightLessFunc( const void *pLhs, const void *pRhs ); static const float m_flEps; struct IndexWeightPair_t { int m_nIndex; float m_flWeight; }; CUtlVector< IndexWeightPair_t > m_weights; }; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- const float CVertexSkin::m_flEps = 1.0e-4; //----------------------------------------------------------------------------- // Used by HandleVertexWeights, sorts vertex weights by weight //----------------------------------------------------------------------------- int CVertexSkin::VertexWeightLessFunc( const void *pLhs, const void *pRhs ) { const IndexWeightPair_t *pVertexWeightL = reinterpret_cast< const IndexWeightPair_t * >( pLhs ); const IndexWeightPair_t *pVertexWeightR = reinterpret_cast< const IndexWeightPair_t * >( pRhs ); if ( pVertexWeightL->m_nIndex < 0 ) { if ( pVertexWeightR->m_nIndex < 0 ) { return 0; } else { return 1; } } else if ( pVertexWeightR->m_nIndex < 0 ) { return -1; } if ( pVertexWeightL->m_flWeight > pVertexWeightR->m_flWeight ) { return -1; } else if ( pVertexWeightL->m_flWeight < pVertexWeightR->m_flWeight ) { return 1; } return 0; } //----------------------------------------------------------------------------- // // Returns: kSkinErrorNone if all ok // kSkinErrorTooManyWeights if more than kMaxWeights // kSkinErrorZeroWeight if total weight is 0 for this vertex // kSkinErrorNegativeWeight if total weight is < 0 for this vertex // //----------------------------------------------------------------------------- CVertexSkin::SkinError_t CVertexSkin::Renormalize() { SkinError_t nErr = kSkinErrorNone; // Sort by weight, largest weights first qsort( m_weights.Base(), m_weights.Count(), sizeof( IndexWeightPair_t ), VertexWeightLessFunc ); // Remove any weights > kMaxWeights if ( m_weights.Count() > kMaxWeights ) { m_weights.RemoveMultipleFromTail( m_weights.Count() - kMaxWeights ); nErr = kSkinErrorTooManyWeights; } float flTotalWeight = 0.0f; for ( int i = 0; i < m_weights.Count(); ++i ) { flTotalWeight += m_weights[i].m_flWeight; } if ( fabs( flTotalWeight ) < m_flEps ) { nErr = kSkinErrorZeroWeight; } else { if ( flTotalWeight < 0.0f ) { nErr = kSkinErrorNegativeWeight; } if ( fabs( fabs( flTotalWeight ) - 1.0f ) > m_flEps ) { for ( int i = 0; i < m_weights.Count(); ++i ) { m_weights[i].m_flWeight /= flTotalWeight; } } } return nErr; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::SkinMesh( CDmeDag *pDmeDag, const FbxToDmxMap_t &fbxToDmxMap, CDmeModel *pDmeModel, FbxNode *pFbxNode ) const { static const char *szClusterModes[] = { "Normalize", "Additive", "Total1" }; if ( !pDmeDag ) return; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( !pFbxNodeAttribute || pFbxNodeAttribute->GetAttributeType() != FbxNodeAttribute::eMesh ) return; FbxMesh *pFbxMesh = reinterpret_cast< FbxMesh * >( pFbxNodeAttribute ); if ( !pFbxMesh ) return; CDmeMesh *pDmeMesh = CastElement< CDmeMesh >( pDmeDag->GetShape() ); if ( !pDmeMesh ) return; CDmeVertexData *pDmeVertexData = pDmeMesh->GetBindBaseState(); if ( !pDmeVertexData ) { Warning( "Warning! No DmeVertexData on DmeMesh (%s)\n", pDmeMesh->GetName() ); return; } const int nPositionCount = pDmeVertexData->GetPositionData().Count(); if ( nPositionCount <= 0 ) { Warning( "Warning! Invalid position count (%d) on DmeMesh (%s)\n", nPositionCount, pDmeMesh->GetName() ); return; } const int nDeformerCount = pFbxMesh->GetDeformerCount( FbxDeformer::eSkin ); if ( nDeformerCount <= 0 ) return; if ( Verbose2() ) { Msg( " * Skinning Mesh: %s\n", pDmeDag->GetName() ); } int nJointCount = 0; CUtlVector< CVertexSkin > vertexSkinWeights; vertexSkinWeights.SetCount( nPositionCount ); CDmElement *pDmRoot = pDmeModel->GetValueElement< CDmElement >( "__rootElement" ); for ( int i = 0; i < nDeformerCount; ++i ) { FbxSkin *pFbxSkin = FbxCast< FbxSkin >( pFbxMesh->GetDeformer( i, FbxDeformer::eSkin ) ); if ( !pFbxSkin ) continue; const int nClusterCount = pFbxSkin->GetClusterCount(); for ( int j = 0; j < nClusterCount; ++j ) { FbxCluster *pFbxCluster = pFbxSkin->GetCluster( j ); FbxNode *pFbxLinkNode = pFbxCluster->GetLink(); if ( !pFbxLinkNode ) continue; const FbxToDmxMap_t::IndexType_t nDmxIndex = fbxToDmxMap.Find( pFbxLinkNode ); if ( !fbxToDmxMap.IsValidIndex( nDmxIndex ) ) { Warning( "Warning! FBX mesh (%s) skinned to unmapped node (%s)\n", pFbxNode->GetName(), pFbxLinkNode->GetName() ); continue; } CDmeJoint *pDmeJoint = CastElement< CDmeJoint >( fbxToDmxMap.Element( nDmxIndex ) ); if ( !pDmeJoint ) { Warning( "Warning! FBX mesh (%s) skinned to non-joint (%s)\n", pFbxNode->GetName(), pFbxLinkNode->GetName() ); continue; } const int nJointIndex = pDmeModel->GetJointIndex( pDmeJoint ); if ( nJointIndex < 0 ) { Warning( "Warning! FBX mesh (%s) skinned to joint (%s) which isn't in DmeModel\n", pFbxNode->GetName(), pDmeJoint->GetName() ); continue; } const FbxCluster::ELinkMode nLinkMode = pFbxCluster->GetLinkMode(); if ( nLinkMode != FbxCluster::eNormalize && nLinkMode != FbxCluster::eTotalOne ) { Warning( "Warning! FBX mesh (%s) skinned to joint (%s) but mode %s isn't supported, only %s & %s\n", pFbxNode->GetName(), pDmeJoint->GetName(), szClusterModes[nLinkMode], szClusterModes[FbxCluster::eNormalize], szClusterModes[FbxCluster::eTotalOne] ); continue; } const int nIndexCount = pFbxCluster->GetControlPointIndicesCount(); const int *pnIndices = pFbxCluster->GetControlPointIndices(); const double *pfWeights = pFbxCluster->GetControlPointWeights(); if ( nIndexCount > 0 ) { ++nJointCount; if ( Verbose2() ) { Msg( " + DmeJoint[%3d] %5d Vertices - %s\n", nJointIndex, nIndexCount, pDmeJoint->GetName() ); } } for ( int k = 0; k < nIndexCount; ++k ) { const int nVertexIndex = pnIndices[k]; Assert( nVertexIndex >= 0 && nVertexIndex <= vertexSkinWeights.Count() ); CVertexSkin &vertexSkinWeight = vertexSkinWeights[ nVertexIndex ]; vertexSkinWeight.AddWeight( nJointIndex, pfWeights[k] ); } } CUtlVector< CVertexSkin::SkinError_t > errorsAdded; // Clean up skin weights for ( int j = 0; j < vertexSkinWeights.Count(); ++j ) { const CVertexSkin::SkinError_t nErr = vertexSkinWeights[j].Renormalize(); if ( nErr == CVertexSkin::kSkinErrorNone ) continue; // Do a linear search, maximum of 3 elements and exit early if this error is already added, AddConversionError does a string compare to an array if ( errorsAdded.HasElement( nErr ) ) continue; errorsAdded.AddToTail( nErr ); switch ( nErr ) { case CVertexSkin::kSkinErrorTooManyWeights: AddConversionError( pDmRoot->GetFileId(), CFmtStr( "Too many skin weights on some vertices, maximum is %d weights per vertex, will renormalize and discard smallest weights", CVertexSkin::kMaxWeights ).Access() ); break; case CVertexSkin::kSkinErrorZeroWeight: AddConversionError( pDmRoot->GetFileId(), "Zero skin weights on one or more vertices" ); break; case CVertexSkin::kSkinErrorNegativeWeight: AddConversionError( pDmRoot->GetFileId(), "Negative total skin weights on one or more vertices" ); break; } } CUtlVector< int > jointIndices; jointIndices.EnsureCapacity( CVertexSkin::kMaxWeights * nPositionCount ); CUtlVector< float > jointWeights; jointWeights.EnsureCapacity( CVertexSkin::kMaxWeights * nPositionCount ); for ( int j = 0; j < vertexSkinWeights.Count(); ++j ) { const CVertexSkin &vertexSkinWeight = vertexSkinWeights[ j ]; for ( int k = 0; k < CVertexSkin::kMaxWeights; ++k ) { jointIndices.AddToTail( vertexSkinWeight.GetIndex( k ) ); jointWeights.AddToTail( vertexSkinWeight.GetWeight( k ) ); } } FieldIndex_t nJointWeightsIndex; FieldIndex_t nJointIndicesIndex; pDmeVertexData->CreateJointWeightsAndIndices( CVertexSkin::kMaxWeights, &nJointWeightsIndex, &nJointIndicesIndex ); Assert( jointIndices.Count() == CVertexSkin::kMaxWeights * nPositionCount ); Assert( jointWeights.Count() == CVertexSkin::kMaxWeights * nPositionCount ); pDmeVertexData->AddVertexData( nJointIndicesIndex, jointIndices.Count() ); pDmeVertexData->SetVertexData( nJointIndicesIndex, 0, jointIndices.Count(), AT_INT, jointIndices.Base() ); pDmeVertexData->AddVertexData( nJointWeightsIndex, jointWeights.Count() ); pDmeVertexData->SetVertexData( nJointWeightsIndex, 0, jointWeights.Count(), AT_FLOAT, jointWeights.Base() ); break; // Only do the first skin deformer } if ( Verbose1() && !Verbose2() && nJointCount ) { Msg( " * Skinning Mesh Joints %3d: %s\n", nJointCount, pDmeDag->GetName() ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::AddBlendShapes_R( const FbxToDmxMap_t &fbxToDmxMap, CDmElement *pDmeRoot, FbxNode *pFbxNode ) const { FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( pFbxNodeAttribute ) { const FbxNodeAttribute::EType nAttributeType = pFbxNodeAttribute->GetAttributeType(); if ( nAttributeType == FbxNodeAttribute::eMesh ) { FbxToDmxMap_t::IndexType_t nDmxIndex = fbxToDmxMap.Find( pFbxNode ); if ( fbxToDmxMap.IsValidIndex( nDmxIndex ) ) { AddBlendShape( fbxToDmxMap.Element( nDmxIndex ), fbxToDmxMap, pDmeRoot, pFbxNode ); } } } for ( int i = 0; i < pFbxNode->GetChildCount(); ++i ) { AddBlendShapes_R( fbxToDmxMap, pDmeRoot, pFbxNode->GetChild( i ) ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::AddBlendShape( CDmeDag *pDmeDag, const FbxToDmxMap_t &fbxToDmxMap, CDmElement *pDmeRoot, FbxNode *pFbxNode ) const { if ( !pDmeDag ) return; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( !pFbxNodeAttribute || pFbxNodeAttribute->GetAttributeType() != FbxNodeAttribute::eMesh ) return; FbxMesh *pFbxMesh = reinterpret_cast< FbxMesh * >( pFbxNodeAttribute ); if ( !pFbxMesh ) return; CDmeMesh *pDmeMesh = CastElement< CDmeMesh >( pDmeDag->GetShape() ); if ( !pDmeMesh ) return; CDmeVertexData *pDmeVertexData = pDmeMesh->GetBindBaseState(); if ( !pDmeVertexData ) { Warning( "Warning! No DmeVertexData on DmeMesh (%s)\n", pDmeMesh->GetName() ); return; } const CUtlVector< Vector > &positionData = pDmeVertexData->GetPositionData(); const int nPositionCount = positionData.Count(); if ( nPositionCount <= 0 ) { Warning( "Warning! Invalid position count (%d) on DmeMesh (%s)\n", nPositionCount, pDmeMesh->GetName() ); return; } const int nBlendShapeCount = pFbxMesh->GetDeformerCount( FbxDeformer::eBlendShape ); if ( nBlendShapeCount <= 0 ) return; CUtlVector< Vector > vDeltas; CUtlVector< int > nDeltaIndices; CDmeCombinationOperator *pDmeComboOp = FindOrCreateComboOp( pDmeRoot ); if ( !pDmeComboOp ) { Warning( "Warning! Couldn't create DmeComboOp for FbxMesh %s\n", pFbxNode->GetName() ); return; } for ( int nBlendShapeIndex = 0; nBlendShapeIndex < nBlendShapeCount; ++nBlendShapeIndex ) { FbxBlendShape *pFbxBlendShape = FbxCast< FbxBlendShape >( pFbxMesh->GetDeformer(nBlendShapeIndex, FbxDeformer::eBlendShape) ); const int nBlendShapeChannelCount = pFbxBlendShape->GetBlendShapeChannelCount(); if ( Verbose1() ) { if ( nBlendShapeCount > 1 ) { Msg( " * BlendShape Mesh: Shapes %3d - BlendShape %3d/%3d - %s\n", nBlendShapeChannelCount, nBlendShapeIndex + 1, nBlendShapeCount, pDmeMesh->GetName() ); } else { Msg( " * BlendShape Mesh: Shapes %3d - %s\n", nBlendShapeChannelCount, pDmeMesh->GetName() ); } } for ( int nBlendShapeChannelIndex = 0; nBlendShapeChannelIndex < nBlendShapeChannelCount; ++nBlendShapeChannelIndex ) { FbxBlendShapeChannel *pFbxBlendShapeChannel = pFbxBlendShape->GetBlendShapeChannel( nBlendShapeChannelIndex ); const char *pszDeltaName = pFbxBlendShapeChannel->GetName(); const char *pszExt = V_GetFileExtension( pszDeltaName ); // FBX will probably name this blendShape. we just want if ( pszExt ) { pszDeltaName = pszExt; } CUtlString sDeltaName; CleanupName( sDeltaName, pszDeltaName ); const int nTargetShapeCount = pFbxBlendShapeChannel->GetTargetShapeCount(); for ( int nTargetShapeIndex = 0; nTargetShapeIndex < nTargetShapeCount; ++nTargetShapeIndex ) { FbxShape *pFbxShape = pFbxBlendShapeChannel->GetTargetShape( nTargetShapeIndex ); const int nControlPointsCount = pFbxShape->GetControlPointsCount(); const FbxVector4 *pvControlPoints = pFbxShape->GetControlPoints(); const int nControlPointIndicesCount = pFbxShape->GetControlPointIndicesCount(); const int *pnControlPointIndices = pFbxShape->GetControlPointIndices(); // pvControlPoints has as many values as the original mesh but only the vertices indexed by pnControlPointIndices are deltas Assert( nControlPointsCount == nPositionCount ); if ( nControlPointsCount == nPositionCount ) { vDeltas.SetCount( nControlPointIndicesCount ); nDeltaIndices.SetCount( nControlPointIndicesCount ); for ( int i = 0; i < nControlPointIndicesCount; ++i ) { const int nDeltaIndex = pnControlPointIndices[i]; const Vector &vDme = positionData[ nDeltaIndex ]; const FbxVector4 &vFbxSrc = pvControlPoints[ nDeltaIndex ]; vDeltas[i] = Vector( vFbxSrc[0] - vDme.x, vFbxSrc[1] - vDme.y, vFbxSrc[2] - vDme.z ); nDeltaIndices[i] = nDeltaIndex; } } else { Warning( "Warning! FbxMesh %s Has Unhandled FbxBlendShapeChannel %s\n", pFbxNode->GetName(), pszDeltaName ); } CDmeVertexDeltaData *pDmeVertexDeltaData = pDmeMesh->FindOrCreateDeltaState( pszDeltaName, m_bOptUnderscoreForCorrectors ); pszDeltaName = pDmeVertexDeltaData->GetName(); // The delta name could be changed if m_bOptUnderscoreForCorrectors is on, i.e. B_A becomes A_B pDmeVertexDeltaData->FlipVCoordinate( true ); FieldIndex_t nDeltaPosIndex = pDmeVertexDeltaData->CreateField( CDmeVertexDeltaData::FIELD_POSITION ); pDmeVertexDeltaData->AddVertexData( nDeltaPosIndex, vDeltas.Count() ); pDmeVertexDeltaData->SetVertexData( nDeltaPosIndex, 0, vDeltas.Count(), AT_VECTOR3, vDeltas.Base() ); pDmeVertexDeltaData->SetVertexIndices( nDeltaPosIndex, 0, nDeltaIndices.Count(), nDeltaIndices.Base() ); if ( FindOrCreateControl( pDmeComboOp, pszDeltaName ) ) { pDmeVertexDeltaData->SetValue( "corrected", true ); } // TODO: See if FBX handle exporting expressions? Face rules? // TODO: Handle normals if ( Verbose2() ) { Msg( " + Shape %3d Deltas: %5d - %s\n", nBlendShapeChannelIndex, nControlPointIndicesCount, sDeltaName.String() ); } AssertMsg( nTargetShapeCount == 1, "TODO: Handle multiple target shapes?" ); break; } } } pDmeComboOp->AddTarget( pDmeMesh ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CDmeCombinationOperator * CDmFbxSerializer::FindOrCreateComboOp( CDmElement *pDmeRoot ) const { CDmeCombinationOperator *pDmeComboOp = pDmeRoot->GetValueElement< CDmeCombinationOperator >( "combinationOperator" ); if ( !pDmeComboOp ) { pDmeComboOp = CreateElement< CDmeCombinationOperator >( "combinationOperator", pDmeRoot->GetFileId() ); pDmeRoot->SetValue( "combinationOperator", pDmeComboOp ); } return pDmeComboOp; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmFbxSerializer::FindOrCreateControl( CDmeCombinationOperator *pDmeComboOp, const char *pszName ) const { if ( m_bOptUnderscoreForCorrectors ) { if ( strchr( pszName, '_' ) ) // Don't create a control if m_bOptUnderscoreForCorrectors is true and name has '_' in it return false; } const ControlIndex_t nControlIndex = pDmeComboOp->FindOrCreateControl( pszName, false, true ); return nControlIndex >= 0; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CUtlString CleanupName( const char *pszName ) { CUtlString sCleanName( pszName ); const int nNameLen = V_strlen( pszName ); if ( nNameLen > 1 ) { const char *pszColon = V_strrchr( pszName, ':' ); if ( pszColon && nNameLen > ( pszColon - pszName + 1 ) && *( pszColon + 1 ) ) { sCleanName.Set( pszColon + 1 ); } } return sCleanName; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CUtlString GetName( const FbxNode *pFbxNode ) { const FbxString sName = pFbxNode->GetNameOnly(); const char *pszName = sName.Buffer(); return CleanupName( pszName ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::GetName( CUtlString &sCleanName, const FbxNode *pFbxNode ) const { FbxString sName = pFbxNode->GetNameOnly(); const char *pszName = sName.Buffer(); sCleanName = ::CleanupName( pszName ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::CleanupName( CUtlString &sCleanName, const char *pszName ) const { sCleanName = ::CleanupName( pszName ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- struct FbxDmxAnimData_t { FbxDmxAnimData_t() : m_pFbxNode( nullptr ) , m_pDmePosLog( nullptr ) , m_pDmeRotLog( nullptr ) , m_pParent( nullptr ) , m_pFbxAnimCurve( nullptr ) , m_pDmeFloatLog( nullptr ) , m_flMin( 0.0f ) , m_flMax( 1.0f ) , m_bNormalize( false ) { SetIdentityMatrix( m_mWorldInverse ); } void SetWorldMatrix( const matrix3x4_t &mWorld ) { MatrixInvert( mWorld, m_mWorldInverse ); } FbxNode *m_pFbxNode; CDmeVector3Log *m_pDmePosLog; CDmeQuaternionLog *m_pDmeRotLog; FbxDmxAnimData_t *m_pParent; matrix3x4_t m_mWorldInverse; FbxProperty m_fbxProperty; FbxAnimCurve *m_pFbxAnimCurve; CDmeFloatLog *m_pDmeFloatLog; bool m_bNormalize; float m_flMin; float m_flMax; }; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static CDmeFloatLog *CreateMorphChannel( const char *pszChannelName, CDmeChannelsClip *pDmeChannelsClip ) { CDmeChannel *pDmeFloatChannel = CreateElement< CDmeChannel >( CFmtStr( "%s_flex_channel", pszChannelName ).Access(), pDmeChannelsClip->GetFileId() ); pDmeFloatChannel->SetMode( CM_PLAY ); CDmElement *pDmeToElement = CreateElement< CDmElement >( pszChannelName, pDmeFloatChannel->GetFileId() ); pDmeToElement->SetValue( "flexWeight", 0.0f ); pDmeFloatChannel->SetOutput( pDmeToElement, "flexWeight" ); CDmeFloatLog *pDmeFloatLog = pDmeFloatChannel->CreateLog< float >(); pDmeFloatLog->SetValueThreshold( 1.0e-6 ); pDmeFloatChannel->SetValue( "channelType", "morph" ); pDmeChannelsClip->m_Channels.AddToTail( pDmeFloatChannel ); return pDmeFloatLog; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::ComputeVstFlexSliderAnimDataList_R( FbxAnimLayer *pFbxAnimLayer, CUtlVector< FbxDmxAnimData_t * > &animDataList, CDmeChannelsClip *pDmeChannelsClip, const CDmFbxSerializer::FbxToDmxMap_t &fbxToDmxMap, FbxNode *pFbxNode ) const { CDmFbxSerializer::FbxToDmxMap_t::IndexType_t nIndex = fbxToDmxMap.Find( pFbxNode ); if ( !fbxToDmxMap.IsValidIndex( nIndex ) ) return; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( pFbxNodeAttribute ) { const FbxNodeAttribute::EType nAttributeType = pFbxNodeAttribute->GetAttributeType(); if ( nAttributeType == FbxNodeAttribute::eNull ) { const CUtlString sNodeName = ::GetName( pFbxNode ); if ( sNodeName == "vstFlexSlider" || pFbxNode->FindProperty( "vstFlexSlider", false ).IsValid() ) { for ( FbxProperty fbxProperty = pFbxNode->GetFirstProperty(); fbxProperty.IsValid(); fbxProperty = pFbxNode->GetNextProperty( fbxProperty ) ) { const FbxDataType fbxDataType = fbxProperty.GetPropertyDataType(); const EFbxType eFbxType = fbxDataType.GetType(); if ( fbxProperty.GetFlag( FbxPropertyFlags::eUserDefined ) && ( eFbxType == eFbxDouble || eFbxType == eFbxFloat ) ) { const char *pszChannelName = fbxProperty.GetNameAsCStr(); FbxDmxAnimData_t *pAnimData = animDataList[ animDataList.AddToTail( new FbxDmxAnimData_t ) ]; animDataList.AddToTail( pAnimData ); pAnimData->m_fbxProperty = fbxProperty; if ( fbxProperty.HasMinLimit() && fbxProperty.HasMaxLimit() ) { pAnimData->m_bNormalize = true; pAnimData->m_flMin = fbxProperty.GetMinLimit(); pAnimData->m_flMax = fbxProperty.GetMaxLimit(); } pAnimData->m_pDmeFloatLog = CreateMorphChannel( pszChannelName, pDmeChannelsClip ); if ( Verbose1() ) { Msg( " - vstFlexSlider FlexControl Channel: %s\n", pszChannelName ); } } } } } } for ( int i = 0; i < pFbxNode->GetChildCount(); ++i ) { ComputeVstFlexSliderAnimDataList_R( pFbxAnimLayer, animDataList, pDmeChannelsClip, fbxToDmxMap, pFbxNode->GetChild( i ) ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::ComputeAnimDataList_R( FbxAnimLayer *pFbxAnimLayer, CUtlVector< FbxDmxAnimData_t * > &animDataList, CDmeChannelsClip *pDmeChannelsClip, const CDmFbxSerializer::FbxToDmxMap_t &fbxToDmxMap, FbxNode *pFbxNode, FbxDmxAnimData_t *pAnimDataParent ) const { CDmFbxSerializer::FbxToDmxMap_t::IndexType_t nIndex = fbxToDmxMap.Find( pFbxNode ); if ( !fbxToDmxMap.IsValidIndex( nIndex ) ) return; FbxNodeAttribute *pFbxNodeAttribute = pFbxNode->GetNodeAttribute(); if ( pFbxNodeAttribute && pFbxNodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh ) { FbxGeometry *pFbxGeometry = static_cast< FbxGeometry * >( pFbxNodeAttribute ); const int nBlendShapeDeformerCount = pFbxGeometry->GetDeformerCount( FbxDeformer::eBlendShape ); for ( int lBlendShapeIndex = 0; lBlendShapeIndex < nBlendShapeDeformerCount; ++lBlendShapeIndex ) { FbxBlendShape *pFbxBlendShape = static_cast< FbxBlendShape * >( pFbxGeometry->GetDeformer( lBlendShapeIndex, FbxDeformer::eBlendShape ) ); if ( pFbxBlendShape ) { const int nBlendShapeChannelCount = pFbxBlendShape->GetBlendShapeChannelCount(); for ( int nChannelIndex = 0; nChannelIndex < nBlendShapeChannelCount; ++nChannelIndex ) { FbxBlendShapeChannel *pFbxBlendShapeChannel = pFbxBlendShape->GetBlendShapeChannel( nChannelIndex ); const CUtlString sChannelName = ::CleanupName( pFbxBlendShapeChannel->GetName() ); const char *pszChannelName = sChannelName.Get(); const char *pszExt = V_GetFileExtension( pszChannelName ); // FBX will probably name this blendShape. we just want if ( pszExt ) { pszChannelName = pszExt; } FbxAnimCurve *pFbxAnimCurve = pFbxGeometry->GetShapeChannel( lBlendShapeIndex, nChannelIndex, pFbxAnimLayer, true ); if ( pFbxAnimCurve ) { bool bDefined = false; FOR_EACH_VEC( animDataList, aIt ) { const FbxDmxAnimData_t *pFbxDmxAnimData = animDataList[aIt]; if ( pFbxDmxAnimData->m_pDmeFloatLog && pFbxDmxAnimData->m_fbxProperty.IsValid() && !V_stricmp( pFbxDmxAnimData->m_fbxProperty.GetName(), pszChannelName ) ) { if ( Verbose1() ) { Warning( " - vstFlexSlider Already defined FlexControl Channel: %s, ignoring FbxBlendShapeChannel with same name\n", pszChannelName ); } bDefined = true; break; } } if ( !bDefined ) { if ( Verbose1() ) { Msg( " - FbxBlendShapeChannel: %s\n", pszChannelName ); } FbxDmxAnimData_t *pAnimData = animDataList[ animDataList.AddToTail( new FbxDmxAnimData_t ) ]; animDataList.AddToTail( pAnimData ); pAnimData->m_pFbxAnimCurve = pFbxAnimCurve; pAnimData->m_pDmeFloatLog = CreateMorphChannel( pszChannelName, pDmeChannelsClip ); } } } } } } FbxDmxAnimData_t *pAnimData = new FbxDmxAnimData_t; animDataList.AddToTail( pAnimData ); CDmeDag *pDmeDag = fbxToDmxMap.Element( nIndex ); const bool bIsRoot = pDmeDag->GetValue( "__rootNode", false ); pAnimData->m_pFbxNode = pFbxNode; pAnimData->m_pParent = bIsRoot ? NULL : pAnimDataParent; CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); CDmeChannel *pDmePosChannel = CreateElement< CDmeChannel >( CFmtStr( "%s_p", pDmeTransform->GetName() ).Access(), pDmeChannelsClip->GetFileId() ); pDmePosChannel->SetMode( CM_PLAY ); pDmePosChannel->SetOutput( pDmeTransform, "position" ); CDmeVector3Log *pDmePosLog = pDmePosChannel->CreateLog< Vector >(); pDmePosLog->SetValueThreshold( 1.0e-6 ); pDmeChannelsClip->m_Channels.AddToTail( pDmePosChannel ); CDmeChannel *pDmeRotChannel = CreateElement< CDmeChannel >( CFmtStr( "%s_o", pDmeTransform->GetName() ).Access(), pDmeChannelsClip->GetFileId() ); pDmeRotChannel->SetMode( CM_PLAY ); pDmeRotChannel->SetOutput( pDmeTransform, "orientation" ); CDmeQuaternionLog *pDmeRotLog = pDmeRotChannel->CreateLog< Quaternion >(); pDmeRotLog->SetValueThreshold( 1.0e-6 ); pDmeChannelsClip->m_Channels.AddToTail( pDmeRotChannel ); pAnimData->m_pDmePosLog = pDmePosLog; pAnimData->m_pDmeRotLog = pDmeRotLog; for ( int i = 0; i < pFbxNode->GetChildCount(); ++i ) { ComputeAnimDataList_R( pFbxAnimLayer, animDataList, pDmeChannelsClip, fbxToDmxMap, pFbxNode->GetChild( i ), pAnimData ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::LoadAnimation( CDmElement *pDmeRoot, CDmeModel *pDmeModel, const FbxToDmxMap_t &fbxToDmxMap, FbxScene *pFbxScene, FbxNode *pFbxRootNode, FbxTime::EMode eFbxTimeMode ) const { // Only process the first animation layer of the first animation stack FbxAnimStack *pFbxAnimStack = pFbxScene->GetSrcObject(); if ( !pFbxAnimStack ) return; FbxAnimLayer *pFbxAnimLayer = pFbxAnimStack->GetMember(); if ( !pFbxAnimLayer ) return; FbxArray< FbxString * > mAnimStackNameArray; pFbxScene->FillAnimStackNameArray( mAnimStackNameArray ); if ( mAnimStackNameArray.GetCount() <= 0 ) return; pFbxScene->SetCurrentAnimationStack( pFbxAnimStack ); FbxTakeInfo *pFbxTakeInfo = pFbxScene->GetTakeInfo( *( mAnimStackNameArray[0] ) ); if ( !pFbxTakeInfo ) return; const FbxTime tFbxStart = pFbxTakeInfo->mLocalTimeSpan.GetStart(); const FbxLongLong nFbxStart = tFbxStart.GetFrameCount( eFbxTimeMode ); const FbxTime tFbxEnd = pFbxTakeInfo->mLocalTimeSpan.GetStop(); const FbxLongLong nFbxEnd = tFbxEnd.GetFrameCount( eFbxTimeMode ); const double flFrameRate = FbxTime::GetFrameRate( eFbxTimeMode ); const DmeFramerate_t dmeFrameRate( static_cast< float >( flFrameRate ) ); if ( Verbose1() ) { Msg( " * Animation Stack %s Layer %s - %s\n", pFbxAnimStack->GetName(), pFbxAnimLayer->GetName(), mAnimStackNameArray[0]->Buffer() ); Msg( " + Fbx Time Start: %6.2f End: %6.2f Duration: %6.2f @ %6.2f fps\n", tFbxStart.GetSecondDouble(), tFbxEnd.GetSecondDouble(), pFbxTakeInfo->mLocalTimeSpan.GetDuration().GetSecondDouble(), flFrameRate ); Msg( " + Fbx Frame Start: %6lld End: %6lld Duration: %6lld @ %6.2f fps\n", nFbxStart, nFbxEnd, pFbxTakeInfo->mLocalTimeSpan.GetDuration().GetFrameCount( eFbxTimeMode ), flFrameRate ); } const char *pszAnimName = "anim"; // TODO: Get this value, name of file? DmFileId_t nFileId = pDmeRoot->GetFileId(); CDmeAnimationList *pDmeAnimationList = CreateElement< CDmeAnimationList >( pszAnimName, nFileId ); CDmeChannelsClip *pDmeChannelsClip = CreateElement< CDmeChannelsClip >( pszAnimName, nFileId ); pDmeAnimationList->AddAnimation( pDmeChannelsClip ); pDmeRoot->SetValue( "animationList", pDmeAnimationList ); pDmeModel->SetValue( "animationList", pDmeAnimationList ); pDmeChannelsClip->SetStartTime( DmeTime_t( tFbxStart.GetSecondDouble() ) ); pDmeChannelsClip->SetTimeOffset( pDmeChannelsClip->GetStartTime() ); pDmeChannelsClip->SetValue( "frameRate", static_cast< int >( dmeFrameRate.GetFramesPerSecond() ) ); CUtlVector< FbxDmxAnimData_t * > animList; // Look for flex animation on vstFlexSlider nodes first for ( int i = 0; i < pFbxRootNode->GetChildCount(); ++i ) { ComputeVstFlexSliderAnimDataList_R( pFbxAnimLayer, animList, pDmeChannelsClip, fbxToDmxMap, pFbxRootNode->GetChild( i ) ); } for ( int i = 0; i < pFbxRootNode->GetChildCount(); ++i ) { ComputeAnimDataList_R( pFbxAnimLayer, animList, pDmeChannelsClip, fbxToDmxMap, pFbxRootNode->GetChild( i ), NULL ); } matrix3x4_t mDmeWorld; matrix3x4_t mDmeLocal; FbxLongLong nFbxCurrent; FbxTime tFbxCurrent; for ( nFbxCurrent = nFbxStart; nFbxCurrent <= nFbxEnd; nFbxCurrent += 1 ) { tFbxCurrent.SetFrame( nFbxCurrent, eFbxTimeMode ); const DmeTime_t tDmeCurrent( tFbxCurrent.GetSecondDouble() ); if ( Verbose2() ) { Msg( " * Fbx Time: %6.2f Fbx Frame: %6lld Dmx Time: %6.2f Dmx Frame: %d\n", tFbxCurrent.GetSecondDouble(), tFbxCurrent.GetFrameCount( eFbxTimeMode ), tDmeCurrent.GetSeconds(), tDmeCurrent.CurrentFrame( dmeFrameRate ) ); } for ( int i = 0; i < animList.Count(); ++i ) { FbxDmxAnimData_t *pAnimData = animList[ i ]; if ( pAnimData->m_pDmeFloatLog ) { if ( pAnimData->m_fbxProperty.IsValid() ) { float flFloatValue = pAnimData->m_fbxProperty.EvaluateValue< float >( tFbxCurrent ); if ( pAnimData->m_bNormalize ) { flFloatValue = RemapVal( flFloatValue, pAnimData->m_flMin, pAnimData->m_flMax, 0.0f, 1.0f ); } pAnimData->m_pDmeFloatLog->SetKey( tDmeCurrent, flFloatValue ); } else if ( pAnimData->m_pFbxAnimCurve ) { const float flFloatValue = pAnimData->m_pFbxAnimCurve->Evaluate( tFbxCurrent ); pAnimData->m_pDmeFloatLog->SetKey( tDmeCurrent, flFloatValue ); } continue; } FbxNode *pFbxNode = pAnimData->m_pFbxNode; const FbxAMatrix &mFbxWorld = pFbxNode->EvaluateGlobalTransform( tFbxCurrent ); const FbxVector4 vFbxTranslate = mFbxWorld.GetT(); const FbxQuaternion qFbxRotate = mFbxWorld.GetQ(); Assert( vFbxTranslate[3] == 0.0 || vFbxTranslate[3] == 1.0 ); Vector vDmeTranslate( vFbxTranslate[0], vFbxTranslate[1], vFbxTranslate[2] ); Quaternion qDmeRotate( qFbxRotate[0], qFbxRotate[1], qFbxRotate[2], qFbxRotate[3] ); AngleMatrix( RadianEuler( qDmeRotate ), vDmeTranslate, mDmeWorld ); pAnimData->SetWorldMatrix( mDmeWorld ); if ( pAnimData->m_pParent ) { MatrixMultiply( pAnimData->m_pParent->m_mWorldInverse, mDmeWorld, mDmeLocal ); MatrixAngles( mDmeLocal, qDmeRotate, vDmeTranslate ); } pAnimData->m_pDmePosLog->SetKey( tDmeCurrent, vDmeTranslate ); pAnimData->m_pDmeRotLog->SetKey( tDmeCurrent, qDmeRotate ); } } pDmeChannelsClip->SetDuration( DmeTime_t( pFbxTakeInfo->mLocalTimeSpan.GetDuration().GetSecondDouble() ) ); if ( Verbose1() ) { Msg( " + Dme Time Start: %6.2f End: %6.2f Duration: %6.2f @ %6.2f fps\n", pDmeChannelsClip->GetStartTime().GetSeconds(), pDmeChannelsClip->GetEndTime().GetSeconds(), pDmeChannelsClip->GetDuration().GetSeconds(), flFrameRate ); Msg( " + Dme Frame Start: %6d End: %6d Duration: %6d @ %6.2f fps\n", pDmeChannelsClip->GetStartTime().CurrentFrame( dmeFrameRate ), pDmeChannelsClip->GetEndTime().CurrentFrame( dmeFrameRate ), pDmeChannelsClip->GetDuration().CurrentFrame( dmeFrameRate ), flFrameRate ); } animList.PurgeAndDeleteElements(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- FbxManager *CDmFbxSerializer::GetFbxManager() { static bool bWarned = false; if ( !g_pFbx ) { if ( !bWarned ) { Log_Warning( LOG_FBX_SYSTEM, "Warning! FBX system not initialized\n" ); bWarned = true; } return NULL; } return g_pFbx->GetFbxManager(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CDmFbxSerializer::AddConversionError( DmFileId_t nDmFileId, const char *pszErrorMsg ) { if ( !pszErrorMsg ) return; CDmElement *pDmRoot = g_pDataModel->GetElement( g_pDataModel->GetFileRoot( nDmFileId ) ); if ( !pDmRoot ) return; CDmAttribute *pConversionErrorsAttr = pDmRoot->AddAttribute( "conversionErrors", AT_STRING_ARRAY ); if ( pConversionErrorsAttr ) { CDmrStringArray conversionErrors( pConversionErrorsAttr ); for ( int i = 0; i < conversionErrors.Count(); ++i ) { if ( !V_stricmp( conversionErrors[i], pszErrorMsg ) ) return; } conversionErrors.AddToTail( pszErrorMsg ); Warning( "%s\n", pszErrorMsg ); } }