You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1175 lines
29 KiB
1175 lines
29 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "stdafx.h" |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <malloc.h> |
|
#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 <VGuiMatSurface/IMatSystemSurface.h> |
|
#include "mapview2d.h" |
|
#include "mapdefs.h" |
|
#include "camera.h" |
|
#include "options.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
|
|
#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<int,int> 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[1024]; |
|
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: 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) |
|
{ |
|
StudioModel *pTest = FindModel( pszModelPath ); |
|
if ( pTest ) |
|
return pTest; |
|
|
|
// |
|
// 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) |
|
{ |
|
// |
|
// 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); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 ) |
|
{ |
|
CUtlVector<char*> searchPathList; |
|
V_SplitString( searchPaths, ";", searchPathList ); |
|
|
|
for ( int i=0; i < searchPathList.Count(); i++ ) |
|
{ |
|
m_Watcher.AddDirectory( searchPathList[i], "models", true ); |
|
} |
|
|
|
searchPathList.PurgeAndDeleteElements(); |
|
} |
|
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 ); |
|
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 ); |
|
|
|
// If we have it in the StudioModel cache, flush its data. |
|
StudioModel *pTest = CStudioModelCache::FindModel( pName ); |
|
if ( pTest ) |
|
{ |
|
pTest->FreeModel(); |
|
pTest->LoadModel( 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, matrix3x4_t *pBoneToWorld ) |
|
{ |
|
CStudioHdr *pStudioHdr = GetStudioHdr(); |
|
|
|
if ( m_pPosePos == NULL ) |
|
{ |
|
bUpdatePose = true; |
|
m_pPosePos = new Vector[pStudioHdr->numbones()] ; |
|
m_pPoseAng = new Quaternion[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 ); |
|
} |
|
|
|
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, 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); |
|
|
|
matrix3x4_t boneToWorld[MAXSTUDIOBONES]; |
|
SetUpBones( false, boneToWorld ); |
|
pRender->DrawModel( &info, boneToWorld, m_origin, flAlpha, bWireframe ); |
|
|
|
m_origin = orgOrigin; |
|
m_angles = orgAngles; |
|
} |
|
else |
|
{ |
|
matrix3x4_t boneToWorld[MAXSTUDIOBONES]; |
|
SetUpBones( true, boneToWorld ); |
|
pRender->DrawModel( &info, boneToWorld, m_origin, flAlpha, bWireframe ); |
|
|
|
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 |
|
{ |
|
matrix3x4_t boneToWorld[MAXSTUDIOBONES]; |
|
SetUpBones( false, boneToWorld ); |
|
pRender->DrawModel( &info, boneToWorld, m_origin, flAlpha, bWireFrame ); |
|
} |
|
|
|
|
|
if ( bTransform ) |
|
{ |
|
// restore original position and angles |
|
m_origin = orgOrigin; |
|
m_angles = orgAngles; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// It's translucent if all 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 false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
|
|
matrix3x4_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(); |
|
} |
|
|
|
|
|
|
|
|