summaryrefslogtreecommitdiff
path: root/utils/studiomdl
diff options
context:
space:
mode:
Diffstat (limited to 'utils/studiomdl')
-rw-r--r--utils/studiomdl/UnifyLODs.cpp1631
-rw-r--r--utils/studiomdl/bmpread.cpp98
-rw-r--r--utils/studiomdl/checkuv.cpp460
-rw-r--r--utils/studiomdl/checkuv.h59
-rw-r--r--utils/studiomdl/collisionmodel.cpp2724
-rw-r--r--utils/studiomdl/collisionmodel.h28
-rw-r--r--utils/studiomdl/dmxsupport.cpp1213
-rw-r--r--utils/studiomdl/filebuffer.h131
-rw-r--r--utils/studiomdl/hardwarematrixstate.cpp232
-rw-r--r--utils/studiomdl/hardwarematrixstate.h71
-rw-r--r--utils/studiomdl/hardwarevertexcache.cpp71
-rw-r--r--utils/studiomdl/hardwarevertexcache.h32
-rw-r--r--utils/studiomdl/mrmsupport.cpp882
-rw-r--r--utils/studiomdl/objsupport.cpp432
-rw-r--r--utils/studiomdl/optimize.cpp4017
-rw-r--r--utils/studiomdl/perfstats.cpp274
-rw-r--r--utils/studiomdl/perfstats.h25
-rw-r--r--utils/studiomdl/simplify.cpp8238
-rw-r--r--utils/studiomdl/studiomdl.cpp10448
-rw-r--r--utils/studiomdl/studiomdl.h1602
-rw-r--r--utils/studiomdl/studiomdl.vpc151
-rw-r--r--utils/studiomdl/tristrip.cpp350
-rw-r--r--utils/studiomdl/v1support.cpp393
-rw-r--r--utils/studiomdl/write.cpp4586
24 files changed, 38148 insertions, 0 deletions
diff --git a/utils/studiomdl/UnifyLODs.cpp b/utils/studiomdl/UnifyLODs.cpp
new file mode 100644
index 0000000..19bf9d7
--- /dev/null
+++ b/utils/studiomdl/UnifyLODs.cpp
@@ -0,0 +1,1631 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <float.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "bone_setup.h"
+#include "tier1/strtools.h"
+#include "mathlib/vmatrix.h"
+#include "optimize.h"
+
+// debugging only - enabling turns off remapping to create all lod vertexes as unique
+// to ensure remapping logic does not introduce collapse anomalies
+//#define UNIQUE_VERTEXES_FOR_LOD
+
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations local to this file
+//-----------------------------------------------------------------------------
+class CVertexDictionary;
+struct VertexInfo_t;
+static void BuildBoneLODMapping( CUtlVector<int> &boneMap, int lodID );
+
+
+//-----------------------------------------------------------------------------
+// Globals
+//-----------------------------------------------------------------------------
+static int g_NumBonesInLOD[MAX_NUM_LODS];
+
+
+//-----------------------------------------------------------------------------
+// Makes sure all boneweights in a s_boneweight_t are valid
+//-----------------------------------------------------------------------------
+static void ValidateBoneWeight( const s_boneweight_t &boneWeight )
+{
+#ifdef _DEBUG
+ int i;
+ if( boneWeight.weight[0] == 1.0f )
+ {
+ Assert( boneWeight.numbones == 1 );
+ }
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ Assert( boneWeight.bone[i] >= 0 && boneWeight.bone[i] < g_numbones );
+ }
+
+ float weight = 0.0f;
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ weight += boneWeight.weight[i] ;
+ }
+ Assert( fabs( weight - 1.0f ) < 1e-3 );
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Swap bones
+//-----------------------------------------------------------------------------
+static inline void SwapBones( s_boneweight_t &boneWeight, int nBone1, int nBone2 )
+{
+ // swap
+ int nTmpBone = boneWeight.bone[nBone1];
+ float flTmpWeight = boneWeight.weight[nBone1];
+ boneWeight.bone[nBone1] = boneWeight.bone[nBone2];
+ boneWeight.weight[nBone1] = boneWeight.weight[nBone2];
+ boneWeight.bone[nBone2] = nTmpBone;
+ boneWeight.weight[nBone2] = flTmpWeight;
+}
+
+
+//-----------------------------------------------------------------------------
+// Sort the bone weight structure to be sorted by bone weight
+//-----------------------------------------------------------------------------
+static void SortBoneWeightByWeight( s_boneweight_t &boneWeight )
+{
+ // bubble sort the bones by weight. . .put the largest weight first.
+ for( int j = boneWeight.numbones; j > 1; j-- )
+ {
+ for( int k = 0; k < j - 1; k++ )
+ {
+ if( boneWeight.weight[k] >= boneWeight.weight[k+1] )
+ continue;
+
+ SwapBones( boneWeight, k, k+1 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Sort the bone weight structure to be sorted by bone index
+//-----------------------------------------------------------------------------
+static void SortBoneWeightByIndex( s_boneweight_t &boneWeight )
+{
+ // bubble sort the bones by index. . .put the smallest index first.
+ for ( int j = boneWeight.numbones; j > 1; j-- )
+ {
+ for( int k = 0; k < j - 1; k++ )
+ {
+ if( boneWeight.bone[k] <= boneWeight.bone[k+1] )
+ continue;
+
+ SwapBones( boneWeight, k, k+1 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// A vertex format
+//-----------------------------------------------------------------------------
+struct VertexInfo_t
+{
+ Vector m_Position;
+ Vector m_Normal;
+ Vector2D m_TexCoord;
+ Vector4D m_TangentS;
+ s_boneweight_t m_BoneWeight;
+ int m_nLodFlag;
+};
+
+
+//-----------------------------------------------------------------------------
+// Stores all vertices in the vertex dictionary
+//-----------------------------------------------------------------------------
+class CVertexDictionary
+{
+public:
+ CVertexDictionary();
+
+ // Adds a vertex to the dictionary
+ int AddVertex( const VertexInfo_t &srcVertex );
+ int AddVertexFromSource( const s_source_t *pSrc, int nVertexIndex, int nLod );
+
+ // Iteration
+ int VertexCount() const;
+ VertexInfo_t &Vertex( int i );
+ const VertexInfo_t &Vertex( int i ) const;
+
+ int RootLODVertexStart() const;
+ int RootLODVertexEnd() const;
+
+ // Gets the vertex count for the previous LOD
+ int PrevLODVertexCount() const;
+
+ // Marks the dictionary as starting defining vertices for a new LOD
+ void StartNewLOD();
+
+ void SetRootVertexRange( int start, int end );
+
+private:
+ CUtlVector<VertexInfo_t> m_Verts;
+ int m_nPrevLODCount;
+ int m_nRootLODStart;
+ int m_nRootLODEnd;
+};
+
+
+//-----------------------------------------------------------------------------
+// Copies in a particular vertex from the s_source_t
+//-----------------------------------------------------------------------------
+CVertexDictionary::CVertexDictionary()
+{
+ m_nPrevLODCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Accessor
+//-----------------------------------------------------------------------------
+inline VertexInfo_t &CVertexDictionary::Vertex( int i )
+{
+ return m_Verts[i];
+}
+
+inline const VertexInfo_t &CVertexDictionary::Vertex( int i ) const
+{
+ return m_Verts[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets the vertex count for the previous LOD
+//-----------------------------------------------------------------------------
+inline int CVertexDictionary::PrevLODVertexCount() const
+{
+ return m_nPrevLODCount;
+}
+
+
+inline int CVertexDictionary::RootLODVertexStart() const
+{
+ return m_nRootLODStart;
+}
+
+
+inline int CVertexDictionary::RootLODVertexEnd() const
+{
+ return m_nRootLODEnd;
+}
+
+
+//-----------------------------------------------------------------------------
+// Marks the dictionary as starting defining vertices for a new LOD
+//-----------------------------------------------------------------------------
+void CVertexDictionary::StartNewLOD()
+{
+ m_nPrevLODCount = VertexCount();
+}
+
+
+void CVertexDictionary::SetRootVertexRange( int start, int end )
+{
+ m_nRootLODStart = start;
+ m_nRootLODEnd = end;
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds a vertex to the dictionary
+//-----------------------------------------------------------------------------
+int CVertexDictionary::AddVertex( const VertexInfo_t &srcVertex )
+{
+ int nDstVertID = m_Verts.AddToTail( srcVertex );
+ VertexInfo_t &vertex = m_Verts[ nDstVertID ];
+ ValidateBoneWeight( vertex.m_BoneWeight );
+ SortBoneWeightByIndex( vertex.m_BoneWeight );
+ ValidateBoneWeight( vertex.m_BoneWeight );
+
+ return nDstVertID;
+}
+
+
+//-----------------------------------------------------------------------------
+// Copies in a particular vertex from the s_source_t
+//-----------------------------------------------------------------------------
+int CVertexDictionary::AddVertexFromSource( const s_source_t *pSrc, int nVertexIndex, int nLod )
+{
+ int nDstVertID = m_Verts.AddToTail( );
+ VertexInfo_t &vertex = m_Verts[ nDstVertID ];
+
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[nVertexIndex];
+ vertex.m_Position = srcVertex.position;
+ vertex.m_Normal = srcVertex.normal;
+ vertex.m_TexCoord = srcVertex.texcoord;
+ vertex.m_TangentS = srcVertex.tangentS;
+ vertex.m_BoneWeight = srcVertex.boneweight;
+ vertex.m_nLodFlag = 1 << nLod;
+
+ ValidateBoneWeight( vertex.m_BoneWeight );
+ SortBoneWeightByIndex( vertex.m_BoneWeight );
+ ValidateBoneWeight( vertex.m_BoneWeight );
+
+ return nDstVertID;
+}
+
+
+//-----------------------------------------------------------------------------
+// How many vertices in the dictionary?
+//-----------------------------------------------------------------------------
+int CVertexDictionary::VertexCount() const
+{
+ return m_Verts.Count();
+}
+
+
+s_source_t* GetModelLODSource( const char *pModelName,
+ const LodScriptData_t& scriptLOD, bool* pFound )
+{
+ // When doing LOD replacement, ignore all path + extension information
+ char* pTempBuf = (char*)_alloca( Q_strlen(pModelName) + 1 );
+
+ // Strip off extensions for the source...
+ strcpy( pTempBuf, pModelName );
+ char* pDot = strrchr( pTempBuf, '.' );
+ if (pDot)
+ {
+ *pDot = 0;
+ }
+
+ for( int i = 0; i < scriptLOD.modelReplacements.Count(); i++ )
+ {
+ // FIXME: Should we strip off path information?
+// char* pSlash = strrchr( pTempBuf1, '\\' );
+// char* pSlash2 = strrchr( pTempBuf1, '/' );
+// if (pSlash2 > pSlash)
+// pSlash = pSlash2;
+// if (!pSlash)
+// pSlash = pTempBuf1;
+
+ if( !Q_stricmp( pTempBuf, scriptLOD.modelReplacements[i].GetSrcName() ) )
+ {
+ *pFound = true;
+ return scriptLOD.modelReplacements[i].m_pSource;
+ }
+ }
+
+ *pFound = false;
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Tolerances for all fields of the vertex
+//-----------------------------------------------------------------------------
+#define POSITION_EPSILON 0.01f // Was 0.05f
+#define TEXCOORD_EPSILON 0.01f
+#define NORMAL_EPSILON 10.0f // in degrees
+#define TANGENT_EPSILON 10.0f // in degrees
+#define BONEWEIGHT_EPSILON 0.05f
+
+#define UNMATCHED_BONE_WEIGHT 1.0f
+
+//-----------------------------------------------------------------------------
+// Computes error between two positions; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool ComparePositionFuzzy( const Vector &p1, const Vector &p2, float &flError )
+{
+ Vector vecDelta;
+ VectorSubtract( p1, p2, vecDelta );
+ flError = DotProduct( vecDelta, vecDelta );
+ return ( flError <= (POSITION_EPSILON * POSITION_EPSILON) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes error between two normals; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareNormalFuzzy( const Vector &n1, const Vector &n2, float &flError )
+{
+ static float flEpsilon = cos( DEG2RAD( NORMAL_EPSILON ) );
+
+ Vector v1, v2;
+ v1 = n1;
+ v2 = n2;
+ VectorNormalize( v1 );
+ VectorNormalize( v2 );
+ float flDot = DotProduct( v1, v2 );
+ flError = 1.0F - flDot;
+ return ( flDot >= flEpsilon );
+}
+
+//-----------------------------------------------------------------------------
+// Computes error between two tangentS vectors; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareTangentSFuzzy( const Vector4D &n1, const Vector4D &n2, float &flError )
+{
+ static float flEpsilon = cos( DEG2RAD( TANGENT_EPSILON ) );
+
+ Vector4D v1, v2;
+ v1 = n1;
+ v2 = n2;
+
+ if (v1.w != v2.w)
+ {
+ // must match as -1 or 1
+ flError = 2;
+ return false;
+ }
+
+ VectorNormalize( v1.AsVector3D() );
+ VectorNormalize( v2.AsVector3D() );
+ float flDot = DotProduct( v1.AsVector3D(), v2.AsVector3D() );
+
+ // error ranges from [0..2]
+ flError = 1.0F - flDot;
+
+ return ( flDot >= flEpsilon );
+}
+
+//-----------------------------------------------------------------------------
+// Computes error between two texcoords; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareTexCoordsFuzzy( const Vector2D &t1, const Vector2D &t2, float &flError )
+{
+ Vector2D vecError;
+ vecError[0] = fabs( t2[0] - t1[0] );
+ vecError[1] = fabs( t2[1] - t1[1] );
+ flError = vecError.LengthSqr();
+ return ( flError <= (TEXCOORD_EPSILON * TEXCOORD_EPSILON) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the error between two bone weights, returns false if they are too far
+//-----------------------------------------------------------------------------
+bool CompareBoneWeightsFuzzy( const s_boneweight_t &b1, const s_boneweight_t &b2, float &flError )
+{
+ // This is a list of which bones that exist in b1 also exist in b2.
+ // Use the index to figure out where in the array for b2 that the corresponding bone in b1 is.
+ int nMatchingBones = 0;
+ int pBoneIndexMap1[MAX_NUM_BONES_PER_VERT];
+ int pBoneIndexMap2[MAX_NUM_BONES_PER_VERT];
+
+ int i;
+ for ( i = 0; i < b2.numbones; ++i )
+ {
+ pBoneIndexMap2[i] = -1;
+ }
+
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ pBoneIndexMap1[i] = -1;
+ for ( int j = 0; j < b2.numbones; ++j )
+ {
+ if ( b2.bone[j] == b1.bone[i] )
+ {
+ pBoneIndexMap1[i] = j;
+ pBoneIndexMap2[j] = i;
+ ++nMatchingBones;
+ break;
+ }
+ }
+ }
+
+ // If no bones match, we're done
+ if ( !nMatchingBones )
+ {
+ flError = FLT_MAX;
+ return false;
+ }
+
+ // At least one bone matches, so we're going to consider this vertex as a potential match
+ // This loop will take care of figuring out the error for all bones that exist in
+ // b1 alone, and all bones that exist in b1 and b2
+ flError = 0;
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ // If we didn't find a match for this bone, compute a more expensive weight
+ if ( pBoneIndexMap1[i] == -1 )
+ {
+ flError += b1.weight[i] * b1.weight[i] * UNMATCHED_BONE_WEIGHT;
+ continue;
+ }
+
+ float flDeltaWeight = fabs( b1.weight[i] - b2.weight[ pBoneIndexMap1[i] ] );
+ flError += flDeltaWeight * flDeltaWeight;
+ }
+
+ // This loop will take care of figuring out the error for all bones that exist in b2 alone
+ for ( i = 0; i < b2.numbones; ++i )
+ {
+ // If we didn't find a match for this bone, compute a more expensive weight
+ if ( pBoneIndexMap2[i] == -1 )
+ {
+ flError += b2.weight[i] * b2.weight[i] * UNMATCHED_BONE_WEIGHT;
+ }
+ }
+
+ // This renormalizes the error. The error will become greater with the total
+ // number of bones in the two vertices.
+ flError /= sqrt( (float) (b1.numbones + b2.numbones));
+ return ( flError <= BONEWEIGHT_EPSILON );
+}
+
+
+//-----------------------------------------------------------------------------
+// Searches for a material in the texture list
+//-----------------------------------------------------------------------------
+int FindMaterialByName( const char *pMaterialName )
+{
+ int i;
+ int allocLen = strlen( pMaterialName ) + 1;
+ char *pBaseName = ( char * )_alloca( allocLen );
+ Q_FileBase( ( char * )pMaterialName, pBaseName, allocLen );
+
+ for( i = 0; i < g_numtextures; i++ )
+ {
+ if( stricmp( pBaseName, g_texture[i].name ) == 0 )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static s_mesh_t *FindMeshByMaterial( s_source_t *pSrc, int nMaterialID )
+{
+ for ( int m = 0; m < pSrc->nummeshes; m++ )
+ {
+ if ( pSrc->meshindex[m] == nMaterialID )
+ return &pSrc->mesh[ pSrc->meshindex[m] ];
+ }
+
+ // this mesh/material doesn't exist at this lod.
+ return NULL;
+}
+
+
+static s_mesh_t *FindOrCullMesh( int nLodID, s_source_t *pSrc, int nMaterialID )
+{
+ char baseMeshName[MAX_PATH];
+ char baseRemovalName[MAX_PATH];
+
+ // possibly marked for removal via $removemesh
+ // determine mesh name
+ int nTextureID = MaterialToTexture( nMaterialID );
+ if (nTextureID == -1)
+ {
+ MdlError( "Unknown Texture for Material %d\n", nMaterialID );
+ }
+
+ Q_FileBase(g_texture[nTextureID].name, baseMeshName, sizeof(baseMeshName)-1);
+ for ( int i = 0; i < g_ScriptLODs[nLodID].meshRemovals.Count(); i++ )
+ {
+ const char *pMeshRemovalName = g_ScriptLODs[nLodID].meshRemovals[i].GetSrcName();
+ Q_FileBase( pMeshRemovalName, baseRemovalName, sizeof(baseRemovalName)-1);
+
+ if (!stricmp( baseRemovalName, baseMeshName ))
+ {
+ // mesh has been marked for removal
+ return NULL;
+ }
+ }
+
+ s_mesh_t *pMesh = FindMeshByMaterial( pSrc, nMaterialID );
+ return pMesh;
+}
+
+
+static void CopyVerts( int nLodID, const s_source_t *pSrc, const s_mesh_t *pSrcMesh, CVertexDictionary &vertexDict, s_mesh_t *pDstMesh, int *pMeshVertIndexMap )
+{
+ // populate the dictionary with the verts
+ for( int srcVertID = 0; srcVertID < pSrcMesh->numvertices; srcVertID++ )
+ {
+ int nVertexIndex = pSrcMesh->vertexoffset + srcVertID;
+ pMeshVertIndexMap[ nVertexIndex ] = vertexDict.AddVertexFromSource( pSrc, nVertexIndex, nLodID ) - pDstMesh->vertexoffset;
+ }
+
+ pDstMesh->numvertices = pSrcMesh->numvertices;
+}
+
+static void CopyFaces( const s_source_t *pSrc, const s_mesh_t *pSrcMesh, CUtlVector<s_face_t> &faces, s_mesh_t *pDstMesh )
+{
+ int srcFaceID;
+ for( srcFaceID = 0; srcFaceID < pSrcMesh->numfaces; srcFaceID++ )
+ {
+ int srcID = srcFaceID + pSrcMesh->faceoffset;
+ s_face_t *pSrcFace = &pSrc->face[srcID];
+ s_face_t *pDstFace = &faces[faces.AddToTail()];
+ pDstFace->a = pSrcFace->a;
+ pDstFace->b = pSrcFace->b;
+ pDstFace->c = pSrcFace->c;
+ pDstMesh->numfaces++;
+ }
+}
+
+#define IGNORE_POSITION 0x01
+#define IGNORE_TEXCOORD 0x02
+#define IGNORE_BONEWEIGHT 0x04
+#define IGNORE_NORMAL 0x08
+#define IGNORE_TANGENTS 0x10
+
+//-----------------------------------------------------------------------------
+// return -1 if there is no match. The index returned is used to index into vertexDict.
+//-----------------------------------------------------------------------------
+static int FindVertexWithinVertexDictionary( const VertexInfo_t &find,
+ const CVertexDictionary &vertexDict, int nStartVert, int nEndVert, int fIgnore )
+{
+ int nBestIndex = -1;
+ float flPositionError = 0.0f;
+ float flNormalError = 0.0f;
+ float flTangentSError = 0.0f;
+ float flTexcoordError = 0.0f;
+ float flBoneWeightError = 0.0f;
+ float flMinPositionError = FLT_MAX;
+ float flMinNormalError = FLT_MAX;
+ float flMinTangentSError = FLT_MAX;
+ float flMinTexcoordError = FLT_MAX;
+ float flMinBoneWeightError = FLT_MAX;
+ bool bFound;
+
+ if (fIgnore & IGNORE_POSITION)
+ {
+ flMinPositionError = 0;
+ flPositionError = 0;
+ }
+
+ if (fIgnore & IGNORE_TEXCOORD)
+ {
+ flMinTexcoordError = 0;
+ flTexcoordError = 0;
+ }
+
+ if (fIgnore & IGNORE_BONEWEIGHT)
+ {
+ flMinBoneWeightError = 0;
+ flBoneWeightError = 0;
+ }
+
+ if (fIgnore & IGNORE_NORMAL)
+ {
+ flMinNormalError = 0;
+ flNormalError = 0;
+ }
+
+ if (fIgnore & IGNORE_TANGENTS)
+ {
+ flMinTangentSError = 0;
+ flTangentSError = 0;
+ }
+
+ for ( int nVertexIndex = nStartVert; nVertexIndex < nEndVert; ++nVertexIndex )
+ {
+ // see if the position is reasonable
+ if ( !(fIgnore & IGNORE_POSITION) && !ComparePositionFuzzy( find.m_Position, vertexDict.Vertex(nVertexIndex).m_Position, flPositionError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_TEXCOORD) && !CompareTexCoordsFuzzy( find.m_TexCoord, vertexDict.Vertex(nVertexIndex).m_TexCoord, flTexcoordError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_BONEWEIGHT) && !CompareBoneWeightsFuzzy( find.m_BoneWeight, vertexDict.Vertex(nVertexIndex).m_BoneWeight, flBoneWeightError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_NORMAL) && !CompareNormalFuzzy( find.m_Normal, vertexDict.Vertex(nVertexIndex).m_Normal, flNormalError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_TANGENTS) && !CompareTangentSFuzzy( find.m_TangentS, vertexDict.Vertex(nVertexIndex).m_TangentS, flTangentSError ) )
+ continue;
+
+ // the vert with minimum error is the best or exact candidate
+ bFound = false;
+ if (flMinPositionError > flPositionError)
+ {
+ bFound = true;
+ }
+ else if (flMinPositionError == flPositionError)
+ {
+ if (flMinTexcoordError > flTexcoordError)
+ {
+ bFound = true;
+ }
+ else if (flMinTexcoordError == flTexcoordError)
+ {
+ if (flMinBoneWeightError > flBoneWeightError)
+ {
+ bFound = true;
+ }
+ else if (flMinBoneWeightError == flBoneWeightError)
+ {
+ if (flMinNormalError > flNormalError)
+ {
+ bFound = true;
+ }
+ else if (flMinNormalError == flNormalError)
+ {
+ if (flMinTangentSError >= flTangentSError)
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!bFound)
+ continue;
+
+ flMinPositionError = flPositionError;
+ flMinTexcoordError = flTexcoordError;
+ flMinBoneWeightError = flBoneWeightError;
+ flMinNormalError = flNormalError;
+ flMinTangentSError = flTangentSError;
+ nBestIndex = nVertexIndex;
+ }
+
+ return nBestIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Use position, normal, and texcoord checks across the entire model to find a boneweight
+//-----------------------------------------------------------------------------
+static void FindBoneWeightWithinModel( const VertexInfo_t &searchVertex, const s_source_t *pSrc, s_boneweight_t &boneWeight, int fIgnore )
+{
+ int nBestIndex = -1;
+ float flPositionError = 0.0f;
+ float flNormalError = 0.0f;
+ float flTangentSError = 0.0f;
+ float flTexcoordError = 0.0f;
+ float flMinPositionError = FLT_MAX;
+ float flMinNormalError = FLT_MAX;
+ float flMinTangentSError = FLT_MAX;
+ float flMinTexcoordError = FLT_MAX;
+ bool bFound;
+
+ if (fIgnore & IGNORE_NORMAL)
+ {
+ flMinNormalError = 0;
+ flNormalError = 0;
+ }
+
+ if (fIgnore & IGNORE_TEXCOORD)
+ {
+ flMinTexcoordError = 0;
+ flTexcoordError = 0;
+ }
+
+ if (fIgnore & IGNORE_TANGENTS)
+ {
+ flMinTangentSError = 0;
+ flTangentSError = 0;
+ }
+
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for ( int i = 0; i < nVertexCount; i++ )
+ {
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[i];
+
+ // Compute error metrics
+ ComparePositionFuzzy( searchVertex.m_Position, srcVertex.position, flPositionError );
+
+ if (!(fIgnore & IGNORE_NORMAL))
+ {
+ CompareNormalFuzzy( searchVertex.m_Normal, srcVertex.normal, flNormalError );
+ }
+
+ if (!(fIgnore & IGNORE_TEXCOORD))
+ {
+ CompareTexCoordsFuzzy( searchVertex.m_TexCoord, srcVertex.texcoord, flTexcoordError );
+ }
+
+ if (!(fIgnore & IGNORE_TANGENTS))
+ {
+ CompareTangentSFuzzy( searchVertex.m_TangentS, srcVertex.tangentS, flTangentSError );
+ }
+
+ // the vert with minimum error is the best or exact candidate
+ bFound = false;
+ if (flMinPositionError > flPositionError)
+ {
+ bFound = true;
+ }
+ else if (flMinPositionError == flPositionError)
+ {
+ if (flMinTexcoordError > flTexcoordError)
+ {
+ bFound = true;
+ }
+ else if (flMinTexcoordError == flTexcoordError)
+ {
+ if (flMinNormalError > flNormalError)
+ {
+ bFound = true;
+ }
+ else if (flMinNormalError == flNormalError)
+ {
+ if (flMinTangentSError >= flTangentSError)
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ flMinPositionError = flPositionError;
+ flMinTexcoordError = flTexcoordError;
+ flMinNormalError = flNormalError;
+ flMinTangentSError = flTangentSError;
+ nBestIndex = i;
+ }
+ }
+
+ if ( nBestIndex == -1 )
+ {
+ MdlError( "Encountered a mesh with no vertices!\n" );
+ }
+
+ memcpy( &boneWeight, &pSrc->m_GlobalVertices[ nBestIndex ].boneweight, sizeof(s_boneweight_t) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Modify the bone weights in all of the vertices....
+//-----------------------------------------------------------------------------
+static void RemapBoneWeights( const CUtlVector<int> &boneMap, s_boneweight_t &boneWeight )
+{
+ for( int i = 0; i < boneWeight.numbones; i++ )
+ {
+ Assert( boneWeight.bone[i] >= 0 && boneWeight.bone[i] < boneMap.Count() );
+ boneWeight.bone[i] = boneMap[ boneWeight.bone[i] ];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// After the remapping, we may get multiple instances of the same bone
+// which we want to collapse into a single bone
+//-----------------------------------------------------------------------------
+static void CollapseBoneWeights( s_boneweight_t &boneWeight )
+{
+ // We need the bones to be sorted by bone index for the loop right below
+ SortBoneWeightByIndex( boneWeight );
+
+ for( int i = 0; i < boneWeight.numbones-1; i++ )
+ {
+ if( boneWeight.bone[i] != boneWeight.bone[i+1] )
+ continue;
+
+ // add i+1's weight to i since they have the same bone index
+ boneWeight.weight[i] += boneWeight.weight[i+1];
+
+ // remove i+1
+ for( int j = i+1; j < boneWeight.numbones-1; j++ )
+ {
+ boneWeight.bone[j] = boneWeight.bone[j+1];
+ boneWeight.weight[j] = boneWeight.weight[j+1];
+ }
+ --boneWeight.numbones;
+
+ // Gotta step back one, may have many bones collapsing into one
+ --i;
+ }
+
+ ValidateBoneWeight( boneWeight );
+}
+
+
+//-----------------------------------------------------------------------------
+// Find a matching vertex within the root lod
+//-----------------------------------------------------------------------------
+static void CalculateBoneWeightFromRootLod( const VertexInfo_t &searchVertex, CVertexDictionary &vertexDict,
+ const s_source_t *pRootLODSrc, VertexInfo_t &idealVertex )
+{
+ idealVertex = searchVertex;
+
+ // Look through the part of the vertex dictionary associated with the root LODs for a match
+ // bone weights are not defined properly in SMDs for lower LODs, so don't consider
+ // we can only accept the boneweight from the root LOD
+ int nVertexDictID = FindVertexWithinVertexDictionary( searchVertex, vertexDict,
+ vertexDict.RootLODVertexStart(), vertexDict.RootLODVertexEnd(), IGNORE_BONEWEIGHT|IGNORE_TANGENTS );
+ if ( nVertexDictID != -1 )
+ {
+ Assert( nVertexDictID >= vertexDict.RootLODVertexStart() && nVertexDictID < vertexDict.RootLODVertexEnd() );
+ Assert( nVertexDictID >= 0 && nVertexDictID < vertexDict.VertexCount() );
+
+ // found vertex in dictionary
+#ifdef UNIQUE_VERTEXES_FOR_LOD
+ // keep entry vertex and fill in the missing bone weight attribute
+ idealVertex.m_BoneWeight = vertexDict.Vertex( nVertexDictID ).m_BoneWeight;
+#else
+ // discard entry vertex in favor of best match
+ // this ensures all the attributes, including bone weight are correct for that vertex
+ // the worst case is that the vertex is not an *exact* match for entry attributes just a "close" match
+ idealVertex = vertexDict.Vertex( nVertexDictID );
+#endif
+ return;
+ }
+
+ // In this case, we didn't find anything within the tolerance, so we need to
+ // do a *positional check only* to give us a bone weight to assign to this vertex.
+ FindBoneWeightWithinModel( searchVertex, pRootLODSrc, idealVertex.m_BoneWeight, IGNORE_BONEWEIGHT|IGNORE_TANGENTS );
+}
+
+//-----------------------------------------------------------------------------
+// Find a matching vertex
+//-----------------------------------------------------------------------------
+static void CalculateIdealVert( const VertexInfo_t &searchVertex, CVertexDictionary &vertexDict,
+ const s_mesh_t *pVertexDictMesh, const s_source_t *pRootLODSrc, VertexInfo_t &idealVertex )
+{
+#ifndef UNIQUE_VERTEXES_FOR_LOD
+ // Only look through the part of the vertex dictionary associated with all *higher* LODs for a match
+ int nVertexDictID = FindVertexWithinVertexDictionary( searchVertex, vertexDict,
+ pVertexDictMesh->vertexoffset, vertexDict.PrevLODVertexCount(), 0 );
+ if ( nVertexDictID != -1 )
+ {
+ Assert( nVertexDictID >= pVertexDictMesh->vertexoffset && nVertexDictID < vertexDict.PrevLODVertexCount() );
+ Assert( nVertexDictID >= 0 && nVertexDictID < vertexDict.VertexCount() );
+
+ // found vertex in dictionary
+ idealVertex = vertexDict.Vertex( nVertexDictID );
+ return;
+ }
+#endif
+
+ // could not find a tolerant match
+ // the search vertex is unique
+ idealVertex = searchVertex;
+}
+
+
+static bool FuzzyFloatCompare( float f1, float f2, float epsilon )
+{
+ if( fabs( f1 - f2 ) < epsilon )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this bone weight structure sorted by bone?
+//-----------------------------------------------------------------------------
+static bool IsBoneWeightSortedByBone( const s_boneweight_t &src )
+{
+ for ( int i = 1; i < src.numbones; ++i )
+ {
+ Assert( src.bone[i] != -1 );
+ if ( src.bone[ i-1 ] > src.bone[ i ] )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Are two bone-weight structures equal?
+//-----------------------------------------------------------------------------
+static bool AreBoneWeightsEqual( const s_boneweight_t &b1, const s_boneweight_t &b2 )
+{
+ // Have to have the same number of bones
+ if ( b1.numbones != b2.numbones )
+ return false;
+
+ // This is a list of which bones that exist in b1 also exist in b2.
+ // Use the index to figure out where in the array for b2 that the corresponding bone in b1 is.
+ int nMatchingBones = 0;
+ int pBoneIndexMap[MAX_NUM_BONES_PER_VERT];
+
+ int i;
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ pBoneIndexMap[i] = -1;
+ for ( int j = 0; j < b2.numbones; ++j )
+ {
+ if ( b2.bone[j] == b1.bone[i] )
+ {
+ pBoneIndexMap[i] = j;
+ ++nMatchingBones;
+ break;
+ }
+ }
+ }
+
+ // If we aren't using the same bone indices, we're done
+ if ( nMatchingBones != b1.numbones )
+ return false;
+
+ // Check to see if the weights are the same
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ Assert( pBoneIndexMap[i] != -1 );
+ if ( b1.weight[i] != b2.weight[ pBoneIndexMap[i] ] )
+ return false;
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Finds an *exact* requested vertex in the dictionary
+//-----------------------------------------------------------------------------
+static int FindVertexInDictionaryExact( CVertexDictionary &vertexDict, int nStartVert, int nEndVert, const VertexInfo_t &vertex )
+{
+ for ( int nVertID = nStartVert; nVertID < nEndVert; ++nVertID )
+ {
+ if ( vertexDict.Vertex( nVertID ).m_Position != vertex.m_Position )
+ continue;
+
+ if ( !AreBoneWeightsEqual( vertexDict.Vertex( nVertID ).m_BoneWeight, vertex.m_BoneWeight ) )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_TexCoord != vertex.m_TexCoord )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_Normal != vertex.m_Normal )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_TangentS != vertex.m_TangentS )
+ continue;
+
+ return nVertID;
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Finds the *exact* requested vertex in the dictionary or creates it
+//-----------------------------------------------------------------------------
+static int FindOrCreateExactVertexInDictionary( CVertexDictionary &vertexDict,
+ const VertexInfo_t &vertex, s_mesh_t *pDstMesh )
+{
+ int nMeshVertID = FindVertexInDictionaryExact( vertexDict, pDstMesh->vertexoffset, pDstMesh->vertexoffset+pDstMesh->numvertices, vertex );
+ if ( nMeshVertID != -1 )
+ {
+ // flag vertex for what LoD's are using it
+ vertexDict.Vertex( nMeshVertID ).m_nLodFlag |= vertex.m_nLodFlag;
+ return nMeshVertID - pDstMesh->vertexoffset;
+ }
+
+ nMeshVertID = vertexDict.AddVertex( vertex );
+ ++pDstMesh->numvertices;
+ return nMeshVertID - pDstMesh->vertexoffset;
+}
+
+static void PrintBonesUsedInLOD( s_source_t *pSrc )
+{
+ printf( "PrintBonesUsedInLOD\n" );
+
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for( int i = 0; i <nVertexCount; i++ )
+ {
+ Vector &pos = pSrc->m_GlobalVertices[i].position;
+ Vector &norm = pSrc->m_GlobalVertices[i].normal;
+ Vector2D &texcoord = pSrc->m_GlobalVertices[i].texcoord;
+ printf( "pos: %f %f %f norm: %f %f %f texcoord: %f %f\n",
+ pos[0], pos[1], pos[2], norm[0], norm[1], norm[2], texcoord[0], texcoord[1] );
+ s_boneweight_t *pBoneWeight = &pSrc->m_GlobalVertices[i].boneweight;
+ int j;
+ for( j = 0; j < pBoneWeight->numbones; j++ )
+ {
+ int globalBoneID = pBoneWeight->bone[j];
+ const char *pBoneName = g_bonetable[globalBoneID].name;
+ printf( "vert: %d bone: %d boneid: %d weight: %f name: \"%s\"\n", i, ( int )j, ( int )pBoneWeight->bone[j],
+ ( float )pBoneWeight->weight[j], pBoneName );
+ }
+ printf( "\n" );
+ fflush( stdout );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates a particular set of bones is used by a particular LOD
+//-----------------------------------------------------------------------------
+static void MarkBonesUsedByLod( const s_boneweight_t &boneWeight, int nLodID )
+{
+ for( int j = 0; j < boneWeight.numbones; ++j )
+ {
+ int nGlobalBoneID = boneWeight.bone[j];
+ s_bonetable_t *pBone = &g_bonetable[nGlobalBoneID];
+ pBone->flags |= ( BONE_USED_BY_VERTEX_LOD0 << nLodID );
+ }
+}
+
+
+static void PrintSBoneWeight( s_boneweight_t *pBoneWeight, const s_source_t *pSrc )
+{
+ int j;
+ for( j = 0; j < pBoneWeight->numbones; j++ )
+ {
+ int globalBoneID;
+ globalBoneID = pBoneWeight->bone[j];
+ const char *pBoneName = g_bonetable[globalBoneID].name;
+ printf( "bone: %d boneid: %d weight: %f name: \"%s\"\n", ( int )j, ( int )pBoneWeight->bone[j],
+ ( float )pBoneWeight->weight[j], pBoneName );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// In the non-top LOD, look for vertices that would be appropriate from the
+// vertex dictionary, and use them if you find them, or add new vertices to the
+// vertex dictionary if not and use those new vertices.
+//-----------------------------------------------------------------------------
+static void CreateLODVertsInDictionary( int nLodID, const s_source_t *pRootLODSrc, s_source_t *pCurrentLODSrc,
+ const s_mesh_t *pCurrLODMesh, s_mesh_t *pVertexDictMesh, CVertexDictionary &vertexDict, int *pMeshVertIndexMap )
+{
+ // this function is specific to lods and not the root
+ Assert( nLodID );
+
+ int nNumCurrentVerts = vertexDict.VertexCount();
+
+ // Used to control where we look for vertices + merging rules
+ vertexDict.StartNewLOD();
+
+ CUtlVector<int> boneMap;
+ BuildBoneLODMapping( boneMap, nLodID );
+
+ for( int nSrcVertID = 0; nSrcVertID < pCurrLODMesh->numvertices; ++nSrcVertID )
+ {
+ int nSrcID = nSrcVertID + pCurrLODMesh->vertexoffset;
+
+ // candidate vertex
+ // vertices at lower LODs have bogus boneweights assigned
+ // must get the boneweight from the nearest or exact vertex at root lod
+ const s_vertexinfo_t& srcVertex = pCurrentLODSrc->m_GlobalVertices[nSrcID];
+ VertexInfo_t vertex;
+ vertex.m_Position = srcVertex.position;
+ vertex.m_Normal = srcVertex.normal;
+ vertex.m_TexCoord = srcVertex.texcoord;
+ vertex.m_TangentS = srcVertex.tangentS;
+
+#ifdef _DEBUG
+ memset( &vertex.m_BoneWeight, 0xDD, sizeof( s_boneweight_t ) );
+#endif
+ // determine the best bone weight for the desired vertex within the root lod only
+ // the root lod contains no bone remappings
+ // this ensures we get a vertex with its matched proper boneweight assignment
+ VertexInfo_t idealVertex;
+ CalculateBoneWeightFromRootLod( vertex, vertexDict, pRootLODSrc, idealVertex );
+
+ // try again to match the candidate vertex
+ // determine the ideal vertex with desired remapped boneweight
+ vertex = idealVertex;
+ CalculateIdealVert( vertex, vertexDict, pVertexDictMesh, pRootLODSrc, idealVertex);
+
+ // remap bone
+ RemapBoneWeights( boneMap, idealVertex.m_BoneWeight );
+ CollapseBoneWeights( idealVertex.m_BoneWeight );
+ SortBoneWeightByWeight( idealVertex.m_BoneWeight );
+
+ // FIXME: this is marking bones based on the slammed vertex data
+ MarkBonesUsedByLod( idealVertex.m_BoneWeight, nLodID );
+
+ // tag ideal vertex as being part of the current lod
+ idealVertex.m_nLodFlag = 1 << nLodID;
+
+ // Find the exact vertex or create it in the dictionary
+ int nMeshVertID = FindOrCreateExactVertexInDictionary( vertexDict, idealVertex, pVertexDictMesh );
+
+ // Indicate where in the higher LODs the vertex we selected resides
+ pMeshVertIndexMap[nSrcID] = nMeshVertID;
+ }
+
+ int nNewVertsCreated = vertexDict.VertexCount() - nNumCurrentVerts;
+ if (!g_quiet && nNewVertsCreated)
+ {
+ printf( "Lod %d: vertexes: %d (%d new)\n", nLodID, vertexDict.VertexCount(), nNewVertsCreated);
+ }
+}
+
+static void PrintSourceVerts( s_source_t *pSrc )
+{
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for( int i = 0; i < nVertexCount; i++ )
+ {
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[i];
+ printf( "v %d ", i );
+ printf( "pos: %f %f %f ", srcVertex.position[0], srcVertex.position[1], srcVertex.position[2] );
+ printf( "norm: %f %f %f ", srcVertex.normal[0], srcVertex.normal[1], srcVertex.normal[2] );
+ printf( "texcoord: %f %f\n", srcVertex.texcoord[0], srcVertex.texcoord[1] );
+ int j;
+ for( j = 0; j < srcVertex.boneweight.numbones; j++ )
+ {
+ printf( "\t%d: %d %f\n", j, ( int )srcVertex.boneweight.bone[j],
+ srcVertex.boneweight.weight[j] );
+ }
+ fflush( stdout );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Copy the vertex dictionary to the finalized processed data
+// Leaves the source data intact, necessary for later processes.
+// Routines can then choose which data they operate on
+//-----------------------------------------------------------------------------
+static void SetProcessedWithDictionary( s_model_t* pSrcModel, CVertexDictionary &vertexDict,
+ CUtlVector<s_face_t> &faces, CUtlVector<s_mesh_t> &meshes, int *pMeshVertIndexMaps[MAX_NUM_LODS] )
+{
+ int i;
+
+ s_loddata_t *pLodData = new s_loddata_t;
+ memset( pLodData, 0, sizeof(s_loddata_t) );
+
+ pSrcModel->m_pLodData = pLodData;
+
+ int nVertexCount = vertexDict.VertexCount();
+
+ pLodData->vertex = (s_lodvertexinfo_t *)kalloc( nVertexCount, sizeof( s_lodvertexinfo_t ) );
+ pLodData->numvertices = nVertexCount;
+ pLodData->face = (s_face_t *)kalloc( faces.Count(), sizeof( s_face_t ));
+ pLodData->numfaces = faces.Count();
+
+ for ( i = 0; i < nVertexCount; ++i )
+ {
+ const VertexInfo_t &srcVertex = vertexDict.Vertex( i );
+ s_lodvertexinfo_t &dstVertex = pLodData->vertex[i];
+
+ dstVertex.boneweight = srcVertex.m_BoneWeight;
+ Assert( dstVertex.boneweight.numbones <= 4 );
+ dstVertex.position = srcVertex.m_Position;
+ dstVertex.normal = srcVertex.m_Normal;
+ dstVertex.texcoord = srcVertex.m_TexCoord;
+ dstVertex.tangentS = srcVertex.m_TangentS;
+ dstVertex.lodFlag = srcVertex.m_nLodFlag;
+ }
+
+ memcpy( pLodData->face, faces.Base(), faces.Count() * sizeof( s_face_t ) );
+ memcpy( pLodData->mesh, meshes.Base(), meshes.Count() * sizeof( s_mesh_t ) );
+
+ for (i=0; i<MAX_NUM_LODS; i++)
+ {
+ pLodData->pMeshVertIndexMaps[i] = pMeshVertIndexMaps[i];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This fills out boneMap, which is a mapping from src bone to src bone replacement (or to itself
+// if there is no bone replacement.
+//-----------------------------------------------------------------------------
+static void BuildBoneLODMapping( CUtlVector<int> &boneMap, int lodID )
+{
+ boneMap.AddMultipleToTail( g_numbones );
+
+ Assert( lodID < g_ScriptLODs.Size() );
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+
+ // First, create a direct mapping where no bones are collapsed
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ boneMap[i] = i;
+ }
+
+ for( i = 0; i < scriptLOD.boneReplacements.Size(); i++ )
+ {
+ const char *src, *dst;
+ src = scriptLOD.boneReplacements[i].GetSrcName();
+ dst = scriptLOD.boneReplacements[i].GetDstName();
+ int j = findGlobalBone( src );
+ int k = findGlobalBone( dst );
+
+ if ( j != -1 && k != -1)
+ {
+ boneMap[j] = k;
+ }
+ else if ( j == -1)
+ {
+ // FIXME: is this really an error? It could just be replacement command for bone that doesnt' exist anymore.
+ if (g_verbose)
+ {
+ MdlWarning( "Couldn't replace unknown bone \"%s\" with \"%s\"\n", src, dst );
+ }
+ }
+ else
+ {
+ // FIXME: is this really an error? It could just be replacement command for bone that doesnt' exist anymore.
+ if (g_verbose)
+ {
+ MdlWarning( "Couldn't replace bone \"%s\" with unknown \"%s\"\n", src, dst );
+ }
+ }
+ }
+}
+
+static void MarkRootLODBones( CVertexDictionary &vertexDictionary )
+{
+ // should result in an identity mapping
+ // because their are no bone remaps at the root lod
+ CUtlVector<int> boneMap;
+ BuildBoneLODMapping( boneMap, 0 );
+
+ // iterate and mark bones
+ for (int nVertDictID=vertexDictionary.RootLODVertexStart(); nVertDictID<vertexDictionary.RootLODVertexEnd(); nVertDictID++)
+ {
+ s_boneweight_t &boneWeight = vertexDictionary.Vertex( nVertDictID ).m_BoneWeight;
+
+ RemapBoneWeights( boneMap, boneWeight );
+ CollapseBoneWeights( boneWeight );
+ SortBoneWeightByWeight( boneWeight );
+
+ MarkBonesUsedByLod( boneWeight, 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes LOD vertices for a model piece.
+//-----------------------------------------------------------------------------
+static void UnifyModelLODs( s_model_t *pSrcModel )
+{
+ if ( !Q_stricmp( pSrcModel->name, "blank" ) )
+ return;
+
+ // each lod has a unique vertex mapping table
+ int nNumLODs = pSrcModel->m_LodSources.Count();
+ int nLodID;
+ int *pMeshVertIndexMaps[MAX_NUM_LODS];
+ for ( nLodID = 0; nLodID < MAX_NUM_LODS; nLodID++ )
+ {
+ if ( nLodID < nNumLODs && pSrcModel->m_LodSources[nLodID] )
+ {
+ int nVertexCount = pSrcModel->m_LodSources[nLodID]->m_GlobalVertices.Count();
+ pMeshVertIndexMaps[nLodID] = new int[ nVertexCount ];
+#ifdef _DEBUG
+ memset( pMeshVertIndexMaps[nLodID], 0xDD, nVertexCount * sizeof(int) );
+#endif
+ }
+ else
+ {
+ pMeshVertIndexMaps[nLodID] = NULL;
+ }
+ }
+
+ // These hold the aggregate data for the model that grows as lods are processed
+ CVertexDictionary vertexDictionary;
+ CUtlVector<s_face_t> faces;
+ CUtlVector<s_mesh_t> meshes;
+
+ meshes.AddMultipleToTail( MAXSTUDIOSKINS );
+ Assert( meshes.Count() == MAXSTUDIOSKINS );
+ memset( meshes.Base(), 0, meshes.Count() * sizeof( s_mesh_t ) );
+
+ int nMeshID;
+ for( nMeshID = 0; nMeshID < pSrcModel->source->nummeshes; nMeshID++ )
+ {
+ s_mesh_t *pVertexDictMesh = &meshes[pSrcModel->source->meshindex[nMeshID]];
+
+ pVertexDictMesh->numvertices = 0;
+ pVertexDictMesh->vertexoffset = vertexDictionary.VertexCount();
+ pVertexDictMesh->numfaces = 0;
+ pVertexDictMesh->faceoffset = faces.Count();
+
+ // First build up information for LOD 0
+ if ( !pSrcModel->m_LodSources[0] )
+ continue;
+
+ s_source_t *pLOD0Source = pSrcModel->m_LodSources[0];
+
+ // lookup the material used by this mesh
+ int nMaterialID = pLOD0Source->meshindex[nMeshID];
+ s_mesh_t *pLOD0Mesh = FindMeshByMaterial( pLOD0Source, nMaterialID );
+ if ( !pLOD0Mesh )
+ continue;
+
+ // populate with all vertices from LOD 0
+ int nStart = vertexDictionary.VertexCount();
+ CopyVerts( 0, pLOD0Source, pLOD0Mesh, vertexDictionary, pVertexDictMesh, pMeshVertIndexMaps[0] );
+ vertexDictionary.SetRootVertexRange( nStart, vertexDictionary.VertexCount() );
+
+ MarkRootLODBones( vertexDictionary );
+
+ // only fix up the faces for the highest lod since the lowest ones are going
+ // to be reprocessed later.
+ CopyFaces( pLOD0Source, pLOD0Mesh, faces, pVertexDictMesh );
+
+ // Now, for each LOD, try to build meshes using the vertices in LOD 0.
+ // Ideally, vertices used in an LOD would be in LOD 0 for the benefit of shared vertices.
+ // If we don't find vertices in LOD 0, this code will add vertices into LOD 0's list
+ // of vertices for the next LOD to find
+ for ( nLodID = 1; nLodID < nNumLODs; ++nLodID )
+ {
+ s_source_t *pCurrLOD = pSrcModel->m_LodSources[nLodID];
+ if ( !pCurrLOD )
+ continue;
+
+ // Find the mesh that matches the material
+ // mesh may not be present or could be culled due to $removemesh commands
+ s_mesh_t *pCurrLODMesh = FindOrCullMesh( nLodID, pCurrLOD, nMaterialID );
+ if ( !pCurrLODMesh )
+ continue;
+
+ CreateLODVertsInDictionary( nLodID, pLOD0Source, pCurrLOD, pCurrLODMesh, pVertexDictMesh, vertexDictionary, pMeshVertIndexMaps[nLodID]);
+ }
+ }
+
+#ifdef _DEBUG
+ Msg( "Total vertex count: %d\n", vertexDictionary.VertexCount() );
+#endif
+
+ // save the data we just built into the processed data section
+ // The processed data has all of the verts that are needed for all LODs.
+ SetProcessedWithDictionary( pSrcModel, vertexDictionary, faces, meshes, pMeshVertIndexMaps );
+// PrintSourceVerts( pSrcModel->m_LodModels[0] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Force the vertex array for a model to have all of the vertices that are needed
+// for all of the LODs of the model.
+//-----------------------------------------------------------------------------
+void UnifyLODs( void )
+{
+ // todo: need to fixup the firstref/lastref stuff . . do we really need it anymore?
+ for( int modelID = 0; modelID < g_nummodelsbeforeLOD; modelID++ )
+ {
+ UnifyModelLODs( g_model[modelID] );
+ }
+}
+
+
+static void PrintSpaces( int numSpaces )
+{
+ int i;
+ for( i = 0; i < numSpaces; i++ )
+ {
+ printf( " " );
+ }
+}
+
+static void SpewBoneInfo( int globalBoneID, int depth )
+{
+ s_bonetable_t *pBone = &g_bonetable[globalBoneID];
+ if( g_bPrintBones )
+ {
+ PrintSpaces( depth * 2 );
+ printf( "%d \"%s\" ", depth, pBone->name );
+ }
+ int i;
+ for( i = 0; i < 8; i++ )
+ {
+ if( pBone->flags & ( BONE_USED_BY_VERTEX_LOD0 << i ) )
+ {
+ if( g_bPrintBones )
+ {
+ printf( "lod%d ", i );
+ }
+ g_NumBonesInLOD[i]++;
+ }
+ }
+ if( g_bPrintBones )
+ {
+ printf( "\n" );
+ }
+
+ int j;
+ for( j = 0; j < g_numbones; j++ )
+ {
+ s_bonetable_t *pBone = &g_bonetable[j];
+ if( pBone->parent == globalBoneID )
+ {
+ SpewBoneInfo( j, depth + 1 );
+ }
+ }
+}
+
+void SpewBoneUsageStats( void )
+{
+ memset( g_NumBonesInLOD, 0, sizeof( int ) * MAX_NUM_LODS );
+ if( g_numbones == 0 )
+ {
+ return;
+ }
+ SpewBoneInfo( 0, 0 );
+ if( g_bPrintBones )
+ {
+ int i;
+ for( i = 0; i < g_ScriptLODs.Count(); i++ )
+ {
+ printf( "\t%d bones used in lod %d\n", g_NumBonesInLOD[i], i );
+ }
+ }
+}
+
+void MarkParentBoneLODs( void )
+{
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ int flags = g_bonetable[i].flags;
+ flags &= BONE_USED_BY_VERTEX_MASK;
+ int globalBoneID = g_bonetable[i].parent;
+ while( globalBoneID != -1 )
+ {
+ g_bonetable[globalBoneID].flags |= flags;
+ globalBoneID = g_bonetable[globalBoneID].parent;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the sources associated with the various LODs based on the script commands
+//-----------------------------------------------------------------------------
+static void GetLODSources( CUtlVector< s_source_t * > &lods, const s_model_t *pSrcModel )
+{
+ int nNumLODs = g_ScriptLODs.Count();
+ lods.EnsureCount( nNumLODs );
+ for( int lodID = 0; lodID < nNumLODs; lodID++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+
+ bool bFound;
+ s_source_t* pSource = GetModelLODSource( pSrcModel->filename, scriptLOD, &bFound );
+ if ( !pSource && !bFound )
+ {
+ pSource = pSrcModel->source;
+ }
+
+ lods[lodID] = pSource;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates models to store converted data for the various LODs
+//-----------------------------------------------------------------------------
+void LoadLODSources( void )
+{
+ g_nummodelsbeforeLOD = g_nummodels;
+ for( int modelID = 0; modelID < g_nummodelsbeforeLOD; modelID++ )
+ {
+ if ( !Q_stricmp( g_model[modelID]->name, "blank" ) )
+ {
+ int nNumLODs = g_ScriptLODs.Count();
+ g_model[modelID]->m_LodSources.SetCount( nNumLODs );
+ for ( int i = 0; i < nNumLODs; ++i )
+ {
+ g_model[modelID]->m_LodSources[i] = NULL;
+ }
+ continue;
+ }
+
+ GetLODSources( g_model[modelID]->m_LodSources, g_model[modelID] );
+ }
+}
+
+static void ReplaceBonesRecursive( int globalBoneID, bool replaceThis,
+ CUtlVector<CLodScriptReplacement_t> &boneReplacements,
+ const char *replacementName )
+{
+ if( replaceThis )
+ {
+ CLodScriptReplacement_t &boneReplacement = boneReplacements[boneReplacements.AddToTail()];
+ boneReplacement.SetSrcName( g_bonetable[globalBoneID].name );
+ boneReplacement.SetDstName( replacementName );
+ }
+
+ // find children and recurse.
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ if( g_bonetable[i].parent == globalBoneID )
+ {
+ ReplaceBonesRecursive( i, true, boneReplacements, replacementName );
+ }
+ }
+}
+
+static void ConvertSingleBoneTreeCollapseToReplaceBones( CLodScriptReplacement_t &boneTreeCollapse,
+ CUtlVector<CLodScriptReplacement_t> &boneReplacements )
+{
+ // find the bone that we are starting with.
+ int i = findGlobalBone( boneTreeCollapse.GetSrcName() );
+ if (i != -1)
+ {
+ ReplaceBonesRecursive( i, false, boneReplacements, g_bonetable[i].name );
+ return;
+ }
+ MdlWarning( "Couldn't find bone %s for bonetreecollapse, skipping\n", boneTreeCollapse.GetSrcName() );
+}
+
+void ConvertBoneTreeCollapsesToReplaceBones( void )
+{
+ int i;
+ for( i = 0; i < g_ScriptLODs.Size(); i++ )
+ {
+ LodScriptData_t& lod = g_ScriptLODs[i];
+ int j;
+ for( j = 0; j < lod.boneTreeCollapses.Size(); j++ )
+ {
+ ConvertSingleBoneTreeCollapseToReplaceBones( lod.boneTreeCollapses[j],
+ lod.boneReplacements );
+ }
+ }
+}
+
+/*
+static void PrintReplacedBones( LodScriptData_t &lod )
+{
+ int i;
+ for( i = 0; i < lod.boneReplacements.Count(); i++ )
+ {
+ printf( "%s -> %s\n",
+ lod.boneReplacements[i].GetSrcName(),
+ lod.boneReplacements[i].GetDstName() );
+ }
+}
+*/
+
+void FixupReplacedBonesForLOD( LodScriptData_t &lod )
+{
+/*
+ printf( "before:\n" );
+ PrintReplacedBones( lod );
+*/
+ bool changed;
+ int i;
+ int j;
+ do
+ {
+ changed = false;
+ for( i = 0; i < lod.boneReplacements.Count(); i++ )
+ {
+ for( j = 0; j < lod.boneReplacements.Count(); j++ )
+ {
+ if( i == j )
+ {
+ continue;
+ }
+ if( Q_stricmp( lod.boneReplacements[i].GetSrcName(), lod.boneReplacements[j].GetDstName() ) == 0 )
+ {
+ lod.boneReplacements[j].SetDstName( lod.boneReplacements[i].GetDstName() );
+ changed = true;
+ }
+ }
+ }
+ } while( changed );
+/*
+ printf( "after:\n" );
+ PrintReplacedBones( lod );
+*/
+}
+
+void FixupReplacedBones( void )
+{
+ int i;
+ for( i = 0; i < g_ScriptLODs.Size(); i++ )
+ {
+ FixupReplacedBonesForLOD( g_ScriptLODs[i] );
+ }
+}
diff --git a/utils/studiomdl/bmpread.cpp b/utils/studiomdl/bmpread.cpp
new file mode 100644
index 0000000..24663fe
--- /dev/null
+++ b/utils/studiomdl/bmpread.cpp
@@ -0,0 +1,98 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+
+#include <windows.h>
+#include <STDIO.H>
+
+
+int
+ReadBmpFile(
+ char* szFile,
+ unsigned char** ppbPalette,
+ unsigned char** ppbBits,
+ int *pwidth,
+ int *pheight)
+ {
+ int rc = 0;
+ FILE *pfile = NULL;
+ BITMAPFILEHEADER bmfh;
+ BITMAPINFOHEADER bmih;
+ RGBQUAD rgrgbPalette[256];
+ ULONG cbPalBytes;
+ ULONG cbBmpBits;
+ BYTE* pbBmpBits;
+
+ // Bogus parameter check
+ if (!(ppbPalette != NULL && ppbBits != NULL))
+ { rc = -1000; goto GetOut; }
+
+ // File exists?
+ if ((pfile = fopen(szFile, "rb")) == NULL)
+ { rc = -1; goto GetOut; }
+
+ // Read file header
+ if (fread(&bmfh, sizeof bmfh, 1/*count*/, pfile) != 1)
+ { rc = -2; goto GetOut; }
+
+ // Bogus file header check
+ if (!(bmfh.bfReserved1 == 0 && bmfh.bfReserved2 == 0))
+ { rc = -2000; goto GetOut; }
+
+ // Read info header
+ if (fread(&bmih, sizeof bmih, 1/*count*/, pfile) != 1)
+ { rc = -3; goto GetOut; }
+
+ // Bogus info header check
+ if (!(bmih.biSize == sizeof bmih && bmih.biPlanes == 1))
+ { rc = -3000; goto GetOut; }
+
+ // Bogus bit depth? Only 8-bit supported.
+ if (bmih.biBitCount != 8)
+ { rc = -4; goto GetOut; }
+
+ // Bogus compression? Only non-compressed supported.
+ if (bmih.biCompression != BI_RGB)
+ { rc = -5; goto GetOut; }
+
+ // Figure out how many entires are actually in the table
+ if (bmih.biClrUsed == 0)
+ {
+ cbPalBytes = (1 << bmih.biBitCount) * sizeof( RGBQUAD );
+ }
+ else
+ {
+ cbPalBytes = bmih.biClrUsed * sizeof( RGBQUAD );
+ }
+
+ // Read palette (256 entries)
+ if (fread(rgrgbPalette, cbPalBytes, 1/*count*/, pfile) != 1)
+ { rc = -6; goto GetOut; }
+
+ // Read bitmap bits (remainder of file)
+ cbBmpBits = bmfh.bfSize - ftell(pfile);
+ pbBmpBits = (BYTE *)malloc(cbBmpBits);
+ if (fread(pbBmpBits, cbBmpBits, 1/*count*/, pfile) != 1)
+ { rc = -7; goto GetOut; }
+
+ // Set output parameters
+ *ppbPalette = (BYTE *)malloc(sizeof rgrgbPalette);
+ memcpy(*ppbPalette, rgrgbPalette, cbPalBytes);
+ *ppbBits = pbBmpBits;
+
+
+ *pwidth = bmih.biWidth;
+ *pheight = bmih.biHeight;
+
+ printf("w %d h %d s %d\n",bmih.biWidth, bmih.biHeight, cbBmpBits );
+
+GetOut:
+ if (pfile) fclose(pfile);
+ return rc;
+ }
+
diff --git a/utils/studiomdl/checkuv.cpp b/utils/studiomdl/checkuv.cpp
new file mode 100644
index 0000000..ff7063b
--- /dev/null
+++ b/utils/studiomdl/checkuv.cpp
@@ -0,0 +1,460 @@
+//============ Copyright (c) Valve Corporation, All rights reserved. ==========
+//
+// Function which do validation tests on UVs values at the s_source_t level
+//
+//=============================================================================
+
+
+#include "tier1/fmtstr.h"
+#include "tier1/utlmap.h"
+
+
+#include "studiomdl.h"
+#include "checkuv.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CCheckUVCmd::CCheckUVCmd()
+{
+ Clear();
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CCheckUVCmd::Clear()
+{
+ ClearCheck( CHECK_UV_ALL_FLAGS );
+ m_nOptGutterTexWidth = 512;
+ m_nOptGutterTexHeight = 512;
+ m_nOptGutterMin = 5;
+}
+
+
+//-----------------------------------------------------------------------------
+// Dumps an s_soruce_t as an OBJ file
+//-----------------------------------------------------------------------------
+static void WriteOBJ( const char *pszFilename, const s_source_t *pSource )
+{
+ FILE *pFile = fopen( pszFilename, "w" );
+
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "# s_source_t: %s\n", pSource->filename );
+ fprintf( pFile, "# Bone Count: %d\n", pSource->numbones );
+
+ for ( int i = 0; i < pSource->numbones; ++i )
+ {
+ if ( pSource->localBone[i].parent >= 0 )
+ {
+ fprintf( pFile, "# Bone %3d: %s Parent %3d: %s\n", i, pSource->localBone[i].name, pSource->localBone[i].parent, pSource->localBone[pSource->localBone[i].parent].name );
+ }
+ else
+ {
+ fprintf( pFile, "# Bone %3d: %s\n", i, pSource->localBone[i].name );
+ }
+ }
+
+ fprintf( pFile, "# Mesh Count: %d\n", pSource->nummeshes );
+ fprintf( pFile, "# Vertex Count: %d\n", pSource->numvertices );
+ fprintf( pFile, "# Face Count: %d\n", pSource->numfaces );
+
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "# positions\n" );
+ fprintf( pFile, "#\n" );
+
+ for ( int i = 0; i < pSource->numvertices; ++i )
+ {
+ const s_vertexinfo_t &v = pSource->vertex[i];
+ fprintf( pFile, "v %.4f %.4f %.4f\n", v.position.x, v.position.y, v.position.z );
+ }
+
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "# texture coordinates\n" );
+ fprintf( pFile, "#\n" );
+
+ for ( int i = 0; i < pSource->numvertices; ++i )
+ {
+ const s_vertexinfo_t &v = pSource->vertex[i];
+ fprintf( pFile, "vt %.4f %.4f\n", v.texcoord.x, v.texcoord.y );
+ }
+
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "# normals\n" );
+ fprintf( pFile, "#\n" );
+
+ for ( int i = 0; i < pSource->numvertices; ++i )
+ {
+ const s_vertexinfo_t &v = pSource->vertex[i];
+ fprintf( pFile, "vn %.4f %.4f %.4f\n", v.normal.x, v.normal.y, v.normal.z );
+ }
+
+ for ( int i = 0; i < pSource->nummeshes; ++i )
+ {
+ const s_mesh_t &m = pSource->mesh[i];
+ const s_texture_t &t = g_texture[pSource->meshindex[i]];
+
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "# mesh %d - %s\n", i, t.name );
+ fprintf( pFile, "# Face Count: %d\n", m.numfaces );
+ fprintf( pFile, "#\n" );
+ fprintf( pFile, "usemtl %s\n", t.name );
+
+ for ( int j = 0; j < m.numfaces; ++j )
+ {
+ const s_face_t &f = pSource->face[m.faceoffset + j];
+ fprintf( pFile, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
+ f.a, f.a, f.a,
+ f.b, f.b, f.b,
+ f.c, f.c, f.c );
+ }
+ }
+
+ fclose( pFile );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CCheckUVCmd::CheckUVs( const s_source_t *const *pSourceList, int nSourceCount ) const
+{
+ if ( !DoAnyCheck() || nSourceCount <= 0 )
+ return true;
+
+ bool bRet = true;
+
+ for ( int i = 0; i < nSourceCount; ++i )
+ {
+ const s_source_t *pSource = pSourceList[i];
+
+ bRet &= CheckNormalized( pSource );
+ bRet &= CheckOverlap( pSource );
+ bRet &= CheckInverse( pSource );
+ bRet &= CheckGutter( pSource );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Check that all UVs are in the [0, 1] range
+//-----------------------------------------------------------------------------
+bool CCheckUVCmd::CheckNormalized( const struct s_source_t *pSource ) const
+{
+ if ( !DoCheck( CHECK_UV_FLAG_NORMALIZED ) )
+ return true;
+
+ CUtlRBTree< int > badVertexIndices( CDefOps< int >::LessFunc );
+
+ for ( int i = 0; i < pSource->numvertices; ++i )
+ {
+ const s_vertexinfo_t &v = pSource->vertex[i];
+
+ if (
+ v.texcoord.x < 0.0f || v.texcoord.x > 1.0f ||
+ v.texcoord.y < 0.0f || v.texcoord.y > 1.0f )
+ {
+ badVertexIndices.InsertIfNotFound( i );
+ }
+ }
+
+ if ( badVertexIndices.Count() <= 0 )
+ return true;
+
+ Msg( "Error! %s\n", pSource->filename );
+ Msg( " UVs outside of [0, 1] range\n" );
+
+ for ( int i = 0; i < pSource->nummeshes; ++i )
+ {
+ const s_mesh_t &m = pSource->mesh[i];
+ const s_texture_t &t = g_texture[pSource->meshindex[i]];
+
+ CUtlRBTree< int > badMeshVertexIndices( CDefOps< int >::LessFunc );
+
+ for ( int j = 0; j < m.numfaces; ++j )
+ {
+ const s_face_t &f = pSource->face[m.faceoffset + j];
+
+ if ( badVertexIndices.HasElement( f.a ) )
+ {
+ badMeshVertexIndices.InsertIfNotFound( f.a );
+ }
+
+ if ( badVertexIndices.HasElement( f.b ) )
+ {
+ badMeshVertexIndices.InsertIfNotFound( f.b );
+ }
+
+ if ( badVertexIndices.HasElement( f.c ) )
+ {
+ badMeshVertexIndices.InsertIfNotFound( f.c );
+ }
+ }
+
+ for ( auto vIt = badMeshVertexIndices.FirstInorder(); badMeshVertexIndices.IsValidIndex( vIt ); vIt = badMeshVertexIndices.NextInorder( vIt ) )
+ {
+ PrintVertex( pSource->vertex[badMeshVertexIndices.Element( vIt )], t );
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Check that all polygons in UV do not overlap
+//-----------------------------------------------------------------------------
+bool CCheckUVCmd::CheckOverlap( const struct s_source_t *pSource ) const
+{
+ if ( !DoCheck( CHECK_UV_FLAG_OVERLAP ) )
+ return true;
+
+ bool bRet = true;
+
+ CUtlVector< CUtlVector< int > > faceOverlapMap;
+ faceOverlapMap.SetCount( pSource->numfaces );
+
+ for ( int i = 0; i < pSource->numfaces; ++i )
+ {
+ const s_face_t &fA = pSource->face[i];
+
+ const Vector2D &tAA = pSource->vertex[fA.a].texcoord;
+ const Vector2D &tAB = pSource->vertex[fA.b].texcoord;
+ const Vector2D &tAC = pSource->vertex[fA.c].texcoord;
+
+ for ( int j = i + 1; j < pSource->numfaces; ++j )
+ {
+ const s_face_t &fB = pSource->face[j];
+
+ const Vector2D tB[] = {
+ pSource->vertex[fB.a].texcoord,
+ pSource->vertex[fB.b].texcoord,
+ pSource->vertex[fB.c].texcoord };
+
+ for ( int k = 0; k < ARRAYSIZE( tB ); ++k )
+ {
+ const Vector vCheck = Barycentric( tB[k], tAA, tAB, tAC );
+
+ if ( vCheck.x > 0.0f && vCheck.y > 0.0f && vCheck.z > 0.0f )
+ {
+ if ( bRet )
+ {
+ Msg( "Error! %s\n", pSource->filename );
+ Msg( " Overlapping UV faces\n" );
+
+ bRet = false;
+ }
+
+ faceOverlapMap[i].AddToTail( j );
+
+ break;
+ }
+ }
+ }
+ }
+
+ for ( int i = 0; i < faceOverlapMap.Count(); ++i )
+ {
+ const CUtlVector< int > &overlapList = faceOverlapMap[i];
+
+ if ( overlapList.IsEmpty() )
+ continue;;
+
+ const int nFaceA = i;
+ const int nMeshA = FindMeshIndex( pSource, nFaceA );
+ PrintFace( pSource, nMeshA, nFaceA );
+ Msg( " Overlaps\n" );
+
+ for ( int j = 0; j < overlapList.Count(); ++j )
+ {
+ const int nFaceB = overlapList[j];
+ const int nMeshB = FindMeshIndex( pSource, nFaceB );
+ PrintFace( pSource, nMeshB, nFaceB, " " );
+ }
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Check that all polygons in UV have the correct winding, i.e. the cross
+// product of edge AB x BC points the right direction
+//-----------------------------------------------------------------------------
+bool CCheckUVCmd::CheckInverse( const struct s_source_t *pSource ) const
+{
+ if ( !DoCheck( CHECK_UV_FLAG_INVERSE ) )
+ return true;
+
+ bool bRetVal = true;
+
+ for ( int i = 0; i < pSource->nummeshes; ++i )
+ {
+ const s_mesh_t &m = pSource->mesh[i];
+
+ for ( int j = 0; j < m.numfaces; ++j )
+ {
+ const int nFaceIndex = m.faceoffset + j;
+
+ const s_face_t &f = pSource->face[nFaceIndex];
+
+ const Vector2D &tA = pSource->vertex[f.a].texcoord;
+ const Vector2D &tB = pSource->vertex[f.b].texcoord;
+ const Vector2D &tC = pSource->vertex[f.c].texcoord;
+
+ const Vector vA( tA.x, tA.y, 0.0f );
+ const Vector vB( tB.x, tB.y, 0.0f );
+ const Vector vC( tC.x, tC.y, 0.0f );
+
+ const Vector vAB = vB - vA;
+ const Vector vBC = vC - vB;
+
+ const Vector vUVNormal = CrossProduct( vAB, vBC );
+
+ const float flDot = DotProduct( vUVNormal, Vector( 0.0f, 0.0f, 1.0f ) );
+
+ if ( flDot < 0.0f )
+ {
+ if ( bRetVal )
+ {
+ Msg( "Error! %s\n", pSource->filename );
+ Msg( " Inverse UV faces\n" );
+
+ bRetVal = false;
+ }
+
+ PrintFace( pSource, i, nFaceIndex );
+ }
+ }
+ }
+
+ return bRetVal;
+}
+
+
+//-----------------------------------------------------------------------------
+// Check that the distance between edges in UV islands is a minimum number of pixels for a given texture size
+//-----------------------------------------------------------------------------
+bool CCheckUVCmd::CheckGutter( const struct s_source_t *pSource ) const
+{
+ if ( !DoCheck( CHECK_UV_FLAG_GUTTER ) )
+ return true;
+
+ // TODO: Implement me!
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+Vector CCheckUVCmd::Barycentric( const Vector2D &vP, const Vector2D &vA, const Vector2D &vB, const Vector2D &vC )
+{
+ const Vector2D v0 = vB - vA;
+ const Vector2D v1 = vC - vA;
+ const Vector2D v2 = vP - vA;
+
+ const float d00 = DotProduct2D( v0, v0 );
+ const float d01 = DotProduct2D( v0, v1 );
+ const float d11 = DotProduct2D( v1, v1 );
+ const float d20 = DotProduct2D( v2, v0 );
+ const float d21 = DotProduct2D( v2, v1 );
+
+ const float flDenom = d00 * d11 - d01 * d01;
+
+ const float flV = ( d11 * d20 - d01 * d21 ) / flDenom;
+ const float flW = ( d00 * d21 - d01 * d20 ) / flDenom;
+ const float flU = 1.0f - flV - flW;
+
+ return Vector( flV, flW, flU );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+Vector CCheckUVCmd::Barycentric( const Vector &vP, const Vector &vA, const Vector &vB, const Vector &vC )
+{
+ const Vector v0 = vB - vA;
+ const Vector v1 = vC - vA;
+ const Vector v2 = vP - vA;
+
+ const float d00 = DotProduct( v0, v0 );
+ const float d01 = DotProduct( v0, v1 );
+ const float d11 = DotProduct( v1, v1 );
+ const float d20 = DotProduct( v2, v0 );
+ const float d21 = DotProduct( v2, v1 );
+
+ const float flDenom = d00 * d11 - d01 * d01;
+
+ const float flV = ( d11 * d20 - d01 * d21 ) / flDenom;
+ const float flW = ( d00 * d21 - d01 * d20 ) / flDenom;
+ const float flU = 1.0f - flV - flW;
+
+ return Vector( flV, flW, flU );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CCheckUVCmd::FindMeshIndex( const struct s_source_t *pSource, int nFaceIndex )
+{
+ for ( int i = 1; i < pSource->nummeshes; ++i )
+ {
+ if ( nFaceIndex <= pSource->mesh[i].faceoffset )
+ return i;
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CCheckUVCmd::PrintVertex( const s_vertexinfo_t &v, const char *pszPrefix /* = " " */ )
+{
+ Msg( "%sP: %8.4f %8.4f %8.4f T: %8.4f %8.4f\n",
+ pszPrefix,
+ v.position.x, v.position.y, v.position.z,
+ v.texcoord.x, v.texcoord.y );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CCheckUVCmd::PrintVertex( const s_vertexinfo_t &v, const s_texture_t &t, const char *pszPrefix /* = " " */ )
+{
+ Msg( "%sP: %8.4f %8.4f %8.4f T: %8.4f %8.4f M: %s\n",
+ pszPrefix,
+ v.position.x, v.position.y, v.position.z,
+ v.texcoord.x, v.texcoord.y,
+ t.name );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CCheckUVCmd::PrintFace( const s_source_t *pSource, const int nMesh, const int nFace, const char *pszPrefix /* = " " */ )
+{
+ const s_texture_t &t = g_texture[pSource->meshindex[nMesh]];
+ const s_face_t &f = pSource->face[nFace];
+
+ Msg( "%sF: %4d %s\n", pszPrefix, nFace, t.name );
+
+ PrintVertex( pSource->vertex[f.a], pszPrefix );
+ PrintVertex( pSource->vertex[f.b], pszPrefix );
+ PrintVertex( pSource->vertex[f.c], pszPrefix );
+} \ No newline at end of file
diff --git a/utils/studiomdl/checkuv.h b/utils/studiomdl/checkuv.h
new file mode 100644
index 0000000..a5ee976
--- /dev/null
+++ b/utils/studiomdl/checkuv.h
@@ -0,0 +1,59 @@
+//============ Copyright (c) Valve Corporation, All rights reserved. ==========
+//
+//=============================================================================
+
+
+#pragma once
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+class CCheckUVCmd
+{
+public:
+ int m_nOptGutterTexWidth; // Width of texture for gutter check
+ int m_nOptGutterTexHeight; // Height of texture for gutter check
+ int m_nOptGutterMin; // Minimum number of pixels between polygon islands in UV space
+
+ enum CheckMask_t
+ {
+ CHECK_UV_FLAG_NORMALIZED = ( 1 << 0 ),
+ CHECK_UV_FLAG_OVERLAP = ( 1 << 1 ),
+ CHECK_UV_FLAG_INVERSE = ( 1 << 2 ),
+ CHECK_UV_FLAG_GUTTER = ( 1 << 3 ),
+ CHECK_UV_ALL_FLAGS = ( CHECK_UV_FLAG_NORMALIZED | CHECK_UV_FLAG_OVERLAP | CHECK_UV_FLAG_INVERSE | CHECK_UV_FLAG_GUTTER )
+ };
+
+ int m_nOptChecks;
+
+ CCheckUVCmd();
+
+ void Clear();
+
+ bool DoCheck( CheckMask_t eCheckMask ) const { return ( m_nOptChecks & eCheckMask ) == eCheckMask; }
+ bool DoAnyCheck() const { return ( m_nOptChecks & CHECK_UV_ALL_FLAGS ) != 0; }
+ void SetCheck( CheckMask_t eCheckMask ) { m_nOptChecks |= ( CHECK_UV_ALL_FLAGS & eCheckMask ); }
+ void ClearCheck( CheckMask_t eCheckMask ) { m_nOptChecks &= ( CHECK_UV_ALL_FLAGS & ~eCheckMask ); }
+
+ bool CheckUVs( const struct s_source_t *const *pSourceList, int nSourceCount ) const;
+
+ // Check that all UVs are in the [0, 1] range
+ bool CheckNormalized( const struct s_source_t *pSource ) const;
+ // Check that all polygons in UV do not overlap
+ bool CheckOverlap( const struct s_source_t *pSource ) const;
+ // Check that all polygons in UV have the correct winding, i.e. the cross
+ // product of edge AB x BC points the right direction
+ bool CheckInverse( const struct s_source_t *pSource ) const;
+ // Check that the distance between edges in UV islands is a minimum number of pixels for a given texture size
+ bool CheckGutter( const struct s_source_t *pSource ) const;
+
+ // Returns barycentric coordinates Vector( u, v, w ) for point vP with respect to triangle ( vA, vB, vC )
+ static Vector Barycentric( const Vector2D &vP, const Vector2D &vA, const Vector2D &vB, const Vector2D &vC );
+ static Vector Barycentric( const Vector &vP, const Vector &vA, const Vector &vB, const Vector &vC );
+
+ static int FindMeshIndex( const struct s_source_t *pSource, int nFaceIndex );
+ static void PrintVertex( const struct s_vertexinfo_t &v, const char *pszPrefix = " " );
+ static void PrintVertex( const struct s_vertexinfo_t &v, const struct s_texture_t &t, const char *pszPrefix = " " );
+ static void PrintFace( const s_source_t *pSource, const int nMesh, const int nFace, const char *pszPrefix = " " );
+}; \ No newline at end of file
diff --git a/utils/studiomdl/collisionmodel.cpp b/utils/studiomdl/collisionmodel.cpp
new file mode 100644
index 0000000..2a6e7e3
--- /dev/null
+++ b/utils/studiomdl/collisionmodel.cpp
@@ -0,0 +1,2724 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Builds physics collision models from studio model source
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+// NOTE: The term joint here is used to mean a bone, collision model, and a joint.
+// Each "joint" is the collision geometry at a named bone (or set of bones that have been merged)
+// and the joint (with constraints) between that set and its parent. The root "joint" has
+// no constraints.
+// I chose to refer to them as joints to avoid confusion. Yes they encompass bones and joints,
+// but they use the same names, and the data is actually linked.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+
+#include "vphysics/constraints.h"
+#include "collisionmodel.h"
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "physdll.h"
+#include "phyfile.h"
+#include "utlvector.h"
+#include "vcollide_parse.h"
+#include "tier1/strtools.h"
+#include "tier2/tier2.h"
+#include "KeyValues.h"
+
+#include "tier1/smartptr.h"
+#include "tier2/p4helpers.h"
+
+
+// these functions just wrap atoi/atof and check for NULL
+static float Safe_atof( const char *pString );
+static int Safe_atoi( const char *pString );
+
+IPhysicsCollision *physcollision = NULL;
+IPhysicsSurfaceProps *physprops = NULL;
+
+float g_WeldVertEpsilon = 0.0f;
+float g_WeldNormalEpsilon = 0.999f;
+
+//-----------------------------------------------------------------------------
+// Purpose: Contains a single convex element of a physical collision system
+//-----------------------------------------------------------------------------
+class CPhysCollisionModel
+{
+public:
+ CPhysCollisionModel( void )
+ {
+ memset( this, 0, sizeof(*this) );
+ }
+
+ const char *m_parent;
+ const char *m_name;
+
+ // physical properties stored on disk
+ float m_mass;
+ float m_volume;
+ float m_surfaceArea;
+ float m_damping;
+ float m_rotdamping;
+ float m_inertia;
+ float m_dragCoefficient;
+
+ // these tune the model building process, they don't go in the file
+ float m_massBias;
+
+ CPhysCollide *m_pCollisionData;
+ CPhysCollisionModel *m_pNext;
+};
+
+enum jointlimit_t
+{
+ JOINT_FREE = 0,
+ JOINT_FIXED = 1,
+ JOINT_LIMIT = 2,
+};
+
+// list of vertex indices that form a convex element
+struct convexlist_t
+{
+ int firstVertIndex;
+ int numVertIndex;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: element of a list of constraints for a jointed model
+//-----------------------------------------------------------------------------
+class CJointConstraint
+{
+public:
+ CJointConstraint( void )
+ {
+ m_pJointName = NULL;
+ }
+
+ CJointConstraint( const char *pName, int axis, jointlimit_t type, float min, float max, float friction )
+ : m_axis(axis), m_jointType(type), m_limitMin(min), m_limitMax(max), m_friction(friction)
+ {
+ m_pJointName = pName;
+ }
+
+ const char *m_pJointName;
+ int m_axis;
+ jointlimit_t m_jointType;
+ float m_limitMin;
+ float m_limitMax;
+ float m_friction;
+
+ CJointConstraint *m_pNext;
+};
+
+struct mergelist_t
+{
+ char *pParent;
+ char *pChild;
+};
+
+struct collisionpair_t
+{
+ int obj0;
+ int obj1;
+ const char *pName0;
+ const char *pName1;
+ collisionpair_t *pNext;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Search a source for a bone with a specified name
+// Input : *pSource -
+// *pName -
+// Output : int boneIndex, -1 if none
+//-----------------------------------------------------------------------------
+int FindLocalBoneNamed( const s_source_t *pSource, const char *pName )
+{
+ if ( pName )
+ {
+ int i;
+ for ( i = 0; i < pSource->numbones; i++ )
+ {
+ if ( !stricmp( pName, pSource->localBone[i].name ) )
+ return i;
+ }
+
+ pName = RenameBone( pName );
+
+ for ( i = 0; i < pSource->numbones; i++ )
+ {
+ if ( !stricmp( pName, pSource->localBone[i].name ) )
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+// Returns the index to pName in g_bonetable
+int FindBoneInTable( const char *pName )
+{
+ return findGlobalBone( pName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Contains a complete physical joint system with constraint relationships
+//-----------------------------------------------------------------------------
+// This class is really just a namespace for a set of globals...
+class CJointedModel
+{
+public:
+ s_source_t *m_pModel;
+ int m_collisionCount;
+ CPhysCollisionModel *m_pCollisionList;
+ collisionpair_t *m_pCollisionPairs;
+ float m_totalMass;
+ int m_bonemap[MAXSTUDIOSRCBONES];
+ CJointConstraint *m_pConstraintList;
+ int m_constraintCount;
+ int m_totalVerts;
+ int m_maxConvex;
+ char m_rootName[128];
+ bool m_allowConcave;
+ bool m_allowConcaveJoints;
+ bool m_isMassCenterForced;
+ bool m_noSelfCollisions;
+ bool m_remove2d;
+ Vector m_massCenterForced;
+
+ float m_defaultDamping;
+ float m_defaultRotdamping;
+ float m_defaultInertia;
+ float m_defaultDrag;
+ CUtlVector<char> m_textCommands;
+ CUtlVector<mergelist_t> m_mergeList;
+
+ CJointedModel( void );
+
+ void SetSource( s_source_t *pmodel );
+
+ void InitBoneMap( void );
+ void SkipBone( int boneIndex );
+ void MergeBones( int parent, int child );
+ void AddMergeCommand( char const *pParent, char const *pChild );
+ bool ShouldProcessBone( int boneIndex );
+ int BoneIndex( const char *pName );
+ int RemapBone( int boneIndex ) const;
+ void AppendCollisionModel( CPhysCollisionModel *pCollide );
+ void UnlinkCollisionModel( CPhysCollisionModel *pCollide );
+ CPhysCollisionModel *GetCollisionModel( const char *pName );
+ void AppendCollisionPair( const char *pName0, const char *pName1 );
+ void AddConstraint( const char *pJointName, int axis, jointlimit_t jointType, float limitMin, float limitMax, float friction );
+ int CollisionIndex( const char *pName );
+ void SortCollisionList( void );
+ void ForceMassCenter( const Vector &centerOfMass );
+ void AllowConcave( void ) { m_allowConcave = true; }
+ void AllowConcaveJoints() { m_allowConcaveJoints = true; }
+ void Remove2DConvex() { m_remove2d = true; }
+ void SetMaxConvex( int newMax ) { m_maxConvex = newMax; }
+ void Simplify();
+ void DefaultDamping( float damping );
+ void DefaultRotdamping( float rotdamping );
+ void DefaultInertia( float inertia );
+ void DefaultDrag( float drag );
+ void SetTotalMass( float mass );
+ void SetAutoMass( void );
+ void SetNoSelfCollisions();
+ void SetCollisionModelDefaults( CPhysCollisionModel *pModel );
+
+ void JointDamping( const char *pJointName, float damping );
+ void JointRotdamping( const char *pJointName, float rotdamping );
+ void JointInertia( const char *pJointName, float inertia );
+ void JointMassBias( const char *pJointName, float massBias );
+
+ void AddText( const char *pText )
+ {
+ int len = strlen(pText);
+ int count = m_textCommands.Size();
+ m_textCommands.AddMultipleToTail( len );
+ memcpy( m_textCommands.Base() + count, pText, len );
+ }
+ void ComputeMass( void );
+
+ float m_flFrictionTimeIn;
+ float m_flFrictionTimeOut;
+ float m_flFrictionTimeHold;
+ int m_iMinAnimatedFriction;
+ int m_iMaxAnimatedFriction;
+ bool m_bHasAnimatedFriction;
+
+ bool m_bAssumeWorldspace; // assume the model is already declared in worldspace, regardless of bone names
+};
+
+
+CJointedModel g_JointedModel;
+bool g_bJointed = false;
+
+CJointedModel::CJointedModel( void )
+{
+ m_pModel = NULL;
+
+ m_collisionCount = 0;
+ m_pCollisionList = NULL;
+ m_pCollisionPairs = NULL;
+ m_totalMass = 1.0;
+
+ memset( m_bonemap, 0, sizeof(m_bonemap) );
+ m_pConstraintList = NULL;
+ m_constraintCount = 0;
+
+ m_totalVerts = 0;
+
+ // UNDONE: Move these defaults elsewhere? They are all overrideable by the QC/script
+ m_defaultDamping = 0;
+ m_defaultRotdamping = 0;
+ m_defaultInertia = 1.0;
+ m_defaultDrag = -1;
+ m_allowConcave = false;
+ m_allowConcaveJoints = false;
+ m_remove2d = false;
+ m_maxConvex = 40;
+ m_isMassCenterForced = false;
+ m_noSelfCollisions = false;
+ m_massCenterForced.Init();
+
+ m_flFrictionTimeIn = 0.0f;
+ m_flFrictionTimeOut = 0.0f;
+ m_iMinAnimatedFriction = 1.0f;
+ m_iMaxAnimatedFriction = 1.0f;
+ m_bHasAnimatedFriction = false;
+}
+
+
+
+void CJointedModel::SetSource( s_source_t *pmodel )
+{
+ m_pModel = pmodel;
+ InitBoneMap();
+ m_totalVerts = pmodel->numvertices;
+}
+
+void CJointedModel::InitBoneMap( void )
+{
+ for ( int i = 0; i < m_pModel->numbones; i++ )
+ {
+ m_bonemap[i] = i;
+ }
+}
+
+void CJointedModel::SkipBone( int boneIndex )
+{
+ if ( boneIndex >= 0 )
+ m_bonemap[boneIndex] = -1;
+}
+
+void CJointedModel::AddMergeCommand( char const *pParent, char const *pChild )
+{
+ int i = m_mergeList.AddToTail();
+ m_mergeList[i].pParent = strdup(pParent);
+ m_mergeList[i].pChild = strdup(pChild);
+}
+
+void CJointedModel::MergeBones( int parent, int child )
+{
+ if ( parent < 0 || child < 0 )
+ return;
+
+ int map = parent;
+ int safety = 0;
+ while ( m_bonemap[map] != map )
+ {
+ map = m_bonemap[map];
+ safety++;
+ // infinite loop?
+ if ( safety > m_pModel->numbones )
+ break;
+
+ if ( map < 0 )
+ break;
+ }
+
+ m_bonemap[child] = map;
+}
+
+
+bool CJointedModel::ShouldProcessBone( int boneIndex )
+{
+ if ( boneIndex >= 0 )
+ {
+ if ( m_bonemap[boneIndex] == boneIndex )
+ return true;
+ }
+ return false;
+}
+
+int CJointedModel::BoneIndex( const char *pName )
+{
+ pName = RenameBone( pName );
+ for ( int boneIndex = 0; boneIndex < m_pModel->numbones; boneIndex++ )
+ {
+ if ( !stricmp( m_pModel->localBone[boneIndex].name, pName ) )
+ return boneIndex;
+ }
+
+ return -1;
+}
+
+int CJointedModel::RemapBone( int boneIndex ) const
+{
+ if ( boneIndex >= 0 )
+ return m_bonemap[boneIndex];
+ return boneIndex;
+}
+
+void CJointedModel::AppendCollisionModel( CPhysCollisionModel *pCollide )
+{
+ if ( m_isMassCenterForced )
+ {
+ physcollision->CollideSetMassCenter( pCollide->m_pCollisionData, m_massCenterForced );
+ }
+
+ pCollide->m_pNext = m_pCollisionList;
+ m_pCollisionList = pCollide;
+ m_collisionCount++;
+}
+
+
+void CJointedModel::UnlinkCollisionModel( CPhysCollisionModel *pCollide )
+{
+ CPhysCollisionModel **pList = &m_pCollisionList;
+
+ if ( !pCollide )
+ return;
+
+ while ( *pList )
+ {
+ CPhysCollisionModel *pNode = *pList;
+ if ( pNode == pCollide )
+ {
+ *pList = pCollide->m_pNext;
+ m_collisionCount--;
+ pCollide->m_pNext = NULL;
+ return;
+ }
+ pList = &pNode->m_pNext;
+ }
+}
+
+int CJointedModel::CollisionIndex( const char *pName )
+{
+ CPhysCollisionModel *pList = m_pCollisionList;
+ int index = 0;
+ while ( pList )
+ {
+ if ( !stricmp( pName, pList->m_name ) )
+ return index;
+
+ pList = pList->m_pNext;
+ index++;
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sort the list so that parents come before their children
+//-----------------------------------------------------------------------------
+void CJointedModel::SortCollisionList( void )
+{
+ if ( !m_collisionCount )
+ return;
+
+ CPhysCollisionModel **pArray;
+ pArray = new CPhysCollisionModel *[m_collisionCount];
+ CPhysCollisionModel *pList = m_pCollisionList;
+
+ // make an array to make sorting easier
+ int i = 0;
+
+ while ( pList )
+ {
+ pArray[i++] = pList;
+ pList = pList->m_pNext;
+ }
+
+ // really stupid bubble sort!
+ // this is really inefficient but it was easy to code and there are never
+ // more than maxConvex elements.
+ bool swapped = true;
+
+ while ( swapped )
+ {
+ swapped = false;
+ // loop over all solids and swap any parent/child pairs that are out of order
+ for ( i = 0; i < m_collisionCount; i++ )
+ {
+ CPhysCollisionModel *pPhys = pArray[i];
+ if ( !pPhys->m_parent )
+ continue;
+
+ // Don't try to move ones where the pPhys and its parent have the same name
+ // otherwise an infinite loop results
+ if ( !Q_stricmp( pPhys->m_name, pPhys->m_parent ) )
+ continue;
+
+ // find the parent
+ int j;
+ for ( j = 0; j < m_collisionCount; j++ )
+ {
+ if ( j == i )
+ continue;
+
+ if ( !stricmp( pPhys->m_parent, pArray[j]->m_name ) )
+ break;
+ }
+
+ // if the child came before the parent, then swap the parent and child positions
+ if ( j > i && j < m_collisionCount )
+ {
+ swapped = true;
+ pArray[i] = pArray[j];
+ pArray[j] = pPhys;
+ }
+ }
+ }
+
+ // link up the sorted list
+ for ( i = 0; i < m_collisionCount-1; i++ )
+ {
+ pArray[i]->m_pNext = pArray[i+1];
+ }
+ // terminate
+ pArray[i]->m_pNext = NULL;
+ // point the list to first joint
+ m_pCollisionList = pArray[0];
+
+ // delete the working array
+ delete[] pArray;
+}
+
+void CJointedModel::AppendCollisionPair( const char *pName0, const char *pName1 )
+{
+ collisionpair_t *pPair = new collisionpair_t;
+ pPair->obj0 = -1;
+ pPair->obj1 = -1;
+ int jointIndex0 = FindLocalBoneNamed( m_pModel, pName0 );
+ pPair->pName0 = (jointIndex0 >= 0) ? m_pModel->localBone[jointIndex0].name : NULL;
+ int jointIndex1 = FindLocalBoneNamed( m_pModel, pName1 );
+ pPair->pName1 = (jointIndex1 >= 0) ? m_pModel->localBone[jointIndex1].name : NULL;
+
+ pPair->pNext = m_pCollisionPairs;
+ m_pCollisionPairs = pPair;
+}
+
+void CJointedModel::ForceMassCenter( const Vector &centerOfMass )
+{
+ m_isMassCenterForced = true;
+ m_massCenterForced = centerOfMass;
+}
+
+// called before processing, after the model has been simplified.
+// Update internal state due to simplification
+void CJointedModel::Simplify()
+{
+ for ( int i = 0; i < m_pModel->numbones; i++ )
+ {
+ if ( m_pModel->boneLocalToGlobal[i] < 0 )
+ {
+ SkipBone(i);
+ }
+ }
+
+ extern int g_rootIndex;
+ const char *pAnimationRootBone = g_bonetable[g_rootIndex].name;
+
+ // merge this root bone with the root of animation
+ MergeBones( FindLocalBoneNamed( m_pModel, pAnimationRootBone ), FindLocalBoneNamed( m_pModel, m_rootName ) );
+
+}
+
+
+CPhysCollisionModel *CJointedModel::GetCollisionModel( const char *pName )
+{
+ CPhysCollisionModel *pList = m_pCollisionList;
+ while ( pList )
+ {
+ if ( !stricmp( pName, pList->m_name ) )
+ return pList;
+
+ pList = pList->m_pNext;
+ }
+
+ return NULL;
+}
+
+void CJointedModel::AddConstraint( const char *pJointName, int axis, jointlimit_t jointType, float limitMin, float limitMax, float friction )
+{
+ // In the editor/qc friction values are shown as 5X so 1.0 can be the default.
+ CJointConstraint *pConstraint = new CJointConstraint( pJointName, axis, jointType, limitMin, limitMax, friction * (1.0f/5.0f) );
+
+ // link it in
+ pConstraint->m_pNext = m_pConstraintList;
+ m_pConstraintList = pConstraint;
+ m_constraintCount++;
+}
+
+void CJointedModel::DefaultDamping( float damping )
+{
+ m_defaultDamping = damping;
+}
+
+void CJointedModel::DefaultRotdamping( float rotdamping )
+{
+ m_defaultRotdamping = rotdamping;
+}
+
+void CJointedModel::DefaultInertia( float inertia )
+{
+ m_defaultInertia = inertia;
+}
+
+void CJointedModel::SetTotalMass( float mass )
+{
+ m_totalMass = mass;
+}
+
+void CJointedModel::SetAutoMass( void )
+{
+ m_totalMass = -1;
+}
+
+void CJointedModel::SetNoSelfCollisions()
+{
+ m_noSelfCollisions = true;
+}
+
+void CJointedModel::SetCollisionModelDefaults( CPhysCollisionModel *pModel )
+{
+ pModel->m_damping = m_defaultDamping;
+ pModel->m_inertia = m_defaultInertia;
+ pModel->m_rotdamping = m_defaultRotdamping;
+ pModel->m_massBias = 1.0;
+
+ // not written unless modified
+ pModel->m_dragCoefficient = m_defaultDrag;
+}
+
+
+
+void CJointedModel::ComputeMass( void )
+{
+ // already set
+ if ( m_totalMass >= 0 )
+ return;
+
+ CPhysCollisionModel *pList = m_pCollisionList;
+ m_totalMass = 0;
+
+ while ( pList )
+ {
+ char* pSurfaceProps = GetSurfaceProp( pList->m_name );
+ int index = physprops->GetSurfaceIndex( pSurfaceProps );
+ float density, thickness;
+ physprops->GetPhysicsProperties( index, &density, &thickness, NULL, NULL );
+
+ if ( thickness > 0 )
+ {
+ m_totalMass += pList->m_surfaceArea * thickness * CUBIC_METERS_PER_CUBIC_INCH * density;
+ }
+ else
+ {
+ // density is in kg/m^3, volume is in in^3
+ m_totalMass += pList->m_volume * CUBIC_METERS_PER_CUBIC_INCH * density;
+ }
+ pList = pList->m_pNext;
+ }
+
+ if( !g_quiet )
+ {
+ printf("Computed Mass: %.2f kg\n", m_totalMass );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a collision object using the defaults in joints
+// Input : &joints - joint system to create the model in
+// *pJointName - name to give this model
+// Output : static CPhysCollisionModel
+//-----------------------------------------------------------------------------
+static CPhysCollisionModel *InitCollisionModel( CJointedModel &joints, const char *pJointName )
+{
+ CPhysCollisionModel *pModel = joints.GetCollisionModel( pJointName );
+ if ( !pModel )
+ {
+ int boneIndex = joints.BoneIndex( pJointName );
+ if ( boneIndex < 0 )
+ return NULL;
+
+ pModel = new CPhysCollisionModel;
+ // this name is the same as pJointName, but guaranteed to be non-volatile (we'd have to copy pJointName)
+ pModel->m_name = joints.m_pModel->localBone[boneIndex].name;
+ if ( joints.m_pModel->localBone[boneIndex].parent >= 0 )
+ {
+ pModel->m_parent = joints.m_pModel->localBone[joints.m_pModel->localBone[boneIndex].parent].name;
+ }
+ else
+ {
+ pModel->m_parent = NULL;
+ }
+
+ joints.SetCollisionModelDefaults( pModel );
+ joints.AppendCollisionModel( pModel );
+ }
+
+ return pModel;
+}
+
+void CJointedModel::JointDamping( const char *pJointName, float damping )
+{
+ CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName );
+ if ( pModel )
+ {
+ pModel->m_damping = damping;
+ }
+}
+
+void CJointedModel::JointRotdamping( const char *pJointName, float rotdamping )
+{
+ CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName );
+ if ( pModel )
+ {
+ pModel->m_rotdamping = rotdamping;
+ }
+}
+
+void CJointedModel::JointMassBias( const char *pJointName, float massBias )
+{
+ CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName );
+ if ( pModel )
+ {
+ pModel->m_massBias = massBias;
+ }
+}
+
+void CJointedModel::JointInertia( const char *pJointName, float inertia )
+{
+ CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName );
+ if ( pModel )
+ {
+ pModel->m_inertia = inertia;
+ }
+}
+
+
+void CJointedModel::DefaultDrag( float drag )
+{
+ m_defaultDrag = drag;
+}
+
+// ----------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Transforms the source's verts into "world" space
+// Input : *psource -
+// *worldVerts -
+//-----------------------------------------------------------------------------
+void ConvertToWorldSpace( CJointedModel &joints, s_source_t *psource, CUtlVector<Vector> &worldVerts )
+{
+ int i, n;
+
+ if (!joints.m_bAssumeWorldspace)
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix
+ CalcBoneTransforms( g_panimation[0], 0, boneToWorld );
+
+ for (i = 0; i < psource->numvertices; i++)
+ {
+ Vector tmp,tmp2;
+ worldVerts[i].Init( 0, 0, 0 );
+
+ int nBoneCount = psource->vertex[i].boneweight.numbones;
+ for (n = 0; n < nBoneCount; n++)
+ {
+ // convert to Half-Life world space
+ // convert vertex into original models' bone local space
+ int localBone = psource->vertex[i].boneweight.bone[n];
+ int globalBone = psource->boneLocalToGlobal[localBone];
+ Assert( localBone >= 0 );
+ Assert( globalBone >= 0 );
+
+ matrix3x4_t boneToPose;
+ ConcatTransforms( psource->boneToPose[localBone], g_bonetable[globalBone].srcRealign, boneToPose );
+ VectorITransform( psource->vertex[i].position, boneToPose, tmp2 );
+
+ // now transform to that bone's world-space position in this animation
+ VectorTransform(tmp2, boneToWorld[globalBone], tmp );
+ VectorMA( worldVerts[i], psource->vertex[i].boneweight.weight[n], tmp, worldVerts[i] );
+ }
+ }
+ }
+ else
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix
+ BuildRawTransforms( psource, "BindPose", 0, psource->scale, psource->adjust, psource->rotation, 0, srcBoneToWorld );
+
+ for (i = 0; i < psource->numvertices; i++)
+ {
+ Vector tmp;
+ worldVerts[i].Init( 0, 0, 0 );
+
+ int nBoneCount = psource->vertex[i].boneweight.numbones;
+ for (n = 0; n < nBoneCount; n++)
+ {
+ int localBone = psource->vertex[i].boneweight.bone[n];
+ Assert( localBone >= 0 );
+
+ // convert vertex into world space
+ VectorTransform( psource->vertex[i].position, srcBoneToWorld[localBone], tmp );
+ // just assume the model is in identity space
+ // FIXME: shouldn't this do an inverse xform of the default boneToWorld?
+
+ VectorMA( worldVerts[i], psource->vertex[i].boneweight.weight[n], tmp, worldVerts[i] );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Transforms the set of verts into the space of a particular bone
+// Input : *psource -
+// boneIndex -
+// *boneVerts -
+//-----------------------------------------------------------------------------
+void ConvertToBoneSpace( s_source_t *psource, int boneIndex, CUtlVector<Vector> &boneVerts )
+{
+ int i;
+
+ int remapIndex = psource->boneLocalToGlobal[boneIndex];
+ matrix3x4_t boneToPose;
+ if ( remapIndex < 0 )
+ {
+ MdlWarning("Error! physics for unused bone %s\n", psource->localBone[boneIndex].name );
+ MatrixCopy( psource->boneToPose[boneIndex], boneToPose );
+ }
+ else
+ {
+ ConcatTransforms( psource->boneToPose[boneIndex], g_bonetable[remapIndex].srcRealign, boneToPose );
+ }
+
+ for (i = 0; i < psource->numvertices; i++)
+ {
+ VectorITransform(psource->vertex[i].position, boneToPose, boneVerts[i] );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Test this face to see if any of its verts are assigned to a particular bone
+// Input : &joints -
+// *pmodel -
+// *face -
+// boneIndex -
+// Output : Returns true if this face has a vert assigned to boneIndex
+//-----------------------------------------------------------------------------
+bool FaceHasVertOnBone( const CJointedModel &joints, s_source_t *pSource, s_face_t *face, int boneIndex )
+{
+ if ( boneIndex < 0 )
+ return true;
+
+ int j;
+ s_boneweight_t *pweight;
+ pweight = &pSource->vertex[ face->a ].boneweight;
+ for ( j = 0; j < pweight->numbones; j++ )
+ {
+ // assigned to boneIndex?
+ if ( joints.RemapBone( pweight->bone[j] ) == boneIndex )
+ return true;
+ }
+
+ pweight = &pSource->vertex[ face->b ].boneweight;
+ for ( j = 0; j < pweight->numbones; j++ )
+ {
+ // assigned to boneIndex?
+ if ( joints.RemapBone( pweight->bone[j] ) == boneIndex )
+ return true;
+ }
+
+ pweight = &pSource->vertex[ face->c ].boneweight;
+ for ( j = 0; j < pweight->numbones; j++ )
+ {
+ // assigned to boneIndex?
+ if ( joints.RemapBone( pweight->bone[j] ) == boneIndex )
+ return true;
+ }
+
+ return false;
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fixup the pointers in this face to reference the mesh globally (source relative)
+// (faces are mesh relative, each source has several meshes)
+// Input : *pout -
+// *pmesh -
+// *pin -
+//-----------------------------------------------------------------------------
+void GlobalFace( s_face_t *pout, s_mesh_t *pmesh, s_face_t *pin )
+{
+ pout->a = pmesh->vertexoffset + pin->a;
+ pout->b = pmesh->vertexoffset + pin->b;
+ pout->c = pmesh->vertexoffset + pin->c;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy all verts assigned to this bone.
+// NOTE: Leaves gaps in the model around joints
+// Input : **verts -
+// *worldVerts -
+// &joints -
+// boneIndex -
+// Output : int vertCount
+//-----------------------------------------------------------------------------
+int CopyVertsByBone( Vector **verts, Vector *worldVerts, const CJointedModel &joints, int boneIndex )
+{
+ int vertCount = 0;
+ s_source_t *pmodel = joints.m_pModel;
+
+ // loop through each vert to find those assigned to this bone
+ for ( int i = 0; i < pmodel->numvertices; i++ )
+ {
+ s_boneweight_t *pweight = &pmodel->vertex[ i ].boneweight;
+
+ // look at each assignment for this vert
+ for ( int j = 0; j < pweight->numbones; j++ )
+ {
+ // Discover the local bone index for this bone
+ int localBone = pweight->bone[j];
+
+ // assigned to boneIndex?
+ if ( joints.RemapBone( localBone ) == boneIndex )
+ {
+ // add this vert to model
+ verts[vertCount++] = &worldVerts[i];
+ }
+ }
+ }
+
+ return vertCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy all verts that are referenced by a face which has a vert assigned
+// to this bone.
+// NOTE: convex hulls of each bone will overlap at the joints
+// Input : **verts -
+// *worldVerts -
+// &joints -
+// boneIndex -
+// Output : int
+//-----------------------------------------------------------------------------
+int CopyFaceVertsByBone( Vector **verts, Vector *worldVerts, const CJointedModel &joints, int boneIndex )
+{
+ int vertCount = 0;
+ s_source_t *pmodel = joints.m_pModel;
+
+ int *vertChecked = new int[pmodel->numvertices];
+ for ( int b = 0; b < pmodel->numvertices; b++ )
+ {
+ vertChecked[b] = 0;
+ }
+
+ for ( int i = 0; i < pmodel->nummeshes; i++ )
+ {
+ s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i];
+ for ( int j = 0; j < pmesh->numfaces; j++ )
+ {
+ s_face_t *face = pmodel->face + pmesh->faceoffset + j;
+ s_face_t globalFace;
+ GlobalFace( &globalFace, pmesh, face );
+ if ( FaceHasVertOnBone( joints, pmodel, &globalFace, boneIndex ) )
+ {
+ if ( !vertChecked[globalFace.a] )
+ {
+ // add this vert to model
+ verts[vertCount++] = &worldVerts[globalFace.a];
+ }
+ if ( !vertChecked[globalFace.b] )
+ {
+ // add this vert to model
+ verts[vertCount++] = &worldVerts[globalFace.b];
+ }
+ if ( !vertChecked[globalFace.c] )
+ {
+ // add this vert to model
+ verts[vertCount++] = &worldVerts[globalFace.c];
+ }
+ // mark these verts so you only add them once
+ vertChecked[globalFace.a] = 1;
+ vertChecked[globalFace.b] = 1;
+ vertChecked[globalFace.c] = 1;
+ }
+ }
+ }
+
+ delete[] vertChecked;
+ return vertCount;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find all verts that differ only by texture coordinates - this allows
+// us to ignore texture coordinates on collision models
+// Input : *weldTable - output table
+// *pmodel - input model
+//-----------------------------------------------------------------------------
+void BuildVertWeldTable( int *weldTable, s_source_t *pmodel )
+{
+ for ( int i = 0; i < pmodel->numvertices; i++ )
+ {
+ bool found = false;
+ for ( int j = 0; j < i; j++ )
+ {
+ float dist = (pmodel->vertex[j].position - pmodel->vertex[i].position).Length();
+ float normalDist = DotProduct( pmodel->vertex[j].normal, pmodel->vertex[i].normal );
+ if ( dist <= g_WeldVertEpsilon && normalDist > g_WeldNormalEpsilon )
+ {
+ found = true;
+ weldTable[i] = j;
+ break;
+ }
+ }
+
+ if ( !found )
+ {
+ weldTable[i] = i;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: marks all verts with a unique ID. Each set of connected verts has
+// the same ID. IDs are the index of the lowest numbered face on the
+// mesh
+// Input : *vertID - array that holds IDs
+// *pmodel - model to process
+//-----------------------------------------------------------------------------
+void MarkConnectedMeshes( int *vertID, s_source_t *pmodel, int *vertMap )
+{
+ int i;
+
+ // mark all verts as max faceid + 1
+ for ( i = 0; i < pmodel->numvertices; i++ )
+ {
+ // If these verts have been welded to a lower-index vert, mark them
+ // as already processed to avoid making additional convex objects out of them.
+ if ( vertMap[i] != i )
+ {
+ vertID[i] = -1;
+ }
+ else
+ {
+ vertID[i] = pmodel->numfaces+1;
+ }
+ }
+
+ int marked = 0;
+ int faceid = 0;
+ // iterate the face list, minimizing the vertID at each vert
+ // until we have an iteration where no vertIDs are changed
+ do
+ {
+ marked = 0;
+ faceid = 0;
+
+ for ( i = 0; i < pmodel->nummeshes; i++ )
+ {
+ s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i];
+ for ( int j = 0; j < pmesh->numfaces; j++ )
+ {
+ s_face_t *face = pmodel->face + pmesh->faceoffset + j;
+ s_face_t globalFace;
+ GlobalFace( &globalFace, pmesh, face );
+ // account for welding
+ globalFace.a = vertMap[globalFace.a];
+ globalFace.b = vertMap[globalFace.b];
+ globalFace.c = vertMap[globalFace.c];
+
+
+ // find min(faceid, vertID[a], vertID[b], vertID[c]);
+ int newid = min(faceid, vertID[globalFace.a]);
+ newid = min( newid, vertID[globalFace.b]);
+ newid = min( newid, vertID[globalFace.c]);
+
+ // mark all verts with the minimum, count the number we had to mark
+ if ( vertID[globalFace.a] != newid )
+ {
+ vertID[globalFace.a] = newid;
+ marked++;
+ }
+ if ( vertID[globalFace.b] != newid )
+ {
+ vertID[globalFace.b] = newid;
+ marked++;
+ }
+ if ( vertID[globalFace.c] != newid )
+ {
+ vertID[globalFace.c] = newid;
+ marked++;
+ }
+ faceid++;
+ }
+ }
+ } while ( marked != 0 );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds a CPhysCollisionModel in a linked list of models.
+// Input : *pHead -
+// *pName -
+// Output : CPhysCollisionModel
+//-----------------------------------------------------------------------------
+CPhysCollisionModel *FindObjectInList( CPhysCollisionModel *pHead, const char *pName )
+{
+ while ( pHead )
+ {
+ if ( !stricmp( pName, pHead->m_name ) )
+ break;
+ pHead = pHead->m_pNext;
+ }
+
+ return pHead;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Fix all bones to reference the remapped/collapsed bone structure
+// Input : *pSource -
+// *pList -
+//-----------------------------------------------------------------------------
+void FixBoneList( int *boneMap, const s_source_t *pSource, CPhysCollisionModel *pList )
+{
+ if ( !g_bJointed )
+ return;
+
+ CPhysCollisionModel *pmodel = pList;
+ while ( pmodel )
+ {
+ int nodeIndex = FindLocalBoneNamed( pSource, pmodel->m_name );
+ if ( nodeIndex < 0 )
+ {
+ MdlWarning("Physics for unknown bone %s\n", pmodel->m_name );
+ }
+ else
+ {
+ int count = 0;
+ // remove simplified bones
+ while ( pSource->boneLocalToGlobal[nodeIndex] < 0 )
+ {
+ if ( count++ > MAXSTUDIOSRCBONES )
+ break;
+
+ // simplified out, move up to the parent
+ nodeIndex = pSource->localBone[nodeIndex].parent;
+ }
+
+ if ( nodeIndex >= 0 )
+ {
+ // bone collapse may have changed parent hierarchy, and the root name.
+ // The vertices are converted to the new reference by ConvertToWorldSpace(), as well as RemapVerticesToGlobalBones()
+ pmodel->m_name = g_bonetable[ pSource->boneLocalToGlobal[nodeIndex] ].name;
+ pmodel->m_parent = NULL;
+ int parentIndex = pSource->localBone[nodeIndex].parent;
+ if ( parentIndex >= 0 && parentIndex != nodeIndex )
+ {
+ parentIndex = boneMap[parentIndex];
+ if (pSource->boneLocalToGlobal[parentIndex] < 0)
+ {
+ pmodel->m_parent = pSource->localBone[parentIndex].name;
+ }
+ else
+ {
+ pmodel->m_parent = g_bonetable[ pSource->boneLocalToGlobal[parentIndex] ].name;
+ }
+ }
+ }
+ else
+ {
+ MdlWarning("Physics for unknown bone %s\n", pmodel->m_name );
+ }
+ }
+
+ pmodel = pmodel->m_pNext;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fixup all references to parents by walking up on models whose parents
+// have no collision geometry. Bones without geometry cannot be physically
+// simulated, so they must be removed.
+// NOTE: This is broken. It won't work for tree structures with an empty parent
+// (i.e. 2 children attached to a parent bone that has no physics geometry - thus empty)
+// It will not convert that parent into a constraint between 2 children
+// Input : *pList -
+// *pSource -
+// *pParentName -
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *FixParent( CPhysCollisionModel *pList, s_source_t *pSource, const char *pParentName )
+{
+ while ( pParentName )
+ {
+ if ( FindObjectInList( pList, pParentName ) )
+ {
+ return pParentName;
+ }
+ int nodeIndex = FindLocalBoneNamed( pSource, pParentName );
+ if ( nodeIndex < 0 )
+ return NULL;
+ int parentIndex = pSource->localBone[nodeIndex].parent;
+ if ( parentIndex < 0 )
+ {
+ break;
+ }
+
+ pParentName = pSource->localBone[parentIndex].name;
+ }
+
+ return NULL;
+}
+
+
+struct boundingvolume_t
+{
+ Vector mins;
+ Vector maxs;
+};
+
+
+void CreateCollide( CPhysCollisionModel *pBase, CPhysConvex **pElements, int elementCount, const boundingvolume_t &bv )
+{
+ int i;
+
+ if ( !pBase )
+ return;
+
+ // NOTE: Must do this before building collide
+ pBase->m_volume = 0;
+ pBase->m_surfaceArea = 0;
+ for ( i = 0; i < elementCount; i++ )
+ {
+ pBase->m_volume += physcollision->ConvexVolume( pElements[i] );
+ pBase->m_surfaceArea += physcollision->ConvexSurfaceArea( pElements[i] );
+ }
+
+ convertconvexparams_t params;
+ params.Defaults();
+ params.buildOuterConvexHull = true;
+ params.buildDragAxisAreas = true;
+ Vector size = bv.maxs - bv.mins;
+
+ int largest = 0;
+ float minSurfaceArea = -1.0f;
+ for ( i = 0; i < 3; i++ )
+ {
+ if ( size[i] > size[largest] )
+ {
+ largest = i;
+ }
+
+ int other = (i+1)%3;
+ int cross = (i+2)%3;
+ float surfaceArea = size[other] * size[cross];
+ if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea )
+ {
+ minSurfaceArea = surfaceArea;
+ }
+ }
+ // this can be really slow with super-large models and a low error tolerance
+ // Basically you get a ray cast through each square of epsilon surface area on each OBB side
+ // So compute it for 0.01% error (on the smallest side, less on larger sides)
+ params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-4f, 0.25f, 128.0f );
+
+ Vector tmp = size;
+ tmp[largest] = 0;
+ float len = tmp.Length();
+ if ( len > 0 )
+ {
+ float sizeRatio = size[largest] / len;
+ // HACKHACK: Hardcoded size ratio to induce damping
+ // This prevents long skinny objects from rolling endlessly
+ if ( sizeRatio > 9 )
+ {
+ pBase->m_rotdamping = 1.0f;
+ }
+ }
+ // THIS DESTROYS pConvex!!
+ pBase->m_pCollisionData = physcollision->ConvertConvexToCollideParams( pElements, elementCount, params );
+
+ // debug output for the drag area calculations
+#if 0
+ Msg("Drag epsilon is %.3f\n", params.dragAreaEpsilon );
+ Vector areas = physcollision->CollideGetOrthographicAreas( pBase->m_pCollisionData );
+ Msg("Drag fractions are %.3f %.3f %.3f\n", areas.x, areas.y, areas.z );
+#endif
+}
+
+
+// is this list of verts contained in a slab of epsilon width? If so, it's probably
+// an error of some kind - we shouldn't be authoring flat or 2d collision models
+bool IsApproximatelyPlanar( Vector **verts, int vertCount, float epsilon )
+{
+ if ( vertCount < 4 )
+ return true;
+
+ // If we're using an un-welded model, then this may generate a degenerate normal
+ // loop to search for an actual plane
+ int v0 = 1, v1 = 2;
+ Vector normal;
+ while ( v0 < vertCount && v1 < vertCount )
+ {
+ Vector edge0 = *verts[v0] - *verts[0];
+ Vector edge1 = *verts[v1] - *verts[0];
+
+ normal = CrossProduct( edge0, edge1 );
+ float len = VectorNormalize( normal );
+ if ( len > 0.001 )
+ break;
+ if ( edge0.Length() < 0.001 )
+ {
+ // verts[0] and v0 are coincident, try new verts
+ v0++;
+ v1++;
+ }
+ else
+ {
+ // v0 seems fine, try a new v1 -- it's probably coincident with v0
+ v1++;
+ }
+ }
+
+ // form the plane and project all of the verts into it
+ float minDist = DotProduct( normal, *verts[0] );
+ float maxDist = minDist;
+
+ for ( int i = 0; i < vertCount; i++ )
+ {
+ float d = DotProduct( *verts[i], normal );
+ if ( d < minDist )
+ {
+ minDist = d;
+ }
+ else if ( d > maxDist )
+ {
+ maxDist = d;
+ }
+ // at least one vert out of the plane, we've got something 3 dimensional
+ if ( fabsf(maxDist-minDist) > epsilon )
+ return false;
+ }
+ return true;
+}
+
+
+
+void BuildConvexListByVertID( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, CUtlVector<int> &vertID )
+{
+ // loop through each island of verts and append it to the convex list
+ convexlist_t current;
+ for ( int i = 0; i < pmodel->numvertices; i++ )
+ {
+ // already processed this group
+ if ( vertID[i] < 0 || vertID[i] > pmodel->numfaces )
+ continue;
+
+ current.firstVertIndex = vertList.Count();
+ current.numVertIndex = 0;
+
+ int id = vertID[i];
+
+ for ( int j = i; j < pmodel->numvertices; j++ )
+ {
+ if ( vertID[j] == id )
+ {
+ vertList.AddToTail(j);
+ current.numVertIndex++;
+ // don't reuse this vert
+ vertID[j] = -1;
+ }
+ }
+ convexList.AddToTail(current);
+ }
+}
+
+// build a list of vertex indices for each connected sub-piece
+void BuildSingleConvexForFaceList( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, const CUtlVector<s_face_t> &faceList )
+{
+ CUtlVector<int> vertID;
+ vertID.SetCount(pmodel->numvertices);
+ int i;
+ for ( i = 0; i < pmodel->numvertices; i++ )
+ {
+ vertID[i] = -1;
+ }
+
+ for ( i = 0; i < faceList.Count(); i++ )
+ {
+ const s_face_t &globalFace = faceList[i];
+ vertID[globalFace.a] = 1;
+ vertID[globalFace.b] = 1;
+ vertID[globalFace.c] = 1;
+ }
+ BuildConvexListByVertID( pmodel, convexList, vertList, vertID );
+}
+
+void BuildConvexListForFaceList( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, const CUtlVector<s_face_t> &faceList )
+{
+ CUtlVector<int> weldTable;
+ weldTable.SetCount(pmodel->numvertices);
+ BuildVertWeldTable( weldTable.Base(), pmodel );
+
+ int i;
+ CUtlVector<int> vertID;
+ vertID.SetCount(pmodel->numvertices);
+
+ // mark all verts as max faceid + 1
+ for ( i = 0; i < pmodel->numvertices; i++ )
+ {
+ // If these verts have been welded to a lower-index vert, mark them
+ // as already processed to avoid making additional convex objects out of them.
+ if ( weldTable[i] != i )
+ {
+ vertID[i] = -1;
+ }
+ else
+ {
+ vertID[i] = pmodel->numfaces+1;
+ }
+ }
+
+ Assert(convexList.Count()==0);
+ Assert(vertList.Count()==0);
+
+ int marked = 0;
+ int faceid = 0;
+ // iterate the face list, minimizing the vertID at each vert
+ // until we have an iteration where no vertIDs are changed
+ do
+ {
+ marked = 0;
+ faceid = 0;
+
+ // basically this flood fills ids out to the verts until each island of connected
+ // verts shares a single id (so new verts got marked)
+ for ( i = 0; i < faceList.Count(); i++ )
+ {
+ s_face_t globalFace = faceList[i];
+ // account for welding
+ globalFace.a = weldTable[globalFace.a];
+ globalFace.b = weldTable[globalFace.b];
+ globalFace.c = weldTable[globalFace.c];
+
+
+ int newid = min(i, vertID[globalFace.a]);
+ newid = min( newid, vertID[globalFace.b]);
+ newid = min( newid, vertID[globalFace.c]);
+
+ // mark all verts with the minimum, count the number we had to mark
+ if ( vertID[globalFace.a] != newid )
+ {
+ vertID[globalFace.a] = newid;
+ marked++;
+ }
+ if ( vertID[globalFace.b] != newid )
+ {
+ vertID[globalFace.b] = newid;
+ marked++;
+ }
+ if ( vertID[globalFace.c] != newid )
+ {
+ vertID[globalFace.c] = newid;
+ marked++;
+ }
+ }
+ } while ( marked != 0 );
+
+ BuildConvexListByVertID( pmodel, convexList, vertList, vertID );
+}
+
+
+// take a list of convex elements (lists of vert indices into master vert list) and build CPhysConvex out of them
+// return true if there are no errors detected
+bool BuildConvexesForLists( CUtlVector<CPhysConvex *> &convexOut, const CUtlVector<convexlist_t> &convexList, const CUtlVector<int> &vertList, const CUtlVector<Vector> &worldspaceVerts, bool bRemove2d )
+{
+ bool bValid = true;
+ CUtlVector<Vector *> vertsThisConvex;
+ for ( int i = 0; i < convexList.Count(); i++ )
+ {
+ const convexlist_t &elem = convexList[i];
+ vertsThisConvex.RemoveAll();
+ for ( int j = 0; j < elem.numVertIndex; j++ )
+ {
+ // this is ok because physcollision won't modify these, but wants non-const
+ Vector *pVert = const_cast<Vector *>(&worldspaceVerts[vertList[j + elem.firstVertIndex]]);
+ vertsThisConvex.AddToTail( pVert );
+ }
+
+ // need at least 3 verts to build a CPhysConvex
+ if ( vertsThisConvex.Count() > 2 )
+ {
+ const float g_epsilon_2d = 0.5f;
+ // HACKHACK: A heuristic to detect models without smoothing groups set
+ // UNDONE: Do a BSP to decompose arbitrary models to convex?
+ if ( IsApproximatelyPlanar( vertsThisConvex.Base(), vertsThisConvex.Count(), g_epsilon_2d ) )
+ {
+ if ( bRemove2d )
+ continue;
+ MdlWarning("Model has 2-dimensional geometry (less than %.3f inches thick on any axis)!!!\n", g_epsilon_2d );
+ bValid = false;
+ }
+ // go ahead and build it out
+ CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertsThisConvex.Base(), vertsThisConvex.Count() );
+ if ( pConvex )
+ {
+ // Got something valid, attach this convex data to the root model
+ physcollision->SetConvexGameData( pConvex, 0 );
+ convexOut.AddToTail(pConvex);
+ }
+ }
+ }
+
+ return bValid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Build a jointed collision model with constraints
+// Input : &joints -
+// Output : int
+//-----------------------------------------------------------------------------
+int ProcessJointedModel( CJointedModel &joints )
+{
+ if( !g_quiet )
+ {
+ printf("Processing jointed collision model\n" );
+ }
+ s_source_t *pmodel = joints.m_pModel;
+ // loop through each bone and form a collision model
+ for ( int boneIndex = 0; boneIndex < joints.m_pModel->numbones; boneIndex++ )
+ {
+ if ( !joints.ShouldProcessBone( boneIndex ) )
+ continue;
+
+ CUtlVector<Vector> bonespaceVerts;
+ bonespaceVerts.SetCount(pmodel->numvertices);
+ ConvertToBoneSpace( joints.m_pModel, boneIndex, bonespaceVerts );
+ CUtlVector<s_face_t> faceList;
+ CUtlVector<convexlist_t> convexList;
+ CUtlVector<int> vertList;
+ CUtlVector<CPhysConvex *> convexOut;
+ bool bValid = false;
+
+ for ( int i = 0; i < pmodel->nummeshes; i++ )
+ {
+ s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i];
+ for ( int j = 0; j < pmesh->numfaces; j++ )
+ {
+ s_face_t *face = pmodel->face + pmesh->faceoffset + j;
+ s_face_t globalFace;
+ GlobalFace( &globalFace, pmesh, face );
+ if ( FaceHasVertOnBone( joints, pmodel, &globalFace, boneIndex ) )
+ {
+ faceList.AddToTail( globalFace );
+ }
+ }
+
+ if ( joints.m_allowConcaveJoints )
+ {
+ BuildConvexListForFaceList( pmodel, convexList, vertList, faceList );
+ }
+ else
+ {
+ BuildSingleConvexForFaceList( pmodel, convexList, vertList, faceList );
+ }
+
+ bValid = BuildConvexesForLists( convexOut, convexList, vertList, bonespaceVerts, joints.m_remove2d );
+ }
+
+ if ( convexOut.Count() > joints.m_maxConvex )
+ {
+ MdlWarning("COSTLY COLLISION MODEL!!!! (%d parts - %d allowed)\n", convexOut.Count(), joints.m_maxConvex );
+ bValid = false;
+ }
+
+ if ( !bValid && convexOut.Count() )
+ {
+ MdlWarning("Error with convex elements of %s, building single convex!!!!\n", pmodel->filename );
+ for ( int i = 0; i < convexOut.Count(); i++ )
+ {
+ physcollision->ConvexFree( convexOut[i] );
+ }
+ convexOut.Purge();
+ }
+
+ if ( convexOut.Count() )
+ {
+ int i;
+
+ CPhysCollisionModel *pPhys = InitCollisionModel( joints, pmodel->localBone[boneIndex].name );
+
+ pPhys->m_mass = 1.0;
+ pPhys->m_name = joints.m_pModel->localBone[boneIndex].name;
+ if ( joints.m_pModel->localBone[boneIndex].parent >= 0 )
+ {
+ pPhys->m_parent = joints.m_pModel->localBone[joints.m_pModel->localBone[boneIndex].parent].name;
+ }
+ else
+ {
+ pPhys->m_parent = NULL;
+ }
+
+ boundingvolume_t bv;
+ ClearBounds( bv.mins, bv.maxs );
+ int vertCount = 0;
+ for ( i = 0; i < convexList.Count(); i++ )
+ {
+ const convexlist_t &elem = convexList[i];
+ for ( int j = 0; j < elem.numVertIndex; j++ )
+ {
+ AddPointToBounds( bonespaceVerts[vertList[elem.firstVertIndex+j]], bv.mins, bv.maxs );
+ vertCount++;
+ }
+ }
+ for ( i = 0; i < convexOut.Count(); i++ )
+ {
+ // Attach this convex data to this particular bone
+ int globalBoneIndex = joints.m_pModel->boneLocalToGlobal[boneIndex];
+ physcollision->SetConvexGameData( convexOut[i], globalBoneIndex + 1 );
+ }
+
+ CreateCollide( pPhys, convexOut.Base(), convexOut.Count(), bv );
+ if( !g_quiet )
+ {
+ printf("%-24s (%3d verts, %d convex elements) volume: %4.2f\n", pPhys->m_name, vertCount, convexOut.Count(), pPhys->m_volume );
+ }
+ joints.UnlinkCollisionModel( pPhys );
+ joints.AppendCollisionModel( pPhys );
+ }
+ }
+ // remove any non-physical joints at this point
+ CPhysCollisionModel *pPhys = joints.m_pCollisionList;
+ while (pPhys)
+ {
+ CPhysCollisionModel *pNext = pPhys->m_pNext;
+ if ( !pPhys->m_pCollisionData )
+ {
+ joints.UnlinkCollisionModel(pPhys);
+ delete pPhys;
+ }
+ pPhys = pNext;
+ }
+
+ return 1;
+}
+
+
+#if 0
+// debug visualization code - use this to dump out intermediate geometry files for visualization in glview.exe
+void DumpToGLView( char const *pName, s_source_t *pmodel, Vector *worldVerts, int *used )
+{
+ int i;
+
+ for ( i = 0; i < pmodel->numvertices; i++ )
+ used[i] = -1;
+
+ FILE *fp = fopen( pName, "w" );
+
+ // dump the model to a glview file
+ for ( i = 0; i < pmodel->nummeshes; i++ )
+ {
+ s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i];
+ for ( int j = 0; j < pmesh->numfaces; j++ )
+ {
+ s_face_t *face = pmodel->face + pmesh->faceoffset + j;
+ s_face_t globalFace;
+ GlobalFace( &globalFace, pmesh, face );
+
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", worldVerts[globalFace.b].x, worldVerts[globalFace.b].y, worldVerts[globalFace.b].z );
+ fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", worldVerts[globalFace.a].x, worldVerts[globalFace.a].y, worldVerts[globalFace.a].z );
+ fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", worldVerts[globalFace.c].x, worldVerts[globalFace.c].y, worldVerts[globalFace.c].z );
+ used[globalFace.a] = 0;
+ used[globalFace.b] = 0;
+ used[globalFace.c] = 0;
+ }
+ }
+
+ // dump a triangle expanded around each vert to the file (to show degenerate tris' verts).
+ for ( i = 0; i < pmodel->numvertices; i++ )
+ {
+ if ( used[i] < 0 )
+ continue;
+
+ fprintf( fp, "3\n" );
+ Vector vert;
+ vert = worldVerts[i] + Vector(0,0,5);
+ fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", vert.x, vert.y, vert.z );
+ vert = worldVerts[i] + Vector(5,0,-5);
+ fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", vert.x, vert.y, vert.z );
+ vert = worldVerts[i] + Vector(-5,0,-5);
+ fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", vert.x, vert.y, vert.z );
+
+ }
+
+ fclose( fp );
+}
+#endif
+
+
+int ProcessSingleBody( CJointedModel &joints )
+{
+ s_source_t *pmodel = joints.m_pModel;
+ // THIS CODE IS ONLY EXECUTED ON PROPS - i.e. NON-JOINTED MODELS
+ CUtlVector<Vector> worldspaceVerts;
+ worldspaceVerts.SetCount(pmodel->numvertices);
+ ConvertToWorldSpace( joints, pmodel, worldspaceVerts );
+ CUtlVector<s_face_t> faceList;
+
+ CUtlVector<convexlist_t> convexList;
+ CUtlVector<int> vertList;
+ CUtlVector<CPhysConvex *> convexOut;
+ bool bValid = false;
+ if ( joints.m_allowConcave )
+ {
+ for ( int i = 0; i < pmodel->nummeshes; i++ )
+ {
+ s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i];
+ for ( int j = 0; j < pmesh->numfaces; j++ )
+ {
+ s_face_t *face = pmodel->face + pmesh->faceoffset + j;
+ s_face_t globalFace;
+ GlobalFace( &globalFace, pmesh, face );
+ faceList.AddToTail( globalFace );
+ }
+ }
+ BuildConvexListForFaceList( pmodel, convexList, vertList, faceList );
+ bValid = BuildConvexesForLists( convexOut, convexList, vertList, worldspaceVerts, joints.m_remove2d );
+ }
+
+ if ( convexOut.Count() > joints.m_maxConvex )
+ {
+ MdlWarning("COSTLY COLLISION MODEL!!!! (%d parts - %d allowed)\n", convexOut.Count(), joints.m_maxConvex );
+ bValid = false;
+ }
+
+ if ( !bValid && convexOut.Count() )
+ {
+ MdlWarning("Error with convex elements of %s, building single convex!!!!\n", pmodel->filename );
+ for ( int i = 0; i < convexOut.Count(); i++ )
+ {
+ physcollision->ConvexFree( convexOut[i] );
+ }
+ convexOut.Purge();
+ }
+
+ // either we don't want concave, or there was an error building it
+ if ( !convexOut.Count() )
+ {
+ convexlist_t elem;
+ elem.firstVertIndex = 0;
+ elem.numVertIndex = pmodel->numvertices;
+ convexList.AddToTail(elem);
+ for ( int i = 0; i < pmodel->numvertices; i++ )
+ {
+ vertList.AddToTail(i);
+ }
+ BuildConvexesForLists( convexOut, convexList, vertList, worldspaceVerts, true );
+ }
+
+ if ( convexOut.Count() )
+ {
+ if( !g_quiet )
+ {
+ printf("Model has %d convex sub-parts\n", convexOut.Count() );
+ }
+
+ CPhysCollisionModel *pPhys = new CPhysCollisionModel;
+ joints.SetCollisionModelDefaults( pPhys );
+
+ boundingvolume_t bv;
+ ClearBounds( bv.mins, bv.maxs );
+ for ( int i = worldspaceVerts.Count()-1; --i >= 0; )
+ {
+ AddPointToBounds( worldspaceVerts[i], bv.mins, bv.maxs );
+ }
+ CreateCollide( pPhys, convexOut.Base(), convexOut.Count(), bv );
+
+ // Init mass, write routine will distribute the total mass
+ pPhys->m_mass = 1.0;
+ char tmp[512];
+ Q_FileBase( pmodel->filename, tmp, sizeof( tmp ) );
+
+ // UNDONE: Memory leak
+ char *out = new char[strlen(tmp)+1];
+ strcpy( out, tmp );
+ pPhys->m_name = out;
+ pPhys->m_parent = NULL;
+
+ joints.AppendCollisionModel( pPhys );
+ }
+ return 1;
+}
+
+
+
+#define MAX_ARGS 16
+#define ARG_SIZE 256
+
+//-----------------------------------------------------------------------------
+// Purpose: HACKETY HACK - get the args into a buffer.
+// This checks for overflow, but it's not very robust - shouldn't be necessary though
+// Input : pArgs[][ARG_SIZE] -
+// maxCount - array size of pargs
+// Output : int - count actually used
+//-----------------------------------------------------------------------------
+int ReadArgs( char pArgs[][ARG_SIZE], int maxCount )
+{
+ int argCount = 0;
+
+ while ( argCount < maxCount && TokenAvailable() )
+ {
+ GetToken(false);
+ strncpy( pArgs[argCount], token, ARG_SIZE );
+ argCount++;
+ }
+
+ return argCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Simple atof wrapper to keep from crashing on bad user input
+// Input : *pString -
+// Output : float
+//-----------------------------------------------------------------------------
+float Safe_atof( const char *pString )
+{
+ if ( !pString )
+ return 0;
+
+ return atof(pString);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Simple atoi wrapper to avoid crashing on bad user input
+// Input : *pString -
+// Output : int
+//-----------------------------------------------------------------------------
+int Safe_atoi( const char *pString )
+{
+ if ( !pString )
+ return 0;
+
+ return atoi(pString);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a constraint to our joint system
+// Input : &joints -
+// *pJointName -
+// *pJointAxis -
+// *pJointType -
+// *pLimitMin -
+// *pLimitMax -
+//-----------------------------------------------------------------------------
+void CCmd_JointConstrain( CJointedModel &joints, const char *pJointName, const char *pJointAxis, const char *pJointType, const char *pLimitMin, const char *pLimitMax, const char *pFriction )
+{
+ float limitMin = Safe_atof(pLimitMin);
+ float limitMax = Safe_atof(pLimitMax);
+ float friction = Safe_atof(pFriction);
+
+ int axis = -1;
+ int jointIndex = FindLocalBoneNamed( joints.m_pModel, pJointName );
+ if ( !g_bCreateMakefile && jointIndex < 0 )
+ {
+ MdlWarning("Can't find joint %s\n", pJointName );
+ return;
+ }
+ pJointName = joints.m_pModel->localBone[jointIndex].name;
+
+ if ( pJointAxis )
+ {
+ axis = tolower(pJointAxis[0]) - 'x';
+ }
+ if ( axis < 0 || axis > 2 || limitMin > limitMax )
+ {
+ MdlError("Invalid joint constraint for %s\nCan't build ragdoll!\n", pJointName );
+ return;
+ }
+
+ jointlimit_t jointType = JOINT_FREE;
+ if ( !stricmp( pJointType, "free" ) )
+ {
+ jointType = JOINT_FREE;
+ }
+ else if ( !stricmp( pJointType, "fixed" ) )
+ {
+ jointType = JOINT_FIXED;
+ }
+ else if ( !stricmp( pJointType, "limit" ) )
+ {
+ jointType = JOINT_LIMIT;
+ }
+ else
+ {
+ MdlWarning("Unknown joint type %s (must be free, fixed, or limit)\n", pJointType );
+ return;
+ }
+ joints.AddConstraint( pJointName, axis, jointType, limitMin, limitMax, friction );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove a joint from the system (don't create physical geometry for it)
+// Input : &joints -
+// args[][ARG_SIZE] -
+// argCount -
+//-----------------------------------------------------------------------------
+// UNDONE: Automatically skip joints that will have mass that is too low?
+void CCmd_JointSkip( CJointedModel &joints, const char *pName )
+{
+ int boneIndex = FindLocalBoneNamed( joints.m_pModel, pName );
+ if ( boneIndex < 0 )
+ {
+ MdlWarning("Can't skip joint %s, not found\n", pName );
+ }
+ else
+ {
+// printf("skipping joint %s\n", pName );
+ joints.SkipBone( boneIndex );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the object's mass. The code will distribute this mass to each
+// part based on the collision model's volume
+// Input : &joints -
+// *pMass -
+//-----------------------------------------------------------------------------
+void CCmd_TotalMass( CJointedModel &joints, const char *pMass )
+{
+ joints.SetTotalMass( Safe_atof(pMass) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: verts from the bone named pChild are added to the collision model of pParent
+// Input : *pmodel - source model
+// *pParent - destination bone name
+// *pChild - source bone name
+//-----------------------------------------------------------------------------
+void CCmd_JointMerge( CJointedModel &joints, const char *pParent, const char *pChild )
+{
+ joints.AddMergeCommand( pParent, pChild );
+ joints.MergeBones( FindLocalBoneNamed( joints.m_pModel, pParent ), FindLocalBoneNamed( joints.m_pModel, pChild ) );
+}
+
+
+void CCmd_JointRoot( CJointedModel &joints, const char *pBone )
+{
+ // save the root bone name
+ strcpy( joints.m_rootName, pBone );
+}
+
+
+void CCmd_JoinAnimatedFriction( CJointedModel &joints, const char *pMinFriction, const char *pMaxFriction, const char *pTimeIn, const char *pTimeHold, const char *pTimeOut )
+{
+ joints.m_flFrictionTimeIn = Safe_atof( pTimeIn );
+ joints.m_flFrictionTimeOut = Safe_atof( pTimeOut );
+ joints.m_flFrictionTimeHold = Safe_atof( pTimeHold );
+ joints.m_iMinAnimatedFriction = Safe_atoi( pMinFriction );
+ joints.m_iMaxAnimatedFriction = Safe_atoi( pMaxFriction );
+ joints.m_bHasAnimatedFriction = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Parses all legal commands inside the $collisionjoints {} block
+// Input : &joints -
+//-----------------------------------------------------------------------------
+void ParseCollisionCommands( CJointedModel &joints )
+{
+ char command[512];
+
+ char args[MAX_ARGS][ARG_SIZE];
+ int argCount;
+
+ while( GetToken( true ) )
+ {
+ if ( !strcmp( token, "}" ) )
+ return;
+
+ strcpy( command, token );
+
+ if ( !stricmp( command, "$mass" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ CCmd_TotalMass( joints, args[0] );
+ }
+ // default properties
+ else if ( !stricmp( command, "$automass" ) )
+ {
+ joints.SetAutoMass();
+ }
+ else if ( !stricmp( command, "$inertia" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ joints.DefaultInertia( Safe_atof( args[0] ) );
+ }
+ else if ( !stricmp( command, "$damping" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ joints.DefaultDamping( Safe_atof( args[0] ) );
+ }
+ else if ( !stricmp( command, "$rotdamping" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ joints.DefaultRotdamping( Safe_atof( args[0] ) );
+ }
+ else if ( !stricmp( command, "$drag" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ joints.DefaultDrag( Safe_atof( args[0] ) );
+ }
+ else if ( !stricmp( command, "$rollingDrag" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ // JAY: Removed this in favor of heuristic/tuning approach
+ //joints.DefaultRollingDrag( Safe_atof( args[0] ) );
+ }
+ else if ( !stricmp( command, "$maxconvexpieces") )
+ {
+ argCount = ReadArgs( args, 1 );
+ joints.SetMaxConvex( Safe_atoi(args[0]) );
+ }
+ else if ( !stricmp( command, "$remove2d") )
+ {
+ joints.Remove2DConvex();
+ }
+ else if ( !stricmp( command, "$concaveperjoint") )
+ {
+ joints.AllowConcaveJoints();
+ }
+ else if ( !stricmp( command, "$weldposition") )
+ {
+ argCount = ReadArgs(args,1);
+ g_WeldVertEpsilon = Safe_atof( args[0] );
+ }
+ else if ( !stricmp( command, "$weldnormal") )
+ {
+ argCount = ReadArgs(args,1);
+ g_WeldNormalEpsilon = Safe_atof( args[0] );
+ }
+ else if ( !stricmp( command, "$concave" ) )
+ {
+ joints.AllowConcave();
+ }
+ else if ( !stricmp( command, "$masscenter" ) )
+ {
+ argCount = ReadArgs( args, 3 );
+ Vector center;
+ center.Init( Safe_atof(args[0]), Safe_atof(args[1]), Safe_atof(args[2]) );
+ joints.ForceMassCenter( center );
+ }
+ // joint commands
+ else if ( !stricmp( command, "$jointskip" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ CCmd_JointSkip( joints, args[0] );
+ }
+ else if ( !stricmp( command, "$jointmerge" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ CCmd_JointMerge( joints, args[0], args[1] );
+ }
+ else if ( !stricmp( command, "$rootbone" ) )
+ {
+ argCount = ReadArgs( args, 1 );
+ CCmd_JointRoot( joints, args[0] );
+ }
+ else if ( !stricmp( command, "$jointconstrain" ) )
+ {
+ argCount = ReadArgs( args, 6 );
+ char *pFriction = args[5];
+ if ( argCount < 6 )
+ {
+ pFriction = "1.0";
+ }
+ CCmd_JointConstrain( joints, args[0], args[1], args[2], args[3], args[4], pFriction );
+ }
+ // joint properties
+ else if ( !stricmp( command, "$jointinertia" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ joints.JointInertia( args[0], Safe_atof( args[1] ) );
+ }
+ else if ( !stricmp( command, "$jointdamping" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ joints.JointDamping( args[0], Safe_atof( args[1] ) );
+ }
+ else if ( !stricmp( command, "$jointrotdamping" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ joints.JointRotdamping( args[0], Safe_atof( args[1] ) );
+ }
+ else if ( !stricmp( command, "$jointmassbias" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ joints.JointMassBias( args[0], Safe_atof( args[1] ) );
+ }
+ else if ( !stricmp( command, "$noselfcollisions" ) )
+ {
+ joints.SetNoSelfCollisions();
+ }
+ else if ( !stricmp( command, "$jointcollide" ) )
+ {
+ argCount = ReadArgs( args, 2 );
+ joints.AppendCollisionPair( args[0], args[1] );
+ }
+ else if ( !stricmp( command, "$animatedfriction" ) )
+ {
+ argCount = ReadArgs( args, 5 );
+
+ if ( argCount == 5 )
+ {
+ CCmd_JoinAnimatedFriction( joints, args[0], args[1], args[2], args[3], args[4] );
+ }
+ }
+ else if ( !stricmp( command, "$assumeworldspace") )
+ {
+ joints.m_bAssumeWorldspace = true;
+ }
+ else
+ {
+ MdlWarning("Unknown command %s in collision series\n", command );
+ }
+ }
+}
+
+
+void Cmd_CollisionText( void )
+{
+ int level = 1;
+
+ if ( !GetToken( true ) )
+ return;
+
+ if ( token[0] != '{' )
+ return;
+
+
+ while ( GetToken(true) )
+ {
+ if ( !strcmp( token, "}" ) )
+ {
+ level--;
+ if ( level <= 0 )
+ break;
+ g_JointedModel.AddText( " }\n" );
+ }
+ else if ( !strcmp( token, "{" ) )
+ {
+ g_JointedModel.AddText( "{" );
+ level++;
+ }
+ else
+ {
+ // tokens inside braces are quoted
+ if ( level > 1 )
+ {
+ g_JointedModel.AddText( "\"" );
+ g_JointedModel.AddText( token );
+ g_JointedModel.AddText( "\" " );
+ }
+ else
+ {
+ g_JointedModel.AddText( token );
+ g_JointedModel.AddText( " " );
+ }
+ }
+ }
+}
+
+static bool LoadSurfaceProps( const char *pMaterialFilename )
+{
+ if ( !physprops )
+ return false;
+
+ FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb", TOOLS_READ_PATH_ID );
+ if ( fp == FILESYSTEM_INVALID_HANDLE )
+ return false;
+
+ int len = g_pFileSystem->Size( fp );
+ char *pText = new char[len+1];
+ g_pFileSystem->Read( pText, len, fp );
+ g_pFileSystem->Close( fp );
+
+ pText[len]=0;
+
+ physprops->ParseSurfaceData( pMaterialFilename, pText );
+
+ delete[] pText;
+
+ return true;
+}
+
+void LoadSurfacePropsAll()
+{
+ // already loaded
+ if ( physprops->SurfacePropCount() )
+ return;
+
+ const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt";
+ KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE );
+ if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) )
+ {
+ for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
+ {
+ if ( !Q_stricmp( sub->GetName(), "file" ) )
+ {
+ // Add
+ LoadSurfaceProps( sub->GetString() );
+ continue;
+ }
+ }
+ }
+
+ manifest->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Entry point for script processing. Delegate to necessary subroutines.
+// Parse the collisionmodel {} and collisionjoints {} chunks
+// Input : separateJoints - whether this has a constraint system or not (true if it does)
+// Output : int
+//-----------------------------------------------------------------------------
+int DoCollisionModel( bool separateJoints )
+{
+ char name[512];
+ s_source_t *pmodel;
+
+ // name
+ if (!GetToken(false)) return 0;
+
+ V_strcpy_safe( name, token );
+
+ PhysicsDLLPath( "VPHYSICS.DLL" );
+
+// CreateInterfaceFn physicsFactory = GetPhysicsFactory();
+ CreateInterfaceFn physicsFactory = Sys_GetFactory(Sys_LoadModule( "vphysics.dll" ));
+ if ( !physicsFactory )
+ return 0;
+
+ physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL );
+ physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL );
+ LoadSurfacePropsAll();
+
+ int nummaterials = g_nummaterials;
+ int numtextures = g_numtextures;
+
+ pmodel = Load_Source( name, "SMD" );
+ if ( !pmodel )
+ return 0;
+
+ // auto-remove any new materials/textures
+ if (nummaterials && numtextures && (numtextures != g_numtextures || nummaterials != g_nummaterials))
+ {
+ g_numtextures = numtextures;
+ g_nummaterials = nummaterials;
+
+ pmodel->texmap[0] = 0;
+ }
+
+ // all bones map to themselves by default
+ g_JointedModel.SetSource( pmodel );
+
+ bool parseCommands = false;
+
+ // If the next token is a { that means a data block for the collision model
+ if (GetToken(true))
+ {
+ if ( !strcmp( token, "{" ) )
+ {
+ parseCommands = true;
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+
+ if ( parseCommands )
+ {
+ ParseCollisionCommands( g_JointedModel );
+ }
+
+ g_bJointed = separateJoints;
+
+ // collision script is stored in g_JointedModel for later processing
+ return 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Walk the list of models, add up the volume
+// Input : *pList -
+// Output : float
+//-----------------------------------------------------------------------------
+float TotalVolume( CPhysCollisionModel *pList )
+{
+ float volume = 0;
+ while ( pList )
+ {
+ volume += pList->m_volume * pList->m_massBias;
+ pList = pList->m_pNext;
+ }
+
+ return volume;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Write key/value pairs out to a file
+// Input : *fp - output file
+// *pKeyName - key name
+// outputData - type specific output data
+//-----------------------------------------------------------------------------
+void KeyWriteInt( FILE *fp, const char *pKeyName, int outputData )
+{
+ fprintf( fp, "\"%s\" \"%d\"\n", pKeyName, outputData );
+}
+
+void KeyWriteIntPair( FILE *fp, const char *pKeyName, int outputData0, int outputData1 )
+{
+ fprintf( fp, "\"%s\" \"%d,%d\"\n", pKeyName, outputData0, outputData1 );
+}
+void KeyWriteString( FILE *fp, const char *pKeyName, const char *outputData )
+{
+ fprintf( fp, "\"%s\" \"%s\"\n", pKeyName, outputData );
+}
+
+void KeyWriteVector3( FILE *fp, const char *pKeyName, const Vector& outputData )
+{
+ fprintf( fp, "\"%s\" \"%f %f %f\"\n", pKeyName, outputData[0], outputData[1], outputData[2] );
+}
+
+void KeyWriteQAngle( FILE *fp, const char *pKeyName, const QAngle& outputData )
+{
+ fprintf( fp, "\"%s\" \"%f %f %f\"\n", pKeyName, outputData[0], outputData[1], outputData[2] );
+}
+
+void KeyWriteFloat( FILE *fp, const char *pKeyName, float outputData )
+{
+ fprintf( fp, "\"%s\" \"%f\"\n", pKeyName, outputData );
+}
+
+
+void FixCollisionHierarchy( CJointedModel &joints )
+{
+ if ( joints.m_pCollisionList )
+ {
+ CPhysCollisionModel *pPhys = joints.m_pCollisionList;
+
+ FixBoneList( joints.m_bonemap, joints.m_pModel, joints.m_pCollisionList );
+ // Point parents at joints that are actually in the model
+ for ( ;pPhys; pPhys = pPhys->m_pNext )
+ {
+ pPhys->m_parent = FixParent( joints.m_pCollisionList, joints.m_pModel, pPhys->m_parent );
+ }
+
+ // sort the list so parents come before children
+ joints.SortCollisionList();
+ // Now remap the constraints to bones to
+ // Now that bones are in order, set physics indices in main bone structure
+
+ CJointConstraint *pList = g_JointedModel.m_pConstraintList;
+ while ( pList )
+ {
+ pList->m_pJointName = FixParent( joints.m_pCollisionList, joints.m_pModel, pList->m_pJointName );
+ pList = pList->m_pNext;
+ }
+
+ pPhys = joints.m_pCollisionList;
+ int i;
+ for ( i = 0; i < g_numbones; i++ )
+ {
+ g_bonetable[i].physicsBoneIndex = -1;
+ }
+ int index = 0;
+ while ( pPhys )
+ {
+ int boneIndex = FindBoneInTable( pPhys->m_name );
+ if ( boneIndex >= 0 )
+ {
+ g_bonetable[boneIndex].physicsBoneIndex = index;
+ }
+ pPhys = pPhys->m_pNext;
+ index ++;
+ }
+ for ( i = 0; i < g_numbones; i++ )
+ {
+ // if no bone was set, set to parent bone
+ if ( g_bonetable[i].physicsBoneIndex < 0 )
+ {
+ int index = g_bonetable[i].parent;
+ int bone = -1;
+ while ( index >= 0 )
+ {
+ bone = g_bonetable[index].physicsBoneIndex;
+ if ( bone >= 0 )
+ break;
+ index = g_bonetable[index].parent;
+ }
+
+ // found one?
+ if ( bone >= 0 )
+ {
+ g_bonetable[i].physicsBoneIndex = bone;
+ }
+ else
+ {
+ // just set physics to affect root
+ g_bonetable[i].physicsBoneIndex = 0;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds the physics/collision model.
+// This must execute after the model has been simplified!!
+//-----------------------------------------------------------------------------
+void CollisionModel_Build( void )
+{
+ // no collision model referenced
+ if ( !g_JointedModel.m_pModel )
+ return;
+
+ g_JointedModel.Simplify();
+ if ( g_bJointed )
+ {
+ ProcessJointedModel( g_JointedModel );
+ }
+ else
+ {
+ ProcessSingleBody( g_JointedModel );
+ }
+ FixCollisionHierarchy( g_JointedModel );
+ if( !g_quiet )
+ {
+ printf("Collision model completed.\n" );
+ }
+ g_JointedModel.ComputeMass();
+}
+
+void BuildRagdollConstraint( CPhysCollisionModel *pPhys, constraint_ragdollparams_t &ragdoll )
+{
+ memset( &ragdoll, 0, sizeof(ragdoll) );
+ ragdoll.parentIndex = g_JointedModel.CollisionIndex(pPhys->m_parent);
+ ragdoll.childIndex = g_JointedModel.CollisionIndex(pPhys->m_name);
+ if ( ragdoll.parentIndex < 0 || ragdoll.childIndex < 0 )
+ {
+ MdlWarning("Constraint between bone %s and %s\n", pPhys->m_name, pPhys->m_parent );
+ if ( ragdoll.childIndex < 0 )
+ MdlWarning("\"%s\" does not appear in collision model!!!\n", pPhys->m_name );
+ if ( ragdoll.parentIndex < 0 )
+ MdlWarning("\"%s\" does not appear in collision model!!!\n", pPhys->m_parent );
+ MdlError("Bad constraint in ragdoll\n");
+ }
+ CJointConstraint *pList = g_JointedModel.m_pConstraintList;
+ while ( pList )
+ {
+ int index = g_JointedModel.CollisionIndex(pList->m_pJointName);
+ CPhysCollisionModel *pListModel = g_JointedModel.GetCollisionModel(pList->m_pJointName);
+ if ( index < 0 )
+ {
+ MdlError("Rotation constraint on bone \"%s\" which does not appear in collision model!!!\n", pList->m_pJointName );
+ }
+ else if ( (!pListModel->m_parent || g_JointedModel.CollisionIndex(pListModel->m_parent) < 0) && stricmp( pList->m_pJointName, g_JointedModel.m_rootName ) )
+ {
+ MdlError("Rotation constraint on bone \"%s\" which has no parent!!!\n", pList->m_pJointName );
+ }
+ else if ( index == ragdoll.childIndex )
+ {
+ switch ( pList->m_jointType )
+ {
+ case JOINT_LIMIT:
+ ragdoll.axes[pList->m_axis].SetAxisFriction( pList->m_limitMin, pList->m_limitMax, pList->m_friction );
+ break;
+ case JOINT_FIXED:
+ ragdoll.axes[pList->m_axis].SetAxisFriction( 0,0,0 );
+ break;
+ case JOINT_FREE:
+ ragdoll.axes[pList->m_axis].SetAxisFriction( -360, 360, pList->m_friction );
+ break;
+ }
+ }
+ pList = pList->m_pNext;
+ }
+}
+
+float GetCollisionModelMass()
+{
+ return g_JointedModel.m_totalMass;
+}
+
+void CollisionModel_ExpandBBox( Vector &mins, Vector &maxs )
+{
+ // don't do fixup for ragdolls
+ if ( g_bJointed )
+ return;
+
+ if ( g_JointedModel.m_pCollisionList )
+ {
+ Vector collideMins, collideMaxs;
+
+ physcollision->CollideGetAABB( &collideMins, &collideMaxs, g_JointedModel.m_pCollisionList->m_pCollisionData, vec3_origin, vec3_angle );
+
+ // add the 0.25 inch collision separation as well
+ const float radius = 0.25;
+ collideMins -= Vector(radius,radius,radius);
+ collideMaxs += Vector(radius,radius,radius);
+
+ AddPointToBounds( collideMins, mins, maxs );
+ AddPointToBounds( collideMaxs, mins, maxs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Write out any data that's been saved in the globals
+//-----------------------------------------------------------------------------
+void CollisionModel_Write( long checkSum )
+{
+ if ( g_JointedModel.m_pCollisionList )
+ {
+ CPhysCollisionModel *pPhys = g_JointedModel.m_pCollisionList;
+
+ char filename[MAX_PATH];
+
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+
+ float volume = TotalVolume( pPhys );
+ if ( volume <= 0 )
+ volume = 1;
+ if( !g_quiet )
+ {
+ printf("Collision model volume %.2f in^3\n", volume );
+ }
+
+ Q_SetExtension( filename, ".phy", sizeof( filename ) );
+ CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( filename ) );
+ spFile->Edit();
+ FILE *fp = fopen( filename, "wb" );
+ if ( fp )
+ {
+ // write out the collision header (size is version)
+ phyheader_t header;
+ header.size = sizeof(header);
+ header.id = 0;
+ header.checkSum = checkSum;
+
+ header.solidCount = 0;
+ pPhys = g_JointedModel.m_pCollisionList;
+ while ( pPhys )
+ {
+ header.solidCount++;
+ pPhys = pPhys->m_pNext;
+ }
+
+ fwrite( &header, sizeof(header), 1, fp );
+
+ // Write out the binary physics collision data
+ pPhys = g_JointedModel.m_pCollisionList;
+ while ( pPhys )
+ {
+ int size = physcollision->CollideSize( pPhys->m_pCollisionData );
+ fwrite( &size, sizeof(int), 1, fp );
+ char *buf = (char *)stackalloc( size );
+ physcollision->CollideWrite( buf, pPhys->m_pCollisionData );
+ fwrite( buf, size, 1, fp );
+ pPhys = pPhys->m_pNext;
+ }
+
+ // write out the properties of each solid
+ int solidIndex = 0;
+ pPhys = g_JointedModel.m_pCollisionList;
+ while ( pPhys )
+ {
+ pPhys->m_mass = ((pPhys->m_volume * pPhys->m_massBias) / volume) * g_JointedModel.m_totalMass;
+ if ( pPhys->m_mass < 1.0 )
+ pPhys->m_mass = 1.0;
+
+ fprintf( fp, "solid {\n" );
+ KeyWriteInt( fp, "index", solidIndex );
+ KeyWriteString( fp, "name", pPhys->m_name );
+ if ( pPhys->m_parent )
+ {
+ KeyWriteString( fp, "parent", pPhys->m_parent );
+ }
+
+ KeyWriteFloat( fp, "mass", pPhys->m_mass );
+ //KeyWriteFloat( fp, "volume", pPhys->m_volume );
+
+ char* pSurfaceProps = GetSurfaceProp( pPhys->m_name );
+
+ KeyWriteString( fp, "surfaceprop", pSurfaceProps );
+ KeyWriteFloat( fp, "damping", pPhys->m_damping );
+ KeyWriteFloat( fp, "rotdamping", pPhys->m_rotdamping );
+
+ if ( pPhys->m_dragCoefficient != -1 )
+ {
+ KeyWriteFloat( fp, "drag", pPhys->m_dragCoefficient );
+ }
+ KeyWriteFloat( fp, "inertia", pPhys->m_inertia );
+ KeyWriteFloat( fp, "volume", pPhys->m_volume );
+ if ( pPhys->m_massBias != 1.0f )
+ {
+ KeyWriteFloat( fp, "massbias", pPhys->m_massBias );
+ }
+
+ fprintf( fp, "}\n" );
+ pPhys = pPhys->m_pNext;
+ solidIndex++;
+
+ }
+
+ // by default, write constraints from each limb to its parent
+ pPhys = g_JointedModel.m_pCollisionList;
+ while ( pPhys )
+ {
+ // check to see if bone collapse/remap has left this with parent pointing at itself
+ if ( pPhys->m_parent )
+ {
+ constraint_ragdollparams_t ragdoll;
+ BuildRagdollConstraint( pPhys, ragdoll );
+ if ( ragdoll.parentIndex != ragdoll.childIndex )
+ {
+ fprintf( fp, "ragdollconstraint {\n" );
+ KeyWriteInt( fp, "parent", ragdoll.parentIndex );
+ KeyWriteInt( fp, "child", ragdoll.childIndex );
+ KeyWriteFloat( fp, "xmin", ragdoll.axes[0].minRotation );
+ KeyWriteFloat( fp, "xmax", ragdoll.axes[0].maxRotation );
+ KeyWriteFloat( fp, "xfriction", ragdoll.axes[0].torque );
+ KeyWriteFloat( fp, "ymin", ragdoll.axes[1].minRotation );
+ KeyWriteFloat( fp, "ymax", ragdoll.axes[1].maxRotation );
+ KeyWriteFloat( fp, "yfriction", ragdoll.axes[1].torque );
+ KeyWriteFloat( fp, "zmin", ragdoll.axes[2].minRotation );
+ KeyWriteFloat( fp, "zmax", ragdoll.axes[2].maxRotation );
+ KeyWriteFloat( fp, "zfriction", ragdoll.axes[2].torque );
+ fprintf( fp, "}\n" );
+ }
+ }
+ pPhys = pPhys->m_pNext;
+
+ }
+ if ( g_JointedModel.m_noSelfCollisions )
+ {
+ fprintf(fp, "collisionrules {\n" );
+ KeyWriteInt( fp, "selfcollisions", 0 );
+ fprintf(fp, "}\n");
+ }
+ else if ( g_JointedModel.m_pCollisionPairs )
+ {
+ fprintf(fp, "collisionrules {\n" );
+ collisionpair_t *pPair = g_JointedModel.m_pCollisionPairs;
+ while ( pPair )
+ {
+ pPair->obj0 = g_JointedModel.CollisionIndex( pPair->pName0 );
+ pPair->obj1 = g_JointedModel.CollisionIndex( pPair->pName1 );
+ if ( pPair->obj0 >= 0 && pPair->obj1 >= 0 && pPair->obj0 != pPair->obj1 )
+ {
+ KeyWriteIntPair( fp, "collisionpair", pPair->obj0, pPair->obj1 );
+ }
+ else
+ {
+ MdlWarning("Invalid collision pair (%s, %s)\n", pPair->pName0, pPair->pName1 );
+ }
+ pPair = pPair->pNext;
+ }
+ fprintf(fp, "}\n");
+ }
+
+ if ( g_JointedModel.m_bHasAnimatedFriction == true )
+ {
+ fprintf( fp, "animatedfriction {\n" );
+ KeyWriteFloat( fp, "animfrictionmin", g_JointedModel.m_iMinAnimatedFriction );
+ KeyWriteFloat( fp, "animfrictionmax", g_JointedModel.m_iMaxAnimatedFriction );
+ KeyWriteFloat( fp, "animfrictiontimein", g_JointedModel.m_flFrictionTimeIn );
+ KeyWriteFloat( fp, "animfrictiontimeout", g_JointedModel.m_flFrictionTimeOut );
+ KeyWriteFloat( fp, "animfrictiontimehold", g_JointedModel.m_flFrictionTimeHold );
+ fprintf( fp, "}\n" );
+ }
+
+ // block that is only parsed by the editor
+ fprintf( fp, "editparams {\n" );
+ KeyWriteString( fp, "rootname", g_JointedModel.m_rootName );
+ KeyWriteFloat( fp, "totalmass", g_JointedModel.m_totalMass );
+ if ( g_JointedModel.m_allowConcave )
+ {
+ KeyWriteInt( fp, "concave", 1 );
+ }
+ for ( int k = 0; k < g_JointedModel.m_mergeList.Count(); k++ )
+ {
+ char buf[512];
+ Q_snprintf( buf, sizeof(buf), "%s,%s", g_JointedModel.m_mergeList[k].pParent, g_JointedModel.m_mergeList[k].pChild );
+ KeyWriteString( fp, "jointmerge", buf );
+ }
+
+ fprintf( fp, "}\n" );
+
+ char terminator = 0;
+ if ( g_JointedModel.m_textCommands.Size() )
+ {
+ fwrite( g_JointedModel.m_textCommands.Base(), g_JointedModel.m_textCommands.Size(), 1, fp );
+ }
+ fwrite( &terminator, sizeof(terminator), 1, fp );
+ fclose( fp );
+ spFile->Add();
+ }
+ else
+ {
+ MdlWarning("Error writing %s!!!\n", filename );
+ }
+ }
+}
+
+
diff --git a/utils/studiomdl/collisionmodel.h b/utils/studiomdl/collisionmodel.h
new file mode 100644
index 0000000..8a3f37d
--- /dev/null
+++ b/utils/studiomdl/collisionmodel.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef COLLISIONMODEL_H
+#define COLLISIONMODEL_H
+#pragma once
+
+extern void Cmd_CollisionText( void );
+extern int DoCollisionModel( bool separateJoints );
+
+// execute after simplification, before writing
+extern void CollisionModel_Build( void );
+// execute during writing
+extern void CollisionModel_Write( long checkSum );
+
+void CollisionModel_ExpandBBox( Vector &mins, Vector &maxs );
+
+#endif // COLLISIONMODEL_H
diff --git a/utils/studiomdl/dmxsupport.cpp b/utils/studiomdl/dmxsupport.cpp
new file mode 100644
index 0000000..2e83255
--- /dev/null
+++ b/utils/studiomdl/dmxsupport.cpp
@@ -0,0 +1,1213 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Loads mesh data from dmx files
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+#include "studiomdl.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"
+
+
+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];
+
+ // BoneRemap[bone index in file] == bone index in studiomdl
+ int m_pBoneRemap[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;
+ 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];
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the vertices from the model
+//-----------------------------------------------------------------------------
+static bool DefineUniqueVertices( CDmeVertexData *pBindState, int nStartingUniqueCount )
+{
+ 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();
+ 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;
+ }
+
+ // 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 = ( nTexcoordCount > 0 ) ? g_numtexcoords + texcoordIndices[i] : -1;
+ vert.balance = s_Balance.Count() + ( ( nBalanceCount > 0 ) ? balanceIndices[i] : 0 );
+ vert.speed = s_Speed.Count() + ( ( nSpeedCount > 0 ) ? speedIndices[i] : 0 );
+
+ bool unique( true );
+ for ( int j = nStartingUniqueCount; j < s_UniqueVertices.Count(); ++j )
+ {
+ const VertIndices_t &tmpVert( s_UniqueVertices[j] );
+
+ if ( vert.v != tmpVert.v )
+ continue;
+ if ( vert.n != tmpVert.n )
+ continue;
+ if ( vert.t != tmpVert.t )
+ continue;
+
+ unique = false;
+ s_UniqueVerticesMap.AddToTail( j );
+ break;
+ }
+
+ if ( !unique )
+ continue;
+
+ int k = s_UniqueVertices.AddToTail();
+ s_UniqueVertices[k] = vert;
+ s_UniqueVerticesMap.AddToTail( k );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the vertices from the model
+//-----------------------------------------------------------------------------
+static bool LoadVertices( CDmeVertexData *pBindState, const matrix3x4_t& mat, float flScale, int nBoneAssign, int *pBoneRemap, int nStartingUniqueCount )
+{
+ if ( nBoneAssign < 0 )
+ {
+ nBoneAssign = s_nDefaultRootNode;
+ }
+
+ // Used by the morphing system to set up delta states
+ DefineUniqueVertices( pBindState, nStartingUniqueCount );
+
+ 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;
+ }
+
+ // 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] = pBoneRemap[ nBoneAssign ];
+ g_bone[g_numverts].weight[0] = 1.0;
+ }
+ else
+ {
+ const float *pJointWeights = pBindState->GetJointWeightData( i );
+ const int *pJointIndices = pBindState->GetJointIndexData( i );
+
+ float *pWeightBuf = (float*)_alloca( nJointCount * sizeof(float) );
+ int *pIndexBuf = (int*)_alloca( nJointCount * sizeof(int) );
+ memcpy( pWeightBuf, pJointWeights, nJointCount * sizeof(float) );
+ memcpy( pIndexBuf, pJointIndices, nJointCount * sizeof(int) );
+
+ int nBoneCount = SortAndBalanceBones( nJointCount, MAXSTUDIOBONEWEIGHTS, pIndexBuf, pWeightBuf );
+
+ g_bone[g_numverts].numbones = nBoneCount;
+ for ( int j = 0; j < nBoneCount; ++j )
+ {
+ g_bone[g_numverts].bone[j] = pBoneRemap[ pIndexBuf[j] ];
+ g_bone[g_numverts].weight[j] = pWeightBuf[j];
+ }
+ }
+ ++g_numverts;
+ }
+
+ // Copy normals
+ nCount = normals.Count();
+ if ( nCount + g_numnormals > MAXSTUDIOVERTS )
+ {
+ MdlError( "Too many normals in model!\n" );
+ return false;
+ }
+ for ( int i = 0; i < nCount; ++i )
+ {
+ VectorRotate( normals[i], normalMat, g_normal[g_numnormals] );
+ VectorNormalize( g_normal[g_numnormals] );
+ ++g_numnormals;
+ }
+
+ // Copy texcoords
+ nCount = texcoords.Count();
+ if ( nCount + g_numtexcoords > MAXSTUDIOVERTS )
+ {
+ MdlError( "Too many texture coordinates in model!\n" );
+ return false;
+ }
+ bool bFlipVCoordinate = pBindState->IsVCoordinateFlipped();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ g_texcoord[g_numtexcoords].x = texcoords[i].x;
+ g_texcoord[g_numtexcoords].y = bFlipVCoordinate ? 1.0f - texcoords[i].y : texcoords[i].y;
+ ++g_numtexcoords;
+ }
+
+ // 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;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 = ( t >= 0 ) ? ti + 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 = ( t >= 0 ) ? ti + 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 = ( t >= 0 ) ? ti + 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 <= (unsigned long)g_numtexcoords && f.tb <= (unsigned long)g_numtexcoords && f.tc <= (unsigned long)g_numtexcoords );
+
+ Assert( g_numfaces < MAXSTUDIOTRIANGLES-1 );
+ if ( g_numfaces >= MAXSTUDIOTRIANGLES-1 )
+ return;
+
+ int i = g_numfaces++;
+ g_face[i] = f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Reads the mesh data from the DMX data
+//-----------------------------------------------------------------------------
+static bool LoadMesh( 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 nStartingTexCoord = g_numtexcoords;
+ int nStartingUniqueCount = s_UniqueVertices.Count();
+ int nStartingUniqueMapCount = s_UniqueVerticesMap.Count();
+
+ // This defines s_UniqueVertices & s_UniqueVerticesMap
+ LoadVertices( pBindState, mat, flScale, nBoneAssign, pBoneRemap, nStartingUniqueCount );
+
+ // 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 );
+
+ // prepare indices
+ int nFirstIndex = 0;
+ int nIndexCount = pFaceSet->NumIndices();
+ while ( nFirstIndex < nIndexCount )
+ {
+ int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex );
+ if ( nVertexCount >= 3 )
+ {
+ int nOutCount = (nVertexCount-2) * 3;
+ int *pIndices = (int*)_alloca( 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 );
+ }
+ }
+ nFirstIndex += nVertexCount + 1;
+ }
+ }
+
+ 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;
+ CDmeTransform *pDagTransform = pDag->GetTransform();
+ int nFoundIndex = info.m_pModel->GetJointTransformIndex( pDagTransform );
+
+ // Update autoskin to autoskin to non-DmeJoint's if they are children of the DmeModel (i.e. they have no parent bone)
+ if ( nFoundIndex >= 0 )
+ {
+ if ( pDag == info.m_pModel || CastElement< CDmeJoint >( pDag ) )
+ {
+ nBoneAssign = nFoundIndex;
+ }
+ else
+ {
+ for ( int i = 0; i < info.m_pModel->GetChildCount(); ++i )
+ {
+ if ( info.m_pModel->GetChild( i ) == pDag )
+ {
+ nBoneAssign = nFoundIndex;
+ break;
+ }
+ }
+ }
+ }
+
+ 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;
+ pDagTransform->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( 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->GetJointTransformCount();
+ 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() == numvlist );
+ s_vertanim_t *pVertAnim = (s_vertanim_t *)_alloca( 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 != uniqueVert.t )
+ 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 *)kalloc( nVertAnimCount, sizeof( s_vertanim_t ) );
+ memcpy( pSourceAnim->vanim[0], pVertAnim, nVertAnimCount * sizeof( s_vertanim_t ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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();
+ const char *pTransformName = pDmeTransform->GetName();
+ int nJointIndex = boneMap.m_nBoneCount++;
+ if ( nJointIndex >= MAXSTUDIOSRCBONES )
+ {
+ MdlWarning( "DMX Model has too many bones [%d, max can be %d]!\n", boneMap.m_nBoneCount, MAXSTUDIOSRCBONES );
+ return false;
+ }
+
+ boneMap.m_ppTransforms[ nJointIndex ] = pDmeTransform;
+ int nFoundIndex = 0;
+ if ( pModel )
+ {
+ nFoundIndex = pModel->GetJointTransformIndex( pDmeTransform );
+ if ( nFoundIndex >= 0 )
+ {
+ boneMap.m_pBoneRemap[nFoundIndex] = nJointIndex;
+ }
+ }
+
+ Q_strncpy( pNodes[ nJointIndex ].name, pTransformName, sizeof( pNodes[ nJointIndex ].name ) );
+ pNodes[ nJointIndex ].parent = nParentIndex;
+
+ // Now deal with children
+ int nChildCount = pDag->GetChildCount();
+ for ( int i = 0; i < nChildCount; ++i )
+ {
+ CDmeDag *pChild = pDag->GetChild( i );
+ if ( !pChild )
+ continue;
+
+ int nCurrentBoneCount = boneMap.m_nBoneCount;
+ if ( !AddDagJoint( pModel, pChild, pNodes, nJointIndex, boneMap ) )
+ return false;
+
+ if ( ( nCurrentBoneCount != boneMap.m_nBoneCount ) && ( nFoundIndex < 0 ) )
+ {
+ MdlWarning( "DMX Model has a joint \"%s\" which is not in its joint transform list.\n"
+ "This joint has children which are in the joint transform list, which is illegal.\n",
+ pDag->GetName() );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Main entry point for loading the skeleton
+//-----------------------------------------------------------------------------
+static int LoadSkeleton( CDmeDag *pRoot, CDmeModel *pModel, s_node_t *pNodes, BoneTransformMap_t &map )
+{
+ // Initialize bone indices
+ map.m_nBoneCount = 0;
+ for ( int i = 0; i < MAXSTUDIOSRCBONES; ++i )
+ {
+ pNodes[i].name[0] = 0;
+ pNodes[i].parent = -1;
+ map.m_pBoneRemap[i] = -1;
+ map.m_ppTransforms[i] = NULL;
+ }
+
+ // Don't create joints for the the root dag ever.. just deal with the children
+ int nChildCount = pRoot->GetChildCount();
+ for ( int i = 0; i < nChildCount; ++i )
+ {
+ CDmeDag *pChild = pRoot->GetChild( i );
+ if ( !pChild )
+ continue;
+
+ if ( !AddDagJoint( pModel, pChild, pNodes, -1, map ) )
+ return 0;
+ }
+
+ // Add a default identity bone used for autoskinning if no joints are specified
+ s_nDefaultRootNode = map.m_nBoneCount;
+ Q_strncpy( pNodes[s_nDefaultRootNode].name, "defaultRoot", sizeof( pNodes[ s_nDefaultRootNode ].name ) );
+ pNodes[s_nDefaultRootNode].parent = -1;
+
+ if ( !pModel )
+ return map.m_nBoneCount + 1;
+
+ // Look for joints listed in the transform list which aren't in the hierarchy
+ int nInitialBoneCount = pModel->GetJointTransformCount();
+ for ( int i = 0; i < nInitialBoneCount; ++i )
+ {
+ int nIndex = map.m_pBoneRemap[i];
+ if ( nIndex < 0 )
+ {
+ map.m_pBoneRemap[i] = map.m_nBoneCount++;
+ nIndex = map.m_pBoneRemap[i];
+ }
+ if ( pNodes[nIndex].name[0] == 0 )
+ {
+ CDmeTransform *pTransform = pModel->GetJointTransform( i );
+ Q_strncpy( pNodes[ nIndex ].name, pTransform->GetName(), sizeof( pNodes[ nIndex ].name ) );
+ MdlWarning( "Joint %s specified in the joint transform list but doesn't appear in the dag hierarchy!\n", pTransform->GetName() );
+ }
+ }
+ return map.m_nBoneCount + 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the skeletal hierarchy from the game model, returns bone count
+//-----------------------------------------------------------------------------
+static void LoadAttachments( CDmeDag *pRoot, CDmeDag *pDag, s_source_t *pSource )
+{
+ 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 ( 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 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the bind pose
+//-----------------------------------------------------------------------------
+static void LoadBindPose( CDmeModel *pModel, float flScale, int *pBoneRemap, 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 *)kalloc( 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();
+ }
+
+ // Override those bones in the bind pose with the real values
+ // NOTE: This means that bones that are not in the bind pose are set to identity!
+ // Is this correct? I think it shouldn't matter, but we may need to fix this.
+ CDmeTransformList *pBindPose = pModel->FindBaseState( "bind" );
+ int nCount = pBindPose ? pBindPose->GetTransformCount() : pModel->GetJointTransformCount();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ CDmeTransform *pTransform = pBindPose ? pBindPose->GetTransform(i) : pModel->GetJointTransform(i);
+
+ matrix3x4_t jointTransform;
+ pTransform->GetTransform( jointTransform );
+
+ int nActualBoneIndex = pBoneRemap[i];
+ s_bone_t &bone = pSourceAnim->rawanim[0][nActualBoneIndex];
+ MatrixAngles( jointTransform, bone.rot, bone.pos );
+ bone.pos *= flScale;
+ }
+
+ Build_Reference( pSource, "BindPose" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Main entry point for loading DMX files
+//-----------------------------------------------------------------------------
+static void PrepareChannels( CDmeChannelsClip *pAnimation )
+{
+ int nChannelsCount = pAnimation->m_Channels.Count();
+ for ( int i = 0; i < nChannelsCount; ++i )
+ {
+ pAnimation->m_Channels[i]->SetMode( CM_PLAY );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Update channels so they are in position for the next frame
+//-----------------------------------------------------------------------------
+static void UpdateChannels( CDmeChannelsClip *pAnimation, DmeTime_t clipTime )
+{
+ int nChannelsCount = pAnimation->m_Channels.Count();
+ DmeTime_t channelTime = pAnimation->ToChildMediaTime( clipTime );
+ CUtlVector< IDmeOperator* > operators( 0, nChannelsCount );
+ for ( int i = 0; i < nChannelsCount; ++i )
+ {
+ pAnimation->m_Channels[i]->SetCurrentTime( channelTime );
+ operators.AddToTail( pAnimation->m_Channels[i] );
+ }
+
+ // 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 *)kalloc( 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;
+
+ // Prepare channels for playback
+ PrepareChannels( pAnimation );
+
+ float flOOFrameRate = 1.0f / (float)nFrameRateVal;
+ DmeTime_t nTime = nStartTime;
+ 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 );
+
+ // Update the current time
+ UpdateChannels( pAnimation, t );
+
+ // Initialize the pose for this frame
+ 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 );
+ }
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Main entry point for loading DMX files
+//-----------------------------------------------------------------------------
+int Load_DMX( s_source_t *pSource )
+{
+ DmFileId_t fileId;
+ s_DeltaStates.RemoveAll();
+ s_Balance.RemoveAll();
+ s_Speed.RemoveAll();
+ s_UniqueVertices.RemoveAll();
+ s_UniqueVerticesMap.RemoveAll();
+
+ // 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 );
+
+ // Extract out the skeleton
+ CDmeDag *pSkeleton = pRoot->GetValueElement< CDmeDag >( "skeleton" );
+ CDmeModel *pModel = pRoot->GetValueElement< CDmeModel >( "model" );
+ CDmeCombinationOperator *pCombinationOperator = pRoot->GetValueElement< CDmeCombinationOperator >( "combinationOperator" );
+ if ( !pSkeleton )
+ goto dmxError;
+
+ // BoneRemap[bone index in file] == bone index in studiomdl
+ BoneTransformMap_t boneMap;
+ pSource->numbones = LoadSkeleton( pSkeleton, pModel, pSource->localBone, boneMap );
+ if ( pSource->numbones == 0 )
+ goto dmxError;
+
+ LoadAttachments( pSkeleton, pSkeleton, pSource );
+
+ g_numfaces = 0;
+ if ( pModel )
+ {
+ if ( pCombinationOperator )
+ {
+ pCombinationOperator->GenerateWrinkleDeltas( false );
+ }
+ LoadBindPose( pModel, g_currentscale, boneMap.m_pBoneRemap, pSource );
+ if ( !LoadMeshes( pModel, g_currentscale, boneMap.m_pBoneRemap, pSource ) )
+ goto dmxError;
+
+ UnifyIndices( pSource );
+ BuildVertexAnimations( pSource );
+ BuildIndividualMeshes( pSource );
+ }
+
+ if ( pCombinationOperator )
+ {
+ AddFlexKeys( pModel, pModel, pCombinationOperator, pSource );
+ AddCombination( pSource, pCombinationOperator );
+ }
+
+ 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;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/utils/studiomdl/filebuffer.h b/utils/studiomdl/filebuffer.h
new file mode 100644
index 0000000..d7c0a42
--- /dev/null
+++ b/utils/studiomdl/filebuffer.h
@@ -0,0 +1,131 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef FILEBUFFER_H
+#define FILEBUFFER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tier1/smartptr.h"
+#include "tier2/p4helpers.h"
+
+class CFileBuffer
+{
+public:
+ CFileBuffer( int size )
+ {
+ m_pData = new unsigned char[size];
+#ifdef _DEBUG
+ m_pUsed = new const char *[size];
+ memset( m_pUsed, 0, size * sizeof( const char * ) );
+#endif
+ m_Size = size;
+ m_pCurPos = m_pData;
+#ifdef _DEBUG
+ memset( m_pData, 0xbaadf00d, size );
+#endif
+ }
+ ~CFileBuffer()
+ {
+ delete [] m_pData;
+#ifdef _DEBUG
+ delete [] m_pUsed;
+#endif
+ }
+
+#ifdef _DEBUG
+ void TestWritten( int EndOfFileOffset )
+ {
+ if ( !g_quiet )
+ {
+ printf( "testing to make sure that the whole file has been written\n" );
+ }
+ int i;
+ for( i = 0; i < EndOfFileOffset; i++ )
+ {
+ if( !m_pUsed[i] )
+ {
+ printf( "offset %d not written, end of file invalid!\n", i );
+ assert( 0 );
+ }
+ }
+ }
+#endif
+
+ void WriteToFile( const char *fileName, int size )
+ {
+ CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( fileName ) );
+ spFile->Edit();
+ FILE *fp = fopen( fileName, "wb" );
+ if( !fp )
+ {
+ MdlWarning( "Can't open \"%s\" for writing!\n", fileName );
+ return;
+ }
+
+ fwrite( m_pData, 1, size, fp );
+
+ fclose( fp );
+ spFile->Add();
+ }
+
+ void WriteAt( int offset, void *data, int size, const char *name )
+ {
+// printf( "WriteAt: \"%s\" offset: %d end: %d size: %d\n", name, offset, offset + size - 1, size );
+ m_pCurPos = m_pData + offset;
+
+#ifdef _DEBUG
+ int i;
+ const char **used = m_pUsed + offset;
+ bool bitched = false;
+ for( i = 0; i < size; i++ )
+ {
+ if( used[i] )
+ {
+ if( !bitched )
+ {
+ printf( "overwrite at %d! (overwriting \"%s\" with \"%s\")\n", i + offset, used[i], name );
+ assert( 0 );
+ bitched = true;
+ }
+ }
+ else
+ {
+ used[i] = name;
+ }
+ }
+#endif // _DEBUG
+
+ Append( data, size );
+ }
+ int GetOffset( void )
+ {
+ return m_pCurPos - m_pData;
+ }
+ void *GetPointer( int offset )
+ {
+ return m_pData + offset;
+ }
+private:
+ void Append( void *data, int size )
+ {
+ assert( m_pCurPos + size - m_pData < m_Size );
+ memcpy( m_pCurPos, data, size );
+ m_pCurPos += size;
+ }
+ CFileBuffer(); // undefined
+ int m_Size;
+ unsigned char *m_pData;
+ unsigned char *m_pCurPos;
+#ifdef _DEBUG
+ const char **m_pUsed;
+#endif
+};
+
+
+#endif // FILEBUFFER_H
diff --git a/utils/studiomdl/hardwarematrixstate.cpp b/utils/studiomdl/hardwarematrixstate.cpp
new file mode 100644
index 0000000..b49430c
--- /dev/null
+++ b/utils/studiomdl/hardwarematrixstate.cpp
@@ -0,0 +1,232 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include <windows.h>
+#include "HardwareMatrixState.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "studio.h"
+#include "studiomdl.h"
+
+CHardwareMatrixState::CHardwareMatrixState()
+{
+ m_LRUCounter = 0;
+ m_NumMatrices = 0;
+ m_matrixState = NULL;
+ m_savedMatrixState = NULL;
+}
+
+void CHardwareMatrixState::Init( int numHardwareMatrices )
+{
+ m_NumMatrices = numHardwareMatrices;
+ delete [] m_matrixState;
+ m_matrixState = new MatrixState_t[m_NumMatrices];
+ Assert( m_matrixState );
+ delete [] m_savedMatrixState;
+ m_savedMatrixState = new MatrixState_t[m_NumMatrices];
+ Assert( m_savedMatrixState );
+ m_LRUCounter = 0;
+ m_AllocatedMatrices = 0;
+
+ int i;
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ m_matrixState[i].allocated = false;
+ }
+}
+
+bool CHardwareMatrixState::AllocateMatrix( int globalMatrixID )
+{
+ int i;
+
+ if( IsMatrixAllocated( globalMatrixID ) )
+ {
+ return true;
+ }
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( !m_matrixState[i].allocated )
+ {
+ m_matrixState[i].globalMatrixID = globalMatrixID;
+ m_matrixState[i].allocated = true;
+ m_matrixState[i].lastUsageID = m_LRUCounter++;
+ ++m_AllocatedMatrices;
+ DumpState();
+ return true;
+ }
+ }
+ DumpState();
+ return false;
+}
+
+int CHardwareMatrixState::FindLocalLRUIndex( void )
+{
+ int oldestLRUCounter = INT_MAX;
+ int i;
+ int oldestID = 0;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( !m_matrixState[i].allocated )
+ {
+ continue;
+ }
+ if( m_matrixState[i].lastUsageID < oldestLRUCounter )
+ {
+ oldestLRUCounter = m_matrixState[i].lastUsageID;
+ oldestID = i;
+ }
+ }
+
+ Assert( oldestLRUCounter != INT_MAX );
+ return oldestID;
+}
+
+void CHardwareMatrixState::DeallocateLRU( void )
+{
+ int id;
+
+ id = FindLocalLRUIndex();
+ m_matrixState[id].allocated = false;
+ --m_AllocatedMatrices;
+}
+
+void CHardwareMatrixState::DeallocateLRU( int n )
+{
+ int i;
+
+ for( i = 0; i < n; i++ )
+ {
+ DeallocateLRU();
+ }
+}
+
+bool CHardwareMatrixState::IsMatrixAllocated( int globalMatrixID ) const
+{
+ int i;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( m_matrixState[i].globalMatrixID == globalMatrixID &&
+ m_matrixState[i].allocated )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void CHardwareMatrixState::DeallocateAll()
+{
+ int i;
+
+ DumpState();
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ m_matrixState[i].allocated = false;
+ m_matrixState[i].globalMatrixID = INT_MAX;
+ m_matrixState[i].lastUsageID = INT_MAX;
+ }
+ m_AllocatedMatrices = 0;
+ DumpState();
+}
+
+void CHardwareMatrixState::SaveState( void )
+{
+ int i;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ m_savedMatrixState[i] = m_matrixState[i];
+ }
+}
+
+void CHardwareMatrixState::RestoreState( void )
+{
+ int i;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ m_matrixState[i] = m_savedMatrixState[i];
+ }
+}
+
+int CHardwareMatrixState::AllocatedMatrixCount() const
+{
+ return m_AllocatedMatrices;
+}
+
+int CHardwareMatrixState::FreeMatrixCount() const
+{
+ return m_NumMatrices - m_AllocatedMatrices;
+}
+
+int CHardwareMatrixState::GetNthBoneGlobalID( int n ) const
+{
+ int i;
+ int m = 0;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( m_matrixState[i].allocated )
+ {
+ if( n == m )
+ {
+ return m_matrixState[i].globalMatrixID;
+ }
+ m++;
+ }
+ }
+ Assert( 0 );
+ MdlError( "GetNthBoneGlobalID() Failure\n" );
+ return 0;
+}
+
+void CHardwareMatrixState::DumpState( void )
+{
+ int i;
+ static char buf[256];
+
+//#ifndef _DEBUG
+ return;
+//#endif
+
+ OutputDebugString( "DumpState\n:" );
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( m_matrixState[i].allocated )
+ {
+ sprintf( buf, "%d: allocated: %s lastUsageID: %d globalMatrixID: %d\n",
+ i,
+ m_matrixState[i].allocated ? "true " : "false",
+ m_matrixState[i].lastUsageID,
+ m_matrixState[i].globalMatrixID );
+ OutputDebugString( buf );
+ }
+ }
+}
+
+int CHardwareMatrixState::FindHardwareMatrix( int globalMatrixID )
+{
+ int i;
+
+ for( i = 0; i < m_NumMatrices; i++ )
+ {
+ if( m_matrixState[i].globalMatrixID == globalMatrixID )
+ {
+ return i;
+ }
+ }
+
+ Assert( 0 );
+ MdlError( "barfing in FindHardwareMatrix\n" );
+
+ return 0;
+}
+
diff --git a/utils/studiomdl/hardwarematrixstate.h b/utils/studiomdl/hardwarematrixstate.h
new file mode 100644
index 0000000..47673df
--- /dev/null
+++ b/utils/studiomdl/hardwarematrixstate.h
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HARDWAREMATRIXSTATE_H
+#define HARDWAREMATRIXSTATE_H
+#pragma once
+
+// This emulates the hardware matrix palette and keeps up with
+// matrix usage, LRU's matrices, etc.
+class CHardwareMatrixState
+{
+public:
+ CHardwareMatrixState();
+
+ void Init( int numHardwareMatrices );
+
+ // return false if there is no slot for this matrix.
+ bool AllocateMatrix( int globalMatrixID );
+
+ // deallocate the least recently used matrix
+ void DeallocateLRU( void );
+ void DeallocateLRU( int n );
+
+ // return true if a matrix is allocate.
+ bool IsMatrixAllocated( int globalMatrixID ) const;
+
+ // flush usage flags - signifies that none of the matrices are being used in the current strip
+ // do this when starting a new strip.
+ void SetAllUnused();
+
+ void DeallocateAll();
+
+ // save the complete state of the hardware matrices
+ void SaveState();
+
+ // restore the complete state of the hardware matrices
+ void RestoreState();
+
+ // Returns the number of free + unsed matrices
+ int AllocatedMatrixCount() const;
+ int FreeMatrixCount() const;
+
+ int GetNthBoneGlobalID( int n ) const;
+
+ void DumpState( void );
+
+private:
+ int FindHardwareMatrix( int globalMatrixID );
+ int FindLocalLRUIndex( void );
+
+ // Increment and return LRU counter.
+ struct MatrixState_t
+ {
+ bool allocated;
+ int lastUsageID;
+ int globalMatrixID;
+ };
+
+ int m_LRUCounter;
+ int m_NumMatrices;
+ int m_AllocatedMatrices;
+
+ MatrixState_t *m_matrixState;
+ MatrixState_t *m_savedMatrixState;
+};
+
+#endif // HARDWAREMATRIXSTATE_H
diff --git a/utils/studiomdl/hardwarevertexcache.cpp b/utils/studiomdl/hardwarevertexcache.cpp
new file mode 100644
index 0000000..1c5a41c
--- /dev/null
+++ b/utils/studiomdl/hardwarevertexcache.cpp
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "HardwareVertexCache.h"
+
+CHardwareVertexCache::CHardwareVertexCache()
+{
+ m_Fifo = NULL;
+ m_Size = 0;
+ Flush();
+}
+
+void CHardwareVertexCache::Init( int size )
+{
+ m_Size = size;
+ m_Fifo = new int[size];
+ Flush();
+}
+
+void CHardwareVertexCache::Flush( void )
+{
+ m_HeadIndex = 0;
+ m_NumEntries = 0;
+}
+
+bool CHardwareVertexCache::IsPresent( int index )
+{
+ int i;
+// printf( "testing if %d is present\n", index );
+ for( i = 0; i < m_NumEntries; i++ )
+ {
+ if( m_Fifo[( m_HeadIndex + i ) % m_Size] == index )
+ {
+// printf( "yes!\n" );
+ return true;
+ }
+ }
+// printf( "no!\n" );
+// Print();
+ return false;
+}
+
+void CHardwareVertexCache::Insert( int index )
+{
+// printf( "Inserting: %d\n", index );
+ m_Fifo[( m_HeadIndex + m_NumEntries ) % m_Size] = index;
+ if( m_NumEntries == m_Size )
+ {
+ m_HeadIndex = ( m_HeadIndex + 1 ) % m_Size;
+ }
+ else
+ {
+ m_NumEntries++;
+ }
+// Print();
+}
+
+void CHardwareVertexCache::Print( void )
+{
+ int i;
+ for( i = 0; i < m_NumEntries; i++ )
+ {
+ printf( "fifo entry %d: %d\n", i, ( int )m_Fifo[( m_HeadIndex + i ) % m_Size] );
+ }
+}
diff --git a/utils/studiomdl/hardwarevertexcache.h b/utils/studiomdl/hardwarevertexcache.h
new file mode 100644
index 0000000..a9eb2a5
--- /dev/null
+++ b/utils/studiomdl/hardwarevertexcache.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HARDWAREVERTEXCACHE_H
+#define HARDWAREVERTEXCACHE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// emulate a hardware post T&L vertex fifo
+
+class CHardwareVertexCache
+{
+public:
+ CHardwareVertexCache();
+ void Init( int size );
+ void Insert( int index );
+ bool IsPresent( int index );
+ void Flush( void );
+ void Print( void );
+private:
+ int m_Size;
+ int *m_Fifo;
+ int m_HeadIndex;
+ int m_NumEntries;
+};
+
+#endif // HARDWAREVERTEXCACHE_H
diff --git a/utils/studiomdl/mrmsupport.cpp b/utils/studiomdl/mrmsupport.cpp
new file mode 100644
index 0000000..60cfcb1
--- /dev/null
+++ b/utils/studiomdl/mrmsupport.cpp
@@ -0,0 +1,882 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// models/<scriptname>.mdl.
+//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+//#include "..\..\dlls\activity.h"
+
+bool IsEnd( char const* pLine )
+{
+ if (strncmp( "end", pLine, 3 ) != 0)
+ return false;
+ return (pLine[3] == '\0') || (pLine[3] == '\n');
+}
+
+
+int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] )
+{
+ int i;
+
+ // collapse duplicate bone weights
+ for (i = 0; i < iCount-1; i++)
+ {
+ int j;
+ for (j = i + 1; j < iCount; j++)
+ {
+ if (bones[i] == bones[j])
+ {
+ weights[i] += weights[j];
+ weights[j] = 0.0;
+ }
+ }
+ }
+
+ // do sleazy bubble sort
+ int bShouldSort;
+ do {
+ bShouldSort = false;
+ for (i = 0; i < iCount-1; i++)
+ {
+ if (weights[i+1] > weights[i])
+ {
+ int j = bones[i+1]; bones[i+1] = bones[i]; bones[i] = j;
+ float w = weights[i+1]; weights[i+1] = weights[i]; weights[i] = w;
+ bShouldSort = true;
+ }
+ }
+ } while (bShouldSort);
+
+ // throw away all weights less than 1/20th
+ while (iCount > 1 && weights[iCount-1] < 0.05)
+ {
+ iCount--;
+ }
+
+ // clip to the top iMaxCount bones
+ if (iCount > iMaxCount)
+ {
+ iCount = iMaxCount;
+ }
+
+ float t = 0;
+ for (i = 0; i < iCount; i++)
+ {
+ t += weights[i];
+ }
+
+ if (t <= 0.0)
+ {
+ // missing weights?, go ahead and evenly share?
+ // FIXME: shouldn't this error out?
+ t = 1.0 / iCount;
+
+ for (i = 0; i < iCount; i++)
+ {
+ weights[i] = t;
+ }
+ }
+ else
+ {
+ // scale to sum to 1.0
+ t = 1.0 / t;
+
+ for (i = 0; i < iCount; i++)
+ {
+ weights[i] = weights[i] * t;
+ }
+ }
+
+ return iCount;
+}
+
+
+
+void Grab_Vertexlist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ int j;
+ int bone;
+ Vector p;
+ int iCount, bones[4];
+ float weights[4];
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+
+ int i = sscanf( g_szLine, "%d %d %f %f %f %d %d %f %d %f %d %f %d %f",
+ &j,
+ &bone,
+ &p[0], &p[1], &p[2],
+ &iCount,
+ &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] );
+
+ if (i == 5)
+ {
+ if (bone < 0 || bone >= psource->numbones)
+ {
+ MdlWarning( "bogus bone index\n" );
+ MdlWarning( "%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine );
+ MdlError( "Exiting due to errors\n" );
+ }
+
+ VectorCopy( p, g_vertex[j] );
+ g_bone[j].numbones = 1;
+ g_bone[j].bone[0] = bone;
+ g_bone[j].weight[0] = 1.0;
+ }
+ else if (i > 5)
+ {
+ iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights );
+
+ VectorCopy( p, g_vertex[j] );
+ g_bone[j].numbones = iCount;
+ for (i = 0; i < iCount; i++)
+ {
+ g_bone[j].bone[i] = bones[i];
+ g_bone[j].weight[i] = weights[i];
+ }
+ }
+ else
+ {
+ MdlError("%s: error on line %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+
+void Grab_Facelist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ int j;
+ s_tmpface_t f;
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+ if (sscanf( g_szLine, "%d %d %d %d",
+ &j,
+ &f.a, &f.b, &f.c) == 4)
+ {
+ g_face[j] = f;
+ }
+ else
+ {
+ MdlError("%s: error on line %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+
+void Grab_Materiallist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ // char name[256];
+ char path[MAX_PATH];
+ rgb2_t a, d, s;
+ float g;
+ int j;
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+ if (sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %f %f %f %f %f \"%[^\"]s",
+ &j,
+ &a.r, &a.g, &a.b, &a.a,
+ &d.r, &d.g, &d.b, &d.a,
+ &s.r, &s.g, &s.b, &s.a,
+ &g,
+ path ) == 15)
+ {
+ if (path[0] == '\0')
+ {
+ psource->texmap[j] = -1;
+ }
+ else if (j < ARRAYSIZE(psource->texmap))
+ {
+ psource->texmap[j] = LookupTexture( path );
+ }
+ else
+ {
+ MdlError( "Too many materials, max %d\n", ARRAYSIZE(psource->texmap) );
+ }
+ }
+ }
+ }
+}
+
+
+void Grab_Texcoordlist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ int j;
+ Vector2D t;
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+ if (sscanf( g_szLine, "%d %f %f",
+ &j,
+ &t[0], &t[1]) == 3)
+ {
+ t[1] = 1.0 - t[1];
+ g_texcoord[j][0] = t[0];
+ g_texcoord[j][1] = t[1];
+ }
+ else
+ {
+ MdlError("%s: error on line %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+void Grab_Normallist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ int j;
+ int bone;
+ Vector n;
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+
+ if (sscanf( g_szLine, "%d %d %f %f %f",
+ &j,
+ &bone,
+ &n[0], &n[1], &n[2]) == 5)
+ {
+ if (bone < 0 || bone >= psource->numbones)
+ {
+ MdlWarning( "bogus bone index\n" );
+ MdlWarning( "%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine );
+ MdlError( "Exiting due to errors\n" );
+ }
+
+ VectorCopy( n, g_normal[j] );
+ }
+ else
+ {
+ MdlError("%s: error on line %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+
+void Grab_Faceattriblist( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ int j;
+ int smooth;
+ int material;
+ s_tmpface_t f;
+ unsigned short s;
+
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+
+ if (sscanf( g_szLine, "%d %d %d %d %d %d %d %d %d",
+ &j,
+ &material,
+ &smooth,
+ &f.ta, &f.tb, &f.tc,
+ &f.na, &f.nb, &f.nc) == 9)
+ {
+ f.a = g_face[j].a;
+ f.b = g_face[j].b;
+ f.c = g_face[j].c;
+
+ f.material = UseTextureAsMaterial( psource->texmap[material] );
+ if (f.material < 0)
+ {
+ MdlError( "face %d references NULL texture %d\n", j, material );
+ }
+
+ if (1)
+ {
+ s = f.b; f.b = f.c; f.c = s;
+ s = f.tb; f.tb = f.tc; f.tc = s;
+ s = f.nb; f.nb = f.nc; f.nc = s;
+ }
+
+ g_face[j] = f;
+ }
+ else
+ {
+ MdlError("%s: error on line %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+int closestNormal( int v, int n )
+{
+ float maxdot = -1.0;
+ float dot;
+ int r = n;
+
+ v_unify_t *cur = v_list[v];
+
+ while (cur)
+ {
+ dot = DotProduct( g_normal[cur->n], g_normal[n] );
+ if (dot > maxdot)
+ {
+ r = cur->n;
+ maxdot = dot;
+ }
+ cur = cur->next;
+ }
+
+ return r;
+}
+
+
+int AddToVlist( int v, int m, int n, int t, int firstref )
+{
+ v_unify_t *prev = NULL;
+ v_unify_t *cur = v_list[v];
+
+ while (cur)
+ {
+ if (cur->m == m && cur->n == n && cur->t == t)
+ {
+ cur->refcount++;
+ return cur - v_listdata;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+
+ if (numvlist >= MAXSTUDIOVERTS)
+ {
+ MdlError( "Too many unified vertices\n");
+ }
+
+ cur = &v_listdata[numvlist++];
+ cur->lastref = -1;
+ cur->refcount = 1;
+ cur->firstref = firstref;
+ cur->v = v;
+ cur->m = m;
+ cur->n = n;
+ cur->t = t;
+
+ if (prev)
+ {
+ prev->next = cur;
+ }
+ else
+ {
+ v_list[v] = cur;
+ }
+
+ return numvlist - 1;
+}
+
+void DecrementReferenceVlist( int uv, int numverts )
+{
+ if (uv < 0 || uv >= MAXSTUDIOVERTS)
+ MdlError( "decrement outside of range\n");
+
+ v_listdata[uv].refcount--;
+
+ if (v_listdata[uv].refcount == 0)
+ {
+ v_listdata[uv].lastref = numverts;
+ }
+ else if (v_listdata[uv].refcount < 0)
+ {
+ MdlError("<0 ref\n");
+ }
+}
+
+
+void UnifyIndices( s_source_t *psource )
+{
+ int i;
+
+ static s_tmpface_t tmpface[MAXSTUDIOTRIANGLES]; // mrm processed g_face
+ static s_face_t uface[MAXSTUDIOTRIANGLES]; // mrm processed unified face
+
+ // clear v_list
+ numvlist = 0;
+ memset( v_list, 0, sizeof( v_list ) );
+ memset( v_listdata, 0, sizeof( v_listdata ) );
+
+ // create an list of all the
+ for (i = 0; i < g_numfaces; i++)
+ {
+ tmpface[i] = g_face[i];
+
+ uface[i].a = AddToVlist( g_face[i].a, g_face[i].material, g_face[i].na, g_face[i].ta, g_numverts );
+ uface[i].b = AddToVlist( g_face[i].b, g_face[i].material, g_face[i].nb, g_face[i].tb, g_numverts );
+ uface[i].c = AddToVlist( g_face[i].c, g_face[i].material, g_face[i].nc, g_face[i].tc, g_numverts );
+
+ // keep an original copy
+ g_src_uface[i] = uface[i];
+ }
+
+ // printf("%d : %d %d %d\n", numvlist, g_numverts, g_numnormals, g_numtexcoords );
+}
+
+void CalcModelTangentSpaces( s_source_t *pSrc );
+
+
+//-----------------------------------------------------------------------------
+// Builds a list of unique vertices in a source
+//-----------------------------------------------------------------------------
+static void BuildUniqueVertexList( s_source_t *pSource, const int *pDesiredToVList )
+{
+ // allocate memory
+ pSource->vertex = (s_vertexinfo_t *)kalloc( pSource->numvertices, sizeof( s_vertexinfo_t ) );
+
+ // create arrays of unique vertexes, normals, texcoords.
+ for (int i = 0; i < pSource->numvertices; i++)
+ {
+ int j = pDesiredToVList[i];
+
+ s_vertexinfo_t &vertex = pSource->vertex[i];
+ VectorCopy( g_vertex[ v_listdata[j].v ], vertex.position );
+ VectorCopy( g_normal[ v_listdata[j].n ], vertex.normal );
+ Vector2Copy( g_texcoord[ v_listdata[j].t ], vertex.texcoord );
+
+ vertex.boneweight.numbones = g_bone[ v_listdata[j].v ].numbones;
+ int k;
+ for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ )
+ {
+ vertex.boneweight.bone[k] = g_bone[ v_listdata[j].v ].bone[k];
+ vertex.boneweight.weight[k] = g_bone[ v_listdata[j].v ].weight[k];
+ }
+
+ // store a bunch of other info
+ vertex.material = v_listdata[j].m;
+
+#if 0
+ pSource->vertexInfo[i].firstref = v_listdata[j].firstref;
+ pSource->vertexInfo[i].lastref = v_listdata[j].lastref;
+#endif
+ // printf("%4d : %2d : %6.2f %6.2f %6.2f\n", i, psource->boneweight[i].bone[0], psource->vertex[i][0], psource->vertex[i][1], psource->vertex[i][2] );
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// sort new vertices by materials, last used
+//-----------------------------------------------------------------------------
+static int vlistCompare( const void *elem1, const void *elem2 )
+{
+ v_unify_t *u1 = &v_listdata[*(int *)elem1];
+ v_unify_t *u2 = &v_listdata[*(int *)elem2];
+
+ // sort by material
+ if (u1->m < u2->m)
+ return -1;
+ if (u1->m > u2->m)
+ return 1;
+
+ // sort by last used
+ if (u1->lastref < u2->lastref)
+ return -1;
+ if (u1->lastref > u2->lastref)
+ return 1;
+
+ return 0;
+}
+
+static void SortVerticesByMaterial( int *pDesiredToVList, int *pVListToDesired )
+{
+ for ( int i = 0; i < numvlist; i++ )
+ {
+ pDesiredToVList[i] = i;
+ }
+ qsort( pDesiredToVList, numvlist, sizeof( int ), vlistCompare );
+ for ( int i = 0; i < numvlist; i++ )
+ {
+ pVListToDesired[ pDesiredToVList[i] ] = i;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// sort new faces by materials, last used
+//-----------------------------------------------------------------------------
+static int faceCompare( const void *elem1, const void *elem2 )
+{
+ int i1 = *(int *)elem1;
+ int i2 = *(int *)elem2;
+
+ // sort by material
+ if (g_face[i1].material < g_face[i2].material)
+ return -1;
+ if (g_face[i1].material > g_face[i2].material)
+ return 1;
+
+ // sort by original usage
+ if (i1 < i2)
+ return -1;
+ if (i1 > i2)
+ return 1;
+
+ return 0;
+}
+
+static void SortFacesByMaterial( int *pDesiredToSrcFace )
+{
+ // NOTE: Unlike SortVerticesByMaterial, srcFaceToDesired isn't needed, so we're not computing it
+ for ( int i = 0; i < g_numfaces; i++ )
+ {
+ pDesiredToSrcFace[i] = i;
+ }
+ qsort( pDesiredToSrcFace, g_numfaces, sizeof( int ), faceCompare );
+}
+
+
+//-----------------------------------------------------------------------------
+// Builds mesh structures in the source
+//-----------------------------------------------------------------------------
+static void PointMeshesToVertexAndFaceData( s_source_t *pSource, int *pDesiredToSrcFace )
+{
+ // First, assign all meshes to be empty
+ // A mesh is a set of faces + vertices that all use 1 material
+ for ( int m = 0; m < MAXSTUDIOSKINS; m++ )
+ {
+ pSource->mesh[m].numvertices = 0;
+ pSource->mesh[m].vertexoffset = pSource->numvertices;
+
+ pSource->mesh[m].numfaces = 0;
+ pSource->mesh[m].faceoffset = pSource->numfaces;
+ }
+
+ // find first and count of vertices per material
+ for ( int i = 0; i < pSource->numvertices; i++ )
+ {
+ int m = pSource->vertex[i].material;
+ pSource->mesh[m].numvertices++;
+ if (pSource->mesh[m].vertexoffset > i)
+ {
+ pSource->mesh[m].vertexoffset = i;
+ }
+ }
+
+ // find first and count of faces per material
+ for ( int i = 0; i < pSource->numfaces; i++ )
+ {
+ int m = g_face[ pDesiredToSrcFace[i] ].material;
+
+ pSource->mesh[m].numfaces++;
+ if (pSource->mesh[m].faceoffset > i)
+ {
+ pSource->mesh[m].faceoffset = i;
+ }
+ }
+
+ /*
+ for (k = 0; k < MAXSTUDIOSKINS; k++)
+ {
+ printf("%d : %d:%d %d:%d\n", k, psource->mesh[k].numvertices, psource->mesh[k].vertexoffset, psource->mesh[k].numfaces, psource->mesh[k].faceoffset );
+ }
+ */
+}
+
+
+//-----------------------------------------------------------------------------
+// Builds the face list in the mesh
+//-----------------------------------------------------------------------------
+static void BuildFaceList( s_source_t *pSource, int *pVListToDesired, int *pDesiredToSrcFace )
+{
+ pSource->face = (s_face_t *)kalloc( pSource->numfaces, sizeof( s_face_t ));
+ for ( int m = 0; m < MAXSTUDIOSKINS; m++)
+ {
+ if ( !pSource->mesh[m].numfaces )
+ continue;
+
+ pSource->meshindex[ pSource->nummeshes++ ] = m;
+
+ for ( int i = pSource->mesh[m].faceoffset; i < pSource->mesh[m].numfaces + pSource->mesh[m].faceoffset; i++)
+ {
+ int j = pDesiredToSrcFace[i];
+
+ // NOTE: per-face vertex indices a,b,c are mesh relative (hence the subtraction),
+ // while g_src_uface are model relative
+ pSource->face[i].a = pVListToDesired[ g_src_uface[j].a ] - pSource->mesh[m].vertexoffset;
+ pSource->face[i].b = pVListToDesired[ g_src_uface[j].b ] - pSource->mesh[m].vertexoffset;
+ pSource->face[i].c = pVListToDesired[ g_src_uface[j].c ] - pSource->mesh[m].vertexoffset;
+ Assert( ((pSource->face[i].a & 0xF0000000) == 0) && ((pSource->face[i].b & 0xF0000000) == 0) &&
+ ((pSource->face[i].c & 0xF0000000) == 0) );
+ // printf("%3d : %4d %4d %4d\n", i, pSource->face[i].a, pSource->face[i].b, pSource->face[i].c );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Remaps the vertex animations based on the new vertex ordering
+//-----------------------------------------------------------------------------
+static void RemapVertexAnimations( s_source_t *pSource, int *pVListToDesired )
+{
+ int nAnimationCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nAnimationCount; ++i )
+ {
+ s_sourceanim_t &anim = pSource->m_Animations[i];
+ if ( !anim.newStyleVertexAnimations )
+ continue;
+
+ for ( int j = 0; j < MAXSTUDIOANIMFRAMES; ++j )
+ {
+ int nVAnimCount = anim.numvanims[j];
+ if ( nVAnimCount == 0 )
+ continue;
+
+ // Copy off the initial vertex data
+ // Have to do it in 2 loops because it'll overwrite itself if we do it in 1
+ int *pTemp = (int*)_alloca( nVAnimCount * sizeof(int) );
+ for ( int k = 0; k < nVAnimCount; ++k )
+ {
+ pTemp[k] = anim.vanim[j][k].vertex;
+ }
+
+ for ( int k = 0; k < nVAnimCount; ++k )
+ {
+ // NOTE: vertex animations are model relative, not mesh relative
+ anim.vanim[j][k].vertex = pVListToDesired[ pTemp[k] ];
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Sorts vertices by material type, re-maps data structures that refer to those vertices
+// to use the new indices
+//-----------------------------------------------------------------------------
+void BuildIndividualMeshes( s_source_t *pSource )
+{
+ static int v_listsort[MAXSTUDIOVERTS]; // map desired order to vlist entry
+ static int v_ilistsort[MAXSTUDIOVERTS]; // map vlist entry to desired order
+ static int facesort[MAXSTUDIOTRIANGLES]; // map desired order to src_face entry
+
+ SortVerticesByMaterial( v_listsort, v_ilistsort );
+ SortFacesByMaterial( facesort );
+
+ pSource->numvertices = numvlist;
+ pSource->numfaces = g_numfaces;
+
+ BuildUniqueVertexList( pSource, v_listsort );
+ PointMeshesToVertexAndFaceData( pSource, facesort );
+ BuildFaceList( pSource, v_ilistsort, facesort );
+ RemapVertexAnimations( pSource, v_ilistsort );
+ CalcModelTangentSpaces( pSource );
+}
+
+
+void Grab_MRMFaceupdates( s_source_t *psource )
+{
+ while (1)
+ {
+ if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ g_iLinecount++;
+
+ // check for end
+ if (IsEnd(g_szLine))
+ return;
+ }
+ }
+}
+
+int Load_VRM ( s_source_t *psource )
+{
+ char cmd[1024];
+ int option;
+
+ if (!OpenGlobalFile( psource->filename ))
+ {
+ return 0;
+ }
+
+ if( !g_quiet )
+ {
+ printf ("grabbing %s\n", psource->filename);
+ }
+
+ g_iLinecount = 0;
+
+ while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ g_iLinecount++;
+ sscanf( g_szLine, "%1023s %d", cmd, &option );
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option != 2)
+ {
+ MdlError("bad version\n");
+ }
+ }
+ else if (stricmp( cmd, "name" ) == 0)
+ {
+ }
+ else if (stricmp( cmd, "vertices" ) == 0)
+ {
+ g_numverts = option;
+ }
+ else if (stricmp( cmd, "faces" ) == 0)
+ {
+ g_numfaces = option;
+ }
+ else if (stricmp( cmd, "materials" ) == 0)
+ {
+ // doesn't matter;
+ }
+ else if (stricmp( cmd, "texcoords" ) == 0)
+ {
+ g_numtexcoords = option;
+ if (option == 0)
+ MdlError( "model has no texture coordinates\n");
+ }
+ else if (stricmp( cmd, "normals" ) == 0)
+ {
+ g_numnormals = option;
+ }
+ else if (stricmp( cmd, "tristrips" ) == 0)
+ {
+ // should be 0;
+ }
+
+ else if (stricmp( cmd, "vertexlist" ) == 0)
+ {
+ Grab_Vertexlist( psource );
+ }
+ else if (stricmp( cmd, "facelist" ) == 0)
+ {
+ Grab_Facelist( psource );
+ }
+ else if (stricmp( cmd, "materiallist" ) == 0)
+ {
+ Grab_Materiallist( psource );
+ }
+ else if (stricmp( cmd, "texcoordlist" ) == 0)
+ {
+ Grab_Texcoordlist( psource );
+ }
+ else if (stricmp( cmd, "normallist" ) == 0)
+ {
+ Grab_Normallist( psource );
+ }
+ else if (stricmp( cmd, "faceattriblist" ) == 0)
+ {
+ Grab_Faceattriblist( psource );
+ }
+
+ else if (stricmp( cmd, "MRM" ) == 0)
+ {
+ }
+ else if (stricmp( cmd, "MRMvertices" ) == 0)
+ {
+ }
+ else if (stricmp( cmd, "MRMfaces" ) == 0)
+ {
+ }
+ else if (stricmp( cmd, "MRMfaceupdates" ) == 0)
+ {
+ Grab_MRMFaceupdates( psource );
+ }
+
+ else if (stricmp( cmd, "nodes" ) == 0)
+ {
+ psource->numbones = Grab_Nodes( psource->localBone );
+ }
+ else if (stricmp( cmd, "skeleton" ) == 0)
+ {
+ Grab_Animation( psource, "BindPose" );
+ }
+/*
+ else if (stricmp( cmd, "triangles" ) == 0) {
+ Grab_Triangles( psource );
+ }
+*/
+ else
+ {
+ MdlError("unknown VRM command : %s \n", cmd );
+ }
+ }
+
+ UnifyIndices( psource );
+ BuildIndividualMeshes( psource );
+
+ fclose( g_fpInput );
+
+ return 1;
+}
+
diff --git a/utils/studiomdl/objsupport.cpp b/utils/studiomdl/objsupport.cpp
new file mode 100644
index 0000000..776e5bc
--- /dev/null
+++ b/utils/studiomdl/objsupport.cpp
@@ -0,0 +1,432 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// models/<scriptname>.mdl.
+//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include "tier1/utlbuffer.h"
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "tier1/characterset.h"
+#include "studiomdl.h"
+//#include "..\..\dlls\activity.h"
+
+bool IsEnd( char const* pLine );
+int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] );
+
+int AddToVlist( int v, int m, int n, int t, int firstref );
+void DecrementReferenceVlist( int uv, int numverts );
+int faceCompare( const void *elem1, const void *elem2 );
+
+
+void UnifyIndices( s_source_t *psource );
+
+struct MtlInfo_t
+{
+ CUtlString m_MtlName;
+ CUtlString m_TgaName;
+};
+
+static CUtlVector<MtlInfo_t> g_MtlLib;
+
+void ParseMtlLib( CUtlBuffer &buf )
+{
+ int nCurrentMtl = -1;
+ while ( buf.IsValid() )
+ {
+ buf.GetLine( g_szLine, sizeof(g_szLine) );
+
+ if ( !Q_strnicmp( g_szLine, "newmtl ", 7 ) )
+ {
+ char mtlName[1024];
+ if ( sscanf( g_szLine, "newmtl %s", mtlName ) == 1 )
+ {
+ nCurrentMtl = g_MtlLib.AddToTail( );
+ g_MtlLib[nCurrentMtl].m_MtlName = mtlName;
+ g_MtlLib[nCurrentMtl].m_TgaName = "debugempty";
+ }
+ continue;
+ }
+
+ if ( !Q_strnicmp( g_szLine, "map_Kd ", 7 ) )
+ {
+ if ( nCurrentMtl < 0 )
+ continue;
+
+ char tgaPath[MAX_PATH];
+ char tgaName[1024];
+ if ( sscanf( g_szLine, "map_Kd %s", tgaPath ) == 1 )
+ {
+ Q_FileBase( tgaPath, tgaName, sizeof(tgaName) );
+ g_MtlLib[nCurrentMtl].m_TgaName = tgaName;
+ }
+ continue;
+ }
+ }
+}
+
+const char *FindMtlEntry( const char *pTgaName )
+{
+ int nCount = g_MtlLib.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !Q_stricmp( g_MtlLib[i].m_MtlName, pTgaName ) )
+ return g_MtlLib[i].m_TgaName;
+ }
+ return pTgaName;
+}
+
+static bool ParseVertex( CUtlBuffer& bufParse, characterset_t &breakSet, int &v, int &t, int &n )
+{
+ char cmd[1024];
+ int nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
+ if ( nLen <= 0 )
+ return false;
+
+ v = atoi( cmd );
+ n = 0;
+ t = 0;
+
+ char c = *(char*)bufParse.PeekGet();
+ bool bHasTexCoord = IN_CHARACTERSET( breakSet, c ) != 0;
+ bool bHasNormal = false;
+ if ( bHasTexCoord )
+ {
+ // Snag the '/'
+ nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
+ Assert( nLen == 1 );
+
+ c = *(char*)bufParse.PeekGet();
+ if ( !IN_CHARACTERSET( breakSet, c ) )
+ {
+ nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
+ Assert( nLen > 0 );
+ t = atoi( cmd );
+
+ c = *(char*)bufParse.PeekGet();
+ bHasNormal = IN_CHARACTERSET( breakSet, c ) != 0;
+ }
+ else
+ {
+ bHasNormal = true;
+ bHasTexCoord = false;
+ }
+
+ if ( bHasNormal )
+ {
+ // Snag the '/'
+ nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
+ Assert( nLen == 1 );
+
+ nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false );
+ Assert( nLen > 0 );
+ n = atoi( cmd );
+ }
+ }
+ return true;
+}
+
+
+int Load_OBJ( s_source_t *psource )
+{
+ char cmd[1024];
+ int i;
+ int material = -1;
+
+ g_MtlLib.RemoveAll();
+
+ if ( !OpenGlobalFile( psource->filename ) )
+ return 0;
+
+ char pFullPath[MAX_PATH];
+ if ( !GetGlobalFilePath( psource->filename, pFullPath, sizeof(pFullPath) ) )
+ return 0;
+
+ char pFullDir[MAX_PATH];
+ Q_ExtractFilePath( pFullPath, pFullDir, sizeof(pFullDir) );
+
+ if( !g_quiet )
+ {
+ printf( "grabbing %s\n", psource->filename );
+ }
+
+ g_iLinecount = 0;
+
+ psource->numbones = 1;
+ strcpy( psource->localBone[0].name, "default" );
+ psource->localBone[0].parent = -1;
+ Assert( psource->m_Animations.Count() == 0 );
+ s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( psource, "BindPose" );
+ pSourceAnim->numframes = 1;
+ pSourceAnim->startframe = 0;
+ pSourceAnim->endframe = 0;
+ pSourceAnim->rawanim[0] = (s_bone_t *)kalloc( 1, sizeof( s_bone_t ) );
+ pSourceAnim->rawanim[0][0].pos.Init();
+ pSourceAnim->rawanim[0][0].rot.Init();
+ Build_Reference( psource, "BindPose" );
+
+ characterset_t breakSet;
+ CharacterSetBuild( &breakSet, "/\\" );
+
+ while ( GetLineInput() )
+ {
+ Vector tmp;
+
+ if ( strncmp( g_szLine, "v ", 2 ) == 0 )
+ {
+ i = g_numverts++;
+
+ sscanf( g_szLine, "v %f %f %f", &g_vertex[i].x, &g_vertex[i].y, &g_vertex[i].z );
+ g_bone[i].numbones = 1;
+ g_bone[i].bone[0] = 0;
+ g_bone[i].weight[0] = 1.0;
+ continue;
+ }
+
+ if (strncmp( g_szLine, "vn ", 3 ) == 0)
+ {
+ i = g_numnormals++;
+ sscanf( g_szLine, "vn %f %f %f", &g_normal[i].x, &g_normal[i].y, &g_normal[i].z );
+ continue;
+ }
+
+ if (strncmp( g_szLine, "vt ", 3 ) == 0)
+ {
+ i = g_numtexcoords++;
+ sscanf( g_szLine, "vt %f %f", &g_texcoord[i].x, &g_texcoord[i].y );
+ g_texcoord[i].y = 1.0 - g_texcoord[i].y;
+ continue;
+ }
+
+ if ( !Q_strncmp( g_szLine, "mtllib ", 7 ) )
+ {
+ sscanf( g_szLine, "mtllib %s", &cmd[0] );
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ char pFullMtlLibPath[MAX_PATH];
+ Q_ComposeFileName( pFullDir, cmd, pFullMtlLibPath, sizeof(pFullMtlLibPath) );
+ if ( g_pFullFileSystem->ReadFile( pFullMtlLibPath, NULL, buf ) )
+ {
+ ParseMtlLib( buf );
+ }
+ continue;
+ }
+
+ if (strncmp( g_szLine, "usemtl ", 7 ) == 0)
+ {
+ sscanf( g_szLine, "usemtl %s", &cmd[0] );
+
+ const char *pTexture = FindMtlEntry( cmd );
+ int texture = LookupTexture( pTexture );
+ psource->texmap[texture] = texture; // hack, make it 1:1
+ material = UseTextureAsMaterial( texture );
+ continue;
+ }
+
+ if (strncmp( g_szLine, "f ", 2 ) == 0)
+ {
+ if ( material < 0 )
+ {
+ int texture = LookupTexture( "debugempty.tga" );
+ psource->texmap[texture] = texture;
+ material = UseTextureAsMaterial( texture );
+ }
+
+ int v0, n0, t0;
+ int v1, n1, t1;
+ int v2, n2, t2;
+
+ s_tmpface_t f;
+
+ // Are we specifying p only, p and t only, p and n only, or p and n and t?
+ char *pData = g_szLine + 2;
+ int nLen = Q_strlen( pData );
+
+ CUtlBuffer bufParse( pData, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
+
+ ParseVertex( bufParse, breakSet, v0, t0, n0 );
+ ParseVertex( bufParse, breakSet, v1, t1, n1 );
+ Assert( v0 <= g_numverts && t0 <= g_numtexcoords && n0 <= g_numnormals );
+ Assert( v1 <= g_numverts && t1 <= g_numtexcoords && n1 <= g_numnormals );
+ while ( bufParse.IsValid() )
+ {
+ if ( !ParseVertex( bufParse, breakSet, v2, t2, n2 ) )
+ break;
+
+ Assert( v2 <= g_numverts && t2 <= g_numtexcoords && n2 <= g_numnormals );
+
+ i = g_numfaces++;
+ f.material = material;
+ f.a = v0 - 1; f.na = (n0 > 0) ? n0 - 1 : 0, f.ta = (t0 > 0) ? t0 - 1 : 0;
+ f.b = v2 - 1; f.nb = (n2 > 0) ? n2 - 1 : 0, f.tb = (t2 > 0) ? t2 - 1 : 0;
+ f.c = v1 - 1; f.nc = (n1 > 0) ? n1 - 1 : 0, f.tc = (t1 > 0) ? t1 - 1 : 0;
+ g_face[i] = f;
+
+ v1 = v2; t1 = t2; n1 = n2;
+ }
+ continue;
+ }
+ }
+
+ UnifyIndices( psource );
+
+ BuildIndividualMeshes( psource );
+
+ fclose( g_fpInput );
+
+ return 1;
+}
+
+
+
+int AppendVTAtoOBJ( s_source_t *psource, char *filename, int frame )
+{
+ char cmd[1024];
+ int i, j;
+ int material = 0;
+
+ Vector tmp;
+ matrix3x4_t m;
+
+ AngleMatrix( RadianEuler( 1.570796, 0, 0 ), m );
+
+ if ( !OpenGlobalFile( filename ) )
+ return 0;
+
+ if( !g_quiet )
+ {
+ printf ("grabbing %s\n", filename );
+ }
+
+ g_iLinecount = 0;
+
+ g_numverts = g_numnormals = g_numtexcoords = g_numfaces = 0;
+
+ while ( GetLineInput() )
+ {
+ Vector tmp;
+
+ if (strncmp( g_szLine, "v ", 2 ) == 0)
+ {
+ i = g_numverts++;
+
+ sscanf( g_szLine, "v %f %f %f", &tmp.x, &tmp.y, &tmp.z );
+ VectorTransform( tmp, m, g_vertex[i] );
+
+ // printf("%f %f %f\n", g_vertex[i].x, g_vertex[i].y, g_vertex[i].z );
+
+ g_bone[i].numbones = 1;
+ g_bone[i].bone[0] = 0;
+ g_bone[i].weight[0] = 1.0;
+ }
+ else if (strncmp( g_szLine, "vn ", 3 ) == 0)
+ {
+ i = g_numnormals++;
+ sscanf( g_szLine, "vn %f %f %f", &tmp.x, &tmp.y, &tmp.z );
+ VectorRotate( tmp, m, g_normal[i] );
+ }
+ else if (strncmp( g_szLine, "vt ", 3 ) == 0)
+ {
+ i = g_numtexcoords++;
+ sscanf( g_szLine, "vt %f %f", &g_texcoord[i].x, &g_texcoord[i].y );
+ }
+ else if (strncmp( g_szLine, "usemtl ", 7 ) == 0)
+ {
+ sscanf( g_szLine, "usemtl %s", &cmd[0] );
+
+ int texture = LookupTexture( cmd );
+ psource->texmap[texture] = texture; // hack, make it 1:1
+ material = UseTextureAsMaterial( texture );
+ }
+ else if (strncmp( g_szLine, "f ", 2 ) == 0)
+ {
+ int v0, n0, t0;
+ int v1, n1, t1;
+ int v2, n2, t2;
+ int v3, n3, t3;
+
+ s_tmpface_t f;
+
+ i = g_numfaces++;
+
+ j = sscanf( g_szLine, "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d", &v0, &t0, &n0, &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3 );
+
+ f.material = material;
+ f.a = v0 - 1; f.na = n0 - 1, f.ta = 0;
+ f.b = v2 - 1; f.nb = n2 - 1, f.tb = 0;
+ f.c = v1 - 1; f.nc = n1 - 1, f.tc = 0;
+
+ Assert( v0 <= g_numverts && v1 <= g_numverts && v2 <= g_numverts );
+ Assert( n0 <= g_numnormals && n1 <= g_numnormals && n2 <= g_numnormals );
+
+ g_face[i] = f;
+
+ if (j == 12)
+ {
+ i = g_numfaces++;
+ f.a = v0 - 1; f.na = n0 - 1, f.ta = 0;
+ f.b = v3 - 1; f.nb = n3 - 1, f.tb = 0;
+ f.c = v2 - 1; f.nc = n2 - 1, f.tc = 0;
+ g_face[i] = f;
+ }
+ }
+ }
+
+
+ UnifyIndices( psource );
+
+ s_sourceanim_t *pSourceAnim = FindOrAddSourceAnim( psource, "BindPose" );
+ if ( frame == 0 )
+ {
+ psource->numbones = 1;
+ strcpy( psource->localBone[0].name, "default" );
+ psource->localBone[0].parent = -1;
+ pSourceAnim->numframes = 1;
+ pSourceAnim->startframe = 0;
+ pSourceAnim->endframe = 0;
+ pSourceAnim->rawanim[0] = (s_bone_t *)kalloc( 1, sizeof( s_bone_t ) );
+ pSourceAnim->rawanim[0][0].pos.Init();
+ pSourceAnim->rawanim[0][0].rot = RadianEuler( 1.570796, 0.0, 0.0 );
+ Build_Reference( psource, "BindPose" );
+
+ BuildIndividualMeshes( psource );
+ }
+
+ // printf("%d %d : %d\n", g_numverts, g_numnormals, numvlist );
+
+ int t = frame;
+ int count = numvlist;
+
+ pSourceAnim->numvanims[t] = count;
+ pSourceAnim->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) );
+ for (i = 0; i < count; i++)
+ {
+ pSourceAnim->vanim[t][i].vertex = i;
+ pSourceAnim->vanim[t][i].pos = g_vertex[v_listdata[i].v];
+ pSourceAnim->vanim[t][i].normal = g_normal[v_listdata[i].n];
+ }
+
+ fclose( g_fpInput );
+
+ return 1;
+}
diff --git a/utils/studiomdl/optimize.cpp b/utils/studiomdl/optimize.cpp
new file mode 100644
index 0000000..88f6d0f
--- /dev/null
+++ b/utils/studiomdl/optimize.cpp
@@ -0,0 +1,4017 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#pragma warning( disable : 4786 )
+
+#pragma warning( disable : 4748 ) // buffer overrun with optimizations off
+// This file has tons of problems with global optimizations. . turn 'em off.
+// NOTE: Would be nice to have a test case for this! - not verified in vs2005
+#pragma optimize( "g", off )
+
+// use this much memory to build an output file in memory.
+#define FILEBUFFER_SIZE ( 4 * 1024 * 1024 )
+
+//#define IGNORE_BONES
+
+#define NVTRISTRIP
+
+#define EMIT_TRILISTS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <float.h>
+#include "mathlib/mathlib.h"
+#include "cmdlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "HardwareMatrixState.h"
+#include "HardwareVertexCache.h"
+#include "optimize.h"
+#include <malloc.h>
+#include <nvtristrip.h>
+#include "FileBuffer.h"
+#include "tier1/utlvector.h"
+#include "materialsystem/imaterial.h"
+#include "tier1/utllinkedlist.h"
+
+#include "tier1/smartptr.h"
+#include "tier2/p4helpers.h"
+
+bool g_bDumpGLViewFiles;
+extern bool g_IHVTest;
+
+// flush bones between strips rather than deallocating the LRU.
+#define USE_FLUSH
+
+extern int FindMaterialByName( const char *pMaterialName );
+
+namespace OptimizedModel
+{
+
+//-----------------------------------------------------------------------------
+// Defines which debugging output file we will see
+//-----------------------------------------------------------------------------
+
+enum
+{
+ WRITEGLVIEW_SHOWMESH = 0x00000001,
+ WRITEGLVIEW_SHOWSTRIPGROUP = 0x00000002,
+ WRITEGLVIEW_SHOWSTRIP = 0x00000004,
+ WRITEGLVIEW_SHOWSUBSTRIP = 0x00000008,
+ WRITEGLVIEW_SHOWFLEXED = 0x00000010,
+ WRITEGLVIEW_SHOWSW = 0x00000020,
+ WRITEGLVIEW_SHOWMESHPROPS = 0x00000040,
+ WRITEGLVIEW_SHOWVERTNUMBONES = 0x00000080,
+ WRITEGLVIEW_SHOWSTRIPNUMBONES = 0x00000100,
+ WRITEGLVIEW_FORCEUINT = 0xffffffff
+};
+
+//-----------------------------------------------------------------------------
+// This is used to help us figure out where in the file data should go
+// and also to display stats
+//-----------------------------------------------------------------------------
+struct TotalMeshStats_t
+{
+ int m_TotalBodyParts;
+ int m_TotalModels;
+ int m_TotalModelLODs;
+ int m_TotalMeshes;
+ int m_TotalStrips;
+ int m_TotalStripGroups;
+ int m_TotalVerts;
+ int m_TotalIndices;
+ int m_TotalBoneStateChanges;
+ int m_TotalMaterialReplacements;
+};
+
+struct Triangle_t
+{
+ Triangle_t()
+ {
+ touched = false;
+ neighborTriID[0] = neighborTriID[1] = neighborTriID[2] = -1;
+ }
+ int vertID[3];
+ int neighborTriID[3];
+ int boneID[MAX_NUM_BONES_PER_TRI];
+ int numBones;
+ bool touched;
+};
+
+
+//-----------------------------------------------------------------------------
+// Associates software bone indices with hardware bone indices
+//-----------------------------------------------------------------------------
+struct BoneStateChange_t
+{
+ int hardwareID;
+ int newBoneID;
+};
+
+struct Strip_t
+{
+ // these are the verts and indices that are used while building.
+ // (there may be verts in here that aren't use in the stripGroup.)
+ CUtlVector<Vertex_t> verts;
+ int numIndices;
+ unsigned short *pIndices;
+
+ unsigned int flags;
+
+ int numBones;
+
+ // These are the final, sorted verts as they appear in the strip group.
+ int stripGroupIndexOffset; // offset into stripGroup's indices
+ int numStripGroupIndices;
+ int stripGroupVertexOffset; // offset into stripGroup's verts
+ int numStripGroupVerts;
+
+ int numBoneStateChanges;
+ BoneStateChange_t boneStateChanges[MAX_NUM_BONES_PER_STRIP];
+};
+
+
+//-----------------------------------------------------------------------------
+// a list of vertices + triangles for a strip group
+//-----------------------------------------------------------------------------
+
+typedef CUtlVector<Vertex_t> VertexList_t;
+typedef CUtlVector<Triangle_t> TriangleList_t;
+typedef CUtlVector<unsigned short> VertexIndexList_t;
+typedef CUtlVector<Strip_t> StripList_t;
+typedef CUtlVector<bool> TriangleProcessedList_t;
+
+//-----------------------------------------------------------------------------
+// String table
+//-----------------------------------------------------------------------------
+class CStringTable
+{
+public:
+ int StringTableOffset( const char *string )
+ {
+ int i;
+ int size = 0;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ if( stricmp( m_Strings[i].Base(), string ) == 0 )
+ {
+ return size;
+ }
+ size += m_Strings[i].Size();
+ }
+ return -1;
+ }
+ bool StringPresent( const char *string )
+ {
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ if( stricmp( m_Strings[i].Base(), string ) == 0 )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ void AddString( const char *newString )
+ {
+ if( StringPresent( newString ) )
+ {
+ return;
+ }
+ CUtlVector<char> &s = m_Strings[m_Strings.AddToTail()];
+ int size = strlen( newString ) + 1;
+ s.AddMultipleToTail( size );
+ strcpy( s.Base(), newString );
+ }
+ void Purge()
+ {
+ m_Strings.Purge();
+ }
+ int CalcSize( void )
+ {
+ int size = 0;
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ size += m_Strings[i].Size();
+ }
+ return size;
+ }
+ void WriteToMem( char *pDst )
+ {
+ int size = 0;
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+#ifdef _DEBUG
+ int j = Q_strlen( m_Strings[i].Base() ) + 1;
+ int k = m_Strings[i].Size();
+ Assert( j == k );
+#endif
+ memcpy( pDst + size, m_Strings[i].Base(), m_Strings[i].Size() );
+ size += m_Strings[i].Size();
+ }
+ }
+private:
+ typedef CUtlVector<char> CharVector_t;
+ CUtlVector<CharVector_t> m_Strings;
+};
+
+// global string table for the whole vtx file.
+static CStringTable s_StringTable;
+
+//-----------------------------------------------------------------------------
+// This is all the indices, vertices, and strips that make up this group
+// a group can be rendered all in one call to the material system
+//-----------------------------------------------------------------------------
+
+struct StripGroup_t
+{
+ VertexIndexList_t indices;
+ VertexList_t verts;
+ StripList_t strips;
+ unsigned int flags;
+};
+
+struct Mesh_t
+{
+ CUtlVector<StripGroup_t> stripGroups;
+ unsigned int flags;
+};
+
+struct ModelLOD_t
+{
+ CUtlVector<Mesh_t> meshes;
+ float switchPoint;
+};
+
+struct Model_t
+{
+ CUtlVector<ModelLOD_t> modelLODs;
+};
+
+//-----------------------------------------------------------------------------
+// Main class that does all the dirty work to stripy + groupify
+//-----------------------------------------------------------------------------
+
+class COptimizedModel
+{
+public:
+ bool OptimizeFromStudioHdr( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts, int vertCacheSize,
+ bool usesFixedFunction, bool bForceSoftwareSkin, bool bHWFlex, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName, const char *glViewFileName );
+
+private:
+ void CleanupEverything();
+
+ // Setup to get the ball rolling
+ void SetupMeshProcessing( studiohdr_t *phdr, int vertCacheSize,
+ bool usesFixedFunction, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName );
+
+ //
+ // Methods associated with pre-processing the mesh
+ //
+
+ //
+ // Methods associated with mesh processing
+ //
+
+ void SourceMeshToTriangleList( s_model_t *pSrcModel, s_mesh_t *pSrcMesh, CUtlVector<mstudioiface_t> &meshTriangleList );
+ void CreateLODTriangleList( s_model_t *pSrcModel, int nLodID, s_source_t* pLODSource,
+ mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList, bool writeDebug );
+
+ // This processes the model + breaks it into strips
+ void ProcessModel( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts, TotalMeshStats_t& stats,
+ bool bForceSoftwareSkin, bool bHWFlex );
+
+ // processes a single mesh within the model
+ void ProcessMesh( Mesh_t *pMesh, studiohdr_t *pStudioHeader, CUtlVector<mstudioiface_t> &srcFaces,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, bool ForceNoFlex,
+ bool bForceSoftwareSkin, bool bHWFlex );
+
+ // Processes a single strip group
+ void ProcessStripGroup( StripGroup_t *pStripGroup, bool isHWSkinned, bool isFlexed,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ TriangleProcessedList_t& trianglesProcessed,
+ int maxBonesPerVert, int maxBonesPerTri, int maxBonesPerStrip,
+ bool forceNoFlex, bool bHWFlex );
+
+ // Constructs vertices appropriate for a strip group based on source face data
+ bool GenerateStripGroupVerticesFromFace( mstudioiface_t* pFace,
+ mstudiomesh_t *pStudioMesh, int maxPreferredBones, Vertex_t* pStripGroupVert );
+
+ // Count the number of unique bones in a set of vertices
+ int CountUniqueBones( int count, Vertex_t *pVertex ) const;
+ int CountMaxVertBones( int count, Vertex_t *pVertex ) const;
+
+ // Counts the unique # of bones in a strip
+ int CountUniqueBonesInStrip( StripGroup_t *pStripGroup, Strip_t *pStrip );
+
+ // Builds SW + HW skinned strips
+ void BuildSWSkinnedStrips( TriangleList_t& tris,
+ VertexList_t const& verts, StripGroup_t *pStripGroup );
+ void BuildHWSkinnedStrips( TriangleList_t& tris, VertexList_t& verts,
+ StripGroup_t *pStripGroup, int maxBonesPerStrip );
+
+ // These methods deal with finding another triangle to batch together
+ // in a similar matrix state group
+ int ComputeNewBonesNeeded( Triangle_t const& triangle ) const;
+ bool AllocateHardwareBonesForTriangle( Triangle_t *tri );
+ Triangle_t* GetNextTriangle( TriangleList_t& triangles, bool allowNewStrip );
+ Triangle_t* GetNextUntouchedWithoutBoneStateChange( TriangleList_t& triangles );
+ Triangle_t* GetNextUntouchedWithLeastBoneStateChanges( TriangleList_t& triangles );
+
+ // Actually does the stripification
+ void Stripify( VertexIndexList_t const& sourceIndices, bool isHWSkinned,
+ int* pNumIndices, unsigned short** ppIndices );
+
+ // Makes sure our vertices are using the correct bones
+ void SanityCheckVertBones( VertexIndexList_t const& list, VertexList_t const& vertices );
+
+ // Sets the flags associated with a particular strip group + mesh
+ void ComputeStripGroupFlags( StripGroup_t *pStripGroup, bool isHWSkinned, bool isFlexed );
+ void ComputeMeshFlags( Mesh_t *pMesh, studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh );
+
+ bool MeshIsTeeth( studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh );
+
+ // Tries to add neighboring vertices that'll fit into the matrix transform state
+ void BuildStripsRecursive( VertexIndexList_t& indices, TriangleList_t& list, Triangle_t *triangle );
+
+ // Figures out all bones affecting a particular triangle
+ void BuildTriangleBoneData( VertexList_t& list, Triangle_t& tri );
+
+ // Memory optimize the strip data
+ void PostProcessStripGroup( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, StripGroup_t *pStripGroup );
+
+ void COptimizedModel::ZeroNumBones( void );
+
+ //
+ // Methods associated with writing VTX files
+ //
+
+ // This writes the strip data out to a VTX file
+ void WriteVTXFile( studiohdr_t *pHdr, char const* pFileName,
+ TotalMeshStats_t const& stats );
+
+
+ //
+ // Methods associated with writing GL View files
+ //
+
+
+ // This writes the GL debugging files
+ void WriteGLViewFiles( studiohdr_t *pHdr, const char *glViewFileName );
+
+ void OutputMemoryUsage( void );
+ bool IsVertexFlexed( mstudiomesh_t *pStudioMesh, int vertID ) const;
+ void BuildNeighborInfo( TriangleList_t& list, int nMaxVertexId );
+ void ClearTouched( void );
+ void PrintVert( Vertex_t *v, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh );
+ void SanityCheckAgainstStudioHDR( studiohdr_t *phdr );
+ void WriteStringTable( int stringTableOffset );
+ void WriteMaterialReplacements( int materialReplacementsOffset );
+ void WriteMaterialReplacementLists( int materialReplacementsOffset, int materialReplacementListOffset );
+ void WriteHeader( int vertCacheSize, int maxBonesPerVert,
+ int maxBonesPerTri, int maxBonesPerStrip, int numBodyParts, long checkSum );
+ void WriteBodyPart( int bodyPartID, mstudiobodyparts_t *pBodyPart, int modelID );
+ void WriteModel( int modelID, mstudiomodel_t *pModel, int lodID );
+ void WriteModelLOD( int lodID, ModelLOD_t *pLOD, int meshID );
+ void WriteMesh( int meshID, Mesh_t *pMesh, int stripGroupID );
+ void WriteStripGroup( int stripGroupID, StripGroup_t *pStripGroup,
+ int vertID, int indexID, int stripID );
+ int WriteVerts( int vertID, StripGroup_t *pStripGroup );
+ int WriteIndices( int indexID, StripGroup_t *pStripGroup );
+ void WriteStrip( int stripID, Strip_t *pStrip, int indexID, int vertID, int boneID );
+ void WriteBoneStateChange( int boneID, BoneStateChange_t *boneStateChange );
+
+ void DrawGLViewTriangle( FILE *fp, Vector& pos1, Vector& pos2, Vector& pos3,
+ Vector& color1, Vector& color2, Vector& color3 );
+ void WriteGLViewFile( studiohdr_t *phdr, const char *pFileName, unsigned int flags, float shrinkFactor );
+ void ShrinkVerts( float shrinkFactor );
+ void GLViewDrawBegin( int mode );
+ void CheckVertBoneWeights( Vertex_t *pVert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh );
+ void GLViewVert( FILE *fp, Vertex_t vert, int index, Vector& color, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, bool showSubStrips, float shrinkFraction );
+ void GLViewDrawEnd( void );
+ void SetMeshPropsColor( unsigned int meshFlags, Vector& color );
+ void SetFlexedAndSkinColor( unsigned int glViewFlags, unsigned int stripGroupFlags, Vector& color );
+ void SetColorFromNumVertexBones( int numBones, Vector& color );
+ Vector& GetOrigVertPosition( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert );
+ float GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ mstudioboneweight_t &GetOrigVertBoneWeight( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert );
+ int GetOrigVertBoneIndex( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ void ShowStats( void );
+ void MapGlobalBonesToHardwareBoneIDsAndSortBones( studiohdr_t *phdr );
+ void RemoveRedundantBoneStateChanges( void );
+ void CheckVert( Vertex_t *pVert, int maxBonesPerTri, int maxBonesPerVert );
+ void CheckAllVerts( int maxBonesPerTri, int maxBonesPerVert );
+ void SortBonesWithinVertex( bool flexed, Vertex_t *vert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, int *globalToHardwareBoneIndex, int *hardwareToGlobalBoneIndex, int maxBonesPerTri, int maxBonesPerVert );
+ int GetTotalVertsForMesh( Mesh_t *pMesh );
+ int GetTotalIndicesForMesh( Mesh_t *pMesh );
+ int GetTotalStripsForMesh( Mesh_t *pMesh );
+ int GetTotalStripGroupsForMesh( Mesh_t *pMesh );
+ int GetTotalBoneStateChangesForMesh( Mesh_t *pMesh );
+ bool MeshNeedsRemoval( studiohdr_t *pHdr, mstudiomesh_t *pStudioMesh,
+ LodScriptData_t& scriptLOD );
+
+ void PrintBoneStateChanges( studiohdr_t *pHdr, int lod );
+ void PrintVerts( studiohdr_t *phdr, int lod );
+ void SanityCheckVertexBoneLODFlags( studiohdr_t *pStudioHdr, FileHeader_t *pVtxHeader );
+
+ //
+ // SOURCE DATA
+ //
+
+ // These are all the built models
+ CUtlVector<Model_t> m_Models;
+
+ // total number of bones in the studio model
+ int m_NumBones;
+
+ // information about the hardware
+ int m_VertexCacheSize;
+ int m_MaxBonesPerTri;
+ int m_MaxBonesPerVert;
+ int m_MaxBonesPerStrip;
+ bool m_UsesFixedFunction;
+
+ // stats
+ int m_NumSkinnedAndFlexedVerts;
+
+ CHardwareMatrixState m_HardwareMatrixState;
+
+ // a place to stick file output.
+ CFileBuffer *m_FileBuffer;
+
+ // offset for different items in the output file.
+ int m_BodyPartsOffset;
+ int m_ModelsOffset;
+ int m_ModelLODsOffset;
+ int m_MeshesOffset;
+ int m_StripGroupsOffset;
+ int m_StripsOffset;
+ int m_VertsOffset;
+ int m_IndicesOffset;
+ int m_BoneStageChangesOffset;
+ int m_StringTableOffset;
+ int m_MaterialReplacementsOffset;
+ int m_MaterialReplacementsListOffset;
+ int m_EndOfFileOffset;
+};
+
+//-----------------------------------------------------------------------------
+// Singleton instance
+//-----------------------------------------------------------------------------
+
+static COptimizedModel s_OptimizedModel;
+
+
+//-----------------------------------------------------------------------------
+// Cleanup method
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::CleanupEverything()
+{
+}
+
+
+
+void COptimizedModel::OutputMemoryUsage( void )
+{
+ printf( "body parts: %7d bytes\n", ( int )( m_ModelsOffset - m_BodyPartsOffset ) );
+ printf( "models: %7d bytes\n", ( int )( m_MeshesOffset - m_ModelsOffset ) );
+ printf( "model LODs: %7d bytes\n", ( int )( m_MeshesOffset - m_ModelLODsOffset ) );
+ printf( "meshes: %7d bytes\n", ( int )( m_StripGroupsOffset - m_MeshesOffset ) );
+ printf( "strip groups: %7d bytes\n", ( int )( m_StripsOffset - m_StripGroupsOffset ) );
+ printf( "strips: %7d bytes\n", ( int )( m_VertsOffset - m_StripsOffset ) );
+ printf( "verts: %7d bytes\n", ( int )( m_IndicesOffset - m_VertsOffset ) );
+ printf( "indices: %7d bytes\n", ( int )( m_BoneStageChangesOffset - m_IndicesOffset ) );
+ printf( "bone changes: %7d bytes\n", ( int )( m_EndOfFileOffset - m_BoneStageChangesOffset ) );
+ printf( "everything: %7d bytes\n", ( int )( m_EndOfFileOffset ) );
+}
+
+void COptimizedModel::SanityCheckAgainstStudioHDR( studiohdr_t *phdr )
+{
+#if 0 // garymcthack
+ printf( "SanityCheckAgainstStudioHDR\n" );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ Assert( header->numBodyParts == phdr->numbodyparts );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ Assert( bodyPart->numModels == pStudioBodyPart->nummodels );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ Assert( model->numMeshes == pStudioModel->nummeshes );
+ for( int meshID = 0; meshID < model->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = model->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *stripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < stripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *strip = stripGroup->pStrip( stripID );
+ }
+ }
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are all related to creating groups of meshes to strip
+//
+//-----------------------------------------------------------------------------
+
+
+
+//-----------------------------------------------------------------------------
+// pick any triangle that hasn't been used yet to start with.
+//-----------------------------------------------------------------------------
+
+static Triangle_t *GetNextUntouched( TriangleList_t& triangles )
+{
+ int i;
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ if( !triangles[i].touched )
+ return &triangles[i];
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the number of bones that are not represented in the current hardware state
+//-----------------------------------------------------------------------------
+
+inline int COptimizedModel::ComputeNewBonesNeeded( Triangle_t const& triangle ) const
+{
+ int numNewBones = 0;
+ for( int i = 0; i < triangle.numBones; ++i )
+ {
+ if( !m_HardwareMatrixState.IsMatrixAllocated( triangle.boneID[i] ) )
+ ++numNewBones;
+ }
+ return numNewBones;
+}
+
+
+//-----------------------------------------------------------------------------
+// returns triangle index
+// Find the next triangle without a bone state change
+// May add new hardware bones if there is space and is necessary.
+//-----------------------------------------------------------------------------
+
+Triangle_t* COptimizedModel::GetNextUntouchedWithoutBoneStateChange( TriangleList_t& triangles )
+{
+ Triangle_t *bestTriangle = 0;
+ int bestNumNewBones = MAX_NUM_BONES_PER_TRI + 1;
+
+ int i;
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ // We haven't processed this one, so let's try it
+ if( !triangles[i].touched )
+ {
+ // How many bones are not represented in the current state?
+ int numNewBones = ComputeNewBonesNeeded( triangles[i] );
+
+ // if this triangle fit and if it's the best so far, save it.
+ if ( (numNewBones <= m_HardwareMatrixState.FreeMatrixCount()) &&
+ (numNewBones < bestNumNewBones ) )
+ {
+ bestNumNewBones = numNewBones;
+ bestTriangle = &triangles[i];
+
+ // Can't get any better than this!
+ if (bestNumNewBones == 0)
+ break;
+ }
+ }
+ }
+ return bestTriangle;
+}
+
+
+//-----------------------------------------------------------------------------
+// This will returns the triangle that requires the last number of bone
+// state changes
+//-----------------------------------------------------------------------------
+
+Triangle_t *COptimizedModel::GetNextUntouchedWithLeastBoneStateChanges( TriangleList_t& triangles )
+{
+ Triangle_t *bestTriangle = 0;
+ int bestNumNewBones = MAX_NUM_BONES_PER_TRI + 1;
+ int i;
+
+ // For this one, just find the triangle that needs the least number
+ // of new bones. That way, we'll not have to change too many states
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ if( !triangles[i].touched )
+ {
+ int numNewBones = ComputeNewBonesNeeded( triangles[i] );
+
+ if( numNewBones < bestNumNewBones )
+ {
+ bestNumNewBones = numNewBones;
+ bestTriangle = &triangles[i];
+ }
+ }
+ }
+
+ // This only happens if there are no triangles untouched
+ if( !bestTriangle )
+ return 0;
+
+#ifdef USE_FLUSH
+ m_HardwareMatrixState.DeallocateAll();
+#else
+ // Remove bones until we have enough space...
+ int numToRemove = bestNumNewBones - m_HardwareMatrixState.FreeMatrixCount();
+ Assert( numToRemove > 0 );
+ m_HardwareMatrixState.DeallocateLRU(numToRemove);
+#endif
+
+ return bestTriangle;
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocate bones for a triangle from the hardware matrix state
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::AllocateHardwareBonesForTriangle( Triangle_t *tri )
+{
+ for( int i = 0; i < tri->numBones; ++i )
+ {
+ int bone = tri->boneID[i];
+ if( !m_HardwareMatrixState.IsMatrixAllocated( bone ) )
+ {
+ if( !m_HardwareMatrixState.AllocateMatrix( bone ) )
+ return false;
+ }
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the next triangle.
+// Try to find one that doesn't cause a bone state change by jumping to
+// a new location in the model that works with the bones that we have allocated.
+// If we can't find one, and allowNewStrip is true, then flush and pick the next
+// best triangle that is close to the current hardware bone state.
+//-----------------------------------------------------------------------------
+
+Triangle_t *COptimizedModel::GetNextTriangle( TriangleList_t& tris, bool allowNewStrip )
+{
+ // First try to get a triangle that doesn't involve changing matrix state
+ Triangle_t *tri;
+ tri = GetNextUntouchedWithoutBoneStateChange( tris );
+
+ // If that didn't work, pick the triangle that changes the state the least
+ if( !tri && allowNewStrip )
+ {
+ tri = GetNextUntouchedWithLeastBoneStateChanges( tris );
+ }
+
+ // Return the triangle we found
+ return tri;
+}
+
+
+//-----------------------------------------------------------------------------
+// Make sure all vertices we've added up to now use bones in the matrix list
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::SanityCheckVertBones( VertexIndexList_t const& list, VertexList_t const& vertices )
+{
+#ifdef _DEBUG
+ Vertex_t const *pVert;
+ int i;
+ for( i = 0; i < list.Size(); i++ )
+ {
+ pVert = &vertices[list[i]];
+ if( !g_staticprop )
+ {
+ Assert( pVert->numBones != 0 );
+ }
+ int j;
+ for( j = 0; j < pVert->numBones; j++ )
+ {
+ if( pVert->boneID[j] == -1 )
+ {
+ continue;
+ }
+ if( !m_HardwareMatrixState.IsMatrixAllocated( pVert->boneID[j] ) )
+ {
+ Assert( 0 );
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Make sure all vertices we've added up to now use bones in the matrix list
+//-----------------------------------------------------------------------------
+void COptimizedModel::Stripify( VertexIndexList_t const& sourceIndices, bool isHWSkinned,
+ int* pNumIndices, unsigned short** ppIndices )
+{
+ if( sourceIndices.Size() == 0 )
+ {
+ *ppIndices = 0;
+ *pNumIndices = 0;
+ return;
+ }
+
+ // Skip the tristripping phase if we're building in preview mode
+ if ( g_bBuildPreview || isHWSkinned == false )
+ {
+ *pNumIndices = sourceIndices.Count();
+ *ppIndices = new unsigned short[*pNumIndices];
+ memcpy( *ppIndices, sourceIndices.Base(), (*pNumIndices) * sizeof(unsigned short) );
+ return;
+ }
+
+/*
+ printf( "Stripify\n" );
+ int i;
+ for( i = 0; i < sourceIndices.Size(); i++ )
+ {
+ printf( "stripindex: %d\n", sourceIndices[i] );
+ }
+*/
+
+#ifdef NVTRISTRIP
+ PrimitiveGroup *primGroups;
+ unsigned short numPrimGroups;
+
+ // Be sure to call delete[] on the returned primGroups to avoid leaking mem
+ GenerateStrips( &sourceIndices[0], sourceIndices.Size(),
+ &primGroups, &numPrimGroups );
+ Assert( numPrimGroups == 1 );
+ *pNumIndices = primGroups->numIndices;
+ *ppIndices = new unsigned short[*pNumIndices];
+ memcpy( *ppIndices, primGroups->indices, sizeof( unsigned short ) * *pNumIndices );
+ delete [] primGroups;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// eat up triangle recursively by flood-filling around the model until
+// we run out of bones on the hardware.
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildStripsRecursive( VertexIndexList_t& indices,
+ TriangleList_t& tris, Triangle_t *triangle )
+{
+ Assert( triangle );
+
+ // Don't process the triangle if it's already been processed
+ if( triangle->touched )
+ return;
+
+ // Only suck in triangles that need no state change
+ if ( ComputeNewBonesNeeded( *triangle ) )
+ return;
+
+ // We've got enough hardware bones. Lets add this triangle's vertices, and
+ // then add the vertices of all the neighboring triangles.
+ triangle->touched = true;
+
+ indices.AddToTail( ( unsigned short )triangle->vertID[0] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[1] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[2] );
+
+ // Try to add our neighbors
+ if( triangle->neighborTriID[0] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[0]] );
+ }
+ if( triangle->neighborTriID[1] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[1]] );
+ }
+ if( triangle->neighborTriID[2] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[2]] );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a HW-skinned strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildHWSkinnedStrips( TriangleList_t& triangles,
+ VertexList_t& vertices, StripGroup_t *pStripGroup, int maxBonesPerStrip )
+{
+ // Set up the hardware matrix state
+ m_HardwareMatrixState.Init( maxBonesPerStrip );
+
+ // Empty out the list of triangles to be stripified.
+ VertexIndexList_t trianglesToStrip;
+ trianglesToStrip.EnsureCapacity( triangles.Size() * 3 );
+
+ // pick any old unused triangle to start with.
+ Triangle_t *pSeedTri = GetNextUntouched( triangles );
+ while( pSeedTri )
+ {
+ // Make sure we've got out transforms allocated
+#ifdef DBGFLAG_ASSERT
+ bool ok =
+#endif
+ AllocateHardwareBonesForTriangle( pSeedTri );
+ Assert( ok );
+
+ // eat up triangle recursively by flood-filling around the model until
+ // we run out of bones on the hardware.
+ BuildStripsRecursive( trianglesToStrip, triangles, pSeedTri );
+
+ // Try to jump to a new location in the mesh without
+ // causing a hardware bone state overflow or flush.
+ pSeedTri = GetNextTriangle( triangles, false );
+ if( pSeedTri )
+ continue;
+
+ // Save the results of the generated strip.
+ int stripIdx = pStripGroup->strips.AddToTail( );
+ Strip_t& newStrip = pStripGroup->strips[stripIdx];
+
+ // Compute the strip flags
+ newStrip.flags = 0;
+#ifdef EMIT_TRILISTS
+ newStrip.flags |= STRIP_IS_TRILIST;
+#else
+ newStrip.flags |= STRIP_IS_TRISTRIP;
+#endif
+
+ // Sanity check the indices of the bones.
+ SanityCheckVertBones( trianglesToStrip, vertices );
+
+ // There are no more triangles to eat up without causing a flush, so
+ // go ahead and stripify what we have and flush.
+ // NOTE: This allocates space for stripIndices.pIndices.
+ Stripify( trianglesToStrip, true, &newStrip.numIndices, &newStrip.pIndices );
+
+ // hack - should just build directly into newStrip.verts instead of using a global.
+ int i;
+ for( i = 0; i < vertices.Size(); i++ )
+ {
+ newStrip.verts.AddToTail( vertices[i] );
+ }
+
+ // Compute the number of bones in this strip
+ newStrip.numBoneStateChanges = m_HardwareMatrixState.AllocatedMatrixCount();
+ Assert( newStrip.numBoneStateChanges <= maxBonesPerStrip );
+
+ // Save off the bones used for this strip.
+ for( i = 0; i < m_HardwareMatrixState.AllocatedMatrixCount(); i++ )
+ {
+ newStrip.boneStateChanges[i].hardwareID = i;
+ newStrip.boneStateChanges[i].newBoneID = m_HardwareMatrixState.GetNthBoneGlobalID( i );
+ }
+
+ // Empty out the triangles to strip so that we can start again with a new strip.
+ trianglesToStrip.RemoveAll();
+
+ // Get the next best triangle, allowing for a bone state flushes.
+ pSeedTri = GetNextTriangle( triangles, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a SW-skinned strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildSWSkinnedStrips( TriangleList_t& triangles,
+ VertexList_t const& vertices, StripGroup_t *pStripGroup )
+{
+ // Save the results of the generated strip.
+ int stripIdx = pStripGroup->strips.AddToTail( );
+ Strip_t& newStrip = pStripGroup->strips[stripIdx];
+
+ // Set the strip flags
+ newStrip.flags = 0;
+#ifdef EMIT_TRILISTS
+ newStrip.flags |= STRIP_IS_TRILIST;
+#else
+ newStrip.flags |= STRIP_IS_TRISTRIP;
+#endif
+
+ int nTriangleCount = triangles.Count();
+
+ VertexIndexList_t indices;
+ indices.EnsureCapacity( nTriangleCount * 3 );
+
+ for( int i = 0; i < nTriangleCount; i++ )
+ {
+ Triangle_t* triangle = &triangles[i];
+ triangle->touched = true;
+ indices.AddToTail( ( unsigned short )triangle->vertID[0] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[1] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[2] );
+ }
+
+ Stripify( indices, false, &newStrip.numIndices, &newStrip.pIndices );
+
+ // hack - should just build directly into newStrip.verts instead of using a global.
+ for( int i = 0; i < vertices.Size(); i++ )
+ {
+ newStrip.verts.AddToTail( vertices[i] );
+ }
+
+ newStrip.numBoneStateChanges = 0;
+ for( int i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ newStrip.boneStateChanges[i].hardwareID = -1;
+ newStrip.boneStateChanges[i].newBoneID = -1;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns true if a particular vertex is part of a flex
+//-----------------------------------------------------------------------------
+bool COptimizedModel::IsVertexFlexed( mstudiomesh_t *pStudioMesh, int vertID ) const
+{
+ mstudioflex_t *pflex = pStudioMesh->pFlex( 0 );
+
+ int i, j, n;
+
+ // Iterate through all the flexes
+ // figure out if the vertex is part of the flex
+ for (i = 0; i < pStudioMesh->numflexes; i++)
+ {
+ byte *pvanim = pflex[i].pBaseVertanim();
+ int nVAnimSizeBytes = pflex[i].VertAnimSizeBytes();
+
+ for (j = 0; j < pflex[i].numverts; j++, pvanim += nVAnimSizeBytes )
+ {
+ mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pvanim );
+
+ n = pAnim->index;
+ if ( n == vertID )
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Computes flags for the strip group
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ComputeStripGroupFlags( StripGroup_t *pStripGroup,
+ bool isHWSkinned, bool isFlexed )
+{
+ pStripGroup->flags = 0;
+ if( isFlexed )
+ {
+ pStripGroup->flags |= STRIPGROUP_IS_FLEXED;
+ pStripGroup->flags |= STRIPGROUP_IS_DELTA_FLEXED; // Going forward, DX9 models are delta flexed
+ }
+ if( isHWSkinned )
+ {
+ pStripGroup->flags |= STRIPGROUP_IS_HWSKINNED;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Try to reduce the number of bones affecting this vert so hardware can deal
+//-----------------------------------------------------------------------------
+
+#define MIN_BONE_INFLUENCE 1.0f
+
+static void TryToReduceBoneInfluence( Vertex_t& stripGroupVert,
+ mstudioboneweight_t const& boneWeights, int maxBones )
+{
+ int i;
+ while ( stripGroupVert.numBones > maxBones)
+ {
+ // Find the minimum bone weight...
+ float minWeight = 2.0;
+ int minIndex = -1;
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; ++i )
+ {
+ if (stripGroupVert.boneID[i] != -1)
+ {
+ float weight = boneWeights.weight[ stripGroupVert.boneWeightIndex[i] ];
+ if (weight < minWeight)
+ {
+ minWeight = weight;
+ minIndex = i;
+ }
+ }
+ }
+ Assert( minIndex >= 0 );
+
+ // Now that we got it, remove that bone influence if it's small enough
+ if (minWeight >= MIN_BONE_INFLUENCE)
+ return;
+
+ // Blat out that bone!
+ for( i = minIndex; i < MAX_NUM_BONES_PER_VERT - 1; ++i )
+ {
+ stripGroupVert.boneID[i] = stripGroupVert.boneID[i+1];
+ stripGroupVert.boneWeightIndex[i] = stripGroupVert.boneWeightIndex[i+1];
+ }
+ stripGroupVert.boneID[ MAX_NUM_BONES_PER_VERT - 1] = -1;
+ stripGroupVert.boneWeightIndex[ MAX_NUM_BONES_PER_VERT - 1] = 0;
+
+ --stripGroupVert.numBones;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Generate three strip-group ready vertices from source mesh data
+// returns true if the face contants a flexed vertex
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::GenerateStripGroupVerticesFromFace( mstudioiface_t* pFace,
+ mstudiomesh_t *pStudioMesh,
+ int maxPreferredBones,
+ Vertex_t* pStripGroupVert )
+{
+ int vertIDs[3];
+ vertIDs[0] = pFace->a;
+ vertIDs[1] = pFace->b;
+ vertIDs[2] = pFace->c;
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+
+ bool triangleIsFlexed = false;
+ for( int faceIndex = 0; faceIndex < 3; ++faceIndex )
+ {
+ // Get the original source vertex id
+ int vertex = vertIDs[faceIndex];
+
+ // Check the verts of the triangle to see if they are flexed
+ triangleIsFlexed = triangleIsFlexed || IsVertexFlexed( pStudioMesh, vertex );
+
+ // How many bones affect this vertex
+ mstudioboneweight_t *pBoneWeight = vertData->BoneWeights(vertex);
+
+ int bonesAffectingVertex = pBoneWeight->numbones;
+ if( !g_staticprop && bonesAffectingVertex <= 0 )
+ {
+ MdlWarning( "too few bones/vert (%d) : it has no bones!\n", bonesAffectingVertex );
+ }
+ else if( bonesAffectingVertex > MAX_NUM_BONES_PER_VERT )
+ {
+ MdlError( "too many bones/vert (%d) : MAX_NUM_BONES_PER_VERT needs to be upped\n", bonesAffectingVertex );
+ }
+
+ // Set the fields of the strip group's vert
+ pStripGroupVert[faceIndex].origMeshVertID = vertex;
+
+ pStripGroupVert[faceIndex].numBones = bonesAffectingVertex;
+
+ int boneID;
+
+#ifndef IGNORE_BONES
+ for( boneID = 0; boneID < bonesAffectingVertex; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = pBoneWeight->bone[boneID];
+ pStripGroupVert[faceIndex].boneWeightIndex[boneID] = boneID;
+ }
+ for( ; boneID < MAX_NUM_BONES_PER_VERT; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = -1;
+ pStripGroupVert[faceIndex].boneWeightIndex[boneID] = boneID;
+ }
+#else
+ // don't let bones have any influence.
+ for( boneID = 0; boneID < MAX_NUM_BONES_PER_VERT; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = -1;
+ pStripGroupVert[faceIndex].boneWeights[boneID] = 0;
+ }
+#endif
+
+ // For hardware skinning, we want to try to reduce the number of
+ // bone influences, which we'll do here if we can.
+ // At the moment, we can't do this for fixed function because
+ // we get seam problems at transitions between SW and HW rendered
+ if (!m_UsesFixedFunction)
+ {
+ if ((maxPreferredBones > 0) && (bonesAffectingVertex > maxPreferredBones))
+ {
+ TryToReduceBoneInfluence( pStripGroupVert[faceIndex], *pBoneWeight,
+ maxPreferredBones );
+ }
+ }
+ }
+
+ return triangleIsFlexed;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the unique number of bones in a specified set of vertices
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::CountUniqueBones( int count, Vertex_t *pVertex ) const
+{
+ int uniqueBoneCount = 0;
+ int uniqueBoneList[MAX_NUM_BONES_PER_STRIP];
+ while ( --count >= 0 )
+ {
+ for (int i = 0; i < pVertex[count].numBones; ++i)
+ {
+ int boneID = pVertex[count].boneID[i];
+ int j = uniqueBoneCount;
+ while ( --j >= 0 )
+ {
+ if (uniqueBoneList[j] == boneID)
+ break;
+ }
+
+ // Didn't find a match!
+ if (j < 0)
+ {
+ Assert( uniqueBoneCount < MAX_NUM_BONES_PER_STRIP );
+ uniqueBoneList[uniqueBoneCount++] = boneID;
+ }
+ }
+ }
+
+ return uniqueBoneCount;
+}
+
+int COptimizedModel::CountMaxVertBones( int count, Vertex_t *pVertex ) const
+{
+ int maxBones = 0;
+ while ( --count >= 0 )
+ {
+ if (maxBones < pVertex[count].numBones)
+ maxBones = pVertex[count].numBones;
+ }
+
+ return maxBones;
+}
+
+//-----------------------------------------------------------------------------
+// Adds a vertex to the list of vertices to be added to the strip group
+//-----------------------------------------------------------------------------
+
+static int FindOrCreateVertex( VertexList_t& list, Vertex_t const& vert )
+{
+ int i;
+ for( i = 0; i < list.Size(); i++ )
+ {
+ if (list[i].origMeshVertID == vert.origMeshVertID)
+ {
+ // If this is the case, then everything else should be too!
+ Assert( !memcmp( &list[i], &vert, sizeof( vert )) );
+ return i;
+ }
+ }
+
+ list.AddToTail( vert );
+ return i;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the bones used by the triangle
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::BuildTriangleBoneData( VertexList_t& list, Triangle_t& tri )
+{
+ int j, k, l;
+
+ // Blat out the bone ID state
+ tri.numBones = 0;
+ for( j = 0; j < MAX_NUM_BONES_PER_TRI; j++ )
+ {
+ tri.boneID[j] = -1;
+ }
+
+ // Iterate through the vertices in the triangle
+ for( j = 0; j < 3; j++ )
+ {
+ // Iterate over the bones influencing the vertex
+ Vertex_t& vert = list[tri.vertID[j]];
+ for( k = 0; k < vert.numBones; ++k )
+ {
+ int bone = vert.boneID[k];
+ Assert( (bone >= 0) && (bone < m_NumBones) );
+
+ // Look for matches with previously found bones
+ for ( l = tri.numBones; --l >= 0; )
+ {
+ if ( bone == tri.boneID[l] )
+ break;
+ }
+
+ // No match, add it to our list
+ if ( l < 0 )
+ tri.boneID[tri.numBones++] = bone;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes neighboring triangles along each face of a triangle
+//-----------------------------------------------------------------------------
+struct EdgeInfo_t
+{
+ int m_nConnectedVertId;
+ int m_nEdgeIndex;
+ int m_nTriangleId;
+};
+
+static void FindMatchingEdge( TriangleList_t& list, int nTriangleId, int nEdgeIndex,
+ const int *pVertIds, CUtlVector< int >& vertexToEdges, CUtlFixedLinkedList< EdgeInfo_t >& edges )
+{
+ // Have we already attached this edge to something?
+ Triangle_t &tri = list[nTriangleId];
+ if ( tri.neighborTriID[nEdgeIndex] != -1 )
+ return;
+
+ int nVertIndex = ( pVertIds[0] < pVertIds[1] ) ? 0 : 1;
+ int nConnectedVertId = pVertIds[ 1-nVertIndex ];
+ int hFirstEdge = vertexToEdges[nVertIndex];
+ for ( int hEdge = hFirstEdge; hEdge != edges.InvalidIndex(); hEdge = edges.Next(hEdge) )
+ {
+ EdgeInfo_t &edge = edges[hEdge];
+ if ( edge.m_nConnectedVertId != nConnectedVertId )
+ continue;
+
+ // Can't attach triangles to themselves
+ if ( edge.m_nTriangleId == nTriangleId )
+ continue;
+
+ // Found a match! Mark the two triangles as sharing an edge
+ tri.neighborTriID[nEdgeIndex] = edge.m_nTriangleId;
+ list[ edge.m_nTriangleId ].neighborTriID[ edge.m_nEdgeIndex ] = nTriangleId;
+ if ( hEdge == hFirstEdge )
+ {
+ vertexToEdges[nVertIndex] = edges.Next( hFirstEdge );
+ }
+ edges.Free( hEdge );
+ return;
+ }
+
+ // No match! Insert the disconnected edge into the edge list
+ int hNewEdge = edges.Alloc( true );
+ EdgeInfo_t &newEdge = edges[hNewEdge];
+ newEdge.m_nConnectedVertId = nConnectedVertId;
+ newEdge.m_nEdgeIndex = nEdgeIndex;
+ newEdge.m_nTriangleId = nTriangleId;
+ edges.LinkBefore( hFirstEdge, hNewEdge );
+ vertexToEdges[nVertIndex] = hNewEdge;
+}
+
+void COptimizedModel::BuildNeighborInfo( TriangleList_t& list, int nMaxVertexId )
+{
+ // NOTE: vertexToEdges[vertId] contains the index of the head of a linked list stored in edges
+ CUtlFixedLinkedList< EdgeInfo_t > edges;
+ CUtlVector< int > vertexToEdges;
+ vertexToEdges.SetCount( nMaxVertexId );
+ memset( vertexToEdges.Base(), 0, nMaxVertexId * sizeof(int) );
+
+ int pEdgeVertIds[2];
+ int nTriCount = list.Count();
+ for ( int i = 0; i < nTriCount; ++i )
+ {
+ Triangle_t &tri = list[i];
+
+ // Add the three edges for this tri into a lookup table indexed by the lower vertID
+ pEdgeVertIds[0] = tri.vertID[0]; pEdgeVertIds[1] = tri.vertID[1];
+ FindMatchingEdge( list, i, 0, pEdgeVertIds, vertexToEdges, edges );
+
+ pEdgeVertIds[0] = tri.vertID[1]; pEdgeVertIds[1] = tri.vertID[2];
+ FindMatchingEdge( list, i, 1, pEdgeVertIds, vertexToEdges, edges );
+
+ pEdgeVertIds[0] = tri.vertID[2]; pEdgeVertIds[1] = tri.vertID[0];
+ FindMatchingEdge( list, i, 2, pEdgeVertIds, vertexToEdges, edges );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a single strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::ProcessStripGroup( StripGroup_t *pStripGroup, bool isHWSkinned,
+ bool isFlexed, mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ TriangleProcessedList_t& trianglesProcessed,
+ int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, bool forceNoFlex, bool bHWFlex )
+{
+ ComputeStripGroupFlags( pStripGroup, isHWSkinned, isFlexed );
+
+ // all of the triangles before stripping for the current stripgroup.
+ TriangleList_t stripGroupSourceTriangles;
+ VertexList_t stripGroupVertices;
+
+ // FIXME: Flexed/HWSkinned state of faces don't change with each pass.
+ // We could precompute those flags just once (instead of doing it 4 times)
+
+ // Add each face to the stripgroup, if it's appropriate
+ for( int n=0; n < srcFaces.Size(); ++n )
+ {
+ // Don't bother processing a triangle that's already been done
+ if (trianglesProcessed[n])
+ continue;
+
+ mstudioiface_t *pFace = &srcFaces[n];
+
+ int preferredBones = isHWSkinned && (bHWFlex || !isFlexed) ? maxBonesPerVert : 0;
+ // start a new strip group header.
+ Vertex_t stripGroupVert[3];
+ bool triangleIsFlexed = GenerateStripGroupVerticesFromFace(
+ pFace, pStudioMesh,
+ preferredBones,
+ stripGroupVert );
+
+ if( forceNoFlex )
+ {
+ triangleIsFlexed = false;
+ }
+
+ // Don't bother to add the vertices to our strip-group vertex list
+ // if we don't need to.
+ if ( triangleIsFlexed != isFlexed )
+ continue;
+
+ // If we're doing software skinning, then always accept, even if
+ // this triangle should have been a HW skinned triangle.
+ if (isHWSkinned)
+ {
+ // Check how many unique bones were in that face, and how many vertices
+ // maximally in a vertex
+ int numVertexBones = CountMaxVertBones( 3, stripGroupVert );
+ int numTriangleBones = CountUniqueBones( 3, stripGroupVert );
+
+ // If we have too many, we'll be skinning in software
+ bool triangleIsHWSkinned = ( numTriangleBones <= maxBonesPerTri ) &&
+ ( numVertexBones <= maxBonesPerVert );
+
+ // Don't bother to add vertices which aren't gonna be used in this pass
+ if (!triangleIsHWSkinned)
+ continue;
+ }
+
+ // Add a new triangle to our list of triangles
+ int triIndex = stripGroupSourceTriangles.AddToTail( );
+
+ Triangle_t& newTri = stripGroupSourceTriangles[triIndex];
+ newTri.vertID[0] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[0] );
+ newTri.vertID[1] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[1] );
+ newTri.vertID[2] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[2] );
+ BuildTriangleBoneData( stripGroupVertices, newTri );
+
+ // By default, this processes a triangle
+ // Later on below, we may decide to un-process triangles
+ // if tristrips that are too small are generated
+ trianglesProcessed[n] = true;
+ }
+
+ // No mesh? bye bye
+ if (stripGroupSourceTriangles.Size() == 0)
+ return;
+
+ // Figure out neighboring triangles
+ BuildNeighborInfo( stripGroupSourceTriangles, stripGroupVertices.Count() );
+
+ // Build the actual strips
+ if( isHWSkinned )
+ {
+ BuildHWSkinnedStrips( stripGroupSourceTriangles, stripGroupVertices, pStripGroup, maxBonesPerStrip );
+
+ // Check to see if any strips were produced that are too small
+ // If so, remove them, and let the software pass take care of them.
+ //if (stripGroupSourceTriangles.Size() < m_MinimumGroupSize
+ // trianglesProcessed[n] = false;
+ }
+ else
+ {
+ BuildSWSkinnedStrips( stripGroupSourceTriangles, stripGroupVertices, pStripGroup );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// How many unique bones are in a strip?
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::CountUniqueBonesInStrip( StripGroup_t *pStripGroup, Strip_t *pStrip )
+{
+ int *boneUsageCounts = ( int * )_alloca( m_NumBones * sizeof( int ) );
+ memset( boneUsageCounts, 0, sizeof( int ) * m_NumBones );
+ int i;
+ for( i = 0; i < pStrip->numStripGroupVerts; i++ )
+ {
+ Vertex_t *pVert = &pStripGroup->verts[i+pStrip->stripGroupVertexOffset];
+ if( !g_staticprop )
+ {
+ Assert( pVert->numBones != 0 );
+ }
+ int j;
+ for( j = 0; j < pVert->numBones; j++ )
+ {
+ int boneID;
+ boneID = pVert->boneID[j];
+ Assert( boneID != -1 );
+ boneUsageCounts[boneID]++;
+ }
+ }
+ int numBones = 0;
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ if( boneUsageCounts[i] )
+ {
+ numBones++;
+ }
+ }
+ return numBones;
+}
+
+
+//-----------------------------------------------------------------------------
+// A little work to be done after we construct the strip groups
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::PostProcessStripGroup( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, StripGroup_t *pStripGroup )
+{
+ int i;
+
+ // We're gonna compile all of the vertices in the current strip into
+ // the current strip group's vertex list
+ for( i = 0; i < pStripGroup->strips.Size(); i++ )
+ {
+ // create sorted strip verts and indices in the stripgroup
+ Strip_t *pStrip = &pStripGroup->strips[i];
+ int vertOffset = pStripGroup->verts.Size();
+ pStrip->stripGroupVertexOffset = vertOffset;
+ pStrip->stripGroupIndexOffset = pStripGroup->indices.Size();
+
+ // make sure we have enough memory allocated
+ pStripGroup->indices.EnsureCapacity( pStripGroup->indices.Size() + pStrip->numIndices );
+
+ // Try to find each of the strip's vertices in the strip group
+ int maxNumBones = 0;
+ int j;
+ for( j = 0; j < pStrip->numIndices; j++ )
+ {
+ int newIndex = -1;
+ int index = pStrip->pIndices[j];
+ Vertex_t *pVert = &pStrip->verts[index];
+
+ // Does this vertex exist in the strip group?
+ int k;
+ for( k = vertOffset; k < pStripGroup->verts.Size(); k++ )
+ {
+ if( pVert->origMeshVertID == pStripGroup->verts[k].origMeshVertID )
+ {
+ newIndex = k;
+ break;
+ }
+ }
+
+ // Didn't find it? Add the vertex to the list
+ if( newIndex == -1 )
+ {
+ newIndex = pStripGroup->verts.AddToTail( *pVert );
+ }
+ pStripGroup->indices.AddToTail( newIndex );
+
+#ifdef _DEBUG
+ // float GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ int i;
+ float *pWeight = ( float * )_alloca( sizeof( float ) * pVert->numBones );
+ for( i = 0; i < pVert->numBones; i++ )
+ {
+ pWeight[i] = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, pVert, i );
+ }
+#endif
+
+ // Keep track of the max # of bones in a vert
+ if (pVert->numBones > maxNumBones)
+ maxNumBones = pVert->numBones;
+ }
+ pStrip->numStripGroupIndices = pStripGroup->indices.Size() - pStrip->stripGroupIndexOffset;
+ pStrip->numStripGroupVerts = pStripGroup->verts.Size() - pStrip->stripGroupVertexOffset;
+
+ // The number of bones in a strip is the max number of
+ // bones in a vertex in this strip for vertex shaders, and it's the
+ // number of unique bones in the strip for fixed-function
+ if (!m_UsesFixedFunction)
+ pStrip->numBones = maxNumBones;
+ else
+ pStrip->numBones = CountUniqueBonesInStrip(pStripGroup, pStrip);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns true if a particular mesh is teeth
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::MeshIsTeeth( studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh )
+{
+ // The mesh is teeth if it's got a skin whose material has a non-zero flags
+ int i;
+ for( i = 0; i < pStudioHeader->numskinfamilies; i++ )
+ {
+ short *pskinref = pStudioHeader->pSkinref( 0 );
+ pskinref += ( i * pStudioHeader->numskinref );
+ mstudiotexture_t *ptexture;
+ ptexture = pStudioHeader->pTexture( pskinref[ pStudioMesh->material ] );
+ if( ptexture->flags )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the flags for a mesh
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ComputeMeshFlags( Mesh_t *pMesh, studiohdr_t *pStudioHeader,
+ mstudiomesh_t *pStudioMesh )
+{
+ // eyeball?
+ pMesh->flags = 0;
+ if( pStudioMesh->materialtype != 0 )
+ {
+ pMesh->flags |= MESH_IS_EYES;
+ }
+
+ // teeth?
+ if( MeshIsTeeth( pStudioHeader, pStudioMesh ) )
+ {
+ pMesh->flags |= MESH_IS_TEETH;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates 4 strip groups for a mesh, combinations of flexed + hwskinned
+// A mesh has a single material
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ProcessMesh( Mesh_t *pMesh, studiohdr_t *pStudioHeader,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ bool forceNoFlex, bool bForceSoftwareSkin, bool bHWFlex )
+{
+ // Compute the mesh flags
+ ComputeMeshFlags( pMesh, pStudioHeader, pStudioMesh );
+
+ // We're gonna keep track of which ones we haven't processed
+ // because we're gonna add all unprocessed faces to the software
+ // lists if for some reason they don't get added to the hardware lists
+ TriangleProcessedList_t trianglesProcessed;
+ trianglesProcessed.AddMultipleToTail( srcFaces.Size() );
+ memset( trianglesProcessed.Base(), 0, trianglesProcessed.Size() );
+
+ // there are up to 4 stripgroups per mesh
+ // Note that we're gonna do the HW skinned versions first
+ // because they have the option of deciding not to be hardware after all
+ int isHWSkinned, isFlexed;
+ for( isHWSkinned = bForceSoftwareSkin ? 0 : 1; isHWSkinned >= 0; --isHWSkinned )
+ {
+ for( isFlexed = 1; isFlexed >= 0; --isFlexed )
+ {
+ int realMaxBonesPerTri, realMaxBonesPerVert, realMaxBonesPerStrip;
+
+ if( isFlexed && !bHWFlex )
+ {
+ realMaxBonesPerTri = 1;
+ realMaxBonesPerVert = 1;
+ realMaxBonesPerStrip = 1;
+ }
+ else
+ {
+ realMaxBonesPerTri = m_MaxBonesPerTri;
+ realMaxBonesPerVert = m_MaxBonesPerVert;
+ realMaxBonesPerStrip = m_MaxBonesPerStrip;
+ }
+
+ int newStripGroupIndex = pMesh->stripGroups.AddToTail( );
+ StripGroup_t& newStripGroup = pMesh->stripGroups[newStripGroupIndex];
+ ProcessStripGroup( &newStripGroup,
+ isHWSkinned ? true : false,
+ isFlexed ? true : false,
+ pStudioModel, pStudioMesh, srcFaces, trianglesProcessed,
+ realMaxBonesPerVert, realMaxBonesPerTri, realMaxBonesPerStrip, forceNoFlex, bHWFlex );
+
+ PostProcessStripGroup( pStudioModel, pStudioMesh, &newStripGroup );
+
+ // Clear out the strip group if there wasn't anything in it
+ if( !newStripGroup.indices.Size() )
+ pMesh->stripGroups.FastRemove( newStripGroupIndex );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// some setup required before we really get into it
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::SetupMeshProcessing( studiohdr_t *pHdr, int vertexCacheSize,
+ bool usesFixedFunction, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName )
+{
+#ifdef NVTRISTRIP
+ // tell nvtristrip all of it's params
+ SetCacheSize( vertexCacheSize );
+ SetStitchStrips( true );
+ SetMinStripSize( 0 );
+# ifdef EMIT_TRILISTS
+ SetListsOnly( true );
+# else
+ SetListsOnly( false );
+# endif
+#endif // NVTRISTRIP
+
+ if( !g_quiet )
+ {
+ printf( "---------------------\n" );
+ printf( "Generating optimized mesh \"%s\":\n", fileName );
+#ifdef _DEBUG
+ printf( "\tvertex cache size: %d\n", vertexCacheSize );
+ printf( "\tmax bones/tri: %d\n", maxBonesPerTri );
+ printf( "\tmax bones/vert: %d\n", maxBonesPerVert );
+ printf( "\tmax bones/strip: %d\n", maxBonesPerStrip );
+#endif
+ }
+
+ CleanupEverything();
+
+ // Total number of bones in the original model
+ m_NumBones = pHdr->numbones;
+
+ // Hardware limitations
+ m_MaxBonesPerVert = maxBonesPerVert;
+ m_MaxBonesPerTri = maxBonesPerTri;
+ m_MaxBonesPerStrip = maxBonesPerStrip;
+ m_UsesFixedFunction = usesFixedFunction;
+ m_VertexCacheSize = vertexCacheSize;
+
+ // stats
+ m_NumSkinnedAndFlexedVerts = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the entire model, return stats...
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::GetTotalVertsForMesh( Mesh_t *pMesh )
+{
+ int numVerts = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numVerts += pStripGroup->verts.Size();
+ }
+ return numVerts;
+}
+
+int COptimizedModel::GetTotalIndicesForMesh( Mesh_t *pMesh )
+{
+ int numIndices = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numIndices += pStripGroup->indices.Size();
+ }
+ return numIndices;
+}
+
+int COptimizedModel::GetTotalStripsForMesh( Mesh_t *pMesh )
+{
+ int numStrips = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numStrips += pStripGroup->strips.Size();
+ }
+ return numStrips;
+}
+
+int COptimizedModel::GetTotalStripGroupsForMesh( Mesh_t *pMesh )
+{
+ return pMesh->stripGroups.Size();
+}
+
+int COptimizedModel::GetTotalBoneStateChangesForMesh( Mesh_t *pMesh )
+{
+ int numBoneStateChanges = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ int j;
+ for( j = 0; j < pStripGroup->strips.Size(); j++ )
+ {
+ Strip_t *pStrip = &pStripGroup->strips[j];
+ numBoneStateChanges += pStrip->numBoneStateChanges;
+ }
+ }
+ return numBoneStateChanges;
+}
+
+
+
+/*
+static void WriteDebugFile( const char *fileName, const char *outFileName, float red, float grn, float blu )
+{
+ char *tmpName = ( char * )_alloca( strlen( fileName ) + 1 );
+ strcpy( tmpName, fileName );
+
+ s_source_t *pSrc = Load_Source( tmpName, "SMD" );
+ Assert( pSrc );
+
+ int i, j;
+
+ FILE *fp;
+ fp = fopen( outFileName, "w" );
+ Assert( fp );
+
+ for( i = 0; i < pSrc->nummeshes; i++ )
+ {
+ s_mesh_t *pMesh = &pSrc->mesh[i];
+ for( j = 0; j < pMesh->numfaces; j++ )
+ {
+ s_face_t *pFace = &pSrc->face[pMesh->faceoffset + j];
+ Vector &a = pSrc->vertex[pMesh->vertexoffset + pFace->a];
+ Vector &b = pSrc->vertex[pMesh->vertexoffset + pFace->b];
+ Vector &c = pSrc->vertex[pMesh->vertexoffset + pFace->c];
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( a[0] ), ( float )( a[1] ), ( float )( a[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( b[0] ), ( float )( b[1] ), ( float )( b[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( c[0] ), ( float )( c[1] ), ( float )( c[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ }
+
+ }
+ fclose( fp );
+}
+*/
+
+void COptimizedModel::SourceMeshToTriangleList( s_model_t *pSrcModel, s_mesh_t *pSrcMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList )
+{
+ s_face_t *pFaces = pSrcModel->source->face + pSrcMesh->faceoffset;
+ int i;
+ for ( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ const s_face_t &pFace = pFaces[i];
+ int j = meshTriangleList.AddToTail();
+ mstudioiface_t &newTriangle = meshTriangleList[j];
+ newTriangle.a = pFace.a;
+ newTriangle.b = pFace.b;
+ newTriangle.c = pFace.c;
+ }
+}
+
+/*
+static void WriteSourceMesh( s_model_t *pSrcModel, s_mesh_t *pSrcMesh, int red_, int grn_, int blu_ )
+{
+ FILE *fp;
+ fp = fopen( "blah.glv", "a+" );
+ float red, grn, blu;
+
+ red = ( float )rand() / ( float )VALVE_RAND_MAX;
+ grn = ( float )rand() / ( float )VALVE_RAND_MAX;
+ blu = ( float )rand() / ( float )VALVE_RAND_MAX;
+ float len = red * red + grn * grn + blu * blu;
+ len = sqrt( len );
+ red *= 255.0f / len;
+ grn *= 255.0f / len;
+ blu *= 255.0f / len;
+
+ s_face_t *pFaces = pSrcModel->source->face + pSrcMesh->faceoffset;
+ int i;
+ for( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ const s_face_t &face = pFaces[i];
+ Vector a, b, c;
+ a = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.a];
+ b = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.b];
+ c = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.c];
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", a[0], a[1], a[2], red, grn, blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", c[0], c[1], c[2], red, grn, blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", b[0], b[1], b[2], red, grn, blu );
+ }
+ fclose( fp );
+}
+*/
+
+static void RandomColor( Vector& color )
+{
+ color[0] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ color[1] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ color[2] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ VectorNormalize( color );
+}
+
+/*
+static void GLViewCube( Vector pos, float size, FILE *fp )
+{
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+}
+*/
+
+#if 0
+static void MStudioBoneWeightToSBoneWeight( s_boneweight_t &sbone, const mstudioboneweight_t &mbone,
+ const s_source_t *pSrc )
+{
+ int i;
+ for( i = 0; i < mbone.numbones; i++ )
+ {
+ sbone.bone[i] = pSrc->boneimap[mbone.bone[i]];
+// sbone.bone[i] = mbone.bone[i];
+ sbone.weight[i] = mbone.weight[i];
+ }
+ sbone.numbones = mbone.numbones;
+}
+#endif
+
+void COptimizedModel::CreateLODTriangleList( s_model_t *pSrcModel, int nLodID, s_source_t* pSrc,
+ mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList,
+ bool writeDebug )
+{
+ if ( !pSrc || !pSrcModel )
+ return;
+
+#ifdef _DEBUG
+// const mstudio_modelvertexdata_t *vertData = pStudioModel->GetVertexData();
+// Assert( vertData ); // This can only return NULL on X360 for now
+// mstudiovertex_t *modelFirstVert = vertData->Vertex( 0 );
+// mstudiovertex_t *modelLastVert = vertData->Vertex( pStudioModel->numvertices - 1 );
+// mstudiovertex_t *meshFirstVert = vertData->Vertex( 0 );
+// mstudiovertex_t *meshLastVert = vertData->Vertex( pStudioMesh->numvertices - 1 );
+#endif
+
+/*
+ if( writeDebug )
+ {
+ printf( "MODEL VERTS:\n" );
+ int i;
+ for( i = 0; i < pStudioModel->numvertices; i++ )
+ {
+ Vector &v = *pStudioModel->pVertex( i );
+ Vector &n = *pStudioModel->pNormal( i );
+ Vector2D &t = *pStudioModel->pTexcoord( i );
+ printf( "model %d: p %f %f %f n: %f %f %f t: %f %f\n",
+ i, v[0], v[1], v[2], n[0], n[1], n[2], t[0], t[1] );
+ }
+ printf( "MESH VERTS:\n" );
+ for( i = 0; i < pStudioMesh->numvertices; i++ )
+ {
+ Vector &v = *pStudioMesh->pVertex( i );
+ Vector &n = *pStudioMesh->pNormal( i );
+ Vector2D &t = *pStudioMesh->pTexcoord( i );
+ printf( "mesh %d: p %f %f %f n: %f %f %f t: %f %f\n",
+ i, v[0], v[1], v[2], n[0], n[1], n[2], t[0], t[1] );
+ }
+ }
+*/
+ // need to find the mesh in the lod model that matches the original model.
+ int i;
+ int textureSearchID = MaterialToTexture( pStudioMesh->material );
+
+ // this is icky.. . pSrc->nummeshes really refers to the total number of non-empty meshes
+ // in the model. In pSrc->meshes, there are some empty meshes in the middle if they
+ // don't exist for the material, so you have to go through all of the meshes to find
+ // the non-empty ones.
+ s_mesh_t *pSrcMesh = NULL;
+ for ( i = 0; i < pStudioModel->nummeshes; i++ )
+ {
+ if ( pSrc->texmap[pSrc->meshindex[i]] == textureSearchID )
+ {
+ // woo hoo! found it.
+ pSrcMesh = &pSrc->mesh[pSrc->meshindex[i]];
+ break;
+ }
+ }
+ if ( !pSrcMesh )
+ {
+ //printf( "%s doesn't have material %d\n", lodSMDName, textureSearchID );
+ // There aren't any triangles in this lower lod with this material on it.
+ return;
+ }
+
+ CUtlVector<int> indexMapping;
+ indexMapping.AddMultipleToTail( pSrcMesh->numvertices );
+
+ for ( i = 0; i < pSrcMesh->numvertices; i++ )
+ {
+ // get the mapping between indices in the lod and their real pool location.
+ indexMapping[i] = pSrcModel->m_pLodData->pMeshVertIndexMaps[nLodID][pSrcMesh->vertexoffset + i];
+ }
+
+ // build the lod's faces so indexes map to remapped vertexes
+ for ( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ int index = meshTriangleList.AddToTail();
+ mstudioiface_t& newFace = meshTriangleList[index];
+ const s_face_t &srcFace = pSrc->face[pSrcMesh->faceoffset + i];
+ newFace.a = indexMapping[srcFace.a];
+ newFace.b = indexMapping[srcFace.b];
+ newFace.c = indexMapping[srcFace.c];
+ }
+}
+
+
+bool ComparePath( const char *a, const char *b )
+{
+ if ( strlen( a ) != strlen( b ) )
+ {
+ return false;
+ }
+
+ // case and seperator invariant
+ for ( ; *a; a++, b++ )
+ {
+ if ( *a == *b )
+ {
+ continue;
+ }
+ if ( tolower( *a ) == tolower( *b ) )
+ {
+ continue;
+ }
+ if ( ( *a == '/' || *a == '\\' ) &&
+ ( *b == '/' || *b == '\\' ) )
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool COptimizedModel::MeshNeedsRemoval( studiohdr_t *pHdr, mstudiomesh_t *pStudioMesh,
+ LodScriptData_t& scriptLOD )
+{
+ mstudiotexture_t *ptexture;
+ short *pskinref = pHdr->pSkinref( 0 );
+ ptexture = pHdr->pTexture( pskinref[ pStudioMesh->material ] );
+ const char *meshName = ptexture->material->GetName();
+ int i;
+ for( i = 0; i < scriptLOD.meshRemovals.Size(); i++ )
+ {
+ const char *meshRemovalName = scriptLOD.meshRemovals[i].GetSrcName();
+ if ( ComparePath( meshName, meshRemovalName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the entire model, return stats...
+//-----------------------------------------------------------------------------
+void COptimizedModel::ProcessModel( studiohdr_t *pHdr, s_bodypart_t *pSrcBodyParts,
+ TotalMeshStats_t& stats, bool bForceSoftwareSkin, bool bHWFlex )
+{
+ memset( &stats, 0, sizeof(stats) );
+ m_Models.RemoveAll();
+
+ int bodyPartID, modelID, meshID, lodID;
+ for ( bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++, stats.m_TotalBodyParts++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ s_bodypart_t *pSrcBodyPart = &pSrcBodyParts[bodyPartID];
+ for ( modelID = 0; modelID < pBodyPart->nummodels; modelID++, stats.m_TotalModels++ )
+ {
+ int i = m_Models.AddToTail();
+ Model_t& newModel = m_Models[i];
+ mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID );
+ s_model_t *pSrcModel = pSrcBodyPart->pmodel[modelID];
+ for ( lodID = 0; lodID < g_ScriptLODs.Count(); lodID++, stats.m_TotalModelLODs++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+ s_source_t *pLODSource = pSrcModel->m_LodSources[lodID];
+
+ int i = newModel.modelLODs.AddToTail();
+ Assert( i == lodID );
+ ModelLOD_t& newLOD = newModel.modelLODs[i];
+ newLOD.switchPoint = scriptLOD.switchValue;
+
+ // In this case, we've been told to remove the model
+ if ( !pLODSource )
+ {
+ if ( pSrcModel && stricmp( pSrcModel->name, "blank" ) != 0)
+ {
+ // This is nonsensical
+ Assert( lodID != 0 );
+ }
+ continue;
+ }
+
+ for ( meshID = 0; meshID < pStudioModel->nummeshes; meshID++, stats.m_TotalMeshes++ )
+ {
+#ifdef _DEBUG
+// printf( "bodyPart: %d model: %d modellod: %d mesh: %d\n", bodyPartID, modelID, lodID, meshID );
+#endif
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ s_mesh_t *pSrcMesh = &pSrcModel->source->mesh[pSrcModel->source->meshindex[meshID]];
+
+ int i = newLOD.meshes.AddToTail();
+ Assert( i == meshID );
+ Mesh_t& newMesh = newLOD.meshes[i];
+
+ if ( MeshNeedsRemoval( pHdr, pStudioMesh, scriptLOD ) )
+ continue;
+#ifdef _DEBUG
+// int textureSearchID = material_to_texture( pStudioMesh->material );
+// const char *pDebugName = pHdr->pTexture( textureSearchID )->pszName( );
+#endif
+ CUtlVector<mstudioiface_t> meshTriangleList;
+ if ( pLODSource )
+ {
+ // map the lod data to triangles
+ // uses the original mesh redirected through a mapping table
+ // this expects built per lod-to-root mapping tables to generate faces
+ CreateLODTriangleList( pSrcModel, lodID, pLODSource, pStudioModel, pStudioMesh, meshTriangleList, false );
+ }
+ else
+ {
+ // build the triangle list from the unmapped source
+ SourceMeshToTriangleList( pSrcModel, pSrcMesh, meshTriangleList );
+ }
+
+ ProcessMesh( &newMesh, pHdr, meshTriangleList, pStudioModel, pStudioMesh,
+ !scriptLOD.GetFacialAnimationEnabled(), bForceSoftwareSkin, bHWFlex );
+
+ stats.m_TotalVerts += GetTotalVertsForMesh( &newMesh );
+ stats.m_TotalIndices += GetTotalIndicesForMesh( &newMesh );
+ stats.m_TotalStrips += GetTotalStripsForMesh( &newMesh );
+ stats.m_TotalStripGroups += GetTotalStripGroupsForMesh( &newMesh );
+ stats.m_TotalBoneStateChanges += GetTotalBoneStateChangesForMesh( &newMesh );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Some processing that happens at the end
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::MapGlobalBonesToHardwareBoneIDsAndSortBones( studiohdr_t *phdr )
+{
+ // garymcthack: holy jesus is this function long!
+#ifdef IGNORE_BONES
+ return;
+#endif
+
+ int *globalToHardwareBoneIndex;
+ int hardwareToGlobalBoneIndex[MAX_NUM_BONES_PER_STRIP];
+
+ globalToHardwareBoneIndex = ( int * )_alloca( m_NumBones * sizeof( int ) );
+ Assert( globalToHardwareBoneIndex );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ {
+ // Dear LORD! This needs to be in another function! (garymcthack)
+ int i;
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ globalToHardwareBoneIndex[i] = -1;
+ }
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ hardwareToGlobalBoneIndex[i] = -1;
+ }
+
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ // Only do this to HW-skinned meshes
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ if( !( pStripGroup->flags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ continue;
+ }
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ // printf( "UPDATING BONE STATE\n" );
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+
+ // Generate mapping back and forth between hardware IDs and original bone IDs
+ int boneStateChangeID;
+ for( boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *pBoneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ globalToHardwareBoneIndex[pBoneStateChange->newBoneID] = pBoneStateChange->hardwareID;
+ if( pBoneStateChange->hardwareID != -1 )
+ {
+ hardwareToGlobalBoneIndex[pBoneStateChange->hardwareID] = pBoneStateChange->newBoneID;
+ }
+ }
+ int vertID;
+ for( vertID = 0; vertID < pStrip->numVerts; vertID++ )
+ {
+ Vertex_t *vert = pStripGroup->pVertex( vertID + pStrip->vertOffset );
+ int boneID;
+ for( boneID = 0; boneID < header->maxBonesPerVert; boneID++ )
+ {
+ int globalBoneID = vert->boneID[boneID];
+ if( globalBoneID == -1 )
+ {
+ // this index isn't used.
+ vert->boneID[boneID] = 0;
+ // make sure it's associated weight is zero.
+ // Assert( vert->boneWeights[boneID] == 0 );
+ continue;
+ }
+ Assert( globalBoneID >= 0 && globalBoneID < m_NumBones );
+ vert->boneID[boneID] = globalToHardwareBoneIndex[globalBoneID];
+ Assert( vert->boneID[boneID] >= 0 && vert->boneID[boneID] < header->maxBonesPerStrip );
+ }
+
+ // We only do index palette skinning when we're not doing fixed function
+ if (m_UsesFixedFunction)
+ {
+// bool flexed = pStripGroup->flags & STRIPGROUP_IS_FLEXED;
+ SortBonesWithinVertex( false /* flexed */, vert, pStudioModel,
+ pStudioMesh, globalToHardwareBoneIndex, hardwareToGlobalBoneIndex,
+ m_MaxBonesPerTri, m_MaxBonesPerVert );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are associated with writing a .vtx file
+//
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::WriteHeader( int vertCacheSize, int maxBonesPerVert,
+ int maxBonesPerTri, int maxBonesPerStrip, int numBodyParts, long checkSum )
+{
+ FileHeader_t fileHeader;
+
+ fileHeader.version = OPTIMIZED_MODEL_FILE_VERSION;
+ fileHeader.vertCacheSize = vertCacheSize;
+ fileHeader.maxBonesPerTri = IsUShort( maxBonesPerTri );
+ fileHeader.maxBonesPerVert = maxBonesPerVert;
+ fileHeader.maxBonesPerStrip = IsUShort( maxBonesPerStrip );
+ fileHeader.numBodyParts = numBodyParts;
+ fileHeader.bodyPartOffset = sizeof( FileHeader_t );
+ fileHeader.checkSum = checkSum;
+ fileHeader.numLODs = g_ScriptLODs.Size();
+ fileHeader.materialReplacementListOffset = m_MaterialReplacementsListOffset;
+ m_FileBuffer->WriteAt( 0, &fileHeader, sizeof( FileHeader_t ), "header" );
+#ifdef _DEBUG
+// FileHeader_t *debug = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+// BodyPartHeader_t *pBodyPart = debug->pBodyPart( 0 );
+#endif
+}
+
+
+void COptimizedModel::WriteBodyPart( int bodyPartID, mstudiobodyparts_t *pBodyPart, int modelID )
+{
+ BodyPartHeader_t bodyPart;
+ bodyPart.numModels = pBodyPart->nummodels;
+ int bodyPartOffset = m_BodyPartsOffset + bodyPartID * sizeof( BodyPartHeader_t );
+ int modelFileOffset = m_ModelsOffset + modelID * sizeof( ModelHeader_t );
+ bodyPart.modelOffset = modelFileOffset - bodyPartOffset;
+ m_FileBuffer->WriteAt( bodyPartOffset, &bodyPart, sizeof( BodyPartHeader_t ), "bodypart" );
+#ifdef _DEBUG
+// BodyPartHeader_t *debug = ( BodyPartHeader_t * )m_FileBuffer->GetPointer( bodyPartOffset );
+// ModelHeader_t *pModel = debug->pModel( 0 );
+#endif
+}
+
+void COptimizedModel::WriteModel( int modelID, mstudiomodel_t *pModel, int lodID )
+{
+ ModelHeader_t model;
+ model.numLODs = IsChar( g_ScriptLODs.Size() );
+ int modelFileOffset = m_ModelsOffset + modelID * sizeof( ModelHeader_t );
+ int lodFileOffset = m_ModelLODsOffset + lodID * sizeof( ModelLODHeader_t );
+ model.lodOffset = lodFileOffset - modelFileOffset;
+ m_FileBuffer->WriteAt( modelFileOffset, &model, sizeof( ModelHeader_t ), "model" );
+#ifdef _DEBUG
+// ModelHeader_t *debug = ( ModelHeader_t * )m_FileBuffer->GetPointer( modelFileOffset );
+// ModelLODHeader_t *pLOD = debug->pLOD( 0 );
+#endif
+}
+
+void COptimizedModel::WriteModelLOD( int lodID, ModelLOD_t *pLOD, int meshID )
+{
+ ModelLODHeader_t lod;
+ int lodFileOffset = m_ModelLODsOffset + lodID * sizeof( ModelLODHeader_t );
+ int meshFileOffset = m_MeshesOffset + meshID * sizeof( MeshHeader_t );
+ lod.meshOffset = meshFileOffset - lodFileOffset;
+ lod.numMeshes = pLOD->meshes.Size();
+ lod.switchPoint = pLOD->switchPoint;
+ m_FileBuffer->WriteAt( lodFileOffset, &lod, sizeof( ModelLODHeader_t ), "modellod" );
+#ifdef _DEBUG
+// ModelLODHeader_t *debug = ( ModelLODHeader_t * )m_FileBuffer->GetPointer( lodFileOffset );
+// MeshHeader_t *pMesh = debug->pMesh( 0 );
+#endif
+}
+
+void COptimizedModel::WriteMesh( int meshID, Mesh_t *pMesh, int stripGroupID )
+{
+ MeshHeader_t mesh;
+ mesh.numStripGroups = IsChar( pMesh->stripGroups.Size() );
+ int meshFileOffset = m_MeshesOffset + meshID * sizeof( MeshHeader_t );
+ int stripGroupFileOffset = m_StripGroupsOffset + stripGroupID * sizeof( StripGroupHeader_t );
+ mesh.stripGroupHeaderOffset = stripGroupFileOffset - meshFileOffset;
+ mesh.flags = pMesh->flags;
+ m_FileBuffer->WriteAt( meshFileOffset, &mesh, sizeof( MeshHeader_t ), "mesh" );
+#ifdef _DEBUG
+// MeshHeader_t *debug = ( MeshHeader_t * )m_FileBuffer->GetPointer( meshFileOffset );
+// StripGroupHeader_t *pStripGroup = debug->pStripGroup( 0 );
+#endif
+}
+
+void COptimizedModel::WriteStripGroup( int stripGroupID, StripGroup_t *pStripGroup,
+ int vertID, int indexID, int stripID )
+{
+ StripGroupHeader_t stripGroup;
+ stripGroup.numVerts = pStripGroup->verts.Size();
+ stripGroup.numIndices = pStripGroup->indices.Size();
+ stripGroup.numStrips = pStripGroup->strips.Size();
+ stripGroup.flags = IsByte( pStripGroup->flags );
+ int stripGroupFileOffset = m_StripGroupsOffset + stripGroupID * sizeof( StripGroupHeader_t );
+ int vertsFileOffset = m_VertsOffset + vertID * sizeof( Vertex_t );
+ int indicesFileOffset = m_IndicesOffset + indexID * sizeof( unsigned short );
+ int stripsFileOffset = m_StripsOffset + stripID * sizeof( StripHeader_t );
+ stripGroup.vertOffset = vertsFileOffset - stripGroupFileOffset;
+ stripGroup.indexOffset = indicesFileOffset - stripGroupFileOffset;
+ stripGroup.stripOffset = stripsFileOffset - stripGroupFileOffset;
+ m_FileBuffer->WriteAt( stripGroupFileOffset, &stripGroup, sizeof( StripGroupHeader_t), "strip group" );
+#ifdef _DEBUG
+// StripGroupHeader_t *debug = ( StripGroupHeader_t * )m_FileBuffer->GetPointer( stripGroupFileOffset );
+// unsigned short *pIndex = debug->pIndex( 0 );
+// Vertex_t *pVert = debug->pVertex( 0 );
+// StripHeader_t *pStripHeader = debug->pStrip( 0 );
+#endif
+}
+
+int COptimizedModel::WriteVerts( int vertID, StripGroup_t *pStripGroup )
+{
+ int vertFileOffset = m_VertsOffset + vertID * sizeof( Vertex_t );
+ int numVerts = pStripGroup->verts.Size();
+ m_FileBuffer->WriteAt( vertFileOffset, &pStripGroup->verts[0], sizeof( Vertex_t ) * numVerts, "verts" );
+#ifdef _DEBUG
+// Vertex_t *debug = ( Vertex_t * )m_FileBuffer->GetPointer( vertFileOffset );
+#endif
+ return numVerts;
+}
+
+int COptimizedModel::WriteIndices( int indexID, StripGroup_t *pStripGroup )
+{
+ int indexFileOffset = m_IndicesOffset + indexID * sizeof( unsigned short );
+ int numIndices = pStripGroup->indices.Size();
+ m_FileBuffer->WriteAt( indexFileOffset, &pStripGroup->indices[0], sizeof( unsigned short ) * numIndices, "indices" );
+#ifdef _DEBUG
+// unsigned short *debug = ( unsigned short * )m_FileBuffer->GetPointer( indexFileOffset );
+#endif
+ return numIndices;
+}
+
+void COptimizedModel::WriteStrip( int stripID, Strip_t *pStrip, int indexID, int vertID, int boneID )
+{
+ StripHeader_t stripHeader;
+
+ stripHeader.numIndices = pStrip->numStripGroupIndices;
+ stripHeader.indexOffset = pStrip->stripGroupIndexOffset;
+ stripHeader.numVerts = pStrip->numStripGroupVerts;
+ stripHeader.vertOffset = pStrip->stripGroupVertexOffset;
+ stripHeader.numBoneStateChanges = pStrip->numBoneStateChanges;
+ stripHeader.numBones = IsShort( pStrip->numBones );
+ stripHeader.flags = IsByte( pStrip->flags );
+ int boneFileOffset = m_BoneStageChangesOffset + boneID * sizeof( BoneStateChangeHeader_t );
+ int stripFileOffset = m_StripsOffset + stripID * sizeof( StripHeader_t );
+ stripHeader.boneStateChangeOffset = IsInt24( boneFileOffset - stripFileOffset );
+ m_FileBuffer->WriteAt( stripFileOffset, &stripHeader, sizeof( StripHeader_t ), "strip" );
+#ifdef _DEBUG
+// StripHeader_t *debug = ( StripHeader_t* )m_FileBuffer->GetPointer( stripFileOffset );
+// BoneStateChangeHeader_t *pBoneStateChange = debug->pBoneStateChange( 0 );
+#endif
+}
+
+void COptimizedModel::WriteBoneStateChange( int boneID, BoneStateChange_t *boneStateChange )
+{
+ BoneStateChangeHeader_t boneHeader;
+ boneHeader.hardwareID = boneStateChange->hardwareID;
+ boneHeader.newBoneID = boneStateChange->newBoneID;
+ int boneFileOffset = m_BoneStageChangesOffset + boneID * sizeof( BoneStateChangeHeader_t );
+#if 0
+ printf( "\tboneStateChange: hwid: %d boneID %d\n", ( int )boneHeader.hardwareID,
+ ( int )boneHeader.newBoneID );
+#endif
+ m_FileBuffer->WriteAt( boneFileOffset, &boneHeader, sizeof( BoneStateChangeHeader_t ), "bone" );
+#ifdef _DEBUG
+// BoneStateChangeHeader_t *debug = ( BoneStateChangeHeader_t * )m_FileBuffer->GetPointer( boneFileOffset );
+#endif
+}
+
+void COptimizedModel::ZeroNumBones( void )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ int bodyPartID, modelID, lodID, meshID, stripGroupID, vertID, stripID;
+ for( bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *pBodyPart = header->pBodyPart( bodyPartID );
+ for( modelID = 0; modelID < pBodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *pModel = pBodyPart->pModel( modelID );
+ for( lodID = 0; lodID < pModel->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = pModel->pLOD( lodID );
+ for( meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *pMesh = pLOD->pMesh( meshID );
+ for( stripGroupID = 0; stripGroupID < pMesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = pMesh->pStripGroup( stripGroupID );
+ for( vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ Vertex_t *pVert = pStripGroup->pVertex( vertID );
+ pVert->numBones = 0;
+ }
+ for( stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ pStrip->numBones = 0;
+ pStrip->numBoneStateChanges = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::WriteStringTable( int stringTableOffset )
+{
+ int stringTableSize = s_StringTable.CalcSize();
+ if( stringTableSize == 0 )
+ {
+ return;
+ }
+ char *pTmp = new char[stringTableSize];
+ s_StringTable.WriteToMem( pTmp );
+ m_FileBuffer->WriteAt( stringTableOffset, pTmp, stringTableSize, "string table" );
+// char *pDebug = ( char * )m_FileBuffer->GetPointer( stringTableOffset );
+ delete [] pTmp;
+}
+
+void COptimizedModel::WriteMaterialReplacements( int materialReplacementsOffset )
+{
+ int offset = materialReplacementsOffset;
+ int i, j;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ for( j = 0; j < scriptLOD.materialReplacements.Size(); j++ )
+ {
+ CLodScriptReplacement_t &materialReplacement = scriptLOD.materialReplacements[j];
+ MaterialReplacementHeader_t tmpHeader;
+ tmpHeader.materialID = FindMaterialByName( materialReplacement.GetSrcName() );
+ tmpHeader.replacementMaterialNameOffset = m_StringTableOffset +
+ s_StringTable.StringTableOffset( materialReplacement.GetDstName() ) - offset;
+ m_FileBuffer->WriteAt( offset, &tmpHeader, sizeof( tmpHeader ), "material replacements" );
+ offset += sizeof( MaterialReplacementHeader_t );
+ }
+ }
+}
+
+void COptimizedModel::WriteMaterialReplacementLists( int materialReplacementsOffset, int materialReplacementListOffset )
+{
+ int replacementOffset = materialReplacementsOffset;
+ int offset = materialReplacementListOffset;
+ int i;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ MaterialReplacementListHeader_t tmpHeader;
+ tmpHeader.numReplacements = IsChar( scriptLOD.materialReplacements.Size() );
+ tmpHeader.replacementOffset = IsInt24( replacementOffset - offset );
+ m_FileBuffer->WriteAt( offset, &tmpHeader, sizeof( tmpHeader ), "material replacement headers" );
+ MaterialReplacementListHeader_t *pDebugList =
+ ( MaterialReplacementListHeader_t * )m_FileBuffer->GetPointer( offset );
+ if( pDebugList->numReplacements )
+ {
+// MaterialReplacementHeader_t *pDebug = pDebugList->pMaterialReplacement( 0 );
+// const char *string = pDebug->pMaterialReplacementName();
+ }
+ replacementOffset += tmpHeader.numReplacements * sizeof( MaterialReplacementHeader_t );
+ offset += sizeof( MaterialReplacementListHeader_t );
+ }
+}
+
+void COptimizedModel::SanityCheckVertexBoneLODFlags( studiohdr_t *pStudioHdr, FileHeader_t *pVtxHeader )
+{
+ int bodyPartID;
+ for( bodyPartID = 0; bodyPartID < pStudioHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyPartID );
+ BodyPartHeader_t *pVtxBodyPart = pVtxHeader->pBodyPart( bodyPartID );
+ int modelID;
+ for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pModel = pBodyPart->pModel( modelID );
+ ModelHeader_t *pVtxModel = pVtxBodyPart->pModel( modelID );
+ int lodID;
+ for( lodID = 0; lodID < pVtxModel->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( lodID );
+ int meshID;
+ Assert( pVtxLOD->numMeshes == pModel->nummeshes );
+ for( meshID = 0; meshID < pVtxLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *pVtxMesh = pVtxLOD->pMesh( meshID );
+ mstudiomesh_t *pMesh = pModel->pMesh( meshID );
+ int stripGroupID;
+ for( stripGroupID = 0; stripGroupID < pVtxMesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = pVtxMesh->pStripGroup( stripGroupID );
+ int vertID;
+ for( vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ Vertex_t *pVertex = pStripGroup->pVertex( vertID );
+ Vector pos = GetOrigVertPosition( pModel, pMesh, pVertex );
+ const mstudioboneweight_t &boneWeight = GetOrigVertBoneWeight( pModel, pMesh, pVertex );
+ int i;
+
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ mstudiobone_t *pBone = pStudioHdr->pBone( boneWeight.bone[i] );
+// const char *pBoneName = pBone->pszName();
+ if (!( pBone->flags & ( BONE_USED_BY_VERTEX_LOD0 << lodID ) ))
+ {
+ MdlError("Mismarked Bone flag");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::WriteVTXFile( studiohdr_t *pHdr, const char *pFileName,
+ TotalMeshStats_t const& stats )
+{
+
+ // calculate file offsets
+ m_FileBuffer = new CFileBuffer( FILEBUFFER_SIZE );
+ m_BodyPartsOffset = sizeof( FileHeader_t );
+ m_ModelsOffset = m_BodyPartsOffset + sizeof( BodyPartHeader_t ) * stats.m_TotalBodyParts;
+ m_ModelLODsOffset = m_ModelsOffset + sizeof( ModelHeader_t ) * stats.m_TotalModels;
+ m_MeshesOffset = m_ModelLODsOffset + sizeof( ModelLODHeader_t ) * stats.m_TotalModelLODs;
+ m_StripGroupsOffset = m_MeshesOffset + sizeof( MeshHeader_t ) * stats.m_TotalMeshes;
+ m_StripsOffset = m_StripGroupsOffset + sizeof( StripGroupHeader_t ) * stats.m_TotalStripGroups;
+ m_VertsOffset = m_StripsOffset + sizeof( StripHeader_t ) * stats.m_TotalStrips;
+ m_IndicesOffset = m_VertsOffset + sizeof( Vertex_t ) * stats.m_TotalVerts;
+ m_BoneStageChangesOffset = m_IndicesOffset + sizeof( unsigned short ) * stats.m_TotalIndices;
+ m_StringTableOffset = m_BoneStageChangesOffset + sizeof( BoneStateChangeHeader_t ) * stats.m_TotalBoneStateChanges;
+ m_MaterialReplacementsOffset = m_StringTableOffset + s_StringTable.CalcSize();
+ m_MaterialReplacementsListOffset = m_MaterialReplacementsOffset + stats.m_TotalMaterialReplacements * sizeof( MaterialReplacementHeader_t );
+ m_EndOfFileOffset = m_MaterialReplacementsListOffset + g_ScriptLODs.Count() * sizeof( MaterialReplacementListHeader_t );
+
+ int curModel = 0;
+ int curLOD = 0;
+ int curMesh = 0;
+ int curStrip = 0;
+ int curStripGroup = 0;
+ int curVert = 0;
+ int curIndex = 0;
+ int curBoneStateChange = 0;
+ int deltaModel = 0;
+ int deltaLOD = 0;
+ int deltaMesh = 0;
+ int deltaStrip = 0;
+ int deltaStripGroup = 0;
+ int deltaVert = 0;
+ int deltaIndex = 0;
+ int deltaBoneStateChange = 0;
+
+ WriteStringTable( m_StringTableOffset );
+ WriteMaterialReplacements( m_MaterialReplacementsOffset );
+ WriteMaterialReplacementLists( m_MaterialReplacementsOffset, m_MaterialReplacementsListOffset );
+
+ for( int bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID );
+ Model_t *pModel = &m_Models[curModel + deltaModel];
+ for( int lodID = 0; lodID < g_ScriptLODs.Count(); lodID++ )
+ {
+// printf( "lod: %d\n", lodID );
+ ModelLOD_t *pLOD = &pModel->modelLODs[lodID];
+ for( int meshID = 0; meshID < pStudioModel->nummeshes; meshID++ )
+ {
+ Mesh_t *pMesh = &pLOD->meshes[meshID];
+ for( int stripGroupID = 0; stripGroupID < pMesh->stripGroups.Count(); stripGroupID++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[stripGroupID];
+ deltaVert += WriteVerts( curVert + deltaVert, pStripGroup );
+ deltaIndex += WriteIndices( curIndex + deltaIndex, pStripGroup );
+
+ int nStripCount = pStripGroup->strips.Count();
+ for( int stripID = 0; stripID < nStripCount; stripID++ )
+ {
+ Strip_t *pStrip = &pStripGroup->strips[stripID];
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ WriteBoneStateChange( curBoneStateChange + deltaBoneStateChange, &pStrip->boneStateChanges[boneStateChangeID] );
+ deltaBoneStateChange++;
+ }
+ WriteStrip( curStrip + deltaStrip, pStrip, curIndex, curVert, curBoneStateChange );
+ deltaStrip++;
+ curBoneStateChange += deltaBoneStateChange;
+ deltaBoneStateChange = 0;
+ }
+ WriteStripGroup( curStripGroup + deltaStripGroup, pStripGroup, curVert, curIndex, curStrip );
+ deltaStripGroup++;
+ curStrip += deltaStrip;
+ deltaStrip = 0;
+ curVert += deltaVert;
+ deltaVert = 0;
+ curIndex += deltaIndex;
+ deltaIndex = 0;
+ }
+ WriteMesh( curMesh + deltaMesh, pMesh, curStripGroup );
+ deltaMesh++;
+ curStripGroup += deltaStripGroup;
+ deltaStripGroup = 0;
+ }
+ WriteModelLOD( curLOD + deltaLOD, pLOD, curMesh );
+ deltaLOD++;
+ curMesh += deltaMesh;
+ deltaMesh = 0;
+ }
+ WriteModel( curModel + deltaModel, pStudioModel, curLOD );
+ deltaModel++;
+ curLOD += deltaLOD;
+ deltaLOD = 0;
+ }
+ WriteBodyPart( bodyPartID, pBodyPart, curModel );
+ curModel += deltaModel;
+ deltaModel = 0;
+ }
+
+ WriteHeader( m_VertexCacheSize, m_MaxBonesPerVert, m_MaxBonesPerTri,
+ m_MaxBonesPerStrip, pHdr->numbodyparts, pHdr->checksum );
+
+#ifdef _DEBUG
+ m_FileBuffer->TestWritten( m_EndOfFileOffset );
+#endif
+
+ MapGlobalBonesToHardwareBoneIDsAndSortBones( pHdr );
+
+// DebugCompareVerts( phdr );
+ SanityCheckAgainstStudioHDR( pHdr );
+
+ if ( !g_quiet )
+ {
+ OutputMemoryUsage();
+ }
+
+ RemoveRedundantBoneStateChanges();
+ if( g_staticprop )
+ {
+ ZeroNumBones();
+ }
+
+ // Show statistics
+#ifdef _DEBUG
+// ShowStats();
+#endif
+
+ m_FileBuffer->WriteToFile( pFileName, m_EndOfFileOffset );
+
+ FileHeader_t *pVtxHeader = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ SanityCheckVertexBoneLODFlags( pHdr, pVtxHeader );
+}
+
+
+
+static void MergeLikeBoneIndicesWithinVert( mstudioboneweight_t *pBoneWeight )
+{
+ if( pBoneWeight->numbones == 1 )
+ {
+ return;
+ }
+
+ int i, j;
+ int realNumBones = pBoneWeight->numbones;
+ for( i = 0; i < pBoneWeight->numbones; i++ )
+ {
+ for( j = i+1; j < pBoneWeight->numbones; j++ )
+ {
+ if( ( pBoneWeight->bone[i] == pBoneWeight->bone[j] ) && ( pBoneWeight->weight[i] != 0.0f ) )
+ {
+ pBoneWeight->weight[i] += pBoneWeight->weight[j];
+ pBoneWeight->weight[j] = 0.0f;
+ realNumBones--;
+ }
+ }
+ }
+
+ // force all of the -1's at the end with a bubble sort
+
+ float tmpWeight;
+ int tmpIndex;
+ // bubble sort the bones.
+ for( j = pBoneWeight->numbones; j > 1; j-- )
+ {
+ int k;
+ for( k = 0; k < j - 1; k++ )
+ {
+ if( ( pBoneWeight->weight[k] == 0.0f ) && ( pBoneWeight->weight[k+1] != 0.0f ) )
+ {
+ // swap
+ tmpIndex = pBoneWeight->bone[k];
+ tmpWeight = pBoneWeight->weight[k];
+ pBoneWeight->bone[k] = pBoneWeight->bone[k+1];
+ pBoneWeight->weight[k] = pBoneWeight->weight[k+1];
+ pBoneWeight->bone[k+1] = tmpIndex;
+ pBoneWeight->weight[k+1] = tmpWeight;
+ }
+ }
+ }
+ pBoneWeight->numbones = realNumBones;
+}
+
+static void MergeLikeBoneIndicesWithinVerts( studiohdr_t *pHdr )
+{
+ int bodyPartID, modelID, vertID;
+ for( bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pModel = pBodyPart->pModel( modelID );
+ for( vertID = 0; vertID < pModel->numvertices; vertID++ )
+ {
+ const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ mstudioboneweight_t *pBoneWeight = vertData->BoneWeights( vertID );
+ MergeLikeBoneIndicesWithinVert( pBoneWeight );
+ }
+ }
+ }
+}
+
+void COptimizedModel::PrintBoneStateChanges( studiohdr_t *phdr, int lod )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+// mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+// for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ int lodID = lod;
+ {
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+// mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+// mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *pBoneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ printf( "bone change: hwid: %d boneid: %d (%s)\n",
+ ( int )pBoneStateChange->hardwareID,
+ ( int )pBoneStateChange->newBoneID,
+ g_bonetable[pBoneStateChange->newBoneID].name);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::PrintVerts( studiohdr_t *phdr, int lod )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+// for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ int lodID = lod;
+ {
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ PrintVert( pStripGroup->pVertex( vertID ), pStudioModel, pStudioMesh );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static int CalcNumMaterialReplacements()
+{
+ int i;
+ int numReplacements = 0;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ numReplacements += scriptLOD.materialReplacements.Size();
+ }
+ return numReplacements;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Main entry point
+//
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::OptimizeFromStudioHdr( studiohdr_t *pHdr, s_bodypart_t *pSrcBodyParts,
+ int vertCacheSize,
+ bool usesFixedFunction, bool bForceSoftwareSkin, bool bHWFlex, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *pFileName, const char *glViewFileName )
+{
+ Assert( maxBonesPerVert <= MAX_NUM_BONES_PER_VERT );
+ Assert( maxBonesPerTri <= MAX_NUM_BONES_PER_TRI );
+ Assert( maxBonesPerStrip <= MAX_NUM_BONES_PER_STRIP );
+
+ MergeLikeBoneIndicesWithinVerts( pHdr );
+
+ // Some initialization shite
+ SetupMeshProcessing( pHdr, vertCacheSize, usesFixedFunction, maxBonesPerVert,
+ maxBonesPerTri, maxBonesPerStrip, pFileName );
+
+ // The dude that does it all
+ TotalMeshStats_t stats;
+ ProcessModel( pHdr, pSrcBodyParts, stats, bForceSoftwareSkin, bHWFlex );
+ stats.m_TotalMaterialReplacements = CalcNumMaterialReplacements();
+
+ // Write it out to disk
+ WriteVTXFile( pHdr, pFileName, stats );
+
+ // Write out debugging files....
+ WriteGLViewFiles( pHdr, glViewFileName );
+
+ // DebugCrap( pHdr );
+
+// PrintBoneStateChanges( pHdr, 1 );
+// PrintVerts( pHdr, 1 );
+
+ delete m_FileBuffer;
+ m_FileBuffer = NULL;
+
+ if( m_NumSkinnedAndFlexedVerts != 0 )
+ {
+ MdlWarning( "!!!!WARNING!!!!: %d flexed verts had more than one bone influence. . will use SLOW path in engine\n", m_NumSkinnedAndFlexedVerts );
+ }
+
+ CleanupEverything();
+
+ return true;
+}
+
+
+static int numGLViewTrangles = 0;
+static int numGLViewSWDegenerates = 0;
+static int numGLViewHWDegenerates = 0;
+
+
+enum
+{
+ GLVIEWDRAW_TRILIST,
+ GLVIEWDRAW_TRISTRIP,
+ GLVIEWDRAW_NONE
+};
+
+static int s_DrawMode;
+static int s_LastThreeIndices[3];
+static Vertex_t s_LastThreeVerts[3];
+static Vector s_LastThreePositions[3];
+static int s_ListID = 0;
+static bool s_Shrunk[3];
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are used in writing out GL View files
+//
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::PrintVert( Vertex_t *v, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh )
+{
+ printf( "vert:\n" );
+#if 0
+ printf( "\tposition: %f %f %f\n",
+ v->position[0], v->position[1], v->position[2] );
+ printf( "\tnormal: %f %f %f\n",
+ v->normal[0], v->normal[1], v->normal[2] );
+ printf( "\ttexcoord: %f %f\n",
+ v->texCoord[0], v->texCoord[1] );
+#endif
+ printf( "\torigMeshVertID: %d\n", v->origMeshVertID );
+ printf( "\tnumBones: %d\n", v->numBones );
+ int i;
+// for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ for( i = 0; i < v->numBones; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, v, i );
+ printf( "\tboneID[%d]: %d weight: %f (%s)\n", i, ( int )v->boneID[i], boneWeight,
+ g_bonetable[v->boneID[i]].name );
+ }
+}
+
+
+static float RandomFloat( float min, float max )
+{
+ float ret;
+
+ ret = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ ret *= max - min;
+ ret += min;
+ return ret;
+}
+
+Vector& COptimizedModel::GetOrigVertPosition( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return *vertData->Position( pVert->origMeshVertID );
+}
+
+float COptimizedModel::GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return vertData->BoneWeights( pVert->origMeshVertID )->weight[pVert->boneWeightIndex[boneID]];
+}
+
+mstudioboneweight_t &COptimizedModel::GetOrigVertBoneWeight( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return *vertData->BoneWeights( pVert->origMeshVertID );
+}
+
+int COptimizedModel::GetOrigVertBoneIndex( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return vertData->BoneWeights( pVert->origMeshVertID )->bone[pVert->boneWeightIndex[boneID]];
+}
+
+void COptimizedModel::SetMeshPropsColor( unsigned int meshFlags, Vector& color )
+{
+ if( meshFlags & MESH_IS_TEETH )
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else if( meshFlags & MESH_IS_EYES )
+ {
+ color[0] = 1.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+}
+
+void COptimizedModel::SetFlexedAndSkinColor( unsigned int glViewFlags, unsigned int stripGroupFlags, Vector& color )
+{
+ if( ( glViewFlags & WRITEGLVIEW_SHOWFLEXED ) && ( glViewFlags & WRITEGLVIEW_SHOWSW ) )
+ {
+ if( ( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ ( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // flexed and hw skinned = yellow
+ color[0] = 1.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else if( !( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ ( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // not flexed and hw skinned = green
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else if( !( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ !( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // not flexed and sw skinned = blue
+ color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f;
+ }
+ else if( ( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ !( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // flexed and sw skinned = red
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ }
+ else if( glViewFlags & WRITEGLVIEW_SHOWFLEXED )
+ {
+ if( stripGroupFlags & STRIPGROUP_IS_FLEXED )
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ }
+ else if( glViewFlags & WRITEGLVIEW_SHOWSW )
+ {
+ if( stripGroupFlags & STRIPGROUP_IS_HWSKINNED )
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ }
+}
+
+void COptimizedModel::SetColorFromNumVertexBones( int numBones, Vector& color )
+{
+ Vector numBonesColor[5] = {
+ Vector( 0.0f, 0.0f, 0.0f ), // 0 bones = black
+ Vector( 0.0f, 1.0f, 0.0f ), // 1 bone = green
+ Vector( 1.0f, 1.0f, 0.0f ), // 2 bones = yellow
+ Vector( 0.0f, 0.0f, 1.0f ), // 3 bones = blue
+ Vector( 1.0f, 0.0f, 0.0f ) // 4 bones = red
+ };
+ Assert( numBones >= 0 && numBones <= 4 );
+ VectorCopy( numBonesColor[numBones], color );
+}
+
+
+void COptimizedModel::DrawGLViewTriangle( FILE *fp, Vector& pos1, Vector& pos2, Vector& pos3,
+ Vector& color1, Vector& color2, Vector& color3 )
+{
+ numGLViewTrangles++;
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos1[0], pos1[1], pos1[2], color1[0], color1[1], color1[2] );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos2[0], pos2[1], pos2[2], color2[0], color2[1], color2[2] );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos3[0], pos3[1], pos3[2], color3[0], color3[1], color3[2] );
+}
+
+//-----------------------------------------------------------------------------
+// use index to test for degenerates. . isn't used for anything else.
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::GLViewVert( FILE *fp, Vertex_t vert, int index,
+ Vector& color, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ bool showSubStrips, float shrinkFactor )
+{
+// CheckVertBoneWeights( &vert, pStudioModel, pStudioMesh );
+ Assert( s_DrawMode != GLVIEWDRAW_NONE );
+ int id = s_ListID % 3;
+ s_LastThreeIndices[id] = index;
+ s_LastThreeVerts[id] = vert;
+ s_Shrunk[id] = false;
+ VectorCopy( GetOrigVertPosition( pStudioModel, pStudioMesh, &s_LastThreeVerts[id] ), s_LastThreePositions[id] );
+ if( s_DrawMode == GLVIEWDRAW_TRILIST )
+ {
+ // trilist
+ if( id == 2 )
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[0],
+ s_LastThreePositions[1],
+ s_LastThreePositions[2],
+ color, color, color );
+ }
+ }
+ else
+ {
+ // tristrip
+ if( s_ListID >= 2 )
+ {
+ // spit out the triangle with both facings. . doesn't matter if we
+ // get the facing right for glview
+ if( s_LastThreeIndices[0] == s_LastThreeIndices[1] ||
+ s_LastThreeIndices[1] == s_LastThreeIndices[2] ||
+ s_LastThreeIndices[0] == s_LastThreeIndices[2] )
+ {
+ // skip degenerate triangles
+ numGLViewHWDegenerates++;
+ if( showSubStrips )
+ {
+ RandomColor( color );
+ }
+ }
+ else if( !( s_ListID & 1 ) )
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[(id+0-2+3)%3],
+ s_LastThreePositions[(id+1-2+3)%3],
+ s_LastThreePositions[(id+2-2+3)%3],
+ color, color, color );
+ }
+ else
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[(id+2-2+3)%3],
+ s_LastThreePositions[(id+1-2+3)%3],
+ s_LastThreePositions[(id+0-2+3)%3],
+ color, color, color );
+ }
+ }
+ }
+
+ s_ListID++;
+}
+
+void COptimizedModel::GLViewDrawEnd( void )
+{
+ s_DrawMode = GLVIEWDRAW_NONE;
+}
+
+/*
+void COptimizedModel::DebugCrap( studiohdr_t *phdr )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ char tmp[256];
+ sprintf( tmp, "crap.lod%d", lodID );
+ printf( "writing %s\n", tmp );
+ FILE *fp = fopen( tmp, "w" );
+ if( !fp )
+ {
+ printf( "can't write crap file %s\n", tmp );
+ return;
+ }
+
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int id = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ Vertex_t& vert = *pStripGroup->pVertex( id );
+ Vector& vertPos = GetOrigVertPosition( pStudioModel, pStudioMesh, &vert );
+ fprintf( fp, "mesh: %04d origvertid: %04d pos: %0.2f %0.2f %0.2f ", meshID, vert.origMeshVertID, vertPos[0], vertPos[1], vertPos[2] );
+ int i;
+ for( i = 0; i < vert.numBones; i++ )
+ {
+ float boneWeight;
+ boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, &vert, i );
+ int boneID;
+ boneID = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, &vert, i );
+ fprintf( fp, "bone: %d %0.1f ", boneID, boneWeight );
+ }
+ fprintf( fp, "\n" );
+ }
+ }
+ }
+ }
+ fclose( fp );
+ }
+ }
+ }
+}
+*/
+
+
+void COptimizedModel::WriteGLViewFile( studiohdr_t *phdr, const char *pFileName, unsigned int flags, float shrinkFactor )
+{
+ Vector color;
+ RandomColor( color );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ char tmp[256];
+ sprintf( tmp, "%s.lod%d", pFileName, lodID );
+ printf( "writing %s\n", tmp );
+ CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( tmp ) );
+ spFile->Edit();
+ FILE *fp = fopen( tmp, "w" );
+ if( !fp )
+ {
+ printf( "can't write glview file %s\n", tmp );
+ return;
+ }
+
+/*
+ // write out tangent space vectors
+ int vertID;
+ for( vertID = 0; vertID < pStudioModel->numvertices; vertID++ )
+ {
+ const Vector &pos = *pStudioModel->pVertex( vertID );
+ const Vector &norm = *pStudioModel->pNormal( vertID );
+ const Vector4D &sVect = *pStudioModel->pTangentS( vertID );
+ Vector tmpVect;
+ tmpVect = pos + norm * .15f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ Vector tmpSVect( sVect[0], sVect[1], sVect[2] );
+ tmpVect = pos + tmpSVect * .15f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ Vector tmpTVect;
+ CrossProduct( norm, tmpSVect, tmpTVect );
+ tmpTVect *= sVect[3];
+ tmpVect = pos + tmpTVect * .15f;
+
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ }
+ continue;
+ */
+
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ if( flags & WRITEGLVIEW_SHOWMESH )
+ {
+ RandomColor( color );
+ }
+ if( flags & WRITEGLVIEW_SHOWMESHPROPS )
+ {
+ SetMeshPropsColor( mesh->flags, color );
+ }
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ if( flags & WRITEGLVIEW_SHOWSTRIPGROUP )
+ {
+ RandomColor( color );
+ }
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+
+ SetFlexedAndSkinColor( flags, pStripGroup->flags, color );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ if( flags & WRITEGLVIEW_SHOWSTRIP )
+ {
+ RandomColor( color );
+ }
+
+ if( flags & WRITEGLVIEW_SHOWSTRIPNUMBONES )
+ {
+ switch (pStrip->numBones)
+ {
+ case 0:
+ case 1:
+ color.Init( 0, 0, 255 );
+ break;
+
+ case 2:
+ color.Init( 0, 255, 0 );
+ break;
+
+ case 3:
+ color.Init( 255, 255, 0 );
+ break;
+
+ case 4:
+ color.Init( 255, 0, 0 );
+ break;
+ }
+ }
+
+ GLViewDrawBegin( ( pStrip->flags & STRIP_IS_TRILIST ) ? GLVIEWDRAW_TRILIST : GLVIEWDRAW_TRISTRIP );
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int id = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ Vertex_t& vert = *pStripGroup->pVertex( id );
+
+ if( flags & WRITEGLVIEW_SHOWVERTNUMBONES )
+ {
+ switch (vert.numBones)
+ {
+ case 0:
+ case 1:
+ color.Init( 0, 0, 255 );
+ break;
+
+ case 2:
+ color.Init( 0, 255, 0 );
+ break;
+
+ case 3:
+ color.Init( 255, 255, 0 );
+ break;
+
+ case 4:
+ color.Init( 255, 0, 0 );
+ break;
+ }
+ }
+
+ GLViewVert( fp, vert, id, color, pStudioModel, pStudioMesh,
+ ( flags & WRITEGLVIEW_SHOWSUBSTRIP ) ? true : false, shrinkFactor );
+ }
+ GLViewDrawEnd();
+ }
+ }
+ }
+ fclose( fp );
+ spFile->Add();
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Write out all GL View files
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::WriteGLViewFiles( studiohdr_t *pHdr, char const* glViewFileName )
+{
+ if( !g_bDumpGLViewFiles )
+ return;
+
+ char tmpFileName[128];
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".mesh" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWMESH, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".stripgroup" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIPGROUP, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".strip" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIP, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".substrip" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSUBSTRIP, .97f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".flexed" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWFLEXED, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".sw" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSW, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".flexedandsw" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSW | WRITEGLVIEW_SHOWFLEXED, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".meshprops" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWMESHPROPS, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".vertnumbones" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWVERTNUMBONES, 1.0f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".stripnumbones" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIPNUMBONES, 1.0f );
+}
+
+
+void COptimizedModel::GLViewDrawBegin( int mode )
+{
+ s_DrawMode = mode;
+ s_ListID = 0;
+}
+
+void COptimizedModel::ShrinkVerts( float shrinkFactor )
+{
+ Vector center;
+ Vector delta;
+
+ VectorCopy( s_LastThreePositions[0], center );
+ VectorAdd( center, s_LastThreePositions[1], center );
+ VectorAdd( center, s_LastThreePositions[2], center );
+ VectorScale( center, 1.0f / 3.0f, center );
+
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ if( s_Shrunk[i] )
+ {
+ continue;
+ }
+ VectorSubtract( s_LastThreePositions[i], center, delta );
+ VectorScale( delta, shrinkFactor, delta );
+ VectorAdd( center, delta, s_LastThreePositions[i] );
+ s_Shrunk[i] = true;
+ }
+}
+
+void COptimizedModel::CheckVertBoneWeights( Vertex_t *pVert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh )
+{
+ int i;
+ float sum = 0;
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, pVert, i );
+ sum += boneWeight;
+ }
+ Assert( sum > 0.95f && sum < 1.1f );
+}
+
+
+#define CACHE_INEFFICIENCY 6
+void COptimizedModel::ShowStats( void )
+{
+ int totalHWTriangles = 0;
+ int totalHWDegenerates = 0;
+ int totalHWIndices = 0;
+ int totalHWVertexCacheHits = 0;
+ int totalHWVertexCacheMisses = 0;
+ int totalSWTriangles = 0;
+ int totalSWDegenerates = 0;
+ int totalSWIndices = 0;
+ int totalSWVertexCacheHits = 0;
+ int totalSWVertexCacheMisses = 0;
+
+ CHardwareVertexCache hardwareVertexCache;
+ hardwareVertexCache.Init( m_VertexCacheSize - CACHE_INEFFICIENCY );
+
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ printf( "header: %d body parts\n", header->numBodyParts );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ printf( " bodyPart %d: %d models\n", bodyPartID, bodyPart->numModels );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ printf( " model: %d lods\n", model->numLODs );
+ for( int lodID = 0; lodID < 1; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ printf( " lod: %d meshes\n", pLOD->numMeshes );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ printf( " mesh %d: %d stripsgroups\n", meshID, mesh->numStripGroups );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ int lastThreeIndices[3];
+
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ printf( " strip: %d numIndices: %d indexOffset: %d\n", stripID, pStrip->numIndices, pStrip->indexOffset );
+
+ hardwareVertexCache.Flush();
+ if( pStrip->flags & STRIP_IS_TRISTRIP )
+ {
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ totalHWIndices++;
+ int newVertOffset = indexID % 3;
+ lastThreeIndices[newVertOffset] = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ if( !hardwareVertexCache.IsPresent( lastThreeIndices[newVertOffset] ) )
+ {
+ totalHWVertexCacheMisses++;
+ hardwareVertexCache.Insert( lastThreeIndices[newVertOffset] );
+ }
+ else
+ {
+ totalHWVertexCacheHits++;
+ }
+ if( indexID >= 2 )
+ {
+ totalHWTriangles++;
+ if( lastThreeIndices[0] != lastThreeIndices[1] &&
+ lastThreeIndices[1] != lastThreeIndices[2] )
+ {
+
+ }
+ else
+ {
+ totalHWDegenerates++;
+ }
+ }
+ }
+ }
+ else
+ {
+ Assert( pStrip->numIndices % 3 == 0 );
+ totalHWTriangles += pStrip->numIndices / 3;
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int newVertOffset = indexID % 3;
+ int index = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+// printf( "%d\n", index );
+ lastThreeIndices[newVertOffset] = index;
+ if( newVertOffset == 2 )
+ {
+ if( lastThreeIndices[0] == lastThreeIndices[1] ||
+ lastThreeIndices[1] == lastThreeIndices[2] ||
+ lastThreeIndices[0] == lastThreeIndices[2] )
+ {
+// printf( "degenerate triangle!!!! %d %d %d\n", lastThreeIndices[0], lastThreeIndices[1], lastThreeIndices[2] );
+ totalHWDegenerates++;
+ }
+ }
+ totalHWIndices++;
+ if( !hardwareVertexCache.IsPresent( index ) )
+ {
+ totalHWVertexCacheMisses++;
+ hardwareVertexCache.Insert( index );
+ }
+ else
+ {
+ totalHWVertexCacheHits++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ int totalRealHWTriangles = totalHWTriangles - totalHWDegenerates;
+ int totalRealSWTriangles = totalSWTriangles - totalSWDegenerates;
+ printf( "TotalHWTriangles: %d\n", totalHWTriangles );
+ printf( "TotalHWDegenerates: %d\n", totalHWDegenerates );
+ printf( "TotalRealHWTriangles: %d\n", totalRealHWTriangles );
+ printf( "TotalHWIndices: %d\n", totalHWIndices );
+ printf( "HW real tris/index: %f\n", ( float )totalRealHWTriangles / ( float )totalHWIndices );
+ printf( "totalHWVertexCacheHits: %d\n", totalHWVertexCacheHits );
+ printf( "totalHWVertexCacheMisses: %d\n", totalHWVertexCacheMisses );
+ printf( "HW vertex cache hit/miss ratio: %f\n", ( float )totalHWVertexCacheHits / ( float )totalHWVertexCacheMisses );
+
+ printf( "TotalSWTriangles: %d\n", totalSWTriangles );
+ printf( "TotalSWDegenerates: %d\n", totalSWDegenerates );
+ printf( "TotalRealSWTriangles: %d\n", totalRealSWTriangles );
+ printf( "TotalSWIndices: %d\n", totalSWIndices );
+ printf( "SW real tris/index: %f\n", ( float )totalRealSWTriangles / ( float )totalSWIndices );
+ printf( "totalSWVertexCacheHits: %d\n", totalSWVertexCacheHits );
+ printf( "totalSWVertexCacheMisses: %d\n", totalSWVertexCacheMisses );
+ printf( "SW vertex cache hit/miss ratio: %f\n", ( float )totalSWVertexCacheHits / ( float )totalSWVertexCacheMisses );
+}
+
+void COptimizedModel::CheckVert( Vertex_t *pVert, int maxBonesPerTri, int maxBonesPerVert )
+{
+#ifndef IGNORE_BONES
+
+#ifdef _DEBUG
+ int offset = ( int )( ( unsigned char * )pVert - ( unsigned char * )m_FileBuffer->GetPointer( 0 ) );
+ Assert( offset >= m_VertsOffset && offset < m_IndicesOffset );
+ Assert( ( ( offset - m_VertsOffset ) % sizeof( Vertex_t ) ) == 0 );
+#endif
+
+ int j;
+ for( j = 0; j < maxBonesPerVert; j++ )
+ {
+ if( pVert->boneID[j] != -1 )
+ {
+ Assert( pVert->boneID[j] >= 0 && pVert->boneID[j] < maxBonesPerTri );
+ }
+#if 0
+ if( pVert->boneWeights[j] != 0 )
+ {
+ Assert( pVert->boneID[j] != -1 );
+ }
+#endif
+ }
+ // Test to make sure we are sorted.
+ for( j = 0; j < maxBonesPerVert-1; j++ )
+ {
+#if 1
+// if( pVert->boneWeights[j] != 0 && pVert->boneWeights[j+1] != 0 )
+ {
+ Assert( pVert->boneID[j] < pVert->boneID[j+1] );
+ }
+#endif
+ }
+#if 0
+ // Make sure that all the non-zero weights are first.
+ bool foundZero = false;
+ for( j = 0; j < maxBonesPerVert; j++ )
+ {
+ if( !foundZero )
+ {
+ if( pVert->boneWeights[j] == 0.0f )
+ {
+ foundZero = true;
+ }
+ }
+ else
+ {
+ Assert( pVert->boneWeights[j] == 0.0f );
+ }
+ }
+#endif
+#endif
+}
+
+void COptimizedModel::CheckAllVerts( int maxBonesPerTri, int maxBonesPerVert )
+{
+ int i;
+ for( i = m_VertsOffset; i < m_IndicesOffset; i += sizeof( Vertex_t ) )
+ {
+ Vertex_t *vert = ( Vertex_t * )m_FileBuffer->GetPointer( i );
+ CheckVert( vert, maxBonesPerTri, maxBonesPerVert );
+ }
+}
+
+void COptimizedModel::SortBonesWithinVertex( bool flexed, Vertex_t *vert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, int *globalToHardwareBoneIndex, int *hardwareToGlobalBoneIndex, int maxBonesPerTri, int maxBonesPerVert )
+{
+ int i;
+/*
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ if( globalToHardwareBoneIndex[i] != -1 )
+ {
+ if( flexed )
+ {
+ printf( "global bone id: %d hardware bone id: %d\n",
+ i, globalToHardwareBoneIndex[i] );
+ }
+ }
+ }
+*/
+#if 0
+ unsigned char tmpWeightIndex;
+ int tmpIndex;
+ int j;
+ // bubble sort the bones.
+ for( j = m_MaxBonesPerVert; j > 1; j-- )
+ {
+ int k;
+ for( k = 0; k < j - 1; k++ )
+ {
+ if( vert->boneID[k] > vert->boneID[k+1] )
+ {
+ // swap
+ tmpIndex = vert->boneID[k];
+ tmpWeightIndex = vert->boneWeightIndex[k];
+ vert->boneID[k] = vert->boneID[k+1];
+ vert->boneWeightIndex[k] = vert->boneWeightIndex[k+1];
+ vert->boneID[k+1] = tmpIndex;
+ vert->boneWeightIndex[k+1] = tmpWeightIndex;
+ }
+ }
+ }
+#else
+
+ int origBoneWeightIndex[MAX_NUM_BONES_PER_VERT];
+ int zeroWeightIndex = -1;
+ // find a orig vert bone index that has a zero weight
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ if( boneWeight == 0.0f )
+ {
+ zeroWeightIndex = i;
+ break;
+ }
+ }
+
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ origBoneWeightIndex[i] = zeroWeightIndex;
+ }
+ for( i = 0; i < vert->numBones; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ int globalBoneIndex = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, vert, i );
+// if( vert->numBones > 1 )
+ {
+ if( flexed )
+ {
+ printf( "boneWeight: %f\n", boneWeight );
+ printf( "globalBoneIndex: %d\n", globalBoneIndex );
+ }
+ }
+ if( boneWeight > 0.0f )
+ {
+ int hardwareBoneIndex = globalToHardwareBoneIndex[globalBoneIndex];
+ Assert( globalBoneIndex != -1 );
+ origBoneWeightIndex[hardwareBoneIndex] = vert->boneWeightIndex[i];
+ }
+ else
+ {
+ int hardwareBoneIndex = globalToHardwareBoneIndex[globalBoneIndex];
+ origBoneWeightIndex[hardwareBoneIndex] = zeroWeightIndex;
+ Assert( zeroWeightIndex != -1 );
+ Assert( globalBoneIndex == -1 );
+ }
+ }
+// if( vert->numBones > 1 )
+ for( i = 0; i < maxBonesPerTri; i++ )
+ {
+ vert->boneID[i] = i;
+ vert->boneWeightIndex[i] = origBoneWeightIndex[i];
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ int globalBoneIndex = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, vert, i );
+ if( flexed )
+ {
+ Assert( boneWeight >= 0.0f && boneWeight <= 1.0f );
+ printf( "boneWeight: %f ", boneWeight );
+ printf( "globalBoneIndex: %d ", globalBoneIndex );
+ printf( "hardwareBoneID: %d\n", i );
+ }
+ }
+ vert->numBones = maxBonesPerTri; // this may be different for software t&l stuff
+#endif
+}
+
+void COptimizedModel::RemoveRedundantBoneStateChanges( void )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+
+ bool allocated[MAX_NUM_BONES_PER_STRIP];
+ int hardwareBoneState[MAX_NUM_BONES_PER_STRIP];
+ bool changed[MAX_NUM_BONES_PER_STRIP];
+
+ // start anew with each body part
+// printf( "START BODY PARTY - RESETTING BONE MATRIX STATE\n" );
+ int i;
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ hardwareBoneState[i] = -1;
+ allocated[i] = false;
+ }
+
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ if( !( pStripGroup->flags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+// printf( "skipping! software skinned stripgroup\n" );
+ continue;
+ }
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+// int startNumBoneChanges = pStrip->numBoneStateChanges;
+/*
+ printf( "HARDWARE BONE STATE\n" );
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ if( allocated[i] )
+ {
+ printf( "\thw: %d global: %d\n", i, hardwareBoneState[i] );
+ }
+ }
+
+ printf( "before optimization\n" );
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ printf( "\thw: %d global: %d\n",
+ ( int )pStrip->pBoneStateChange( i )->hardwareID,
+ ( int )pStrip->pBoneStateChange( i )->newBoneID );
+ }
+*/
+
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ changed[i] = false;
+ }
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *boneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ Assert( boneStateChange->hardwareID >= 0 && boneStateChange->hardwareID < MAX_NUM_BONES_PER_STRIP );
+ if( allocated[boneStateChange->hardwareID] &&
+ hardwareBoneState[boneStateChange->hardwareID] == boneStateChange->newBoneID )
+ {
+ // already got this one!
+ }
+ else
+ {
+ changed[boneStateChange->hardwareID] = true;
+ allocated[boneStateChange->hardwareID] = true;
+ hardwareBoneState[boneStateChange->hardwareID] = boneStateChange->newBoneID;
+ }
+ }
+/*
+ // now "changed" should tell us which ones we care about.
+ int curOutBoneID = 0;
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ // hack . . going to stomp over what is already there with new data.
+ if( changed[i] )
+ {
+ BoneStateChangeHeader_t *boneStateChange = pStrip->pBoneStateChange( curOutBoneID );
+ boneStateChange->hardwareID = i;
+ boneStateChange->newBoneID = hardwareBoneState[i];
+ curOutBoneID++;
+ }
+ }
+ pStrip->numBoneStateChanges = curOutBoneID;
+ printf( "start bone changes: %d end bone changes: %d\n", startNumBoneChanges, pStrip->numBoneStateChanges );
+ printf( "after optimization\n" );
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ printf( "\thw: %d global: %d\n",
+ ( int )pStrip->pBoneStateChange( i )->hardwareID,
+ ( int )pStrip->pBoneStateChange( i )->newBoneID );
+ }
+
+ */
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void AddMaterialReplacementsToStringTable( void )
+{
+ int i, j;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ for( j = 0; j < scriptLOD.materialReplacements.Size(); j++ )
+ {
+ CLodScriptReplacement_t &materialReplacement = scriptLOD.materialReplacements[j];
+ s_StringTable.AddString( materialReplacement.GetDstName() );
+ }
+ }
+}
+
+
+// Check that all replacematerial/removemesh commands map to valid source materials
+void ValidateLODReplacements( studiohdr_t *pHdr )
+{
+ bool failed = false;
+ int lodID;
+ for( lodID = 0; lodID < g_ScriptLODs.Size(); lodID++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+ int j;
+ for( j = 0; j < scriptLOD.meshRemovals.Count(); j++ )
+ {
+ const char *pName1 = scriptLOD.meshRemovals[j].GetSrcName();
+ int i;
+ for( i = 0; i < pHdr->numtextures; i++ )
+ {
+ const char *pName2 = pHdr->pTexture( i )->material->GetName();
+ if( ComparePath( pName1, pName2 ) )
+ {
+ goto got_one;
+ }
+ }
+
+ // no match
+ MdlWarning( "\"%s\" doesn't match any of the materals in the model\n", pName1 );
+ failed = true;
+got_one:
+ ;
+ }
+ }
+ if( failed )
+ {
+ MdlWarning( "possible materials in model:\n" );
+ int i;
+ for( i = 0; i < pHdr->numtextures; i++ )
+ {
+ MdlWarning( "\t\"%s\"\n", pHdr->pTexture( i )->material->GetName() );
+ }
+ MdlError( "Exiting due to errors\n" );
+ }
+}
+
+void WriteOptimizedFiles( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts )
+{
+ char filename[MAX_PATH];
+ char tmpFileName[MAX_PATH];
+ char glViewFilename[MAX_PATH];
+
+ ValidateLODReplacements( phdr );
+
+ s_StringTable.Purge();
+
+ // hack! This should really go in the mdl file since it's common to all LODs.
+ AddMaterialReplacementsToStringTable();
+
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+ Q_StripExtension( filename, filename, sizeof( filename ) );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".sw.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".sw.glview" );
+ bool bForceSoftwareSkinning = phdr->numbones > 0 && !g_staticprop;
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 512, //vert cache size FIXME: figure out the correct size for L1
+ false, /* doesn't use fixed function */
+ bForceSoftwareSkinning, // force software skinning if not static prop
+ false, // No hardware flex
+ 3, // bones/vert
+ 3*3, // bones/tri
+ 512, // bones/strip
+ tmpFileName, glViewFilename );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".dx80.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".dx80.glview" );
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 24 /* vert cache size (real size, not effective!)*/,
+ false, /* doesn't use fixed function */
+ false, // don't force software skinning
+ false, // No hardware flex
+ 3 /* bones/vert */,
+ 9 /* bones/tri */,
+ 16 /* bones/strip */,
+ tmpFileName, glViewFilename );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".dx90.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".dx90.glview" );
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 24 /* vert cache size (real size, not effective!)*/,
+ false, /* doesn't use fixed function */
+ false, // don't force software skinning
+ true, // Hardware flex on DX9 parts
+ 3 /* bones/vert */,
+ 9 /* bones/tri */,
+ 53 /* bones/strip */,
+ tmpFileName, glViewFilename );
+
+ s_StringTable.Purge();
+}
+
+}; // namespace OptimizedModel
+#pragma optimize( "", on )
diff --git a/utils/studiomdl/perfstats.cpp b/utils/studiomdl/perfstats.cpp
new file mode 100644
index 0000000..c450843
--- /dev/null
+++ b/utils/studiomdl/perfstats.cpp
@@ -0,0 +1,274 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#include <stdlib.h>
+#include <tier0/dbg.h>
+#include "interface.h"
+#include "istudiorender.h"
+#include "studio.h"
+#include "optimize.h"
+#include "cmdlib.h"
+#include "studiomdl.h"
+#include "perfstats.h"
+
+extern void MdlError( char const *pMsg, ... );
+
+static StudioRenderConfig_t s_StudioRenderConfig;
+
+class CStudioDataCache : public CBaseAppSystem<IStudioDataCache>
+{
+public:
+ bool VerifyHeaders( studiohdr_t *pStudioHdr );
+ vertexFileHeader_t *CacheVertexData( studiohdr_t *pStudioHdr );
+};
+
+static CStudioDataCache g_StudioDataCache;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CStudioDataCache, IStudioDataCache, STUDIO_DATA_CACHE_INTERFACE_VERSION, g_StudioDataCache );
+
+
+/*
+=================
+VerifyHeaders
+
+Minimal presence and header validation, no data loads
+Return true if successful, false otherwise.
+=================
+*/
+bool CStudioDataCache::VerifyHeaders( studiohdr_t *pStudioHdr )
+{
+ // default valid
+ return true;
+}
+
+/*
+=================
+CacheVertexData
+
+Cache model's specified dynamic data
+=================
+*/
+vertexFileHeader_t *CStudioDataCache::CacheVertexData( studiohdr_t *pStudioHdr )
+{
+ // minimal implementation - return persisted data
+ return (vertexFileHeader_t*)pStudioHdr->pVertexBase;
+}
+
+static void UpdateStudioRenderConfig( void )
+{
+ memset( &s_StudioRenderConfig, 0, sizeof(s_StudioRenderConfig) );
+
+ s_StudioRenderConfig.bEyeMove = true;
+ s_StudioRenderConfig.fEyeShiftX = 0.0f;
+ s_StudioRenderConfig.fEyeShiftY = 0.0f;
+ s_StudioRenderConfig.fEyeShiftZ = 0.0f;
+ s_StudioRenderConfig.fEyeSize = 10.0f;
+ s_StudioRenderConfig.bSoftwareSkin = false;
+ s_StudioRenderConfig.bNoHardware = false;
+ s_StudioRenderConfig.bNoSoftware = false;
+ s_StudioRenderConfig.bTeeth = true;
+ s_StudioRenderConfig.drawEntities = true;
+ s_StudioRenderConfig.bFlex = true;
+ s_StudioRenderConfig.bEyes = true;
+ s_StudioRenderConfig.bWireframe = false;
+ s_StudioRenderConfig.bDrawNormals = false;
+ s_StudioRenderConfig.skin = 0;
+ s_StudioRenderConfig.maxDecalsPerModel = 0;
+ s_StudioRenderConfig.bWireframeDecals = false;
+ s_StudioRenderConfig.fullbright = false;
+ s_StudioRenderConfig.bSoftwareLighting = false;
+ s_StudioRenderConfig.bShowEnvCubemapOnly = false;
+ g_pStudioRender->UpdateConfig( s_StudioRenderConfig );
+}
+
+static SpewOutputFunc_t s_pSavedSpewFunc;
+
+SpewRetval_t NullSpewOutputFunc( SpewType_t spewType, const tchar *pMsg )
+{
+ switch( spewType )
+ {
+ case SPEW_WARNING:
+ return SPEW_CONTINUE;
+ case SPEW_MESSAGE:
+ case SPEW_ASSERT:
+ case SPEW_ERROR:
+ case SPEW_LOG:
+ Assert( s_pSavedSpewFunc );
+ if( s_pSavedSpewFunc )
+ {
+ return s_pSavedSpewFunc( spewType, pMsg );
+ }
+ break;
+ }
+ Assert( 0 );
+ return SPEW_CONTINUE;
+}
+
+void SpewPerfStats( studiohdr_t *pStudioHdr, const char *pFilename, unsigned int flags )
+{
+ char fileName[260];
+ vertexFileHeader_t *pNewVvdHdr;
+ vertexFileHeader_t *pVvdHdr = 0;
+ OptimizedModel::FileHeader_t *pVtxHdr = 0;
+ studiohwdata_t studioHWData;
+ int vvdSize = 0;
+ const char *prefix[] = {".dx80.vtx", ".dx90.vtx", ".sw.vtx"};
+ s_pSavedSpewFunc = NULL;
+ if( !( flags & SPEWPERFSTATS_SHOWSTUDIORENDERWARNINGS ) )
+ {
+ s_pSavedSpewFunc = GetSpewOutputFunc();
+ SpewOutputFunc( NullSpewOutputFunc );
+ }
+
+ // no stats on these
+ if (!pStudioHdr->numbodyparts)
+ return;
+
+ // Need to update the render config to spew perf stats.
+ UpdateStudioRenderConfig();
+
+ // persist the vvd data
+ Q_StripExtension( pFilename, fileName, sizeof( fileName ) );
+ strcat( fileName, ".vvd" );
+
+ if (FileExists( fileName ))
+ {
+ vvdSize = LoadFile( fileName, (void**)&pVvdHdr );
+ }
+ else
+ {
+ MdlError( "Could not open '%s'\n", fileName );
+ }
+
+ // validate header
+ if (pVvdHdr->id != MODEL_VERTEX_FILE_ID)
+ {
+ MdlError( "Bad id for '%s' (got %d expected %d)\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID);
+ }
+ if (pVvdHdr->version != MODEL_VERTEX_FILE_VERSION)
+ {
+ MdlError( "Bad version for '%s' (got %d expected %d)\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION);
+ }
+ if (pVvdHdr->checksum != pStudioHdr->checksum)
+ {
+ MdlError( "Bad checksum for '%s' (got %d expected %d)\n", fileName, pVvdHdr->checksum, pStudioHdr->checksum);
+ }
+
+ if (pVvdHdr->numFixups)
+ {
+ // need to perform mesh relocation fixups
+ // allocate a new copy
+ pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize );
+ if (!pNewVvdHdr)
+ {
+ MdlError( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName );
+ }
+
+ Studio_LoadVertexes( pVvdHdr, pNewVvdHdr, 0, true );
+
+ // discard original
+ free( pVvdHdr );
+
+ pVvdHdr = pNewVvdHdr;
+ }
+
+ // iterate all ???.vtx files
+ for (int j=0; j<sizeof(prefix)/sizeof(prefix[0]); j++)
+ {
+ // make vtx filename
+ Q_StripExtension( pFilename, fileName, sizeof( fileName ) );
+ strcat( fileName, prefix[j] );
+
+ // persist the vtx data
+ if (FileExists(fileName))
+ {
+ LoadFile( fileName, (void**)&pVtxHdr );
+ }
+ else
+ {
+ MdlError( "Could not open '%s'\n", fileName );
+ }
+
+ // validate header
+ if (pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION)
+ {
+ MdlError( "Bad version for '%s' (got %d expected %d)\n", fileName, pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION );
+ }
+ if (pVtxHdr->checkSum != pStudioHdr->checksum)
+ {
+ MdlError( "Bad checksum for '%s' (got %d expected %d)\n", fileName, pVtxHdr->checkSum, pStudioHdr->checksum );
+ }
+
+ // studio render will request these through cache interface
+ pStudioHdr->pVertexBase = (void *)pVvdHdr;
+ pStudioHdr->pIndexBase = (void *)pVtxHdr;
+
+ g_pStudioRender->LoadModel( pStudioHdr, pVtxHdr, &studioHWData );
+
+ if( flags & SPEWPERFSTATS_SHOWPERF )
+ {
+ if( flags & SPEWPERFSTATS_SPREADSHEET )
+ {
+ printf( "%s,%s,%d,", fileName, prefix[j], studioHWData.m_NumLODs - studioHWData.m_RootLOD );
+ }
+ else
+ {
+ printf( "\n" );
+ printf( "Performance Stats: %s\n", fileName );
+ printf( "------------------\n" );
+ }
+ }
+
+ int i;
+ if( flags & SPEWPERFSTATS_SHOWPERF )
+ {
+ for( i = studioHWData.m_RootLOD; i < studioHWData.m_NumLODs; i++ )
+ {
+ DrawModelInfo_t drawModelInfo;
+ drawModelInfo.m_Skin = 0;
+ drawModelInfo.m_Body = 0;
+ drawModelInfo.m_HitboxSet = 0;
+ drawModelInfo.m_pClientEntity = 0;
+ drawModelInfo.m_pColorMeshes = 0;
+ drawModelInfo.m_pStudioHdr = pStudioHdr;
+ drawModelInfo.m_pHardwareData = &studioHWData;
+ CUtlBuffer statsOutput( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if( !( flags & SPEWPERFSTATS_SPREADSHEET ) )
+ {
+ printf( "LOD:%d\n", i );
+ }
+ drawModelInfo.m_Lod = i;
+
+ DrawModelResults_t results;
+ g_pStudioRender->GetPerfStats( &results, drawModelInfo, &statsOutput );
+ if( flags & SPEWPERFSTATS_SPREADSHEET )
+ {
+ printf( "%d,%d,%d,", results.m_ActualTriCount, results.m_NumBatches, results.m_NumMaterials );
+ }
+ else
+ {
+ printf( " actual tris:%d\n", ( int )results.m_ActualTriCount );
+ printf( " texture memory bytes: %d (only valid in a rendering app)\n", ( int )results.m_TextureMemoryBytes );
+ printf( ( char * )statsOutput.Base() );
+ }
+ }
+ if( flags & SPEWPERFSTATS_SPREADSHEET )
+ {
+ printf( "\n" );
+ }
+ }
+ g_pStudioRender->UnloadModel( &studioHWData );
+ free(pVtxHdr);
+ }
+
+ if (pVvdHdr)
+ free(pVvdHdr);
+
+ if( !( flags & SPEWPERFSTATS_SHOWSTUDIORENDERWARNINGS ) )
+ {
+ SpewOutputFunc( s_pSavedSpewFunc );
+ }
+}
+
diff --git a/utils/studiomdl/perfstats.h b/utils/studiomdl/perfstats.h
new file mode 100644
index 0000000..acf2926
--- /dev/null
+++ b/utils/studiomdl/perfstats.h
@@ -0,0 +1,25 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef PERFSTATS_H
+#define PERFSTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "studio.h"
+#include "optimize.h"
+
+enum
+{
+ SPEWPERFSTATS_SHOWSTUDIORENDERWARNINGS = 1,
+ SPEWPERFSTATS_SHOWPERF = 2,
+ SPEWPERFSTATS_SPREADSHEET = 4,
+};
+
+void SpewPerfStats( studiohdr_t *pStudioHdr, const char *pFilename, unsigned int flags );
+
+#endif // PERFSTATS_H
diff --git a/utils/studiomdl/simplify.cpp b/utils/studiomdl/simplify.cpp
new file mode 100644
index 0000000..a9baf4d
--- /dev/null
+++ b/utils/studiomdl/simplify.cpp
@@ -0,0 +1,8238 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// models/<scriptname>.mdl.
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <float.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "bone_setup.h"
+#include "tier1/strtools.h"
+#include "mathlib/vmatrix.h"
+#include "mdlobjects/dmeboneflexdriver.h"
+
+
+class CBoneRenderBounds
+{
+public:
+ Vector m_Mins; // In bone space.
+ Vector m_Maxs;
+};
+
+// this is computed once so render models and their physics hulls get translated by the same amount
+static Vector g_PropCenterOffset(0,0,0);
+
+
+//----------------------------------------------------------------------
+// underlay:
+// studiomdl : delta = new_anim * ( -1 * base_anim )
+// engine : result = (w * delta) * base_anim
+//
+// overlay
+//
+// studiomdl : delta = (-1 * base_anim ) * new_anim
+// engine : result = base_anim * (w * delta)
+//
+//----------------------------------------------------------------------
+void QuaternionSMAngles( float s, Quaternion const &p, Quaternion const &q, RadianEuler &angles )
+{
+ Quaternion qt;
+ QuaternionSM( s, p, q, qt );
+ QuaternionAngles( qt, angles );
+}
+
+
+void QuaternionMAAngles( Quaternion const &p, float s, Quaternion const &q, RadianEuler &angles )
+{
+ Quaternion qt;
+ QuaternionMA( p, s, q, qt );
+ QuaternionAngles( qt, angles );
+}
+
+
+// q = p * (-q * p)
+
+//-----------------------------------------------------------------------------
+// Purpose: subtract linear motion from root bone animations
+// fixup missing frames from looping animations
+// create "delta" animations
+//-----------------------------------------------------------------------------
+int g_rootIndex = 0;
+
+
+void buildAnimationWeights( void );
+void extractLinearMotion( s_animation_t *panim, int motiontype, int iStartFrame, int iEndFrame, int iSrcFrame, s_animation_t *pRefAnim, int iRefFrame /* , Vector pos, QAngle angles */ );
+void fixupMissingFrame( s_animation_t *panim );
+void realignLooping( s_animation_t *panim );
+void extractUnusedMotion( s_animation_t *panim );
+
+// TODO: psrc and pdest as terms are ambigious, replace with something better
+void setAnimationWeight( s_animation_t *panim, int index );
+void processMatch( s_animation_t *psrc, s_animation_t *pdest, int flags );
+void worldspaceBlend( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags );
+void processAutoorigin( s_animation_t *psrc, s_animation_t *pdest, int flags, int srcframe, int destframe, int bone );
+void subtractBaseAnimations( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags );
+void fixupLoopingDiscontinuities( s_animation_t *panim, int start, int end );
+void matchBlend( s_animation_t *pDestAnim, s_animation_t *pSrcAnimation, int iSrcFrame, int iDestFrame, int iPre, int iPost );
+void makeAngle( s_animation_t *panim, float angle );
+void fixupIKErrors( s_animation_t *panim, s_ikrule_t *pRule );
+void createDerivative( s_animation_t *panim, float scale );
+void clearAnimations( s_animation_t *panim );
+void counterRotateBone( s_animation_t *panim, int bone, QAngle target );
+void localHierarchy( s_animation_t *panim, char *pBonename, char *pParentname, int start, int peak, int tail, int end );
+
+void linearDelta( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags );
+void splineDelta( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags );
+void reencodeAnimation( s_animation_t *panim, int frameskip );
+void forceNumframes( s_animation_t *panim, int frames );
+
+void forceAnimationLoop( s_animation_t *panim );
+
+void solveBone( s_animation_t *panim, int iFrame, int iBone, matrix3x4_t* pBoneToWorld );
+
+
+void ClearModel (void)
+{
+
+}
+
+
+void processAnimations()
+{
+ int i, j;
+
+ // find global root bone.
+ if ( strlen( rootname ) )
+ {
+ g_rootIndex = findGlobalBone( rootname );
+ if (g_rootIndex == -1)
+ g_rootIndex = 0;
+ }
+
+ buildAnimationWeights( );
+
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+
+ extractUnusedMotion( panim ); // FIXME: this should be part of LinearMotion()
+
+ setAnimationWeight( panim, 0 );
+
+ int startframe = 0;
+
+ if (panim->fudgeloop)
+ {
+ fixupMissingFrame( panim );
+ }
+
+ for (j = 0; j < panim->numcmds; j++)
+ {
+ s_animcmd_t *pcmd = &panim->cmds[j];
+
+ switch( pcmd->cmd )
+ {
+ case CMD_WEIGHTS:
+ setAnimationWeight( panim, pcmd->u.weightlist.index );
+ break;
+ case CMD_SUBTRACT:
+ panim->flags |= STUDIO_DELTA;
+ subtractBaseAnimations( pcmd->u.subtract.ref, panim, pcmd->u.subtract.frame, pcmd->u.subtract.flags );
+ break;
+ case CMD_AO:
+ {
+ int bone = g_rootIndex;
+ if (pcmd->u.ao.pBonename != NULL)
+ {
+ bone = findGlobalBone( pcmd->u.ao.pBonename );
+ if (bone == -1)
+ {
+ MdlError("unable to find bone %s to alignbone\n", pcmd->u.ao.pBonename );
+ }
+ }
+ processAutoorigin( pcmd->u.ao.ref, panim, pcmd->u.ao.motiontype, pcmd->u.ao.srcframe, pcmd->u.ao.destframe, bone );
+ }
+ break;
+ case CMD_MATCH:
+ processMatch( pcmd->u.match.ref, panim, false );
+ break;
+ case CMD_FIXUP:
+ fixupLoopingDiscontinuities( panim, pcmd->u.fixuploop.start, pcmd->u.fixuploop.end );
+ break;
+ case CMD_ANGLE:
+ makeAngle( panim, pcmd->u.angle.angle );
+ break;
+ case CMD_IKFIXUP:
+ break;
+ case CMD_IKRULE:
+ // processed later
+ break;
+ case CMD_MOTION:
+ {
+ extractLinearMotion(
+ panim,
+ pcmd->u.motion.motiontype,
+ startframe,
+ pcmd->u.motion.iEndFrame,
+ pcmd->u.motion.iEndFrame,
+ panim,
+ startframe );
+ startframe = pcmd->u.motion.iEndFrame;
+ }
+ break;
+ case CMD_REFMOTION:
+ {
+ extractLinearMotion(
+ panim,
+ pcmd->u.motion.motiontype,
+ startframe,
+ pcmd->u.motion.iEndFrame,
+ pcmd->u.motion.iSrcFrame,
+ pcmd->u.motion.pRefAnim,
+ pcmd->u.motion.iRefFrame );
+ startframe = pcmd->u.motion.iEndFrame;
+ }
+ break;
+ case CMD_DERIVATIVE:
+ {
+ createDerivative(
+ panim,
+ pcmd->u.derivative.scale );
+ }
+ break;
+ case CMD_NOANIMATION:
+ {
+ clearAnimations( panim );
+ }
+ break;
+ case CMD_LINEARDELTA:
+ {
+ panim->flags |= STUDIO_DELTA;
+ linearDelta( panim, panim, panim->numframes - 1, pcmd->u.linear.flags );
+ }
+ break;
+ case CMD_COMPRESS:
+ {
+ reencodeAnimation( panim, pcmd->u.compress.frames );
+ }
+ break;
+ case CMD_NUMFRAMES:
+ {
+ forceNumframes( panim, pcmd->u.numframes.frames );
+ }
+ break;
+ case CMD_COUNTERROTATE:
+ {
+ int bone = findGlobalBone( pcmd->u.counterrotate.pBonename );
+ if (bone != -1)
+ {
+ QAngle target;
+
+ if (!pcmd->u.counterrotate.bHasTarget)
+ {
+ matrix3x4_t rootxform;
+ matrix3x4_t defaultBoneToWorld;
+ AngleMatrix( panim->rotation, rootxform );
+ ConcatTransforms( rootxform, g_bonetable[bone].boneToPose, defaultBoneToWorld );
+
+ MatrixAngles( defaultBoneToWorld, target );
+ }
+ else
+ {
+ target.Init( pcmd->u.counterrotate.targetAngle[0], pcmd->u.counterrotate.targetAngle[1], pcmd->u.counterrotate.targetAngle[2] );
+ }
+
+ counterRotateBone( panim, bone, target );
+ }
+ else
+ {
+ MdlError("unable to find bone %s to counterrotate\n", pcmd->u.counterrotate.pBonename );
+ }
+ }
+ break;
+ case CMD_WORLDSPACEBLEND:
+ worldspaceBlend( pcmd->u.world.ref, panim, pcmd->u.world.startframe, pcmd->u.world.loops );
+ break;
+ case CMD_MATCHBLEND:
+ matchBlend( panim, pcmd->u.match.ref, pcmd->u.match.srcframe, pcmd->u.match.destframe, pcmd->u.match.destpre, pcmd->u.match.destpost );
+ break;
+ case CMD_LOCALHIERARCHY:
+ localHierarchy( panim, pcmd->u.localhierarchy.pBonename, pcmd->u.localhierarchy.pParentname, pcmd->u.localhierarchy.start, pcmd->u.localhierarchy.peak, pcmd->u.localhierarchy.tail, pcmd->u.localhierarchy.end );
+ // localHierarchy( panim, char *pBonename, char *pParentname, int start, int peak, int tail, int end );
+ break;
+ }
+ }
+
+ if (panim->motiontype)
+ {
+ int lastframe;
+ if (!(panim->flags & STUDIO_LOOPING) )
+ {
+ // roll back 0.2 seconds to try to prevent popping
+ int frames = panim->fps * panim->motionrollback;
+ lastframe = max( min( startframe + 1, panim->numframes - 1), panim->numframes - frames - 1 );
+ //printf("%s : %d %d (%d)\n", panim->name, startframe, lastframe, panim->numframes - 1 );
+ }
+ else
+ {
+ lastframe = panim->numframes - 1;
+ }
+ extractLinearMotion( panim, panim->motiontype, startframe, lastframe, panim->numframes - 1, panim, startframe );
+ startframe = panim->numframes - 1;
+ }
+
+ realignLooping( panim );
+
+ forceAnimationLoop( panim );
+ }
+
+ // merge weightlists
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ int k, n;
+ for (n = 0; n < g_numbones; n++)
+ {
+ g_sequence[i].weight[n] = 0.0;
+
+ for (j = 0; j < g_sequence[i].groupsize[0]; j++)
+ {
+ for (k = 0; k < g_sequence[i].groupsize[1]; k++)
+ {
+ g_sequence[i].weight[n] = max( g_sequence[i].weight[n], g_sequence[i].panim[j][k]->weight[n] );
+ }
+ }
+ }
+ }
+}
+
+
+/*
+void lookupLinearMotion( s_animation_t *panim, int motiontype, int startframe, int endframe, Vector &p1, Vector &p2 )
+{
+ Vector p0 = panim->sanim[startframe][g_rootIndex].pos;
+ p2 = panim->sanim[endframe][g_rootIndex].pos[0];
+
+ float fFrame = (startframe + endframe) / 2.0;
+ int iFrame = (int)fFrame;
+ float s = fFrame - iFrame;
+
+ p1 = panim->sanim[iFrame][g_rootIndex].pos * (1 - s) + panim->sanim[iFrame+1][g_rootIndex].pos * s;
+}
+*/
+
+
+ // 0.375 * v1 + 0.125 * v2 - d2 = 0.5 * v1 + 0.5 * v2 - d3;
+
+ // 0.375 * v1 - 0.5 * v1 = 0.5 * v2 - d3 - 0.125 * v2 + d2;
+ // 0.375 * v1 - 0.5 * v1 = 0.5 * v2 - d3 - 0.125 * v2 + d2;
+ // -0.125 * v1 = 0.375 * v2 - d3 + d2;
+ // v1 = (0.375 * v2 - d3 + d2) / -0.125;
+
+ // -3 * (0.375 * v2 - d3 + d2) + 0.125 * v2 - d2 = 0
+ // -3 * (0.375 * v2 - d3 + d2) + 0.125 * v2 - d2 = 0
+ // -1 * v2 + 3 * d3 - 3 * d2 - d2 = 0
+ // v2 = 3 * d3 - 4 * d2
+
+ // 0.5 * v1 + 0.5 * v2 - d3
+ // -4 * (0.375 * v2 - d3 + d2) + 0.5 * v2 - d3 = 0
+ // -1.5 * v2 + 4 * d3 - 4 * d2 + 0.5 * v2 - d3 = 0
+ // v2 = 4 * d3 - 4 * d2 - d3
+ // v2 = 3 * d3 - 4 * d2
+
+ // 0.5 * v1 + 0.5 * (3 * d3 - 4 * d2) - d3 = 0
+ // v1 + (3 * d3 - 4 * d2) - 2 * d3 = 0
+ // v1 = -3 * d3 + 4 * d2 + 2 * d3
+ // v1 = -1 * d3 + 4 * d2
+
+
+
+void ConvertToAnimLocal( s_animation_t *panim, Vector &pos, QAngle &angles )
+{
+ matrix3x4_t bonematrix;
+ matrix3x4_t adjmatrix;
+
+ // convert explicit position/angle into animation relative values
+ AngleMatrix( angles, pos, bonematrix );
+ AngleMatrix( panim->rotation, panim->adjust, adjmatrix );
+ MatrixInvert( adjmatrix, adjmatrix );
+ ConcatTransforms( adjmatrix, bonematrix, bonematrix );
+ MatrixAngles( bonematrix, angles, pos );
+ // pos = pos * panim->scale;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: find the linear movement/rotation between two frames, subtract that
+// out of the animation and add it back on as a "piecewise movement" command
+// panim - current animation
+// motiontype - what to extract
+// iStartFrame - first frame to apply motion over
+// iEndFrame - last end frame to apply motion over
+// iSrcFrame - match refFrame against what frame of the current animation
+// pRefAnim - reference animtion
+// iRefFrame - frame of reference animation to match
+//-----------------------------------------------------------------------------
+
+void extractLinearMotion( s_animation_t *panim, int motiontype, int iStartFrame, int iEndFrame, int iSrcFrame, s_animation_t *pRefAnim, int iRefFrame /* , Vector pos, QAngle angles */ )
+{
+ int j, k;
+ matrix3x4_t adjmatrix;
+
+ // Can't extract motion with only 1 frame of animation!
+ if ( panim->numframes <= 1 )
+ {
+ MdlError( "Can't extract motion from sequence %s (%s). Check your QC options!\n", panim->name, panim->filename );
+ }
+
+ if (panim->numpiecewisekeys >= MAXSTUDIOMOVEKEYS)
+ {
+ MdlError( "Too many piecewise movement keys in %s (%s)\n", panim->name, panim->filename );
+ }
+
+ if (iEndFrame > panim->numframes - 1)
+ iEndFrame = panim->numframes - 1;
+
+ if (iSrcFrame > panim->numframes - 1)
+ iSrcFrame = panim->numframes - 1;
+
+ if (iStartFrame >= iEndFrame)
+ {
+ MdlWarning("Motion extraction ignored, no frames remaining in %s (%s)\n", panim->name, panim->filename );
+ return;
+ }
+
+ float fFrame = (iStartFrame + iSrcFrame) / 2.0;
+ int iMidFrame = (int)fFrame;
+ float s = fFrame - iMidFrame;
+
+ // find rotation
+ RadianEuler rot( 0, 0, 0 );
+
+ if (motiontype & (STUDIO_LXR | STUDIO_LYR | STUDIO_LZR))
+ {
+ Quaternion q0;
+ Quaternion q1;
+ Quaternion q2;
+
+ AngleQuaternion( pRefAnim->sanim[iRefFrame][g_rootIndex].rot, q0 );
+ AngleQuaternion( panim->sanim[iMidFrame][g_rootIndex].rot, q1 ); // only used for rotation checking
+ AngleQuaternion( panim->sanim[iSrcFrame][g_rootIndex].rot, q2 );
+
+ Quaternion deltaQ1;
+ QuaternionMA( q1, -1, q0, deltaQ1 );
+ Quaternion deltaQ2;
+ QuaternionMA( q2, -1, q0, deltaQ2 );
+
+ // FIXME: this is still wrong, but it should be slightly more robust
+ RadianEuler a3;
+ if (motiontype & STUDIO_LXR)
+ {
+ Quaternion q4;
+ q4.Init( deltaQ2.x, 0, 0, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+ rot.x = a3.x;
+ }
+ if (motiontype & STUDIO_LYR)
+ {
+ Quaternion q4;
+ q4.Init( 0, deltaQ2.y, 0, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+ rot.y = a3.y;
+ }
+ if (motiontype & STUDIO_LZR)
+ {
+ Quaternion q4;
+ q4.Init( 0, 0, deltaQ2.z, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+
+ // check for possible rotations >180 degrees by looking at the
+ // halfway point and seeing if it's rotating a different direction
+ // than the shortest path to the end point
+ Quaternion q5;
+ RadianEuler a5;
+ q5.Init( 0, 0, deltaQ1.z, deltaQ1.w );
+ QuaternionNormalize( q5 );
+ QuaternionAngles( q5, a5 );
+ if (a3.z > M_PI) a5.z -= 2*M_PI;
+ if (a3.z < -M_PI) a5.z += 2*M_PI;
+ if (a5.z > M_PI) a5.z -= 2*M_PI;
+ if (a5.z < -M_PI) a5.z += 2*M_PI;
+ if (a5.z > M_PI/4 && a3.z < 0)
+ {
+ a3.z += 2*M_PI;
+ }
+ if (a5.z < -M_PI/4 && a3.z > 0)
+ {
+ a3.z -= 2*M_PI;
+ }
+
+ rot.z = a3.z;
+ }
+ }
+
+ // find movement
+ Vector p0;
+ AngleMatrix(rot, adjmatrix );
+ VectorRotate( pRefAnim->sanim[iRefFrame][g_rootIndex].pos, adjmatrix, p0 );
+
+ Vector p2 = panim->sanim[iSrcFrame][g_rootIndex].pos;
+ Vector p1 = panim->sanim[iMidFrame][g_rootIndex].pos * (1 - s) + panim->sanim[iMidFrame+1][g_rootIndex].pos * s;
+
+ // ConvertToAnimLocal( panim, pos, angles ); // FIXME: unused
+
+ p2 = p2 - p0;
+ p1 = p1 - p0;
+
+ if (!(motiontype & STUDIO_LX)) { p2.x = 0; p1.x = 0; };
+ if (!(motiontype & STUDIO_LY)) { p2.y = 0; p1.y = 0; };
+ if (!(motiontype & STUDIO_LZ)) { p2.z = 0; p1.z = 0; };
+
+ // printf("%s %.1f %.1f %.1f\n", g_bonetable[g_rootIndex].name, p2.x, p2.y, p2.z );
+
+ float d1 = p1.Length();
+ float d2 = p2.Length();
+
+ float v0 = -1 * d2 + 4 * d1;
+ float v1 = 3 * d2 - 4 * d1;
+
+ if ( g_verbose )
+ {
+ printf("%s : %d - %d : %.1f %.1f %.1f\n", panim->name, iStartFrame, iEndFrame, p2.x, p2.y, RAD2DEG( rot[2] ) );
+ }
+
+ int numframes = iEndFrame - iStartFrame + 1;
+ if (numframes < 1)
+ return;
+
+ float n = numframes - 1;
+
+ //printf("%f %f : ", v0, v1 );
+
+ if (motiontype & STUDIO_LINEAR)
+ {
+ v0 = v1 = p2.Length();
+ }
+ else if (v0 < 0.0f)
+ {
+ v0 = 0.0;
+ v1 = p2.Length() * 2.0;
+ }
+ else if (v1 < 0.0)
+ {
+ v0 = p2.Length() * 2.0;
+ v1 = 0.0;
+ }
+ else if ((v0+v1) > 0.01 && (fabs(v0-v1) / (v0+v1)) < 0.2)
+ {
+ // if they're within 10% of each other, assum no acceleration
+ v0 = v1 = p2.Length();
+ }
+
+ //printf("%f %f\n", v0, v1 );
+
+ Vector v = p2;
+ VectorNormalize( v );
+
+ Vector A, B, C;
+ if (motiontype & STUDIO_QUADRATIC_MOTION)
+ {
+ SolveInverseQuadratic( 0, 0, 0.5, p1.x, 1.0, p2.x, A.x, B.x, C.x );
+ SolveInverseQuadratic( 0, 0, 0.5, p1.y, 1.0, p2.y, A.y, B.y, C.y );
+ SolveInverseQuadratic( 0, 0, 0.5, p1.z, 1.0, p2.z, A.z, B.z, C.z );
+ }
+
+ Vector adjpos;
+ RadianEuler adjangle;
+ matrix3x4_t bonematrix;
+ for (j = 0; j < numframes; j++)
+ {
+ float t = (j / n);
+
+ if (motiontype & STUDIO_QUADRATIC_MOTION)
+ {
+ adjpos.x = t * t * A.x + t * B.x + C.x;
+ adjpos.y = t * t * A.y + t * B.y + C.y;
+ adjpos.z = t * t * A.z + t * B.z + C.z;
+ }
+ else
+ {
+ VectorScale( v, v0 * t + 0.5 * (v1 - v0) * t * t, adjpos );
+ }
+
+ VectorScale( rot, t, adjangle );
+
+ AngleMatrix( adjangle, adjpos, adjmatrix );
+ MatrixInvert( adjmatrix, adjmatrix );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ // printf(" %.1f %.1f %.1f : ", adjpos[0], adjpos[1], RAD2DEG( adjangle[2] ));
+
+ // printf(" %.1f %.1f %.1f\n", adjpos[0], adjpos[1], adjpos[2] );
+
+ AngleMatrix( panim->sanim[j+iStartFrame][k].rot, panim->sanim[j+iStartFrame][k].pos, bonematrix );
+ ConcatTransforms( adjmatrix, bonematrix, bonematrix );
+
+ MatrixAngles( bonematrix, panim->sanim[j+iStartFrame][k].rot, panim->sanim[j+iStartFrame][k].pos );
+ // printf("%d : %.1f %.1f %.1f\n", j, panim->sanim[j+iStartFrame][k].pos.x, panim->sanim[j+iStartFrame][k].pos.y, RAD2DEG( panim->sanim[j+iStartFrame][k].rot.z ) );
+ }
+ }
+ }
+
+ for (; j+iStartFrame < panim->numframes; j++)
+ {
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ AngleMatrix( panim->sanim[j+iStartFrame][k].rot, panim->sanim[j+iStartFrame][k].pos, bonematrix );
+ ConcatTransforms( adjmatrix, bonematrix, bonematrix );
+ MatrixAngles( bonematrix, panim->sanim[j+iStartFrame][k].rot, panim->sanim[j+iStartFrame][k].pos );
+ }
+ }
+ }
+
+ // create piecewise motion paths
+
+ s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys++];
+
+ pmove->endframe = iEndFrame;
+
+ pmove->flags = motiontype;
+
+ // concatinate xforms
+ if (panim->numpiecewisekeys > 1)
+ {
+ AngleMatrix( adjangle, adjpos, bonematrix );
+ AngleMatrix( pmove[-1].rot, pmove[-1].pos, adjmatrix );
+ ConcatTransforms( adjmatrix, bonematrix, bonematrix );
+ MatrixAngles( bonematrix, pmove[0].rot, pmove[0].pos );
+ pmove->vector = pmove[0].pos - pmove[-1].pos;
+ }
+ else
+ {
+ VectorCopy( adjpos, pmove[0].pos );
+ VectorCopy( adjangle, pmove[0].rot );
+ pmove->vector = pmove[0].pos;
+ }
+ VectorNormalize( pmove->vector );
+
+ // printf("%d : %.1f %.1f %.1f\n", iEndFrame, pmove[0].pos.x, pmove[0].pos.y, RAD2DEG( pmove[0].rot.z ) );
+
+ pmove->v0 = v0;
+ pmove->v1 = v1;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: process the "piecewise movement" commands and return where the animation
+// would move to on a given frame (assuming frame 0 is at the origin)
+//-----------------------------------------------------------------------------
+
+Vector calcPosition( s_animation_t *panim, int iFrame )
+{
+ Vector vecPos;
+
+ vecPos.Init();
+
+ if (panim->numpiecewisekeys == 0)
+ return vecPos;
+
+ if (panim->numframes == 1)
+ return vecPos;
+
+ int iLoops = 0;
+ while (iFrame >= (panim->numframes - 1))
+ {
+ iLoops++;
+ iFrame = iFrame - (panim->numframes - 1);
+ }
+
+ float prevframe = 0.0f;
+
+ for (int i = 0; i < panim->numpiecewisekeys; i++)
+ {
+ s_linearmove_t *pmove = &panim->piecewisemove[i];
+
+ if (pmove->endframe >= iFrame)
+ {
+ float f = (iFrame - prevframe) / (pmove->endframe - prevframe);
+
+ float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f;
+
+ vecPos = vecPos + d * pmove->vector;
+ if (iLoops != 0)
+ {
+ s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys - 1];
+ vecPos = vecPos + iLoops * pmove->pos;
+ }
+ return vecPos;
+ }
+ else
+ {
+ prevframe = pmove->endframe;
+ vecPos = pmove->pos;
+ }
+ }
+ return vecPos;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calculate how far an animation travels between two frames
+//-----------------------------------------------------------------------------
+
+Vector calcMovement( s_animation_t *panim, int iFrom, int iTo )
+{
+ Vector p1 = calcPosition( panim, iFrom );
+ Vector p2 = calcPosition( panim, iTo );
+
+ return p2 - p1;
+}
+
+#if 0
+ // FIXME: add in correct motion!!!
+ int iFrame = pRule->peak - pRule->start - k;
+ if (pRule->start + k > panim->numframes - 1)
+ {
+ iFrame = iFrame + 1;
+ }
+ Vector pos = footfall;
+ if (panim->numframes > 1)
+ pos = pos + panim->piecewisemove[0].pos * (iFrame) / (panim->numframes - 1.0f);
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose: try to calculate a "missing" frame of animation, i.e the overlapping frame
+//-----------------------------------------------------------------------------
+
+void fixupMissingFrame( s_animation_t *panim )
+{
+ // the animations DIDN'T have the end frame the same as the start frame, so fudge it
+ int size = g_numbones * sizeof( s_bone_t );
+ int j = panim->numframes;
+
+ float scale = 1 / (j - 1.0f);
+
+ panim->sanim[j] = (s_bone_t *)kalloc( 1, size );
+
+ Vector deltapos;
+
+ for (int k = 0; k < g_numbones; k++)
+ {
+ VectorSubtract( panim->sanim[j-1][k].pos, panim->sanim[0][k].pos, deltapos );
+ VectorMA( panim->sanim[j-1][k].pos, scale, deltapos, panim->sanim[j][k].pos );
+ VectorCopy( panim->sanim[0][k].rot, panim->sanim[j][k].rot );
+ }
+
+ panim->numframes = j + 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: shift the frames of the animation so that it starts on the desired frame
+//-----------------------------------------------------------------------------
+
+void realignLooping( s_animation_t *panim )
+{
+ int j, k;
+
+ // realign looping animations
+ if (panim->numframes > 1 && panim->looprestart)
+ {
+ if (panim->looprestart >= panim->numframes)
+ {
+ MdlError( "loopstart (%d) out of range for animation %s (%d)", panim->looprestart, panim->name, panim->numframes );
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ int n;
+
+ Vector shiftpos[MAXSTUDIOANIMFRAMES];
+ RadianEuler shiftrot[MAXSTUDIOANIMFRAMES];
+
+ // printf("%f %f %f\n", motion[0], motion[1], motion[2] );
+ for (j = 0; j < panim->numframes - 1; j++)
+ {
+ n = (j + panim->looprestart) % (panim->numframes - 1);
+ VectorCopy( panim->sanim[n][k].pos, shiftpos[j] );
+ VectorCopy( panim->sanim[n][k].rot, shiftrot[j] );
+ }
+
+ n = panim->looprestart;
+ j = panim->numframes - 1;
+ VectorCopy( panim->sanim[n][k].pos, shiftpos[j] );
+ VectorCopy( panim->sanim[n][k].rot, shiftrot[j] );
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ VectorCopy( shiftpos[j], panim->sanim[j][k].pos );
+ VectorCopy( shiftrot[j], panim->sanim[j][k].rot );
+ }
+ }
+ }
+}
+
+void extractUnusedMotion( s_animation_t *panim )
+{
+ int j, k;
+
+ int type = panim->motiontype;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ float motion[6];
+ motion[0] = panim->sanim[0][k].pos[0];
+ motion[1] = panim->sanim[0][k].pos[1];
+ motion[2] = panim->sanim[0][k].pos[2];
+ motion[3] = panim->sanim[0][k].rot[0];
+ motion[4] = panim->sanim[0][k].rot[1];
+ motion[5] = panim->sanim[0][k].rot[2];
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ if (type & STUDIO_X)
+ panim->sanim[j][k].pos[0] = motion[0];
+ if (type & STUDIO_Y)
+ panim->sanim[j][k].pos[1] = motion[1];
+ if (type & STUDIO_Z)
+ panim->sanim[j][k].pos[2] = motion[2];
+ if (type & STUDIO_XR)
+ panim->sanim[j][k].rot[0] = motion[3];
+ if (type & STUDIO_YR)
+ panim->sanim[j][k].rot[1] = motion[4];
+ if (type & STUDIO_ZR)
+ panim->sanim[j][k].rot[2] = motion[5];
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: find the difference between the src and dest animations, then add that
+// difference to all the frames of the dest animation.
+//-----------------------------------------------------------------------------
+
+void processMatch( s_animation_t *psrc, s_animation_t *pdest, int flags )
+{
+ int j, k;
+
+ // process "match"
+ Vector delta_pos[MAXSTUDIOSRCBONES];
+ Quaternion delta_q[MAXSTUDIOSRCBONES];
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (flags)
+ VectorSubtract( psrc->sanim[0][k].pos, pdest->sanim[0][k].pos, delta_pos[k] );
+ QuaternionSM( -1, pdest->sanim[0][k].rot, psrc->sanim[0][k].rot, delta_q[k] );
+ }
+
+ // printf("%.2f %.2f %.2f\n", adj.x, adj.y, adj.z );
+ for (j = 0; j < pdest->numframes; j++)
+ {
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (pdest->weight[k] > 0)
+ {
+ if (flags)
+ VectorAdd( pdest->sanim[j][k].pos, delta_pos[k], pdest->sanim[j][k].pos );
+ QuaternionMAAngles( pdest->sanim[j][k].rot, 1.0, delta_q[k], pdest->sanim[j][k].rot );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: blend the psrc animation overtop the pdest animation, but blend the
+// quaternions in world space instead of parent bone space.
+// Also, blend bone lengths, but only for non root animations.
+//-----------------------------------------------------------------------------
+
+void worldspaceBlend( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
+{
+ int j, k, n;
+
+ // process "match"
+ Quaternion srcQ[MAXSTUDIOSRCBONES];
+ Vector srcPos[MAXSTUDIOSRCBONES];
+ Vector tmp;
+
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t destBoneToWorld[MAXSTUDIOBONES];
+
+ if (!flags)
+ {
+ CalcBoneTransforms( psrc, srcframe, srcBoneToWorld );
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixAngles( srcBoneToWorld[k], srcQ[k], tmp );
+ srcPos[k] = psrc->sanim[srcframe][k].pos;
+ }
+ }
+
+ Quaternion targetQ, destQ;
+
+ // printf("%.2f %.2f %.2f\n", adj.x, adj.y, adj.z );
+ for (j = 0; j < pdest->numframes; j++)
+ {
+ if (flags)
+ {
+ // pull from a looping source
+ float flCycle = (float)j / (pdest->numframes - 1);
+ flCycle += (float)srcframe / (psrc->numframes - 1);
+ CalcBoneTransformsCycle( psrc, psrc, flCycle, srcBoneToWorld );
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixAngles( srcBoneToWorld[k], srcQ[k], tmp );
+
+ n = g_bonetable[k].parent;
+ if (n == -1)
+ {
+ MatrixPosition( srcBoneToWorld[k], srcPos[k] );
+ }
+ else
+ {
+ matrix3x4_t worldToBone;
+ MatrixInvert( srcBoneToWorld[n], worldToBone );
+
+ matrix3x4_t local;
+ ConcatTransforms( worldToBone, srcBoneToWorld[k], local );
+ MatrixPosition( local, srcPos[k] );
+ }
+ }
+ }
+
+
+ CalcBoneTransforms( pdest, j, destBoneToWorld );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (pdest->weight[k] > 0)
+ {
+ // blend the boneToWorld transforms in world space
+ MatrixAngles( destBoneToWorld[k], destQ, tmp );
+ QuaternionSlerp( destQ, srcQ[k], pdest->weight[k], targetQ );
+
+ AngleMatrix( targetQ, tmp, destBoneToWorld[k] );
+ }
+
+ // back solve
+ n = g_bonetable[k].parent;
+ if (n == -1)
+ {
+ MatrixAngles( destBoneToWorld[k], pdest->sanim[j][k].rot, tmp );
+
+ // FIXME: it's not clear if this should blend position or not....it'd be
+ // better if weight lists could do quat and pos independently.
+ }
+ else
+ {
+ matrix3x4_t worldToBone;
+ MatrixInvert( destBoneToWorld[n], worldToBone );
+
+ matrix3x4_t local;
+ ConcatTransforms( worldToBone, destBoneToWorld[k], local );
+ MatrixAngles( local, pdest->sanim[j][k].rot, tmp );
+
+ // blend bone lengths (local space)
+ pdest->sanim[j][k].pos = Lerp( pdest->posweight[k], pdest->sanim[j][k].pos, srcPos[k] );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: match one animations position/orientation to another animations position/orientation
+//-----------------------------------------------------------------------------
+
+void processAutoorigin( s_animation_t *psrc, s_animation_t *pdest, int motiontype, int srcframe, int destframe, int bone )
+{
+ int j, k;
+ matrix3x4_t adjmatrix;
+
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t destBoneToWorld[MAXSTUDIOBONES];
+
+ CalcBoneTransforms( psrc, srcframe, srcBoneToWorld );
+ CalcBoneTransforms( pdest, destframe, destBoneToWorld );
+
+ // find rotation
+ RadianEuler rot( 0, 0, 0 );
+
+ Quaternion q0;
+ Quaternion q2;
+ Vector srcPos;
+ Vector destPos;
+
+ MatrixAngles( srcBoneToWorld[bone], q0, srcPos );
+ MatrixAngles( destBoneToWorld[bone], q2, destPos );
+
+ if (motiontype & (STUDIO_LXR | STUDIO_LYR | STUDIO_LZR | STUDIO_XR | STUDIO_YR | STUDIO_ZR))
+ {
+ Quaternion deltaQ2;
+ QuaternionMA( q2, -1, q0, deltaQ2 );
+
+ RadianEuler a3;
+ if (motiontype & (STUDIO_LXR | STUDIO_XR))
+ {
+ Quaternion q4;
+ q4.Init( deltaQ2.x, 0, 0, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+ rot.x = a3.x;
+ }
+ if (motiontype & (STUDIO_LYR | STUDIO_YR))
+ {
+ Quaternion q4;
+ q4.Init( 0, deltaQ2.y, 0, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+ rot.y = a3.y;
+ }
+ if (motiontype & (STUDIO_LZR | STUDIO_ZR))
+ {
+ Quaternion q4;
+ q4.Init( 0, 0, deltaQ2.z, deltaQ2.w );
+ QuaternionNormalize( q4 );
+ QuaternionAngles( q4, a3 );
+ rot.z = a3.z;
+ }
+ if ((motiontype & STUDIO_XR) && (motiontype & STUDIO_YR) && (motiontype & STUDIO_ZR))
+ {
+ QuaternionAngles( deltaQ2, rot );
+ }
+ }
+
+ // find movement
+ Vector p0 = srcPos;
+ Vector p2;
+ AngleMatrix(rot, adjmatrix );
+ MatrixInvert( adjmatrix, adjmatrix );
+ VectorRotate( destPos, adjmatrix, p2 );
+
+ Vector adj = p0 - p2;
+
+ if (!(motiontype & (STUDIO_X | STUDIO_LX)))
+ adj.x = 0;
+ if (!(motiontype & (STUDIO_Y | STUDIO_LY)))
+ adj.y = 0;
+ if (!(motiontype & (STUDIO_Z | STUDIO_LZ)))
+ adj.z = 0;
+
+ PositionMatrix( adj, adjmatrix );
+
+ if (g_verbose && bone != g_rootIndex)
+ {
+ printf("%s aligning to %s - %.2f %.2f %.2f\n", pdest->name, g_bonetable[bone].name, adj.x, adj.y, adj.z );
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ for (j = 0; j < pdest->numframes; j++)
+ {
+ matrix3x4_t bonematrix;
+ AngleMatrix( pdest->sanim[j][k].rot, pdest->sanim[j][k].pos, bonematrix );
+ ConcatTransforms( adjmatrix, bonematrix, bonematrix );
+ MatrixAngles( bonematrix, pdest->sanim[j][k].rot, pdest->sanim[j][k].pos );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: subtract one animaiton from animation to create an animation of the "difference"
+//-----------------------------------------------------------------------------
+
+void subtractBaseAnimations( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
+{
+ int j, k;
+
+ // create delta animations
+ s_bone_t src[MAXSTUDIOSRCBONES];
+
+ if (srcframe >= psrc->numframes)
+ {
+ MdlError( "subtract frame %d out of range for %s\n", srcframe, psrc->name );
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ VectorCopy( psrc->sanim[srcframe][k].pos, src[k].pos );
+ VectorCopy( psrc->sanim[srcframe][k].rot, src[k].rot );
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ for (j = 0; j < pdest->numframes; j++)
+ {
+ if (pdest->weight[k] > 0)
+ {
+ /*
+ printf("%2d : %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ k,
+ src[k].pos[0], src[k].pos[1], src[k].pos[2],
+ src[k].rot[0], src[k].rot[1], src[k].rot[2] );
+
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ RAD2DEG(pdest->sanim[j][k].pos[0]), RAD2DEG(pdest->sanim[j][k].pos[1]), RAD2DEG(pdest->sanim[j][k].pos[2]),
+ RAD2DEG(pdest->sanim[j][k].rot[0]), RAD2DEG(pdest->sanim[j][k].rot[1]), RAD2DEG(pdest->sanim[j][k].rot[2]) );
+ */
+
+ // calc differences between two rotations
+ if (flags & STUDIO_POST)
+ {
+ // find pdest in src's reference frame
+ QuaternionSMAngles( -1, src[k].rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot );
+ VectorSubtract( pdest->sanim[j][k].pos, src[k].pos, pdest->sanim[j][k].pos );
+ }
+ else
+ {
+ // find src in pdest's reference frame?
+ QuaternionMAAngles( pdest->sanim[j][k].rot, -1, src[k].rot, pdest->sanim[j][k].rot );
+ VectorSubtract( src[k].pos, pdest->sanim[j][k].pos, pdest->sanim[j][k].pos );
+ }
+
+ /*
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ pdest->sanim[j][k].pos[0], pdest->sanim[j][k].pos[1], pdest->sanim[j][k].pos[2],
+ RAD2DEG(pdest->sanim[j][k].rot[0]), RAD2DEG(pdest->sanim[j][k].rot[1]), RAD2DEG(pdest->sanim[j][k].rot[2]) );
+ */
+ }
+ }
+ }
+
+#if 0
+ // cleanup weightlists
+ for (k = 0; k < g_numbones; k++)
+ {
+ panim->weight[k] = 0.0;
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_weightlist[panim->weightlist].weight[k] > 0.0)
+ {
+ for (j = 0; j < panim->numframes; j++)
+ {
+ if (fabs(panim->sanim[j][k].pos[0]) > 0.001 ||
+ fabs(panim->sanim[j][k].pos[1]) > 0.001 ||
+ fabs(panim->sanim[j][k].pos[2]) > 0.001 ||
+ fabs(panim->sanim[j][k].rot[0]) > 0.001 ||
+ fabs(panim->sanim[j][k].rot[1]) > 0.001 ||
+ fabs(panim->sanim[j][k].rot[2]) > 0.001)
+ {
+ panim->weight[k] = g_weightlist[panim->weightlist].weight[k];
+ break;
+ }
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void QuaternionSlerp( const RadianEuler &r0, const RadianEuler &r1, float t, RadianEuler &r2 )
+{
+ Quaternion q0, q1, q2;
+ AngleQuaternion( r0, q0 );
+ AngleQuaternion( r1, q1 );
+ QuaternionSlerp( q0, q1, t, q2 );
+ QuaternionAngles( q2, r2 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: subtract each frame running interpolation of the first frame to the last frame
+//-----------------------------------------------------------------------------
+
+void linearDelta( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
+{
+ int j, k;
+
+ // create delta animations
+ s_bone_t src0[MAXSTUDIOSRCBONES];
+ s_bone_t src1[MAXSTUDIOSRCBONES];
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ VectorCopy( psrc->sanim[0][k].pos, src0[k].pos );
+ VectorCopy( psrc->sanim[0][k].rot, src0[k].rot );
+ VectorCopy( psrc->sanim[srcframe][k].pos, src1[k].pos );
+ VectorCopy( psrc->sanim[srcframe][k].rot, src1[k].rot );
+ }
+
+ if (pdest->numframes == 1)
+ {
+ MdlWarning( "%s too short for splinedelta\n", pdest->name );
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ for (j = 0; j < pdest->numframes; j++)
+ {
+ float s = 1;
+ if (pdest->numframes > 1)
+ {
+ s = (float)j / (pdest->numframes - 1);
+ }
+
+ // make it a spline curve
+ if (flags & STUDIO_AL_SPLINE)
+ {
+ s = 3 * s * s - 2 * s * s * s;
+ }
+
+ if (pdest->weight[k] > 0)
+ {
+ /*
+ printf("%2d : %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ k,
+ src[k].pos[0], src[k].pos[1], src[k].pos[2],
+ src[k].rot[0], src[k].rot[1], src[k].rot[2] );
+
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ RAD2DEG(pdest->sanim[j][k].pos[0]), RAD2DEG(pdest->sanim[j][k].pos[1]), RAD2DEG(pdest->sanim[j][k].pos[2]),
+ RAD2DEG(pdest->sanim[j][k].rot[0]), RAD2DEG(pdest->sanim[j][k].rot[1]), RAD2DEG(pdest->sanim[j][k].rot[2]) );
+ */
+
+ s_bone_t src;
+
+ src.pos = src0[k].pos * (1 - s) + src1[k].pos * s;
+ QuaternionSlerp( src0[k].rot, src1[k].rot, s, src.rot );
+
+ // calc differences between two rotations
+ if (flags & STUDIO_AL_POST)
+ {
+ // find pdest in src's reference frame
+ QuaternionSMAngles( -1, src.rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot );
+ VectorSubtract( pdest->sanim[j][k].pos, src.pos, pdest->sanim[j][k].pos );
+ }
+ else
+ {
+ // find src in pdest's reference frame?
+ QuaternionMAAngles( pdest->sanim[j][k].rot, -1, src.rot, pdest->sanim[j][k].rot );
+ VectorSubtract( src.pos, pdest->sanim[j][k].pos, pdest->sanim[j][k].pos );
+ }
+
+ /*
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ pdest->sanim[j][k].pos[0], pdest->sanim[j][k].pos[1], pdest->sanim[j][k].pos[2],
+ RAD2DEG(pdest->sanim[j][k].rot[0]), RAD2DEG(pdest->sanim[j][k].rot[1]), RAD2DEG(pdest->sanim[j][k].rot[2]) );
+ */
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: turn the animation into a lower fps encoded version
+//-----------------------------------------------------------------------------
+
+void reencodeAnimation( s_animation_t *panim, int frameskip )
+{
+ int j, k, n;
+
+ n = 1;
+ for (j = frameskip; j < panim->numframes; j += frameskip)
+ {
+ for (k = 0; k < g_numbones; k++)
+ {
+ panim->sanim[n][k] = panim->sanim[j][k];
+ }
+ n++;
+ }
+ panim->numframes = n;
+
+ panim->fps = panim->fps / frameskip;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: clip or pad the animation as nessesary to be a specified number of frames
+//-----------------------------------------------------------------------------
+
+void forceNumframes( s_animation_t *panim, int numframes )
+{
+ int j;
+
+ int size = g_numbones * sizeof( s_bone_t );
+
+ // copy
+ for (j = panim->numframes; j < numframes; j++)
+ {
+ panim->sanim[j] = (s_bone_t *)kalloc( 1, size );
+ memcpy( panim->sanim[j], panim->sanim[panim->numframes-1], size );
+ }
+
+ panim->numframes = numframes;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: subtract each frame from the previous to calculate the animations derivative
+//-----------------------------------------------------------------------------
+
+void createDerivative( s_animation_t *panim, float scale )
+{
+ int j, k;
+
+ s_bone_t orig[MAXSTUDIOSRCBONES];
+
+ j = panim->numframes - 1;
+ if (panim->flags & STUDIO_LOOPING)
+ {
+ j--;
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ VectorCopy( panim->sanim[j][k].pos, orig[k].pos );
+ VectorCopy( panim->sanim[j][k].rot, orig[k].rot );
+ }
+
+ for (j = panim->numframes - 1; j >= 0; j--)
+ {
+ s_bone_t *psrc;
+ s_bone_t *pdest;
+
+ if (j - 1 >= 0)
+ {
+ psrc = panim->sanim[j-1];
+ }
+ else
+ {
+ psrc = orig;
+ }
+ pdest = panim->sanim[j];
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (panim->weight[k] > 0)
+ {
+ /*
+ {
+ printf("%2d : %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ k,
+ psrc[k].pos[0], psrc[k].pos[1], psrc[k].pos[2],
+ RAD2DEG(psrc[k].rot[0]), RAD2DEG(psrc[k].rot[1]), RAD2DEG(psrc[k].rot[2]) );
+
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ pdest[k].pos[0], pdest[k].pos[1], pdest[k].pos[2],
+ RAD2DEG(pdest[k].rot[0]), RAD2DEG(pdest[k].rot[1]), RAD2DEG(pdest[k].rot[2]) );
+ }
+ */
+
+ // find pdest in src's reference frame
+ QuaternionSMAngles( -1, psrc[k].rot, pdest[k].rot, pdest[k].rot );
+ VectorSubtract( pdest[k].pos, psrc[k].pos, pdest[k].pos );
+
+ // rescale results (not sure what basis physics system is expecting)
+ {
+ // QuaternionScale( pdest[k].rot, scale, pdest[k].rot );
+ Quaternion q;
+ AngleQuaternion( pdest[k].rot, q );
+ QuaternionScale( q, scale, q );
+ QuaternionAngles( q, pdest[k].rot );
+ VectorScale( pdest[k].pos, scale, pdest[k].pos );
+ }
+
+ /*
+ {
+ printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
+ pdest[k].pos[0], pdest[k].pos[1], pdest[k].pos[2],
+ RAD2DEG(pdest[k].rot[0]), RAD2DEG(pdest[k].rot[1]), RAD2DEG(pdest[k].rot[2]) );
+ }
+ */
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: subtract each frame from the previous to calculate the animations derivative
+//-----------------------------------------------------------------------------
+
+void clearAnimations( s_animation_t *panim )
+{
+ panim->flags |= STUDIO_DELTA;
+ panim->flags |= STUDIO_ALLZEROS;
+
+ panim->numframes = 1;
+ panim->startframe = 0;
+ panim->endframe = 1;
+
+ int k;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ panim->sanim[0][k].pos = Vector( 0, 0, 0 );
+ panim->sanim[0][k].rot = RadianEuler( 0, 0, 0 );
+ panim->weight[k] = 0.0;
+ panim->posweight[k] = 0.0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: remove all world rotation from a bone
+//-----------------------------------------------------------------------------
+
+void counterRotateBone( s_animation_t *panim, int iBone, QAngle target )
+{
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ Vector pos;
+ matrix3x4_t defaultBoneToWorld;
+
+ int j;
+
+ AngleMatrix( target, defaultBoneToWorld );
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ CalcBoneTransforms( panim, j, boneToWorld );
+
+ MatrixPosition( boneToWorld[iBone], pos );
+ PositionMatrix( pos, defaultBoneToWorld );
+ boneToWorld[iBone] = defaultBoneToWorld;
+
+ solveBone( panim, j, iBone, boneToWorld );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: build transforms in source space, assuming source bones
+//-----------------------------------------------------------------------------
+void BuildRawTransforms( const s_source_t *psource, const char *pAnimationName,
+ int frame, float scale, Vector const &shift, RadianEuler const &rotate, int flags, matrix3x4_t* boneToWorld )
+{
+ int k;
+ Vector tmp;
+ Vector pos;
+ RadianEuler rot;
+ matrix3x4_t bonematrix;
+
+ matrix3x4_t rootxform;
+
+ AngleMatrix( rotate, rootxform );
+
+ const s_sourceanim_t *pSourceAnim = FindSourceAnim( psource, pAnimationName );
+ if ( !pSourceAnim )
+ {
+ MdlError( "Unknown animation name %s\n", pAnimationName );
+ return;
+ }
+
+ if ( flags & STUDIO_LOOPING )
+ {
+ if ( frame )
+ {
+ while ( frame < 0)
+ frame += pSourceAnim->numframes;
+ frame = frame % pSourceAnim->numframes;
+ }
+ }
+ else
+ {
+ frame = clamp( frame, 0, pSourceAnim->numframes - 1 );
+ }
+
+ // build source space local to world transforms
+ for (k = 0; k < psource->numbones; k++)
+ {
+ VectorScale( pSourceAnim->rawanim.Element(frame)[k].pos, scale, pos );
+ VectorCopy( pSourceAnim->rawanim.Element(frame)[k].rot, rot );
+
+ if ( psource->localBone[k].parent == -1 )
+ {
+ // translate
+ VectorSubtract( pos, shift, tmp );
+
+ // rotate
+ VectorRotate( tmp, rootxform, pos );
+
+ matrix3x4_t m;
+ AngleMatrix( rot, m );
+ ConcatTransforms( rootxform, m, bonematrix );
+ MatrixAngles( bonematrix, rot );
+ clip_rotations( rot );
+ }
+
+ AngleMatrix( rot, pos, bonematrix );
+
+ if ( psource->localBone[k].parent == -1 )
+ {
+ MatrixCopy( bonematrix, boneToWorld[k] );
+ }
+ else
+ {
+ ConcatTransforms( boneToWorld[psource->localBone[k].parent], bonematrix, boneToWorld[k] );
+ // ConcatTransforms( worldToBone[psource->localBone[k].parent], boneToWorld[k], bonematrix );
+ // B * C => A
+ // C <= B-1 * A
+ }
+ }
+}
+
+
+void BuildRawTransforms( const s_source_t *psource, const char *pAnimationName, int frame, matrix3x4_t* boneToWorld )
+{
+ BuildRawTransforms( psource, pAnimationName, frame, 1.0f, Vector( 0, 0, 0 ), RadianEuler( 0, 0, 0 ), 0, boneToWorld );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: convert source bone animation into global bone animation
+//-----------------------------------------------------------------------------
+void TranslateAnimations( const s_source_t *pSource, const matrix3x4_t *pSrcBoneToWorld, matrix3x4_t *pDestBoneToWorld )
+{
+ matrix3x4_t bonematrix;
+
+ for (int k = 0; k < g_numbones; k++)
+ {
+ int q = pSource->boneGlobalToLocal[k];
+ if ( q == -1 )
+ {
+ // unknown bone, copy over defaults
+ if ( g_bonetable[k].parent >= 0 )
+ {
+ AngleMatrix( g_bonetable[k].rot, g_bonetable[k].pos, bonematrix );
+ ConcatTransforms( pDestBoneToWorld[g_bonetable[k].parent], bonematrix, pDestBoneToWorld[k] );
+ }
+ else
+ {
+ AngleMatrix( g_bonetable[k].rot, g_bonetable[k].pos, pDestBoneToWorld[k] );
+ }
+ }
+ else
+ {
+ ConcatTransforms( pSrcBoneToWorld[q], g_bonetable[k].srcRealign, pDestBoneToWorld[k] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: convert source bone animation into global bone animation
+//-----------------------------------------------------------------------------
+void ConvertAnimation( const s_source_t *psource, const char *pAnimationName, int frame, float scale, Vector const &shift, RadianEuler const &rotate, s_bone_t *dest )
+{
+ int k;
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ //matrix3x4_t srcWorldToBone[MAXSTUDIOSRCBONES];
+ matrix3x4_t destBoneToWorld[MAXSTUDIOSRCBONES];
+ matrix3x4_t destWorldToBone[MAXSTUDIOSRCBONES];
+
+ matrix3x4_t bonematrix;
+
+ BuildRawTransforms( psource, pAnimationName, frame, scale, shift, rotate, 0, srcBoneToWorld );
+
+ /*
+ for (k = 0; k < psource->numbones; k++)
+ {
+ MatrixInvert( srcBoneToWorld[k], srcWorldToBone[k] );
+ }
+ */
+
+ TranslateAnimations( psource, srcBoneToWorld, destBoneToWorld );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixInvert( destBoneToWorld[k], destWorldToBone[k] );
+ }
+
+ // convert source_space_local_to_world transforms to shared_space_local_to_world transforms
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( destBoneToWorld[k], bonematrix );
+ }
+ else
+ {
+ // convert my transform into parent relative space
+ ConcatTransforms( destWorldToBone[g_bonetable[k].parent], destBoneToWorld[k], bonematrix );
+
+ // printf("%s : %s\n", psource->localBone[q2].name, psource->localBone[q].name );
+
+ // B * C => A
+ // C <= B-1 * A
+ }
+
+ MatrixAngles( bonematrix, dest[k].rot, dest[k].pos );
+
+ clip_rotations( dest[k].rot );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: copy the raw animation data from the source files into the individual animations
+//-----------------------------------------------------------------------------
+void RemapAnimations(void)
+{
+ int i, j;
+
+ // copy source animations
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+
+ s_source_t *psource = panim->source;
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( psource, panim->animationname );
+ int size = g_numbones * sizeof( s_bone_t );
+
+ int n = panim->startframe - pSourceAnim->startframe;
+ // printf("%s %d:%d\n", g_panimation[i]->filename, g_panimation[i]->startframe, pSourceAnim->startframe );
+ for (j = 0; j < panim->numframes; j++)
+ {
+ panim->sanim[j] = (s_bone_t *)kalloc( 1, size );
+
+ ConvertAnimation( psource, panim->animationname, n + j, panim->scale, panim->adjust, panim->rotation, panim->sanim[j] );
+ }
+ }
+}
+
+void buildAnimationWeights()
+{
+ int i, j, k;
+
+ // rlink animation weights
+ for (i = 0; i < g_numweightlist; i++)
+ {
+ if (i == 0)
+ {
+ // initialize weights
+ for (j = 0; j < g_numbones; j++)
+ {
+ if (g_bonetable[j].parent != -1)
+ {
+ // set child bones to uninitialized
+ g_weightlist[i].weight[j] = -1;
+ }
+ else if (i == 0)
+ {
+ // set root bones to 1
+ g_weightlist[i].weight[j] = 1;
+ g_weightlist[i].posweight[j] = 1;
+ }
+ }
+ }
+ else
+ {
+ // initialize weights
+ for (j = 0; j < g_numbones; j++)
+ {
+ if (g_bonetable[j].parent != -1)
+ {
+ // set child bones to uninitialized
+ g_weightlist[i].weight[j] = g_weightlist[0].weight[j];
+ g_weightlist[i].posweight[j] = g_weightlist[0].posweight[j];
+ }
+ else
+ {
+ // set root bones to 0
+ g_weightlist[i].weight[j] = 0;
+ g_weightlist[i].posweight[j] = 0;
+ }
+ }
+ }
+
+ // match up weights
+ for (j = 0; j < g_weightlist[i].numbones; j++)
+ {
+ k = findGlobalBone( g_weightlist[i].bonename[j] );
+ if (k == -1)
+ {
+ MdlError("unknown bone reference '%s' in weightlist '%s'\n", g_weightlist[i].bonename[j], g_weightlist[i].name );
+ }
+ g_weightlist[i].weight[k] = g_weightlist[i].boneweight[j];
+ g_weightlist[i].posweight[k] = g_weightlist[i].boneposweight[j];
+ }
+ }
+
+ for (i = 0; i < g_numweightlist; i++)
+ {
+ // copy weights forward
+ for (j = 0; j < g_numbones; j++)
+ {
+ if (g_weightlist[i].weight[j] < 0.0)
+ {
+ if (g_bonetable[j].parent != -1)
+ {
+ g_weightlist[i].weight[j] = g_weightlist[i].weight[g_bonetable[j].parent];
+ g_weightlist[i].posweight[j] = g_weightlist[i].posweight[g_bonetable[j].parent];
+ }
+ }
+ }
+ }
+}
+
+void setAnimationWeight( s_animation_t *panim, int index )
+{
+ // copy weightlists to animations
+ for (int k = 0; k < g_numbones; k++)
+ {
+ panim->weight[k] = g_weightlist[index].weight[k];
+ panim->posweight[k] = g_weightlist[index].posweight[k];
+ }
+}
+
+void addDeltas( s_animation_t *panim, int frame, float s, Vector delta_pos[], Quaternion delta_q[] )
+{
+ for (int k = 0; k < g_numbones; k++)
+ {
+ if (panim->weight[k] > 0)
+ {
+ QuaternionSMAngles( s, delta_q[k], panim->sanim[frame][k].rot, panim->sanim[frame][k].rot );
+ VectorMA( panim->sanim[frame][k].pos, s, delta_pos[k], panim->sanim[frame][k].pos );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: find the difference between the overlapping frames and spread out
+// the difference over multiple frames.
+// start: negative number, specifies how far back from the end to start blending
+// end: positive number, specifies how many frames from the beginning to blend
+//-----------------------------------------------------------------------------
+
+void fixupLoopingDiscontinuities( s_animation_t *panim, int start, int end )
+{
+ int j, k, m, n;
+
+ // fix C0 errors on looping animations
+ m = panim->numframes - 1;
+
+ Vector delta_pos[MAXSTUDIOSRCBONES];
+ Quaternion delta_q[MAXSTUDIOSRCBONES];
+
+ // skip if there's nothing to smooth
+ if (m == 0)
+ return;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ VectorSubtract( panim->sanim[m][k].pos, panim->sanim[0][k].pos, delta_pos[k] );
+ QuaternionMA( panim->sanim[m][k].rot, -1, panim->sanim[0][k].rot, delta_q[k] );
+ QAngle ang;
+ QuaternionAngles( delta_q[k], ang );
+ // printf("%2d %.1f %.1f %.1f\n", k, ang.x, ang.y, ang.z );
+ }
+
+ // HACK: skip fixup for motion that'll be matched with linear extraction
+ // FIXME: remove when "global" extraction moved into normal ordered processing loop
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ if (panim->motiontype & STUDIO_LX)
+ delta_pos[k].x = 0.0;
+ if (panim->motiontype & STUDIO_LY)
+ delta_pos[k].y = 0.0;
+ if (panim->motiontype & STUDIO_LZ)
+ delta_pos[k].z = 0.0;
+ // FIXME: add rotation
+ }
+ }
+
+ // make sure loop doesn't exceed animation length
+ if (end-start > panim->numframes)
+ {
+ end = panim->numframes + start;
+ if (end < 0)
+ {
+ end = 0;
+ start = -(panim->numframes - 1);
+ }
+ }
+
+ // FIXME: figure out S
+ float s = 0;
+ float nf = end - start;
+
+ for (j = start + 1; j <= 0; j++)
+ {
+ n = j - start;
+ s = (n / nf);
+ s = 3 * s * s - 2 * s * s * s;
+ // printf("%d : %d (%lf)\n", m+j, n, -s );
+ addDeltas( panim, m+j, -s, delta_pos, delta_q );
+ }
+
+ for (j = 0; j < end; j++)
+ {
+ n = end - j;
+ s = (n / nf);
+ s = 3 * s * s - 2 * s * s * s;
+ //printf("%d : %d (%lf)\n", j, n, s );
+ addDeltas( panim, j, s, delta_pos, delta_q );
+ }
+}
+
+
+
+void matchBlend( s_animation_t *pDestAnim, s_animation_t *pSrcAnimation, int iSrcFrame, int iDestFrame, int iPre, int iPost )
+{
+ int j, k;
+
+ if (pDestAnim->flags & STUDIO_LOOPING)
+ {
+ iPre = max( iPre, -pDestAnim->numframes );
+ iPost = min( iPost, pDestAnim->numframes );
+ }
+ else
+ {
+ iPre = max( iPre, -iDestFrame );
+ iPost = min( iPost, pDestAnim->numframes - iDestFrame );
+ }
+
+ Vector delta_pos[MAXSTUDIOSRCBONES];
+ Quaternion delta_q[MAXSTUDIOSRCBONES];
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ VectorSubtract( pSrcAnimation->sanim[iSrcFrame][k].pos, pDestAnim->sanim[iDestFrame][k].pos, delta_pos[k] );
+ QuaternionMA( pSrcAnimation->sanim[iSrcFrame][k].rot, -1, pDestAnim->sanim[iDestFrame][k].rot, delta_q[k] );
+ /*
+ QAngle ang;
+ QuaternionAngles( delta_q[k], ang );
+ printf("%2d %.1f %.1f %.1f\n", k, ang.x, ang.y, ang.z );
+ */
+ }
+
+ // HACK: skip fixup for motion that'll be matched with linear extraction
+ // FIXME: remove when "global" extraction moved into normal ordered processing loop
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ if (pDestAnim->motiontype & STUDIO_LX)
+ delta_pos[k].x = 0.0;
+ if (pDestAnim->motiontype & STUDIO_LY)
+ delta_pos[k].y = 0.0;
+ if (pDestAnim->motiontype & STUDIO_LZ)
+ delta_pos[k].z = 0.0;
+ // FIXME: add rotation
+ }
+ }
+
+ // FIXME: figure out S
+ float s = 0;
+
+ for (j = iPre; j <= iPost; j++)
+ {
+ if (j < 0)
+ {
+ s = j / (float)(iPre-1);
+ }
+ else
+ {
+ s = j / (float)(iPost+1);
+ }
+ s = SimpleSpline( 1 - s );
+ k = iDestFrame + j;
+ if (k < 0)
+ {
+ k += (pDestAnim->numframes - 1);
+ }
+ else
+ {
+ k = k % (pDestAnim->numframes - 1);
+ }
+ //printf("%d : %d (%lf)\n", iDestFrame + j, k, s );
+ addDeltas( pDestAnim, k, s, delta_pos, delta_q );
+ // make sure final frame of a looping animation matches frame 0
+ if ((pDestAnim->flags & STUDIO_LOOPING) && k == 0)
+ {
+ addDeltas( pDestAnim, pDestAnim->numframes - 1, s, delta_pos, delta_q );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: copy the first frame overtop the last frame
+//-----------------------------------------------------------------------------
+
+void forceAnimationLoop( s_animation_t *panim )
+{
+ int k, m, n;
+
+ // force looping animations to be looping
+ if (panim->flags & STUDIO_LOOPING)
+ {
+ n = 0;
+ m = panim->numframes - 1;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ int type = panim->motiontype;
+
+ if (!(type & STUDIO_LX))
+ panim->sanim[m][k].pos[0] = panim->sanim[n][k].pos[0];
+ if (!(type & STUDIO_LY))
+ panim->sanim[m][k].pos[1] = panim->sanim[n][k].pos[1];
+ if (!(type & STUDIO_LZ))
+ panim->sanim[m][k].pos[2] = panim->sanim[n][k].pos[2];
+
+ if (!(type & STUDIO_LXR))
+ panim->sanim[m][k].rot[0] = panim->sanim[n][k].rot[0];
+ if (!(type & STUDIO_LYR))
+ panim->sanim[m][k].rot[1] = panim->sanim[n][k].rot[1];
+ if (!(type & STUDIO_LZR))
+ panim->sanim[m][k].rot[2] = panim->sanim[n][k].rot[2];
+ }
+ }
+
+ // printf("\n");
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calculate an single bones animation in a different parent's reference frame
+//-----------------------------------------------------------------------------
+
+void localHierarchy( s_animation_t *panim, char *pBonename, char *pParentname, int start, int peak, int tail, int end )
+{
+ s_localhierarchy_t *pRule;
+
+ pRule = &panim->localhierarchy[ panim->numlocalhierarchy ];
+ panim->numlocalhierarchy++;
+
+ pRule->start = start;
+ pRule->peak = peak;
+ pRule->tail = tail;
+ pRule->end = end;
+
+ if (pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0)
+ {
+ pRule->tail = panim->numframes - 1;
+ pRule->end = panim->numframes - 1;
+ }
+
+ if (pRule->start != -1 && pRule->peak == -1 && pRule->tail == -1 && pRule->end != -1)
+ {
+ pRule->peak = (pRule->start + pRule->end) / 2;
+ pRule->tail = (pRule->start + pRule->end) / 2;
+ }
+
+ if (pRule->start != -1 && pRule->peak == -1 && pRule->tail != -1)
+ {
+ pRule->peak = (pRule->start + pRule->tail) / 2;
+ }
+
+ if (pRule->peak != -1 && pRule->tail == -1 && pRule->end != -1)
+ {
+ pRule->tail = (pRule->peak + pRule->end) / 2;
+ }
+
+ if (pRule->peak == -1)
+ {
+ pRule->start = 0;
+ pRule->peak = 0;
+ }
+
+ if (pRule->tail == -1)
+ {
+ pRule->tail = panim->numframes - 1;
+ pRule->end = panim->numframes - 1;
+ }
+
+ // check for wrapping
+ if (pRule->peak < pRule->start)
+ {
+ pRule->peak += panim->numframes - 1;
+ }
+ if (pRule->tail < pRule->peak)
+ {
+ pRule->tail += panim->numframes - 1;
+ }
+ if (pRule->end < pRule->tail)
+ {
+ pRule->end += panim->numframes - 1;
+ }
+
+
+ pRule->localData.numerror = pRule->end - pRule->start + 1;
+ if (pRule->end >= panim->numframes)
+ pRule->localData.numerror = pRule->localData.numerror + 2;
+
+ pRule->localData.pError = (s_streamdata_t *)kalloc( pRule->localData.numerror, sizeof( s_streamdata_t ));
+
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t worldToBone;
+ matrix3x4_t local;
+
+ pRule->bone = findGlobalBone( pBonename );
+ if (pRule->bone == -1)
+ {
+ MdlError("anim '%s' references unknown bone '%s' in localhierarchy\n", panim->name, pBonename );
+ }
+
+ if (strlen( pParentname ) == 0)
+ {
+ pRule->newparent = -1;
+ }
+ else
+ {
+ pRule->newparent = findGlobalBone( pParentname );
+ if (pRule->newparent == -1)
+ {
+ MdlError("anim '%s' references unknown bone '%s' in localhierarchy\n", panim->name, pParentname );
+ }
+ }
+
+ int k;
+ const char *pAnimationName = panim->animationname;
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, pAnimationName );
+
+
+ for (k = 0; k < pRule->localData.numerror; k++)
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pAnimationName, k + pRule->start + panim->startframe - pSourceAnim->startframe, panim->scale, panim->adjust, panim->rotation, panim->flags, srcBoneToWorld );
+
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+
+ if (pRule->newparent != -1)
+ {
+ MatrixInvert( boneToWorld[pRule->newparent], worldToBone );
+ ConcatTransforms( worldToBone, boneToWorld[pRule->bone], local );
+ }
+ else
+ {
+ MatrixCopy( boneToWorld[pRule->bone], local );
+ }
+
+ MatrixAngles( local, pRule->localData.pError[k].q, pRule->localData.pError[k].pos );
+
+ /*
+ QAngle ang;
+ QuaternionAngles( pRule->errorData.pError[k].q, ang );
+ printf("%d %.1f %.1f %.1f : %.1f %.1f %.1f\n",
+ k,
+ pRule->errorData.pError[k].pos.x, pRule->errorData.pError[k].pos.y, pRule->errorData.pError[k].pos.z,
+ ang.x, ang.y, ang.z );
+ */
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: rotate the animation so that it's moving in the specified angle
+//-----------------------------------------------------------------------------
+
+
+void makeAngle( s_animation_t *panim, float angle )
+{
+ float da = 0.0f;
+
+ if (panim->numpiecewisekeys != 0)
+ {
+ // look for movement in total piecewise movement
+ Vector pos = panim->piecewisemove[panim->numpiecewisekeys-1].pos;
+ if (pos[0] != 0 || pos[1] != 0)
+ {
+ float a = atan2( pos[1], pos[0] ) * (180 / M_PI);
+ da = angle - a;
+ }
+
+ for (int i = 0; i < panim->numpiecewisekeys; i++)
+ {
+ VectorYawRotate( panim->piecewisemove[i].pos, da, panim->piecewisemove[i].pos );
+ VectorYawRotate( panim->piecewisemove[i].vector, da, panim->piecewisemove[i].vector );
+ }
+ }
+ else
+ {
+ // look for movement in root bone
+ Vector pos = panim->sanim[(panim->numframes - 1)][g_rootIndex].pos - panim->sanim[0][g_rootIndex].pos;
+ if (pos[0] != 0 || pos[1] != 0)
+ {
+ float a = atan2( pos[1], pos[0] ) * (180 / M_PI);
+ da = angle - a;
+ }
+ }
+
+ /*
+ if (da > -0.01 && da < 0.01)
+ return;
+ */
+
+ matrix3x4_t rootxform;
+ matrix3x4_t src;
+ matrix3x4_t dest;
+
+ AngleMatrix( QAngle( 0, da, 0), rootxform );
+
+ for (int j = 0; j < panim->numframes; j++)
+ {
+ for (int k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ AngleMatrix( panim->sanim[j][k].rot, panim->sanim[j][k].pos, src );
+ ConcatTransforms( rootxform, src, dest );
+ MatrixAngles( dest, panim->sanim[j][k].rot, panim->sanim[j][k].pos );
+ }
+ }
+ }
+
+ // FIXME: not finished
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: convert pBoneToWorld back into rot/pos data
+//-----------------------------------------------------------------------------
+
+void solveBone(
+ s_animation_t *panim,
+ int iFrame,
+ int iBone,
+ matrix3x4_t* pBoneToWorld
+ )
+{
+ int iParent = g_bonetable[iBone].parent;
+
+ if (iParent == -1)
+ {
+ MatrixAngles( pBoneToWorld[iBone], panim->sanim[iFrame][iBone].rot, panim->sanim[iFrame][iBone].pos );
+ return;
+ }
+
+ matrix3x4_t worldToBone;
+ MatrixInvert( pBoneToWorld[iParent], worldToBone );
+
+ matrix3x4_t local;
+ ConcatTransforms( worldToBone, pBoneToWorld[iBone], local );
+
+ iFrame = iFrame % panim->numframes;
+
+ MatrixAngles( local, panim->sanim[iFrame][iBone].rot, panim->sanim[iFrame][iBone].pos );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calc the influence of a ik rule for a specific point in the animation cycle
+//-----------------------------------------------------------------------------
+
+float IKRuleWeight( s_ikrule_t *pRule, float flCycle )
+{
+ if (pRule->end > 1.0f && flCycle < pRule->start)
+ {
+ flCycle = flCycle + 1.0f;
+ }
+
+ float value = 0.0f;
+ if (flCycle < pRule->start)
+ {
+ return 0.0f;
+ }
+ else if (flCycle < pRule->peak )
+ {
+ value = (flCycle - pRule->start) / (pRule->peak - pRule->start);
+ }
+ else if (flCycle < pRule->tail )
+ {
+ return 1.0f;
+ }
+ else if (flCycle < pRule->end )
+ {
+ value = 1.0f - ((flCycle - pRule->tail) / (pRule->end - pRule->tail));
+ }
+ return 3.0f * value * value - 2.0f * value * value * value;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lock the ik target to a specific location in order to clean up bad animations (shouldn't be needed).
+//-----------------------------------------------------------------------------
+void fixupIKErrors( s_animation_t *panim, s_ikrule_t *pRule )
+{
+ int k;
+
+ if (pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0)
+ {
+ pRule->tail = panim->numframes - 1;
+ pRule->end = panim->numframes - 1;
+ }
+
+ // check for wrapping
+ if (pRule->peak < pRule->start)
+ {
+ pRule->peak += panim->numframes - 1;
+ }
+ if (pRule->tail < pRule->peak)
+ {
+ pRule->tail += panim->numframes - 1;
+ }
+ if (pRule->end < pRule->tail)
+ {
+ pRule->end += panim->numframes - 1;
+ }
+
+ if (pRule->contact == -1)
+ {
+ pRule->contact = pRule->peak;
+ }
+
+ if (panim->numframes <= 1)
+ return;
+
+ pRule->errorData.numerror = pRule->end - pRule->start + 1;
+
+ switch( pRule->type )
+ {
+ case IK_SELF:
+#if 0
+ // this code has never been run.....
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t worldToBone;
+ matrix3x4_t local;
+ Vector targetPos;
+ Quaternion targetQuat;
+
+ pRule->bone = findGlobalBone( pRule->bonename );
+ if (pRule->bone == -1)
+ {
+ MdlError("unknown bone '%s' in ikrule\n", pRule->bonename );
+ }
+
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pRule->contact + panim->startframe - panim->source->startframe, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+
+ MatrixInvert( boneToWorld[pRule->bone], worldToBone );
+ ConcatTransforms( worldToBone, boneToWorld[g_ikchain[pRule->chain].link[2].bone], local );
+ MatrixAngles( local, targetQuat, targetPos );
+
+ for (k = 0; k < pRule->errorData.numerror; k++)
+ {
+ BuildRawTransforms( panim->source, k + pRule->start + panim->startframe - panim->source->startframe, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+
+ float cycle = (panim->numframes <= 1) ? 0 : (k + pRule->start) / (panim->numframes - 1);
+ float s = IKRuleWeight( pRule, cycle );
+
+ Quaternion curQuat;
+ Vector curPos;
+
+ // convert into rule bone space
+ MatrixInvert( boneToWorld[pRule->bone], worldToBone );
+ ConcatTransforms( worldToBone, boneToWorld[g_ikchain[pRule->chain].link[2].bone], local );
+ MatrixAngles( local, curQuat, curPos );
+
+ // find blended rule bone relative position
+ Vector rulePos = curPos * s + targetPos * (1.0 - s);
+ Quaternion ruleQuat;
+ QuaternionSlerp( curQuat, targetQuat, s, ruleQuat );
+ QuaternionMatrix( ruleQuat, rulePos, local );
+
+ Vector worldPos;
+ VectorTransform( rulePos, boneToWorld[pRule->bone], worldPos );
+
+ // printf("%d (%d) : %.1f %.1f %1.f\n", k + pRule->start, pRule->peak, pos.x, pos.y, pos.z );
+ Studio_SolveIK(
+ g_ikchain[pRule->chain].link[0].bone,
+ g_ikchain[pRule->chain].link[1].bone,
+ g_ikchain[pRule->chain].link[2].bone,
+ worldPos,
+ boneToWorld );
+
+ // slam final matrix
+ // FIXME: this isn't taking into account the IK may have failed
+ ConcatTransforms( boneToWorld[pRule->bone], local, boneToWorld[g_ikchain[pRule->chain].link[2].bone] );
+
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[0].bone, boneToWorld );
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[1].bone, boneToWorld );
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[2].bone, boneToWorld );
+ }
+ }
+#endif
+ break;
+ case IK_WORLD:
+ case IK_GROUND:
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+
+ int bone = g_ikchain[pRule->chain].link[2].bone;
+ CalcBoneTransforms( panim, pRule->contact, boneToWorld );
+ // FIXME: add in motion
+
+ Vector footfall;
+ MatrixGetColumn( boneToWorld[bone], 3, footfall );
+
+ //printf("%d %d %d %d (%d)\n", pRule->start, pRule->peak, pRule->tail, pRule->end, pRule->errorData.numerror );
+ for (k = 0; k < pRule->errorData.numerror; k++)
+ {
+ CalcBoneTransforms( panim, k + pRule->start, boneToWorld );
+
+ float cycle = (panim->numframes <= 1) ? 0 : (float)(k + pRule->start) / (panim->numframes - 1);
+ float s = IKRuleWeight( pRule, cycle );
+ s = 1.0; // FIXME - the weight rule is wrong
+
+ Vector orig;
+ MatrixPosition( boneToWorld[g_ikchain[pRule->chain].link[2].bone], orig );
+
+ Vector pos = (footfall + calcMovement( panim, k + pRule->start, pRule->contact )) * s + orig * (1.0 - s);
+
+ //printf("%d (%.1f:%.1f) : %.1f %.1f %1.f\n", k + pRule->start, cycle, s, pos.x, pos.y, pos.z );
+
+ Studio_SolveIK(
+ g_ikchain[pRule->chain].link[0].bone,
+ g_ikchain[pRule->chain].link[1].bone,
+ g_ikchain[pRule->chain].link[2].bone,
+ pos,
+ boneToWorld );
+
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[0].bone, boneToWorld );
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[1].bone, boneToWorld );
+ solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[2].bone, boneToWorld );
+ }
+ }
+ }
+ forceAnimationLoop( panim ); // !!!
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: map the vertex animations to their equivalent vertex in the base animations
+//-----------------------------------------------------------------------------
+static void ComputeSideAndScale( const s_flexkey_t &flexKey, s_vertanim_t *pVAnim, float *pSide, float *pScale )
+{
+ *pScale = 1.0f;
+ *pSide = 0.0f;
+
+ if ( flexKey.split > 0.0f )
+ {
+ if ( pVAnim->pos.x > flexKey.split )
+ {
+ *pScale = 0.0f;
+ }
+ else if ( pVAnim->pos.x < -flexKey.split )
+ {
+ *pScale = 1.0f;
+ }
+ else
+ {
+ float t = ( flexKey.split - pVAnim->pos.x ) / (2.0 * flexKey.split);
+ *pScale = 3.0f * t * t - 2.0f * t * t * t;
+ // printf( "%.1f : %.2f\n", pSrcAnim->pos.x, *pScale );
+ }
+ }
+ else if ( flexKey.split < 0.0f )
+ {
+ if ( pVAnim->pos.x < flexKey.split)
+ {
+ *pScale = 0.0f;
+ }
+ else if ( pVAnim->pos.x > -flexKey.split)
+ {
+ *pScale = 1.0f;
+ }
+ else
+ {
+ float t = ( flexKey.split - pVAnim->pos.x ) / ( 2.0f * flexKey.split );
+ *pScale = 3.0f * t * t - 2.0f * t * t * t;
+ // printf( "%.1f : %.2f\n", pSrcAnim->pos.x, *pScale );
+ }
+ }
+
+ if ( flexKey.flexpair != 0)
+ {
+ // paired flexes are full scale but variable side to side
+ *pSide = 1.0 - *pScale;
+ *pScale = 1.0;
+ }
+ else
+ {
+ // unpaired flexes are variable scale, one sided
+ *pSide = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: map the vertex animations to their equivalent vertex in the base animations
+//-----------------------------------------------------------------------------
+static void ComputeVertexAnimationSpeed( s_flexkey_t& flexKey )
+{
+ // calc max total scale for deltas
+ float flScale = 0.0f;
+ for ( int m = 0; m < flexKey.numvanims; m++ )
+ {
+ float s =flexKey.vanim[m].pos.Length();
+
+ if ( s > flScale )
+ {
+ flScale = s;
+ }
+ }
+ if ( flScale == 0.0f )
+ {
+ flScale = 0.01f;
+ }
+
+ // set
+ for ( int m = 0; m < flexKey.numvanims; m++ )
+ {
+ if ( flexKey.decay == 0.0f )
+ {
+ flexKey.vanim[m].speed = 1.0f;
+ }
+ else
+ {
+ flexKey.vanim[m].speed = clamp( flexKey.vanim[m].pos.Length() / (flScale * flexKey.decay), 0.0f, 1.0f );
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: map the vertex animations to their equivalent vertex in the base animations
+//-----------------------------------------------------------------------------
+static void BuildVAnimFlags( s_source_t *pVSource, s_sourceanim_t *pVSourceAnim, int nCurrentFlexKey )
+{
+ pVSourceAnim->vanim_flag = (int *)kalloc( pVSource->numvertices, sizeof( int ));
+ for ( int n = nCurrentFlexKey; n < g_numflexkeys; n++ )
+ {
+ // make sure it's the current flex file and that it's not frame 0 (happens with eyeball stuff).
+ if ( g_flexkey[n].source != pVSource )
+ continue;
+
+ if ( Q_stricmp( g_flexkey[n].animationname, pVSourceAnim->animationname ) )
+ continue;
+
+ const s_sourceanim_t *pAnim = FindSourceAnim( g_flexkey[n].source, g_flexkey[n].animationname );
+ if ( !pAnim )
+ continue;
+
+ if ( pAnim->newStyleVertexAnimations != pVSourceAnim->newStyleVertexAnimations )
+ continue;
+
+ if ( !pAnim->newStyleVertexAnimations && g_flexkey[n].frame == 0 )
+ continue;
+
+ int k = g_flexkey[n].frame;
+ for ( int m = 0; m < pVSourceAnim->numvanims[k]; m++ )
+ {
+ pVSourceAnim->vanim_flag[ pVSourceAnim->vanim[k][m].vertex ] = 1;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Build an array indexed by model vertex which indicates which vanim vertex corresponds best to it
+//-----------------------------------------------------------------------------
+static void BuildModelToVAnimMap( s_source_t *pVSource, s_sourceanim_t *pVSourceAnim, s_loddata_t *pmLodSource, bool bNewVertexAnimations, int *pModelToVAnim )
+{
+ static float imapdist[MAXSTUDIOVERTS]; // distance from src vert to vanim vert
+ static float imapdot[MAXSTUDIOVERTS]; // dot product of src norm to vanim normal
+ Vector tmp;
+
+ // find frame 0 vertices to closest g_model vertex
+ for ( int j = 0; j < pmLodSource->numvertices; j++ )
+ {
+ imapdist[j] = 1E30;
+ imapdot[j] = -1.0;
+ pModelToVAnim[j] = -1;
+ }
+
+ int nMinLod = min( g_minLod, g_ScriptLODs.Count() - 1 );
+
+ for ( int j = 0; j < pVSource->numvertices; j++ )
+ {
+ float flMinDist = 1E30;
+ int n = -1;
+ for ( int k = 0; k < pmLodSource->numvertices; k++ )
+ {
+ // go ahead and skip vertices that are just going to be stripped later
+ // TODO: take this out when the lod clamping stuff gets moved into the LOD code instead of being a post process
+ if ( nMinLod && !( pmLodSource->vertex[k].lodFlag & (0xFFFFFF << nMinLod) ) )
+ continue;
+
+ const Vector& vecModelPos = bNewVertexAnimations ? pVSource->vertex[j].position : pVSourceAnim->vanim[0][j].pos;
+
+ // TODO: Length() gives inconsistent results in release build
+ VectorSubtract( pmLodSource->vertex[k].position, vecModelPos, tmp );
+ float flDist = tmp.LengthSqr();
+ if ( flDist >= 0.15f )
+ continue;
+
+ const Vector& vecModelNormal = bNewVertexAnimations ? pVSource->vertex[j].normal : pVSourceAnim->vanim[0][j].normal;
+ float flDot = DotProduct( pmLodSource->vertex[k].normal, vecModelNormal );
+ if ( flDist < imapdist[k] || ( flDist == imapdist[k] && flDot > imapdot[k]))
+ {
+ imapdist[k] = flDist;
+ imapdot[k] = flDot;
+ pModelToVAnim[k] = j;
+ }
+
+ if ( flDist < flMinDist )
+ {
+ flMinDist = flDist;
+ n = j;
+ }
+ }
+
+ if ( flMinDist > 0.01 )
+ {
+ // printf("vert %d dist %.4f\n", j, minDist );
+ // printf("%.4f %.4f %.4f\n", pvsource->vanim[0][j].pos[0], pvsource->vanim[0][j].pos[1], pvsource->vanim[0][j].pos[2] );
+ }
+
+ // VectorSubtract( modelpos[n], pvsource->vanim[0][j].pos, matchdelta[j] );
+
+ if ( n == -1 )
+ {
+ // printf("no match for animated vertex %d : %.4f %.4f %.4f\n", j, pVSourceAnim->vanim[0][j].pos[0], pVSourceAnim->vanim[0][j].pos[1], pVSourceAnim->vanim[0][j].pos[2] );
+ }
+ }
+
+ /*
+ for (j = 0; j < pmsource->numvertices; j++)
+ {
+ printf("%4d : %7.4f %7.4f : %5d", j, imapdist[j], imapdot[j], model_to_vanim_vert_imap[j] );
+ printf(" : %8.4f %8.4f %8.4f", modelpos[j][0], modelpos[j][1], modelpos[j][2] );
+ printf("\n");
+ }
+ */
+
+ /*
+ for (j = 0; j < pmsource->numvertices; j++)
+ {
+ if (fabs( modelpos[j][2] - 64.36) > 0.01)
+ continue;
+
+ printf("%4d : %8.4f %8.4f %8.4f\n", j, modelpos[j][0], modelpos[j][1], modelpos[j][2] );
+ }
+
+ for (j = 0; j < pvsource->numvertices; j++)
+ {
+ if (!pvsource->vanim_flag[j])
+ continue;
+
+ printf("%4d : %8.2f %8.2f %8.2f : ", j, pvsource->vanim[0][j].pos[0], pvsource->vanim[0][j].pos[1], pvsource->vanim[0][j].pos[2] );
+ for (k = 0; k < pmsource->numvertices; k++)
+ {
+ if (model_to_vanim_vert_imap[k] == j)
+ printf(" %d", k );
+ }
+ printf("\n");
+ }
+ */
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Build an array indexed by model vertex which indicates which vanim vertex corresponds best to it
+//-----------------------------------------------------------------------------
+static void BuildVAnimMap( s_source_t *pVSource, s_sourceanim_t *pVSourceAnim, s_loddata_t *pmLodSource, const int *pModelToVAnim )
+{
+ // indexed by vertex anim vertex index
+ static int *mapp[MAXSTUDIOVERTS*4];
+
+ // count number of times each vanim vert connectes to a model vert
+ int n = 0;
+ pVSourceAnim->vanim_mapcount = (int *)kalloc( pVSource->numvertices, sizeof( int ) );
+ for ( int j = 0; j < pmLodSource->numvertices; j++ )
+ {
+ if ( pModelToVAnim[j] != -1 )
+ {
+ pVSourceAnim->vanim_mapcount[ pModelToVAnim[j] ]++;
+ n++;
+ }
+ }
+
+ pVSourceAnim->vanim_map = (int **)kalloc( pVSource->numvertices, sizeof( int * ));
+ int *vmap = (int *)kalloc( n, sizeof( int ) );
+
+ // build mapping arrays
+ for ( int j = 0; j < pVSource->numvertices; j++ )
+ {
+ if ( pVSourceAnim->vanim_mapcount[j] )
+ {
+ pVSourceAnim->vanim_map[j] = vmap;
+ mapp[j] = vmap;
+ vmap += pVSourceAnim->vanim_mapcount[j];
+ }
+ else if ( pVSourceAnim->vanim_flag[j] )
+ {
+ // printf("%d animates but no matching vertex\n", j );
+ }
+ }
+
+ for ( int j = 0; j < pmLodSource->numvertices; j++ )
+ {
+ if (pModelToVAnim[j] != -1)
+ {
+ *(mapp[ pModelToVAnim[j] ]++) = j;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the number of unique desination vanims, allocates space for it
+//-----------------------------------------------------------------------------
+static void AllocateDestVAnim( s_flexkey_t &flexKey, s_sourceanim_t *pVSourceAnim )
+{
+ int nVAnimCount = pVSourceAnim->numvanims[ flexKey.frame ];
+ s_vertanim_t *pVAnim = pVSourceAnim->vanim[ flexKey.frame ];
+
+ // frame 0 is special. Always assume zero vertex animations
+ if ( !pVSourceAnim->newStyleVertexAnimations && flexKey.frame == 0 )
+ {
+ nVAnimCount = 0;
+ }
+
+ // count total possible remapped animations
+ int nNumDestVAnims = 0;
+ for ( int m = 0; m < nVAnimCount; m++)
+ {
+ nNumDestVAnims += pVSourceAnim->vanim_mapcount[ pVAnim[m].vertex ];
+ }
+
+ // allocate room to all possible resulting deltas
+ s_vertanim_t *pDestAnim = (s_vertanim_t *)kalloc( nNumDestVAnims, sizeof( s_vertanim_t ) );
+ flexKey.vanim = pDestAnim;
+ flexKey.vanimtype = STUDIO_VERT_ANIM_NORMAL; // default
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: map the vertex animations to their equivalent vertex in the base animations
+//-----------------------------------------------------------------------------
+void RemapVertexAnimations(void)
+{
+ int i, j, k;
+ int n, m;
+ s_source_t *pvsource; // vertex animation source
+ const char *pAnimationName;
+ s_sourceanim_t *pSourceAnim;
+ s_loddata_t *pmLodSource; // original model source
+ Vector tmp;
+
+ // index by vertex in targets root LOD
+ static int model_to_vanim_vert_imap[MAXSTUDIOVERTS]; // model vert to vanim vert mapping
+
+ // for all the sources of flexes, find a mapping of vertex animations to base model.
+ // There can be multiple "vertices" in the base model for each animated vertex since vertices
+ // are duplicated along material boundaries.
+ for ( i = 0; i < g_numflexkeys; i++ )
+ {
+ s_source_t *pVSource = g_flexkey[i].source;
+ s_sourceanim_t *pVSourceAnim = FindSourceAnim( pVSource, g_flexkey[i].animationname );
+
+ // We only do old-style vertex animations
+ if ( pVSourceAnim->newStyleVertexAnimations )
+ continue;
+
+ // skip if it's already been done or if has doesn't have any animations
+ if ( pVSourceAnim->vanim_flag )
+ continue;
+
+ // flag all the vertices that animate (builds the vanim_flag field of the source anim)
+ BuildVAnimFlags( pVSource, pVSourceAnim, i );
+
+ s_loddata_t *pLodData = g_model[ g_flexkey[i].imodel ]->m_pLodData;
+
+ // Map vertex indices specified in the model to ones specified in the vanim data
+ BuildModelToVAnimMap( pVSource, pVSourceAnim, pLodData, false, model_to_vanim_vert_imap );
+
+ // Build the vanim_mapcount, vanim_map fields of the source anim
+ BuildVAnimMap( pVSource, pVSourceAnim, pLodData, model_to_vanim_vert_imap );
+ }
+
+#if 0
+ s_vertanim_t *defaultanims = NULL;
+
+ if (g_defaultflexkey)
+ {
+ defaultanims = g_defaultflexkey->source->vanim[g_defaultflexkey->frame];
+ }
+ else
+ {
+ defaultanims = g_flexkey[0].source->vanim[0];
+ }
+#endif
+
+ // reset model to be default animations
+ if ( g_defaultflexkey )
+ {
+ pvsource = g_defaultflexkey->source;
+ pAnimationName = g_defaultflexkey->animationname;
+ pSourceAnim = FindSourceAnim( pvsource, pAnimationName );
+ pmLodSource = g_model[g_defaultflexkey->imodel]->m_pLodData;
+
+ int numsrcanims = pSourceAnim->numvanims[g_defaultflexkey->frame];
+ s_vertanim_t *psrcanim = pSourceAnim->vanim[g_defaultflexkey->frame];
+
+ for (m = 0; m < numsrcanims; m++)
+ {
+ if ( pSourceAnim->vanim_mapcount[psrcanim->vertex]) // bah, only do it for ones that found a match!
+ {
+ for (n = 0; n < pSourceAnim->vanim_mapcount[psrcanim->vertex]; n++)
+ {
+ // copy "default" pos to original model
+ k = pSourceAnim->vanim_map[psrcanim->vertex][n];
+ VectorCopy( psrcanim->pos, pmLodSource->vertex[k].position );
+ VectorCopy( psrcanim->normal, pmLodSource->vertex[k].normal );
+
+ // copy "default" pos to frame 0 of vertex animation source
+ // FIXME: this needs to copy to all sources of vertex animation.
+ // FIXME: the "default" pose needs to be in each vertex animation source since it's likely that the vertices won't be numbered the same in each file.
+ VectorCopy( psrcanim->pos, pSourceAnim->vanim[0][psrcanim->vertex].pos );
+ VectorCopy( psrcanim->normal, pSourceAnim->vanim[0][psrcanim->vertex].normal );
+ }
+ }
+ psrcanim++;
+ }
+ }
+
+ static bool doesMove[MAXSTUDIOVERTS];
+ int numMoved;
+
+ memset( doesMove, 0, MAXSTUDIOVERTS * sizeof( bool ) );
+ numMoved = 0;
+
+ for (i = 0; i < g_numflexkeys; i++)
+ {
+ pvsource = g_flexkey[i].source;
+ pAnimationName = g_flexkey[i].animationname;
+ pSourceAnim = FindSourceAnim( pvsource, pAnimationName );
+ if ( pSourceAnim->newStyleVertexAnimations )
+ continue;
+
+ pmLodSource = g_model[g_flexkey[i].imodel]->m_pLodData;
+
+ // Allocate g_flexkey[i].vanim
+ AllocateDestVAnim( g_flexkey[i], pSourceAnim );
+
+ s_vertanim_t *psrcanim = pSourceAnim->vanim[g_flexkey[i].frame];
+ s_vertanim_t *pdestanim = g_flexkey[i].vanim;
+
+ // frame 0 is special. Always assume zero vertex animations
+ int numsrcanims = ( g_flexkey[i].frame != 0 ) ? pSourceAnim->numvanims[g_flexkey[i].frame] : 0;
+
+ for (m = 0; m < numsrcanims; m++, psrcanim++)
+ {
+ Vector delta, ndelta;
+ float flSide, flScale;
+ ComputeSideAndScale( g_flexkey[i], psrcanim, &flSide, &flScale );
+
+ // bah, only do it for ones that found a match!
+ if ( flScale <= 0.0f || !pSourceAnim->vanim_mapcount[psrcanim->vertex] )
+ continue;
+
+ j = pSourceAnim->vanim_map[psrcanim->vertex][0];
+
+ //VectorSubtract( psrcanim->pos, pSourceAnim->vanim[0][psrcanim->vertex].pos, tmp );
+ //VectorTransform( tmp, pmsource->bonefixup[k].im, delta );
+ VectorSubtract( psrcanim->pos, pSourceAnim->vanim[0][psrcanim->vertex].pos, delta );
+
+ //VectorSubtract( psrcanim->normal, pSourceAnim->vanim[0][psrcanim->vertex].normal, tmp );
+ //VectorTransform( tmp, pmsource->bonefixup[k].im, ndelta );
+ VectorSubtract( psrcanim->normal, pSourceAnim->vanim[0][psrcanim->vertex].normal, ndelta );
+
+ // if the changes are too small, skip 'em
+ // FIXME: the clamp needs to be paired with the other matching positions.
+ // currently this is set to the float16 min value. Sucky.
+ if (DotProduct( delta, delta ) <= (0.001f*0.001f) /* 0.0001 */ && DotProduct( ndelta, ndelta ) <= 0.001)
+ {
+ // printf("%4d %6.4f %6.4f %6.4f\n", pdestanim->vertex, delta.x, delta.y, delta.z );
+ continue;
+ }
+
+ for (n = 0; n < pSourceAnim->vanim_mapcount[psrcanim->vertex]; n++)
+ {
+ pdestanim->vertex = pSourceAnim->vanim_map[psrcanim->vertex][n];
+ VectorScale( delta, flScale, pdestanim->pos );
+ VectorScale( ndelta, flScale, pdestanim->normal );
+ pdestanim->side = flSide;
+
+ // count all the unique verts that actually move
+ if (!doesMove[pdestanim->vertex])
+ {
+ doesMove[pdestanim->vertex] = true;
+ numMoved++;
+ }
+
+ /*
+ printf("%4d %6.2f %6.2f %6.2f : %4d %5.2f %5.2f %5.2f\n",
+ pdestanim->vertex,
+ // pmsource->vertex[pdestanim->vertex][0], pmsource->vertex[pdestanim->vertex][1], pmsource->vertex[pdestanim->vertex][2],
+ modelpos[pdestanim->vertex][0], modelpos[pdestanim->vertex][1], modelpos[pdestanim->vertex][2],
+ psrcanim->vertex,
+ pdestanim->pos[0], pdestanim->pos[1], pdestanim->pos[2] );
+ */
+ g_flexkey[i].numvanims++;
+ pdestanim++;
+ }
+ }
+
+ ComputeVertexAnimationSpeed( g_flexkey[i] );
+ }
+
+ if (numMoved > MAXSTUDIOFLEXVERTS)
+ {
+ MdlError( "Too many flexed verts %d (%d)\n", numMoved, MAXSTUDIOFLEXVERTS );
+ }
+ else if (numMoved > 0 && !g_quiet)
+ {
+ printf("Max flex verts %d\n", numMoved );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: map the vertex animations to their equivalent vertex in the base animations
+//-----------------------------------------------------------------------------
+static int FlexKeysSortFunc( const void *pv1, const void *pv2 )
+{
+ const s_flexkey_t *pKey1 = (const s_flexkey_t*)pv1;
+ const s_flexkey_t *pKey2 = (const s_flexkey_t*)pv2;
+
+ if ( pKey1->source != pKey2->source )
+ return (size_t)pKey1->source - (size_t)pKey2->source;
+ return Q_stricmp( pKey1->animationname, pKey2->animationname );
+}
+
+static int SortFlexKeys( s_flexkey_t **ppSortedFlexKeys )
+{
+ int nSortedFlexKeyCount = 0;
+ for ( int i = 0; i < g_numflexkeys; i++ )
+ {
+ s_source_t *pVSource = g_flexkey[i].source;
+ s_sourceanim_t *pVSourceAnim = FindSourceAnim( pVSource, g_flexkey[i].animationname );
+
+ // We only do new-style vertex animations
+ if ( !pVSourceAnim->newStyleVertexAnimations )
+ continue;
+
+ ppSortedFlexKeys[nSortedFlexKeyCount++] = &g_flexkey[i];
+ }
+
+ if ( nSortedFlexKeyCount > 0 )
+ {
+ qsort( ppSortedFlexKeys, nSortedFlexKeyCount, sizeof(s_flexkey_t*), FlexKeysSortFunc );
+ }
+
+ return nSortedFlexKeyCount;
+}
+
+static void RemapVertexAnimationsNewVersion(void)
+{
+ // index by vertex in targets root LOD
+ static int model_to_vanim_vert_imap[MAXSTUDIOVERTS];
+
+ // Sort flexkeys by source
+ s_flexkey_t **ppSortedFlexKeys = (s_flexkey_t**)_alloca( g_numflexkeys * sizeof(s_flexkey_t*) );
+ int nSortedFlexKeyCount = SortFlexKeys( ppSortedFlexKeys );
+ if ( nSortedFlexKeyCount == 0 )
+ return;
+
+ // for all the sources of flexes, find a mapping of vertex animations to base model.
+ // There can be multiple "vertices" in the base model for each animated vertex since vertices
+ // are duplicated along material boundaries.
+ s_source_t *pVLastSource = NULL;
+ for ( int i = 0; i < nSortedFlexKeyCount; i++ )
+ {
+ s_flexkey_t *pFlexKey = ppSortedFlexKeys[i];
+ s_source_t *pVSource = pFlexKey->source;
+ s_sourceanim_t *pVSourceAnim = FindSourceAnim( pVSource, pFlexKey->animationname );
+ s_loddata_t *pLodSource = g_model[ pFlexKey->imodel ]->m_pLodData;
+
+ if ( pVSource != pVLastSource )
+ {
+ // Map vertex indices specified in the model to ones specified in the vanim data
+ BuildModelToVAnimMap( pVSource, NULL, pLodSource, true, model_to_vanim_vert_imap );
+ pVLastSource = pVSource;
+ }
+
+ // We only do new-style vertex animations
+ Assert( pVSourceAnim->newStyleVertexAnimations );
+
+ // skip if it's already been done or if has doesn't have any animations
+ if ( pVSourceAnim->vanim_flag )
+ continue;
+
+ pVSourceAnim->vanim_flag = (int *)kalloc( pVSource->numvertices, sizeof( int ));
+
+ // flag all the vertices that animate (builds the vanim_flag field of the source anim)
+ int j;
+ for ( j = i+1; j < nSortedFlexKeyCount; ++j )
+ {
+ if ( ( ppSortedFlexKeys[j]->source != pVSource ) ||
+ Q_stricmp( ppSortedFlexKeys[j]->animationname, pFlexKey->animationname ) )
+ break;
+ }
+
+ for ( ; i < j; ++i )
+ {
+ int k = ppSortedFlexKeys[i]->frame;
+ for ( int m = 0; m < pVSourceAnim->numvanims[k]; m++ )
+ {
+ pVSourceAnim->vanim_flag[ pVSourceAnim->vanim[k][m].vertex ] = 1;
+ }
+ }
+ --i;
+
+ // Build the vanim_mapcount, vanim_map fields of the source anim
+ BuildVAnimMap( pVSource, pVSourceAnim, pLodSource, model_to_vanim_vert_imap );
+ }
+
+ int nNumMoved = 0;
+ static bool pDoesMove[MAXSTUDIOVERTS];
+ memset( pDoesMove, 0, MAXSTUDIOVERTS * sizeof( bool ) );
+
+ for ( int i = 0; i < g_numflexkeys; i++ )
+ {
+ s_source_t *pVSource = g_flexkey[i].source;
+ s_sourceanim_t *pVSourceAnim = FindSourceAnim( pVSource, g_flexkey[i].animationname );
+ if ( !pVSourceAnim->newStyleVertexAnimations )
+ continue;
+
+ // Allocate g_flexkey[i].vanim
+ AllocateDestVAnim( g_flexkey[i], pVSourceAnim );
+
+ int nNumSrcVAnims = pVSourceAnim->numvanims[ g_flexkey[i].frame ];
+ s_vertanim_t *pSrcVAnim = pVSourceAnim->vanim[ g_flexkey[i].frame ];
+ s_vertanim_t *pDestVAnim = g_flexkey[i].vanim;
+
+ for ( int m = 0; m < nNumSrcVAnims; m++, pSrcVAnim++ )
+ {
+ // bah, only do it for ones that found a match!
+ if ( !pVSourceAnim->vanim_mapcount[pSrcVAnim->vertex] )
+ continue;
+
+ // if the changes are too small, skip 'em
+ // FIXME: the clamp needs to be paired with the other matching positions.
+ // currently this is set to the float16 min value. Sucky.
+ if ( DotProduct( pSrcVAnim->pos, pSrcVAnim->pos ) <= (0.001f*0.001f) /* 0.0001 */ && DotProduct( pSrcVAnim->normal, pSrcVAnim->normal ) <= 0.001f && pSrcVAnim->wrinkle <= 0.001f )
+ {
+ // printf("%4d %6.4f %6.4f %6.4f\n", pDestAnim->vertex, delta.x, delta.y, delta.z );
+ continue;
+ }
+
+ for ( int n = 0; n < pVSourceAnim->vanim_mapcount[pSrcVAnim->vertex]; n++ )
+ {
+ memcpy( pDestVAnim, pSrcVAnim, sizeof(s_vertanim_t) );
+ pDestVAnim->vertex = pVSourceAnim->vanim_map[pSrcVAnim->vertex][n];
+
+ if ( pDestVAnim->wrinkle != 0.0f )
+ {
+ g_flexkey[i].vanimtype = STUDIO_VERT_ANIM_WRINKLE;
+ }
+
+ // count all the unique verts that actually move
+ if ( !pDoesMove[pDestVAnim->vertex] )
+ {
+ pDoesMove[pDestVAnim->vertex] = true;
+ nNumMoved++;
+ }
+
+ g_flexkey[i].numvanims++;
+ pDestVAnim++;
+ }
+ }
+ }
+
+ if ( nNumMoved > MAXSTUDIOFLEXVERTS )
+ {
+ MdlError( "Too many flexed verts %d (%d)\n", nNumMoved, MAXSTUDIOFLEXVERTS );
+ }
+ else if ( nNumMoved > 0 && !g_quiet )
+ {
+ printf("Max flex verts %d\n", nNumMoved );
+ }
+}
+
+
+// Finds the bone index for a particular source
+extern int FindLocalBoneNamed( const s_source_t *pSource, const char *pName );
+
+//-----------------------------------------------------------------------------
+// Purpose: finds the bone index in the global bone table
+//-----------------------------------------------------------------------------
+
+int findGlobalBone( const char *name )
+{
+ name = RenameBone( name );
+ for ( int k = 0; k < g_numbones; k++ )
+ {
+ if ( !Q_stricmp( g_bonetable[k].name, name ) )
+ return k;
+ }
+
+ return -1;
+}
+
+
+bool IsGlobalBoneXSI( const char *name, const char *bonename )
+{
+ name = RenameBone( name );
+
+ int len = strlen( name );
+
+ int len2 = strlen( bonename );
+ if ( len2 == len && strchr( bonename, '.' ) == NULL && stricmp( bonename, name ) == 0 )
+ return true;
+
+ if (len2 > len)
+ {
+
+ if (bonename[len2-len-1] == '.')
+ {
+ if (stricmp( &bonename[len2-len], name ) == 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+
+int findGlobalBoneXSI( const char *name )
+{
+ int k;
+
+ name = RenameBone( name );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (IsGlobalBoneXSI( name, g_bonetable[k].name ))
+ {
+ return k;
+ }
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Acculumate quaternions and try to find the swept area of rotation
+// so that a "midpoint" of the rotation area can be found
+//-----------------------------------------------------------------------------
+
+void findAnimQuaternionAlignment( int k, int i, Quaternion &qBase, Quaternion &qMin, Quaternion &qMax )
+{
+ int j;
+
+ AngleQuaternion( g_panimation[i]->sanim[0][k].rot, qBase );
+ qMin = qBase;
+ float dMin = 1.0;
+ qMax = qBase;
+ float dMax = 1.0;
+
+ for (j = 1; j < g_panimation[i]->numframes; j++)
+ {
+ Quaternion q;
+
+ AngleQuaternion( g_panimation[i]->sanim[j][k].rot, q );
+ QuaternionAlign( qBase, q, q );
+
+ float d0 = QuaternionDotProduct( q, qBase );
+ float d1 = QuaternionDotProduct( q, qMin );
+ float d2 = QuaternionDotProduct( q, qMax );
+
+ /*
+ if (i != 0)
+ printf("%f %f %f : %f\n", d0, d1, d2, QuaternionDotProduct( qMin, qMax ) );
+ */
+ if (d1 >= d0)
+ {
+ if (d0 < dMin)
+ {
+ qMin = q;
+ dMin = d0;
+ if (dMax == 1.0)
+ {
+ QuaternionMA( qBase, -0.01, qMin, qMax );
+ QuaternionAlign( qBase, qMax, qMax );
+ }
+ }
+ }
+ else if (d2 >= d0)
+ {
+ if (d0 < dMax)
+ {
+ qMax = q;
+ dMax = d0;
+ }
+ }
+
+ /*
+ if (i != 0)
+ printf("%f ", QuaternionDotProduct( qMin, qMax ) );
+ */
+
+ QuaternionSlerpNoAlign( qMin, qMax, 0.5, qBase );
+ Assert( qBase.IsValid() );
+
+ /*
+ if (i != 0)
+ {
+ QAngle ang;
+ QuaternionAngles( qMin, ang );
+ printf("(%.1f %.1f %.1f) ", ang.x, ang.y, ang.z );
+ QuaternionAngles( qMax, ang );
+ printf("(%.1f %.1f %.1f) ", ang.x, ang.y, ang.z );
+ QuaternionAngles( qBase, ang );
+ printf("(%.1f %.1f %.1f)\n", ang.x, ang.y, ang.z );
+ }
+ */
+
+ dMin = QuaternionDotProduct( qBase, qMin );
+ dMax = QuaternionDotProduct( qBase, qMax );
+ }
+
+ // printf("%s (%s): %.3f :%.3f\n", g_bonetable[k].name, g_panimation[i]->name, QuaternionDotProduct( qMin, qMax ), QuaternionDotProduct( qMin, qBase ) );
+ /*
+ if (i != 0)
+ exit(0);
+ */
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: For specific bones, try to find the total valid area of rotation so
+// that their mid point of rotation can be used at run time to "pre-align"
+// the quaternions so that rotations > 180 degrees don't get blended the
+// "short way round".
+//-----------------------------------------------------------------------------
+
+void limitBoneRotations( void )
+{
+ int i, j, k;
+
+ for (i = 0; i < g_numlimitrotation; i++)
+ {
+ Quaternion qBase;
+
+ k = findGlobalBone( g_limitrotation[i].name );
+ if (k == -1)
+ {
+ MdlError("unknown bone \"%s\" in $limitrotation\n", g_limitrotation[i].name );
+ }
+
+ AngleQuaternion( g_bonetable[k].rot, qBase );
+
+ if (g_limitrotation[i].numseq == 0)
+ {
+ for (j = 0; j < g_numani; j++)
+ {
+ if (!(g_panimation[j]->flags & STUDIO_DELTA) && g_panimation[j]->numframes > 3)
+ {
+ Quaternion qBase2, qMin2, qMax2;
+ findAnimQuaternionAlignment( k, j, qBase2, qMin2, qMax2 );
+
+ QuaternionAdd( qBase, qBase2, qBase );
+ }
+ }
+ QuaternionNormalize( qBase );
+ }
+ else
+ {
+ for (j = 0; j < g_limitrotation[i].numseq; j++)
+ {
+
+ }
+ }
+
+ /*
+ QAngle ang;
+ QuaternionAngles( qBase, ang );
+ printf("%s : (%.1f %.1f %.1f) \n", g_bonetable[k].name, ang.x, ang.y, ang.z );
+ */
+
+ g_bonetable[k].qAlignment = qBase;
+ g_bonetable[k].flags |= BONE_FIXED_ALIGNMENT;
+
+ // QuaternionAngles( qBase, g_panimation[0]->sanim[0][k].rot );
+ }
+}
+
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: For specific bones, try to find the total valid area of rotation so
+// that their mid point of rotation can be used at run time to "pre-align"
+// the quaternions so that rotations > 180 degrees don't get blended the
+// "short way round".
+//-----------------------------------------------------------------------------
+
+void limitIKChainLength( void )
+{
+ int i, j, k;
+ matrix3x4_t boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix
+
+ for (k = 0; k < g_numikchains; k++)
+ {
+ bool needsFixup = false;
+ bool hasKnees = false;
+
+ Vector kneeDir = g_ikchain[k].link[0].kneeDir;
+ if (kneeDir.Length() > 0.0)
+ {
+ hasKnees = true;
+ }
+ else
+ {
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+
+ if (panim->flags & STUDIO_DELTA)
+ continue;
+
+ if (panim->flags & STUDIO_HIDDEN)
+ continue;
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ CalcBoneTransforms( panim, j, boneToWorld );
+
+ Vector worldThigh;
+ Vector worldKnee;
+ Vector worldFoot;
+
+ MatrixPosition( boneToWorld[ g_ikchain[k].link[0].bone ], worldThigh );
+ MatrixPosition( boneToWorld[ g_ikchain[k].link[1].bone ], worldKnee );
+ MatrixPosition( boneToWorld[ g_ikchain[k].link[2].bone ], worldFoot );
+
+ float l1 = (worldKnee-worldThigh).Length();
+ float l2 = (worldFoot-worldKnee).Length();
+ float l3 = (worldFoot-worldThigh).Length();
+
+ Vector ikHalf = (worldFoot+worldThigh) * 0.5;
+
+ // FIXME: what to do when the knee completely straight?
+ Vector ikKneeDir = worldKnee - ikHalf;
+ VectorNormalize( ikKneeDir );
+ // ikTargetKnee = ikKnee + ikKneeDir * l1;
+
+ // leg too straight to figure out knee?
+ if (l3 > (l1 + l2) * 0.999)
+ {
+ needsFixup = true;
+ }
+ else
+ {
+ // rotate knee into local space
+ Vector tmp;
+ VectorIRotate( ikKneeDir, boneToWorld[ g_ikchain[k].link[0].bone ], tmp );
+ float bend = (((DotProduct( worldThigh - worldKnee, worldFoot - worldKnee ) ) / (l1 * l3)) + 1) / 2.0;
+ kneeDir += tmp * bend;
+ hasKnees = true;
+ }
+ }
+ }
+ }
+
+ if (!needsFixup)
+ continue;
+
+ if (!hasKnees)
+ {
+ MdlWarning( "ik rules for %s but no clear knee direction\n", g_ikchain[k].name );
+ continue;
+ }
+
+ VectorNormalize( kneeDir );
+ g_ikchain[k].link[0].kneeDir = kneeDir;
+
+ if (g_verbose)
+ {
+ printf("knee %s %f %f %f\n", g_ikchain[k].name, kneeDir.x, kneeDir.y, kneeDir.z );
+ }
+
+#if 0
+ // don't bother for now, storing the knee direction should fix the runtime problems.
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+
+ if (panim->flags & STUDIO_DELTA)
+ continue;
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ CalcBoneTransforms( panim, j, boneToWorld );
+
+ Vector worldFoot;
+ MatrixPosition( boneToWorld[ g_ikchain[k].link[2].bone ], worldFoot );
+
+ Vector targetKneeDir;
+ VectorRotate( kneeDir, boneToWorld[ g_ikchain[k].link[0].bone ], targetKneeDir );
+
+ // run it through the normal IK solver, this should move the foot positions to someplace legal
+ Studio_SolveIK( g_ikchain[k].link[0].bone, g_ikchain[k].link[1].bone, g_ikchain[k].link[2].bone, worldFoot, targetKneeDir, boneToWorld );
+
+ solveBone( panim, j, g_ikchain[k].link[0].bone, boneToWorld );
+ solveBone( panim, j, g_ikchain[k].link[1].bone, boneToWorld );
+ solveBone( panim, j, g_ikchain[k].link[2].bone, boneToWorld );
+ }
+ }
+#endif
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: build "next node" table that links every transition "node" to
+// every other transition "node", if possible
+//-----------------------------------------------------------------------------
+void MakeTransitions( )
+{
+ int i, j, k;
+ bool iHit = g_bMultistageGraph;
+
+ // add in direct node transitions
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ if (g_sequence[i].entrynode != g_sequence[i].exitnode)
+ {
+ g_xnode[g_sequence[i].entrynode-1][g_sequence[i].exitnode-1] = g_sequence[i].exitnode;
+ if (g_sequence[i].nodeflags)
+ {
+ g_xnode[g_sequence[i].exitnode-1][g_sequence[i].entrynode-1] = g_sequence[i].entrynode;
+ }
+ }
+ }
+
+ // calculate multi-stage transitions
+ while (iHit)
+ {
+ iHit = false;
+ for (i = 1; i <= g_numxnodes; i++)
+ {
+ for (j = 1; j <= g_numxnodes; j++)
+ {
+ // if I can't go there directly
+ if (i != j && g_xnode[i-1][j-1] == 0)
+ {
+ for (k = 1; k <= g_numxnodes; k++)
+ {
+ // but I found someone who knows how that I can get to
+ if (g_xnode[k-1][j-1] > 0 && g_xnode[i-1][k-1] > 0)
+ {
+ // then go to them
+ g_xnode[i-1][j-1] = -g_xnode[i-1][k-1];
+ iHit = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ // reset previous pass so the links can be used in the next pass
+ for (i = 1; i <= g_numxnodes; i++)
+ {
+ for (j = 1; j <= g_numxnodes; j++)
+ {
+ g_xnode[i-1][j-1] = abs( g_xnode[i-1][j-1] );
+ }
+ }
+ }
+
+ // add in allowed "skips"
+ for (i = 0; i < g_numxnodeskips; i++)
+ {
+ g_xnode[g_xnodeskip[i][0]-1][g_xnodeskip[i][1]-1] = 0;
+ }
+
+ if (g_bDumpGraph)
+ {
+ for (j = 1; j <= g_numxnodes; j++)
+ {
+ printf("%2d : %s\n", j, g_xnodename[j] );
+ }
+ printf(" " );
+ for (j = 1; j <= g_numxnodes; j++)
+ {
+ printf("%2d ", j );
+ }
+ printf("\n" );
+
+ for (i = 1; i <= g_numxnodes; i++)
+ {
+ printf("%2d: ", i );
+ for (j = 1; j <= g_numxnodes; j++)
+ {
+ printf("%2d ", g_xnode[i-1][j-1] );
+ }
+ printf("\n" );
+ }
+ }
+}
+
+
+int VectorCompareEpsilon(const Vector& v1, const Vector& v2, float epsilon)
+{
+ int i;
+
+ for (i=0 ; i<3 ; i++)
+ if (fabs(v1[i] - v2[i]) > epsilon)
+ return 0;
+
+ return 1;
+}
+
+int RadianEulerCompareEpsilon(const RadianEuler& v1, const RadianEuler& v2, float epsilon)
+{
+ int i;
+
+ for (i=0 ; i<3 ; i++)
+ {
+ // clamp to 2pi
+ float a1 = fmod(v1[i],(float) (2*M_PI));
+ float a2 = fmod(v2[i],(float) (2*M_PI));
+ float delta = fabs(a1-a2);
+
+ // use the smaller angle (359 == 1 degree off)
+ if ( delta > M_PI )
+ {
+ delta = 2*M_PI - delta;
+ }
+
+ if (delta > epsilon)
+ return 0;
+ }
+
+ return 1;
+}
+
+bool AnimationDifferent( const Vector& startPos, const RadianEuler& startRot, const Vector& pos, const RadianEuler& rot )
+{
+ if ( !VectorCompareEpsilon( startPos, pos, 0.01 ) )
+ return true;
+ if ( !RadianEulerCompareEpsilon( startRot, rot, 0.01 ) )
+ return true;
+
+ return false;
+}
+
+bool BoneHasAnimation( const char *pName )
+{
+ bool first = true;
+ Vector pos;
+ RadianEuler rot;
+
+ if ( !g_numani )
+ return false;
+
+ int globalIndex = findGlobalBone( pName );
+
+ // don't check root bones for animation
+ if (globalIndex >= 0 && g_bonetable[globalIndex].parent == -1)
+ return true;
+
+ // find used bones per g_model
+ for (int i = 0; i < g_numani; i++)
+ {
+ s_source_t *psource = g_panimation[i]->source;
+ const char *pAnimationName = g_panimation[i]->animationname;
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( psource, pAnimationName );
+
+ int boneIndex = FindLocalBoneNamed(psource, pName);
+
+ // not in this source?
+ if (boneIndex < 0)
+ continue;
+
+ // this is not right, but enough of the bones are moved unintentionally between
+ // animations that I put this in to catch them.
+ first = true;
+ int n = g_panimation[i]->startframe - pSourceAnim->startframe;
+ // printf("%s %d:%d\n", g_panimation[i]->filename, g_panimation[i]->startframe, psource->startframe );
+ for (int j = 0; j < g_panimation[i]->numframes; j++)
+ {
+ if ( first )
+ {
+ VectorCopy( pSourceAnim->rawanim[j+n][boneIndex].pos, pos );
+ VectorCopy( pSourceAnim->rawanim[j+n][boneIndex].rot, rot );
+ first = false;
+ }
+ else
+ {
+ if ( AnimationDifferent( pos, rot, pSourceAnim->rawanim[j+n][boneIndex].pos, pSourceAnim->rawanim[j+n][boneIndex].rot ) )
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool BoneHasAttachments( char const *pname )
+{
+ for (int k = 0; k < g_numattachments; k++)
+ {
+ if ( !stricmp( g_attachment[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool BoneIsProcedural( char const *pname )
+{
+ int k;
+
+ for (k = 0; k < g_numaxisinterpbones; k++)
+ {
+ if (! stricmp( g_axisinterpbones[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+
+ for (k = 0; k < g_numquatinterpbones; k++)
+ {
+ if (IsGlobalBoneXSI( g_quatinterpbones[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+
+ for (k = 0; k < g_numaimatbones; k++)
+ {
+ if (IsGlobalBoneXSI( g_aimatbones[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+
+ for (k = 0; k < g_numjigglebones; k++)
+ {
+ if (! stricmp( g_jigglebones[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool BoneIsIK( char const *pname )
+{
+ int k;
+
+ // tag bones used by ikchains
+ for (k = 0; k < g_numikchains; k++)
+ {
+ if ( !stricmp( g_ikchain[k].bonename, pname ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool BoneShouldCollapse( char const *pname )
+{
+ int k;
+
+ for (k = 0; k < g_collapse.Count(); k++)
+ {
+ if (stricmp( g_collapse[k], pname ) == 0)
+ {
+ return true;
+ }
+ }
+
+ return (!BoneHasAnimation( pname ) && !BoneIsProcedural( pname ) && !BoneIsIK( pname ) /* && !BoneHasAttachments( pname ) */);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Collapse vertex assignments up to parent on bones that are not needed
+// This can optimize a model substantially if the animator is using
+// lots of helper bones with no animation.
+//-----------------------------------------------------------------------------
+void CollapseBones( void )
+{
+ int j, k;
+ int count = 0;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if ( g_bonetable[k].bDontCollapse )
+ continue;
+
+ if ( (g_bonetable[k].flags != 0 || g_bonetable[k].bPreDefined) && !BoneShouldCollapse( g_bonetable[k].name ) )
+ {
+ // printf("skipping %s : %d\n", g_bonetable[k].name, g_bonetable[k].flags );
+ continue;
+ }
+
+ count++;
+
+ if( !g_quiet && g_verbose )
+ {
+ printf("collapsing %s\n", g_bonetable[k].name );
+ }
+
+ g_numbones--;
+ int m = g_bonetable[k].parent;
+
+ for (j = k; j < g_numbones; j++)
+ {
+ g_bonetable[j] = g_bonetable[j+1];
+ if (g_bonetable[j].parent == k)
+ {
+ g_bonetable[j].parent = m;
+ }
+ else if (g_bonetable[j].parent >= k)
+ {
+ g_bonetable[j].parent = g_bonetable[j].parent - 1;
+ }
+ }
+ k--;
+ }
+
+ if( !g_quiet && count)
+ {
+ printf("Collapsed %d bones\n", count );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: replace all animation, rotation and translation, etc. with a single bone
+//-----------------------------------------------------------------------------
+void MakeStaticProp()
+{
+ int i, j, k;
+ matrix3x4_t rotated;
+
+ AngleMatrix( g_defaultrotation, rotated );
+
+ // FIXME: missing attachment point recalcs!
+
+ // replace bone 0 with "static_prop" bone and attach everything to it.
+ for (i = 0; i < g_numsources; i++)
+ {
+ s_source_t *psource = g_source[i];
+
+ strcpy( psource->localBone[0].name, "static_prop" );
+ psource->localBone[0].parent = -1;
+
+ for (k = 1; k < psource->numbones; k++)
+ {
+ psource->localBone[k].parent = -1;
+ }
+
+ rotated[0][3] = g_defaultadjust[0];
+ rotated[1][3] = g_defaultadjust[1];
+ rotated[2][3] = g_defaultadjust[2];
+
+ Vector mins, maxs;
+ ClearBounds( mins, maxs );
+
+ for (j = 0; j < psource->numvertices; j++)
+ {
+ for (k = 0; k < psource->vertex[j].boneweight.numbones; k++)
+ {
+ // attach everything to root
+ psource->vertex[j].boneweight.bone[k] = 0;
+ }
+
+ // **shift everything into identity space**
+ // position
+ Vector tmp;
+ VectorTransform( psource->vertex[j].position, rotated, tmp );
+ VectorCopy( tmp, psource->vertex[j].position );
+
+ // normal
+ VectorRotate( psource->vertex[j].normal, rotated, tmp );
+ VectorCopy( tmp, psource->vertex[j].normal );
+
+ // tangentS
+ VectorRotate( psource->vertex[j].tangentS.AsVector3D(), rotated, tmp );
+ VectorCopy( tmp, psource->vertex[j].tangentS.AsVector3D() );
+
+ // incrementally compute identity space bbox
+ AddPointToBounds( psource->vertex[j].position, mins, maxs );
+ }
+
+ if ( g_centerstaticprop )
+ {
+ const char *pAttachmentName = "placementOrigin";
+ bool bFound = false;
+ for ( k = 0; k < g_numattachments; k++ )
+ {
+ if ( !Q_stricmp( g_attachment[k].name, pAttachmentName ) )
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ if ( !bFound )
+ {
+ g_PropCenterOffset = -0.5f * (mins + maxs);
+ }
+
+ for ( j = 0; j < psource->numvertices; j++ )
+ {
+ psource->vertex[j].position += g_PropCenterOffset;
+ }
+
+ if ( !bFound )
+ {
+ // now add an attachment point to store this offset
+ Q_strncpy( g_attachment[g_numattachments].name, pAttachmentName, sizeof(g_attachment[g_numattachments].name) );
+ Q_strncpy( g_attachment[g_numattachments].bonename, "static_prop", sizeof(g_attachment[g_numattachments].name) );
+ g_attachment[g_numattachments].bone = 0;
+ g_attachment[g_numattachments].type = 0;
+ AngleMatrix( vec3_angle, g_PropCenterOffset, g_attachment[g_numattachments].local );
+ g_numattachments++;
+ }
+ }
+
+ // force the animation to be identity
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( psource, "BindPose" );
+ pSourceAnim->rawanim[0][0].pos = Vector( 0, 0, 0 );
+ pSourceAnim->rawanim[0][0].rot = RadianEuler( 0, 0, 0 );
+
+ // make an identity boneToPose transform
+ AngleMatrix( QAngle( 0, 0, 0 ), psource->boneToPose[0] );
+
+ // make it all a single frame animation
+ pSourceAnim->numframes = 1;
+ pSourceAnim->startframe = 0;
+ pSourceAnim->endframe = 1;
+ }
+
+ // throw away all animations
+ g_numani = 1;
+ g_panimation[0]->numframes = 1;
+ g_panimation[0]->startframe = 0;
+ g_panimation[0]->endframe = 1;
+ Q_strncpy( g_panimation[0]->animationname, "BindPose", sizeof(g_panimation[0]->animationname) );
+ g_panimation[0]->rotation = RadianEuler( 0, 0, 0 );
+ g_panimation[0]->adjust = Vector( 0, 0, 0 );
+
+ // throw away all vertex animations
+ g_numflexkeys = 0;
+ g_defaultflexkey = NULL;
+
+ // Recalc attachment points:
+ for( i = 0; i < g_numattachments; i++ )
+ {
+ if( g_centerstaticprop && ( i == g_numattachments - 1 ) )
+ continue;
+
+ ConcatTransforms( rotated, g_attachment[i].local, g_attachment[i].local );
+
+ Q_strncpy( g_attachment[i].bonename, "static_prop", sizeof(g_attachment[i].name) );
+ g_attachment[i].bone = 0;
+ g_attachment[i].type = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Marks the boneref all the way up the bone hierarchy
+//-----------------------------------------------------------------------------
+static void UpdateBonerefRecursive( s_source_t *psource, int nBoneIndex, int nFlags )
+{
+ if ( nFlags == 0 )
+ return;
+
+ psource->boneref[nBoneIndex] |= nFlags;
+
+ // Chain the flag up the parent
+ int n = psource->localBone[nBoneIndex].parent;
+ while (n != -1)
+ {
+ psource->boneref[n] |= psource->boneref[nBoneIndex];
+ n = psource->localBone[n].parent;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the axis of the bone after remapping. Axis 0:X, 1:Y, 2:Z
+// If the bone has a parent, axis is returned as is, if bone does not
+// have a parent then the $upaxis determines how the axes are mapped.
+// Only $upaxis Y is supported (see comment in Cmd_UpAxis).
+//-----------------------------------------------------------------------------
+int GetRemappedBoneAxis( int nBoneIndex, int nAxis )
+{
+ if ( nBoneIndex < 0 || nBoneIndex >= g_numbones )
+ return nAxis;
+
+ if ( g_bonetable[nBoneIndex].parent >= 0 )
+ return nAxis;
+
+ // Y Up
+ if ( g_defaultrotation.x == static_cast< float >( M_PI / 2.0f ) && g_defaultrotation.y == 0.0f && g_defaultrotation.z == static_cast< float >( M_PI / 2.0f ) )
+ {
+ static const int nAxisMap[3] = { 1, 2, 0 };
+ return nAxisMap[ nAxis ];
+ }
+
+ // Default Z Up
+ return nAxis;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Map the flex driver bones to the global bone table
+// Also cleans up any that do not match to a global bone
+//-----------------------------------------------------------------------------
+void MapFlexDriveBonesToGlobalBoneTable()
+{
+ CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList );
+ if ( !pDmeBoneFlexDriverList )
+ return;
+
+ // Loop backwards so we can remove elements as we go
+ for ( int i = pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Count() - 1; i >= 0; --i )
+ {
+ CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->m_eBoneFlexDriverList[i];
+ if ( !pDmeBoneFlexDriver )
+ {
+ pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Remove( i );
+ continue;
+ }
+
+ for ( int j = 0; j < g_numbones; ++j )
+ {
+ if ( !Q_stricmp( g_bonetable[j].name, pDmeBoneFlexDriver->m_sBoneName.Get() ) )
+ {
+ if ( g_bonetable[j].flags & BONE_ALWAYS_PROCEDURAL )
+ {
+ MdlWarning( "DmeBoneFlexDriver Bone: %s is marked procedural, Ignoring flex drivers\n", pDmeBoneFlexDriver->m_sBoneName.Get() );
+ pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Remove( i );
+ pDmeBoneFlexDriver = NULL;
+ }
+
+ pDmeBoneFlexDriver->SetValue( "__boneIndex", j );
+ // Map the axis for Y up stuff
+ for ( int k = 0; k < pDmeBoneFlexDriver->m_eControlList.Count(); ++k )
+ {
+ pDmeBoneFlexDriver->m_eControlList[k]->m_nBoneComponent = GetRemappedBoneAxis( j, pDmeBoneFlexDriver->m_eControlList[k]->m_nBoneComponent );
+ }
+ break;
+ }
+ }
+
+ // Was removed because it was referencing a procedural bone
+ if ( !pDmeBoneFlexDriver )
+ continue;
+
+ CDmAttribute *pBoneIndexAttr = pDmeBoneFlexDriver->GetAttribute( "__boneIndex" );
+ if ( pBoneIndexAttr )
+ {
+ pBoneIndexAttr->AddFlag( FATTRIB_DONTSAVE );
+ }
+ else
+ {
+ MdlWarning( "DmeBoneFlexDriver Bone: %s - No Bone Found With That Name, Ignoring\n", pDmeBoneFlexDriver->m_sBoneName.Get() );
+ pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Remove( i );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tag bones in the specified source that are used as a bone flex driver
+// Also cleans up any empty bone flex driver elements
+// Also tags the DmeBoneFlexDriverControl with
+//-----------------------------------------------------------------------------
+void TagFlexDriverBones( s_source_t *pSource )
+{
+ CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList );
+ if ( !pDmeBoneFlexDriverList )
+ return;
+
+ // Loop backwards so we can remove elements as we go
+ for ( int i = pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Count() - 1; i >= 0; --i )
+ {
+ CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->m_eBoneFlexDriverList[i];
+ if ( !pDmeBoneFlexDriver )
+ {
+ pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Remove( i );
+ continue;
+ }
+
+ for ( int j = pDmeBoneFlexDriver->m_eControlList.Count() - 1; j >= 0; --j )
+ {
+ CDmeBoneFlexDriverControl *pDmeBoneFlexDriverControl = pDmeBoneFlexDriver->m_eControlList[j];
+ if ( !pDmeBoneFlexDriverControl )
+ {
+ pDmeBoneFlexDriver->m_eControlList.Remove( j );
+ continue;
+ }
+
+ if ( pDmeBoneFlexDriverControl->m_nBoneComponent < STUDIO_BONE_FLEX_TX || pDmeBoneFlexDriverControl->m_nBoneComponent > STUDIO_BONE_FLEX_TZ )
+ {
+ MdlWarning( "DmeBoneFlexDriver Bone: %s - Flex Controller: %s, Bone Component Out Of Range: %d [0-2], Ignoring\n", pDmeBoneFlexDriver->m_sBoneName.Get(), pDmeBoneFlexDriverControl->m_sFlexControllerName.Get(), pDmeBoneFlexDriverControl->m_nBoneComponent.Get() );
+ pDmeBoneFlexDriver->m_eControlList.Remove( j );
+ continue;
+ }
+
+ for ( int k = 0; k < g_numflexcontrollers; ++k )
+ {
+ if ( !Q_stricmp( g_flexcontroller[k].name, pDmeBoneFlexDriverControl->m_sFlexControllerName.Get() ) )
+ {
+ pDmeBoneFlexDriverControl->SetValue( "__flexControlIndex", k );
+ break;
+ }
+ }
+
+ if ( !pDmeBoneFlexDriverControl->HasAttribute( "__flexControlIndex" ) )
+ {
+ MdlWarning( "DmeBoneFlexDriver Bone: %s - No Flex Controller Named: %s, Ignoring\n", pDmeBoneFlexDriver->m_sBoneName.Get(), pDmeBoneFlexDriverControl->m_sFlexControllerName.Get() );
+ pDmeBoneFlexDriver->m_eControlList.Remove( j );
+ }
+ }
+
+ if ( pDmeBoneFlexDriver->m_eControlList.Count() <= 0 )
+ {
+ MdlWarning( "DmeBoneFlexDriver Bone: %s - No Flex Controllers Defined, Ignoring\n", pDmeBoneFlexDriver->m_sBoneName.Get() );
+ pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Remove( i );
+ continue;
+ }
+
+ for ( int j = 0; j < pSource->numbones; ++j )
+ {
+ if ( !Q_stricmp( pSource->localBone[j].name, pDmeBoneFlexDriver->m_sBoneName.Get() ) )
+ {
+ // Mark used by all LODs
+ pSource->boneflags[j] |= BONE_USED_BY_VERTEX_MASK;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: set "boneref" for all the source bones used by vertices, attachments, eyeballs, etc.
+//-----------------------------------------------------------------------------
+void TagUsedBones( )
+{
+ int i, j, k;
+ int n;
+
+ // find used bones per g_model
+ for (i = 0; i < g_numsources; i++)
+ {
+ s_source_t *psource = g_source[i];
+
+ for (k = 0; k < MAXSTUDIOSRCBONES; k++)
+ {
+ psource->boneflags[k] = 0;
+ psource->boneref[k] = 0;
+ }
+
+ if (!psource->isActiveModel)
+ continue;
+
+ // printf("active: %s\n", psource->filename );
+ for (j = 0; j < psource->numvertices; j++)
+ {
+ for (k = 0; k < psource->vertex[j].boneweight.numbones; k++)
+ {
+ psource->boneflags[psource->vertex[j].boneweight.bone[k]] |= BONE_USED_BY_VERTEX_LOD0;
+ }
+ }
+ }
+
+ // find used bones per g_model
+ for (i = 0; i < g_numsources; i++)
+ {
+ s_source_t *psource = g_source[i];
+
+ // FIXME: this is in the wrong place. The attachment may be rigid and it never defined in a reference file
+ for (k = 0; k < g_numattachments; k++)
+ {
+ for (j = 0; j < psource->numbones; j++)
+ {
+ if ( !stricmp( g_attachment[k].bonename, psource->localBone[j].name ) )
+ {
+ // this bone is a keeper with or without associated vertices
+ // because an attachment point depends on it.
+ if (g_attachment[k].type & IS_RIGID)
+ {
+ for (n = j; n != -1; n = psource->localBone[n].parent)
+ {
+ if (psource->boneflags[n] & BONE_USED_BY_VERTEX_LOD0)
+ {
+ psource->boneflags[n] |= BONE_USED_BY_ATTACHMENT;
+ break;
+ }
+ }
+ }
+ else
+ {
+ psource->boneflags[j] |= BONE_USED_BY_ATTACHMENT;
+ }
+ }
+ }
+ }
+
+ for (k = 0; k < g_numikchains; k++)
+ {
+ for (j = 0; j < psource->numbones; j++)
+ {
+ if ( !stricmp( g_ikchain[k].bonename, psource->localBone[j].name ) )
+ {
+ // this bone is a keeper with or without associated vertices
+ // because a ikchain depends on it.
+ psource->boneflags[j] |= BONE_USED_BY_ATTACHMENT;
+ }
+ }
+ }
+
+ for (k = 0; k < g_nummouths; k++)
+ {
+ for (j = 0; j < psource->numbones; j++)
+ {
+ if ( !stricmp( g_mouth[k].bonename, psource->localBone[j].name ) )
+ {
+ // this bone is a keeper with or without associated vertices
+ // because a mouth shader depends on it.
+ psource->boneflags[j] |= BONE_USED_BY_ATTACHMENT;
+ }
+ }
+ }
+
+ // Tag all bones marked as being used by bonemerge
+ int nBoneMergeCount = g_BoneMerge.Count();
+ for ( k = 0; k < nBoneMergeCount; ++k )
+ {
+ for ( j = 0; j < psource->numbones; j++ )
+ {
+ if ( stricmp( g_BoneMerge[k].bonename, psource->localBone[j].name ) )
+ continue;
+
+ psource->boneflags[j] |= BONE_USED_BY_BONE_MERGE;
+ }
+ }
+
+ // Tag bones used as bone flex drivers, these need to be client side only
+ TagFlexDriverBones( psource );
+
+ // NOTE: This must come last; after all flags have been set!
+ // tag bonerefs as being used the union of the boneflags all their children
+ for (k = 0; k < psource->numbones; k++)
+ {
+ UpdateBonerefRecursive( psource, k, psource->boneflags[k] );
+ }
+ }
+
+ // tag all eyeball bones
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ s_source_t *psource = g_model[i]->source;
+ for (k = 0; k < g_model[i]->numeyeballs; k++)
+ {
+ psource->boneref[g_model[i]->eyeball[k].bone] |= BONE_USED_BY_ATTACHMENT;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: change the names in the source files for bones that max auto-renamed on us
+//-----------------------------------------------------------------------------
+void RenameBones( )
+{
+ int i, j, k;
+
+ // rename source bones if needed
+ for (i = 0; i < g_numsources; i++)
+ {
+ for (j = 0; j < g_source[i]->numbones; j++)
+ {
+ for (k = 0; k < g_numrenamedbones; k++)
+ {
+ if (!stricmp( g_source[i]->localBone[j].name, g_renamedbone[k].from))
+ {
+ strcpy( g_source[i]->localBone[j].name, g_renamedbone[k].to );
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+const char *RenameBone( const char *pName )
+{
+ for ( int k = 0; k < g_numrenamedbones; k++)
+ {
+ if ( !Q_stricmp( pName, g_renamedbone[k].from ) )
+ return g_renamedbone[k].to;
+ }
+ return pName;
+}
+
+
+//-----------------------------------------------------------------------------
+// Tags bones in the global bone table
+//-----------------------------------------------------------------------------
+void TagUsedImportedBones()
+{
+ // NOTE: This has to happen because some bones referenced by bonemerge
+ // can be set up using the importbones feature
+ int k, j;
+
+ // Tag all bones marked as being used by bonemerge
+ int nBoneMergeCount = g_BoneMerge.Count();
+ for ( k = 0; k < nBoneMergeCount; ++k )
+ {
+ for ( j = 0; j < g_numbones; j++ )
+ {
+ if ( stricmp( g_BoneMerge[k].bonename, g_bonetable[j].name ) )
+ continue;
+
+ g_bonetable[j].flags |= BONE_USED_BY_BONE_MERGE;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: look through all the sources and build a table of used bones
+//-----------------------------------------------------------------------------
+int BuildGlobalBonetable( )
+{
+ int i, j, k, n;
+ int iError = 0;
+
+ g_numbones = 0;
+
+ for (i = 0; i < MAXSTUDIOSRCBONES; i++)
+ {
+ SetIdentityMatrix( g_bonetable[i].srcRealign );
+ }
+
+ // insert predefined bones first
+ for (i = 0; i < g_numimportbones; i++)
+ {
+ k = findGlobalBone( g_importbone[i].name );
+ if (k == -1)
+ {
+ k = g_numbones;
+ V_strcpy_safe( g_bonetable[k].name, g_importbone[i].name );
+ if ( strlen( g_importbone[i].parent ) == 0 )
+ {
+ g_bonetable[k].parent = -1;
+ }
+ else
+ {
+ // FIXME: This won't work if the imported bone refers to
+ // another imported bone which is further along in the list
+ g_bonetable[k].parent = findGlobalBone( g_importbone[i].parent );
+ if ( g_bonetable[k].parent == -1 )
+ {
+ Warning("Imported bone %s tried to access parent bone %s and failed!\n",
+ g_importbone[i].name, g_importbone[i].parent );
+ }
+ }
+ g_bonetable[k].bPreDefined = true;
+ g_bonetable[k].rawLocal = g_importbone[i].rawLocal;
+ g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal;
+ g_numbones++;
+ }
+ g_bonetable[k].bDontCollapse = true;
+ g_bonetable[k].srcRealign = g_importbone[i].srcRealign;
+ g_bonetable[k].bPreAligned = true;
+ }
+
+ TagUsedImportedBones();
+
+ // union of all used bones
+ for ( i = 0; i < g_numsources; i++ )
+ {
+ s_source_t *psource = g_source[i];
+
+ // skip sources with no bones
+ if (psource->numbones == 0)
+ continue;
+
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( psource, "BindPose" );
+ if ( !pSourceAnim )
+ {
+ pSourceAnim = &psource->m_Animations[0];
+ }
+ BuildRawTransforms( psource, pSourceAnim->animationname, 0, srcBoneToWorld );
+
+ for ( j = 0; j < psource->numbones; j++ )
+ {
+ if ( g_collapse_bones_aggressive )
+ {
+ if ( psource->boneflags[j] == 0 )
+ continue;
+ }
+ else
+ {
+ if ( psource->boneref[j] == 0 )
+ continue;
+ }
+
+ k = findGlobalBone( psource->localBone[j].name );
+ if (k == -1)
+ {
+ // create new bone
+ k = g_numbones;
+ V_strcpy_safe( g_bonetable[k].name, psource->localBone[j].name );
+ if ((n = psource->localBone[j].parent) != -1)
+ g_bonetable[k].parent = findGlobalBone( psource->localBone[n].name );
+ else
+ g_bonetable[k].parent = -1;
+ g_bonetable[k].bonecontroller = 0;
+ g_bonetable[k].flags = psource->boneflags[j];
+
+ if ( g_bonetable[k].parent == -1 || !g_bonetable[g_bonetable[k].parent].bPreAligned )
+ {
+ AngleMatrix( pSourceAnim->rawanim[0][j].rot, pSourceAnim->rawanim[0][j].pos, g_bonetable[k].rawLocal );
+ g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal;
+ }
+ else
+ {
+ // convert the local relative position into a realigned relative position
+ matrix3x4_t srcParentBoneToWorld;
+ ConcatTransforms( srcBoneToWorld[n], g_bonetable[g_bonetable[k].parent].srcRealign, srcParentBoneToWorld );
+ matrix3x4_t invSrcParentBoneToWorld;
+ MatrixInvert( srcParentBoneToWorld, invSrcParentBoneToWorld );
+ ConcatTransforms( invSrcParentBoneToWorld, srcBoneToWorld[j], g_bonetable[k].rawLocal );
+ }
+
+ g_bonetable[k].boneToPose.Invalidate();
+
+ // printf("%d : %s (%s)\n", k, g_bonetable[k].name, g_bonetable[g_bonetable[k].parent].name );
+ g_numbones++;
+ continue;
+ }
+
+ if (g_bOverridePreDefinedBones && g_bonetable[k].bPreDefined)
+ {
+ g_bonetable[k].flags |= psource->boneflags[j];
+
+ ConcatTransforms( srcBoneToWorld[j], g_bonetable[k].srcRealign, g_bonetable[k].boneToPose );
+
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( g_bonetable[k].boneToPose, g_bonetable[k].rawLocal );
+ }
+ else
+ {
+ matrix3x4_t tmp;
+ MatrixInvert( g_bonetable[g_bonetable[k].parent].boneToPose, tmp );
+ ConcatTransforms( tmp, g_bonetable[k].boneToPose, g_bonetable[k].rawLocal );
+ }
+ continue;
+ }
+
+ // accumlate flags
+ g_bonetable[k].flags |= psource->boneflags[j];
+ }
+ }
+
+ return iError;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void BuildGlobalBoneToPose( )
+{
+ int k;
+
+ // build reference pose
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( g_bonetable[k].rawLocal, g_bonetable[k].boneToPose );
+ }
+ else
+ {
+ ConcatTransforms (g_bonetable[g_bonetable[k].parent].boneToPose, g_bonetable[k].rawLocal, g_bonetable[k].boneToPose);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void RebuildLocalPose( )
+{
+ int k;
+
+ matrix3x4_t boneToPose[MAXSTUDIOBONES];
+
+ // build reference pose
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixCopy( g_bonetable[k].boneToPose, boneToPose[k] );
+ }
+
+ matrix3x4_t poseToBone[MAXSTUDIOBONES];
+
+ // rebuild local pose
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( boneToPose[k], g_bonetable[k].rawLocal );
+ }
+ else
+ {
+ ConcatTransforms (poseToBone[g_bonetable[k].parent], boneToPose[k], g_bonetable[k].rawLocal );
+ }
+ MatrixAngles( g_bonetable[k].rawLocal, g_bonetable[k].rot, g_bonetable[k].pos );
+ MatrixCopy( boneToPose[k], g_bonetable[k].boneToPose );
+ MatrixInvert( boneToPose[k], poseToBone[k] );
+
+ // printf("%d \"%s\" %d\n", k, g_bonetable[k].name, g_bonetable[k].parent );
+ }
+ //exit(0);
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: attach bones to different parents if needed
+//-----------------------------------------------------------------------------
+
+void EnforceHierarchy( )
+{
+ int i, j, k;
+
+ // force changes to hierarchy
+ for (i = 0; i < g_numforcedhierarchy; i++)
+ {
+ j = findGlobalBone( g_forcedhierarchy[i].parentname );
+ k = findGlobalBone( g_forcedhierarchy[i].childname );
+
+ if (j == -1 && strlen( g_forcedhierarchy[i].parentname ) > 0 )
+ {
+ MdlError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].parentname );
+ }
+ if (k == -1)
+ {
+ MdlError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].childname );
+ }
+
+ /*
+ if (j > k)
+ {
+ MdlError( "parent \"%s\" declared after child \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].parentname, g_forcedhierarchy[i].childname );
+ }
+ */
+
+ /*
+ if (strlen(g_forcedhierarchy[i].subparentname) != 0)
+ {
+ int n, m;
+
+ m = findGlobalBone( g_forcedhierarchy[i].subparentname );
+ if (m != -1)
+ {
+ MdlError( "inserted bone \"%s\" matches name of existing bone in hierarchy\n", g_forcedhierarchy[i].parentname, g_forcedhierarchy[i].subparentname );
+ }
+
+ printf("inserting bone \"%s\"\n", g_forcedhierarchy[i].subparentname );
+
+ // shift the bone list up
+ for (n = g_numbones; n > k; n--)
+ {
+ g_bonetable[n] = g_bonetable[n-1];
+ if (g_bonetable[n].parent >= k)
+ {
+ g_bonetable[n].parent = g_bonetable[n].parent + 1;
+ }
+ MatrixCopy( boneToPose[n-1], boneToPose[n] );
+ }
+ g_numbones++;
+
+ // add the bone
+ strcpy( g_bonetable[k].name, g_forcedhierarchy[i].subparentname );
+ g_bonetable[k].parent = j;
+ g_bonetable[k].split = true;
+ g_bonetable[k+1].parent = k;
+
+ // split the bone
+ Quaternion q1, q2;
+ Vector p;
+ MatrixAngles( boneToPose[k], q1, p ); // FIXME: badly named!
+
+ // !!!!
+ // QuaternionScale( q1, 0.5, q2 );
+ // q2.Init( 0, 0, 0, 1 );
+ // AngleQuaternion( QAngle( 0, 0, 0 ), q2 );
+ //QuaternionMatrix( q2, p, boneToPose[k] );
+ QuaternionMatrix( q1, p, boneToPose[k] );
+ QuaternionMatrix( q1, p, boneToPose[k+1] );
+ }
+ else
+ */
+ {
+ g_bonetable[k].parent = j;
+ }
+ }
+
+
+ // resort hierarchy
+ bool bSort = true;
+ int count = 0;
+
+ while (bSort)
+ {
+ count++;
+ bSort = false;
+ for (i = 0; i < g_numbones; i++)
+ {
+ if (g_bonetable[i].parent > i)
+ {
+ // swap
+ j = g_bonetable[i].parent;
+ s_bonetable_t tmp;
+ tmp = g_bonetable[i];
+ g_bonetable[i] = g_bonetable[j];
+ g_bonetable[j] = tmp;
+
+ // relink parents
+ for (k = i; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent == i)
+ {
+ g_bonetable[k].parent = j;
+ }
+ else if (g_bonetable[k].parent == j)
+ {
+ g_bonetable[k].parent = i;
+ }
+ }
+
+ bSort = true;
+ }
+ }
+ if (count > 1000)
+ {
+ MdlError( "Circular bone hierarchy\n");
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: find procedural bones and tag for inclusion even if they don't animate
+//-----------------------------------------------------------------------------
+
+void TagProceduralBones( )
+{
+ int j;
+
+ // look for AxisInterp bone definitions
+ int numaxisinterpbones = 0;
+ for (j = 0; j < g_numaxisinterpbones; j++)
+ {
+ g_axisinterpbones[j].bone = findGlobalBone( g_axisinterpbones[j].bonename );
+ g_axisinterpbones[j].control = findGlobalBone( g_axisinterpbones[j].controlname );
+
+ if (g_axisinterpbones[j].bone == -1)
+ {
+ if (!g_quiet)
+ {
+ printf("axisinterpbone \"%s\" unused\n", g_axisinterpbones[j].bonename );
+ }
+ continue; // optimized out, don't complain
+ }
+
+ if (g_axisinterpbones[j].control == -1)
+ {
+ MdlError( "Missing control bone \"%s\" for procedural bone \"%s\"\n", g_axisinterpbones[j].bonename, g_axisinterpbones[j].controlname );
+ }
+
+ g_bonetable[g_axisinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
+ g_axisinterpbonemap[numaxisinterpbones++] = j;
+ }
+ g_numaxisinterpbones = numaxisinterpbones;
+
+ // look for QuatInterp bone definitions
+ int numquatinterpbones = 0;
+ for (j = 0; j < g_numquatinterpbones; j++)
+ {
+ g_quatinterpbones[j].bone = findGlobalBoneXSI( g_quatinterpbones[j].bonename );
+ g_quatinterpbones[j].control = findGlobalBoneXSI( g_quatinterpbones[j].controlname );
+
+ if (g_quatinterpbones[j].bone == -1)
+ {
+ if (!g_quiet && !g_bCreateMakefile )
+ {
+ printf("quatinterpbone \"%s\" unused\n", g_quatinterpbones[j].bonename );
+ }
+ continue; // optimized out, don't complain
+ }
+
+ if (g_quatinterpbones[j].control == -1)
+ {
+ MdlError( "Missing control bone \"%s\" for procedural bone \"%s\"\n", g_quatinterpbones[j].bonename, g_quatinterpbones[j].controlname );
+ }
+
+ g_bonetable[g_quatinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
+ g_quatinterpbonemap[numquatinterpbones++] = j;
+ }
+ g_numquatinterpbones = numquatinterpbones;
+ // look for AimAt bone definitions
+ int numaimatbones = 0;
+ for (j = 0; j < g_numaimatbones; j++)
+ {
+ g_aimatbones[j].bone = findGlobalBoneXSI( g_aimatbones[j].bonename );
+
+ if (g_aimatbones[j].bone == -1)
+ {
+ if (!g_quiet && !g_bCreateMakefile )
+ {
+ printf("<aimconstraint> \"%s\" unused\n", g_aimatbones[j].bonename );
+ }
+ continue; // optimized out, don't complain
+ }
+
+ g_aimatbones[j].parent = findGlobalBoneXSI( g_aimatbones[j].parentname );
+
+ if (g_aimatbones[j].parent == -1)
+ {
+ MdlError( "Missing parent control bone \"%s\" for procedural bone \"%s\"\n", g_aimatbones[j].parentname, g_aimatbones[j].bonename );
+ }
+
+ // Look for the aim bone as an attachment first
+
+ g_aimatbones[j].aimAttach = -1;
+
+ for ( int ai( 0 ); ai < g_numattachments; ++ai )
+ {
+ if ( strcmp( g_attachment[ ai ].name, g_aimatbones[j].aimname ) == 0 )
+ {
+ g_aimatbones[j].aimAttach = ai;
+ break;
+ }
+ }
+
+ if ( g_aimatbones[j].aimAttach == -1 )
+ {
+ g_aimatbones[j].aimBone = findGlobalBoneXSI( g_aimatbones[j].aimname );
+
+ if ( g_aimatbones[j].aimBone == -1 )
+ {
+ MdlError( "Missing aim control attachment or bone \"%s\" for procedural bone \"%s\"\n",
+ g_aimatbones[j].aimname, g_aimatbones[j].bonename );
+ }
+ }
+
+ g_bonetable[g_aimatbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
+ g_aimatbonemap[numaimatbones++] = j;
+ }
+
+ // look for Jiggle bone definitions
+ int numjigglebones = 0;
+ for (j = 0; j < g_numjigglebones; j++)
+ {
+ g_jigglebones[j].bone = findGlobalBone( g_jigglebones[j].bonename );
+
+ if (g_jigglebones[j].bone == -1)
+ {
+ if (!g_quiet)
+ {
+ printf("jigglebone \"%s\" unused\n", g_jigglebones[j].bonename );
+ }
+ continue; // optimized out, don't complain
+ }
+
+ g_bonetable[g_jigglebones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
+ g_jigglebonemap[numjigglebones++] = j;
+ }
+ g_numjigglebones = numjigglebones;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: convert original procedural bone info into correct values for existing skeleton
+//-----------------------------------------------------------------------------
+void RemapProceduralBones( )
+{
+ int j;
+
+ // look for QuatInterp bone definitions
+ for (j = 0; j < g_numquatinterpbones; j++)
+ {
+ s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]];
+
+ int origParent = findGlobalBoneXSI( pInterp->parentname );
+ int origControlParent = findGlobalBoneXSI( pInterp->controlparentname );
+
+ if (origParent == -1)
+ {
+ MdlError( "procedural bone \"%s\", can't find orig parent \"%s\"\n\n", pInterp->bonename, pInterp->parentname );
+ }
+
+ if (origControlParent == -1)
+ {
+ MdlError( "procedural bone \"%s\", can't find control parent \"%s\n\n", pInterp->bonename, pInterp->controlparentname );
+ }
+
+ if ( g_bonetable[pInterp->bone].parent != origParent)
+ {
+ MdlError( "unknown procedural bone parent remapping\n" );
+ }
+
+ if ( g_bonetable[pInterp->control].parent != origControlParent)
+ {
+ MdlError( "procedural bone \"%s\", parent remapping error, control parent was \"%s\", is now \"%s\"\n",
+ pInterp->bonename,
+ pInterp->controlparentname,
+ g_bonetable[g_bonetable[pInterp->control].parent].name );
+ }
+
+ // remap triggers and movements/rotations due to skeleton changes and realignment
+ for (int k = 0; k < pInterp->numtriggers; k++)
+ {
+ int parent = g_bonetable[pInterp->control].parent;
+
+ // triggers are the "control" bone relative to the control's parent bone
+ if (parent != -1)
+ {
+ matrix3x4_t invControlParentRealign;
+ MatrixInvert( g_bonetable[parent].srcRealign, invControlParentRealign );
+
+ matrix3x4_t srcControlParentBoneToPose;
+ ConcatTransforms( g_bonetable[parent].boneToPose, invControlParentRealign, srcControlParentBoneToPose );
+
+ matrix3x4_t srcControlRelative;
+ QuaternionMatrix( pInterp->trigger[k], srcControlRelative );
+
+ matrix3x4_t srcControlBoneToPose;
+ ConcatTransforms( srcControlParentBoneToPose, srcControlRelative, srcControlBoneToPose );
+
+ matrix3x4_t destControlParentBoneToPose;
+ ConcatTransforms( srcControlParentBoneToPose, g_bonetable[parent].srcRealign, destControlParentBoneToPose );
+
+ matrix3x4_t destControlBoneToPose;
+ ConcatTransforms( srcControlBoneToPose, g_bonetable[pInterp->control].srcRealign, destControlBoneToPose );
+
+ matrix3x4_t invDestControlParentBoneToPose;
+ MatrixInvert( destControlParentBoneToPose, invDestControlParentBoneToPose );
+
+ matrix3x4_t destControlRelative;
+ ConcatTransforms( invDestControlParentBoneToPose, destControlBoneToPose, destControlRelative );
+
+ Vector tmp;
+ MatrixAngles( destControlRelative, pInterp->trigger[k], tmp );
+
+ /*
+ Vector pos;
+ RadianEuler angles;
+
+
+ MatrixAngles( srcControlRelative, angles, pos );
+ printf("srcControlRelative : %7.2f %7.2f %7.2f\n", RAD2DEG( angles.x ), RAD2DEG( angles.y ), RAD2DEG( angles.z ) );
+
+ MatrixAngles( destControlRelative, angles, pos );
+ printf("destControlRelative : %7.2f %7.2f %7.2f\n", RAD2DEG( angles.x ), RAD2DEG( angles.y ), RAD2DEG( angles.z ) );
+
+ printf("\n");
+ */
+ }
+
+ // movements are relative to the bone's parent
+ parent = g_bonetable[pInterp->bone].parent;
+ if (parent != -1)
+ {
+ //printf("procedural bone \"%s\"\n", pInterp->bonename );
+ //printf("pre : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z );
+ // get local transform
+ matrix3x4_t srcParentRelative;
+ QuaternionMatrix( pInterp->quat[k], pInterp->pos[k] + pInterp->basepos, srcParentRelative );
+
+ // get original boneToPose
+ matrix3x4_t invSrcRealign;
+ MatrixInvert( g_bonetable[parent].srcRealign, invSrcRealign );
+ matrix3x4_t origParentBoneToPose;
+ ConcatTransforms( g_bonetable[parent].boneToPose, invSrcRealign, origParentBoneToPose );
+
+ // move bone adjustment into world position
+ matrix3x4_t srcBoneToWorld;
+ ConcatTransforms( origParentBoneToPose, srcParentRelative, srcBoneToWorld );
+
+ // calculate local transform
+ matrix3x4_t parentPoseToBone;
+ MatrixInvert( g_bonetable[parent].boneToPose, parentPoseToBone );
+ matrix3x4_t destBoneToWorld;
+ ConcatTransforms( parentPoseToBone, srcBoneToWorld, destBoneToWorld );
+
+ // save out the local transform
+ MatrixAngles( destBoneToWorld, pInterp->quat[k], pInterp->pos[k] );
+
+ pInterp->pos[k] += g_bonetable[pInterp->control].pos * pInterp->percentage;
+
+ //printf("post : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z );
+ }
+
+ }
+ }
+
+ // look for aimatbones
+ for (j = 0; j < g_numaimatbones; j++)
+ {
+ s_aimatbone_t *pAimAtBone = &g_aimatbones[g_aimatbonemap[j]];
+
+ int origParent = findGlobalBoneXSI( pAimAtBone->parentname );
+
+ if (origParent == -1)
+ {
+ MdlError( "<aimconstraint> bone \"%s\", can't find parent bone \"%s\"\n\n", pAimAtBone->bonename, pAimAtBone->parentname );
+ }
+
+ int origAim( -1 );
+
+ for ( int ai( 0 ); ai < g_numattachments; ++ai )
+ {
+ if ( strcmp( g_attachment[ ai ].name, pAimAtBone->aimname ) == 0 )
+ {
+ origAim = ai;
+ break;
+ }
+ }
+
+ if (origAim == -1)
+ {
+ MdlError( "<aimconstraint> bone \"%s\", can't find aim bone \"%s\n\n", pAimAtBone->bonename, pAimAtBone->aimname );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: propogate procedural bone usage up its chain
+//-----------------------------------------------------------------------------
+void MarkProceduralBoneChain()
+{
+ int j;
+ int k;
+ int fBoneFlags;
+
+ // look for QuatInterp bone definitions
+ for (j = 0; j < g_numquatinterpbones; j++)
+ {
+ s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]];
+
+ fBoneFlags = g_bonetable[pInterp->bone].flags & BONE_USED_MASK;
+
+ // propogate the procedural bone usage up its hierarchy
+ k = pInterp->control;
+ while (k != -1)
+ {
+ g_bonetable[k].flags |= fBoneFlags;
+ k = g_bonetable[k].parent;
+ }
+
+ // propogate the procedural bone usage up its hierarchy
+ k = pInterp->bone;
+ while (k != -1)
+ {
+ g_bonetable[k].flags |= fBoneFlags;
+ k = g_bonetable[k].parent;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: go through all source files and link local bone indices and global bonetable indicies
+//-----------------------------------------------------------------------------
+static int MapSourcesToGlobalBonetable( )
+{
+ int i, j, k;
+ int iError = 0;
+
+ // map each source bone list to master list
+ for (i = 0; i < g_numsources; i++)
+ {
+ s_source_t *pSource = g_source[i];
+
+ memset( pSource->boneLocalToGlobal, 0xFF, sizeof(pSource->boneLocalToGlobal) );
+ memset( pSource->boneGlobalToLocal, 0xFF, sizeof(pSource->boneGlobalToLocal) );
+
+ for ( j = 0; j < pSource->numbones; j++ )
+ {
+ k = findGlobalBone( pSource->localBone[j].name );
+ if ( k >= 0 )
+ {
+ pSource->boneLocalToGlobal[j] = k;
+ pSource->boneGlobalToLocal[k] = j;
+ continue;
+ }
+
+ int m = pSource->localBone[j].parent;
+ while ( m != -1 && ( k = findGlobalBone( pSource->localBone[m].name ) ) == -1 )
+ {
+ m = pSource->localBone[m].parent;
+ }
+ if (k == -1)
+ {
+ /*
+ if (!g_quiet)
+ {
+ printf("unable to find connection for collapsed bone \"%s\" \n", pSource->localBone[j].name );
+ }
+ */
+ k = 0;
+ }
+ pSource->boneLocalToGlobal[j] = k;
+ }
+ }
+ return iError;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: go through bone and find any that arent aligned on the X axis
+//-----------------------------------------------------------------------------
+void RealignBones( )
+{
+ int k;
+
+ int childbone[MAXSTUDIOBONES];
+ for (k = 0; k < g_numbones; k++)
+ {
+ childbone[k] = -1;
+ }
+
+ // force bones with IK rules to realign themselves
+ for (int i = 0; i < g_numikchains; i++)
+ {
+ k = g_ikchain[i].link[0].bone;
+ if (childbone[k] == -1 || childbone[k] == g_ikchain[i].link[1].bone)
+ {
+ childbone[k] = g_ikchain[i].link[1].bone;
+ }
+ else
+ {
+ MdlError("Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n",
+ g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[1].bone].name );
+ }
+
+ k = g_ikchain[i].link[1].bone;
+ if (childbone[k] == -1 || childbone[k] == g_ikchain[i].link[2].bone)
+ {
+ childbone[k] = g_ikchain[i].link[2].bone;
+ }
+ else
+ {
+ MdlError("Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n",
+ g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[2].bone].name );
+ }
+ }
+
+ if (g_realignbones)
+ {
+ int children[MAXSTUDIOBONES];
+
+ // count children
+ for (k = 0; k < g_numbones; k++)
+ {
+ children[k] = 0;
+ }
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent != -1)
+ {
+ children[g_bonetable[k].parent]++;
+ }
+ }
+
+ // if my parent bone only has one child, then tell it to align to me
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent != -1 && children[g_bonetable[k].parent] == 1)
+ {
+ childbone[g_bonetable[k].parent] = k;
+ }
+ }
+ }
+
+ matrix3x4_t boneToPose[MAXSTUDIOBONES];
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixCopy( g_bonetable[k].boneToPose, boneToPose[k] );
+ }
+
+ // look for bones that aren't on a primary X axis
+ for (k = 0; k < g_numbones; k++)
+ {
+ // printf("%s %.4f %.4f %.4f (%d)\n", g_bonetable[k].name, g_bonetable[k].pos.x, g_bonetable[k].pos.y, g_bonetable[k].pos.z, children[k] );
+ if (!g_bonetable[k].bPreAligned && childbone[k] != -1)
+ {
+ float d = g_bonetable[childbone[k]].pos.Length();
+
+ // check to see that it's on positive X
+ if (d - g_bonetable[childbone[k]].pos.x > 0.01)
+ {
+ Vector v2;
+ Vector v3;
+ // printf("%s:%s %.4f %.4f %.4f\n", g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[childbone[k]].pos.x, g_bonetable[childbone[k]].pos.y, g_bonetable[childbone[k]].pos.z );
+
+ Vector forward, left, up;
+
+ // calc X axis
+ MatrixGetColumn( g_bonetable[childbone[k]].boneToPose, 3, v2 );
+ MatrixGetColumn( g_bonetable[k].boneToPose, 3, v3 );
+ forward = v2 - v3;
+ VectorNormalize( forward );
+
+ // try to align to existing bone/boundingbox by finding most perpendicular
+ // existing axis and aligning the new Z axis to it.
+ Vector forward2, left2, up2;
+ MatrixGetColumn( boneToPose[k], 0, forward2 );
+ MatrixGetColumn( boneToPose[k], 1, left2 );
+ MatrixGetColumn( boneToPose[k], 2, up2 );
+ float d1 = fabs(DotProduct( forward, forward2 ));
+ float d2 = fabs(DotProduct( forward, left2 ));
+ float d3 = fabs(DotProduct( forward, up2 ));
+ if (d1 <= d2 && d1 <= d3)
+ {
+ up = CrossProduct( forward, forward2 );
+ VectorNormalize( up );
+ }
+ else if (d2 <= d1 && d2 <= d3)
+ {
+ up = CrossProduct( forward, left2 );
+ VectorNormalize( up );
+ }
+ else
+ {
+ up = CrossProduct( forward, up2 );
+ VectorNormalize( up );
+ }
+ left = CrossProduct( up, forward );
+
+ // setup matrix
+ MatrixSetColumn( forward, 0, boneToPose[k] );
+ MatrixSetColumn( left, 1, boneToPose[k] );
+ MatrixSetColumn( up, 2, boneToPose[k] );
+
+ // check orthonormality of matrix
+ d = fabs( DotProduct( forward, left ) )
+ + fabs( DotProduct( left, up ) )
+ + fabs( DotProduct( up, forward ) )
+ + fabs( DotProduct( boneToPose[k][0], boneToPose[k][1] ) )
+ + fabs( DotProduct( boneToPose[k][1], boneToPose[k][2] ) )
+ + fabs( DotProduct( boneToPose[k][2], boneToPose[k][0] ) );
+
+ if (d > 0.0001)
+ {
+ MdlError( "error with realigning bone %s\n", g_bonetable[k].name );
+ }
+
+ // printf("%f %f %f\n", DotProduct( boneToPose[k][0], boneToPose[k][1] ), DotProduct( boneToPose[k][1], boneToPose[k][2] ), DotProduct( boneToPose[k][2], boneToPose[k][0] ) );
+
+ // printf("%f %f %f\n", DotProduct( forward, left ), DotProduct( left, up ), DotProduct( up, forward ) );
+
+ // VectorMatrix( forward, boneToPose[k] );
+
+ MatrixSetColumn( v3, 3, boneToPose[k] );
+ }
+ }
+ }
+
+ for (int i = 0; i < g_numforcedrealign; i++)
+ {
+ k = findGlobalBone( g_forcedrealign[i].name );
+ if (k == -1)
+ {
+ MdlError( "unknown bone %s in $forcedrealign\n", g_forcedrealign[i].name );
+ }
+
+ matrix3x4_t local;
+ matrix3x4_t tmp;
+
+ AngleMatrix( g_forcedrealign[i].rot, local );
+ ConcatTransforms( boneToPose[k], local, tmp );
+ MatrixCopy( tmp, boneToPose[k] );
+ }
+
+ // build realignment transforms
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (!g_bonetable[k].bPreAligned)
+ {
+ matrix3x4_t poseToBone;
+
+ MatrixInvert( g_bonetable[k].boneToPose, poseToBone );
+ ConcatTransforms( poseToBone, boneToPose[k], g_bonetable[k].srcRealign );
+
+ MatrixCopy( boneToPose[k], g_bonetable[k].boneToPose );
+ }
+ }
+
+ // printf("\n");
+
+ // rebuild default angles, position, etc.
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (!g_bonetable[k].bPreAligned)
+ {
+ matrix3x4_t bonematrix;
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( g_bonetable[k].boneToPose, bonematrix );
+ }
+ else
+ {
+ matrix3x4_t poseToBone;
+ // convert my transform into parent relative space
+ MatrixInvert( g_bonetable[g_bonetable[k].parent].boneToPose, poseToBone );
+ ConcatTransforms( poseToBone, g_bonetable[k].boneToPose, bonematrix );
+ }
+
+ MatrixAngles( bonematrix, g_bonetable[k].rot, g_bonetable[k].pos );
+ }
+ }
+
+ // exit(0);
+
+ // printf("\n");
+
+ // build reference pose
+ for (k = 0; k < g_numbones; k++)
+ {
+ matrix3x4_t bonematrix;
+ AngleMatrix( g_bonetable[k].rot, g_bonetable[k].pos, bonematrix );
+ // MatrixCopy( g_bonetable[k].rawLocal, bonematrix );
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( bonematrix, g_bonetable[k].boneToPose );
+ }
+ else
+ {
+ ConcatTransforms (g_bonetable[g_bonetable[k].parent].boneToPose, bonematrix, g_bonetable[k].boneToPose);
+ }
+ /*
+ Vector v1;
+ MatrixGetColumn( g_bonetable[k].boneToPose, 3, v1 );
+ printf("%s %.4f %.4f %.4f\n", g_bonetable[k].name, v1.x, v1.y, v1.z );
+ */
+ }
+}
+
+void CenterBonesOnVerts( void )
+{
+ Vector bmin[MAXSTUDIOBONES];
+ Vector bmax[MAXSTUDIOBONES];
+
+ int i, j, k, n;
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ bmin[k] = Vector( 1, 1, 1 ) * 99999999.0;
+ bmax[k] = Vector( 1, 1, 1 ) * -99999999.0;
+ }
+
+ // find domain of all the vertices
+ for (i = 0; i < g_numsources; i++)
+ {
+ s_source_t *pSource = g_source[i];
+ if ( !pSource->vertex )
+ continue;
+
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( pSource, "BindPose" );
+ if ( !pSourceAnim )
+ {
+ pSourceAnim = &pSource->m_Animations[0];
+ }
+
+ pSource->m_GlobalVertices.AddMultipleToTail( pSource->numvertices );
+
+ Vector p;
+ for (j = 0; j < pSource->numvertices; j++)
+ {
+ for (n = 0; n < pSource->m_GlobalVertices[j].boneweight.numbones; n++)
+ {
+ k = pSource->m_GlobalVertices[j].boneweight.bone[n];
+ p = pSource->m_GlobalVertices[j].position;
+
+ bmin[k] = bmin[k].Min( p );
+ bmax[k] = bmax[k].Max( p );
+ }
+ }
+ }
+
+ // copy min/maxs up to parent
+ for (k = g_numbones - 1; k >= 0; k--)
+ {
+ if (bmin[k].x > bmax[k].x)
+ {
+ for (j = k + 1; j < g_numbones; j++)
+ {
+ if (g_bonetable[j].parent == k)
+ {
+ bmin[k] = bmin[k].Min( bmin[j] );
+ bmax[k] = bmax[k].Max( bmax[j] );
+ }
+ }
+ }
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (bmin[k].x <= bmax[k].x)
+ {
+ Vector center = (bmin[k] + bmax[k]) * 0.5;
+
+ // printf("%d %.1f %.1f %.1f\n", k, center.x, center.y, center.z );
+ matrix3x4_t updateCenter;
+ MatrixCopy( g_bonetable[k].boneToPose, updateCenter );
+ PositionMatrix( center, updateCenter );
+
+ matrix3x4_t invPoseToBone;
+ MatrixInvert( g_bonetable[k].boneToPose, invPoseToBone );
+ ConcatTransforms( invPoseToBone, updateCenter, g_bonetable[k].srcRealign );
+
+ MatrixCopy( updateCenter, g_bonetable[k].boneToPose );
+ }
+ }
+
+ // rebuild default angles, position, etc.
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (!g_bonetable[k].bPreAligned)
+ {
+ matrix3x4_t bonematrix;
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( g_bonetable[k].boneToPose, bonematrix );
+ }
+ else
+ {
+ matrix3x4_t poseToBone;
+ // convert my transform into parent relative space
+ MatrixInvert( g_bonetable[g_bonetable[k].parent].boneToPose, poseToBone );
+ ConcatTransforms( poseToBone, g_bonetable[k].boneToPose, bonematrix );
+ }
+
+ MatrixAngles( bonematrix, g_bonetable[k].rot, g_bonetable[k].pos );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: find all the different bones used in all the source files and map everything
+// to a common bonetable.
+//-----------------------------------------------------------------------------
+void RemapBones( )
+{
+ int iError = 0;
+
+ if ( g_staticprop )
+ {
+ MakeStaticProp( );
+ }
+ else if ( g_centerstaticprop )
+ {
+ MdlWarning("Ignoring option $autocenter. Only supported on $staticprop models!!!\n" );
+ }
+
+ TagUsedBones( );
+
+ RenameBones( );
+
+ iError = BuildGlobalBonetable( );
+
+ BuildGlobalBoneToPose( );
+
+ EnforceHierarchy( );
+
+ {
+ int k, n;
+ for ( k = 0; k < g_numbones; k++ )
+ {
+ // tag parent bones as being in the same way as their children
+ n = g_bonetable[k].parent;
+ while (n != -1)
+ {
+ g_bonetable[n].flags |= g_bonetable[k].flags;
+ n = g_bonetable[n].parent;
+ }
+ }
+ }
+
+ if ( g_collapse_bones || g_numimportbones )
+ {
+ CollapseBones( );
+ }
+
+
+ if ( g_numbones >= MAXSTUDIOBONES )
+ {
+ MdlError( "Too many bones used in model, used %d, max %d\n", g_numbones, MAXSTUDIOBONES );
+ }
+
+ /*
+ for (i = 0; i < g_numbones; i++)
+ {
+ printf("%2d %s %d\n", i, g_bonetable[i].name, g_bonetable[i].parent );
+ }
+ */
+
+ RebuildLocalPose( );
+
+ TagProceduralBones( );
+
+ if ( iError && !(ignore_warnings) )
+ {
+ MdlError( "Exiting due to errors\n" );
+ }
+ MapSourcesToGlobalBonetable( );
+
+ if ( iError && !(ignore_warnings) )
+ {
+ MdlError( "Exiting due to errors\n" );
+ }
+
+ // Map the bone names to global bone indices for all BoneFlexDrivers
+ MapFlexDriveBonesToGlobalBoneTable();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calculate the bone to world transforms for a processed animation
+//-----------------------------------------------------------------------------
+void CalcBoneTransforms( s_animation_t *panimation, int frame, matrix3x4_t* pBoneToWorld )
+{
+ CalcBoneTransforms( panimation, g_panimation[0], frame, pBoneToWorld );
+}
+
+
+void CalcBoneTransforms( s_animation_t *panimation, s_animation_t *pbaseanimation, int frame, matrix3x4_t* pBoneToWorld )
+{
+ if ((panimation->flags & STUDIO_LOOPING) && panimation->numframes > 1)
+ {
+ while (frame >= (panimation->numframes - 1))
+ {
+ frame = frame - (panimation->numframes - 1);
+ }
+ }
+ if (frame < 0 || frame >= panimation->numframes)
+ {
+ MdlError("requested out of range frame on animation \"%s\" : %d (%d)\n", panimation->name, frame, panimation->numframes );
+ }
+
+ for (int k = 0; k < g_numbones; k++)
+ {
+ Vector angle;
+ matrix3x4_t bonematrix;
+
+ if (!(panimation->flags & STUDIO_DELTA))
+ {
+ AngleMatrix( panimation->sanim[frame][k].rot, panimation->sanim[frame][k].pos, bonematrix );
+ }
+ else if (pbaseanimation)
+ {
+ Quaternion q1, q2, q3;
+ Vector p3;
+
+ //AngleQuaternion( g_bonetable[k].rot, q1 );
+ AngleQuaternion( pbaseanimation->sanim[0][k].rot, q1 );
+ AngleQuaternion( panimation->sanim[frame][k].rot, q2 );
+
+ float s = panimation->weight[k];
+
+ QuaternionMA( q1, s, q2, q3 );
+ //p3 = g_bonetable[k].pos + s * panimation->sanim[frame][k].pos;
+ p3 = pbaseanimation->sanim[0][k].pos + s * panimation->sanim[frame][k].pos;
+
+ AngleMatrix( q3, p3, bonematrix );
+ }
+ else
+ {
+ Quaternion q1, q2, q3;
+ Vector p3;
+
+ AngleQuaternion( g_bonetable[k].rot, q1 );
+ AngleQuaternion( panimation->sanim[frame][k].rot, q2 );
+
+ float s = panimation->weight[k];
+
+ QuaternionMA( q1, s, q2, q3 );
+ //p3 = g_bonetable[k].pos + s * panimation->sanim[frame][k].pos;
+ p3 = pbaseanimation->sanim[0][k].pos + s * g_bonetable[k].pos;
+
+ AngleMatrix( q3, p3, bonematrix );
+ }
+
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( bonematrix, pBoneToWorld[k] );
+ }
+ else
+ {
+ ConcatTransforms (pBoneToWorld[g_bonetable[k].parent], bonematrix, pBoneToWorld[k]);
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calculate the bone to world transforms for a processed animation
+//-----------------------------------------------------------------------------
+
+void CalcBoneTransformsCycle( s_animation_t *panimation, s_animation_t *pbaseanimation, float flCycle, matrix3x4_t* pBoneToWorld )
+{
+ float fFrame = flCycle * (panimation->numframes - 1);
+ int iFrame = (int)fFrame;
+ float s = (fFrame - iFrame);
+
+ int iFrame1 = iFrame % (panimation->numframes - 1);
+ int iFrame2 = (iFrame + 1) % (panimation->numframes - 1);
+
+ for (int k = 0; k < g_numbones; k++)
+ {
+ Quaternion q1, q2, q3;
+ Vector p3;
+ matrix3x4_t bonematrix;
+
+ // if (!(panimation->flags & STUDIO_DELTA))
+ {
+ AngleQuaternion( panimation->sanim[iFrame1][k].rot, q1 );
+ AngleQuaternion( panimation->sanim[iFrame2][k].rot, q2 );
+ QuaternionSlerp( q1, q2, s, q3 );
+
+ VectorLerp( panimation->sanim[iFrame1][k].pos, panimation->sanim[iFrame2][k].pos, s, p3 );
+
+ AngleMatrix( q3, p3, bonematrix );
+ }
+ /*
+ else
+ {
+ Vector p3;
+
+ //AngleQuaternion( g_bonetable[k].rot, q1 );
+ AngleQuaternion( pbaseanimation->sanim[0][k].rot, q1 );
+ AngleQuaternion( panimation->sanim[frame][k].rot, q2 );
+
+ float s = panimation->weight[k];
+
+ QuaternionMA( q1, s, q2, q3 );
+ //p3 = g_bonetable[k].pos + s * panimation->sanim[frame][k].pos;
+ p3 = pbaseanimation->sanim[0][k].pos + s * panimation->sanim[frame][k].pos;
+
+ AngleMatrix( q3, p3, bonematrix );
+ }
+ */
+
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( bonematrix, pBoneToWorld[k] );
+ }
+ else
+ {
+ ConcatTransforms (pBoneToWorld[g_bonetable[k].parent], bonematrix, pBoneToWorld[k]);
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calculate the bone to world transforms for a processed sequence
+//-----------------------------------------------------------------------------
+
+
+void SlerpBones(
+ Quaternion q1[MAXSTUDIOBONES],
+ Vector pos1[MAXSTUDIOBONES],
+ int sequence,
+ const Quaternion q2[MAXSTUDIOBONES],
+ const Vector pos2[MAXSTUDIOBONES],
+ float s )
+{
+ int i;
+ Quaternion q3, q4;
+ float s1, s2;
+
+ s_sequence_t *pseqdesc = &g_sequence[sequence];
+
+ if (s <= 0.0f)
+ {
+ return;
+ }
+ else if (s > 1.0f)
+ {
+ s = 1.0f;
+ }
+
+ if (pseqdesc->flags & STUDIO_DELTA)
+ {
+ for (i = 0; i < g_numbones; i++)
+ {
+
+ s2 = s * pseqdesc->weight[i]; // blend in based on this bones weight
+ if (s2 > 0.0)
+ {
+ if (pseqdesc->flags & STUDIO_POST)
+ {
+ QuaternionMA( q1[i], s2, q2[i], q1[i] );
+
+ // FIXME: are these correct?
+ pos1[i][0] = pos1[i][0] + pos2[i][0] * s2;
+ pos1[i][1] = pos1[i][1] + pos2[i][1] * s2;
+ pos1[i][2] = pos1[i][2] + pos2[i][2] * s2;
+ }
+ else
+ {
+ QuaternionSM( s2, q2[i], q1[i], q1[i] );
+
+ // FIXME: are these correct?
+ pos1[i][0] = pos1[i][0] + pos2[i][0] * s2;
+ pos1[i][1] = pos1[i][1] + pos2[i][1] * s2;
+ pos1[i][2] = pos1[i][2] + pos2[i][2] * s2;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i <g_numbones; i++)
+ {
+ s2 = s * pseqdesc->weight[i]; // blend in based on this animations weights
+ if (s2 > 0.0)
+ {
+ s1 = 1.0 - s2;
+
+ if (g_bonetable[i].flags & BONE_FIXED_ALIGNMENT)
+ {
+ QuaternionSlerpNoAlign( q2[i], q1[i], s1, q3 );
+ }
+ else
+ {
+ QuaternionSlerp( q2[i], q1[i], s1, q3 );
+ }
+ q1[i][0] = q3[0];
+ q1[i][1] = q3[1];
+ q1[i][2] = q3[2];
+ q1[i][3] = q3[3];
+ pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s2;
+ pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s2;
+ pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s2;
+ }
+ }
+ }
+}
+
+
+void CalcPoseSingle( Vector pos[], Quaternion q[], int sequence, float frame )
+{
+ s_sequence_t *pseqdesc = &g_sequence[sequence];
+
+ s_animation_t *panim = pseqdesc->panim[0][0];
+
+ // FIXME: is this modulo correct?
+ int iframe = ((int)frame) % panim->numframes;
+
+ for (int k = 0; k < g_numbones; k++)
+ {
+ // FIXME: this isn't doing a fractional frame
+ AngleQuaternion( panim->sanim[iframe][k].rot, q[k] );
+ pos[k] = panim->sanim[iframe][k].pos;
+ }
+}
+
+void AccumulateSeqLayers( Vector pos[], Quaternion q[], int sequence, float frame, float flWeight );
+
+void AccumulatePose( Vector pos[], Quaternion q[], int sequence, float frame, float flWeight )
+{
+ Vector pos2[MAXSTUDIOBONES];
+ Quaternion q2[MAXSTUDIOBONES];
+
+ // printf("accumulate %s : %.1f\n", g_sequence[sequence].name, frame );
+
+ CalcPoseSingle( pos2, q2, sequence, frame );
+
+ SlerpBones( q, pos, sequence, q2, pos2, flWeight );
+
+ AccumulateSeqLayers( pos, q, sequence, frame, flWeight );
+}
+
+void AccumulateSeqLayers( Vector pos[], Quaternion q[], int sequence, float frame, float flWeight )
+{
+ s_sequence_t *pseqdesc = &g_sequence[sequence];
+
+ for (int i = 0; i < pseqdesc->numautolayers; i++)
+ {
+ s_autolayer_t *pLayer = &pseqdesc->autolayer[i];
+
+ float layerFrame = frame;
+ float layerWeight = flWeight;
+
+ if (pLayer->start != pLayer->end)
+ {
+ float s = 1.0;
+ float index;
+
+ if (!(pLayer->flags & STUDIO_AL_POSE))
+ {
+ index = frame;
+ }
+ else
+ {
+ int iPose = pLayer->pose;
+ if (iPose != -1)
+ {
+ index = 0; // undefined?
+ }
+ else
+ {
+ index = 0;
+ }
+ }
+
+ if (index < pLayer->start)
+ continue;
+ if (index >= pLayer->end)
+ continue;
+
+ if (index < pLayer->peak && pLayer->start != pLayer->peak)
+ {
+ s = (index - pLayer->start) / (pLayer->peak - pLayer->start);
+ }
+ else if (index > pLayer->tail && pLayer->end != pLayer->tail)
+ {
+ s = (pLayer->end - index) / (pLayer->end - pLayer->tail);
+ }
+
+ if (pLayer->flags & STUDIO_AL_SPLINE)
+ {
+ s = 3 * s * s - 2 * s * s * s;
+ }
+
+ if ((pLayer->flags & STUDIO_AL_XFADE) && (frame > pLayer->tail))
+ {
+ layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight );
+ }
+ else if (pLayer->flags & STUDIO_AL_NOBLEND)
+ {
+ layerWeight = s;
+ }
+ else
+ {
+ layerWeight = flWeight * s;
+ }
+
+ if (!(pLayer->flags & STUDIO_AL_POSE))
+ {
+ layerFrame = ((frame - pLayer->start) / (pLayer->end - pLayer->start)) * (g_sequence[pLayer->sequence].panim[0][0]->numframes - 1);
+ }
+ else
+ {
+ layerFrame = (frame / g_sequence[sequence].panim[0][0]->numframes - 1) * (g_sequence[pLayer->sequence].panim[0][0]->numframes - 1);
+ }
+ }
+
+ AccumulatePose( pos, q, pLayer->sequence, layerFrame, layerWeight );
+ }
+}
+
+
+void CalcSeqTransforms( int sequence, int frame, matrix3x4_t* pBoneToWorld )
+{
+ int k;
+ Vector pos[MAXSTUDIOBONES];
+ Quaternion q[MAXSTUDIOBONES];
+
+ // CalcPoseSingle( pos, q, 0, 0 );
+ /*
+ for (k = 0; k < g_numbones; k++)
+ {
+ //AngleQuaternion( g_bonetable[k].rot, q[k] );
+ //pos[k] = g_bonetable[k].pos;
+ AngleQuaternion( g_bonetable[k].rot, q[k] );
+ pos[k] = g_bonetable[k].pos;
+ }
+ */
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ //AngleQuaternion( g_bonetable[k].rot, q[k] );
+ //pos[k] = g_bonetable[k].pos;
+ AngleQuaternion( g_bonetable[k].rot, q[k] );
+ pos[k] = g_bonetable[k].pos;
+ }
+
+
+ AccumulatePose( pos, q, sequence, frame, 1.0 );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ matrix3x4_t bonematrix;
+
+ QuaternionMatrix( q[k], pos[k], bonematrix );
+
+ if (g_bonetable[k].parent == -1)
+ {
+ MatrixCopy( bonematrix, pBoneToWorld[k] );
+ }
+ else
+ {
+ ConcatTransforms (pBoneToWorld[g_bonetable[k].parent], bonematrix, pBoneToWorld[k]);
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void CalcBonePos( s_animation_t *panimation, int frame, int bone, Vector &pos )
+{
+ matrix3x4_t boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix
+
+ CalcBoneTransforms( panimation, frame, boneToWorld );
+
+ pos.x = boneToWorld[bone][0][3];
+ pos.y = boneToWorld[bone][1][3];
+ pos.z = boneToWorld[bone][2][3];
+}
+
+
+#define SMALL_FLOAT 1e-12
+
+// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo
+// Creates basis vectors, based on a vertex and index list.
+// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description
+// of how this computation works
+static void CalcTriangleTangentSpace( s_source_t *pSrc, int v1, int v2, int v3,
+ Vector &sVect, Vector &tVect )
+{
+/*
+ static bool firstTime = true;
+ static FILE *fp = NULL;
+ if( firstTime )
+ {
+ firstTime = false;
+ fp = fopen( "crap.out", "w" );
+ }
+*/
+
+ Vector2D t0( pSrc->vertex[v1].texcoord[0], pSrc->vertex[v1].texcoord[1] );
+ Vector2D t1( pSrc->vertex[v2].texcoord[0], pSrc->vertex[v2].texcoord[1] );
+ Vector2D t2( pSrc->vertex[v3].texcoord[0], pSrc->vertex[v3].texcoord[1] );
+ Vector p0( pSrc->vertex[v1].position[0], pSrc->vertex[v1].position[1], pSrc->vertex[v1].position[2] );
+ Vector p1( pSrc->vertex[v2].position[0], pSrc->vertex[v2].position[1], pSrc->vertex[v2].position[2] );
+ Vector p2( pSrc->vertex[v3].position[0], pSrc->vertex[v3].position[1], pSrc->vertex[v3].position[2] );
+ CalcTriangleTangentSpace( p0, p1, p2, t0, t1, t2, sVect, tVect );
+
+/*
+ // Calculate flat normal
+ Vector flatNormal;
+ edge01 = p1 - p0;
+ edge02 = p2 - p0;
+ CrossProduct( edge02, edge01, flatNormal );
+ VectorNormalize( flatNormal );
+
+ // Get the average position
+ Vector avgPos = ( p0 + p1 + p2 ) / 3.0f;
+
+ // Draw the svect
+ Vector endS = avgPos + sVect * .2f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", endS[0], endS[1], endS[2] );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] );
+
+ // Draw the tvect
+ Vector endT = avgPos + tVect * .2f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", endT[0], endT[1], endT[2] );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] );
+
+ // Draw the normal
+ Vector endN = avgPos + flatNormal * .2f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", endN[0], endN[1], endN[2] );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", avgPos[0], avgPos[1], avgPos[2] );
+
+ // Draw the wireframe of the triangle in white.
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] );
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] );
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] );
+ fprintf( fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] );
+
+ // Draw a slightly shrunken version of the geometry to hide surfaces
+ Vector tmp0 = p0 - flatNormal * .1f;
+ Vector tmp1 = p1 - flatNormal * .1f;
+ Vector tmp2 = p2 - flatNormal * .1f;
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f 0.1 0.1 0.1\n", tmp0[0], tmp0[1], tmp0[2] );
+ fprintf( fp, "%f %f %f 0.1 0.1 0.1\n", tmp1[0], tmp1[1], tmp1[2] );
+ fprintf( fp, "%f %f %f 0.1 0.1 0.1\n", tmp2[0], tmp2[1], tmp2[2] );
+
+ fflush( fp );
+*/
+}
+
+typedef CUtlVector<int> CIntVector;
+
+void CalcModelTangentSpaces( s_source_t *pSrc )
+{
+ // Build a map from vertex to a list of triangles that share the vert.
+ int meshID;
+ for( meshID = 0; meshID < pSrc->nummeshes; meshID++ )
+ {
+ s_mesh_t *pMesh = &pSrc->mesh[pSrc->meshindex[meshID]];
+ CUtlVector<CIntVector> vertToTriMap;
+ vertToTriMap.AddMultipleToTail( pMesh->numvertices );
+ int triID;
+ for( triID = 0; triID < pMesh->numfaces; triID++ )
+ {
+ s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset];
+ vertToTriMap[pFace->a].AddToTail( triID );
+ vertToTriMap[pFace->b].AddToTail( triID );
+ vertToTriMap[pFace->c].AddToTail( triID );
+ }
+
+ // Calculate the tangent space for each triangle.
+ CUtlVector<Vector> triSVect;
+ CUtlVector<Vector> triTVect;
+ triSVect.AddMultipleToTail( pMesh->numfaces );
+ triTVect.AddMultipleToTail( pMesh->numfaces );
+ for( triID = 0; triID < pMesh->numfaces; triID++ )
+ {
+ s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset];
+ CalcTriangleTangentSpace( pSrc,
+ pMesh->vertexoffset + pFace->a,
+ pMesh->vertexoffset + pFace->b,
+ pMesh->vertexoffset + pFace->c,
+ triSVect[triID], triTVect[triID] );
+ }
+
+ // calculate an average tangent space for each vertex.
+ int vertID;
+ for( vertID = 0; vertID < pMesh->numvertices; vertID++ )
+ {
+ const Vector &normal = pSrc->vertex[vertID+pMesh->vertexoffset].normal;
+ Vector4D &finalSVect = pSrc->vertex[vertID+pMesh->vertexoffset].tangentS;
+ Vector sVect, tVect;
+
+ sVect.Init( 0.0f, 0.0f, 0.0f );
+ tVect.Init( 0.0f, 0.0f, 0.0f );
+ for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ )
+ {
+ sVect += triSVect[vertToTriMap[vertID][triID]];
+ tVect += triTVect[vertToTriMap[vertID][triID]];
+ }
+
+ // In the case of zbrush, everything needs to be treated as smooth.
+ if( g_bZBrush )
+ {
+ int vertID2;
+ Vector vertPos1( pSrc->vertex[vertID].position[0], pSrc->vertex[vertID].position[1], pSrc->vertex[vertID].position[2] );
+ for( vertID2 = 0; vertID2 < pMesh->numvertices; vertID2++ )
+ {
+ if( vertID2 == vertID )
+ {
+ continue;
+ }
+ Vector vertPos2( pSrc->vertex[vertID2].position[0], pSrc->vertex[vertID2].position[1], pSrc->vertex[vertID2].position[2] );
+ if( vertPos1 == vertPos2 )
+ {
+ int triID2;
+ for( triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ )
+ {
+ sVect += triSVect[vertToTriMap[vertID2][triID2]];
+ tVect += triTVect[vertToTriMap[vertID2][triID2]];
+ }
+ }
+ }
+ }
+
+ // make an orthonormal system.
+ // need to check if we are left or right handed.
+ Vector tmpVect;
+ CrossProduct( sVect, tVect, tmpVect );
+ bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f;
+ if( !leftHanded )
+ {
+ CrossProduct( normal, sVect, tVect );
+ CrossProduct( tVect, normal, sVect );
+ VectorNormalize( sVect );
+ VectorNormalize( tVect );
+ finalSVect[0] = sVect[0];
+ finalSVect[1] = sVect[1];
+ finalSVect[2] = sVect[2];
+ finalSVect[3] = 1.0f;
+ }
+ else
+ {
+ CrossProduct( sVect, normal, tVect );
+ CrossProduct( normal, tVect, sVect );
+ VectorNormalize( sVect );
+ VectorNormalize( tVect );
+ finalSVect[0] = sVect[0];
+ finalSVect[1] = sVect[1];
+ finalSVect[2] = sVect[2];
+ finalSVect[3] = -1.0f;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Generate a model vertex from a source vertex
+//-----------------------------------------------------------------------------
+static void InitRemappedVertex( s_source_t *pSource, matrix3x4_t *pDestBoneToWorld, const s_vertexinfo_t &srcVertex, s_vertexinfo_t &dstVertex )
+{
+ Vector tmp1, tmp2, vdest, ndest;
+
+ memcpy( &dstVertex, &srcVertex, sizeof(s_vertexinfo_t) );
+ dstVertex.boneweight.numbones = 0;
+
+ vdest.Init();
+ ndest.Init();
+
+ int n;
+ for ( n = 0; n < srcVertex.boneweight.numbones; n++ )
+ {
+ // src bone
+ int q = srcVertex.boneweight.bone[n];
+
+ // mapping to global bone
+ int k = pSource->boneLocalToGlobal[q];
+ if ( k == -1 )
+ {
+ VectorCopy( srcVertex.position, vdest );
+ VectorCopy( srcVertex.normal, ndest );
+ break;
+ // printf("%s:%s (%d) missing global\n", psource->filename, psource->localBone[q].name, q );
+ }
+
+ // If the global bone is already in the list, then this vertex
+ // contains influences from multiple local bones which have been collapsed
+ // into a single global bone
+ int m;
+ for ( m = 0; m < dstVertex.boneweight.numbones; m++ )
+ {
+ if ( k == dstVertex.boneweight.bone[m] )
+ {
+ // bone got collapsed out
+ dstVertex.boneweight.weight[m] += srcVertex.boneweight.weight[n];
+ break;
+ }
+ }
+ if ( m == dstVertex.boneweight.numbones )
+ {
+ // add new bone
+ dstVertex.boneweight.bone[m] = k;
+ dstVertex.boneweight.weight[m] = srcVertex.boneweight.weight[n];
+ dstVertex.boneweight.numbones++;
+ }
+
+ // convert vertex into original models' bone local space
+ VectorITransform( srcVertex.position, pDestBoneToWorld[k], tmp1 );
+ // convert that into global world space using stardard pose
+ VectorTransform( tmp1, g_bonetable[k].boneToPose, tmp2 );
+ // accumulate
+ VectorMA( vdest, srcVertex.boneweight.weight[n], tmp2, vdest );
+
+ // convert normal into original models' bone local space
+ VectorIRotate( srcVertex.normal, pDestBoneToWorld[k], tmp1 );
+ // convert that into global world space using stardard pose
+ VectorRotate( tmp1, g_bonetable[k].boneToPose, tmp2 );
+ // accumulate
+ VectorMA( ndest, srcVertex.boneweight.weight[n], tmp2, ndest );
+ }
+
+ // printf("%d %.2f %.2f %.2f\n", j, vdest.x, vdest.y, vdest.z );
+
+ // save, normalize
+ VectorCopy( vdest, dstVertex.position );
+ VectorNormalize( ndest );
+ VectorCopy( ndest, dstVertex.normal );
+
+ // FIXME: Remapping will whack tangentS. Need to recompute tangents after remapping
+}
+
+
+//-----------------------------------------------------------------------------
+// When read off disk, s_source_t contains bone indices local to the source
+// we need to make the bone indices use the global bone list
+//-----------------------------------------------------------------------------
+void RemapVerticesToGlobalBones( )
+{
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ matrix3x4_t destBoneToWorld[MAXSTUDIOSRCBONES];
+
+ for (int i = 0; i < g_numsources; i++)
+ {
+ s_source_t *pSource = g_source[i];
+ if ( !pSource->vertex )
+ continue;
+
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( pSource, "BindPose" );
+ if ( !pSourceAnim )
+ {
+ pSourceAnim = &pSource->m_Animations[0];
+ }
+ BuildRawTransforms( pSource, pSourceAnim->animationname, 0, srcBoneToWorld );
+ TranslateAnimations( pSource, srcBoneToWorld, destBoneToWorld );
+
+ pSource->m_GlobalVertices.AddMultipleToTail( pSource->numvertices );
+
+ for ( int j = 0; j < pSource->numvertices; j++ )
+ {
+ InitRemappedVertex( pSource, destBoneToWorld, pSource->vertex[j], pSource->m_GlobalVertices[j] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Links bone controllers
+//-----------------------------------------------------------------------------
+
+static void FindAutolayers()
+{
+ int i;
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ int k;
+ for (k = 0; k < g_sequence[i].numautolayers; k++)
+ {
+ int j;
+ for ( j = 0; j < g_sequence.Count(); j++)
+ {
+ if (stricmp( g_sequence[i].autolayer[k].name, g_sequence[j].name) == 0)
+ {
+ g_sequence[i].autolayer[k].sequence = j;
+ break;
+ }
+ }
+ if (j == g_sequence.Count())
+ {
+ MdlError( "sequence \"%s\" cannot find autolayer sequence \"%s\"\n",
+ g_sequence[i].name, g_sequence[i].autolayer[k].name );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Links bone controllers
+//-----------------------------------------------------------------------------
+
+static void LinkBoneControllers()
+{
+ for (int i = 0; i < g_numbonecontrollers; i++)
+ {
+ int j = findGlobalBone( g_bonecontroller[i].name );
+ if (j == -1)
+ {
+ MdlError("unknown g_bonecontroller link '%s'\n", g_bonecontroller[i].name );
+ }
+ g_bonecontroller[i].bone = j;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Links screen aligned bones
+//-----------------------------------------------------------------------------
+
+static void TagScreenAlignedBones()
+{
+ for (int i = 0; i < g_numscreenalignedbones; i++)
+ {
+ int j = findGlobalBone( g_screenalignedbone[i].name );
+ if (j == -1)
+ {
+ MdlError("unknown g_screenalignedbone link '%s'\n", g_screenalignedbone[i].name );
+ }
+
+ g_bonetable[j].flags |= g_screenalignedbone[i].flags;
+ printf("tagging bone: %s as screen aligned (index %i, flags:%x)\n", g_bonetable[j].name, j, g_bonetable[j].flags );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Links attachments
+//-----------------------------------------------------------------------------
+
+static void LinkAttachments()
+{
+ int i, j, k;
+
+ // attachments may be connected to bones that can be optimized out
+ // so search through all the sources and move to a valid location
+
+ matrix3x4_t boneToPose;
+ matrix3x4_t world;
+ matrix3x4_t poseToBone;
+
+ for (i = 0; i < g_numattachments; i++)
+ {
+ bool found = false;
+ // search through known bones
+ for (k = 0; k < g_numbones; k++)
+ {
+ if ( !stricmp( g_attachment[i].bonename, g_bonetable[k].name ))
+ {
+ g_attachment[i].bone = k;
+ MatrixCopy( g_bonetable[k].boneToPose, boneToPose );
+ MatrixInvert( boneToPose, poseToBone );
+ // printf("%s : %d\n", g_bonetable[k].name, k );
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ // search all the loaded sources for the first occurance of the named bone
+ for (j = 0; j < g_numsources && !found; j++)
+ {
+ for (k = 0; k < g_source[j]->numbones && !found; k++)
+ {
+ if ( !stricmp( g_attachment[i].bonename, g_source[j]->localBone[k].name ) )
+ {
+ MatrixCopy( g_source[j]->boneToPose[k], boneToPose );
+
+ // check to make sure that this bone is actually referenced in the output model
+ // if not, try parent bone until we find a referenced bone in this chain
+ while (k != -1 && g_source[j]->boneGlobalToLocal[g_source[j]->boneLocalToGlobal[k]] != k)
+ {
+ k = g_source[j]->localBone[k].parent;
+ }
+ if (k == -1)
+ {
+ MdlError( "unable to find valid bone for attachment %s:%s\n",
+ g_attachment[i].name,
+ g_attachment[i].bonename );
+ }
+
+ MatrixInvert( g_source[j]->boneToPose[k], poseToBone );
+ g_attachment[i].bone = g_source[j]->boneLocalToGlobal[k];
+ found = true;
+ }
+ }
+ }
+ }
+
+ if (!found)
+ {
+ MdlError("unknown attachment link '%s'\n", g_attachment[i].bonename );
+ }
+ // printf("%s: %s / %s\n", g_attachment[i].name, g_attachment[i].bonename, g_bonetable[g_attachment[i].bone].name );
+
+ if (g_attachment[i].type & IS_ABSOLUTE)
+ {
+ MatrixCopy( g_attachment[i].local, world );
+ }
+ else
+ {
+ ConcatTransforms( boneToPose, g_attachment[i].local, world );
+ }
+
+ ConcatTransforms( poseToBone, world, g_attachment[i].local );
+ }
+
+ // flag all bones used by attachments
+ for (i = 0; i < g_numattachments; i++)
+ {
+ j = g_attachment[i].bone;
+ while (j != -1)
+ {
+ g_bonetable[j].flags |= BONE_USED_BY_ATTACHMENT;
+ j = g_bonetable[j].parent;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Links mouths
+//-----------------------------------------------------------------------------
+
+static void LinkMouths()
+{
+ for (int i = 0; i < g_nummouths; i++)
+ {
+ int j;
+ for ( j = 0; j < g_numbones; j++)
+ {
+ if (g_mouth[i].bonename[0] && stricmp( g_mouth[i].bonename, g_bonetable[j].name) == 0)
+ break;
+ }
+ if (j >= g_numbones)
+ {
+ MdlError("unknown mouth link '%s'\n", g_mouth[i].bonename );
+ }
+ g_mouth[i].bone = j;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+static float CalcPoseParameterValue( int control, RadianEuler &angle, Vector &pos )
+{
+ switch( control )
+ {
+ case STUDIO_X:
+ return pos.x;
+ case STUDIO_Y:
+ return pos.y;
+ case STUDIO_Z:
+ return pos.z;
+ case STUDIO_XR:
+ return RAD2DEG( angle.x );
+ case STUDIO_YR:
+ return RAD2DEG( angle.y );
+ case STUDIO_ZR:
+ return RAD2DEG( angle.z );
+ }
+ return 0.0;
+}
+
+static void CalcPoseParameters( void )
+{
+ int i;
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ RadianEuler angles;
+ Vector pos;
+
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ s_sequence_t *pseq = &g_sequence[i];
+
+ for (int iPose = 0; iPose < 2; iPose++)
+ {
+ if (pseq->groupsize[iPose] > 1)
+ {
+ if (pseq->paramattachment[iPose] != -1)
+ {
+ int j0 = pseq->paramindex[iPose];
+ int n0 = pseq->paramattachment[iPose];
+ int k0 = g_attachment[n0].bone;
+
+ matrix3x4_t boneToWorldRel;
+ matrix3x4_t boneToWorldMid;
+ matrix3x4_t worldToBoneMid;
+ matrix3x4_t boneRel;
+
+ // printf("%s\n", pseq->name );
+
+ if (pseq->paramanim == NULL)
+ {
+ pseq->paramanim = g_panimation[0];
+ }
+
+ if (pseq->paramcompanim == NULL)
+ {
+ pseq->paramcompanim = pseq->paramanim;
+ }
+
+
+ // calculate what "zero" looks like to the attachment
+ CalcBoneTransforms( pseq->paramanim, 0, boneToWorld );
+ ConcatTransforms( boneToWorld[k0], g_attachment[n0].local, boneToWorldMid );
+ MatrixAngles( boneToWorldMid, angles, pos );
+ // printf("%s : %s : %6.2f %6.2f %6.2f : %6.2f %6.2f %6.2f\n", pseq->name, g_pose[j0].name, RAD2DEG( angles.x ), RAD2DEG( angles.y ), RAD2DEG( angles.z ), pos.x, pos.y, pos.z );
+ MatrixInvert( boneToWorldMid, worldToBoneMid );
+
+ if ( g_verbose )
+ {
+ printf("%s : %s", pseq->name, g_pose[j0].name );
+ }
+
+ // for 2D animation, figure out what opposite row/column to use
+ // FIXME: make these 2D instead of 2 1D!
+ int m[2];
+ bool found = false;
+ if (pseq->paramcenter != NULL)
+ {
+ for (int i0 = 0; !found && i0 < pseq->groupsize[0]; i0++)
+ {
+ for (int i1 = 0; !found && i1 < pseq->groupsize[1]; i1++)
+ {
+ if (pseq->panim[i0][i1] == pseq->paramcenter)
+ {
+ m[0] = i0;
+ m[1] = i1;
+ found = true;
+ }
+ }
+ }
+ }
+ if (!found)
+ {
+ m[1-iPose] = (pseq->groupsize[1-iPose]) / 2;
+ }
+
+ // find changes to attachment
+ for (m[iPose] = 0; m[iPose] < pseq->groupsize[iPose]; m[iPose]++)
+ {
+ CalcBoneTransforms( pseq->panim[m[0]][m[1]], pseq->paramcompanim, 0, boneToWorld );
+ ConcatTransforms( boneToWorld[k0], g_attachment[n0].local, boneToWorldRel );
+ ConcatTransforms( worldToBoneMid, boneToWorldRel, boneRel );
+ MatrixAngles( boneRel, angles, pos );
+ // printf("%6.2f %6.2f %6.2f : %6.2f %6.2f %6.2f\n", RAD2DEG( angles.x ), RAD2DEG( angles.y ), RAD2DEG( angles.z ), pos.x, pos.y, pos.z );
+
+ float v = CalcPoseParameterValue( pseq->paramcontrol[iPose], angles, pos );
+
+ if ( g_verbose )
+ {
+ printf(" %6.2f", v );
+ }
+
+ if (iPose == 0)
+ {
+ pseq->param0[m[iPose]] = v;
+ }
+ else
+ {
+ pseq->param1[m[iPose]] = v;
+ }
+
+
+ // pseq->param1[i0][i1] = CalcPoseParameterValue( pseq->paramcontrol[1], angles, pos );
+
+ if (m[iPose] == 0)
+ {
+ pseq->paramstart[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]];
+ }
+ if (m[iPose] == pseq->groupsize[iPose] - 1)
+ {
+ pseq->paramend[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]];
+ }
+ }
+
+ if ( g_verbose )
+ {
+ printf("\n");
+ }
+
+ if (fabs( pseq->paramstart[iPose] - pseq->paramend[iPose]) < 0.01 )
+ {
+ MdlError( "calcblend failed in %s\n", pseq->name );
+ }
+
+ g_pose[j0].min = min( g_pose[j0].min, pseq->paramstart[iPose] );
+ g_pose[j0].max = max( g_pose[j0].max, pseq->paramstart[iPose] );
+ g_pose[j0].min = min( g_pose[j0].min, pseq->paramend[iPose] );
+ g_pose[j0].max = max( g_pose[j0].max, pseq->paramend[iPose] );
+ }
+ else
+ {
+
+ for (int m = 0; m < pseq->groupsize[iPose]; m++)
+ {
+ float f = (m / (float)(pseq->groupsize[iPose] - 1.0));
+ if (iPose == 0)
+ {
+ pseq->param0[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f;
+ }
+ else
+ {
+ pseq->param1[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f;
+ }
+ }
+ }
+ }
+ }
+ }
+ // exit(0);
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Link ikchains
+//-----------------------------------------------------------------------------
+
+static void LinkIKChains( )
+{
+ int i, k;
+
+ // create IK links
+ for (i = 0; i < g_numikchains; i++)
+ {
+ g_ikchain[i].numlinks = 3;
+
+ k = findGlobalBone( g_ikchain[i].bonename );
+ if (k == -1)
+ {
+ MdlError("unknown bone '%s' in ikchain '%s'\n", g_ikchain[i].bonename, g_ikchain[i].name );
+ }
+ g_ikchain[i].link[2].bone = k;
+ g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
+
+ k = g_bonetable[k].parent;
+ if (k == -1)
+ {
+ MdlError("ikchain '%s' too close to root, no parent knee/elbow\n", g_ikchain[i].name );
+ }
+ g_ikchain[i].link[1].bone = k;
+ g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
+
+ k = g_bonetable[k].parent;
+ if (k == -1)
+ {
+ MdlError("ikchain '%s' too close to root, no parent hip/shoulder\n", g_ikchain[i].name );
+ }
+ g_ikchain[i].link[0].bone = k;
+ g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
+
+ // FIXME: search for toes
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Link ikchains
+//-----------------------------------------------------------------------------
+
+static void LinkIKLocks( )
+{
+ int i, j;
+
+ // create IK links
+ for (i = 0; i < g_numikautoplaylocks; i++)
+ {
+ for (j = 0; j < g_numikchains; j++)
+ {
+ if (stricmp( g_ikchain[j].name, g_ikautoplaylock[i].name) == 0)
+ {
+ break;
+ }
+ }
+ if (j == g_numikchains)
+ {
+ MdlError("unknown chain '%s' in ikautoplaylock\n", g_ikautoplaylock[i].name );
+ }
+
+ g_ikautoplaylock[i].chain = j;
+ }
+
+ int k;
+
+ for (k = 0; k < g_sequence.Count(); k++)
+ {
+ for (i = 0; i < g_sequence[k].numiklocks; i++)
+ {
+ for (j = 0; j < g_numikchains; j++)
+ {
+ if (stricmp( g_ikchain[j].name, g_sequence[k].iklock[i].name) == 0)
+ {
+ break;
+ }
+ }
+ if (j == g_numikchains)
+ {
+ MdlError("unknown chain '%s' in sequence iklock\n", g_sequence[k].iklock[i].name );
+ }
+
+ g_sequence[k].iklock[i].chain = j;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Process IK links
+//-----------------------------------------------------------------------------
+
+s_ikrule_t *FindPrevIKRule( s_animation_t *panim, int iRule )
+{
+ int i, j;
+
+ s_ikrule_t *pRule = &panim->ikrule[iRule];
+
+ for (i = 1; i < panim->numikrules; i++)
+ {
+ j = ( iRule - i + panim->numikrules) % panim->numikrules;
+ if (panim->ikrule[j].chain == pRule->chain)
+ return &panim->ikrule[j];
+ }
+ return pRule;
+}
+
+s_ikrule_t *FindNextIKRule( s_animation_t *panim, int iRule )
+{
+ int i, j;
+
+ s_ikrule_t *pRule = &panim->ikrule[iRule];
+
+ for (i = 1; i < panim->numikrules; i++)
+ {
+ j = (iRule + i ) % panim->numikrules;
+ if (panim->ikrule[j].chain == pRule->chain)
+ return &panim->ikrule[j];
+ }
+ return pRule;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: don't allow bones to change their length if they're predefined.
+// go through all the animations and reset them, but move anything on an ikchain back to where it was.
+//-----------------------------------------------------------------------------
+static void LockBoneLengths()
+{
+ int i, j, k;
+
+ int n;
+
+ if (!g_bLockBoneLengths)
+ return;
+
+ Vector origLocalPos[MAXSTUDIOBONES];
+
+ // find original lengths
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixPosition( g_bonetable[k].rawLocalOriginal, origLocalPos[k] );
+
+ if ( g_verbose )
+ {
+ Vector prev, delta;
+ MatrixPosition( g_bonetable[k].rawLocal, prev );
+ delta = prev - origLocalPos[k];
+ printf("%s - %f %f %f\n", g_bonetable[k].name, delta.x, delta.y, delta.z );
+ }
+ }
+
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+
+ if (panim->flags & STUDIO_DELTA)
+ continue;
+
+ for (j = 0; j < panim->numframes; j++)
+ {
+ matrix3x4_t boneToWorldOriginal[MAXSTUDIOBONES];
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+
+ // calc original transformations
+ CalcBoneTransforms( panim, j, boneToWorldOriginal );
+
+ // force bones back to original lengths
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].parent != -1)
+ {
+ //Vector delta = panim->sanim[j][k].pos - origLocalPos[k];
+ //printf("%f %f %f\n", delta.x, delta.y, delta.z );
+ panim->sanim[j][k].pos = origLocalPos[k];
+ }
+ }
+
+ // calc new transformations
+ CalcBoneTransforms( panim, j, boneToWorld );
+
+ for (n = 0; n < g_numikchains; n++)
+ {
+ if (panim->weight[g_ikchain[n].link[2].bone] > 0)
+ {
+ Vector worldPos;
+ MatrixPosition( boneToWorldOriginal[g_ikchain[n].link[2].bone], worldPos );
+
+ Studio_SolveIK(
+ g_ikchain[n].link[0].bone,
+ g_ikchain[n].link[1].bone,
+ g_ikchain[n].link[2].bone,
+ worldPos,
+ boneToWorld );
+
+ solveBone( panim, j, g_ikchain[n].link[0].bone, boneToWorld );
+ solveBone( panim, j, g_ikchain[n].link[1].bone, boneToWorld );
+ solveBone( panim, j, g_ikchain[n].link[2].bone, boneToWorld );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: go through all the IK rules and calculate the animated path the IK'd
+// end point moves relative to its IK target.
+//-----------------------------------------------------------------------------
+static void ProcessIKRules( )
+{
+ int i, j, k;
+
+ // copy source animations
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+ const char *pAnimationName = g_panimation[i]->animationname;
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, pAnimationName );
+
+ for (j = 0; j < panim->numcmds; j++)
+ {
+ if ( panim->cmds[j].cmd == CMD_IKFIXUP )
+ {
+ fixupIKErrors( panim, panim->cmds[j].u.ikfixup.pRule );
+ }
+
+ if (panim->cmds[j].cmd != CMD_IKRULE)
+ continue;
+
+ if (panim->numikrules >= MAXSTUDIOIKRULES)
+ {
+ MdlError("Too many IK rules in %s (%s)\n", panim->name, panim->filename );
+ }
+ s_ikrule_t *pRule = &panim->ikrule[panim->numikrules++];
+
+ // make a copy of the rule;
+ *pRule = *panim->cmds[j].u.ikrule.pRule;
+ }
+
+ for (j = 0; j < panim->numikrules; j++)
+ {
+ s_ikrule_t *pRule = &panim->ikrule[j];
+
+ if (pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0)
+ {
+ pRule->tail = panim->numframes - 1;
+ pRule->end = panim->numframes - 1;
+ }
+
+ if (pRule->start != -1 && pRule->peak == -1 && pRule->tail == -1 && pRule->end != -1)
+ {
+ pRule->peak = (pRule->start + pRule->end) / 2;
+ pRule->tail = (pRule->start + pRule->end) / 2;
+ }
+
+ if (pRule->start != -1 && pRule->peak == -1 && pRule->tail != -1)
+ {
+ pRule->peak = (pRule->start + pRule->tail) / 2;
+ }
+
+ if (pRule->peak != -1 && pRule->tail == -1 && pRule->end != -1)
+ {
+ pRule->tail = (pRule->peak + pRule->end) / 2;
+ }
+
+ if (pRule->peak == -1)
+ {
+ pRule->start = 0;
+ pRule->peak = 0;
+ }
+
+ if (pRule->tail == -1)
+ {
+ pRule->tail = panim->numframes - 1;
+ pRule->end = panim->numframes - 1;
+ }
+
+ if (pRule->contact == -1)
+ {
+ pRule->contact = pRule->peak;
+ }
+
+ // huh, make up start and end numbers
+ if (pRule->start == -1)
+ {
+ s_ikrule_t *pPrev = FindPrevIKRule( panim, j );
+
+ if (pPrev->slot == pRule->slot)
+ {
+ if (pRule->peak < pPrev->tail)
+ {
+ pRule->start = pRule->peak + (pPrev->tail - pRule->peak) / 2;
+ }
+ else
+ {
+ pRule->start = pRule->peak + (pPrev->tail - pRule->peak + panim->numframes - 1) / 2;
+ }
+ pRule->start = (pRule->start + panim->numframes / 2) % (panim->numframes - 1);
+ pPrev->end = (pRule->start + panim->numframes - 1) % (panim->numframes - 1);
+ }
+ else
+ {
+ pRule->start = pPrev->tail;
+ pPrev->end = pRule->peak;
+ }
+ // printf("%s : %d (%d) : %d %d %d %d\n", panim->name, pRule->chain, panim->numframes - 1, pRule->start, pRule->peak, pRule->tail, pRule->end );
+ }
+
+ // huh, make up start and end numbers
+ if (pRule->end == -1)
+ {
+ s_ikrule_t *pNext = FindNextIKRule( panim, j );
+
+ if (pNext->slot == pRule->slot)
+ {
+ if (pNext->peak < pRule->tail)
+ {
+ pNext->start = pNext->peak + (pRule->tail - pNext->peak) / 2;
+ }
+ else
+ {
+ pNext->start = pNext->peak + (pRule->tail - pNext->peak + panim->numframes - 1) / 2;
+ }
+ pNext->start = (pNext->start + panim->numframes / 2) % (panim->numframes - 1);
+ pRule->end = (pNext->start + panim->numframes - 1) % (panim->numframes - 1);
+ }
+ else
+ {
+ pNext->start = pRule->tail;
+ pRule->end = pNext->peak;
+ }
+ // printf("%s : %d (%d) : %d %d %d %d\n", panim->name, pRule->chain, panim->numframes - 1, pRule->start, pRule->peak, pRule->tail, pRule->end );
+ }
+
+ // check for wrapping
+ if (pRule->peak < pRule->start)
+ {
+ pRule->peak += panim->numframes - 1;
+ }
+ if (pRule->tail < pRule->peak)
+ {
+ pRule->tail += panim->numframes - 1;
+ }
+ if (pRule->end < pRule->tail)
+ {
+ pRule->end += panim->numframes - 1;
+ }
+ if (pRule->contact < pRule->start)
+ {
+ pRule->contact += panim->numframes - 1;
+ }
+
+ /*
+ printf("%s : %d (%d) : %d %d %d %d : %s\n", panim->name, pRule->chain, panim->numframes - 1, pRule->start, pRule->peak, pRule->tail, pRule->end,
+ pRule->usesequence ? "usesequence" : pRule->usesource ? "source" : "" );
+ */
+
+ pRule->errorData.numerror = pRule->end - pRule->start + 1;
+ if (pRule->end >= panim->numframes)
+ pRule->errorData.numerror = pRule->errorData.numerror + 2;
+
+ pRule->errorData.pError = (s_streamdata_t *)kalloc( pRule->errorData.numerror, sizeof( s_streamdata_t ));
+
+ int n = 0;
+
+ if (pRule->usesequence)
+ {
+ // FIXME: bah, this is horrendously hacky, add a damn back pointer
+ for (n = 0; n < g_sequence.Count(); n++)
+ {
+ if (g_sequence[n].panim[0][0] == panim)
+ break;
+ }
+ }
+
+ switch( pRule->type )
+ {
+ case IK_SELF:
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t worldToBone;
+ matrix3x4_t local;
+
+ if (strlen(pRule->bonename) == 0)
+ {
+ pRule->bone = -1;
+ }
+ else
+ {
+
+ pRule->bone = findGlobalBone( pRule->bonename );
+ if (pRule->bone == -1)
+ {
+ MdlError("unknown bone '%s' in ikrule\n", pRule->bonename );
+ }
+ }
+
+ for (k = 0; k < pRule->errorData.numerror; k++)
+ {
+ if (pRule->usesequence)
+ {
+ CalcSeqTransforms( n, k + pRule->start, boneToWorld );
+ }
+ else if (pRule->usesource)
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pAnimationName, k + pRule->start + panim->startframe - pSourceAnim->startframe, panim->scale, panim->adjust, panim->rotation, panim->flags, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+ }
+ else
+ {
+ CalcBoneTransforms( panim, k + pRule->start, boneToWorld );
+ }
+
+
+ if (pRule->bone != -1)
+ {
+ MatrixInvert( boneToWorld[pRule->bone], worldToBone );
+ ConcatTransforms( worldToBone, boneToWorld[g_ikchain[pRule->chain].link[2].bone], local );
+ }
+ else
+ {
+ MatrixCopy( boneToWorld[g_ikchain[pRule->chain].link[2].bone], local );
+ }
+
+ MatrixAngles( local, pRule->errorData.pError[k].q, pRule->errorData.pError[k].pos );
+
+ /*
+ QAngle ang;
+ QuaternionAngles( pRule->errorData.pError[k].q, ang );
+ printf("%d %.1f %.1f %.1f : %.1f %.1f %.1f\n",
+ k,
+ pRule->errorData.pError[k].pos.x, pRule->errorData.pError[k].pos.y, pRule->errorData.pError[k].pos.z,
+ ang.x, ang.y, ang.z );
+ */
+ }
+ }
+ break;
+ case IK_WORLD:
+ break;
+ case IK_ATTACHMENT:
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t worldToBone;
+ matrix3x4_t local;
+
+ int bone = g_ikchain[pRule->chain].link[2].bone;
+ CalcBoneTransforms( panim, pRule->contact, boneToWorld );
+ // FIXME: add in motion
+
+ // pRule->pos = footfall;
+ // pRule->q = RadianEuler( 0, 0, 0 );
+
+ if (strlen(pRule->bonename) == 0)
+ {
+ if (pRule->bone != -1)
+ {
+ pRule->bone = bone;
+ }
+ }
+ else
+ {
+ pRule->bone = findGlobalBone( pRule->bonename );
+ if (pRule->bone == -1)
+ {
+ MdlError("unknown bone '%s' in ikrule\n", pRule->bonename );
+ }
+ }
+
+ if (pRule->bone != -1)
+ {
+ // FIXME: look for local bones...
+ CalcBoneTransforms( panim, pRule->contact, boneToWorld );
+ MatrixAngles( boneToWorld[pRule->bone], pRule->q, pRule->pos );
+ }
+
+#if 0
+ printf("%d %.1f %.1f %.1f\n",
+ pRule->peak,
+ pRule->pos.x, pRule->pos.y, pRule->pos.z );
+#endif
+
+ for (k = 0; k < pRule->errorData.numerror; k++)
+ {
+ int t = k + pRule->start;
+
+ if (pRule->usesequence)
+ {
+ CalcSeqTransforms( n, t, boneToWorld );
+ }
+ else if (pRule->usesource)
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pAnimationName, t + panim->startframe - pSourceAnim->startframe, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+ }
+ else
+ {
+ CalcBoneTransforms( panim, t, boneToWorld );
+ }
+
+ Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact );
+
+ // printf("%2d : %2d : %4.2f %6.1f %6.1f %6.1f\n", k, t, s, pos.x, pos.y, pos.z );
+
+
+ AngleMatrix( pRule->q, pos, local );
+ MatrixInvert( local, worldToBone );
+
+ // calc position error
+ ConcatTransforms( worldToBone, boneToWorld[bone], local );
+ MatrixAngles( local, pRule->errorData.pError[k].q, pRule->errorData.pError[k].pos );
+
+#if 0
+ QAngle ang;
+ QuaternionAngles( pRule->errorData.pError[k].q, ang );
+ printf("%d %.1f %.1f %.1f : %.1f %.1f %.1f\n",
+ k + pRule->start,
+ pRule->errorData.pError[k].pos.x, pRule->errorData.pError[k].pos.y, pRule->errorData.pError[k].pos.z,
+ ang.x, ang.y, ang.z );
+#endif
+ }
+ }
+ break;
+ case IK_GROUND:
+ {
+ matrix3x4_t boneToWorld[MAXSTUDIOBONES];
+ matrix3x4_t worldToBone;
+ matrix3x4_t local;
+
+ int bone = g_ikchain[pRule->chain].link[2].bone;
+
+ if (pRule->usesequence)
+ {
+ CalcSeqTransforms( n, pRule->contact, boneToWorld );
+ }
+ else if (pRule->usesource)
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pAnimationName, pRule->contact + panim->startframe - pSourceAnim->startframe, panim->scale, panim->adjust, panim->rotation, panim->flags, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+ }
+ else
+ {
+ CalcBoneTransforms( panim, pRule->contact, boneToWorld );
+ }
+
+ // FIXME: add in motion
+
+ Vector footfall;
+ VectorTransform( g_ikchain[pRule->chain].center, boneToWorld[bone], footfall );
+ footfall.z = pRule->floor;
+
+ AngleMatrix( RadianEuler( 0, 0, 0 ), footfall, local );
+ MatrixInvert( local, worldToBone );
+
+ pRule->pos = footfall;
+ pRule->q = RadianEuler( 0, 0, 0 );
+
+#if 0
+ printf("%d %.1f %.1f %.1f\n",
+ pRule->peak,
+ pRule->pos.x, pRule->pos.y, pRule->pos.z );
+#endif
+
+ float s;
+ for (k = 0; k < pRule->errorData.numerror; k++)
+ {
+ int t = k + pRule->start;
+ /*
+ if (t > pRule->end)
+ {
+ t = t - (panim->numframes - 1);
+ }
+ */
+
+ if (pRule->usesequence)
+ {
+ CalcSeqTransforms( n, t, boneToWorld );
+ }
+ else if (pRule->usesource)
+ {
+ matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES];
+ BuildRawTransforms( panim->source, pAnimationName, pRule->contact + panim->startframe - pSourceAnim->startframe, panim->scale, panim->adjust, panim->rotation, panim->flags, srcBoneToWorld );
+ TranslateAnimations( panim->source, srcBoneToWorld, boneToWorld );
+ }
+ else
+ {
+ CalcBoneTransforms( panim, t, boneToWorld );
+ }
+ Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact );
+ s = 0.0;
+
+ Vector cur;
+ VectorTransform( g_ikchain[pRule->chain].center, boneToWorld[bone], cur );
+ cur.z = pos.z;
+
+ if (t < pRule->start || t >= pRule->end)
+ {
+ // s = (float)(t - pRule->start) / (pRule->peak - pRule->start);
+ // pos = startPos * (1 - s) + pos * s;
+ pos = cur;
+ }
+ else if (t < pRule->peak)
+ {
+ s = (float)(pRule->peak - t) / (pRule->peak - pRule->start);
+ s = 3 * s * s - 2 * s * s * s;
+ pos = pos * (1 - s) + cur * s;
+ }
+ else if (t > pRule->tail)
+ {
+ s = (float)(t - pRule->tail) / (pRule->end - pRule->tail);
+ s = 3 * s * s - 2 * s * s * s;
+ pos = pos * (1 - s) + cur * s;
+ //pos = endPos - calcMovement( panim, t, pRule->tail );
+ }
+
+ //MatrixPosition( boneToWorld[bone], pos );
+ //pos.z = pRule->floor;
+
+ // printf("%2d : %2d : %4.2f %6.1f %6.1f %6.1f\n", k, t, s, pos.x, pos.y, pos.z );
+
+
+ AngleMatrix( pRule->q, pos, local );
+ MatrixInvert( local, worldToBone );
+
+ // calc position error
+ ConcatTransforms( worldToBone, boneToWorld[bone], local );
+ MatrixAngles( local, pRule->errorData.pError[k].q, pRule->errorData.pError[k].pos );
+
+#if 0
+ QAngle ang;
+ QuaternionAngles( pRule->errorData.pError[k].q, ang );
+ printf("%d %.1f %.1f %.1f : %.1f %.1f %.1f\n",
+ k + pRule->start,
+ pRule->errorData.pError[k].pos.x, pRule->errorData.pError[k].pos.y, pRule->errorData.pError[k].pos.z,
+ ang.x, ang.y, ang.z );
+#endif
+ }
+ }
+ break;
+ case IK_RELEASE:
+ case IK_UNLATCH:
+ break;
+ }
+ }
+
+ if ((panim->flags & STUDIO_DELTA) || panim->noAutoIK)
+ continue;
+
+ // auto release ik chains that are moved but not referenced and have no explicit rules
+ int count[16];
+
+ for (j = 0; j < g_numikchains; j++)
+ {
+ count[j] = 0;
+ }
+
+ for (j = 0; j < panim->numikrules; j++)
+ {
+ count[panim->ikrule[j].chain]++;
+ }
+
+ for (j = 0; j < g_numikchains; j++)
+ {
+ if (count[j] == 0 && panim->weight[g_ikchain[j].link[2].bone] > 0.0)
+ {
+ // printf("%s - %s\n", panim->name, g_ikchain[j].name );
+ k = panim->numikrules++;
+ panim->ikrule[k].chain = j;
+ panim->ikrule[k].slot = j;
+ panim->ikrule[k].type = IK_RELEASE;
+ panim->ikrule[k].start = 0;
+ panim->ikrule[k].peak = 0;
+ panim->ikrule[k].tail = panim->numframes - 1;
+ panim->ikrule[k].end = panim->numframes - 1;
+ }
+ }
+ }
+ // exit(0);
+
+
+ // realign IK across multiple animations
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ for (j = 0; j < g_sequence[i].groupsize[0]; j++)
+ {
+ for (k = 0; k < g_sequence[i].groupsize[1]; k++)
+ {
+ g_sequence[i].numikrules = max( g_sequence[i].numikrules, g_sequence[i].panim[j][k]->numikrules );
+ }
+ }
+
+ // check for mismatched ik rules
+ s_animation_t *panim1 = g_sequence[i].panim[0][0];
+ for (j = 0; j < g_sequence[i].groupsize[0]; j++)
+ {
+ for (k = 0; k < g_sequence[i].groupsize[1]; k++)
+ {
+ s_animation_t *panim2 = g_sequence[i].panim[j][k];
+ if (panim1->numikrules != panim2->numikrules)
+ {
+ MdlError( "%s - mismatched number of IK rules: \"%s\" \"%s\"\n",
+ g_sequence[i].name, panim1->name, panim2->name );
+ }
+ for (int n = 0; n < panim1->numikrules; n++)
+ {
+ if ((panim1->ikrule[n].type != panim2->ikrule[n].type) ||
+ (panim1->ikrule[n].chain != panim2->ikrule[n].chain) ||
+ (panim1->ikrule[n].slot != panim2->ikrule[n].slot))
+ {
+ MdlError( "%s - mismatched IK rule %d: \n\"%s\" : %d %d %d\n\"%s\" : %d %d %d\n",
+ g_sequence[i].name, n,
+ panim1->name, panim1->ikrule[n].type, panim1->ikrule[n].chain, panim1->ikrule[n].slot,
+ panim2->name, panim2->ikrule[n].type, panim2->ikrule[n].chain, panim2->ikrule[n].slot );
+ }
+ }
+ }
+ }
+
+ // FIXME: this doesn't check alignment!!!
+ for (j = 0; j < g_sequence[i].groupsize[0]; j++)
+ {
+ for (k = 0; k < g_sequence[i].groupsize[1]; k++)
+ {
+ for (int n = 0; n < g_sequence[i].panim[j][k]->numikrules; n++)
+ {
+ g_sequence[i].panim[j][k]->ikrule[n].index = n;
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// CompressAnimations
+//-----------------------------------------------------------------------------
+
+static void CompressAnimations( )
+{
+ int i, j, k, n, m;
+
+ // find scales for all bones
+ for (j = 0; j < g_numbones; j++)
+ {
+ // printf("%s : ", g_bonetable[j].name );
+ for (k = 0; k < 6; k++)
+ {
+ float minv, maxv, scale;
+ float total_minv, total_maxv;
+
+ if (k < 3)
+ {
+ minv = -128.0;
+ maxv = 128.0;
+ total_maxv = total_minv = g_bonetable[j].pos[k];
+ }
+ else
+ {
+ minv = -M_PI / 8.0;
+ maxv = M_PI / 8.0;
+ total_maxv = total_minv = g_bonetable[j].rot[k-3];
+ }
+
+ for (i = 0; i < g_numani; i++)
+ {
+ for (n = 0; n < g_panimation[i]->numframes; n++)
+ {
+ float v = 0.0f;
+ switch(k)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if (g_panimation[i]->flags & STUDIO_DELTA)
+ {
+ v = g_panimation[i]->sanim[n][j].pos[k];
+ }
+ else
+ {
+ v = ( g_panimation[i]->sanim[n][j].pos[k] - g_bonetable[j].pos[k] );
+
+ if (g_panimation[i]->sanim[n][j].pos[k] < total_minv)
+ total_minv = g_panimation[i]->sanim[n][j].pos[k];
+ if (g_panimation[i]->sanim[n][j].pos[k] > total_maxv)
+ total_maxv = g_panimation[i]->sanim[n][j].pos[k];
+ }
+ break;
+ case 3:
+ case 4:
+ case 5:
+ if (g_panimation[i]->flags & STUDIO_DELTA)
+ {
+ v = g_panimation[i]->sanim[n][j].rot[k-3];
+ }
+ else
+ {
+ v = ( g_panimation[i]->sanim[n][j].rot[k-3] - g_bonetable[j].rot[k-3] );
+ }
+ while (v >= M_PI)
+ v -= M_PI * 2;
+ while (v < -M_PI)
+ v += M_PI * 2;
+ break;
+ }
+ if (v < minv)
+ minv = v;
+ if (v > maxv)
+ maxv = v;
+ }
+ }
+ if (minv < maxv)
+ {
+ if (-minv> maxv)
+ {
+ scale = minv / -32768.0;
+ }
+ else
+ {
+ scale = maxv / 32767;
+ }
+ }
+ else
+ {
+ scale = 1.0 / 32.0;
+ }
+ switch(k)
+ {
+ case 0:
+ case 1:
+ case 2:
+ g_bonetable[j].posscale[k] = scale;
+ g_bonetable[j].posrange[k] = total_maxv - total_minv;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ // printf("(%.1f %.1f)", RAD2DEG(minv), RAD2DEG(maxv) );
+ // printf("(%.1f)", RAD2DEG(maxv-minv) );
+ g_bonetable[j].rotscale[k-3] = scale;
+ break;
+ }
+ // printf("%.0f ", 1.0 / scale );
+ }
+ // printf("\n" );
+ }
+
+
+ // reduce animations
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *panim = g_panimation[i];
+ s_source_t *psource = panim->source;
+
+ if (g_bCheckLengths)
+ {
+ printf("%s\n", panim->name );
+ }
+
+ // setup animation interior sections
+ int iSectionFrames = panim->numframes;
+ if ( panim->numframes >= g_minSectionFrameLimit )
+ {
+ iSectionFrames = g_sectionFrames;
+ panim->sectionframes = g_sectionFrames;
+ panim->numsections = (int)(panim->numframes / panim->sectionframes) + 2;
+ }
+ else
+ {
+ panim->sectionframes = 0;
+ panim->numsections = 1;
+ }
+
+ for (int w = 0; w < panim->numsections; w++)
+ {
+ int iStartFrame = w * iSectionFrames;
+ int iEndFrame = (w + 1) * iSectionFrames;
+
+ iStartFrame = min( iStartFrame, panim->numframes - 1 );
+ iEndFrame = min( iEndFrame, panim->numframes - 1 );
+
+ // printf("%s : %d %d\n", panim->name, iStartFrame, iEndFrame );
+
+ for (j = 0; j < g_numbones; j++)
+ {
+ for (k = 0; k < 6; k++)
+ {
+ panim->anim[w][j].num[k] = 0;
+ panim->anim[w][j].data[k] = NULL;
+ }
+
+ // skip bones that are always procedural
+ if (g_bonetable[j].flags & BONE_ALWAYS_PROCEDURAL)
+ {
+ // panim->weight[j] = 0.0;
+ continue;
+ }
+
+ // skip bones that have no influence
+ if (panim->weight[j] < 0.001)
+ continue;
+
+ float checkmin[6], checkmax[6];
+ for (k = 0; k < 6; k++)
+ {
+ checkmin[k] = 9999;
+ checkmax[k] = -9999;
+ }
+
+ for (k = 0; k < 6; k++)
+ {
+ mstudioanimvalue_t *pcount, *pvalue;
+ float v;
+ short value[MAXSTUDIOANIMFRAMES];
+ mstudioanimvalue_t data[MAXSTUDIOANIMFRAMES];
+
+ // find deltas from default pose
+ for (n = 0; n <= iEndFrame - iStartFrame; n++)
+ {
+ s_bone_t *psrcdata = &panim->sanim[n+iStartFrame][j];
+ switch(k)
+ {
+ case 0: /* X Position */
+ case 1: /* Y Position */
+ case 2: /* Z Position */
+ if (panim->flags & STUDIO_DELTA)
+ {
+ value[n] = psrcdata->pos[k] / g_bonetable[j].posscale[k];
+ // pre-scale pos delta since format only has room for "overall" weight
+ float r = panim->posweight[j] / panim->weight[j];
+ value[n] *= r;
+ }
+ else
+ {
+ value[n] = ( psrcdata->pos[k] - g_bonetable[j].pos[k] ) / g_bonetable[j].posscale[k];
+ }
+
+ checkmin[k] = min( value[n] * g_bonetable[j].posscale[k], checkmin[k] );
+ checkmax[k] = max( value[n] * g_bonetable[j].posscale[k], checkmax[k] );
+ break;
+ case 3: /* X Rotation */
+ case 4: /* Y Rotation */
+ case 5: /* Z Rotation */
+ if (panim->flags & STUDIO_DELTA)
+ {
+ v = psrcdata->rot[k-3];
+ }
+ else
+ {
+ v = ( psrcdata->rot[k-3] - g_bonetable[j].rot[k-3] );
+ }
+
+ while (v >= M_PI)
+ v -= M_PI * 2;
+ while (v < -M_PI)
+ v += M_PI * 2;
+
+ checkmin[k] = min( v, checkmin[k] );
+ checkmax[k] = max( v, checkmax[k] );
+ value[n] = v / g_bonetable[j].rotscale[k-3];
+ break;
+ }
+ }
+ if (n == 0)
+ MdlError("no animation frames: \"%s\"\n", psource->filename );
+
+ // FIXME: this compression algorithm needs work
+
+ // initialize animation RLE block
+ memset( data, 0, sizeof( data ) );
+ pcount = data;
+ pvalue = pcount + 1;
+
+ pcount->num.valid = 1;
+ pcount->num.total = 1;
+ pvalue->value = value[0];
+ pvalue++;
+
+ // build a RLE of deltas from the default pose
+ for (m = 1; m < n; m++)
+ {
+ if (pcount->num.total == 255)
+ {
+ // chain too long, force a new entry
+ pcount = pvalue;
+ pvalue = pcount + 1;
+ pcount->num.valid++;
+ pvalue->value = value[m];
+ pvalue++;
+ }
+ // insert value if they're not equal,
+ // or if we're not on a run and the run is less than 3 units
+ else if ((value[m] != value[m-1])
+ || ((pcount->num.total == pcount->num.valid) && ((m < n - 1) && value[m] != value[m+1])))
+ {
+ if (pcount->num.total != pcount->num.valid)
+ {
+ //if (j == 0) printf("%d:%d ", pcount->num.valid, pcount->num.total );
+ pcount = pvalue;
+ pvalue = pcount + 1;
+ }
+ pcount->num.valid++;
+ pvalue->value = value[m];
+ pvalue++;
+ }
+ pcount->num.total++;
+ }
+ //if (j == 0) printf("%d:%d\n", pcount->num.valid, pcount->num.total );
+
+ panim->anim[w][j].num[k] = pvalue - data;
+ if (panim->anim[w][j].num[k] == 2 && value[0] == 0)
+ {
+ panim->anim[w][j].num[k] = 0;
+ }
+ else
+ {
+ panim->anim[w][j].data[k] = (mstudioanimvalue_t *)kalloc( pvalue - data, sizeof( mstudioanimvalue_t ) );
+ memmove( panim->anim[w][j].data[k], data, (pvalue - data) * sizeof( mstudioanimvalue_t ) );
+ }
+ // printf("%d(%d) ", g_source[i]->panim[q]->numanim[j][k], n );
+ }
+
+ if (g_bCheckLengths)
+ {
+ char *tmp[6] = { "X", "Y", "Z", "XR", "YR", "ZR" };
+ n = 0;
+ for (k = 0; k < 3; k++)
+ {
+ if (checkmin[k] != 0)
+ {
+ if (n == 0)
+ printf("%s :", g_bonetable[j].name );
+
+ printf("%s(%.1f: %.1f %.1f) ", tmp[k], g_bonetable[j].pos[k], checkmin[k], checkmax[k] );
+ n = 1;
+ }
+ }
+ if (n)
+ printf("\n");
+ }
+ }
+ }
+
+ if (panim->numsections == 1)
+ {
+ panim->sectionframes = 0;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Compress a single animation stream
+//-----------------------------------------------------------------------------
+
+static void CompressSingle( s_animationstream_t *pStream )
+{
+ int k, n, m;
+
+ if (pStream->numerror == 0)
+ return;
+
+ // printf("%s : ", g_bonetable[j].name );
+ for (k = 0; k < 6; k++)
+ {
+ float minv, maxv, scale;
+ RadianEuler ang;
+
+ if (k < 3)
+ {
+ minv = -128.0;
+ maxv = 128.0;
+ }
+ else
+ {
+ minv = -M_PI / 8.0;
+ maxv = M_PI / 8.0;
+ }
+
+ for (n = 0; n < pStream->numerror; n++)
+ {
+ float v = 0.0f;
+ switch(k)
+ {
+ case 0:
+ case 1:
+ case 2:
+ v = pStream->pError[n].pos[k];
+ break;
+ case 3:
+ case 4:
+ case 5:
+ QuaternionAngles( pStream->pError[n].q, ang );
+ v = ang[k-3];
+ while (v >= M_PI)
+ v -= M_PI * 2;
+ while (v < -M_PI)
+ v += M_PI * 2;
+ break;
+ }
+ if (v < minv)
+ minv = v;
+ if (v > maxv)
+ maxv = v;
+ }
+ // printf("%f %f\n", minv, maxv );
+ if (minv < maxv)
+ {
+ if (-minv> maxv)
+ {
+ scale = minv / -32768.0;
+ }
+ else
+ {
+ scale = maxv / 32767;
+ }
+ }
+ else
+ {
+ scale = 1.0 / 32.0;
+ }
+
+ pStream->scale[k] = scale;
+
+ mstudioanimvalue_t *pcount, *pvalue;
+ float v;
+ short value[MAXSTUDIOANIMFRAMES];
+ mstudioanimvalue_t data[MAXSTUDIOANIMFRAMES];
+
+ // find deltas from default pose
+ for (n = 0; n < pStream->numerror; n++)
+ {
+ switch(k)
+ {
+ case 0: /* X Position */
+ case 1: /* Y Position */
+ case 2: /* Z Position */
+ value[n] = pStream->pError[n].pos[k] / pStream->scale[k];
+ break;
+ case 3: /* X Rotation */
+ case 4: /* Y Rotation */
+ case 5: /* Z Rotation */
+ QuaternionAngles( pStream->pError[n].q, ang );
+ v = ang[k-3];
+ while (v >= M_PI)
+ v -= M_PI * 2;
+ while (v < -M_PI)
+ v += M_PI * 2;
+ value[n] = v / pStream->scale[k];
+ break;
+ }
+ }
+
+ // initialize animation RLE block
+ pStream->numanim[k] = 0;
+
+ memset( data, 0, sizeof( data ) );
+ pcount = data;
+ pvalue = pcount + 1;
+
+ pcount->num.valid = 1;
+ pcount->num.total = 1;
+ pvalue->value = value[0];
+ pvalue++;
+
+ // build a RLE of deltas from the default pose
+ for (m = 1; m < n; m++)
+ {
+ if (pcount->num.total == 255)
+ {
+ // chain too long, force a new entry
+ pcount = pvalue;
+ pvalue = pcount + 1;
+ pcount->num.valid++;
+ pvalue->value = value[m];
+ pvalue++;
+ }
+ // insert value if they're not equal,
+ // or if we're not on a run and the run is less than 3 units
+ else if ((value[m] != value[m-1])
+ || ((pcount->num.total == pcount->num.valid) && ((m < n - 1) && value[m] != value[m+1])))
+ {
+ if (pcount->num.total != pcount->num.valid)
+ {
+ //if (j == 0) printf("%d:%d ", pcount->num.valid, pcount->num.total );
+ pcount = pvalue;
+ pvalue = pcount + 1;
+ }
+ pcount->num.valid++;
+ pvalue->value = value[m];
+ pvalue++;
+ }
+ pcount->num.total++;
+ }
+ //if (j == 0) printf("%d:%d\n", pcount->num.valid, pcount->num.total );
+
+ pStream->numanim[k] = pvalue - data;
+ pStream->anim[k] = (mstudioanimvalue_t *)kalloc( pvalue - data, sizeof( mstudioanimvalue_t ) );
+ memmove( pStream->anim[k], data, (pvalue - data) * sizeof( mstudioanimvalue_t ) );
+ // printf("%d (%d) : %d\n", pRule->numanim[k], n, pRule->errorData.numerror );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Compress all the IK data
+//-----------------------------------------------------------------------------
+
+static void CompressIKErrors( )
+{
+ int i, j;
+
+ // find scales for all bones
+ for (i = 0; i < g_numani; i++)
+ {
+ for (j = 0; j < g_panimation[i]->numikrules; j++)
+ {
+ s_ikrule_t *pRule = &g_panimation[i]->ikrule[j];
+
+ if (pRule->errorData.numerror == 0)
+ continue;
+
+ CompressSingle( &pRule->errorData );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Compress all the Local Hierarchy data
+//-----------------------------------------------------------------------------
+
+static void CompressLocalHierarchy( )
+{
+ int i, j;
+
+ // find scales for all bones
+ for (i = 0; i < g_numani; i++)
+ {
+ for (j = 0; j < g_panimation[i]->numlocalhierarchy; j++)
+ {
+ s_localhierarchy_t *pRule = &g_panimation[i]->localhierarchy[j];
+
+ if (pRule->localData.numerror == 0)
+ continue;
+
+ CompressSingle( &pRule->localData );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+struct BonePriority_s
+{
+ int m_nGlobalBoneId;
+ float m_nGlobalBoneWeight;
+};
+
+
+//-----------------------------------------------------------------------------
+// Sort by bone weight
+//-----------------------------------------------------------------------------
+int compareBonePriority( const void *a, const void *b )
+{
+ return
+ reinterpret_cast< const BonePriority_s * >( a )->m_nGlobalBoneWeight < reinterpret_cast< const BonePriority_s * >( b )->m_nGlobalBoneWeight ? -1 :
+ reinterpret_cast< const BonePriority_s * >( a )->m_nGlobalBoneWeight > reinterpret_cast< const BonePriority_s * >( b )->m_nGlobalBoneWeight ? 1 : 0;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Dump A $definebone line, ensuring any parents are already dumped
+//-----------------------------------------------------------------------------
+void DumpDefineBone( int nBoneId, bool *pBoneDumpedList )
+{
+ Assert( nBoneId < g_numbones );
+
+ if ( pBoneDumpedList[ nBoneId ] )
+ return;
+
+ const s_bonetable_t &bone = g_bonetable[ nBoneId ];
+
+ // Ensure the parent bone is dumped before the child
+ if ( bone.parent >= 0 )
+ {
+ DumpDefineBone( bone.parent, pBoneDumpedList );
+ }
+
+ printf( "$definebone " );
+
+ printf( "\"%s\" ", bone.name );
+ if ( bone.parent != -1 )
+ {
+ printf( "\"%s\" ", g_bonetable[ bone.parent ].name );
+ }
+ else
+ {
+ printf( "\"\" " );
+ }
+
+ Vector pos;
+ QAngle angles;
+
+ pos = bone.pos;
+ angles.Init( RAD2DEG( bone.rot.y ), RAD2DEG( bone.rot.z ), RAD2DEG( bone.rot.x ) );
+ printf( "%f %f %f %f %f %f", pos.x, pos.y, pos.z, angles.x, angles.y, angles.z );
+
+ MatrixAngles( bone.srcRealign, angles, pos );
+ printf( " %f %f %f %f %f %f", pos.x, pos.y, pos.z, angles.x, angles.y, angles.z );
+
+ printf( "\n" );
+
+ pBoneDumpedList[ nBoneId ] = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Dump a $definebones .qci file with the bones in an optimal order
+// i.e. Bones that are removed or replaced in LODs are later in the list
+// bones that are used all of the time are at the top of the list
+//-----------------------------------------------------------------------------
+void DumpDefineBones()
+{
+ BonePriority_s *pBonePriorityList = reinterpret_cast< BonePriority_s * >( stackalloc( g_numbones * sizeof( BonePriority_s ) ) );
+
+ for ( int i = 0; i < g_numbones; ++i )
+ {
+ BonePriority_s &bonePriority = pBonePriorityList[ i ];
+ bonePriority.m_nGlobalBoneId = i;
+ bonePriority.m_nGlobalBoneWeight = 0.0f;
+ }
+
+ for ( int i = 0; i < g_ScriptLODs.Count(); ++i )
+ {
+ const LodScriptData_t &scriptLOD = g_ScriptLODs[ i ];
+
+ for ( int j = 0; j < scriptLOD.boneReplacements.Count(); ++j )
+ {
+ // Ignore Shadow LOD
+ if ( scriptLOD.switchValue <= 0.0f )
+ continue;
+
+ const int nBoneId = findGlobalBone( scriptLOD.boneReplacements[ j ].GetSrcName() );
+ if ( nBoneId < 0 )
+ {
+ Warning( "Can't Find BoneReplacement Bone %s\n", scriptLOD.boneReplacements[ j ].GetSrcName() );
+ continue;
+ }
+
+ pBonePriorityList[ nBoneId ].m_nGlobalBoneWeight += scriptLOD.switchValue;
+ }
+ }
+
+ // bones used by hitboxes and attachments should always go first since they're used by the server
+ for ( int i = 0; i < g_numbones; ++i )
+ {
+ if ( g_bonetable[i].flags & (BONE_USED_BY_HITBOX | BONE_USED_BY_ATTACHMENT | BONE_USED_BY_BONE_MERGE ))
+ {
+ pBonePriorityList[ i ].m_nGlobalBoneWeight = -1.0f;
+ }
+ }
+
+ qsort( pBonePriorityList, g_numbones, sizeof( BonePriority_s ), compareBonePriority );
+
+ bool *pBoneDumpedList = reinterpret_cast< bool * >( stackalloc( g_numbones * sizeof( bool ) ) );
+ memset( pBoneDumpedList, 0, g_numbones * sizeof( bool ) );
+
+ for (int i = 0; i < g_numbones; i++)
+ {
+ const BonePriority_s &bonePriority = pBonePriorityList[ i ];
+ const int nBoneId = bonePriority.m_nGlobalBoneId;
+
+ if (g_bonetable[ nBoneId ].flags & BONE_ALWAYS_PROCEDURAL)
+ {
+ pBoneDumpedList[ nBoneId ] = true;
+ continue;
+ }
+
+ DumpDefineBone( nBoneId, pBoneDumpedList );
+ }
+}
+
+
+void ReLinkAttachments()
+{
+ int i;
+ int j;
+ int k;
+
+ // relink per-model attachments, eyeballs
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ s_source_t *psource = g_model[i]->source;
+ for (j = 0; j < g_model[i]->numattachments; j++)
+ {
+ k = findGlobalBone( g_model[i]->attachment[j].bonename );
+ if (k == -1)
+ {
+ MdlError("unknown model attachment link '%s'\n", g_model[i]->attachment[j].bonename );
+ }
+ g_model[i]->attachment[j].bone = j;
+ }
+
+ for (j = 0; j < g_model[i]->numeyeballs; j++)
+ {
+ g_model[i]->eyeball[j].bone = psource->boneLocalToGlobal[g_model[i]->eyeball[j].bone];
+ }
+ }
+}
+
+void CheckEyeballSetup()
+{
+
+ for (int i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ for (int j = 0; j < g_model[i]->numeyeballs; j++)
+ {
+ s_eyeball_t *peyeball = &g_model[i]->eyeball[j];
+ if (peyeball->upperlidflexdesc == -1)
+ {
+// MdlWarning( "eyeball %s missing upperlid data\n", peyeball->name );
+
+ int dummy = Add_Flexdesc( "dummy_eyelid" );
+
+ peyeball->upperlidflexdesc = dummy;
+ peyeball->upperflexdesc[0] = dummy;
+ peyeball->uppertarget[0] = -1;
+ peyeball->upperflexdesc[1] = dummy;
+ peyeball->uppertarget[1] = 0;
+ peyeball->upperflexdesc[2] = dummy;
+ peyeball->uppertarget[2] = 1;
+ }
+
+ if (peyeball->lowerlidflexdesc == -1)
+ {
+// MdlWarning( "eyeball %s missing lower data\n", peyeball->name );
+
+ int dummy = Add_Flexdesc( "dummy_eyelid" );
+
+ peyeball->lowerlidflexdesc = dummy;
+ peyeball->lowerflexdesc[0] = dummy;
+ peyeball->lowertarget[0] = -1;
+ peyeball->lowerflexdesc[1] = dummy;
+ peyeball->lowertarget[1] = 0;
+ peyeball->lowerflexdesc[2] = dummy;
+ peyeball->lowertarget[2] = 1;
+ }
+ }
+ }
+
+
+}
+
+void SetupHitBoxes()
+{
+ int i;
+ int j;
+ int k;
+ int n;
+
+ // set hitgroups
+ for (k = 0; k < g_numbones; k++)
+ {
+ g_bonetable[k].group = -9999;
+ }
+ for (j = 0; j < g_numhitgroups; j++)
+ {
+ k = findGlobalBone( g_hitgroup[j].name );
+ if (k != -1)
+ {
+ g_bonetable[k].group = g_hitgroup[j].group;
+ }
+ else
+ {
+ MdlError( "cannot find bone %s for hitgroup %d\n", g_hitgroup[j].name, g_hitgroup[j].group );
+ }
+ }
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].group == -9999)
+ {
+ if (g_bonetable[k].parent != -1)
+ g_bonetable[k].group = g_bonetable[g_bonetable[k].parent].group;
+ else
+ g_bonetable[k].group = 0;
+ }
+ }
+
+ if ( g_hitboxsets.Size() == 0 )
+ {
+ int index = g_hitboxsets.AddToTail();
+
+ s_hitboxset *set = &g_hitboxsets[ index ];
+ memset( set, 0, sizeof( *set) );
+ strcpy( set->hitboxsetname, "default" );
+
+ gflags |= STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX;
+
+ // find intersection box volume for each bone
+ for (k = 0; k < g_numbones; k++)
+ {
+ for (j = 0; j < 3; j++)
+ {
+ if (g_bUseBoneInBBox)
+ {
+ g_bonetable[k].bmin[j] = 0.0;
+ g_bonetable[k].bmax[j] = 0.0;
+ }
+ else
+ {
+ g_bonetable[k].bmin[j] = 9999.0;
+ g_bonetable[k].bmax[j] = -9999.0;
+ }
+ }
+ }
+ // try all the connect vertices
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ s_loddata_t *pLodData = g_model[i]->m_pLodData;
+ if ( !pLodData )
+ continue;
+
+ Vector p;
+ for (j = 0; j < pLodData->numvertices; j++)
+ {
+ for (n = 0; n < pLodData->vertex[j].boneweight.numbones; n++)
+ {
+ k = pLodData->vertex[j].boneweight.bone[n];
+ VectorITransform( pLodData->vertex[j].position, g_bonetable[k].boneToPose, p );
+
+ if (p[0] < g_bonetable[k].bmin[0]) g_bonetable[k].bmin[0] = p[0];
+ if (p[1] < g_bonetable[k].bmin[1]) g_bonetable[k].bmin[1] = p[1];
+ if (p[2] < g_bonetable[k].bmin[2]) g_bonetable[k].bmin[2] = p[2];
+ if (p[0] > g_bonetable[k].bmax[0]) g_bonetable[k].bmax[0] = p[0];
+ if (p[1] > g_bonetable[k].bmax[1]) g_bonetable[k].bmax[1] = p[1];
+ if (p[2] > g_bonetable[k].bmax[2]) g_bonetable[k].bmax[2] = p[2];
+ }
+ }
+ }
+ // add in all your children as well
+ for (k = 0; k < g_numbones; k++)
+ {
+ if ((j = g_bonetable[k].parent) != -1)
+ {
+ if (g_bonetable[k].pos[0] < g_bonetable[j].bmin[0]) g_bonetable[j].bmin[0] = g_bonetable[k].pos[0];
+ if (g_bonetable[k].pos[1] < g_bonetable[j].bmin[1]) g_bonetable[j].bmin[1] = g_bonetable[k].pos[1];
+ if (g_bonetable[k].pos[2] < g_bonetable[j].bmin[2]) g_bonetable[j].bmin[2] = g_bonetable[k].pos[2];
+ if (g_bonetable[k].pos[0] > g_bonetable[j].bmax[0]) g_bonetable[j].bmax[0] = g_bonetable[k].pos[0];
+ if (g_bonetable[k].pos[1] > g_bonetable[j].bmax[1]) g_bonetable[j].bmax[1] = g_bonetable[k].pos[1];
+ if (g_bonetable[k].pos[2] > g_bonetable[j].bmax[2]) g_bonetable[j].bmax[2] = g_bonetable[k].pos[2];
+ }
+ }
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ if (g_bonetable[k].bmin[0] < g_bonetable[k].bmax[0] - 1
+ && g_bonetable[k].bmin[1] < g_bonetable[k].bmax[1] - 1
+ && g_bonetable[k].bmin[2] < g_bonetable[k].bmax[2] - 1)
+ {
+ set->hitbox[set->numhitboxes].bone = k;
+ set->hitbox[set->numhitboxes].group = g_bonetable[k].group;
+ VectorCopy( g_bonetable[k].bmin, set->hitbox[set->numhitboxes].bmin );
+ VectorCopy( g_bonetable[k].bmax, set->hitbox[set->numhitboxes].bmax );
+
+ if (dump_hboxes)
+ {
+ printf("$hbox %d \"%s\" %.2f %.2f %.2f %.2f %.2f %.2f\n",
+ set->hitbox[set->numhitboxes].group,
+ g_bonetable[set->hitbox[set->numhitboxes].bone].name,
+ set->hitbox[set->numhitboxes].bmin[0], set->hitbox[set->numhitboxes].bmin[1], set->hitbox[set->numhitboxes].bmin[2],
+ set->hitbox[set->numhitboxes].bmax[0], set->hitbox[set->numhitboxes].bmax[1], set->hitbox[set->numhitboxes].bmax[2] );
+
+ }
+ set->numhitboxes++;
+ }
+ }
+ }
+ else
+ {
+ gflags &= ~STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX;
+
+ for (int s = 0; s < g_hitboxsets.Size(); s++ )
+ {
+ s_hitboxset *set = &g_hitboxsets[ s ];
+
+ for (j = 0; j < set->numhitboxes; j++)
+ {
+ k = findGlobalBone( set->hitbox[j].name );
+ if (k != -1)
+ {
+ set->hitbox[j].bone = k;
+ }
+ else
+ {
+ MdlError( "cannot find bone %s for bbox\n", set->hitbox[j].name );
+ }
+ }
+ }
+ }
+
+ for (int s = 0; s < g_hitboxsets.Size(); s++ )
+ {
+ s_hitboxset *set = &g_hitboxsets[ s ];
+
+ // flag all bones used by hitboxes
+ for (j = 0; j < set->numhitboxes; j++)
+ {
+ k = set->hitbox[j].bone;
+ while (k != -1)
+ {
+ g_bonetable[k].flags |= BONE_USED_BY_HITBOX;
+ k = g_bonetable[k].parent;
+ }
+ }
+ }
+}
+
+void SetupFullBoneRenderBounds( CUtlVector<CBoneRenderBounds> &boneRenderBounds )
+{
+ boneRenderBounds.SetSize( g_numbones );
+
+
+ // First, add the ones already calculated from vertices.
+ for ( int i=0; i < g_numbones; i++ )
+ {
+ CBoneRenderBounds *pOut = &boneRenderBounds[i];
+ pOut->m_Mins = g_bonetable[i].bmin;
+ pOut->m_Maxs = g_bonetable[i].bmax;
+ }
+
+ // Note: shared animation files will need to include the hitboxes or else their sequence
+ // boxes won't use this stuff.
+ // Now add hitboxes.
+ for ( int i=0; i < g_hitboxsets.Count(); i++ )
+ {
+ const s_hitboxset *pSet = &g_hitboxsets[i];
+
+ for ( int k=0; k < pSet->numhitboxes; k++ )
+ {
+ const s_bbox_t *pIn = &pSet->hitbox[k];
+
+ if ( pIn->bone >= 0 )
+ {
+ CBoneRenderBounds *pOut = &boneRenderBounds[pIn->bone];
+ VectorMin( pIn->bmin, pOut->m_Mins, pOut->m_Mins );
+ VectorMax( pIn->bmax, pOut->m_Maxs, pOut->m_Maxs );
+ }
+ }
+ }
+}
+
+
+void CalcSequenceBoundingBoxes()
+{
+ int i;
+ int j;
+ int k;
+ int n;
+ int m;
+
+ CUtlVector<CBoneRenderBounds> boneRenderBounds;
+ SetupFullBoneRenderBounds( boneRenderBounds );
+
+ // find bounding box for each g_sequence
+ for (i = 0; i < g_numani; i++)
+ {
+ Vector bmin, bmax;
+
+ // find intersection box volume for each bone
+ for (j = 0; j < 3; j++)
+ {
+ bmin[j] = 9999.0;
+ bmax[j] = -9999.0;
+ }
+
+ for (j = 0; j < g_panimation[i]->numframes; j++)
+ {
+ matrix3x4_t bonetransform[MAXSTUDIOBONES]; // bone transformation matrix
+ matrix3x4_t posetransform[MAXSTUDIOBONES]; // bone transformation matrix
+ matrix3x4_t bonematrix; // local transformation matrix
+ Vector pos;
+
+ CalcBoneTransforms( g_panimation[i], j, bonetransform );
+
+ for (k = 0; k < g_numbones; k++)
+ {
+ MatrixInvert( g_bonetable[k].boneToPose, bonematrix );
+ ConcatTransforms (bonetransform[k], bonematrix, posetransform[k]);
+ }
+
+ // include hitboxes as well.
+ for (k = 0; k < g_numbones; k++)
+ {
+ Vector tmpMin, tmpMax;
+ TransformAABB( bonetransform[k], boneRenderBounds[k].m_Mins, boneRenderBounds[k].m_Maxs, tmpMin, tmpMax );
+ VectorMin( tmpMin, bmin, bmin );
+ VectorMax( tmpMax, bmax, bmax );
+ }
+
+ // include vertices
+ for (k = 0; k < g_nummodelsbeforeLOD; k++)
+ {
+ s_loddata_t *pLodData = g_model[k]->m_pLodData;
+
+ // skip blank empty model
+ if ( !pLodData )
+ continue;
+
+ for (n = 0; n < pLodData->numvertices; n++)
+ {
+ Vector tmp;
+ pos = Vector( 0, 0, 0 );
+ for (m = 0; m < pLodData->vertex[n].boneweight.numbones; m++)
+ {
+ VectorTransform( pLodData->vertex[n].position, posetransform[pLodData->vertex[n].boneweight.bone[m]], tmp ); // bug: should use all bones!
+ VectorMA( pos, pLodData->vertex[n].boneweight.weight[m], tmp, pos );
+ }
+
+ VectorMin( pos, bmin, bmin );
+ VectorMax( pos, bmax, bmax );
+ }
+ }
+ }
+
+ if (bmin.x < g_vecMinWorldspace.x || bmin.y < g_vecMinWorldspace.y || bmin.z < g_vecMinWorldspace.z || bmax.x > g_vecMaxWorldspace.x || bmax.y > g_vecMaxWorldspace.y || bmax.z > g_vecMaxWorldspace.z)
+ {
+ MdlWarning("%s : bounding box out of range : %.0f %.0f %.0f : %.0f %.0f %.0f\n",
+ g_panimation[i]->name,
+ bmin.x, bmin.y, bmin.z, bmax.z, bmax.y, bmax.z );
+
+ VectorMax( bmin, g_vecMinWorldspace, bmin );
+ VectorMin( bmax, g_vecMaxWorldspace, bmax );
+ }
+
+ VectorCopy( bmin, g_panimation[i]->bmin );
+ VectorCopy( bmax, g_panimation[i]->bmax );
+
+ /*
+ printf("%s : %.0f %.0f %.0f %.0f %.0f %.0f\n",
+ g_panimation[i]->name, bmin[0], bmax[0], bmin[1], bmax[1], bmin[2], bmax[2] );
+ */
+
+ // printf("%s %.2f\n", g_sequence[i].name, g_sequence[i].panim[0]->pos[9][0][0] / g_bonetable[9].pos[0] );
+ }
+
+ for (i = 0; i < g_sequence.Count(); i++)
+ {
+ Vector bmin, bmax;
+
+ // find intersection box volume for each bone
+ for (j = 0; j < 3; j++)
+ {
+ bmin[j] = 9999.0;
+ bmax[j] = -9999.0;
+ }
+
+ for (j = 0; j < g_sequence[i].groupsize[0]; j++)
+ {
+ for (k = 0; k < g_sequence[i].groupsize[1]; k++)
+ {
+ s_animation_t *panim = g_sequence[i].panim[j][k];
+
+ if (panim->bmin[0] < bmin[0]) bmin[0] = panim->bmin[0];
+ if (panim->bmin[1] < bmin[1]) bmin[1] = panim->bmin[1];
+ if (panim->bmin[2] < bmin[2]) bmin[2] = panim->bmin[2];
+ if (panim->bmax[0] > bmax[0]) bmax[0] = panim->bmax[0];
+ if (panim->bmax[1] > bmax[1]) bmax[1] = panim->bmax[1];
+ if (panim->bmax[2] > bmax[2]) bmax[2] = panim->bmax[2];
+ }
+ }
+
+ VectorCopy( bmin, g_sequence[i].bmin );
+ VectorCopy( bmax, g_sequence[i].bmax );
+ }
+}
+
+void SetIlluminationPosition()
+{
+ // find center of domain
+ if (!illumpositionset)
+ {
+ // Only use the 0th sequence; that should be the idle sequence
+ VectorFill( illumposition, 0 );
+ if (g_sequence.Count() != 0)
+ {
+ VectorAdd( g_sequence[0].bmin, g_sequence[0].bmax, illumposition );
+ illumposition *= 0.5f;
+ }
+ illumpositionset = true;
+ }
+}
+
+void SimplifyModel()
+{
+ if (g_sequence.Count() == 0 && g_numincludemodels == 0)
+ {
+ MdlError( "model has no sequences\n");
+ }
+
+ // have to load the lod sources before remapping bones so that the remap
+ // happens for all LODs.
+ LoadLODSources();
+
+ RemapBones();
+
+ LinkIKChains();
+
+ LinkIKLocks();
+
+ RealignBones();
+
+ ConvertBoneTreeCollapsesToReplaceBones();
+
+ // export bones
+ if (g_definebones)
+ {
+ DumpDefineBones();
+ exit( 0 );
+ }
+
+ // translate:
+ // replacebone "bone0" "bone1"
+ // replacebone "bone1" "bone2"
+ // replacebone "bone2" "bone3"
+ // to:
+ // replacebone "bone0" "bone3"
+ // replacebone "bone1" "bone3"
+ // replacebone "bone2" "bone3"
+ FixupReplacedBones();
+
+ RemapVerticesToGlobalBones();
+
+ if (g_bCenterBonesOnVerts)
+ {
+ CenterBonesOnVerts();
+ }
+
+ // remap lods to root, building aggregate final pools
+ // mark bones used by an lod
+ UnifyLODs();
+
+ if ( g_bPrintBones )
+ {
+ printf( "Hardware bone usage:\n" );
+ }
+ SpewBoneUsageStats();
+
+ MarkParentBoneLODs();
+
+ if ( g_bPrintBones )
+ {
+ printf( "CPU bone usage:\n" );
+ }
+ SpewBoneUsageStats();
+
+ RemapAnimations();
+
+ processAnimations();
+
+ limitBoneRotations();
+
+ limitIKChainLength();
+
+ RemapProceduralBones();
+
+ MakeTransitions();
+ RemapVertexAnimations();
+ RemapVertexAnimationsNewVersion();
+
+ FindAutolayers();
+
+ // link bonecontrollers
+ LinkBoneControllers();
+
+ // link screen aligned bones
+ TagScreenAlignedBones();
+
+ // link attachments
+ LinkAttachments();
+
+ // link mouths
+ LinkMouths();
+
+ // procedural bone needs to propogate its bone usage up its chain
+ // ensures runtime sets up dependent bone hierarchy
+ MarkProceduralBoneChain();
+
+ LockBoneLengths();
+
+ ProcessIKRules();
+
+ CompressIKErrors( );
+
+ CompressLocalHierarchy( );
+
+ CalcPoseParameters();
+
+ ReLinkAttachments();
+
+ CheckEyeballSetup();
+
+ SetupHitBoxes();
+
+ CompressAnimations( );
+
+ CalcSequenceBoundingBoxes();
+
+ SetIlluminationPosition();
+
+ if ( g_bBuildPreview )
+ {
+ gflags |= STUDIOHDR_FLAGS_BUILT_IN_PREVIEW_MODE;
+ }
+}
+
+
+
+
+
diff --git a/utils/studiomdl/studiomdl.cpp b/utils/studiomdl/studiomdl.cpp
new file mode 100644
index 0000000..4441617
--- /dev/null
+++ b/utils/studiomdl/studiomdl.cpp
@@ -0,0 +1,10448 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// models/<scriptname>.mdl.
+//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+#include <windows.h>
+#undef GetCurrentDirectory
+
+#include <Shlwapi.h> // PathCanonicalize
+#pragma comment( lib, "shlwapi" )
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <direct.h>
+#include "istudiorender.h"
+#include "filesystem_tools.h"
+#include "tier2/fileutils.h"
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#define EXTERN
+#include "studio.h"
+#include "studiomdl.h"
+#include "collisionmodel.h"
+#include "optimize.h"
+#include "byteswap.h"
+#include "studiobyteswap.h"
+#include "tier1/strtools.h"
+#include "bspflags.h"
+#include "tier0/icommandline.h"
+#include "utldict.h"
+#include "tier1/utlsortvector.h"
+#include "bitvec.h"
+#include "appframework/appframework.h"
+#include "datamodel/idatamodel.h"
+#include "materialsystem/materialsystem_config.h"
+#include "vstdlib/cvar.h"
+#include "tier1/tier1.h"
+#include "tier2/tier2.h"
+#include "tier3/tier3.h"
+#include "datamodel/dmelementfactoryhelper.h"
+#include "mdlobjects/dmeboneflexdriver.h"
+#include "movieobjects/dmeanimationset.h"
+#include "movieobjects/dmemdlmakefile.h"
+#include "movieobjects/dmevertexdata.h"
+#include "movieobjects/dmecombinationoperator.h"
+#include "dmserializers/idmserializers.h"
+#include "tier2/p4helpers.h"
+#include "p4lib/ip4.h"
+#include "mdllib/mdllib.h"
+#include "perfstats.h"
+#include "worldsize.h"
+
+bool g_collapse_bones = false;
+bool g_collapse_bones_aggressive = false;
+bool g_quiet = false;
+bool g_badCollide = false;
+bool g_IHVTest = false;
+bool g_bCheckLengths = false;
+bool g_bPrintBones = false;
+bool g_bPerf = false;
+bool g_bDumpGraph = false;
+bool g_bMultistageGraph = false;
+bool g_verbose = false;
+bool g_bCreateMakefile = false;
+bool g_bHasModelName = false;
+bool g_bZBrush = false;
+bool g_bVerifyOnly = false;
+bool g_bUseBoneInBBox = true;
+bool g_bLockBoneLengths = false;
+bool g_bOverridePreDefinedBones = false;
+int g_minLod = 0;
+int g_numAllowedRootLODs = 0;
+bool g_bNoWarnings = false;
+int g_maxWarnings = -1;
+bool g_bX360 = false;
+bool g_bBuildPreview = false;
+bool g_bCenterBonesOnVerts = false;
+bool g_bDumpMaterials = false;
+bool g_bStripLods = false;
+bool g_bMakeVsi = false;
+float g_flDefaultMotionRollback = 0.3f;
+int g_minSectionFrameLimit = 120;
+int g_sectionFrames = 30;
+bool g_bNoAnimblockStall = false;
+
+char g_path[MAX_PATH];
+Vector g_vecMinWorldspace = Vector( MIN_COORD_INTEGER, MIN_COORD_INTEGER, MIN_COORD_INTEGER );
+Vector g_vecMaxWorldspace = Vector( MAX_COORD_INTEGER, MAX_COORD_INTEGER, MAX_COORD_INTEGER );
+DmElementHandle_t g_hDmeBoneFlexDriverList = DMELEMENT_HANDLE_INVALID;
+
+enum RunMode
+{
+ RUN_MODE_BUILD,
+ RUN_MODE_STRIP_MODEL,
+ RUN_MODE_STRIP_VHV
+} g_eRunMode = RUN_MODE_BUILD;
+
+bool g_bNoP4 = false;
+
+
+CUtlVector< s_hitboxset > g_hitboxsets;
+CUtlVector< char > g_KeyValueText;
+CUtlVector<s_flexcontrollerremap_t> g_FlexControllerRemap;
+CCheckUVCmd g_StudioMdlCheckUVCmd;
+
+
+//-----------------------------------------------------------------------------
+// Parsed data from a .qc or .dmx file
+//-----------------------------------------------------------------------------
+struct IKLock_t
+{
+ CUtlString m_Name;
+ float m_flPosWeight;
+ float m_flLocalQWeight;
+};
+
+struct SequenceOption_t
+{
+ bool m_bSnap : 1;
+ bool m_bIsDelta : 1;
+ bool m_bIsWorldSpace : 1;
+ bool m_bIsPost : 1;
+ bool m_bIsPreDelta : 1;
+ bool m_bIsAutoplay : 1;
+ bool m_bIsRealTime : 1;
+ bool m_bIsHidden : 1;
+ float m_flFadeInTime;
+ float m_flFadeOutTime;
+ int m_nBlendWidth;
+ CUtlVector< CUtlString > m_AutoLayers;
+ CUtlVector< IKLock_t > m_IKLocks;
+};
+
+struct CmdSequence_t
+{
+ CUtlString m_Name;
+ CUtlString m_FileName;
+ SequenceOption_t m_Options;
+};
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+void AddBodyFlexData( s_source_t *pSource, int imodel );
+void AddBodyAttachments( s_source_t *pSource );
+void AddBodyFlexRules( s_source_t *pSource );
+
+//-----------------------------------------------------------------------------
+// Stuff for writing a makefile to build models incrementally.
+//-----------------------------------------------------------------------------
+CUtlVector<CUtlSymbol> m_CreateMakefileDependencies;
+
+void CreateMakefile_AddDependency( const char *pFileName )
+{
+ EnsureDependencyFileCheckedIn( pFileName );
+
+ if( !g_bCreateMakefile )
+ {
+ return;
+ }
+
+ CUtlSymbol sym( pFileName );
+ int i;
+ for( i = 0; i < m_CreateMakefileDependencies.Count(); i++ )
+ {
+ if( m_CreateMakefileDependencies[i] == sym )
+ {
+ return;
+ }
+ }
+ m_CreateMakefileDependencies.AddToTail( sym );
+}
+
+void EnsureDependencyFileCheckedIn( const char *pFileName )
+{
+ // Early out: if no p4
+ if ( g_bNoP4 )
+ return;
+
+ char pFullPath[MAX_PATH];
+ if ( !GetGlobalFilePath( pFileName, pFullPath, sizeof(pFullPath) ) )
+ {
+ MdlWarning( "Model dependency file '%s' is missing.\n", pFileName );
+ return;
+ }
+
+ Q_FixSlashes( pFullPath );
+ char bufCanonicalPath[ MAX_PATH ] = {0};
+ PathCanonicalize( bufCanonicalPath, pFullPath );
+ CP4AutoAddFile p4_add_dep_file( bufCanonicalPath );
+}
+
+void StudioMdl_ScriptLoadedCallback( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber )
+{
+ EnsureDependencyFileCheckedIn( pFilenameLoaded );
+}
+
+void CreateMakefile_OutputMakefile( void )
+{
+ if( !g_bHasModelName )
+ {
+ MdlError( "Can't write makefile since a target mdl hasn't been specified!" );
+ }
+ FILE *fp = fopen( "makefile.tmp", "a" );
+ if( !fp )
+ {
+ MdlError( "can't open makefile.tmp!\n" );
+ }
+ char mdlname[MAX_PATH];
+ V_strcpy_safe( mdlname, gamedir );
+// if( *g_pPlatformName )
+// {
+// V_strcat_safe( mdlname, "platform_" );
+// V_strcat_safe( mdlname, g_pPlatformName );
+// V_strcat_safe( mdlname, "/" );
+// }
+ V_strcat_safe( mdlname, "models/" );
+ V_strcat_safe( mdlname, outname );
+ Q_StripExtension( mdlname, mdlname, sizeof( mdlname ) );
+ V_strcat_safe( mdlname, ".mdl" );
+ Q_FixSlashes( mdlname );
+
+ fprintf( fp, "%s:", mdlname );
+ int i;
+ for( i = 0; i < m_CreateMakefileDependencies.Count(); i++ )
+ {
+ fprintf( fp, " %s", m_CreateMakefileDependencies[i].String() );
+ }
+ fprintf( fp, "\n" );
+ char mkdirpath[MAX_PATH];
+ V_strcpy_safe( mkdirpath, mdlname );
+ Q_StripFilename( mkdirpath );
+ fprintf( fp, "\tmkdir \"%s\"\n", mkdirpath );
+ fprintf( fp, "\t%s -quiet %s\n\n", CommandLine()->GetParm( 0 ), fullpath );
+ fclose( fp );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+static bool g_bFirstWarning = true;
+
+void TokenError( const char *fmt, ... )
+{
+ static char output[1024];
+ va_list args;
+
+ char *pFilename;
+ int iLineNumber;
+
+ if (GetTokenizerStatus( &pFilename, &iLineNumber ))
+ {
+ va_start( args, fmt );
+ vsprintf( output, fmt, args );
+
+ MdlError( "%s(%d): - %s", pFilename, iLineNumber, output );
+ }
+ else
+ {
+ va_start( args, fmt );
+ vsprintf( output, fmt, args );
+ MdlError( "%s", output );
+ }
+}
+
+void MdlError( const char *fmt, ... )
+{
+ static char output[1024];
+ static char *knownExtensions[] = {".mdl", ".ani", ".phy", ".sw.vtx", ".dx80.vtx", ".dx90.vtx", ".vvd"};
+ char fileName[MAX_PATH];
+ char baseName[MAX_PATH];
+ va_list args;
+
+ Assert( 0 );
+ if (g_quiet)
+ {
+ if (g_bFirstWarning)
+ {
+ printf("%s :\n", fullpath );
+ g_bFirstWarning = false;
+ }
+ printf("\t");
+ }
+
+ printf("ERROR: ");
+ va_start( args, fmt );
+ vprintf( fmt, args );
+
+ // delete premature files
+ // unforunately, content is built without verification
+ // ensuring that targets are not available, prevents check-in
+ if (g_bHasModelName)
+ {
+ // undescriptive errors in batch processes could be anonymous
+ printf("ERROR: Aborted Processing on '%s'\n", outname);
+
+ V_strcpy_safe( fileName, gamedir );
+ V_strcat_safe( fileName, "models/" );
+ V_strcat_safe( fileName, outname );
+ Q_FixSlashes( fileName );
+ Q_StripExtension( fileName, baseName, sizeof( baseName ) );
+
+ for (int i=0; i<ARRAYSIZE(knownExtensions); i++)
+ {
+ V_strcpy_safe( fileName, baseName);
+ V_strcat_safe( fileName, knownExtensions[i] );
+
+ // really need filesystem concept here
+// g_pFileSystem->RemoveFile( fileName );
+ unlink( fileName );
+ }
+ }
+
+ exit( -1 );
+}
+
+
+void MdlWarning( const char *fmt, ... )
+{
+ va_list args;
+ static char output[1024];
+
+ if (g_bNoWarnings || g_maxWarnings == 0)
+ return;
+
+ WORD old = SetConsoleTextColor( 1, 1, 0, 1 );
+
+ if (g_quiet)
+ {
+ if (g_bFirstWarning)
+ {
+ printf("%s :\n", fullpath );
+ g_bFirstWarning = false;
+ }
+ printf("\t");
+ }
+
+ Assert( 0 );
+
+ printf("WARNING: ");
+ va_start( args, fmt );
+ vprintf( fmt, args );
+
+ if (g_maxWarnings > 0)
+ g_maxWarnings--;
+
+ if (g_maxWarnings == 0)
+ {
+ if (g_quiet)
+ {
+ printf("\t");
+ }
+ printf("suppressing further warnings...\n");
+ }
+
+ RestoreConsoleTextColor( old );
+}
+
+SpewRetval_t MdlSpewOutputFunc( SpewType_t type, char const *pMsg )
+{
+ if ((( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) && g_quiet)
+ {
+ // suppress
+ }
+ else if (type == SPEW_WARNING)
+ {
+ MdlWarning( "%s", pMsg );
+ }
+ else
+ {
+ return CmdLib_SpewOutputFunc( type, pMsg );
+ }
+
+ return SPEW_CONTINUE;
+}
+
+
+#ifndef _DEBUG
+
+void MdlHandleCrash( const char *pMessage, bool bAssert )
+{
+ static LONG crashHandlerCount = 0;
+ if ( InterlockedIncrement( &crashHandlerCount ) == 1 )
+ {
+ MdlError( "'%s' (assert: %d)\n", pMessage, bAssert );
+ }
+
+ InterlockedDecrement( &crashHandlerCount );
+}
+
+
+// This is called if we crash inside our crash handler. It just terminates the process immediately.
+LONG __stdcall MdlSecondExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
+{
+ TerminateProcess( GetCurrentProcess(), 2 );
+ return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway)
+}
+
+
+void MdlExceptionFilter( unsigned long code )
+{
+ // This is called if we crash inside our crash handler. It just terminates the process immediately.
+ SetUnhandledExceptionFilter( MdlSecondExceptionFilter );
+
+ //DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode;
+
+ #define ERR_RECORD( name ) { name, #name }
+ struct
+ {
+ int code;
+ char *pReason;
+ } errors[] =
+ {
+ ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ),
+ ERR_RECORD( EXCEPTION_ARRAY_BOUNDS_EXCEEDED ),
+ ERR_RECORD( EXCEPTION_BREAKPOINT ),
+ ERR_RECORD( EXCEPTION_DATATYPE_MISALIGNMENT ),
+ ERR_RECORD( EXCEPTION_FLT_DENORMAL_OPERAND ),
+ ERR_RECORD( EXCEPTION_FLT_DIVIDE_BY_ZERO ),
+ ERR_RECORD( EXCEPTION_FLT_INEXACT_RESULT ),
+ ERR_RECORD( EXCEPTION_FLT_INVALID_OPERATION ),
+ ERR_RECORD( EXCEPTION_FLT_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_FLT_STACK_CHECK ),
+ ERR_RECORD( EXCEPTION_FLT_UNDERFLOW ),
+ ERR_RECORD( EXCEPTION_ILLEGAL_INSTRUCTION ),
+ ERR_RECORD( EXCEPTION_IN_PAGE_ERROR ),
+ ERR_RECORD( EXCEPTION_INT_DIVIDE_BY_ZERO ),
+ ERR_RECORD( EXCEPTION_INT_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_INVALID_DISPOSITION ),
+ ERR_RECORD( EXCEPTION_NONCONTINUABLE_EXCEPTION ),
+ ERR_RECORD( EXCEPTION_PRIV_INSTRUCTION ),
+ ERR_RECORD( EXCEPTION_SINGLE_STEP ),
+ ERR_RECORD( EXCEPTION_STACK_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ),
+ };
+
+ int nErrors = sizeof( errors ) / sizeof( errors[0] );
+ {
+ int i;
+ for ( i=0; i < nErrors; i++ )
+ {
+ if ( errors[i].code == code )
+ MdlHandleCrash( errors[i].pReason, true );
+ }
+
+ if ( i == nErrors )
+ {
+ MdlHandleCrash( "Unknown reason", true );
+ }
+ }
+
+ TerminateProcess( GetCurrentProcess(), 1 );
+}
+
+#endif
+
+/*
+=================
+=================
+*/
+
+int k_memtotal;
+void *kalloc( int num, int size )
+{
+ // printf( "calloc( %d, %d )\n", num, size );
+ // printf( "%d ", num * size );
+ int nMemSize = num * size;
+ k_memtotal += nMemSize;
+
+ // ensure memory alignment on maximum of ALIGN
+ nMemSize += 511;
+ void *ptr = malloc( nMemSize );
+ memset( ptr, 0, nMemSize );
+ ptr = (byte *)((int)((byte *)ptr + 511) & ~511);
+ return ptr;
+}
+
+void kmemset( void *ptr, int value, int size )
+{
+ // printf( "kmemset( %x, %d, %d )\n", ptr, value, size );
+ memset( ptr, value, size );
+ return;
+}
+
+
+int verify_atoi( const char *token )
+{
+ if (token[0] != '-' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atoi( token );
+}
+
+float verify_atof( const char *token )
+{
+ if (token[0] != '-' && token[0] != '.' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atof( token );
+}
+
+float verify_atof_with_null( const char *token )
+{
+ if (strcmp( token, ".." ) == 0)
+ return -1;
+
+ if (token[0] != '-' && token[0] != '.' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Key value block
+//-----------------------------------------------------------------------------
+static void AppendKeyValueText( CUtlVector< char > *pKeyValue, const char *pString )
+{
+ int nLen = strlen(pString);
+ int nFirst = pKeyValue->AddMultipleToTail( nLen );
+ memcpy( pKeyValue->Base() + nFirst, pString, nLen );
+}
+
+int KeyValueTextSize( CUtlVector< char > *pKeyValue )
+{
+ return pKeyValue->Count();
+}
+
+const char *KeyValueText( CUtlVector< char > *pKeyValue )
+{
+ return pKeyValue->Base();
+}
+
+void Option_KeyValues( CUtlVector< char > *pKeyValue );
+
+//-----------------------------------------------------------------------------
+// Read global input into common string
+//-----------------------------------------------------------------------------
+
+bool GetLineInput( void )
+{
+ while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ g_iLinecount++;
+ // skip comments
+ if (g_szLine[0] == '/' && g_szLine[1] == '/')
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+
+/*
+=================
+=================
+*/
+
+
+
+
+int lookupControl( char *string )
+{
+ if (stricmp(string,"X")==0) return STUDIO_X;
+ if (stricmp(string,"Y")==0) return STUDIO_Y;
+ if (stricmp(string,"Z")==0) return STUDIO_Z;
+ if (stricmp(string,"XR")==0) return STUDIO_XR;
+ if (stricmp(string,"YR")==0) return STUDIO_YR;
+ if (stricmp(string,"ZR")==0) return STUDIO_ZR;
+
+ if (stricmp(string,"LX")==0) return STUDIO_LX;
+ if (stricmp(string,"LY")==0) return STUDIO_LY;
+ if (stricmp(string,"LZ")==0) return STUDIO_LZ;
+ if (stricmp(string,"LXR")==0) return STUDIO_LXR;
+ if (stricmp(string,"LYR")==0) return STUDIO_LYR;
+ if (stricmp(string,"LZR")==0) return STUDIO_LZR;
+
+ if (stricmp(string,"LM")==0) return STUDIO_LINEAR;
+ if (stricmp(string,"LQ")==0) return STUDIO_QUADRATIC_MOTION;
+
+ return -1;
+}
+
+
+
+/*
+=================
+=================
+*/
+
+int LookupPoseParameter( char *name )
+{
+ int i;
+ for ( i = 0; i < g_numposeparameters; i++)
+ {
+ if (!stricmp( name, g_pose[i].name))
+ {
+ return i;
+ }
+ }
+ V_strcpy_safe( g_pose[i].name, name );
+ g_numposeparameters = i + 1;
+
+ if (g_numposeparameters > MAXSTUDIOPOSEPARAM)
+ {
+ TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
+ }
+
+ return i;
+}
+
+
+//-----------------------------------------------------------------------------
+// Stuff for writing a makefile to build models incrementally.
+//-----------------------------------------------------------------------------
+s_sourceanim_t *FindSourceAnim( s_source_t *pSource, const char *pAnimName )
+{
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+ return NULL;
+}
+
+const s_sourceanim_t *FindSourceAnim( const s_source_t *pSource, const char *pAnimName )
+{
+ if ( !pAnimName[0] )
+ return NULL;
+
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ const s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+ return NULL;
+}
+
+s_sourceanim_t *FindOrAddSourceAnim( s_source_t *pSource, const char *pAnimName )
+{
+ if ( !pAnimName[0] )
+ return NULL;
+
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+
+ int nIndex = pSource->m_Animations.AddToTail();
+ s_sourceanim_t *pAnim = &pSource->m_Animations[nIndex];
+ memset( pAnim, 0, sizeof(s_sourceanim_t) );
+ Q_strncpy( pAnim->animationname, pAnimName, sizeof(pAnim->animationname) );
+ return pAnim;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the $boneflexdriver command
+// QC: $boneflexdriver <bone name> <tx|ty|tz> <flex controller name> <min> <max>
+//-----------------------------------------------------------------------------
+void Cmd_BoneFlexDriver()
+{
+ CDisableUndoScopeGuard undoDisable; // Turn of Dme undo
+
+ // Find or create the DmeBoneFlexDriverList
+ CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList );
+ if ( !pDmeBoneFlexDriverList )
+ {
+ pDmeBoneFlexDriverList = CreateElement< CDmeBoneFlexDriverList >( "boneDriverFlexList", DMFILEID_INVALID );
+ if ( pDmeBoneFlexDriverList )
+ {
+ g_hDmeBoneFlexDriverList = pDmeBoneFlexDriverList->GetHandle();
+ }
+ }
+
+ if ( !pDmeBoneFlexDriverList )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneDriverFlexList\n", "$boneflexdriver" );
+ return;
+ }
+
+ // <bone name>
+ GetToken( false );
+ CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->FindOrCreateBoneFlexDriver( token );
+ if ( !pDmeBoneFlexDriver )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneFlexDriver for bone \"%s\"\n", "$boneflexdriver", token );
+ return;
+ }
+
+ // <tx|ty|tz|rx|ry|rz>
+ GetToken( false );
+ const char *ppszComponentTypeList[] = { "tx", "ty", "tz" };
+ int nBoneComponent = -1;
+ for ( int i = 0; i < ARRAYSIZE( ppszComponentTypeList ); ++i )
+ {
+ if ( StringHasPrefix( token, ppszComponentTypeList[i] ) )
+ {
+ nBoneComponent = i;
+ break;
+ }
+ }
+
+ if ( nBoneComponent < STUDIO_BONE_FLEX_TX || nBoneComponent > STUDIO_BONE_FLEX_TZ )
+ {
+ TokenError( "%s: Invalid bone component, must be one of <tx|ty|tz>\n", "$boneflexdriver" );
+ return;
+ }
+
+ // <flex controller name>
+ GetToken( false );
+ CDmeBoneFlexDriverControl *pDmeBoneFlexDriverControl = pDmeBoneFlexDriver->FindOrCreateControl( token );
+ if ( !pDmeBoneFlexDriverControl )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneFlexDriverControl for bone \"%s\"\n", "$boneflexdriver", token );
+ return;
+ }
+
+ pDmeBoneFlexDriverControl->m_nBoneComponent = nBoneComponent;
+
+ // <min>
+ GetToken( false );
+ pDmeBoneFlexDriverControl->m_flMin = verify_atof( token );
+
+ // <max>
+ GetToken( false );
+ pDmeBoneFlexDriverControl->m_flMax = verify_atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the $checkuv command
+// QC: $checkuv [0to1] [overlap] [inverse] [gutter <res> <min>]
+//-----------------------------------------------------------------------------
+void Cmd_CheckUV()
+{
+ g_StudioMdlCheckUVCmd.ClearCheck( CCheckUVCmd::CHECK_UV_ALL_FLAGS );
+
+ while ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( !V_stricmp( token, "0to1" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_NORMALIZED );
+ }
+ else if ( !V_stricmp( token, "overlap" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_OVERLAP );
+ }
+ else if ( !V_stricmp( token, "inverse" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_INVERSE );
+ }
+ else if ( !V_stricmp( token, "gutter" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_GUTTER );
+ if ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( V_isdigit( *token ) )
+ {
+ const int nOptRes = V_atoi( token );
+ if ( nOptRes <= 0 )
+ {
+ MdlError( "$checkuv: Invalid resolution, \"%s\", for gutter check specified, must be > 0\n", token );
+ return;
+ }
+
+ g_StudioMdlCheckUVCmd.m_nOptGutterTexWidth = nOptRes;
+ g_StudioMdlCheckUVCmd.m_nOptGutterTexHeight = nOptRes;
+
+ if ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( V_isdigit( *token ) )
+ {
+ const int nOptMin = V_atoi( token );
+ if ( nOptMin <= 0 )
+ {
+ MdlError( "$checkuv: Invalid minimum, \"%s\", for gutter check specified, must be > 0\n", token );
+ return;
+ }
+
+ g_StudioMdlCheckUVCmd.m_nOptGutterMin = nOptMin;
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ MdlError( "$checkuv: Unknown argument \"%s\", expected one of [ 0to1, overlap, inverse, gutter ]\n", token );
+ return;
+ }
+ }
+
+ if ( !g_StudioMdlCheckUVCmd.DoAnyCheck() )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_ALL_FLAGS );
+ }
+}
+
+
+void Cmd_PoseParameter( )
+{
+ if ( g_numposeparameters >= MAXSTUDIOPOSEPARAM )
+ {
+ TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
+ }
+
+ int i = LookupPoseParameter( token );
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( g_pose[i].name, token );
+
+ if ( TokenAvailable() )
+ {
+ // min
+ GetToken (false);
+ g_pose[i].min = verify_atof (token);
+ }
+
+ if ( TokenAvailable() )
+ {
+ // max
+ GetToken (false);
+ g_pose[i].max = verify_atof (token);
+ }
+
+ while ( TokenAvailable() )
+ {
+ GetToken (false);
+
+ if ( !Q_stricmp( token, "wrap" ) )
+ {
+ g_pose[i].flags |= STUDIO_LOOPING;
+ g_pose[i].loop = g_pose[i].max - g_pose[i].min;
+ }
+ else if ( !Q_stricmp( token, "loop" ) )
+ {
+ g_pose[i].flags |= STUDIO_LOOPING;
+ GetToken (false);
+ g_pose[i].loop = verify_atof( token );
+ }
+ }
+}
+
+
+/*
+=================
+=================
+*/
+
+int LookupTexture( const char *pTextureName, bool bRelativePath )
+{
+ char pTextureNoExt[MAX_PATH];
+ char pTextureBase[MAX_PATH];
+ char pTextureBase2[MAX_PATH];
+ Q_StripExtension( pTextureName, pTextureNoExt, sizeof(pTextureNoExt) );
+ Q_FileBase( pTextureName, pTextureBase, sizeof(pTextureBase) );
+
+ int nFlags = bRelativePath ? RELATIVE_TEXTURE_PATH_SPECIFIED : 0;
+ int i;
+ for ( i = 0; i < g_numtextures; i++ )
+ {
+ if ( g_texture[i].flags == nFlags )
+ {
+ if ( !Q_stricmp( pTextureNoExt, g_texture[i].name ) )
+ return i;
+ continue;
+ }
+
+ // Comparing relative vs non-relative
+ if ( bRelativePath )
+ {
+ if ( !Q_stricmp( pTextureBase, g_texture[i].name ) )
+ return i;
+ continue;
+ }
+
+ // Comparing non-relative vs relative
+ Q_FileBase( g_texture[i].name, pTextureBase2, sizeof(pTextureBase2) );
+ if ( !Q_stricmp( pTextureNoExt, pTextureBase2 ) )
+ return i;
+ }
+
+ if ( i >= MAXSTUDIOSKINS )
+ {
+ MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS );
+ }
+
+ Q_strncpy( g_texture[i].name, pTextureNoExt, sizeof(g_texture[i].name) );
+ g_texture[i].material = -1;
+ g_texture[i].flags = nFlags;
+ g_numtextures++;
+ return i;
+}
+
+
+void Cmd_RenameMaterial( void )
+{
+ char from[256];
+ char to[256];
+
+ GetToken( false );
+ V_strcpy_safe( from, token );
+
+ GetToken( false );
+ V_strcpy_safe( to, token );
+
+ int i;
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if (stricmp( g_texture[i].name, from ) == 0)
+ {
+ V_strcpy_safe( g_texture[i].name, to );
+ return;
+ }
+ }
+ MdlError( "unknown material \"%s\" in rename\n", from );
+}
+
+
+int UseTextureAsMaterial( int textureindex )
+{
+ if ( g_texture[textureindex].material == -1 )
+ {
+ if (g_bDumpMaterials)
+ {
+ printf("material %d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name );
+ }
+ g_material[g_nummaterials] = textureindex;
+ g_texture[textureindex].material = g_nummaterials++;
+ }
+
+ return g_texture[textureindex].material;
+}
+
+int MaterialToTexture( int material )
+{
+ int i;
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if (g_texture[i].material == material)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+//Wrong name for the use of it.
+void scale_vertex( Vector &org )
+{
+ org[0] = org[0] * g_currentscale;
+ org[1] = org[1] * g_currentscale;
+ org[2] = org[2] * g_currentscale;
+}
+
+
+
+void SetSkinValues( )
+{
+ int i, j;
+ int index;
+
+ // Check all textures to see if we have relative paths specified
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if ( g_texture[i].flags & RELATIVE_TEXTURE_PATH_SPECIFIED )
+ {
+ // Add an empty path to prepend if anything specifies a relative path
+ cdtextures[numcdtextures] = 0;
+ ++numcdtextures;
+ break;
+ }
+ }
+
+ if ( numcdtextures == 0 )
+ {
+ char szName[MAX_PATH];
+
+ // strip down till it finds "models"
+ V_strcpy_safe( szName, fullpath );
+ while (szName[0] != '\0' && strnicmp( "models", szName, 6 ) != 0)
+ {
+ strcpy( &szName[0], &szName[1] );
+ }
+ if (szName[0] != '\0')
+ {
+ Q_StripFilename( szName );
+ V_strcat_safe( szName, "/" );
+ }
+ else
+ {
+// if( *g_pPlatformName )
+// {
+// V_strcat_safe( szName, "platform_" );
+// V_strcat_safe( szName, g_pPlatformName );
+// V_strcat_safe( szName, "/" );
+// }
+ V_strcpy_safe( szName, "models/" );
+ V_strcat_safe( szName, outname );
+ Q_StripExtension( szName, szName, sizeof( szName ) );
+ V_strcat_safe( szName, "/" );
+ }
+ cdtextures[0] = strdup( szName );
+ numcdtextures = 1;
+ }
+
+ for (i = 0; i < g_numtextures; i++)
+ {
+ char szName[256];
+ Q_StripExtension( g_texture[i].name, szName, sizeof( szName ) );
+ Q_strncpy( g_texture[i].name, szName, sizeof( g_texture[i].name ) );
+ }
+
+ // build texture groups
+ for (i = 0; i < MAXSTUDIOSKINS; i++)
+ {
+ for (j = 0; j < MAXSTUDIOSKINS; j++)
+ {
+ g_skinref[i][j] = j;
+ }
+ }
+ index = 0;
+ for (i = 0; i < g_numtexturelayers[0]; i++)
+ {
+ for (j = 0; j < g_numtexturereps[0]; j++)
+ {
+ g_skinref[i][g_texturegroup[0][0][j]] = g_texturegroup[0][i][j];
+ }
+ }
+
+ if (i != 0)
+ {
+ g_numskinfamilies = i;
+ }
+ else
+ {
+ g_numskinfamilies = 1;
+ }
+ g_numskinref = g_numtextures;
+
+ // printf ("width: %i height: %i\n",width, height);
+ /*
+ printf ("adjusted width: %i height: %i top : %i left: %i\n",
+ pmesh->skinwidth, pmesh->skinheight, pmesh->skintop, pmesh->skinleft );
+ */
+}
+
+/*
+=================
+=================
+*/
+
+
+int LookupXNode( char *name )
+{
+ int i;
+ for ( i = 1; i <= g_numxnodes; i++)
+ {
+ if (stricmp( name, g_xnodename[i] ) == 0)
+ {
+ return i;
+ }
+ }
+ g_xnodename[i] = strdup( name );
+ g_numxnodes = i;
+ return i;
+}
+
+
+/*
+=================
+=================
+*/
+
+char g_szFilename[1024];
+FILE *g_fpInput;
+char g_szLine[4096];
+int g_iLinecount;
+
+
+void Build_Reference( s_source_t *pSource, const char *pAnimName )
+{
+ int i, parent;
+ Vector angle;
+
+ s_sourceanim_t *pReferenceAnim = FindSourceAnim( pSource, pAnimName );
+ for (i = 0; i < pSource->numbones; i++)
+ {
+ matrix3x4_t m;
+ if ( pReferenceAnim )
+ {
+ AngleMatrix( pReferenceAnim->rawanim[0][i].rot, m );
+ m[0][3] = pReferenceAnim->rawanim[0][i].pos[0];
+ m[1][3] = pReferenceAnim->rawanim[0][i].pos[1];
+ m[2][3] = pReferenceAnim->rawanim[0][i].pos[2];
+ }
+ else
+ {
+ SetIdentityMatrix( m );
+ }
+
+ parent = pSource->localBone[i].parent;
+ if (parent == -1)
+ {
+ // scale the done pos.
+ // calc rotational matrices
+ MatrixCopy( m, pSource->boneToPose[i] );
+ }
+ else
+ {
+ // calc compound rotational matrices
+ // FIXME : Hey, it's orthogical so inv(A) == transpose(A)
+ Assert( parent < i );
+ ConcatTransforms( pSource->boneToPose[parent], m, pSource->boneToPose[i] );
+ }
+ // printf("%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] );
+ /*
+ AngleMatrix( angle, m );
+ printf("%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] );
+ printf("%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] );
+ printf("%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] );
+ */
+ }
+}
+
+
+
+
+int Grab_Nodes( s_node_t *pnodes )
+{
+ int index;
+ char name[1024];
+ int parent;
+ int numbones = 0;
+
+ for (index = 0; index < MAXSTUDIOSRCBONES; index++)
+ {
+ pnodes[index].parent = -1;
+ }
+
+ while (GetLineInput())
+ {
+ if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3)
+ {
+ // check for duplicated bones
+ /*
+ if (strlen(pnodes[index].name) != 0)
+ {
+ MdlError( "bone \"%s\" exists more than once\n", name );
+ }
+ */
+
+ V_strcpy_safe( pnodes[index].name, name );
+ pnodes[index].parent = parent;
+ if (index > numbones)
+ {
+ numbones = index;
+ }
+ }
+ else
+ {
+ return numbones + 1;
+ }
+ }
+ MdlError( "Unexpected EOF at line %d\n", g_iLinecount );
+ return 0;
+}
+
+
+
+
+void clip_rotations( RadianEuler& rot )
+{
+ int j;
+ // clip everything to : -M_PI <= x < M_PI
+
+ for (j = 0; j < 3; j++) {
+ while (rot[j] >= M_PI)
+ rot[j] -= M_PI*2;
+ while (rot[j] < -M_PI)
+ rot[j] += M_PI*2;
+ }
+}
+
+
+void clip_rotations( Vector& rot )
+{
+ int j;
+ // clip everything to : -180 <= x < 180
+
+ for (j = 0; j < 3; j++) {
+ while (rot[j] >= 180)
+ rot[j] -= 180*2;
+ while (rot[j] < -180)
+ rot[j] += 180*2;
+ }
+}
+
+
+
+/*
+=================
+Cmd_Eyeposition
+=================
+*/
+void Cmd_Eyeposition (void)
+{
+// rotate points into frame of reference so g_model points down the positive x
+// axis
+ // FIXME: these coords are bogus
+ GetToken (false);
+ eyeposition[1] = verify_atof (token);
+
+ GetToken (false);
+ eyeposition[0] = -verify_atof (token);
+
+ GetToken (false);
+ eyeposition[2] = verify_atof (token);
+}
+
+
+//-----------------------------------------------------------------------------
+// Cmd_MaxEyeDeflection
+//-----------------------------------------------------------------------------
+void Cmd_MaxEyeDeflection()
+{
+ GetToken( false );
+ g_flMaxEyeDeflection = cosf( verify_atof( token ) * M_PI / 180.0f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Cmd_Illumposition
+//-----------------------------------------------------------------------------
+void Cmd_Illumposition( void )
+{
+ GetToken( false );
+ illumposition[0] = verify_atof( token );
+
+ GetToken( false );
+ illumposition[1] = verify_atof( token );
+
+ GetToken( false );
+ illumposition[2] = verify_atof( token );
+
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+
+ Q_strncpy( g_attachment[g_numattachments].name, "__illumPosition", sizeof(g_attachment[g_numattachments].name) );
+ Q_strncpy( g_attachment[g_numattachments].bonename, token, sizeof(g_attachment[g_numattachments].bonename) );
+ AngleMatrix( QAngle( 0, 0, 0 ), illumposition, g_attachment[g_numattachments].local );
+ g_attachment[g_numattachments].type |= IS_RIGID;
+
+ g_illumpositionattachment = g_numattachments + 1;
+ ++g_numattachments;
+ }
+ else
+ {
+ g_illumpositionattachment = 0;
+
+ // rotate points into frame of reference so
+ // g_model points down the positive x axis
+ // FIXME: these coords are bogus
+ float flTemp = illumposition[0];
+ illumposition[0] = -illumposition[1];
+ illumposition[1] = flTemp;
+ }
+
+ illumpositionset = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process Cmd_Modelname
+//-----------------------------------------------------------------------------
+void ProcessModelName( const char *pModelName )
+{
+ // Abort early if modelname is too big
+ // - actually that's okay, it's just an identifier and can be truncated
+
+ g_bHasModelName = true;
+ Q_strncpy( outname, pModelName, sizeof( outname ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse Cmd_Modelname
+//-----------------------------------------------------------------------------
+void Cmd_Modelname (void)
+{
+ GetToken (false);
+ if ( token[0] == '/' || token[0] == '\\' )
+ {
+ MdlWarning( "$modelname key has slash as first character. Removing.\n" );
+ ProcessModelName( &token[1] );
+ }
+ else
+ {
+ ProcessModelName( token );
+ }
+}
+
+void Cmd_Autocenter()
+{
+ g_centerstaticprop = true;
+}
+
+/*
+===============
+===============
+*/
+
+
+//-----------------------------------------------------------------------------
+// Parse the body command from a .qc file
+//-----------------------------------------------------------------------------
+void ProcessOptionStudio( s_model_t *pmodel, const char *pFullPath, CDmeSourceSkin *pSkin )
+{
+ Q_strncpy( pmodel->filename, pFullPath, sizeof(pmodel->filename) );
+
+ if ( pSkin->m_flScale != 0.0f )
+ {
+ pmodel->scale = g_currentscale = pSkin->m_flScale;
+ }
+ else
+ {
+ pmodel->scale = g_currentscale = g_defaultscale;
+ }
+
+ // load source
+ pmodel->source = Load_Source( pmodel->filename, "", pSkin->m_bFlipTriangles, true );
+
+ // Reset currentscale to whatever global we currently have set
+ // g_defaultscale gets set in Cmd_ScaleUp everytime the $scale command is used.
+ g_currentscale = g_defaultscale;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the studio options from a .qc file
+//-----------------------------------------------------------------------------
+bool ParseOptionStudio( CDmeSourceSkin *pSkin )
+{
+ if ( !GetToken( false ) )
+ return false;
+
+ pSkin->SetRelativeFileName( token );
+ while ( TokenAvailable() )
+ {
+ GetToken(false);
+ if ( !Q_stricmp( "reverse", token ) )
+ {
+ pSkin->m_bFlipTriangles = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( "scale", token ) )
+ {
+ GetToken(false);
+ pSkin->m_flScale = verify_atof( token );
+ continue;
+ }
+
+ if ( !Q_stricmp( "faces", token ) )
+ {
+ GetToken( false );
+ GetToken( false );
+ continue;
+ }
+
+ if ( !Q_stricmp( "bias", token ) )
+ {
+ GetToken( false );
+ continue;
+ }
+
+ if ( !Q_stricmp( "{", token ) )
+ {
+ UnGetToken( );
+ break;
+ }
+
+ MdlError("unknown command \"%s\"\n", token );
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse + process the studio options from a .qc file
+//-----------------------------------------------------------------------------
+void Option_Studio( s_model_t *pmodel )
+{
+ CDmeSourceSkin *pSourceSkin = CreateElement< CDmeSourceSkin >( "" );
+
+ // Set defaults
+ pSourceSkin->m_flScale = g_defaultscale;
+
+ if ( ParseOptionStudio( pSourceSkin ) )
+ {
+ ProcessOptionStudio( pmodel, pSourceSkin->GetRelativeFileName(), pSourceSkin );
+ }
+ DestroyElement( pSourceSkin );
+}
+
+
+int Option_Blank( )
+{
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+
+ g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ g_model[g_nummodels]->source = g_source[g_numsources];
+ g_numsources++;
+
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+
+ V_strcpy_safe( g_model[g_nummodels]->name, "blank" );
+
+ g_bodypart[g_numbodyparts].nummodels++;
+ g_nummodels++;
+ return 0;
+}
+
+
+void Cmd_Bodygroup( )
+{
+ int is_started = 0;
+
+ if ( !GetToken( false ) )
+ return;
+
+ if (g_numbodyparts == 0)
+ {
+ g_bodypart[g_numbodyparts].base = 1;
+ }
+ else
+ {
+ g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
+ }
+ V_strcpy_safe( g_bodypart[g_numbodyparts].name, token );
+
+ do
+ {
+ GetToken (true);
+ if (endofscript)
+ return;
+ else if (token[0] == '{')
+ {
+ is_started = 1;
+ }
+ else if (token[0] == '}')
+ {
+ break;
+ }
+ else if (stricmp("studio", token ) == 0)
+ {
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+ g_bodypart[g_numbodyparts].nummodels++;
+
+ Option_Studio( g_model[g_nummodels] );
+
+ // Body command should add any flex commands in the source loaded
+ if ( g_model[g_nummodels]->source )
+ {
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ }
+
+ g_nummodels++;
+ }
+ else if (stricmp("blank", token ) == 0)
+ {
+ Option_Blank( );
+ }
+ else
+ {
+ MdlError("unknown bodygroup option: \"%s\"\n", token );
+ }
+ } while (1);
+
+ g_numbodyparts++;
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Add A Body Flex Rule
+//-----------------------------------------------------------------------------
+void AddBodyFlexFetchRule(
+ s_source_t *pSource,
+ s_flexrule_t *pRule,
+ int rawIndex,
+ const CUtlVector< int > &pRawIndexToRemapSourceIndex,
+ const CUtlVector< int > &pRawIndexToRemapLocalIndex,
+ const CUtlVector< int > &pRemapSourceIndexToGlobalFlexControllerIndex )
+{
+ // Lookup the various indices of the requested input to fetch
+ // Relative to the remapped controls in the current s_source_t
+ const int remapSourceIndex = pRawIndexToRemapSourceIndex[ rawIndex ];
+ // Relative to the specific remapped control
+ const int remapLocalIndex = pRawIndexToRemapLocalIndex[ rawIndex ];
+ // The global flex controller index that the user ultimately twiddles
+ const int globalFlexControllerIndex = pRemapSourceIndexToGlobalFlexControllerIndex[ remapSourceIndex ];
+
+ // Get the Remap record
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[ remapSourceIndex ];
+ switch ( remap.m_RemapType )
+ {
+ case FLEXCONTROLLER_REMAP_PASSTHRU:
+ // Easy As!
+ pRule->op[ pRule->numops ].op = STUDIO_FETCH1;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ break;
+
+ case FLEXCONTROLLER_REMAP_EYELID:
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_EyesUpDownFlexController >= 0 ? remap.m_EyesUpDownFlexController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_BlinkController >= 0 ? remap.m_BlinkController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = globalFlexControllerIndex; // CloseLid
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_DME_LOWER_EYELID;
+ pRule->op[ pRule->numops ].d.index = remap.m_MultiIndex; // CloseLidV
+ pRule->numops++;
+ }
+ else
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_EyesUpDownFlexController >= 0 ? remap.m_EyesUpDownFlexController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_BlinkController >= 0 ? remap.m_BlinkController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = globalFlexControllerIndex; // CloseLid
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_DME_UPPER_EYELID;
+ pRule->op[ pRule->numops ].d.index = remap.m_MultiIndex; // CloseLidV
+ pRule->numops++;
+ }
+ break;
+
+ case FLEXCONTROLLER_REMAP_2WAY:
+ // A little trickier... local index 0 is on the left, local index 1 is on the right
+ // Left Equivalent RemapVal( -1.0, 0.0, 0.0, 1.0 )
+ // Right Equivalent RemapVal( 0.0, 1.0, 0.0, 1.0 )
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_2WAY_0;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ else
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_2WAY_1;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ break;
+
+ case FLEXCONTROLLER_REMAP_NWAY:
+ {
+ int nRemapCount = remap.m_RawControls.Count();
+ float flStep = ( nRemapCount > 2 ) ? 2.0f / ( nRemapCount - 1 ) : 0.0f;
+
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -11.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -10.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -1.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -1.0f + flStep;
+ pRule->numops++;
+ }
+ else if ( remapLocalIndex == nRemapCount - 1 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f - flStep;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 10.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 11.0f;
+ pRule->numops++;
+ }
+ else
+ {
+ float flPeak = remapLocalIndex * flStep - 1.0f;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak - flStep;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak + flStep;
+ pRule->numops++;
+ }
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_MultiIndex;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_NWAY;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ break;
+ default:
+ Assert( 0 );
+ // This is an error condition
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f;
+ pRule->numops++;
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Add A Body Flex Rule
+//-----------------------------------------------------------------------------
+void AddBodyFlexRule(
+ s_source_t *pSource,
+ s_combinationrule_t &rule,
+ int nFlexDesc,
+ const CUtlVector< int > &pRawIndexToRemapSourceIndex,
+ const CUtlVector< int > &pRawIndexToRemapLocalIndex,
+ const CUtlVector< int > &pRemapSourceIndexToGlobalFlexControllerIndex )
+{
+ if ( g_numflexrules >= MAXSTUDIOFLEXRULES )
+ MdlError( "Line %d: Too many flex rules, max %d",
+ g_iLinecount, MAXSTUDIOFLEXRULES );
+
+ s_flexrule_t *pRule = &g_flexrule[g_numflexrules++];
+ pRule->flex = nFlexDesc;
+
+ // This will multiply the combination together
+ const int nCombinationCount = rule.m_Combination.Count();
+ if ( nCombinationCount )
+ {
+ for ( int j = 0; j < nCombinationCount; ++j )
+ {
+ // Handle any controller remapping
+ AddBodyFlexFetchRule( pSource, pRule, rule.m_Combination[ j ],
+ pRawIndexToRemapSourceIndex, pRawIndexToRemapLocalIndex,
+ pRemapSourceIndexToGlobalFlexControllerIndex );
+ }
+
+ if ( nCombinationCount > 1 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_COMBO;
+ pRule->op[ pRule->numops ].d.index = nCombinationCount;
+ pRule->numops++;
+ }
+ }
+
+ // This will multiply in the suppressors
+ int nDominators = rule.m_Dominators.Count();
+ for ( int j = 0; j < nDominators; ++j )
+ {
+ const int nFactorCount = rule.m_Dominators[j].Count();
+ if ( nFactorCount )
+ {
+ for ( int k = 0; k < nFactorCount; ++k )
+ {
+ AddBodyFlexFetchRule( pSource, pRule, rule.m_Dominators[ j ][ k ],
+ pRawIndexToRemapSourceIndex, pRawIndexToRemapLocalIndex,
+ pRemapSourceIndexToGlobalFlexControllerIndex );
+ }
+
+ pRule->op[ pRule->numops ].op = STUDIO_DOMINATE;
+ pRule->op[ pRule->numops ].d.index = nFactorCount;
+ pRule->numops++;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flex controller data to a particular source
+//-----------------------------------------------------------------------------
+void AddFlexControllers(
+ s_source_t *pSource )
+{
+ CUtlVector< int > &r2s = pSource->m_rawIndexToRemapSourceIndex;
+ CUtlVector< int > &r2l = pSource->m_rawIndexToRemapLocalIndex;
+ CUtlVector< int > &l2i = pSource->m_leftRemapIndexToGlobalFlexControllIndex;
+ CUtlVector< int > &r2i = pSource->m_rightRemapIndexToGlobalFlexControllIndex;
+
+ // Number of Raw controls in this source
+ const int nRawControlCount = pSource->m_CombinationControls.Count();
+ // Initialize rawToRemapIndices
+ r2s.SetSize( nRawControlCount );
+ r2l.SetSize( nRawControlCount );
+ for ( int i = 0; i < nRawControlCount; ++i )
+ {
+ r2s[ i ] = -1;
+ r2l[ i ] = -1;
+ }
+
+ // Number of Remapped Controls in this source
+ const int nRemappedControlCount = pSource->m_FlexControllerRemaps.Count();
+ l2i.SetSize( nRemappedControlCount );
+ r2i.SetSize( nRemappedControlCount );
+
+ for ( int i = 0; i < nRemappedControlCount; ++i )
+ {
+ s_flexcontrollerremap_t &remapControl = pSource->m_FlexControllerRemaps[ i ];
+
+ // Number of Raw Controls In This Remapped Control
+ const int nRemappedRawControlCount = remapControl.m_RawControls.Count();
+
+ // Figure out the mapping from raw to remapped
+ for ( int j = 0; j < nRemappedRawControlCount; ++j )
+ {
+ for ( int k = 0; k < nRawControlCount; ++k )
+ {
+ if ( remapControl.m_RawControls[ j ] == pSource->m_CombinationControls[ k ].name )
+ {
+ Assert( r2s[ k ] == -1 );
+ Assert( r2l[ k ] == -1 );
+ r2s[ k ] = i; // The index of the remapped control
+ r2l[ k ] = j; // The index of which control this is in the remap
+ break;
+ }
+ }
+ }
+
+ if ( remapControl.m_bIsStereo )
+ {
+ // The controls have to be named 'right_' and 'left_' and right has to be first for
+ // hlfaceposer to recognize them
+
+ // See if we can add two more flex controllers
+ if ( ( g_numflexcontrollers + 1 ) >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add split control %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ s_flexcontroller_t *pController;
+
+ int nLen = remapControl.m_Name.Length();
+ char *pTemp = (char*)_alloca( nLen + 7 ); // 'left_' && 'right_'
+
+ memcpy( pTemp + 6, remapControl.m_Name.Get(), nLen + 1 );
+ memcpy( pTemp, "right_", 6 );
+ pTemp[nLen + 6] = '\0';
+
+ remapControl.m_RightIndex = g_numflexcontrollers;
+ r2i[ i ] = g_numflexcontrollers;
+ pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+
+ memcpy( pTemp + 5, remapControl.m_Name.Get(), nLen + 1 );
+ memcpy( pTemp, "left_", 5 );
+ pTemp[nLen + 5] = '\0';
+
+ remapControl.m_LeftIndex = g_numflexcontrollers;
+ l2i[ i ] = g_numflexcontrollers;
+ pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+ else
+ {
+ // See if we can add one more flex controller
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add control %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_Index = g_numflexcontrollers;
+ r2i[ i ] = g_numflexcontrollers;
+ l2i[ i ] = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, remapControl.m_Name.Get(), sizeof( pController->name ) );
+ Q_strncpy( pController->type, remapControl.m_Name.Get(), sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_NWAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add value control for nWay %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_MultiIndex = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+ const int nLen = remapControl.m_Name.Length();
+ char *pTemp = ( char * )_alloca( nLen + 6 + 1 ); // 'multi_' + 1 for the NULL
+
+ memcpy( pTemp, "multi_", 6 );
+ memcpy( pTemp + 6, remapControl.m_Name.Get(), nLen + 1 );
+ pTemp[nLen+6] = '\0';
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ // Make a blink controller
+
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add value control for nWay %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_BlinkController = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+
+ Q_strncpy( pController->name, "blink", sizeof( pController->name ) );
+ Q_strncpy( pController->type, "blink", sizeof( pController->type ) );
+
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+
+#ifdef _DEBUG
+ for ( int j = 0; j != nRawControlCount; ++j )
+ {
+ Assert( r2s[ j ] != -1 );
+ Assert( r2l[ j ] != -1 );
+ }
+#endif // def _DEBUG
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flex controller remappers
+//-----------------------------------------------------------------------------
+void AddBodyFlexRemaps( s_source_t *pSource )
+{
+ int nCount = pSource->m_FlexControllerRemaps.Count();
+ for( int i = 0; i < nCount; ++i )
+ {
+ int k = g_FlexControllerRemap.AddToTail();
+ s_flexcontrollerremap_t &remap = g_FlexControllerRemap[k];
+ remap = pSource->m_FlexControllerRemaps[i];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void AddBodyFlexRules( s_source_t *pSource )
+{
+ const int nRemapCount = pSource->m_FlexControllerRemaps.Count();
+ for ( int i = 0; i < nRemapCount; ++i )
+ {
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[ i ];
+ if ( remap.m_RemapType == FLEXCONTROLLER_REMAP_EYELID && !remap.m_EyesUpDownFlexName.IsEmpty() )
+ {
+ for ( int j = 0; j < g_numflexcontrollers; ++j )
+ {
+ if ( !Q_strcmp( g_flexcontroller[ j ].name, remap.m_EyesUpDownFlexName.Get() ) )
+ {
+ Assert( remap.m_EyesUpDownFlexController == -1 );
+ remap.m_EyesUpDownFlexController = j;
+ break;
+ }
+ }
+ }
+ }
+
+ const int nCount = pSource->m_CombinationRules.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_combinationrule_t &rule = pSource->m_CombinationRules[i];
+ s_flexkey_t &flexKey = g_flexkey[ pSource->m_nKeyStartIndex + rule.m_nFlex ];
+ AddBodyFlexRule( pSource, rule, flexKey.flexdesc,
+ pSource->m_rawIndexToRemapSourceIndex, pSource->m_rawIndexToRemapLocalIndex, pSource->m_leftRemapIndexToGlobalFlexControllIndex );
+ if ( flexKey.flexpair != 0 )
+ {
+ AddBodyFlexRule( pSource, rule, flexKey.flexpair,
+ pSource->m_rawIndexToRemapSourceIndex, pSource->m_rawIndexToRemapLocalIndex, pSource->m_rightRemapIndexToGlobalFlexControllIndex );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Process a body command
+//-----------------------------------------------------------------------------
+void AddBodyFlexData( s_source_t *pSource, int imodel )
+{
+ pSource->m_nKeyStartIndex = g_numflexkeys;
+
+ // Add flex keys
+ int nCount = pSource->m_FlexKeys.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_flexkey_t &key = pSource->m_FlexKeys[i];
+
+ if ( g_numflexkeys >= MAXSTUDIOFLEXKEYS )
+ MdlError( "Line %d: Too many flex keys, max %d, cannot add flexKey %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXKEYS, key.animationname, pSource->filename );
+
+ memcpy( &g_flexkey[g_numflexkeys], &key, sizeof(s_flexkey_t) );
+ g_flexkey[g_numflexkeys].imodel = imodel;
+
+ // flexpair was set up in AddFlexKey
+ if ( key.flexpair )
+ {
+ char mod[512];
+ Q_snprintf( mod, sizeof(mod), "%sL", key.animationname );
+ g_flexkey[g_numflexkeys].flexdesc = Add_Flexdesc( mod );
+ Q_snprintf( mod, sizeof(mod), "%sR", key.animationname );
+ g_flexkey[g_numflexkeys].flexpair = Add_Flexdesc( mod );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys].flexdesc = Add_Flexdesc( key.animationname );
+ g_flexkey[g_numflexkeys].flexpair = 0;
+ }
+
+ ++g_numflexkeys;
+ }
+
+ AddFlexControllers( pSource );
+
+ AddBodyFlexRemaps( pSource );
+}
+
+//-----------------------------------------------------------------------------
+// Comparison operator for s_attachment_t
+//-----------------------------------------------------------------------------
+bool s_attachment_t::operator==( const s_attachment_t &rhs ) const
+{
+ if ( Q_strcmp( name, rhs.name ) )
+ return false;
+
+ if ( Q_stricmp( bonename, rhs.bonename ) ||
+ bone != rhs.bone ||
+ type != rhs.type ||
+ flags != rhs.flags ||
+ Q_memcmp( local.Base(), rhs.local.Base(), sizeof( local ) ) )
+ {
+ RadianEuler iEuler, jEuler;
+ Vector iPos, jPos;
+ MatrixAngles( local, iEuler, iPos );
+ MatrixAngles( rhs.local, jEuler, jPos );
+ MdlWarning(
+ "Attachments with the same name but different parameters found\n"
+ " %s: ParentBone: %s Type: %d Flags: 0x%08x P: %6.2f %6.2f %6.2f R: %6.2f %6.2f %6.2f\n"
+ " %s: ParentBone: %s Type: %d Flags: 0x%08x P: %6.2f %6.2f %6.2f R: %6.2f %6.2f %6.2f\n",
+ name, bonename, type, flags,
+ iPos.x, iPos.y, iPos.z, RAD2DEG( iEuler.x ), RAD2DEG( iEuler.y ), RAD2DEG( iEuler.z ),
+ rhs.name, rhs.bonename, rhs.type, rhs.flags,
+ jPos.x, jPos.y, jPos.z, RAD2DEG( jEuler.x ), RAD2DEG( jEuler.y ), RAD2DEG( jEuler.z ) );
+
+ return false;
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Add attachments from the s_source_t that aren't already present in the
+// global attachment list. At this point, the attachments aren't linked
+// to the bone, but since that is done by string matching on the bone name
+// the test for an attachment being a duplicate is still valid this early.
+//-----------------------------------------------------------------------------
+void AddBodyAttachments( s_source_t *pSource )
+{
+ for ( int i = 0; i < pSource->m_Attachments.Count(); ++i )
+ {
+ const s_attachment_t &sourceAtt = pSource->m_Attachments[i];
+
+ bool bDuplicate = false;
+
+ for ( int j = 0; j < g_numattachments; ++j )
+ {
+ if ( sourceAtt == g_attachment[j] )
+ {
+ bDuplicate = true;
+ break;
+ }
+ }
+
+ if ( bDuplicate )
+ continue;
+
+ if ( g_numattachments >= ARRAYSIZE( g_attachment ) )
+ {
+ MdlWarning( "Too Many Attachments (Max %d), Ignoring Attachment %s:%s\n",
+ ARRAYSIZE( g_attachment ), pSource->filename, pSource->m_Attachments[i].name );
+ continue;;
+ }
+
+ memcpy( &g_attachment[g_numattachments], &( pSource->m_Attachments[i] ), sizeof( s_attachment_t ) );
+ ++g_numattachments;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Process a body command
+//-----------------------------------------------------------------------------
+void ProcessCmdBody( const char *pFullPath, CDmeSourceSkin *pSkin )
+{
+ if ( g_numbodyparts == 0 )
+ {
+ g_bodypart[g_numbodyparts].base = 1;
+ }
+ else
+ {
+ g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
+ }
+ Q_strncpy( g_bodypart[g_numbodyparts].name, pSkin->m_SkinName.Get(), sizeof(g_bodypart[g_numbodyparts].name) );
+
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+ g_bodypart[g_numbodyparts].nummodels = 1;
+
+ ProcessOptionStudio( g_model[g_nummodels], pFullPath, pSkin );
+
+ // Body command should add any flex commands in the source loaded
+ if ( g_model[g_nummodels]->source )
+ {
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ AddBodyFlexRules( g_model[ g_nummodels ]->source );
+ }
+
+ g_nummodels++;
+ g_numbodyparts++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the body command from a .qc file
+//-----------------------------------------------------------------------------
+void Cmd_Body( )
+{
+ if ( !GetToken(false) )
+ return;
+
+ CDmeSourceSkin *pSourceSkin = CreateElement< CDmeSourceSkin >( "" );
+
+ // Set defaults
+ pSourceSkin->m_flScale = g_defaultscale;
+
+ pSourceSkin->m_SkinName = token;
+ if ( ParseOptionStudio( pSourceSkin ) )
+ {
+ ProcessCmdBody( pSourceSkin->GetRelativeFileName(), pSourceSkin );
+ }
+ DestroyElement( pSourceSkin );
+}
+
+
+
+/*
+===============
+===============
+*/
+
+void Grab_Animation( s_source_t *pSource, const char *pAnimName )
+{
+ Vector pos;
+ RadianEuler rot;
+ char cmd[1024];
+ int index;
+ int t = -99999999;
+ int size;
+
+ s_sourceanim_t *pAnim = FindOrAddSourceAnim( pSource, pAnimName );
+ pAnim->startframe = -1;
+
+ size = pSource->numbones * sizeof( s_bone_t );
+
+ while ( GetLineInput() )
+ {
+ if ( sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 )
+ {
+ if ( pAnim->startframe < 0 )
+ {
+ MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ scale_vertex( pos );
+ VectorCopy( pos, pAnim->rawanim[t][index].pos );
+ VectorCopy( rot, pAnim->rawanim[t][index].rot );
+
+ clip_rotations( rot ); // !!!
+ continue;
+ }
+
+ if ( sscanf( g_szLine, "%1023s %d", cmd, &index ) == 0 )
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ continue;
+ }
+
+ if ( !Q_stricmp( cmd, "time" ) )
+ {
+ t = index;
+ if ( pAnim->startframe == -1 )
+ {
+ pAnim->startframe = t;
+ }
+ if ( t < pAnim->startframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ if ( t > pAnim->endframe )
+ {
+ pAnim->endframe = t;
+ }
+ t -= pAnim->startframe;
+
+ if ( t >= pAnim->rawanim.Count())
+ {
+ s_bone_t *ptr = NULL;
+ pAnim->rawanim.AddMultipleToTail( t - pAnim->rawanim.Count() + 1, &ptr );
+ }
+
+ if ( pAnim->rawanim[t] != NULL )
+ {
+ continue;
+ }
+
+ pAnim->rawanim[t] = (s_bone_t *)kalloc( 1, size );
+
+ // duplicate previous frames keys
+ if ( t > 0 && pAnim->rawanim[t-1] )
+ {
+ for ( int j = 0; j < pSource->numbones; j++ )
+ {
+ VectorCopy( pAnim->rawanim[t-1][j].pos, pAnim->rawanim[t][j].pos );
+ VectorCopy( pAnim->rawanim[t-1][j].rot, pAnim->rawanim[t][j].rot );
+ }
+ }
+ continue;
+ }
+
+ if ( !Q_stricmp( cmd, "end" ) )
+ {
+ pAnim->numframes = pAnim->endframe - pAnim->startframe + 1;
+
+ for ( t = 0; t < pAnim->numframes; t++ )
+ {
+ if ( pAnim->rawanim[t] == NULL)
+ {
+ MdlError( "%s is missing frame %d\n", pSource->filename, t + pAnim->startframe );
+ }
+ }
+
+ Build_Reference( pSource, pAnimName );
+ return;
+ }
+
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ MdlError( "unexpected EOF: %s\n", pSource->filename );
+}
+
+
+
+
+
+int Option_Activity( s_sequence_t *psequence )
+{
+ qboolean found;
+
+ found = false;
+
+ GetToken(false);
+ V_strcpy_safe( psequence->activityname, token );
+
+ GetToken(false);
+ psequence->actweight = verify_atoi(token);
+
+ if ( psequence->actweight == 0 )
+ {
+ TokenError( "Activity %s has a zero weight (weights must be integers > 0)\n", psequence->activityname );
+ }
+
+ return 0;
+}
+
+
+int Option_ActivityModifier( s_sequence_t *psequence )
+{
+ GetToken(false);
+ V_strcpy_safe( psequence->activitymodifier[ psequence->numactivitymodifiers++ ].name, token );
+
+ return 0;
+}
+
+
+/*
+===============
+===============
+*/
+
+
+int Option_Event ( s_sequence_t *psequence )
+{
+ if (psequence->numevents + 1 >= MAXSTUDIOEVENTS)
+ {
+ TokenError("too many events\n");
+ }
+
+ GetToken (false);
+
+ V_strcpy_safe( psequence->event[psequence->numevents].eventname, token );
+
+ GetToken( false );
+ psequence->event[psequence->numevents].frame = verify_atoi( token );
+
+ psequence->numevents++;
+
+ // option token
+ if (TokenAvailable())
+ {
+ GetToken( false );
+ if (token[0] == '}') // opps, hit the end
+ return 1;
+ // found an option
+ V_strcpy_safe( psequence->event[psequence->numevents-1].options, token );
+ }
+
+ return 0;
+}
+
+
+
+void Option_IKRule( s_ikrule_t *pRule )
+{
+ // chain
+ GetToken( false );
+
+ int i;
+ for ( i = 0; i < g_numikchains; i++)
+ {
+ if (stricmp( token, g_ikchain[i].name ) == 0)
+ {
+ break;
+ }
+ }
+ if (i >= g_numikchains)
+ {
+ TokenError( "unknown chain \"%s\" in ikrule\n", token );
+ }
+ pRule->chain = i;
+ // default slot
+ pRule->slot = i;
+
+ // type
+ GetToken( false );
+ if (stricmp( token, "touch" ) == 0)
+ {
+ pRule->type = IK_SELF;
+
+ // bone
+ GetToken( false );
+ V_strcpy_safe( pRule->bonename, token );
+ }
+ else if (stricmp( token, "footstep" ) == 0)
+ {
+ pRule->type = IK_GROUND;
+
+ pRule->height = g_ikchain[pRule->chain].height;
+ pRule->floor = g_ikchain[pRule->chain].floor;
+ pRule->radius = g_ikchain[pRule->chain].radius;
+ }
+ else if (stricmp( token, "attachment" ) == 0)
+ {
+ pRule->type = IK_ATTACHMENT;
+
+ // name of attachment
+ GetToken( false );
+ V_strcpy_safe( pRule->attachment, token );
+ }
+ else if (stricmp( token, "release" ) == 0)
+ {
+ pRule->type = IK_RELEASE;
+ }
+ else if (stricmp( token, "unlatch" ) == 0)
+ {
+ pRule->type = IK_UNLATCH;
+ }
+
+ pRule->contact = -1;
+
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ if (stricmp( token, "height" ) == 0)
+ {
+ GetToken( false );
+ pRule->height = verify_atof( token );
+ }
+ else if (stricmp( token, "target" ) == 0)
+ {
+ // slot
+ GetToken( false );
+ pRule->slot = verify_atoi( token );
+ }
+ else if (stricmp( token, "range" ) == 0)
+ {
+ // ramp
+ GetToken( false );
+ if (token[0] == '.')
+ pRule->start = -1;
+ else
+ pRule->start = verify_atoi( token );
+
+ GetToken( false );
+ if (token[0] == '.')
+ pRule->peak = -1;
+ else
+ pRule->peak = verify_atoi( token );
+
+ GetToken( false );
+ if (token[0] == '.')
+ pRule->tail = -1;
+ else
+ pRule->tail = verify_atoi( token );
+
+ GetToken( false );
+ if (token[0] == '.')
+ pRule->end = -1;
+ else
+ pRule->end = verify_atoi( token );
+ }
+ else if (stricmp( token, "floor" ) == 0)
+ {
+ GetToken( false );
+ pRule->floor = verify_atof( token );
+ }
+ else if (stricmp( token, "pad" ) == 0)
+ {
+ GetToken( false );
+ pRule->radius = verify_atof( token ) / 2.0f;
+ }
+ else if (stricmp( token, "radius" ) == 0)
+ {
+ GetToken( false );
+ pRule->radius = verify_atof( token );
+ }
+ else if (stricmp( token, "contact" ) == 0)
+ {
+ GetToken( false );
+ pRule->contact = verify_atoi( token );
+ }
+ else if (stricmp( token, "usesequence" ) == 0)
+ {
+ pRule->usesequence = true;
+ pRule->usesource = false;
+ }
+ else if (stricmp( token, "usesource" ) == 0)
+ {
+ pRule->usesequence = false;
+ pRule->usesource = true;
+ }
+ else if (stricmp( token, "fakeorigin" ) == 0)
+ {
+ GetToken( false );
+ pRule->pos.x = verify_atof( token );
+ GetToken( false );
+ pRule->pos.y = verify_atof( token );
+ GetToken( false );
+ pRule->pos.z = verify_atof( token );
+
+ pRule->bone = -1;
+ }
+ else if (stricmp( token, "fakerotate" ) == 0)
+ {
+ QAngle ang;
+
+ GetToken( false );
+ ang.x = verify_atof( token );
+ GetToken( false );
+ ang.y = verify_atof( token );
+ GetToken( false );
+ ang.z = verify_atof( token );
+
+ AngleQuaternion( ang, pRule->q );
+
+ pRule->bone = -1;
+ }
+ else if (stricmp( token, "bone" ) == 0)
+ {
+ V_strcpy_safe( pRule->bonename, token );
+ }
+ else
+ {
+ UnGetToken();
+ return;
+ }
+ }
+}
+
+
+/*
+=================
+Cmd_Origin
+=================
+*/
+void Cmd_Origin (void)
+{
+ GetToken (false);
+ g_defaultadjust.x = verify_atof (token);
+
+ GetToken (false);
+ g_defaultadjust.y = verify_atof (token);
+
+ GetToken (false);
+ g_defaultadjust.z = verify_atof (token);
+
+ if (TokenAvailable())
+ {
+ GetToken (false);
+ g_defaultrotation.z = DEG2RAD( verify_atof( token ) + 90);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the default root rotation so that the Y axis is up instead of the Z axis (for Maya)
+//-----------------------------------------------------------------------------
+void ProcessUpAxis( const RadianEuler &angles )
+{
+ g_defaultrotation = angles;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the default root rotation so that the Y axis is up instead of the Z axis (for Maya)
+//-----------------------------------------------------------------------------
+void Cmd_UpAxis( void )
+{
+ // We want to create a rotation that rotates from the art space
+ // (specified by the up direction) to a z up space
+ // Note: x, -x, -y are untested
+ RadianEuler angles( 0.0f, 0.0f, M_PI / 2.0f );
+ GetToken (false);
+ if (!Q_stricmp( token, "x" ))
+ {
+ // rotate 90 degrees around y to move x into z
+ angles.x = 0.0f;
+ angles.y = M_PI / 2.0f;
+ }
+ else if (!Q_stricmp( token, "-x" ))
+ {
+ // untested
+ angles.x = 0.0f;
+ angles.y = -M_PI / 2.0f;
+ }
+ else if (!Q_stricmp( token, "y" ))
+ {
+ // rotate 90 degrees around x to move y into z
+ angles.x = M_PI / 2.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "-y" ))
+ {
+ // untested
+ angles.x = -M_PI / 2.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "z" ))
+ {
+ // there's still a built in 90 degree Z rotation :(
+ angles.x = 0.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "-z" ))
+ {
+ // there's still a built in 90 degree Z rotation :(
+ angles.x = 0.0f;
+ angles.y = 0.0f;
+ }
+ else
+ {
+ TokenError( "unknown $upaxis option: \"%s\"\n", token );
+ return;
+ }
+
+ ProcessUpAxis( angles );
+}
+
+
+/*
+=================
+=================
+*/
+void Cmd_ScaleUp (void)
+{
+ GetToken (false);
+ g_defaultscale = verify_atof (token);
+
+ g_currentscale = g_defaultscale;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets how what size chunks to cut the animations into
+//-----------------------------------------------------------------------------
+void Cmd_AnimBlockSize( void )
+{
+ GetToken( false );
+ g_animblocksize = verify_atoi( token );
+ if (g_animblocksize < 1024)
+ {
+ g_animblocksize *= 1024;
+ }
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ if (!Q_stricmp( token, "nostall" ))
+ {
+ g_bNoAnimblockStall = true;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+static void FlipFacing( s_source_t *pSrc )
+{
+ unsigned short tmp;
+
+ int i, j;
+ for( i = 0; i < pSrc->nummeshes; i++ )
+ {
+ s_mesh_t *pMesh = &pSrc->mesh[i];
+ for( j = 0; j < pMesh->numfaces; j++ )
+ {
+ s_face_t &f = pSrc->face[pMesh->faceoffset + j];
+ tmp = f.b; f.b = f.c; f.c = tmp;
+ }
+ }
+}
+
+
+// Processes source comment line and extracts information about the data file
+void ProcessSourceComment( s_source_t *psource, const char *pCommentString )
+{
+ if ( char const *szSceneComment = StringAfterPrefix( pCommentString, "// SCENE=" ) )
+ {
+ char szScene[1024];
+ Q_strncpy( szScene, szSceneComment, ARRAYSIZE( szScene ) );
+
+ Q_FixSlashes( szScene );
+
+ ProcessOriginalContentFile( psource->filename, szScene );
+ }
+}
+
+// Processes original content file "szOriginalContentFile" that was used to generate
+// data file "szDataFile"
+void ProcessOriginalContentFile( char const *szDataFile, char const *szOriginalContentFile )
+{
+ // Early out: if no p4
+ if ( g_bNoP4 )
+ return;
+
+ char const *szContentDirRootEnd = strstr( szDataFile, "\\content\\" );
+ char const *szSceneName = strstr( szOriginalContentFile, "\\content\\" );
+ if ( szContentDirRootEnd && szSceneName )
+ {
+ char chScenePath[ MAX_PATH ] = {0};
+ Q_snprintf( chScenePath, sizeof( chScenePath ) - 1, "%.*s%s",
+ szContentDirRootEnd - szDataFile, szDataFile, szSceneName );
+ EnsureDependencyFileCheckedIn( chScenePath );
+ }
+ else if ( szContentDirRootEnd && !szSceneName )
+ {
+ // Assume relative path
+ char chScenePath[ MAX_PATH ] = {0};
+ Q_snprintf( chScenePath, sizeof( chScenePath ) - 1, "%.*s%s",
+ max( strrchr( szDataFile, '\\' ), strrchr( szDataFile, '/' ) ) + 1 - szDataFile,
+ szDataFile, szOriginalContentFile );
+ EnsureDependencyFileCheckedIn( chScenePath );
+ }
+ else
+ {
+ MdlWarning( "ProcessOriginalContentFile for '%s' cannot detect scene source file from '%s'!\n", szDataFile, szOriginalContentFile );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks to see if the model source was already loaded
+//-----------------------------------------------------------------------------
+static s_source_t *FindCachedSource( const char* name, const char* xext )
+{
+ int i;
+
+ if( xext[0] )
+ {
+ // we know what extension is necessary. . look for it.
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.%s", cddir[numdirs], name, xext );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ }
+ else
+ {
+ // we don't know what extension to use, so look for all of 'em.
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.vrm", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.xml", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.obj", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ /*
+ sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
+ return g_source[i];
+ }
+ */
+ }
+
+ // Not found
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads an animation/model source
+//-----------------------------------------------------------------------------
+s_source_t *Load_Source( const char *name, const char *ext, bool reverse, bool isActiveModel )
+{
+ if ( g_numsources >= MAXSTUDIOSEQUENCES )
+ TokenError( "Load_Source( %s ) - overflowed g_numsources.", name );
+
+ Assert(name);
+ int namelen = strlen(name) + 1;
+ char* pTempName = (char*)_alloca( namelen );
+ char xext[32];
+ int result = false;
+
+ V_strncpy( pTempName, name, namelen );
+ Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) );
+
+ if (xext[0] == '\0')
+ {
+ V_strcpy_safe( xext, ext );
+ }
+ else
+ {
+ Q_StripExtension( pTempName, pTempName, namelen );
+ }
+
+ s_source_t* pSource = FindCachedSource( pTempName, xext );
+ if (pSource)
+ {
+ if (isActiveModel)
+ pSource->isActiveModel = true;
+
+ return pSource;
+ }
+
+ g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );
+
+
+ if (isActiveModel)
+ {
+ g_source[g_numsources]->isActiveModel = true;
+ }
+
+ char const * load_extensions[] = { "vrm", "smd", "sma", "phys", "vta", "obj", "dmx", "xml" };
+ int ( *load_procs[] )( s_source_t * ) = { Load_VRM, Load_SMD, Load_SMD, Load_SMD, Load_VTA, Load_OBJ, Load_DMX, Load_DMX };
+
+ Assert( ARRAYSIZE(load_extensions) == ARRAYSIZE(load_procs) );
+ for ( int kk = 0; kk < ARRAYSIZE( load_extensions ); ++ kk )
+ {
+ if ( ( !result && xext[0] == '\0' ) || Q_stricmp( xext, load_extensions[kk] ) == 0)
+ {
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.%s", cddir[numdirs], pTempName, load_extensions[kk] );
+ V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );
+ result = (load_procs[kk])( g_source[g_numsources] );
+
+ if ( result )
+ EnsureDependencyFileCheckedIn( g_source[g_numsources]->filename );
+ }
+ }
+
+ if (!g_bCreateMakefile && !result)
+ {
+ if (xext[0] == '\0')
+ TokenError( "could not load file '%s%s'\n", cddir[numdirs], pTempName );
+ else
+ TokenError( "could not load file '%s%s.%s'\n", cddir[numdirs], pTempName, xext );
+ }
+
+ if ( g_source[g_numsources]->numbones == 0 )
+ {
+ TokenError( "missing all bones in file '%s'\n", g_source[g_numsources]->filename );
+ }
+
+ // copy over default settings of when the model was loaded (since there's no actual animation for some of the systems)
+ VectorCopy( g_defaultadjust, g_source[g_numsources]->adjust );
+ g_source[g_numsources]->scale = 1.0f;
+ g_source[g_numsources]->rotation = g_defaultrotation;
+
+
+ g_numsources++;
+ if( reverse )
+ {
+ FlipFacing( g_source[g_numsources-1] );
+ }
+
+ return g_source[g_numsources-1];
+}
+
+
+s_sequence_t *LookupSequence( const char *name )
+{
+ int i;
+ for ( i = 0; i < g_sequence.Count(); ++i )
+ {
+ if ( !Q_stricmp( g_sequence[i].name, name ) )
+ return &g_sequence[i];
+ }
+ return NULL;
+}
+
+
+s_animation_t *LookupAnimation( const char *name )
+{
+ int i;
+ for ( i = 0; i < g_numani; i++)
+ {
+ if ( !Q_stricmp( g_panimation[i]->name, name ) )
+ return g_panimation[i];
+ }
+
+ s_sequence_t *pseq = LookupSequence( name );
+ return pseq ? pseq->panim[0][0] : NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: parse order dependant s_animcmd_t token for $animations
+//-----------------------------------------------------------------------------
+int ParseCmdlistToken( int &numcmds, s_animcmd_t *cmds )
+{
+ if (numcmds >= MAXSTUDIOCMDS)
+ {
+ return false;
+ }
+ s_animcmd_t *pcmd = &cmds[numcmds];
+ if (stricmp("fixuploop", token ) == 0)
+ {
+ pcmd->cmd = CMD_FIXUP;
+
+ GetToken( false );
+ pcmd->u.fixuploop.start = verify_atoi( token );
+ GetToken( false );
+ pcmd->u.fixuploop.end = verify_atoi( token );
+ }
+ else if (strnicmp("weightlist", token, 6 ) == 0)
+ {
+ GetToken( false );
+
+ int i;
+ for ( i = 1; i < g_numweightlist; i++)
+ {
+ if (stricmp( g_weightlist[i].name, token ) == 0)
+ {
+ break;
+ }
+ }
+ if (i == g_numweightlist)
+ {
+ TokenError( "unknown weightlist '%s\'\n", token );
+ }
+ pcmd->cmd = CMD_WEIGHTS;
+ pcmd->u.weightlist.index = i;
+ }
+ else if (stricmp("subtract", token ) == 0)
+ {
+ pcmd->cmd = CMD_SUBTRACT;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown subtract animation '%s\'\n", token );
+ }
+
+ pcmd->u.subtract.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.subtract.frame = verify_atoi( token );
+
+ pcmd->u.subtract.flags |= STUDIO_POST;
+ }
+ else if (stricmp("presubtract", token ) == 0) // FIXME: rename this to something better
+ {
+ pcmd->cmd = CMD_SUBTRACT;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown presubtract animation '%s\'\n", token );
+ }
+
+ pcmd->u.subtract.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.subtract.frame = verify_atoi( token );
+ }
+ else if (stricmp( "alignto", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ pcmd->u.ao.pBonename = NULL;
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+ pcmd->u.ao.motiontype = STUDIO_X | STUDIO_Y;
+ pcmd->u.ao.srcframe = 0;
+ pcmd->u.ao.destframe = 0;
+ }
+ else if (stricmp( "align", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ pcmd->u.ao.pBonename = NULL;
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown align animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+
+ // motion type to match
+ pcmd->u.ao.motiontype = 0;
+ GetToken( false );
+ int ctrl;
+ while ((ctrl = lookupControl( token )) != -1)
+ {
+ pcmd->u.ao.motiontype |= ctrl;
+ GetToken( false );
+ }
+ if (pcmd->u.ao.motiontype == 0)
+ {
+ TokenError( "missing controls on align\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.ao.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.ao.destframe = verify_atoi( token );
+ }
+ else if (stricmp( "alignboneto", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ GetToken( false );
+ pcmd->u.ao.pBonename = strdup( token );
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignboneto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+ pcmd->u.ao.motiontype = STUDIO_X | STUDIO_Y;
+ pcmd->u.ao.srcframe = 0;
+ pcmd->u.ao.destframe = 0;
+ }
+ else if (stricmp( "alignbone", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ GetToken( false );
+ pcmd->u.ao.pBonename = strdup( token );
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignboneto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+
+ // motion type to match
+ pcmd->u.ao.motiontype = 0;
+ GetToken( false );
+ int ctrl;
+ while ((ctrl = lookupControl( token )) != -1)
+ {
+ pcmd->u.ao.motiontype |= ctrl;
+ GetToken( false );
+ }
+ if (pcmd->u.ao.motiontype == 0)
+ {
+ TokenError( "missing controls on align\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.ao.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.ao.destframe = verify_atoi( token );
+ }
+ else if (stricmp( "match", token ) == 0)
+ {
+ pcmd->cmd = CMD_MATCH;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown match animation '%s\'\n", token );
+ }
+
+ pcmd->u.match.ref = extanim;
+ }
+ else if (stricmp( "matchblend", token ) == 0)
+ {
+ pcmd->cmd = CMD_MATCHBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ MdlError( "unknown match animation '%s\'\n", token );
+ }
+
+ pcmd->u.match.ref = extanim;
+
+ // frame of reference animation to match
+ GetToken( false );
+ pcmd->u.match.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.match.destframe = verify_atoi( token );
+
+ // backup and starting match in here
+ GetToken( false );
+ pcmd->u.match.destpre = verify_atoi( token );
+
+ // continue blending match till here
+ GetToken( false );
+ pcmd->u.match.destpost = verify_atoi( token );
+
+ }
+ else if (stricmp( "worldspaceblend", token ) == 0)
+ {
+ pcmd->cmd = CMD_WORLDSPACEBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown worldspaceblend animation '%s\'\n", token );
+ }
+
+ pcmd->u.world.ref = extanim;
+ pcmd->u.world.startframe = 0;
+ pcmd->u.world.loops = false;
+ }
+ else if (stricmp( "worldspaceblendloop", token ) == 0)
+ {
+ pcmd->cmd = CMD_WORLDSPACEBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown worldspaceblend animation '%s\'\n", token );
+ }
+
+ pcmd->u.world.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.world.startframe = atoi( token );
+
+ pcmd->u.world.loops = true;
+ }
+ else if (stricmp( "rotateto", token ) == 0)
+ {
+ pcmd->cmd = CMD_ANGLE;
+
+ GetToken( false );
+ pcmd->u.angle.angle = verify_atof( token );
+ }
+ else if (stricmp( "ikrule", token ) == 0)
+ {
+ pcmd->cmd = CMD_IKRULE;
+
+ pcmd->u.ikrule.pRule = (s_ikrule_t *)kalloc( 1, sizeof( s_ikrule_t ) );
+
+ Option_IKRule( pcmd->u.ikrule.pRule );
+ }
+ else if (stricmp( "ikfixup", token ) == 0)
+ {
+ pcmd->cmd = CMD_IKFIXUP;
+
+ pcmd->u.ikfixup.pRule = (s_ikrule_t *)kalloc( 1, sizeof( s_ikrule_t ) );
+
+ Option_IKRule( pcmd->u.ikrule.pRule );
+ }
+ else if (stricmp( "walkframe", token ) == 0)
+ {
+ pcmd->cmd = CMD_MOTION;
+
+ // frame
+ GetToken( false );
+ pcmd->u.motion.iEndFrame = verify_atoi( token );
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ UnGetToken();
+ break;
+ }
+ }
+
+ /*
+ GetToken( false ); // X
+ pcmd->u.motion.x = verify_atof( token );
+
+ GetToken( false ); // Y
+ pcmd->u.motion.y = verify_atof( token );
+
+ GetToken( false ); // A
+ pcmd->u.motion.zr = verify_atof( token );
+ */
+ }
+ else if (stricmp( "walkalignto", token ) == 0)
+ {
+ pcmd->cmd = CMD_REFMOTION;
+
+ GetToken( false );
+ pcmd->u.motion.iEndFrame = verify_atoi( token );
+
+ pcmd->u.motion.iSrcFrame = pcmd->u.motion.iEndFrame;
+
+ GetToken( false ); // reference animation
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignto animation '%s\'\n", token );
+ }
+ pcmd->u.motion.pRefAnim = extanim;
+
+ pcmd->u.motion.iRefFrame = 0;
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ UnGetToken();
+ break;
+ }
+ }
+
+
+ /*
+ GetToken( false ); // X
+ pcmd->u.motion.x = verify_atof( token );
+
+ GetToken( false ); // Y
+ pcmd->u.motion.y = verify_atof( token );
+
+ GetToken( false ); // A
+ pcmd->u.motion.zr = verify_atof( token );
+ */
+ }
+ else if (stricmp( "walkalign", token ) == 0)
+ {
+ pcmd->cmd = CMD_REFMOTION;
+
+ // end frame to apply motion over
+ GetToken( false );
+ pcmd->u.motion.iEndFrame = verify_atoi( token );
+
+ // reference animation
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignto animation '%s\'\n", token );
+ }
+ pcmd->u.motion.pRefAnim = extanim;
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (pcmd->u.motion.motiontype == 0)
+ {
+ TokenError( "missing controls on walkalign\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.motion.iRefFrame = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.motion.iSrcFrame = verify_atoi( token );
+ }
+ else if (stricmp("derivative", token ) == 0)
+ {
+ pcmd->cmd = CMD_DERIVATIVE;
+
+ // get scale
+ GetToken( false );
+ pcmd->u.derivative.scale = verify_atof( token );
+ }
+ else if (stricmp("noanimation", token ) == 0)
+ {
+ pcmd->cmd = CMD_NOANIMATION;
+ }
+ else if (stricmp("lineardelta", token ) == 0)
+ {
+ pcmd->cmd = CMD_LINEARDELTA;
+ pcmd->u.linear.flags |= STUDIO_AL_POST;
+ }
+ else if (stricmp("splinedelta", token ) == 0)
+ {
+ pcmd->cmd = CMD_LINEARDELTA;
+ pcmd->u.linear.flags |= STUDIO_AL_POST;
+ pcmd->u.linear.flags |= STUDIO_AL_SPLINE;
+ }
+ else if (stricmp("compress", token ) == 0)
+ {
+ pcmd->cmd = CMD_COMPRESS;
+
+ // get frames to skip
+ GetToken( false );
+ pcmd->u.compress.frames = verify_atoi( token );
+ }
+ else if (stricmp("numframes", token ) == 0)
+ {
+ pcmd->cmd = CMD_NUMFRAMES;
+
+ // get frames to force
+ GetToken( false );
+ pcmd->u.compress.frames = verify_atoi( token );
+ }
+ else if (stricmp("counterrotate", token ) == 0)
+ {
+ pcmd->cmd = CMD_COUNTERROTATE;
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.counterrotate.pBonename = strdup( token );
+ }
+ else if (stricmp("counterrotateto", token ) == 0)
+ {
+ pcmd->cmd = CMD_COUNTERROTATE;
+
+ pcmd->u.counterrotate.bHasTarget = true;
+
+ // get pitch
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[0] = verify_atof( token );
+
+ // get yaw
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[1] = verify_atof( token );
+
+ // get roll
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[2] = verify_atof( token );
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.counterrotate.pBonename = strdup( token );
+ }
+ else if (stricmp("localhierarchy", token ) == 0)
+ {
+ pcmd->cmd = CMD_LOCALHIERARCHY;
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.localhierarchy.pBonename = strdup( token );
+
+ // get parent name
+ GetToken( false );
+ pcmd->u.localhierarchy.pParentname = strdup( token );
+
+ pcmd->u.localhierarchy.start = -1;
+ pcmd->u.localhierarchy.peak = -1;
+ pcmd->u.localhierarchy.tail = -1;
+ pcmd->u.localhierarchy.end = -1;
+
+ if (TokenAvailable())
+ {
+ GetToken( false );
+ if (stricmp( token, "range" ) == 0)
+ {
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.start = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.peak = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.tail = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.end = verify_atof_with_null( token );
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ numcmds++;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: parse order independant s_animation_t token for $animations
+//-----------------------------------------------------------------------------
+bool ParseAnimationToken( s_animation_t *panim )
+{
+ if ( !Q_stricmp( "if", token ) )
+ {
+ // fixme: add expression evaluation
+ GetToken( false );
+ if (atoi( token ) == 0 && stricmp( token, "true" ) != 0)
+ {
+ GetToken(true);
+ if (token[0] == '{')
+ {
+ int depth = 1;
+ while (TokenAvailable() && depth > 0)
+ {
+ GetToken( true );
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ if ( !Q_stricmp( "fps", token ) )
+ {
+ GetToken( false );
+ panim->fps = verify_atof( token );
+ if ( panim->fps <= 0.0f )
+ {
+ TokenError( "ParseAnimationToken: fps (%f from '%s') <= 0.0\n", panim->fps, token );
+ }
+ return true;
+ }
+
+ if ( !Q_stricmp( "origin", token ) )
+ {
+ GetToken (false);
+ panim->adjust.x = verify_atof (token);
+
+ GetToken (false);
+ panim->adjust.y = verify_atof (token);
+
+ GetToken (false);
+ panim->adjust.z = verify_atof (token);
+ return true;
+ }
+
+ if ( !Q_stricmp( "rotate", token ) )
+ {
+ GetToken( false );
+ // FIXME: broken for Maya
+ panim->rotation.z = DEG2RAD( verify_atof( token ) + 90 );
+ return true;
+ }
+
+ if ( !Q_stricmp( "angles", token ) )
+ {
+ GetToken( false );
+ panim->rotation.x = DEG2RAD( verify_atof( token ) );
+ GetToken( false );
+ panim->rotation.y = DEG2RAD( verify_atof( token ) );
+ GetToken( false );
+ panim->rotation.z = DEG2RAD( verify_atof( token ) + 90.0f);
+ return true;
+ }
+
+ if ( !Q_stricmp( "scale", token ) )
+ {
+ GetToken( false );
+ panim->scale = verify_atof( token );
+ return true;
+ }
+
+ if ( !Q_strnicmp( "loop", token, 4 ) )
+ {
+ panim->flags |= STUDIO_LOOPING;
+ return true;
+ }
+
+ if ( !Q_strnicmp( "startloop", token, 5 ) )
+ {
+ GetToken( false );
+ panim->looprestart = verify_atoi( token );
+ panim->flags |= STUDIO_LOOPING;
+ return true;
+ }
+
+ if ( !Q_stricmp( "fudgeloop", token ) )
+ {
+ panim->fudgeloop = true;
+ panim->flags |= STUDIO_LOOPING;
+ return true;
+ }
+
+ if ( !Q_strnicmp( "snap", token, 4 ) )
+ {
+ panim->flags |= STUDIO_SNAP;
+ return true;
+ }
+
+ if ( !Q_strnicmp( "frame", token, 5 ) )
+ {
+ GetToken( false );
+ panim->startframe = verify_atoi( token );
+ GetToken( false );
+ panim->endframe = verify_atoi( token );
+
+ // NOTE: This always affects the first source anim read in
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, panim->animationname );
+ if ( pSourceAnim )
+ {
+ if ( panim->startframe < pSourceAnim->startframe )
+ {
+ panim->startframe = pSourceAnim->startframe;
+ }
+
+ if ( panim->endframe > pSourceAnim->endframe )
+ {
+ panim->endframe = pSourceAnim->endframe;
+ }
+ }
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ return true;
+ }
+
+ if ( !Q_stricmp( "blockname", token ) )
+ {
+ GetToken( false );
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, token );
+
+ // NOTE: This always affects the first source anim read in
+ if ( pSourceAnim )
+ {
+ panim->startframe = pSourceAnim->startframe;
+ panim->endframe = pSourceAnim->endframe;
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+ Q_strncpy( panim->animationname, token, sizeof(panim->animationname) );
+ }
+ else
+ {
+ MdlError( "Requested unknown animation block name %s\n", token );
+ }
+ return true;
+ }
+
+ if ( !Q_stricmp( "post", token ) )
+ {
+ panim->flags |= STUDIO_POST;
+ return true;
+ }
+
+ if ( !Q_stricmp( "noautoik", token ) )
+ {
+ panim->noAutoIK = true;
+ return true;
+ }
+
+ if ( !Q_stricmp( "autoik", token ) )
+ {
+ panim->noAutoIK = false;
+ return true;
+ }
+
+ if ( ParseCmdlistToken( panim->numcmds, panim->cmds ) )
+ return true;
+
+ if ( !Q_stricmp( "cmdlist", token ) )
+ {
+ GetToken( false ); // A
+
+ int i;
+ for ( i = 0; i < g_numcmdlists; i++)
+ {
+ if (stricmp( g_cmdlist[i].name, token) == 0)
+ {
+ break;
+ }
+ }
+ if (i == g_numcmdlists)
+ TokenError( "unknown cmdlist %s\n", token );
+
+ for (int j = 0; j < g_cmdlist[i].numcmds; j++)
+ {
+ if (panim->numcmds >= MAXSTUDIOCMDS)
+ {
+ TokenError("Too many cmds in %s\n", panim->name );
+ }
+ panim->cmds[panim->numcmds++] = g_cmdlist[i].cmds[j];
+ }
+ return true;
+ }
+
+ if ( !Q_stricmp( "motionrollback", token ) )
+ {
+ GetToken( false );
+ panim->motionrollback = atof( token );
+ return true;
+ }
+
+ if ( !Q_stricmp( "noanimblock", token ) )
+ {
+ panim->disableAnimblocks = true;
+ return true;
+ }
+
+ if ( !Q_stricmp( "noanimblockstall", token ) )
+ {
+ panim->isFirstSectionLocal = true;
+ return true;
+ }
+
+ if ( lookupControl( token ) != -1 )
+ {
+ panim->motiontype |= lookupControl( token );
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: create named order dependant s_animcmd_t blocks, used as replicated token list for $animations
+//-----------------------------------------------------------------------------
+
+void Cmd_Cmdlist( )
+{
+ int depth = 0;
+
+ // name
+ GetToken(false);
+ V_strcpy_safe( g_cmdlist[g_numcmdlists].name, token );
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (ParseCmdlistToken( g_cmdlist[g_numcmdlists].numcmds, g_cmdlist[g_numcmdlists].cmds ))
+ {
+
+ }
+ else
+ {
+ TokenError( "unknown command: %s\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ g_numcmdlists++;
+}
+
+int ParseAnimation( s_animation_t *panim, bool isAppend );
+int ParseEmpty( void );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: allocate an entry for $animation
+//-----------------------------------------------------------------------------
+void Cmd_Animation( )
+{
+ // name
+ GetToken(false);
+
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim != NULL)
+ {
+ if (!panim->isOverride)
+ {
+ TokenError( "Duplicate animation name \"%s\"\n", token );
+ }
+ else
+ {
+ panim->doesOverride = true;
+ ParseEmpty();
+ return;
+ }
+ }
+
+ // allocate animation entry
+ g_panimation[g_numani] = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani]->index = g_numani;
+ panim = g_panimation[g_numani];
+ V_strcpy_safe( panim->name, token );
+ g_numani++;
+
+ // filename
+ GetToken(false);
+ V_strcpy_safe( panim->filename, token );
+
+ panim->source = Load_Source( panim->filename, "" );
+ if ( panim->source->m_Animations.Count() )
+ {
+ s_sourceanim_t *pSourceAnim = &panim->source->m_Animations[0];
+ panim->startframe = pSourceAnim->startframe;
+ panim->endframe = pSourceAnim->endframe;
+ Q_strncpy( panim->animationname, pSourceAnim->animationname, sizeof(panim->animationname) );
+ }
+ else
+ {
+ panim->startframe = 0;
+ panim->endframe = 0;
+ Q_strncpy( panim->animationname, "", sizeof(panim->animationname) );
+ }
+ VectorCopy( g_defaultadjust, panim->adjust );
+ panim->rotation = g_defaultrotation;
+ panim->scale = 1.0f;
+ panim->fps = 30.0;
+ panim->motionrollback = g_flDefaultMotionRollback;
+
+ ParseAnimation( panim, false );
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ //CheckAutoShareAnimationGroup( panim->name );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: wrapper for parsing $animation tokens
+//-----------------------------------------------------------------------------
+
+int ParseAnimation( s_animation_t *panim, bool isAppend )
+{
+ int depth = 0;
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return 1;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (ParseAnimationToken( panim ))
+ {
+
+ }
+ else
+ {
+ TokenError( "Unknown animation option\'%s\'\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: create a virtual $animation command from a $sequence reference
+//-----------------------------------------------------------------------------
+s_animation_t *Cmd_ImpliedAnimation( s_sequence_t *psequence, const char *filename )
+{
+ // allocate animation entry
+ g_panimation[g_numani] = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani]->index = g_numani;
+ s_animation_t *panim = g_panimation[g_numani];
+ g_numani++;
+
+ panim->isImplied = true;
+
+ panim->startframe = 0;
+ panim->endframe = MAXSTUDIOANIMFRAMES - 1;
+
+ V_strcpy_safe( panim->name, "@" );
+ V_strcat_safe( panim->name, psequence->name );
+ V_strcpy_safe( panim->filename, filename );
+
+ VectorCopy( g_defaultadjust, panim->adjust );
+ panim->scale = 1.0f;
+ panim->rotation = g_defaultrotation;
+ panim->fps = 30;
+ panim->motionrollback = g_flDefaultMotionRollback;
+
+ //panim->source = Load_Source( panim->filename, "smd" );
+ panim->source = Load_Source( panim->filename, "" );
+ if ( panim->source->m_Animations.Count() )
+ {
+ s_sourceanim_t *pSourceAnim = &panim->source->m_Animations[0];
+ Q_strncpy( panim->animationname, panim->source->m_Animations[0].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 ) );
+ }
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ //CheckAutoShareAnimationGroup( panim->name );
+
+ return panim;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: copy globally reavent $animation options from one $animation to another
+//-----------------------------------------------------------------------------
+
+void CopyAnimationSettings( s_animation_t *pdest, s_animation_t *psrc )
+{
+ pdest->fps = psrc->fps;
+
+ VectorCopy( psrc->adjust, pdest->adjust );
+ pdest->scale = psrc->scale;
+ pdest->rotation = psrc->rotation;
+
+ pdest->motiontype = psrc->motiontype;
+
+ //Adrian - Hey! Revisit me later.
+ /*if (pdest->startframe < psrc->startframe)
+ pdest->startframe = psrc->startframe;
+
+ if (pdest->endframe > psrc->endframe)
+ pdest->endframe = psrc->endframe;
+
+ if (pdest->endframe < pdest->startframe)
+ TokenError( "fixedup end frame before start frame in %s", pdest->name );
+
+ pdest->numframes = pdest->endframe - pdest->startframe + 1;*/
+
+ for (int i = 0; i < psrc->numcmds; i++)
+ {
+ if (pdest->numcmds >= MAXSTUDIOCMDS)
+ {
+ TokenError("Too many cmds in %s\n", pdest->name );
+ }
+ pdest->cmds[pdest->numcmds++] = psrc->cmds[i];
+ }
+}
+
+int ParseSequence( s_sequence_t *pseq, bool isAppend );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: allocate an entry for $sequence
+//-----------------------------------------------------------------------------
+s_sequence_t *ProcessCmdSequence( const char *pSequenceName )
+{
+ s_animation_t *panim = LookupAnimation( pSequenceName );
+
+ // allocate sequence
+ if ( panim != NULL )
+ {
+ if ( !panim->isOverride )
+ {
+ TokenError( "Duplicate sequence name \"%s\"\n", pSequenceName );
+ }
+ else
+ {
+ panim->doesOverride = true;
+ return NULL;
+ }
+ }
+
+ if ( g_sequence.Count() >= MAXSTUDIOSEQUENCES )
+ {
+ TokenError("Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES );
+ }
+
+ s_sequence_t *pseq = &g_sequence[ g_sequence.AddToTail() ];
+ memset( pseq, 0, sizeof( s_sequence_t ) );
+
+ // initialize sequence
+ Q_strncpy( pseq->name, pSequenceName, sizeof(pseq->name) );
+
+ pseq->actweight = 0;
+ pseq->activityname[0] = '\0';
+ pseq->activity = -1; // -1 is the default for 'no activity'
+
+ pseq->paramindex[0] = -1;
+ pseq->paramindex[1] = -1;
+
+ pseq->groupsize[0] = 0;
+ pseq->groupsize[1] = 0;
+
+ pseq->fadeintime = 0.2;
+ pseq->fadeouttime = 0.2;
+ return pseq;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the sequence command
+//-----------------------------------------------------------------------------
+void Cmd_Sequence( )
+{
+ if ( !GetToken(false) )
+ return;
+
+ // Find existing sequences
+ const char *pSequenceName = token;
+ s_animation_t *panim = LookupAnimation( pSequenceName );
+ if ( panim != NULL && panim->isOverride )
+ {
+ ParseEmpty( );
+ }
+
+ s_sequence_t *pseq = ProcessCmdSequence( pSequenceName );
+ if ( pseq )
+ {
+ ParseSequence( pseq, false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Performs processing on a sequence
+//-----------------------------------------------------------------------------
+void ProcessSequence( s_sequence_t *pseq, int numblends, s_animation_t **animations, bool isAppend )
+{
+ if (isAppend)
+ return;
+
+ if ( numblends == 0 )
+ {
+ TokenError("no animations found\n");
+ }
+
+ if ( pseq->groupsize[0] == 0 )
+ {
+ if (numblends < 4)
+ {
+ pseq->groupsize[0] = numblends;
+ pseq->groupsize[1] = 1;
+ }
+ else
+ {
+ int i = sqrt( (float) numblends );
+ if (i * i == numblends)
+ {
+ pseq->groupsize[0] = i;
+ pseq->groupsize[1] = i;
+ }
+ else
+ {
+ TokenError( "non-square (%d) number of blends without \"blendwidth\" set\n", numblends );
+ }
+ }
+ }
+ else
+ {
+ pseq->groupsize[1] = numblends / pseq->groupsize[0];
+
+ if (pseq->groupsize[0] * pseq->groupsize[1] != numblends)
+ {
+ TokenError( "missing animation blends. Expected %d, found %d\n",
+ pseq->groupsize[0] * pseq->groupsize[1], numblends );
+ }
+ }
+
+ for (int i = 0; i < numblends; i++)
+ {
+ int j = i % pseq->groupsize[0];
+ int k = i / pseq->groupsize[0];
+
+ pseq->panim[j][k] = animations[i];
+
+ if (i > 0 && animations[i]->isImplied)
+ {
+ CopyAnimationSettings( animations[i], animations[0] );
+ }
+ animations[i]->isImplied = false; // don't copy any more commands
+ pseq->flags |= animations[i]->flags;
+ }
+
+ pseq->numblends = numblends;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parse options unique to $sequence
+//-----------------------------------------------------------------------------
+int ParseSequence( s_sequence_t *pseq, bool isAppend )
+{
+ int depth = 0;
+ s_animation_t *animations[64];
+ int i, j, n;
+ int numblends = 0;
+
+ if (isAppend)
+ {
+ animations[0] = pseq->panim[0][0];
+ }
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return 1;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ /*
+ else if (stricmp("deform", token ) == 0)
+ {
+ Option_Deform( pseq );
+ }
+ */
+
+ else if (stricmp("event", token ) == 0)
+ {
+ depth -= Option_Event( pseq );
+ }
+ else if (stricmp("activity", token ) == 0)
+ {
+ Option_Activity( pseq );
+ }
+ else if (stricmp("activitymodifier", token ) == 0)
+ {
+ Option_ActivityModifier( pseq );
+ }
+ else if (strnicmp( token, "ACT_", 4 ) == 0)
+ {
+ UnGetToken( );
+ Option_Activity( pseq );
+ }
+
+ else if (stricmp("snap", token ) == 0)
+ {
+ pseq->flags |= STUDIO_SNAP;
+ }
+
+ else if (stricmp("blendwidth", token ) == 0)
+ {
+ GetToken( false );
+ pseq->groupsize[0] = verify_atoi( token );
+ }
+
+ else if (stricmp("blend", token ) == 0)
+ {
+ i = 0;
+ if (pseq->paramindex[0] != -1)
+ {
+ i = 1;
+ }
+
+ GetToken( false );
+ j = LookupPoseParameter( token );
+ pseq->paramindex[i] = j;
+ pseq->paramattachment[i] = -1;
+ GetToken( false );
+ pseq->paramstart[i] = verify_atof( token );
+ GetToken( false );
+ pseq->paramend[i] = verify_atof( token );
+
+ g_pose[j].min = min( g_pose[j].min, pseq->paramstart[i] );
+ g_pose[j].min = min( g_pose[j].min, pseq->paramend[i] );
+ g_pose[j].max = max( g_pose[j].max, pseq->paramstart[i] );
+ g_pose[j].max = max( g_pose[j].max, pseq->paramend[i] );
+ }
+ else if (stricmp("calcblend", token ) == 0)
+ {
+ i = 0;
+ if (pseq->paramindex[0] != -1)
+ {
+ i = 1;
+ }
+
+ GetToken( false );
+ j = LookupPoseParameter( token );
+ pseq->paramindex[i] = j;
+
+ GetToken( false );
+ pseq->paramattachment[i] = LookupAttachment( token );
+ if (pseq->paramattachment[i] == -1)
+ {
+ TokenError( "Unknown calcblend attachment \"%s\"\n", token );
+ }
+
+ GetToken( false );
+ pseq->paramcontrol[i] = lookupControl( token );
+ }
+ else if (stricmp("blendref", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramanim = LookupAnimation( token );
+ if (pseq->paramanim == NULL)
+ {
+ TokenError( "Unknown blendref animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("blendcomp", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramcompanim = LookupAnimation( token );
+ if (pseq->paramcompanim == NULL)
+ {
+ TokenError( "Unknown blendcomp animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("blendcenter", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramcenter = LookupAnimation( token );
+ if (pseq->paramcenter == NULL)
+ {
+ TokenError( "Unknown blendcenter animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("node", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = pseq->exitnode = LookupXNode( token );
+ }
+ else if (stricmp("transition", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = LookupXNode( token );
+ GetToken( false );
+ pseq->exitnode = LookupXNode( token );
+ }
+ else if (stricmp("rtransition", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = LookupXNode( token );
+ GetToken( false );
+ pseq->exitnode = LookupXNode( token );
+ pseq->nodeflags |= 1;
+ }
+ else if (stricmp("exitphase", token ) == 0)
+ {
+ GetToken( false );
+ pseq->exitphase = verify_atof( token );
+ }
+ else if (stricmp("delta", token) == 0)
+ {
+ pseq->flags |= STUDIO_DELTA;
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("worldspace", token) == 0)
+ {
+ pseq->flags |= STUDIO_WORLD;
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("post", token) == 0) // remove
+ {
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("predelta", token) == 0)
+ {
+ pseq->flags |= STUDIO_DELTA;
+ }
+ else if (stricmp("autoplay", token) == 0)
+ {
+ pseq->flags |= STUDIO_AUTOPLAY;
+ }
+ else if (stricmp( "fadein", token ) == 0)
+ {
+ GetToken( false );
+ pseq->fadeintime = verify_atof( token );
+ }
+ else if (stricmp( "fadeout", token ) == 0)
+ {
+ GetToken( false );
+ pseq->fadeouttime = verify_atof( token );
+ }
+ else if (stricmp( "realtime", token ) == 0)
+ {
+ pseq->flags |= STUDIO_REALTIME;
+ }
+ else if (stricmp( "posecycle", token ) == 0)
+ {
+ pseq->flags |= STUDIO_CYCLEPOSE;
+
+ GetToken( false );
+ pseq->cycleposeindex = LookupPoseParameter( token );
+ }
+ else if (stricmp( "hidden", token ) == 0)
+ {
+ pseq->flags |= STUDIO_HIDDEN;
+ }
+ else if (stricmp( "addlayer", token ) == 0)
+ {
+ GetToken( false );
+ V_strcpy_safe( pseq->autolayer[pseq->numautolayers].name, token );
+ pseq->numautolayers++;
+ }
+ else if (stricmp( "iklock", token ) == 0)
+ {
+ GetToken(false);
+ V_strcpy_safe( pseq->iklock[pseq->numiklocks].name, token );
+
+ GetToken(false);
+ pseq->iklock[pseq->numiklocks].flPosWeight = verify_atof( token );
+
+ GetToken(false);
+ pseq->iklock[pseq->numiklocks].flLocalQWeight = verify_atof( token );
+
+ pseq->numiklocks++;
+ }
+ else if (stricmp( "keyvalues", token ) == 0)
+ {
+ Option_KeyValues( &pseq->KeyValue );
+ }
+ else if (stricmp( "blendlayer", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags = 0;
+
+ GetToken( false );
+ V_strcpy_safe( pseq->autolayer[pseq->numautolayers].name, token );
+
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].start = verify_atof( token );
+
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].peak = verify_atof( token );
+
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].tail = verify_atof( token );
+
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].end = verify_atof( token );
+
+ while (TokenAvailable( ))
+ {
+ GetToken( false );
+ if (stricmp( "xfade", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_XFADE;
+ }
+ else if (stricmp( "spline", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_SPLINE;
+ }
+ else if (stricmp( "noblend", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_NOBLEND;
+ }
+ else if (stricmp( "poseparameter", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_POSE;
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].pose = LookupPoseParameter( token );
+ }
+ else if (stricmp( "local", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_LOCAL;
+ pseq->flags |= STUDIO_LOCAL;
+ }
+ else
+ {
+ UnGetToken();
+ break;
+ }
+ }
+
+ pseq->numautolayers++;
+ }
+ else if ((numblends || isAppend) && ParseAnimationToken( animations[0] ))
+ {
+
+ }
+ else if (!isAppend)
+ {
+ // assume it's an animation reference
+ // first look up an existing animation
+ for (n = 0; n < g_numani; n++)
+ {
+ if (stricmp( token, g_panimation[n]->name ) == 0)
+ {
+ animations[numblends++] = g_panimation[n];
+ break;
+ }
+ }
+
+ if (n >= g_numani)
+ {
+ // assume it's an implied animation
+ animations[numblends++] = Cmd_ImpliedAnimation( pseq, token );
+ }
+ // hack to allow animation commands to refer to same sequence
+ if (numblends == 1)
+ {
+ pseq->panim[0][0] = animations[0];
+ }
+
+ }
+ else
+ {
+ TokenError( "unknown command \"%s\"\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ }
+
+ ProcessSequence( pseq, numblends, animations, isAppend );
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: throw away all the options for a specific sequence or animation
+//-----------------------------------------------------------------------------
+
+int ParseEmpty( )
+{
+ int depth = 0;
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return 1;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: append commands to either a sequence or an animation
+//-----------------------------------------------------------------------------
+void Cmd_Append( )
+{
+ GetToken(false);
+
+
+ s_sequence_t *pseq = LookupSequence( token );
+
+ if (pseq)
+ {
+ ParseSequence( pseq, true );
+ return;
+ }
+ else
+ {
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim)
+ {
+ ParseAnimation( panim, true );
+ return;
+ }
+ }
+ TokenError( "unknown append animation %s\n", token );
+}
+
+
+
+void Cmd_Prepend( )
+{
+ GetToken(false);
+
+ s_sequence_t *pseq = LookupSequence( token );
+ int count = 0;
+ s_animation_t *panim = NULL;
+ int iRet = false;
+
+ if (pseq)
+ {
+ panim = pseq->panim[0][0];
+ count = panim->numcmds;
+ iRet = ParseSequence( pseq, true );
+ }
+ else
+ {
+ panim = LookupAnimation( token );
+ if (panim)
+ {
+ count = panim->numcmds;
+ iRet = ParseAnimation( panim, true );
+ }
+ }
+ if (panim && count != panim->numcmds)
+ {
+ s_animcmd_t tmp;
+ tmp = panim->cmds[panim->numcmds - 1];
+ int i;
+ for (i = panim->numcmds - 1; i > 0; i--)
+ {
+ panim->cmds[i] = panim->cmds[i-1];
+ }
+ panim->cmds[0] = tmp;
+ return;
+ }
+ TokenError( "unknown prepend animation \"%s\"\n", token );
+}
+
+void Cmd_Continue( )
+{
+ GetToken(false);
+
+ s_sequence_t *pseq = LookupSequence( token );
+
+ if (pseq)
+ {
+ GetToken(true);
+ UnGetToken();
+ if (token[0] != '$')
+ ParseSequence( pseq, true );
+ return;
+ }
+ else
+ {
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim)
+ {
+ GetToken(true);
+ UnGetToken();
+ if (token[0] != '$')
+ ParseAnimation( panim, true );
+ return;
+ }
+ }
+ TokenError( "unknown continue animation %s\n", token );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: foward declare an empty sequence
+//-----------------------------------------------------------------------------
+
+void Cmd_DeclareSequence( void )
+{
+ if (g_sequence.Count() >= MAXSTUDIOSEQUENCES)
+ {
+ TokenError("Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES );
+ }
+
+ s_sequence_t *pseq = &g_sequence[ g_sequence.AddToTail() ];
+ memset( pseq, 0, sizeof( s_sequence_t ) );
+ pseq->flags = STUDIO_OVERRIDE;
+
+ // initialize sequence
+ GetToken( false );
+ V_strcpy_safe( pseq->name, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: foward declare an empty sequence
+//-----------------------------------------------------------------------------
+void Cmd_DeclareAnimation( void )
+{
+ if (g_numani >= MAXSTUDIOANIMS)
+ {
+ TokenError("Too many animations (%d max)\n", MAXSTUDIOANIMS );
+ }
+
+ // allocate animation entry
+ s_animation_t *panim = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani] = panim;
+ panim->index = g_numani;
+ panim->flags = STUDIO_OVERRIDE;
+ g_numani++;
+
+ // initialize animation
+ GetToken( false );
+ V_strcpy_safe( panim->name, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: create named list of boneweights
+//-----------------------------------------------------------------------------
+void Option_Weightlist( s_weightlist_t *pweightlist )
+{
+ int depth = 0;
+ int i;
+
+ pweightlist->numbones = 0;
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (stricmp("posweight", token ) == 0)
+ {
+ i = pweightlist->numbones - 1;
+ if (i < 0)
+ {
+ MdlError( "Error with specifing bone Position weight \'%s:%s\'\n", pweightlist->name, pweightlist->bonename[i] );
+ }
+ GetToken( false );
+ pweightlist->boneposweight[i] = verify_atof( token );
+ if (pweightlist->boneweight[i] == 0 && pweightlist->boneposweight[i] > 0)
+ {
+ MdlError( "Non-zero Position weight with zero Rotation weight not allowed \'%s:%s %f %f\'\n",
+ pweightlist->name, pweightlist->bonename[i], pweightlist->boneweight[i], pweightlist->boneposweight[i] );
+ }
+ }
+ else
+ {
+ i = pweightlist->numbones++;
+ if (i >= MAXWEIGHTSPERLIST)
+ {
+ TokenError("Too many bones (%d) in weightlist '%s'\n", i, pweightlist->name );
+ }
+ pweightlist->bonename[i] = strdup( token );
+ GetToken( false );
+ pweightlist->boneweight[i] = verify_atof( token );
+ pweightlist->boneposweight[i] = pweightlist->boneweight[i];
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+}
+
+
+void Cmd_Weightlist( )
+{
+ int i;
+
+ if (!GetToken(false))
+ return;
+
+ if (g_numweightlist >= MAXWEIGHTLISTS)
+ {
+ TokenError( "Too many weightlist commands (%d)\n", MAXWEIGHTLISTS );
+ }
+
+ for (i = 1; i < g_numweightlist; i++)
+ {
+ if (stricmp( g_weightlist[i].name, token ) == 0)
+ {
+ TokenError( "Duplicate weightlist '%s'\n", token );
+ }
+ }
+
+ V_strcpy_safe( g_weightlist[i].name, token );
+
+ Option_Weightlist( &g_weightlist[g_numweightlist] );
+
+ g_numweightlist++;
+}
+
+void Cmd_DefaultWeightlist( )
+{
+ Option_Weightlist( &g_weightlist[0] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Eyeball( s_model_t *pmodel )
+{
+ Vector tmp;
+ int i, j;
+ int mesh_material;
+ char szMeshMaterial[256];
+
+ s_eyeball_t *eyeball = &(pmodel->eyeball[pmodel->numeyeballs++]);
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( eyeball->name, token );
+
+ // bone name
+ GetToken (false);
+ for (i = 0; i < pmodel->source->numbones; i++)
+ {
+ if ( !Q_stricmp( pmodel->source->localBone[i].name, token ) )
+ {
+ eyeball->bone = i;
+ break;
+ }
+ }
+ if (!g_bCreateMakefile && i >= pmodel->source->numbones)
+ {
+ TokenError( "unknown eyeball bone \"%s\"\n", token );
+ }
+
+ // X
+ GetToken (false);
+ tmp[0] = verify_atof (token);
+
+ // Y
+ GetToken (false);
+ tmp[1] = verify_atof (token);
+
+ // Z
+ GetToken (false);
+ tmp[2] = verify_atof (token);
+
+ // mesh material
+ GetToken (false);
+ Q_strncpy( szMeshMaterial, token, sizeof(szMeshMaterial) );
+ mesh_material = UseTextureAsMaterial( LookupTexture( token ) );
+
+ // diameter
+ GetToken (false);
+ eyeball->radius = verify_atof (token) / 2.0;
+
+ // Z angle offset
+ GetToken (false);
+ eyeball->zoffset = tan( DEG2RAD( verify_atof (token) ) );
+
+ // iris material (no longer used, but we need to remove the token)
+ GetToken (false);
+
+ // pupil scale
+ GetToken (false);
+ eyeball->iris_scale = 1.0 / verify_atof( token );
+
+ VectorCopy( tmp, eyeball->org );
+
+ for (i = 0; i < pmodel->source->nummeshes; i++)
+ {
+ j = pmodel->source->meshindex[i]; // meshes are internally stored by material index
+
+ if (j == mesh_material)
+ {
+ eyeball->mesh = i; // FIXME: should this be pre-adjusted?
+ break;
+ }
+ }
+
+ if (!g_bCreateMakefile && i >= pmodel->source->nummeshes)
+ {
+ TokenError("can't find eyeball texture \"%s\" on model\n", szMeshMaterial );
+ }
+
+ // translate eyeball into bone space
+ VectorITransform( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->org );
+
+ matrix3x4_t vtmp;
+ AngleMatrix( g_defaultrotation, vtmp );
+
+ VectorIRotate( Vector( 0, 0, 1 ), vtmp, tmp );
+ VectorIRotate( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->up );
+
+ VectorIRotate( Vector( 1, 0, 0 ), vtmp, tmp );
+ VectorIRotate( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->forward );
+
+ // these get overwritten by "eyelid" data
+ eyeball->upperlidflexdesc = -1;
+ eyeball->lowerlidflexdesc = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Spherenormals( s_source_t *psource )
+{
+ Vector pos;
+ int i, j;
+ int mesh_material;
+ char szMeshMaterial[256];
+
+ // mesh material
+ GetToken (false);
+ V_strcpy_safe( szMeshMaterial, token );
+ mesh_material = UseTextureAsMaterial( LookupTexture( token ) );
+
+ // X
+ GetToken (false);
+ pos[0] = verify_atof (token);
+
+ // Y
+ GetToken (false);
+ pos[1] = verify_atof (token);
+
+ // Z
+ GetToken (false);
+ pos[2] = verify_atof (token);
+
+ for (i = 0; i < psource->nummeshes; i++)
+ {
+ j = psource->meshindex[i]; // meshes are internally stored by material index
+
+ if (j == mesh_material)
+ {
+ s_vertexinfo_t *vertex = &psource->vertex[psource->mesh[i].vertexoffset];
+
+ for (int k = 0; k < psource->mesh[i].numvertices; k++)
+ {
+ Vector n = vertex[k].position - pos;
+ VectorNormalize( n );
+ if (DotProduct( n, vertex[k].normal ) < 0.0)
+ {
+ vertex[k].normal = -1 * n;
+ }
+ else
+ {
+ vertex[k].normal = n;
+ }
+#if 0
+ vertex[k].normal[0] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ vertex[k].normal[1] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ vertex[k].normal[2] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ VectorNormalize( vertex[k].normal );
+#endif
+ }
+ break;
+ }
+ }
+
+ if (i >= psource->nummeshes)
+ {
+ TokenError("can't find spherenormal texture \"%s\" on model\n", szMeshMaterial );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int Add_Flexdesc( const char *name )
+{
+ int flexdesc;
+ for ( flexdesc = 0; flexdesc < g_numflexdesc; flexdesc++)
+ {
+ if (stricmp( name, g_flexdesc[flexdesc].FACS ) == 0)
+ {
+ break;
+ }
+ }
+
+ if (flexdesc >= MAXSTUDIOFLEXDESC)
+ {
+ TokenError( "Too many flex types, max %d\n", MAXSTUDIOFLEXDESC );
+ }
+
+ if (flexdesc == g_numflexdesc)
+ {
+ V_strcpy_safe( g_flexdesc[flexdesc].FACS, name );
+
+ g_numflexdesc++;
+ }
+ return flexdesc;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Flex( char *name, char *vtafile, int imodel, float pairsplit )
+{
+ if (g_numflexkeys >= MAXSTUDIOFLEXKEYS)
+ {
+ TokenError( "Too many flexes, max %d\n", MAXSTUDIOFLEXKEYS );
+ }
+
+ int flexdesc, flexpair;
+
+ if (pairsplit != 0)
+ {
+ char mod[256];
+ sprintf( mod, "%sR", name );
+ flexdesc = Add_Flexdesc( mod );
+
+ sprintf( mod, "%sL", name );
+ flexpair = Add_Flexdesc( mod );
+ }
+ else
+ {
+ flexdesc = Add_Flexdesc( name );
+ flexpair = 0;
+ }
+
+ // initialize
+ g_flexkey[g_numflexkeys].imodel = imodel;
+ g_flexkey[g_numflexkeys].flexdesc = flexdesc;
+ g_flexkey[g_numflexkeys].target0 = 0.0;
+ g_flexkey[g_numflexkeys].target1 = 1.0;
+ g_flexkey[g_numflexkeys].target2 = 10;
+ g_flexkey[g_numflexkeys].target3 = 11;
+ g_flexkey[g_numflexkeys].split = pairsplit;
+ g_flexkey[g_numflexkeys].flexpair = flexpair;
+ g_flexkey[g_numflexkeys].decay = 1.0;
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ if (stricmp( token, "frame") == 0)
+ {
+ GetToken (false);
+
+ g_flexkey[g_numflexkeys].frame = verify_atoi( token );
+ }
+ else if (stricmp( token, "position") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].target1 = verify_atof( token );
+ }
+ else if (stricmp( token, "split") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].split = verify_atof( token );
+ }
+ else if (stricmp( token, "decay") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].decay = verify_atof( token );
+ }
+ else
+ {
+ TokenError( "unknown option: %s", token );
+ }
+
+ }
+
+ if (g_numflexkeys > 1)
+ {
+ if (g_flexkey[g_numflexkeys-1].flexdesc == g_flexkey[g_numflexkeys].flexdesc)
+ {
+ g_flexkey[g_numflexkeys-1].target2 = g_flexkey[g_numflexkeys-1].target1;
+ g_flexkey[g_numflexkeys-1].target3 = g_flexkey[g_numflexkeys].target1;
+ g_flexkey[g_numflexkeys].target0 = g_flexkey[g_numflexkeys-1].target1;
+ }
+ }
+
+ // link to source
+ s_source_t *pSource = Load_Source( vtafile, "vta" );
+ g_flexkey[g_numflexkeys].source = pSource;
+ if ( pSource->m_Animations.Count() )
+ {
+ Q_strncpy( g_flexkey[g_numflexkeys].animationname, pSource->m_Animations[0].animationname, sizeof( g_flexkey[g_numflexkeys].animationname ) );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys].animationname[0] = 0;
+ }
+ g_numflexkeys++;
+ // this needs to be per model.
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds combination data to the source
+//-----------------------------------------------------------------------------
+int FindSourceFlexKey( s_source_t *pSource, const char *pName )
+{
+ int nCount = pSource->m_FlexKeys.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !Q_stricmp( pSource->m_FlexKeys[i].animationname, pName ) )
+ return i;
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flexkey data to a particular source
+//-----------------------------------------------------------------------------
+void AddFlexKey( s_source_t *pSource, CDmeCombinationOperator *pComboOp, const char *pFlexKeyName )
+{
+ // See if the delta state is already accounted for
+ if ( FindSourceFlexKey( pSource, pFlexKeyName ) >= 0 )
+ return;
+
+ int i = pSource->m_FlexKeys.AddToTail();
+
+ s_flexkey_t &key = pSource->m_FlexKeys[i];
+ memset( &key, 0, sizeof(key) );
+
+ key.target0 = 0.0f;
+ key.target1 = 1.0f;
+ key.target2 = 10.0f;
+ key.target3 = 11.0f;
+ key.decay = 1.0f;
+ key.source = pSource;
+
+ Q_strncpy( key.animationname, pFlexKeyName, sizeof(key.animationname) );
+ key.flexpair = pComboOp->IsDeltaStateStereo( pFlexKeyName ); // Signal used by AddBodyFlexData
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds combination data to the source
+//-----------------------------------------------------------------------------
+void AddCombination( s_source_t *pSource, CDmeCombinationOperator *pCombination )
+{
+ // Define the remapped controls
+ int nControlCount = pCombination->GetRawControlCount();
+ for ( int i = 0; i < nControlCount; ++i )
+ {
+ int m = pSource->m_CombinationControls.AddToTail();
+ s_combinationcontrol_t &control = pSource->m_CombinationControls[m];
+ Q_strncpy( control.name, pCombination->GetRawControlName( i ), sizeof(control.name) );
+ }
+
+ // Define the combination + domination rules
+ int nTargetCount = pCombination->GetOperationTargetCount();
+ for ( int i = 0; i < nTargetCount; ++i )
+ {
+ int nOpCount = pCombination->GetOperationCount( i );
+ for ( int j = 0; j < nOpCount; ++j )
+ {
+ CDmElement *pDeltaState = pCombination->GetOperationDeltaState( i, j );
+ if ( !pDeltaState )
+ continue;
+
+ int nFlex = FindSourceFlexKey( pSource, pDeltaState->GetName() );
+ if ( nFlex < 0 )
+ continue;
+
+ int k = pSource->m_CombinationRules.AddToTail();
+ s_combinationrule_t &rule = pSource->m_CombinationRules[k];
+ rule.m_nFlex = nFlex;
+ rule.m_Combination = pCombination->GetOperationControls( i, j );
+ int nDominatorCount = pCombination->GetOperationDominatorCount( i, j );
+ for ( int l = 0; l < nDominatorCount; ++l )
+ {
+ int m = rule.m_Dominators.AddToTail();
+ rule.m_Dominators[m] = pCombination->GetOperationDominator( i, j, l );
+ }
+ }
+ }
+
+ // Define the remapping controls
+ nControlCount = pCombination->GetControlCount();
+ for ( int i = 0; i < nControlCount; ++i )
+ {
+ int k = pSource->m_FlexControllerRemaps.AddToTail();
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[k];
+ remap.m_Name = pCombination->GetControlName( i );
+ remap.m_bIsStereo = pCombination->IsStereoControl( i );
+ remap.m_Index = -1; // Don't know this right now
+ remap.m_LeftIndex = -1; // Don't know this right now
+ remap.m_RightIndex = -1; // Don't know this right now
+ remap.m_MultiIndex = -1; // Don't know this right now
+ remap.m_EyesUpDownFlexController = -1;
+ remap.m_BlinkController = -1;
+
+ int nRemapCount = pCombination->GetRawControlCount( i );
+ if ( pCombination->IsEyelidControl( i ) )
+ {
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_EYELID;
+
+ // Save the eyes_updown flex for later
+ const char *pEyesUpDownFlexName = pCombination->GetEyesUpDownFlexName( i );
+ remap.m_EyesUpDownFlexName = pEyesUpDownFlexName ? pEyesUpDownFlexName : "eyes_updown";
+ }
+ else
+ {
+ switch( nRemapCount )
+ {
+ case 0:
+ case 1:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_PASSTHRU;
+ break;
+ case 2:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_2WAY;
+ break;
+ default:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_NWAY;
+ break;
+ }
+ }
+
+ Assert( nRemapCount != 0 );
+ for ( int j = 0; j < nRemapCount; ++j )
+ {
+ const char *pRemapName = pCombination->GetRawControlName( i, j );
+ remap.m_RawControls.AddToTail( pRemapName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Eyelid( int imodel )
+{
+ char type[256];
+ char vtafile[256];
+
+ // type
+ GetToken (false);
+ V_strcpy_safe( type, token );
+
+ // source
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+
+ int lowererframe = 0;
+ int neutralframe = 0;
+ int raiserframe = 0;
+ float lowerertarget = 0.0f;
+ float neutraltarget = 0.0f;
+ float raisertarget = 0.0f;
+ int lowererdesc = 0;
+ int neutraldesc = 0;
+ int raiserdesc = 0;
+ int basedesc;
+ float split = 0;
+ char szEyeball[64] = {""};
+
+ basedesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, type );
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ char localdesc[256];
+ V_strcpy_safe( localdesc, type );
+ V_strcat_safe( localdesc, "_" );
+ V_strcat_safe( localdesc, token );
+
+ if (stricmp( token, "lowerer") == 0)
+ {
+ GetToken (false);
+ lowererframe = verify_atoi( token );
+ GetToken (false);
+ lowerertarget = verify_atof( token );
+ lowererdesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "neutral") == 0)
+ {
+ GetToken (false);
+ neutralframe = verify_atoi( token );
+ GetToken (false);
+ neutraltarget = verify_atof( token );
+ neutraldesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "raiser") == 0)
+ {
+ GetToken (false);
+ raiserframe = verify_atoi( token );
+ GetToken (false);
+ raisertarget = verify_atof( token );
+ raiserdesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "split") == 0)
+ {
+ GetToken (false);
+ split = verify_atof( token );
+ }
+ else if (stricmp( token, "eyeball") == 0)
+ {
+ GetToken (false);
+ V_strcpy_safe( szEyeball, token );
+ }
+ else
+ {
+ TokenError( "unknown option: %s", token );
+ }
+ }
+
+ s_source_t *pSource = Load_Source( vtafile, "vta" );
+ g_flexkey[g_numflexkeys+0].source = pSource;
+ g_flexkey[g_numflexkeys+0].frame = lowererframe;
+ g_flexkey[g_numflexkeys+0].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+0].imodel = imodel;
+ g_flexkey[g_numflexkeys+0].split = split;
+ g_flexkey[g_numflexkeys+0].target0 = -11;
+ g_flexkey[g_numflexkeys+0].target1 = -10;
+ g_flexkey[g_numflexkeys+0].target2 = lowerertarget;
+ g_flexkey[g_numflexkeys+0].target3 = neutraltarget;
+ g_flexkey[g_numflexkeys+0].decay = 0.0;
+ if ( pSource->m_Animations.Count() > 0 )
+ {
+ Q_strncpy( g_flexkey[g_numflexkeys+0].animationname, pSource->m_Animations[0].animationname, sizeof(g_flexkey[g_numflexkeys+0].animationname) );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys+0].animationname[0] = 0;
+ }
+
+ g_flexkey[g_numflexkeys+1].source = g_flexkey[g_numflexkeys+0].source;
+ Q_strncpy( g_flexkey[g_numflexkeys+1].animationname, g_flexkey[g_numflexkeys+0].animationname, sizeof(g_flexkey[g_numflexkeys+1].animationname) );
+ g_flexkey[g_numflexkeys+1].frame = neutralframe;
+ g_flexkey[g_numflexkeys+1].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+1].imodel = imodel;
+ g_flexkey[g_numflexkeys+1].split = split;
+ g_flexkey[g_numflexkeys+1].target0 = lowerertarget;
+ g_flexkey[g_numflexkeys+1].target1 = neutraltarget;
+ g_flexkey[g_numflexkeys+1].target2 = neutraltarget;
+ g_flexkey[g_numflexkeys+1].target3 = raisertarget;
+ g_flexkey[g_numflexkeys+1].decay = 0.0;
+
+ g_flexkey[g_numflexkeys+2].source = g_flexkey[g_numflexkeys+0].source;
+ Q_strncpy( g_flexkey[g_numflexkeys+2].animationname, g_flexkey[g_numflexkeys+0].animationname, sizeof(g_flexkey[g_numflexkeys+2].animationname) );
+ g_flexkey[g_numflexkeys+2].frame = raiserframe;
+ g_flexkey[g_numflexkeys+2].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+2].imodel = imodel;
+ g_flexkey[g_numflexkeys+2].split = split;
+ g_flexkey[g_numflexkeys+2].target0 = neutraltarget;
+ g_flexkey[g_numflexkeys+2].target1 = raisertarget;
+ g_flexkey[g_numflexkeys+2].target2 = 10;
+ g_flexkey[g_numflexkeys+2].target3 = 11;
+ g_flexkey[g_numflexkeys+2].decay = 0.0;
+ g_numflexkeys += 3;
+
+ s_model_t *pmodel = g_model[imodel];
+ for (int i = 0; i < pmodel->numeyeballs; i++)
+ {
+ s_eyeball_t *peyeball = &(pmodel->eyeball[i]);
+
+ if (szEyeball[0] != '\0')
+ {
+ if (stricmp( peyeball->name, szEyeball ) != 0)
+ continue;
+ }
+
+ if (fabs( lowerertarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" lowerer out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+ if (fabs( neutraltarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" neutral out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+ if (fabs( raisertarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" raiser out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+
+ switch( type[0] )
+ {
+ case 'u':
+ peyeball->upperlidflexdesc = basedesc;
+ peyeball->upperflexdesc[0] = lowererdesc;
+ peyeball->uppertarget[0] = lowerertarget;
+ peyeball->upperflexdesc[1] = neutraldesc;
+ peyeball->uppertarget[1] = neutraltarget;
+ peyeball->upperflexdesc[2] = raiserdesc;
+ peyeball->uppertarget[2] = raisertarget;
+ break;
+ case 'l':
+ peyeball->lowerlidflexdesc = basedesc;
+ peyeball->lowerflexdesc[0] = lowererdesc;
+ peyeball->lowertarget[0] = lowerertarget;
+ peyeball->lowerflexdesc[1] = neutraldesc;
+ peyeball->lowertarget[1] = neutraltarget;
+ peyeball->lowerflexdesc[2] = raiserdesc;
+ peyeball->lowertarget[2] = raisertarget;
+ break;
+ }
+ }
+}
+
+/*
+=================
+=================
+*/
+int Option_Mouth( s_model_t *pmodel )
+{
+ // index
+ GetToken (false);
+ int index = verify_atoi( token );
+ if (index >= g_nummouths)
+ g_nummouths = index + 1;
+
+ // flex controller name
+ GetToken (false);
+ g_mouth[index].flexdesc = Add_Flexdesc( token );
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_mouth[index].bonename, token );
+
+ // vector
+ GetToken (false);
+ g_mouth[index].forward[0] = verify_atof( token );
+ GetToken (false);
+ g_mouth[index].forward[1] = verify_atof( token );
+ GetToken (false);
+ g_mouth[index].forward[2] = verify_atof( token );
+ return 0;
+}
+
+
+
+void Option_Flexcontroller( s_model_t *pmodel )
+{
+ char type[256];
+ float range_min = 0.0f;
+ float range_max = 1.0f;
+
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( type, token );
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ if (stricmp( token, "range") == 0)
+ {
+ GetToken(false);
+ range_min = verify_atof( token );
+
+ GetToken(false);
+ range_max = verify_atof( token );
+ }
+ else
+ {
+ if (g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ {
+ TokenError( "Too many flex controllers, max %d\n", MAXSTUDIOFLEXCTRL );
+ }
+
+ V_strcpy_safe( g_flexcontroller[g_numflexcontrollers].name, token );
+ V_strcpy_safe( g_flexcontroller[g_numflexcontrollers].type, type );
+ g_flexcontroller[g_numflexcontrollers].min = range_min;
+ g_flexcontroller[g_numflexcontrollers].max = range_max;
+ g_numflexcontrollers++;
+ }
+ }
+
+ // this needs to be per model.
+}
+
+void Option_Flexrule( s_model_t *pmodel, char *name )
+{
+ int precedence[32];
+ precedence[ STUDIO_CONST ] = 0;
+ precedence[ STUDIO_FETCH1 ] = 0;
+ precedence[ STUDIO_FETCH2 ] = 0;
+ precedence[ STUDIO_ADD ] = 1;
+ precedence[ STUDIO_SUB ] = 1;
+ precedence[ STUDIO_MUL ] = 2;
+ precedence[ STUDIO_DIV ] = 2;
+ precedence[ STUDIO_NEG ] = 4;
+ precedence[ STUDIO_EXP ] = 3;
+ precedence[ STUDIO_OPEN ] = 0; // only used in token parsing
+ precedence[ STUDIO_CLOSE ] = 0;
+ precedence[ STUDIO_COMMA ] = 0;
+ precedence[ STUDIO_MAX ] = 5;
+ precedence[ STUDIO_MIN ] = 5;
+
+ s_flexop_t stream[MAX_OPS];
+ int i = 0;
+ s_flexop_t stack[MAX_OPS];
+ int j = 0;
+ int k = 0;
+
+ s_flexrule_t *pRule = &g_flexrule[g_numflexrules++];
+
+ if (g_numflexrules > MAXSTUDIOFLEXRULES)
+ {
+ TokenError( "Too many flex rules (max %d)\n", MAXSTUDIOFLEXRULES );
+ }
+
+ int flexdesc;
+ for ( flexdesc = 0; flexdesc < g_numflexdesc; flexdesc++)
+ {
+ if (stricmp( name, g_flexdesc[flexdesc].FACS ) == 0)
+ {
+ break;
+ }
+ }
+
+ if (flexdesc >= g_numflexdesc)
+ {
+ TokenError( "Rule for unknown flex %s\n", name );
+ }
+
+ pRule->flex = flexdesc;
+ pRule->numops = 0;
+
+ // =
+ GetToken(false);
+
+ // parse all the tokens
+ bool linecontinue = false;
+ while ( linecontinue || TokenAvailable())
+ {
+ GetExprToken(linecontinue);
+
+ linecontinue = false;
+
+ if ( token[0] == '\\' )
+ {
+ if (!GetToken(false) || token[0] != '\\')
+ {
+ TokenError( "unknown expression token '\\%s\n", token );
+ }
+ linecontinue = true;
+ }
+ else if ( token[0] == '(' )
+ {
+ stream[i++].op = STUDIO_OPEN;
+ }
+ else if ( token[0] == ')' )
+ {
+ stream[i++].op = STUDIO_CLOSE;
+ }
+ else if ( token[0] == '+' )
+ {
+ stream[i++].op = STUDIO_ADD;
+ }
+ else if ( token[0] == '-' )
+ {
+ stream[i].op = STUDIO_SUB;
+ if (i > 0)
+ {
+ switch( stream[i-1].op )
+ {
+ case STUDIO_OPEN:
+ case STUDIO_ADD:
+ case STUDIO_SUB:
+ case STUDIO_MUL:
+ case STUDIO_DIV:
+ case STUDIO_COMMA:
+ // it's a unary if it's preceded by a "(+-*/,"?
+ stream[i].op = STUDIO_NEG;
+ break;
+ }
+ }
+ i++;
+ }
+ else if ( token[0] == '*' )
+ {
+ stream[i++].op = STUDIO_MUL;
+ }
+ else if ( token[0] == '/' )
+ {
+ stream[i++].op = STUDIO_DIV;
+ }
+ else if ( V_isdigit( token[0] ))
+ {
+ stream[i].op = STUDIO_CONST;
+ stream[i++].d.value = verify_atof( token );
+ }
+ else if ( token[0] == ',' )
+ {
+ stream[i++].op = STUDIO_COMMA;
+ }
+ else if ( stricmp( token, "max" ) == 0)
+ {
+ stream[i++].op = STUDIO_MAX;
+ }
+ else if ( stricmp( token, "min" ) == 0)
+ {
+ stream[i++].op = STUDIO_MIN;
+ }
+ else
+ {
+ if (token[0] == '%')
+ {
+ GetExprToken(false);
+
+ for (k = 0; k < g_numflexdesc; k++)
+ {
+ if (stricmp( token, g_flexdesc[k].FACS ) == 0)
+ {
+ stream[i].op = STUDIO_FETCH2;
+ stream[i++].d.index = k;
+ break;
+ }
+ }
+ if (k >= g_numflexdesc)
+ {
+ TokenError( "unknown flex %s\n", token );
+ }
+ }
+ else
+ {
+ for (k = 0; k < g_numflexcontrollers; k++)
+ {
+ if (stricmp( token, g_flexcontroller[k].name ) == 0)
+ {
+ stream[i].op = STUDIO_FETCH1;
+ stream[i++].d.index = k;
+ break;
+ }
+ }
+ if (k >= g_numflexcontrollers)
+ {
+ TokenError( "unknown controller %s\n", token );
+ }
+ }
+ }
+ }
+
+ if (i > MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ if (0)
+ {
+ printf("%s = ", g_flexdesc[pRule->flex].FACS );
+ for ( k = 0; k < i; k++)
+ {
+ switch( stream[k].op )
+ {
+ case STUDIO_CONST: printf("%f ", stream[k].d.value ); break;
+ case STUDIO_FETCH1: printf("%s ", g_flexcontroller[stream[k].d.index].name ); break;
+ case STUDIO_FETCH2: printf("[%d] ", stream[k].d.index ); break;
+ case STUDIO_ADD: printf("+ "); break;
+ case STUDIO_SUB: printf("- "); break;
+ case STUDIO_MUL: printf("* "); break;
+ case STUDIO_DIV: printf("/ "); break;
+ case STUDIO_NEG: printf("neg "); break;
+ case STUDIO_MAX: printf("max "); break;
+ case STUDIO_MIN: printf("min "); break;
+ case STUDIO_COMMA: printf(", "); break; // error
+ case STUDIO_OPEN: printf("( " ); break; // error
+ case STUDIO_CLOSE: printf(") " ); break; // error
+ default:
+ printf("err%d ", stream[k].op ); break;
+ }
+ }
+ printf("\n");
+ // exit(1);
+ }
+
+ j = 0;
+ for (k = 0; k < i; k++)
+ {
+ if (j >= MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+ switch( stream[k].op )
+ {
+ case STUDIO_CONST:
+ case STUDIO_FETCH1:
+ case STUDIO_FETCH2:
+ pRule->op[pRule->numops++] = stream[k];
+ break;
+ case STUDIO_OPEN:
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_CLOSE:
+ // pop all operators off of the stack until an open paren
+ while (j > 0 && stack[j-1].op != STUDIO_OPEN)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ if (j == 0)
+ {
+ TokenError( "unmatched closed parentheses\n" );
+ }
+ if (j > 0)
+ j--;
+ break;
+ case STUDIO_COMMA:
+ // pop all operators off of the stack until an open paren
+ while (j > 0 && stack[j-1].op != STUDIO_OPEN)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_ADD:
+ case STUDIO_SUB:
+ case STUDIO_MUL:
+ case STUDIO_DIV:
+ // pop all operators off of the stack that have equal or higher precedence
+ while (j > 0 && precedence[stream[k].op] <= precedence[stack[j-1].op])
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_NEG:
+ if (stream[k+1].op == STUDIO_CONST)
+ {
+ // change sign of constant, skip op
+ stream[k+1].d.value = -stream[k+1].d.value;
+ }
+ else
+ {
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ }
+ break;
+ case STUDIO_MAX:
+ case STUDIO_MIN:
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ }
+ if (pRule->numops >= MAX_OPS)
+ TokenError("expression for \"%s\" too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+ // pop all operators off of the stack
+ while (j > 0)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ if (pRule->numops >= MAX_OPS)
+ TokenError("expression for \"%s\" too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ // reprocess the operands, eating commas for all functions
+ int numCommas = 0;
+ j = 0;
+ for (k = 0; k < pRule->numops; k++)
+ {
+ switch( pRule->op[k].op )
+ {
+ case STUDIO_MAX:
+ case STUDIO_MIN:
+ if (pRule->op[j-1].op != STUDIO_COMMA)
+ {
+ TokenError( "missing comma\n");
+ }
+ // eat the comma operator
+ numCommas--;
+ pRule->op[j-1] = pRule->op[k];
+ break;
+ case STUDIO_COMMA:
+ numCommas++;
+ pRule->op[j++] = pRule->op[k];
+ break;
+ default:
+ pRule->op[j++] = pRule->op[k];
+ break;
+ }
+ }
+ pRule->numops = j;
+ if (numCommas != 0)
+ {
+ TokenError( "too many comma's\n" );
+ }
+
+ if (pRule->numops > MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ if (0)
+ {
+ printf("%s = ", g_flexdesc[pRule->flex].FACS );
+ for ( i = 0; i < pRule->numops; i++)
+ {
+ switch( pRule->op[i].op )
+ {
+ case STUDIO_CONST: printf("%f ", pRule->op[i].d.value ); break;
+ case STUDIO_FETCH1: printf("%s ", g_flexcontroller[pRule->op[i].d.index].name ); break;
+ case STUDIO_FETCH2: printf("[%d] ", pRule->op[i].d.index ); break;
+ case STUDIO_ADD: printf("+ "); break;
+ case STUDIO_SUB: printf("- "); break;
+ case STUDIO_MUL: printf("* "); break;
+ case STUDIO_DIV: printf("/ "); break;
+ case STUDIO_NEG: printf("neg "); break;
+ case STUDIO_MAX: printf("max "); break;
+ case STUDIO_MIN: printf("min "); break;
+ case STUDIO_COMMA: printf(", "); break; // error
+ case STUDIO_OPEN: printf("( " ); break; // error
+ case STUDIO_CLOSE: printf(") " ); break; // error
+ default:
+ printf("err%d ", pRule->op[i].op ); break;
+ }
+ }
+ printf("\n");
+ // exit(1);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Model( )
+{
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+
+ // name
+ if (!GetToken(false))
+ return;
+ V_strcpy_safe( g_model[g_nummodels]->name, token );
+
+ // fake g_bodypart stuff
+ if (g_numbodyparts == 0)
+ {
+ g_bodypart[g_numbodyparts].base = 1;
+ }
+ else
+ {
+ g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
+ }
+ V_strcpy_safe( g_bodypart[g_numbodyparts].name, token );
+
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+ g_bodypart[g_numbodyparts].nummodels = 1;
+ g_numbodyparts++;
+
+ Option_Studio( g_model[g_nummodels] );
+
+ if ( g_model[g_nummodels]->source )
+ {
+ // Body command should add any flex commands in the source loaded
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ }
+
+ int depth = 0;
+ while (1)
+ {
+ char FAC[256], vtafile[256];
+ if (depth > 0)
+ {
+ if( !GetToken(true) )
+ break;
+ }
+ else
+ {
+ if ( !TokenAvailable() )
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if ( endofscript )
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if ( !Q_stricmp("{", token ) )
+ {
+ depth++;
+ }
+ else if ( !Q_stricmp("}", token ) )
+ {
+ depth--;
+ }
+ else if ( !Q_stricmp( "eyeball", token ) )
+ {
+ Option_Eyeball( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( "eyelid", token ) )
+ {
+ Option_Eyelid( g_nummodels );
+ }
+ else if ( !Q_stricmp( "flex", token ) )
+ {
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( FAC, token );
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ Option_Flex( FAC, vtafile, g_nummodels, 0.0 ); // FIXME: this needs to point to a model used, not loaded!!!
+ }
+ else if ( !Q_stricmp( "flexpair", token ) )
+ {
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( FAC, token );
+
+ GetToken( false );
+ float split = atof( token );
+
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ Option_Flex( FAC, vtafile, g_nummodels, split ); // FIXME: this needs to point to a model used, not loaded!!!
+ }
+ else if ( !Q_stricmp( "defaultflex", token ) )
+ {
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+
+ // g_flex
+ Option_Flex( "default", vtafile, g_nummodels, 0.0 ); // FIXME: this needs to point to a model used, not loaded!!!
+ g_defaultflexkey = &g_flexkey[g_numflexkeys-1];
+ }
+ else if ( !Q_stricmp( "flexfile", token ) )
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ else if ( !Q_stricmp( "localvar", token ) )
+ {
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ Add_Flexdesc( token );
+ }
+ }
+ else if ( !Q_stricmp( "mouth", token ) )
+ {
+ Option_Mouth( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( "flexcontroller", token ) )
+ {
+ Option_Flexcontroller( g_model[g_nummodels] );
+ }
+ else if ( token[0] == '%' )
+ {
+ Option_Flexrule( g_model[g_nummodels], &token[1] );
+ }
+ else if ( !Q_stricmp("attachment", token ) )
+ {
+ // Option_Attachment( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( token, "spherenormals" ) )
+ {
+ Option_Spherenormals( g_model[g_nummodels]->source );
+ }
+ else
+ {
+ TokenError( "unknown model option \"%s\"\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ // Actually connect up the expressions between the Dme Flex Controllers & Flex Descriptors
+ // In case there was data added by some other eyeball command (like eyelid)
+ AddBodyFlexRules( g_model[ g_nummodels ]->source );
+
+ g_nummodels++;
+}
+
+
+void Cmd_FakeVTA( void )
+{
+ int depth = 0;
+
+ GetToken( false );
+
+ s_source_t *psource = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ g_source[g_numsources] = psource;
+ V_strcpy_safe( g_source[g_numsources]->filename, token );
+ g_numsources++;
+
+ while (1)
+ {
+ if (depth > 0)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!TokenAvailable())
+ {
+ break;
+ }
+ else
+ {
+ GetToken (false);
+ }
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (stricmp("appendvta", token ) == 0)
+ {
+ char filename[256];
+ // file
+ GetToken (false);
+ V_strcpy_safe( filename, token );
+
+ GetToken( false );
+ int frame = verify_atoi( token );
+
+ AppendVTAtoOBJ( psource, filename, frame );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_IKChain( )
+{
+ if (!GetToken(false))
+ return;
+
+ int i;
+ for ( i = 0; i < g_numikchains; i++)
+ {
+ if (stricmp( token, g_ikchain[i].name ) == 0)
+ {
+ break;
+ }
+ }
+ if (i < g_numikchains)
+ {
+ if (!g_quiet)
+ {
+ printf("duplicate ikchain \"%s\" ignored\n", token );
+ }
+ while (TokenAvailable())
+ {
+ GetToken(false);
+ }
+ return;
+ }
+
+ V_strcpy_safe( g_ikchain[g_numikchains].name, token );
+
+ GetToken(false);
+ V_strcpy_safe( g_ikchain[g_numikchains].bonename, token );
+
+ g_ikchain[g_numikchains].axis = STUDIO_Z;
+ g_ikchain[g_numikchains].value = 0.0;
+ g_ikchain[g_numikchains].height = 18.0;
+ g_ikchain[g_numikchains].floor = 0.0;
+ g_ikchain[g_numikchains].radius = 0.0;
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ if (lookupControl( token ) != -1)
+ {
+ g_ikchain[g_numikchains].axis = lookupControl( token );
+ GetToken(false);
+ g_ikchain[g_numikchains].value = verify_atof( token );
+ }
+ else if (stricmp( "height", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].height = verify_atof( token );
+ }
+ else if (stricmp( "pad", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].radius = verify_atof( token ) / 2.0;
+ }
+ else if (stricmp( "floor", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].floor = verify_atof( token );
+ }
+ else if (stricmp( "knee", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].link[0].kneeDir.x = verify_atof( token );
+ GetToken(false);
+ g_ikchain[g_numikchains].link[0].kneeDir.y = verify_atof( token );
+ GetToken(false);
+ g_ikchain[g_numikchains].link[0].kneeDir.z = verify_atof( token );
+ }
+ else if (stricmp( "center", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].center.x = verify_atof( token );
+ GetToken(false);
+ g_ikchain[g_numikchains].center.y = verify_atof( token );
+ GetToken(false);
+ g_ikchain[g_numikchains].center.z = verify_atof( token );
+ }
+ }
+ g_numikchains++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+
+void Cmd_IKAutoplayLock( )
+{
+ GetToken(false);
+ V_strcpy_safe( g_ikautoplaylock[g_numikautoplaylocks].name, token );
+
+ GetToken(false);
+ g_ikautoplaylock[g_numikautoplaylocks].flPosWeight = verify_atof( token );
+
+ GetToken(false);
+ g_ikautoplaylock[g_numikautoplaylocks].flLocalQWeight = verify_atof( token );
+
+ g_numikautoplaylocks++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Root ()
+{
+ if (GetToken (false))
+ {
+ V_strcpy_safe( rootname, token );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Controller (void)
+{
+ if (GetToken (false))
+ {
+ if (!stricmp("mouth",token))
+ {
+ g_bonecontroller[g_numbonecontrollers].inputfield = 4;
+ }
+ else
+ {
+ g_bonecontroller[g_numbonecontrollers].inputfield = verify_atoi(token);
+ }
+ if (GetToken(false))
+ {
+ V_strcpy_safe( g_bonecontroller[g_numbonecontrollers].name, token );
+ GetToken(false);
+ if ((g_bonecontroller[g_numbonecontrollers].type = lookupControl(token)) == -1)
+ {
+ MdlWarning("unknown g_bonecontroller type '%s'\n", token );
+ return;
+ }
+ GetToken(false);
+ g_bonecontroller[g_numbonecontrollers].start = verify_atof( token );
+ GetToken(false);
+ g_bonecontroller[g_numbonecontrollers].end = verify_atof( token );
+
+ if (g_bonecontroller[g_numbonecontrollers].type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR))
+ {
+ if (((int)(g_bonecontroller[g_numbonecontrollers].start + 360) % 360) == ((int)(g_bonecontroller[g_numbonecontrollers].end + 360) % 360))
+ {
+ g_bonecontroller[g_numbonecontrollers].type |= STUDIO_RLOOP;
+ }
+ }
+ g_numbonecontrollers++;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+// Debugging function that enumerate all a models bones to stdout.
+static void SpewBones()
+{
+ MdlWarning("g_numbones %i\n",g_numbones);
+
+ for ( int i = g_numbones; --i >= 0; )
+ {
+ printf("%s\n",g_bonetable[i].name);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_ScreenAlign ( void )
+{
+ if (GetToken (false))
+ {
+
+ Assert( g_numscreenalignedbones < MAXSTUDIOSRCBONES );
+
+ V_strcpy_safe( g_screenalignedbone[g_numscreenalignedbones].name, token );
+ g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE;
+
+ if( GetToken( false ) )
+ {
+ if( !stricmp( "sphere", token ) )
+ {
+ g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE;
+ }
+ else if( !stricmp( "cylinder", token ) )
+ {
+ g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_CYLINDER;
+ }
+ }
+
+ g_numscreenalignedbones++;
+
+ } else
+ {
+ TokenError( "$screenalign: expected bone name\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_BBox (void)
+{
+ GetToken (false);
+ bbox[0][0] = verify_atof( token );
+
+ GetToken (false);
+ bbox[0][1] = verify_atof( token );
+
+ GetToken (false);
+ bbox[0][2] = verify_atof( token );
+
+ GetToken (false);
+ bbox[1][0] = verify_atof( token );
+
+ GetToken (false);
+ bbox[1][1] = verify_atof( token );
+
+ GetToken (false);
+ bbox[1][2] = verify_atof( token );
+
+ g_wrotebbox = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_CBox (void)
+{
+ GetToken (false);
+ cbox[0][0] = verify_atof( token );
+
+ GetToken (false);
+ cbox[0][1] = verify_atof( token );
+
+ GetToken (false);
+ cbox[0][2] = verify_atof( token );
+
+ GetToken (false);
+ cbox[1][0] = verify_atof( token );
+
+ GetToken (false);
+ cbox[1][1] = verify_atof( token );
+
+ GetToken (false);
+ cbox[1][2] = verify_atof( token );
+
+ g_wrotecbox = true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Gamma (void)
+{
+ GetToken (false);
+ g_gamma = verify_atof( token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_TextureGroup( )
+{
+ if( g_bCreateMakefile )
+ {
+ return;
+ }
+ int i;
+ int depth = 0;
+ int index = 0;
+ int group = 0;
+
+
+ if (!GetToken(false))
+ return;
+
+ if (g_numskinref == 0)
+ g_numskinref = g_numtextures;
+
+ while (1)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if (token[0] == '{')
+ {
+ depth++;
+ }
+ else if (token[0] == '}')
+ {
+ depth--;
+ if (depth == 0)
+ break;
+ group++;
+ index = 0;
+ }
+ else if (depth == 2)
+ {
+ i = UseTextureAsMaterial( LookupTexture( token ) );
+ g_texturegroup[g_numtexturegroups][group][index] = i;
+ if (group != 0)
+ g_texture[i].parent = g_texturegroup[g_numtexturegroups][0][index];
+ index++;
+ g_numtexturereps[g_numtexturegroups] = index;
+ g_numtexturelayers[g_numtexturegroups] = group + 1;
+ }
+ }
+
+ g_numtexturegroups++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Hitgroup( )
+{
+ GetToken (false);
+ g_hitgroup[g_numhitgroups].group = verify_atoi( token );
+ GetToken (false);
+ V_strcpy_safe( g_hitgroup[g_numhitgroups].name, token );
+ g_numhitgroups++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Hitbox( )
+{
+ bool autogenerated = false;
+ if ( g_hitboxsets.Size() == 0 )
+ {
+ g_hitboxsets.AddToTail();
+ autogenerated = true;
+ }
+
+ // Last one
+ s_hitboxset *set = &g_hitboxsets[ g_hitboxsets.Size() - 1 ];
+ if ( autogenerated )
+ {
+ memset( set, 0, sizeof( *set ) );
+
+ // fill in name if it wasn't specified in the .qc
+ V_strcpy_safe( set->hitboxsetname, "default" );
+ }
+
+ GetToken (false);
+ set->hitbox[set->numhitboxes].group = verify_atoi( token );
+
+ // Grab the bone name:
+ GetToken (false);
+ V_strcpy_safe( set->hitbox[set->numhitboxes].name, token );
+
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmin[0] = verify_atof( token );
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmin[1] = verify_atof( token );
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmin[2] = verify_atof( token );
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmax[0] = verify_atof( token );
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmax[1] = verify_atof( token );
+ GetToken (false);
+ set->hitbox[set->numhitboxes].bmax[2] = verify_atof( token );
+
+ //Scale hitboxes
+ scale_vertex( set->hitbox[set->numhitboxes].bmin );
+ scale_vertex( set->hitbox[set->numhitboxes].bmax );
+ // clear out the hitboxname:
+ memset( set->hitbox[set->numhitboxes].hitboxname, 0, sizeof( set->hitbox[set->numhitboxes].hitboxname ) );
+
+ // Grab the hit box name if present:
+ if( TokenAvailable() )
+ {
+ GetToken (false);
+ V_strcpy_safe( set->hitbox[set->numhitboxes].hitboxname, token );
+ }
+
+
+ set->numhitboxes++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_HitboxSet( void )
+{
+ // Add a new hitboxset
+ s_hitboxset *set = &g_hitboxsets[ g_hitboxsets.AddToTail() ];
+ GetToken( false );
+ memset( set, 0, sizeof( *set ) );
+ V_strcpy_safe( set->hitboxsetname, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default surface property to the entire model
+//-----------------------------------------------------------------------------
+struct SurfacePropName_t
+{
+ char m_pJointName[128];
+ char m_pSurfaceProp[128];
+};
+
+static char s_pDefaultSurfaceProp[128] = {"default"};
+static CUtlVector<SurfacePropName_t> s_JointSurfaceProp;
+
+//-----------------------------------------------------------------------------
+// Assigns a default surface property to the entire model
+//-----------------------------------------------------------------------------
+void Cmd_SurfaceProp ()
+{
+ GetToken (false);
+ V_strcpy_safe( s_pDefaultSurfaceProp, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a surface property to a particular joint
+//-----------------------------------------------------------------------------
+void Cmd_JointSurfaceProp ()
+{
+ // Get joint name...
+ GetToken (false);
+
+ // Search for the name in our list
+ int i;
+ for ( i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointSurfaceProp[i].m_pJointName, token))
+ {
+ break;
+ }
+ }
+
+ // Add new entry if we haven't seen this name before
+ if (i < 0)
+ {
+ i = s_JointSurfaceProp.AddToTail();
+ V_strcpy_safe( s_JointSurfaceProp[i].m_pJointName, token );
+ }
+
+ // surface property name
+ GetToken(false);
+ V_strcpy_safe( s_JointSurfaceProp[i].m_pSurfaceProp, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the default surface prop name
+//-----------------------------------------------------------------------------
+char* GetDefaultSurfaceProp ( )
+{
+ return s_pDefaultSurfaceProp;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+static char* FindSurfaceProp ( const char* pJointName )
+{
+ for ( int i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointSurfaceProp[i].m_pJointName, pJointName))
+ {
+ return s_JointSurfaceProp[i].m_pSurfaceProp;
+ }
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+char* GetSurfaceProp ( const char* pJointName )
+{
+ while( pJointName )
+ {
+ // First try to find this joint
+ char* pSurfaceProp = FindSurfaceProp( pJointName );
+ if (pSurfaceProp)
+ return pSurfaceProp;
+
+ // If we can't find the joint, then find it's parent...
+ if (!g_numbones)
+ return s_pDefaultSurfaceProp;
+
+ int i = findGlobalBone( pJointName );
+
+ if ((i >= 0) && (g_bonetable[i].parent >= 0))
+ {
+ pJointName = g_bonetable[g_bonetable[i].parent].name;
+ }
+ else
+ {
+ pJointName = 0;
+ }
+ }
+
+ // No match, return the default one
+ return s_pDefaultSurfaceProp;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+void ConsistencyCheckSurfaceProp ( )
+{
+ for ( int i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ int j = findGlobalBone( s_JointSurfaceProp[i].m_pJointName );
+
+ if (j < 0)
+ {
+ MdlWarning("You specified a joint surface property for joint\n"
+ " \"%s\" which either doesn't exist or was optimized out.\n", s_JointSurfaceProp[i].m_pJointName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default contents to the entire model
+//-----------------------------------------------------------------------------
+struct ContentsName_t
+{
+ char m_pJointName[128];
+ int m_nContents;
+};
+
+static int s_nDefaultContents = CONTENTS_SOLID;
+static CUtlVector<ContentsName_t> s_JointContents;
+
+
+//-----------------------------------------------------------------------------
+// Parse contents flags
+//-----------------------------------------------------------------------------
+static void ParseContents( int *pAddFlags, int *pRemoveFlags )
+{
+ *pAddFlags = 0;
+ *pRemoveFlags = 0;
+ do
+ {
+ GetToken (false);
+
+ if ( !stricmp( token, "grate" ) )
+ {
+ *pAddFlags |= CONTENTS_GRATE;
+ *pRemoveFlags |= CONTENTS_SOLID;
+ }
+ else if ( !stricmp( token, "ladder" ) )
+ {
+ *pAddFlags |= CONTENTS_LADDER;
+ }
+ else if ( !stricmp( token, "solid" ) )
+ {
+ *pAddFlags |= CONTENTS_SOLID;
+ }
+ else if ( !stricmp( token, "monster" ) )
+ {
+ *pAddFlags |= CONTENTS_MONSTER;
+ }
+ else if ( !stricmp( token, "notsolid" ) )
+ {
+ *pRemoveFlags |= CONTENTS_SOLID;
+ }
+ } while (TokenAvailable());
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default contents to the entire model
+//-----------------------------------------------------------------------------
+void Cmd_Contents()
+{
+ int nAddFlags, nRemoveFlags;
+ ParseContents( &nAddFlags, &nRemoveFlags );
+ s_nDefaultContents |= nAddFlags;
+ s_nDefaultContents &= ~nRemoveFlags;
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns contents to a particular joint
+//-----------------------------------------------------------------------------
+void Cmd_JointContents ()
+{
+ // Get joint name...
+ GetToken (false);
+
+ // Search for the name in our list
+ int i;
+ for ( i = s_JointContents.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointContents[i].m_pJointName, token))
+ {
+ break;
+ }
+ }
+
+ // Add new entry if we haven't seen this name before
+ if (i < 0)
+ {
+ i = s_JointContents.AddToTail();
+ V_strcpy_safe( s_JointContents[i].m_pJointName, token );
+ }
+
+ int nAddFlags, nRemoveFlags;
+ ParseContents( &nAddFlags, &nRemoveFlags );
+ s_JointContents[i].m_nContents = CONTENTS_SOLID;
+ s_JointContents[i].m_nContents |= nAddFlags;
+ s_JointContents[i].m_nContents &= ~nRemoveFlags;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the default contents
+//-----------------------------------------------------------------------------
+int GetDefaultContents( )
+{
+ return s_nDefaultContents;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns contents for a given joint
+//-----------------------------------------------------------------------------
+static int FindContents( const char* pJointName )
+{
+ for ( int i = s_JointContents.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointContents[i].m_pJointName, pJointName))
+ {
+ return s_JointContents[i].m_nContents;
+ }
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns contents for a given joint
+//-----------------------------------------------------------------------------
+int GetContents( const char* pJointName )
+{
+ while( pJointName )
+ {
+ // First try to find this joint
+ int nContents = FindContents( pJointName );
+ if (nContents != -1)
+ return nContents;
+
+ // If we can't find the joint, then find it's parent...
+ if (!g_numbones)
+ return s_nDefaultContents;
+
+ int i = findGlobalBone( pJointName );
+
+ if ((i >= 0) && (g_bonetable[i].parent >= 0))
+ {
+ pJointName = g_bonetable[g_bonetable[i].parent].name;
+ }
+ else
+ {
+ pJointName = 0;
+ }
+ }
+
+ // No match, return the default one
+ return s_nDefaultContents;
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks specified contents
+//-----------------------------------------------------------------------------
+void ConsistencyCheckContents( )
+{
+ for ( int i = s_JointContents.Count(); --i >= 0; )
+ {
+ int j = findGlobalBone( s_JointContents[i].m_pJointName );
+
+ if (j < 0)
+ {
+ MdlWarning("You specified a joint contents for joint\n"
+ " \"%s\" which either doesn't exist or was optimized out.\n", s_JointSurfaceProp[i].m_pJointName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_BoneMerge( )
+{
+ if( g_bCreateMakefile )
+ return;
+
+ int nIndex = g_BoneMerge.AddToTail();
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_BoneMerge[nIndex].bonename, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Attachment( )
+{
+ if( g_bCreateMakefile )
+ return;
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( g_attachment[g_numattachments].name, token );
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_attachment[g_numattachments].bonename, token );
+
+ Vector tmp;
+
+ // position
+ GetToken (false);
+ tmp.x = verify_atof( token );
+ GetToken (false);
+ tmp.y = verify_atof( token );
+ GetToken (false);
+ tmp.z = verify_atof( token );
+
+ scale_vertex( tmp );
+ // identity matrix
+ AngleMatrix( QAngle( 0, 0, 0 ), g_attachment[g_numattachments].local );
+
+ while (TokenAvailable())
+ {
+ GetToken (false);
+
+ if (stricmp(token,"absolute") == 0)
+ {
+ g_attachment[g_numattachments].type |= IS_ABSOLUTE;
+ AngleIMatrix( g_defaultrotation, g_attachment[g_numattachments].local );
+ // AngleIMatrix( Vector( 0, 0, 0 ), g_attachment[g_numattachments].local );
+ }
+ else if (stricmp(token,"rigid") == 0)
+ {
+ g_attachment[g_numattachments].type |= IS_RIGID;
+ }
+ else if (stricmp(token,"world_align") == 0)
+ {
+ g_attachment[g_numattachments].flags |= ATTACHMENT_FLAG_WORLD_ALIGN;
+ }
+ else if (stricmp(token,"rotate") == 0)
+ {
+ QAngle angles;
+ for (int i = 0; i < 3; ++i)
+ {
+ if (!TokenAvailable())
+ break;
+
+ GetToken(false);
+ angles[i] = verify_atof( token );
+ }
+ AngleMatrix( angles, g_attachment[g_numattachments].local );
+ }
+ else if (stricmp(token,"x_and_z_axes") == 0)
+ {
+ int i;
+ Vector xaxis, yaxis, zaxis;
+ for (i = 0; i < 3; ++i)
+ {
+ if (!TokenAvailable())
+ break;
+
+ GetToken(false);
+ xaxis[i] = verify_atof( token );
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (!TokenAvailable())
+ break;
+
+ GetToken(false);
+ zaxis[i] = verify_atof( token );
+ }
+ VectorNormalize( xaxis );
+ VectorMA( zaxis, -DotProduct( zaxis, xaxis ), xaxis, zaxis );
+ VectorNormalize( zaxis );
+ CrossProduct( zaxis, xaxis, yaxis );
+ MatrixSetColumn( xaxis, 0, g_attachment[g_numattachments].local );
+ MatrixSetColumn( yaxis, 1, g_attachment[g_numattachments].local );
+ MatrixSetColumn( zaxis, 2, g_attachment[g_numattachments].local );
+ MatrixSetColumn( vec3_origin, 3, g_attachment[g_numattachments].local );
+ }
+ else
+ {
+ TokenError("unknown attachment (%s) option: ", g_attachment[g_numattachments].name, token );
+ }
+ }
+
+ g_attachment[g_numattachments].local[0][3] = tmp.x;
+ g_attachment[g_numattachments].local[1][3] = tmp.y;
+ g_attachment[g_numattachments].local[2][3] = tmp.z;
+
+ g_numattachments++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int LookupAttachment( char *name )
+{
+ int i;
+ for (i = 0; i < g_numattachments; i++)
+ {
+ if (stricmp( g_attachment[i].name, name ) == 0)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Renamebone( )
+{
+ // from
+ GetToken (false);
+ V_strcpy_safe( g_renamedbone[g_numrenamedbones].from, token );
+
+ // to
+ GetToken (false);
+ V_strcpy_safe( g_renamedbone[g_numrenamedbones].to, token );
+
+ g_numrenamedbones++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Skiptransition( )
+{
+ int nskips = 0;
+ int list[10];
+
+ while (TokenAvailable())
+ {
+ GetToken (false);
+ list[nskips++] = LookupXNode( token );
+ }
+
+ for (int i = 0; i < nskips; i++)
+ {
+ for (int j = 0; j < nskips; j++)
+ {
+ if (list[i] != list[j])
+ {
+ g_xnodeskip[g_numxnodeskips][0] = list[i];
+ g_xnodeskip[g_numxnodeskips][1] = list[j];
+ g_numxnodeskips++;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// The following code is all related to LODs
+//
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Parse replacemodel command, causes an LOD to use a new model
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceModel( LodScriptData_t& lodData )
+{
+ int i = lodData.modelReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.modelReplacements[i];
+
+ // from
+ GetToken( false );
+
+ // Strip off extensions for the source...
+ char* pDot = strrchr( token, '.' );
+ if (pDot)
+ {
+ *pDot = 0;
+ }
+
+ if (!FindCachedSource( token, "" ))
+ {
+ // must have prior knowledge of the from
+ TokenError( "Unknown replace model '%s'\n", token );
+ }
+
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+
+ // check for "reverse"
+ bool reverse = false;
+ if( TokenAvailable() && GetToken( false ) )
+ {
+ if( stricmp( "reverse", token ) == 0 )
+ {
+ reverse = true;
+ }
+ else
+ {
+ TokenError( "\"%s\" unexpected\n", token );
+ }
+ }
+
+ // If the LOD system tells us to replace "blank", let's forget
+ // we ever read this. Have to do it here so parsing works
+ if( !stricmp( newReplacement.GetSrcName(), "blank" ) )
+ {
+ lodData.modelReplacements.FastRemove( i );
+ return;
+ }
+
+ // Load the source right here baby! That way its bones will get converted
+ if ( !lodData.IsStrippedFromModel() )
+ {
+ newReplacement.m_pSource = Load_Source( newReplacement.GetDstName(), "smd", reverse, false );
+ }
+ else if ( !g_quiet )
+ {
+ printf( "Stripped lod \"%s\" @ %.1f\n", newReplacement.GetDstName(), lodData.switchValue );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse removemodel command, causes an LOD to stop using a model
+//-----------------------------------------------------------------------------
+
+static void Cmd_RemoveModel( LodScriptData_t& lodData )
+{
+ int i = lodData.modelReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.modelReplacements[i];
+
+ // from
+ GetToken( false );
+
+ // Strip off extensions...
+ char* pDot = strrchr( token, '.' );
+ if (pDot)
+ *pDot = 0;
+
+ newReplacement.SetSrcName( token );
+
+ // to
+ newReplacement.SetDstName( "" );
+
+ // If the LOD system tells us to replace "blank", let's forget
+ // we ever read this. Have to do it here so parsing works
+ if( !stricmp( newReplacement.GetSrcName(), "blank" ) )
+ {
+ lodData.modelReplacements.FastRemove( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse replacebone command, causes a part of an LOD model to use a different bone
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceBone( LodScriptData_t& lodData )
+{
+ int i = lodData.boneReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.boneReplacements[i];
+
+ // from
+ GetToken( false );
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+}
+
+//-----------------------------------------------------------------------------
+// Parse bonetreecollapse command, causes the entire subtree to use the same bone as the node
+//-----------------------------------------------------------------------------
+
+static void Cmd_BoneTreeCollapse( LodScriptData_t& lodData )
+{
+ int i = lodData.boneTreeCollapses.AddToTail();
+ CLodScriptReplacement_t& newCollapse = lodData.boneTreeCollapses[i];
+
+ // from
+ GetToken( false );
+ newCollapse.SetSrcName( token );
+}
+
+//-----------------------------------------------------------------------------
+// Parse replacematerial command, causes a material to be used in place of another
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceMaterial( LodScriptData_t& lodData )
+{
+ int i = lodData.materialReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.materialReplacements[i];
+
+ // from
+ GetToken( false );
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+
+ if ( !lodData.IsStrippedFromModel() )
+ {
+ // make sure it goes into the master list
+ UseTextureAsMaterial( LookupTexture( token ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse removemesh command, causes a mesh to not be used anymore
+//-----------------------------------------------------------------------------
+
+static void Cmd_RemoveMesh( LodScriptData_t& lodData )
+{
+ int i = lodData.meshRemovals.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.meshRemovals[i];
+
+ // from
+ GetToken( false );
+ Q_FixSlashes( token );
+ newReplacement.SetSrcName( token );
+}
+
+void Cmd_LOD( const char *cmdname )
+{
+ if ( gflags & STUDIOHDR_FLAGS_HASSHADOWLOD )
+ {
+ MdlError( "Model can only have one $shadowlod and it must be the last lod in the .qc (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+
+ int i = g_ScriptLODs.AddToTail();
+ LodScriptData_t& newLOD = g_ScriptLODs[i];
+
+ if( g_ScriptLODs.Count() > MAX_NUM_LODS )
+ {
+ MdlError( "Too many LODs (MAX_NUM_LODS==%d)\n", ( int )MAX_NUM_LODS );
+ }
+
+ // Shadow lod reserves -1 as switch value
+ // which uniquely identifies a shadow lod
+ newLOD.switchValue = -1.0f;
+
+ bool isShadowCall = ( !stricmp( cmdname, "$shadowlod" ) ) ? true : false;
+
+ if ( isShadowCall )
+ {
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+ MdlWarning( "(%d) : %s: Ignoring switch value on %s command line\n", g_iLinecount, cmdname, g_szLine );
+ }
+
+ // Disable facial animation by default
+ newLOD.EnableFacialAnimation( false );
+ }
+ else
+ {
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+ newLOD.switchValue = verify_atof( token );
+ if ( newLOD.switchValue < 0.0f )
+ {
+ MdlError( "Negative switch value reserved for $shadowlod (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "Expected LOD switch value (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+ }
+
+ GetToken( true );
+ if( stricmp( "{", token ) != 0 )
+ {
+ MdlError( "\"{\" expected while processing %s (%d) : %s", cmdname, g_iLinecount, g_szLine );
+ }
+
+ // In case we are stripping all lods and it's not Lod0, strip it
+ if ( i && g_bStripLods )
+ newLOD.StripFromModel( true );
+
+ while( 1 )
+ {
+ GetToken( true );
+ if( stricmp( "replacemodel", token ) == 0 )
+ {
+ Cmd_ReplaceModel(newLOD);
+ }
+ else if( stricmp( "removemodel", token ) == 0 )
+ {
+ Cmd_RemoveModel(newLOD);
+ }
+ else if( stricmp( "replacebone", token ) == 0 )
+ {
+ Cmd_ReplaceBone( newLOD );
+ }
+ else if( stricmp( "bonetreecollapse", token ) == 0 )
+ {
+ Cmd_BoneTreeCollapse( newLOD );
+ }
+ else if( stricmp( "replacematerial", token ) == 0 )
+ {
+ Cmd_ReplaceMaterial( newLOD );
+ }
+ else if( stricmp( "removemesh", token ) == 0 )
+ {
+ Cmd_RemoveMesh( newLOD );
+ }
+ else if( stricmp( "nofacial", token ) == 0 )
+ {
+ newLOD.EnableFacialAnimation( false );
+ }
+ else if( stricmp( "facial", token ) == 0 )
+ {
+ if (isShadowCall)
+ {
+ // facial animation has no reasonable purpose on a shadow lod
+ TokenError( "Facial animation is not allowed for $shadowlod\n" );
+ }
+
+ newLOD.EnableFacialAnimation( true );
+ }
+ else if ( stricmp( "use_shadowlod_materials", token ) == 0 )
+ {
+ if (isShadowCall)
+ {
+ gflags |= STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS;
+ }
+ }
+ else if( stricmp( "}", token ) == 0 )
+ {
+ break;
+ }
+ else
+ {
+ MdlError( "invalid input while processing %s (%d) : %s", cmdname, g_iLinecount, g_szLine );
+ }
+ }
+
+ // If the LOD is stripped, then forget we saw it
+ if ( newLOD.IsStrippedFromModel() )
+ {
+ g_ScriptLODs.FastRemove( i );
+ }
+}
+
+void Cmd_ShadowLOD( void )
+{
+ if (!g_quiet)
+ {
+ printf( "Processing $shadowlod\n" );
+ }
+
+ // Act like it's a regular lod entry
+ Cmd_LOD( "$shadowlod" );
+
+ // Mark .mdl as having shadow lod (we also check above that we have only one of these
+ // and that it's the last entered lod )
+ gflags |= STUDIOHDR_FLAGS_HASSHADOWLOD;
+}
+
+
+//-----------------------------------------------------------------------------
+// A couple commands related to translucency sorting
+//-----------------------------------------------------------------------------
+void Cmd_Opaque( )
+{
+ // Force Opaque has precedence
+ gflags |= STUDIOHDR_FLAGS_FORCE_OPAQUE;
+ gflags &= ~STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS;
+}
+
+void Cmd_TranslucentTwoPass( )
+{
+ // Force Opaque has precedence
+ if ((gflags & STUDIOHDR_FLAGS_FORCE_OPAQUE) == 0)
+ {
+ gflags |= STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model be rendered with ambient boost heuristic (first used on Alyx in Episode 1)
+//-----------------------------------------------------------------------------
+void Cmd_AmbientBoost()
+{
+ gflags |= STUDIOHDR_FLAGS_AMBIENT_BOOST;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not cast shadows (useful for first-person models as used in L4D)
+//-----------------------------------------------------------------------------
+void Cmd_DoNotCastShadows()
+{
+ gflags |= STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should cast texutre-based shadows in vrad (NOTE: only applicable to prop_static)
+//-----------------------------------------------------------------------------
+void Cmd_CastTextureShadows()
+{
+ gflags |= STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not fade out even if the level or fallback settings say to
+//-----------------------------------------------------------------------------
+void Cmd_NoForcedFade()
+{
+ gflags |= STUDIOHDR_FLAGS_NO_FORCED_FADE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not use the bone origin when calculating bboxes, sequence boxes, etc.
+//-----------------------------------------------------------------------------
+void Cmd_SkipBoneInBBox()
+{
+ g_bUseBoneInBBox = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model will lengthen the viseme check to always include two phonemes
+//-----------------------------------------------------------------------------
+void Cmd_ForcePhonemeCrossfade()
+{
+ gflags |= STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should keep pre-defined bone lengths regardless of animation changes
+//-----------------------------------------------------------------------------
+void Cmd_LockBoneLengths()
+{
+ g_bLockBoneLengths = true;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should replace pre-defined bone lengths and default orientations
+//-----------------------------------------------------------------------------
+void Cmd_UnlockDefineBones()
+{
+ g_bOverridePreDefinedBones = true;
+}
+
+//-----------------------------------------------------------------------------
+// Mark this model as obsolete so that it'll show the obsolete material in game.
+//-----------------------------------------------------------------------------
+void Cmd_Obsolete( )
+{
+ // Force Opaque has precedence
+ gflags |= STUDIOHDR_FLAGS_OBSOLETE;
+}
+
+//-----------------------------------------------------------------------------
+// The bones should be moved so that they center themselves on the verts they own.
+//-----------------------------------------------------------------------------
+void Cmd_CenterBonesOnVerts( )
+{
+ // force centering on bones
+ g_bCenterBonesOnVerts = true;
+}
+
+//-----------------------------------------------------------------------------
+// How far back should simple motion extract pull back from the last frame
+//-----------------------------------------------------------------------------
+void Cmd_MotionExtractionRollBack( )
+{
+ GetToken( false );
+ g_flDefaultMotionRollback = atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// rules for breaking up long animations into multiple sub anims
+//-----------------------------------------------------------------------------
+void Cmd_SectionFrames( )
+{
+ GetToken( false );
+ g_sectionFrames = atof( token );
+ GetToken( false );
+ g_minSectionFrameLimit = atoi( token );
+}
+
+
+//-----------------------------------------------------------------------------
+// world space clamping boundaries for animations
+//-----------------------------------------------------------------------------
+void Cmd_ClampWorldspace( )
+{
+ GetToken (false);
+ g_vecMinWorldspace[0] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMinWorldspace[1] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMinWorldspace[2] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[0] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[1] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[2] = verify_atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Key value block!
+//-----------------------------------------------------------------------------
+void Option_KeyValues( CUtlVector< char > *pKeyValue )
+{
+ // Simply read in the block between { }s as text
+ // and plop it out unchanged into the .mdl file.
+ // Make sure to respect the fact that we may have nested {}s
+ int nLevel = 1;
+
+ if ( !GetToken( true ) )
+ return;
+
+ if ( token[0] != '{' )
+ return;
+
+ AppendKeyValueText( pKeyValue, "mdlkeyvalue\n{\n" );
+
+ while ( GetToken(true) )
+ {
+ if ( !stricmp( token, "}" ) )
+ {
+ nLevel--;
+ if ( nLevel <= 0 )
+ break;
+ AppendKeyValueText( pKeyValue, " }\n" );
+ }
+ else if ( !stricmp( token, "{" ) )
+ {
+ AppendKeyValueText( pKeyValue, "{\n" );
+ nLevel++;
+ }
+ else
+ {
+ // tokens inside braces are quoted
+ if ( nLevel > 1 )
+ {
+ AppendKeyValueText( pKeyValue, "\"" );
+ AppendKeyValueText( pKeyValue, token );
+ AppendKeyValueText( pKeyValue, "\" " );
+ }
+ else
+ {
+ AppendKeyValueText( pKeyValue, token );
+ AppendKeyValueText( pKeyValue, " " );
+ }
+ }
+ }
+
+ if ( nLevel >= 1 )
+ {
+ TokenError( "Keyvalue block missing matching braces.\n" );
+ }
+
+ AppendKeyValueText( pKeyValue, "}\n" );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: force a specific parent child relationship
+//-----------------------------------------------------------------------------
+
+void Cmd_ForcedHierarchy( )
+{
+ // child name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].childname, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].parentname, token );
+
+ g_numforcedhierarchy++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: insert a virtual bone between a child and parent (currently unsupported)
+//-----------------------------------------------------------------------------
+
+void Cmd_InsertHierarchy( )
+{
+ // child name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].childname, token );
+
+ // subparent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].subparentname, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].parentname, token );
+
+ g_numforcedhierarchy++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: rotate a specific bone
+//-----------------------------------------------------------------------------
+
+void Cmd_ForceRealign( )
+{
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_forcedrealign[g_numforcedrealign].name, token );
+
+ // skip
+ GetToken (false);
+
+ // X axis
+ GetToken (false);
+ g_forcedrealign[g_numforcedrealign].rot.x = DEG2RAD( verify_atof( token ) );
+
+ // Y axis
+ GetToken (false);
+ g_forcedrealign[g_numforcedrealign].rot.y = DEG2RAD( verify_atof( token ) );
+
+ // Z axis
+ GetToken (false);
+ g_forcedrealign[g_numforcedrealign].rot.z = DEG2RAD( verify_atof( token ) );
+
+ g_numforcedrealign++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: specify a bone to allow > 180 but < 360 rotation (forces a calculated "mid point" to rotation)
+//-----------------------------------------------------------------------------
+
+void Cmd_LimitRotation( )
+{
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_limitrotation[g_numlimitrotation].name, token );
+
+ while (TokenAvailable())
+ {
+ // sequence name
+ GetToken (false);
+ // This was a call to strcpyn but since sequencename is an array of char*
+ // it was passing sizeof(char*) as the number of characters to copy, which
+ // makes no sense. Commenting out until a better idea comes along.
+ Assert( 0 );
+ //V_strcpy_safe( g_limitrotation[g_numlimitrotation].sequencename[g_limitrotation[g_numlimitrotation].numseq++], token );
+ }
+
+ g_numlimitrotation++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: specify bones to store, even if nothing references them
+//-----------------------------------------------------------------------------
+
+void Cmd_DefineBone( )
+{
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_importbone[g_numimportbones].name, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_importbone[g_numimportbones].parent, token );
+
+ Vector pos;
+ QAngle angles;
+
+ // default pos
+ GetToken (false);
+ pos.x = verify_atof( token );
+ GetToken (false);
+ pos.y = verify_atof( token );
+ GetToken (false);
+ pos.z = verify_atof( token );
+ GetToken (false);
+ angles.x = verify_atof( token );
+ GetToken (false);
+ angles.y = verify_atof( token );
+ GetToken (false);
+ angles.z = verify_atof( token );
+ AngleMatrix( angles, pos, g_importbone[g_numimportbones].rawLocal );
+
+ if (TokenAvailable())
+ {
+ g_importbone[g_numimportbones].bPreAligned = true;
+ // realign pos
+ GetToken (false);
+ pos.x = verify_atof( token );
+ GetToken (false);
+ pos.y = verify_atof( token );
+ GetToken (false);
+ pos.z = verify_atof( token );
+ GetToken (false);
+ angles.x = verify_atof( token );
+ GetToken (false);
+ angles.y = verify_atof( token );
+ GetToken (false);
+ angles.z = verify_atof( token );
+
+ AngleMatrix( angles, pos, g_importbone[g_numimportbones].srcRealign );
+ }
+ else
+ {
+ SetIdentityMatrix( g_importbone[g_numimportbones].srcRealign );
+ }
+
+ g_numimportbones++;
+}
+
+
+//----------------------------------------------------------------------------------------------
+float ParseJiggleStiffness( void )
+{
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting stiffness value\n" );
+ return 0.0f;
+ }
+
+ float stiffness = verify_atof( token );
+
+ const float minStiffness = 0.0f;
+ const float maxStiffness = 1000.0f;
+
+ return clamp( stiffness, minStiffness, maxStiffness );
+}
+
+
+//----------------------------------------------------------------------------------------------
+float ParseJiggleDamping( void )
+{
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting damping value\n" );
+ return 0.0f;
+ }
+
+ float damping = verify_atof( token );
+
+ const float minDamping = 0.0f;
+ const float maxDamping = 10.0f;
+
+ return clamp( damping, minDamping, maxDamping );
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJiggleAngleConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_ANGLE_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting angle value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.angleLimit = verify_atof( token ) * M_PI / 180.0f;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJiggleYawConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_YAW_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting minimum yaw value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.minYaw = verify_atof( token ) * M_PI / 180.0f;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting maximum yaw value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.maxYaw = verify_atof( token ) * M_PI / 180.0f;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJigglePitchConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_PITCH_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting minimum pitch value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.minPitch = verify_atof( token ) * M_PI / 180.0f;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting maximum pitch value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.maxPitch = verify_atof( token ) * M_PI / 180.0f;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse common parameters.
+ * This assumes a token has already been read, and returns true if
+ * the token is recognized and parsed.
+ */
+bool ParseCommonJiggle( s_jigglebone_t *jiggleInfo )
+{
+ if (!stricmp( token, "tip_mass" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.tipMass = verify_atof( token );
+ }
+ else if (!stricmp( token, "length" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.length = verify_atof( token );
+ }
+ else if (!stricmp( token, "angle_constraint" ))
+ {
+ if (ParseJiggleAngleConstraint( jiggleInfo ) == false)
+ {
+ return false;
+ }
+ }
+ else if (!stricmp( token, "yaw_constraint" ))
+ {
+ if (ParseJiggleYawConstraint( jiggleInfo ) == false)
+ {
+ return false;
+ }
+ }
+ else if (!stricmp( token, "yaw_friction" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.yawFriction = verify_atof( token );
+ }
+ else if (!stricmp( token, "yaw_bounce" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.yawBounce = verify_atof( token );
+ }
+ else if (!stricmp( token, "pitch_constraint" ))
+ {
+ if (ParseJigglePitchConstraint( jiggleInfo ) == false)
+ {
+ return false;
+ }
+ }
+ else if (!stricmp( token, "pitch_friction" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.pitchFriction = verify_atof( token );
+ }
+ else if (!stricmp( token, "pitch_bounce" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.pitchBounce = verify_atof( token );
+ }
+ else
+ {
+ // unknown token
+ MdlError( "$jigglebone: invalid syntax '%s'\n", token );
+ return false;
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_flexible subsection
+ */
+bool ParseFlexibleJiggle( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= (JIGGLE_IS_FLEXIBLE | JIGGLE_HAS_LENGTH_CONSTRAINT);
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:is_flexible: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:is_flexible: missing '{'\n" );
+ return false;
+ }
+ else if (!stricmp( token, "}" ))
+ {
+ // definition complete
+ break;
+ }
+ else if (!stricmp( token, "yaw_stiffness" ))
+ {
+ jiggleInfo->data.yawStiffness = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "yaw_damping" ))
+ {
+ jiggleInfo->data.yawDamping = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "pitch_stiffness" ))
+ {
+ jiggleInfo->data.pitchStiffness = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "pitch_damping" ))
+ {
+ jiggleInfo->data.pitchDamping = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "along_stiffness" ))
+ {
+ jiggleInfo->data.alongStiffness = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "along_damping" ))
+ {
+ jiggleInfo->data.alongDamping = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "allow_length_flex" ))
+ {
+ jiggleInfo->data.flags &= ~JIGGLE_HAS_LENGTH_CONSTRAINT;
+ }
+ else if (ParseCommonJiggle( jiggleInfo ) == false)
+ {
+ MdlError( "$jigglebone:is_flexible: invalid syntax '%s'\n", token );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_rigid subsection
+ */
+bool ParseRigidJiggle( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= (JIGGLE_IS_RIGID | JIGGLE_HAS_LENGTH_CONSTRAINT);
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:is_rigid: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:is_rigid: missing '{'\n" );
+ return false;
+ }
+ else if (!stricmp( token, "}" ))
+ {
+ // definition complete
+ break;
+ }
+ else if (ParseCommonJiggle( jiggleInfo ) == false)
+ {
+ MdlError( "$jigglebone:is_rigid: invalid syntax '%s'\n", token );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for has_base_spring subsection
+ */
+bool ParseBaseSpringJiggle( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_BASE_SPRING;
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:has_base_spring: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:has_base_spring: missing '{'\n" );
+ return false;
+ }
+ else if (!stricmp( token, "}" ))
+ {
+ // definition complete
+ break;
+ }
+ else if (!stricmp( token, "stiffness" ))
+ {
+ jiggleInfo->data.baseStiffness = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "damping" ))
+ {
+ jiggleInfo->data.baseDamping = ParseJiggleStiffness();
+ }
+ else if (!stricmp( token, "left_constraint" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMinLeft = verify_atof( token );
+
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMaxLeft = verify_atof( token );
+ }
+ else if (!stricmp( token, "left_friction" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseLeftFriction = verify_atof( token );
+ }
+ else if (!stricmp( token, "up_constraint" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMinUp = verify_atof( token );
+
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMaxUp = verify_atof( token );
+ }
+ else if (!stricmp( token, "up_friction" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseUpFriction = verify_atof( token );
+ }
+ else if (!stricmp( token, "forward_constraint" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMinForward = verify_atof( token );
+
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMaxForward = verify_atof( token );
+ }
+ else if (!stricmp( token, "forward_friction" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseForwardFriction = verify_atof( token );
+ }
+ else if (!stricmp( token, "base_mass" ))
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.baseMass = verify_atof( token );
+ }
+ else if (ParseCommonJiggle( jiggleInfo ) == false)
+ {
+ MdlError( "$jigglebone:has_base_spring: invalid syntax '%s'\n", token );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_boing subsection
+ */
+bool ParseBoing( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_IS_BOING;
+
+ // default values
+ jiggleInfo->data.boingImpactSpeed = 100.0f;
+ jiggleInfo->data.boingImpactAngle = 0.7071f;
+ jiggleInfo->data.boingDampingRate = 0.25f;
+ jiggleInfo->data.boingFrequency = 30.0f;
+ jiggleInfo->data.boingAmplitude = 0.35f;
+
+ bool gotOpenBracket = false;
+ while ( true )
+ {
+ if ( GetToken( true ) == false )
+ {
+ MdlError( "$jigglebone:is_boing: parse error\n" );
+ return false;
+ }
+
+ if ( !stricmp( token, "{" ) )
+ {
+ gotOpenBracket = true;
+ }
+ else if ( !gotOpenBracket )
+ {
+ MdlError( "$jigglebone:is_boing: missing '{'\n" );
+ return false;
+ }
+ else if ( !stricmp( token, "}" ) )
+ {
+ // definition complete
+ break;
+ }
+ else if ( !stricmp( token, "impact_speed" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingImpactSpeed = verify_atof( token );
+ }
+ else if ( !stricmp( token, "impact_angle" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingImpactAngle = cos( DEG2RAD( verify_atof( token ) ) );
+ }
+ else if ( !stricmp( token, "damping_rate" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingDampingRate = verify_atof( token );
+ }
+ else if ( !stricmp( token, "frequency" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingFrequency = verify_atof( token );
+ }
+ else if ( !stricmp( token, "amplitude" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingAmplitude = verify_atof( token );
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse $jigglebone parameters
+ */
+void Cmd_JiggleBone( void )
+{
+ struct s_jigglebone_t *jiggleInfo = &g_jigglebones[ g_numjigglebones ];
+
+ // bone name
+ GetToken( false );
+ V_strcpy_safe( jiggleInfo->bonename, token );
+
+ // default values
+ memset( &jiggleInfo->data, 0, sizeof( mstudiojigglebone_t ) );
+ jiggleInfo->data.length = 10.0f;
+ jiggleInfo->data.yawStiffness = 100.0f;
+ jiggleInfo->data.pitchStiffness = 100.0f;
+ jiggleInfo->data.alongStiffness = 100.0f;
+ jiggleInfo->data.baseStiffness = 100.0f;
+ jiggleInfo->data.baseMinUp = -100.0f;
+ jiggleInfo->data.baseMaxUp = 100.0f;
+ jiggleInfo->data.baseMinLeft = -100.0f;
+ jiggleInfo->data.baseMaxLeft = 100.0f;
+ jiggleInfo->data.baseMinForward = -100.0f;
+ jiggleInfo->data.baseMaxForward = 100.0f;
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone: parse error\n" );
+ return;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone: missing '{'\n" );
+ return;
+ }
+ else if (!stricmp( token, "}" ))
+ {
+ // definition complete
+ break;
+ }
+ else if (!stricmp( token, "is_flexible" ))
+ {
+ if (ParseFlexibleJiggle( jiggleInfo ) == false)
+ {
+ return;
+ }
+ }
+ else if (!stricmp( token, "is_rigid" ))
+ {
+ if (ParseRigidJiggle( jiggleInfo ) == false)
+ {
+ return;
+ }
+ }
+ else if (!stricmp( token, "has_base_spring" ))
+ {
+ if (ParseBaseSpringJiggle( jiggleInfo ) == false)
+ {
+ return;
+ }
+ }
+ else if ( !stricmp( token, "is_boing" ) )
+ {
+ if ( ParseBoing( jiggleInfo ) == false )
+ {
+ return;
+ }
+ }
+ else
+ {
+ MdlError( "$jigglebone: invalid syntax '%s'\n", token );
+ return;
+ }
+ }
+
+ if (!g_quiet)
+ Msg( "Marking bone %s as a jiggle bone\n", jiggleInfo->bonename );
+
+ g_numjigglebones++;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: specify bones to store, even if nothing references them
+//-----------------------------------------------------------------------------
+
+void Cmd_IncludeModel( )
+{
+ GetToken( false );
+ V_strcpy_safe( g_includemodel[g_numincludemodels].name, "models/" );
+ V_strcat_safe( g_includemodel[g_numincludemodels].name, token );
+ g_numincludemodels++;
+}
+
+
+/*
+=================
+=================
+*/
+
+void Grab_Vertexanimation( s_source_t *psource, const char *pAnimName )
+{
+ char cmd[1024];
+ int index;
+ Vector pos;
+ Vector normal;
+ int t = -1;
+ int count = 0;
+ static s_vertanim_t tmpvanim[MAXSTUDIOVERTS*4];
+
+ s_sourceanim_t *pAnim = FindSourceAnim( psource, pAnimName );
+ if ( !pAnim )
+ {
+ MdlError( "Unknown animation %s(%d) : %s\n", pAnimName, g_iLinecount, g_szLine );
+ }
+
+ while (GetLineInput())
+ {
+ if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7)
+ {
+ if ( pAnim->startframe < 0 )
+ {
+ MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ if (t < 0)
+ {
+ MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ tmpvanim[count].vertex = index;
+ VectorCopy( pos, tmpvanim[count].pos );
+ VectorCopy( normal, tmpvanim[count].normal );
+ count++;
+
+ if ( index >= psource->numvertices )
+ {
+ psource->numvertices = index + 1;
+ }
+ }
+ else
+ {
+ // flush data
+
+ if (count)
+ {
+ pAnim->numvanims[t] = count;
+
+ pAnim->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) );
+
+ memcpy( pAnim->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) );
+ }
+ else if (t > 0)
+ {
+ pAnim->numvanims[t] = 0;
+ }
+
+ // next command
+ if (sscanf( g_szLine, "%1023s %d", cmd, &index ))
+ {
+ if (stricmp( cmd, "time" ) == 0)
+ {
+ t = index;
+ count = 0;
+
+ if ( t < pAnim->startframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ if ( t > pAnim->endframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ t -= pAnim->startframe;
+ }
+ else if ( !Q_stricmp( cmd, "end" ) )
+ {
+ pAnim->numframes = pAnim->endframe - pAnim->startframe + 1;
+ return;
+ }
+ else
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ }
+ }
+ MdlError( "unexpected EOF: %s\n", psource->filename );
+}
+
+bool GetGlobalFilePath( const char *pSrc, char *pFullPath, int nMaxLen )
+{
+ char pFileName[1024];
+ Q_strncpy( pFileName, ExpandPath( (char*)pSrc ), sizeof(pFileName) );
+
+ // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead.
+ int nPathLength;
+ if( CmdLib_HasBasePath( pFileName, nPathLength ) )
+ {
+ char tmp[1024];
+ int i;
+
+ int nNumBasePaths = CmdLib_GetNumBasePaths();
+ for( i = 0; i < nNumBasePaths; i++ )
+ {
+ V_strcpy_safe( tmp, CmdLib_GetBasePath( i ) );
+ V_strcat_safe( tmp, pFileName + nPathLength );
+
+ struct _stat buf;
+ int rt = _stat( tmp, &buf );
+ if ( rt != -1 && ( buf.st_size > 0 ) && ( ( buf.st_mode & _S_IFDIR ) == 0 ) )
+ {
+ Q_strncpy( pFullPath, tmp, nMaxLen );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ struct _stat buf;
+ int rt = _stat( pFileName, &buf );
+ if ( rt != -1 && ( buf.st_size > 0 ) && ( ( buf.st_mode & _S_IFDIR ) == 0 ) )
+ {
+ Q_strncpy( pFullPath, pFileName, nMaxLen );
+ return true;
+ }
+ return false;
+}
+
+
+int OpenGlobalFile( char *src )
+{
+ int time1;
+ char filename[1024];
+
+ V_strcpy_safe( filename, ExpandPath( src ) );
+
+ int pathLength;
+ int numBasePaths = CmdLib_GetNumBasePaths();
+ // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead.
+ if( CmdLib_HasBasePath( filename, pathLength ) )
+ {
+ char tmp[1024];
+ int i;
+ for( i = 0; i < numBasePaths; i++ )
+ {
+ V_strcpy_safe( tmp, CmdLib_GetBasePath( i ) );
+ V_strcat_safe( tmp, filename + pathLength );
+ if( g_bCreateMakefile )
+ {
+ CreateMakefile_AddDependency( tmp );
+ return 0;
+ }
+
+ time1 = FileTime( tmp );
+ if( time1 != -1 )
+ {
+ if ((g_fpInput = fopen(tmp, "r" ) ) == 0)
+ {
+ MdlWarning( "reader: could not open file '%s'\n", src );
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ time1 = FileTime (filename);
+ if (time1 == -1)
+ return 0;
+
+ if( g_bCreateMakefile )
+ {
+ CreateMakefile_AddDependency( filename );
+ return 0;
+ }
+ if ((g_fpInput = fopen(filename, "r" ) ) == 0)
+ {
+ MdlWarning( "reader: could not open file '%s'\n", src );
+ return 0;
+ }
+
+ return 1;
+ }
+}
+
+
+
+int Load_VTA( s_source_t *psource )
+{
+ char cmd[1024];
+ int option;
+
+ if (!OpenGlobalFile( psource->filename ))
+ return 0;
+
+ if (!g_quiet)
+ printf ("VTA MODEL %s\n", psource->filename);
+
+ g_iLinecount = 0;
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ sscanf( g_szLine, "%s %d", cmd, &option );
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option != 1)
+ {
+ MdlError("bad version\n");
+ }
+ }
+ else if (stricmp( cmd, "nodes" ) == 0)
+ {
+ psource->numbones = Grab_Nodes( psource->localBone );
+ }
+ else if (stricmp( cmd, "skeleton" ) == 0)
+ {
+ Grab_Animation( psource, "VertexAnimation" );
+ }
+ else if (stricmp( cmd, "vertexanimation" ) == 0)
+ {
+ Grab_Vertexanimation( psource, "VertexAnimation" );
+ }
+ else
+ {
+ MdlWarning("unknown studio command \"%s\"\n", cmd );
+ }
+ }
+ fclose( g_fpInput );
+
+ return 1;
+}
+
+
+void Grab_AxisInterpBones( )
+{
+ char cmd[1024], tmp[1025];
+ Vector basepos;
+ s_axisinterpbone_t *pAxis = NULL;
+ s_axisinterpbone_t *pBone = &g_axisinterpbones[g_numaxisinterpbones];
+
+ while (GetLineInput())
+ {
+ if (IsEnd( g_szLine ))
+ {
+ return;
+ }
+ int i = sscanf( g_szLine, "%1023s \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" %d", cmd, pBone->bonename, tmp, pBone->controlname, tmp, &pBone->axis );
+ if (i == 6 && stricmp( cmd, "bone") == 0)
+ {
+ // printf( "\"%s\" \"%s\" \"%s\" \"%s\"\n", cmd, pBone->bonename, tmp, pBone->controlname );
+ pAxis = pBone;
+ pBone->axis = pBone->axis - 1; // MAX uses 1..3, engine 0..2
+ g_numaxisinterpbones++;
+ pBone = &g_axisinterpbones[g_numaxisinterpbones];
+ }
+ else if (stricmp( cmd, "display" ) == 0)
+ {
+ // skip all display info
+ }
+ else if (stricmp( cmd, "type" ) == 0)
+ {
+ // skip all type info
+ }
+ else if (stricmp( cmd, "basepos" ) == 0)
+ {
+ i = sscanf( g_szLine, "basepos %f %f %f", &basepos.x, &basepos.y, &basepos.z );
+ // skip all type info
+ }
+ else if (stricmp( cmd, "axis" ) == 0)
+ {
+ Vector pos;
+ QAngle rot;
+ int j;
+ i = sscanf( g_szLine, "axis %d %f %f %f %f %f %f", &j, &pos[0], &pos[1], &pos[2], &rot[2], &rot[0], &rot[1] );
+ if (i == 7)
+ {
+ VectorAdd( basepos, pos, pAxis->pos[j] );
+ AngleQuaternion( rot, pAxis->quat[j] );
+ }
+ }
+ }
+}
+
+
+bool Grab_AimAtBones( )
+{
+ s_aimatbone_t *pAimAtBone( &g_aimatbones[g_numaimatbones] );
+
+ // Already know it's <aimconstraint> in the first string, otherwise wouldn't be here
+ if ( sscanf( g_szLine, "%*s %127s %127s %127s", pAimAtBone->bonename, pAimAtBone->parentname, pAimAtBone->aimname ) == 3 )
+ {
+ g_numaimatbones++;
+
+ char cmd[1024];
+ Vector vector;
+
+ while ( GetLineInput() )
+ {
+ g_iLinecount++;
+
+ if (IsEnd( g_szLine ))
+ {
+ return false;
+ }
+
+ if ( sscanf( g_szLine, "%1024s %f %f %f", cmd, &vector[0], &vector[1], &vector[2] ) != 4 )
+ {
+ // Allow blank lines to be skipped without error
+ bool allSpace( true );
+ for ( const char *pC( g_szLine ); *pC != '\0' && pC < ( g_szLine + 4096 ); ++pC )
+ {
+ if ( !V_isspace( *pC ) )
+ {
+ allSpace = false;
+ break;
+ }
+ }
+
+ if ( allSpace )
+ {
+ continue;
+ }
+
+ return true;
+ }
+
+ if ( stricmp( cmd, "<aimvector>" ) == 0)
+ {
+ // Make sure these are unit length on read
+ VectorNormalize( vector );
+ pAimAtBone->aimvector = vector;
+ }
+ else if ( stricmp( cmd, "<upvector>" ) == 0)
+ {
+ // Make sure these are unit length on read
+ VectorNormalize( vector );
+ pAimAtBone->upvector = vector;
+ }
+ else if ( stricmp( cmd, "<basepos>" ) == 0)
+ {
+ pAimAtBone->basepos = vector;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ // If we get here, we're at EOF
+ return false;
+}
+
+
+
+void Grab_QuatInterpBones( )
+{
+ char cmd[1024];
+ Vector basepos;
+ RadianEuler rotateaxis( 0.0f, 0.0f, 0.0f );
+ RadianEuler jointorient( 0.0f, 0.0f, 0.0f );
+ s_quatinterpbone_t *pAxis = NULL;
+ s_quatinterpbone_t *pBone = &g_quatinterpbones[g_numquatinterpbones];
+
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ if (IsEnd( g_szLine ))
+ {
+ return;
+ }
+
+ int i = sscanf( g_szLine, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
+
+ while ( i == 4 && stricmp( cmd, "<aimconstraint>" ) == 0 )
+ {
+ // If Grab_AimAtBones() returns false, there file is at EOF
+ if ( !Grab_AimAtBones() )
+ {
+ return;
+ }
+
+ // Grab_AimAtBones will read input into g_szLine same as here until it gets a line it doesn't understand, at which point
+ // it will exit leaving that line in g_szLine, so check for the end and scan the current buffer again and continue on with
+ // the normal QuatInterpBones process
+
+ i = sscanf( g_szLine, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
+ }
+
+ if (i == 5 && stricmp( cmd, "<helper>") == 0)
+ {
+ // printf( "\"%s\" \"%s\" \"%s\" \"%s\"\n", cmd, pBone->bonename, tmp, pBone->controlname );
+ pAxis = pBone;
+ g_numquatinterpbones++;
+ pBone = &g_quatinterpbones[g_numquatinterpbones];
+ }
+ else if ( i > 0 )
+ {
+ // There was a bug before which could cause the same command to be parsed twice
+ // because if the sscanf above completely fails, it will return 0 and not
+ // change the contents of cmd, so i should be greater than 0 in order for
+ // any of these checks to be valid... Still kind of buggy as these checks
+ // do case insensitive stricmp but then the sscanf does case sensitive
+ // matching afterwards... Should probably change those to
+ // sscanf( g_szLine, "%*s %f ... ) etc...
+
+ if ( stricmp( cmd, "<display>" ) == 0)
+ {
+ // skip all display info
+ Vector size;
+ float distance;
+
+ i = sscanf( g_szLine, "<display> %f %f %f %f",
+ &size[0], &size[1], &size[2],
+ &distance );
+
+ if (i == 4)
+ {
+ pAxis->percentage = distance / 100.0;
+ pAxis->size = size;
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual <display> bone: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else if ( stricmp( cmd, "<basepos>" ) == 0)
+ {
+ i = sscanf( g_szLine, "<basepos> %f %f %f", &basepos.x, &basepos.y, &basepos.z );
+ // skip all type info
+ }
+ else if ( stricmp( cmd, "<rotateaxis>" ) == 0)
+ {
+ i = sscanf( g_szLine, "%*s %f %f %f", &rotateaxis.x, &rotateaxis.y, &rotateaxis.z );
+ rotateaxis.x = DEG2RAD( rotateaxis.x );
+ rotateaxis.y = DEG2RAD( rotateaxis.y );
+ rotateaxis.z = DEG2RAD( rotateaxis.z );
+ }
+ else if ( stricmp( cmd, "<jointorient>" ) == 0)
+ {
+ i = sscanf( g_szLine, "%*s %f %f %f", &jointorient.x, &jointorient.y, &jointorient.z );
+ jointorient.x = DEG2RAD( jointorient.x );
+ jointorient.y = DEG2RAD( jointorient.y );
+ jointorient.z = DEG2RAD( jointorient.z );
+ }
+ else if ( stricmp( cmd, "<trigger>" ) == 0)
+ {
+ float tolerance;
+ RadianEuler trigger;
+ Vector pos;
+ RadianEuler ang;
+
+ QAngle rot;
+ int j;
+ i = sscanf( g_szLine, "<trigger> %f %f %f %f %f %f %f %f %f %f",
+ &tolerance,
+ &trigger.x, &trigger.y, &trigger.z,
+ &ang.x, &ang.y, &ang.z,
+ &pos.x, &pos.y, &pos.z );
+
+ if (i == 10)
+ {
+ trigger.x = DEG2RAD( trigger.x );
+ trigger.y = DEG2RAD( trigger.y );
+ trigger.z = DEG2RAD( trigger.z );
+ ang.x = DEG2RAD( ang.x );
+ ang.y = DEG2RAD( ang.y );
+ ang.z = DEG2RAD( ang.z );
+
+ Quaternion q;
+ AngleQuaternion( ang, q );
+
+ if ( rotateaxis.x != 0.0 || rotateaxis.y != 0.0 || rotateaxis.z != 0.0 )
+ {
+ Quaternion q1;
+ Quaternion q2;
+ AngleQuaternion( rotateaxis, q1 );
+ QuaternionMult( q1, q, q2 );
+ q = q2;
+ }
+
+ if ( jointorient.x != 0.0 || jointorient.y != 0.0 || jointorient.z != 0.0 )
+ {
+ Quaternion q1;
+ Quaternion q2;
+ AngleQuaternion( jointorient, q1 );
+ QuaternionMult( q, q1, q2 );
+ q = q2;
+ }
+
+ j = pAxis->numtriggers++;
+ pAxis->tolerance[j] = DEG2RAD( tolerance );
+ AngleQuaternion( trigger, pAxis->trigger[j] );
+ VectorAdd( basepos, pos, pAxis->pos[j] );
+ pAxis->quat[j] = q;
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual <trigger> bone: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual bone data: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ // Allow blank lines to be skipped without error
+ bool allSpace( true );
+ for ( const char *pC( g_szLine ); *pC != '\0' && pC < ( g_szLine + 4096 ); ++pC )
+ {
+ if ( !V_isspace( *pC ) )
+ {
+ allSpace = false;
+ break;
+ }
+ }
+
+ if ( !allSpace )
+ {
+ MdlError( "Line %d: Unable to parse procedual bone data: %s", g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+void Load_ProceduralBones( )
+{
+ char filename[256];
+ char cmd[1024];
+ int option;
+
+ GetToken( false );
+ V_strcpy_safe( filename, token );
+
+ if (!OpenGlobalFile( filename ))
+ return;
+
+ g_iLinecount = 0;
+
+ char ext[32];
+ Q_ExtractFileExtension( filename, ext, sizeof( ext ) );
+
+ if (stricmp( ext, "vrd") == 0)
+ {
+ Grab_QuatInterpBones( );
+ }
+ else
+ {
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ sscanf( g_szLine, "%s %d", cmd, &option );
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option != 1)
+ {
+ MdlError("bad version\n");
+ }
+ }
+ else if (stricmp( cmd, "proceduralbones" ) == 0)
+ {
+ Grab_AxisInterpBones( );
+ }
+ }
+ }
+ fclose( g_fpInput );
+}
+
+
+void Cmd_CD()
+{
+ if (cdset)
+ MdlError ("Two $cd in one model");
+ cdset = true;
+ GetToken (false);
+ V_strcpy_safe (cddir[0], token);
+ V_strcat_safe (cddir[0], "/" );
+ numdirs = 0;
+}
+
+
+void Cmd_CDMaterials()
+{
+ while (TokenAvailable())
+ {
+ GetToken (false);
+
+ char szPath[512];
+ Q_strncpy( szPath, token, sizeof( szPath ) );
+
+ int len = strlen( szPath );
+ if ( len > 0 && szPath[len-1] != '/' && szPath[len-1] != '\\' )
+ {
+ Q_strncat( szPath, "/", sizeof( szPath ), COPY_ALL_CHARACTERS );
+ }
+
+ Q_FixSlashes( szPath );
+ cdtextures[numcdtextures] = strdup( szPath );
+ numcdtextures++;
+ }
+}
+
+
+void Cmd_Pushd()
+{
+ GetToken(false);
+
+ V_strcpy_safe( cddir[numdirs+1], cddir[numdirs] );
+ V_strcat_safe( cddir[numdirs+1], token );
+ V_strcat_safe( cddir[numdirs+1], "/" );
+ numdirs++;
+}
+
+void Cmd_Popd()
+{
+ if (numdirs > 0)
+ numdirs--;
+}
+
+void Cmd_CollisionModel()
+{
+ DoCollisionModel( false );
+}
+
+void Cmd_CollisionJoints()
+{
+ DoCollisionModel( true );
+}
+
+void Cmd_ExternalTextures()
+{
+ MdlWarning( "ignoring $externaltextures, obsolete..." );
+}
+
+void Cmd_ClipToTextures()
+{
+ clip_texcoords = 1;
+}
+
+void Cmd_CollapseBones()
+{
+ g_collapse_bones = true;
+}
+
+void Cmd_CollapseBonesAggressive()
+{
+ g_collapse_bones = true;
+ g_collapse_bones_aggressive = true;
+}
+
+void Cmd_AlwaysCollapse()
+{
+ g_collapse_bones = true;
+ GetToken(false);
+ g_collapse.AddToTail( strdup( token ) );
+}
+
+void Cmd_CalcTransitions()
+{
+ g_bMultistageGraph = true;
+}
+
+void Cmd_StaticProp()
+{
+ g_staticprop = true;
+ gflags |= STUDIOHDR_FLAGS_STATIC_PROP;
+}
+
+void Cmd_ZBrush()
+{
+ g_bZBrush = true;
+}
+
+void Cmd_RealignBones()
+{
+ g_realignbones = true;
+}
+
+void Cmd_BaseLOD()
+{
+ Cmd_LOD( "$lod" );
+}
+
+void Cmd_KeyValues()
+{
+ Option_KeyValues( &g_KeyValueText );
+}
+
+void Cmd_ConstDirectionalLight()
+{
+ gflags |= STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT;
+
+ GetToken (false);
+ g_constdirectionalightdot = (byte)( verify_atof(token) * 255.0f );
+}
+
+void Cmd_MinLOD()
+{
+ GetToken( false );
+ g_minLod = atoi( token );
+
+ // "minlod" rules over "allowrootlods"
+ if ( g_numAllowedRootLODs > 0 && g_numAllowedRootLODs < g_minLod )
+ {
+ MdlWarning( "$minlod %d overrides $allowrootlods %d, proceeding with $allowrootlods %d.\n", g_minLod, g_numAllowedRootLODs, g_minLod );
+ g_numAllowedRootLODs = g_minLod;
+ }
+}
+
+void Cmd_AllowRootLODs()
+{
+ GetToken( false );
+ g_numAllowedRootLODs = atoi( token );
+
+ // Root LOD restriction has to obey "minlod" request
+ if ( g_numAllowedRootLODs > 0 && g_numAllowedRootLODs < g_minLod )
+ {
+ MdlWarning( "$allowrootlods %d is conflicting with $minlod %d, proceeding with $allowrootlods %d.\n", g_numAllowedRootLODs, g_minLod, g_minLod );
+ g_numAllowedRootLODs = g_minLod;
+ }
+}
+
+
+void Cmd_BoneSaveFrame( )
+{
+ s_bonesaveframe_t tmp;
+
+ // bone name
+ GetToken( false );
+ V_strcpy_safe( tmp.name, token );
+
+ tmp.bSavePos = false;
+ tmp.bSaveRot = false;
+ while (TokenAvailable( ))
+ {
+ GetToken( false );
+ if (stricmp( "position", token ) == 0)
+ {
+ tmp.bSavePos = true;
+ }
+ else if (stricmp( "rotation", token ) == 0)
+ {
+ tmp.bSaveRot = true;
+ }
+ else
+ {
+ MdlError( "unknown option \"%s\" on $bonesaveframe : %s\n", token, tmp.name );
+ }
+ }
+
+ g_bonesaveframe.AddToTail( tmp );
+}
+
+
+//
+// This is the master list of the commands a QC file supports.
+// To add a new command to the QC files, add it here.
+//
+struct
+{
+ char *m_pName;
+ void (*m_pCmd)();
+} g_Commands[] =
+{
+ { "$cd", Cmd_CD },
+ { "$modelname", Cmd_Modelname },
+ { "$cdmaterials", Cmd_CDMaterials },
+ { "$pushd", Cmd_Pushd },
+ { "$popd", Cmd_Popd },
+ { "$scale", Cmd_ScaleUp },
+ { "$root", Cmd_Root },
+ { "$controller", Cmd_Controller },
+ { "$screenalign", Cmd_ScreenAlign },
+ { "$model", Cmd_Model },
+ { "$collisionmodel", Cmd_CollisionModel },
+ { "$collisionjoints", Cmd_CollisionJoints },
+ { "$collisiontext", Cmd_CollisionText },
+ { "$body", Cmd_Body },
+ { "$bodygroup", Cmd_Bodygroup },
+ { "$animation", Cmd_Animation },
+ { "$autocenter", Cmd_Autocenter },
+ { "$sequence", Cmd_Sequence },
+ { "$append", Cmd_Append },
+ { "$prepend", Cmd_Prepend },
+ { "$continue", Cmd_Continue },
+ { "$declaresequence", Cmd_DeclareSequence },
+ { "$declareanimation", Cmd_DeclareAnimation },
+ { "$cmdlist", Cmd_Cmdlist },
+ { "$animblocksize", Cmd_AnimBlockSize },
+ { "$weightlist", Cmd_Weightlist },
+ { "$defaultweightlist", Cmd_DefaultWeightlist },
+ { "$ikchain", Cmd_IKChain },
+ { "$ikautoplaylock", Cmd_IKAutoplayLock },
+ { "$eyeposition", Cmd_Eyeposition },
+ { "$illumposition", Cmd_Illumposition },
+ { "$origin", Cmd_Origin },
+ { "$upaxis", Cmd_UpAxis },
+ { "$bbox", Cmd_BBox },
+ { "$cbox", Cmd_CBox },
+ { "$gamma", Cmd_Gamma },
+ { "$texturegroup", Cmd_TextureGroup },
+ { "$hgroup", Cmd_Hitgroup },
+ { "$hbox", Cmd_Hitbox },
+ { "$hboxset", Cmd_HitboxSet },
+ { "$surfaceprop", Cmd_SurfaceProp },
+ { "$jointsurfaceprop", Cmd_JointSurfaceProp },
+ { "$contents", Cmd_Contents },
+ { "$jointcontents", Cmd_JointContents },
+ { "$attachment", Cmd_Attachment },
+ { "$bonemerge", Cmd_BoneMerge },
+ { "$externaltextures", Cmd_ExternalTextures },
+ { "$cliptotextures", Cmd_ClipToTextures },
+ { "$renamebone", Cmd_Renamebone },
+ { "$collapsebones", Cmd_CollapseBones },
+ { "$collapsebonesaggressive", Cmd_CollapseBonesAggressive },
+ { "$alwayscollapse", Cmd_AlwaysCollapse },
+ { "$proceduralbones", Load_ProceduralBones },
+ { "$skiptransition", Cmd_Skiptransition },
+ { "$calctransitions", Cmd_CalcTransitions },
+ { "$staticprop", Cmd_StaticProp },
+ { "$zbrush", Cmd_ZBrush },
+ { "$realignbones", Cmd_RealignBones },
+ { "$forcerealign", Cmd_ForceRealign },
+ { "$lod", Cmd_BaseLOD },
+ { "$shadowlod", Cmd_ShadowLOD },
+ { "$poseparameter", Cmd_PoseParameter },
+ { "$heirarchy", Cmd_ForcedHierarchy },
+ { "$hierarchy", Cmd_ForcedHierarchy },
+ { "$insertbone", Cmd_InsertHierarchy },
+ { "$limitrotation", Cmd_LimitRotation },
+ { "$definebone", Cmd_DefineBone },
+ { "$jigglebone", Cmd_JiggleBone },
+ { "$includemodel", Cmd_IncludeModel },
+ { "$opaque", Cmd_Opaque },
+ { "$mostlyopaque", Cmd_TranslucentTwoPass },
+// { "$platform", Cmd_Platform },
+ { "$keyvalues", Cmd_KeyValues },
+ { "$obsolete", Cmd_Obsolete },
+ { "$renamematerial", Cmd_RenameMaterial },
+ { "$fakevta", Cmd_FakeVTA },
+ { "$noforcedfade", Cmd_NoForcedFade },
+ { "$skipboneinbbox", Cmd_SkipBoneInBBox },
+ { "$forcephonemecrossfade", Cmd_ForcePhonemeCrossfade },
+ { "$lockbonelengths", Cmd_LockBoneLengths },
+ { "$unlockdefinebones", Cmd_UnlockDefineBones },
+ { "$constantdirectionallight", Cmd_ConstDirectionalLight },
+ { "$minlod", Cmd_MinLOD },
+ { "$allowrootlods", Cmd_AllowRootLODs },
+ { "$bonesaveframe", Cmd_BoneSaveFrame },
+ { "$ambientboost", Cmd_AmbientBoost },
+ { "$centerbonesonverts", Cmd_CenterBonesOnVerts },
+ { "$donotcastshadows", Cmd_DoNotCastShadows },
+ { "$casttextureshadows", Cmd_CastTextureShadows },
+ { "$motionrollback", Cmd_MotionExtractionRollBack },
+ { "$sectionframes", Cmd_SectionFrames },
+ { "$clampworldspace", Cmd_ClampWorldspace },
+ { "$maxeyedeflection", Cmd_MaxEyeDeflection },
+ { "$boneflexdriver", Cmd_BoneFlexDriver },
+ { "$checkuv", Cmd_CheckUV }
+};
+
+
+/*
+===============
+ParseScript
+===============
+*/
+void ParseScript (void)
+{
+ while (1)
+ {
+ GetToken (true);
+ if (endofscript)
+ return;
+
+ // Check all the commands we know about.
+ int i;
+ for ( i=0; i < ARRAYSIZE( g_Commands ); i++ )
+ {
+ if ( !stricmp( g_Commands[i].m_pName, token ) )
+ {
+ g_Commands[i].m_pCmd();
+ break;
+ }
+ }
+ if ( i == ARRAYSIZE( g_Commands ) )
+ {
+ if( !g_bCreateMakefile )
+ {
+ TokenError("bad command %s\n", token);
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Generate the model name
+//-----------------------------------------------------------------------------
+bool GenerateModelName( CDmeMDLMakefile *pMDLMakeFile )
+{
+ // The model name is implicit in the makefile name
+ // NOTE: Model name is relative to the 'models' directory
+ char pOutputFullPath[MAX_PATH];
+ pMDLMakeFile->GetOutputName( pOutputFullPath, sizeof(pOutputFullPath) );
+ Q_SetExtension( pOutputFullPath, ".mdl", sizeof( pOutputFullPath) );
+
+ char pModelSubDir[MAX_PATH];
+ GetModSubdirectory( "models", pModelSubDir, sizeof(pModelSubDir) );
+
+ char pRelativePath[MAX_PATH];
+ if ( !Q_MakeRelativePath( pOutputFullPath, pModelSubDir, pRelativePath, sizeof(pRelativePath) ) )
+ {
+ MdlError( "Makefile \"%s\" doesn't lie under the correct vproject \"%s\"!\n",
+ pOutputFullPath, pModelSubDir );
+ return false;
+ }
+
+ ProcessModelName( pRelativePath );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process skins
+//-----------------------------------------------------------------------------
+bool GenerateSkin( CDmeMDLMakefile *pMDLMakeFile )
+{
+ CUtlVector< CDmeHandle< CDmeSourceSkin > > bodies;
+ pMDLMakeFile->GetSources< CDmeSourceSkin >( bodies );
+ int nCount = bodies.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !bodies[i] )
+ continue;
+
+ char pFullPath[MAX_PATH];
+ pMDLMakeFile->GetSourceFullPath( bodies[i], pFullPath, sizeof(pFullPath) );
+
+ // Empty strings are ignored
+ if ( !pFullPath[0] )
+ continue;
+
+ ProcessCmdBody( pFullPath, bodies[i] );
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process animations
+//-----------------------------------------------------------------------------
+bool GenerateAnimations( CDmeMDLMakefile *pMDLMakeFile )
+{
+ CUtlVector< CDmeHandle< CDmeSourceAnimation > > animationFiles;
+ pMDLMakeFile->GetSources< CDmeSourceAnimation >( animationFiles );
+
+ int nCount = animationFiles.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !animationFiles[i] )
+ continue;
+
+ char pFullPath[MAX_PATH];
+ pMDLMakeFile->GetSourceFullPath( animationFiles[i], pFullPath, sizeof(pFullPath) );
+
+ // Empty strings are ignored
+ if ( !pFullPath[0] )
+ continue;
+
+ // Totally spoof the animation info.. not sure where to get it from yet
+ // assume it's an animation reference
+ // first look up an existing animation
+ s_sequence_t *pseq = ProcessCmdSequence( animationFiles[i]->m_AnimationName );
+ if ( !pseq )
+ continue;
+
+ int n;
+ s_animation_t *animations[64];
+ int numblends = 0;
+ for ( n = 0; n < g_numani; n++ )
+ {
+ if ( !Q_stricmp( pFullPath, g_panimation[n]->name ) )
+ {
+ animations[numblends++] = g_panimation[n];
+ break;
+ }
+ }
+
+ if ( n >= g_numani )
+ {
+ // assume it's an implied animation
+ animations[numblends++] = Cmd_ImpliedAnimation( pseq, pFullPath );
+ }
+ // hack to allow animation commands to refer to same sequence
+ if ( numblends == 1 )
+ {
+ pseq->panim[0][0] = animations[0];
+ }
+
+ // Look up the source animation from the animation name
+ for ( int j = 0; j < numblends; ++j )
+ {
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( animations[j]->source, animationFiles[i]->m_SourceAnimationName );
+
+ // NOTE: This always affects the first source anim read in
+ if ( pSourceAnim )
+ {
+ animations[j]->startframe = pSourceAnim->startframe;
+ animations[j]->endframe = pSourceAnim->endframe;
+
+ if ( !g_bCreateMakefile && animations[j]->endframe < animations[j]->startframe )
+ {
+ TokenError( "end frame before start frame in %s", animations[j]->name );
+ }
+
+ animations[j]->numframes = animations[j]->endframe - animations[j]->startframe + 1;
+ Q_strncpy( animations[j]->animationname, animationFiles[i]->m_SourceAnimationName, sizeof(animations[j]->animationname) );
+ }
+ else
+ {
+ MdlError( "Requested unknown animation block name %s\n", animationFiles[i]->m_SourceAnimationName.Get() );
+ }
+ }
+
+ ProcessSequence( pseq, numblends, animations, false );
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the MDL makefile
+//-----------------------------------------------------------------------------
+void ParseMDLMakeFile( CDmeMDLMakefile *pMDLMakeFile )
+{
+ if ( !GenerateModelName( pMDLMakeFile ) )
+ return;
+
+ // All DMX files have Y as the up axis
+ RadianEuler angles( M_PI / 2.0f, 0.0f, M_PI / 2.0f );
+ ProcessUpAxis( angles );
+
+ // Process bodies
+ if ( !GenerateSkin( pMDLMakeFile ) )
+ return;
+
+ // Process animations
+ if ( !GenerateAnimations( pMDLMakeFile ) )
+ return;
+}
+
+
+// Used by the CheckSurfaceProps.py script.
+// They specify the .mdl file and it prints out all the surface props that the model uses.
+bool HandlePrintSurfaceProps( int &returnValue )
+{
+ const char *pFilename = CommandLine()->ParmValue( "-PrintSurfaceProps", (const char*)NULL );
+ if ( pFilename )
+ {
+ CUtlVector<char> buf;
+
+ FILE *fp = fopen( pFilename, "rb" );
+ if ( fp )
+ {
+ fseek( fp, 0, SEEK_END );
+ buf.SetSize( ftell( fp ) );
+ fseek( fp, 0, SEEK_SET );
+ fread( buf.Base(), 1, buf.Count(), fp );
+
+ fclose( fp );
+
+ studiohdr_t *pHdr = (studiohdr_t*)buf.Base();
+
+ Studio_ConvertStudioHdrToNewVersion( pHdr );
+
+ if ( pHdr->version == STUDIO_VERSION )
+ {
+ for ( int i=0; i < pHdr->numbones; i++ )
+ {
+ mstudiobone_t *pBone = pHdr->pBone( i );
+ printf( "%s\n", pBone->pszSurfaceProp() );
+ }
+
+ returnValue = 0;
+ }
+ else
+ {
+ printf( "-PrintSurfaceProps: '%s' is wrong version (%d should be %d).\n",
+ pFilename, pHdr->version, STUDIO_VERSION );
+ returnValue = 1;
+ }
+ }
+ else
+ {
+ printf( "-PrintSurfaceProps: can't open '%s'\n", pFilename );
+ returnValue = 1;
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// Used by the modelstats.pl script.
+// They specify the .mdl file and it prints out perf info.
+bool HandleMdlReport( int &returnValue )
+{
+ const char *pFilename = CommandLine()->ParmValue( "-mdlreport", (const char*)NULL );
+ if ( pFilename )
+ {
+ CUtlVector<char> buf;
+
+ FILE *fp = fopen( pFilename, "rb" );
+ if ( fp )
+ {
+ fseek( fp, 0, SEEK_END );
+ buf.SetSize( ftell( fp ) );
+ fseek( fp, 0, SEEK_SET );
+ fread( buf.Base(), 1, buf.Count(), fp );
+
+ fclose( fp );
+
+ studiohdr_t *pHdr = (studiohdr_t*)buf.Base();
+
+ Studio_ConvertStudioHdrToNewVersion( pHdr );
+
+ if ( pHdr->version == STUDIO_VERSION )
+ {
+ int flags = SPEWPERFSTATS_SHOWPERF;
+ if( CommandLine()->CheckParm( "-mdlreportspreadsheet", NULL ) )
+ {
+ flags |= SPEWPERFSTATS_SPREADSHEET;
+ }
+ SpewPerfStats( pHdr, pFilename, flags );
+
+ returnValue = 0;
+ }
+ else
+ {
+ printf( "-mdlreport: '%s' is wrong version (%d should be %d).\n",
+ pFilename, pHdr->version, STUDIO_VERSION );
+ returnValue = 1;
+ }
+ }
+ else
+ {
+ printf( "-mdlreport: can't open '%s'\n", pFilename );
+ returnValue = 1;
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void UsageAndExit()
+{
+ MdlError( "Bad or missing options\n"
+ "usage: studiomdl [options] <file.qc>\n"
+ "options:\n"
+ "[-a <normal_blend_angle>]\n"
+ "[-checklengths]\n"
+ "[-d] - dump glview files\n"
+ "[-definebones]\n"
+ "[-f] - flip all triangles\n"
+ "[-fullcollide] - don't truncate really big collisionmodels\n"
+ "[-game <gamedir>]\n"
+ "[-h] - dump hboxes\n"
+ "[-i] - ignore warnings\n"
+ "[-minlod <lod>] - truncate to highest detail <lod>\n"
+ "[-n] - tag bad normals\n"
+ "[-perf] report perf info upon compiling model\n"
+ "[-printbones]\n"
+ "[-printgraph]\n"
+ "[-quiet] - operate silently\n"
+ "[-r] - tag reversed\n"
+ "[-t <texture>]\n"
+ "[-x360] - generate xbox360 output\n"
+ "[-nox360] - disable xbox360 output(default)\n"
+ "[-nowarnings] - disable warnings\n"
+ "[-dumpmaterials] - dump out material names\n"
+ "[-mdlreport] model.mdl - report perf info\n"
+ "[-mdlreportspreadsheet] - report perf info as a comma-delimited spreadsheet\n"
+ "[-striplods] - use only lod0\n"
+ "[-overridedefinebones] - equivalent to specifying $unlockdefinebones in .qc file\n"
+ "[-stripmodel] - process binary model files and strip extra lod data\n"
+ "[-stripvhv] - strip hardware verts to match the stripped model\n"
+ "[-vsi] - generate stripping information .vsi file - can be used on .mdl files too\n"
+ );
+}
+
+#ifndef _DEBUG
+
+LONG __stdcall VExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
+{
+ MdlExceptionFilter( ExceptionInfo->ExceptionRecord->ExceptionCode );
+ return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway)
+}
+
+#endif
+/*
+==============
+main
+==============
+*/
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+class CStudioMDLApp : public CDefaultAppSystemGroup< CSteamAppSystemGroup >
+{
+ typedef CDefaultAppSystemGroup< CSteamAppSystemGroup > BaseClass;
+
+public:
+ // Methods of IApplication
+ virtual bool Create();
+ virtual bool PreInit( );
+ virtual int Main();
+ virtual void PostShutdown();
+
+private:
+ int Main_StripModel();
+ int Main_StripVhv();
+ int Main_MakeVsi();
+
+private:
+ bool ParseArguments();
+};
+
+static bool CStudioMDLApp_SuggestGameInfoDirFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories )
+{
+ const char *pProcessFileName = NULL;
+ int nParmCount = CommandLine()->ParmCount();
+ if ( nParmCount > 1 )
+ {
+ pProcessFileName = CommandLine()->GetParm( nParmCount - 1 );
+ }
+
+ if ( pProcessFileName )
+ {
+ Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, pProcessFileName );
+
+ if ( pbBubbleDirectories )
+ *pbBubbleDirectories = true;
+
+ return true;
+ }
+
+ return false;
+}
+
+int main( int argc, char **argv )
+{
+ SetSuggestGameInfoDirFn( CStudioMDLApp_SuggestGameInfoDirFn );
+
+ CStudioMDLApp s_ApplicationObject;
+ CSteamApplication s_SteamApplicationObject( &s_ApplicationObject );
+ return AppMain( argc, argv, &s_SteamApplicationObject );
+}
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+bool CStudioMDLApp::Create()
+{
+ InstallSpewFunction();
+ // override the default spew function
+ SpewOutputFunc( MdlSpewOutputFunc );
+
+ MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
+
+#ifndef _DEBUG
+ SetUnhandledExceptionFilter( VExceptionFilter );
+#endif
+
+ if ( CommandLine()->ParmCount() == 1 )
+ {
+ UsageAndExit();
+ return false;
+ }
+
+ int nReturnValue;
+ if ( HandlePrintSurfaceProps( nReturnValue ) )
+ return false;
+
+ if ( !ParseArguments() )
+ return false;
+
+ AppSystemInfo_t appSystems[] =
+ {
+ { "vstdlib.dll", PROCESS_UTILS_INTERFACE_VERSION },
+ { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION },
+ { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION },
+ { "mdllib.dll", MDLLIB_INTERFACE_VERSION },
+ { "", "" } // Required to terminate the list
+ };
+
+ AddSystem( g_pDataModel, VDATAMODEL_INTERFACE_VERSION );
+ AddSystem( g_pDmElementFramework, VDMELEMENTFRAMEWORK_VERSION );
+ AddSystem( g_pDmSerializers, DMSERIALIZERS_INTERFACE_VERSION );
+
+ // Add in the locally-defined studio data cache
+ AppModule_t studioDataCacheModule = LoadModule( Sys_GetFactoryThis() );
+ AddSystem( studioDataCacheModule, STUDIO_DATA_CACHE_INTERFACE_VERSION );
+
+ // Add the P4 module separately so that if it is absent (say in the SDK) then the other system will initialize properly
+ if ( !CommandLine()->FindParm( "-nop4" ) )
+ {
+ AppModule_t p4Module = LoadModule( "p4lib.dll" );
+ AddSystem( p4Module, P4_INTERFACE_VERSION );
+ }
+
+ bool bOk = AddSystems( appSystems );
+ if ( !bOk )
+ return false;
+
+ IMaterialSystem *pMaterialSystem = (IMaterialSystem*)FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION );
+ if ( !pMaterialSystem )
+ return false;
+
+ pMaterialSystem->SetShaderAPI( "shaderapiempty.dll" );
+
+ return true;
+}
+
+bool CStudioMDLApp::PreInit( )
+{
+ CreateInterfaceFn factory = GetFactory();
+ ConnectTier1Libraries( &factory, 1 );
+ ConnectTier2Libraries( &factory, 1 );
+ ConnectTier3Libraries( &factory, 1 );
+
+ if ( !g_pFullFileSystem || !g_pDataModel || !g_pMaterialSystem || !g_pStudioRender )
+ {
+ Warning( "StudioMDL is missing a required interface!\n" );
+ return false;
+ }
+
+ if ( !SetupSearchPaths( g_path, false, true ) )
+ return false;
+
+ // NOTE: This is necessary to get the cmdlib filesystem stuff to work.
+ g_pFileSystem = g_pFullFileSystem;
+
+ // NOTE: This is stuff copied out of cmdlib necessary to get
+ // the tools in cmdlib working
+ FileSystem_SetupStandardDirectories( g_path, GetGameInfoPath() );
+ return true;
+}
+
+
+void CStudioMDLApp::PostShutdown()
+{
+ DisconnectTier3Libraries();
+ DisconnectTier2Libraries();
+ DisconnectTier1Libraries();
+}
+
+
+//-----------------------------------------------------------------------------
+// Method which parses arguments
+//-----------------------------------------------------------------------------
+bool CStudioMDLApp::ParseArguments()
+{
+ g_currentscale = g_defaultscale = 1.0;
+ g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 );
+
+ // skip weightlist 0
+ g_numweightlist = 1;
+
+ eyeposition = Vector( 0, 0, 0 );
+ gflags = 0;
+ numrep = 0;
+ tag_reversed = 0;
+ tag_normals = 0;
+
+ normal_blend = cos( DEG2RAD( 2.0 ));
+
+ g_gamma = 2.2;
+
+ g_staticprop = false;
+ g_centerstaticprop = false;
+
+ g_realignbones = false;
+ g_constdirectionalightdot = 0;
+
+ g_bDumpGLViewFiles = false;
+ g_quiet = false;
+
+ g_illumpositionattachment = 0;
+ g_flMaxEyeDeflection = 0.0f;
+
+ int argc = CommandLine()->ParmCount();
+ int i;
+ for ( i = 1; i < argc - 1; i++ )
+ {
+ const char *pArgv = CommandLine()->GetParm( i );
+ if ( pArgv[0] != '-' )
+ continue;
+
+ if ( !Q_stricmp( pArgv, "-allowdebug" ) )
+ {
+ // Ignore, used by interface system to catch debug builds checked into release tree
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-mdlreport" ) )
+ {
+ // Will reparse later, ignore rest of arguments.
+ return true;
+ }
+
+ if ( !Q_stricmp( pArgv, "-mdlreportspreadsheet" ) )
+ {
+ // Will reparse later, ignore for now.
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-ihvtest" ) )
+ {
+ ++i;
+ g_IHVTest = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-overridedefinebones" ) )
+ {
+ g_bOverridePreDefinedBones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-striplods" ) )
+ {
+ g_bStripLods = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-stripmodel" ) )
+ {
+ g_eRunMode = RUN_MODE_STRIP_MODEL;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-stripvhv" ) )
+ {
+ g_eRunMode = RUN_MODE_STRIP_VHV;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-vsi" ) )
+ {
+ g_bMakeVsi = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-quiet" ) )
+ {
+ g_quiet = true;
+ g_verbose = false;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-verbose" ) )
+ {
+ g_quiet = false;
+ g_verbose = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-fullcollide" ) )
+ {
+ g_badCollide = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-checklengths" ) )
+ {
+ g_bCheckLengths = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-printbones" ) )
+ {
+ g_bPrintBones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-perf" ) )
+ {
+ g_bPerf = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-printgraph" ) )
+ {
+ g_bDumpGraph = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-definebones" ) )
+ {
+ g_definebones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-makefile" ) )
+ {
+ g_bCreateMakefile = true;
+ g_quiet = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-verify" ) )
+ {
+ g_bVerifyOnly = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-minlod" ) )
+ {
+ g_minLod = atoi( CommandLine()->GetParm( ++i ) );
+ continue;
+ }
+
+ if (!Q_stricmp( pArgv, "-x360"))
+ {
+ StudioByteSwap::ActivateByteSwapping( true ); // Set target to big endian
+ g_bX360 = true;
+ continue;
+ }
+
+ if (!Q_stricmp( pArgv, "-nox360"))
+ {
+ g_bX360 = false;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-nowarnings" ) )
+ {
+ g_bNoWarnings = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-maxwarnings" ) )
+ {
+ g_maxWarnings = atoi( CommandLine()->GetParm( ++i ) );
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-preview" ) )
+ {
+ g_bBuildPreview = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-dumpmaterials" ) )
+ {
+ g_bDumpMaterials = true;
+ continue;
+ }
+
+ if ( pArgv[1] && pArgv[2] == '\0' )
+ {
+ switch( pArgv[1] )
+ {
+ case 't':
+ i++;
+ V_strcpy_safe( defaulttexture[numrep], pArgv );
+ if (i < argc - 2 && CommandLine()->GetParm(i + 1)[0] != '-')
+ {
+ i++;
+ V_strcpy_safe( sourcetexture[numrep], pArgv );
+ printf("Replacing %s with %s\n", sourcetexture[numrep], defaulttexture[numrep] );
+ }
+ printf( "Using default texture: %s\n", defaulttexture[numrep] );
+ numrep++;
+ break;
+ case 'r':
+ tag_reversed = 1;
+ break;
+ case 'n':
+ tag_normals = 1;
+ break;
+ case 'a':
+ i++;
+ normal_blend = cos( DEG2RAD( verify_atof( pArgv ) ) );
+ break;
+ case 'h':
+ dump_hboxes = 1;
+ break;
+ case 'i':
+ ignore_warnings = 1;
+ break;
+ case 'd':
+ g_bDumpGLViewFiles = true;
+ break;
+// case 'p':
+// i++;
+// V_strcpy_safe( qproject, pArgv );
+// break;
+ }
+ }
+ }
+
+ if ( i >= argc )
+ {
+ // misformed arguments
+ // otherwise generating unintended results
+ UsageAndExit();
+ return false;
+ }
+
+ const char *pArgv = CommandLine()->GetParm( i );
+ Q_strncpy( g_path, pArgv, sizeof(g_path) );
+ if ( Q_IsAbsolutePath( g_path ) )
+ {
+ // Set the working directory to be the path of the qc file
+ // so the relative-file fopen code works
+ char pQCDir[MAX_PATH];
+ Q_ExtractFilePath( g_path, pQCDir, sizeof(pQCDir) );
+ _chdir( pQCDir );
+ }
+ Q_StripExtension( pArgv, outname, sizeof( outname ) );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: search through the "GamePath" key and create a mirrored version in the content path searches
+//-----------------------------------------------------------------------------
+
+void AddContentPaths( )
+{
+ // look for the "content" in the path to the initial QC file
+ char *match = "content\\";
+ char *sp = strstr( qdir, match );
+ if (!sp)
+ return;
+
+ // copy off everything before and including "content"
+ char pre[1024];
+ strncpy( pre, qdir, sp - qdir + strlen( match ) );
+ pre[sp - qdir + strlen( match )] = '\0';
+ sp = sp + strlen( match );
+
+ // copy off everything folling the word after "content"
+ char post[1024];
+ sp = strstr( sp+1, "\\" );
+ V_strcpy_safe( post, sp );
+
+ // get a copy of the game search paths
+ char paths[1024];
+ g_pFullFileSystem->GetSearchPath( "GAME", false, paths, sizeof( paths ) );
+ if (!g_quiet)
+ printf("all paths:%s\n", paths );
+
+ // pull out the game names and insert them into a content path string
+ sp = strstr( paths, "game\\" );
+ while (sp)
+ {
+ char temp[1024];
+ sp = sp + 5;
+ char *sz = strstr( sp, "\\" );
+ if (!sz)
+ return;
+
+ V_strcpy_safe( temp, pre );
+ strncat( temp, sp, sz - sp );
+ V_strcat_safe( temp, post );
+ sp = sz;
+ sp = strstr( sp, "game\\" );
+ CmdLib_AddBasePath( temp );
+ if (!g_quiet)
+ printf("content:%s\n", temp );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+int CStudioMDLApp::Main()
+{
+ const bool bP4DLLExists = g_pFullFileSystem->FileExists( "p4lib.dll", "EXECUTABLE_PATH" );
+
+ // No p4 mode if specified on the command line or no p4lib.dll found
+ if ( ( CommandLine()->FindParm( "-nop4" ) ) || ( !bP4DLLExists ) )
+ {
+ g_bNoP4 = true;
+ g_p4factory->SetDummyMode( true );
+ }
+
+ // Set the named changelist
+ g_p4factory->SetOpenFileChangeList( "StudioMDL Auto Checkout" );
+
+ // This bit of hackery allows us to access files on the harddrive
+ g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
+
+ MaterialSystem_Config_t config;
+ g_pMaterialSystem->OverrideConfig( config, false );
+
+ int nReturnValue;
+ if ( HandleMdlReport( nReturnValue ) )
+ return false;
+
+ // Don't bother with undo here
+ g_pDataModel->SetUndoEnabled( false );
+
+ // look for the "content\hl2x" string in the qdir and add what should be the correct path as an alternate
+ // FIXME: add these to an envvar if folks are using complicated directory mappings instead of defaults
+ char *match = "content\\hl2x\\";
+ char *sp = strstr( qdir, match );
+ if (sp)
+ {
+ char temp[1024];
+ strncpy( temp, qdir, sp - qdir + strlen( match ) );
+ temp[sp - qdir + strlen( match )] = '\0';
+ CmdLib_AddBasePath( temp );
+ V_strcat_safe( temp, "..\\..\\..\\..\\main\\content\\hl2\\" );
+ CmdLib_AddBasePath( temp );
+ }
+
+ AddContentPaths();
+
+ if (!g_quiet)
+ {
+ printf("qdir: \"%s\"\n", qdir );
+ printf("gamedir: \"%s\"\n", gamedir );
+ printf("g_path: \"%s\"\n", g_path );
+ }
+
+ switch ( g_eRunMode )
+ {
+ case RUN_MODE_STRIP_MODEL:
+ return Main_StripModel();
+
+ case RUN_MODE_STRIP_VHV:
+ return Main_StripVhv();
+
+ case RUN_MODE_BUILD:
+ default:
+ break;
+ }
+
+ const char *pExt = Q_GetFileExtension( g_path );
+
+ // Look for the presence of a .mdl file (only -vsi is currently supported for .mdl files)
+ if ( pExt && !Q_stricmp( pExt, "mdl" ) )
+ {
+ if ( g_bMakeVsi )
+ return Main_MakeVsi();
+
+ printf( "ERROR: .qc or .dmx file should be specified to build.\n" );
+ return 1;
+ }
+
+
+ if ( !g_quiet )
+ printf( "Building binary model files...\n" );
+
+ // Look for the presence of a .dmx file of the same name
+ // If so, load it first
+ CDmeMDLMakefile *pMDLMakeFile = NULL;
+
+ if ( pExt && !Q_stricmp( pExt, "dmx" ) )
+ {
+ CDmElement *pRoot;
+ if ( g_pDataModel->RestoreFromFile( g_path, NULL, NULL, &pRoot ) != DMFILEID_INVALID )
+ {
+ pMDLMakeFile = CastElement<CDmeMDLMakefile>( pRoot );
+ }
+ };
+
+ Q_FileBase( g_path, g_path, sizeof( g_path ) );
+ Q_DefaultExtension( g_path, pMDLMakeFile ? ".dmx" : ".qc", sizeof( g_path ) );
+ if (!g_quiet)
+ {
+ printf( "Working on \"%s\"\n", g_path );
+ }
+
+ // Turn on checking for special single character tokens while parsing
+ SetCheckSingleCharTokens( true );
+ SetSingleCharTokenList( "{}()," );
+
+ // Set up script loading callback, discarding default callback
+ ( void ) SetScriptLoadedCallback( StudioMdl_ScriptLoadedCallback );
+
+ // load the script
+ if ( !pMDLMakeFile )
+ {
+ LoadScriptFile(g_path);
+ }
+
+ V_strcpy_safe( fullpath, g_path );
+ V_strcpy_safe( fullpath, ExpandPath( fullpath ) );
+ V_strcpy_safe( fullpath, ExpandArg( fullpath ) );
+
+ // default to having one entry in the LOD list that doesn't do anything so
+ // that we don't have to do any special cases for the first LOD.
+ g_ScriptLODs.Purge();
+ g_ScriptLODs.AddToTail(); // add an empty one
+ g_ScriptLODs[0].switchValue = 0.0f;
+
+ //
+ // parse it
+ //
+ ClearModel();
+
+// V_strcpy_safe( g_pPlatformName, "" );
+ if ( pMDLMakeFile )
+ {
+ ParseMDLMakeFile( pMDLMakeFile );
+ }
+ else
+ {
+ ParseScript();
+ }
+
+ if ( !g_bCreateMakefile )
+ {
+ SetSkinValues();
+
+ SimplifyModel();
+
+ ConsistencyCheckSurfaceProp();
+ ConsistencyCheckContents();
+
+ CollisionModel_Build();
+
+ // ValidateSharedAnimationGroups();
+
+ WriteModelFiles();
+ }
+
+ if ( pMDLMakeFile )
+ {
+ DestroyElement( pMDLMakeFile );
+ pMDLMakeFile = NULL;
+ }
+
+ if ( g_bCreateMakefile )
+ {
+ CreateMakefile_OutputMakefile();
+ }
+ else if ( g_bMakeVsi )
+ {
+ Q_snprintf( g_path, ARRAYSIZE( g_path ), "%smodels/%s", gamedir, outname );
+ Main_MakeVsi();
+ }
+
+ if (!g_quiet)
+ {
+ printf("\nCompleted \"%s\"\n", g_path);
+ }
+
+ g_pDataModel->UnloadFile( DMFILEID_INVALID );
+
+ return 0;
+}
+
+
+
+
+
+//
+// WriteFileToDisk
+// Equivalent to g_pFullFileSystem->WriteFile( pFileName, pPath, buf ), but works
+// for relative paths.
+//
+bool WriteFileToDisk( const char *pFileName, const char *pPath, CUtlBuffer &buf )
+{
+ // For some reason calling full filesystem will write into hl2 root dir
+ // return g_pFullFileSystem->WriteFile( pFileName, pPath, buf );
+
+ FILE *f = fopen( pFileName, "wb" );
+ if ( !f )
+ return false;
+
+ fwrite( buf.Base(), 1, buf.TellPut(), f );
+ fclose( f );
+ return true;
+}
+
+//
+// WriteBufferToFile
+// Helper to concatenate file base and extension.
+//
+bool WriteBufferToFile( CUtlBuffer &buf, const char *szFilebase, const char *szExt )
+{
+ char szFilename[ 1024 ];
+ Q_snprintf( szFilename, ARRAYSIZE( szFilename ), "%s%s", szFilebase, szExt );
+ return WriteFileToDisk( szFilename, NULL, buf );
+}
+
+
+//
+// LoadBufferFromFile
+// Loads the buffer from file, return true on success, false otherwise.
+// If bError is true prints an error upon failure.
+//
+bool LoadBufferFromFile( CUtlBuffer &buffer, char const *szFilebase, char const *szExt, bool bError = true )
+{
+ char szFilename[1024];
+ Q_snprintf( szFilename, ARRAYSIZE( szFilename ), "%s%s", szFilebase, szExt );
+
+ if ( g_pFullFileSystem->ReadFile( szFilename, NULL, buffer ) )
+ return true;
+
+ if ( bError )
+ MdlError( "Failed to open '%s'!\n", szFilename );
+
+ return false;
+}
+
+
+bool Load3ModelBuffers( CUtlBuffer &bufMDL, CUtlBuffer &bufVVD, CUtlBuffer &bufVTX, char const *szFilebase )
+{
+ // Load up the mdl file
+ if ( !LoadBufferFromFile( bufMDL, szFilebase, ".mdl" ) )
+ return false;
+
+ // Load up the vvd file
+ if ( !LoadBufferFromFile( bufVVD, szFilebase, ".vvd" ) )
+ return false;
+
+ // Load up the dx90.vtx file
+ if ( !LoadBufferFromFile( bufVTX, szFilebase, ".dx90.vtx" ) )
+ return false;
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Studiomdl hooks to call the stripping routines:
+// Main_StripVhv
+// Main_StripModel
+//
+//////////////////////////////////////////////////////////////////////////
+
+int CStudioMDLApp::Main_StripVhv()
+{
+ if ( !g_quiet )
+ {
+ printf( "Stripping vhv data...\n" );
+ }
+
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_StripExtension( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ //
+ // ====== Load files
+ //
+
+ // Load up the vhv file
+ CUtlBuffer bufVHV;
+ if ( !LoadBufferFromFile( bufVHV, g_path, ".vhv" ) )
+ return 1;
+
+ // Load up the info.strip file
+ CUtlBuffer bufRemapping;
+ if ( !LoadBufferFromFile( bufRemapping, g_path, ".info.strip", false ) &&
+ !LoadBufferFromFile( bufRemapping, g_path, ".vsi" ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ bool bResult = false;
+ {
+ SpewActivate( "mdllib", 3 );
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ if ( mdllib->CreateNewStripInfo( &pMdlStripInfo ) )
+ {
+ pMdlStripInfo->UnSerialize( bufRemapping );
+ bResult = pMdlStripInfo->StripHardwareVertsBuffer( bufVHV );
+ }
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save vhv
+ if ( !WriteBufferToFile( bufVHV, g_path, ".vhv.strip" ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ return 0;
+}
+
+int CStudioMDLApp::Main_MakeVsi()
+{
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_StripExtension( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ // Load up the files
+ CUtlBuffer bufMDL;
+ CUtlBuffer bufVVD;
+ CUtlBuffer bufVTX;
+ if ( !Load3ModelBuffers( bufMDL, bufVVD, bufVTX, g_path ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ CUtlBuffer bufMappingTable;
+ bool bResult = false;
+ {
+ if ( !g_quiet )
+ {
+ printf( "---------------------\n" );
+ printf( "Generating .vsi stripping information...\n" );
+
+ SpewActivate( "mdllib", 3 );
+ }
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ bResult =
+ mdllib->StripModelBuffers( bufMDL, bufVVD, bufVTX, &pMdlStripInfo ) &&
+ pMdlStripInfo->Serialize( bufMappingTable );
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save remapping data using "P4 edit -> save -> P4 add" approach
+ sprintf( pExt, ".vsi" );
+ CP4AutoEditAddFile _auto_edit_vsi( g_path );
+
+ if ( !WriteFileToDisk( g_path, NULL, bufMappingTable ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+ else if ( !g_quiet )
+ {
+ printf( "Generated .vsi stripping information.\n" );
+ }
+
+ return 0;
+}
+
+int CStudioMDLApp::Main_StripModel()
+{
+ if ( !g_quiet )
+ {
+ printf( "Stripping binary model files...\n" );
+ }
+
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_FileBase( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ // Load up the files
+ CUtlBuffer bufMDL;
+ CUtlBuffer bufVVD;
+ CUtlBuffer bufVTX;
+ if ( !Load3ModelBuffers( bufMDL, bufVVD, bufVTX, g_path ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ CUtlBuffer bufMappingTable;
+ bool bResult = false;
+ {
+ SpewActivate( "mdllib", 3 );
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ bResult =
+ mdllib->StripModelBuffers( bufMDL, bufVVD, bufVTX, &pMdlStripInfo ) &&
+ pMdlStripInfo->Serialize( bufMappingTable );
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save mdl
+ sprintf( pExt, ".mdl.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufMDL ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save vvd
+ sprintf( pExt, ".vvd.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufVVD ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save vtx
+ sprintf( pExt, ".vtx.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufVTX ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save remapping data
+ sprintf( pExt, ".info.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufMappingTable ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/utils/studiomdl/studiomdl.h b/utils/studiomdl/studiomdl.h
new file mode 100644
index 0000000..413860e
--- /dev/null
+++ b/utils/studiomdl/studiomdl.h
@@ -0,0 +1,1602 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+#ifndef STUDIOMDL_H
+#define STUDIOMDL_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include <stdio.h>
+#include "basetypes.h"
+#include "tier1/utlvector.h"
+#include "tier1/utlsymbol.h"
+#include "tier1/utlstring.h"
+#include "mathlib/vector.h"
+#include "studio.h"
+#include "datamodel/dmelementhandle.h"
+#include "checkuv.h"
+
+struct LodScriptData_t;
+struct s_flexkey_t;
+struct s_flexcontroller_t;
+struct s_flexcontrollerremap_t;
+struct s_combinationrule_t;
+struct s_combinationcontrol_t;
+class CDmeVertexDeltaData;
+class CDmeCombinationOperator;
+
+#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I')
+ // little-endian "IDST"
+#define IDSTUDIOANIMGROUPHEADER (('G'<<24)+('A'<<16)+('D'<<8)+'I')
+ // little-endian "IDAG"
+
+
+#define STUDIO_QUADRATIC_MOTION 0x00002000
+
+#define MAXSTUDIOANIMFRAMES 5000 // max frames per animation
+#define MAXSTUDIOANIMS 2000 // total animations
+#define MAXSTUDIOSEQUENCES 1524 // total sequences
+#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement
+#define MAXSTUDIOMODELS 32 // sub-models per model
+#define MAXSTUDIOBODYPARTS 32
+#define MAXSTUDIOMESHES 256
+#define MAXSTUDIOEVENTS 1024
+#define MAXSTUDIOFLEXKEYS 512
+#define MAXSTUDIOFLEXRULES 1024
+#define MAXSTUDIOBONEWEIGHTS 3
+#define MAXSTUDIOCMDS 64
+#define MAXSTUDIOMOVEKEYS 64
+#define MAXSTUDIOIKRULES 64
+#define MAXSTUDIONAME 128
+
+#ifndef EXTERN
+#define EXTERN extern
+#endif
+
+EXTERN char outname[MAX_PATH];
+//EXTERN char g_pPlatformName[1024];
+EXTERN qboolean cdset;
+EXTERN int numdirs;
+EXTERN char cddir[32][MAX_PATH];
+EXTERN int numcdtextures;
+EXTERN char * cdtextures[16];
+EXTERN char fullpath[1024];
+
+EXTERN char rootname[MAXSTUDIONAME]; // name of the root bone
+EXTERN float g_defaultscale;
+EXTERN float g_currentscale;
+EXTERN RadianEuler g_defaultrotation;
+
+
+EXTERN char defaulttexture[16][MAX_PATH];
+EXTERN char sourcetexture[16][MAX_PATH];
+
+EXTERN int numrep;
+
+EXTERN int tag_reversed;
+EXTERN int tag_normals;
+EXTERN float normal_blend;
+EXTERN int dump_hboxes;
+EXTERN int ignore_warnings;
+
+EXTERN Vector eyeposition;
+EXTERN float g_flMaxEyeDeflection;
+EXTERN int g_illumpositionattachment;
+EXTERN Vector illumposition;
+EXTERN int illumpositionset;
+EXTERN int gflags;
+EXTERN Vector bbox[2];
+EXTERN Vector cbox[2];
+EXTERN bool g_wrotebbox;
+EXTERN bool g_wrotecbox;
+
+EXTERN int clip_texcoords;
+EXTERN bool g_staticprop;
+EXTERN bool g_centerstaticprop;
+
+EXTERN bool g_realignbones;
+EXTERN bool g_definebones;
+
+EXTERN byte g_constdirectionalightdot;
+
+// Methods associated with the key value text block
+extern CUtlVector< char > g_KeyValueText;
+int KeyValueTextSize( CUtlVector< char > *pKeyValue );
+const char *KeyValueText( CUtlVector< char > *pKeyValue );
+
+extern vec_t Q_rint (vec_t in);
+
+extern void WriteModelFiles(void);
+void *kalloc( int num, int size );
+
+// --------------------------------------------------------------------
+
+template< class T >
+class CUtlVectorAuto : public CUtlVector< T >
+{
+ // typedef CUtlVectorAuto< T, CUtlVector<T > > BaseClass;
+public:
+ T& operator[]( int i );
+};
+
+template< typename T >
+inline T& CUtlVectorAuto<T>::operator[]( int i )
+{
+ EnsureCount( i + 1 );
+ Assert( IsValidIndex(i) );
+ return Base()[i];
+}
+
+// --------------------------------------------------------------------
+
+struct s_trianglevert_t
+{
+ int vertindex;
+ int normindex; // index into normal array
+ int s,t;
+ float u,v;
+};
+
+struct s_boneweight_t
+{
+ int numbones;
+
+ int bone[MAXSTUDIOBONEWEIGHTS];
+ float weight[MAXSTUDIOBONEWEIGHTS];
+};
+
+
+struct s_tmpface_t
+{
+ int material;
+ unsigned long a, b, c;
+ unsigned long ta, tb, tc;
+ unsigned long na, nb, nc;
+};
+
+struct s_face_t
+{
+ unsigned long a, b, c;
+};
+
+
+struct s_vertexinfo_t
+{
+ int material;
+ int mesh;
+ Vector position;
+ Vector normal;
+ Vector4D tangentS;
+ Vector2D texcoord;
+ s_boneweight_t boneweight;
+};
+
+
+//============================================================================
+
+// dstudiobone_t bone[MAXSTUDIOBONES];
+struct s_bonefixup_t
+{
+ matrix3x4_t m;
+};
+
+EXTERN int g_numbones;
+struct s_bonetable_t
+{
+ char name[MAXSTUDIONAME]; // bone name for symbolic links
+ int parent; // parent bone
+ bool split;
+ int bonecontroller; // -1 == 0
+ Vector pos; // default pos
+ Vector posscale; // pos values scale
+ RadianEuler rot; // default pos
+ Vector rotscale; // rotation values scale
+ int group; // hitgroup
+ Vector bmin, bmax; // bounding box
+ bool bPreDefined;
+ matrix3x4_t rawLocalOriginal; // original transform of preDefined bone
+ matrix3x4_t rawLocal;
+ matrix3x4_t srcRealign;
+ bool bPreAligned;
+ matrix3x4_t boneToPose;
+ int flags;
+ int proceduralindex;
+ int physicsBoneIndex;
+ int surfacePropIndex;
+ Quaternion qAlignment;
+ bool bDontCollapse;
+ Vector posrange;
+};
+EXTERN s_bonetable_t g_bonetable[MAXSTUDIOSRCBONES];
+extern int findGlobalBone( const char *name ); // finds a named bone in the global bone table
+
+EXTERN int g_numrenamedbones;
+struct s_renamebone_t
+{
+ char from[MAXSTUDIONAME];
+ char to[MAXSTUDIONAME];
+};
+EXTERN s_renamebone_t g_renamedbone[MAXSTUDIOSRCBONES];
+const char *RenameBone( const char *pName ); // returns new name if available, else return pName.
+
+EXTERN int g_numimportbones;
+struct s_importbone_t
+{
+ char name[MAXSTUDIONAME];
+ char parent[MAXSTUDIONAME];
+ matrix3x4_t rawLocal;
+ bool bPreAligned;
+ matrix3x4_t srcRealign;
+};
+EXTERN s_importbone_t g_importbone[MAXSTUDIOSRCBONES];
+
+
+EXTERN int g_numincludemodels;
+struct s_includemodel_t
+{
+ char name[MAXSTUDIONAME];
+};
+EXTERN s_includemodel_t g_includemodel[128];
+
+struct s_bbox_t
+{
+ char name[MAXSTUDIONAME]; // bone name
+ char hitboxname[MAXSTUDIONAME]; // hitbox name
+ int bone;
+ int group; // hitgroup
+ int model;
+ Vector bmin, bmax; // bounding box
+};
+
+#define MAXSTUDIOHITBOXSETNAME 64
+
+struct s_hitboxset
+{
+ char hitboxsetname[ MAXSTUDIOHITBOXSETNAME ];
+
+ int numhitboxes;
+
+ s_bbox_t hitbox[MAXSTUDIOSRCBONES];
+};
+
+extern CUtlVector< s_hitboxset > g_hitboxsets;
+
+EXTERN int g_numhitgroups;
+struct s_hitgroup_t
+{
+ int models;
+ int group;
+ char name[MAXSTUDIONAME]; // bone name
+};
+EXTERN s_hitgroup_t g_hitgroup[MAXSTUDIOSRCBONES];
+
+
+struct s_bonecontroller_t
+{
+ char name[MAXSTUDIONAME];
+ int bone;
+ int type;
+ int inputfield;
+ float start;
+ float end;
+};
+
+EXTERN s_bonecontroller_t g_bonecontroller[MAXSTUDIOSRCBONES];
+EXTERN int g_numbonecontrollers;
+
+struct s_screenalignedbone_t
+{
+ char name[MAXSTUDIONAME];
+ int flags;
+};
+
+EXTERN s_screenalignedbone_t g_screenalignedbone[MAXSTUDIOSRCBONES];
+EXTERN int g_numscreenalignedbones;
+
+struct s_attachment_t
+{
+ char name[MAXSTUDIONAME];
+ char bonename[MAXSTUDIONAME];
+ int bone;
+ int type;
+ int flags;
+ matrix3x4_t local;
+ int found; // a owning bone has been flagged
+
+ bool operator==( const s_attachment_t &rhs ) const;
+};
+
+
+#define IS_ABSOLUTE 0x0001
+#define IS_RIGID 0x0002
+
+EXTERN s_attachment_t g_attachment[MAXSTUDIOSRCBONES];
+EXTERN int g_numattachments;
+
+struct s_bonemerge_t
+{
+ char bonename[MAXSTUDIONAME];
+};
+
+EXTERN CUtlVector< s_bonemerge_t > g_BoneMerge;
+
+struct s_mouth_t
+{
+ char bonename[MAXSTUDIONAME];
+ int bone;
+ Vector forward;
+ int flexdesc;
+};
+
+EXTERN s_mouth_t g_mouth[MAXSTUDIOSRCBONES]; // ?? skins?
+EXTERN int g_nummouths;
+
+struct s_node_t
+{
+ char name[MAXSTUDIONAME];
+ int parent;
+};
+
+struct s_bone_t
+{
+ Vector pos;
+ RadianEuler rot;
+};
+
+struct s_linearmove_t
+{
+ int endframe; // frame when pos, rot is valid.
+ int flags; // type of motion. Only linear, linear accel, and linear decel is allowed
+ float v0;
+ float v1;
+ Vector vector; // movement vector
+ Vector pos; // final position
+ RadianEuler rot; // final rotation
+};
+
+
+#define CMD_WEIGHTS 1
+#define CMD_SUBTRACT 2
+#define CMD_AO 3
+#define CMD_MATCH 4
+#define CMD_FIXUP 5
+#define CMD_ANGLE 6
+#define CMD_IKFIXUP 7
+#define CMD_IKRULE 8
+#define CMD_MOTION 9
+#define CMD_REFMOTION 10
+#define CMD_DERIVATIVE 11
+#define CMD_NOANIMATION 12
+#define CMD_LINEARDELTA 13
+#define CMD_SPLINEDELTA 14
+#define CMD_COMPRESS 15
+#define CMD_NUMFRAMES 16
+#define CMD_COUNTERROTATE 17
+#define CMD_SETBONE 18
+#define CMD_WORLDSPACEBLEND 19
+#define CMD_MATCHBLEND 20
+#define CMD_LOCALHIERARCHY 21
+
+struct s_animation_t;
+struct s_ikrule_t;
+
+
+struct s_motion_t
+{
+ int motiontype;
+ int iStartFrame;// starting frame to apply motion over
+ int iEndFrame; // end frame to apply motion over
+ int iSrcFrame; // frame that matches the "reference" animation
+ s_animation_t *pRefAnim; // animation to match
+ int iRefFrame; // reference animation's frame to match
+};
+
+
+struct s_animcmd_t
+{
+ int cmd;
+ union
+ {
+ struct
+ {
+ int index;
+ } weightlist;
+
+ struct
+ {
+ s_animation_t *ref;
+ int frame;
+ int flags;
+ } subtract;
+
+ struct
+ {
+ s_animation_t *ref;
+ int motiontype;
+ int srcframe;
+ int destframe;
+ char *pBonename;
+ } ao;
+
+ struct
+ {
+ s_animation_t *ref;
+ int srcframe;
+ int destframe;
+ int destpre;
+ int destpost;
+ } match;
+
+ struct
+ {
+ s_animation_t *ref;
+ int startframe;
+ int loops;
+ } world;
+
+ struct
+ {
+ int start;
+ int end;
+ } fixuploop;
+
+ struct
+ {
+ float angle;
+ } angle;
+
+ struct
+ {
+ s_ikrule_t *pRule;
+ } ikfixup;
+
+ struct
+ {
+ s_ikrule_t *pRule;
+ } ikrule;
+
+ struct
+ {
+ float scale;
+ } derivative;
+
+ struct
+ {
+ int flags;
+ } linear;
+
+ struct
+ {
+ int frames;
+ } compress;
+
+ struct
+ {
+ int frames;
+ } numframes;
+
+ struct
+ {
+ char *pBonename;
+ bool bHasTarget;
+ float targetAngle[3];
+ } counterrotate;
+
+ struct
+ {
+ char *pBonename;
+ char *pParentname;
+ int start;
+ int peak;
+ int tail;
+ int end;
+ } localhierarchy;
+
+ struct s_motion_t motion;
+ } u;
+};
+
+struct s_streamdata_t
+{
+ Vector pos;
+ Quaternion q;
+};
+
+
+struct s_animationstream_t
+{
+ // source animations
+ int numerror;
+ s_streamdata_t *pError;
+ // compressed animations
+ float scale[6];
+ int numanim[6];
+ mstudioanimvalue_t *anim[6];
+};
+
+struct s_ikrule_t
+{
+ int chain;
+
+ int index;
+ int type;
+ int slot;
+ char bonename[MAXSTUDIONAME];
+ char attachment[MAXSTUDIONAME];
+ int bone;
+ Vector pos;
+ Quaternion q;
+ float height;
+ float floor;
+ float radius;
+
+ int start;
+ int peak;
+ int tail;
+ int end;
+
+ int contact;
+
+ bool usesequence;
+ bool usesource;
+
+ int flags;
+
+ s_animationstream_t errorData;
+};
+
+struct s_localhierarchy_t
+{
+ int bone;
+ int newparent;
+
+ int start;
+ int peak;
+ int tail;
+ int end;
+
+ s_animationstream_t localData;
+};
+
+
+struct s_source_t;
+EXTERN int g_numani;
+struct s_compressed_t
+{
+ int num[6];
+ mstudioanimvalue_t *data[6];
+};
+
+struct s_animation_t
+{
+ bool isImplied;
+ bool isOverride;
+ bool doesOverride;
+ int index;
+ char name[MAXSTUDIONAME];
+ char filename[MAX_PATH];
+
+ /*
+ int animsubindex;
+
+ // For sharing outside of current .mdl file
+ bool shared_group_checkvalidity;
+ bool shared_group_valid;
+ char shared_animgroup_file[ MAX_PATH ]; // share file name
+ char shared_animgroup_name[ MAXSTUDIONAME ]; // group name in share file
+ int shared_group_subindex;
+ studioanimhdr_t *shared_group_header;
+ */
+
+ float fps;
+ int startframe;
+ int endframe;
+ int flags;
+ // animations processed (time shifted, linearized, and bone adjusted ) from source animations
+ CUtlVectorAuto< s_bone_t * > sanim; // [MAXSTUDIOANIMFRAMES]; // [frame][bones];
+
+ int motiontype;
+
+ int fudgeloop;
+ int looprestart; // new starting frame for looping animations
+
+ // piecewise linear motion
+ int numpiecewisekeys;
+ s_linearmove_t piecewisemove[MAXSTUDIOMOVEKEYS];
+
+ // default adjustments
+ Vector adjust;
+ float scale; // ????
+ RadianEuler rotation;
+
+ s_source_t *source;
+ char animationname[MAX_PATH];
+
+ Vector bmin;
+ Vector bmax;
+
+ int numframes;
+
+ // compressed animation data
+ int numsections;
+ int sectionframes;
+ CUtlVectorAuto< CUtlVectorAuto< s_compressed_t > > anim;
+
+ // int weightlist;
+ float weight[MAXSTUDIOSRCBONES];
+ float posweight[MAXSTUDIOSRCBONES];
+
+ int numcmds;
+ s_animcmd_t cmds[MAXSTUDIOCMDS];
+
+ int numikrules;
+ s_ikrule_t ikrule[MAXSTUDIOIKRULES];
+ bool noAutoIK;
+
+ int numlocalhierarchy;
+ s_localhierarchy_t localhierarchy[MAXSTUDIOIKRULES];
+
+ float motionrollback;
+
+ bool disableAnimblocks; // no demand loading
+ bool isFirstSectionLocal; // first block of a section isn't demand loaded
+};
+EXTERN s_animation_t *g_panimation[MAXSTUDIOANIMS];
+
+
+EXTERN int g_numcmdlists;
+struct s_cmdlist_t
+{
+ char name[MAXSTUDIONAME];
+ int numcmds;
+ s_animcmd_t cmds[MAXSTUDIOCMDS];
+};
+EXTERN s_cmdlist_t g_cmdlist[MAXSTUDIOANIMS];
+
+
+struct s_iklock_t
+{
+ char name[MAXSTUDIONAME];
+ int chain;
+ float flPosWeight;
+ float flLocalQWeight;
+};
+
+EXTERN int g_numikautoplaylocks;
+EXTERN s_iklock_t g_ikautoplaylock[16];
+
+
+struct s_event_t
+{
+ int event;
+ int frame;
+ char options[64];
+ char eventname[MAXSTUDIONAME];
+};
+
+struct s_autolayer_t
+{
+ char name[MAXSTUDIONAME];
+ int sequence;
+ int flags;
+ int pose;
+ float start;
+ float peak;
+ float tail;
+ float end;
+};
+
+
+class s_sequence_t
+{
+public:
+ char name[MAXSTUDIONAME];
+ char activityname[MAXSTUDIONAME]; // index into the string table, the name of this activity.
+
+ int flags;
+ // float fps;
+ // int numframes;
+
+ int activity;
+ int actweight;
+
+ int numevents;
+ s_event_t event[MAXSTUDIOEVENTS];
+
+ int numblends;
+ int groupsize[2];
+ CUtlVectorAuto< CUtlVectorAuto< s_animation_t * > > panim; // [MAXSTUDIOBLENDS][MAXSTUDIOBLENDS];
+
+ int paramindex[2];
+ float paramstart[2];
+ float paramend[2];
+ int paramattachment[2];
+ int paramcontrol[2];
+ CUtlVectorAuto< float >param0; // [MAXSTUDIOBLENDS];
+ CUtlVectorAuto< float >param1; // [MAXSTUDIOBLENDS];
+ s_animation_t *paramanim;
+ s_animation_t *paramcompanim;
+ s_animation_t *paramcenter;
+
+ // Vector automovepos[MAXSTUDIOANIMATIONS];
+ // Vector automoveangle[MAXSTUDIOANIMATIONS];
+
+ int animindex;
+
+ Vector bmin;
+ Vector bmax;
+
+ float fadeintime;
+ float fadeouttime;
+
+ int entrynode;
+ int exitnode;
+ int nodeflags;
+ float entryphase;
+ float exitphase;
+
+ int numikrules;
+
+ int numautolayers;
+ s_autolayer_t autolayer[64];
+
+ float weight[MAXSTUDIOSRCBONES];
+
+ s_iklock_t iklock[64];
+ int numiklocks;
+
+ int cycleposeindex;
+
+ CUtlVector< char > KeyValue;
+};
+EXTERN CUtlVector< s_sequence_t > g_sequence;
+//EXTERN int g_numseq;
+
+
+EXTERN int g_numanimblocks;
+struct s_animblock_t
+{
+ int iStartAnim;
+ int iEndAnim;
+ byte *start;
+ byte *end;
+};
+EXTERN s_animblock_t g_animblock[MAXSTUDIOANIMBLOCKS];
+EXTERN int g_animblocksize;
+EXTERN char g_animblockname[260];
+
+
+EXTERN int g_numposeparameters;
+struct s_poseparameter_t
+{
+ char name[MAXSTUDIONAME];
+ float min;
+ float max;
+ int flags;
+ float loop;
+};
+EXTERN s_poseparameter_t g_pose[32]; // FIXME: this shouldn't be hard coded
+
+
+EXTERN int g_numxnodes;
+EXTERN char *g_xnodename[100];
+EXTERN int g_xnode[100][100];
+EXTERN int g_numxnodeskips;
+EXTERN int g_xnodeskip[10000][2];
+
+struct rgb_t
+{
+ byte r, g, b;
+};
+struct rgb2_t
+{
+ float r, g, b, a;
+};
+
+// FIXME: what about texture overrides inline with loading models
+enum TextureFlags_t
+{
+ RELATIVE_TEXTURE_PATH_SPECIFIED = 0x1
+};
+
+struct s_texture_t
+{
+ char name[MAX_PATH];
+ int flags;
+ int parent;
+ int material;
+ float width;
+ float height;
+ float dPdu;
+ float dPdv;
+};
+EXTERN s_texture_t g_texture[MAXSTUDIOSKINS];
+EXTERN int g_numtextures;
+EXTERN int g_material[MAXSTUDIOSKINS]; // link into texture array
+EXTERN int g_nummaterials;
+
+EXTERN float g_gamma;
+EXTERN int g_numskinref;
+EXTERN int g_numskinfamilies;
+EXTERN int g_skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index
+EXTERN int g_numtexturegroups;
+EXTERN int g_numtexturelayers[32];
+EXTERN int g_numtexturereps[32];
+EXTERN int g_texturegroup[32][32][32];
+
+struct s_mesh_t
+{
+ int numvertices;
+ int vertexoffset;
+
+ int numfaces;
+ int faceoffset;
+};
+
+
+struct s_vertanim_t
+{
+ int vertex;
+ float speed;
+ float side;
+ Vector pos;
+ Vector normal;
+ float wrinkle;
+};
+
+struct s_lodvertexinfo_t : public s_vertexinfo_t
+{
+ int lodFlag;
+};
+
+// processed aggregate lod pools
+struct s_loddata_t
+{
+ int numvertices;
+ s_lodvertexinfo_t *vertex;
+
+ int numfaces;
+ s_face_t *face;
+
+ s_mesh_t mesh[MAXSTUDIOSKINS];
+
+ // remaps verts from an lod's source mesh to this all-lod processed aggregate pool
+ int *pMeshVertIndexMaps[MAX_NUM_LODS];
+};
+
+// Animations stored in raw off-disk source files. Raw data should be not processed.
+class s_sourceanim_t
+{
+public:
+ char animationname[MAX_PATH];
+ int numframes;
+ int startframe;
+ int endframe;
+ CUtlVectorAuto< s_bone_t * >rawanim;
+
+ // vertex animation
+ bool newStyleVertexAnimations; // new style doesn't store a base pose in vertex anim[0]
+ int *vanim_mapcount; // local verts map to N target verts
+ int **vanim_map; // local vertices to target vertices mapping list
+ int *vanim_flag; // local vert does animate
+
+ int numvanims[MAXSTUDIOANIMFRAMES];
+ s_vertanim_t *vanim[MAXSTUDIOANIMFRAMES]; // [frame][vertex]
+};
+
+// raw off-disk source files. Raw data should be not processed.
+struct s_source_t
+{
+ char filename[MAX_PATH];
+ int time; // time stamp
+
+ bool isActiveModel;
+
+ // local skeleton hierarchy
+ int numbones;
+ s_node_t localBone[MAXSTUDIOSRCBONES];
+ matrix3x4_t boneToPose[MAXSTUDIOSRCBONES]; // converts bone local data into initial pose data
+
+ // bone remapping
+ int boneflags[MAXSTUDIOSRCBONES]; // attachment, vertex, etc flags for this bone
+ int boneref[MAXSTUDIOSRCBONES]; // flags for this and child bones
+ int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // bonemap : local bone to world bone mapping
+ int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // boneimap : world bone to local bone mapping
+
+ int texmap[MAXSTUDIOSKINS*4]; // map local MAX materials to unique textures
+
+ // per material mesh
+ int nummeshes;
+ int meshindex[MAXSTUDIOSKINS]; // mesh to skin index
+ s_mesh_t mesh[MAXSTUDIOSKINS];
+
+ // vertices defined in "local" space (not remapped to global bones)
+ int numvertices;
+ s_vertexinfo_t *vertex;
+
+ // vertices defined in "global" space (remapped to global bones)
+ CUtlVector< s_vertexinfo_t > m_GlobalVertices;
+
+ int numfaces;
+ s_face_t *face; // vertex indexs per face
+
+ // raw skeletal animation
+ CUtlVector< s_sourceanim_t > m_Animations;
+ // default adjustments
+ Vector adjust;
+ float scale; // ????
+ RadianEuler rotation;
+
+
+ // Flex keys stored in the source data
+ CUtlVector< s_flexkey_t > m_FlexKeys;
+
+ // Combination controls stored in the source data
+ CUtlVector< s_combinationcontrol_t > m_CombinationControls;
+
+ // Combination rules stored in the source data
+ CUtlVector< s_combinationrule_t > m_CombinationRules;
+
+ // Flexcontroller remaps
+ CUtlVector< s_flexcontrollerremap_t > m_FlexControllerRemaps;
+
+ // Attachment points stored in the SMD/DMX/etc. file
+ CUtlVector< s_attachment_t > m_Attachments;
+
+ // Information about how flex controller remaps map into flex rules
+ int m_nKeyStartIndex; // The index at which the flex keys for this model start in the global list
+ CUtlVector< int > m_rawIndexToRemapSourceIndex;
+ CUtlVector< int > m_rawIndexToRemapLocalIndex;
+ CUtlVector< int > m_leftRemapIndexToGlobalFlexControllIndex;
+ CUtlVector< int > m_rightRemapIndexToGlobalFlexControllIndex;
+};
+
+
+EXTERN int g_numsources;
+EXTERN s_source_t *g_source[MAXSTUDIOSEQUENCES];
+
+struct s_eyeball_t
+{
+ char name[MAXSTUDIONAME];
+ int index;
+ int bone;
+ Vector org;
+ float zoffset;
+ float radius;
+ Vector up;
+ Vector forward;
+
+ int mesh;
+ float iris_scale;
+
+ int upperlidflexdesc;
+ int upperflexdesc[3];
+ float uppertarget[3];
+
+ int lowerlidflexdesc;
+ int lowerflexdesc[3];
+ float lowertarget[3];
+
+ int m_flags;
+
+ enum StudioMdlEyeBallFlags
+ {
+ STUDIOMDL_EYELID_DME = 1 << 0
+ };
+};
+
+struct s_model_t
+{
+ char name[MAXSTUDIONAME];
+ char filename[MAX_PATH];
+
+ // needs local scaling and rotation paramaters
+ s_source_t *source; // index into source table
+
+ float scale; // UNUSED
+
+ float boundingradius;
+
+ Vector boundingbox[MAXSTUDIOSRCBONES][2];
+
+ int numattachments;
+ s_attachment_t attachment[32];
+
+ int numeyeballs;
+ s_eyeball_t eyeball[4];
+
+ int numflexes;
+ int flexoffset;
+
+ // References to sources which are the LODs for this model
+ CUtlVector< s_source_t* > m_LodSources;
+
+ // processed aggregate lod data
+ s_loddata_t *m_pLodData;
+};
+
+EXTERN int g_nummodels;
+EXTERN int g_nummodelsbeforeLOD;
+EXTERN s_model_t *g_model[MAXSTUDIOMODELS];
+
+
+struct s_flexdesc_t
+{
+ char FACS[MAXSTUDIONAME]; // FACS identifier
+};
+EXTERN int g_numflexdesc;
+EXTERN s_flexdesc_t g_flexdesc[MAXSTUDIOFLEXDESC];
+int Add_Flexdesc( const char *name );
+
+
+struct s_flexcontroller_t
+{
+ char name[MAXSTUDIONAME];
+ char type[MAXSTUDIONAME];
+ float min;
+ float max;
+};
+EXTERN int g_numflexcontrollers;
+EXTERN s_flexcontroller_t g_flexcontroller[MAXSTUDIOFLEXCTRL];
+
+struct s_flexcontrollerremap_t
+{
+ CUtlString m_Name;
+ FlexControllerRemapType_t m_RemapType;
+ bool m_bIsStereo;
+ CUtlVector< CUtlString > m_RawControls;
+ int m_Index; ///< The model relative index of the slider control for value for this if it's not split, -1 otherwise
+ int m_LeftIndex; ///< The model relative index of the left slider control for this if it's split, -1 otherwise
+ int m_RightIndex; ///< The model relative index of the right slider control for this if it's split, -1 otherwise
+ int m_MultiIndex; ///< The model relative index of the value slider control for this if it's multi, -1 otherwise
+ CUtlString m_EyesUpDownFlexName; // The name of the eyes up/down flex controller
+ int m_EyesUpDownFlexController; // The global index of the Eyes Up/Down Flex Controller
+ int m_BlinkController; // The global index of the Blink Up/Down Flex Controller
+};
+
+extern CUtlVector<s_flexcontrollerremap_t> g_FlexControllerRemap;
+
+
+struct s_flexkey_t
+{
+ int flexdesc;
+ int flexpair;
+
+ s_source_t *source; // index into source table
+ char animationname[MAX_PATH];
+
+ int imodel;
+ int frame;
+
+ float target0;
+ float target1;
+ float target2;
+ float target3;
+
+ int original;
+ float split;
+
+ float decay;
+
+ // extracted and remapped vertex animations
+ int numvanims;
+ s_vertanim_t *vanim;
+ int vanimtype;
+ int weighttable;
+};
+EXTERN int g_numflexkeys;
+EXTERN s_flexkey_t g_flexkey[MAXSTUDIOFLEXKEYS];
+EXTERN s_flexkey_t *g_defaultflexkey;
+
+#define MAX_OPS 512
+
+struct s_flexop_t
+{
+ int op;
+ union
+ {
+ int index;
+ float value;
+ } d;
+};
+
+struct s_flexrule_t
+{
+ int flex;
+ int numops;
+ s_flexop_t op[MAX_OPS];
+};
+
+EXTERN int g_numflexrules;
+EXTERN s_flexrule_t g_flexrule[MAXSTUDIOFLEXRULES];
+
+struct s_combinationcontrol_t
+{
+ char name[MAX_PATH];
+};
+
+struct s_combinationrule_t
+{
+ // The 'ints' here are indices into the m_Controls array
+ CUtlVector< int > m_Combination;
+ CUtlVector< CUtlVector< int > > m_Dominators;
+
+ // The index into the flexkeys to put the result in
+ // (should affect both left + right if the key is sided)
+ int m_nFlex;
+};
+
+EXTERN Vector g_defaultadjust;
+
+struct s_bodypart_t
+{
+ char name[MAXSTUDIONAME];
+ int nummodels;
+ int base;
+ s_model_t *pmodel[MAXSTUDIOMODELS];
+};
+
+EXTERN int g_numbodyparts;
+EXTERN s_bodypart_t g_bodypart[MAXSTUDIOBODYPARTS];
+
+
+#define MAXWEIGHTLISTS 128
+#define MAXWEIGHTSPERLIST (MAXSTUDIOBONES)
+
+struct s_weightlist_t
+{
+ // weights, indexed by numbones per weightlist
+ char name[MAXSTUDIONAME];
+ int numbones;
+ char *bonename[MAXWEIGHTSPERLIST];
+ float boneweight[MAXWEIGHTSPERLIST];
+ float boneposweight[MAXWEIGHTSPERLIST];
+
+ // weights, indexed by global bone index
+ float weight[MAXSTUDIOBONES];
+ float posweight[MAXSTUDIOBONES];
+};
+
+EXTERN int g_numweightlist;
+EXTERN s_weightlist_t g_weightlist[MAXWEIGHTLISTS];
+
+struct s_iklink_t
+{
+ int bone;
+ Vector kneeDir;
+};
+
+struct s_ikchain_t
+{
+ char name[MAXSTUDIONAME];
+ char bonename[MAXSTUDIONAME];
+ int axis;
+ float value;
+ int numlinks;
+ s_iklink_t link[10]; // hip, knee, ankle, toes...
+ float height;
+ float radius;
+ float floor;
+ Vector center;
+};
+
+EXTERN int g_numikchains;
+EXTERN s_ikchain_t g_ikchain[16];
+
+
+struct s_jigglebone_t
+{
+ int flags;
+ char bonename[MAXSTUDIONAME];
+ int bone;
+
+ mstudiojigglebone_t data; // the actual jiggle properties
+};
+
+EXTERN int g_numjigglebones;
+EXTERN s_jigglebone_t g_jigglebones[MAXSTUDIOBONES];
+EXTERN int g_jigglebonemap[MAXSTUDIOBONES]; // map used jigglebone's to source jigglebonebone's
+
+
+struct s_axisinterpbone_t
+{
+ int flags;
+ char bonename[MAXSTUDIONAME];
+ int bone;
+ char controlname[MAXSTUDIONAME];
+ int control;
+ int axis;
+ Vector pos[6];
+ Quaternion quat[6];
+};
+
+EXTERN int g_numaxisinterpbones;
+EXTERN s_axisinterpbone_t g_axisinterpbones[MAXSTUDIOBONES];
+EXTERN int g_axisinterpbonemap[MAXSTUDIOBONES]; // map used axisinterpbone's to source axisinterpbone's
+
+struct s_quatinterpbone_t
+{
+ int flags;
+ char bonename[MAXSTUDIONAME];
+ int bone;
+ char parentname[MAXSTUDIONAME];
+ // int parent;
+ char controlparentname[MAXSTUDIONAME];
+ // int controlparent;
+ char controlname[MAXSTUDIONAME];
+ int control;
+ int numtriggers;
+ Vector size;
+ Vector basepos;
+ float percentage;
+ float tolerance[32];
+ Quaternion trigger[32];
+ Vector pos[32];
+ Quaternion quat[32];
+};
+
+EXTERN int g_numquatinterpbones;
+EXTERN s_quatinterpbone_t g_quatinterpbones[MAXSTUDIOBONES];
+EXTERN int g_quatinterpbonemap[MAXSTUDIOBONES]; // map used quatinterpbone's to source axisinterpbone's
+
+
+struct s_aimatbone_t
+{
+ char bonename[MAXSTUDIONAME];
+ int bone;
+ char parentname[MAXSTUDIONAME];
+ int parent;
+ char aimname[MAXSTUDIONAME];
+ int aimAttach;
+ int aimBone;
+ Vector aimvector;
+ Vector upvector;
+ Vector basepos;
+};
+
+EXTERN int g_numaimatbones;
+EXTERN s_aimatbone_t g_aimatbones[MAXSTUDIOBONES];
+EXTERN int g_aimatbonemap[MAXSTUDIOBONES]; // map used aimatpbone's to source aimatpbone's (may be optimized out)
+
+
+struct s_forcedhierarchy_t
+{
+ char parentname[MAXSTUDIONAME];
+ char childname[MAXSTUDIONAME];
+ char subparentname[MAXSTUDIONAME];
+};
+
+EXTERN int g_numforcedhierarchy;
+EXTERN s_forcedhierarchy_t g_forcedhierarchy[MAXSTUDIOBONES];
+
+struct s_forcedrealign_t
+{
+ char name[MAXSTUDIONAME];
+ RadianEuler rot;
+};
+EXTERN int g_numforcedrealign;
+EXTERN s_forcedrealign_t g_forcedrealign[MAXSTUDIOBONES];
+
+struct s_limitrotation_t
+{
+ char name[MAXSTUDIONAME];
+ int numseq;
+ char *sequencename[64];
+};
+
+EXTERN int g_numlimitrotation;
+EXTERN s_limitrotation_t g_limitrotation[MAXSTUDIOBONES];
+
+extern int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata );
+
+
+struct s_bonesaveframe_t
+{
+ char name[ MAXSTUDIOHITBOXSETNAME ];
+ bool bSavePos;
+ bool bSaveRot;
+};
+
+EXTERN CUtlVector< s_bonesaveframe_t > g_bonesaveframe;
+
+int OpenGlobalFile( char *src );
+bool GetGlobalFilePath( const char *pSrc, char *pFullPath, int nMaxLen );
+s_source_t *Load_Source( char const *filename, const char *ext, bool reverse = false, bool isActiveModel = false );
+int Load_VRM( s_source_t *psource );
+int Load_SMD( s_source_t *psource );
+int Load_VTA( s_source_t *psource );
+int Load_OBJ( s_source_t *psource );
+int Load_DMX( s_source_t *psource );
+int AppendVTAtoOBJ( s_source_t *psource, char *filename, int frame );
+void Build_Reference( s_source_t *psource, const char *pAnimName );
+int Grab_Nodes( s_node_t *pnodes );
+void Grab_Animation( s_source_t *psource, const char *pAnimName );
+
+// Processes source comment line and extracts information about the data file
+void ProcessSourceComment( s_source_t *psource, const char *pCommentString );
+
+// Processes original content file "szOriginalContentFile" that was used to generate
+// data file "szDataFile"
+void ProcessOriginalContentFile( const char *szDataFile, const char *szOriginalContentFile );
+
+//-----------------------------------------------------------------------------
+// Utility methods to get or add animation data from sources
+//-----------------------------------------------------------------------------
+s_sourceanim_t *FindSourceAnim( s_source_t *pSource, const char *pAnimName );
+const s_sourceanim_t *FindSourceAnim( const s_source_t *pSource, const char *pAnimName );
+s_sourceanim_t *FindOrAddSourceAnim( s_source_t *pSource, const char *pAnimName );
+
+// Adds flexkey data to a particular source
+void AddFlexKey( s_source_t *pSource, CDmeCombinationOperator *pComboOp, const char *pFlexKeyName );
+
+// Adds combination data to the source
+void AddCombination( s_source_t *pSource, CDmeCombinationOperator *pCombination );
+
+int LookupTexture( const char *pTextureName, bool bRelativePath = false );
+int UseTextureAsMaterial( int textureindex );
+int MaterialToTexture( int material );
+
+int LookupAttachment( char *name );
+
+void ClearModel (void);
+void SimplifyModel (void);
+void CollapseBones (void);
+
+void adjust_vertex( float *org );
+void scale_vertex( Vector &org );
+void clip_rotations( RadianEuler& rot );
+void clip_rotations( Vector& rot );
+
+void *kalloc( int num, int size );
+void kmemset( void *ptr, int value, int size );
+char *stristr( const char *string, const char *string2 );
+
+void CalcBoneTransforms( s_animation_t *panimation, int frame, matrix3x4_t* pBoneToWorld );
+void CalcBoneTransforms( s_animation_t *panimation, s_animation_t *pbaseanimation, int frame, matrix3x4_t* pBoneToWorld );
+void CalcBoneTransformsCycle( s_animation_t *panimation, s_animation_t *pbaseanimation, float flCycle, matrix3x4_t* pBoneToWorld );
+
+void BuildRawTransforms( const s_source_t *psource, const char *pAnimationName, int frame, float scale, Vector const &shift, RadianEuler const &rotate, int flags, matrix3x4_t* boneToWorld );
+void BuildRawTransforms( const s_source_t *psource, const char *pAnimationName, int frame, matrix3x4_t* boneToWorld );
+
+void TranslateAnimations( const s_source_t *pSource, const matrix3x4_t *pSrcBoneToWorld, matrix3x4_t *pDestBoneToWorld );
+
+// Returns surface property for a given joint
+char* GetSurfaceProp ( char const* pJointName );
+int GetContents ( char const* pJointName );
+char* GetDefaultSurfaceProp ( );
+int GetDefaultContents( );
+
+// Did we read 'end'
+bool IsEnd( char const* pLine );
+
+// Parses an LOD command
+void Cmd_LOD( char const *cmdname );
+void Cmd_ShadowLOD( void );
+
+// Fixes up the LOD source files
+void FixupLODSources();
+
+// Get model LOD source
+s_source_t* GetModelLODSource( const char *pModelName,
+ const LodScriptData_t& scriptLOD, bool* pFound );
+
+
+void LoadLODSources( void );
+void ConvertBoneTreeCollapsesToReplaceBones( void );
+void FixupReplacedBones( void );
+void UnifyLODs( void );
+void SpewBoneUsageStats( void );
+void MarkParentBoneLODs( void );
+//void CheckAutoShareAnimationGroup( char const *animation_name );
+
+/*
+=================
+=================
+*/
+
+extern bool GetLineInput(void);
+extern char g_szFilename[1024];
+extern FILE *g_fpInput;
+extern char g_szLine[4096];
+extern int g_iLinecount;
+
+extern int g_min_faces, g_max_faces;
+extern float g_min_resolution, g_max_resolution;
+
+EXTERN int g_numverts;
+EXTERN Vector g_vertex[MAXSTUDIOVERTS];
+EXTERN s_boneweight_t g_bone[MAXSTUDIOVERTS];
+
+EXTERN int g_numnormals;
+EXTERN Vector g_normal[MAXSTUDIOVERTS];
+
+EXTERN int g_numtexcoords;
+EXTERN Vector2D g_texcoord[MAXSTUDIOVERTS];
+
+EXTERN int g_numfaces;
+EXTERN s_tmpface_t g_face[MAXSTUDIOTRIANGLES];
+EXTERN s_face_t g_src_uface[MAXSTUDIOTRIANGLES]; // max res unified faces
+
+struct v_unify_t
+{
+ int refcount;
+ int lastref;
+ int firstref;
+ int v;
+ int m;
+ int n;
+ int t;
+ v_unify_t *next;
+};
+
+EXTERN v_unify_t *v_list[MAXSTUDIOVERTS];
+EXTERN v_unify_t v_listdata[MAXSTUDIOVERTS];
+EXTERN int numvlist;
+
+int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] );
+void Grab_Vertexanimation( s_source_t *psource, const char *pAnimationName );
+extern void BuildIndividualMeshes( s_source_t *psource );
+
+//-----------------------------------------------------------------------------
+// A little class used to deal with replacement commands
+//-----------------------------------------------------------------------------
+
+class CLodScriptReplacement_t
+{
+public:
+ void SetSrcName( const char *pSrcName )
+ {
+ if( m_pSrcName )
+ {
+ delete [] m_pSrcName;
+ }
+ m_pSrcName = new char[strlen( pSrcName ) + 1];
+ strcpy( m_pSrcName, pSrcName );
+ }
+ void SetDstName( const char *pDstName )
+ {
+ if( m_pDstName )
+ {
+ delete [] m_pDstName;
+ }
+ m_pDstName = new char[strlen( pDstName ) + 1];
+ strcpy( m_pDstName, pDstName );
+ }
+
+ const char *GetSrcName( void ) const
+ {
+ return m_pSrcName;
+ }
+ const char *GetDstName( void ) const
+ {
+ return m_pDstName;
+ }
+ CLodScriptReplacement_t()
+ {
+ m_pSrcName = NULL;
+ m_pDstName = NULL;
+ m_pSource = 0;
+ }
+ ~CLodScriptReplacement_t()
+ {
+ delete [] m_pSrcName;
+ delete [] m_pDstName;
+ }
+
+ s_source_t* m_pSource;
+
+private:
+ char *m_pSrcName;
+ char *m_pDstName;
+ bool m_bReverse;
+};
+
+
+struct LodScriptData_t
+{
+public:
+ float switchValue;
+ CUtlVector<CLodScriptReplacement_t> modelReplacements;
+ CUtlVector<CLodScriptReplacement_t> boneReplacements;
+ CUtlVector<CLodScriptReplacement_t> boneTreeCollapses;
+ CUtlVector<CLodScriptReplacement_t> materialReplacements;
+ CUtlVector<CLodScriptReplacement_t> meshRemovals;
+
+
+ void EnableFacialAnimation( bool val )
+ {
+ m_bFacialAnimation = val;
+ }
+ bool GetFacialAnimationEnabled() const
+ {
+ return m_bFacialAnimation;
+ }
+
+ void StripFromModel( bool val )
+ {
+ m_bStrippedFromModel = val;
+ }
+ bool IsStrippedFromModel() const
+ {
+ return m_bStrippedFromModel;
+ }
+
+ LodScriptData_t()
+ {
+ m_bFacialAnimation = true;
+ m_bStrippedFromModel = false;
+ }
+
+private:
+ bool m_bFacialAnimation;
+ bool m_bStrippedFromModel;
+};
+
+EXTERN CUtlVector<LodScriptData_t> g_ScriptLODs;
+
+extern bool g_collapse_bones;
+extern bool g_collapse_bones_aggressive;
+extern bool g_quiet;
+extern bool g_verbose;
+extern bool g_bCheckLengths;
+extern bool g_bPrintBones;
+extern bool g_bPerf;
+extern bool g_bFast;
+extern bool g_bDumpGraph;
+extern bool g_bMultistageGraph;
+extern bool g_bCreateMakefile;
+extern bool g_bZBrush;
+extern bool g_bVerifyOnly;
+extern bool g_bUseBoneInBBox;
+extern bool g_bLockBoneLengths;
+extern bool g_bOverridePreDefinedBones;
+extern bool g_bX360;
+extern int g_minLod;
+extern int g_numAllowedRootLODs;
+extern bool g_bBuildPreview;
+extern bool g_bCenterBonesOnVerts;
+extern float g_flDefaultMotionRollback;
+extern int g_minSectionFrameLimit;
+extern int g_sectionFrames;
+extern bool g_bNoAnimblockStall;
+
+extern Vector g_vecMinWorldspace;
+extern Vector g_vecMaxWorldspace;
+
+EXTERN CUtlVector< char * >g_collapse;
+
+extern float GetCollisionModelMass();
+
+// List of defined bone flex drivers
+extern DmElementHandle_t g_hDmeBoneFlexDriverList;
+
+// the first time these are called, the name of the model/QC file is printed so that when
+// running in batch mode, no echo, when dumping to a file, it can be determined which file is broke.
+void MdlError( PRINTF_FORMAT_STRING char const *pMsg, ... );
+void MdlWarning( PRINTF_FORMAT_STRING char const *pMsg, ... );
+
+void CreateMakefile_AddDependency( const char *pFileName );
+void EnsureDependencyFileCheckedIn( const char *pFileName );
+
+bool ComparePath( const char *a, const char *b );
+
+byte IsByte( int val );
+char IsChar( int val );
+int IsInt24( int val );
+short IsShort( int val );
+unsigned short IsUShort( int val );
+
+
+extern CCheckUVCmd g_StudioMdlCheckUVCmd;
+
+
+#endif // STUDIOMDL_H
+
diff --git a/utils/studiomdl/studiomdl.vpc b/utils/studiomdl/studiomdl.vpc
new file mode 100644
index 0000000..123d35c
--- /dev/null
+++ b/utils/studiomdl/studiomdl.vpc
@@ -0,0 +1,151 @@
+//-----------------------------------------------------------------------------
+// STUDIOMDL.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR "..\.."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE,..\common,..\nvtristriplib,$SRCDIR\Game_Shared"
+ $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE"
+ }
+
+ $Linker
+ {
+ $AdditionalDependencies "$BASE winmm.lib"
+ }
+}
+
+$Project "Studiomdl"
+{
+ $Folder "Source Files"
+ {
+ $File "$SRCDIR\public\bone_setup.cpp"
+ $File "..\common\cmdlib.cpp"
+ $File "collisionmodel.cpp"
+ $File "$SRCDIR\public\CollisionUtils.cpp"
+ $File "dmxsupport.cpp"
+ $File "$SRCDIR\public\filesystem_helpers.cpp"
+ $File "$SRCDIR\public\filesystem_init.cpp"
+ $File "..\common\FileSystem_Tools.cpp"
+ $File "HardwareMatrixState.cpp"
+ $File "HardwareVertexCache.cpp"
+ $File "$SRCDIR\public\interpolatortypes.cpp"
+ $File "$SRCDIR\public\mdlobjects\mdlobjects.cpp"
+ $File "$SRCDIR\public\movieobjects\movieobjects_compiletools.cpp"
+ $File "mrmsupport.cpp"
+ $File "objsupport.cpp"
+ $File "optimize.cpp"
+ $File "perfstats.cpp"
+ $File "..\common\physdll.cpp"
+ $File "..\common\scriplib.cpp"
+ $File "simplify.cpp"
+ $File "$SRCDIR\public\studio.cpp"
+ $File "$SRCDIR\common\studiobyteswap.cpp"
+ $File "studiomdl.cpp"
+ $File "UnifyLODs.cpp"
+ $File "v1support.cpp"
+ $File "write.cpp"
+ $File "checkuv.cpp"
+ }
+
+ $Folder "Header Files"
+ {
+ $File "..\common\cmdlib.h"
+ $File "collisionmodel.h"
+ $File "FileBuffer.h"
+ $File "..\common\FileSystem_Tools.h"
+ $File "HardwareMatrixState.h"
+ $File "HardwareVertexCache.h"
+ $File "..\NvTriStripLib\NvTriStrip.h"
+ $File "perfstats.h"
+ $File "..\common\physdll.h"
+ $File "..\common\scriplib.h"
+ $File "studiomdl.h"
+ $File "checkuv.h"
+ }
+
+ $Folder "Public Header Files"
+ {
+ $File "$SRCDIR\public\gametrace.h"
+ $File "$SRCDIR\public\filesystem.h"
+ $File "$SRCDIR\public\filesystem_helpers.h"
+ $File "$SRCDIR\public\cmodel.h"
+ $File "$SRCDIR\public\mathlib\amd3dx.h"
+ $File "$SRCDIR\public\basehandle.h"
+ $File "$SRCDIR\public\tier0\basetypes.h"
+ $File "$SRCDIR\public\bitvec.h"
+ $File "$SRCDIR\public\bone_accessor.h"
+ $File "$SRCDIR\public\bone_setup.h"
+ $File "$SRCDIR\public\bspflags.h"
+ $File "$SRCDIR\public\tier1\byteswap.h"
+ $File "$SRCDIR\public\tier1\characterset.h"
+ $File "$SRCDIR\public\CollisionUtils.h"
+ $File "$SRCDIR\public\tier0\commonmacros.h"
+ $File "$SRCDIR\public\mathlib\compressed_vector.h"
+ $File "$SRCDIR\public\const.h"
+ $File "$SRCDIR\public\vphysics\constraints.h"
+ $File "$SRCDIR\public\tier0\dbg.h"
+ $File "$SRCDIR\public\tier0\fasttimer.h"
+ $File "$SRCDIR\public\appframework\IAppSystem.h"
+ $File "$SRCDIR\public\tier0\icommandline.h"
+ $File "$SRCDIR\public\ihandleentity.h"
+ $File "$SRCDIR\public\materialsystem\imaterial.h"
+ $File "$SRCDIR\public\materialsystem\imaterialsystem.h"
+ $File "$SRCDIR\public\materialsystem\imaterialvar.h"
+ $File "$SRCDIR\public\tier1\interface.h"
+ $File "$SRCDIR\public\istudiorender.h"
+ $File "$SRCDIR\public\tier1\KeyValues.h"
+ $File "$SRCDIR\public\materialsystem\materialsystem_config.h"
+ $File "$SRCDIR\public\mathlib\mathlib.h"
+ $File "$SRCDIR\public\tier0\memdbgoff.h"
+ $File "$SRCDIR\public\tier0\memdbgon.h"
+ $File "$SRCDIR\public\phyfile.h"
+ $File "$SRCDIR\public\optimize.h"
+ $File "$SRCDIR\public\tier0\platform.h"
+ $File "$SRCDIR\public\tier0\protected_things.h"
+ $File "$SRCDIR\public\vstdlib\random.h"
+ $File "$SRCDIR\common\studiobyteswap.h"
+ $File "$SRCDIR\public\string_t.h"
+ $File "$SRCDIR\public\tier1\strtools.h"
+ $File "$SRCDIR\public\studio.h"
+ $File "$SRCDIR\public\tier3\tier3.h"
+ $File "$SRCDIR\public\tier1\utlbuffer.h"
+ $File "$SRCDIR\public\tier1\utldict.h"
+ $File "$SRCDIR\public\tier1\utllinkedlist.h"
+ $File "$SRCDIR\public\tier1\utlmemory.h"
+ $File "$SRCDIR\public\tier1\utlrbtree.h"
+ $File "$SRCDIR\public\tier1\utlsymbol.h"
+ $File "$SRCDIR\public\tier1\utlvector.h"
+ $File "$SRCDIR\public\vcollide.h"
+ $File "$SRCDIR\public\vcollide_parse.h"
+ $File "$SRCDIR\public\mathlib\vector.h"
+ $File "$SRCDIR\public\mathlib\vector2d.h"
+ $File "$SRCDIR\public\mathlib\vector4d.h"
+ $File "$SRCDIR\public\mathlib\vmatrix.h"
+ $File "$SRCDIR\public\vphysics_interface.h"
+ $File "$SRCDIR\public\mathlib\vplane.h"
+ $File "$SRCDIR\public\tier0\vprof.h"
+ $File "$SRCDIR\public\vstdlib\vstdlib.h"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib appframework
+ $Lib datamodel
+ $Lib dmserializers
+ $Lib mathlib
+ $Lib mdlobjects
+ $Lib movieobjects
+ $Lib nvtristrip
+ $Lib tier2
+ $Lib tier3
+ }
+}
diff --git a/utils/studiomdl/tristrip.cpp b/utils/studiomdl/tristrip.cpp
new file mode 100644
index 0000000..2a42836
--- /dev/null
+++ b/utils/studiomdl/tristrip.cpp
@@ -0,0 +1,350 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+// tristrip - convert triangle list into tristrips and fans
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "cmdlib.h"
+#include "lbmlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "..\..\engine\studio.h"
+#include "studiomdl.h"
+
+int used[MAXSTUDIOTRIANGLES];
+
+// the command list holds counts and s/t values that are valid for
+// every frame
+short commands[MAXSTUDIOTRIANGLES * 13];
+int numcommands;
+
+// all frames will have their vertexes rearranged and expanded
+// so they are in the order expected by the command list
+
+int allverts, alltris;
+
+int stripverts[MAXSTUDIOTRIANGLES+2];
+int striptris[MAXSTUDIOTRIANGLES+2];
+int stripcount;
+
+int neighbortri[MAXSTUDIOTRIANGLES][3];
+int neighboredge[MAXSTUDIOTRIANGLES][3];
+
+
+s_trianglevert_t (*triangles)[3];
+s_mesh_t *pmesh;
+
+
+void FindNeighbor (int starttri, int startv)
+{
+ s_trianglevert_t m1, m2;
+ int j;
+ s_trianglevert_t *last, *check;
+ int k;
+
+ // used[starttri] |= (1 << startv);
+
+ last = &triangles[starttri][0];
+
+ m1 = last[(startv+1)%3];
+ m2 = last[(startv+0)%3];
+
+ for (j=starttri+1, check=&triangles[starttri+1][0] ; j<pmesh->numtris ; j++, check += 3)
+ {
+ if (used[j] == 7)
+ continue;
+ for (k=0 ; k<3 ; k++)
+ {
+ if (memcmp(&check[k],&m1,sizeof(m1)))
+ continue;
+ if (memcmp(&check[ (k+1)%3 ],&m2,sizeof(m2)))
+ continue;
+
+ neighbortri[starttri][startv] = j;
+ neighboredge[starttri][startv] = k;
+
+ neighbortri[j][k] = starttri;
+ neighboredge[j][k] = startv;
+
+ used[starttri] |= (1 << startv);
+ used[j] |= (1 << k);
+ return;
+ }
+ }
+}
+
+
+/*
+================
+StripLength
+================
+*/
+int StripLength (int starttri, int startv)
+{
+ int j;
+ int k;
+
+ used[starttri] = 2;
+
+ stripverts[0] = (startv)%3;
+ stripverts[1] = (startv+1)%3;
+ stripverts[2] = (startv+2)%3;
+
+ striptris[0] = starttri;
+ striptris[1] = starttri;
+ striptris[2] = starttri;
+ stripcount = 3;
+
+ while( 1 )
+ {
+ if (stripcount & 1)
+ {
+ j = neighbortri[starttri][(startv+1)%3];
+ k = neighboredge[starttri][(startv+1)%3];
+ }
+ else
+ {
+ j = neighbortri[starttri][(startv+2)%3];
+ k = neighboredge[starttri][(startv+2)%3];
+ }
+ if (j == -1 || used[j])
+ goto done;
+
+ stripverts[stripcount] = (k+2)%3;
+ striptris[stripcount] = j;
+ stripcount++;
+
+ used[j] = 2;
+
+ starttri = j;
+ startv = k;
+ }
+
+done:
+
+ // clear the temp used flags
+ for (j=0 ; j<pmesh->numtris ; j++)
+ if (used[j] == 2)
+ used[j] = 0;
+
+ return stripcount;
+}
+
+/*
+===========
+FanLength
+===========
+*/
+int FanLength (int starttri, int startv)
+{
+ int j;
+ int k;
+
+ used[starttri] = 2;
+
+ stripverts[0] = (startv)%3;
+ stripverts[1] = (startv+1)%3;
+ stripverts[2] = (startv+2)%3;
+
+ striptris[0] = starttri;
+ striptris[1] = starttri;
+ striptris[2] = starttri;
+ stripcount = 3;
+
+ while( 1 )
+ {
+ j = neighbortri[starttri][(startv+2)%3];
+ k = neighboredge[starttri][(startv+2)%3];
+
+ if (j == -1 || used[j])
+ goto done;
+
+ stripverts[stripcount] = (k+2)%3;
+ striptris[stripcount] = j;
+ stripcount++;
+
+ used[j] = 2;
+
+ starttri = j;
+ startv = k;
+ }
+
+done:
+
+ // clear the temp used flags
+ for (j=0 ; j<pmesh->numtris ; j++)
+ if (used[j] == 2)
+ used[j] = 0;
+
+ return stripcount;
+}
+
+
+/*
+================
+BuildTris
+
+Generate a list of trifans or strips
+for the model, which holds for all frames
+================
+*/
+int numcommandnodes;
+
+int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata )
+{
+ int i, j, k, m;
+ int startv;
+ int len, bestlen, besttype;
+ int bestverts[MAXSTUDIOTRIANGLES];
+ int besttris[MAXSTUDIOTRIANGLES];
+ int peak[MAXSTUDIOTRIANGLES];
+ int type;
+ int total = 0;
+ long t;
+ int maxlen;
+
+ triangles = x;
+ pmesh = y;
+
+
+ t = time( NULL );
+
+ for (i=0 ; i<pmesh->numtris ; i++)
+ {
+ neighbortri[i][0] = neighbortri[i][1] = neighbortri[i][2] = -1;
+ used[i] = 0;
+ peak[i] = pmesh->numtris;
+ }
+
+ // printf("finding neighbors\n");
+ for (i=0 ; i<pmesh->numtris; i++)
+ {
+ for (k = 0; k < 3; k++)
+ {
+ if (used[i] & (1 << k))
+ continue;
+
+ FindNeighbor( i, k );
+ }
+ // printf("%d", used[i] );
+ }
+ // printf("\n");
+
+ //
+ // build tristrips
+ //
+ numcommandnodes = 0;
+ numcommands = 0;
+ memset (used, 0, sizeof(used));
+
+ for (i=0 ; i<pmesh->numtris ;)
+ {
+ // pick an unused triangle and start the trifan
+ if (used[i])
+ {
+ i++;
+ continue;
+ }
+
+ maxlen = 9999;
+ bestlen = 0;
+ m = 0;
+ for (k = i; k < pmesh->numtris && bestlen < 127; k++)
+ {
+ int localpeak = 0;
+
+ if (used[k])
+ continue;
+
+ if (peak[k] <= bestlen)
+ continue;
+
+ m++;
+ for (type = 0 ; type < 2 ; type++)
+ {
+ for (startv =0 ; startv < 3 ; startv++)
+ {
+ if (type == 1)
+ len = FanLength (k, startv);
+ else
+ len = StripLength (k, startv);
+ if (len > 127)
+ {
+ // skip these, they are too long to encode
+ }
+ else if (len > bestlen)
+ {
+ besttype = type;
+ bestlen = len;
+ for (j=0 ; j<bestlen ; j++)
+ {
+ besttris[j] = striptris[j];
+ bestverts[j] = stripverts[j];
+ }
+ // printf("%d %d\n", k, bestlen );
+ }
+ if (len > localpeak)
+ localpeak = len;
+ }
+ }
+ peak[k] = localpeak;
+ if (localpeak == maxlen)
+ break;
+ }
+ total += (bestlen - 2);
+
+ // printf("%d (%d) %d\n", bestlen, pmesh->numtris - total, i );
+
+ maxlen = bestlen;
+
+ // mark the tris on the best strip as used
+ for (j=0 ; j<bestlen ; j++)
+ used[besttris[j]] = 1;
+
+ if (besttype == 1)
+ commands[numcommands++] = -bestlen;
+ else
+ commands[numcommands++] = bestlen;
+
+ for (j=0 ; j<bestlen ; j++)
+ {
+ s_trianglevert_t *tri;
+
+ tri = &triangles[besttris[j]][bestverts[j]];
+
+ commands[numcommands++] = tri->vertindex;
+ commands[numcommands++] = tri->normindex;
+ commands[numcommands++] = tri->s;
+ commands[numcommands++] = tri->t;
+ }
+ // printf("%d ", bestlen - 2 );
+ numcommandnodes++;
+
+ if (t != time(NULL))
+ {
+ printf("%2d%%\r", (total * 100) / pmesh->numtris );
+ t = time(NULL);
+ }
+ }
+
+ commands[numcommands++] = 0; // end of list marker
+
+ *ppdata = (byte *)commands;
+
+ // printf("%d %d %d\n", numcommandnodes, numcommands, pmesh->numtris );
+ return numcommands * sizeof( short );
+}
+
diff --git a/utils/studiomdl/v1support.cpp b/utils/studiomdl/v1support.cpp
new file mode 100644
index 0000000..a6ac369
--- /dev/null
+++ b/utils/studiomdl/v1support.cpp
@@ -0,0 +1,393 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// sources/<scriptname>.mdl.
+//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+
+// The current version of the SMD file being parsed
+// Yes, I know this file is called 'v1support' and there's never actually
+// been a v > 1 but now there is
+// (actually, there was a while when we were using developing progressive mesh
+// stuff in the middle of HL2 development, but most all that code has long since
+// been deleted)
+int g_smdVersion = 1;
+
+int lookup_index( s_source_t *psource, int material, Vector& vertex, Vector& normal, Vector2D texcoord, int iCount, int bones[], float weights[] )
+{
+ int i, j;
+
+ for (i = 0; i < numvlist; i++)
+ {
+ if (v_listdata[i].m == material
+ && DotProduct( g_normal[i], normal ) > normal_blend
+ && VectorCompare( g_vertex[i], vertex )
+ && g_texcoord[i][0] == texcoord[0]
+ && g_texcoord[i][1] == texcoord[1])
+ {
+ if (g_bone[i].numbones == iCount)
+ {
+ for (j = 0; j < iCount; j++)
+ {
+ if (g_bone[i].bone[j] != bones[j] || g_bone[i].weight[j] != weights[j])
+ break;
+ }
+ if (j == iCount)
+ {
+ v_listdata[i].lastref = numvlist;
+ return i;
+ }
+ }
+ }
+ }
+ if (i >= MAXSTUDIOVERTS) {
+ MdlError( "too many indices in source: \"%s\"\n", psource->filename);
+ }
+
+ VectorCopy( vertex, g_vertex[i] );
+ VectorCopy( normal, g_normal[i] );
+ Vector2Copy( texcoord, g_texcoord[i] );
+
+ g_bone[i].numbones = iCount;
+ for ( j = 0; j < iCount; j++)
+ {
+ g_bone[i].bone[j] = bones[j];
+ g_bone[i].weight[j] = weights[j];
+ }
+
+ v_listdata[i].v = i;
+ v_listdata[i].m = material;
+ v_listdata[i].n = i;
+ v_listdata[i].t = i;
+
+ v_listdata[i].firstref = numvlist;
+ v_listdata[i].lastref = numvlist;
+
+ numvlist = i + 1;
+ return i;
+}
+
+
+void ParseFaceData( s_source_t *psource, int material, s_face_t *pFace )
+{
+ int index[3] = {};
+ int i, j;
+ Vector p;
+ Vector normal;
+ Vector2D t;
+ int iCount, bones[MAXSTUDIOSRCBONES];
+ float weights[MAXSTUDIOSRCBONES];
+ int bone;
+
+ for (j = 0; j < 3; j++)
+ {
+ memset( g_szLine, 0, sizeof( g_szLine ) );
+
+ if (!GetLineInput())
+ {
+ MdlError("%s: error on g_szLine %d: %s", g_szFilename, g_iLinecount, g_szLine );
+ }
+
+ iCount = 0;
+
+ i = sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f",
+ &bone,
+ &p[0], &p[1], &p[2],
+ &normal[0], &normal[1], &normal[2],
+ &t[0], &t[1],
+ &iCount,
+ &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] );
+
+ if (i < 9)
+ continue;
+
+ if (bone < 0 || bone >= psource->numbones)
+ {
+ MdlError("bogus bone index\n%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine );
+ }
+
+ //Scale face pos
+ scale_vertex( p );
+
+ // continue parsing more bones.
+ // FIXME: don't we have a built in parser that'll do this?
+ if (iCount > 4)
+ {
+ int k;
+ int ctr = 0;
+ char *token;
+ for (k = 0; k < 18; k++)
+ {
+ while (g_szLine[ctr] == ' ')
+ {
+ ctr++;
+ }
+ token = strtok( &g_szLine[ctr], " " );
+ ctr += strlen( token ) + 1;
+ }
+ for (k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++)
+ {
+ while (g_szLine[ctr] == ' ')
+ {
+ ctr++;
+ }
+ token = strtok( &g_szLine[ctr], " " );
+ ctr += strlen( token ) + 1;
+
+ bones[k] = atoi(token);
+
+ token = strtok( &g_szLine[ctr], " " );
+ ctr += strlen( token ) + 1;
+
+ weights[k] = atof(token);
+ }
+ // printf("%d ", iCount );
+
+ //printf("\n");
+ //exit(1);
+ }
+
+ // adjust_vertex( p );
+ // scale_vertex( p );
+
+ // move vertex position to object space.
+ // VectorSubtract( p, psource->bonefixup[bone].worldorg, tmp );
+ // VectorTransform(tmp, psource->bonefixup[bone].im, p );
+
+ // move normal to object space.
+ // VectorCopy( normal, tmp );
+ // VectorTransform(tmp, psource->bonefixup[bone].im, normal );
+ // VectorNormalize( normal );
+
+ // invert v
+ t[1] = 1.0 - t[1];
+
+ if (i == 9 || iCount == 0)
+ {
+ iCount = 1;
+ bones[0] = bone;
+ weights[0] = 1.0;
+ }
+ else
+ {
+ iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights );
+ }
+
+
+ index[j] = lookup_index( psource, material, p, normal, t, iCount, bones, weights );
+ }
+
+ // pFace->material = material; // BUG
+ pFace->a = index[0];
+ pFace->b = index[2];
+ pFace->c = index[1];
+ Assert( ((pFace->a & 0xF0000000) == 0) && ((pFace->b & 0xF0000000) == 0) &&
+ ((pFace->c & 0xF0000000) == 0) );
+}
+
+void Grab_Triangles( s_source_t *psource )
+{
+ int i;
+ Vector vmin, vmax;
+
+ vmin[0] = vmin[1] = vmin[2] = 99999;
+ vmax[0] = vmax[1] = vmax[2] = -99999;
+
+ g_numfaces = 0;
+ numvlist = 0;
+
+ //
+ // load the base triangles
+ //
+ int texture;
+ int material;
+ char texturename[MAX_PATH];
+
+ while (1)
+ {
+ if (!GetLineInput())
+ break;
+
+ // check for end
+ if (IsEnd( g_szLine ))
+ break;
+
+ // Look for extra junk that we may want to avoid...
+ int nLineLength = strlen( g_szLine );
+ if (nLineLength >= sizeof( texturename ))
+ {
+ MdlWarning("Unexpected data at line %d, (need a texture name) ignoring...\n", g_iLinecount );
+ continue;
+ }
+
+ // strip off trailing smag
+ V_strcpy_safe( texturename, g_szLine );
+ for (i = strlen( texturename ) - 1; i >= 0 && ! V_isgraph( texturename[i] ); i--)
+ {
+ }
+ texturename[i + 1] = '\0';
+
+ // funky texture overrides
+ for (i = 0; i < numrep; i++)
+ {
+ if (sourcetexture[i][0] == '\0')
+ {
+ V_strcpy_safe( texturename, defaulttexture[i] );
+ break;
+ }
+ if (stricmp( texturename, sourcetexture[i]) == 0)
+ {
+ V_strcpy_safe( texturename, defaulttexture[i] );
+ break;
+ }
+ }
+
+ if (texturename[0] == '\0')
+ {
+ // weird source problem, skip them
+ GetLineInput();
+ GetLineInput();
+ GetLineInput();
+ continue;
+ }
+
+ if (stricmp( texturename, "null.bmp") == 0 || stricmp( texturename, "null.tga") == 0 || stricmp( texturename, "debug/debugempty" ) == 0)
+ {
+ // skip all faces with the null texture on them.
+ GetLineInput();
+ GetLineInput();
+ GetLineInput();
+ continue;
+ }
+
+ texture = LookupTexture( texturename, ( g_smdVersion > 1 ) );
+ psource->texmap[texture] = texture; // hack, make it 1:1
+ material = UseTextureAsMaterial( texture );
+
+ s_face_t f;
+ ParseFaceData( psource, material, &f );
+
+ // remove degenerate triangles
+ if (f.a == f.b || f.b == f.c || f.a == f.c)
+ {
+ // printf("Degenerate triangle %d %d %d\n", f.a, f.b, f.c );
+ continue;
+ }
+
+ g_src_uface[g_numfaces] = f;
+ g_face[g_numfaces].material = material;
+ g_numfaces++;
+ }
+
+ BuildIndividualMeshes( psource );
+}
+
+
+int Load_SMD ( s_source_t *psource )
+{
+ char cmd[1024];
+ int option;
+
+ // Reset smdVersion
+ g_smdVersion = 1;
+
+ if (!OpenGlobalFile( psource->filename ))
+ return 0;
+
+ if( !g_quiet )
+ {
+ printf ("SMD MODEL %s\n", psource->filename);
+ }
+
+ g_iLinecount = 0;
+
+ while (GetLineInput())
+ {
+ int numRead = sscanf( g_szLine, "%s %d", cmd, &option );
+
+ // Blank line
+ if ((numRead == EOF) || (numRead == 0))
+ continue;
+
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option < 1 || option > 2)
+ {
+ MdlError("bad version\n");
+ }
+ g_smdVersion = option;
+ }
+ else if (stricmp( cmd, "nodes" ) == 0)
+ {
+ psource->numbones = Grab_Nodes( psource->localBone );
+ }
+ else if (stricmp( cmd, "skeleton" ) == 0)
+ {
+ Grab_Animation( psource, "BindPose" );
+ }
+ else if (stricmp( cmd, "triangles" ) == 0)
+ {
+ Grab_Triangles( psource );
+ }
+ else if (stricmp( cmd, "vertexanimation" ) == 0)
+ {
+ Grab_Vertexanimation( psource, "BindPose" );
+ }
+ else if ((strncmp( cmd, "//", 2 ) == 0) || (strncmp( cmd, ";", 1 ) == 0) || (strncmp( cmd, "#", 1 ) == 0))
+ {
+ ProcessSourceComment( psource, cmd );
+ continue;
+ }
+ else
+ {
+ MdlWarning("unknown studio command \"%s\"\n", cmd );
+ }
+ }
+ fclose( g_fpInput );
+
+ return 1;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/utils/studiomdl/write.cpp b/utils/studiomdl/write.cpp
new file mode 100644
index 0000000..d3867f4
--- /dev/null
+++ b/utils/studiomdl/write.cpp
@@ -0,0 +1,4586 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+//
+// write.c: writes a studio .mdl file
+//
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+
+#include <io.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <limits.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "collisionmodel.h"
+#include "optimize.h"
+#include "studiobyteswap.h"
+#include "byteswap.h"
+#include "materialsystem/imaterial.h"
+#include "materialsystem/imaterialvar.h"
+#include "mdlobjects/dmeboneflexdriver.h"
+#include "perfstats.h"
+
+#include "tier1/smartptr.h"
+#include "tier2/p4helpers.h"
+
+int totalframes = 0;
+float totalseconds = 0;
+extern int numcommandnodes;
+
+// WriteFile is the only externally visible function in this file.
+// pData points to the current location in an output buffer and pStart is
+// the beginning of the buffer.
+
+bool FixupToSortedLODVertexes( studiohdr_t *pStudioHdr );
+bool Clamp_RootLOD( studiohdr_t *phdr );
+static void WriteAllSwappedFiles( const char *filename );
+
+/*
+============
+WriteModel
+============
+*/
+
+static byte *pData;
+static byte *pStart;
+static byte *pBlockData;
+static byte *pBlockStart;
+
+#undef ALIGN4
+#undef ALIGN16
+#undef ALIGN32
+#define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3)
+#define ALIGN16( a ) a = (byte *)((int)((byte *)a + 15) & ~ 15)
+#define ALIGN32( a ) a = (byte *)((int)((byte *)a + 31) & ~ 31)
+#define ALIGN64( a ) a = (byte *)((int)((byte *)a + 63) & ~ 63)
+#define ALIGN512( a ) a = (byte *)((int)((byte *)a + 511) & ~ 511)
+// make sure kalloc aligns to maximum alignment size
+
+#define FILEBUFFER (8 * 1024 * 1024)
+
+void WriteSeqKeyValues( mstudioseqdesc_t *pseqdesc, CUtlVector< char > *pKeyValue );
+
+//-----------------------------------------------------------------------------
+// Purpose: stringtable is a session global string table.
+//-----------------------------------------------------------------------------
+
+struct stringtable_t
+{
+ byte *base;
+ int *ptr;
+ const char *string;
+ int dupindex;
+ byte *addr;
+};
+
+static int numStrings;
+static stringtable_t strings[32768];
+
+static void BeginStringTable( )
+{
+ strings[0].base = NULL;
+ strings[0].ptr = NULL;
+ strings[0].string = "";
+ strings[0].dupindex = -1;
+ numStrings = 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: add a string to the file-global string table.
+// Keep track of fixup locations
+//-----------------------------------------------------------------------------
+static void AddToStringTable( void *base, int *ptr, const char *string )
+{
+ for (int i = 0; i < numStrings; i++)
+ {
+ if (!string || !strcmp( string, strings[i].string ))
+ {
+ strings[numStrings].base = (byte *)base;
+ strings[numStrings].ptr = ptr;
+ strings[numStrings].string = string;
+ strings[numStrings].dupindex = i;
+ numStrings++;
+ return;
+ }
+ }
+
+ strings[numStrings].base = (byte *)base;
+ strings[numStrings].ptr = ptr;
+ strings[numStrings].string = string;
+ strings[numStrings].dupindex = -1;
+ numStrings++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Write out stringtable
+// fixup local pointers
+//-----------------------------------------------------------------------------
+static byte *WriteStringTable( byte *pData )
+{
+ // force null at first address
+ strings[0].addr = pData;
+ *pData = '\0';
+ pData++;
+
+ // save all the rest
+ for (int i = 1; i < numStrings; i++)
+ {
+ if (strings[i].dupindex == -1)
+ {
+ // not in table yet
+ // calc offset relative to local base
+ *strings[i].ptr = pData - strings[i].base;
+ // keep track of address in case of duplication
+ strings[i].addr = pData;
+ // copy string data, add a terminating \0
+ strcpy( (char *)pData, strings[i].string );
+ pData += strlen( strings[i].string );
+ *pData = '\0';
+ pData++;
+ }
+ else
+ {
+ // already in table, calc offset of existing string relative to local base
+ *strings[i].ptr = strings[strings[i].dupindex].addr - strings[i].base;
+ }
+ }
+ ALIGN4( pData );
+ return pData;
+}
+
+
+// compare function for qsort below
+static int BoneNameCompare( const void *elem1, const void *elem2 )
+{
+ int index1 = *(byte *)elem1;
+ int index2 = *(byte *)elem2;
+
+ // compare bones by name
+ return strcmpi( g_bonetable[index1].name, g_bonetable[index2].name );
+}
+
+
+static void WriteBoneInfo( studiohdr_t *phdr )
+{
+ int i, j, k;
+ mstudiobone_t *pbone;
+ mstudiobonecontroller_t *pbonecontroller;
+ mstudioattachment_t *pattachment;
+ mstudiobbox_t *pbbox;
+
+ // save bone info
+ pbone = (mstudiobone_t *)pData;
+ phdr->numbones = g_numbones;
+ phdr->boneindex = pData - pStart;
+
+ char* pSurfacePropName = GetDefaultSurfaceProp( );
+ AddToStringTable( phdr, &phdr->surfacepropindex, pSurfacePropName );
+ phdr->contents = GetDefaultContents();
+
+ for (i = 0; i < g_numbones; i++)
+ {
+ AddToStringTable( &pbone[i], &pbone[i].sznameindex, g_bonetable[i].name );
+ pbone[i].parent = g_bonetable[i].parent;
+ pbone[i].flags = g_bonetable[i].flags;
+ pbone[i].procindex = 0;
+ pbone[i].physicsbone = g_bonetable[i].physicsBoneIndex;
+ pbone[i].pos = g_bonetable[i].pos;
+ pbone[i].rot = g_bonetable[i].rot;
+ pbone[i].posscale = g_bonetable[i].posscale;
+ pbone[i].rotscale = g_bonetable[i].rotscale;
+ MatrixInvert( g_bonetable[i].boneToPose, pbone[i].poseToBone );
+ pbone[i].qAlignment = g_bonetable[i].qAlignment;
+
+ AngleQuaternion( RadianEuler( g_bonetable[i].rot[0], g_bonetable[i].rot[1], g_bonetable[i].rot[2] ), pbone[i].quat );
+ QuaternionAlign( pbone[i].qAlignment, pbone[i].quat, pbone[i].quat );
+
+ pSurfacePropName = GetSurfaceProp( g_bonetable[i].name );
+ AddToStringTable( &pbone[i], &pbone[i].surfacepropidx, pSurfacePropName );
+ pbone[i].contents = GetContents( g_bonetable[i].name );
+ }
+
+ pData += g_numbones * sizeof( mstudiobone_t );
+ ALIGN4( pData );
+
+ // save procedural bone info
+ if (g_numaxisinterpbones)
+ {
+ mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pData;
+ for (i = 0; i < g_numaxisinterpbones; i++)
+ {
+ j = g_axisinterpbonemap[i];
+ k = g_axisinterpbones[j].bone;
+ pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k];
+ pbone[k].proctype = STUDIO_PROC_AXISINTERP;
+ // printf("bone %d %d\n", j, pbone[k].procindex );
+ pProc[i].control = g_axisinterpbones[j].control;
+ pProc[i].axis = g_axisinterpbones[j].axis;
+ for (k = 0; k < 6; k++)
+ {
+ VectorCopy( g_axisinterpbones[j].pos[k], pProc[i].pos[k] );
+ pProc[i].quat[k] = g_axisinterpbones[j].quat[k];
+ }
+ }
+ pData += g_numaxisinterpbones * sizeof( mstudioaxisinterpbone_t );
+ ALIGN4( pData );
+ }
+
+ if (g_numquatinterpbones)
+ {
+ mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pData;
+ pData += g_numquatinterpbones * sizeof( mstudioquatinterpbone_t );
+ ALIGN4( pData );
+
+ for (i = 0; i < g_numquatinterpbones; i++)
+ {
+ j = g_quatinterpbonemap[i];
+ k = g_quatinterpbones[j].bone;
+ pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k];
+ pbone[k].proctype = STUDIO_PROC_QUATINTERP;
+ // printf("bone %d %d\n", j, pbone[k].procindex );
+ pProc[i].control = g_quatinterpbones[j].control;
+
+ mstudioquatinterpinfo_t *pTrigger = (mstudioquatinterpinfo_t *)pData;
+ pProc[i].numtriggers = g_quatinterpbones[j].numtriggers;
+ pProc[i].triggerindex = (byte *)pTrigger - (byte *)&pProc[i];
+ pData += pProc[i].numtriggers * sizeof( mstudioquatinterpinfo_t );
+
+ for (k = 0; k < pProc[i].numtriggers; k++)
+ {
+ pTrigger[k].inv_tolerance = 1.0 / g_quatinterpbones[j].tolerance[k];
+ pTrigger[k].trigger = g_quatinterpbones[j].trigger[k];
+ pTrigger[k].pos = g_quatinterpbones[j].pos[k];
+ pTrigger[k].quat = g_quatinterpbones[j].quat[k];
+ }
+ }
+ }
+
+ if (g_numjigglebones)
+ {
+ mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)pData;
+
+ for (i = 0; i < g_numjigglebones; i++)
+ {
+ j = g_jigglebonemap[i];
+ k = g_jigglebones[j].bone;
+ pbone[k].procindex = (byte *)&jiggleInfo[i] - (byte *)&pbone[k];
+ pbone[k].proctype = STUDIO_PROC_JIGGLE;
+
+ jiggleInfo[i] = g_jigglebones[j].data;
+ }
+ pData += g_numjigglebones * sizeof( mstudiojigglebone_t );
+ ALIGN4( pData );
+ }
+
+ // write aim at bones
+ if (g_numaimatbones)
+ {
+ mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pData;
+ for (i = 0; i < g_numaimatbones; i++)
+ {
+ j = g_aimatbonemap[i];
+ k = g_aimatbones[j].bone;
+ pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k];
+ pbone[k].proctype = g_aimatbones[j].aimAttach == -1 ? STUDIO_PROC_AIMATBONE : STUDIO_PROC_AIMATATTACH;
+ pProc[i].parent = g_aimatbones[j].parent;
+ pProc[i].aim = g_aimatbones[j].aimAttach == -1 ? g_aimatbones[j].aimBone : g_aimatbones[j].aimAttach;
+ pProc[i].aimvector = g_aimatbones[j].aimvector;
+ pProc[i].upvector = g_aimatbones[j].upvector;
+ pProc[i].basepos = g_aimatbones[j].basepos;
+ }
+ pData += g_numaimatbones * sizeof( mstudioaimatbone_t );
+ ALIGN4( pData );
+ }
+
+ // map g_bonecontroller to bones
+ for (i = 0; i < g_numbones; i++)
+ {
+ for (j = 0; j < 6; j++)
+ {
+ pbone[i].bonecontroller[j] = -1;
+ }
+ }
+
+ for (i = 0; i < g_numbonecontrollers; i++)
+ {
+ j = g_bonecontroller[i].bone;
+ switch( g_bonecontroller[i].type & STUDIO_TYPES )
+ {
+ case STUDIO_X:
+ pbone[j].bonecontroller[0] = i;
+ break;
+ case STUDIO_Y:
+ pbone[j].bonecontroller[1] = i;
+ break;
+ case STUDIO_Z:
+ pbone[j].bonecontroller[2] = i;
+ break;
+ case STUDIO_XR:
+ pbone[j].bonecontroller[3] = i;
+ break;
+ case STUDIO_YR:
+ pbone[j].bonecontroller[4] = i;
+ break;
+ case STUDIO_ZR:
+ pbone[j].bonecontroller[5] = i;
+ break;
+ default:
+ MdlError("unknown g_bonecontroller type\n");
+ }
+ }
+
+ // save g_bonecontroller info
+ pbonecontroller = (mstudiobonecontroller_t *)pData;
+ phdr->numbonecontrollers = g_numbonecontrollers;
+ phdr->bonecontrollerindex = pData - pStart;
+
+ for (i = 0; i < g_numbonecontrollers; i++)
+ {
+ pbonecontroller[i].bone = g_bonecontroller[i].bone;
+ pbonecontroller[i].inputfield = g_bonecontroller[i].inputfield;
+ pbonecontroller[i].type = g_bonecontroller[i].type;
+ pbonecontroller[i].start = g_bonecontroller[i].start;
+ pbonecontroller[i].end = g_bonecontroller[i].end;
+ }
+ pData += g_numbonecontrollers * sizeof( mstudiobonecontroller_t );
+ ALIGN4( pData );
+
+ // save attachment info
+ pattachment = (mstudioattachment_t *)pData;
+ phdr->numlocalattachments = g_numattachments;
+ phdr->localattachmentindex = pData - pStart;
+
+ for (i = 0; i < g_numattachments; i++)
+ {
+ pattachment[i].localbone = g_attachment[i].bone;
+ AddToStringTable( &pattachment[i], &pattachment[i].sznameindex, g_attachment[i].name );
+ MatrixCopy( g_attachment[i].local, pattachment[i].local );
+ pattachment[i].flags = g_attachment[i].flags;
+ }
+ pData += g_numattachments * sizeof( mstudioattachment_t );
+ ALIGN4( pData );
+
+ // save hitbox sets
+ phdr->numhitboxsets = g_hitboxsets.Size();
+
+ // Remember start spot
+ mstudiohitboxset_t *hitboxset = (mstudiohitboxset_t *)pData;
+ phdr->hitboxsetindex = pData - pStart;
+
+ pData += phdr->numhitboxsets * sizeof( mstudiohitboxset_t );
+ ALIGN4( pData );
+
+ for ( int s = 0; s < g_hitboxsets.Size(); s++, hitboxset++ )
+ {
+ s_hitboxset *set = &g_hitboxsets[ s ];
+
+ AddToStringTable( hitboxset, &hitboxset->sznameindex, set->hitboxsetname );
+
+ hitboxset->numhitboxes = set->numhitboxes;
+ hitboxset->hitboxindex = ( pData - (byte *)hitboxset );
+
+ // save bbox info
+ pbbox = (mstudiobbox_t *)pData;
+ for (i = 0; i < hitboxset->numhitboxes; i++)
+ {
+ pbbox[i].bone = set->hitbox[i].bone;
+ pbbox[i].group = set->hitbox[i].group;
+ VectorCopy( set->hitbox[i].bmin, pbbox[i].bbmin );
+ VectorCopy( set->hitbox[i].bmax, pbbox[i].bbmax );
+ pbbox[i].szhitboxnameindex = 0;
+ AddToStringTable( &(pbbox[i]), &(pbbox[i].szhitboxnameindex), set->hitbox[i].hitboxname );
+ }
+
+ pData += hitboxset->numhitboxes * sizeof( mstudiobbox_t );
+ ALIGN4( pData );
+ }
+ byte *pBoneTable = pData;
+ phdr->bonetablebynameindex = (pData - pStart);
+
+ // make a table in bone order and sort it with qsort
+ for ( i = 0; i < phdr->numbones; i++ )
+ {
+ pBoneTable[i] = i;
+ }
+ qsort( pBoneTable, phdr->numbones, sizeof(byte), BoneNameCompare );
+ pData += phdr->numbones * sizeof( byte );
+ ALIGN4( pData );
+}
+
+// load a preexisting model to remember its sequence names and indices
+CUtlVector< CUtlString > g_vecPreexistingSequences;
+void LoadPreexistingSequenceOrder( const char *pFilename )
+{
+ g_vecPreexistingSequences.RemoveAll();
+
+ if ( !FileExists( pFilename ) )
+ return;
+
+ Msg( "Loading preexisting model: %s\n", pFilename );
+
+ studiohdr_t *pStudioHdr;
+ int len = LoadFile((char*)pFilename, (void **)&pStudioHdr);
+
+ if ( len && pStudioHdr && pStudioHdr->SequencesAvailable() )
+ {
+ Msg( " Found %i preexisting sequences.\n", pStudioHdr->GetNumSeq() );
+
+ for ( int i=0; i<pStudioHdr->GetNumSeq(); i++ )
+ {
+ //Msg( " Sequence %i : \"%s\"\n", i, pStudioHdr->pSeqdesc(i).pszLabel() );
+ g_vecPreexistingSequences.AddToTail( pStudioHdr->pSeqdesc(i).pszLabel() );
+ }
+ }
+ else
+ {
+ MdlWarning( "Zero-size file or no sequences.\n" );
+ }
+}
+
+static void WriteSequenceInfo( studiohdr_t *phdr )
+{
+ int i, j, k;
+
+ mstudioseqdesc_t *pseqdesc;
+ mstudioseqdesc_t *pbaseseqdesc;
+ mstudioevent_t *pevent;
+ byte *ptransition;
+
+ // write models to disk with this flag set false. This will force
+ // the sequences to be indexed by activity whenever the g_model is loaded
+ // from disk.
+ phdr->activitylistversion = 0;
+ phdr->eventsindexed = 0;
+
+ // save g_sequence info
+ pseqdesc = (mstudioseqdesc_t *)pData;
+ pbaseseqdesc = pseqdesc;
+ phdr->numlocalseq = g_sequence.Count();
+ phdr->localseqindex = (pData - pStart);
+ pData += g_sequence.Count() * sizeof( mstudioseqdesc_t );
+
+ bool bErrors = false;
+
+
+ // build a table to remap new sequence indices to match the preexisting model
+ bool bUseSeqOrderRemapping = false;
+ int nSeqOrderRemappingTable[MAXSTUDIOSEQUENCES];
+ for (i=0; i<MAXSTUDIOSEQUENCES; i++)
+ nSeqOrderRemappingTable[i] = -1;
+
+ bool bAllowSequenceRemoval = false;
+
+ if ( g_vecPreexistingSequences.Count() )
+ {
+
+ if ( g_sequence.Count() < g_vecPreexistingSequences.Count() && !bAllowSequenceRemoval )
+ {
+ Msg( "\n" );
+ MdlWarning( "This model has fewer sequences than its predecessor.\nPlease confirm sequence deletion: [y/n] " );
+ int nInput = 0;
+ do { nInput = getchar(); } while ( nInput != 121 /* y */ && nInput != 110 /* n */ );
+
+ if ( nInput == 110 )
+ {
+ MdlError( "Model contains fewer sequences than its predecessor!\n" );
+ }
+ else if ( nInput == 121 )
+ {
+ bAllowSequenceRemoval = true;
+ }
+ }
+
+ {
+ Msg( "Building sequence index remapping table...\n" );
+
+ CUtlVector<int> vecNewIndices;
+ vecNewIndices.RemoveAll();
+
+ // map current sequences to their old indices
+ for (i = 0; i < g_sequence.Count(); i++ )
+ {
+ int nIdx = g_vecPreexistingSequences.Find( g_sequence[i].name );
+ if ( nIdx >= 0 )
+ {
+ nSeqOrderRemappingTable[nIdx] = i;
+ }
+ else
+ {
+ if ( i < g_vecPreexistingSequences.Count() )
+ {
+ Msg( " Found new sequence \"%s\" using index of old sequence \"%s\".\n", g_sequence[i].name, g_vecPreexistingSequences[i].String() );
+ }
+ else
+ {
+ Msg( " Found new sequence \"%s\".\n", g_sequence[i].name );
+ }
+
+ vecNewIndices.AddToTail(i);
+ }
+ }
+
+ // slot new sequences into unused indices
+ while ( vecNewIndices.Count() )
+ {
+ for (i = 0; i < MAXSTUDIOSEQUENCES; i++ )
+ {
+ if ( nSeqOrderRemappingTable[i] == -1 )
+ {
+ nSeqOrderRemappingTable[i] = vecNewIndices[0];
+ vecNewIndices.Remove(0);
+ break;
+ }
+ }
+ }
+
+ // verify no indices are undefined
+ for (i = 0; i < g_sequence.Count(); i++ )
+ {
+ if ( nSeqOrderRemappingTable[i] == -1 )
+ {
+ if ( bAllowSequenceRemoval )
+ {
+ do
+ {
+ for ( int nB=i; nB<g_vecPreexistingSequences.Count(); nB++ )
+ {
+ nSeqOrderRemappingTable[nB] = nSeqOrderRemappingTable[nB+1];
+ }
+ }
+ while (nSeqOrderRemappingTable[i] == -1);
+ }
+ else
+ {
+ MdlError( "Failed to reorder sequence indices.\n" );
+ }
+
+ }
+ else if ( nSeqOrderRemappingTable[i] != i )
+ {
+ bUseSeqOrderRemapping = true;
+ }
+ }
+
+ if ( bUseSeqOrderRemapping )
+ {
+ Msg( "Sequence indices need re-ordering.\n" );
+ }
+ else
+ {
+ Msg( "No re-ordering required.\n" );
+ }
+ }
+ }
+
+ // build an inverted remapping table so autolayer sequence indices can find their sources later
+ int nSeqOrderRemappingTableInv[MAXSTUDIOSEQUENCES];
+ if ( bUseSeqOrderRemapping )
+ {
+ for (i=0; i<MAXSTUDIOSEQUENCES; i++)
+ nSeqOrderRemappingTableInv[nSeqOrderRemappingTable[i]] = i;
+ }
+
+ int m;
+ for (m = 0; m < g_sequence.Count(); m++, pseqdesc++)
+ {
+
+ if ( bUseSeqOrderRemapping )
+ {
+ i = nSeqOrderRemappingTable[m];
+ if ( i != m )
+ {
+ Msg( " Remapping sequence %i to index %i (%s) to retain existing order.\n", i, m, g_sequence[i].name );
+ }
+ }
+ else
+ {
+ i = m;
+ }
+
+ byte *pSequenceStart = (byte *)pseqdesc;
+
+ AddToStringTable( pseqdesc, &pseqdesc->szlabelindex, g_sequence[i].name );
+ AddToStringTable( pseqdesc, &pseqdesc->szactivitynameindex, g_sequence[i].activityname );
+
+ pseqdesc->baseptr = pStart - (byte *)pseqdesc;
+
+ pseqdesc->flags = g_sequence[i].flags;
+
+ pseqdesc->numblends = g_sequence[i].numblends;
+ pseqdesc->groupsize[0] = g_sequence[i].groupsize[0];
+ pseqdesc->groupsize[1] = g_sequence[i].groupsize[1];
+
+ pseqdesc->paramindex[0] = g_sequence[i].paramindex[0];
+ pseqdesc->paramstart[0] = g_sequence[i].paramstart[0];
+ pseqdesc->paramend[0] = g_sequence[i].paramend[0];
+ pseqdesc->paramindex[1] = g_sequence[i].paramindex[1];
+ pseqdesc->paramstart[1] = g_sequence[i].paramstart[1];
+ pseqdesc->paramend[1] = g_sequence[i].paramend[1];
+
+ if (g_sequence[i].groupsize[0] > 1 || g_sequence[i].groupsize[1] > 1)
+ {
+ // save posekey values
+ float *pposekey = (float *)pData;
+ pseqdesc->posekeyindex = (pData - pSequenceStart);
+ pData += (pseqdesc->groupsize[0] + pseqdesc->groupsize[1]) * sizeof( float );
+ for (j = 0; j < pseqdesc->groupsize[0]; j++)
+ {
+ *(pposekey++) = g_sequence[i].param0[j];
+ // printf("%.2f ", g_sequence[i].param0[j] );
+ }
+ for (j = 0; j < pseqdesc->groupsize[1]; j++)
+ {
+ *(pposekey++) = g_sequence[i].param1[j];
+ // printf("%.2f ", g_sequence[i].param1[j] );
+ }
+ // printf("\n" );
+ }
+
+ // pseqdesc->motiontype = g_sequence[i].motiontype;
+ // pseqdesc->motionbone = 0; // g_sequence[i].motionbone;
+ // VectorCopy( g_sequence[i].linearmovement, pseqdesc->linearmovement );
+
+ pseqdesc->activity = g_sequence[i].activity;
+ pseqdesc->actweight = g_sequence[i].actweight;
+
+ pseqdesc->bbmin = g_sequence[i].bmin;
+ pseqdesc->bbmax = g_sequence[i].bmax;
+
+ pseqdesc->fadeintime = g_sequence[i].fadeintime;
+ pseqdesc->fadeouttime = g_sequence[i].fadeouttime;
+
+ pseqdesc->localentrynode = g_sequence[i].entrynode;
+ pseqdesc->localexitnode = g_sequence[i].exitnode;
+ //pseqdesc->entryphase = g_sequence[i].entryphase;
+ //pseqdesc->exitphase = g_sequence[i].exitphase;
+ pseqdesc->nodeflags = g_sequence[i].nodeflags;
+
+ // save events
+ pevent = (mstudioevent_t *)pData;
+ pseqdesc->numevents = g_sequence[i].numevents;
+ pseqdesc->eventindex = (pData - pSequenceStart);
+ pData += pseqdesc->numevents * sizeof( mstudioevent_t );
+ for (j = 0; j < g_sequence[i].numevents; j++)
+ {
+ k = g_sequence[i].panim[0][0]->numframes - 1;
+
+ if (g_sequence[i].event[j].frame <= k)
+ pevent[j].cycle = g_sequence[i].event[j].frame / ((float)k);
+ else if (k == 0 && g_sequence[i].event[j].frame == 0)
+ pevent[j].cycle = 0;
+ else
+ {
+ MdlWarning("Event %d (frame %d) out of range in %s\n", g_sequence[i].event[j].event, g_sequence[i].event[j].frame, g_sequence[i].name );
+ bErrors = true;
+ }
+
+ //Adrian - Remove me once we phase out the old event system.
+ if ( V_isdigit( g_sequence[i].event[j].eventname[0] ) )
+ {
+ pevent[j].event = atoi( g_sequence[i].event[j].eventname );
+ pevent[j].type = 0;
+ pevent[j].szeventindex = 0;
+ }
+ else
+ {
+ AddToStringTable( &pevent[j], &pevent[j].szeventindex, g_sequence[i].event[j].eventname );
+ pevent[j].type = NEW_EVENT_STYLE;
+ }
+
+
+ // printf("%4d : %d %f\n", pevent[j].event, g_sequence[i].event[j].frame, pevent[j].cycle );
+ // AddToStringTable( &pevent[j], &pevent[j].szoptionindex, g_sequence[i].event[j].options );
+ strcpy( pevent[j].options, g_sequence[i].event[j].options );
+ }
+ ALIGN4( pData );
+
+ // save ikrules
+ pseqdesc->numikrules = g_sequence[i].numikrules;
+
+ // save autolayers
+ mstudioautolayer_t *pautolayer = (mstudioautolayer_t *)pData;
+ pseqdesc->numautolayers = g_sequence[i].numautolayers;
+ pseqdesc->autolayerindex = (pData - pSequenceStart);
+ pData += pseqdesc->numautolayers * sizeof( mstudioautolayer_t );
+ for (j = 0; j < g_sequence[i].numautolayers; j++)
+ {
+ pautolayer[j].iSequence = g_sequence[i].autolayer[j].sequence;
+ pautolayer[j].iPose = g_sequence[i].autolayer[j].pose;
+ pautolayer[j].flags = g_sequence[i].autolayer[j].flags;
+
+ // autolayer indices are stored by index, so remap them now using the invertex lookup table
+ if ( bUseSeqOrderRemapping )
+ {
+ int nRemapAutoLayer = nSeqOrderRemappingTableInv[ pautolayer[j].iSequence ];
+ if ( nRemapAutoLayer != pautolayer[j].iSequence )
+ {
+ Msg( " Autolayer remapping index %i to %i.\n", pautolayer[j].iSequence, nRemapAutoLayer );
+ pautolayer[j].iSequence = nRemapAutoLayer;
+ }
+ }
+
+ if (!(pautolayer[j].flags & STUDIO_AL_POSE))
+ {
+ pautolayer[j].start = g_sequence[i].autolayer[j].start / (g_sequence[i].panim[0][0]->numframes - 1);
+ pautolayer[j].peak = g_sequence[i].autolayer[j].peak / (g_sequence[i].panim[0][0]->numframes - 1);
+ pautolayer[j].tail = g_sequence[i].autolayer[j].tail / (g_sequence[i].panim[0][0]->numframes - 1);
+ pautolayer[j].end = g_sequence[i].autolayer[j].end / (g_sequence[i].panim[0][0]->numframes - 1);
+ }
+ else
+ {
+ pautolayer[j].start = g_sequence[i].autolayer[j].start;
+ pautolayer[j].peak = g_sequence[i].autolayer[j].peak;
+ pautolayer[j].tail = g_sequence[i].autolayer[j].tail;
+ pautolayer[j].end = g_sequence[i].autolayer[j].end;
+ }
+ }
+
+
+ // save boneweights
+ float *pweight = 0;
+ j = 0;
+ // look up previous sequence weights and try to find a match
+ for (k = 0; k < m; k++)
+ {
+ j = 0;
+ // only check newer boneweights than the last one
+ if (pseqdesc[k-m].pBoneweight( 0 ) > pweight)
+ {
+ pweight = pseqdesc[k-m].pBoneweight( 0 );
+ for (j = 0; j < g_numbones; j++)
+ {
+ // we're not walking the linear sequence list if we're remapping, so we need to remap this check
+ int nRemap = k;
+ if ( bUseSeqOrderRemapping )
+ nRemap = nSeqOrderRemappingTable[k];
+
+ if (g_sequence[i].weight[j] != g_sequence[nRemap].weight[j])
+ break;
+ }
+ if (j == g_numbones)
+ break;
+ }
+ }
+
+ // check to see if all the bones matched
+ if (j < g_numbones)
+ {
+ // allocate new block
+ //printf("new %08x\n", pData );
+ pweight = (float *)pData;
+ pseqdesc->weightlistindex = (pData - pSequenceStart);
+ pData += g_numbones * sizeof( float );
+ for (j = 0; j < g_numbones; j++)
+ {
+ pweight[j] = g_sequence[i].weight[j];
+ }
+ }
+ else
+ {
+ // use previous boneweight
+ //printf("prev %08x\n", pweight );
+ pseqdesc->weightlistindex = ((byte *)pweight - pSequenceStart);
+ }
+
+
+ // save iklocks
+ mstudioiklock_t *piklock = (mstudioiklock_t *)pData;
+ pseqdesc->numiklocks = g_sequence[i].numiklocks;
+ pseqdesc->iklockindex = (pData - pSequenceStart);
+ pData += pseqdesc->numiklocks * sizeof( mstudioiklock_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < pseqdesc->numiklocks; j++)
+ {
+ piklock->chain = g_sequence[i].iklock[j].chain;
+ piklock->flPosWeight = g_sequence[i].iklock[j].flPosWeight;
+ piklock->flLocalQWeight = g_sequence[i].iklock[j].flLocalQWeight;
+ piklock++;
+ }
+
+ // Write animation blend parameters
+ short *blends = ( short * )pData;
+ pseqdesc->animindexindex = ( pData - pSequenceStart );
+ pData += ( g_sequence[i].groupsize[0] * g_sequence[i].groupsize[1] ) * sizeof( short );
+ ALIGN4( pData );
+
+ for ( j = 0; j < g_sequence[i].groupsize[0] ; j++ )
+ {
+ for ( k = 0; k < g_sequence[i].groupsize[1]; k++ )
+ {
+ // height value * width of row + width value
+ int offset = k * g_sequence[i].groupsize[0] + j;
+
+ if ( g_sequence[i].panim[j][k] )
+ {
+ int animindex = g_sequence[i].panim[j][k]->index;
+
+ Assert( animindex >= 0 && animindex < SHRT_MAX );
+
+ blends[ offset ] = (short)animindex;
+ }
+ else
+ {
+ blends[ offset ] = 0;
+ }
+ }
+ }
+
+ // Write cycle overrides
+ pseqdesc->cycleposeindex = g_sequence[i].cycleposeindex;
+
+ WriteSeqKeyValues( pseqdesc, &g_sequence[i].KeyValue );
+ }
+
+ if (bErrors)
+ {
+ MdlError( "Exiting due to Errors\n");
+ }
+
+ // save transition graph
+ int *pxnodename = (int *)pData;
+ phdr->localnodenameindex = (pData - pStart);
+ pData += g_numxnodes * sizeof( *pxnodename );
+ ALIGN4( pData );
+ for (i = 0; i < g_numxnodes; i++)
+ {
+ AddToStringTable( phdr, pxnodename, g_xnodename[i+1] );
+ // printf("%d : %s\n", i, g_xnodename[i+1] );
+ pxnodename++;
+ }
+
+ ptransition = (byte *)pData;
+ phdr->numlocalnodes = g_numxnodes;
+ phdr->localnodeindex = pData - pStart;
+ pData += g_numxnodes * g_numxnodes * sizeof( byte );
+ ALIGN4( pData );
+ for (i = 0; i < g_numxnodes; i++)
+ {
+// printf("%2d (%12s) : ", i + 1, g_xnodename[i+1] );
+ for (j = 0; j < g_numxnodes; j++)
+ {
+ *ptransition++ = g_xnode[i][j];
+// printf(" %2d", g_xnode[i][j] );
+ }
+// printf("\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stub implementation
+// Input : *group -
+//-----------------------------------------------------------------------------
+
+const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *modelname ) const
+{
+ return NULL;
+}
+
+virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const
+{
+ return NULL;
+}
+
+const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const
+{
+ return (studiohdr_t *)cache;
+}
+
+byte *studiohdr_t::GetAnimBlock( int i ) const
+{
+ return NULL;
+}
+
+int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const
+{
+ return 0;
+}
+
+
+int rawanimbytes = 0;
+int animboneframes = 0;
+
+int numAxis[4] = { 0, 0, 0, 0 };
+int numPos[4] = { 0, 0, 0, 0 };
+int useRaw = 0;
+
+void WriteAnimationData( s_animation_t *srcanim, mstudioanimdesc_t *destanimdesc, byte *&pLocalData, byte *&pExtData )
+{
+ int j, k, n;
+
+ byte *pData = NULL;
+
+ for (int w = 0; w < srcanim->numsections; w++)
+ {
+ bool bUseExtData = false;
+ pData = pLocalData;
+
+ if (pExtData != NULL && !srcanim->disableAnimblocks && !(w == 0 && srcanim->isFirstSectionLocal))
+ {
+ pData = pExtData;
+ bUseExtData = true;
+ }
+
+ mstudioanim_t *destanim = (mstudioanim_t *)pData;
+ byte *pStartSection = pData;
+ pData += sizeof( *destanim );
+
+ destanim->bone = 255;
+
+ mstudioanim_t *prevanim = NULL;
+
+ // save animation value info
+ for (j = 0; j < g_numbones; j++)
+ {
+ // destanim->weight = srcanim->weight[j];
+ // printf( "%s %.1f\n", g_bonetable[j].name, destanim->weight );
+ destanim->flags = 0;
+ s_compressed_t *psrcdata = &srcanim->anim[w][j];
+
+ numPos[ (psrcdata->num[0] != 0) + (psrcdata->num[1] != 0) + (psrcdata->num[2] != 0) ]++;
+ numAxis[ (psrcdata->num[3] != 0) + (psrcdata->num[4] != 0) + (psrcdata->num[5] != 0) ]++;
+
+ if (psrcdata->num[0] + psrcdata->num[1] + psrcdata->num[2] + psrcdata->num[3] + psrcdata->num[4] + psrcdata->num[5] == 0)
+ {
+ // no animation, skip
+ continue;
+ }
+
+ destanim->bone = j;
+
+ // copy flags over if delta animation
+ if (srcanim->flags & STUDIO_DELTA)
+ {
+ destanim->flags |= STUDIO_ANIM_DELTA;
+ }
+
+ if ((srcanim->numframes == 1) || (psrcdata->num[0] <= 2 && psrcdata->num[1] <= 2 && psrcdata->num[2] <= 2 && psrcdata->num[3] <= 2 && psrcdata->num[4] <= 2 && psrcdata->num[5] <= 2))
+ {
+ // printf("%d : %d %d %d : %d %d %d\n", j, psrcdata->num[0], psrcdata->num[1], psrcdata->num[2], psrcdata->num[3], psrcdata->num[4], psrcdata->num[5] );
+ // single frame, if animation detected just store as raw
+ int iFrame = min( w * srcanim->sectionframes, srcanim->numframes - 1 );
+ if (psrcdata->num[3] != 0 || psrcdata->num[4] != 0 || psrcdata->num[5] != 0)
+ {
+ Quaternion q;
+ AngleQuaternion( srcanim->sanim[iFrame][j].rot, q );
+ *((Quaternion64 *)pData) = q;
+ pData += sizeof( Quaternion64 );
+ rawanimbytes += sizeof( Quaternion64 );
+ destanim->flags |= STUDIO_ANIM_RAWROT2;
+ }
+
+ if (psrcdata->num[0] != 0 || psrcdata->num[1] != 0 || psrcdata->num[2] != 0)
+ {
+ *((Vector48 *)pData) = srcanim->sanim[iFrame][j].pos;
+ pData += sizeof( Vector48 );
+ rawanimbytes += sizeof( Vector48 );
+ destanim->flags |= STUDIO_ANIM_RAWPOS;
+ }
+ }
+ else
+ {
+ // look to see if storing raw quat's would have taken less space
+ if (psrcdata->num[3] >= srcanim->numframes && psrcdata->num[4] >= srcanim->numframes && psrcdata->num[5] >= srcanim->numframes)
+ {
+ useRaw++;
+ }
+
+ mstudioanim_valueptr_t *posvptr = NULL;
+ mstudioanim_valueptr_t *rotvptr = NULL;
+
+ // allocate room for rotation ptrs
+ rotvptr = (mstudioanim_valueptr_t *)pData;
+ pData += sizeof( *rotvptr );
+
+ // skip all position info if there's no animation
+ if (psrcdata->num[0] != 0 || psrcdata->num[1] != 0 || psrcdata->num[2] != 0)
+ {
+ posvptr = (mstudioanim_valueptr_t *)pData;
+ pData += sizeof( *posvptr );
+ }
+
+ mstudioanimvalue_t *destanimvalue = (mstudioanimvalue_t *)pData;
+
+ if (rotvptr)
+ {
+ // store rotation animations
+ for (k = 3; k < 6; k++)
+ {
+ if (psrcdata->num[k] == 0)
+ {
+ rotvptr->offset[k-3] = 0;
+ }
+ else
+ {
+ rotvptr->offset[k-3] = ((byte *)destanimvalue - (byte *)rotvptr);
+ for (n = 0; n < psrcdata->num[k]; n++)
+ {
+ destanimvalue->value = psrcdata->data[k][n].value;
+ destanimvalue++;
+ }
+ }
+ }
+ destanim->flags |= STUDIO_ANIM_ANIMROT;
+ }
+
+ if (posvptr)
+ {
+ // store position animations
+ for (k = 0; k < 3; k++)
+ {
+ if (psrcdata->num[k] == 0)
+ {
+ posvptr->offset[k] = 0;
+ }
+ else
+ {
+ posvptr->offset[k] = ((byte *)destanimvalue - (byte *)posvptr);
+ for (n = 0; n < psrcdata->num[k]; n++)
+ {
+ destanimvalue->value = psrcdata->data[k][n].value;
+ destanimvalue++;
+ }
+ }
+ }
+ destanim->flags |= STUDIO_ANIM_ANIMPOS;
+ }
+ rawanimbytes += ((byte *)destanimvalue - pData);
+ pData = (byte *)destanimvalue;
+ }
+
+ prevanim = destanim;
+ destanim->nextoffset = pData - (byte *)destanim;
+ destanim = (mstudioanim_t *)pData;
+ pData += sizeof( *destanim );
+ }
+
+ if (prevanim)
+ {
+ prevanim->nextoffset = 0;
+ }
+
+ ALIGN4( pData );
+
+ // write into anim blocks if needed
+ if (destanimdesc->sectionindex)
+ {
+ if (bUseExtData)
+ {
+ if (g_numanimblocks && pData - g_animblock[g_numanimblocks-1].start > g_animblocksize)
+ {
+ // advance to next animblock
+ g_animblock[g_numanimblocks-1].end = pStartSection;
+ g_animblock[g_numanimblocks].start = pStartSection;
+ g_numanimblocks++;
+ }
+
+ destanimdesc->pSection(w)->animblock = g_numanimblocks - 1;
+ destanimdesc->pSection(w)->animindex = pStartSection - g_animblock[g_numanimblocks-1].start;
+ }
+ else
+ {
+ destanimdesc->pSection(w)->animblock = 0;
+ destanimdesc->pSection(w)->animindex = pStartSection - (byte *)destanimdesc;
+ }
+ // printf("%s (%d) : %d:%d\n", srcanim->name, w, destanimdesc->pSection(w)->animblock, destanimdesc->pSection(w)->animindex );
+ }
+
+ if (!bUseExtData)
+ {
+ pLocalData = pData;
+ }
+ else
+ {
+ pExtData = pData;
+ }
+ }
+}
+
+
+byte *WriteIkErrors( s_animation_t *srcanim, byte *pData )
+{
+ int j, k;
+
+ // write IK error keys
+ mstudioikrule_t *pikruledata = (mstudioikrule_t *)pData;
+ pData += srcanim->numikrules * sizeof( *pikruledata );
+ ALIGN4( pData );
+
+ for (j = 0; j < srcanim->numikrules; j++)
+ {
+ mstudioikrule_t *pikrule = pikruledata + j;
+
+ pikrule->index = srcanim->ikrule[j].index;
+
+ pikrule->chain = srcanim->ikrule[j].chain;
+ pikrule->bone = srcanim->ikrule[j].bone;
+ pikrule->type = srcanim->ikrule[j].type;
+ pikrule->slot = srcanim->ikrule[j].slot;
+ pikrule->pos = srcanim->ikrule[j].pos;
+ pikrule->q = srcanim->ikrule[j].q;
+ pikrule->height = srcanim->ikrule[j].height;
+ pikrule->floor = srcanim->ikrule[j].floor;
+ pikrule->radius = srcanim->ikrule[j].radius;
+
+ if (srcanim->numframes > 1.0)
+ {
+ pikrule->start = srcanim->ikrule[j].start / (srcanim->numframes - 1.0f);
+ pikrule->peak = srcanim->ikrule[j].peak / (srcanim->numframes - 1.0f);
+ pikrule->tail = srcanim->ikrule[j].tail / (srcanim->numframes - 1.0f);
+ pikrule->end = srcanim->ikrule[j].end / (srcanim->numframes - 1.0f);
+ pikrule->contact= srcanim->ikrule[j].contact / (srcanim->numframes - 1.0f);
+ }
+ else
+ {
+ pikrule->start = 0.0f;
+ pikrule->peak = 0.0f;
+ pikrule->tail = 1.0f;
+ pikrule->end = 1.0f;
+ pikrule->contact= 0.0f;
+ }
+
+ /*
+ printf("%d %d %d %d : %.2f %.2f %.2f %.2f\n",
+ srcanim->ikrule[j].start, srcanim->ikrule[j].peak, srcanim->ikrule[j].tail, srcanim->ikrule[j].end,
+ pikrule->start, pikrule->peak, pikrule->tail, pikrule->end );
+ */
+
+ pikrule->iStart = srcanim->ikrule[j].start;
+
+#if 0
+ // uncompressed
+ pikrule->ikerrorindex = (pData - (byte*)pikrule);
+ mstudioikerror_t *perror = (mstudioikerror_t *)pData;
+ pData += srcanim->ikrule[j].numerror * sizeof( *perror );
+
+ for (k = 0; k < srcanim->ikrule[j].numerror; k++)
+ {
+ perror[k].pos = srcanim->ikrule[j].pError[k].pos;
+ perror[k].q = srcanim->ikrule[j].pError[k].q;
+ }
+#endif
+#if 1
+ // skip writting the header if there's no IK data
+ for (k = 0; k < 6; k++)
+ {
+ if (srcanim->ikrule[j].errorData.numanim[k]) break;
+ }
+
+ if (k == 6)
+ continue;
+
+ // compressed
+ pikrule->compressedikerrorindex = (pData - (byte*)pikrule);
+ mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pData;
+ pData += sizeof( *pCompressed );
+
+ for (k = 0; k < 6; k++)
+ {
+ pCompressed->scale[k] = srcanim->ikrule[j].errorData.scale[k];
+ pCompressed->offset[k] = (pData - (byte*)pCompressed);
+ int size = srcanim->ikrule[j].errorData.numanim[k] * sizeof( mstudioanimvalue_t );
+ memmove( pData, srcanim->ikrule[j].errorData.anim[k], size );
+ pData += size;
+ }
+
+ if (strlen( srcanim->ikrule[j].attachment ) > 0)
+ {
+ // don't use string table, we're probably not in the same file.
+ int size = strlen( srcanim->ikrule[j].attachment ) + 1;
+ strcpy( (char *)pData, srcanim->ikrule[j].attachment );
+ pikrule->szattachmentindex = pData - (byte *)pikrule;
+ pData += size;
+ }
+
+ ALIGN4( pData );
+
+#endif
+ // AddToStringTable( pikrule, &pikrule->szattachmentindex, srcanim->ikrule[j].attachment );
+ }
+
+ return pData;
+}
+
+
+
+
+
+
+byte *WriteLocalHierarchy( s_animation_t *srcanim, byte *pData )
+{
+ int j, k;
+
+ // write hierarchy keys
+ mstudiolocalhierarchy_t *pHierarchyData = (mstudiolocalhierarchy_t *)pData;
+ pData += srcanim->numlocalhierarchy * sizeof( *pHierarchyData );
+ ALIGN4( pData );
+
+ for (j = 0; j < srcanim->numlocalhierarchy; j++)
+ {
+ mstudiolocalhierarchy_t *pHierarchy = pHierarchyData + j;
+
+ pHierarchy->iBone = srcanim->localhierarchy[j].bone;
+ pHierarchy->iNewParent = srcanim->localhierarchy[j].newparent;
+
+ if (srcanim->numframes > 1.0)
+ {
+ pHierarchy->start = srcanim->localhierarchy[j].start / (srcanim->numframes - 1.0f);
+ pHierarchy->peak = srcanim->localhierarchy[j].peak / (srcanim->numframes - 1.0f);
+ pHierarchy->tail = srcanim->localhierarchy[j].tail / (srcanim->numframes - 1.0f);
+ pHierarchy->end = srcanim->localhierarchy[j].end / (srcanim->numframes - 1.0f);
+ }
+ else
+ {
+ pHierarchy->start = 0.0f;
+ pHierarchy->peak = 0.0f;
+ pHierarchy->tail = 1.0f;
+ pHierarchy->end = 1.0f;
+ }
+
+ pHierarchy->iStart = srcanim->localhierarchy[j].start;
+
+#if 0
+ // uncompressed
+ pHierarchy->ikerrorindex = (pData - (byte*)pHierarchy);
+ mstudioikerror_t *perror = (mstudioikerror_t *)pData;
+ pData += srcanim->ikrule[j].numerror * sizeof( *perror );
+
+ for (k = 0; k < srcanim->ikrule[j].numerror; k++)
+ {
+ perror[k].pos = srcanim->ikrule[j].pError[k].pos;
+ perror[k].q = srcanim->ikrule[j].pError[k].q;
+ }
+#endif
+#if 1
+ // skip writting the header if there's no IK data
+ for (k = 0; k < 6; k++)
+ {
+ if (srcanim->localhierarchy[j].localData.numanim[k]) break;
+ }
+
+ if (k == 6)
+ continue;
+
+ // compressed
+ pHierarchy->localanimindex = (pData - (byte*)pHierarchy);
+ mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pData;
+ pData += sizeof( *pCompressed );
+
+ for (k = 0; k < 6; k++)
+ {
+ pCompressed->scale[k] = srcanim->localhierarchy[j].localData.scale[k];
+ pCompressed->offset[k] = (pData - (byte*)pCompressed);
+ int size = srcanim->localhierarchy[j].localData.numanim[k] * sizeof( mstudioanimvalue_t );
+ memmove( pData, srcanim->localhierarchy[j].localData.anim[k], size );
+ pData += size;
+ }
+
+ ALIGN4( pData );
+
+#endif
+ // AddToStringTable( pHierarchy, &pHierarchy->szattachmentindex, srcanim->ikrule[j].attachment );
+ }
+
+ return pData;
+}
+
+
+static byte *WriteAnimations( byte *pData, byte *pStart, studiohdr_t *phdr )
+{
+ int i, j;
+
+ mstudioanimdesc_t *panimdesc;
+
+ // save animations
+ panimdesc = (mstudioanimdesc_t *)pData;
+ if( phdr )
+ {
+ phdr->numlocalanim = g_numani;
+ phdr->localanimindex = (pData - pStart);
+ }
+ pData += g_numani * sizeof( *panimdesc );
+ ALIGN4( pData );
+ // ------------ ------- ------- : ------- (-------)
+ if( g_verbose )
+ {
+ printf(" animation x y ips angle\n");
+ }
+
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *srcanim = g_panimation[ i ];
+ mstudioanimdesc_t *destanim = &panimdesc[i];
+ Assert( srcanim );
+
+ AddToStringTable( destanim, &destanim->sznameindex, srcanim->name );
+
+ destanim->baseptr = pStart - (byte *)destanim;
+ destanim->fps = srcanim->fps;
+ destanim->flags = srcanim->flags;
+
+ destanim->sectionframes = srcanim->sectionframes;
+
+ totalframes += srcanim->numframes;
+ totalseconds += srcanim->numframes / srcanim->fps;
+
+ destanim->numframes = srcanim->numframes;
+
+ // destanim->motiontype = srcanim->motiontype;
+ // destanim->motionbone = srcanim->motionbone;
+ // VectorCopy( srcanim->linearpos, destanim->linearpos );
+
+ j = srcanim->numpiecewisekeys - 1;
+ if (srcanim->piecewisemove[j].pos[0] != 0 || srcanim->piecewisemove[j].pos[1] != 0)
+ {
+ float t = (srcanim->numframes - 1) / srcanim->fps;
+
+ float r = 1 / t;
+
+ float a = atan2( srcanim->piecewisemove[j].pos[1], srcanim->piecewisemove[j].pos[0] ) * (180 / M_PI);
+ float d = sqrt( DotProduct( srcanim->piecewisemove[j].pos, srcanim->piecewisemove[j].pos ) );
+ if( g_verbose )
+ {
+ printf("%12s %7.2f %7.2f : %7.2f (%7.2f) %.1f\n", srcanim->name, srcanim->piecewisemove[j].pos[0], srcanim->piecewisemove[j].pos[1], d * r, a, t );
+ }
+ }
+
+ if (srcanim->numsections > 1)
+ {
+ destanim->sectionindex = pData - (byte *)destanim;
+ pData += srcanim->numsections * sizeof( mstudioanimsections_t );
+ }
+
+ // VectorCopy( srcanim->linearrot, destanim->linearrot );
+ // destanim->automoveposindex = srcanim->automoveposindex;
+ // destanim->automoveangleindex = srcanim->automoveangleindex;
+
+ // align all animation data to cache line boundaries
+ ALIGN16( pData );
+ ALIGN16( pBlockData );
+
+ if (pBlockStart)
+ {
+ // allocate the first block if needed
+ if (g_numanimblocks == 0)
+ {
+ g_numanimblocks = 1;
+ g_animblock[g_numanimblocks].start = pBlockData;
+ g_numanimblocks++;
+ }
+ }
+
+ if (!pBlockStart || (g_bonesaveframe.Count() == 0 && srcanim->numframes == 1))
+ {
+ // hack
+ srcanim->disableAnimblocks = true;
+ }
+ else if (g_bNoAnimblockStall)
+ {
+ srcanim->isFirstSectionLocal = true;
+ }
+
+ // block zero is relative to me
+ g_animblock[0].start = (byte *)(destanim);
+
+ byte *pAnimData = NULL;
+ byte *pIkData = NULL;
+ byte *pLocalHierarchy = NULL;
+ byte *pBlockEnd = pBlockData;
+
+ if (srcanim->disableAnimblocks || srcanim->isFirstSectionLocal)
+ {
+ destanim->animblock = 0;
+ pAnimData = pData;
+ WriteAnimationData( srcanim, destanim, pData, pBlockEnd );
+ pIkData = pData;
+ pLocalHierarchy = WriteIkErrors( srcanim, pIkData );
+ pData = WriteLocalHierarchy( srcanim, pLocalHierarchy );
+ }
+ else
+ {
+ pAnimData = pBlockEnd;
+ WriteAnimationData( srcanim, destanim, pData, pBlockEnd );
+ if ( destanim->sectionindex )
+ {
+ // if sections were written, don't move the data already written to the last block
+ pBlockData = pBlockEnd;
+ }
+ destanim->animblock = g_numanimblocks-1;
+ pIkData = pBlockEnd;
+ pLocalHierarchy = WriteIkErrors( srcanim, pIkData );
+ pBlockEnd = WriteLocalHierarchy( srcanim, pLocalHierarchy );
+ }
+
+ // printf("%d %x %x %x %s : %d\n", g_numanimblocks - 1, g_animblock[g_numanimblocks-1].start, pBlockData, pBlockEnd, srcanim->name, srcanim->numsections );
+
+ if (pBlockData != pBlockEnd && pBlockEnd - g_animblock[g_numanimblocks-1].start > g_animblocksize)
+ {
+ g_animblock[g_numanimblocks-1].end = pBlockData;
+ g_animblock[g_numanimblocks].start = pBlockData;
+ g_numanimblocks++;
+ destanim->animblock = g_numanimblocks-1;
+ }
+
+ destanim->animindex = pAnimData - g_animblock[destanim->animblock].start;
+
+ if ( srcanim->numikrules )
+ {
+ destanim->numikrules = srcanim->numikrules;
+ if (destanim->animblock == 0)
+ {
+ destanim->ikruleindex = pIkData - g_animblock[destanim->animblock].start;
+ }
+ else
+ {
+ destanim->animblockikruleindex = pIkData - g_animblock[destanim->animblock].start;
+ }
+ }
+ if ( srcanim->numlocalhierarchy )
+ {
+ destanim->numlocalhierarchy = srcanim->numlocalhierarchy;
+ destanim->localhierarchyindex = pLocalHierarchy - g_animblock[destanim->animblock].start;
+ }
+
+ if (g_numanimblocks)
+ {
+ g_animblock[g_numanimblocks-1].end = pBlockEnd;
+ pBlockData = pBlockEnd;
+ }
+
+ // printf("%s : %d:%d\n", srcanim->name, destanim->animblock, destanim->animindex );
+
+ // printf("raw bone data %d : %s\n", (byte *)destanimvalue - pData, srcanim->name);
+ }
+
+ if( !g_quiet )
+ {
+ /*
+ for (i = 0; i < g_numanimblocks; i++)
+ {
+ printf("%2d (%3d:%3d): %d\n", i, g_animblock[i].iStartAnim, g_animblock[i].iEndAnim, g_animblock[i].end - g_animblock[i].start );
+ }
+ */
+ }
+
+ if( !g_quiet )
+ {
+ /*
+ printf("raw anim data %d : %d\n", rawanimbytes, animboneframes );
+ printf("pos %d %d %d %d\n", numPos[0], numPos[1], numPos[2], numPos[3] );
+ printf("axis %d %d %d %d : %d\n", numAxis[0], numAxis[1], numAxis[2], numAxis[3], useRaw );
+ */
+ }
+
+ // write movement keys
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *anim = g_panimation[ i ];
+
+ // panimdesc[i].entrancevelocity = anim->entrancevelocity;
+ panimdesc[i].nummovements = anim->numpiecewisekeys;
+ if (panimdesc[i].nummovements)
+ {
+ panimdesc[i].movementindex = pData - (byte*)&panimdesc[i];
+
+ mstudiomovement_t *pmove = (mstudiomovement_t *)pData;
+ pData += panimdesc[i].nummovements * sizeof( *pmove );
+ ALIGN4( pData );
+
+ for (j = 0; j < panimdesc[i].nummovements; j++)
+ {
+ pmove[j].endframe = anim->piecewisemove[j].endframe;
+ pmove[j].motionflags = anim->piecewisemove[j].flags;
+ pmove[j].v0 = anim->piecewisemove[j].v0;
+ pmove[j].v1 = anim->piecewisemove[j].v1;
+ pmove[j].angle = RAD2DEG( anim->piecewisemove[j].rot[2] );
+ VectorCopy( anim->piecewisemove[j].vector, pmove[j].vector );
+ VectorCopy( anim->piecewisemove[j].pos, pmove[j].position );
+ }
+ }
+ }
+
+ // only write zero frames if the animation data is demand loaded
+ if (!pBlockStart)
+ return pData;
+
+
+ // calculate what bones should be have zero frame saved out
+ if (g_bonesaveframe.Count() == 0)
+ {
+ for (j = 0; j < g_numbones; j++)
+ {
+ if ((g_bonetable[j].parent == -1) || (g_bonetable[j].posrange.Length() > 2.0))
+ {
+ g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_POS;
+ }
+ g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_ROT;
+
+ if ((!g_quiet) && (g_bonetable[j].flags & (BONE_HAS_SAVEFRAME_POS | BONE_HAS_SAVEFRAME_ROT)))
+ {
+ printf("$BoneSaveFrame \"%s\"", g_bonetable[j].name );
+ if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_POS)
+ printf(" position" );
+ if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_ROT)
+ printf(" rotation" );
+ printf("\n");
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < g_bonesaveframe.Count(); i++)
+ {
+ j = findGlobalBone( g_bonesaveframe[i].name );
+
+ if (j != -1)
+ {
+ if (g_bonesaveframe[i].bSavePos)
+ {
+ g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_POS;
+ }
+ if (g_bonesaveframe[i].bSaveRot)
+ {
+ g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_ROT;
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < g_numbones; j++)
+ {
+ phdr->pBone(j)->flags |= g_bonetable[j].flags;
+ }
+
+ ALIGN4( pData );
+
+ // write zero frames
+ for (i = 0; i < g_numani; i++)
+ {
+ s_animation_t *anim = g_panimation[ i ];
+
+ if (panimdesc[i].animblock != 0)
+ {
+ panimdesc[i].zeroframeindex = pData - (byte *)&panimdesc[i];
+
+ int k = min( panimdesc[i].numframes - 1, 9 );
+ if (panimdesc[i].flags & STUDIO_LOOPING)
+ {
+ k = min( (panimdesc[i].numframes - 1) / 2, k );
+ }
+ panimdesc[i].zeroframespan = k;
+ if (k > 2)
+ {
+ panimdesc[i].zeroframecount = min( (panimdesc[i].numframes - 1) / panimdesc[i].zeroframespan, 3 ); // save frames 0..24 frames
+ }
+ if (panimdesc[i].zeroframecount < 1)
+ panimdesc[i].zeroframecount = 1;
+
+ for (j = 0; j < g_numbones; j++)
+ {
+ if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_POS)
+ {
+ for (int n = 0; n < panimdesc[i].zeroframecount; n++)
+ {
+ *(Vector48 *)pData = anim->sanim[panimdesc[i].zeroframespan*n][j].pos;
+ pData += sizeof( Vector48 );
+ }
+ }
+ if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_ROT)
+ {
+ for (int n = 0; n < panimdesc[i].zeroframecount; n++)
+ {
+ Quaternion q;
+ AngleQuaternion( anim->sanim[panimdesc[i].zeroframespan*n][j].rot, q );
+ *((Quaternion64 *)pData) = q;
+ pData += sizeof( Quaternion64 );
+ }
+ }
+ }
+ }
+ }
+
+ ALIGN4( pData );
+
+ return pData;
+}
+
+
+
+static void WriteTextures( studiohdr_t *phdr )
+{
+ int i, j;
+ short *pref;
+
+ // save texture info
+ mstudiotexture_t *ptexture = (mstudiotexture_t *)pData;
+ phdr->numtextures = g_nummaterials;
+ phdr->textureindex = pData - pStart;
+ pData += g_nummaterials * sizeof( mstudiotexture_t );
+ for (i = 0; i < g_nummaterials; i++)
+ {
+ j = g_material[i];
+ AddToStringTable( &ptexture[i], &ptexture[i].sznameindex, g_texture[j].name );
+ }
+ ALIGN4( pData );
+
+ int *cdtextureoffset = (int *)pData;
+ phdr->numcdtextures = numcdtextures;
+ phdr->cdtextureindex = pData - pStart;
+ pData += numcdtextures * sizeof( int );
+ for (i = 0; i < numcdtextures; i++)
+ {
+ AddToStringTable( phdr, &cdtextureoffset[i], cdtextures[i] );
+ }
+ ALIGN4( pData );
+
+ // save texture directory info
+ phdr->skinindex = (pData - pStart);
+ phdr->numskinref = g_numskinref;
+ phdr->numskinfamilies = g_numskinfamilies;
+ pref = (short *)pData;
+
+ for (i = 0; i < phdr->numskinfamilies; i++)
+ {
+ for (j = 0; j < phdr->numskinref; j++)
+ {
+ *pref = g_skinref[i][j];
+ pref++;
+ }
+ }
+ pData = (byte *)pref;
+ ALIGN4( pData );
+}
+
+
+//-----------------------------------------------------------------------------
+// Write source bone transforms
+//-----------------------------------------------------------------------------
+static void WriteBoneTransforms( studiohdr2_t *phdr, mstudiobone_t *pBone )
+{
+ matrix3x4_t identity;
+ SetIdentityMatrix( identity );
+
+ int nTransformCount = 0;
+ for (int i = 0; i < g_numbones; i++)
+ {
+ if ( g_bonetable[i].flags & BONE_ALWAYS_PROCEDURAL )
+ continue;
+ int nParent = g_bonetable[i].parent;
+
+ // Transformation is necessary if either you or your parent was realigned
+ if ( MatricesAreEqual( identity, g_bonetable[i].srcRealign ) &&
+ ( ( nParent < 0 ) || MatricesAreEqual( identity, g_bonetable[nParent].srcRealign ) ) )
+ continue;
+
+ ++nTransformCount;
+ }
+
+ // save bone transform info
+ mstudiosrcbonetransform_t *pSrcBoneTransform = (mstudiosrcbonetransform_t *)pData;
+ phdr->numsrcbonetransform = nTransformCount;
+ phdr->srcbonetransformindex = pData - pStart;
+ pData += nTransformCount * sizeof( mstudiosrcbonetransform_t );
+ int bt = 0;
+ for ( int i = 0; i < g_numbones; i++ )
+ {
+ if ( g_bonetable[i].flags & BONE_ALWAYS_PROCEDURAL )
+ continue;
+ int nParent = g_bonetable[i].parent;
+ if ( MatricesAreEqual( identity, g_bonetable[i].srcRealign ) &&
+ ( ( nParent < 0 ) || MatricesAreEqual( identity, g_bonetable[nParent].srcRealign ) ) )
+ continue;
+
+ // What's going on here?
+ // So, when we realign a bone, we want to do it in a way so that the child bones
+ // have the same bone->world transform. If we take T as the src realignment transform
+ // for the parent, P is the parent to world, and C is the child to parent, we expect
+ // the child->world is constant after realignment:
+ // CtoW = P * C = ( P * T ) * ( T^-1 * C )
+ // therefore Cnew = ( T^-1 * C )
+ if ( nParent >= 0 )
+ {
+ MatrixInvert( g_bonetable[nParent].srcRealign, pSrcBoneTransform[bt].pretransform );
+ }
+ else
+ {
+ SetIdentityMatrix( pSrcBoneTransform[bt].pretransform );
+ }
+ MatrixCopy( g_bonetable[i].srcRealign, pSrcBoneTransform[bt].posttransform );
+ AddToStringTable( &pSrcBoneTransform[bt], &pSrcBoneTransform[bt].sznameindex, g_bonetable[i].name );
+ ++bt;
+ }
+ ALIGN4( pData );
+
+ if (g_numbones > 1)
+ {
+ // write second bone table
+ phdr->linearboneindex = pData - (byte *)phdr;
+ mstudiolinearbone_t *pLinearBone = (mstudiolinearbone_t *)pData;
+ pData += sizeof( *pLinearBone );
+
+ pLinearBone->numbones = g_numbones;
+
+#define WRITE_BONE_BLOCK( type, srcfield, dest, destindex ) \
+ type *##dest = (type *)pData; \
+ pLinearBone->##destindex = pData - (byte *)pLinearBone; \
+ pData += g_numbones * sizeof( *##dest ); \
+ ALIGN4( pData ); \
+ for ( int i = 0; i < g_numbones; i++) \
+ dest##[i] = pBone[i].##srcfield;
+
+ WRITE_BONE_BLOCK( int, flags, pFlags, flagsindex );
+ WRITE_BONE_BLOCK( int, parent, pParent, parentindex );
+ WRITE_BONE_BLOCK( Vector, pos, pPos, posindex );
+ WRITE_BONE_BLOCK( Quaternion, quat, pQuat, quatindex );
+ WRITE_BONE_BLOCK( RadianEuler, rot, pRot, rotindex );
+ WRITE_BONE_BLOCK( matrix3x4_t, poseToBone, pPoseToBone, posetoboneindex );
+ WRITE_BONE_BLOCK( Vector, posscale, pPoseScale, posscaleindex );
+ WRITE_BONE_BLOCK( Vector, rotscale, pRotScale, rotscaleindex );
+ WRITE_BONE_BLOCK( Quaternion, qAlignment, pQAlignment, qalignmentindex );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Write the bone flex drivers
+//-----------------------------------------------------------------------------
+static void WriteBoneFlexDrivers( studiohdr2_t *pStudioHdr2 )
+{
+ ALIGN4( pData );
+
+ pStudioHdr2->m_nBoneFlexDriverCount = 0;
+ pStudioHdr2->m_nBoneFlexDriverIndex = 0;
+
+ CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList );
+ if ( !pDmeBoneFlexDriverList )
+ return;
+
+ const int nBoneFlexDriverCount = pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Count();
+ if ( nBoneFlexDriverCount <= 0 )
+ return;
+
+ mstudioboneflexdriver_t *pBoneFlexDriver = (mstudioboneflexdriver_t *)pData;
+ pStudioHdr2->m_nBoneFlexDriverCount = nBoneFlexDriverCount;
+ pStudioHdr2->m_nBoneFlexDriverIndex = pData - (byte *)pStudioHdr2;
+ pData += nBoneFlexDriverCount * sizeof( mstudioboneflexdriver_t );
+ ALIGN4( pData );
+
+ for ( int i = 0; i < nBoneFlexDriverCount; ++i )
+ {
+ CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->m_eBoneFlexDriverList[i];
+ Assert( pDmeBoneFlexDriver );
+ Assert( pDmeBoneFlexDriver->m_eControlList.Count() > 0 );
+ Assert( pDmeBoneFlexDriver->GetValue< int >( "__boneIndex", -1 ) >= 0 );
+
+ pBoneFlexDriver->m_nBoneIndex = pDmeBoneFlexDriver->GetValue< int >( "__boneIndex", 0 );
+ pBoneFlexDriver->m_nControlCount = pDmeBoneFlexDriver->m_eControlList.Count();
+ pBoneFlexDriver->m_nControlIndex = pData - (byte *)pBoneFlexDriver;
+
+ mstudioboneflexdrivercontrol_t *pControl = reinterpret_cast< mstudioboneflexdrivercontrol_t * >( pData );
+
+ for ( int j = 0; j < pBoneFlexDriver->m_nControlCount; ++j )
+ {
+ CDmeBoneFlexDriverControl *pDmeControl = pDmeBoneFlexDriver->m_eControlList[j];
+ Assert( pDmeControl );
+ Assert( pDmeControl->GetValue< int >( "__flexControlIndex", -1 ) >= 0 );
+ Assert( pDmeControl->m_nBoneComponent >= STUDIO_BONE_FLEX_TX );
+ Assert( pDmeControl->m_nBoneComponent <= STUDIO_BONE_FLEX_TZ );
+
+ pControl[j].m_nFlexControllerIndex = pDmeControl->GetValue< int >( "__flexControlIndex", 0 );
+ pControl[j].m_nBoneComponent = pDmeControl->m_nBoneComponent;
+ pControl[j].m_flMin = pDmeControl->m_flMin;
+ pControl[j].m_flMax = pDmeControl->m_flMax;
+ }
+
+ pData += pBoneFlexDriver->m_nControlCount * sizeof( mstudioboneflexdrivercontrol_t );
+ ALIGN4( pData );
+
+ ++pBoneFlexDriver;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Write the processed vertices
+//-----------------------------------------------------------------------------
+static void WriteVertices( studiohdr_t *phdr )
+{
+ char fileName[MAX_PATH];
+ byte *pStart;
+ byte *pData;
+ int i;
+ int j;
+ int k;
+ int cur;
+
+ if (!g_nummodelsbeforeLOD)
+ return;
+
+ V_strcpy_safe( fileName, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( fileName, "platform_" );
+// strcat( fileName, g_pPlatformName );
+// strcat( fileName, "/" );
+// }
+ V_strcat_safe( fileName, "models/" );
+ V_strcat_safe( fileName, outname );
+ Q_StripExtension( fileName, fileName, sizeof( fileName ) );
+ V_strcat_safe( fileName, ".vvd" );
+
+ if ( !g_quiet )
+ {
+ printf ("---------------------\n");
+ printf ("writing %s:\n", fileName);
+ }
+
+ pStart = (byte *)kalloc( 1, FILEBUFFER );
+ pData = pStart;
+
+ vertexFileHeader_t *fileHeader = (vertexFileHeader_t *)pData;
+ pData += sizeof(vertexFileHeader_t);
+
+ fileHeader->id = MODEL_VERTEX_FILE_ID;
+ fileHeader->version = MODEL_VERTEX_FILE_VERSION;
+ fileHeader->checksum = phdr->checksum;
+
+ // data has no fixes and requires no fixes
+ fileHeader->numFixups = 0;
+ fileHeader->fixupTableStart = 0;
+
+ // unfinalized during first pass, fixed during second pass
+ // data can be considered as single lod at lod 0
+ fileHeader->numLODs = 1;
+ fileHeader->numLODVertexes[0] = 0;
+
+ // store vertexes grouped by mesh order
+ ALIGN16( pData );
+ fileHeader->vertexDataStart = pData-pStart;
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ s_loddata_t *pLodData = g_model[i]->m_pLodData;
+
+ // skip blank empty model
+ if (!pLodData)
+ continue;
+
+ // save vertices
+ ALIGN16( pData );
+ cur = (int)pData;
+ mstudiovertex_t *pVert = (mstudiovertex_t *)pData;
+ pData += pLodData->numvertices * sizeof( mstudiovertex_t );
+ for (j = 0; j < pLodData->numvertices; j++)
+ {
+// printf( "saving bone weight %d for model %d at 0x%p\n",
+// j, i, &pbone[j] );
+
+ const s_vertexinfo_t &lodVertex = pLodData->vertex[j];
+ VectorCopy( lodVertex.position, pVert[j].m_vecPosition );
+ VectorCopy( lodVertex.normal, pVert[j].m_vecNormal );
+ Vector2DCopy( lodVertex.texcoord, pVert[j].m_vecTexCoord );
+
+ mstudioboneweight_t *pBoneWeight = &pVert[j].m_BoneWeights;
+ memset( pBoneWeight, 0, sizeof( mstudioboneweight_t ) );
+ pBoneWeight->numbones = lodVertex.boneweight.numbones;
+ for (k = 0; k < pBoneWeight->numbones; k++)
+ {
+ pBoneWeight->bone[k] = lodVertex.boneweight.bone[k];
+ pBoneWeight->weight[k] = lodVertex.boneweight.weight[k];
+ }
+ }
+
+ fileHeader->numLODVertexes[0] += pLodData->numvertices;
+
+ if (!g_quiet)
+ {
+ printf( "vertices %7d bytes (%d vertices)\n", (int)(pData - cur), pLodData->numvertices );
+ }
+ }
+
+ // store tangents grouped by mesh order
+ ALIGN4( pData );
+ fileHeader->tangentDataStart = pData-pStart;
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ s_loddata_t *pLodData = g_model[i]->m_pLodData;
+
+ // skip blank empty model
+ if (!pLodData)
+ continue;
+
+ // save tangent space S
+ ALIGN4( pData );
+ cur = (int)pData;
+ Vector4D *ptangents = (Vector4D *)pData;
+ pData += pLodData->numvertices * sizeof( Vector4D );
+ for (j = 0; j < pLodData->numvertices; j++)
+ {
+ Vector4DCopy( pLodData->vertex[j].tangentS, ptangents[j] );
+#ifdef _DEBUG
+ float w = ptangents[j].w;
+ Assert( w == 1.0f || w == -1.0f );
+#endif
+ }
+
+ if (!g_quiet)
+ {
+ printf( "tangents %7d bytes (%d vertices)\n", (int)(pData - cur), pLodData->numvertices );
+ }
+ }
+
+ if (!g_quiet)
+ {
+ printf( "total %7d bytes\n", pData - pStart );
+ }
+
+ // fileHeader->length = pData - pStart;
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile( fileName, pStart, pData - pStart );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the maximum absolute value of any component of all vertex animation
+// pos (x,y,z) normal (x,y,z) or wrinkle
+//
+// Returns the fixed point scale and also sets appropriate values & flags in
+// passed studiohdr_t
+//-----------------------------------------------------------------------------
+float ComputeVertAnimFixedPointScale( studiohdr_t *pStudioHdr )
+{
+ float flVertAnimRange = 0.0f;
+
+ for ( int j = 0; j < g_numflexkeys; ++j )
+ {
+ if ( g_flexkey[j].numvanims <= 0 )
+ continue;
+
+ const bool bWrinkleVAnim = ( g_flexkey[j].vanimtype == STUDIO_VERT_ANIM_WRINKLE );
+
+ s_vertanim_t *pVertAnim = g_flexkey[j].vanim;
+
+ for ( int k = 0; k < g_flexkey[j].numvanims; ++k )
+ {
+ if ( fabs( pVertAnim->pos.x ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->pos.x );
+ }
+
+ if ( fabs( pVertAnim->pos.y ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->pos.y );
+ }
+
+ if ( fabs( pVertAnim->pos.z ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->pos.z );
+ }
+
+ if ( fabs( pVertAnim->normal.x ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->normal.x );
+ }
+
+ if ( fabs( pVertAnim->normal.y ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->normal.y );
+ }
+
+ if ( fabs( pVertAnim->normal.z ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->normal.z );
+ }
+
+ if ( bWrinkleVAnim )
+ {
+ if ( fabs( pVertAnim->wrinkle ) > flVertAnimRange )
+ {
+ flVertAnimRange = fabs( pVertAnim->wrinkle );
+ }
+ }
+
+ pVertAnim++;
+ }
+ }
+
+ // Legacy value
+ float flVertAnimFixedPointScale = 1.0 / 4096.0f;
+
+ if ( flVertAnimRange > 0.0f )
+ {
+ if ( flVertAnimRange > 32767 )
+ {
+ MdlWarning( "Flex value too large: %.2f, Max: 32767\n", flVertAnimRange );
+
+ flVertAnimFixedPointScale = 1.0f;
+ }
+ else
+ {
+ const float flTmpScale = flVertAnimRange / 32767.0f;
+ if ( flTmpScale > flVertAnimFixedPointScale )
+ {
+ flVertAnimFixedPointScale = flTmpScale;
+ }
+ }
+ }
+
+ if ( flVertAnimFixedPointScale != 1.0f / 4096.0f )
+ {
+ pStudioHdr->flags |= STUDIOHDR_FLAGS_VERT_ANIM_FIXED_POINT_SCALE;
+ pStudioHdr->flVertAnimFixedPointScale = flVertAnimFixedPointScale;
+ }
+
+ return flVertAnimFixedPointScale;
+}
+
+
+static void WriteModel( studiohdr_t *phdr )
+{
+ int i, j, k, m;
+ mstudiobodyparts_t *pbodypart;
+ mstudiomodel_t *pmodel;
+ s_source_t *psource;
+ mstudiovertanim_t *pvertanim;
+ s_vertanim_t *pvanim;
+
+ int cur = (int)pData;
+
+ // vertex data is written to external file, offsets kept internal
+ // track expected external base to store proper offsets
+ byte *externalVertexIndex = 0;
+ byte *externalTangentsIndex = 0;
+
+ // write bodypart info
+ pbodypart = (mstudiobodyparts_t *)pData;
+ phdr->numbodyparts = g_numbodyparts;
+ phdr->bodypartindex = pData - pStart;
+ pData += g_numbodyparts * sizeof( mstudiobodyparts_t );
+
+ pmodel = (mstudiomodel_t *)pData;
+ pData += g_nummodelsbeforeLOD * sizeof( mstudiomodel_t );
+
+ for (i = 0, j = 0; i < g_numbodyparts; i++)
+ {
+ AddToStringTable( &pbodypart[i], &pbodypart[i].sznameindex, g_bodypart[i].name );
+ pbodypart[i].nummodels = g_bodypart[i].nummodels;
+ pbodypart[i].base = g_bodypart[i].base;
+ pbodypart[i].modelindex = ((byte *)&pmodel[j]) - (byte *)&pbodypart[i];
+ j += g_bodypart[i].nummodels;
+ }
+ ALIGN4( pData );
+
+ // write global flex names
+ mstudioflexdesc_t *pflexdesc = (mstudioflexdesc_t *)pData;
+ phdr->numflexdesc = g_numflexdesc;
+ phdr->flexdescindex = pData - pStart;
+ pData += g_numflexdesc * sizeof( mstudioflexdesc_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numflexdesc; j++)
+ {
+ // printf("%d %s\n", j, g_flexdesc[j].FACS );
+ AddToStringTable( pflexdesc, &pflexdesc->szFACSindex, g_flexdesc[j].FACS );
+ pflexdesc++;
+ }
+
+ // write global flex controllers
+ mstudioflexcontroller_t *pflexcontroller = (mstudioflexcontroller_t *)pData;
+ phdr->numflexcontrollers = g_numflexcontrollers;
+ phdr->flexcontrollerindex = pData - pStart;
+ pData += g_numflexcontrollers * sizeof( mstudioflexcontroller_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numflexcontrollers; j++)
+ {
+ AddToStringTable( pflexcontroller, &pflexcontroller->sznameindex, g_flexcontroller[j].name );
+ AddToStringTable( pflexcontroller, &pflexcontroller->sztypeindex, g_flexcontroller[j].type );
+ pflexcontroller->min = g_flexcontroller[j].min;
+ pflexcontroller->max = g_flexcontroller[j].max;
+ pflexcontroller->localToGlobal = -1;
+ pflexcontroller++;
+ }
+
+ // write flex rules
+ mstudioflexrule_t *pflexrule = (mstudioflexrule_t *)pData;
+ phdr->numflexrules = g_numflexrules;
+ phdr->flexruleindex = pData - pStart;
+ pData += g_numflexrules * sizeof( mstudioflexrule_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numflexrules; j++)
+ {
+ pflexrule->flex = g_flexrule[j].flex;
+ pflexrule->numops = g_flexrule[j].numops;
+ pflexrule->opindex = (pData - (byte *)pflexrule);
+
+ mstudioflexop_t *pflexop = (mstudioflexop_t *)pData;
+
+ for (i = 0; i < pflexrule->numops; i++)
+ {
+ pflexop[i].op = g_flexrule[j].op[i].op;
+ pflexop[i].d.index = g_flexrule[j].op[i].d.index;
+ }
+
+ pData += sizeof( mstudioflexop_t ) * pflexrule->numops;
+ ALIGN4( pData );
+
+ pflexrule++;
+ }
+
+ // write global flex controller information
+
+ mstudioflexcontrollerui_t *pFlexControllerUI = (mstudioflexcontrollerui_t *)pData;
+ phdr->numflexcontrollerui = 0;
+ phdr->flexcontrolleruiindex = pData - pStart;
+
+ // Loop through all defined controllers and create a UI structure for them
+ // All actual controllers will be defined as a member of some ui structure
+ // and all actual controllers can only be a member of one ui structure
+ bool *pControllerHandled = ( bool * )_alloca( g_numflexcontrollers * sizeof( bool ) );
+ memset( pControllerHandled, 0, g_numflexcontrollers * sizeof( bool ) );
+
+ for ( j = 0; j < g_numflexcontrollers; ++j )
+ {
+ // Don't handle controls twice
+ if ( pControllerHandled[ j ] )
+ continue;
+
+ const s_flexcontroller_t &flexcontroller = g_flexcontroller[ j ];
+
+ bool found = false;
+
+ // See if this controller is in the remap table
+ for ( k = 0; k < g_FlexControllerRemap.Count(); ++k )
+ {
+ s_flexcontrollerremap_t &remap = g_FlexControllerRemap[ k ];
+ if ( j == remap.m_Index || j == remap.m_LeftIndex || j == remap.m_RightIndex || j == remap.m_MultiIndex )
+ {
+ AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, remap.m_Name );
+
+ pFlexControllerUI->stereo = remap.m_bIsStereo;
+ if ( pFlexControllerUI->stereo )
+ {
+ Assert( !pControllerHandled[ remap.m_LeftIndex ] );
+ pFlexControllerUI->szindex0 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ remap.m_LeftIndex * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ remap.m_LeftIndex ] = true;
+
+ Assert( !pControllerHandled[ remap.m_RightIndex ] );
+ pFlexControllerUI->szindex1 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ remap.m_RightIndex * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ remap.m_RightIndex ] = true;
+ }
+ else
+ {
+ Assert( !pControllerHandled[ remap.m_Index ] );
+ pFlexControllerUI->szindex0 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ remap.m_Index * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ remap.m_Index ] = true;
+ pFlexControllerUI->szindex1 = ( 0 );
+ }
+
+ pFlexControllerUI->remaptype = remap.m_RemapType;
+ if ( pFlexControllerUI->remaptype == FLEXCONTROLLER_REMAP_NWAY || pFlexControllerUI->remaptype == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ Assert( remap.m_MultiIndex != -1 );
+ Assert( !pControllerHandled[ remap.m_MultiIndex ] );
+ pFlexControllerUI->szindex2 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ remap.m_MultiIndex * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ remap.m_MultiIndex ] = true;
+ }
+ else
+ {
+ pFlexControllerUI->szindex2 = 0;
+ }
+
+ found = true;
+ break;
+ }
+ }
+
+ if ( !found )
+ {
+ pFlexControllerUI->remaptype = FLEXCONTROLLER_REMAP_PASSTHRU;
+ pFlexControllerUI->szindex2 = 0; // Unused in this case
+
+ if ( j < g_numflexcontrollers - 1 &&
+ StringAfterPrefixCaseSensitive( flexcontroller.name, "right_" ) &&
+ StringAfterPrefixCaseSensitive( g_flexcontroller[ j + 1 ].name, "left_" ) &&
+ !Q_strcmp( StringAfterPrefixCaseSensitive( flexcontroller.name, "right_" ), StringAfterPrefixCaseSensitive( g_flexcontroller[ j + 1 ].name, "left_" ) ) )
+ {
+ AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name + 6 );
+
+ pFlexControllerUI->stereo = true;
+
+ Assert( !pControllerHandled[ j + 1 ] );
+ pFlexControllerUI->szindex0 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ ( j + 1 ) * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ j + 1 ] = true;
+
+ Assert( !pControllerHandled[ j ] );
+ pFlexControllerUI->szindex1 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ j * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ j ] = true;
+ }
+ else if ( j > 0 &&
+ StringAfterPrefixCaseSensitive( flexcontroller.name, "left_" ) &&
+ StringAfterPrefixCaseSensitive( g_flexcontroller[ j - 1 ].name, "right_" ) &&
+ !Q_strcmp( StringAfterPrefixCaseSensitive( flexcontroller.name, "left_" ), StringAfterPrefixCaseSensitive( g_flexcontroller[ j - 1 ].name, "right_" ) ) )
+ {
+ AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name + 5 );
+
+ pFlexControllerUI->stereo = true;
+
+ Assert( !pControllerHandled[ j ] );
+ pFlexControllerUI->szindex0 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ j * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ j ] = true;
+
+ Assert( !pControllerHandled[ j - 1 ] );
+ pFlexControllerUI->szindex1 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ ( j - 1 ) * sizeof( mstudioflexcontroller_t ) );
+ pControllerHandled[ j - 1 ] = true;
+ }
+ else
+ {
+ AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name );
+ pFlexControllerUI->stereo = false;
+ pFlexControllerUI->szindex0 = (
+ phdr->flexcontrollerindex - int( pData - pStart ) +
+ j * sizeof( mstudioflexcontroller_t ) );
+ pFlexControllerUI->szindex1 = 0; // Unused in this case
+ pControllerHandled[ j ] = true;
+ }
+ }
+
+ phdr->numflexcontrollerui++;
+ pData += sizeof( mstudioflexcontrollerui_t );
+ ++pFlexControllerUI;
+ }
+ ALIGN4( pData );
+
+#ifdef _DEBUG
+ for ( j = 0; j < g_numflexcontrollers; ++j )
+ {
+ Assert( pControllerHandled[ j ] );
+ }
+#endif // _DEBUG
+
+ // write ik chains
+ mstudioikchain_t *pikchain = (mstudioikchain_t *)pData;
+ phdr->numikchains = g_numikchains;
+ phdr->ikchainindex = pData - pStart;
+ pData += g_numikchains * sizeof( mstudioikchain_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numikchains; j++)
+ {
+ AddToStringTable( pikchain, &pikchain->sznameindex, g_ikchain[j].name );
+ pikchain->numlinks = g_ikchain[j].numlinks;
+
+ mstudioiklink_t *piklink = (mstudioiklink_t *)pData;
+ pikchain->linkindex = (pData - (byte *)pikchain);
+ pData += pikchain->numlinks * sizeof( mstudioiklink_t );
+
+ for (i = 0; i < pikchain->numlinks; i++)
+ {
+ piklink[i].bone = g_ikchain[j].link[i].bone;
+ piklink[i].kneeDir = g_ikchain[j].link[i].kneeDir;
+ }
+
+ pikchain++;
+ }
+
+ // save autoplay locks
+ mstudioiklock_t *piklock = (mstudioiklock_t *)pData;
+ phdr->numlocalikautoplaylocks = g_numikautoplaylocks;
+ phdr->localikautoplaylockindex = pData - pStart;
+ pData += g_numikautoplaylocks * sizeof( mstudioiklock_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numikautoplaylocks; j++)
+ {
+ piklock->chain = g_ikautoplaylock[j].chain;
+ piklock->flPosWeight = g_ikautoplaylock[j].flPosWeight;
+ piklock->flLocalQWeight = g_ikautoplaylock[j].flLocalQWeight;
+ piklock++;
+ }
+
+ // save mouth info
+ mstudiomouth_t *pmouth = (mstudiomouth_t *)pData;
+ phdr->nummouths = g_nummouths;
+ phdr->mouthindex = pData - pStart;
+ pData += g_nummouths * sizeof( mstudiomouth_t );
+ ALIGN4( pData );
+
+ for (i = 0; i < g_nummouths; i++) {
+ pmouth[i].bone = g_mouth[i].bone;
+ VectorCopy( g_mouth[i].forward, pmouth[i].forward );
+ pmouth[i].flexdesc = g_mouth[i].flexdesc;
+ }
+
+ // save pose parameters
+ mstudioposeparamdesc_t *ppose = (mstudioposeparamdesc_t *)pData;
+ phdr->numlocalposeparameters = g_numposeparameters;
+ phdr->localposeparamindex = pData - pStart;
+ pData += g_numposeparameters * sizeof( mstudioposeparamdesc_t );
+ ALIGN4( pData );
+
+ for (i = 0; i < g_numposeparameters; i++)
+ {
+ AddToStringTable( &ppose[i], &ppose[i].sznameindex, g_pose[i].name );
+ ppose[i].start = g_pose[i].min;
+ ppose[i].end = g_pose[i].max;
+ ppose[i].flags = g_pose[i].flags;
+ ppose[i].loop = g_pose[i].loop;
+ }
+
+ if( !g_quiet )
+ {
+ printf("ik/pose %7d bytes\n", (int)(pData - cur) );
+ }
+ cur = (int)pData;
+
+ const float flVertAnimFixedPointScale = ComputeVertAnimFixedPointScale( phdr );
+
+ // write model
+ for (i = 0; i < g_nummodelsbeforeLOD; i++)
+ {
+ int n = 0;
+
+ byte *pModelStart = (byte *)(&pmodel[i]);
+
+ strcpy( pmodel[i].name, g_model[i]->filename );
+ // AddToStringTable( &pmodel[i], &pmodel[i].sznameindex, g_model[i]->filename );
+
+ // pmodel[i].mrmbias = g_model[i]->mrmbias;
+ // pmodel[i].minresolution = g_model[i]->minresolution;
+ // pmodel[i].maxresolution = g_model[i]->maxresolution;
+
+ // save bbox info
+
+ psource = g_model[i]->source;
+ s_loddata_t *pLodData = g_model[i]->m_pLodData;
+
+ // save mesh info
+ if (pLodData)
+ {
+ pmodel[i].numvertices = pLodData->numvertices;
+ }
+ else
+ {
+ // empty model
+ pmodel[i].numvertices = 0;
+ }
+
+ if ( pmodel[i].numvertices >= MAXSTUDIOVERTS )
+ {
+ // We have to check this here so that we don't screw up decal
+ // vert caching in the runtime.
+ MdlError( "Too many verts in model. (%d verts, MAXSTUDIOVERTS==%d)\n",
+ pmodel[i].numvertices, ( int )MAXSTUDIOVERTS );
+ }
+
+ mstudiomesh_t *pmesh = (mstudiomesh_t *)pData;
+ pmodel[i].meshindex = (pData - pModelStart);
+ pData += psource->nummeshes * sizeof( mstudiomesh_t );
+ ALIGN4( pData );
+
+ pmodel[i].nummeshes = psource->nummeshes;
+ for (m = 0; m < pmodel[i].nummeshes; m++)
+ {
+ n = psource->meshindex[m];
+
+ pmesh[m].material = n;
+ pmesh[m].modelindex = (byte *)&pmodel[i] - (byte *)&pmesh[m];
+ pmesh[m].numvertices = pLodData->mesh[n].numvertices;
+ pmesh[m].vertexoffset = pLodData->mesh[n].vertexoffset;
+ }
+
+ // set expected base offsets to external data
+ ALIGN16( externalVertexIndex );
+ pmodel[i].vertexindex = (int)externalVertexIndex;
+ externalVertexIndex += pmodel[i].numvertices * sizeof(mstudiovertex_t);
+
+ // set expected base offsets to external data
+ ALIGN4( externalTangentsIndex );
+ pmodel[i].tangentsindex = (int)externalTangentsIndex;
+ externalTangentsIndex += pmodel[i].numvertices * sizeof( Vector4D );
+
+ cur = (int)pData;
+
+ // save eyeballs
+ mstudioeyeball_t *peyeball;
+ peyeball = (mstudioeyeball_t *)pData;
+ pmodel[i].numeyeballs = g_model[i]->numeyeballs;
+ pmodel[i].eyeballindex = pData - pModelStart;
+ pData += g_model[i]->numeyeballs * sizeof( mstudioeyeball_t );
+
+ ALIGN4( pData );
+ for (j = 0; j < g_model[i]->numeyeballs; j++)
+ {
+ k = g_model[i]->eyeball[j].mesh;
+ pmesh[k].materialtype = 1; // FIXME: tag custom material
+ pmesh[k].materialparam = j; // FIXME: tag custom material
+
+ peyeball[j].bone = g_model[i]->eyeball[j].bone;
+ VectorCopy( g_model[i]->eyeball[j].org, peyeball[j].org );
+ peyeball[j].zoffset = g_model[i]->eyeball[j].zoffset;
+ peyeball[j].radius = g_model[i]->eyeball[j].radius;
+ VectorCopy( g_model[i]->eyeball[j].up, peyeball[j].up );
+ VectorCopy( g_model[i]->eyeball[j].forward, peyeball[j].forward );
+ peyeball[j].iris_scale = g_model[i]->eyeball[j].iris_scale;
+
+ for (k = 0; k < 3; k++)
+ {
+ peyeball[j].upperflexdesc[k] = g_model[i]->eyeball[j].upperflexdesc[k];
+ peyeball[j].lowerflexdesc[k] = g_model[i]->eyeball[j].lowerflexdesc[k];
+ peyeball[j].uppertarget[k] = g_model[i]->eyeball[j].uppertarget[k];
+ peyeball[j].lowertarget[k] = g_model[i]->eyeball[j].lowertarget[k];
+ }
+
+ peyeball[j].upperlidflexdesc = g_model[i]->eyeball[j].upperlidflexdesc;
+ peyeball[j].lowerlidflexdesc = g_model[i]->eyeball[j].lowerlidflexdesc;
+ }
+
+ if ( !g_quiet )
+ {
+ printf("eyeballs %7d bytes (%d eyeballs)\n", (int)(pData - cur), g_model[i]->numeyeballs );
+ }
+
+ // move flexes into individual meshes
+ cur = (int)pData;
+ for (m = 0; m < pmodel[i].nummeshes; m++)
+ {
+ int numflexkeys[MAXSTUDIOFLEXKEYS];
+ pmesh[m].numflexes = 0;
+
+ // initialize array
+ for (j = 0; j < g_numflexkeys; j++)
+ {
+ numflexkeys[j] = 0;
+ }
+
+ // count flex instances per mesh
+ for (j = 0; j < g_numflexkeys; j++)
+ {
+ if (g_flexkey[j].imodel == i)
+ {
+ for (k = 0; k < g_flexkey[j].numvanims; k++)
+ {
+ n = g_flexkey[j].vanim[k].vertex - pmesh[m].vertexoffset;
+ if (n >= 0 && n < pmesh[m].numvertices)
+ {
+ if (numflexkeys[j]++ == 0)
+ {
+ pmesh[m].numflexes++;
+ }
+ }
+ }
+ }
+ }
+
+ if (pmesh[m].numflexes)
+ {
+ pmesh[m].flexindex = ( pData - (byte *)&pmesh[m] );
+ mstudioflex_t *pflex = (mstudioflex_t *)pData;
+ pData += pmesh[m].numflexes * sizeof( mstudioflex_t );
+ ALIGN4( pData );
+
+ for (j = 0; j < g_numflexkeys; j++)
+ {
+ if (!numflexkeys[j])
+ continue;
+
+ pflex->flexdesc = g_flexkey[j].flexdesc;
+ pflex->target0 = g_flexkey[j].target0;
+ pflex->target1 = g_flexkey[j].target1;
+ pflex->target2 = g_flexkey[j].target2;
+ pflex->target3 = g_flexkey[j].target3;
+ pflex->numverts = numflexkeys[j];
+ pflex->vertindex = (pData - (byte *)pflex);
+ pflex->flexpair = g_flexkey[j].flexpair;
+ pflex->vertanimtype = g_flexkey[j].vanimtype;
+
+ // printf("%d %d %s : %f %f %f %f\n", j, g_flexkey[j].flexdesc, g_flexdesc[g_flexkey[j].flexdesc].FACS, g_flexkey[j].target0, g_flexkey[j].target1, g_flexkey[j].target2, g_flexkey[j].target3 );
+ // if (j < 9) printf("%d %d %s : %d (%d) %f\n", j, g_flexkey[j].flexdesc, g_flexdesc[g_flexkey[j].flexdesc].FACS, g_flexkey[j].numvanims, pflex->numverts, g_flexkey[j].target );
+
+ // printf("%d %d : %d %f\n", j, g_flexkey[j].flexnum, g_flexkey[j].numvanims, g_flexkey[j].target );
+
+ pvanim = g_flexkey[j].vanim;
+
+ bool bWrinkleVAnim = ( pflex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE );
+ int nVAnimDeltaSize = bWrinkleVAnim ? sizeof(mstudiovertanim_wrinkle_t) : sizeof(mstudiovertanim_t);
+
+ pvertanim = (mstudiovertanim_t *)pData;
+ pData += pflex->numverts * nVAnimDeltaSize;
+ ALIGN4( pData );
+
+ for ( k = 0; k < g_flexkey[j].numvanims; k++ )
+ {
+ n = g_flexkey[j].vanim[k].vertex - pmesh[m].vertexoffset;
+ if ( n >= 0 && n < pmesh[m].numvertices )
+ {
+ pvertanim->index = n;
+ pvertanim->speed = 255.0F*pvanim->speed;
+ pvertanim->side = 255.0F*pvanim->side;
+
+ pvertanim->SetDeltaFloat( pvanim->pos );
+ pvertanim->SetNDeltaFloat( pvanim->normal );
+
+ if ( bWrinkleVAnim )
+ {
+ ( (mstudiovertanim_wrinkle_t*)pvertanim )->SetWrinkleFixed( pvanim->wrinkle, flVertAnimFixedPointScale );
+ }
+
+ pvertanim = (mstudiovertanim_t*)( (byte*)pvertanim + nVAnimDeltaSize );
+
+ /*
+ if ((tmp - pvanim->pos).Length() > 0.1)
+ {
+ pvertanim->delta.x = pvanim->pos.x;
+ printf("%f %f %f : %f %f %f\n",
+ pvanim->pos[0], pvanim->pos[1], pvanim->pos[2],
+ tmp.x, tmp.y, tmp.z );
+ }
+ */
+ // if (j < 9) printf("%d %.2f %.2f %.2f\n", n, pvanim->pos[0], pvanim->pos[1], pvanim->pos[2] );
+ }
+ // printf("%d %.2f %.2f %.2f\n", pvanim->vertex, pvanim->pos[0], pvanim->pos[1], pvanim->pos[2] );
+ pvanim++;
+ }
+ pflex++;
+ }
+ }
+ }
+
+ if( !g_quiet )
+ {
+ printf("flexes %7d bytes (%d flexes)\n", (int)(pData - cur), g_numflexkeys );
+ }
+ cur = (int)pData;
+ }
+
+
+ ALIGN4( pData );
+
+ mstudiomodelgroup_t *pincludemodel = (mstudiomodelgroup_t *)pData;
+ phdr->numincludemodels = g_numincludemodels;
+ phdr->includemodelindex = pData - pStart;
+ pData += g_numincludemodels * sizeof( mstudiomodelgroup_t );
+
+ for (i = 0; i < g_numincludemodels; i++)
+ {
+ AddToStringTable( pincludemodel, &pincludemodel->sznameindex, g_includemodel[i].name );
+ pincludemodel++;
+ }
+
+ // save animblock group info
+ mstudioanimblock_t *panimblock = (mstudioanimblock_t *)pData;
+ phdr->numanimblocks = g_numanimblocks;
+ phdr->animblockindex = pData - pStart;
+ pData += phdr->numanimblocks * sizeof( mstudioanimblock_t );
+ ALIGN4( pData );
+
+ for (i = 1; i < g_numanimblocks; i++)
+ {
+ panimblock[i].datastart = g_animblock[i].start - pBlockStart;
+ panimblock[i].dataend = g_animblock[i].end - pBlockStart;
+ // printf("block %d : %x %x (%d)\n", i, panimblock[i].datastart, panimblock[i].dataend, panimblock[i].dataend - panimblock[i].datastart );
+ }
+ AddToStringTable( phdr, &phdr->szanimblocknameindex, g_animblockname );
+}
+
+static void AssignMeshIDs( studiohdr_t *pStudioHdr )
+{
+ int i;
+ int j;
+ int m;
+ int numMeshes;
+ mstudiobodyparts_t *pStudioBodyPart;
+ mstudiomodel_t *pStudioModel;
+ mstudiomesh_t *pStudioMesh;
+
+ numMeshes = 0;
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ for (m=0; m<pStudioModel->nummeshes; m++)
+ {
+ // get each mesh
+ pStudioMesh = pStudioModel->pMesh(m);
+ pStudioMesh->meshid = numMeshes + m;
+ }
+ numMeshes += pStudioModel->nummeshes;
+ }
+ }
+}
+
+
+void LoadMaterials( studiohdr_t *phdr )
+{
+ int i, j;
+
+ // get index of each material
+ if( phdr->textureindex != 0 )
+ {
+ for( i = 0; i < phdr->numtextures; i++ )
+ {
+ char szPath[256];
+ IMaterial *pMaterial = NULL;
+ // search through all specified directories until a valid material is found
+ for( j = 0; j < phdr->numcdtextures && IsErrorMaterial( pMaterial ); j++ )
+ {
+ strcpy( szPath, phdr->pCdtexture( j ) );
+ strcat( szPath, phdr->pTexture( i )->pszName( ) );
+
+ pMaterial = g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_OTHER, false );
+ }
+ if( IsErrorMaterial( pMaterial ) && !g_quiet )
+ {
+ // hack - if it isn't found, go through the motions of looking for it again
+ // so that the materialsystem will give an error.
+ for( j = 0; j < phdr->numcdtextures; j++ )
+ {
+ strcpy( szPath, phdr->pCdtexture( j ) );
+ strcat( szPath, phdr->pTexture( i )->pszName( ) );
+ g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_OTHER, true );
+ }
+ }
+
+ phdr->pTexture( i )->material = pMaterial;
+
+ // FIXME: hack, needs proper client side material system interface
+ bool found = false;
+ IMaterialVar *clientShaderVar = pMaterial->FindVar( "$clientShader", &found, false );
+ if( found )
+ {
+ if (stricmp( clientShaderVar->GetStringValue(), "MouthShader") == 0)
+ {
+ phdr->pTexture( i )->flags = 1;
+ }
+ phdr->pTexture( i )->used = 1;
+ }
+ }
+ }
+}
+
+
+void WriteKeyValues( studiohdr_t *phdr, CUtlVector< char > *pKeyValue )
+{
+ phdr->keyvalueindex = (pData - pStart);
+ phdr->keyvaluesize = KeyValueTextSize( pKeyValue );
+ if (phdr->keyvaluesize)
+ {
+ memcpy( pData, KeyValueText( pKeyValue ), phdr->keyvaluesize );
+
+ // Add space for a null terminator
+ pData[phdr->keyvaluesize] = 0;
+ ++phdr->keyvaluesize;
+
+ pData += phdr->keyvaluesize * sizeof( char );
+ }
+ ALIGN4( pData );
+}
+
+
+void WriteSeqKeyValues( mstudioseqdesc_t *pseqdesc, CUtlVector< char > *pKeyValue )
+{
+ pseqdesc->keyvalueindex = (pData - (byte *)pseqdesc);
+ pseqdesc->keyvaluesize = KeyValueTextSize( pKeyValue );
+ if (pseqdesc->keyvaluesize)
+ {
+ memcpy( pData, KeyValueText( pKeyValue ), pseqdesc->keyvaluesize );
+
+ // Add space for a null terminator
+ pData[pseqdesc->keyvaluesize] = 0;
+ ++pseqdesc->keyvaluesize;
+
+ pData += pseqdesc->keyvaluesize * sizeof( char );
+ }
+ ALIGN4( pData );
+}
+
+
+void EnsureFileDirectoryExists( const char *pFilename )
+{
+ char dirName[MAX_PATH];
+ Q_strncpy( dirName, pFilename, sizeof( dirName ) );
+ Q_FixSlashes( dirName );
+ char *pLastSlash = strrchr( dirName, CORRECT_PATH_SEPARATOR );
+ if ( pLastSlash )
+ {
+ *pLastSlash = 0;
+
+ if ( _access( dirName, 0 ) != 0 )
+ {
+ char cmdLine[512];
+ Q_snprintf( cmdLine, sizeof( cmdLine ), "md \"%s\"", dirName );
+ system( cmdLine );
+ }
+ }
+}
+
+
+void WriteModelFiles(void)
+{
+ FileHandle_t modelouthandle = 0;
+ FileHandle_t blockouthandle = 0;
+ CPlainAutoPtr< CP4File > spFileBlockOut, spFileModelOut;
+ int total = 0;
+ int i;
+ char filename[MAX_PATH];
+ studiohdr_t *phdr;
+ studiohdr_t *pblockhdr = 0;
+
+ pStart = (byte *)kalloc( 1, FILEBUFFER );
+
+ pBlockData = NULL;
+ pBlockStart = NULL;
+
+ Q_StripExtension( outname, outname, sizeof( outname ) );
+
+ if (g_animblocksize != 0)
+ {
+ // write the non-default g_sequence group data to separate files
+ sprintf( g_animblockname, "models/%s.ani", outname );
+
+ V_strcpy_safe( filename, gamedir );
+ V_strcat_safe( filename, g_animblockname );
+
+ EnsureFileDirectoryExists( filename );
+
+ if (!g_bVerifyOnly)
+ {
+ spFileBlockOut.Attach( g_p4factory->AccessFile( filename ) );
+ spFileBlockOut->Edit();
+
+ // Create the directory hierarchy for the ANI
+ char parentdir[MAX_PATH];
+ V_strcpy_safe( parentdir, filename );
+ V_StripFilename( parentdir );
+ g_pFullFileSystem->CreateDirHierarchy( parentdir );
+
+ blockouthandle = SafeOpenWrite( filename );
+ }
+
+ pBlockStart = (byte *)kalloc( 1, FILEBUFFER );
+ pBlockData = pBlockStart;
+
+ pblockhdr = (studiohdr_t *)pBlockData;
+ pblockhdr->id = IDSTUDIOANIMGROUPHEADER;
+ pblockhdr->version = STUDIO_VERSION;
+
+ pBlockData += sizeof( *pblockhdr );
+ }
+
+//
+// write the g_model output file
+//
+ phdr = (studiohdr_t *)pStart;
+
+ phdr->id = IDSTUDIOHEADER;
+ phdr->version = STUDIO_VERSION;
+
+ V_strcat_safe (outname, ".mdl");
+
+ // strcpy( outname, ExpandPath( outname ) );
+
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+
+
+ // Create the directory.
+ EnsureFileDirectoryExists( filename );
+
+
+ if( !g_quiet )
+ {
+ printf ("---------------------\n");
+ printf ("writing %s:\n", filename);
+ }
+
+ LoadPreexistingSequenceOrder( filename );
+
+ if (!g_bVerifyOnly)
+ {
+ spFileModelOut.Attach( g_p4factory->AccessFile( filename ) );
+ spFileModelOut->Edit();
+
+ // Create the directory hierarchy for the MDL
+ char parentdir[MAX_PATH];
+ V_strcpy_safe( parentdir, filename );
+ V_StripFilename( parentdir );
+ g_pFullFileSystem->CreateDirHierarchy( parentdir );
+
+ modelouthandle = SafeOpenWrite (filename);
+ }
+
+ phdr->eyeposition = eyeposition;
+ phdr->illumposition = illumposition;
+
+ if ( !g_wrotebbox && g_sequence.Count() > 0)
+ {
+ VectorCopy( g_sequence[0].bmin, bbox[0] );
+ VectorCopy( g_sequence[0].bmax, bbox[1] );
+ CollisionModel_ExpandBBox( bbox[0], bbox[1] );
+ VectorCopy( bbox[0], g_sequence[0].bmin );
+ VectorCopy( bbox[1], g_sequence[0].bmax );
+ }
+ if ( !g_wrotecbox )
+ {
+ // no default clipping box, just use per-sequence box
+ VectorCopy( vec3_origin, cbox[0] );
+ VectorCopy( vec3_origin, cbox[1] );
+ }
+
+ phdr->hull_min = bbox[0];
+ phdr->hull_max = bbox[1];
+ phdr->view_bbmin = cbox[0];
+ phdr->view_bbmax = cbox[1];
+
+ phdr->flags = gflags;
+ phdr->mass = GetCollisionModelMass();
+ phdr->constdirectionallightdot = g_constdirectionalightdot;
+
+ if ( g_numAllowedRootLODs > 0 )
+ {
+ phdr->numAllowedRootLODs = g_numAllowedRootLODs;
+ }
+
+ pData = (byte *)phdr + sizeof( studiohdr_t );
+
+ // FIXME: Remove when we up the model version
+ phdr->studiohdr2index = ( pData - pStart );
+ studiohdr2_t* phdr2 = (studiohdr2_t*)pData;
+ memset( phdr2, 0, sizeof(studiohdr2_t) );
+ pData = (byte*)phdr2 + sizeof(studiohdr2_t);
+
+ phdr2->illumpositionattachmentindex = g_illumpositionattachment;
+ phdr2->flMaxEyeDeflection = g_flMaxEyeDeflection;
+
+ BeginStringTable( );
+
+ // Copy the full path for compatibility with older programs
+ //V_strcpy_safe( phdr->name, V_UnqualifiedFileName( outname ) );
+ V_strcpy_safe( phdr->name, outname );
+ AddToStringTable( phdr2, &phdr2->sznameindex, outname );
+
+ WriteBoneInfo( phdr );
+ if( !g_quiet )
+ {
+ printf("bones %7d bytes (%d)\n", pData - pStart - total, g_numbones );
+ }
+ total = pData - pStart;
+
+ pData = WriteAnimations( pData, pStart, phdr );
+ if( !g_quiet )
+ {
+ printf("animations %7d bytes (%d anims) (%d frames) [%d:%02d]\n", pData - pStart - total, g_numani, totalframes, (int)totalseconds / 60, (int)totalseconds % 60 );
+ }
+ total = pData - pStart;
+
+ WriteSequenceInfo( phdr );
+ if( !g_quiet )
+ {
+ printf("sequences %7d bytes (%d seq) \n", pData - pStart - total, g_sequence.Count() );
+ }
+ total = pData - pStart;
+
+ WriteModel( phdr );
+ /*
+ if( !g_quiet )
+ {
+ printf("models %7d bytes\n", pData - pStart - total );
+ }
+ */
+ total = pData - pStart;
+
+ WriteTextures( phdr );
+ if( !g_quiet )
+ {
+ printf("textures %7d bytes\n", pData - pStart - total );
+ }
+ total = pData - pStart;
+
+ WriteKeyValues( phdr, &g_KeyValueText );
+ if( !g_quiet )
+ {
+ printf("keyvalues %7d bytes\n", pData - pStart - total );
+ }
+ total = pData - pStart;
+
+ WriteBoneTransforms( phdr2, phdr->pBone( 0 ) );
+ if( !g_quiet )
+ {
+ printf("bone transforms %7d bytes\n", pData - pStart - total );
+ }
+ total = pData - pStart;
+ if ( total > FILEBUFFER )
+ {
+ MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total );
+ }
+
+ WriteBoneFlexDrivers( phdr2 );
+ if ( !g_quiet )
+ {
+ printf("bone flex driver %7d bytes\n", pData - pStart - total );
+ }
+ total = pData - pStart;
+ if ( total > FILEBUFFER )
+ {
+ MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total );
+ }
+
+ pData = WriteStringTable( pData );
+
+ total = pData - pStart;
+ if ( total > FILEBUFFER )
+ {
+ MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total );
+ }
+
+ phdr->checksum = 0;
+ for (i = 0; i < total; i += 4)
+ {
+ // TODO: does this need something more than a simple shift left and add checksum?
+ phdr->checksum = (phdr->checksum << 1) + ((phdr->checksum & 0x8000000) ? 1 : 0) + *((long *)(pStart + i));
+ }
+
+ if (g_bVerifyOnly)
+ return;
+
+ CollisionModel_Write( phdr->checksum );
+
+ if( !g_quiet )
+ {
+ printf("collision %7d bytes\n", pData - pStart - total );
+ }
+
+ AssignMeshIDs( phdr );
+
+ phdr->length = pData - pStart;
+ if( !g_quiet )
+ {
+ printf("total %7d\n", phdr->length );
+ }
+ if ( phdr->length > FILEBUFFER )
+ {
+ MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total );
+ }
+
+ // Load materials for this model via the material system so that the
+ // optimizer can ask questions about the materials.
+ LoadMaterials( phdr );
+
+ SafeWrite( modelouthandle, pStart, phdr->length );
+
+ g_pFileSystem->Close(modelouthandle);
+ if ( spFileModelOut.IsValid() ) spFileModelOut->Add();
+
+ if (pBlockStart)
+ {
+ pblockhdr->length = pBlockData - pBlockStart;
+
+ if ( g_bX360 )
+ {
+ // Before writing this .ani, write the byteswapped version
+ void *pOutBase = kalloc(1, pblockhdr->length + BYTESWAP_ALIGNMENT_PADDING);
+ int finalSize = StudioByteSwap::ByteswapANI( phdr, pOutBase, pBlockStart, pblockhdr->length );
+ if ( finalSize == 0 )
+ {
+ MdlError("Aborted ANI byteswap on '%s':\n", g_animblockname);
+ }
+
+ char outname[ MAX_PATH ];
+ Q_StripExtension( g_animblockname, outname, sizeof( outname ) );
+ Q_strcat( outname, ".360.ani", sizeof( outname ) );
+
+ {
+ CP4AutoEditAddFile autop4( outname );
+ SaveFile( outname, pOutBase, finalSize );
+ }
+ }
+
+ SafeWrite( blockouthandle, pBlockStart, pblockhdr->length );
+ g_pFileSystem->Close( blockouthandle );
+ if ( spFileBlockOut.IsValid() ) spFileBlockOut->Add();
+
+
+ if ( !g_quiet )
+ {
+ printf ("---------------------\n");
+ printf("writing %s:\n", g_animblockname);
+ printf("blocks %7d\n", g_numanimblocks );
+ printf("total %7d\n", pblockhdr->length );
+ }
+ }
+
+ if (phdr->numbodyparts != 0)
+ {
+ // vertices have become an external peer data store
+ // write now prior to impending vertex access from any further code
+ // vertex accessors hide shifting vertex data
+ WriteVertices( phdr );
+
+ #ifdef _DEBUG
+ int bodyPartID;
+ for( bodyPartID = 0; bodyPartID < phdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = phdr->pBodypart( bodyPartID );
+ int modelID;
+ for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pModel = pBodyPart->pModel( modelID );
+ const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ int vertID;
+ for( vertID = 0; vertID < pModel->numvertices; vertID++ )
+ {
+ Vector4D *pTangentS = vertData->TangentS( vertID );
+ Assert( pTangentS->w == -1.0f || pTangentS->w == 1.0f );
+ }
+ }
+ }
+ #endif
+
+ if ( !g_StudioMdlCheckUVCmd.CheckUVs( g_source, g_numsources ) )
+ {
+ MdlError( "UV checks failed\n" );
+ }
+
+ OptimizedModel::WriteOptimizedFiles( phdr, g_bodypart );
+
+ // now have external finalized vtx (windings) and vvd (vertexes)
+ // re-open files, sort vertexes, perform fixups, and rewrite
+ // purposely isolated as a post process for stability
+ if (!FixupToSortedLODVertexes( phdr ))
+ {
+ MdlError("Aborted vertex sort fixup on '%s':\n", filename);
+ }
+
+ if (!Clamp_RootLOD( phdr ))
+ {
+ MdlError("Aborted root lod shift '%s':\n", filename);
+ }
+ }
+
+ if ( g_bX360 )
+ {
+ // now all files have been finalized and fixed up.
+ // re-open the files once more and swap all little-endian
+ // data to big-endian format to produce Xbox360 files.
+ WriteAllSwappedFiles( filename );
+ }
+
+ // NOTE! If you don't want to go through the effort of loading studiorender for perf reasons,
+ // make sure spewFlags ends up being zero.
+ unsigned int spewFlags = SPEWPERFSTATS_SHOWSTUDIORENDERWARNINGS;
+
+ if ( g_bPerf )
+ {
+ spewFlags |= SPEWPERFSTATS_SHOWPERF;
+ }
+ if( spewFlags )
+ {
+ SpewPerfStats( phdr, filename, spewFlags );
+ }
+}
+
+const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData )
+{
+ static vertexFileHeader_t *pVertexHdr;
+ char filename[MAX_PATH];
+
+ Assert( pModelData == NULL );
+
+ if (pVertexHdr)
+ {
+ // studiomdl is a single model process, can simply persist data in static
+ goto hasData;
+ }
+
+ // load and persist the vertex file
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+ Q_StripExtension( filename, filename, sizeof( filename ) );
+ V_strcat_safe( filename, ".vvd" );
+
+ LoadFile(filename, (void**)&pVertexHdr);
+
+ // check id
+ if (pVertexHdr->id != MODEL_VERTEX_FILE_ID)
+ {
+ MdlError("Error Vertex File: '%s' (id %d should be %d)\n", filename, pVertexHdr->id, MODEL_VERTEX_FILE_ID);
+ }
+
+ // check version
+ if (pVertexHdr->version != MODEL_VERTEX_FILE_VERSION)
+ {
+ MdlError("Error Vertex File: '%s' (version %d should be %d)\n", filename, pVertexHdr->version, MODEL_VERTEX_FILE_VERSION);
+ }
+
+hasData:
+ return pVertexHdr;
+}
+
+typedef struct
+{
+ int meshVertID;
+ int finalMeshVertID;
+ int vertexOffset;
+ int lodFlags;
+} usedVertex_t;
+
+typedef struct
+{
+ int offsets[MAX_NUM_LODS];
+ int numVertexes[MAX_NUM_LODS];
+} lodMeshInfo_t;
+
+typedef struct
+{
+ usedVertex_t *pVertexList;
+ unsigned short *pVertexMap;
+ int numVertexes;
+ lodMeshInfo_t lodMeshInfo;
+} vertexPool_t;
+
+#define ALIGN(b,s) (((unsigned int)(b)+(s)-1)&~((s)-1))
+
+//-----------------------------------------------------------------------------
+// FindVertexOffsets
+//
+// Iterate sorted vertex list to determine mesh starts and counts.
+//-----------------------------------------------------------------------------
+void FindVertexOffsets(int vertexOffset, int offsets[MAX_NUM_LODS], int counts[MAX_NUM_LODS], int numLods, const usedVertex_t *pVertexList, int numVertexes)
+{
+ int lodFlags;
+ int i;
+ int j;
+ int k;
+
+ // vertexOffset uniquely identifies a single mesh's vertexes in lod vertex sorted list
+ // lod vertex list is sorted from lod N..lod 0
+ for (i=numLods-1; i>=0; i--)
+ {
+ offsets[i] = 0;
+ counts[i] = 0;
+
+ lodFlags = (1<<(i+1))-1;
+ for (j=0; j<numVertexes; j++)
+ {
+ // determine start of mesh at desired lod
+ if (pVertexList[j].lodFlags > lodFlags)
+ continue;
+ if (pVertexList[j].vertexOffset != vertexOffset)
+ continue;
+
+ for (k=j; k<numVertexes; k++)
+ {
+ // determine end of mesh at desired lod
+ if (pVertexList[k].vertexOffset != vertexOffset)
+ break;
+ if (!(pVertexList[k].lodFlags & (1<<i)))
+ break;
+ }
+
+ offsets[i] = j;
+ counts[i] = k-j;
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// _CompareUsedVertexes
+//
+// qsort callback
+//-----------------------------------------------------------------------------
+static int _CompareUsedVertexes(const void *a, const void *b)
+{
+ usedVertex_t *pVertexA;
+ usedVertex_t *pVertexB;
+ int sort;
+ int lodA;
+ int lodB;
+
+ pVertexA = (usedVertex_t*)a;
+ pVertexB = (usedVertex_t*)b;
+
+ // determine highest (lowest detail) lod
+ // forces grouping into discrete MAX_NUM_LODS sections
+ lodA = Q_log2(pVertexA->lodFlags);
+ lodB = Q_log2(pVertexB->lodFlags);
+
+ // descending sort (LodN..Lod0)
+ sort = lodB-lodA;
+ if (sort)
+ return sort;
+
+ // within same lod, sub sort (ascending) by mesh
+ sort = pVertexA->vertexOffset - pVertexB->vertexOffset;
+ if (sort)
+ return sort;
+
+ // within same mesh, sub sort (ascending) by vertex
+ sort = pVertexA->meshVertID - pVertexB->meshVertID;
+ return sort;
+}
+
+//-----------------------------------------------------------------------------
+// BuildSortedVertexList
+//
+// Generates the sorted vertex list. Routine is purposely serial to
+// ensure vertex integrity.
+//-----------------------------------------------------------------------------
+bool BuildSortedVertexList(const studiohdr_t *pStudioHdr, const void *pVtxBuff, vertexPool_t **ppVertexPools, int *pNumVertexPools, usedVertex_t **ppVertexList, int *pNumVertexes)
+{
+ OptimizedModel::FileHeader_t *pVtxHdr;
+ OptimizedModel::BodyPartHeader_t *pBodyPartHdr;
+ OptimizedModel::ModelHeader_t *pModelHdr;
+ OptimizedModel::ModelLODHeader_t *pModelLODHdr;
+ OptimizedModel::MeshHeader_t *pMeshHdr;
+ OptimizedModel::StripGroupHeader_t *pStripGroupHdr;
+ OptimizedModel::Vertex_t *pStripVertex;
+ mstudiobodyparts_t *pStudioBodyPart;
+ mstudiomodel_t *pStudioModel;
+ mstudiomesh_t *pStudioMesh;
+ usedVertex_t *usedVertexes;
+ vertexPool_t *pVertexPools;
+ vertexPool_t *pPool;
+ usedVertex_t *pVertexList;
+ int *pVertexes;
+ unsigned short *pVertexMap;
+ int index;
+ int currLod;
+ int vertexOffset;
+ int i,j,k,m,n,p;
+ int poolStart;
+ int numVertexPools;
+ int numVertexes;
+ int numMeshVertexes;
+ int offsets[MAX_NUM_LODS];
+ int counts[MAX_NUM_LODS];
+ int finalMeshVertID;
+ int baseMeshVertID;
+
+ *ppVertexPools = NULL;
+ *pNumVertexPools = 0;
+ *ppVertexList = NULL;
+ *pNumVertexes = 0;
+
+ pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff;
+
+ // determine number of vertex pools
+ if (pStudioHdr->numbodyparts != pVtxHdr->numBodyParts)
+ return false;
+ numVertexPools = 0;
+ for (i=0; i<pVtxHdr->numBodyParts; i++)
+ {
+ pBodyPartHdr = pVtxHdr->pBodyPart(i);
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ if (pStudioBodyPart->nummodels != pBodyPartHdr->numModels)
+ return false;
+
+ // the model's subordinate lods only reference from a single top level pool
+ // no new verts are created for sub lods
+ // each model's subordinate mesh dictates its own vertex pool
+ for (j=0; j<pBodyPartHdr->numModels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ numVertexPools += pStudioModel->nummeshes;
+ }
+ }
+
+ // allocate pools
+ pVertexPools = (vertexPool_t*)malloc(numVertexPools*sizeof(vertexPool_t));
+ memset(pVertexPools, 0, numVertexPools*sizeof(vertexPool_t));
+
+ // iterate lods, mark referenced indexes
+ numVertexPools = 0;
+ for (i=0; i<pVtxHdr->numBodyParts; i++)
+ {
+ pBodyPartHdr = pVtxHdr->pBodyPart(i);
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+
+ for (j=0; j<pBodyPartHdr->numModels; j++)
+ {
+ pModelHdr = pBodyPartHdr->pModel(j);
+ pStudioModel = pStudioBodyPart->pModel(j);
+
+ // allocate each mesh's vertex list
+ poolStart = numVertexPools;
+ for (k=0; k<pStudioModel->nummeshes; k++)
+ {
+ // track the expected relative offset into a flattened vertex list
+ vertexOffset = 0;
+ for (m=0; m<poolStart+k; m++)
+ vertexOffset += pVertexPools[m].numVertexes;
+
+ pStudioMesh = pStudioModel->pMesh(k);
+ numMeshVertexes = pStudioMesh->numvertices;
+ if (numMeshVertexes)
+ {
+ usedVertexes = (usedVertex_t*)malloc(numMeshVertexes*sizeof(usedVertex_t));
+ pVertexMap = (unsigned short*)malloc(numMeshVertexes*sizeof(unsigned short));
+
+ for (n=0; n<numMeshVertexes; n++)
+ {
+ // setup mapping
+ // due to the hierarchial layout, the vertID's map per mesh's pool
+ // a linear layout of the vertexes requires a unique signature to achieve a remap
+ // the offset and index form a unique signature
+ usedVertexes[n].meshVertID = n;
+ usedVertexes[n].finalMeshVertID = -1;
+ usedVertexes[n].vertexOffset = vertexOffset;
+ usedVertexes[n].lodFlags = 0;
+ pVertexMap[n] = n;
+ }
+
+ pVertexPools[numVertexPools].pVertexList = usedVertexes;
+ pVertexPools[numVertexPools].pVertexMap = pVertexMap;
+ }
+ pVertexPools[numVertexPools].numVertexes = numMeshVertexes;
+ numVertexPools++;
+ }
+
+ // iterate all lods
+ for (currLod=0; currLod<pVtxHdr->numLODs; currLod++)
+ {
+ pModelLODHdr = pModelHdr->pLOD(currLod);
+
+ if (pModelLODHdr->numMeshes != pStudioModel->nummeshes)
+ return false;
+
+ for (k=0; k<pModelLODHdr->numMeshes; k++)
+ {
+ pMeshHdr = pModelLODHdr->pMesh(k);
+ pStudioMesh = pStudioModel->pMesh(k);
+ for (m=0; m<pMeshHdr->numStripGroups; m++)
+ {
+ pStripGroupHdr = pMeshHdr->pStripGroup(m);
+
+ // sanity check the indexes have 100% coverage of the vertexes
+ pVertexes = (int*)malloc(pStripGroupHdr->numVerts*sizeof(int));
+ memset(pVertexes, 0xFF, pStripGroupHdr->numVerts*sizeof(int));
+
+ for (n=0; n<pStripGroupHdr->numIndices; n++)
+ {
+ index = *pStripGroupHdr->pIndex(n);
+ if (index < 0 || index >= pStripGroupHdr->numVerts)
+ return false;
+ pVertexes[index] = index;
+ }
+
+ // sanity check for coverage
+ for (n=0; n<pStripGroupHdr->numVerts; n++)
+ {
+ if (pVertexes[n] != n)
+ return false;
+ }
+
+ free(pVertexes);
+
+ // iterate vertexes
+ pPool = &pVertexPools[poolStart + k];
+ for (n=0; n<pStripGroupHdr->numVerts; n++)
+ {
+ pStripVertex = pStripGroupHdr->pVertex(n);
+ if (pStripVertex->origMeshVertID < 0 || pStripVertex->origMeshVertID >= pPool->numVertexes)
+ return false;
+
+ // arrange binary flags for numerical sorting
+ // the lowest detail lod's verts at the top, the root lod's verts at the bottom
+ pPool->pVertexList[pStripVertex->origMeshVertID].lodFlags |= 1<<currLod;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // flatten the vertex pool hierarchy into a linear sequence
+ numVertexes = 0;
+ for (i=0; i<numVertexPools; i++)
+ numVertexes += pVertexPools[i].numVertexes;
+ pVertexList = (usedVertex_t*)malloc(numVertexes*sizeof(usedVertex_t));
+ numVertexes = 0;
+ for (i=0; i<numVertexPools; i++)
+ {
+ pPool = &pVertexPools[i];
+ for (j=0; j<pPool->numVertexes; j++)
+ {
+ if (!pPool->pVertexList[j].lodFlags)
+ {
+ // found an orphaned vertex that is unreferenced at any lod strip winding
+ // don't know how these occur or who references them
+ // cannot cull the orphaned vertexes, otherwise vertex counts are wrong
+ // every vertex must be remapped
+ // force the vertex to belong to the lowest lod
+ // lod flags must be nonzero for proper sorted runs
+ pPool->pVertexList[j].lodFlags = 1<<(pVtxHdr->numLODs-1);
+ }
+ }
+
+ memcpy(&pVertexList[numVertexes], pPool->pVertexList, pPool->numVertexes*sizeof(usedVertex_t));
+ numVertexes += pPool->numVertexes;
+ }
+
+ // sort the vertexes based on lod flags
+ // the sort dictates the linear sequencing of the .vvd data file
+ // the vtx file indexes get remapped to the new sort order
+ qsort(pVertexList, numVertexes, sizeof(usedVertex_t), _CompareUsedVertexes);
+
+ // build a mapping table from mesh relative indexes to the flat lod sorted array
+ vertexOffset = 0;
+ for (i=0; i<numVertexPools; i++)
+ {
+ pPool = &pVertexPools[i];
+ for (j=0; j<pPool->numVertexes; j++)
+ {
+ // scan flattened sorted vertexes
+ for (k=0; k<numVertexes; k++)
+ {
+ if (pVertexList[k].vertexOffset == vertexOffset && pVertexList[k].meshVertID == j)
+ break;
+ }
+ pPool->pVertexMap[j] = k;
+ }
+ vertexOffset += pPool->numVertexes;
+ }
+
+ // build offsets and counts that identifies mesh's distribution across lods
+ // calc final fixed vertex location if vertexes were gathered to mesh order from lod sorted list
+ finalMeshVertID = 0;
+ poolStart = 0;
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ for (m=0; m<pStudioModel->nummeshes; m++)
+ {
+ // track the expected offset into linear vertexes
+ vertexOffset = 0;
+ for (n=0; n<poolStart+m; n++)
+ vertexOffset += pVertexPools[n].numVertexes;
+
+ // vertexOffset works as unique key to identify vertexes for a specific mesh
+ // a mesh's verts are distributed, but guaranteed sequential in the lod sorted vertex list
+ // determine base index and offset and run length for target mesh for all lod levels
+ FindVertexOffsets(vertexOffset, offsets, counts, pVtxHdr->numLODs, pVertexList, numVertexes);
+
+ for (n=0; n<pVtxHdr->numLODs; n++)
+ {
+ if (!counts[n])
+ offsets[n] = 0;
+
+ pVertexPools[poolStart+m].lodMeshInfo.offsets[n] = offsets[n];
+ pVertexPools[poolStart+m].lodMeshInfo.numVertexes[n] = counts[n];
+ }
+
+ // iterate using calced offsets to walk each mesh
+ // set its expected final vertex id, which is its "gathered" index relative to mesh
+ baseMeshVertID = finalMeshVertID;
+ for (n=pVtxHdr->numLODs-1; n>=0; n--)
+ {
+ // iterate each vert in the mesh
+ // vertex id is relative to
+ for (p=0; p<counts[n]; p++)
+ {
+ pVertexList[offsets[n] + p].finalMeshVertID = finalMeshVertID - baseMeshVertID;
+ finalMeshVertID++;
+ }
+ }
+ }
+ poolStart += pStudioModel->nummeshes;
+ }
+ }
+
+ // safety check
+ // every referenced vertex should have been remapped correctly
+ // some models do have orphaned vertexes, ignore these
+ for (i=0; i<numVertexes; i++)
+ {
+ if (pVertexList[i].lodFlags && pVertexList[i].finalMeshVertID == -1)
+ {
+ // should never happen, data occured in unknown manner
+ // don't build corrupted data
+ return false;
+ }
+ }
+
+ // provide generated tables
+ *ppVertexPools = pVertexPools;
+ *pNumVertexPools = numVertexPools;
+ *ppVertexList = pVertexList;
+ *pNumVertexes = numVertexes;
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// FixupVVDFile
+//
+// VVD files get vertexes remapped to a flat lod sorted order.
+//-----------------------------------------------------------------------------
+bool FixupVVDFile(const char *fileName, const studiohdr_t *pStudioHdr, const void *pVtxBuff, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes)
+{
+ OptimizedModel::FileHeader_t *pVtxHdr;
+ vertexFileHeader_t *pFileHdr_old;
+ vertexFileHeader_t *pFileHdr_new;
+ mstudiobodyparts_t *pStudioBodyPart;
+ mstudiomodel_t *pStudioModel;
+ mstudiomesh_t *pStudioMesh;
+ mstudiovertex_t *pVertex_old;
+ mstudiovertex_t *pVertex_new;
+ Vector4D *pTangent_new;
+ Vector4D *pTangent_old;
+ mstudiovertex_t **pFlatVertexes;
+ Vector4D **pFlatTangents;
+ vertexFileFixup_t *pFixupTable;
+ const lodMeshInfo_t *pLodMeshInfo;
+ byte *pStart_new;
+ byte *pData_new;
+ byte *pStart_base;
+ byte *pVertexBase_old;
+ byte *pTangentBase_old;
+ void *pVvdBuff;
+ int i;
+ int j;
+ int k;
+ int n;
+ int p;
+ int numFixups;
+ int numFlat;
+ int oldIndex;
+ int mask;
+ int maxCount;
+ int numMeshes;
+ int numOutFixups;
+
+ pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff;
+
+ LoadFile((char*)fileName, &pVvdBuff);
+
+ pFileHdr_old = (vertexFileHeader_t*)pVvdBuff;
+ if (pFileHdr_old->numLODs != 1)
+ {
+ // file has wrong expected state
+ return false;
+ }
+
+ // meshes need relocation fixup from lod order back to mesh order
+ numFixups = 0;
+ numMeshes = 0;
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ for (k=0; k<pStudioModel->nummeshes; k++)
+ {
+ pStudioMesh = pStudioModel->pMesh(k);
+ if (!pStudioMesh->numvertices)
+ {
+ // no vertexes for this mesh, skip it
+ continue;
+ }
+ for (n=pVtxHdr->numLODs-1; n>=0; n--)
+ {
+ pLodMeshInfo = &pVertexPools[numMeshes+k].lodMeshInfo;
+ if (!pLodMeshInfo->numVertexes[n])
+ {
+ // no vertexes for this portion of the mesh at this lod, skip it
+ continue;
+ }
+ numFixups++;
+ }
+ }
+ numMeshes += k;
+ }
+ }
+ if (numMeshes == 1 || numFixups == 1 || pVtxHdr->numLODs == 1)
+ {
+ // no fixup required for a single mesh
+ // no fixup required for single lod
+ // no fixup required when mesh data is contiguous as expected
+ numFixups = 0;
+ }
+
+ pStart_base = (byte*)malloc(FILEBUFFER);
+ memset(pStart_base, 0, FILEBUFFER);
+ pStart_new = (byte*)ALIGN(pStart_base,16);
+ pData_new = pStart_new;
+
+ // setup headers
+ pFileHdr_new = (vertexFileHeader_t*)pData_new;
+ pData_new += sizeof(vertexFileHeader_t);
+
+ // clone and fixup new header
+ *pFileHdr_new = *pFileHdr_old;
+ pFileHdr_new->numLODs = pVtxHdr->numLODs;
+ pFileHdr_new->numFixups = numFixups;
+
+ // skip new fixup table
+ pData_new = (byte*)ALIGN(pData_new, 4);
+ pFixupTable = (vertexFileFixup_t*)pData_new;
+ pFileHdr_new->fixupTableStart = pData_new - pStart_new;
+ pData_new += numFixups*sizeof(vertexFileFixup_t);
+
+ // skip new vertex data
+ pData_new = (byte*)ALIGN(pData_new, 16);
+ pVertex_new = (mstudiovertex_t*)pData_new;
+ pFileHdr_new->vertexDataStart = pData_new - pStart_new;
+ pData_new += numVertexes*sizeof(mstudiovertex_t);
+
+ // skip new tangent data
+ pData_new = (byte*)ALIGN(pData_new, 16);
+ pTangent_new = (Vector4D*)pData_new;
+ pFileHdr_new->tangentDataStart = pData_new - pStart_new;
+ pData_new += numVertexes*sizeof(Vector4D);
+
+ pVertexBase_old = (byte*)pFileHdr_old + pFileHdr_old->vertexDataStart;
+ pTangentBase_old = (byte*)pFileHdr_old + pFileHdr_old->tangentDataStart;
+
+ // determine number of aggregate verts towards root lod
+ // loader can truncate read according to desired root lod
+ maxCount = -1;
+ for (n=pVtxHdr->numLODs-1; n>=0; n--)
+ {
+ mask = 1<<n;
+ for (p=0; p<numVertexes; p++)
+ {
+ if (mask & pVertexList[p].lodFlags)
+ {
+ if (maxCount < p)
+ maxCount = p;
+ }
+ }
+ pFileHdr_new->numLODVertexes[n] = maxCount+1;
+ }
+ for (n=pVtxHdr->numLODs; n<MAX_NUM_LODS; n++)
+ {
+ // ripple the last valid lod entry all the way down
+ pFileHdr_new->numLODVertexes[n] = pFileHdr_new->numLODVertexes[pVtxHdr->numLODs-1];
+ }
+
+ // build mesh relocation fixup table
+ if (numFixups)
+ {
+ numMeshes = 0;
+ numOutFixups = 0;
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ for (k=0; k<pStudioModel->nummeshes; k++)
+ {
+ pStudioMesh = pStudioModel->pMesh(k);
+ if (!pStudioMesh->numvertices)
+ {
+ // not vertexes for this mesh, skip it
+ continue;
+ }
+ for (n=pVtxHdr->numLODs-1; n>=0; n--)
+ {
+ pLodMeshInfo = &pVertexPools[numMeshes+k].lodMeshInfo;
+ if (!pLodMeshInfo->numVertexes[n])
+ {
+ // no vertexes for this portion of the mesh at this lod, skip it
+ continue;
+ }
+ pFixupTable[numOutFixups].lod = n;
+ pFixupTable[numOutFixups].numVertexes = pLodMeshInfo->numVertexes[n];
+ pFixupTable[numOutFixups].sourceVertexID = pLodMeshInfo->offsets[n];
+ numOutFixups++;
+ }
+ }
+ numMeshes += pStudioModel->nummeshes;
+ }
+ }
+
+ if (numOutFixups != numFixups)
+ {
+ // logic sync error, final calc should match precalc, otherwise memory corruption
+ return false;
+ }
+ }
+
+ // generate offsets to vertexes
+ numFlat = 0;
+ pFlatVertexes = (mstudiovertex_t**)malloc(numVertexes*sizeof(mstudiovertex_t*));
+ pFlatTangents = (Vector4D**)malloc(numVertexes*sizeof(Vector4D*));
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+ pVertex_old = (mstudiovertex_t*)&pVertexBase_old[pStudioModel->vertexindex];
+ pTangent_old = (Vector4D*)&pTangentBase_old[pStudioModel->tangentsindex];
+ for (k=0; k<pStudioModel->nummeshes; k++)
+ {
+ // get each mesh's vertexes
+ pStudioMesh = pStudioModel->pMesh(k);
+ for (n=0; n<pStudioMesh->numvertices; n++)
+ {
+ // old vertex pools are per model, seperated per mesh by a start offset
+ // vertexes are then isolated subpools per mesh
+ // build the flat linear array of lookup pointers
+ pFlatVertexes[numFlat] = &pVertex_old[pStudioMesh->vertexoffset + n];
+ pFlatTangents[numFlat] = &pTangent_old[pStudioMesh->vertexoffset + n];
+ numFlat++;
+ }
+ }
+ }
+ }
+
+ // write in lod sorted order
+ for (i=0; i<numVertexes; i++)
+ {
+ // iterate sorted order, remap old vert location to new vert location
+ oldIndex = pVertexList[i].vertexOffset + pVertexList[i].meshVertID;
+
+ memcpy(&pVertex_new[i], pFlatVertexes[oldIndex], sizeof(mstudiovertex_t));
+ memcpy(&pTangent_new[i], pFlatTangents[oldIndex], sizeof(Vector4D));
+ }
+
+ // pFileHdr_new->length = pData_new-pStart_new;
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile((char*)fileName, pStart_new, pData_new-pStart_new);
+ }
+
+ free(pStart_base);
+ free(pFlatVertexes);
+ free(pFlatTangents);
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// FixupVTXFile
+//
+// VTX files get their windings remapped.
+//-----------------------------------------------------------------------------
+bool FixupVTXFile(const char *fileName, const studiohdr_t *pStudioHdr, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes)
+{
+ OptimizedModel::FileHeader_t *pVtxHdr;
+ OptimizedModel::BodyPartHeader_t *pBodyPartHdr;
+ OptimizedModel::ModelHeader_t *pModelHdr;
+ OptimizedModel::ModelLODHeader_t *pModelLODHdr;
+ OptimizedModel::MeshHeader_t *pMeshHdr;
+ OptimizedModel::StripGroupHeader_t *pStripGroupHdr;
+ OptimizedModel::Vertex_t *pStripVertex;
+ int currLod;
+ int vertexOffset;
+ mstudiobodyparts_t *pStudioBodyPart;
+ mstudiomodel_t *pStudioModel;
+ int i,j,k,m,n;
+ int poolStart;
+ int VtxLen;
+ int newMeshVertID;
+ void *pVtxBuff;
+
+ VtxLen = LoadFile((char*)fileName, &pVtxBuff);
+ pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff;
+
+ // iterate all lod's windings
+ poolStart = 0;
+ for (i=0; i<pVtxHdr->numBodyParts; i++)
+ {
+ pBodyPartHdr = pVtxHdr->pBodyPart(i);
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+
+ for (j=0; j<pBodyPartHdr->numModels; j++)
+ {
+ pModelHdr = pBodyPartHdr->pModel(j);
+ pStudioModel = pStudioBodyPart->pModel(j);
+
+ // iterate all lods
+ for (currLod=0; currLod<pVtxHdr->numLODs; currLod++)
+ {
+ pModelLODHdr = pModelHdr->pLOD(currLod);
+
+ if (pModelLODHdr->numMeshes != pStudioModel->nummeshes)
+ return false;
+
+ for (k=0; k<pModelLODHdr->numMeshes; k++)
+ {
+ // track the expected relative offset into the flat vertexes
+ vertexOffset = 0;
+ for (m=0; m<poolStart+k; m++)
+ vertexOffset += pVertexPools[m].numVertexes;
+
+ pMeshHdr = pModelLODHdr->pMesh(k);
+ for (m=0; m<pMeshHdr->numStripGroups; m++)
+ {
+ pStripGroupHdr = pMeshHdr->pStripGroup(m);
+
+ for (n=0; n<pStripGroupHdr->numVerts; n++)
+ {
+ pStripVertex = pStripGroupHdr->pVertex(n);
+
+ // remap old mesh relative vertex index to absolute flat sorted list
+ newMeshVertID = pVertexPools[poolStart+k].pVertexMap[pStripVertex->origMeshVertID];
+
+ // map to expected final fixed vertex locations
+ // final fixed vertex location is performed by runtime loading code
+ newMeshVertID = pVertexList[newMeshVertID].finalMeshVertID;
+
+ // fixup to expected
+ pStripVertex->origMeshVertID = newMeshVertID;
+ }
+ }
+ }
+ }
+ poolStart += pStudioModel->nummeshes;
+ }
+ }
+
+ // pVtxHdr->length = VtxLen;
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile((char*)fileName, pVtxBuff, VtxLen);
+ }
+
+ free(pVtxBuff);
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// FixupMDLFile
+//
+// MDL files get flexes/vertex/tangent data offsets fixed
+//-----------------------------------------------------------------------------
+bool FixupMDLFile(const char *fileName, studiohdr_t *pStudioHdr, const void *pVtxBuff, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes)
+{
+ OptimizedModel::FileHeader_t *pVtxHdr;
+ const lodMeshInfo_t *pLodMeshInfo;
+ mstudiobodyparts_t *pStudioBodyPart;
+ mstudiomodel_t *pStudioModel;
+ mstudiomesh_t *pStudioMesh;
+ mstudioflex_t *pStudioFlex;
+ mstudiovertanim_t *pStudioVertAnim;
+ int newMeshVertID;
+ int i;
+ int j;
+ int m;
+ int n;
+ int p;
+ int numLODs;
+ int numMeshes;
+ int total;
+
+ pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff;
+
+ numLODs = pVtxHdr->numLODs;
+
+ numMeshes = 0;
+ for (i=0; i<pStudioHdr->numbodyparts; i++)
+ {
+ pStudioBodyPart = pStudioHdr->pBodypart(i);
+
+ for (j=0; j<pStudioBodyPart->nummodels; j++)
+ {
+ pStudioModel = pStudioBodyPart->pModel(j);
+
+ for (m=0; m<pStudioModel->nummeshes; m++)
+ {
+ // get each mesh
+ pStudioMesh = pStudioModel->pMesh(m);
+ pLodMeshInfo = &pVertexPools[numMeshes+m].lodMeshInfo;
+
+ for (n=0; n<numLODs; n++)
+ {
+ // the root lod, contains all the lower detail lods verts
+ // tally the verts that are at each lod
+ total = 0;
+ for (p=n; p<numLODs; p++)
+ total += pLodMeshInfo->numVertexes[p];
+
+ // embed the fixup for loader
+ pStudioMesh->vertexdata.numLODVertexes[n] = total;
+ }
+ for (p=n; p<MAX_NUM_LODS; p++)
+ {
+ // duplicate last valid lod to end of list
+ pStudioMesh->vertexdata.numLODVertexes[p] = pStudioMesh->vertexdata.numLODVertexes[numLODs-1];
+ }
+
+ // fix the flexes
+ for (n=0; n<pStudioMesh->numflexes; n++)
+ {
+ pStudioFlex = pStudioMesh->pFlex(n);
+
+ byte *pvanim = pStudioFlex->pBaseVertanim();
+ int nVAnimSizeBytes = pStudioFlex->VertAnimSizeBytes();
+
+ for (p=0; p<pStudioFlex->numverts; p++, pvanim += nVAnimSizeBytes )
+ {
+ pStudioVertAnim = (mstudiovertanim_t*)( pvanim );
+
+ if (pStudioVertAnim->index < 0 || pStudioVertAnim->index >= pStudioMesh->numvertices)
+ return false;
+
+ // remap old mesh relative vertex index to absolute flat sorted list
+ newMeshVertID = pVertexPools[numMeshes+m].pVertexMap[pStudioVertAnim->index];
+
+ // map to expected final fixed vertex locations
+ // final fixed vertex location is performed by runtime loading code
+ newMeshVertID = pVertexList[newMeshVertID].finalMeshVertID;
+
+ // fixup to expected
+ pStudioVertAnim->index = newMeshVertID;
+ }
+ }
+ }
+ numMeshes += pStudioModel->nummeshes;
+ }
+ }
+
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile((char*)fileName, (void*)pStudioHdr, pStudioHdr->length);
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// FixupToSortedLODVertexes
+//
+// VVD files get vertexes fixed to a flat sorted order, ascending in lower detail lod usage
+// VTX files get their windings remapped to the sort.
+//-----------------------------------------------------------------------------
+bool FixupToSortedLODVertexes(studiohdr_t *pStudioHdr)
+{
+ char filename[MAX_PATH];
+ char tmpFileName[MAX_PATH];
+ void *pVtxBuff;
+ usedVertex_t *pVertexList;
+ vertexPool_t *pVertexPools;
+ int numVertexes;
+ int numVertexPools;
+ int VtxLen;
+ int i;
+ const char *vtxPrefixes[] = {".dx80.vtx", ".dx90.vtx", ".sw.vtx"};
+
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+ Q_StripExtension( filename, filename, sizeof( filename ) );
+
+ // determine lod usage per vertex
+ // all vtx files enumerate model's lod verts, but differ in their mesh makeup
+ // use xxx.dx80.vtx to establish which vertexes are used by each lod
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".dx80.vtx" );
+ VtxLen = LoadFile( tmpFileName, &pVtxBuff );
+
+ // build the sorted vertex tables
+ if (!BuildSortedVertexList(pStudioHdr, pVtxBuff, &pVertexPools, &numVertexPools, &pVertexList, &numVertexes))
+ {
+ // data sync error
+ return false;
+ }
+
+ // fixup ???.vvd
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".vvd" );
+ if (!FixupVVDFile(tmpFileName, pStudioHdr, pVtxBuff, pVertexPools, numVertexPools, pVertexList, numVertexes))
+ {
+ // data error
+ return false;
+ }
+
+ for (i=0; i<ARRAYSIZE(vtxPrefixes); i++)
+ {
+ // fixup ???.vtx
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, vtxPrefixes[i] );
+ if (!FixupVTXFile(tmpFileName, pStudioHdr, pVertexPools, numVertexPools, pVertexList, numVertexes))
+ {
+ // data error
+ return false;
+ }
+ }
+
+ // fixup ???.mdl
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".mdl" );
+ if (!FixupMDLFile(tmpFileName, pStudioHdr, pVtxBuff, pVertexPools, numVertexPools, pVertexList, numVertexes))
+ {
+ // data error
+ return false;
+ }
+
+ // free the tables
+ for (i=0; i<numVertexPools; i++)
+ {
+ if (pVertexPools[i].pVertexList)
+ free(pVertexPools[i].pVertexList);
+ if (pVertexPools[i].pVertexMap)
+ free(pVertexPools[i].pVertexMap);
+ }
+ if (numVertexPools)
+ free(pVertexPools);
+ free(pVtxBuff);
+
+ // success
+ return true;
+}
+
+
+byte IsByte( int val )
+{
+ if (val < 0 || val > 0xFF)
+ {
+ MdlError("byte conversion out of range %d\n", val );
+ }
+ return val;
+}
+
+char IsChar( int val )
+{
+ if (val < -0x80 || val > 0x7F)
+ {
+ MdlError("char conversion out of range %d\n", val );
+ }
+ return val;
+}
+
+int IsInt24( int val )
+{
+ if (val < -0x800000 || val > 0x7FFFFF)
+ {
+ MdlError("int24 conversion out of range %d\n", val );
+ }
+ return val;
+}
+
+
+short IsShort( int val )
+{
+ if (val < -0x8000 || val > 0x7FFF)
+ {
+ MdlError("short conversion out of range %d\n", val );
+ }
+ return val;
+}
+
+unsigned short IsUShort( int val )
+{
+ if (val < 0 || val > 0xFFFF)
+ {
+ MdlError("ushort conversion out of range %d\n", val );
+ }
+ return val;
+}
+
+
+bool Clamp_MDL_LODS( const char *fileName, int rootLOD )
+{
+ studiohdr_t *pStudioHdr;
+ int len;
+
+ len = LoadFile((char*)fileName, (void **)&pStudioHdr);
+
+ Studio_SetRootLOD( pStudioHdr, rootLOD );
+
+#if 0
+ // shift down bone LOD masks
+ int iBone;
+ for ( iBone = 0; iBone < pStudioHdr->numbones; iBone++)
+ {
+ mstudiobone_t *pBone = pStudioHdr->pBone( iBone );
+
+ int nLodID;
+ for ( nLodID = 0; nLodID < rootLOD; nLodID++)
+ {
+ int iLodMask = BONE_USED_BY_VERTEX_LOD0 << nLodID;
+
+ if (pBone->flags & (BONE_USED_BY_VERTEX_LOD0 << rootLOD))
+ {
+ pBone->flags = pBone->flags | iLodMask;
+ }
+ else
+ {
+ pBone->flags = pBone->flags & (~iLodMask);
+ }
+ }
+ }
+#endif
+
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile( (char *)fileName, pStudioHdr, len );
+ }
+
+ return true;
+}
+
+
+
+
+bool Clamp_VVD_LODS( const char *fileName, int rootLOD )
+{
+ vertexFileHeader_t *pTempVvdHdr;
+ int len;
+
+ len = LoadFile((char*)fileName, (void **)&pTempVvdHdr);
+
+ int newLength = Studio_VertexDataSize( pTempVvdHdr, rootLOD, true );
+
+ // printf("was %d now %d\n", len, newLength );
+
+ vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)calloc( newLength, 1 );
+
+ Studio_LoadVertexes( pTempVvdHdr, pNewVvdHdr, rootLOD, true );
+
+ if (!g_quiet)
+ {
+ printf ("---------------------\n");
+ printf ("writing %s:\n", fileName);
+ printf( "vertices (%d vertices)\n", pNewVvdHdr->numLODVertexes[ 0 ] );
+ }
+
+ // pNewVvdHdr->length = newLength;
+
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile( (char *)fileName, pNewVvdHdr, newLength );
+ }
+
+ return true;
+}
+
+
+bool Clamp_VTX_LODS( const char *fileName, int rootLOD, studiohdr_t *pStudioHdr )
+{
+ int i, j, k, m, n;
+ int nLodID;
+ int size;
+
+ OptimizedModel::FileHeader_t *pVtxHdr;
+ int len;
+
+ len = LoadFile((char*)fileName, (void **)&pVtxHdr);
+
+ OptimizedModel::FileHeader_t *pNewVtxHdr = (OptimizedModel::FileHeader_t *)calloc( FILEBUFFER, 1 );
+
+ byte *pData = (byte *)pNewVtxHdr;
+ pData += sizeof( OptimizedModel::FileHeader_t );
+ ALIGN4( pData );
+
+ // header
+ pNewVtxHdr->version = pVtxHdr->version;
+ pNewVtxHdr->vertCacheSize = pVtxHdr->vertCacheSize;
+ pNewVtxHdr->maxBonesPerStrip = pVtxHdr->maxBonesPerStrip;
+ pNewVtxHdr->maxBonesPerTri = pVtxHdr->maxBonesPerTri;
+ pNewVtxHdr->maxBonesPerVert = pVtxHdr->maxBonesPerVert;
+ pNewVtxHdr->checkSum = pVtxHdr->checkSum;
+ pNewVtxHdr->numLODs = pVtxHdr->numLODs;
+
+ // material replacement list
+ pNewVtxHdr->materialReplacementListOffset = (pData - (byte *)pNewVtxHdr);
+ pData += pVtxHdr->numLODs * sizeof( OptimizedModel::MaterialReplacementListHeader_t );
+ // ALIGN4( pData );
+
+ BeginStringTable( );
+
+ // allocate replacement list arrays
+ for ( nLodID = rootLOD; nLodID < pVtxHdr->numLODs; nLodID++ )
+ {
+ OptimizedModel::MaterialReplacementListHeader_t *pReplacementList = pVtxHdr->pMaterialReplacementList( nLodID );
+ OptimizedModel::MaterialReplacementListHeader_t *pNewReplacementList = pNewVtxHdr->pMaterialReplacementList( nLodID );
+
+ pNewReplacementList->numReplacements = pReplacementList->numReplacements;
+ pNewReplacementList->replacementOffset = (pData - (byte *)pNewReplacementList);
+ pData += pNewReplacementList->numReplacements * sizeof( OptimizedModel::MaterialReplacementHeader_t );
+ // ALIGN4( pData );
+
+ for (i = 0; i < pReplacementList->numReplacements; i++)
+ {
+ OptimizedModel::MaterialReplacementHeader_t *pReplacement = pReplacementList->pMaterialReplacement( i );
+ OptimizedModel::MaterialReplacementHeader_t *pNewReplacement = pNewReplacementList->pMaterialReplacement( i );
+
+ pNewReplacement->materialID = pReplacement->materialID;
+ AddToStringTable( pNewReplacement, &pNewReplacement->replacementMaterialNameOffset, pReplacement->pMaterialReplacementName() );
+ }
+ }
+ pData = WriteStringTable( pData );
+
+ // link previous LODs to higher LODs
+ for ( nLodID = 0; nLodID < rootLOD; nLodID++ )
+ {
+ OptimizedModel::MaterialReplacementListHeader_t *pRootReplacementList = pNewVtxHdr->pMaterialReplacementList( rootLOD );
+ OptimizedModel::MaterialReplacementListHeader_t *pNewReplacementList = pNewVtxHdr->pMaterialReplacementList( nLodID );
+
+ int delta = (byte *)pRootReplacementList - (byte *)pNewReplacementList;
+
+ pNewReplacementList->numReplacements = pRootReplacementList->numReplacements;
+ pNewReplacementList->replacementOffset = pRootReplacementList->replacementOffset + delta;
+ }
+
+ // body parts
+ pNewVtxHdr->numBodyParts = pStudioHdr->numbodyparts;
+ pNewVtxHdr->bodyPartOffset = (pData - (byte *)pNewVtxHdr);
+ pData += pNewVtxHdr->numBodyParts * sizeof( OptimizedModel::BodyPartHeader_t );
+ // ALIGN4( pData );
+
+ // Iterate over every body part...
+ for ( i = 0; i < pStudioHdr->numbodyparts; i++ )
+ {
+ mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i);
+ OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart(i);
+ OptimizedModel::BodyPartHeader_t* pNewVtxBodyPart = pNewVtxHdr->pBodyPart(i);
+
+ pNewVtxBodyPart->numModels = pBodyPart->nummodels;
+ pNewVtxBodyPart->modelOffset = (pData - (byte *)pNewVtxBodyPart);
+ pData += pNewVtxBodyPart->numModels * sizeof( OptimizedModel::ModelHeader_t );
+ // ALIGN4( pData );
+
+ // Iterate over every submodel...
+ for (j = 0; j < pBodyPart->nummodels; ++j)
+ {
+ mstudiomodel_t* pModel = pBodyPart->pModel(j);
+ OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel(j);
+ OptimizedModel::ModelHeader_t* pNewVtxModel = pNewVtxBodyPart->pModel(j);
+
+ pNewVtxModel->numLODs = pVtxModel->numLODs;
+ pNewVtxModel->lodOffset = (pData - (byte *)pNewVtxModel);
+ pData += pNewVtxModel->numLODs * sizeof( OptimizedModel::ModelLODHeader_t );
+ ALIGN4( pData );
+
+ for ( nLodID = rootLOD; nLodID < pVtxModel->numLODs; nLodID++ )
+ {
+ OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLodID );
+ OptimizedModel::ModelLODHeader_t *pNewVtxLOD = pNewVtxModel->pLOD( nLodID );
+
+ pNewVtxLOD->numMeshes = pVtxLOD->numMeshes;
+ pNewVtxLOD->switchPoint = pVtxLOD->switchPoint;
+ pNewVtxLOD->meshOffset = (pData - (byte *)pNewVtxLOD);
+ pData += pNewVtxLOD->numMeshes * sizeof( OptimizedModel::MeshHeader_t );
+ ALIGN4( pData );
+
+ // Iterate over all the meshes....
+ for (k = 0; k < pModel->nummeshes; ++k)
+ {
+ Assert( pModel->nummeshes == pVtxLOD->numMeshes );
+// mstudiomesh_t* pMesh = pModel->pMesh(k);
+ OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k);
+ OptimizedModel::MeshHeader_t* pNewVtxMesh = pNewVtxLOD->pMesh(k);
+
+ pNewVtxMesh->numStripGroups = pVtxMesh->numStripGroups;
+ pNewVtxMesh->flags = pVtxMesh->flags;
+ pNewVtxMesh->stripGroupHeaderOffset = (pData - (byte *)pNewVtxMesh);
+ pData += pNewVtxMesh->numStripGroups * sizeof( OptimizedModel::StripGroupHeader_t );
+
+ // printf("part %d : model %d : lod %d : mesh %d : strips %d : offset %d\n", i, j, nLodID, k, pVtxMesh->numStripGroups, pVtxMesh->stripGroupHeaderOffset );
+
+ for (m = 0; m < pVtxMesh->numStripGroups; m++)
+ {
+ OptimizedModel::StripGroupHeader_t *pStripGroup = pVtxMesh->pStripGroup( m );
+ OptimizedModel::StripGroupHeader_t *pNewStripGroup = pNewVtxMesh->pStripGroup( m );
+
+ // int delta = ((byte *)pStripGroup - (byte *)pVtxHdr) - ((byte *)pNewStripGroup - (byte *)pNewVtxHdr);
+
+ pNewStripGroup->numVerts = pStripGroup->numVerts;
+ pNewStripGroup->vertOffset = (pData - (byte *)pNewStripGroup);
+ size = pNewStripGroup->numVerts * sizeof( OptimizedModel::Vertex_t );
+ memcpy( pData, pStripGroup->pVertex(0), size );
+ pData += size;
+
+ pNewStripGroup->numIndices = pStripGroup->numIndices;
+ pNewStripGroup->indexOffset = (pData - (byte *)pNewStripGroup);
+ size = pNewStripGroup->numIndices * sizeof( unsigned short );
+ memcpy( pData, pStripGroup->pIndex(0), size );
+ pData += size;
+
+ pNewStripGroup->numStrips = pStripGroup->numStrips;
+ pNewStripGroup->stripOffset = (pData - (byte *)pNewStripGroup);
+ size = pNewStripGroup->numStrips * sizeof( OptimizedModel::StripHeader_t );
+ pData += size;
+
+ pNewStripGroup->flags = pStripGroup->flags;
+
+ /*
+ printf("\tnumVerts %d %d :\n", pStripGroup->numVerts, pStripGroup->vertOffset );
+ printf("\tnumIndices %d %d :\n", pStripGroup->numIndices, pStripGroup->indexOffset );
+ printf("\tnumStrips %d %d :\n", pStripGroup->numStrips, pStripGroup->stripOffset );
+ */
+
+ for (n = 0; n < pStripGroup->numStrips; n++)
+ {
+ OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( n );
+ OptimizedModel::StripHeader_t *pNewStrip = pNewStripGroup->pStrip( n );
+
+ pNewStrip->numIndices = pStrip->numIndices;
+ pNewStrip->indexOffset = pStrip->indexOffset;
+
+ pNewStrip->numVerts = pStrip->numVerts;
+ pNewStrip->vertOffset = pStrip->vertOffset;
+
+ pNewStrip->numBones = pStrip->numBones;
+ pNewStrip->flags = pStrip->flags;
+
+ pNewStrip->numBoneStateChanges = pStrip->numBoneStateChanges;
+ pNewStrip->boneStateChangeOffset = (pData - (byte *)pNewStrip);
+ size = pNewStrip->numBoneStateChanges * sizeof( OptimizedModel::BoneStateChangeHeader_t );
+ memcpy( pData, pStrip->pBoneStateChange(0), size );
+ pData += size;
+
+ /*
+ printf("\t\tnumIndices %d %d :\n", pNewStrip->numIndices, pNewStrip->indexOffset );
+ printf("\t\tnumVerts %d %d :\n", pNewStrip->numVerts, pNewStrip->vertOffset );
+ printf("\t\tnumBoneStateChanges %d %d :\n", pNewStrip->numBoneStateChanges, pNewStrip->boneStateChangeOffset );
+ */
+ // printf("(%d)\n", delta );
+ }
+ // printf("(%d)\n", delta );
+ }
+ }
+ }
+ }
+ }
+
+ // Iterate over every body part...
+ for ( i = 0; i < pStudioHdr->numbodyparts; i++ )
+ {
+ mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i);
+
+ // Iterate over every submodel...
+ for (j = 0; j < pBodyPart->nummodels; ++j)
+ {
+ // link previous LODs to higher LODs
+ for ( nLodID = 0; nLodID < rootLOD; nLodID++ )
+ {
+ OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxHdr->pBodyPart(i)->pModel(j)->pLOD(nLodID);
+ OptimizedModel::ModelLODHeader_t *pRootVtxLOD = pNewVtxHdr->pBodyPart(i)->pModel(j)->pLOD(rootLOD);
+ OptimizedModel::ModelLODHeader_t *pNewVtxLOD = pNewVtxHdr->pBodyPart(i)->pModel(j)->pLOD(nLodID);
+
+ pNewVtxLOD->numMeshes = pRootVtxLOD->numMeshes;
+ pNewVtxLOD->switchPoint = pVtxLOD->switchPoint;
+
+ int delta = (byte *)pRootVtxLOD - (byte *)pNewVtxLOD;
+ pNewVtxLOD->meshOffset = pRootVtxLOD->meshOffset + delta;
+ }
+ }
+ }
+
+ int newLen = pData - (byte *)pNewVtxHdr;
+ // printf("len %d : %d\n", len, newLen );
+
+ // pNewVtxHdr->length = newLen;
+
+ if (!g_quiet)
+ {
+ printf ("writing %s:\n", fileName);
+ printf( "everything (%d bytes)\n", newLen );
+ }
+
+ {
+ CP4AutoEditAddFile autop4( fileName, "binary" );
+ SaveFile( (char *)fileName, pNewVtxHdr, newLen );
+ }
+
+ free( pNewVtxHdr );
+
+ return true;
+}
+
+
+
+
+bool Clamp_RootLOD( studiohdr_t *phdr )
+{
+ char filename[MAX_PATH];
+ char tmpFileName[MAX_PATH];
+ int i;
+ const char *vtxPrefixes[] = {".dx80.vtx", ".dx90.vtx", ".sw.vtx"};
+
+ int rootLOD = g_minLod;
+
+ if (rootLOD > g_ScriptLODs.Size() - 1)
+ {
+ rootLOD = g_ScriptLODs.Size() -1;
+ }
+
+ if (rootLOD == 0)
+ {
+ return true;
+ }
+
+ V_strcpy_safe( filename, gamedir );
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+ Q_StripExtension( filename, filename, sizeof( filename ) );
+
+ // shift the files so that g_minLod is the root LOD
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".mdl" );
+ Clamp_MDL_LODS( tmpFileName, rootLOD );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".vvd" );
+ Clamp_VVD_LODS( tmpFileName, rootLOD );
+
+ for (i=0; i<ARRAYSIZE(vtxPrefixes); i++)
+ {
+ // fixup ???.vtx
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, vtxPrefixes[i] );
+ Clamp_VTX_LODS( tmpFileName, rootLOD, phdr );
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------
+// For a particular .qc, converts all studiomdl generated files to big-endian format.
+//----------------------------------------------------------------------
+void WriteSwappedFile( char *srcname, char *outname, int(*pfnSwapFunc)(void*, const void*, int) )
+{
+ if ( FileExists( srcname ) )
+ {
+ if( !g_quiet )
+ {
+ printf( "---------------------\n" );
+ printf( "Generating Xbox360 file format for \"%s\":\n", srcname );
+ }
+
+ void *pFileBase = NULL;
+ int fileSize = LoadFile( srcname, &pFileBase );
+ int paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING;
+
+ void *pOutBase = malloc( paddedSize );
+
+ int bytes = pfnSwapFunc( pOutBase, pFileBase, fileSize );
+
+ if ( bytes != 0 )
+ {
+ CP4AutoEditAddFile autop4( outname, "binary" );
+ SaveFile( outname, pOutBase, bytes );
+ }
+
+ free(pOutBase);
+ free(pFileBase);
+
+ if ( bytes == 0 )
+ {
+ MdlError( "Aborted byteswap on '%s':\n", srcname );
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// For a particular .qc, converts all studiomdl generated files to big-endian format.
+//----------------------------------------------------------------------
+void WriteAllSwappedFiles( const char *filename )
+{
+ char srcname[ MAX_PATH ];
+ char outname[ MAX_PATH ];
+
+ extern IPhysicsCollision *physcollision;
+ if ( physcollision )
+ {
+ StudioByteSwap::SetCollisionInterface( physcollision );
+ }
+
+ // Convert PHY
+ Q_StripExtension( filename, srcname, sizeof( srcname ) );
+ Q_strncpy( outname, srcname, sizeof( outname ) );
+
+ Q_strcat( srcname, ".phy", sizeof( srcname ) );
+ Q_strcat( outname, ".360.phy", sizeof( outname ) );
+
+ WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapPHY );
+
+ // Convert VVD
+ Q_StripExtension( filename, srcname, sizeof( srcname ) );
+ Q_strncpy( outname, srcname, sizeof( outname ) );
+
+ Q_strcat( srcname, ".vvd", sizeof( srcname ) );
+ Q_strcat( outname, ".360.vvd", sizeof( outname ) );
+
+ WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapVVD );
+
+ // Convert VTX
+ Q_StripExtension( filename, srcname, sizeof( srcname ) );
+ Q_StripExtension( srcname, srcname, sizeof( srcname ) );
+ Q_strncpy( outname, srcname, sizeof( outname ) );
+
+ Q_strcat( srcname, ".dx90.vtx", sizeof( srcname ) );
+ Q_strcat( outname, ".360.vtx", sizeof( outname ) );
+
+ WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapVTX );
+
+ // Convert MDL
+ Q_StripExtension( filename, srcname, sizeof( srcname ) );
+ Q_strncpy( outname, srcname, sizeof( outname ) );
+
+ Q_strcat( srcname, ".mdl", sizeof( srcname ) );
+ Q_strcat( outname, ".360.mdl", sizeof( outname ) );
+
+ WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapMDL );
+} \ No newline at end of file