You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3319 lines
109 KiB
3319 lines
109 KiB
/* = 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 );
|
|
}
|
|
}
|