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.
1706 lines
45 KiB
1706 lines
45 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Read SMD and create DMX data |
|
// |
|
//============================================================================= |
|
|
|
|
|
// Because we use STL |
|
#pragma warning( disable: 4530 ) |
|
|
|
|
|
// Standard includes |
|
#include <io.h> |
|
#include <algorithm> |
|
#include <deque> |
|
#include <fstream> |
|
#include <list> |
|
#include <map> |
|
#include <string> |
|
|
|
|
|
// Valve includes |
|
#include "movieobjects/dmeanimationlist.h" |
|
#include "movieobjects/dmechannel.h" |
|
#include "movieobjects/dmedag.h" |
|
#include "movieobjects/dmemesh.h" |
|
#include "movieobjects/dmefaceset.h" |
|
#include "movieobjects/dmematerial.h" |
|
#include "movieobjects/dmemodel.h" |
|
#include "movieobjects/dmsmdserializer.h" |
|
#include "filesystem.h" |
|
#include "tier1/characterset.h" |
|
#include "tier1/fmtstr.h" |
|
#include "tier2/tier2.h" |
|
#include "mathlib/mathlib.h" |
|
|
|
|
|
// Last include |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//============================================================================= |
|
// |
|
// CDmSmdSerializer |
|
// |
|
//============================================================================= |
|
//----------------------------------------------------------------------------- |
|
// Convert from SMD -> DME |
|
//----------------------------------------------------------------------------- |
|
bool CDmSmdSerializer::Unserialize( |
|
CUtlBuffer &utlBuf, |
|
const char * /* pszEncodingName */, |
|
int /* nEncodingVersion */, |
|
const char * /* pszSourceFormatName */, |
|
int /* nSourceFormatVersion */, |
|
DmFileId_t nDmFileId, |
|
DmConflictResolution_t /* nDmConflictResolution */, |
|
CDmElement **ppDmRoot ) |
|
{ |
|
if ( !ppDmRoot ) |
|
return false; |
|
|
|
const char *pszFilename = g_pDataModel->GetFileName( nDmFileId ); |
|
|
|
if ( pszFilename ) |
|
{ |
|
char szFilename[ MAX_PATH ]; |
|
V_strncpy( szFilename, pszFilename, ARRAYSIZE( szFilename ) ); |
|
V_FixSlashes( szFilename ); |
|
|
|
*ppDmRoot = ReadSMD( utlBuf, nDmFileId, szFilename, NULL ); |
|
} |
|
else |
|
{ |
|
*ppDmRoot = ReadSMD( utlBuf, nDmFileId, "utlBuffer", NULL ); |
|
} |
|
|
|
return *ppDmRoot != NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
CDmElement *CDmSmdSerializer::ReadSMD( |
|
const char *pszFilename, |
|
CDmeMesh **ppDmeMeshCreated /* = NULL */ ) |
|
{ |
|
char szFilename[ MAX_PATH ]; |
|
V_strncpy( szFilename, pszFilename, ARRAYSIZE( szFilename ) ); |
|
V_FixSlashes( szFilename ); |
|
|
|
CUtlBuffer utlBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, utlBuf ) ) |
|
return NULL; |
|
|
|
DmFileId_t nDmFileId = g_pDataModel->FindOrCreateFileId( pszFilename ); |
|
|
|
return ReadSMD( utlBuf, nDmFileId, szFilename, ppDmeMeshCreated ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmSmdSerializer::SetUpAxis( CDmSmdSerializer::Axis_t nUpAxis ) |
|
{ |
|
m_nUpAxis = clamp( nUpAxis, X_AXIS, Z_AXIS ); |
|
|
|
switch ( m_nUpAxis ) |
|
{ |
|
case X_AXIS: // X Up |
|
AngleMatrix( RadianEuler( -M_PI / 2.0, M_PI / 2.0, 0.0 ), m_mAdj ); |
|
MatrixInverseTranspose( m_mAdj, m_mAdjNormal ); |
|
break; |
|
case Y_AXIS: // Y Up |
|
SetIdentityMatrix( m_mAdj ); |
|
SetIdentityMatrix( m_mAdjNormal ); |
|
break; |
|
case Z_AXIS: |
|
default: |
|
AngleMatrix( RadianEuler( -M_PI / 2.0, 0.0, 0.0 ), m_mAdj ); |
|
MatrixInverseTranspose( m_mAdj, m_mAdjNormal ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Tests whether the passed buffer is an all whitespace line or not |
|
//----------------------------------------------------------------------------- |
|
static bool ParserIsBlankLine( const char *pszBuf ) |
|
{ |
|
for ( const char *pChar = pszBuf; *pChar; ++pChar ) |
|
{ |
|
if ( !V_isspace( static_cast< unsigned char >( *pChar ) ) ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Skips over whitespace |
|
//----------------------------------------------------------------------------- |
|
static const char *ParserSkipSpace( const char *pszBuf ) |
|
{ |
|
// Skip to first non-whitespace character |
|
for ( ;; ) |
|
{ |
|
if ( !V_isspace( static_cast< unsigned char >( *pszBuf ) ) ) |
|
break; |
|
|
|
++pszBuf; |
|
} |
|
|
|
return pszBuf; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true the specified buffer is a comment line |
|
// meaning that it starts with // (with optional white space preceding the //) |
|
//----------------------------------------------------------------------------- |
|
static bool IsCommentLine( const char *pszBuf ) |
|
{ |
|
pszBuf = ParserSkipSpace( pszBuf ); |
|
|
|
if ( *pszBuf && *pszBuf == '/' && *( pszBuf + 1 ) == '/' ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool ParserHandleVersion( const char *pszBuf, int *pnVersion = NULL ) |
|
{ |
|
pszBuf = ParserSkipSpace( pszBuf ); |
|
|
|
if ( !( *pszBuf && V_strnicmp( pszBuf, "version", 7 ) == 0 ) ) |
|
return false; |
|
|
|
if ( pnVersion ) |
|
{ |
|
*pnVersion = strtol( pszBuf + 7, NULL, 0 ); // Skip past "version" |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool ParserHandleSectionStart( const char *pszBuf, const char *pszSectionId ) |
|
{ |
|
pszBuf = ParserSkipSpace( pszBuf ); |
|
|
|
if ( ! *pszBuf ) |
|
return false; |
|
|
|
const int nCmd = V_stricmp( pszBuf, pszSectionId ); |
|
if ( !nCmd ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool ParserHandleTime( const char *pszBuf, int &nTime ) |
|
{ |
|
if ( sscanf( pszBuf, "time %d", &nTime ) == 1 ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Strip trailing CR/NL |
|
//----------------------------------------------------------------------------- |
|
static void Chomp( char *pszBuf ) |
|
{ |
|
char *pChar = pszBuf + V_strlen( pszBuf ) - 1; |
|
|
|
while ( pChar >= pszBuf && ( *pChar == '\n' || *pChar == '\r' ) ) |
|
{ |
|
*pChar = '\0'; |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void GetLine( CUtlBuffer &utlBuf, char *pszBuf, int nMaxLen ) |
|
{ |
|
utlBuf.GetLine( pszBuf, nMaxLen ); |
|
Chomp( pszBuf ); |
|
} |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
class CQcData |
|
{ |
|
public: |
|
CQcData() |
|
: m_nUpAxis( CDmSmdSerializer::Z_AXIS ) |
|
, m_scale( 1.0f ) |
|
{} |
|
|
|
bool ParseQc( const CUtlString &smdPath, const CUtlString &qcPath ); |
|
|
|
bool GetQcData( const CUtlString &smdPath ); |
|
|
|
CDmSmdSerializer::Axis_t m_nUpAxis; |
|
float m_scale; |
|
std::list< std::string > m_cdmaterials; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool HandleQcHints( |
|
const char *pBuf, |
|
CQcData &qcData ) |
|
{ |
|
if ( !IsCommentLine( pBuf ) ) |
|
return false; |
|
|
|
char key[ 512 ]; |
|
key[0] = '\0'; |
|
|
|
char val[ 512 ]; |
|
val[0] = '\0'; |
|
|
|
if ( sscanf( pBuf, "// %511s=%511s", key, val ) == 2 ) |
|
{ |
|
if ( Q_stricmp( key, "UPAXIS" ) == 0 ) |
|
{ |
|
if ( strpbrk( val, "xX" ) ) |
|
{ |
|
qcData.m_nUpAxis = CDmSmdSerializer::X_AXIS; |
|
} |
|
else if ( strpbrk( val, "yY" ) ) |
|
{ |
|
qcData.m_nUpAxis = CDmSmdSerializer::Y_AXIS; |
|
} |
|
else if ( strpbrk( val, "zZ" ) ) |
|
{ |
|
qcData.m_nUpAxis = CDmSmdSerializer::Z_AXIS; |
|
} |
|
} |
|
} |
|
|
|
key[ ARRAYSIZE(key) - 1 ] = '\0'; |
|
val[ ARRAYSIZE(val) - 1 ] = '\0'; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void Tokenize( CUtlVector< CUtlString > &tokens, const char *pszBuf ) |
|
{ |
|
tokens.RemoveAll(); |
|
CUtlString strBuf( pszBuf ); |
|
|
|
for ( char *pszToken = strtok( (char *)strBuf.Get(), " \t\n" ); pszToken; pszToken = strtok( NULL, " \t\n" ) ) |
|
{ |
|
tokens.AddToTail( pszToken ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmSmdSerializer::ParserGetNodeName( const char *pszBuf, CUtlString &sName ) const |
|
{ |
|
sName = ParserSkipSpace( pszBuf ); |
|
FixNodeName( sName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CDmSmdSerializer::ParserHandleSkeletonLine( |
|
const char *pszBuf, |
|
CUtlString &sName, |
|
int &nId, |
|
int &nParentId ) const |
|
{ |
|
const char *pszOrigBuf = pszBuf; |
|
|
|
pszBuf = ParserSkipSpace( pszBuf ); |
|
|
|
char szTmpBuf[ 512 ]; |
|
if ( sscanf( pszBuf, "%d \"%[^\"]\" %d", &nId, szTmpBuf, &nParentId ) == 3 ) |
|
{ |
|
ParserGetNodeName( szTmpBuf, sName ); |
|
return true; |
|
} |
|
|
|
Warning( "Warning! Ignoring malformed skeleton line: %s\n", pszOrigBuf ); |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static CUtlString PathJoin( const char *pszStr1, const char *pszStr2 ) |
|
{ |
|
char szPath[MAX_PATH]; |
|
V_ComposeFileName( pszStr1, pszStr2, szPath, sizeof( szPath ) ); |
|
return CUtlString( szPath ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CQcData::ParseQc( |
|
const CUtlString &smdPath, |
|
const CUtlString &qcPath ) |
|
{ |
|
bool bRetVal = false; |
|
|
|
if ( _access( qcPath.Get(), 04 ) == 0 ) |
|
{ |
|
try |
|
{ |
|
std::string buf; |
|
std::ifstream ifs( qcPath.Get() ); |
|
|
|
CUtlVector< CUtlString > tokens; |
|
|
|
while ( std::getline( ifs, buf ) ) |
|
{ |
|
Tokenize( tokens, buf.c_str() ); |
|
|
|
if ( tokens.Count() < 1 ) |
|
continue; |
|
|
|
if ( !V_stricmp( tokens[0], "$upaxis" ) ) |
|
{ |
|
if ( strchr( tokens[1].Get(), 'y' ) || strchr( tokens[1].Get(), 'Y' ) ) |
|
{ |
|
m_nUpAxis = CDmSmdSerializer::Y_AXIS; |
|
} |
|
else if ( strchr( tokens[1].Get(), 'x' ) || strchr( tokens[1].Get(), 'X' ) ) |
|
{ |
|
m_nUpAxis = CDmSmdSerializer::X_AXIS; |
|
} |
|
else |
|
{ |
|
m_nUpAxis = CDmSmdSerializer::Z_AXIS; |
|
} |
|
} |
|
else if ( !V_stricmp( tokens[0], "$scale" ) ) |
|
{ |
|
m_scale = strtod( tokens[1].Get(), NULL ); |
|
} |
|
else if ( !V_stricmp( tokens[0], "$cdmaterials" ) ) |
|
{ |
|
m_cdmaterials.push_back( tokens[1].Get() ); |
|
} |
|
} |
|
|
|
bRetVal = true; |
|
} |
|
catch ( ... ) |
|
{ |
|
} |
|
} |
|
|
|
if ( m_cdmaterials.empty() ) |
|
{ |
|
// If m_cdmaterials is empty, then put the relative smd path onto the cdmaterials path |
|
|
|
char szBuf0[MAX_PATH]; |
|
V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); |
|
V_StripFilename( szBuf0 ); |
|
V_FixSlashes( szBuf0, '/' ); |
|
|
|
CUtlVector< char *, CUtlMemory< char *, int > > sPathArray; |
|
V_SplitString( szBuf0, "/", sPathArray ); |
|
|
|
CUtlString sRelSmdPath; |
|
|
|
bool bJoin = false; |
|
for ( int i = 0; i < sPathArray.Count(); ++i ) |
|
{ |
|
if ( !bJoin && !V_stricmp( sPathArray[i], "models" ) ) |
|
{ |
|
bJoin = true; |
|
} |
|
|
|
if ( bJoin ) |
|
{ |
|
sRelSmdPath = PathJoin( sRelSmdPath.Get(), sPathArray[i] ); |
|
} |
|
} |
|
sRelSmdPath.FixSlashes( '/' ); |
|
|
|
m_cdmaterials.push_back( sRelSmdPath.Get() ); |
|
} |
|
|
|
return bRetVal; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CQcData::GetQcData( |
|
const CUtlString &smdPath ) |
|
{ |
|
try |
|
{ |
|
// Look for same thing named with a .qc extension |
|
char szBuf0[MAX_PATH]; |
|
char szBuf1[MAX_PATH]; |
|
|
|
V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); |
|
V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); |
|
if ( _access( szBuf0, 04 ) == 0 ) |
|
return ParseQc( smdPath, szBuf0 ); |
|
|
|
// Remove "_reference" if found |
|
char *pszRef = V_stristr( szBuf0, "_reference.qc" ); |
|
if ( pszRef ) |
|
{ |
|
*pszRef = '\0'; |
|
|
|
V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); |
|
if ( _access( szBuf0, 04 ) == 0 ) |
|
return ParseQc( smdPath, szBuf0 ); |
|
} |
|
else |
|
{ |
|
// Add _reference |
|
|
|
V_SetExtension( szBuf0, "", ARRAYSIZE( szBuf0 ) ); |
|
V_strcat( szBuf0, "_reference", ARRAYSIZE( szBuf0 ) ); |
|
V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); |
|
if ( _access( szBuf0, 04 ) == 0 ) |
|
return ParseQc( smdPath, szBuf0 ); |
|
} |
|
|
|
// Look for any *.qc file in the same directory as the smd that contains the smd pathname |
|
V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); |
|
V_FixSlashes( szBuf0 ); |
|
|
|
V_FileBase( szBuf0, szBuf1, ARRAYSIZE( szBuf1 ) ); |
|
CUtlString sFileBase0( "\"" ); |
|
sFileBase0 += szBuf1; |
|
sFileBase0 += "\""; |
|
|
|
V_SetExtension( szBuf1, ".smd", ARRAYSIZE( szBuf1 ) ); |
|
CUtlString sFileBase1( "\"" ); |
|
sFileBase1 += szBuf1; |
|
sFileBase1 += "\""; |
|
|
|
V_ExtractFilePath( szBuf0, szBuf1, ARRAYSIZE( szBuf1 ) ); |
|
CUtlString sFilePath = szBuf1; |
|
|
|
if ( sFileBase0.Length() > 0 && sFilePath.Length() > 0 ) |
|
{ |
|
struct _finddata_t qcFile; |
|
long hFile; |
|
|
|
CUtlVector< CUtlString > tokens; |
|
|
|
/* Find first .qc file in current directory */ |
|
|
|
CUtlString sQcGlob = sFilePath; |
|
sQcGlob += "*.qc"; |
|
|
|
if ( ( hFile = _findfirst( sQcGlob.Get(), &qcFile ) ) != -1L ) |
|
{ |
|
/* Find the rest of the .qc files */ |
|
do { |
|
CUtlString sQcFile = sFilePath; |
|
sQcFile += qcFile.name; |
|
|
|
std::ifstream ifs( sQcFile.Get() ); |
|
std::string buf; |
|
|
|
while ( std::getline( ifs, buf ) ) |
|
{ |
|
if ( V_stristr( buf.c_str(), sFileBase0.Get() ) || V_stristr( buf.c_str(), sFileBase1.Get() ) ) |
|
{ |
|
_findclose( hFile ); |
|
return ParseQc( smdPath, sQcFile ); |
|
} |
|
} |
|
} while( _findnext( hFile, &qcFile ) == 0 ); |
|
|
|
_findclose( hFile ); |
|
} |
|
} |
|
} |
|
catch ( const std::exception &e ) |
|
{ |
|
Error( "Exception: %s\n", e.what() ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
#define MAX_WEIGHTS_PER_VERTEX 3 |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
class CVertexWeight |
|
{ |
|
public: |
|
int m_nBoneIndex; |
|
float m_flWeight; |
|
|
|
CVertexWeight() |
|
: m_nBoneIndex( -1 ) |
|
, m_flWeight( 0.0f ) |
|
{} |
|
|
|
inline void Reset() |
|
{ |
|
m_nBoneIndex = -1; |
|
m_flWeight = 0.0f; |
|
} |
|
|
|
inline bool operator==( const CVertexWeight &rhs ) const |
|
{ |
|
return m_nBoneIndex == rhs.m_nBoneIndex && m_flWeight == rhs.m_flWeight; |
|
} |
|
|
|
inline bool operator!=( const CVertexWeight &rhs ) const |
|
{ |
|
return m_nBoneIndex != rhs.m_nBoneIndex || m_flWeight != rhs.m_flWeight; |
|
} |
|
}; |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
class CVertex |
|
{ |
|
public: |
|
Vector m_vPosition; |
|
Vector m_vNormal; |
|
Vector2D m_vUV; |
|
uint m_nWeights; |
|
CVertexWeight m_vertexWeights[ MAX_WEIGHTS_PER_VERTEX ]; |
|
|
|
CVertex() |
|
{ |
|
Reset(); |
|
} |
|
|
|
inline bool operator==( const CVertex &rhs ) const |
|
{ |
|
if ( m_vPosition != rhs.m_vPosition || |
|
m_vNormal != rhs.m_vNormal || |
|
m_vUV != rhs.m_vUV || |
|
m_nWeights != rhs.m_nWeights ) |
|
return false; |
|
|
|
for ( uint i = 0; i != m_nWeights; ++i ) |
|
{ |
|
if ( m_vertexWeights[ i ] != rhs.m_vertexWeights[ i ] ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
inline void Reset() |
|
{ |
|
m_nWeights = 0; |
|
for ( int i = 0; i < ARRAYSIZE( m_vertexWeights ); ++i ) |
|
{ |
|
m_vertexWeights[i].Reset(); |
|
} |
|
} |
|
}; |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
class CTriangle |
|
{ |
|
public: |
|
uint m_nVertices; |
|
CVertex m_vertices[ 3 ]; |
|
|
|
CTriangle() |
|
{ |
|
Reset(); |
|
} |
|
|
|
bool Valid() const; |
|
|
|
inline void Reset() |
|
{ |
|
m_nVertices = 0; |
|
m_vertices[ 0U ].Reset(); |
|
m_vertices[ 1U ].Reset(); |
|
m_vertices[ 2U ].Reset(); |
|
} |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CTriangle::Valid() const |
|
{ |
|
// A triangle is valid it it has three vertices and all three vertices are unique |
|
|
|
if ( m_nVertices != 3U ) |
|
return false; |
|
|
|
if ( m_vertices[0] == m_vertices[1] ) |
|
return false; |
|
|
|
if ( m_vertices[1] == m_vertices[2] ) |
|
return false; |
|
|
|
if ( m_vertices[2] == m_vertices[0] ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
class CShadingGroup |
|
{ |
|
public: |
|
std::string m_materialPath; |
|
std::map< int, std::deque< int > > m_componentListMap; |
|
}; |
|
|
|
|
|
//============================================================================= |
|
// |
|
//============================================================================= |
|
enum ParserState_t |
|
{ |
|
kUnknown, |
|
kPreamble, |
|
kGeneral, |
|
kNodes, |
|
kSkeleton, |
|
kTriangles |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool ParserAddJoint( |
|
CDmSmdSerializer::SmdJointMap_t &smdJointMap, |
|
int nId, |
|
const char *pszName, |
|
int nParentId, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
if ( nId < 0 ) |
|
{ |
|
Error( "Error! %s(%d) : node error : invalid node id %d, must be >= 0: Line '%d \"%s\" %d'\n", |
|
pszFilename, nLineNumber, nId, nId, pszName, nParentId ); |
|
return false; |
|
} |
|
|
|
if ( smdJointMap.IsValidIndex( smdJointMap.Find( nId ) ) ) |
|
{ |
|
Error( "Error! %s(%d) : node error : node id %d already defined\n", |
|
pszFilename, nLineNumber, nId ); |
|
return false; |
|
} |
|
|
|
CDmSmdSerializer::SmdJoint_t &smdJoint = smdJointMap.Element( smdJointMap.Insert( nId ) ); |
|
|
|
smdJoint.m_nId = nId; |
|
smdJoint.m_sName = pszName; |
|
smdJoint.m_nParentId = MAX( -1, nParentId ); |
|
smdJoint.m_nLineNumber = nLineNumber; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static bool ParserCreateJoint( |
|
const CDmSmdSerializer::SmdJointMap_t &smdJointMap, |
|
CDmeModel *pDmeModel, |
|
CDmeChannelsClip *pDmeChannelsClip, |
|
CDmSmdSerializer::SmdJoint_t *pSmdJoint, |
|
const char *pszFilename ) |
|
{ |
|
const CUtlString &sName = pSmdJoint->m_sName; |
|
const int nId = pSmdJoint->m_nId; |
|
const int nParentId = pSmdJoint->m_nParentId; |
|
const int nLineNumber = pSmdJoint->m_nLineNumber; |
|
|
|
CDmeJoint *pDmeJoint = CreateElement< CDmeJoint >( sName.Get(), pDmeModel->GetFileId() ); |
|
|
|
if ( !pDmeJoint ) |
|
{ |
|
Error( "%s(%d) : node error : can't create joint %d(%s)\n", |
|
pszFilename, nLineNumber, nId, sName.Get() ); |
|
return false; |
|
} |
|
|
|
if ( nParentId < 0 ) |
|
{ |
|
if ( !pDmeModel ) |
|
{ |
|
Error( "%s(%d) : node error : No DmeModel passed for root joint %d(%s)\n", |
|
pszFilename, nLineNumber, nId, sName.Get() ); |
|
DestroyElement( pDmeJoint ); |
|
|
|
return false; |
|
} |
|
|
|
pDmeModel->AddChild( pDmeJoint ); |
|
CDmAttribute *pRootJointAttr = pDmeJoint->AddAttribute( "__rootJoint", AT_BOOL ); |
|
pRootJointAttr->AddFlag( FATTRIB_DONTSAVE ); |
|
pRootJointAttr->SetValue( true ); |
|
} |
|
else |
|
{ |
|
if ( nParentId == nId ) |
|
{ |
|
Error( "%s(%d) : node error : joint %d(%s) is its own parent\n", |
|
pszFilename, nLineNumber, nId, sName.Get() ); |
|
DestroyElement( pDmeJoint ); |
|
|
|
return false; |
|
} |
|
|
|
const int nParentIdIndex = smdJointMap.Find( nParentId ); |
|
|
|
if ( !smdJointMap.IsValidIndex( nParentIdIndex ) ) |
|
{ |
|
Error( "%s(%d) : node error : joint %d(%s) has invalid parentId (%d)\n", |
|
pszFilename, nLineNumber, nId, sName.Get(), nParentId ); |
|
DestroyElement( pDmeJoint ); |
|
|
|
return false; |
|
} |
|
|
|
CDmeDag *pDmeDagParent = smdJointMap.Element( nParentIdIndex ).m_pDmeDag; |
|
if ( pDmeDagParent ) |
|
{ |
|
pDmeDagParent->AddChild( pDmeJoint ); |
|
} |
|
else |
|
{ |
|
Error( "%s(%d) : node error : joint %d(%s) has invalid parentId (%d)\n", |
|
pszFilename, nLineNumber, nId, sName.Get(), nParentId ); |
|
} |
|
} |
|
|
|
if ( pDmeChannelsClip ) |
|
{ |
|
CDmeTransform *pDmeTransform = pDmeJoint->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 ); |
|
|
|
CDmAttribute *pPosLogAttr = pDmeJoint->AddAttribute( "__posLog", AT_ELEMENT ); |
|
pPosLogAttr->AddFlag( FATTRIB_DONTSAVE ); |
|
pPosLogAttr->SetValue( pDmePosLog ); |
|
|
|
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 ); |
|
|
|
CDmAttribute *pRotLogAttr = pDmeJoint->AddAttribute( "__rotLog", AT_ELEMENT ); |
|
pRotLogAttr->AddFlag( FATTRIB_DONTSAVE ); |
|
pRotLogAttr->SetValue( pDmeRotLog ); |
|
} |
|
|
|
pSmdJoint->m_pDmeDag = pDmeJoint; |
|
pDmeModel->AddJoint( pDmeJoint ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used by ParserAddJoints, sorts joints by parent index |
|
//----------------------------------------------------------------------------- |
|
static bool SmdJointLessFunc( const CDmSmdSerializer::SmdJoint_t *pLhs, const CDmSmdSerializer::SmdJoint_t *pRhs ) |
|
{ |
|
// try to preserve joints without parents in their original order as much as possible |
|
if ( pLhs->m_nParentId < 0 ) |
|
return pLhs->m_nId < pRhs->m_nId; |
|
|
|
return pLhs->m_nParentId < pRhs->m_nParentId; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void ParserCreateJoints( |
|
CDmSmdSerializer::SmdJointMap_t &smdJointMap, |
|
CDmeModel *pDmeModel, |
|
CDmeChannelsClip *pDmeChannelsClip, |
|
const char *pszFilename ) |
|
{ |
|
// Sort the joints in order of parent, pointers only temporary and no elements |
|
// added/removed from map while pointers exist, so pointers are stable |
|
CUtlVector< CDmSmdSerializer::SmdJoint_t * > smdJointList; |
|
FOR_EACH_MAP_FAST( smdJointMap, nSmdJointMapIndex ) |
|
{ |
|
smdJointList.AddToTail( &smdJointMap.Element( nSmdJointMapIndex ) ); |
|
} |
|
|
|
std::stable_sort( smdJointList.Base(), smdJointList.Base() + smdJointList.Count(), SmdJointLessFunc ); |
|
|
|
for ( int i = 0; i < smdJointList.Count(); ++i ) |
|
{ |
|
CDmSmdSerializer::SmdJoint_t *pSmdJoint = smdJointList[i]; |
|
pSmdJoint->m_nActualId = i; |
|
|
|
ParserCreateJoint( smdJointMap, pDmeModel, pDmeChannelsClip, pSmdJoint, pszFilename ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmSmdSerializer::ParserSetJoint( |
|
const SmdJointMap_t &smdJointMap, |
|
int nFrame, |
|
int nId, |
|
const Vector &vPosition, |
|
const RadianEuler &eRadianEulerXYZ, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
const SmdJointMap_t::IndexType_t nIdIndex = smdJointMap.Find( nId ); |
|
if ( !smdJointMap.IsValidIndex( nIdIndex ) ) |
|
{ |
|
Error( "%s(%d) : skeleton error : can't find joint %d\n", |
|
pszFilename, nLineNumber, nId ); |
|
return; |
|
} |
|
|
|
CDmeDag *pDmeDag = smdJointMap.Element( nIdIndex ).m_pDmeDag; |
|
if ( !pDmeDag ) |
|
{ |
|
Error( "%s(%d) : skeleton error : no dmedag created for can't find joint %d (%s)\n", |
|
pszFilename, nLineNumber, nId, smdJointMap.Element( nIdIndex ).m_sName.Get() ); |
|
return; |
|
} |
|
|
|
const Quaternion qOrientation = eRadianEulerXYZ; |
|
|
|
if ( pDmeDag->GetValue( "__rootJoint", false ) && GetUpAxis() != CDmSmdSerializer::Y_AXIS ) |
|
{ |
|
matrix3x4_t mPre; |
|
matrix3x4_t mPost; |
|
AngleMatrix( qOrientation, vPosition, mPre ); |
|
ConcatTransforms( m_mAdj, mPre, mPost ); |
|
pDmeDag->GetTransform()->SetTransform( mPost ); |
|
} |
|
else |
|
{ |
|
pDmeDag->GetTransform()->SetPosition( vPosition ); |
|
pDmeDag->GetTransform()->SetOrientation( qOrientation ); |
|
} |
|
|
|
if ( m_bOptAnimation ) |
|
{ |
|
const DmeTime_t tCurrent( static_cast< float >( nFrame ), DmeFramerate_t( m_flFrameRate ) ); |
|
|
|
CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); |
|
|
|
CDmeVector3Log *pDmePosLog = pDmeDag->GetValueElement< CDmeVector3Log >( "__posLog" ); |
|
pDmePosLog->SetKey( tCurrent, pDmeTransform->GetPosition() ); |
|
|
|
CDmeQuaternionLog *pDmeRotLog = pDmeDag->GetValueElement< CDmeQuaternionLog >( "__rotLog" ); |
|
pDmeRotLog->SetKey( tCurrent, pDmeTransform->GetOrientation() ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns -1 if invalid |
|
//----------------------------------------------------------------------------- |
|
static int GetActualId( const CDmSmdSerializer::SmdJointMap_t &smdJointMap, int nId ) |
|
{ |
|
const CDmSmdSerializer::SmdJointMap_t::IndexType_t nIdIndex = smdJointMap.Find( nId ); |
|
|
|
if ( !smdJointMap.IsValidIndex( nIdIndex ) ) |
|
{ |
|
Error( "Error! Invalid node id %d looked up\n", nId ); |
|
return -1; |
|
} |
|
|
|
return smdJointMap.Element( nIdIndex ).m_nActualId; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used by HandleVertexWeights, sorts vertex weights by weight |
|
//----------------------------------------------------------------------------- |
|
static int VertexWeightLessFunc( const void *pLhs, const void *pRhs ) |
|
{ |
|
const CVertexWeight *pVertexWeightL = reinterpret_cast< const CVertexWeight * >( pLhs ); |
|
const CVertexWeight *pVertexWeightR = reinterpret_cast< const CVertexWeight * >( pRhs ); |
|
|
|
if ( pVertexWeightL->m_nBoneIndex < 0 ) |
|
{ |
|
if ( pVertexWeightR->m_nBoneIndex < 0 ) |
|
{ |
|
return 0; |
|
} |
|
else |
|
{ |
|
return 1; |
|
} |
|
} |
|
else if ( pVertexWeightR->m_nBoneIndex < 0 ) |
|
{ |
|
return -1; |
|
} |
|
|
|
if ( pVertexWeightL->m_flWeight > pVertexWeightR->m_flWeight ) |
|
{ |
|
return -1; |
|
} |
|
else if ( pVertexWeightL->m_flWeight < pVertexWeightR->m_flWeight ) |
|
{ |
|
return 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void HandleVertexWeights( |
|
const CDmSmdSerializer::SmdJointMap_t &smdJointMap, |
|
int nFallbackBoneIndexId, |
|
CVertex &vertex, |
|
const char *pszLine, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
for ( int i = 0; i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) |
|
{ |
|
vertex.m_vertexWeights[i].Reset(); |
|
} |
|
|
|
CUtlVector< CVertexWeight > tmpVertexWeights; |
|
|
|
CUtlVector< CUtlString > tokens; |
|
Tokenize( tokens, pszLine ); |
|
|
|
const float flEps = 1.0e-6; |
|
|
|
uint nTokenEnd = tokens.Count(); |
|
if ( nTokenEnd > 10 ) |
|
{ |
|
int nId = -1; |
|
float flWeight = 0.0f; |
|
for ( uint i = 10; i < nTokenEnd; ++i ) |
|
{ |
|
nId = strtol( tokens[ i ].Get(), NULL, 0 ); |
|
++i; |
|
flWeight = strtod( tokens[ i ].Get(), NULL ); |
|
|
|
if ( nId < 0 || flWeight < flEps ) |
|
continue; |
|
|
|
const int nActualId = GetActualId( smdJointMap, nId ); |
|
|
|
if ( nActualId < 0 ) |
|
{ |
|
Error( "%s(%d) : triangle error : ignoring unknown joint id(%d) with actual id(%d) for vertex weight\n", |
|
pszFilename, nLineNumber, nId, nActualId ); |
|
continue; |
|
} |
|
|
|
CVertexWeight &tmpVertexWeight = tmpVertexWeights[ tmpVertexWeights.AddToTail() ]; |
|
tmpVertexWeight.m_nBoneIndex = nActualId; |
|
tmpVertexWeight.m_flWeight = flWeight; |
|
} |
|
|
|
// Sort vertex weights inversely by weight |
|
qsort( tmpVertexWeights.Base(), tmpVertexWeights.Count(), sizeof( CVertexWeight ), VertexWeightLessFunc ); |
|
|
|
// Take at most the top ARRAYSIZE( vertex.m_vertexWeights ) from tmpVertexWeights |
|
// Figure out actual weight count and total weight |
|
|
|
float flTotalWeight = 0.0f; |
|
vertex.m_nWeights = 0; |
|
|
|
for ( int i = 0; i < tmpVertexWeights.Count() && i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) |
|
{ |
|
CVertexWeight &vertexWeight( vertex.m_vertexWeights[ i ] ); |
|
vertexWeight = tmpVertexWeights[i]; |
|
flTotalWeight += vertexWeight.m_flWeight; |
|
vertex.m_nWeights += 1; |
|
} |
|
|
|
// If there are valid user specified weights then renormalize |
|
if ( vertex.m_nWeights > 0 && flTotalWeight > flEps ) |
|
{ |
|
for ( uint i = 0; i != ARRAYSIZE( vertex.m_vertexWeights ); ++i ) |
|
{ |
|
CVertexWeight &vertexWeight( vertex.m_vertexWeights[ i ] ); |
|
if ( vertexWeight.m_nBoneIndex >= 0 ) |
|
{ |
|
vertexWeight.m_flWeight /= flTotalWeight; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// No valid user weights, assign to fallback bone with weight of 1 |
|
vertex.m_vertexWeights[ 0 ].m_nBoneIndex = GetActualId( smdJointMap, nFallbackBoneIndexId ); |
|
if ( vertex.m_vertexWeights[0].m_nBoneIndex < 0 ) |
|
{ |
|
// Vertex is not weighted, can't find fallback bone, this is invalid |
|
vertex.m_nWeights = 0; |
|
vertex.m_vertexWeights[0].m_nBoneIndex = -1; |
|
vertex.m_vertexWeights[0].m_flWeight = 0.0f; |
|
} |
|
else |
|
{ |
|
vertex.m_nWeights = 1; |
|
vertex.m_vertexWeights[0].m_flWeight = 1.0f; |
|
} |
|
|
|
// Assign rest of weights to -1, 0.0f |
|
for ( uint i = 1; i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) |
|
{ |
|
vertex.m_vertexWeights[i].m_nBoneIndex = -1; |
|
vertex.m_vertexWeights[i].m_flWeight = 0.0f; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static CDmeMesh *CreateDmeMesh( |
|
CDmeModel *pDmeModel, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
char szFileBase[MAX_PATH]; |
|
szFileBase[0] = '\0'; |
|
|
|
const char *pszMeshName = "mesh"; |
|
|
|
if ( pszFilename ) |
|
{ |
|
V_FileBase( pszFilename, szFileBase, ARRAYSIZE( szFileBase ) ); |
|
|
|
if ( V_strlen( szFileBase ) > 0 ) |
|
{ |
|
pszMeshName = szFileBase; |
|
} |
|
} |
|
|
|
CDmeDag *pDmeDag = CreateElement< CDmeDag >( pszMeshName, pDmeModel->GetFileId() ); |
|
if ( !pDmeDag ) |
|
return NULL; |
|
|
|
pDmeModel->AddChild( pDmeDag ); |
|
|
|
CUtlString sMeshName = pDmeDag->GetName(); |
|
sMeshName += "Shape"; |
|
|
|
CDmeMesh *pDmeMesh = CreateElement< CDmeMesh >( sMeshName.Get(), pDmeDag->GetFileId() ); |
|
if ( !pDmeMesh ) |
|
return NULL; |
|
|
|
CDmeVertexData *pDmeVertexData = pDmeMesh->FindOrCreateBaseState( "bind" ); |
|
pDmeMesh->SetCurrentBaseState( "bind" ); |
|
|
|
pDmeVertexData->FlipVCoordinate( true ); |
|
pDmeVertexData->CreateField( CDmeVertexData::FIELD_POSITION ); |
|
pDmeVertexData->CreateField( CDmeVertexData::FIELD_NORMAL ); |
|
pDmeVertexData->CreateField( CDmeVertexData::FIELD_TEXCOORD ); |
|
FieldIndex_t nJointWeightField; |
|
FieldIndex_t nJointIndexField; |
|
pDmeVertexData->CreateJointWeightsAndIndices( MAX_WEIGHTS_PER_VERTEX, &nJointWeightField, &nJointIndexField ); |
|
|
|
pDmeDag->SetShape( pDmeMesh ); |
|
|
|
pDmeVertexData->Resolve(); |
|
pDmeMesh->Resolve(); |
|
pDmeDag->Resolve(); |
|
|
|
return pDmeMesh; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static CDmeFaceSet *FindOrCreateFaceSet( |
|
CDmeMesh *pDmeMesh, |
|
const CUtlString &sMaterial, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
// Could cache in a hashed map or something... |
|
for ( int i = 0; i < pDmeMesh->FaceSetCount(); ++i ) |
|
{ |
|
CDmeFaceSet *pDmeFaceSet = pDmeMesh->GetFaceSet( i ); |
|
if ( !pDmeFaceSet ) |
|
continue; |
|
|
|
CDmeMaterial *pDmeMaterial = pDmeFaceSet->GetMaterial(); |
|
if ( !pDmeMaterial ) |
|
continue; |
|
|
|
if ( !V_strcmp( pDmeMaterial->GetMaterialName(), sMaterial.Get() ) ) |
|
return pDmeFaceSet; |
|
} |
|
|
|
char szFaceSetName[ MAX_PATH ]; |
|
V_FileBase( sMaterial.Get(), szFaceSetName, ARRAYSIZE( szFaceSetName ) ); |
|
|
|
CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( szFaceSetName, pDmeMesh->GetFileId() ); |
|
Assert( pDmeFaceSet ); |
|
|
|
CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( szFaceSetName, pDmeMesh->GetFileId() ); |
|
Assert( pDmeMaterial ); |
|
|
|
pDmeMaterial->SetMaterial( sMaterial.Get() ); |
|
pDmeFaceSet->SetMaterial( pDmeMaterial ); |
|
|
|
pDmeMesh->AddFaceSet( pDmeFaceSet ); |
|
|
|
return pDmeFaceSet; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void CreatePolygon( |
|
CDmeMesh *pDmeMesh, |
|
const CUtlString &sMaterial, |
|
CTriangle &triangle, |
|
const char *pszFilename, |
|
int nLineNumber ) |
|
{ |
|
if ( !triangle.Valid() || !pDmeMesh ) |
|
return; |
|
|
|
CDmeVertexData *pDmeVertexData = pDmeMesh->GetBindBaseState(); |
|
Assert( pDmeVertexData ); |
|
|
|
FieldIndex_t nPositionField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); |
|
CDmrArray< Vector > positionData = pDmeVertexData->GetVertexData( nPositionField ); |
|
|
|
FieldIndex_t nNormalField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); |
|
CDmrArray< Vector > normalData = pDmeVertexData->GetVertexData( nNormalField ); |
|
|
|
FieldIndex_t nUvField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); |
|
CDmrArray< Vector2D > uvData = pDmeVertexData->GetVertexData( nUvField ); |
|
|
|
FieldIndex_t nJointWeightField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); |
|
CDmrArray< float > jointWeightData = pDmeVertexData->GetVertexData( nJointWeightField ); |
|
|
|
FieldIndex_t nJointIndexField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); |
|
CDmrArray< int > jointIndexData = pDmeVertexData->GetVertexData( nJointIndexField ); |
|
|
|
CDmeFaceSet *pDmeFaceSet = FindOrCreateFaceSet( pDmeMesh, sMaterial, pszFilename, nLineNumber ); |
|
if ( !pDmeFaceSet ) |
|
{ |
|
Error( "%s(%d) : mesh error : couldn't find or create DmeFace set for material \"%s\" on mesh \"%s\"\n", |
|
pszFilename, nLineNumber, sMaterial.Get(), pDmeMesh->GetName() ); |
|
return; |
|
} |
|
|
|
for ( uint i = 0; i < 3; ++i ) |
|
{ |
|
const int nNewVertexIndex = pDmeVertexData->AddVertexIndices( 1 ); |
|
|
|
CVertex &cVertex = triangle.m_vertices[i]; |
|
|
|
{ |
|
// TODO: Make two positions if they are skinned differently |
|
int nPositionIndex = positionData.Find( cVertex.m_vPosition ); |
|
if ( !positionData.IsValidIndex( nPositionIndex ) ) |
|
{ |
|
nPositionIndex = positionData.AddToTail( cVertex.m_vPosition ); |
|
|
|
for ( int j = 0; j < ARRAYSIZE( cVertex.m_vertexWeights ); ++j ) |
|
{ |
|
jointWeightData.AddToTail( cVertex.m_vertexWeights[j].m_flWeight ); |
|
jointIndexData.AddToTail( cVertex.m_vertexWeights[j].m_nBoneIndex ); |
|
} |
|
} |
|
|
|
pDmeVertexData->SetVertexIndices( nPositionField, nNewVertexIndex, 1, &nPositionIndex ); |
|
|
|
const int nFaceSetIndex = pDmeFaceSet->AddIndices( 1 ); |
|
pDmeFaceSet->SetIndices( nFaceSetIndex, 1, const_cast< int * >( &nNewVertexIndex ) ); |
|
|
|
} |
|
|
|
{ |
|
int nNormalIndex = normalData.Find( cVertex.m_vNormal ); |
|
if ( !normalData.IsValidIndex( nNormalIndex ) ) |
|
{ |
|
nNormalIndex = normalData.AddToTail( cVertex.m_vNormal ); |
|
} |
|
|
|
pDmeVertexData->SetVertexIndices( nNormalField, nNewVertexIndex, 1, &nNormalIndex ); |
|
} |
|
|
|
|
|
{ |
|
int nUvIndex = uvData.Find( cVertex.m_vUV ); |
|
if ( !uvData.IsValidIndex( nUvIndex ) ) |
|
{ |
|
nUvIndex = uvData.AddToTail( cVertex.m_vUV ); |
|
} |
|
|
|
pDmeVertexData->SetVertexIndices( nUvField, nNewVertexIndex, 1, &nUvIndex ); |
|
} |
|
|
|
} |
|
|
|
static const int nFaceDelimiter = -1; |
|
const int nFaceSetIndex = pDmeFaceSet->AddIndices( 1 ); |
|
pDmeFaceSet->SetIndices( nFaceSetIndex, 1, const_cast< int * >( &nFaceDelimiter ) ); |
|
|
|
triangle.Reset(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
static CDmeFaceSet *FindOrAddFaceSet( |
|
CDmeMesh *pDmeMesh, |
|
CUtlMap< CUtlString, CDmeFaceSet * > &faceListMap, |
|
const char *pszMaterialPath ) |
|
{ |
|
CUtlMap< CUtlString, CDmeFaceSet * >::IndexType_t nIndex = faceListMap.Find( CUtlString( pszMaterialPath ) ); |
|
|
|
if ( faceListMap.IsValidIndex( nIndex ) ) |
|
return faceListMap.Element( nIndex ); |
|
|
|
CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( pszMaterialPath, pDmeMesh->GetFileId() ); |
|
CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( pszMaterialPath, pDmeMesh->GetFileId() ); |
|
Assert( pDmeFaceSet && pDmeMaterial ); |
|
pDmeMaterial->SetMaterial( pszMaterialPath ); |
|
pDmeMesh->AddFaceSet( pDmeFaceSet ); |
|
|
|
return faceListMap.Element( faceListMap.Insert( CUtlString( pszMaterialPath ), pDmeFaceSet ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
CDmeChannelsClip *FindOrCreateChannelsClip( CDmElement *pDmeRoot, const char *pszAnimationName ) |
|
{ |
|
CDmeAnimationList *pDmeAnimationList = pDmeRoot->GetValueElement< CDmeAnimationList >( "animationList" ); |
|
CDmeChannelsClip *pDmeChannelsClip = NULL; |
|
|
|
if ( pDmeAnimationList ) |
|
{ |
|
pDmeChannelsClip = pDmeAnimationList->GetAnimation( 0 ); |
|
} |
|
else |
|
{ |
|
CDmeModel *pDmeModel = pDmeRoot->GetValueElement< CDmeModel >( "skeleton" ); |
|
|
|
pDmeAnimationList = CreateElement< CDmeAnimationList >( pszAnimationName, pDmeRoot->GetFileId() ); |
|
pDmeChannelsClip = CreateElement< CDmeChannelsClip >( pszAnimationName, pDmeRoot->GetFileId() ); |
|
pDmeAnimationList->AddAnimation( pDmeChannelsClip ); |
|
pDmeRoot->SetValue( "animationList", pDmeAnimationList ); |
|
pDmeModel->SetValue( "animationList", pDmeAnimationList ); |
|
} |
|
|
|
return pDmeChannelsClip; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Common function both ReadSMD & Unserialize can call |
|
//----------------------------------------------------------------------------- |
|
CDmElement *CDmSmdSerializer::ReadSMD( |
|
CUtlBuffer &utlBuf, |
|
DmFileId_t nDmFileId, |
|
const char *pszFilename, |
|
CDmeMesh **ppDmeMeshCreated ) |
|
{ |
|
CDmElement *pDmeRoot = CreateElement< CDmElement >( "root", nDmFileId ); |
|
CDmeModel *pDmeModel = CreateElement< CDmeModel >( "model", nDmFileId ); |
|
|
|
char szAnimationName[ MAX_PATH ] = ""; |
|
|
|
if ( pszFilename ) |
|
{ |
|
V_FileBase( pszFilename, szAnimationName, ARRAYSIZE( szAnimationName ) ); |
|
} |
|
else |
|
{ |
|
pszFilename = "unknown"; |
|
} |
|
|
|
if ( V_strlen( szAnimationName ) > 0 ) |
|
{ |
|
pDmeModel->SetName( szAnimationName ); |
|
} |
|
else |
|
{ |
|
V_strcpy_safe( szAnimationName, "anim" ); |
|
} |
|
|
|
pDmeRoot->SetValue( "skeleton", pDmeModel ); |
|
|
|
if ( !m_bOptAnimation ) |
|
{ |
|
// Don't set root.model if animation only, studiomdl can't handle it |
|
pDmeRoot->SetValue( "model", pDmeModel ); |
|
} |
|
|
|
SmdJointMap_t smdJointMap( CDefOps< int >::LessFunc ); |
|
|
|
CUtlString sMaterial; |
|
|
|
CTriangle triangle; |
|
|
|
uint nLineNumber = 0; |
|
uint nBadPreambleCount = 0; |
|
ParserState_t nParserState = kPreamble; |
|
int nFrame = 0; |
|
|
|
CDmeChannelsClip *pDmeChannelsClip = NULL; |
|
|
|
if ( m_bOptAnimation ) |
|
{ |
|
pDmeChannelsClip = FindOrCreateChannelsClip( pDmeRoot, szAnimationName ); |
|
} |
|
|
|
bool bStartTimeSet = false; |
|
|
|
CQcData qcData; |
|
|
|
CDmeMesh *pDmeMesh = NULL; |
|
|
|
char szLine[ 4096 ]; |
|
|
|
while ( utlBuf.IsValid() ) |
|
{ |
|
GetLine( utlBuf, szLine, ARRAYSIZE( szLine ) ); |
|
++nLineNumber; |
|
|
|
const char *pszLine = ParserSkipSpace( szLine ); |
|
|
|
if ( ParserIsBlankLine( pszLine ) ) |
|
continue; |
|
|
|
if ( nParserState == kPreamble ) |
|
{ |
|
if ( HandleQcHints( pszLine, qcData ) ) |
|
continue; |
|
|
|
if ( ParserHandleVersion( pszLine ) ) |
|
{ |
|
if ( qcData.m_nUpAxis != m_nUpAxis ) |
|
{ |
|
Warning( "Importer UpAxis (%d) is different from UpAxis in SMD data (%d), using value found in SMD/QC\n", m_nUpAxis, qcData.m_nUpAxis ); |
|
} |
|
|
|
SetUpAxis( qcData.m_nUpAxis ); |
|
nParserState = kGeneral; |
|
continue; |
|
} |
|
|
|
Error( "%s(%d) : preamble error : expecting comment: \"%s\"\n", |
|
pszFilename, nLineNumber, szLine ); |
|
|
|
++nBadPreambleCount; |
|
if ( nBadPreambleCount > 10 ) |
|
{ |
|
Error( "%s(%d) : preamble error : too many errors, not an SMD file, aborting\n", |
|
pszFilename, nLineNumber ); |
|
break; |
|
} |
|
} |
|
else if ( nParserState == kGeneral ) |
|
{ |
|
if ( ParserHandleSectionStart( pszLine, "nodes" ) ) |
|
{ |
|
nParserState = kNodes; |
|
} |
|
else if ( ParserHandleSectionStart( pszLine, "skeleton" ) ) |
|
{ |
|
nParserState = kSkeleton; |
|
} |
|
else if ( ParserHandleSectionStart( pszLine, "triangles" ) ) |
|
{ |
|
nParserState = kTriangles; |
|
} |
|
} |
|
else if ( nParserState == kNodes ) |
|
{ |
|
if ( ParserHandleSectionStart( pszLine, "end" ) ) |
|
{ |
|
ParserCreateJoints( smdJointMap, pDmeModel, pDmeChannelsClip, pszFilename ); |
|
|
|
nParserState = kGeneral; |
|
continue; |
|
} |
|
|
|
CUtlString sName; |
|
int nId; |
|
int nParentId; |
|
|
|
if ( !m_bOptImportSkeleton || !ParserHandleSkeletonLine( pszLine, sName, nId, nParentId ) ) |
|
continue; |
|
|
|
if ( !ParserAddJoint( smdJointMap, nId, sName, nParentId, pszFilename, nLineNumber ) ) |
|
continue; |
|
} |
|
else if ( nParserState == kSkeleton ) |
|
{ |
|
if ( ParserHandleSectionStart( pszLine, "end" ) ) |
|
{ |
|
nParserState = kGeneral; |
|
continue; |
|
} |
|
|
|
if ( m_bOptAnimation && ParserHandleTime( pszLine, nFrame ) ) |
|
{ |
|
if ( !bStartTimeSet ) |
|
{ |
|
pDmeChannelsClip->SetStartTime( DmeTime_t( static_cast< float >( nFrame ), DmeFramerate_t( m_flFrameRate ) ) ); |
|
bStartTimeSet = true; |
|
} |
|
continue; |
|
} |
|
|
|
int nId; |
|
Vector vPosition; |
|
RadianEuler eRadianEulerXYZ; |
|
|
|
if ( sscanf( pszLine, "%d %f %f %f %f %f %f", &nId, &vPosition.x, &vPosition.y, &vPosition.z, &eRadianEulerXYZ.x, &eRadianEulerXYZ.y, &eRadianEulerXYZ.z ) == 7 ) |
|
{ |
|
if ( !m_bOptImportSkeleton ) |
|
continue; |
|
|
|
ParserSetJoint( smdJointMap, nFrame, nId, vPosition, eRadianEulerXYZ, pszFilename, nLineNumber ); |
|
} |
|
} |
|
else if ( nParserState == kTriangles ) |
|
{ |
|
if ( ParserHandleSectionStart( pszLine, "end" ) ) |
|
{ |
|
triangle.Reset(); |
|
nParserState = kGeneral; |
|
continue; |
|
} |
|
|
|
int nFallbackSkinJoint; |
|
Vector vPosition; |
|
Vector vNormal; |
|
Vector2D vUV; |
|
char szShadingGroup[ ARRAYSIZE( szLine ) ]; |
|
|
|
if ( sscanf( pszLine, "%d %f %f %f %f %f %f %f %f", |
|
&nFallbackSkinJoint, |
|
&vPosition.x, &vPosition.y, &vPosition.z, |
|
&vNormal.x, &vNormal.y, &vNormal.z, &vUV.x, &vUV.y ) == 9 ) |
|
{ |
|
if ( triangle.m_nVertices >= 3 ) |
|
{ |
|
Error( "%s(%d) : triangle error : Too many vertices in triangle\n", |
|
pszFilename, nLineNumber ); |
|
continue; |
|
} |
|
|
|
CVertex &vertex( triangle.m_vertices[ triangle.m_nVertices++ ] ); |
|
vertex.m_vUV = vUV; |
|
|
|
if ( GetUpAxis() != CDmSmdSerializer::Y_AXIS ) |
|
{ |
|
VectorTransform( vPosition, m_mAdj, vertex.m_vPosition ); |
|
VectorTransform( vNormal, m_mAdjNormal, vertex.m_vNormal ); |
|
} |
|
else |
|
{ |
|
vertex.m_vPosition = vPosition; |
|
vertex.m_vNormal = vNormal; |
|
} |
|
|
|
if ( m_bOptImportSkeleton ) |
|
{ |
|
HandleVertexWeights( smdJointMap, nFallbackSkinJoint, vertex, pszLine, pszFilename, nLineNumber ); |
|
} |
|
|
|
if ( !pDmeMesh ) |
|
{ |
|
pDmeMesh = CreateDmeMesh( pDmeModel, pszFilename, nLineNumber ); |
|
if ( !pDmeMesh ) |
|
{ |
|
Error( "%s(%d) : Couldn't create DmeMesh\n", |
|
pszFilename, nLineNumber ); |
|
break; |
|
} |
|
} |
|
|
|
CreatePolygon( pDmeMesh, sMaterial, triangle, pszFilename, nLineNumber ); |
|
} |
|
else if ( sscanf( pszLine, "%1024s", szShadingGroup ) == 1 ) |
|
{ |
|
char pszMaterialPath[ ARRAYSIZE( szShadingGroup ) ]; |
|
V_StripExtension( szShadingGroup, pszMaterialPath, ARRAYSIZE( pszMaterialPath ) ); |
|
|
|
sMaterial = pszMaterialPath; |
|
|
|
triangle.Reset(); |
|
} |
|
else |
|
{ |
|
Error( "%s(%d) : triangle error : unexpected data in triangles\n", |
|
pszFilename, nLineNumber ); |
|
triangle.Reset(); |
|
} |
|
} |
|
else |
|
{ |
|
Error( "%s(%d) : parser error : unknown parser state\n", |
|
pszFilename, nLineNumber ); |
|
nParserState = kGeneral; |
|
} |
|
} |
|
|
|
pDmeModel->CaptureJointsToBaseState( "bind" ); |
|
|
|
if ( pDmeChannelsClip ) |
|
{ |
|
// Reset skeleton values to the first sample |
|
FOR_EACH_MAP_FAST( smdJointMap, nJointMapIndex ) |
|
{ |
|
CDmeDag *pDmeDag = smdJointMap.Element( nJointMapIndex ).m_pDmeDag; |
|
if ( pDmeDag ) |
|
{ |
|
CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); |
|
|
|
CDmeVector3Log *pDmePosLog = pDmeDag->GetValueElement< CDmeVector3Log >( "__posLog" ); |
|
pDmeTransform->SetPosition( pDmePosLog->GetKeyValue( 0 ) ); |
|
|
|
CDmeQuaternionLog *pDmeRotLog = pDmeDag->GetValueElement< CDmeQuaternionLog >( "__rotLog" ); |
|
pDmeTransform->SetOrientation( pDmeRotLog->GetKeyValue( 0 ) ); |
|
} |
|
} |
|
|
|
pDmeChannelsClip->SetDuration( DmeTime_t( nFrame, DmeFramerate_t( m_flFrameRate ) ) - pDmeChannelsClip->GetStartTime() ); |
|
} |
|
|
|
return pDmeRoot; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CDmSmdSerializer::FixNodeName( CUtlString &sName ) const |
|
{ |
|
char szTmpBuf0[ MAX_PATH ]; |
|
char szTmpBuf1[ MAX_PATH ]; |
|
|
|
V_strncpy( szTmpBuf0, sName.Get(), ARRAYSIZE( szTmpBuf0 ) ); |
|
|
|
// Strip trailing quotes |
|
char *pszName = szTmpBuf0 + sName.Length() - 1; |
|
while ( pszName >= szTmpBuf0 && *pszName && *pszName == '"' ) |
|
{ |
|
*pszName = '\0'; |
|
--pszName; |
|
} |
|
|
|
// Strip leading quotes |
|
pszName = szTmpBuf0; |
|
while ( *pszName && *pszName == '"') |
|
{ |
|
++pszName; |
|
} |
|
|
|
if ( m_bOptAutoStripPrefix ) |
|
{ |
|
// Get the string before the '.' |
|
V_FileBase( pszName, szTmpBuf1, ARRAYSIZE( szTmpBuf1 ) ); |
|
V_strncpy( szTmpBuf0, szTmpBuf1, ARRAYSIZE( szTmpBuf0 ) ); |
|
pszName = szTmpBuf0; |
|
} |
|
|
|
if ( !m_sNodeDelPrefix.IsEmpty() && StringHasPrefix( pszName, m_sNodeDelPrefix.Get() ) ) |
|
{ |
|
pszName = const_cast< char * >( StringAfterPrefix( pszName, m_sNodeDelPrefix.Get() ) ); |
|
} |
|
|
|
if ( m_sNodeAddPrefix.IsEmpty() ) |
|
{ |
|
sName = pszName; |
|
} |
|
else |
|
{ |
|
sName = m_sNodeAddPrefix; |
|
sName += pszName; |
|
} |
|
} |