//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include #include #include #include #include #include #include #include #include #include #include "materialsystem/imaterialsystem.h" #include "engine/ivmodelinfo.h" #include "c_sceneentity.h" #include "gamestringpool.h" #include "model_types.h" #include "view_shared.h" #include "view.h" #include "ivrenderview.h" #include "iefx.h" #include "dlight.h" #include "activitylist.h" #include "basemodelpanel.h" bool UseHWMorphModels(); using namespace vgui; DECLARE_BUILD_FACTORY( CModelPanel ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CModelPanel::CModelPanel( vgui::Panel *pParent, const char *pName ) : vgui::EditablePanel( pParent, pName ) { m_nFOV = 54; m_hModel = NULL; m_pModelInfo = NULL; m_hScene = NULL; m_iDefaultAnimation = 0; m_bPanelDirty = true; m_bStartFramed = false; m_bAllowOffscreen = false; ListenForGameEvent( "game_newmap" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CModelPanel::~CModelPanel() { if ( m_pModelInfo ) { delete m_pModelInfo; m_pModelInfo = NULL; } DeleteVCDData(); DeleteModelData(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); m_nFOV = inResourceData->GetInt( "fov", 54 ); m_bStartFramed = inResourceData->GetInt( "start_framed", false ); m_bAllowOffscreen = inResourceData->GetInt( "allow_offscreen", false ); // do we have a valid "model" section in the .res file? for ( KeyValues *pData = inResourceData->GetFirstSubKey() ; pData != NULL ; pData = pData->GetNextKey() ) { if ( !Q_stricmp( pData->GetName(), "model" ) ) { ParseModelInfo( pData ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::OnCommand( const char *command ) { if (!Q_strnicmp("animation", command, 9)) { UpdateModel(); SetSequence( command + 9 + 1 ); return; } BaseClass::OnCommand(command); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::ParseModelInfo( KeyValues *inResourceData ) { // delete any current info if ( m_pModelInfo ) { delete m_pModelInfo; m_pModelInfo = NULL; } m_pModelInfo = new CModelPanelModelInfo; if ( !m_pModelInfo ) return; m_pModelInfo->m_pszModelName = ReadAndAllocStringValue( inResourceData, "modelname" ); m_pModelInfo->m_pszModelName_HWM = ReadAndAllocStringValue( inResourceData, "modelname_hwm" ); m_pModelInfo->m_nSkin = inResourceData->GetInt( "skin", -1 ); m_pModelInfo->m_vecAbsAngles.Init( inResourceData->GetFloat( "angles_x", 0.0 ), inResourceData->GetFloat( "angles_y", 0.0 ), inResourceData->GetFloat( "angles_z", 0.0 ) ); m_pModelInfo->m_vecOriginOffset.Init( inResourceData->GetFloat( "origin_x", 110.0 ), inResourceData->GetFloat( "origin_y", 5.0 ), inResourceData->GetFloat( "origin_z", 5.0 ) ); m_pModelInfo->m_vecFramedOriginOffset.Init( inResourceData->GetFloat( "frame_origin_x", 110.0 ), inResourceData->GetFloat( "frame_origin_y", 5.0 ), inResourceData->GetFloat( "frame_origin_z", 5.0 ) ); m_pModelInfo->m_pszVCD = ReadAndAllocStringValue( inResourceData, "vcd" ); m_pModelInfo->m_bUseSpotlight = ( inResourceData->GetInt( "spotlight", 0 ) == 1 ); m_pModelInfo->m_vecViewportOffset.Init(); for ( KeyValues *pData = inResourceData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) { if ( !Q_stricmp( pData->GetName(), "animation" ) ) { OnAddAnimation( pData ); } else if ( !Q_stricmp( pData->GetName(), "attached_model" ) ) { CModelPanelAttachedModelInfo *pAttachedModelInfo = new CModelPanelAttachedModelInfo; if ( pAttachedModelInfo ) { pAttachedModelInfo->m_pszModelName = ReadAndAllocStringValue( pData, "modelname" ); pAttachedModelInfo->m_nSkin = pData->GetInt( "skin", -1 ); m_pModelInfo->m_AttachedModelsInfo.AddToTail( pAttachedModelInfo ); } } } m_bPanelDirty = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::OnAddAnimation( KeyValues *pData ) { if ( !pData ) return; CModelPanelModelAnimation *pAnimation = new CModelPanelModelAnimation; if ( pAnimation ) { pAnimation->m_pszName = ReadAndAllocStringValue( pData, "name" ); pAnimation->m_pszSequence = ReadAndAllocStringValue( pData, "sequence" ); pAnimation->m_pszActivity = ReadAndAllocStringValue( pData, "activity" ); pAnimation->m_bDefault = ( pData->GetInt( "default", 0 ) == 1 ); for ( KeyValues *pAnimData = pData->GetFirstSubKey(); pAnimData != NULL; pAnimData = pAnimData->GetNextKey() ) { if ( !Q_stricmp( pAnimData->GetName(), "pose_parameters" ) ) { pAnimation->m_pPoseParameters = pAnimData->MakeCopy(); } } m_pModelInfo->m_Animations.AddToTail( pAnimation ); if ( pAnimation->m_bDefault ) { m_iDefaultAnimation = m_pModelInfo->m_Animations.Find( pAnimation ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::FireGameEvent( IGameEvent * event ) { const char *type = event->GetName(); if ( Q_strcmp( type, "game_newmap" ) == 0 ) { // force the models to re-setup themselves m_bPanelDirty = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::SetDefaultAnimation( const char *pszName ) { if ( m_pModelInfo ) { for ( int i = 0; i < m_pModelInfo->m_Animations.Count(); i++ ) { if ( m_pModelInfo->m_Animations[i] && m_pModelInfo->m_Animations[i]->m_pszName ) { if ( !Q_stricmp( m_pModelInfo->m_Animations[i]->m_pszName, pszName ) ) { m_iDefaultAnimation = i; return; } } } } Assert( 0 ); } //----------------------------------------------------------------------------- // Purpose: Replaces the current model with a new one, without changing the camera settings //----------------------------------------------------------------------------- void CModelPanel::SwapModel( const char *pszName, const char *pszAttached ) { if ( !m_pModelInfo || !pszName || !pszName[0] ) return; int len = Q_strlen( pszName ) + 1; char *pAlloced = new char[ len ]; Assert( pAlloced ); Q_strncpy( pAlloced, pszName, len ); m_pModelInfo->m_pszModelName = pAlloced; ClearAttachedModelInfos(); if ( pszAttached ) { CModelPanelAttachedModelInfo *pAttachedModelInfo = new CModelPanelAttachedModelInfo; if ( pAttachedModelInfo ) { len = Q_strlen( pszAttached ) + 1; pAlloced = new char[ len ]; Assert( pAlloced ); Q_strncpy( pAlloced, pszAttached, len ); pAttachedModelInfo->m_pszModelName = pAlloced; pAttachedModelInfo->m_nSkin = 0; m_pModelInfo->m_AttachedModelsInfo.AddToTail( pAttachedModelInfo ); } } m_bPanelDirty = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::DeleteVCDData( void ) { if ( m_hScene.Get() ) { m_hScene->StopClientOnlyScene(); m_hScene->Remove(); m_hScene = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::SetupVCD( void ) { if ( !m_pModelInfo ) return; DeleteVCDData(); C_SceneEntity *pEnt = new class C_SceneEntity; if ( !pEnt ) return; if ( pEnt->InitializeAsClientEntity( "", RENDER_GROUP_OTHER ) == false ) { // we failed to initialize this entity so just return gracefully pEnt->Remove(); return; } // setup the handle m_hScene = pEnt; // setup the scene pEnt->SetupClientOnlyScene( m_pModelInfo->m_pszVCD, m_hModel, true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::ClearAttachedModelInfos( void ) { if ( m_pModelInfo ) { m_pModelInfo->m_AttachedModelsInfo.PurgeAndDeleteElements(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::DeleteModelData( void ) { if ( m_hModel.Get() ) { m_hModel->Remove(); m_hModel = NULL; m_flFrameDistance = 0; } for ( int i = 0 ; i < m_AttachedModels.Count() ; i++ ) { if ( m_AttachedModels[i].Get() ) { m_AttachedModels[i]->Remove(); } m_AttachedModels.Remove( i ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CModelPanel::GetModelName( void ) { if ( !m_pModelInfo ) return NULL; // check to see if we want to use a HWM model if ( UseHWMorphModels() ) { // do we have a valid HWM model filename if ( m_pModelInfo->m_pszModelName_HWM && ( Q_strlen( m_pModelInfo->m_pszModelName_HWM ) > 0 ) ) { // does the file exist model_t *pModel = (model_t *)engine->LoadModel( m_pModelInfo->m_pszModelName_HWM ); if ( pModel ) { return m_pModelInfo->m_pszModelName_HWM; } } } return m_pModelInfo->m_pszModelName; } void CModelPanel::SetBodyGroup( const char* pszBodyGroupName, int nGroup ) { if ( !m_pModelInfo ) return; if ( !m_hModel.Get() ) return; int nBodyGroupNum = m_hModel->FindBodygroupByName( pszBodyGroupName ); if ( nBodyGroupNum == -1 ) return; m_pModelInfo->m_mapBodygroupValues.InsertOrReplace( nBodyGroupNum, nGroup ); m_bPanelDirty = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::SetupModel( void ) { if ( !m_pModelInfo ) return; MDLCACHE_CRITICAL_SECTION(); // remove any current models we're using DeleteModelData(); const char *pszModelName = GetModelName(); if ( !pszModelName || !pszModelName[0] ) return; // create the new model CModelPanelModel *pEnt = new CModelPanelModel; if ( !pEnt ) return; if ( pEnt->InitializeAsClientEntity( pszModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) { // we failed to initialize this entity so just return gracefully pEnt->Remove(); return; } // setup the handle m_hModel = pEnt; pEnt->DontRecordInTools(); pEnt->AddEffects( EF_NODRAW ); // don't let the renderer draw the model normally if ( m_pModelInfo->m_nSkin >= 0 ) { pEnt->m_nSkin = m_pModelInfo->m_nSkin; } FOR_EACH_MAP_FAST( m_pModelInfo->m_mapBodygroupValues, i ) { pEnt->SetBodygroup( m_pModelInfo->m_mapBodygroupValues.Key( i ), m_pModelInfo->m_mapBodygroupValues[ i ] ); } // do we have any animation information? if ( m_pModelInfo->m_Animations.Count() > 0 && m_pModelInfo->m_Animations.IsValidIndex( m_iDefaultAnimation ) ) { CModelPanelModelAnimation *pAnim = m_pModelInfo->m_Animations[ m_iDefaultAnimation ]; int sequence = ACT_INVALID; if ( pAnim->m_pszActivity && pAnim->m_pszActivity[0] ) { Activity activity = (Activity)ActivityList_IndexForName( pAnim->m_pszActivity ); sequence = pEnt->SelectWeightedSequence( activity ); } else if ( pAnim->m_pszSequence && pAnim->m_pszSequence[0] ) { sequence = pEnt->LookupSequence( pAnim->m_pszSequence ); } if ( sequence != ACT_INVALID ) { pEnt->ResetSequence( sequence ); pEnt->SetCycle( 0 ); if ( pAnim->m_pPoseParameters ) { for ( KeyValues *pData = pAnim->m_pPoseParameters->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) { const char *pName = pData->GetName(); float flValue = pData->GetFloat(); pEnt->SetPoseParameter( pName, flValue ); } } pEnt->m_flAnimTime = gpGlobals->curtime; } } // setup any attached models for ( int i = 0 ; i < m_pModelInfo->m_AttachedModelsInfo.Count() ; i++ ) { CModelPanelAttachedModelInfo *pInfo = m_pModelInfo->m_AttachedModelsInfo[i]; C_BaseAnimating *pTemp = new C_BaseAnimating; if ( pTemp ) { if ( pTemp->InitializeAsClientEntity( pInfo->m_pszModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) { // we failed to initialize this model so just skip it pTemp->Remove(); continue; } pTemp->DontRecordInTools(); pTemp->AddEffects( EF_NODRAW ); // don't let the renderer draw the model normally pTemp->FollowEntity( m_hModel.Get() ); // attach to parent model if ( pInfo->m_nSkin >= 0 ) { pTemp->m_nSkin = pInfo->m_nSkin; } pTemp->m_flAnimTime = gpGlobals->curtime; m_AttachedModels.AddToTail( pTemp ); } } CalculateFrameDistance(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::InitCubeMaps() { ITexture *pCubemapTexture; // Deal with the default cubemap if ( g_pMaterialSystemHardwareConfig->GetHDREnabled() ) { pCubemapTexture = materials->FindTexture( "editor/cubemap.hdr", NULL, true ); m_DefaultHDREnvCubemap.Init( pCubemapTexture ); } else { pCubemapTexture = materials->FindTexture( "editor/cubemap", NULL, true ); m_DefaultEnvCubemap.Init( pCubemapTexture ); } } //----------------------------------------------------------------------------- // Purpose: If the panel is marked as dirty, update it and mark it as clean //----------------------------------------------------------------------------- void CModelPanel::UpdateModel() { if ( m_bPanelDirty ) { InitCubeMaps(); SetupModel(); // are we trying to play a VCD? if ( Q_strlen( m_pModelInfo->m_pszVCD ) > 0 ) { SetupVCD(); } m_bPanelDirty = false; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::Paint() { BaseClass::Paint(); C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if ( !pLocalPlayer || !m_pModelInfo ) return; MDLCACHE_CRITICAL_SECTION(); UpdateModel(); if ( !m_hModel.Get() ) return; int i = 0; int x, y, w, h; GetBounds( x, y, w, h ); ParentLocalToScreen( x, y ); if ( !m_bAllowOffscreen && x < 0 ) { // prevent x from being pushed off the left side of the screen // for modes like 1280 x 1024 (prevents model from being drawn in the panel) x = 0; } Vector vecExtraModelOffset( 0, 0, 0 ); float flWidthRatio = ((float)w / (float)h ) / ( 4.0f / 3.0f ); // is this a player model? if ( Q_strstr( GetModelName(), "models/player/" ) ) { // need to know if the ratio is not 4/3 // HACK! HACK! to get our player models to appear the way they do in 4/3 if we're using other aspect ratios if ( flWidthRatio > 1.05f ) { vecExtraModelOffset.Init( -60, 0, 0 ); } else if ( flWidthRatio < 0.95f ) { vecExtraModelOffset.Init( 15, 0, 0 ); } } m_hModel->SetAbsOrigin( m_pModelInfo->m_vecOriginOffset + vecExtraModelOffset ); m_hModel->SetAbsAngles( QAngle( m_pModelInfo->m_vecAbsAngles.x, m_pModelInfo->m_vecAbsAngles.y, m_pModelInfo->m_vecAbsAngles.z ) ); // do we have a valid sequence? if ( m_hModel->GetSequence() != -1 ) { m_hModel->FrameAdvance( gpGlobals->frametime ); } CMatRenderContextPtr pRenderContext( materials ); // figure out what our viewport is right now int viewportX, viewportY, viewportWidth, viewportHeight; pRenderContext->GetViewport( viewportX, viewportY, viewportWidth, viewportHeight ); // Now draw it. CViewSetup view; view.x = x + m_pModelInfo->m_vecViewportOffset.x + viewportX; // we actually want to offset by the view.y = y + m_pModelInfo->m_vecViewportOffset.y + viewportY; // viewport origin here because Push3DView expects global coords below view.width = w; view.height = h; view.m_bOrtho = false; // scale the FOV for aspect ratios other than 4/3 view.fov = ScaleFOVByWidthRatio( m_nFOV, flWidthRatio ); view.origin = vec3_origin; view.angles.Init(); view.zNear = VIEW_NEARZ; view.zFar = 1000; // Not supported by queued material system - doesn't appear to be necessary // ITexture *pLocalCube = pRenderContext->GetLocalCubemap(); if ( g_pMaterialSystemHardwareConfig->GetHDREnabled() ) { pRenderContext->BindLocalCubemap( m_DefaultHDREnvCubemap ); } else { pRenderContext->BindLocalCubemap( m_DefaultEnvCubemap ); } pRenderContext->SetLightingOrigin( vec3_origin ); pRenderContext->SetAmbientLight( 0.4, 0.4, 0.4 ); static Vector white[6] = { Vector( 0.4, 0.4, 0.4 ), Vector( 0.4, 0.4, 0.4 ), Vector( 0.4, 0.4, 0.4 ), Vector( 0.4, 0.4, 0.4 ), Vector( 0.4, 0.4, 0.4 ), Vector( 0.4, 0.4, 0.4 ), }; g_pStudioRender->SetAmbientLightColors( white ); g_pStudioRender->SetLocalLights( 0, NULL ); if ( m_pModelInfo->m_bUseSpotlight ) { Vector vecMins, vecMaxs; m_hModel->GetRenderBounds( vecMins, vecMaxs ); LightDesc_t spotLight( vec3_origin + Vector( 0, 0, 200 ), Vector( 1, 1, 1 ), m_hModel->GetAbsOrigin() + Vector( 0, 0, ( vecMaxs.z - vecMins.z ) * 0.75 ), 0.035, 0.873 ); g_pStudioRender->SetLocalLights( 1, &spotLight ); } Frustum dummyFrustum; render->Push3DView( view, 0, NULL, dummyFrustum ); modelrender->SuppressEngineLighting( true ); float color[3] = { 1.0f, 1.0f, 1.0f }; render->SetColorModulation( color ); render->SetBlend( 1.0f ); m_hModel->DrawModel( STUDIO_RENDER ); for ( i = 0 ; i < m_AttachedModels.Count() ; i++ ) { if ( m_AttachedModels[i].Get() ) { m_AttachedModels[i]->DrawModel( STUDIO_RENDER ); } } modelrender->SuppressEngineLighting( false ); render->PopView( dummyFrustum ); pRenderContext->BindLocalCubemap( NULL ); /* vgui::surface()->DrawSetColor( Color(0,0,0,255) ); vgui::surface()->DrawOutlinedRect( 0,0, GetWide(), GetTall() ); */ } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CModelPanel::FindAnimByName( const char *pszName ) { // first try to find the sequence using pszName as the friendly name for ( int iIndex = 0 ; iIndex < m_pModelInfo->m_Animations.Count() ; iIndex++ ) { CModelPanelModelAnimation *pAnimation = m_pModelInfo->m_Animations[ iIndex ]; if ( FStrEq( pAnimation->m_pszName, pszName ) ) return iIndex; } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CModelPanel::SetSequence( const char *pszName ) { bool bRetVal = false; const char *pszAnim = NULL; MDLCACHE_CRITICAL_SECTION(); if ( m_pModelInfo ) { int iIndex = FindAnimByName(pszName); if ( iIndex != -1 ) { pszAnim = m_pModelInfo->m_Animations[iIndex]->m_pszSequence; } else { // if not, just use the passed name as the sequence pszAnim = pszName; } if ( m_hModel.Get() ) { int sequence = m_hModel->LookupSequence( pszAnim ); if ( sequence != ACT_INVALID ) { m_hModel->ResetSequence( sequence ); m_hModel->SetCycle( 0 ); bRetVal = true; } } } return bRetVal; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::SetSkin( int nSkin ) { if ( m_pModelInfo ) { m_pModelInfo->m_nSkin = nSkin; m_bPanelDirty = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CModelPanel::OnSetAnimation( KeyValues *data ) { UpdateModel(); // If there's no model, these commands will be ignored. Assert(m_hModel); if ( data ) { const char *pszAnimation = data->GetString( "animation", "" ); const char *pszActivity = data->GetString( "activity", "" ); if ( pszActivity && pszActivity[0] ) { if ( m_hModel ) { int iIndex = FindAnimByName(pszActivity); if ( iIndex != -1 ) { pszActivity = m_pModelInfo->m_Animations[iIndex]->m_pszActivity; } Activity activity = (Activity)ActivityList_IndexForName( pszActivity ); int sequence = m_hModel->SelectWeightedSequence( activity ); if ( sequence != ACT_INVALID ) { m_hModel->ResetSequence( sequence ); m_hModel->SetCycle( 0 ); } } } else { SetSequence( pszAnimation ); } } } void CModelPanel::CalculateFrameDistanceInternal( const model_t *pModel ) { // Get the model space render bounds. Vector vecMin, vecMax; modelinfo->GetModelRenderBounds( pModel, vecMin, vecMax ); Vector vecCenter = ( vecMax + vecMin ) * 0.5f; vecMin -= vecCenter; vecMax -= vecCenter; // Get the bounds points and transform them by the desired model panel rotation. Vector aBoundsPoints[8]; aBoundsPoints[0].Init( vecMax.x, vecMax.y, vecMax.z ); aBoundsPoints[1].Init( vecMin.x, vecMax.y, vecMax.z ); aBoundsPoints[2].Init( vecMax.x, vecMin.y, vecMax.z ); aBoundsPoints[3].Init( vecMin.x, vecMin.y, vecMax.z ); aBoundsPoints[4].Init( vecMax.x, vecMax.y, vecMin.z ); aBoundsPoints[5].Init( vecMin.x, vecMax.y, vecMin.z ); aBoundsPoints[6].Init( vecMax.x, vecMin.y, vecMin.z ); aBoundsPoints[7].Init( vecMin.x, vecMin.y, vecMin.z ); // Translated center point (offset from camera center). Vector vecTranslateCenter = -vecCenter; // Build the rotation matrix. QAngle angPanelAngles( m_pModelInfo->m_vecAbsAngles.x, m_pModelInfo->m_vecAbsAngles.y, m_pModelInfo->m_vecAbsAngles.z ); matrix3x4_t matRotation; AngleMatrix( angPanelAngles, matRotation ); Vector aXFormPoints[8]; for ( int iPoint = 0; iPoint < 8; ++iPoint ) { VectorTransform( aBoundsPoints[iPoint], matRotation, aXFormPoints[iPoint] ); } Vector vecXFormCenter; VectorTransform( -vecTranslateCenter, matRotation, vecXFormCenter ); int w, h; GetSize( w, h ); float flW = (float)w; float flH = (float)h; float flFOVx = DEG2RAD( m_nFOV * 0.5f ); float flFOVy = CalcFovY( ( m_nFOV * 0.5f ), flW/flH ); flFOVy = DEG2RAD( flFOVy ); float flTanFOVx = tan( flFOVx ); float flTanFOVy = tan( flFOVy ); // Find the max value of x, y, or z float flDist = 0.0f; for ( int iPoint = 0; iPoint < 8; ++iPoint ) { float flDistZ = fabs( aXFormPoints[iPoint].z / flTanFOVy - aXFormPoints[iPoint].x ); float flDistY = fabs( aXFormPoints[iPoint].y / flTanFOVx - aXFormPoints[iPoint].x ); float flTestDist = MAX( flDistZ, flDistY ); flDist = MAX( flDist, flTestDist ); } // Scale the object down by 10%. flDist *= 1.10f; // Add the framing offset. vecXFormCenter += m_pModelInfo->m_vecFramedOriginOffset; // Zoom to the frame distance m_pModelInfo->m_vecOriginOffset.x = flDist - vecXFormCenter.x; m_pModelInfo->m_vecOriginOffset.y = -vecXFormCenter.y; m_pModelInfo->m_vecOriginOffset.z = -vecXFormCenter.z; // Screen space points. Vector2D aScreenPoints[8]; Vector aCameraPoints[8]; for ( int iPoint = 0; iPoint < 8; ++iPoint ) { aCameraPoints[iPoint] = aXFormPoints[iPoint]; aCameraPoints[iPoint].x += flDist; aScreenPoints[iPoint].x = aCameraPoints[iPoint].y / ( flTanFOVx * aCameraPoints[iPoint].x ); aScreenPoints[iPoint].y = aCameraPoints[iPoint].z / ( flTanFOVy * aCameraPoints[iPoint].x ); aScreenPoints[iPoint].x = ( aScreenPoints[iPoint].x * 0.5f + 0.5f ) * flW; aScreenPoints[iPoint].y = ( aScreenPoints[iPoint].y * 0.5f + 0.5f ) * flH; } // Find the min/max and center of the 2D bounding box of the object. Vector2D vecScreenMin( 99999.0f, 99999.0f ), vecScreenMax( -99999.0f, -99999.0f ); for ( int iPoint = 0; iPoint < 8; ++iPoint ) { vecScreenMin.x = MIN( vecScreenMin.x, aScreenPoints[iPoint].x ); vecScreenMin.y = MIN( vecScreenMin.y, aScreenPoints[iPoint].y ); vecScreenMax.x = MAX( vecScreenMax.x, aScreenPoints[iPoint].x ); vecScreenMax.y = MAX( vecScreenMax.y, aScreenPoints[iPoint].y ); } vecScreenMin.x = clamp( vecScreenMin.x, 0.0f, flW ); vecScreenMin.y = clamp( vecScreenMin.y, 0.0f, flH ); vecScreenMax.x = clamp( vecScreenMax.x, 0.0f, flW ); vecScreenMax.y = clamp( vecScreenMax.y, 0.0f, flH ); // Offset the view port based on the calculated model 2D center and the center of the viewport. Vector2D vecScreenCenter = ( vecScreenMax + vecScreenMin ) * 0.5f; m_pModelInfo->m_vecViewportOffset.x = -( ( flW * 0.5f ) - vecScreenCenter.x ); m_pModelInfo->m_vecViewportOffset.y = -( ( flH * 0.5f ) - vecScreenCenter.y ); } //----------------------------------------------------------------------------- // Purpose: Calculates the distance the camera should be at to frame the model on the screen. //----------------------------------------------------------------------------- void CModelPanel::CalculateFrameDistance( void ) { m_flFrameDistance = 0; if ( !m_hModel ) return; // Compute a bounding radius for the model const model_t *mod = modelinfo->GetModel( m_hModel->GetModelIndex() ); if ( !mod ) return; if ( m_bStartFramed ) { CalculateFrameDistanceInternal( mod ); } } //----------------------------------------------------------------------------- // Purpose: Moves the camera forward/backward along the current view angle to // frame the model on the screen. //----------------------------------------------------------------------------- void CModelPanel::ZoomToFrameDistance( void ) { if ( !m_flFrameDistance || !m_hModel ) return; const model_t *mod = modelinfo->GetModel( m_hModel->GetModelIndex() ); if ( !mod ) return; // Move the model to the midpoint Vector mins, maxs, vecModelCenter; modelinfo->GetModelRenderBounds( mod, mins, maxs ); VectorLerp( mins, maxs, 0.5f, vecModelCenter ); vecModelCenter += m_pModelInfo->m_vecFramedOriginOffset; // Zoom to the frame distance m_pModelInfo->m_vecOriginOffset.x = m_flFrameDistance; m_pModelInfo->m_vecOriginOffset.y = -vecModelCenter.y; m_pModelInfo->m_vecOriginOffset.z = -vecModelCenter.z; }