|
|
//===== Copyright � 1996-2008, Valve Corporation, All rights reserved. ======//
//
// Purpose: Loads mesh data from dmx files
//
// $NoKeywords: $
//
//===========================================================================//
// Valve includes
#include "bspflags.h"
#include "movieobjects/dmeattributereference.h"
#include "movieobjects/dmeconnectionoperator.h"
#include "movieobjects/dmemodel.h"
#include "movieobjects/dmemesh.h"
#include "movieobjects/dmefaceset.h"
#include "movieobjects/dmematerial.h"
#include "movieobjects/dmeclip.h"
#include "movieobjects/dmechannel.h"
#include "movieobjects/dmeattachment.h"
#include "movieobjects/dmeanimationlist.h"
#include "movieobjects/dmecombinationoperator.h"
#include "movieobjects/dmerigconstraintoperators.h"
#include "mdlobjects/dmebbox.h"
#include "mdlobjects/dmelod.h"
#include "mdlobjects/dmelodlist.h"
#include "mdlobjects/dmebodygroup.h"
#include "mdlobjects/dmebodygrouplist.h"
#include "mdlobjects/dmehitbox.h"
#include "mdlobjects/dmehitboxset.h"
#include "mdlobjects/dmehitboxsetlist.h"
#include "mdlobjects/dmesequence.h"
#include "mdlobjects/dmesequencelist.h"
#include "mdlobjects/dmecollisionmodel.h"
#include "mdlobjects/dmecollisionjoints.h"
#include "mdlobjects/dmeincludemodellist.h"
#include "mdlobjects/dmedefinebone.h"
#include "mdlobjects/dmedefinebonelist.h"
#include "mdlobjects/dmematerialgroup.h"
#include "mdlobjects/dmematerialgrouplist.h"
#include "mdlobjects/dmeeyeball.h"
#include "mdlobjects/dmeeyeballglobals.h"
#include "mdlobjects/dmeeyelid.h"
#include "mdlobjects/dmeboneweight.h"
#include "mdlobjects/dmebonemask.h"
#include "mdlobjects/dmebonemasklist.h"
#include "mdlobjects/dmeik.h"
#include "mdlobjects/dmeanimcmd.h"
#include "mdlobjects/dmemotioncontrol.h"
#include "mdlobjects/dmeposeparameter.h"
#include "mdlobjects/dmeposeparameterlist.h"
#include "mdlobjects/dmeanimblocksize.h"
#include "mdlobjects/dmeboneflexdriver.h"
#include "mdlobjects/dmejigglebone.h"
#include "mdlobjects/dmemouth.h"
#include "fbxutils/dmfbxserializer.h"
#include "mdlobjects/mpp_utils.h"
// Local includes
#include "studiomdl.h"
#include "collisionmodel.h"
#include "tier1/fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// The current model being loaded...
//-----------------------------------------------------------------------------
s_model_t *g_pCurrentModel = NULL;
void UnifyIndices( s_source_t *psource );
//-----------------------------------------------------------------------------
// Mapping of bone transforms
//-----------------------------------------------------------------------------
struct BoneTransformMap_t { // Number of bones
int m_nBoneCount;
// The order in which transforms appear in this list specifies their bone indices
CDmeTransform *m_ppTransforms[MAXSTUDIOSRCBONES];
// m_pnDmeModelToMdl[bone index in DmeModel] == bone index in studiomdl
int m_pnDmeModelToMdl[MAXSTUDIOSRCBONES];
// m_pnMdlToDmeModel[bone index in studiomdl] == bone index in DmeModel
int m_pnMdlToDmeModel[MAXSTUDIOSRCBONES]; };
//-----------------------------------------------------------------------------
// Index into an s_node_t array for the default root node
//-----------------------------------------------------------------------------
static int s_nDefaultRootNode;
//-----------------------------------------------------------------------------
// Balance/speed data
//-----------------------------------------------------------------------------
static CUtlVector<float> s_Balance; static CUtlVector<float> s_Speed;
//-----------------------------------------------------------------------------
// List of unique vertices
//-----------------------------------------------------------------------------
struct VertIndices_t { int v; int n; int t[MAXSTUDIOTEXCOORDS]; int balance; int speed; };
static CUtlVector< VertIndices_t > s_UniqueVertices; // A list of the unique vertices in the mesh
// Given the non-unique vertex index, return the unique vertex index
// The indices are absolute indices into s_UniqueVertices
// But as both arrays contain information for all meshes in the DMX
// The proper offset for the desired mesh must be added to the lookup
// into the map but the value returned has the offset already built in
static CUtlVector< int > s_UniqueVerticesMap;
//-----------------------------------------------------------------------------
// Delta state intermediate data [used for positions, normals, etc.]
//-----------------------------------------------------------------------------
struct DeltaIndex_t { DeltaIndex_t() : m_nPositionIndex(-1), m_nNormalIndex(-1), m_nNextDelta(-1), m_nWrinkleIndex(-1), m_bInList(false) {} int m_nPositionIndex; // Index into DeltaState_t::m_PositionDeltas
int m_nNormalIndex; // Index into DeltaState_t::m_NormalDeltas
int m_nWrinkleIndex; // Index into DeltaState_t::m_WrinkleDeltas
int m_nNextDelta; // Index into DeltaState_t::m_DeltaIndices;
bool m_bInList; };
struct DeltaState_t { DeltaState_t() : m_nDeltaCount( 0 ), m_nFirstDelta( -1 ) {}
CUtlString m_Name; CUtlVector< Vector > m_PositionDeltas; CUtlVector< Vector > m_NormalDeltas; CUtlVector< float > m_WrinkleDeltas; CUtlVector< DeltaIndex_t > m_DeltaIndices; int m_nDeltaCount; int m_nFirstDelta; };
// NOTE: This is a temporary which loses its state once Load_DMX is exited.
static CUtlVector<DeltaState_t> s_DeltaStates;
// Finds or adds delta states. These pointers are invalidated by calling FindOrAddDeltaState again
//-----------------------------------------------------------------------------
static DeltaState_t* FindOrAddDeltaState( const char *pDeltaStateName, int nBaseStateVertexCount ) { int nCount = s_DeltaStates.Count(); for ( int i = 0; i < nCount; ++i ) { if ( !Q_stricmp( s_DeltaStates[i].m_Name, pDeltaStateName ) ) { MdlWarning( "Unsupported duplicate delta state named \"%s\" in DMX file\n", pDeltaStateName );
s_DeltaStates[i].m_DeltaIndices.EnsureCount( nBaseStateVertexCount ); return &s_DeltaStates[i]; } }
int j = s_DeltaStates.AddToTail(); s_DeltaStates[j].m_Name = pDeltaStateName; s_DeltaStates[j].m_DeltaIndices.SetCount( nBaseStateVertexCount ); return &s_DeltaStates[j]; }
struct VertexLookup_t { int v, n, t; int index; };
static bool VertexLookup_CompareFunc( VertexLookup_t const &a, VertexLookup_t const &b ) { return ( ( a.v == b.v ) && ( a.n == b.n ) && ( a.t == b.t ) ); }
static unsigned int VertexLookup_KeyFunc( VertexLookup_t const &a ) { return Hash12( &a ); }
//-----------------------------------------------------------------------------
// Loads the vertices from the model
//-----------------------------------------------------------------------------
static bool DefineUniqueVertices( CDmeVertexData *pBindState ) { const CUtlVector<int> &positionIndices = pBindState->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ); const CUtlVector<int> &normalIndices = pBindState->GetVertexIndexData( CDmeVertexData::FIELD_NORMAL ); const CUtlVector<int> &texcoordIndices = pBindState->GetVertexIndexData( CDmeVertexData::FIELD_TEXCOORD ); const CUtlVector<int> &balanceIndices = pBindState->GetVertexIndexData( CDmeVertexData::FIELD_BALANCE ); const CUtlVector<int> &speedIndices = pBindState->GetVertexIndexData( CDmeVertexData::FIELD_MORPH_SPEED );
int nPositionCount = positionIndices.Count(); int nNormalCount = normalIndices.Count(); int nTexcoordCount = texcoordIndices.Count(); int nBalanceCount = balanceIndices.Count(); int nSpeedCount = speedIndices.Count();
int nExtraTexcoordCount[MAXSTUDIOTEXCOORDS-1]; const CUtlVector<int> * extraTexcoordIndices[MAXSTUDIOTEXCOORDS - 1];
for (int i = 1; i < MAXSTUDIOTEXCOORDS; ++i) { FieldIndex_t nExtra = pBindState->FindFieldIndex(CFmtStr("texcoord$%d", i).Get()); if (nExtra != -1) { extraTexcoordIndices[i-1] = &pBindState->GetVertexIndexData(nExtra); nExtraTexcoordCount[i-1] = extraTexcoordIndices[i-1]->Count(); if (nPositionCount != nExtraTexcoordCount[i-1]) { MdlError("Encountered a mesh with invalid geometry (different number of indices for various data fields)\n"); return false; } } else { extraTexcoordIndices[i-1] = NULL; nExtraTexcoordCount[i-1] = 0; } }
if ( nNormalCount && nPositionCount != nNormalCount ) { MdlError( "Encountered a mesh with invalid geometry (different number of indices for various data fields)\n" ); return false; } if ( nTexcoordCount && nPositionCount != nTexcoordCount ) { MdlError( "Encountered a mesh with invalid geometry (different number of indices for various data fields)\n" ); return false; } if ( nBalanceCount && nPositionCount != nBalanceCount ) { MdlError( "Encountered a mesh with invalid geometry (different number of indices for various data fields)\n" ); return false; } if ( nSpeedCount && nPositionCount != nSpeedCount ) { MdlError( "Encountered a mesh with invalid geometry (different number of indices for various data fields)\n" ); return false; }
// Make a hash table to speed up this de-duplication process:
CUtlHash< VertexLookup_t > vertexLookupHash( nPositionCount, 0, 0, VertexLookup_CompareFunc, VertexLookup_KeyFunc );
// Only add unique vertices to the list as in UnifyIndices
for ( int i = 0; i < nPositionCount; ++i ) { VertIndices_t vert; vert.v = g_numverts + positionIndices[i]; vert.n = ( nNormalCount > 0 ) ? g_numnormals + normalIndices[i] : -1; vert.t[0] = ( nTexcoordCount > 0 ) ? g_numtexcoords[0] + texcoordIndices[i] : -1; vert.balance = s_Balance.Count() + ( ( nBalanceCount > 0 ) ? balanceIndices[i] : 0 ); vert.speed = s_Speed.Count() + ( ( nSpeedCount > 0 ) ? speedIndices[i] : 0 ); for (int j = 1; j < MAXSTUDIOTEXCOORDS; ++j) { vert.t[j] = (nExtraTexcoordCount[j-1] > 0) ? g_numtexcoords[j] + extraTexcoordIndices[j-1]->Element(i) : -1; }
VertexLookup_t vertexLookup = { vert.v, vert.n, vert.t[0], -1 }; UtlHashHandle_t vertexHandle = vertexLookupHash.Find( vertexLookup ); if ( vertexHandle == vertexLookupHash.InvalidHandle() ) { // Unique
int k = s_UniqueVertices.AddToTail(); s_UniqueVertices[k] = vert; s_UniqueVerticesMap.AddToTail( k ); vertexLookup.index = k; vertexLookupHash.Insert( vertexLookup ); } else { // Not unique
VertexLookup_t &equivalentVertex = vertexLookupHash.Element( vertexHandle ); s_UniqueVerticesMap.AddToTail( equivalentVertex.index ); } }
return true; }
//-----------------------------------------------------------------------------
// Loads the vertices from the model
//-----------------------------------------------------------------------------
static bool LoadVertices( CDmeDag *pDmeDag, CDmeVertexData *pBindState, const matrix3x4_t& mat, float flScale, int nBoneAssign, int *pBoneRemap, s_source_t *pSource ) { // nBoneAssign is only used if the mesh has no skinning information
// It's the DMX bone index, but it might be < 0, in which case
// no bone has been defined yet. There are two options, use the
// default root bone which is always defined and at the origin
// or try and use the DmeDag's bone that was created when the mesh was
// loaded
if ( nBoneAssign < 0 ) { nBoneAssign = s_nDefaultRootNode; } else { nBoneAssign = pBoneRemap[ nBoneAssign ]; if ( nBoneAssign < 0 ) { nBoneAssign = s_nDefaultRootNode; } }
// Used by the morphing system to set up delta states
DefineUniqueVertices( pBindState );
matrix3x4_t normalMat; MatrixInverseTranspose( mat, normalMat );
const CUtlVector<Vector> &positions = pBindState->GetPositionData( ); const CUtlVector<Vector> &normals = pBindState->GetNormalData( ); const CUtlVector<Vector2D> &texcoords = pBindState->GetTextureCoordData( ); const CUtlVector<float> &balances = pBindState->GetBalanceData( ); const CUtlVector<float> &speeds = pBindState->GetMorphSpeedData( );
int nCount = positions.Count();
int nJointCount = pBindState->HasSkinningData() ? pBindState->JointCount() : 0; if ( nJointCount > MAXSTUDIOBONEWEIGHTS ) { MdlError( "Too many bone influences per vertex!\n" ); return false; }
if ( nJointCount <= 0 && nBoneAssign == s_nDefaultRootNode && pDmeDag ) { // Use the bone created for the DmeDag node of the DmeMesh
for ( int i = 0; i < pSource->numbones; ++i ) { if ( !Q_strcmp( pDmeDag->GetName(), pSource->localBone[ i ].name ) ) { nBoneAssign = i; break; } } }
bool pbWarnmap[ MAXSTUDIOBONES ]; Q_memset( pbWarnmap, 0, MAXSTUDIOBONES * sizeof( bool ) );
float *pWeightBuf = (float *)malloc( nJointCount * sizeof( float ) ); int *pIndexBuf = (int *)malloc( nJointCount * sizeof( int ) );
// Copy positions + bone info
for ( int i = 0; i < nCount; ++i ) { // NOTE: The transform transforms the positions into the bind space
VectorTransform( positions[i], mat, g_vertex[g_numverts] ); g_vertex[g_numverts] *= flScale; if ( nJointCount == 0 ) { g_bone[g_numverts].numbones = 1; g_bone[g_numverts].bone[0] = nBoneAssign; g_bone[g_numverts].weight[0] = 1.0; } else { const float *pJointWeights = pBindState->GetJointWeightData( i ); const int *pJointIndices = pBindState->GetJointIndexData( i );
memcpy( pWeightBuf, pJointWeights, nJointCount * sizeof(float) ); memcpy( pIndexBuf, pJointIndices, nJointCount * sizeof(int) );
int nBoneCount = SortAndBalanceBones( nJointCount, MAXSTUDIOBONEWEIGHTS, pIndexBuf, pWeightBuf ); int nBoneIndex = -1;
g_bone[g_numverts].numbones = nBoneCount; for ( int j = 0; j < nBoneCount; ++j ) { nBoneIndex = pBoneRemap[ pIndexBuf[j] ]; if ( nBoneIndex < 0 ) { if ( pIndexBuf[j] < MAXSTUDIOBONES && !pbWarnmap[ pIndexBuf[j] ] ) { MdlWarning( "DmeMesh[%s] Verts Assigned To DmeModel.jointList[%d] Which Isn't Mapped To The Dag Hierarchy\n", pDmeDag->GetName(), pIndexBuf[j] ); pbWarnmap[ pIndexBuf[j] ] = true; } g_bone[g_numverts].bone[j] = nBoneAssign; } else { g_bone[g_numverts].bone[j] = nBoneIndex; } g_bone[g_numverts].weight[j] = pWeightBuf[j]; } } ++g_numverts; }
free( pWeightBuf ); free( pIndexBuf );
// Copy normals
Vector vNormal; nCount = normals.Count(); for ( int i = 0; i < nCount; ++i ) { VectorCopy( normals[i], vNormal ); VectorNormalize( vNormal ); if ( fabs( VectorLength( vNormal ) - 1.0f ) > 0.01 ) { MdlWarning( "Non-Unit Length Normal [%d] < %8.6f %8.6f %8.6f >\n", i, vNormal.x, vNormal.y, vNormal.z ); } VectorRotate( vNormal, normalMat, g_normal[g_numnormals] ); ++g_numnormals; }
// Copy texcoords
nCount = texcoords.Count(); bool bFlipVCoordinate = pBindState->IsVCoordinateFlipped(); for ( int i = 0; i < nCount; ++i ) { g_texcoord[0][g_numtexcoords[0]].x = texcoords[i].x; g_texcoord[0][g_numtexcoords[0]].y = bFlipVCoordinate ? 1.0f - texcoords[i].y : texcoords[i].y; ++g_numtexcoords[0]; }
// Check for additional texcoords
for (int i = 1; i < MAXSTUDIOTEXCOORDS; ++i) { g_numtexcoords[i] = 0; FieldIndex_t nFieldIndex = pBindState->FindFieldIndex(CFmtStr("texcoord$%d", i).Get()); if (nFieldIndex > -1) { CDmrArrayConst<Vector2D> vertexData = pBindState->GetVertexData(nFieldIndex); const CUtlVector<Vector2D> &extraTexcoords = vertexData.Get(); nCount = extraTexcoords.Count(); for (int j = 0; j < nCount; ++j) { g_texcoord[i][g_numtexcoords[i]].x = extraTexcoords[j].x; g_texcoord[i][g_numtexcoords[i]].y = bFlipVCoordinate ? 1.0f - extraTexcoords[j].y : extraTexcoords[j].y; ++g_numtexcoords[i]; } } else { break; } }
// In the event of no speed or balance map, use the same value of 1 for all vertices
if ( balances.Count() ) { s_Balance.AddMultipleToTail( balances.Count(), balances.Base() ); } else { s_Balance.AddToTail( 1.0f ); }
if ( speeds.Count() ) { s_Speed.AddMultipleToTail( speeds.Count(), speeds.Base() ); } else { s_Speed.AddToTail( 1.0f ); }
return true; }
//-----------------------------------------------------------------------------
// Hook delta into delta list
//-----------------------------------------------------------------------------
static void AddToDeltaList( DeltaState_t *pDeltaStateData, int nUniqueVertex ) { DeltaIndex_t &index = pDeltaStateData->m_DeltaIndices[ nUniqueVertex ]; if ( !index.m_bInList ) { index.m_nNextDelta = pDeltaStateData->m_nFirstDelta; pDeltaStateData->m_nFirstDelta = nUniqueVertex; pDeltaStateData->m_nDeltaCount++; index.m_bInList = true; } }
//-----------------------------------------------------------------------------
// Loads the vertices from the delta state
//-----------------------------------------------------------------------------
static bool LoadDeltaState( CDmeVertexDeltaData *pDeltaState, CDmeVertexData *pBindState, const matrix3x4_t& mat, float flScale, int nStartingUniqueVertex, int nStartingUniqueVertexMap ) { DeltaState_t *pDeltaStateData = FindOrAddDeltaState( pDeltaState->GetName(), nStartingUniqueVertex + pBindState->VertexCount() );
matrix3x4_t normalMat; MatrixInverseTranspose( mat, normalMat );
const CUtlVector<Vector> &positions = pDeltaState->GetPositionData( ); const CUtlVector<int> &positionIndices = pDeltaState->GetVertexIndexData( CDmeVertexDataBase::FIELD_POSITION ); const CUtlVector<Vector> &normals = pDeltaState->GetNormalData( ); const CUtlVector<int> &normalIndices = pDeltaState->GetVertexIndexData( CDmeVertexDataBase::FIELD_NORMAL ); const CUtlVector<float> &wrinkle = pDeltaState->GetWrinkleData( ); const CUtlVector<int> &wrinkleIndices = pDeltaState->GetVertexIndexData( CDmeVertexDataBase::FIELD_WRINKLE );
if ( positions.Count() != positionIndices.Count() ) { MdlError( "DeltaState %s contains a different number of positions + position indices!\n", pDeltaState->GetName() ); return false; }
if ( normals.Count() != normalIndices.Count() ) { MdlError( "DeltaState %s contains a different number of normals + normal indices!\n", pDeltaState->GetName() ); return false; }
if ( wrinkle.Count() != wrinkleIndices.Count() ) { MdlError( "DeltaState %s contains a different number of wrinkles + wrinkle indices!\n", pDeltaState->GetName() ); return false; }
// Copy position delta
int nCount = positions.Count(); for ( int i = 0; i < nCount; ++i ) { Vector vecDelta;
// NOTE NOTE!!: This is VectorRotate, *not* VectorTransform. This is because
// we're transforming a delta, which is basically a direction vector. To
// move it into the new space, we must rotate it
VectorRotate( positions[i], mat, vecDelta ); vecDelta *= flScale;
int nPositionIndex = pDeltaStateData->m_PositionDeltas.AddToTail( vecDelta );
// Indices
const CUtlVector< int > &baseVerts = pBindState->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, positionIndices[i] ); int nBaseVertCount = baseVerts.Count(); for ( int k = 0; k < nBaseVertCount; ++k ) { int nUniqueVertexIndex = s_UniqueVerticesMap[ nStartingUniqueVertexMap + baseVerts[k] ]; AddToDeltaList( pDeltaStateData, nUniqueVertexIndex ); DeltaIndex_t &index = pDeltaStateData->m_DeltaIndices[ nUniqueVertexIndex ]; index.m_nPositionIndex = nPositionIndex; } }
// Copy normals
nCount = normals.Count(); for ( int i = 0; i < nCount; ++i ) { Vector vecDelta; VectorRotate( normals[i], normalMat, vecDelta ); int nNormalIndex = pDeltaStateData->m_NormalDeltas.AddToTail( vecDelta );
// Indices
const CUtlVector< int > &baseVerts = pBindState->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_NORMAL, normalIndices[i] ); int nBaseVertCount = baseVerts.Count(); for ( int k = 0; k < nBaseVertCount; ++k ) { int nUniqueVertexIndex = s_UniqueVerticesMap[ nStartingUniqueVertexMap + baseVerts[k] ]; AddToDeltaList( pDeltaStateData, nUniqueVertexIndex ); DeltaIndex_t &index = pDeltaStateData->m_DeltaIndices[ nUniqueVertexIndex ]; index.m_nNormalIndex = nNormalIndex; } }
// Copy wrinkle
nCount = wrinkle.Count(); for ( int i = 0; i < nCount; ++i ) { int nWrinkleIndex = pDeltaStateData->m_WrinkleDeltas.AddToTail( wrinkle[i] );
// Indices
const CUtlVector< int > &baseVerts = pBindState->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_WRINKLE, wrinkleIndices[i] ); int nBaseVertCount = baseVerts.Count(); for ( int k = 0; k < nBaseVertCount; ++k ) { int nUniqueVertexIndex = s_UniqueVerticesMap[ nStartingUniqueVertexMap + baseVerts[k] ]; AddToDeltaList( pDeltaStateData, nUniqueVertexIndex ); DeltaIndex_t &index = pDeltaStateData->m_DeltaIndices[ nUniqueVertexIndex ]; index.m_nWrinkleIndex = nWrinkleIndex; } } return true; }
static int GetExtraTexcoordIndex(CDmeVertexData *pVertexData, int nVertexIndex, int nChannel) { FieldIndex_t nFieldIndex = pVertexData->FindFieldIndex(CFmtStr("texcoord$%d", nChannel).Get()); if (nFieldIndex < 0) return -1;
CDmrArrayConst<int> indices(pVertexData->GetIndexData(nFieldIndex)); return indices[nVertexIndex]; }
// JasonM TODO: unify ParseQuadFaceData() and ParseFaceData()
//-----------------------------------------------------------------------------
// Reads the quad face data from the DMX data
//-----------------------------------------------------------------------------
static void ParseQuadFaceData( CDmeVertexData *pVertexData, int material, int *pIndices, int vi, int ni, int* ti ) { s_tmpface_t f; f.material = material;
int p, n, t; p = pVertexData->GetPositionIndex(pIndices[0]); n = pVertexData->GetNormalIndex(pIndices[0]); t = pVertexData->GetTexCoordIndex(pIndices[0]); f.a = (p >= 0) ? vi + p : 0; f.na = (n >= 0) ? ni + n : 0; f.ta[0] = (t >= 0) ? ti[0] + t : 0; p = pVertexData->GetPositionIndex(pIndices[3]); n = pVertexData->GetNormalIndex(pIndices[3]); t = pVertexData->GetTexCoordIndex(pIndices[3]); f.b = (p >= 0) ? vi + p : 0; f.nb = (n >= 0) ? ni + n : 0; f.tb[0] = (t >= 0) ? ti[0] + t : 0; p = pVertexData->GetPositionIndex(pIndices[2]); n = pVertexData->GetNormalIndex(pIndices[2]); t = pVertexData->GetTexCoordIndex(pIndices[2]); f.c = (p >= 0) ? vi + p : 0; f.nc = (n >= 0) ? ni + n : 0; f.tc[0] = (t >= 0) ? ti[0] + t : 0; p = pVertexData->GetPositionIndex(pIndices[1]); n = pVertexData->GetNormalIndex(pIndices[1]); t = pVertexData->GetTexCoordIndex(pIndices[1]); f.d = (p >= 0) ? vi + p : 0; f.nd = (n >= 0) ? ni + n : 0; f.td[0] = (t >= 0) ? ti[0] + t : 0;
Assert( f.a <= (unsigned long)g_numverts && f.b <= (unsigned long)g_numverts && f.c <= (unsigned long)g_numverts && f.d <= (unsigned long)g_numverts ); Assert( f.na <= (unsigned long)g_numnormals && f.nb <= (unsigned long)g_numnormals && f.nc <= (unsigned long)g_numnormals && f.nd <= (unsigned long)g_numnormals ); Assert(f.ta[0] <= (unsigned long)g_numtexcoords[0] && f.tb[0] <= (unsigned long)g_numtexcoords[0] && f.tc[0] <= (unsigned long)g_numtexcoords[0] && f.td[0] <= (unsigned long)g_numtexcoords[0]);
for (int i = 1; i < (MAXSTUDIOTEXCOORDS); ++i) { t = GetExtraTexcoordIndex(pVertexData, pIndices[0], i); f.ta[i] = (t >= 0) ? ti[i] + t : 0; t = GetExtraTexcoordIndex(pVertexData, pIndices[3], i); f.tb[i] = (t >= 0) ? ti[i] + t : 0; t = GetExtraTexcoordIndex(pVertexData, pIndices[2], i); f.tc[i] = (t >= 0) ? ti[i] + t : 0; t = GetExtraTexcoordIndex(pVertexData, pIndices[1], i); f.td[i] = (t >= 0) ? ti[i] + t : 0; Assert(f.ta[i] <= (unsigned long)g_numtexcoords[i] && f.tb[i] <= (unsigned long)g_numtexcoords[i] && f.tc[i] <= (unsigned long)g_numtexcoords[i] && f.td[i] <= (unsigned long)g_numtexcoords[i]); }
int i = g_numfaces++; g_face[i] = f; }
//-----------------------------------------------------------------------------
// Reads the face data from the DMX data
//-----------------------------------------------------------------------------
static void ParseFaceData( CDmeVertexData *pVertexData, int material, int v1, int v2, int v3, int vi, int ni, int* ti ) { s_tmpface_t f; f.material = material;
int p, n, t; p = pVertexData->GetPositionIndex(v1); n = pVertexData->GetNormalIndex(v1); t = pVertexData->GetTexCoordIndex(v1); f.a = (p >= 0) ? vi + p : 0; f.na = (n >= 0) ? ni + n : 0; f.ta[0] = (t >= 0) ? ti[0] + t : 0; p = pVertexData->GetPositionIndex(v2); n = pVertexData->GetNormalIndex(v2); t = pVertexData->GetTexCoordIndex(v2); f.b = (p >= 0) ? vi + p : 0; f.nb = (n >= 0) ? ni + n : 0; f.tb[0] = (t >= 0) ? ti[0] + t : 0; p = pVertexData->GetPositionIndex(v3); n = pVertexData->GetNormalIndex(v3); t = pVertexData->GetTexCoordIndex(v3); f.c = (p >= 0) ? vi + p : 0; f.nc = (n >= 0) ? ni + n : 0; f.tc[0] = (t >= 0) ? ti[0] + t : 0;
Assert( f.a <= (unsigned long)g_numverts && f.b <= (unsigned long)g_numverts && f.c <= (unsigned long)g_numverts ); Assert( f.na <= (unsigned long)g_numnormals && f.nb <= (unsigned long)g_numnormals && f.nc <= (unsigned long)g_numnormals ); Assert(f.ta[0] <= (unsigned long)g_numtexcoords[0] && f.tb[0] <= (unsigned long)g_numtexcoords[0] && f.tc[0] <= (unsigned long)g_numtexcoords[0]);
for (int i = 1; i < (MAXSTUDIOTEXCOORDS); ++i) { t = GetExtraTexcoordIndex(pVertexData, v1, i); f.ta[i] = (t >= 0) ? ti[i] + t : 0; t = GetExtraTexcoordIndex(pVertexData, v2, i); f.tb[i] = (t >= 0) ? ti[i] + t : 0; t = GetExtraTexcoordIndex(pVertexData, v3, i); f.tc[i] = (t >= 0) ? ti[i] + t : 0; Assert(f.ta[i] <= (unsigned long)g_numtexcoords[i] && f.tb[i] <= (unsigned long)g_numtexcoords[i] && f.tc[i] <= (unsigned long)g_numtexcoords[i]); }
int i = g_numfaces++; g_face[i] = f; }
//-----------------------------------------------------------------------------
// Reads the mesh data from the DMX data
//-----------------------------------------------------------------------------
static bool LoadMesh( CDmeDag *pDmeDag, CDmeMesh *pMesh, CDmeVertexData *pBindState, const matrix3x4_t& mat, float flScale, int nBoneAssign, int *pBoneRemap, s_source_t *pSource ) { pMesh->CollapseRedundantNormals( normal_blend );
// Load the vertices
int nStartingVertex = g_numverts; int nStartingNormal = g_numnormals; int nStartingUniqueCount = s_UniqueVertices.Count(); int nStartingUniqueMapCount = s_UniqueVerticesMap.Count(); int nStartingTexCoord[MAXSTUDIOTEXCOORDS]; for (int i = 0; i < MAXSTUDIOTEXCOORDS; ++i) { nStartingTexCoord[i] = g_numtexcoords[i]; }
// This defines s_UniqueVertices & s_UniqueVerticesMap
LoadVertices( pDmeDag, pBindState, mat, flScale, nBoneAssign, pBoneRemap, pSource );
// Load the deltas
int nDeltaStateCount = pMesh->DeltaStateCount(); for ( int i = 0; i < nDeltaStateCount; ++i ) { CDmeVertexDeltaData *pDeltaState = pMesh->GetDeltaState( i ); if ( !LoadDeltaState( pDeltaState, pBindState, mat, flScale, nStartingUniqueCount, nStartingUniqueMapCount ) ) return false; }
// load the base triangles
int texture; int material; char pTextureName[MAX_PATH];
int nFaceSetCount = pMesh->FaceSetCount(); for ( int i = 0; i < nFaceSetCount; ++i ) { CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); CDmeMaterial *pMaterial = pFaceSet->GetMaterial();
// Get the material name
Q_strncpy( pTextureName, pMaterial->GetMaterialName(), sizeof(pTextureName) );
// funky texture overrides (specified with the -t command-line argument)
for ( int j = 0; j < numrep; j++ ) { if ( sourcetexture[j][0] == '\0' ) { Q_strncpy( pTextureName, defaulttexture[j], sizeof(pTextureName) ); break; } if ( Q_stricmp( pTextureName, sourcetexture[j]) == 0 ) { Q_strncpy( pTextureName, defaulttexture[j], sizeof(pTextureName) ); break; } }
// skip all faces with the null texture on them.
char pPathNoExt[MAX_PATH]; Q_StripExtension( pTextureName, pPathNoExt, sizeof(pPathNoExt) ); if ( !Q_stricmp( pPathNoExt, "null" ) ) continue;
texture = LookupTexture( pTextureName, true ); pSource->texmap[texture] = texture; // hack, make it 1:1
material = UseTextureAsMaterial( texture );
// Is this a quad-only subd?
bool bQuadSubd = ( gflags & STUDIOHDR_FLAGS_SUBDIVISION_SURFACE ) != 0;
// prepare indices
int nFirstIndex = 0; int nIndexCount = pFaceSet->NumIndices(); while ( nFirstIndex < nIndexCount ) { int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex );
// Quad subd face?
if ( bQuadSubd && ( nVertexCount == 4 ) ) { int quadIndices[4];
for ( int j = 0; j < 4; j++ ) { quadIndices[j] = pFaceSet->GetIndex( nFirstIndex + j ); }
ParseQuadFaceData( pBindState, material, quadIndices, nStartingVertex, nStartingNormal, nStartingTexCoord );
nFirstIndex += 5; // -1 in list between face indices, so jump over 5 elements, not 4
} else { if ( nVertexCount >= 3 ) { int nOutCount = (nVertexCount-2) * 3; int *pIndices = (int*)malloc( nOutCount * sizeof(int) ); pMesh->ComputeTriangulatedIndices( pBindState, pFaceSet, nFirstIndex, pIndices, nOutCount ); for ( int ii = 0; ii < nOutCount; ii +=3 ) { ParseFaceData( pBindState, material, pIndices[ii], pIndices[ii+2], pIndices[ii+1], nStartingVertex, nStartingNormal, nStartingTexCoord ); } free( pIndices ); }
nFirstIndex += nVertexCount + 1; // -1 in list between face indices, so jump over nVertexCount + 1 elements, not nVertexCount elements
} } }
return true; }
//-----------------------------------------------------------------------------
// Method used to add mesh data
//-----------------------------------------------------------------------------
struct LoadMeshInfo_t { s_source_t *m_pSource; CDmeModel *m_pModel; float m_flScale; int *m_pBoneRemap; matrix3x4_t m_pBindPose[MAXSTUDIOSRCBONES]; };
static bool LoadMeshes( const LoadMeshInfo_t &info, CDmeDag *pDag, const matrix3x4_t &parentToBindPose, int nBoneAssign ) { // We want to create an aggregate matrix transforming from this dag to its closest
// parent which actually is an animated joint. This is done so we can autoskin
// meshes to their closest parents if they have not been skinned.
matrix3x4_t dagToBindPose; int nFoundIndex = info.m_pModel->GetJointIndex( pDag ); if ( nFoundIndex >= 0 /* && ( pDag == info.m_pModel || CastElement< CDmeJoint >( pDag ) ) */ ) { nBoneAssign = nFoundIndex; }
if ( nFoundIndex >= 0 ) { ConcatTransforms( parentToBindPose, info.m_pBindPose[nFoundIndex], dagToBindPose ); } else { // NOTE: This isn't particularly kosher; we're using the current pose instead of the bind pose
// because there's no transform in the bind pose
matrix3x4_t dagToParent; pDag->GetTransform()->GetTransform( dagToParent ); ConcatTransforms( parentToBindPose, dagToParent, dagToBindPose ); }
CDmeMesh *pMesh = CastElement< CDmeMesh >( pDag->GetShape() ); if ( pMesh ) { CDmeVertexData *pBindState = pMesh->FindBaseState( "bind" ); if ( !pBindState ) return false;
if ( !LoadMesh( pDag, pMesh, pBindState, dagToBindPose, info.m_flScale, nBoneAssign, info.m_pBoneRemap, info.m_pSource ) ) return false; }
int nCount = pDag->GetChildCount(); for ( int i = 0; i < nCount; ++i ) { CDmeDag *pChild = pDag->GetChild( i ); if ( !LoadMeshes( info, pChild, dagToBindPose, nBoneAssign ) ) return false; }
return true; }
//-----------------------------------------------------------------------------
// Method used to add mesh data
//-----------------------------------------------------------------------------
static bool LoadMeshes( CDmeModel *pModel, float flScale, int *pBoneRemap, s_source_t *pSource ) { matrix3x4_t mat; SetIdentityMatrix( mat );
LoadMeshInfo_t info; info.m_pModel = pModel; info.m_flScale = flScale; info.m_pBoneRemap = pBoneRemap; info.m_pSource = pSource;
CDmeTransformList *pBindPose = pModel->FindBaseState( "bind" ); int nCount = pBindPose ? pBindPose->GetTransformCount() : pModel->GetJointCount(); for ( int i = 0; i < nCount; ++i ) { CDmeTransform *pTransform = pBindPose ? pBindPose->GetTransform(i) : pModel->GetJointTransform(i);
matrix3x4_t jointTransform; pTransform->GetTransform( info.m_pBindPose[i] ); }
int nChildCount = pModel->GetChildCount(); for ( int i = 0; i < nChildCount; ++i ) { CDmeDag *pChild = pModel->GetChild( i ); if ( !LoadMeshes( info, pChild, mat, -1 ) ) return false; }
return true; }
//-----------------------------------------------------------------------------
// Builds s_vertanim_ts
//-----------------------------------------------------------------------------
static void BuildVertexAnimations( s_source_t *pSource ) { int nCount = s_DeltaStates.Count(); if ( nCount == 0 ) return;
Assert( s_Speed.Count() > 0 ); Assert( s_Balance.Count() > 0 );
Assert( s_UniqueVertices.Count() == g_numvlist ); s_vertanim_t *pVertAnim = (s_vertanim_t *)malloc( g_numvlist * sizeof( s_vertanim_t ) ); for ( int i = 0; i < nCount; ++i ) { DeltaState_t &state = s_DeltaStates[i];
s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( pSource, state.m_Name ); pSourceAnim->numframes = 1; pSourceAnim->startframe = 0; pSourceAnim->endframe = 0; pSourceAnim->newStyleVertexAnimations = true;
// Traverse the linked list of unique vertex indices j that has a delta
int nVertAnimCount = 0; for ( int j = state.m_nFirstDelta; j >= 0; j = state.m_DeltaIndices[j].m_nNextDelta ) { // The Delta Indices array is a parallel array to s_UniqueVertices
// j is used to index into both
DeltaIndex_t &delta = state.m_DeltaIndices[j]; Assert( delta.m_nPositionIndex >= 0 || delta.m_nNormalIndex >= 0 || delta.m_nWrinkleIndex >= 0 );
VertIndices_t &uniqueVert = s_UniqueVertices[j];
const v_unify_t *pList = v_list[uniqueVert.v]; for( ; pList; pList = pList->next ) { if ( pList->n != uniqueVert.n || pList->t[0] != uniqueVert.t[0] ) continue;
s_vertanim_t& vertanim = pVertAnim[nVertAnimCount++]; vertanim.vertex = pList - v_listdata; vertanim.speed = s_Speed[ s_UniqueVertices[j].speed ]; vertanim.side = s_Balance[ s_UniqueVertices[j].balance ]; if ( delta.m_nPositionIndex >= 0 ) { vertanim.pos = state.m_PositionDeltas[ delta.m_nPositionIndex ]; } else { vertanim.pos = vec3_origin; } if ( delta.m_nNormalIndex >= 0 ) { vertanim.normal = state.m_NormalDeltas[ delta.m_nNormalIndex ]; } else { vertanim.normal = vec3_origin; }
if ( delta.m_nWrinkleIndex >= 0 ) { vertanim.wrinkle = state.m_WrinkleDeltas[ delta.m_nWrinkleIndex ]; } else { vertanim.wrinkle = 0.0f; } } } pSourceAnim->numvanims[0] = nVertAnimCount; pSourceAnim->vanim[0] = (s_vertanim_t *)calloc( nVertAnimCount, sizeof( s_vertanim_t ) ); memcpy( pSourceAnim->vanim[0], pVertAnim, nVertAnimCount * sizeof( s_vertanim_t ) ); } free( pVertAnim ); }
//-----------------------------------------------------------------------------
// Handles DmeJiggleBones
//-----------------------------------------------------------------------------
static void HandleDmeJiggleBone( const CDmeDag *pDmeDag ) { const CDmeJiggleBone *pDmeJiggleBone = CastElementConst< CDmeJiggleBone >( pDmeDag ); if ( !pDmeJiggleBone ) return;
const char *pName = pDmeJiggleBone->GetName(); for ( int i = 0; i < g_numjigglebones; ++i ) { if ( !Q_stricmp( pName, g_jigglebones[i].bonename ) ) { MdlWarning( "2000: Jiggle Bone: %s already defined, ignoring additional declarations\n", pName ); return; } }
struct s_jigglebone_t *pJiggleBone = &g_jigglebones[ g_numjigglebones ]; ++g_numjigglebones;
Q_strncpy( pJiggleBone->bonename, pName, sizeof( pJiggleBone->bonename ) );
// default values
memset( &pJiggleBone->data, 0, sizeof( mstudiojigglebone_t ) ); pJiggleBone->data.length = 10.0f; pJiggleBone->data.yawStiffness = 100.0f; pJiggleBone->data.pitchStiffness = 100.0f; pJiggleBone->data.alongStiffness = 100.0f; pJiggleBone->data.baseStiffness = 100.0f; pJiggleBone->data.baseMinUp = -100.0f; pJiggleBone->data.baseMaxUp = 100.0f; pJiggleBone->data.baseMinLeft = -100.0f; pJiggleBone->data.baseMaxLeft = 100.0f; pJiggleBone->data.baseMinForward = -100.0f; pJiggleBone->data.baseMaxForward = 100.0f;
// Common Parameters
pJiggleBone->data.length = pDmeJiggleBone->m_flLength; pJiggleBone->data.tipMass = pDmeJiggleBone->m_flTipMass; pJiggleBone->data.flags |= ( pDmeJiggleBone->m_bLengthConstrained ? JIGGLE_HAS_LENGTH_CONSTRAINT : 0 ); pJiggleBone->data.angleLimit = DEG2RAD( pDmeJiggleBone->m_flAngleLimit );
pJiggleBone->data.flags |= ( pDmeJiggleBone->m_bYawConstrained ? JIGGLE_HAS_YAW_CONSTRAINT : 0 ); pJiggleBone->data.minYaw = DEG2RAD( pDmeJiggleBone->m_flYawMin ); pJiggleBone->data.maxYaw = DEG2RAD( pDmeJiggleBone->m_flYawMax ); pJiggleBone->data.yawFriction = pDmeJiggleBone->m_flYawFriction; pJiggleBone->data.yawBounce = pDmeJiggleBone->m_flYawBounce;
pJiggleBone->data.flags |= ( pDmeJiggleBone->m_bAngleConstrained ? JIGGLE_HAS_ANGLE_CONSTRAINT : 0 );
pJiggleBone->data.minPitch = DEG2RAD( pDmeJiggleBone->m_flPitchMin ); pJiggleBone->data.maxPitch = DEG2RAD( pDmeJiggleBone->m_flPitchMax ); pJiggleBone->data.pitchFriction = pDmeJiggleBone->m_flPitchFriction; pJiggleBone->data.pitchBounce = pDmeJiggleBone->m_flPitchBounce;
if ( pDmeJiggleBone->m_bFlexible ) { if ( pDmeJiggleBone->m_bRigid ) { MdlWarning( "2001: Jiggle Bone %s: Both flexible and rigid set, ignoring rigid\n", pName ); }
pJiggleBone->data.flags |= ( JIGGLE_IS_FLEXIBLE ); pJiggleBone->data.flags |= ( pDmeJiggleBone->m_bPitchConstrained ? JIGGLE_HAS_PITCH_CONSTRAINT : 0 );
// flexible parameters - I think damping should be clamped [0, 10] but code
// in studiomdl looks incorrect and clamps 0, 1000.0f
pJiggleBone->data.yawStiffness = clamp( pDmeJiggleBone->m_flYawStiffness.Get(), 0.0f, 1000.0f ); pJiggleBone->data.yawDamping = clamp( pDmeJiggleBone->m_flYawDamping.Get(), 0.0f, 10.0f );
pJiggleBone->data.pitchStiffness = clamp( pDmeJiggleBone->m_flPitchStiffness.Get(), 0.0f, 1000.0f ); pJiggleBone->data.pitchDamping = clamp( pDmeJiggleBone->m_flPitchDamping.Get(), 0.0f, 10.0f );
pJiggleBone->data.alongStiffness = clamp( pDmeJiggleBone->m_flAlongStiffness.Get(), 0.0f, 1000.0f ); pJiggleBone->data.alongDamping = clamp( pDmeJiggleBone->m_flAlongDamping.Get(), 0.0f, 10.0f ); } else if ( pDmeJiggleBone->m_bRigid ) { pJiggleBone->data.flags |= ( JIGGLE_IS_FLEXIBLE | JIGGLE_HAS_LENGTH_CONSTRAINT ); } else { // TODO: Is neither rigid or flexible an error?
}
if ( pDmeJiggleBone->m_bBaseSpring ) { // flexible parameters - I think damping should be clamped [0, 10] but code
// in studiomdl looks incorrect and clamps 0, 1000.0f
pJiggleBone->data.baseMass = pDmeJiggleBone->m_flBaseMass; pJiggleBone->data.baseStiffness = clamp( pDmeJiggleBone->m_flBaseStiffness.Get(), 0.0f, 1000.0f ); pJiggleBone->data.baseDamping = clamp( pDmeJiggleBone->m_flBaseStiffness.Get(), 0.0f, 10.0f );
pJiggleBone->data.baseMinLeft = pDmeJiggleBone->m_flBaseYawMin; pJiggleBone->data.baseMaxLeft = pDmeJiggleBone->m_flBaseYawMax; pJiggleBone->data.baseLeftFriction = pDmeJiggleBone->m_flBaseYawFriction;
pJiggleBone->data.baseMinUp = pDmeJiggleBone->m_flBasePitchMin; pJiggleBone->data.baseMaxUp = pDmeJiggleBone->m_flBasePitchMax; pJiggleBone->data.baseUpFriction = pDmeJiggleBone->m_flBasePitchFriction;
pJiggleBone->data.baseMinForward = pDmeJiggleBone->m_flBaseAlongMin; pJiggleBone->data.baseMaxForward = pDmeJiggleBone->m_flBaseAlongMax; pJiggleBone->data.baseForwardFriction = pDmeJiggleBone->m_flBaseAlongFriction; } }
//-----------------------------------------------------------------------------
// Loads the skeletal hierarchy from the game model, returns bone count
//-----------------------------------------------------------------------------
static bool AddDagJoint( CDmeModel *pModel, CDmeDag *pDag, s_node_t *pNodes, int nParentIndex, BoneTransformMap_t &boneMap ) { CDmeTransform *pDmeTransform = pDag->GetTransform();
if ( !pDmeTransform ) return true;
// Need room for one implicit bone added
if ( boneMap.m_nBoneCount >= ( MAXSTUDIOSRCBONES - 1 ) ) { MdlWarning( "Ignoring Bone %s and children, too many bones [max can be %d]!\n", pDag->GetName(), MAXSTUDIOSRCBONES - 1 ); return false; }
const int nJointIndex = boneMap.m_nBoneCount++;
boneMap.m_ppTransforms[ nJointIndex ] = pDmeTransform;
if ( pModel ) { const int nFoundIndex = pModel->GetJointIndex( pDag ); if ( nFoundIndex >= 0 ) { boneMap.m_pnDmeModelToMdl[ nFoundIndex ] = nJointIndex; boneMap.m_pnMdlToDmeModel[ nJointIndex ] = nFoundIndex; } else { MdlWarning( "Joint %s doesn't appear in DmeModel[%s].jointList\n", pDag->GetName(), pModel->GetName() ); } }
HandleDmeJiggleBone( pDag );
Q_strncpy( pNodes[ nJointIndex ].name, pDag->GetName(), sizeof( pNodes[ nJointIndex ].name ) ); pNodes[ nJointIndex ].parent = nParentIndex;
// Now deal with children
for ( int i = 0; i < pDag->GetChildCount(); ++i ) { CDmeDag *pChild = pDag->GetChild( i ); if ( !pChild ) continue;
if ( !AddDagJoint( pModel, pChild, pNodes, nJointIndex, boneMap ) ) return false; }
return true; }
//-----------------------------------------------------------------------------
// Main entry point for loading the skeleton
//-----------------------------------------------------------------------------
static int LoadSkeleton( CDmeDag *pRoot, CDmeModel *pModel, s_node_t *pNodes, BoneTransformMap_t &boneMap ) { // Initialize bone indices
boneMap.m_nBoneCount = 0; for ( int i = 0; i < MAXSTUDIOSRCBONES; ++i ) { pNodes[i].name[0] = 0; pNodes[i].parent = -1; boneMap.m_pnDmeModelToMdl[i] = -1; boneMap.m_pnMdlToDmeModel[i] = -1; boneMap.m_ppTransforms[i] = NULL; }
// Don't create joints for the the root dag ever.. just deal with the children
for ( int i = 0; i < pRoot->GetChildCount(); ++i ) { CDmeDag *pChild = pRoot->GetChild( i ); if ( !pChild ) continue;
if ( !AddDagJoint( pModel, pChild, pNodes, -1, boneMap ) ) return 0; }
// Add a default identity bone used for autoskinning if no joints are specified
s_nDefaultRootNode = boneMap.m_nBoneCount; Q_strncpy( pNodes[s_nDefaultRootNode].name, "defaultRoot", sizeof( pNodes[ s_nDefaultRootNode ].name ) ); pNodes[s_nDefaultRootNode].parent = -1;
// +1 for the default identity bone just added
return boneMap.m_nBoneCount + 1; }
//-----------------------------------------------------------------------------
// Loads the attachments found in the file
//-----------------------------------------------------------------------------
static void LoadAttachments( CDmeDag *pRoot, CDmeDag *pDag, s_source_t *pSource, bool bStaticProp ) { CDmeAttachment *pAttachment = CastElement< CDmeAttachment >( pDag->GetShape() ); if ( pAttachment && ( pDag != pRoot ) ) { int i = pSource->m_Attachments.AddToTail(); s_attachment_t &attachment = pSource->m_Attachments[i]; memset( &attachment, 0, sizeof(s_attachment_t) ); Q_strncpy( attachment.name, pAttachment->GetName(), sizeof( attachment.name ) ); Q_strncpy( attachment.bonename, pDag->GetName(), sizeof( attachment.bonename ) ); SetIdentityMatrix( attachment.local );
if ( bStaticProp ) { // Static prop will remove all bones so put the attachment transform
// on the attachment rather than the bone. Also ignore all attachment
// flags
pDag->GetAbsTransform( attachment.local ); } else { if ( pAttachment->m_bIsRigid ) { attachment.type |= IS_RIGID; } if ( pAttachment->m_bIsWorldAligned ) { attachment.flags |= ATTACHMENT_FLAG_WORLD_ALIGN; } } }
// Don't create joints for the the root dag ever.. just deal with the children
int nChildCount = pDag->GetChildCount(); for ( int i = 0; i < nChildCount; ++i ) { CDmeDag *pChild = pDag->GetChild( i ); if ( !pChild ) continue;
LoadAttachments( pRoot, pChild, pSource, bStaticProp ); } }
//-----------------------------------------------------------------------------
// Loads the bind pose
//-----------------------------------------------------------------------------
static void LoadBindPose( CDmeModel *pModel, float flScale, const BoneTransformMap_t &boneMap, s_source_t *pSource ) { s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( pSource, "BindPose" ); pSourceAnim->startframe = 0; pSourceAnim->endframe = 0; pSourceAnim->numframes = 1;
// Default all transforms to identity
pSourceAnim->rawanim[0] = (s_bone_t *)calloc( pSource->numbones, sizeof(s_bone_t) ); for ( int i = 0; i < pSource->numbones; ++i ) { pSourceAnim->rawanim[0][i].pos.Init(); pSourceAnim->rawanim[0][i].rot.Init(); }
{ matrix3x4_t jointTransform;
CDmeTransformList *pBindPose = pModel->FindBaseState( "bind" ); for ( int nMdlBoneIndex = 0; nMdlBoneIndex < boneMap.m_nBoneCount; ++nMdlBoneIndex ) { CDmeTransform *pDmeTransform = NULL;
const int nDmeModelBoneIndex = boneMap.m_pnMdlToDmeModel[ nMdlBoneIndex ]; if ( nDmeModelBoneIndex < 0 ) { // No bind pose stored for the specified joint, use the current
// position of the joint from the skeleton instead
pDmeTransform = boneMap.m_ppTransforms[ nMdlBoneIndex ]; } else { pDmeTransform = pBindPose ? pBindPose->GetTransform( nDmeModelBoneIndex ) : pModel->GetJointTransform( nDmeModelBoneIndex ); }
if ( pDmeTransform ) { pDmeTransform->GetTransform( jointTransform );
s_bone_t &mdlBone = pSourceAnim->rawanim[0][ nMdlBoneIndex ]; MatrixAngles( jointTransform, mdlBone.rot, mdlBone.pos ); mdlBone.pos *= flScale; } else { MdlWarning( "Cannot find DmeTransform for MDL Bone %d\n", nMdlBoneIndex ); } } }
Build_Reference( pSource, "BindPose" ); }
//-----------------------------------------------------------------------------
// Does a search through connection operators for dependent DmeOperators
//-----------------------------------------------------------------------------
static void GetDependentOperators( CUtlVector< IDmeOperator * > &operatorList, CDmeOperator *pDmeOperator ) { if ( !pDmeOperator || !CastElement< CDmeOperator >( pDmeOperator ) ) return;
for ( int i = 0; i < operatorList.Count(); ++i ) { CDmeOperator *pTmpDmeOperator = CastElement< CDmeOperator >( reinterpret_cast< CDmeOperator * >( operatorList[i] ) ); if ( pTmpDmeOperator && pTmpDmeOperator == pDmeOperator ) return; }
operatorList.AddToTail( pDmeOperator );
CUtlVector< CDmAttribute * > outAttrList; pDmeOperator->GetOutputAttributes( outAttrList ); for ( int i = 0; i < outAttrList.Count(); ++i ) { CDmElement *pDmElement = outAttrList[i]->GetOwner(); if ( !pDmElement ) continue;
if ( pDmElement == pDmeOperator ) { CUtlVector< CDmElement * > reList0; FindReferringElements( reList0, pDmElement, g_pDataModel->GetSymbol( "element" ) ); for ( int j = 0; j < reList0.Count(); ++j ) { CDmeAttributeReference *pRe0 = CastElement< CDmeAttributeReference >( reList0[j] ); if ( !pRe0 ) continue;
CUtlVector< CDmElement * > reList1; FindReferringElements( reList1, pRe0, g_pDataModel->GetSymbol( "input" ) ); for ( int k = 0; k < reList1.Count(); ++k ) { CDmeConnectionOperator *pRe1 = CastElement< CDmeConnectionOperator >( reList1[k] ); if ( !pRe1 ) continue;
GetDependentOperators( operatorList, pRe1 ); } } } else { GetDependentOperators( operatorList, CastElement< CDmeOperator >( pDmElement ) ); } } }
//-----------------------------------------------------------------------------
// Main entry point for loading DMX files
//-----------------------------------------------------------------------------
static void PrepareChannels( CUtlVector< IDmeOperator * > &operatorList, CDmeChannelsClip *pAnimation ) { int nChannelsCount = pAnimation->m_Channels.Count(); for ( int i = 0; i < nChannelsCount; ++i ) { pAnimation->m_Channels[i]->SetMode( CM_PLAY ); GetDependentOperators( operatorList, pAnimation->m_Channels[i] ); } }
//-----------------------------------------------------------------------------
// Update channels so they are in position for the next frame
//-----------------------------------------------------------------------------
static void UpdateChannels( CUtlVector< IDmeOperator * > &operators, CDmeChannelsClip *pAnimation, DmeTime_t clipTime ) { int nChannelsCount = pAnimation->m_Channels.Count(); DmeTime_t channelTime = pAnimation->ToChildMediaTime( clipTime ); for ( int i = 0; i < nChannelsCount; ++i ) { pAnimation->m_Channels[i]->SetCurrentTime( channelTime ); }
// Recompute the position of the joints
{ CDisableUndoScopeGuard guard; g_pDmElementFramework->SetOperators( operators ); g_pDmElementFramework->Operate( true ); } g_pDmElementFramework->BeginEdit(); }
//-----------------------------------------------------------------------------
// Initialize the pose for this frame
//-----------------------------------------------------------------------------
static void ComputeFramePose( s_sourceanim_t *pSourceAnim, int nFrame, float flScale, BoneTransformMap_t& boneMap ) { pSourceAnim->rawanim[nFrame] = (s_bone_t *)calloc( boneMap.m_nBoneCount, sizeof( s_bone_t ) );
for ( int i = 0; i < boneMap.m_nBoneCount; ++i ) { matrix3x4_t jointTransform; boneMap.m_ppTransforms[i]->GetTransform( jointTransform ); MatrixAngles( jointTransform, pSourceAnim->rawanim[nFrame][i].rot, pSourceAnim->rawanim[nFrame][i].pos ); pSourceAnim->rawanim[nFrame][i].pos *= flScale; } }
//-----------------------------------------------------------------------------
// Main entry point for loading animations
//-----------------------------------------------------------------------------
static void LoadAnimations( s_source_t *pSource, CDmeAnimationList *pAnimationList, float flScale, BoneTransformMap_t &boneMap ) { int nAnimationCount = pAnimationList->GetAnimationCount(); for ( int i = 0; i < nAnimationCount; ++i ) { CDmeChannelsClip *pAnimation = pAnimationList->GetAnimation( i );
if ( !Q_stricmp( pAnimationList->GetName(), "BindPose" ) ) { MdlError( "Error: Cannot use \"BindPose\" as an animation name!\n" ); break; }
s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( pSource, pAnimation->GetName() ); DmeTime_t nStartTime = pAnimation->GetStartTime(); DmeTime_t nEndTime = pAnimation->GetEndTime(); int nFrameRateVal = pAnimation->GetValue<int>( "frameRate" ); if ( nFrameRateVal <= 0 ) { nFrameRateVal = 30; } DmeFramerate_t nFrameRate = nFrameRateVal; pSourceAnim->startframe = nStartTime.CurrentFrame( nFrameRate ); pSourceAnim->endframe = nEndTime.CurrentFrame( nFrameRate ); pSourceAnim->numframes = pSourceAnim->endframe - pSourceAnim->startframe + 1; CUtlVector< IDmeOperator * > operatorList; PrepareChannels( operatorList, pAnimation ); float flOOFrameRate = 1.0f / (float)nFrameRateVal; int nFrame = 0; while ( nFrame < pSourceAnim->numframes ) { int nSecond = nFrame / nFrameRateVal; int nFraction = nFrame - nSecond * nFrameRateVal; DmeTime_t t = nStartTime + DmeTime_t( nSecond * 10000 ) + DmeTime_t( (float)nFraction * flOOFrameRate ); UpdateChannels( operatorList, pAnimation, t ); ComputeFramePose( pSourceAnim, nFrame, flScale, boneMap ); ++nFrame; } } }
//-----------------------------------------------------------------------------
// Loads the skeletal hierarchy from the game model, returns bone count
//-----------------------------------------------------------------------------
static void AddFlexKeys( CDmeDag *pRoot, CDmeDag *pDag, CDmeCombinationOperator *pComboOp, s_source_t *pSource ) { CDmeMesh *pMesh = CastElement< CDmeMesh >( pDag->GetShape() ); if ( pMesh && ( pDag != pRoot ) ) { int nDeltaStateCount = pMesh->DeltaStateCount(); for ( int i = 0; i < nDeltaStateCount; ++i ) { CDmeVertexDeltaData *pDeltaState = pMesh->GetDeltaState( i ); AddFlexKey( pSource, pComboOp, pDeltaState->GetName() ); } }
// Don't create joints for the the root dag ever.. just deal with the children
int nChildCount = pDag->GetChildCount(); for ( int i = 0; i < nChildCount; ++i ) { CDmeDag *pChild = pDag->GetChild( i ); if ( !pChild ) continue;
AddFlexKeys( pRoot, pChild, pComboOp, pSource ); } }
//-----------------------------------------------------------------------------
// Loads all auxilliary model info:
//
// * Determine original source files used to generate
// the current DMX file and schedule them for processing.
//-----------------------------------------------------------------------------
void LoadModelInfo( CDmElement *pRoot, char const *pFullPath ) { // Determine original source files and schedule them for processing
if ( CDmElement *pMakeFile = pRoot->GetValueElement< CDmElement >( "makefile" ) ) { if ( CDmAttribute *pSources = pMakeFile->GetAttribute( "sources" ) ) { CDmrElementArray< CDmElement > arrSources( pSources ); for ( int kk = 0; kk < arrSources.Count(); ++ kk ) { if ( CDmElement *pModelSource = arrSources.Element( kk ) ) { if ( char const *szName = pModelSource->GetName() ) { ProcessOriginalContentFile( pFullPath, szName ); } } } } } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool LoadTwistConstraint( CDmElement *pDmElement ) { CDmeRigTwistConstraintOperator *pDmeTwist = CastElement< CDmeRigTwistConstraintOperator >( pDmElement ); if ( !pDmeTwist ) return false;
if ( g_twistbones.Count() == MAXSTUDIOBONES ) return false;
CDmeDag *pDmeDagParent = pDmeTwist->GetParentTarget(); CDmeDag *pDmeDagChild = pDmeTwist->GetChildTarget();
if ( !pDmeDagParent || !pDmeDagChild ) return false;
CTwistBone &twistBone = g_twistbones[ g_twistbones.AddToTail() ];
twistBone.m_bInverse = pDmeTwist->GetInverse(); twistBone.m_vUpVector = pDmeTwist->GetUpAxis(); V_strncpy( twistBone.m_szParentBoneName, pDmeDagParent->GetName(), ARRAYSIZE( twistBone.m_szParentBoneName ) ); twistBone.m_qBaseRotation = twistBone.m_bInverse ? pDmeTwist->GetParentBindRotation() : pDmeTwist->GetChildBindRotation(); V_strncpy( twistBone.m_szChildBoneName, pDmeDagChild->GetName(), ARRAYSIZE( twistBone.m_szChildBoneName ) );
for ( int i = 0; i < pDmeTwist->SlaveCount(); ++i ) { CDmeDag *pDmeTwistDag = pDmeTwist->GetSlaveDag( i ); if ( !pDmeTwistDag ) continue;
const char *pszTwistTargetName = pDmeTwistDag->GetName(); if ( !pszTwistTargetName || *pszTwistTargetName == '\0' ) continue;
bool bFound = false;
// Check to see if this target is already the target of a twist bone
for ( int j = 0; !bFound && j < g_twistbones.Count(); ++j ) { const CTwistBone &tmpTwistBone = g_twistbones[j]; for ( int k = 0; k < tmpTwistBone.m_twistBoneTargets.Count(); ++k ) { if ( !Q_stricmp( pszTwistTargetName, tmpTwistBone.m_twistBoneTargets[k].m_szBoneName ) ) { bFound = true; break; } } }
// A Twist bone is already driving this bone, don't make another
if ( bFound ) continue;
s_constraintbonetarget_t &twistBoneTarget = twistBone.m_twistBoneTargets[ twistBone.m_twistBoneTargets.AddToTail() ]; V_strncpy( twistBoneTarget.m_szBoneName, pDmeTwistDag->GetName(), ARRAYSIZE( twistBoneTarget.m_szBoneName ) ); twistBoneTarget.m_nBone = -1; twistBoneTarget.m_flWeight = pDmeTwist->GetSlaveWeight( i ); twistBoneTarget.m_vOffset = pDmeTwistDag->GetTransform()->GetPosition(); twistBoneTarget.m_qOffset = pDmeTwist->GetSlaveBindOrientation( i ); }
// No targets, twist constraint is useless
if ( twistBone.m_twistBoneTargets.Count() <= 0 ) { g_twistbones.RemoveMultipleFromTail( 1 ); return true; }
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool LoadBaseConstraintParams( CConstraintBoneBase *pConstraintBone, CDmeRigBaseConstraintOperator *pDmeBaseConstraint ) { const CDmaElementArray< CDmeConstraintTarget > &constraintTargets = pDmeBaseConstraint->GetTargets();
if ( constraintTargets.Count() <= 0 ) return false;
const Quaternion qRot = Quaternion( g_defaultrotation );
for ( int i = 0; i < constraintTargets.Count(); ++i ) { CDmeConstraintTarget *pDmeConstraintTarget = constraintTargets[i]; if ( !pDmeConstraintTarget ) continue;
CDmeDag *pDmeTargetDag = pDmeConstraintTarget->GetDag(); if ( !pDmeTargetDag ) continue;
// Load targets
s_constraintbonetarget_t &target = pConstraintBone->m_targets[ pConstraintBone->m_targets.AddToTail() ]; V_strncpy( target.m_szBoneName, pDmeTargetDag->GetName(), ARRAYSIZE( target.m_szBoneName ) ); target.m_nBone = -1; target.m_flWeight = pDmeConstraintTarget->GetWeight(); target.m_qOffset = pDmeConstraintTarget->GetOrientationOffset();
if ( pDmeBaseConstraint->IsA( CDmeRigPointConstraintOperator::GetStaticTypeSymbol() ) ) { // Target offsets are in world space for Point constraint
VectorRotate( pDmeConstraintTarget->GetPositionOfffset(), qRot, target.m_vOffset ); } else { target.m_vOffset = pDmeConstraintTarget->GetPositionOfffset(); }
// QuaternionMult( pDmeConstraintTarget->GetOrientationOffset(), qRot, target.m_qOffset );
}
if ( pConstraintBone->m_targets.Count() <= 0 ) return false;
const CDmeConstraintSlave *pDmeConstraintSlave = pDmeBaseConstraint->GetConstraintSlave(); if ( !pDmeConstraintSlave ) return false;
const CDmeDag *pDmeSlaveDag = pDmeBaseConstraint->GetSlave(); if ( !pDmeSlaveDag ) return false;
s_constraintboneslave_t &slave = pConstraintBone->m_slave;
V_strncpy( slave.m_szBoneName, pDmeSlaveDag->GetName(), ARRAYSIZE( slave.m_szBoneName ) ); slave.m_nBone = -1; slave.m_vBaseTranslate = pDmeConstraintSlave->GetBasePosition(); slave.m_qBaseRotation = pDmeConstraintSlave->GetBaseOrientation();
return true; }
//-----------------------------------------------------------------------------
// Most constraints don't have any specialized constraint parameters
//-----------------------------------------------------------------------------
template < class S, class T > static bool LoadSpecializedConstraintParams( S *pDmeConstraint, T *pConstraint ) { return true; }
//-----------------------------------------------------------------------------
// Load specialized Aim Constraint parameters
//-----------------------------------------------------------------------------
template <> static bool LoadSpecializedConstraintParams( CDmeRigAimConstraintOperator *pDmeConstraint, CAimConstraint *pConstraint ) { const Quaternion qRot = Quaternion( g_defaultrotation );
// Set Aim Constraint Parameters
pConstraint->m_qAimOffset = pDmeConstraint->GetValue< Quaternion >( "aimOffset" );
// Up vectors are specified in world space
VectorRotate( pDmeConstraint->GetValue< Vector >( "upVector" ), qRot, pConstraint->m_vUpVector );
CDmElement *pDmeUpSpaceTarget = pDmeConstraint->GetValueElement< CDmElement >( "upSpaceTarget" ); if ( pDmeUpSpaceTarget ) { V_strncpy( pConstraint->m_szUpSpaceTargetBone, pDmeUpSpaceTarget->GetName(), ARRAYSIZE( pConstraint->m_szUpSpaceTargetBone ) ); } pConstraint->m_nUpSpaceTargetBone = -1;
pConstraint->m_nUpType = pDmeConstraint->GetValue< int > ( "upType" );
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool FindDuplicateConstraint( const CConstraintBoneBase *pConstraintA ) { for ( int i = 0; i < g_constraintBones.Count(); ++i ) { const CConstraintBoneBase *pConstraintB = g_constraintBones[i];
if ( *pConstraintA == *pConstraintB ) return true; }
return false; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
template < class S, class T > static bool LoadConstraint( CDmElement *pDmElement ) { if ( g_constraintBones.Count() == MAXSTUDIOBONES ) { MdlError( "Too Many Constraint Bones, Max: %d\n", MAXSTUDIOBONES ); return false; }
S *pDmeConstraint = CastElement< S >( pDmElement ); if ( !pDmeConstraint ) return false;
T *pConstraint = new T(); if ( !pConstraint ) return false;
if ( !LoadBaseConstraintParams( pConstraint, pDmeConstraint ) ) { delete pConstraint; return false; }
if ( !LoadSpecializedConstraintParams( pDmeConstraint, pConstraint ) ) { delete pConstraint; return false; }
if ( FindDuplicateConstraint( pConstraint ) ) { // Delete this
delete pConstraint; } else { g_constraintBones.AddToTail( pConstraint ); }
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadConstraints( CDmElement *pDmeRoot ) { if ( !pDmeRoot ) return;
CDmAttribute *pDmeConstraintsAttr = pDmeRoot->GetAttribute( "constraints", AT_ELEMENT_ARRAY ); if ( !pDmeConstraintsAttr ) return;
CDmrElementArray< CDmElement > constraints( pDmeConstraintsAttr ); for ( int i = 0; i < constraints.Count(); ++i ) { if ( LoadTwistConstraint( constraints[i] ) ) continue;
if ( LoadConstraint< CDmeRigPointConstraintOperator, CPointConstraint >( constraints[i] ) ) continue;
if ( LoadConstraint< CDmeRigOrientConstraintOperator, COrientConstraint >( constraints[i] ) ) continue;
if ( LoadConstraint< CDmeRigAimConstraintOperator, CAimConstraint >( constraints[i] ) ) continue;
if ( LoadConstraint< CDmeRigParentConstraintOperator, CParentConstraint >( constraints[i] ) ) continue;
Error( "TODO: Support Constraint: %s\n", constraints[i]->GetName() ); } }
//-----------------------------------------------------------------------------
// Load model and skeleton
//-----------------------------------------------------------------------------
static bool LoadModelAndSkeleton( s_source_t *pSource, BoneTransformMap_t &boneMap, CDmeDag *pSkeleton, CDmeModel *pModel, CDmeCombinationOperator *pCombinationOperator, bool bStaticProp ) { s_DeltaStates.RemoveAll(); s_Balance.RemoveAll(); s_Speed.RemoveAll(); s_UniqueVertices.RemoveAll(); s_UniqueVerticesMap.RemoveAll();
if ( !pSkeleton ) return false;
// BoneRemap[bone index in file] == bone index in studiomdl
pSource->numbones = LoadSkeleton( pSkeleton, pModel, pSource->localBone, boneMap ); if ( pSource->numbones == 0 ) return false;
g_numfaces = 0; if ( pModel ) { if ( pCombinationOperator ) { pCombinationOperator->GenerateWrinkleDeltas( false ); } LoadBindPose( pModel, g_currentscale, boneMap, pSource ); if ( !LoadMeshes( pModel, g_currentscale, boneMap.m_pnDmeModelToMdl, pSource ) ) return false;
UnifyIndices( pSource ); BuildVertexAnimations( pSource ); BuildIndividualMeshes( pSource ); }
if ( g_numfaces == 0 && pSource->numbones == 1 && !V_strcmp( pSource->localBone[0].name, "defaultRoot" ) ) { MdlError( "Error - dmx has no contents: %s\n", pSource->filename ); }
if ( pCombinationOperator ) { AddFlexKeys( pModel, pModel, pCombinationOperator, pSource ); AddCombination( pSource, pCombinationOperator ); }
LoadAttachments( pSkeleton, pSkeleton, pSource, bStaticProp );
return true; }
//-----------------------------------------------------------------------------
// Given the s_model_t pointer, finds the index of it in g_model
// Returns -1 if it cannot be found
//-----------------------------------------------------------------------------
static int FindModelIndex( s_model_t *pModel ) { if ( pModel ) { for ( int i = 0; i < g_nummodels; ++i ) { if ( g_model[ i ] == pModel ) return i; }
MdlWarning( "Cannot Find s_model_t: \"%s\" in g_model\n", pModel->name ); }
return -1; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool AddFlexKey( s_source_t *pSource, s_model_t *pModel, const char *pszAnimationName, int nFlexDesc, int nFlexPair, EyelidType_t nEyelidType, float flLowererHeight, float flNeutralHeight, float flRaiserHeight, float flSplit = 0.0f, float flDecay = 1.0f, int nFrame = 0 ) // DMX frame is always 0
{ if ( g_numflexkeys >= ARRAYSIZE( g_flexkey ) ) { MdlError( "Too Many Flex Keys, Cannot Add %s\n", "TODO: FlexKeyName" ); return false; }
const int nModelIndex = FindModelIndex( pModel );
s_flexkey_t *pFlexKey = &g_flexkey[ g_numflexkeys ]; pFlexKey->source = pSource; V_strncpy( pFlexKey->animationname, pszAnimationName, sizeof( pFlexKey->animationname ) ); pFlexKey->imodel = nModelIndex; pFlexKey->flexdesc = nFlexDesc; pFlexKey->flexpair = nFlexPair;
pFlexKey->split = flSplit; pFlexKey->decay = flDecay; pFlexKey->frame = nFrame;
switch ( nEyelidType ) { case kLowerer: pFlexKey->target0 = -11; pFlexKey->target1 = -10; pFlexKey->target2 = flLowererHeight; pFlexKey->target3 = flNeutralHeight; break; case kNeutral: pFlexKey->target0 = flLowererHeight; pFlexKey->target1 = flNeutralHeight; pFlexKey->target2 = flNeutralHeight; pFlexKey->target3 = flRaiserHeight; break; case kRaiser: pFlexKey->target0 = flNeutralHeight; pFlexKey->target1 = flRaiserHeight; pFlexKey->target2 = 10; pFlexKey->target3 = 11; break; }
// Check for a duplicate
for ( int i = 0; i < g_numflexkeys; ++i ) { const s_flexkey_t *pTmpFlexKey = &( g_flexkey[ i ] );
if ( !V_stricmp( pFlexKey->animationname, pTmpFlexKey->animationname ) && pFlexKey->flexdesc == pTmpFlexKey->flexdesc && pFlexKey->flexpair == pTmpFlexKey->flexpair && pFlexKey->frame == pTmpFlexKey->frame && pFlexKey->target0 == pTmpFlexKey->target0 && pFlexKey->target1 == pTmpFlexKey->target1 && pFlexKey->target2 == pTmpFlexKey->target2 && pFlexKey->target3 == pTmpFlexKey->target3 ) { // Duplicate, get rid of it
V_memset( pFlexKey, 0, sizeof( s_flexkey_t ) );
return false; } }
// Not a duplicate, keep it
++g_numflexkeys;
return true; }
//-----------------------------------------------------------------------------
// In studiomdl.cpp
//-----------------------------------------------------------------------------
const s_sourceanim_t *GetNewStyleSourceVertexAnim( s_source_t *pSource, const char *pszVertexAnimName );
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool LoadEyelid( s_model_t *pModel, CDmeEyelid *pDmeEyelid ) { if ( !pModel || !pDmeEyelid ) return false;
s_source_t *pSource = pModel->source; if ( !pSource ) return false;
const bool bUpper = pDmeEyelid->m_bUpper.Get();
enum RightLeftType_t { kLeft = 0, kRight = 1, kRightLeftTypeCount = 2 };
struct EyelidData_t { int m_nFlexDesc[ kRightLeftTypeCount ]; const s_sourceanim_t *m_pSourceAnim; float m_flTarget; const char *m_pszSuffix; };
EyelidData_t eyelidData[3] = { { { -1, -1 }, NULL, 0.0f, "lowerer" }, { { -1, -1 }, NULL, 0.0f, "neutral" }, { { -1, -1 }, NULL, 0.0f, "raiser" } };
eyelidData[kLowerer].m_pSourceAnim = GetNewStyleSourceVertexAnim( pSource, pDmeEyelid->m_sLowererFlex.Get() ); eyelidData[kLowerer].m_flTarget = pDmeEyelid->m_flLowererHeight.Get(); eyelidData[kNeutral].m_pSourceAnim = GetNewStyleSourceVertexAnim( pSource, pDmeEyelid->m_sNeutralFlex.Get() ); eyelidData[kNeutral].m_flTarget = pDmeEyelid->m_flNeutralHeight.Get(); eyelidData[kRaiser].m_pSourceAnim = GetNewStyleSourceVertexAnim( pSource, pDmeEyelid->m_sRaiserFlex.Get() ); eyelidData[kRaiser].m_flTarget = pDmeEyelid->m_flRaiserHeight.Get();
// Add a flexdesc for <type>_right & <type>_left
// Where <type> is "upper" or "lower"
int nRightLeftBaseDesc[kRightLeftTypeCount] = { -1, -1 };
const char *sRightBaseDesc = bUpper ? "upper_right" : "lower_right"; nRightLeftBaseDesc[kRight] = Add_Flexdesc( sRightBaseDesc );
for ( int i = 0; i < kEyelidTypeCount; ++i ) { CUtlString sRightLocalDesc = sRightBaseDesc; sRightLocalDesc += "_"; sRightLocalDesc += eyelidData[i].m_pszSuffix; eyelidData[i].m_nFlexDesc[kRight] = Add_Flexdesc( sRightLocalDesc.Get() ); }
const char *sLeftBaseDesc = bUpper ? "upper_left" : "lower_left"; nRightLeftBaseDesc[kLeft] = Add_Flexdesc( sLeftBaseDesc );
for ( int i = 0; i < kEyelidTypeCount; ++i ) { CUtlString sLeftLocalDesc = sLeftBaseDesc; sLeftLocalDesc += "_"; sLeftLocalDesc += eyelidData[i].m_pszSuffix; eyelidData[i].m_nFlexDesc[kLeft] = Add_Flexdesc( sLeftLocalDesc.Get() ); }
for ( int i = 0; i < kEyelidTypeCount; ++i ) { if ( !AddFlexKey( pSource, pModel, eyelidData[i].m_pSourceAnim->animationname, nRightLeftBaseDesc[kLeft], nRightLeftBaseDesc[kRight], EyelidType_t( i ), eyelidData[kLowerer].m_flTarget, eyelidData[kNeutral].m_flTarget, eyelidData[kRaiser].m_flTarget ) ) { // Flex Keys Already Added - Eyelid is a duplicate
return false; } }
bool bRightOk = false; bool bLeftOk = false;
for ( int i = 0; i < pModel->numeyeballs; ++i ) { s_eyeball_t *pEyeball = &( pModel->eyeball[i] ); if ( !pEyeball ) continue;
RightLeftType_t nRightLeftIndex = kRight; if ( !V_stricmp( pDmeEyelid->m_sRightEyeballName.Get(), pEyeball->name ) ) { nRightLeftIndex = kRight; bRightOk = true; } else if ( !Q_stricmp( pDmeEyelid->m_sLeftEyeballName, pEyeball->name ) ) { nRightLeftIndex = kLeft; bLeftOk = true; } else { MdlWarning( "Unknown Eyeball: %s\n", pEyeball->name ); continue; }
for ( int j = 0; j < kEyelidTypeCount; ++j ) { if ( fabs( eyelidData[j].m_flTarget ) > pEyeball->radius ) { MdlError( "Eyelid \"%s\" %s %.1f out of range (+-%.1f)\n", bUpper ? "upper" : "lower", eyelidData[j].m_pszSuffix, eyelidData[j].m_flTarget, pEyeball->radius ); } }
if ( bUpper ) { pEyeball->upperlidflexdesc = nRightLeftBaseDesc[nRightLeftIndex]; for ( int j = 0; j < kEyelidTypeCount; ++j ) { pEyeball->upperflexdesc[j] = eyelidData[j].m_nFlexDesc[nRightLeftIndex]; pEyeball->uppertarget[j] = eyelidData[j].m_flTarget; } } else { pEyeball->lowerlidflexdesc = nRightLeftBaseDesc[nRightLeftIndex]; for ( int j = 0; j < kEyelidTypeCount; ++j ) { pEyeball->lowerflexdesc[j] = eyelidData[j].m_nFlexDesc[nRightLeftIndex]; pEyeball->lowertarget[j] = eyelidData[j].m_flTarget; } } }
if ( !bRightOk ) { MdlError( "Could not find right eye \"%s\"\n", pDmeEyelid->m_sRightEyeballName.Get() ); return false; }
if ( !bLeftOk ) { MdlError( "Could not find left eye \"%s\"\n", pDmeEyelid->m_sLeftEyeballName.Get() ); return false; }
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool LoadMouth( CDmeMouth *pDmeMouth ) { if ( !pDmeMouth ) return false;
const int nMouthIndex = pDmeMouth->m_nMouthNumber.Get();
// Check if mouth is already defined... not sure why people need to specify a mouth number
// in the QC though.
if ( g_nummouths > nMouthIndex ) return false;
g_nummouths = nMouthIndex + 1;
s_mouth_t *pMouth = &( g_mouth[ nMouthIndex ] ); pMouth->flexdesc = Add_Flexdesc( pDmeMouth->m_sFlexControllerName.Get() ); V_strncpy( pMouth->bonename, pDmeMouth->m_sBoneName, sizeof( pMouth->bonename ) ); pMouth->forward = pDmeMouth->m_vForward.Get(); // TODO: Adjust Y Up?
return false; }
//-----------------------------------------------------------------------------
// Loads Eyeballs
//-----------------------------------------------------------------------------
static void LoadEyeballs( s_source_t *pSource, s_model_t *pModel, CDmrElementArray< CDmElement > &elementArray ) { if ( !pSource || !pModel || elementArray.Count() <= 0 ) return;
Assert( pModel->source == NULL || pModel->source == pSource );
matrix3x4_t mDefRot; AngleMatrix( g_defaultrotation, mDefRot ); Vector vTmp;
for ( int i = 0; i < elementArray.Count(); ++i ) { CDmeEyeball *pDmeEyeball = CastElement< CDmeEyeball >( elementArray.Element( i ) ); if ( !pDmeEyeball ) continue;
if ( pModel->numeyeballs >= ARRAYSIZE( pModel->eyeball ) ) { MdlWarning( "1100: Max number of eyeballs reached for model %s, ignoring eyeball %s\n", pModel->name, pDmeEyeball->GetName() ); continue; }
int nFoundBoneIndex = -1; for ( int nSearchBoneIndex = 0; nSearchBoneIndex < pSource->numbones; ++nSearchBoneIndex ) { if ( !Q_stricmp( pSource->localBone[ nSearchBoneIndex ].name, pDmeEyeball->m_sParentBoneName.Get() ) ) { nFoundBoneIndex = nSearchBoneIndex; break; } }
if ( nFoundBoneIndex < 0 ) { MdlWarning( "1101: Couldn't find bone %s on model %s, ignoring eyeball %s\n", pDmeEyeball->m_sParentBoneName.Get(), pModel->name, pDmeEyeball->GetName() ); continue; }
const char *pszMaterialName = pDmeEyeball->m_sMaterialName.Get(); const bool bRelative = ( strchr( pszMaterialName, '/' ) || strchr( pszMaterialName, '\\' ) ) ? true : false;
const int nSearchMeshMatIndex = UseTextureAsMaterial( LookupTexture( pDmeEyeball->m_sMaterialName.Get(), bRelative ) ); int nFoundMeshMatIndex = -1; for ( int i = 0; i < pSource->nummeshes; ++i ) { const int nTmpMeshMatIndex = pSource->meshindex[ i ]; // meshes are internally stored by material index
if ( nTmpMeshMatIndex == nSearchMeshMatIndex ) { nFoundMeshMatIndex = i; break; } }
if ( nFoundMeshMatIndex < 0 ) { MdlWarning( "1102: Couldn't find eyeball material %s on model %s, ignoring eyeball %s\n", pDmeEyeball->m_sMaterialName.Get(), pModel->name, pDmeEyeball->GetName() ); continue; }
s_eyeball_t *ps_eyeball_t = &( pModel->eyeball[ pModel->numeyeballs ] ); Q_strncpy( ps_eyeball_t->name, pDmeEyeball->GetName(), sizeof( ps_eyeball_t->name ) ); ps_eyeball_t->bone = nFoundBoneIndex; ps_eyeball_t->mesh = nFoundMeshMatIndex; ps_eyeball_t->radius = pDmeEyeball->m_flRadius.Get(); ps_eyeball_t->zoffset = tan( DEG2RAD( pDmeEyeball->m_flYawAngle.Get() ) ); ps_eyeball_t->iris_scale = 1.0f / pDmeEyeball->m_flIrisScale;
// translate eyeball into bone space
VectorITransform( pDmeEyeball->m_vPosition.Get(), pSource->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->org );
VectorIRotate( Vector( 0, 0, 1 ), mDefRot, vTmp ); VectorIRotate( vTmp, pSource->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->up );
VectorIRotate( Vector( 1, 0, 0 ), mDefRot, vTmp ); VectorIRotate( vTmp, pSource->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->forward );
// Not applicable
ps_eyeball_t->upperlidflexdesc = -1; ps_eyeball_t->lowerlidflexdesc = -1;
bool bOk = true;
// Check for a duplicate eyeball
for ( int j = 0; j < pModel->numeyeballs; ++j ) { s_eyeball_t *pTmp = &( pModel->eyeball[ j ] ); if ( !V_stricmp( ps_eyeball_t->name, pTmp->name ) ) { // TODO: Check and warn about duplicate eyeballs with mis-matched parameters
bOk = false; break; } }
if ( bOk ) { // Keep eyeball
pModel->numeyeballs += 1; } else { // Clear data
V_memset( ps_eyeball_t, 0, sizeof( s_eyeball_t ) ); } }
// Make the standard flex controllers for eyes if required
static const char *szEyesFlexControllers[] = { "eyes_updown", "eyes_rightleft" };
for ( int nNewFlexIndex = 0; nNewFlexIndex < ARRAYSIZE( szEyesFlexControllers ); ++nNewFlexIndex ) { bool bHasEyeFlexController = false;
for ( int nFlexIndex = 0; nFlexIndex < g_numflexcontrollers; ++nFlexIndex ) { if ( !Q_strcmp( szEyesFlexControllers[ nNewFlexIndex ], g_flexcontroller[ nFlexIndex ].name ) ) { bHasEyeFlexController = true; break; } }
// The flex controller range for eyes_updown & eyes_rightleft is default [-45, 45] because it's clamped by eyesMaxDeflection
// and changing it based on max deflection would cause animatin changes since flex controller values are normalized [0, 1]
// [-45, 45 ] gives a maxium range that's useful
if ( !bHasEyeFlexController ) { if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL ) { MdlWarning( "1103: Couldn't make eyes flexcontroller %s, too many flex controllers defined\n", szEyesFlexControllers[ nNewFlexIndex ] ); continue; }
Q_strncpy( g_flexcontroller[g_numflexcontrollers].name, szEyesFlexControllers[ nNewFlexIndex ], sizeof( g_flexcontroller[ g_numflexcontrollers ].name ) ); Q_strncpy( g_flexcontroller[g_numflexcontrollers].type, "eyes", sizeof( g_flexcontroller[ g_numflexcontrollers ].name ) ); g_flexcontroller[g_numflexcontrollers].min = -45.0f; g_flexcontroller[g_numflexcontrollers].max = 45.0f; ++g_numflexcontrollers; } } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadQcModelElements( s_source_t *pSource, s_model_t *pModel, CDmeModel *pDmeModel ) { if ( !pModel || !pDmeModel ) return;
CDmAttribute *pQcModelElementsAttr = pDmeModel->GetAttribute( "qcModelElements", AT_ELEMENT_ARRAY ); if ( !pQcModelElementsAttr ) return;
CDmrElementArray< CDmElement > qcModelElements( pQcModelElementsAttr );
LoadEyeballs( pSource, pModel, qcModelElements );
for ( int i = 0; i < qcModelElements.Count(); ++i ) { LoadEyelid( pModel, CastElement< CDmeEyelid >( qcModelElements.Element( i ) ) ); }
for ( int i = 0; i < qcModelElements.Count(); ++i ) { LoadMouth( CastElement< CDmeMouth >( qcModelElements.Element( i ) ) ); } }
#ifdef MDLCOMPILE
//-----------------------------------------------------------------------------
// Allocates a source
// Applies the .dmx extension because the source searching algorithm looks
// for .dmx
//-----------------------------------------------------------------------------
static s_source_t *AllocateDmxSource( const char *pSourceName ) { // Allocate a new source
s_source_t *pSource = (s_source_t *)calloc( 1, sizeof( s_source_t ) ); g_source[g_numsources++] = pSource; Q_strncpy( pSource->filename, pSourceName, sizeof( pSource->filename ) ); Q_SetExtension( pSource->filename, "dmx", sizeof( pSource->filename ) ); VectorCopy( g_defaultadjust, pSource->adjust ); pSource->scale = 1.0f; pSource->rotation = g_defaultrotation; return pSource; }
//-----------------------------------------------------------------------------
// Purpose: Creates an animation from the Dme skeleton specified in the
// bone map. NOTE: It doesn't look for 'bindPose', it uses the
// current pose of the skeleton
//-----------------------------------------------------------------------------
static void CreateAnimFromSkeleton( s_source_t *pSource, const char *pSequenceName, const BoneTransformMap_t &boneMap ) { s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( pSource, pSequenceName ); pSourceAnim->startframe = 0; pSourceAnim->endframe = 0; pSourceAnim->numframes = 1;
// Default all transforms to identity
pSourceAnim->rawanim[0] = (s_bone_t *)calloc( pSource->numbones, sizeof(s_bone_t) ); for ( int i = 0; i < pSource->numbones; ++i ) { pSourceAnim->rawanim[0][i].pos.Init(); pSourceAnim->rawanim[0][i].rot.Init(); }
matrix3x4_t jointTransform; for ( int nBoneIndex = 0; nBoneIndex < boneMap.m_nBoneCount; ++nBoneIndex ) { const CDmeTransform *pDmeTransform = boneMap.m_ppTransforms[ nBoneIndex ]; s_bone_t &mdlBone = pSourceAnim->rawanim[0][ nBoneIndex ]; VectorCopy( pDmeTransform->GetPosition() * g_currentscale, mdlBone.pos ); QuaternionAngles( pDmeTransform->GetOrientation(), mdlBone.rot ); }
// This copies the named animation into the poseToBone array in the source
Build_Reference( pSource, pSequenceName ); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadBoneMaskList( CDmeBoneMaskList *pDmeBoneMaskList ) { if ( !pDmeBoneMaskList ) return;
CDmeBoneMask *pDmeDefaultBoneMask = pDmeBoneMaskList->m_eDefaultBoneMask.GetElement();
for ( int i = 0; i < pDmeBoneMaskList->m_BoneMaskList.Count(); ++i ) { CDmeBoneMask *pDmeBoneMask = pDmeBoneMaskList->m_BoneMaskList.Element( i ); if ( !pDmeBoneMask ) return;
int nBoneMaskIndex = g_numweightlist;
if ( pDmeBoneMask == pDmeDefaultBoneMask ) { nBoneMaskIndex = 0; }
if ( nBoneMaskIndex >= MAXWEIGHTLISTS ) { MdlWarning( "1300: Too many bone masks, max %d, ignoring %s\n", MAXWEIGHTLISTS, pDmeBoneMask->GetName() ); }
bool bDuplicate = false; for ( int j = 0; j < g_numweightlist; ++j ) { if ( g_weightlist[ j ].name && !Q_stricmp( g_weightlist[ j ].name, pDmeBoneMask->GetName() ) ) { bDuplicate = true; MdlWarning( "1301: Ignoring duplicate bone mask %s\n", pDmeBoneMask->GetName() ); break; } }
if ( bDuplicate ) continue;
s_weightlist_t *pWeightList = &( g_weightlist[ nBoneMaskIndex ] );
Q_strncpy( pWeightList->name, pDmeBoneMask->GetName(), sizeof( pWeightList->name ) ); pWeightList->numbones = 0; for ( int j = 0; j < pDmeBoneMask->m_BoneWeights.Count(); ++j ) { CDmeBoneWeight *pDmeBoneWeight = pDmeBoneMask->m_BoneWeights.Element( j ); if ( !pDmeBoneWeight ) continue;
int nBoneWeightIndex = pWeightList->numbones; if ( nBoneWeightIndex >= MAXWEIGHTSPERLIST ) { MdlWarning( "1302: Too many bones in weightlist %s, ignoring weight for %s (%f)\n", pWeightList->name, pDmeBoneWeight->GetName(), pDmeBoneWeight->m_flWeight.Get() ); continue; }
pWeightList->bonename[ nBoneWeightIndex ] = strdup( pDmeBoneWeight->GetName() );
// 'weight' attribute is normally used for both rotation and position weight
pWeightList->boneweight[ nBoneWeightIndex ] = pDmeBoneWeight->m_flWeight.Get(); pWeightList->boneposweight[ nBoneWeightIndex ] = pWeightList->boneweight[ nBoneWeightIndex ];
// Look for optional 'positionWeight' attribute, use it separately for position if it exists
CDmAttribute *pDmePositionWeightAttr = pDmeBoneWeight->GetAttribute( "positionWeight", AT_FLOAT ); if ( pDmePositionWeightAttr ) { pWeightList->boneposweight[ nBoneWeightIndex ] = pDmePositionWeightAttr->GetValue< float >(); if ( pWeightList->boneweight[ nBoneWeightIndex ] == 0 && pWeightList->boneposweight[ nBoneWeightIndex ] > 0 ) { MdlWarning( "1303: Non-zero position weight with zero rotation weight not allowed for bone weight list %s:%s P: %f R: %f, ignoring position weight\n", pWeightList->name, pWeightList->bonename[ nBoneWeightIndex ], pWeightList->boneposweight[ nBoneWeightIndex ], pWeightList->boneweight[ nBoneWeightIndex ] ); pWeightList->boneposweight[ nBoneWeightIndex ] = pWeightList->boneweight[ nBoneWeightIndex ]; } }
++pWeightList->numbones; }
if ( nBoneMaskIndex != 0 ) { ++g_numweightlist; } } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadPoseParameterList( CDmePoseParameterList *pDmePoseParameterList ) { if ( !pDmePoseParameterList ) return;
const int nPoseParameterCount = pDmePoseParameterList->m_ePoseParameterList.Count(); for ( int i = 0; i < nPoseParameterCount; ++i ) { const CDmePoseParameter *pDmePoseParameter = pDmePoseParameterList->m_ePoseParameterList[ i ];
if ( g_numposeparameters >= MAXSTUDIOPOSEPARAM ) { MdlWarning( "1900: Too many pose parameters, ignoring from %s\n", pDmePoseParameter->GetName() ); }
// This is like FindOrCreatePoseParamater, name is copied into g_pose[].name
const int nPoseParameterIndex = LookupPoseParameter( pDmePoseParameter->GetName() );
s_poseparameter_t *pPoseParameter = &g_pose[ nPoseParameterIndex ]; Q_strncpy( pPoseParameter->name, pDmePoseParameter->GetName(), sizeof( pPoseParameter->name ) ); pPoseParameter->min = pDmePoseParameter->m_flMin; pPoseParameter->max = pDmePoseParameter->m_flMax; if ( pDmePoseParameter->m_bWrap ) { pPoseParameter->flags |= STUDIO_LOOPING; pPoseParameter->loop = pPoseParameter->max - pPoseParameter->min; } else if ( pDmePoseParameter->m_bLoop ) { pPoseParameter->flags |= STUDIO_LOOPING; pPoseParameter->loop = pDmePoseParameter->m_flLoopRange; } } }
//-----------------------------------------------------------------------------
// Purpose: Load dmeSequence.ikChainList into the sequence
//-----------------------------------------------------------------------------
static void LoadIkChainList( CDmeSequenceList *pDmeSequenceList ) { if ( !pDmeSequenceList ) return;
const int nIkChainCount = pDmeSequenceList->m_eIkChainList.Count(); for ( int i = 0; i < nIkChainCount; ++i ) { CDmeIkChain *pDmeIkChain = pDmeSequenceList->m_eIkChainList[ i ]; if ( !pDmeIkChain ) continue;
const char *pIkChainName = pDmeIkChain->GetName();
// Check for duplicates
int j; for ( j = 0; j < g_numikchains; ++j ) { if ( !Q_stricmp( pIkChainName, g_ikchain[ j ].name ) ) break; }
if ( j < g_numikchains ) { if ( !g_quiet ) { MdlWarning( "1401: Duplicate IkChain: %s Ignored\n", pIkChainName ); } }
// Set defaults
g_ikchain[g_numikchains].axis = STUDIO_Z; // Not actually used
g_ikchain[g_numikchains].value = 0.0; // Not actually used
g_ikchain[g_numikchains].height = 18.0; g_ikchain[g_numikchains].floor = 0.0; g_ikchain[g_numikchains].radius = 0.0;
Q_strncpy( g_ikchain[ g_numikchains ].name, pIkChainName, sizeof( g_ikchain[ g_numikchains ].name ) ); Q_strncpy( g_ikchain[ g_numikchains ].bonename, pDmeIkChain->m_sEndJoint, sizeof( g_ikchain[ g_numikchains ].bonename ) ); g_ikchain[ g_numikchains ].height = pDmeIkChain->m_flHeight; g_ikchain[ g_numikchains ].floor = pDmeIkChain->m_flFloor; g_ikchain[ g_numikchains ].radius = pDmeIkChain->m_flPad / 2.0f; g_ikchain[ g_numikchains ].link[0].kneeDir = pDmeIkChain->m_vKnee; g_ikchain[ g_numikchains ].center = pDmeIkChain->m_vCenter;
++g_numikchains; } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadAnimationOptions( CDmeSequence *pDmeSimpleSequence, s_animation_t *pAnimation ) { if ( !pAnimation || !pDmeSimpleSequence ) return;
pAnimation->fps = pDmeSimpleSequence->m_flFPS; pAnimation->adjust = pDmeSimpleSequence->m_vOrigin; // Right now don't include rotation, but if added, likely combine it with the
// default rotation which is already in pAnimation->rotation
// QuaternionAngles( pDmeSequence->m_qRotation, pAnimation->rotation );
pAnimation->scale = pDmeSimpleSequence->m_flScale;
pAnimation->looprestart = pDmeSimpleSequence->m_nStartLoop;
if ( pDmeSimpleSequence->m_bLoop ) { pAnimation->flags |= STUDIO_LOOPING; }
// This should be removed and handled by assemble, i.e. STUDIO_NOFORCELOOP should always be set
if ( !pDmeSimpleSequence->m_bForceLoop ) { pAnimation->flags |= STUDIO_NOFORCELOOP; }
if ( pDmeSimpleSequence->m_bSnap ) { pAnimation->flags |= STUDIO_SNAP; }
if ( pDmeSimpleSequence->m_bPost ) { pAnimation->flags |= STUDIO_POST; }
if ( pDmeSimpleSequence->m_bAutoIk ) { pAnimation->noAutoIK = false; } else { pAnimation->noAutoIK = true; }
pAnimation->motionrollback = pDmeSimpleSequence->m_flMotionRollback;
if ( pDmeSimpleSequence->m_bAnimBlocks ) { pAnimation->disableAnimblocks = false; } else { pAnimation->disableAnimblocks = true; }
if ( pDmeSimpleSequence->m_bAnimBlockStall ) { pAnimation->isFirstSectionLocal = false; } else { pAnimation->isFirstSectionLocal = true; }
pAnimation->motiontype = pDmeSimpleSequence->m_eMotionControl.GetElement()->GetStudioMotionControl(); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int FindWeightList( const char *pWeightListName ) { for ( int i = 0; i < g_numweightlist; ++i ) { if ( !Q_stricmp( pWeightListName, g_weightlist[ i ].name ) ) return i; }
return -1; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool ValidateControlType( int nControlType, const char *pszTypeString, const char *pszNameString ) { switch ( nControlType ) { case STUDIO_X: case STUDIO_Y: case STUDIO_Z: case STUDIO_XR: case STUDIO_YR: case STUDIO_ZR: case STUDIO_LX: case STUDIO_LY: case STUDIO_LZ: case STUDIO_LXR: case STUDIO_LYR: case STUDIO_LZR: case STUDIO_LINEAR: case STUDIO_QUADRATIC_MOTION: return true; // OK!
break; default: MdlWarning( "1605: Unknown controlType \"0x%04x\" specified for %s:%s\n", nControlType, pszTypeString, pszNameString ); }
return false; }
//-----------------------------------------------------------------------------
//
// Handles
//
// alignto
// align
// alignboneto
// alignbone
//
//-----------------------------------------------------------------------------
bool HandleDmeAnimCmdAlign( s_animcmd_t *pAnimCmd, CDmeAnimCmd *pDmeAnimCmd ) { if ( !pDmeAnimCmd ) return false;
CDmeAnimCmdAlign *pDmeAnimCmdAlign = CastElement< CDmeAnimCmdAlign >( pDmeAnimCmd ); if ( !pDmeAnimCmdAlign ) return false;
CDmeSequenceBase *pDmeSequenceBase = pDmeAnimCmdAlign->m_eAnimation.GetElement(); if ( !pDmeSequenceBase ) { MdlWarning( "1608: No DmeSequence specified for %s:%s\n", pDmeAnimCmdAlign->GetTypeString(), pDmeAnimCmd->GetName() ); return false; }
s_animation_t *pExtAnim = LookupAnimation( pDmeSequenceBase->GetName() ); if ( !pExtAnim ) { MdlWarning( "1604: Unknown animation \"%s\" specified for %s:%s\n", pDmeSequenceBase->GetName(), pDmeAnimCmdAlign->GetTypeString(), pDmeAnimCmd->GetName() ); return false; }
pAnimCmd->cmd = CMD_AO; pAnimCmd->u.ao.ref = pExtAnim; pAnimCmd->u.ao.pBonename = pDmeAnimCmdAlign->m_sBoneName.IsEmpty() ? NULL : strdup( pDmeAnimCmdAlign->m_sBoneName ); pAnimCmd->u.ao.srcframe = pDmeAnimCmdAlign->m_nSourceFrame; pAnimCmd->u.ao.destframe = pDmeAnimCmdAlign->m_nDestinatonFrame; pAnimCmd->u.ao.motiontype = pDmeAnimCmdAlign->m_eMotionControl.GetElement()->GetStudioMotionControl();
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadAnimationCommands( CDmeSequence *pDmeSimpleSequence, s_animation_t *pAnimation ) { if ( !pDmeSimpleSequence || !pAnimation ) return;
const int nAnimCmdCount = pDmeSimpleSequence->m_eAnimationCommandList.Count(); for ( int i = 0; i < nAnimCmdCount; ++i ) { CDmeAnimCmd *pDmeAnimCmd = pDmeSimpleSequence->m_eAnimationCommandList[ i ]; if ( !pDmeAnimCmd ) continue;
if ( pAnimation->numcmds >= MAXSTUDIOCMDS ) { MdlWarning( "1600: Too many animation commands for anim: %s, ignoring from %d:%s\n", pAnimation->name, i, pDmeAnimCmd->GetName() ); return; }
s_animcmd_t *pAnimCmd = &pAnimation->cmds[ pAnimation->numcmds ];
if ( pDmeAnimCmd->IsA( CDmeAnimCmdFixupLoop::GetStaticTypeSymbol() ) ) { CDmeAnimCmdFixupLoop *pDmeAnimCmdFixupLoop = CastElement< CDmeAnimCmdFixupLoop >( pDmeAnimCmd ); pAnimCmd->cmd = CMD_FIXUP; pAnimCmd->u.fixuploop.start = pDmeAnimCmdFixupLoop->m_nStartFrame; pAnimCmd->u.fixuploop.end = pDmeAnimCmdFixupLoop->m_nEndFrame; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdWeightList::GetStaticTypeSymbol() ) ) { CDmeAnimCmdWeightList *pDmeAnimCmdWeightList = CastElement< CDmeAnimCmdWeightList >( pDmeAnimCmd );
const int nWeightListIndex = FindWeightList( pDmeAnimCmdWeightList->m_sWeightListName ); if ( nWeightListIndex < 0 ) { MdlWarning( "1602: Unknown weightList \"%s\" specified for %s animation command %s.%s[%d] %s\n", pDmeAnimCmdWeightList->m_sWeightListName, pDmeAnimCmdWeightList->GetTypeString(), pDmeSimpleSequence->GetName(), pDmeSimpleSequence->m_eAnimationCommandList.GetAttribute()->GetName(), i, pDmeAnimCmd->GetName() ); continue; } else { pAnimCmd->cmd = CMD_WEIGHTS; pAnimCmd->u.weightlist.index = nWeightListIndex; } } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdSubtract::GetStaticTypeSymbol() ) ) { CDmeAnimCmdSubtract *pDmeAnimCmdSubtract = CastElement< CDmeAnimCmdSubtract >( pDmeAnimCmd ); CDmeSequenceBase *pDmeSequenceBase = pDmeAnimCmdSubtract->m_eAnimation.GetElement(); if ( pDmeSequenceBase ) { s_animation_t *pExtAnim = LookupAnimation( pDmeSequenceBase->GetName() ); if ( pExtAnim ) { pAnimCmd->cmd = CMD_SUBTRACT; pAnimCmd->u.subtract.ref = pExtAnim; pAnimCmd->u.subtract.frame = pDmeAnimCmdSubtract->m_nFrame; if ( !pDmeAnimCmd->IsA( CDmeAnimCmdPreSubtract::GetStaticTypeSymbol() ) ) { pAnimCmd->u.subtract.flags |= STUDIO_POST; } } else { MdlWarning( "1603: Unknown animation \"%s\" specified for %s %s.%s[%d] %s\n", pDmeSequenceBase->GetName(), pDmeAnimCmdSubtract->GetTypeString(), pDmeSimpleSequence->GetName(), pDmeSimpleSequence->m_eAnimationCommandList.GetAttribute()->GetName(), i, pDmeAnimCmd->GetName() ); continue; } } else { MdlWarning( "1607: No DmeSequenceBase specified for %s %s.%s[%d] %s\n", pDmeAnimCmdSubtract->GetTypeString(), pDmeSimpleSequence->GetName(), pDmeSimpleSequence->m_eAnimationCommandList.GetAttribute()->GetName(), i, pDmeAnimCmd->GetName() ); continue; } } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdAlign::GetStaticTypeSymbol() ) ) { if ( !HandleDmeAnimCmdAlign( pAnimCmd, pDmeAnimCmd ) ) continue; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdRotateTo::GetStaticTypeSymbol() ) ) { CDmeAnimCmdRotateTo *pDmeAnimCmdRotateTo = CastElement< CDmeAnimCmdRotateTo >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_ANGLE; pAnimCmd->u.angle.angle = pDmeAnimCmdRotateTo->m_flAngle; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdWalkFrame::GetStaticTypeSymbol() ) ) { CDmeAnimCmdWalkFrame *pDmeAnimCmdWalkFrame = CastElement< CDmeAnimCmdWalkFrame >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_MOTION; pAnimCmd->u.motion.iEndFrame = pDmeAnimCmdWalkFrame->m_nEndFrame; pAnimCmd->u.motion.motiontype = pDmeAnimCmdWalkFrame->m_eMotionControl.GetElement()->GetStudioMotionControl(); } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdDerivative::GetStaticTypeSymbol() ) ) { CDmeAnimCmdDerivative *pDmeAnimCmdDerivative = CastElement< CDmeAnimCmdDerivative >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_DERIVATIVE; pAnimCmd->u.derivative.scale = pDmeAnimCmdDerivative->m_flScale; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdDerivative::GetStaticTypeSymbol() ) ) { CDmeAnimCmdDerivative *pDmeAnimCmdDerivative = CastElement< CDmeAnimCmdDerivative >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_DERIVATIVE; pAnimCmd->u.derivative.scale = pDmeAnimCmdDerivative->m_flScale; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdLinearDelta::GetStaticTypeSymbol() ) ) { pAnimCmd->cmd = CMD_LINEARDELTA; pAnimCmd->u.linear.flags |= STUDIO_AL_POST; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdSplineDelta::GetStaticTypeSymbol() ) ) { pAnimCmd->cmd = CMD_LINEARDELTA; pAnimCmd->u.linear.flags |= STUDIO_AL_POST; pAnimCmd->u.linear.flags |= STUDIO_AL_SPLINE; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdCompress::GetStaticTypeSymbol() ) ) { CDmeAnimCmdCompress *pDmeAnimCmdCompress = CastElement< CDmeAnimCmdCompress >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_COMPRESS; pAnimCmd->u.compress.frames = pDmeAnimCmdCompress->m_nSkipFrames; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdNumFrames::GetStaticTypeSymbol() ) ) { CDmeAnimCmdNumFrames *pDmeAnimCmdNumFrames = CastElement< CDmeAnimCmdNumFrames >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_NUMFRAMES; pAnimCmd->u.compress.frames = pDmeAnimCmdNumFrames->m_nFrames; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdLocalHierarchy::GetStaticTypeSymbol() ) ) { CDmeAnimCmdLocalHierarchy *pDmeAnimCmdLocalHierarchy = CastElement< CDmeAnimCmdLocalHierarchy >( pDmeAnimCmd );
pAnimCmd->cmd = CMD_LOCALHIERARCHY; pAnimCmd->u.localhierarchy.pBonename = strdup( pDmeAnimCmdLocalHierarchy->m_sBoneName.Get() ); pAnimCmd->u.localhierarchy.pParentname = strdup( pDmeAnimCmdLocalHierarchy->m_sParentBoneName.Get() ); pAnimCmd->u.localhierarchy.start = pDmeAnimCmdLocalHierarchy->m_flStartFrame; pAnimCmd->u.localhierarchy.peak = pDmeAnimCmdLocalHierarchy->m_flPeakFrame; pAnimCmd->u.localhierarchy.tail = pDmeAnimCmdLocalHierarchy->m_flTailFrame; pAnimCmd->u.localhierarchy.end = pDmeAnimCmdLocalHierarchy->m_flEndFrame; } else if ( pDmeAnimCmd->IsA( CDmeAnimCmdNoAnimation::GetStaticTypeSymbol() ) ) { pAnimCmd->cmd = CMD_NOANIMATION; } else { MdlWarning( "1601: Unhandled DmeAnimCmd %s (%s)\n", pDmeAnimCmd->GetTypeString(), pDmeAnimCmd->GetName() ); continue; }
++pAnimation->numcmds; } }
//-----------------------------------------------------------------------------
// TODO: Rework to handle all animation related commands
//-----------------------------------------------------------------------------
void LoadIkRuleList( CDmeSequence *pDmeSimpleSequence, s_animation_t *pAnimation ) { if ( !pDmeSimpleSequence ) return;
matrix3x4_t mDefRot; AngleMatrix( g_defaultrotation, mDefRot );
// See if we have any IkRules to load
const int nIkRuleCount = pDmeSimpleSequence->m_eIkRuleList.Count(); if ( nIkRuleCount <= 0 ) return;
for ( int i = 0; i < nIkRuleCount; ++i ) { CDmeIkRule *pDmeIkRule = pDmeSimpleSequence->m_eIkRuleList[ i ]; if ( !pDmeIkRule ) continue;
if ( pAnimation->numcmds >= MAXSTUDIOCMDS ) { // TODO: Issue a warning
MdlWarning( "1503: Too many animation commands for anim: %s, ignoring from %s:%s\n", pAnimation->name, pDmeIkRule->GetTypeString(), pDmeIkRule->GetName() ); return; }
// Find the IkChain
CDmeIkChain *pDmeIkChain = pDmeIkRule->m_eIkChain.GetElement(); if ( !pDmeIkChain ) { MdlError( "1501: No IkChain element assigned to %s:%s\n", pDmeIkRule->GetTypeString(), pDmeIkRule->GetName() ); continue; }
int nIkChainIndex = -1; for ( int j = 0; j < g_numikchains; ++j ) { if ( !Q_stricmp( pDmeIkChain->GetName(), g_ikchain[ j ].name ) ) { nIkChainIndex = j; break; } }
if ( nIkChainIndex < 0 ) { MdlWarning( "1504: Cannot find IkChain referenced by IkRule %s:%s, ignoring\n", pDmeIkRule->GetName(), pDmeIkRule->GetTypeString() ); continue; }
// calloc sets memory to 0
s_ikrule_t *pIkRule = reinterpret_cast< s_ikrule_t * >( calloc( 1, sizeof( s_ikrule_t ) ) ); if ( !pIkRule ) { MdlWarning( "1502: Cannot allocate memory for IkRule %s:%s, ignoring\n", pDmeIkRule->GetName(), pDmeIkRule->GetTypeString() ); return; }
pIkRule->chain = nIkChainIndex; pIkRule->slot = nIkChainIndex; // Default slot
CDmeIkTouchRule *pDmeIkTouchRule = CastElement< CDmeIkTouchRule >( pDmeIkRule ); CDmeIkFootstepRule *pDmeIkFootstepRule = CastElement< CDmeIkFootstepRule >( pDmeIkRule ); CDmeIkAttachmentRule *pDmeIkAttachmentRule = CastElement< CDmeIkAttachmentRule >( pDmeIkRule ); CDmeIkReleaseRule *pDmeIkReleaseRule = CastElement< CDmeIkReleaseRule >( pDmeIkRule );
if ( pDmeIkTouchRule ) { pIkRule->type = IK_SELF; Q_strncpy( pIkRule->bonename, pDmeIkTouchRule->m_sBoneName, sizeof( pIkRule->bonename ) ); } else if ( pDmeIkFootstepRule ) { pIkRule->type = IK_GROUND; pIkRule->height = pDmeIkFootstepRule->GetValue< float >( "height", g_ikchain[ pIkRule->chain ].height ); pIkRule->floor = pDmeIkFootstepRule->GetValue< float >( "floor", g_ikchain[ pIkRule->chain ].floor ); pIkRule->radius = pDmeIkFootstepRule->GetValue< float >( "pad", g_ikchain[ pIkRule->chain ].radius * 2.0f ) / 2.0f; if ( pDmeIkFootstepRule->HasAttribute( "contact", AT_INT ) ) { pIkRule->contact = pDmeIkFootstepRule->GetValue< int >( "contact" ); } } else if ( pDmeIkAttachmentRule ) { pIkRule->type = IK_ATTACHMENT; Q_strncpy( pIkRule->attachment, pDmeIkAttachmentRule->m_sAttachmentName, sizeof( pIkRule->attachment ) );
if ( pDmeIkAttachmentRule->HasAttribute( "fallbackBone", AT_STRING ) ) { Q_strncpy( pIkRule->bonename, pDmeIkAttachmentRule->GetValueString( "fallbackBone" ), sizeof( pIkRule->bonename ) ); }
if ( pDmeIkAttachmentRule->HasAttribute( "fallbackPosition", AT_VECTOR3 ) ) { VectorIRotate( pDmeIkAttachmentRule->GetValue< Vector >( "fallbackPosition" ), mDefRot, pIkRule->pos ); pIkRule->bone = -1; }
if ( pDmeIkAttachmentRule->HasAttribute( "fallbackRotation", AT_QUATERNION ) ) { // TODO: Adjust rotation as above for position adjustment?
pIkRule->q = pDmeIkAttachmentRule->GetValue< Quaternion >( "fallbackRotation" ); pIkRule->bone = -1; } } else if ( pDmeIkReleaseRule ) { pIkRule->type = IK_RELEASE; } else { MdlWarning( "1500: Unknown IkRuleType %s:%s, ignoring\n", pDmeIkRule->GetName(), pDmeIkRule->GetTypeString() ); continue; }
switch ( pDmeIkRule->m_nUseType ) { case CDmeIkRule::USE_SEQUENCE: pIkRule->usesequence = true; pIkRule->usesource = false; break; case CDmeIkRule::USE_SOURCE: pIkRule->usesequence = false; pIkRule->usesource = true; break; case CDmeIkRule::USE_NONE: default: // Nothing
break; }
CDmeIkRange *pDmeIkRange = pDmeIkRule->m_eRange.GetElement(); if ( pDmeIkRange ) { pIkRule->start = pDmeIkRange->m_nStartFrame; pIkRule->peak = pDmeIkRange->m_nMaxStartFrame; pIkRule->tail = pDmeIkRange->m_nMaxEndFrame; pIkRule->end = pDmeIkRange->m_nEndFrame; }
s_animcmd_t *pAnimCmd = &( pAnimation->cmds[ pAnimation->numcmds ] );
pAnimCmd->cmd = CMD_IKRULE; pAnimCmd->u.ikrule.pRule = pIkRule;
pAnimation->numcmds++; } }
//-----------------------------------------------------------------------------
// Purpose: Loads all of the AddLayer/BlendLayer for the specified sequence
//-----------------------------------------------------------------------------
void LoadLayerList( CDmeSequenceBase *pDmeSequenceBase, s_sequence_t *pSequence ) { if ( !pDmeSequenceBase || !pSequence ) return;
const int nLayerCount = pDmeSequenceBase->m_eLayerList.Count(); for ( int i = 0; i < nLayerCount; ++i ) { CDmeSequenceLayerBase *pDmeLayerBase = pDmeSequenceBase->m_eLayerList[ i ]; if ( !pDmeLayerBase ) continue;
CDmeSequenceBase *pDmeRefSeq = pDmeLayerBase->m_eAnimation.GetElement(); if ( !pDmeRefSeq ) { // TODO: Warning message - No sequence specified
continue; }
if ( pSequence->numautolayers >= ARRAYSIZE( pSequence->autolayer ) ) { // TODO: Warning message - Too many layers
break; }
Q_strncpy( pSequence->autolayer[ pSequence->numautolayers ].name, pDmeRefSeq->GetName(), sizeof( pSequence->autolayer[ pSequence->numautolayers ].name ) );
CDmeSequenceAddLayer *pDmeAddLayer = CastElement< CDmeSequenceAddLayer >( pDmeSequenceBase->m_eLayerList[ i ] ); if ( pDmeAddLayer ) { // Nothing to do
}
CDmeSequenceBlendLayer *pDmeBlendLayer = CastElement< CDmeSequenceBlendLayer >( pDmeSequenceBase->m_eLayerList[ i ] ); if ( pDmeBlendLayer ) { pSequence->autolayer[ pSequence->numautolayers ].start = pDmeBlendLayer->m_flStartFrame; pSequence->autolayer[ pSequence->numautolayers ].peak = pDmeBlendLayer->m_flPeakFrame; pSequence->autolayer[ pSequence->numautolayers ].tail = pDmeBlendLayer->m_flTailFrame; pSequence->autolayer[ pSequence->numautolayers ].end = pDmeBlendLayer->m_flEndFrame; pSequence->autolayer[ pSequence->numautolayers ].flags |= pDmeBlendLayer->m_bCrossfade ? STUDIO_AL_XFADE : 0; pSequence->autolayer[ pSequence->numautolayers ].flags |= pDmeBlendLayer->m_bSpline ? STUDIO_AL_SPLINE : 0; pSequence->autolayer[ pSequence->numautolayers ].flags |= pDmeBlendLayer->m_bNoBlend ? STUDIO_AL_NOBLEND : 0; pSequence->autolayer[ pSequence->numautolayers ].flags |= pDmeBlendLayer->m_bLocal ? STUDIO_AL_LOCAL : 0; pSequence->flags |= pDmeBlendLayer->m_bLocal ? STUDIO_AL_LOCAL : 0; if ( !pDmeBlendLayer->m_sPoseParameterName.IsEmpty() ) { pSequence->autolayer[ pSequence->numautolayers ].flags |= STUDIO_AL_POSE; pSequence->autolayer[ pSequence->numautolayers ].pose = LookupPoseParameter( pDmeBlendLayer->m_sPoseParameterName.Get() ); } }
pSequence->numautolayers++; } }
//-----------------------------------------------------------------------------
// Purpose: Loads all of the IkLocks for the specified Sequence
//-----------------------------------------------------------------------------
void LoadIkLockList( CDmeSequenceBase *pDmeSequenceBase, s_sequence_t *pSequence ) { if ( !pDmeSequenceBase || !pSequence ) return;
const int nIkLockCount = pDmeSequenceBase->m_eIkLockList.Count(); for ( int i = 0; i < nIkLockCount; ++i ) { CDmeIkLock *pDmeIkLock = pDmeSequenceBase->m_eIkLockList[ i ]; if ( !pDmeIkLock ) continue;
// TODO: Check for duplicates?
CDmeIkChain *pDmeIkChain = pDmeIkLock->m_eIkChain.GetElement(); if ( pDmeIkChain ) { Q_strncpy( pSequence->iklock[ pSequence->numiklocks ].name, pDmeIkChain->GetName(), sizeof( pSequence->iklock[ pSequence->numiklocks ].name ) ); } else { MdlError( "1700: No IkChain element assigned to %s:%s\n", pDmeIkLock->GetTypeString(), pDmeIkLock->GetName() ); } pSequence->iklock[ pSequence->numiklocks ].flPosWeight = clamp( pDmeIkLock->m_flLockPosition.Get(), 0.0f, 1.0f ); pSequence->iklock[ pSequence->numiklocks ].flLocalQWeight = RemapValClamped( pDmeIkLock->m_flLockRotation, 0.0f, 1.0f, 1.0f, 0.0f ); // Invert it for historical reasons
pSequence->numiklocks++; } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadAnimationEventList( CDmeSequenceBase *pDmeSequenceBase, s_sequence_t *pSequence ) { if ( !pDmeSequenceBase || !pSequence ) return;
CDmAttribute *pBlah = pDmeSequenceBase->GetAttribute( "animationEventList" ); int nFoo = 0; if ( pBlah ) { nFoo = CDmrGenericArrayConst( pBlah ).Count(); } const int nAnimEventCount = pDmeSequenceBase->m_eAnimationEventList.Count(); for ( int i = 0; i < nAnimEventCount; ++i ) { CDmeAnimationEvent *pDmeAnimationEvent = pDmeSequenceBase->m_eAnimationEventList[ i ]; if ( !pDmeAnimationEvent ) continue;
// Not sure why this is +1... always one special event??
if ( pSequence->numevents + 1 >= MAXSTUDIOEVENTS ) { MdlError( "1800: Too many %s's on %s:%s, ignoring from %s\n", pDmeAnimationEvent->GetTypeString(), pDmeSequenceBase->GetTypeString(), pDmeSequenceBase->GetName(), pDmeAnimationEvent->GetName() ); return; }
Q_strncpy( pSequence->event[ pSequence->numevents ].eventname, pDmeAnimationEvent->GetName(), sizeof( pSequence->event[ pSequence->numevents ].eventname ) ); pSequence->event[ pSequence->numevents ].frame = pDmeAnimationEvent->m_nFrame; if ( pDmeAnimationEvent->m_sDataString.IsEmpty() ) { pSequence->event[ pSequence->numevents ].options[0] = '\0'; } else { Q_strncpy( pSequence->event[ pSequence->numevents ].options, pDmeAnimationEvent->m_sDataString, sizeof( pSequence->event[ pSequence->numevents ].options ) ); } ++pSequence->numevents; } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadSequenceList( CDmeMultiSequence *pDmeMultiSequence, CUtlVector< s_animation_t * > &animations ) { if ( !pDmeMultiSequence ) return;
const int nSequenceCount = pDmeMultiSequence->m_eSequenceList.Count(); for ( int i = 0; i < nSequenceCount; ++i ) { CDmeSequence *pDmeSubSequence = pDmeMultiSequence->m_eSequenceList[ i ]; if ( !pDmeSubSequence ) continue;
s_animation_t *pAnim = LookupAnimation( pDmeSubSequence->GetName() ); if ( !pAnim ) { MdlWarning( "1208: DmeSequence %s: Couldn't find referenced animation: %s\n", pDmeMultiSequence->GetName(), pDmeSubSequence->GetName() ); continue; }
// TODO: Limit to 64?
animations.AddToTail( pAnim ); } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadSequenceBlends( CDmeMultiSequence *pDmeMultiSequence, s_sequence_t *pSequence ) { if ( !pSequence || !pDmeMultiSequence ) return;
const int nBlendCount = pDmeMultiSequence->m_eBlendList.Count(); for ( int i = 0; i < nBlendCount; ++i ) { CDmeSequenceBlendBase *pDmeSequenceBlendBase = pDmeMultiSequence->m_eBlendList[ i ]; if ( !pDmeSequenceBlendBase ) continue;
if ( pDmeSequenceBlendBase->IsA( CDmeSequenceBlend::GetStaticTypeSymbol() ) ) { CDmeSequenceBlend *pDmeSequenceBlend = CastElement< CDmeSequenceBlend >( pDmeSequenceBlendBase );
const int i = ( pSequence->paramindex[0] != -1 ) ? 1 : 0; const int j = LookupPoseParameter( pDmeSequenceBlend->m_sPoseParameterName.Get() ); pSequence->paramindex[i] = j; pSequence->paramattachment[i] = -1; pSequence->paramstart[i] = pDmeSequenceBlend->m_flParamStart; pSequence->paramend[i] = pDmeSequenceBlend->m_flParamEnd;
g_pose[j].min = MIN( g_pose[j].min, pSequence->paramstart[i] ); g_pose[j].min = MIN( g_pose[j].min, pSequence->paramend[i] ); g_pose[j].max = MAX( g_pose[j].max, pSequence->paramstart[i] ); g_pose[j].max = MAX( g_pose[j].max, pSequence->paramend[i] ); } else if ( pDmeSequenceBlendBase->IsA( CDmeSequenceCalcBlend::GetStaticTypeSymbol() ) ) { // TODO: This really isn't an animation command but the distinction might not be important?
CDmeSequenceCalcBlend *pDmeSequenceCalcBlend = CastElement< CDmeSequenceCalcBlend >( pDmeSequenceBlendBase );
const int i = ( pSequence->paramindex[0] != -1 ) ? 1 : 0; const int j = LookupPoseParameter( pDmeSequenceCalcBlend->m_sPoseParameterName.Get() ); pSequence->paramindex[i] = j; pSequence->paramattachment[i] = LookupAttachment( pDmeSequenceCalcBlend->m_sAttachmentName.Get() ); pSequence->paramcontrol[i] = pDmeSequenceCalcBlend->m_eMotionControl.GetElement()->GetStudioMotionControl();
if ( pSequence->paramattachment[i] < 0 ) { MdlWarning( "1606: Unknown Attachment For %s - %s.%s = %s\n", pDmeSequenceCalcBlend->GetTypeString(), pDmeSequenceCalcBlend->GetName(), pDmeSequenceCalcBlend->m_sAttachmentName.GetAttribute()->GetName(), pDmeSequenceCalcBlend->m_sAttachmentName.Get() ); }
continue; // Don't add it as an animation command
} } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void LoadBlendRefCompCenter( CDmeMultiSequence *pDmeMultiSequence, s_sequence_t *pSequence ) { if ( !pDmeMultiSequence || !pSequence ) return;
CDmeSequence *pDmeBlendRefSequence = CastElement< CDmeSequence >( pDmeMultiSequence->m_eBlendRef.GetElement() ); if ( pDmeBlendRefSequence ) { const char *pszBlendRef = pDmeBlendRefSequence->GetName(); if ( pszBlendRef && *pszBlendRef ) { pSequence->paramanim = LookupAnimation( pszBlendRef ); if ( pSequence->paramanim == NULL ) { MdlWarning( "1202: DmeSequence %s: Unknown .blendRef animation: %s\n", pDmeMultiSequence->GetName(), pszBlendRef ); } } }
CDmeSequence *pDmeBlendRefComp = CastElement< CDmeSequence >( pDmeMultiSequence->m_eBlendComp.GetElement() ); if ( pDmeBlendRefComp ) { const char *pszBlendComp = pDmeBlendRefComp->GetName(); if ( pszBlendComp && *pszBlendComp ) { pSequence->paramcompanim = LookupAnimation( pszBlendComp ); if ( pSequence->paramcompanim == NULL ) { MdlWarning( "1203: DmeSequence %s: Unknown .blendComp animation: %s\n", pDmeMultiSequence->GetName(), pszBlendComp ); } } }
CDmeSequence *pDmeBlendRefCenter = CastElement< CDmeSequence >( pDmeMultiSequence->m_eBlendCenter.GetElement() ); if ( pDmeBlendRefCenter ) { const char *pszBlendCenter = pDmeBlendRefCenter->GetName(); if ( pszBlendCenter && *pszBlendCenter ) { pSequence->paramcenter = LookupAnimation( pszBlendCenter ); if ( pSequence->paramcenter == NULL ) { MdlWarning( "1204: DmeSequence %s: Unknown .blendCenter animation: %s\n", pDmeMultiSequence->GetName(), pszBlendCenter ); } } } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void CreateBindPoseSequence( s_source_t *pMainSource ) { s_sourceanim_t *pSourceAnim = FindSourceAnim( pMainSource, "BindPose" ); if ( pSourceAnim ) { // Create the first "BindPose" default sequence
s_sequence_t *pSeq = ProcessCmdSequence( "BindPose" ); s_animation_t *pAnim = ProcessImpliedAnimation( pSeq, pMainSource->filename ); pSeq->panim[0][0] = pAnim; ProcessSequence( pSeq, 1, &pAnim, false ); } }
//-----------------------------------------------------------------------------
// Purpose: Load the data from the CDmeAnimBlockSize element which is
// specified by the .animBlockSize attribute on the root node
// of the .mpp file. If it's NULL, do nothing
// Basically implements Cmd_AnimBlockSize
//-----------------------------------------------------------------------------
static void LoadAnimBlockSize( CDmeAnimBlockSize *pDmeAnimBlockSize ) { if ( !pDmeAnimBlockSize ) return;
g_animblocksize = pDmeAnimBlockSize->m_nSize; if ( g_animblocksize < 1024 ) { g_animblocksize *= 1024; }
g_bNoAnimblockStall = !pDmeAnimBlockSize->m_bStall; switch ( pDmeAnimBlockSize->m_nStorageType ) { case CDmeAnimBlockSize::ANIMBLOCKSTORAGETYPE_HIRES: g_bAnimblockLowRes = false; g_bAnimblockHighRes = true; break; case CDmeAnimBlockSize::ANIMBLOCKSTORAGETYPE_LOWRES: // Fallthrough deliberate
default: g_bAnimblockLowRes = true; g_bAnimblockHighRes = false; break; } }
//-----------------------------------------------------------------------------
// Purpose: Load all of the DmeSequence elements, if the passed
// CDmeSequenceList is NULL, empty or doesn't contain any valid
// sequences, a default "BindPose" sequence is created
// Returns: The number of sequences created
//-----------------------------------------------------------------------------
static int LoadAndCreateSequences( s_source_t *pMainSource, CDmeSequenceList *pSequenceList, bool &bSetUpAxis ) { CreateBindPoseSequence( pMainSource );
LoadIkChainList( pSequenceList );
int nSequenceAddedCount = 0;
char szSeqName[ MAX_PATH ]; const int nSequenceCount = pSequenceList ? pSequenceList->m_Sequences.Count() : 0;
CUtlVector< CDmeSequenceBase * > sortedSequenceList;
// Add all CDmeSequence elements to the list first
for ( int nSequenceIndex = 0; nSequenceIndex < nSequenceCount; ++nSequenceIndex ) { CDmeSequenceBase *pDmeSequenceBase = pSequenceList->m_Sequences[ nSequenceIndex ]; if ( !pDmeSequenceBase ) { MdlWarning( "1201: Empty DmeSequence %s[ %d ]\n", pSequenceList->GetName(), nSequenceIndex ); continue; }
const char *pszSeqName = pDmeSequenceBase->GetName(); if ( pszSeqName == NULL || *pszSeqName == '\0' ) { MdlWarning( "1200: Ignoring Unnamed Sequence On %s[%d]\n", pSequenceList->GetName(), nSequenceIndex ); continue; }
sortedSequenceList.AddToTail( pDmeSequenceBase ); }
qsort( sortedSequenceList.Base(), sortedSequenceList.Count(), sizeof( CDmeSequenceBase * ), CDmeSequenceBase::QSortFunction );
// Process the reordered DmeSequenceBase list
for ( int nSequenceIndex = 0; nSequenceIndex < sortedSequenceList.Count(); ++nSequenceIndex ) { CUtlVector< s_animation_t * > animations;
CDmeSequenceBase *pDmeSequenceBase = sortedSequenceList[ nSequenceIndex ]; // Already checked for NULL & unnamed
const char *pszSeqName = pDmeSequenceBase->GetName(); Q_StripExtension( pszSeqName, szSeqName, sizeof( szSeqName ) );
s_sequence_t *pSeq = ProcessCmdSequence( szSeqName );
{
CDmeSequence *pDmeSimpleSequence = CastElement< CDmeSequence >( pDmeSequenceBase ); CDmeMultiSequence *pDmeMultiSequence = CastElement< CDmeMultiSequence >( pDmeSequenceBase ); if ( !pDmeSimpleSequence && !pDmeMultiSequence ) { MdlWarning( "1209: Invalid DmeSequence %s[ %d ], not Simple or Multi\n", pSequenceList->GetName(), nSequenceIndex ); continue; }
if ( pDmeSimpleSequence ) { CDmeModel *pDmeModel = CastElement< CDmeModel >( pDmeSimpleSequence->m_eSkeleton.GetElement() );
if ( bSetUpAxis && pDmeModel ) { const char *pUpAxis = pDmeModel->GetValueString( "upAxis" ); if ( pUpAxis ) { if ( StringHasPrefix( pUpAxis, "Y" ) ) { // rotate 90 degrees around x to move y into z
g_defaultrotation = RadianEuler( M_PI / 2.0f, 0.0f, M_PI / 2.0f ); } } bSetUpAxis = false; }
CDmeDag *pDmeSkeleton = pDmeSimpleSequence->m_eSkeleton.GetElement(); if ( !pDmeSkeleton ) { MdlWarning( "1205: Ignoring Sequence %s, No Skeleton Specified\n", pszSeqName ); continue; }
s_source_t *pSource = AllocateDmxSource( szSeqName );
BoneTransformMap_t boneMap; if ( !LoadModelAndSkeleton( pSource, boneMap, pDmeSkeleton, pDmeModel, NULL, false ) ) { MdlWarning( "1206: Sequence %s: Ignoring Sequence, Couldn't Load Specified Skeleton: %s\n", pszSeqName, pDmeSkeleton->GetName() ); continue; }
CDmeAnimationList *pDmeAnimationList = pDmeSimpleSequence->m_eAnimationList.GetElement(); s_animation_t *pAnim = NULL;
if ( pDmeAnimationList ) { // Simple sequence created from animation data
LoadAnimations( pSource, pDmeAnimationList, g_currentscale, boneMap );
// Check for various CDmeAnimCmd's which may or may not be flagged with bDelta
bool bDmeAnimCmdDelta = false; for ( int i = 0; i < pDmeSimpleSequence->m_eAnimationCommandList.Count(); ++i ) { if ( pDmeSimpleSequence->m_eAnimationCommandList[i]->IsA( CDmeAnimCmdSubtract::GetStaticTypeSymbol() ) || pDmeSimpleSequence->m_eAnimationCommandList[i]->IsA( CDmeAnimCmdPreSubtract::GetStaticTypeSymbol() ) || pDmeSimpleSequence->m_eAnimationCommandList[i]->IsA( CDmeAnimCmdLinearDelta::GetStaticTypeSymbol() ) || pDmeSimpleSequence->m_eAnimationCommandList[i]->IsA( CDmeAnimCmdSplineDelta::GetStaticTypeSymbol() ) ) { bDmeAnimCmdDelta = true; pSeq->flags |= STUDIO_DELTA; } }
{ // allocate animation entry
g_panimation[g_numani] = (s_animation_t *)calloc( 1, sizeof( s_animation_t ) ); g_panimation[g_numani]->index = g_numani; pAnim = g_panimation[g_numani]; g_numani++;
pAnim->isImplied = true;
pAnim->startframe = 0; pAnim->endframe = MAXSTUDIOANIMFRAMES - 1;
strcpy( pAnim->name, "@" ); strcat( pAnim->name, pSeq->name ); strcpyn( pAnim->filename, pSource->filename );
VectorCopy( g_defaultadjust, pAnim->adjust ); pAnim->scale = 1.0f;
// Don't re-orient delta animations
if ( bDmeAnimCmdDelta || !( pDmeSimpleSequence->m_bDelta || pDmeSimpleSequence->m_bPreDelta ) ) { pAnim->rotation = g_defaultrotation; }
pAnim->fps = 30; pAnim->motionrollback = g_flDefaultMotionRollback;
pAnim->source = pSource; const int nSourceAnimCount = pAnim->source->m_Animations.Count(); if ( nSourceAnimCount > 0 ) { s_sourceanim_t *pSourceAnim = &pAnim->source->m_Animations[nSourceAnimCount-1]; Q_strncpy( pAnim->animationname, pAnim->source->m_Animations[nSourceAnimCount-1].animationname, sizeof(pAnim->animationname) ); if ( pAnim->startframe < pSourceAnim->startframe ) { pAnim->startframe = pSourceAnim->startframe; }
if ( pAnim->endframe > pSourceAnim->endframe ) { pAnim->endframe = pSourceAnim->endframe; } } else { Q_strncpy( pAnim->animationname, "", sizeof( pAnim->animationname ) ); }
pAnim->numframes = pAnim->endframe - pAnim->startframe + 1; } } else { // Simple sequence created from a single pose
CreateAnimFromSkeleton( pSource, szSeqName, boneMap );
pAnim = ProcessImpliedAnimation( pSeq, pSource->filename ); }
Assert( pAnim ); animations.AddToTail( pAnim );
// Hack to allow animations commands to refer to same sequence in a simple DmeSequence
pSeq->panim[0][0] = pAnim;
// Animation Options
LoadAnimationOptions( pDmeSimpleSequence, pAnim );
// Animation Commands
LoadAnimationCommands( pDmeSimpleSequence, pAnim );
// IkRules
LoadIkRuleList( pDmeSimpleSequence, pAnim );
AddBodyAttachments( pSource ); }
// Only apply to multi-sequences
if ( pDmeMultiSequence ) { LoadSequenceList( pDmeMultiSequence, animations );
LoadSequenceBlends( pDmeMultiSequence, pSeq );
LoadBlendRefCompCenter( pDmeMultiSequence, pSeq );
pSeq->groupsize[ 0 ] = pDmeMultiSequence->m_nBlendWidth.Get(); } }
LoadAnimationEventList( pDmeSequenceBase, pSeq );
// Same As ParseSequence
CDmeSequenceActivity *pDmeSequenceActivity = pDmeSequenceBase->m_eActivity.GetElement(); if ( pDmeSequenceActivity ) { const char *pszActivityName = pDmeSequenceActivity->GetName(); if ( pszActivityName && Q_strlen( pszActivityName ) > 0 ) { Q_strncpy( pSeq->activityname, pszActivityName, sizeof( pSeq->activityname ) ); pSeq->actweight = pDmeSequenceActivity->m_nWeight.Get();
for ( int nModifierIndex = 0; nModifierIndex < pDmeSequenceActivity->m_sModifierList.Count(); ++nModifierIndex ) { strcpyn( pSeq->activitymodifier[ pSeq->numactivitymodifiers++ ].name, pDmeSequenceActivity->m_sModifierList.Get( nModifierIndex ) );
if ( pSeq->numactivitymodifiers == MAXSTUDIOACTIVITYMODIFIERS ) break; }
if ( pDmeSequenceActivity->m_sModifierList.Count() > MAXSTUDIOACTIVITYMODIFIERS ) { MdlWarning( "1210: Too many activity modifiers (%d) on DmeSequence %s, only using first %d\n", pDmeSequenceActivity->m_sModifierList.Count(), pSeq->name, MAXSTUDIOACTIVITYMODIFIERS ); } } }
if ( pDmeSequenceBase->m_bLoop ) { pSeq->flags |= STUDIO_LOOPING; }
if ( pDmeSequenceBase->m_bSnap ) { pSeq->flags |= STUDIO_SNAP; }
if ( pDmeSequenceBase->m_bPost ) { pSeq->flags |= STUDIO_POST; }
if ( pDmeSequenceBase->m_bHidden ) { pSeq->flags |= STUDIO_HIDDEN; }
if ( pDmeSequenceBase->m_bDelta ) { pSeq->flags |= STUDIO_DELTA; pSeq->flags |= STUDIO_POST; }
if ( pDmeSequenceBase->m_bWorldSpace ) { pSeq->flags |= STUDIO_WORLD; pSeq->flags |= STUDIO_POST; }
if ( pDmeSequenceBase->m_bPreDelta ) { pSeq->flags |= STUDIO_DELTA; }
if ( pDmeSequenceBase->m_bAutoPlay ) { pSeq->flags |= STUDIO_AUTOPLAY; }
if ( pDmeSequenceBase->m_bRealtime ) { pSeq->flags |= STUDIO_REALTIME; }
// AddLayer/BlendLayer
LoadLayerList( pDmeSequenceBase, pSeq );
// IkLock
LoadIkLockList( pDmeSequenceBase, pSeq );
pSeq->fadeintime = pDmeSequenceBase->m_flFadeIn.Get(); pSeq->fadeouttime = pDmeSequenceBase->m_flFadeOut.Get();
const CUtlString &entryNode = pDmeSequenceBase->m_sEntryNode.Get(); const CUtlString &exitNode = pDmeSequenceBase->m_sExitNode.Get(); if ( !entryNode.IsEmpty() ) { if ( !exitNode.IsEmpty() ) { pSeq->entrynode = LookupXNode( entryNode.Get() ); pSeq->exitnode = LookupXNode( exitNode.Get() ); if ( pDmeSequenceBase->m_bReverseNodeTransition ) { pSeq->nodeflags |= 1; } } else { pSeq->entrynode = pSeq->exitnode = LookupXNode( entryNode.Get() ); } }
if ( animations.Count() > 0 ) { ProcessSequence( pSeq, animations.Count(), animations.Base(), false ); ++nSequenceAddedCount; } else { MdlWarning( "1207: DmeSequence %s: No animations created or referenced, ignoring\n", pszSeqName ); }
const CUtlString &keyValues = pDmeSequenceBase->m_sKeyValues.Get(); const int nKeyValuesLength = keyValues.Length(); if ( nKeyValuesLength > 0 ) { pSeq->KeyValue.AddMultipleToTail( nKeyValuesLength, keyValues.Get() ); } }
return nSequenceAddedCount; }
//-----------------------------------------------------------------------------
// Purpose: Load all of the strings from the passed
// CDmeIncludeModelList.includeModels array
//-----------------------------------------------------------------------------
static void LoadIncludeModelList( CDmeIncludeModelList *pIncludeModelList ) { if ( !pIncludeModelList ) return;
const int nIncludeModelsCount = pIncludeModelList->m_IncludeModels.Count(); for ( int ni = 0; ni < nIncludeModelsCount; ++ni ) { if ( g_numincludemodels >= ARRAYSIZE( g_includemodel ) ) { MdlError( "Too Many Include Models while including: \"%s\", Max: %d\n", pIncludeModelList->m_IncludeModels[ ni ], ARRAYSIZE( g_includemodel ) ); } Q_strncpy( g_includemodel[ g_numincludemodels ].name, pIncludeModelList->m_IncludeModels[ ni ], MAXSTUDIONAME ); Q_FixSlashes( g_includemodel[ g_numincludemodels ].name, '/' ); ++g_numincludemodels; } }
// return true if this QAngle is (0,0,0) within tolerance
static bool IsZero( const QAngle &qa, float tolerance = 0.01f ) { return (qa.x > -tolerance && qa.x < tolerance && qa.y > -tolerance && qa.y < tolerance && qa.z > -tolerance && qa.z < tolerance); }
//-----------------------------------------------------------------------------
// Split the specified string into substrings separated by "_"'s and then
// sort the strings alphabetically
//-----------------------------------------------------------------------------
static void UnderscoreSplitAndSortStrings( const char *pszString, CUtlVector< CUtlString > &splitAndSortedString ) { splitAndSortedString.RemoveAll();
const int nStrLen = Q_strlen( pszString ); if ( nStrLen <= 0 ) return;
char *pszTmpString = reinterpret_cast< char * >( stackalloc( ( nStrLen + 1 ) * sizeof( char ) ) ); if ( !pszTmpString ) return;
Q_memset( pszTmpString, 0, ( nStrLen + 1 ) * sizeof( char ) ); Q_strncpy( pszTmpString, pszString, nStrLen + 1 );
char *pszEnd = NULL; for ( char *pszName = pszTmpString; pszName && *pszName; pszName = pszEnd ) { pszEnd = strchr( pszName, '_' ); if ( !pszEnd ) { splitAndSortedString.AddToTail( CUtlString( pszName ) ); } else { *pszEnd = '\0'; ++pszEnd; splitAndSortedString.AddToTail( CUtlString( pszName ) ); } }
// Insertion sort
for ( int i = 1; i < splitAndSortedString.Count(); ++i ) { const CUtlString sValue = splitAndSortedString[i]; int j = i - 1; while ( j >= 0 && Q_stricmp( splitAndSortedString[j].Get(), sValue.Get() ) > 0 ) { splitAndSortedString[ j + 1 ] = splitAndSortedString[ j ]; --j; } splitAndSortedString[j + 1] = sValue; } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool GetContentsDescription( int &nContentsDescription, CDmElement *pDmElement ) { nContentsDescription = CONTENTS_SOLID;
if ( !pDmElement ) return false;
CDmAttribute *pDmeContentsDescriptionAttr = pDmElement->GetAttribute( "contentsDescription" ); if ( !pDmeContentsDescriptionAttr ) return false;
if ( pDmeContentsDescriptionAttr->GetType() != AT_STRING ) return false;
const char *pszContentsDesc = pDmeContentsDescriptionAttr->GetValueString(); if ( !pszContentsDesc || Q_strlen( pszContentsDesc ) <= 0 ) return false;
struct ContentDesc_t { const char *m_pszName; int m_nContentDescriptionFlags; };
ContentDesc_t contentDescs[] = { { "notsolid", CONTENTS_EMPTY }, { "monster", CONTENTS_MONSTER }, { "ladder", CONTENTS_LADDER }, { "solid", CONTENTS_SOLID }, { "solid_monster", CONTENTS_SOLID | CONTENTS_MONSTER }, { "solid_ladder", CONTENTS_SOLID | CONTENTS_LADDER }, { "solid_monster_ladder", CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_LADDER }, { "grate", CONTENTS_GRATE }, { "grate_monster", CONTENTS_GRATE | CONTENTS_MONSTER }, { "grate_ladder", CONTENTS_GRATE | CONTENTS_LADDER }, { "grate_monster_ladder", CONTENTS_GRATE | CONTENTS_MONSTER | CONTENTS_LADDER }, };
CUtlVector< CUtlString > userContentsDesc; UnderscoreSplitAndSortStrings( pszContentsDesc, userContentsDesc );
CUtlVector< CUtlString > validContentsDesc;
for ( int i = 0; i < ARRAYSIZE( contentDescs ); ++i ) { UnderscoreSplitAndSortStrings( contentDescs[i].m_pszName, validContentsDesc ); if ( userContentsDesc.Count() != validContentsDesc.Count() ) continue;
bool bFound = true;
for ( int j = 0; j < userContentsDesc.Count(); ++j ) { if ( Q_stricmp( userContentsDesc[j].Get(), validContentsDesc[j].Get() ) ) { bFound = false; break; } }
if ( bFound ) { nContentsDescription = contentDescs[i].m_nContentDescriptionFlags; return true; } }
MdlWarning( "2100: Invalid \"contentsDescription\" \"%s\" on \"%s\"\n", pszContentsDesc, pDmElement->GetName() );
static bool bWarned = false; if ( !bWarned ) { bWarned = true;
CUtlString sWarn;
const int nContentDescsCount = ARRAYSIZE( contentDescs ); if ( nContentDescsCount > 0 ) { sWarn = contentDescs[0].m_pszName;
for ( int i = 1; i < nContentDescsCount; ++i ) { sWarn += ", "; sWarn += contentDescs[i].m_pszName; } }
MdlWarning( " Valid ones are: %s\n", sWarn.Get() ); }
return false; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadDefineBoneList( CDmeDefineBoneList *pDmeDefineBoneList ) { if ( !pDmeDefineBoneList ) return;
CDmeDefineBone *pDmeDefineBone = NULL; s_importbone_t *pImportBone = NULL;
const int nDefineBoneCount = pDmeDefineBoneList->m_DefineBones.Count(); for ( int ni = 0; ni < nDefineBoneCount; ++ni ) { pDmeDefineBone = pDmeDefineBoneList->m_DefineBones[ ni ]; if ( !pDmeDefineBone ) continue;
if ( g_numimportbones >= ARRAYSIZE( g_importbone ) ) { MdlError( "Too Many Define Bones while processing: \"%s\", Max: %d\n", pDmeDefineBone->GetName(), ARRAYSIZE( g_importbone ) ); break; }
pImportBone = &g_importbone[ g_numimportbones++ ];
Q_strncpy( pImportBone->name, pDmeDefineBone->GetName(), MAXSTUDIONAME ); Q_strncpy( pImportBone->parent, pDmeDefineBone->m_Parent.Get(), MAXSTUDIONAME );
AngleMatrix( pDmeDefineBone->m_Rotation.Get(), pDmeDefineBone->m_Translation.Get(), pImportBone->rawLocal );
const Vector &rt = pDmeDefineBone->m_RealignTranslation.Get(); const QAngle &rr = pDmeDefineBone->m_RealignRotation.Get(); if ( !rt.IsZero() || !IsZero( rr ) ) { pImportBone->bPreAligned = true; AngleMatrix( pDmeDefineBone->m_RealignRotation.Get(), pDmeDefineBone->m_RealignTranslation.Get(), pImportBone->srcRealign ); } else { pImportBone->bPreAligned = false; SetIdentityMatrix( pImportBone->srcRealign ); }
int nContentsDescription = CONTENTS_SOLID; if ( GetContentsDescription( nContentsDescription, pDmeDefineBone ) ) { ContentsName_t *pContentsName = NULL;
for ( int j = 0; j < s_JointContents.Count(); ++j ) { if ( !Q_stricmp( s_JointContents[j].m_pJointName, pDmeDefineBone->GetName() ) ) { pContentsName = &s_JointContents[j]; break; } }
if ( !pContentsName ) { pContentsName = &s_JointContents[ s_JointContents.AddToTail() ]; Q_strncpy( pContentsName->m_pJointName, pDmeDefineBone->GetName(), ARRAYSIZE( pContentsName->m_pJointName ) ); }
pContentsName->m_nContents = nContentsDescription; } } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadKeyValues( const char *pszKeyValues ) { if ( !pszKeyValues ) return;
const int nKeyValueCount = Q_strlen( pszKeyValues ); if ( nKeyValueCount <= 0 ) return;
const char *pszHeader = "mdlkeyvalue\n{\n"; const char *pszFooter = "}\n";
g_KeyValueText.AddMultipleToTail( Q_strlen( pszHeader ), pszHeader ); g_KeyValueText.AddMultipleToTail( nKeyValueCount, pszKeyValues ); g_KeyValueText.AddMultipleToTail( Q_strlen( pszFooter ), pszFooter ); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadGlobalFlags( CDmElement *pDmeRoot ) { // Get default rotation as a matrix
matrix3x4_t mDefRot; AngleMatrix( g_defaultrotation, mDefRot );
// QC $bbox
CDmeBBox *pDmeBBox = pDmeRoot->GetValueElement< CDmeBBox >( "bbox" ); if ( pDmeBBox ) { ITransformAABB( mDefRot, pDmeBBox->m_vMinBounds, pDmeBBox->m_vMaxBounds, bbox[0], bbox[1] ); }
if ( pDmeRoot->HasAttribute( "opacity" ) ) { switch ( pDmeRoot->GetValue< int >( "opacity" ) ) { case 0: // Auto, do nothing
gflags &= ~STUDIOHDR_FLAGS_FORCE_OPAQUE; gflags &= ~STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS; break; case 1: // opaque
// Opaque has precedence over Mostly Opaque (TRANSLUCENT_TWOPASS)
gflags |= STUDIOHDR_FLAGS_FORCE_OPAQUE; gflags &= ~STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS; break; case 2: // mostly opaque
// Opaque has precedence over Mostly Opaque (TRANSLUCENT_TWOPASS)
if ( ( gflags & STUDIOHDR_FLAGS_FORCE_OPAQUE) == 0) { gflags |= STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS; } break; } }
if ( pDmeRoot->HasAttribute( "illuminationPosition", AT_VECTOR3 ) ) { VectorIRotate( pDmeRoot->GetValue< Vector >( "illuminationPosition"), mDefRot, illumposition ); }
if ( pDmeRoot->GetValue< bool >( "ambientBoost", false ) ) { gflags |= STUDIOHDR_FLAGS_AMBIENT_BOOST; }
if ( pDmeRoot->GetValue< bool >( "subdivisionSurface", false ) ) { gflags |= STUDIOHDR_FLAGS_SUBDIVISION_SURFACE; }
if ( pDmeRoot->GetValue< bool >( "doNotCastShadows", false ) ) { gflags |= STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS; }
if ( pDmeRoot->GetValue< bool >( "castTextureShadows", false ) ) { gflags |= STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS; }
if ( pDmeRoot->GetValue< bool >( "noForcedFade", false ) ) { gflags |= STUDIOHDR_FLAGS_NO_FORCED_FADE; }
int nContentsDescription = CONTENTS_SOLID; if ( GetContentsDescription( nContentsDescription, pDmeRoot ) ) { s_nDefaultContents = nContentsDescription; } }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadEyeballGlobals( CDmeEyeballGlobals *pDmeEyeballGlobals ) { if ( !pDmeEyeballGlobals ) return;
g_flMaxEyeDeflection = cosf( DEG2RAD( pDmeEyeballGlobals->m_flMaxEyeDeflection.Get() ) );
matrix3x4_t mDefRot; AngleMatrix( g_defaultrotation, mDefRot ); VectorIRotate( pDmeEyeballGlobals->m_vEyePosition.Get(), mDefRot, eyeposition ); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadMaterialGroups( const CDmeMaterialGroupList *pDmeMaterialGroupList ) { if ( !pDmeMaterialGroupList ) return;
if ( g_numskinref == 0 ) g_numskinref = g_numtextures;
// Studiomdl will read in multiple $texturegroup calls but only the first one
// is ever used, so in mdlcompile, there will only ever be one
Assert( g_numtexturegroups == 0 );
CDmeMaterialGroup *pDmeMaterialGroup = NULL;
const int nMaterialGroupCount = pDmeMaterialGroupList->m_MaterialGroups.Count(); if ( nMaterialGroupCount >= ARRAYSIZE( g_texturegroup[ 0 ] ) ) { MdlError( "Too Many Material Groups, Max: %d\n", ARRAYSIZE( g_texturegroup[ 0 ] ) ); return; }
const int nTextureCount = g_numtextures;
for ( int nGroupIndex = 0; nGroupIndex < nMaterialGroupCount; ++nGroupIndex ) { pDmeMaterialGroup = pDmeMaterialGroupList->m_MaterialGroups[ nGroupIndex ]; if ( !pDmeMaterialGroup ) continue;
const int nMaterialCount = pDmeMaterialGroup->m_MaterialList.Count(); if ( nGroupIndex > 0 && nMaterialCount < nTextureCount ) { MdlWarning( "Only Setting %d of %d Textures via MaterialGroup %d, Skin %d will have bad materials\n", nMaterialCount, nTextureCount, nGroupIndex, nGroupIndex ); } for ( int nMaterialIndex = 0; nMaterialIndex < nMaterialCount; ++nMaterialIndex ) { const int nMdlMaterialIndex = UseTextureAsMaterial( LookupTexture( pDmeMaterialGroup->m_MaterialList[ nMaterialIndex ], true ) ); g_texturegroup[ g_numtexturegroups ][ nGroupIndex ][ nMaterialIndex ] = nMdlMaterialIndex; if ( nGroupIndex != 0 ) { g_texture[ nMdlMaterialIndex ].parent = g_texturegroup[ g_numtexturegroups ][ 0 ][ nMaterialIndex ]; } g_numtexturelayers[ g_numtexturegroups ] = nGroupIndex + 1; g_numtexturereps[ g_numtexturegroups ] = nMaterialIndex + 1; } }
++g_numtexturegroups; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static s_hitboxset *AllocateHitboxSet() { s_hitboxset *pHitboxSet = &g_hitboxsets[ g_hitboxsets.AddToTail() ]; memset( pHitboxSet, 0, sizeof( s_hitboxset ) ); return pHitboxSet; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static s_bbox_t *AllocateHitbox( s_hitboxset *pHitboxSet ) { if ( pHitboxSet->numhitboxes < ARRAYSIZE( pHitboxSet->hitbox ) ) return &pHitboxSet->hitbox[ pHitboxSet->numhitboxes++ ];
MdlWarning( "Too many hitboxes request for hitbox set \"%s\", max %d\n", pHitboxSet->hitboxsetname, ARRAYSIZE( pHitboxSet->hitbox ) ); return NULL; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadHitboxSetList( const CDmeHitboxSetList *pDmeHitboxSetList ) { if ( !pDmeHitboxSetList ) return;
const int nHitboxSetCount = pDmeHitboxSetList->m_HitboxSetList.Count(); for ( int nHitBoxSetIndex = 0; nHitBoxSetIndex < nHitboxSetCount; ++nHitBoxSetIndex ) { CDmeHitboxSet *pSrcHitboxSet = pDmeHitboxSetList->m_HitboxSetList[ nHitBoxSetIndex ];
// Add a new hitboxset
s_hitboxset *pHitboxSet = AllocateHitboxSet(); Q_strncpy( pHitboxSet->hitboxsetname, pSrcHitboxSet->GetName(), sizeof(pHitboxSet->hitboxsetname) );
int nHitboxCount = pSrcHitboxSet->m_HitboxList.Count(); for ( int nHitboxIndex = 0; nHitboxIndex < nHitboxCount; ++nHitboxIndex ) { CDmeHitbox *pSrcHitbox = pSrcHitboxSet->m_HitboxList[ nHitboxIndex ];
// Find bone
s_bonetable_t *pStudioBone = NULL; const int nBoneIndex = findGlobalBone( pSrcHitbox->m_sBoneName ); if ( nBoneIndex >= 0 && nBoneIndex < g_numbones ) { pStudioBone = &g_bonetable[ nBoneIndex ]; }
// Find bone
for ( int nBoneIndex = 0; nBoneIndex < g_numbones; ++nBoneIndex ) { }
s_bbox_t *pHitbox = AllocateHitbox( pHitboxSet ); Q_strncpy( pHitbox->name, pSrcHitbox->m_sBoneName, sizeof( pHitbox->name ) ); Q_strncpy( pHitbox->hitboxname, pSrcHitbox->GetName(), sizeof( pHitbox->hitboxname ) ); pHitbox->group = pSrcHitbox->m_nGroupId; pHitbox->bmin = pSrcHitbox->m_vMinBounds; pHitbox->bmax = pSrcHitbox->m_vMaxBounds;
if ( !pSrcHitbox->m_sSurfaceProperty.IsEmpty() ) { const char *pSurfaceProp = FindSurfaceProp( pHitbox->name ); if ( pSurfaceProp && Q_stricmp( pSurfaceProp, pSrcHitbox->m_sSurfaceProperty ) ) { MdlWarning( "Hitbox surface property \"%s\" for bone \"%s\" conflicts with existing \"%s\"", pSrcHitbox->m_sSurfaceProperty.Get(), pSrcHitbox->m_sBoneName.Get(), pSurfaceProp ); } else { AddSurfaceProp( pHitbox->name, pSrcHitbox->m_sSurfaceProperty ); } } } } }
//-----------------------------------------------------------------------------
// Purpose: Handle the root.boneFlexDriverList
//-----------------------------------------------------------------------------
static void LoadBoneFlexDriverList( const CDmeBoneFlexDriverList *pDmeBoneFlexDriverList ) { if ( !pDmeBoneFlexDriverList ) return;
const CDmeBoneFlexDriverList *pDmeTmp = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList ); if ( pDmeTmp != NULL ) { char szTmpBuf0[40]; UniqueIdToString( pDmeTmp->GetId(), szTmpBuf0, sizeof( szTmpBuf0 ) ); char szTmpBuf1[40]; UniqueIdToString( pDmeBoneFlexDriverList->GetId(), szTmpBuf1, sizeof( szTmpBuf1 ) );
MdlError( "DmeBoneFlexDriverList already defined (%s:%s), ignoring (%s:%s)\n", pDmeTmp->GetName(), szTmpBuf0, pDmeBoneFlexDriverList->GetName(), szTmpBuf1 );
return; }
CDmeBoneFlexDriverList *pDmeCopy = pDmeBoneFlexDriverList->Copy(); pDmeCopy->SetFileId( DMFILEID_INVALID, TD_DEEP );
g_hDmeBoneFlexDriverList = pDmeCopy->GetHandle(); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static void LoadBoneMergeList( CDmAttribute *pDmeBoneMergeListAttr ) { if ( !pDmeBoneMergeListAttr ) return;
CDmrStringArrayConst boneMergeList( pDmeBoneMergeListAttr ); if ( !boneMergeList.IsValid() ) return;
const int nBoneMergeCount = boneMergeList.Count(); for ( int nBoneMergeIndex = 0; nBoneMergeIndex < nBoneMergeCount; ++nBoneMergeIndex ) { strcpyn( g_BoneMerge[ g_BoneMerge.AddToTail() ].bonename, boneMergeList[ nBoneMergeIndex ] ); } }
//-----------------------------------------------------------------------------
// Loads LODs from the preprocessed file
//-----------------------------------------------------------------------------
static int LodDistanceCompare( const void *elem1, const void *elem2 ) { const CDmeLOD *l1 = *(const CDmeLOD **)elem1; const CDmeLOD *l2 = *(const CDmeLOD **)elem2; if ( l1->m_bIsShadowLOD != l2->m_bIsShadowLOD ) return l1->m_bIsShadowLOD ? 1 : -1; if ( l1->m_flSwitchMetric > l2->m_flSwitchMetric ) return 1; return ( l1->m_flSwitchMetric == l2->m_flSwitchMetric ) ? 0 : -1; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static bool LoadLODs( s_source_t** ppRootLODSource, CDmeLODList *pLodList, bool &bSetUpAxis, bool bStaticProp ) { // NOTE: This is a little bit of a hack; it generates new s_source_ts
// based on the LOD data, which is somewhat contrary to the existing
// architecture, but is the easiest method of achieving the goal
// of loading LODs from the mpp file without reloading the giant mpp
// file over and over again.
*ppRootLODSource = NULL; int nCount = pLodList->m_LODs.Count(); if ( nCount == 0 ) return true;
// Sort LODs by switch distance
CDmeLOD **ppLODs = (CDmeLOD**)stackalloc( nCount * sizeof(CDmeLOD*) ); for ( int i = 0; i < nCount; ++i ) { ppLODs[i] = pLodList->m_LODs[i]; } qsort( ppLODs, nCount, sizeof( CDmeLOD* ), LodDistanceCompare );
CDmeLOD *pRootLOD = pLodList->GetRootLOD();
if ( pRootLOD && bSetUpAxis ) { CDmeModel *pDmeModel = pRootLOD->GetValueElement< CDmeModel >( "model" ); if ( pDmeModel ) { const char *pUpAxis = pDmeModel->GetValueString( "upAxis" ); if ( pUpAxis ) { if ( StringHasPrefix( pUpAxis, "Y" ) ) { // rotate 90 degrees around x to move y into z
g_defaultrotation = RadianEuler( M_PI / 2.0f, 0.0f, M_PI / 2.0f ); } } bSetUpAxis = false; } }
char pSrcLODName[MAX_PATH]; Q_StripExtension( pRootLOD->GetName(), pSrcLODName, sizeof(pSrcLODName) ); for ( int i = 0; i < nCount; ++i ) { CDmeLOD *pLOD = ppLODs[i];
bool bIsShadowLOD = pLOD->m_bIsShadowLOD; if ( bIsShadowLOD ) { if ( ( gflags & STUDIOHDR_FLAGS_HASSHADOWLOD ) != 0 ) { MdlError( "Invalid LOD: \"%s\": Multiple Shadow LODs Defined\n", pLOD->GetName() ); return false; } gflags |= STUDIOHDR_FLAGS_HASSHADOWLOD; } else if ( pLOD->m_flSwitchMetric.Get() < 0.0f ) { MdlError( "Invalid LOD: \"%s\": Negative switch value\n", pLOD->GetName() ); return false; }
// Skip lower LODs if we're stripping them
bool bIsRootLOD = ( pLOD == pRootLOD ); if ( g_bStripLods && !bIsShadowLOD && ( !bIsRootLOD ) ) { if ( !g_quiet ) { Msg( "Stripped lod \"%s\" @ %.1f\n", pLOD->m_Model.GetElement() ? pLOD->m_Model->GetName() : "<none>", pLOD->m_flSwitchMetric ); } continue; }
// Now, fill her up! No model means LOD is valid, but has no geometry
const bool bHasModel = pLOD->m_Model.GetHandle() != DMELEMENT_HANDLE_INVALID; const bool bHasSkeleton = pLOD->m_Skeleton.GetHandle() != DMELEMENT_HANDLE_INVALID;
// No model & no skeleton means LOD is invalid
if ( !bHasModel && !bHasSkeleton ) { MdlError( "Invalid LOD: \"%s\": No Model or Skeleton defined\n", pLOD->GetName() ); return false; }
if ( g_ScriptLODs.Count() == MAX_NUM_LODS ) { MdlError( "Too many LODs (MAX_NUM_LODS==%d) while loading LOD: \"%s\"\n", static_cast< int >( MAX_NUM_LODS ), pLOD->GetName() ); return false; }
// Check for overflow
if ( g_numsources >= MAXSTUDIOSEQUENCES ) { MdlError( "Too many source models/animations (MAXSTUDIOSEQUENCES==%d) while loading LOD: \"%s\"\n", static_cast< int >( MAXSTUDIOSEQUENCES ), pLOD->GetName() ); return false; }
s_source_t *pLODSource = AllocateDmxSource( pLOD->m_Path.Get() );
if ( !pLODSource ) { MdlError( "Couldn't allocate new source while loading LOD: \"%s\"\n", pLOD->GetName() ); return false; }
if ( bIsRootLOD ) { *ppRootLODSource = pLODSource; pLODSource->isActiveModel = true; }
if ( bHasModel ) { BoneTransformMap_t boneMap; if ( !LoadModelAndSkeleton( pLODSource, boneMap, pLOD->m_Skeleton, pLOD->m_Model, pLOD->m_CombinationOperator, bStaticProp ) ) { MdlError( "Couldn't load skeleton and model while loading LOD: \"%s\"\n", pLOD->GetName() ); return false; }
LoadQcModelElements( pLODSource, g_pCurrentModel, pLOD->m_Model ); }
if ( bIsRootLOD ) continue;
// Create LOD information in terms of how the old system does it
// Shadow lod reserves -1 as switch value which uniquely identifies a shadow lod
int j = g_ScriptLODs.AddToTail(); LodScriptData_t& lod = g_ScriptLODs[j]; lod.switchValue = bIsShadowLOD ? -1.0f : pLOD->m_flSwitchMetric; lod.EnableFacialAnimation( bIsShadowLOD ? false : !pLOD->m_bNoFlex ); lod.StripFromModel( false );
// We only support simple model replacement here. Other processing
// is expected to have occurred in the preprocessing phase
j = lod.modelReplacements.AddToTail(); CLodScriptReplacement_t& replacement = lod.modelReplacements[j]; replacement.SetSrcName( pSrcLODName ); replacement.SetDstName( pLODSource->filename ); replacement.m_pSource = pLODSource; }
return true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
extern int Option_Blank();
//-----------------------------------------------------------------------------
// Loads Eyeballs
//-----------------------------------------------------------------------------
static void LoadEyeballs( s_model_t *ps_model_t, CDmeLODList *pDmeLODList ) { if ( !pDmeLODList ) return;
matrix3x4_t mDefRot; AngleMatrix( g_defaultrotation, mDefRot ); Vector vTmp;
const int nEyeballCount = pDmeLODList->m_EyeballList.Count(); if ( nEyeballCount <= 0 ) return;
for ( int nEyeballIndex = 0; nEyeballIndex < nEyeballCount; ++nEyeballIndex ) { CDmeEyeball *pDmeEyeball = pDmeLODList->m_EyeballList.Get( nEyeballIndex ); if ( !pDmeEyeball ) continue;
if ( ps_model_t->numeyeballs >= ARRAYSIZE( ps_model_t->eyeball ) ) { MdlWarning( "1100: Max number of eyeballs reached for model %s, ignoring eyeball %s\n", ps_model_t->name, pDmeEyeball->GetName() ); continue; }
int nFoundBoneIndex = -1; for ( int nSearchBoneIndex = 0; nSearchBoneIndex < ps_model_t->source->numbones; ++nSearchBoneIndex ) { if ( !Q_stricmp( ps_model_t->source->localBone[ nSearchBoneIndex ].name, pDmeEyeball->m_sParentBoneName.Get() ) ) { nFoundBoneIndex = nSearchBoneIndex; break; } }
if ( nFoundBoneIndex < 0 ) { MdlWarning( "1101: Couldn't find bone %s on model %s, ignoring eyeball %s\n", pDmeEyeball->m_sParentBoneName.Get(), ps_model_t->name, pDmeEyeball->GetName() ); continue; }
const char *pszMaterialName = pDmeEyeball->m_sMaterialName.Get(); const bool bRelative = ( strchr( pszMaterialName, '/' ) || strchr( pszMaterialName, '\\' ) ) ? true : false;
const int nSearchMeshMatIndex = UseTextureAsMaterial( LookupTexture( pDmeEyeball->m_sMaterialName.Get(), bRelative ) ); int nFoundMeshMatIndex = -1; for ( int i = 0; i < ps_model_t->source->nummeshes; ++i ) { const int nTmpMeshMatIndex = ps_model_t->source->meshindex[ i ]; // meshes are internally stored by material index
if ( nTmpMeshMatIndex == nSearchMeshMatIndex ) { nFoundMeshMatIndex = i; break; } }
if ( nFoundMeshMatIndex < 0 ) { MdlWarning( "1102: Couldn't find eyeball material %s on model %s, ignoring eyeball %s\n", pDmeEyeball->m_sMaterialName.Get(), ps_model_t->name, pDmeEyeball->GetName() ); continue; }
s_eyeball_t *ps_eyeball_t = &( ps_model_t->eyeball[ ps_model_t->numeyeballs++ ] ); Q_strncpy( ps_eyeball_t->name, pDmeEyeball->GetName(), sizeof( ps_eyeball_t->name ) ); ps_eyeball_t->bone = nFoundBoneIndex; ps_eyeball_t->mesh = nFoundMeshMatIndex; ps_eyeball_t->radius = pDmeEyeball->m_flRadius.Get(); ps_eyeball_t->zoffset = tan( DEG2RAD( pDmeEyeball->m_flYawAngle.Get() ) ); ps_eyeball_t->iris_scale = 1.0f / pDmeEyeball->m_flIrisScale;
// translate eyeball into bone space
VectorITransform( pDmeEyeball->m_vPosition.Get(), ps_model_t->source->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->org );
VectorIRotate( Vector( 0, 0, 1 ), mDefRot, vTmp ); VectorIRotate( vTmp, ps_model_t->source->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->up );
VectorIRotate( Vector( 1, 0, 0 ), mDefRot, vTmp ); VectorIRotate( vTmp, ps_model_t->source->boneToPose[ ps_eyeball_t->bone ], ps_eyeball_t->forward );
// Not applicable
ps_eyeball_t->upperlidflexdesc = -1; ps_eyeball_t->lowerlidflexdesc = -1; }
// Make the standard flex controllers for eyes if required
static const char *szEyesFlexControllers[] = { "eyes_updown", "eyes_rightleft" };
for ( int nNewFlexIndex = 0; nNewFlexIndex < ARRAYSIZE( szEyesFlexControllers ); ++nNewFlexIndex ) { bool bHasEyeFlexController = false;
for ( int nFlexIndex = 0; nFlexIndex < g_numflexcontrollers; ++nFlexIndex ) { if ( !Q_strcmp( szEyesFlexControllers[ nNewFlexIndex ], g_flexcontroller[ nFlexIndex ].name ) ) { bHasEyeFlexController = true; break; } }
// The flex controller range for eyes_updown & eyes_rightleft is default [-45, 45] because it's clamped by eyesMaxDeflection
// and changing it based on max deflection would cause animatin changes since flex controller values are normalized [0, 1]
// [-45, 45 ] gives a maxium range that's useful
if ( !bHasEyeFlexController ) { if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL ) { MdlWarning( "1103: Couldn't make eyes flexcontroller %s, too many flex controllers defined\n", szEyesFlexControllers[ nNewFlexIndex ] ); continue; }
Q_strncpy( g_flexcontroller[g_numflexcontrollers].name, szEyesFlexControllers[ nNewFlexIndex ], sizeof( g_flexcontroller[ g_numflexcontrollers ].name ) ); Q_strncpy( g_flexcontroller[g_numflexcontrollers].type, "eyes", sizeof( g_flexcontroller[ g_numflexcontrollers ].name ) ); g_flexcontroller[g_numflexcontrollers].min = -45.0f; g_flexcontroller[g_numflexcontrollers].max = 45.0f; ++g_numflexcontrollers; } } }
//-----------------------------------------------------------------------------
// Loads Body Group Lists from the preprocessed file
//-----------------------------------------------------------------------------
static bool LoadBodyGroupList( s_source_t **ppMainSource, CDmeBodyGroupList *pBodyGroupList, CDmeEyeballGlobals *pDmeEyeballGlobals, bool bStaticProp, bool &bSetUpAxis ) { *ppMainSource = NULL;
CDmaElementArray< CDmeBodyGroup > &dmeBodyGroups = pBodyGroupList->m_BodyGroups; const int nDmeBodyGroupCount = dmeBodyGroups.Count(); if ( nDmeBodyGroupCount == 0 ) return true;
// Eyeball globals needs to be loaded after g_defaultrotation is set, which is after the first LOD is loaded
bool bLoadEyeballGlobals = true;
// Compute the 'main' body part
const CDmeLODList *pMainBodyPart = pBodyGroupList->GetMainBodyPart(); for ( int i = 0; i < nDmeBodyGroupCount; ++i ) { const CDmeBodyGroup *pDmeBodyGroup = dmeBodyGroups[i];
s_bodypart_t *pBodyPart = &g_bodypart[ g_numbodyparts ]; pBodyPart->nummodels = 0; if ( g_numbodyparts == 0 ) { pBodyPart->base = 1; } else { pBodyPart->base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels; } Q_strncpy( pBodyPart->name, pDmeBodyGroup->GetName(), sizeof( pBodyPart->name ) ); ++g_numbodyparts;
// Load all body parts for this body group
const int nDmeBodyPartCount = pDmeBodyGroup->m_BodyParts.Count(); for ( int j = 0; j < nDmeBodyPartCount; ++j ) { s_model_t *pModel = (s_model_t *)calloc( 1, sizeof( s_model_t ) ); const int nModel = g_nummodels++; g_model[nModel] = pModel; pBodyPart->pmodel[ pBodyPart->nummodels++ ] = pModel; pModel->scale = 1.0f;
CDmeBodyPart *pDmeBodyPart = pDmeBodyGroup->m_BodyParts[j]; CDmeLODList *pDmeLODList = CastElement< CDmeLODList >( pDmeBodyPart );
if ( pDmeBodyPart->LODCount() == 0 || !pDmeLODList ) { pModel->source = AllocateDmxSource( "blank" ); Q_strncpy( pModel->name, "blank", sizeof(pModel->name) ); } else { Q_strncpy( pModel->name, pDmeLODList->GetName(), sizeof(pModel->name) );
g_pCurrentModel = pModel;
if ( !LoadLODs( &pModel->source, pDmeLODList, bSetUpAxis, bStaticProp ) ) { MdlError( "Bad LOD On BodyGroup \"%s\".bodyPartList[%d]\n", pDmeBodyGroup->GetName(), j ); return false; }
g_pCurrentModel = NULL;
// This needs to be done after g_defaultRotation is set, which should be set after the first LOD is loaded
if ( bLoadEyeballGlobals ) { bLoadEyeballGlobals = false; LoadEyeballGlobals( pDmeEyeballGlobals ); }
LoadEyeballs( pModel, pDmeLODList );
if ( pModel->source ) { Q_strncpy( pModel->filename, pModel->source->filename, sizeof(pModel->filename) ); }
PostProcessSource( pModel->source, nModel );
if ( pMainBodyPart == pDmeLODList ) { *ppMainSource = pModel->source; } } } }
return true; }
//-----------------------------------------------------------------------------
// Loads the collision model from the preprocessed file
//-----------------------------------------------------------------------------
bool LoadCollisionModel( CDmeCollisionModel *pCollisionInfo, bool bStaticProp ) { if ( !pCollisionInfo ) return false;
// Check for overflow - TODO: Move to AllocateSource()
if ( g_numsources >= MAXSTUDIOSEQUENCES ) { MdlError( "Load_Source - overflowed g_numsources loading LODs." ); return false; }
s_source_t *pCollisionSource = AllocateDmxSource( pCollisionInfo->GetName() ); if ( !pCollisionSource ) return false;
int nummaterials = g_nummaterials; int numtextures = g_numtextures;
// BoneRemap[bone index in file] == bone index in studiomdl
CDmeDag *pSkeleton = pCollisionInfo->GetValueElement< CDmeDag >( "skeleton" ); if ( !pSkeleton ) { MdlError( "%s(%s): No \"skeleton\" defined\n", pCollisionInfo->GetTypeString(), pCollisionInfo->GetName() ); return false; }
CDmeModel *pModel = pCollisionInfo->GetValueElement< CDmeModel >( "model" ); if ( !pModel ) { MdlError( "%s(%s): No \"model\" defined\n", pCollisionInfo->GetTypeString(), pCollisionInfo->GetName() ); return false; }
BoneTransformMap_t boneMap; if ( !LoadModelAndSkeleton( pCollisionSource, boneMap, pSkeleton, pModel, NULL, bStaticProp ) ) { MdlError( "%s(%s): Couldn't Load Skeleton: %s(%s) & Model: %s(%s)\n", pCollisionInfo->GetTypeString(), pCollisionInfo->GetName(), pSkeleton->GetTypeString(), pSkeleton->GetName(), pModel->GetTypeString(), pModel->GetName() ); return false; }
// auto-remove any new materials/textures
if ( nummaterials && numtextures && ( numtextures != g_numtextures || nummaterials != g_nummaterials ) ) { g_numtextures = numtextures; g_nummaterials = nummaterials; pCollisionSource->texmap[0] = 0; }
if ( DoCollisionModel( pCollisionSource, pCollisionInfo, bStaticProp ) == 0 ) return false;
const char *pSurfaceProperty = pCollisionInfo->GetValueString( "surfaceProperty" ); if ( pSurfaceProperty && pSurfaceProperty[0] ) { SetDefaultSurfaceProp( pSurfaceProperty ); }
return true; }
#endif // MDLCOMPILE
//-----------------------------------------------------------------------------
// Sets up the DMX if it was a static prop
//-----------------------------------------------------------------------------
static void SetupStaticProp( s_source_t *pSource ) { #ifdef MDLCOMPILE
ProcessStaticProp(); s_sequence_t *pSeq = ProcessCmdSequence( "BindPose" ); s_animation_t *pAnim = ProcessImpliedAnimation( pSeq, pSource->filename ); pSeq->panim[0][0] = pAnim; ProcessSequence( pSeq, 1, &pAnim, false ); #endif
}
//-----------------------------------------------------------------------------
// Main entry point for loading DMX files
//-----------------------------------------------------------------------------
int Load_DMX( s_source_t *pSource ) { DmFileId_t fileId;
// use the full search tree, including mod hierarchy to find the file
char pFullPath[MAX_PATH]; if ( !GetGlobalFilePath( pSource->filename, pFullPath, sizeof(pFullPath) ) ) return 0;
// When reading, keep the CRLF; this will make ReadFile read it in binary format
// and also append a couple 0s to the end of the buffer.
CDmElement *pRoot; if ( g_pDataModel->RestoreFromFile( pFullPath, NULL, NULL, &pRoot ) == DMFILEID_INVALID ) return 0;
if ( !g_quiet ) { Msg( "DMX Model %s\n", pFullPath ); }
// Load model info
LoadModelInfo( pRoot, pFullPath );
// Load constraints
LoadConstraints( pRoot );
// Extract out the skeleton
// BoneRemap[bone index in file] == bone index in studiomdl
CDmeDag *pSkeleton = pRoot->GetValueElement< CDmeDag >( "skeleton" ); CDmeModel *pModel = pRoot->GetValueElement< CDmeModel >( "model" ); CDmeCombinationOperator *pCombinationOperator = pRoot->GetValueElement< CDmeCombinationOperator >( "combinationOperator" ); BoneTransformMap_t boneMap; if ( !LoadModelAndSkeleton( pSource, boneMap, pSkeleton, pModel, pCombinationOperator, false ) ) goto dmxError;
LoadQcModelElements( pSource, g_pCurrentModel, pModel );
CDmeAnimationList *pAnimationList = pRoot->GetValueElement< CDmeAnimationList >( "animationList" ); if ( pAnimationList ) { LoadAnimations( pSource, pAnimationList, g_currentscale, boneMap ); }
fileId = pRoot->GetFileId(); g_pDataModel->RemoveFileId( fileId ); return 1;
dmxError: fileId = pRoot->GetFileId(); g_pDataModel->RemoveFileId( fileId ); return 0; }
//-----------------------------------------------------------------------------
// Main entry point for loading FBX files
//-----------------------------------------------------------------------------
int Load_FBX( s_source_t *pSource ) { // use the full search tree, including mod hierarchy to find the file
char pFullPath[ MAX_PATH ]; if ( !GetGlobalFilePath( pSource->filename, pFullPath, sizeof( pFullPath ) ) ) return 0; CDmFbxSerializer dmFbxSerializer; dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_Y; dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_ODD; CDmElement *pRoot = dmFbxSerializer.ReadFBX( pFullPath ); if ( !pRoot ) return 0;
if ( !g_quiet ) { Msg( "FBX Model %s\n", pFullPath ); } // Load model info
LoadModelInfo( pRoot, pFullPath );
// Load constraints
LoadConstraints( pRoot );
// Extract out the skeleton
// BoneRemap[bone index in file] == bone index in studiomdl
CDmeDag *pSkeleton = pRoot->GetValueElement< CDmeDag >( "skeleton" ); CDmeModel *pModel = pRoot->GetValueElement< CDmeModel >( "model" ); CDmeCombinationOperator *pCombinationOperator = pRoot->GetValueElement< CDmeCombinationOperator >( "combinationOperator" ); BoneTransformMap_t boneMap; int nReturn = 0; if ( LoadModelAndSkeleton( pSource, boneMap, pSkeleton, pModel, pCombinationOperator, false ) ) { LoadQcModelElements( pSource, g_pCurrentModel, pModel );
CDmeAnimationList *pAnimationList = pRoot->GetValueElement< CDmeAnimationList >( "animationList" ); if ( pAnimationList ) { LoadAnimations( pSource, pAnimationList, g_currentscale, boneMap ); }
if ( CommandLine()->FindParm( "-debugfbx2dmx" ) ) g_pDataModel->SaveToFile( CUtlString( pFullPath ).StripExtension() + ".fbx2dmx.dmx", NULL, "keyvalues2", "model", pRoot ); nReturn = 1; // loaded ok
} g_pDataModel->UnloadFile/*RemoveFileId?*/( pRoot->GetFileId() );
return nReturn; }
//-----------------------------------------------------------------------------
// Declare it so we can call it, defined in studiomdl.cpp
//-----------------------------------------------------------------------------
extern void ProcessModelName( const char *pMdlName );
//-----------------------------------------------------------------------------
// Main entry point for loading preprocessed files
//-----------------------------------------------------------------------------
bool LoadPreprocessedFile( const char *pFileName, float flScale ) { #ifndef MDLCOMPILE
return false; #else
DmFileId_t fileId;
// use the full search tree, including mod hierarchy to find the file
char pFullPath[MAX_PATH]; if ( !GetGlobalFilePath( pFileName, pFullPath, sizeof(pFullPath) ) ) { MdlError( "Invalid MPP Filename: %s\n", pFileName ); return false; }
// When reading, keep the CRLF; this will make ReadFile read it in binary format
// and also append a couple 0s to the end of the buffer.
CDmElement *pRoot; if ( g_pDataModel->RestoreFromFile( pFullPath, NULL, NULL, &pRoot ) == DMFILEID_INVALID ) { MdlError( "0001: Couldn't Load MPP File: %s\n", pFullPath ); return false; }
if ( !g_quiet ) { Msg( "Loaded Preprocessed File %s\n", pFullPath ); }
const char *pMdlPath = pRoot->GetValueString( "mdlPath" ); if ( pMdlPath && *pMdlPath ) { // This is a hack... really the model path should be able to be anywhere relative to the game
// directory but currently "models/" is prepended everywhere... would like to get rid of that
// pattern but would also like to limit changes required for mdlcompile
if ( !Q_strnicmp( pMdlPath, "models", 6 ) && pMdlPath[6] == '/' || pMdlPath[7] == '\\' ) { ProcessModelName( pMdlPath + 7 ); } else { ProcessModelName( pMdlPath ); } }
// Get whether we're doing skinned LODs from the pre-process file
g_bSkinnedLODs = pRoot->GetValue< bool >( "skinnedLODs", false );
// Load model info
LoadModelInfo( pRoot, pFullPath );
// Find out if it's marked as a static prop
const bool bStaticProp = pRoot->GetValue< bool >( "staticProp" );
bool bSetUpAxis = true; s_source_t *pMainSource = NULL;
CDmeBodyGroupList *pBodyGroupList = pRoot->GetValueElement< CDmeBodyGroupList >( "bodyGroupList" ); if ( pBodyGroupList ) { // Loads all body groups
if ( !LoadBodyGroupList( &pMainSource, pBodyGroupList, pRoot->GetValueElement< CDmeEyeballGlobals >( "eyeballGlobals" ), bStaticProp, bSetUpAxis ) ) goto dmxError;
CDmeCollisionModel *pCollisionModel = pRoot->GetValueElement< CDmeCollisionModel >( "collisionModel" ); if ( pCollisionModel && !LoadCollisionModel( pCollisionModel, bStaticProp ) ) goto dmxError;
LoadCollisionText( pRoot->GetValueString( "collisionText" ) );
// Deal with material groups. Ok to pass NULL for CDmeMaterialGroupList
LoadMaterialGroups( pRoot->GetValueElement< CDmeMaterialGroupList >( "materialGroupList" ) );
// Deal with bone merge directives. Ok to pass NULL DmAttribute for string array
LoadBoneMergeList( pBodyGroupList->GetAttribute( "boneMergeList" ) );
// Deal with bone merge directives. Ok to pass NULL DmAttribute for string array
// TODO: Bone keep list is a little funny, right now just treat the same as bone
// merge list which will ensure the bone is always present. This will also
// increase the bone priority and make the bone available to the server
LoadBoneMergeList( pBodyGroupList->GetAttribute( "boneKeepList" ) ); } else { if ( bStaticProp ) { MdlError( "0002: Static prop specified but no body groups present\n" ); goto dmxError; }
// This MPP has no body groups, so maybe it's an animation only MPP?
pMainSource = AllocateDmxSource( "anim" ); }
// Deal with static props
if ( bStaticProp && pBodyGroupList ) { // FIXME: This source should come from the skeleton;
// need to figure out if static props can deal with multiple sources
SetupStaticProp( pMainSource ); goto dmxSuccess; }
// Nothing after here is applied if this is a static prop
LoadBoneMaskList( pRoot->GetValueElement< CDmeBoneMaskList >( "boneMaskList" ) );
// Deal with pose parameters. Ok to pass NULL for CDmePoseParameterList
LoadPoseParameterList( pRoot->GetValueElement< CDmePoseParameterList >( "poseParameterList" ) );
// Deal with animBlockSize, Ok to pass NULL for CDmeAnimBlockSize
LoadAnimBlockSize( pRoot->GetValueElement< CDmeAnimBlockSize >( "animBlockSize" ) );
// Deal with sequences. Ok to pass NULL for CDmeSequenceList
const int nSequenceCount = LoadAndCreateSequences( pMainSource, pRoot->GetValueElement< CDmeSequenceList >( "sequenceList" ), bSetUpAxis ); if ( nSequenceCount == 0 && !pBodyGroupList ) { MdlError( "0003: MPP has no body groups and no animations\n" ); goto dmxError; }
// Deal with include models. Ok to pass NULL for CDmeIncludeModelList
LoadIncludeModelList( pRoot->GetValueElement< CDmeIncludeModelList >( "includeModelList" ) );
// Deal with define bones. Ok to pass NULL for CDmeDefineBoneList
LoadDefineBoneList( pRoot->GetValueElement< CDmeDefineBoneList >( "defineBoneList" ) );
// Deal with hitbox sets. Ok to pass NULL for CDmeHitboxSetList
LoadHitboxSetList( pRoot->GetValueElement< CDmeHitboxSetList >( "hitboxSetList" ) );
// Deal with boneFlexDriver
LoadBoneFlexDriverList( pRoot->GetValueElement< CDmeBoneFlexDriverList >( "boneFlexDriverList" ) );
// At this point, reorienting of skeleton defined stuff will likely be required
// Deal with KeyValues, Ok to pass NULL or empty string
LoadKeyValues( pRoot->GetValueString( "keyValues" ) );
LoadGlobalFlags( pRoot );
dmxSuccess: fileId = pRoot->GetFileId(); g_pDataModel->RemoveFileId( fileId ); return true;
dmxError: fileId = pRoot->GetFileId(); g_pDataModel->RemoveFileId( fileId ); return false; #endif // MDLCOMPILE
}
|