diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/studiomdl | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/studiomdl')
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 ¢erOfMass ); + 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 ¢erOfMass ) +{ + 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 |