//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "stdafx.h" #include #include #include #include #include "mapdoc.h" #include "MapWorld.h" #include "Material.h" #include "Render2D.h" #include "Render3D.h" #include "StudioModel.h" #include "ViewerSettings.h" #include "materialsystem/IMesh.h" #include "TextureSystem.h" #include "bone_setup.h" #include "IStudioRender.h" #include "GlobalFunctions.h" #include "UtlMemory.h" #include "UtlDict.h" #include "bone_accessor.h" #include "optimize.h" #include "FileSystem.h" #include "Hammer.h" #include "HammerVGui.h" #include #include "mapview2d.h" #include "mapdefs.h" #include "camera.h" #include "options.h" // memdbgon must be the last include file in a .cpp file!!! #include #pragma warning(disable : 4244) // double to float //----------------------------------------------------------------------------- // Purpose: Monitors the filesystem for changes to model files and flushes // any stuff in memory for the model if necessary. //----------------------------------------------------------------------------- class CStudioFileChangeWatcher : private CFileChangeWatcher::ICallbacks { public: void Init(); void Update(); // Call this periodically to update. private: // CFileChangeWatcher::ICallbacks.. virtual void OnFileChange( const char *pRelativeFilename, const char *pFullFilename ); private: CFileChangeWatcher m_Watcher; CUtlDict m_ChangedModels; }; static CStudioFileChangeWatcher g_StudioFileChangeWatcher; Vector g_lightvec; // light vector in model reference frame Vector g_blightvec[MAXSTUDIOBONES]; // light vectors in bone reference frames int g_ambientlight; // ambient world light float g_shadelight; // direct world light Vector g_lightcolor; bool g_bUpdateBones2D = true; //----------------------------------------------------------------------------- // Model meshes themselves are cached to avoid redundancy. There should never be // more than one copy of a given studio model in memory at once. //----------------------------------------------------------------------------- ModelCache_t CStudioModelCache::m_Cache[MAX_STUDIOMODELCACHE]; int CStudioModelCache::m_nItems = 0; //----------------------------------------------------------------------------- // Purpose: Find a model in the cache. Returns null if it's not in the cache. //----------------------------------------------------------------------------- StudioModel *CStudioModelCache::FindModel(const char *pszModelPath) { char testPath[MAX_PATH]; V_strncpy( testPath, pszModelPath, sizeof( testPath ) ); V_FixSlashes( testPath ); // // First look for the model in the cache. If it's there, increment the // reference count and return a pointer to the cached model. // for (int i = 0; i < m_nItems; i++) { char testPath2[MAX_PATH]; V_strncpy( testPath2, m_Cache[i].pszPath, sizeof( testPath2 ) ); V_FixSlashes( testPath2 ); if (!stricmp(testPath, testPath2)) { m_Cache[i].nRefCount++; return(m_Cache[i].pModel); } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Find all models in the cache with the same model name and reload them //----------------------------------------------------------------------------- void CStudioModelCache::ReloadModel( const char *pszModelPath ) { // Fix up our name to match what will be in the cache char testPath[MAX_PATH]; V_strncpy( testPath, pszModelPath, sizeof( testPath ) ); V_FixSlashes( testPath ); // Look through all models in the cache, updating them as we find them for ( int i = 0; i < m_nItems; i++ ) { char testPath2[MAX_PATH]; V_strncpy( testPath2, m_Cache[i].pszPath, sizeof( testPath2 ) ); V_FixSlashes( testPath2 ); // If it's a match, reload it if (!stricmp(testPath, testPath2)) { m_Cache[i].pModel->FreeModel(); m_Cache[i].pModel->LoadModel( pszModelPath ); } } } //----------------------------------------------------------------------------- // Purpose: Returns an instance of a particular studio model. If the model is // in the cache, a pointer to that model is returned. If not, a new one // is created and added to the cache. // Input : pszModelPath - Full path of the .MDL file. //----------------------------------------------------------------------------- StudioModel *CStudioModelCache::CreateModel(const char *pszModelPath) { // // If it isn't there, try to create one. // StudioModel *pModel = new StudioModel; if (pModel != NULL) { bool bLoaded = pModel->LoadModel(pszModelPath); if (bLoaded) { bLoaded = pModel->PostLoadModel(pszModelPath); } if (!bLoaded) { delete pModel; pModel = NULL; } } // // If we successfully created it, add it to the cache. // if (pModel != NULL) { CStudioModelCache::AddModel(pModel, pszModelPath); } return(pModel); } //----------------------------------------------------------------------------- // Purpose: Adds the model to the cache, setting the reference count to one. // Input : pModel - Model to add to the cache. // pszModelPath - The full path of the .MDL file, which is used as a // key in the model cache. // Output : Returns TRUE if the model was successfully added, FALSE if we ran // out of memory trying to add the model to the cache. //----------------------------------------------------------------------------- BOOL CStudioModelCache::AddModel(StudioModel *pModel, const char *pszModelPath) { if ( m_nItems < MAX_STUDIOMODELCACHE ) { // // Copy the model pointer. // m_Cache[m_nItems].pModel = pModel; // // Allocate space for and copy the model path. // m_Cache[m_nItems].pszPath = new char [strlen(pszModelPath) + 1]; if (m_Cache[m_nItems].pszPath != NULL) { strcpy(m_Cache[m_nItems].pszPath, pszModelPath); } else { return(FALSE); } m_Cache[m_nItems].nRefCount = 1; m_nItems++; return(TRUE); } return(FALSE); } //----------------------------------------------------------------------------- // Purpose: Advances the animation of all models in the cache for the given interval. // Input : flInterval - delta time in seconds. //----------------------------------------------------------------------------- void CStudioModelCache::AdvanceAnimation(float flInterval) { for (int i = 0; i < m_nItems; i++) { m_Cache[i].pModel->AdvanceFrame(flInterval); } } //----------------------------------------------------------------------------- // Purpose: Increments the reference count on a model in the cache. Called by // client code when a pointer to the model is copied, making that // reference independent. // Input : pModel - Model for which to increment the reference count. //----------------------------------------------------------------------------- void CStudioModelCache::AddRef(StudioModel *pModel) { for (int i = 0; i < m_nItems; i++) { if (m_Cache[i].pModel == pModel) { m_Cache[i].nRefCount++; return; } } } //----------------------------------------------------------------------------- // Purpose: Called by client code to release an instance of a model. If the // model's reference count is zero, the model is freed. // Input : pModel - Pointer to the model to release. //----------------------------------------------------------------------------- void CStudioModelCache::Release(StudioModel *pModel) { for (int i = 0; i < m_nItems; i++) { if (m_Cache[i].pModel == pModel) { m_Cache[i].nRefCount--; Assert(m_Cache[i].nRefCount >= 0); // // If this model is no longer referenced, free it and remove it // from the cache. // if (m_Cache[i].nRefCount <= 0) { // // Free the path, which was allocated by AddModel. // delete [] m_Cache[i].pszPath; delete m_Cache[i].pModel; // // Decrement the item count and copy the last element in the cache over // this element. // m_nItems--; m_Cache[i].pModel = m_Cache[m_nItems].pModel; m_Cache[i].pszPath = m_Cache[m_nItems].pszPath; m_Cache[i].nRefCount = m_Cache[m_nItems].nRefCount; } break; } } } //----------------------------------------------------------------------------- // Purpose: Watch for changes to studio models and reload them if necessary. //----------------------------------------------------------------------------- void CStudioFileChangeWatcher::Init() { m_Watcher.Init( this ); char searchPaths[1024 * 16]; if ( g_pFullFileSystem->GetSearchPath( "GAME", false, searchPaths, sizeof( searchPaths ) ) > 0 ) { CSplitString searchPathList( searchPaths, ";" ); for ( int i=0; i < searchPathList.Count(); i++ ) { m_Watcher.AddDirectory( searchPathList[i], "models", true ); } } else { Warning( "Error in GetSearchPath. Hammer will not automatically reload modified models." ); } } void CStudioFileChangeWatcher::OnFileChange( const char *pRelativeFilename, const char *pFullFilename ) { char relativeFilename[MAX_PATH]; V_ComposeFileName( "models", pRelativeFilename, relativeFilename, sizeof( relativeFilename ) ); V_FixSlashes( relativeFilename ); // Check the cache. const char *pExt = V_GetFileExtension( relativeFilename ); if ( !pExt ) return; if ( V_stricmp( pExt, "mdl" ) == 0 || V_stricmp( pExt, "vtx" ) == 0 || V_stricmp( pExt, "phy" ) == 0 || V_stricmp( pExt, "vvd" ) == 0 ) { // Ok, it's at least related to a model. Flush out the model. char tempFilename[MAX_PATH]; V_strncpy( tempFilename, relativeFilename, pExt - relativeFilename ); // Now it might have a "dx80" or "dx90" or some other extension. Get rid of that too. const char *pTestFilename = V_UnqualifiedFileName( tempFilename ); const char *pExt = V_GetFileExtension( pTestFilename ); char filename[MAX_PATH]; if ( pExt ) V_strncpy( filename, tempFilename, pExt - tempFilename ); else V_strncpy( filename, tempFilename, sizeof( filename ) ); // Now we've got the filename with any extension or "dx80"-type stuff at the end. V_strncat( filename, ".mdl", sizeof( filename ) ); // Queue up the list of changes because if they copied all the files for a model, // we'd like to only reload it once. if ( m_ChangedModels.Find( filename ) == m_ChangedModels.InvalidIndex() ) m_ChangedModels.Insert( filename ); } } void CStudioFileChangeWatcher::Update() { if ( !g_pMDLCache ) return; m_Watcher.Update(); if ( m_ChangedModels.Count() > 0 ) { // Reload whatever models were changed. for ( int i=m_ChangedModels.First(); i != m_ChangedModels.InvalidIndex(); i=m_ChangedModels.Next( i ) ) { const char *pName = m_ChangedModels.GetElementName( i ); MDLHandle_t hModel = g_pMDLCache->FindMDL( pName ); g_pMDLCache->Flush( hModel ); g_pMDLCache->ResetErrorModelStatus( hModel ); // Find all studio models and refresh their data CStudioModelCache::ReloadModel( pName ); } m_ChangedModels.Purge(); for ( int i=0; i < CMapDoc::GetDocumentCount(); i++ ) { CMapDoc *pDoc = CMapDoc::GetDocument( i ); pDoc->GetMapWorld()->CalcBounds( true ); } } } //----------------------------------------------------------------------------- // Purpose: Loads up the IStudioRender interface. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool StudioModel::Initialize() { return true; } void StudioModel::Shutdown( void ) { } //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- StudioModel::StudioModel(void) : m_pModelName(0) { int i; m_origin.Init(); m_angles.Init(); m_sequence = 0; m_cycle = 0; m_bodynum = 0; m_skinnum = 0; for (i = 0; i < sizeof(m_controller) / sizeof(m_controller[0]); i++) { m_controller[i] = 0; } for (i = 0; i < sizeof(m_poseParameter) / sizeof(m_poseParameter[0]); i++) { m_poseParameter[i] = 0; } m_mouth = 0; m_MDLHandle = MDLHANDLE_INVALID; m_pModel = NULL; m_pStudioHdr = NULL; m_pPosePos = NULL; m_pPoseAng = NULL; } //----------------------------------------------------------------------------- // Purpose: Destructor. Frees dynamically allocated data. //----------------------------------------------------------------------------- StudioModel::~StudioModel(void) { FreeModel(); if (m_pModelName) { delete[] m_pModelName; } delete m_pStudioHdr; delete []m_pPosePos; delete []m_pPoseAng; } //----------------------------------------------------------------------------- // Purpose: Sets the Euler angles for the model. // Input : fAngles - A pointer to engine PITCH, YAW, and ROLL angles. //----------------------------------------------------------------------------- void StudioModel::SetAngles(QAngle& pfAngles) { m_angles[PITCH] = pfAngles[PITCH]; m_angles[YAW] = pfAngles[YAW]; m_angles[ROLL] = pfAngles[ROLL]; } void StudioModel::AdvanceFrame( float dt ) { if (dt > 0.1) dt = 0.1f; CStudioHdr *pStudioHdr = GetStudioHdr(); float t = Studio_Duration( pStudioHdr, m_sequence, m_poseParameter ); if (t > 0) { m_cycle += dt / t; // wrap m_cycle -= (int)(m_cycle); } else { m_cycle = 0; } } void StudioModel::SetUpBones( bool bUpdatePose, matrix3x4a_t *pBoneToWorld ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if ( m_pPosePos == NULL ) { bUpdatePose = true; m_pPosePos = new Vector[pStudioHdr->numbones()] ; m_pPoseAng = new QuaternionAligned[pStudioHdr->numbones()]; } if ( bUpdatePose ) { IBoneSetup boneSetup( pStudioHdr, BONE_USED_BY_ANYTHING, m_poseParameter ); boneSetup.InitPose( m_pPosePos, m_pPoseAng ); boneSetup.AccumulatePose( m_pPosePos, m_pPoseAng, m_sequence, m_cycle, 1.0f, 0.0f, NULL ); } const mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); matrix3x4_t cameraTransform; AngleMatrix( m_angles, cameraTransform ); cameraTransform[0][3] = m_origin[0]; cameraTransform[1][3] = m_origin[1]; cameraTransform[2][3] = m_origin[2]; for (int i = 0; i < pStudioHdr->numbones(); i++) { if ( CalcProceduralBone( pStudioHdr, i, CBoneAccessor( pBoneToWorld ) )) continue; matrix3x4_t bonematrix; QuaternionMatrix( m_pPoseAng[i], bonematrix ); bonematrix[0][3] = m_pPosePos[i][0]; bonematrix[1][3] = m_pPosePos[i][1]; bonematrix[2][3] = m_pPosePos[i][2]; if (pbones[i].parent == -1) { ConcatTransforms( cameraTransform, bonematrix, pBoneToWorld[ i ] ); } else { ConcatTransforms ( pBoneToWorld[ pbones[i].parent ], bonematrix, pBoneToWorld[ i ] ); } } } /* ================= StudioModel::SetupModel based on the body part, figure out which mesh it should be using. inputs: currententity outputs: pstudiomesh pmdl ================= */ void StudioModel::SetupModel ( int bodypart ) { int index; CStudioHdr *pStudioHdr = GetStudioHdr(); if (bodypart > pStudioHdr->numbodyparts()) { // Con_DPrintf ("StudioModel::SetupModel: no such bodypart %d\n", bodypart); bodypart = 0; } mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( bodypart ); index = m_bodynum / pbodypart->base; index = index % pbodypart->nummodels; m_pModel = pbodypart->pModel( index ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void StudioModel::DrawModel3D( CRender3D *pRender, const Color &color, float flAlpha, bool bWireframe ) { studiohdr_t *pStudioHdr = GetStudioRenderHdr(); if (!pStudioHdr) return; if (pStudioHdr->numbodyparts == 0) return; CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); DrawModelInfo_t info; info.m_pStudioHdr = pStudioHdr; info.m_pHardwareData = GetHardwareData(); info.m_Decals = STUDIORENDER_DECAL_INVALID; info.m_Skin = m_skinnum; info.m_Body = m_bodynum; info.m_HitboxSet = 0; info.m_pClientEntity = NULL; info.m_Lod = -1; info.m_pColorMeshes = NULL; if ( pRender->IsInLocalTransformMode() ) { // WHACKY HACKY Vector orgOrigin = m_origin; QAngle orgAngles = m_angles; VMatrix matrix; pRender->GetLocalTranform(matrix); // baseclass rotates the origin matrix.V3Mul( orgOrigin, m_origin ); matrix3x4_t fCurrentMatrix,fMatrixNew; AngleMatrix(m_angles, fCurrentMatrix); ConcatTransforms(matrix.As3x4(), fCurrentMatrix, fMatrixNew); QAngle newAngles; MatrixAngles(fMatrixNew, m_angles); CMatRenderData< matrix3x4a_t > rdBoneToWorld( pRenderContext, GetStudioHdr()->numbones() ); SetUpBones( false, rdBoneToWorld.Base() ); pRender->DrawModel( &info, rdBoneToWorld.Base(), m_origin, flAlpha, bWireframe, color ); m_origin = orgOrigin; m_angles = orgAngles; } else { CMatRenderData< matrix3x4a_t > rdBoneToWorld( pRenderContext, GetStudioHdr()->numbones() ); SetUpBones( true, rdBoneToWorld.Base() ); pRender->DrawModel( &info, rdBoneToWorld.Base(), m_origin, flAlpha, bWireframe, color ); if ( Options.general.bShowCollisionModels ) { VMatrix mViewMatrix = SetupMatrixOrgAngles( m_origin, m_angles ); pRender->DrawCollisionModel( m_MDLHandle, mViewMatrix ); } } } void StudioModel::DrawModel2D( CRender2D *pRender, float flAlpha, bool bWireFrame ) { studiohdr_t *pStudioHdr = GetStudioRenderHdr(); if (!pStudioHdr) return; if (pStudioHdr->numbodyparts == 0) return; Vector orgOrigin = m_origin; QAngle orgAngles = m_angles; DrawModelInfo_t info; info.m_pStudioHdr = pStudioHdr; info.m_pHardwareData = GetHardwareData(); info.m_Decals = STUDIORENDER_DECAL_INVALID; info.m_Skin = m_skinnum; info.m_Body = m_bodynum; info.m_HitboxSet = 0; info.m_pClientEntity = NULL; info.m_Lod = -1; info.m_pColorMeshes = NULL; bool bTransform = pRender->IsInLocalTransformMode(); if ( bTransform ) { // WHACKY HACKY VMatrix matrix; pRender->GetLocalTranform(matrix); // baseclass rotates the origin matrix.V3Mul( orgOrigin, m_origin ); matrix3x4_t fCurrentMatrix,fMatrixNew; AngleMatrix(m_angles, fCurrentMatrix); ConcatTransforms(matrix.As3x4(), fCurrentMatrix, fMatrixNew); QAngle newAngles; MatrixAngles(fMatrixNew, m_angles); } if ( Options.general.bShowCollisionModels ) { VMatrix mViewMatrix = SetupMatrixOrgAngles( orgOrigin, orgAngles ); pRender->DrawCollisionModel( m_MDLHandle, mViewMatrix ); } else { CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); CMatRenderData< matrix3x4a_t > rdBoneToWorld( pRenderContext, GetStudioHdr()->numbones() ); SetUpBones( false, rdBoneToWorld.Base() ); pRender->DrawModel( &info, rdBoneToWorld.Base(), m_origin, flAlpha, bWireFrame ); } if ( bTransform ) { // restore original position and angles m_origin = orgOrigin; m_angles = orgAngles; } } //----------------------------------------------------------------------------- // It's translucent if any its materials are translucent //----------------------------------------------------------------------------- bool StudioModel::IsTranslucent() { // garymcthack - shouldn't crack hardwaredata studiohwdata_t *pHardwareData = GetHardwareData(); if ( pHardwareData == NULL ) return false; int lodID; for( lodID = pHardwareData->m_RootLOD; lodID < pHardwareData->m_NumLODs; lodID++ ) { for (int i = 0; i < pHardwareData->m_pLODs[lodID].numMaterials; ++i) { if (pHardwareData->m_pLODs[lodID].ppMaterials[i]->IsTranslucent()) return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Frees the model data and releases textures from OpenGL. //----------------------------------------------------------------------------- void StudioModel::FreeModel(void) { /*int nRef = */g_pMDLCache->Release( m_MDLHandle ); // Assert( nRef == 0 ); m_MDLHandle = MDLHANDLE_INVALID; m_pModel = NULL; } CStudioHdr *StudioModel::GetStudioHdr() const { // return g_pMDLCache->GetStudioHdr( m_MDLHandle ); if (m_pStudioHdr->IsValid()) return m_pStudioHdr; studiohdr_t *hdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); m_pStudioHdr->Init( hdr ); Assert(m_pStudioHdr->IsValid()); return m_pStudioHdr; } studiohdr_t *StudioModel::GetStudioRenderHdr() const { CStudioHdr *pStudioHdr = GetStudioHdr(); if (pStudioHdr) { return (studiohdr_t *)pStudioHdr->GetRenderHdr(); } return NULL; } studiohwdata_t* StudioModel::GetHardwareData() { return g_pMDLCache->GetHardwareData( m_MDLHandle ); } bool StudioModel::LoadModel( const char *modelname ) { // Load the MDL file data Assert( m_MDLHandle == MDLHANDLE_INVALID ); // for easier fall through cleanup m_MDLHandle = MDLHANDLE_INVALID; if ( !g_pStudioRender || !modelname ) return false; // In the case of restore, m_pModelName == modelname if (m_pModelName != modelname) { // Copy over the model name; we'll need it later... if (m_pModelName) { delete[] m_pModelName; } m_pModelName = new char[strlen(modelname) + 1]; strcpy( m_pModelName, modelname ); } m_MDLHandle = g_pMDLCache->FindMDL( modelname ); if (m_MDLHandle == MDLHANDLE_INVALID) return false; // Cache a bunch of stuff into memory g_pMDLCache->GetStudioHdr( m_MDLHandle ); g_pMDLCache->GetHardwareData( m_MDLHandle ); if (m_pStudioHdr) { delete m_pStudioHdr; m_pStudioHdr = NULL; } m_pStudioHdr = new CStudioHdr; return true; } bool StudioModel::PostLoadModel(const char *modelname) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (pStudioHdr == NULL) { return(false); } SetSequence (0); for (int n = 0; n < pStudioHdr->numbodyparts(); n++) { if (SetBodygroup (n, 0) < 0) { return false; } } SetSkin (0); /* Vector mins, maxs; ExtractBbox (mins, maxs); if (mins[2] < 5.0f) m_origin[2] = -mins[2]; */ return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int StudioModel::GetSequenceCount( void ) { CStudioHdr *pStudioHdr = GetStudioHdr(); return pStudioHdr->GetNumSeq(); } //----------------------------------------------------------------------------- // Purpose: // Input : nIndex - // szName - //----------------------------------------------------------------------------- void StudioModel::GetSequenceName( int nIndex, char *szName ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (nIndex < pStudioHdr->GetNumSeq()) { strcpy(szName, pStudioHdr->pSeqdesc(nIndex).pszLabel()); } } //----------------------------------------------------------------------------- // Purpose: Returns the index of the current sequence. //----------------------------------------------------------------------------- int StudioModel::GetSequence( ) { return m_sequence; } //----------------------------------------------------------------------------- // Purpose: Sets the current sequence by index. //----------------------------------------------------------------------------- int StudioModel::SetSequence( int iSequence ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (iSequence > pStudioHdr->GetNumSeq()) return m_sequence; m_sequence = iSequence; m_cycle = 0; return m_sequence; } //----------------------------------------------------------------------------- // Purpose: Rotates the given bounding box by the given angles and computes the // bounds of the rotated box. This is used to take the rotation angles // into consideration when returning the bounding box. Note that this // can produce a larger than optimal bounding box. // Input : Mins - // Maxs - // Angles - //----------------------------------------------------------------------------- void StudioModel::RotateBbox(Vector &Mins, Vector &Maxs, const QAngle &Angles) { Vector Points[8]; PointsFromBox( Mins, Maxs, Points ); // // Rotate the corner points by the specified angles, in the same // order that our Render code uses. // VMatrix mMatrix; mMatrix.SetupMatrixOrgAngles( vec3_origin, Angles ); matrix3x4_t fMatrix2 = mMatrix.As3x4(); Vector RotatedPoints[8]; for (int i = 0; i < 8; i++) { VectorRotate(Points[i], fMatrix2, RotatedPoints[i]); } // // Calculate the new mins and maxes. // for (int i = 0; i < 8; i++) { for (int nDim = 0; nDim < 3; nDim++) { if ((i == 0) || (RotatedPoints[i][nDim] < Mins[nDim])) { Mins[nDim] = RotatedPoints[i][nDim]; } if ((i == 0) || (RotatedPoints[i][nDim] > Maxs[nDim])) { Maxs[nDim] = RotatedPoints[i][nDim]; } } } } //----------------------------------------------------------------------------- // Purpose: // Input : mins - // maxs - //----------------------------------------------------------------------------- void StudioModel::ExtractBbox(Vector &mins, Vector &maxs) { CStudioHdr *pStudioHdr = GetStudioHdr(); mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( m_sequence ); mins = seqdesc.bbmin; maxs = seqdesc.bbmax; RotateBbox(mins, maxs, m_angles); } void StudioModel::ExtractClippingBbox( Vector& mins, Vector& maxs ) { studiohdr_t *pStudioHdr = GetStudioRenderHdr(); mins[0] = pStudioHdr->view_bbmin[0]; mins[1] = pStudioHdr->view_bbmin[1]; mins[2] = pStudioHdr->view_bbmin[2]; maxs[0] = pStudioHdr->view_bbmax[0]; maxs[1] = pStudioHdr->view_bbmax[1]; maxs[2] = pStudioHdr->view_bbmax[2]; } void StudioModel::ExtractMovementBbox( Vector& mins, Vector& maxs ) { studiohdr_t *pStudioHdr = GetStudioRenderHdr(); mins[0] = pStudioHdr->hull_min[0]; mins[1] = pStudioHdr->hull_min[1]; mins[2] = pStudioHdr->hull_min[2]; maxs[0] = pStudioHdr->hull_max[0]; maxs[1] = pStudioHdr->hull_max[1]; maxs[2] = pStudioHdr->hull_max[2]; } void StudioModel::GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed ) { CStudioHdr *pStudioHdr = GetStudioHdr(); float t = Studio_Duration( pStudioHdr, m_sequence, m_poseParameter ); if (t > 0) { *pflFrameRate = 1.0 / t; *pflGroundSpeed = 0; // sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); // *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); } else { *pflFrameRate = 1.0; *pflGroundSpeed = 0.0; } } void StudioModel::SetOrigin( float x, float y, float z ) { m_origin[0] = x; m_origin[1] = y; m_origin[2] = z; } int StudioModel::SetBodygroups( int iValue ) { m_bodynum = iValue; return m_bodynum; } void StudioModel::SetOrigin( const Vector &v ) { m_origin = v; } void StudioModel::GetOrigin( float &x, float &y, float &z ) { x = m_origin[0]; y = m_origin[1]; z = m_origin[2]; } void StudioModel::GetOrigin( Vector &v ) { v = m_origin; } int StudioModel::SetBodygroup( int iGroup, int iValue ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (!pStudioHdr) return 0; if (iGroup > pStudioHdr->numbodyparts()) return -1; mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( iGroup ); if ((pbodypart->base == 0) || (pbodypart->nummodels == 0)) { return -1; } int iCurrent = (m_bodynum / pbodypart->base) % pbodypart->nummodels; if (iValue >= pbodypart->nummodels) return iCurrent; m_bodynum = (m_bodynum - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); return iValue; } int StudioModel::SetSkin( int iValue ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (!pStudioHdr) return 0; if (iValue >= pStudioHdr->numskinfamilies()) { iValue = 0; } m_skinnum = iValue; return iValue; } const char *StudioModel::GetModelName( void ) { return m_pModelName; } void StudioModel::SetFrame( int nFrame ) { CStudioHdr *pStudioHdr = GetStudioHdr(); if ( !pStudioHdr ) return; if ( nFrame <= 0 ) nFrame = 0; int maxFrame = GetMaxFrame(); if ( nFrame >= maxFrame ) { nFrame = maxFrame; m_cycle = 0.99999; return; } m_cycle = nFrame / (float)maxFrame; return; } int StudioModel::GetMaxFrame( void ) { CStudioHdr *pStudioHdr = GetStudioHdr(); return Studio_MaxFrame( pStudioHdr, m_sequence, NULL ); } //----------------------------------------------------------------------------- // Purpose: // Input : pRender - //----------------------------------------------------------------------------- /*void StudioModel::DrawModel2D(CRender2D *pRender) { studiohdr_t *pStudioHdr = GetStudioRenderHdr(); CMapView2D *pView = (CMapView2D*) pRender->GetView(); DrawModelInfo_t info; ZeroMemory(&info, sizeof(info)); info.m_pStudioHdr = pStudioHdr; info.m_pHardwareData = GetHardwareData(); info.m_Decals = STUDIORENDER_DECAL_INVALID; info.m_Skin = m_skinnum; info.m_Body = m_bodynum; info.m_HitboxSet = 0; info.m_pClientEntity = NULL; info.m_Lod = -1; info.m_ppColorMeshes = NULL; if ( pView->m_fZoom < 3 ) info.m_Lod = 3; matrix3x4a_t *pBoneToWorld = SetUpBones( g_bUpdateBones2D ); GetTriangles_Output_t tris; g_pStudioRender->GetTriangles( info, tris ); for ( int batchID = 0; batchID < tris.m_MaterialBatches.Count(); batchID++ ) { GetTriangles_MaterialBatch_t &materialBatch = tris.m_MaterialBatches[batchID]; int numStrips = materialBatch.m_TriListIndices.Count() / 3; int numVertices = materialBatch.m_Verts.Count(); POINT *points = (POINT*)_alloca( sizeof(POINT) * numVertices ); // translate all vertices for ( int vertID = 0; vertID < numVertices; vertID++) { GetTriangles_Vertex_t &vert = materialBatch.m_Verts[vertID]; const Vector &pos = vert.m_Position; Vector newPos(0,0,0); for ( int k = 0; k < vert.m_NumBones; k++ ) { const matrix3x4_t &poseToWorld = tris.m_PoseToWorld[ vert.m_BoneIndex[k] ]; Vector tmp; VectorTransform( pos, poseToWorld, tmp ); newPos += vert.m_BoneWeight[k] * tmp; } pView->WorldToClient( points[vertID], newPos ); // pRender->TransformPoint3D( points[vertID], newPos ); } // Send the vertices down to the hardware. int stripIndex = 0; for ( int strip = 0; strip < numStrips; strip++ ) { int ptx[3]; int pty[3]; int numPoints = 0; POINT lastPt; lastPt.x = lastPt.y = -99999; for ( int i = 0; i<3; i++ ) { POINT pt = points[ materialBatch.m_TriListIndices[stripIndex++] ]; if ( pt.x == lastPt.x && pt.y == lastPt.y ) continue; ptx[numPoints] = pt.x; pty[numPoints] = pt.y; lastPt = pt; numPoints++; } // for performance sake bypass the renderer interface, buuuhhh if ( numPoints == 2 ) { g_pMatSystemSurface->DrawLine( ptx[0], pty[0], ptx[1], pty[1] ); } else if ( numPoints == 3 ) { g_pMatSystemSurface->DrawPolyLine( ptx, pty, 3 ); } } } } */ void InitStudioFileChangeWatcher() { g_StudioFileChangeWatcher.Init(); } void UpdateStudioFileChangeWatcher() { g_StudioFileChangeWatcher.Update(); }