csgo-2018-source/fbxutils/dmfbxserializer.cpp
2021-07-24 21:11:47 -07:00

3320 lines
109 KiB
C++

/* = 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.<THING> we just want <THING>
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.<THING> we just want <THING>
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<FbxAnimStack>();
if ( !pFbxAnimStack )
return;
FbxAnimLayer *pFbxAnimLayer = pFbxAnimStack->GetMember<FbxAnimLayer >();
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 );
}
}