VGUI Screen Creation: Difference between revisions
Jump to navigation
Jump to search
Note:This section assumes a mod where VGUI screens have been fixed (see the code modifications section below) but otherwise left the same. VGUI screens do not work in HL2 or HL2:DM; they crash the game. (CS:S not tested.)
Line 37: | Line 37: | ||
==VGUI code modifications== | ==VGUI code modifications== | ||
There is a problem with VGUI screens receiving input. Unless it is fixed, the game | There is a problem with VGUI screens receiving input. Unless it is fixed, the game will crash when the cursor points at the screen. | ||
< | <pre>//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// | ||
// | |||
// Purpose: | |||
// | |||
// $NoKeywords: $ | |||
//=============================================================================// | |||
#include "cbase.h" | |||
#include "networkstringtable_clientdll.h" | |||
#include <KeyValues.h> | |||
#include "PanelMetaClassMgr.h" | |||
#include <vgui_controls/Controls.h> | |||
#include "VMatrix.h" | |||
#include "VGUIMatSurface/IMatSystemSurface.h" | |||
#include "view.h" | |||
#include "CollisionUtils.h" | |||
#include <vgui/IInput.h> | |||
#include <vgui/IPanel.h> | |||
#include <vgui/IVGui.h> | |||
#include "ienginevgui.h" | |||
#include <vgui/Mousecode.h> | |||
#include "materialsystem/IMesh.h" | |||
#include "ClientEffectPrecacheSystem.h" | |||
#include "C_VGuiScreen.h" | |||
#include "IClientMode.h" | |||
#include "vgui_bitmapbutton.h" | |||
#include "vgui_bitmappanel.h" | |||
#include "filesystem.h" | |||
#include <vgui/IInputInternal.h> | |||
extern vgui::IInputInternal *g_InputInternal; | |||
// memdbgon must be the last include file in a .cpp file!!! | |||
#include "tier0/memdbgon.h" | |||
#define VGUI_SCREEN_MODE_RADIUS 80 | |||
//Precache the materials | |||
CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectVGuiScreen ) | |||
CLIENTEFFECT_MATERIAL( "engine/writez" ) | |||
CLIENTEFFECT_REGISTER_END() | |||
using namespace vgui; | |||
// ----------------------------------------------------------------------------- // | |||
// This is a cache of preloaded keyvalues. | |||
// ----------------------------------------------------------------------------- // | |||
CUtlDict<KeyValues*, int> g_KeyValuesCache; | |||
KeyValues* CacheKeyValuesForFile( const char *pFilename ) | |||
{ | |||
int i = g_KeyValuesCache.Find( pFilename ); | |||
if ( i == g_KeyValuesCache.InvalidIndex() ) | |||
{ | |||
KeyValues *rDat = new KeyValues( pFilename ); | |||
rDat->LoadFromFile( vgui::filesystem(), pFilename, NULL ); | |||
g_KeyValuesCache.Insert( pFilename, rDat ); | |||
return rDat; | |||
} | |||
else | |||
{ | |||
return g_KeyValuesCache[i]; | |||
} | |||
} | |||
void ClearKeyValuesCache() | |||
{ | |||
for ( int i=g_KeyValuesCache.First(); i != g_KeyValuesCache.InvalidIndex(); i=g_KeyValuesCache.Next( i ) ) | |||
{ | |||
g_KeyValuesCache[i]->deleteThis(); | |||
} | |||
g_KeyValuesCache.Purge(); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: | |||
//----------------------------------------------------------------------------- | |||
class C_VGuiScreen : public C_BaseEntity | |||
{ | |||
DECLARE_CLASS( C_VGuiScreen, C_BaseEntity ); | |||
public: | |||
DECLARE_CLIENTCLASS(); | |||
C_VGuiScreen(); | |||
virtual void PreDataUpdate( DataUpdateType_t updateType ); | |||
virtual void OnDataChanged( DataUpdateType_t type ); | |||
virtual int DrawModel( int flags ); | |||
virtual bool ShouldDraw() { return !IsEffectActive(EF_NODRAW); } | |||
virtual void ClientThink( ); | |||
virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ); | |||
const char *PanelName() const; | |||
// The view screen has the cursor pointing at it | |||
void GainFocus( ); | |||
void LoseFocus(); | |||
// Is the screen backfaced given a view position? | |||
bool IsBackfacing( const Vector &viewOrigin ); | |||
// Return intersection point of ray with screen in barycentric coords | |||
bool IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ); | |||
// Is the screen turned on? | |||
bool IsActive() const; | |||
// Are we only visible to teammates? | |||
bool IsVisibleOnlyToTeammates() const; | |||
// Are we visible to someone on this team? | |||
bool IsVisibleToTeam( int nTeam ); | |||
bool IsAttachedToViewModel() const; | |||
virtual RenderGroup_t GetRenderGroup(); | |||
bool AcceptsInput() const; | |||
void SetAcceptsInput( bool acceptsinput ); | |||
private: | |||
// Vgui screen management | |||
void CreateVguiScreen( const char *pTypeName ); | |||
void DestroyVguiScreen( ); | |||
// Computes the panel to world transform | |||
void ComputePanelToWorld(); | |||
// Computes control points of the quad describing the screen | |||
void ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ); | |||
// Writes the z buffer | |||
void DrawScreenOverlay(); | |||
private: | |||
int m_nPixelWidth; | |||
int m_nPixelHeight; | |||
float m_flWidth; | |||
float m_flHeight; | |||
int m_nPanelName; // The name of the panel | |||
int m_nOldPx; | |||
int m_nOldPy; | |||
int m_nAttachmentIndex; | |||
int m_nOverlayMaterial; | |||
int m_fScreenFlags; | |||
int m_nOldPanelName; | |||
int m_nOldOverlayMaterial; | |||
bool m_bAcceptsInput; | |||
CMaterialReference m_WriteZMaterial; | |||
CMaterialReference m_OverlayMaterial; | |||
VMatrix m_PanelToWorld; | |||
CPanelWrapper m_PanelWrapper; | |||
VPANEL _focus; | |||
bool m_bRightMouseDown; | |||
bool m_bOldRightMouseDown; | |||
bool m_bLeftMouseDown; | |||
bool m_bOldLeftMouseDown; | |||
}; | |||
void SetVGuiScreenButtonState( C_BaseEntity *pVguiScreen, int nButtonState ) | |||
{ | |||
} | |||
IMPLEMENT_CLIENTCLASS_DT(C_VGuiScreen, DT_VGuiScreen, CVGuiScreen) | |||
RecvPropFloat( RECVINFO(m_flWidth) ), | |||
RecvPropFloat( RECVINFO(m_flHeight) ), | |||
RecvPropInt( RECVINFO(m_fScreenFlags) ), | |||
RecvPropInt( RECVINFO(m_nPanelName) ), | |||
RecvPropInt( RECVINFO(m_nAttachmentIndex) ), | |||
RecvPropInt( RECVINFO(m_nOverlayMaterial) ), | |||
END_RECV_TABLE() | |||
//----------------------------------------------------------------------------- | |||
// Constructor | |||
//----------------------------------------------------------------------------- | |||
C_VGuiScreen::C_VGuiScreen() | |||
{ | |||
m_nOldPanelName = m_nPanelName = -1; | |||
m_nOldOverlayMaterial = m_nOverlayMaterial = -1; | |||
m_nOldPx = m_nOldPy = -1; | |||
m_bAcceptsInput = true; | |||
_focus = NULL; | |||
m_bRightMouseDown = m_bLeftMouseDown = m_bOldRightMouseDown = m_bOldLeftMouseDown = false; | |||
m_WriteZMaterial.Init( "engine/writez", TEXTURE_GROUP_VGUI ); | |||
m_OverlayMaterial.Init( m_WriteZMaterial ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Network updates | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::PreDataUpdate( DataUpdateType_t updateType ) | |||
{ | |||
BaseClass::PreDataUpdate( updateType ); | |||
m_nOldPanelName = m_nPanelName; | |||
m_nOldOverlayMaterial = m_nOverlayMaterial; | |||
} | |||
void C_VGuiScreen::OnDataChanged( DataUpdateType_t type ) | |||
{ | |||
BaseClass::OnDataChanged( type ); | |||
if ((type == DATA_UPDATE_CREATED) || (m_nPanelName != m_nOldPanelName)) | |||
{ | |||
CreateVguiScreen( PanelName() ); | |||
} | |||
// Set up the overlay material | |||
if (m_nOldOverlayMaterial != m_nOverlayMaterial) | |||
{ | |||
m_OverlayMaterial.Shutdown(); | |||
const char *pMaterialName = GetMaterialNameFromIndex(m_nOverlayMaterial); | |||
if (pMaterialName) | |||
{ | |||
m_OverlayMaterial.Init( pMaterialName, TEXTURE_GROUP_VGUI ); | |||
} | |||
else | |||
{ | |||
m_OverlayMaterial.Init( m_WriteZMaterial ); | |||
} | |||
} | |||
} | |||
void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); | |||
//----------------------------------------------------------------------------- | |||
// Returns the attachment render origin + origin | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ) | |||
{ | |||
C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); | |||
if (pEnt && (m_nAttachmentIndex > 0)) | |||
{ | |||
C_BaseAnimating::PushAllowBoneAccess( true, true ); | |||
pEnt->GetAttachment( m_nAttachmentIndex, *pOrigin, *pAngles ); | |||
C_BaseAnimating::PopBoneAccess(); | |||
if ( IsAttachedToViewModel() ) | |||
{ | |||
FormatViewModelAttachment( *pOrigin, true ); | |||
} | |||
} | |||
else | |||
{ | |||
BaseClass::GetAimEntOrigin( pAttachedTo, pOrigin, pAngles ); | |||
} | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Create, destroy vgui panels... | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::CreateVguiScreen( const char *pTypeName ) | |||
{ | |||
// Clear out any old screens. | |||
DestroyVguiScreen(); | |||
// Create the new screen... | |||
VGuiScreenInitData_t initData( this ); | |||
m_PanelWrapper.Activate( pTypeName, NULL, 0, &initData ); | |||
// Retrieve the panel dimensions | |||
vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); | |||
if (pPanel) | |||
{ | |||
int x, y; | |||
pPanel->GetBounds( x, y, m_nPixelWidth, m_nPixelHeight ); | |||
} | |||
else | |||
{ | |||
m_nPixelWidth = m_nPixelHeight = 0; | |||
} | |||
} | |||
void C_VGuiScreen::DestroyVguiScreen( ) | |||
{ | |||
m_PanelWrapper.Deactivate(); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Is the screen active? | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IsActive() const | |||
{ | |||
return (m_fScreenFlags & VGUI_SCREEN_ACTIVE) != 0; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: | |||
// Output : Returns true on success, false on failure. | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IsAttachedToViewModel() const | |||
{ | |||
return (m_fScreenFlags & VGUI_SCREEN_ATTACHED_TO_VIEWMODEL) != 0; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: | |||
// Output : Returns true on success, false on failure. | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::AcceptsInput() const | |||
{ | |||
return m_bAcceptsInput; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: | |||
// Input : acceptsinput - | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::SetAcceptsInput( bool acceptsinput ) | |||
{ | |||
m_bAcceptsInput = acceptsinput; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: | |||
// Output : RenderGroup_t | |||
//----------------------------------------------------------------------------- | |||
RenderGroup_t C_VGuiScreen::GetRenderGroup() | |||
{ | |||
if ( IsAttachedToViewModel() ) | |||
return RENDER_GROUP_VIEW_MODEL_TRANSLUCENT; | |||
return BaseClass::GetRenderGroup(); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Are we only visible to teammates? | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IsVisibleOnlyToTeammates() const | |||
{ | |||
return (m_fScreenFlags & VGUI_SCREEN_VISIBLE_TO_TEAMMATES) != 0; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Are we visible to someone on this team? | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IsVisibleToTeam( int nTeam ) | |||
{ | |||
// FIXME: Should this maybe go into a derived class of some sort? | |||
// Don't bother with screens on the wrong team | |||
if (IsVisibleOnlyToTeammates() && (nTeam > 0)) | |||
{ | |||
// Hmmm... sort of a hack... | |||
C_BaseEntity *pOwner = GetOwnerEntity(); | |||
if ( pOwner && (nTeam != pOwner->GetTeamNumber()) ) | |||
return false; | |||
} | |||
return true; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Activate, deactivate the view screen | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::GainFocus( ) | |||
{ | |||
SetNextClientThink( CLIENT_THINK_ALWAYS ); | |||
ClientThink(); | |||
} | |||
void C_VGuiScreen::LoseFocus() | |||
{ | |||
if(_focus) | |||
{ | |||
ivgui()->PostMessage(_focus, new KeyValues("CursorExited"), NULL); | |||
_focus = NULL; | |||
} | |||
SetNextClientThink( CLIENT_THINK_NEVER ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Returns the panel name | |||
//----------------------------------------------------------------------------- | |||
const char *C_VGuiScreen::PanelName() const | |||
{ | |||
return g_StringTableVguiScreen->GetString( m_nPanelName ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Purpose: Deal with input | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::ClientThink( void ) | |||
{ | |||
m_bOldLeftMouseDown = m_bLeftMouseDown; | |||
m_bLeftMouseDown = input()->IsMouseDown(MOUSE_LEFT); | |||
m_bOldRightMouseDown = m_bRightMouseDown; | |||
m_bRightMouseDown = input()->IsMouseDown(MOUSE_LEFT); | |||
BaseClass::ClientThink(); | |||
// FIXME: We should really be taking bob, shake, and roll into account | |||
// but if we did, then all the inputs would be generated multiple times | |||
// if the world was rendered multiple times (for things like water, etc.) | |||
vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); | |||
if (!pPanel) | |||
return; | |||
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); | |||
if (!pLocalPlayer) | |||
return; | |||
// Generate a ray along the view direction | |||
Vector vecEyePosition = pLocalPlayer->EyePosition(); | |||
QAngle viewAngles = pLocalPlayer->EyeAngles( ); | |||
Vector viewDir, endPos; | |||
AngleVectors( viewAngles, &viewDir ); | |||
VectorMA( vecEyePosition, 1000.0f, viewDir, endPos ); | |||
// Compute cursor position... | |||
Ray_t lookDir; | |||
lookDir.Init( vecEyePosition, endPos ); | |||
float u, v; | |||
if (!IntersectWithRay( lookDir, &u, &v, NULL )) | |||
return; | |||
if ( (u < 0) || (v < 0) || (u > 1) || (v > 1)) | |||
return; | |||
// This will cause our panel to grab all input! | |||
g_pClientMode->ActivateInGameVGuiContext( pPanel ); | |||
// Convert (u,v) into (px,py) | |||
int px = (int)(u * m_nPixelWidth + 0.5f); | |||
int py = (int)(v * m_nPixelHeight + 0.5f); | |||
// Generate mouse input commands | |||
if ((px != m_nOldPx) || (py != m_nOldPy)) | |||
{ | |||
//cursor has moved, so make sure the mouseFocus is current | |||
VPANEL oldFocus = _focus; | |||
_focus = m_PanelWrapper.GetPanel()->IsWithinTraverse(px,py,true); | |||
if(oldFocus!=_focus) | |||
{ | |||
if(oldFocus) | |||
{ | |||
ivgui()->PostMessage(oldFocus, new KeyValues("CursorExited"), NULL); | |||
} | |||
if(_focus) | |||
{ | |||
ivgui()->PostMessage(_focus, new KeyValues("CursorEntered"), NULL); | |||
ivgui()->PostMessage(_focus, new KeyValues("OnCursorMoved", "xpos", px, "ypos", py), NULL); | |||
} | |||
} | |||
else if(_focus) | |||
{ | |||
ivgui()->PostMessage(_focus, new KeyValues("OnCursorMoved", "xpos", px, "ypos", py), NULL); | |||
} | |||
m_nOldPx = px; | |||
m_nOldPy = py; | |||
} | |||
if (m_bLeftMouseDown!=m_bOldLeftMouseDown) | |||
{ | |||
ivgui()->PostMessage(_focus,new KeyValues(m_bLeftMouseDown?"MousePressed":"MouseReleased", "code", MOUSE_LEFT),NULL); | |||
} | |||
if (m_bRightMouseDown!=m_bOldRightMouseDown) | |||
{ | |||
ivgui()->PostMessage(_focus,new KeyValues(m_bRightMouseDown?"MousePressed":"MouseReleased", "code", MOUSE_RIGHT),NULL); | |||
} | |||
g_pClientMode->DeactivateInGameVGuiContext( ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Computes control points of the quad describing the screen | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ) | |||
{ | |||
Vector vecOrigin = GetAbsOrigin(); | |||
Vector xaxis, yaxis; | |||
AngleVectors( GetAbsAngles(), &xaxis, &yaxis, NULL ); | |||
// NOTE: Have to multiply by -1 here because yaxis goes out the -y axis in AngleVectors actually... | |||
yaxis *= -1.0f; | |||
VectorCopy( vecOrigin, *pLowerLeft ); | |||
VectorMA( vecOrigin, m_flHeight, yaxis, *pUpperLeft ); | |||
VectorMA( *pUpperLeft, m_flWidth, xaxis, *pUpperRight ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Return intersection point of ray with screen in barycentric coords | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ) | |||
{ | |||
// Perform a raycast to see where in barycentric coordinates the ray hits | |||
// the viewscreen; if it doesn't hit it, you're not in the mode | |||
Vector origin, upt, vpt; | |||
ComputeEdges( &origin, &upt, &vpt ); | |||
return ComputeIntersectionBarycentricCoordinates( ray, origin, upt, vpt, *u, *v, t ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Is the vgui screen backfacing? | |||
//----------------------------------------------------------------------------- | |||
bool C_VGuiScreen::IsBackfacing( const Vector &viewOrigin ) | |||
{ | |||
// Compute a ray from camera to center of the screen.. | |||
Vector cameraToScreen; | |||
VectorSubtract( GetAbsOrigin(), viewOrigin, cameraToScreen ); | |||
// Figure out the face normal | |||
Vector zaxis; | |||
GetVectors( NULL, NULL, &zaxis ); | |||
// The actual backface cull | |||
return (DotProduct( zaxis, cameraToScreen ) > 0.0f); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Computes the panel center to world transform | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::ComputePanelToWorld() | |||
{ | |||
// The origin is at the upper-left corner of the screen | |||
Vector vecOrigin, vecUR, vecLL; | |||
ComputeEdges( &vecOrigin, &vecUR, &vecLL ); | |||
m_PanelToWorld.SetupMatrixOrgAngles( vecOrigin, GetAbsAngles() ); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// a pass to set the z buffer... | |||
//----------------------------------------------------------------------------- | |||
void C_VGuiScreen::DrawScreenOverlay() | |||
{ | |||
materials->MatrixMode( MATERIAL_MODEL ); | |||
materials->PushMatrix(); | |||
materials->LoadMatrix( m_PanelToWorld ); | |||
unsigned char pColor[4] = {255, 255, 255, 255}; | |||
CMeshBuilder meshBuilder; | |||
IMesh *pMesh = materials->GetDynamicMesh( true, NULL, NULL, m_OverlayMaterial ); | |||
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); | |||
meshBuilder.Position3f( 0.0f, 0.0f, 0 ); | |||
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); | |||
meshBuilder.Color4ubv( pColor ); | |||
meshBuilder.AdvanceVertex(); | |||
meshBuilder.Position3f( m_flWidth, 0.0f, 0 ); | |||
meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); | |||
meshBuilder.Color4ubv( pColor ); | |||
meshBuilder.AdvanceVertex(); | |||
meshBuilder.Position3f( m_flWidth, -m_flHeight, 0 ); | |||
meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); | |||
meshBuilder.Color4ubv( pColor ); | |||
meshBuilder.AdvanceVertex(); | |||
meshBuilder.Position3f( 0.0f, -m_flHeight, 0 ); | |||
meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); | |||
meshBuilder.Color4ubv( pColor ); | |||
meshBuilder.AdvanceVertex(); | |||
meshBuilder.End(); | |||
pMesh->Draw(); | |||
materials->PopMatrix(); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// Draws the panel using a 3D transform... | |||
//----------------------------------------------------------------------------- | |||
int C_VGuiScreen::DrawModel( int flags ) | |||
{ | |||
vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); | |||
if (!pPanel || !IsActive()) | |||
return 0; | |||
// Don't bother drawing stuff not visible to me... | |||
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); | |||
if (!pLocalPlayer || !IsVisibleToTeam(pLocalPlayer->GetTeamNumber()) ) | |||
return 0; | |||
// Backface cull the entire panel here... | |||
if (IsBackfacing(CurrentViewOrigin())) | |||
return 0; | |||
// Recompute the panel-to-world center | |||
// FIXME: Can this be cached off? | |||
ComputePanelToWorld(); | |||
g_pMatSystemSurface->DrawPanelIn3DSpace( pPanel->GetVPanel(), m_PanelToWorld, | |||
m_nPixelWidth, m_nPixelHeight, m_flWidth, m_flHeight ); | |||
// Finally, a pass to set the z buffer... | |||
DrawScreenOverlay(); | |||
return 1; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// | |||
// Enumator class for finding vgui screens close to the local player | |||
// | |||
//----------------------------------------------------------------------------- | |||
class CVGuiScreenEnumerator : public IPartitionEnumerator | |||
{ | |||
DECLARE_CLASS_GAMEROOT( CVGuiScreenEnumerator, IPartitionEnumerator ); | |||
public: | |||
virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); | |||
int GetScreenCount(); | |||
C_VGuiScreen *GetVGuiScreen( int index ); | |||
private: | |||
CUtlVector< CHandle< C_VGuiScreen > > m_VguiScreens; | |||
}; | |||
IterationRetval_t CVGuiScreenEnumerator::EnumElement( IHandleEntity *pHandleEntity ) | |||
{ | |||
C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); | |||
if ( pEnt == NULL ) | |||
return ITERATION_CONTINUE; | |||
// FIXME.. pretty expensive... | |||
C_VGuiScreen *pScreen = dynamic_cast<C_VGuiScreen*>(pEnt); | |||
if ( pScreen ) | |||
{ | |||
int i = m_VguiScreens.AddToTail( ); | |||
m_VguiScreens[i].Set( pScreen ); | |||
} | |||
return ITERATION_CONTINUE; | |||
} | |||
int CVGuiScreenEnumerator::GetScreenCount() | |||
{ | |||
return m_VguiScreens.Count(); | |||
} | |||
C_VGuiScreen *CVGuiScreenEnumerator::GetVGuiScreen( int index ) | |||
{ | |||
return m_VguiScreens[index].Get(); | |||
} | |||
//----------------------------------------------------------------------------- | |||
// | |||
// Look for vgui screens, returns true if it found one ... | |||
// | |||
//----------------------------------------------------------------------------- | |||
C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &viewAngle, int nTeam ) | |||
{ | |||
// Get the view direction... | |||
Vector lookDir; | |||
AngleVectors( viewAngle, &lookDir ); | |||
// Create a ray used for raytracing | |||
Vector lookEnd; | |||
VectorMA( viewPosition, 2.0f * VGUI_SCREEN_MODE_RADIUS, lookDir, lookEnd ); | |||
Ray_t lookRay; | |||
lookRay.Init( viewPosition, lookEnd ); | |||
// Look for vgui screens that are close to the player | |||
CVGuiScreenEnumerator localScreens; | |||
partition->EnumerateElementsInSphere( PARTITION_CLIENT_NON_STATIC_EDICTS, viewPosition, VGUI_SCREEN_MODE_RADIUS, false, &localScreens ); | |||
Vector vecOut, vecViewDelta; | |||
float flBestDist = 2.0f; | |||
C_VGuiScreen *pBestScreen = NULL; | |||
for (int i = localScreens.GetScreenCount(); --i >= 0; ) | |||
{ | |||
C_VGuiScreen *pScreen = localScreens.GetVGuiScreen(i); | |||
// Don't bother with screens I'm behind... | |||
if (pScreen->IsBackfacing(viewPosition)) | |||
continue; | |||
// Don't bother with screens that are turned off | |||
if (!pScreen->IsActive()) | |||
continue; | |||
// FIXME: Should this maybe go into a derived class of some sort? | |||
// Don't bother with screens on the wrong team | |||
if (!pScreen->IsVisibleToTeam(nTeam)) | |||
continue; | |||
if ( !pScreen->AcceptsInput() ) | |||
continue; | |||
// Test perpendicular distance from the screen... | |||
pScreen->GetVectors( NULL, NULL, &vecOut ); | |||
VectorSubtract( viewPosition, pScreen->GetAbsOrigin(), vecViewDelta ); | |||
float flPerpDist = DotProduct(vecViewDelta, vecOut); | |||
if ( (flPerpDist < 0) || (flPerpDist > VGUI_SCREEN_MODE_RADIUS) ) | |||
continue; | |||
// Perform a raycast to see where in barycentric coordinates the ray hits | |||
// the viewscreen; if it doesn't hit it, you're not in the mode | |||
float u, v, t; | |||
if (!pScreen->IntersectWithRay( lookRay, &u, &v, &t )) | |||
continue; | |||
// Barycentric test | |||
if ((u < 0) || (v < 0) || (u > 1) || (v > 1)) | |||
continue; | |||
if ( t < flBestDist ) | |||
{ | |||
flBestDist = t; | |||
pBestScreen = pScreen; | |||
} | |||
} | |||
return pBestScreen; | |||
} | |||
void ActivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) | |||
{ | |||
if (pVguiScreenEnt) | |||
{ | |||
Assert( dynamic_cast<C_VGuiScreen*>(pVguiScreenEnt) ); | |||
C_VGuiScreen *pVguiScreen = static_cast<C_VGuiScreen*>(pVguiScreenEnt); | |||
pVguiScreen->GainFocus( ); | |||
} | |||
} | |||
void DeactivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) | |||
{ | |||
if (pVguiScreenEnt) | |||
{ | |||
Assert( dynamic_cast<C_VGuiScreen*>(pVguiScreenEnt) ); | |||
C_VGuiScreen *pVguiScreen = static_cast<C_VGuiScreen*>(pVguiScreenEnt); | |||
pVguiScreen->LoseFocus( ); | |||
} | |||
} | |||
CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName ) | |||
: BaseClass( parent, panelName ) | |||
{ | |||
m_hEntity = NULL; | |||
} | |||
CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName, vgui::HScheme hScheme ) | |||
: BaseClass( parent, panelName, hScheme ) | |||
{ | |||
m_hEntity = NULL; | |||
} | |||
bool CVGuiScreenPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) | |||
{ | |||
const char *pResFile = pKeyValues->GetString( "resfile" ); | |||
if (pResFile[0] != 0) | |||
{ | |||
KeyValues *pCachedKeyValues = CacheKeyValuesForFile( pResFile ); | |||
LoadControlSettings( pResFile, NULL, pCachedKeyValues ); | |||
} | |||
// Dimensions in pixels | |||
int nWidth, nHeight; | |||
nWidth = pKeyValues->GetInt( "pixelswide", 240 ); | |||
nHeight = pKeyValues->GetInt( "pixelshigh", 160 ); | |||
if ((nWidth <= 0) || (nHeight <= 0)) | |||
return false; | |||
// If init data isn't specified, then we're just precaching. | |||
if ( pInitData ) | |||
{ | |||
m_hEntity.Set( pInitData->m_pEntity ); | |||
C_VGuiScreen *screen = dynamic_cast< C_VGuiScreen * >( pInitData->m_pEntity ); | |||
if ( screen ) | |||
{ | |||
bool acceptsInput = pKeyValues->GetInt( "acceptsinput", 1 ) ? true : false; | |||
screen->SetAcceptsInput( acceptsInput ); | |||
} | |||
} | |||
SetBounds( 0, 0, nWidth, nHeight ); | |||
return true; | |||
} | |||
vgui::Panel *CVGuiScreenPanel::CreateControlByName(const char *controlName) | |||
{ | |||
// Check the panel metaclass manager to make these controls... | |||
if (!Q_strncmp(controlName, "MaterialImage", 20)) | |||
{ | |||
return new CBitmapPanel(NULL, "BitmapPanel"); | |||
} | |||
if (!Q_strncmp(controlName, "MaterialButton", 20)) | |||
{ | |||
return new CBitmapButton(NULL, "BitmapButton", ""); | |||
} | |||
// Didn't find it? Just use the default stuff | |||
return BaseClass::CreateControlByName( controlName ); | |||
} | |||
DECLARE_VGUI_SCREEN_FACTORY( CVGuiScreenPanel, "vgui_screen_panel" );</pre> | |||
== Example Screenshots == | == Example Screenshots == |
Revision as of 16:20, 11 March 2006
Adding a VGUI screen to a map

- Create a vgui_screen entity. This is a point entity.
- Set its Panel Name to the name of the screen that should be shown. (This is not the filename of the screen.) The available screens are listed in
vgui_screens.txt
(which should be in thescripts
directory). - Set Panel Width in World and Panel Height in World to match the size of the brush. 64 wide by 32 tall would be a reasonable starting size.
- Compile the map and test.
The position of the entity in the map marks the bottom-left corner of the panel. The panel's direction (the normal of its face) is set by the entity's angle (Yaw).
Creating a VGUI screen
VGUI screen files have a .res extension and should be placed in scripts\screens\
.
vgui_test_screen.res
is a good starting point for creating a VGUI screen file; it can be extracted fromsource engine.gcf
at pathhl2/scripts/screens/
using GCFScape.- The materials used in the .res file can be found in
source materials.gfc
. Extract the material files mentioned in the .res file tomaterials\vgui\screens\
. - Add the screen to
scripts\vgui_screens.txt
. If it does not exist, create it. Here is an example which describes screenvgui_test_screen
atscripts/screens/vgui_test_screen.res
. Adjust it for your screen.
"VGUI_Screens" { "vgui_test_screen" { // This is our example screen "type" "vgui_screen_panel" "pixelswide" 480 "pixelshigh" 240 // This must be the file you created in step 1 "resfile" "scripts/screens/vgui_test_screen.res" } }
An example vgui_screen.txt
file can be extracted from source engine.gcf
at root/hl2/scripts
.
VGUI code modifications
There is a problem with VGUI screens receiving input. Unless it is fixed, the game will crash when the cursor points at the screen.
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "networkstringtable_clientdll.h" #include <KeyValues.h> #include "PanelMetaClassMgr.h" #include <vgui_controls/Controls.h> #include "VMatrix.h" #include "VGUIMatSurface/IMatSystemSurface.h" #include "view.h" #include "CollisionUtils.h" #include <vgui/IInput.h> #include <vgui/IPanel.h> #include <vgui/IVGui.h> #include "ienginevgui.h" #include <vgui/Mousecode.h> #include "materialsystem/IMesh.h" #include "ClientEffectPrecacheSystem.h" #include "C_VGuiScreen.h" #include "IClientMode.h" #include "vgui_bitmapbutton.h" #include "vgui_bitmappanel.h" #include "filesystem.h" #include <vgui/IInputInternal.h> extern vgui::IInputInternal *g_InputInternal; // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define VGUI_SCREEN_MODE_RADIUS 80 //Precache the materials CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectVGuiScreen ) CLIENTEFFECT_MATERIAL( "engine/writez" ) CLIENTEFFECT_REGISTER_END() using namespace vgui; // ----------------------------------------------------------------------------- // // This is a cache of preloaded keyvalues. // ----------------------------------------------------------------------------- // CUtlDict<KeyValues*, int> g_KeyValuesCache; KeyValues* CacheKeyValuesForFile( const char *pFilename ) { int i = g_KeyValuesCache.Find( pFilename ); if ( i == g_KeyValuesCache.InvalidIndex() ) { KeyValues *rDat = new KeyValues( pFilename ); rDat->LoadFromFile( vgui::filesystem(), pFilename, NULL ); g_KeyValuesCache.Insert( pFilename, rDat ); return rDat; } else { return g_KeyValuesCache[i]; } } void ClearKeyValuesCache() { for ( int i=g_KeyValuesCache.First(); i != g_KeyValuesCache.InvalidIndex(); i=g_KeyValuesCache.Next( i ) ) { g_KeyValuesCache[i]->deleteThis(); } g_KeyValuesCache.Purge(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class C_VGuiScreen : public C_BaseEntity { DECLARE_CLASS( C_VGuiScreen, C_BaseEntity ); public: DECLARE_CLIENTCLASS(); C_VGuiScreen(); virtual void PreDataUpdate( DataUpdateType_t updateType ); virtual void OnDataChanged( DataUpdateType_t type ); virtual int DrawModel( int flags ); virtual bool ShouldDraw() { return !IsEffectActive(EF_NODRAW); } virtual void ClientThink( ); virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ); const char *PanelName() const; // The view screen has the cursor pointing at it void GainFocus( ); void LoseFocus(); // Is the screen backfaced given a view position? bool IsBackfacing( const Vector &viewOrigin ); // Return intersection point of ray with screen in barycentric coords bool IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ); // Is the screen turned on? bool IsActive() const; // Are we only visible to teammates? bool IsVisibleOnlyToTeammates() const; // Are we visible to someone on this team? bool IsVisibleToTeam( int nTeam ); bool IsAttachedToViewModel() const; virtual RenderGroup_t GetRenderGroup(); bool AcceptsInput() const; void SetAcceptsInput( bool acceptsinput ); private: // Vgui screen management void CreateVguiScreen( const char *pTypeName ); void DestroyVguiScreen( ); // Computes the panel to world transform void ComputePanelToWorld(); // Computes control points of the quad describing the screen void ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ); // Writes the z buffer void DrawScreenOverlay(); private: int m_nPixelWidth; int m_nPixelHeight; float m_flWidth; float m_flHeight; int m_nPanelName; // The name of the panel int m_nOldPx; int m_nOldPy; int m_nAttachmentIndex; int m_nOverlayMaterial; int m_fScreenFlags; int m_nOldPanelName; int m_nOldOverlayMaterial; bool m_bAcceptsInput; CMaterialReference m_WriteZMaterial; CMaterialReference m_OverlayMaterial; VMatrix m_PanelToWorld; CPanelWrapper m_PanelWrapper; VPANEL _focus; bool m_bRightMouseDown; bool m_bOldRightMouseDown; bool m_bLeftMouseDown; bool m_bOldLeftMouseDown; }; void SetVGuiScreenButtonState( C_BaseEntity *pVguiScreen, int nButtonState ) { } IMPLEMENT_CLIENTCLASS_DT(C_VGuiScreen, DT_VGuiScreen, CVGuiScreen) RecvPropFloat( RECVINFO(m_flWidth) ), RecvPropFloat( RECVINFO(m_flHeight) ), RecvPropInt( RECVINFO(m_fScreenFlags) ), RecvPropInt( RECVINFO(m_nPanelName) ), RecvPropInt( RECVINFO(m_nAttachmentIndex) ), RecvPropInt( RECVINFO(m_nOverlayMaterial) ), END_RECV_TABLE() //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- C_VGuiScreen::C_VGuiScreen() { m_nOldPanelName = m_nPanelName = -1; m_nOldOverlayMaterial = m_nOverlayMaterial = -1; m_nOldPx = m_nOldPy = -1; m_bAcceptsInput = true; _focus = NULL; m_bRightMouseDown = m_bLeftMouseDown = m_bOldRightMouseDown = m_bOldLeftMouseDown = false; m_WriteZMaterial.Init( "engine/writez", TEXTURE_GROUP_VGUI ); m_OverlayMaterial.Init( m_WriteZMaterial ); } //----------------------------------------------------------------------------- // Network updates //----------------------------------------------------------------------------- void C_VGuiScreen::PreDataUpdate( DataUpdateType_t updateType ) { BaseClass::PreDataUpdate( updateType ); m_nOldPanelName = m_nPanelName; m_nOldOverlayMaterial = m_nOverlayMaterial; } void C_VGuiScreen::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); if ((type == DATA_UPDATE_CREATED) || (m_nPanelName != m_nOldPanelName)) { CreateVguiScreen( PanelName() ); } // Set up the overlay material if (m_nOldOverlayMaterial != m_nOverlayMaterial) { m_OverlayMaterial.Shutdown(); const char *pMaterialName = GetMaterialNameFromIndex(m_nOverlayMaterial); if (pMaterialName) { m_OverlayMaterial.Init( pMaterialName, TEXTURE_GROUP_VGUI ); } else { m_OverlayMaterial.Init( m_WriteZMaterial ); } } } void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); //----------------------------------------------------------------------------- // Returns the attachment render origin + origin //----------------------------------------------------------------------------- void C_VGuiScreen::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ) { C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); if (pEnt && (m_nAttachmentIndex > 0)) { C_BaseAnimating::PushAllowBoneAccess( true, true ); pEnt->GetAttachment( m_nAttachmentIndex, *pOrigin, *pAngles ); C_BaseAnimating::PopBoneAccess(); if ( IsAttachedToViewModel() ) { FormatViewModelAttachment( *pOrigin, true ); } } else { BaseClass::GetAimEntOrigin( pAttachedTo, pOrigin, pAngles ); } } //----------------------------------------------------------------------------- // Create, destroy vgui panels... //----------------------------------------------------------------------------- void C_VGuiScreen::CreateVguiScreen( const char *pTypeName ) { // Clear out any old screens. DestroyVguiScreen(); // Create the new screen... VGuiScreenInitData_t initData( this ); m_PanelWrapper.Activate( pTypeName, NULL, 0, &initData ); // Retrieve the panel dimensions vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); if (pPanel) { int x, y; pPanel->GetBounds( x, y, m_nPixelWidth, m_nPixelHeight ); } else { m_nPixelWidth = m_nPixelHeight = 0; } } void C_VGuiScreen::DestroyVguiScreen( ) { m_PanelWrapper.Deactivate(); } //----------------------------------------------------------------------------- // Is the screen active? //----------------------------------------------------------------------------- bool C_VGuiScreen::IsActive() const { return (m_fScreenFlags & VGUI_SCREEN_ACTIVE) != 0; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool C_VGuiScreen::IsAttachedToViewModel() const { return (m_fScreenFlags & VGUI_SCREEN_ATTACHED_TO_VIEWMODEL) != 0; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool C_VGuiScreen::AcceptsInput() const { return m_bAcceptsInput; } //----------------------------------------------------------------------------- // Purpose: // Input : acceptsinput - //----------------------------------------------------------------------------- void C_VGuiScreen::SetAcceptsInput( bool acceptsinput ) { m_bAcceptsInput = acceptsinput; } //----------------------------------------------------------------------------- // Purpose: // Output : RenderGroup_t //----------------------------------------------------------------------------- RenderGroup_t C_VGuiScreen::GetRenderGroup() { if ( IsAttachedToViewModel() ) return RENDER_GROUP_VIEW_MODEL_TRANSLUCENT; return BaseClass::GetRenderGroup(); } //----------------------------------------------------------------------------- // Are we only visible to teammates? //----------------------------------------------------------------------------- bool C_VGuiScreen::IsVisibleOnlyToTeammates() const { return (m_fScreenFlags & VGUI_SCREEN_VISIBLE_TO_TEAMMATES) != 0; } //----------------------------------------------------------------------------- // Are we visible to someone on this team? //----------------------------------------------------------------------------- bool C_VGuiScreen::IsVisibleToTeam( int nTeam ) { // FIXME: Should this maybe go into a derived class of some sort? // Don't bother with screens on the wrong team if (IsVisibleOnlyToTeammates() && (nTeam > 0)) { // Hmmm... sort of a hack... C_BaseEntity *pOwner = GetOwnerEntity(); if ( pOwner && (nTeam != pOwner->GetTeamNumber()) ) return false; } return true; } //----------------------------------------------------------------------------- // Activate, deactivate the view screen //----------------------------------------------------------------------------- void C_VGuiScreen::GainFocus( ) { SetNextClientThink( CLIENT_THINK_ALWAYS ); ClientThink(); } void C_VGuiScreen::LoseFocus() { if(_focus) { ivgui()->PostMessage(_focus, new KeyValues("CursorExited"), NULL); _focus = NULL; } SetNextClientThink( CLIENT_THINK_NEVER ); } //----------------------------------------------------------------------------- // Returns the panel name //----------------------------------------------------------------------------- const char *C_VGuiScreen::PanelName() const { return g_StringTableVguiScreen->GetString( m_nPanelName ); } //----------------------------------------------------------------------------- // Purpose: Deal with input //----------------------------------------------------------------------------- void C_VGuiScreen::ClientThink( void ) { m_bOldLeftMouseDown = m_bLeftMouseDown; m_bLeftMouseDown = input()->IsMouseDown(MOUSE_LEFT); m_bOldRightMouseDown = m_bRightMouseDown; m_bRightMouseDown = input()->IsMouseDown(MOUSE_LEFT); BaseClass::ClientThink(); // FIXME: We should really be taking bob, shake, and roll into account // but if we did, then all the inputs would be generated multiple times // if the world was rendered multiple times (for things like water, etc.) vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); if (!pPanel) return; C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if (!pLocalPlayer) return; // Generate a ray along the view direction Vector vecEyePosition = pLocalPlayer->EyePosition(); QAngle viewAngles = pLocalPlayer->EyeAngles( ); Vector viewDir, endPos; AngleVectors( viewAngles, &viewDir ); VectorMA( vecEyePosition, 1000.0f, viewDir, endPos ); // Compute cursor position... Ray_t lookDir; lookDir.Init( vecEyePosition, endPos ); float u, v; if (!IntersectWithRay( lookDir, &u, &v, NULL )) return; if ( (u < 0) || (v < 0) || (u > 1) || (v > 1)) return; // This will cause our panel to grab all input! g_pClientMode->ActivateInGameVGuiContext( pPanel ); // Convert (u,v) into (px,py) int px = (int)(u * m_nPixelWidth + 0.5f); int py = (int)(v * m_nPixelHeight + 0.5f); // Generate mouse input commands if ((px != m_nOldPx) || (py != m_nOldPy)) { //cursor has moved, so make sure the mouseFocus is current VPANEL oldFocus = _focus; _focus = m_PanelWrapper.GetPanel()->IsWithinTraverse(px,py,true); if(oldFocus!=_focus) { if(oldFocus) { ivgui()->PostMessage(oldFocus, new KeyValues("CursorExited"), NULL); } if(_focus) { ivgui()->PostMessage(_focus, new KeyValues("CursorEntered"), NULL); ivgui()->PostMessage(_focus, new KeyValues("OnCursorMoved", "xpos", px, "ypos", py), NULL); } } else if(_focus) { ivgui()->PostMessage(_focus, new KeyValues("OnCursorMoved", "xpos", px, "ypos", py), NULL); } m_nOldPx = px; m_nOldPy = py; } if (m_bLeftMouseDown!=m_bOldLeftMouseDown) { ivgui()->PostMessage(_focus,new KeyValues(m_bLeftMouseDown?"MousePressed":"MouseReleased", "code", MOUSE_LEFT),NULL); } if (m_bRightMouseDown!=m_bOldRightMouseDown) { ivgui()->PostMessage(_focus,new KeyValues(m_bRightMouseDown?"MousePressed":"MouseReleased", "code", MOUSE_RIGHT),NULL); } g_pClientMode->DeactivateInGameVGuiContext( ); } //----------------------------------------------------------------------------- // Computes control points of the quad describing the screen //----------------------------------------------------------------------------- void C_VGuiScreen::ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ) { Vector vecOrigin = GetAbsOrigin(); Vector xaxis, yaxis; AngleVectors( GetAbsAngles(), &xaxis, &yaxis, NULL ); // NOTE: Have to multiply by -1 here because yaxis goes out the -y axis in AngleVectors actually... yaxis *= -1.0f; VectorCopy( vecOrigin, *pLowerLeft ); VectorMA( vecOrigin, m_flHeight, yaxis, *pUpperLeft ); VectorMA( *pUpperLeft, m_flWidth, xaxis, *pUpperRight ); } //----------------------------------------------------------------------------- // Return intersection point of ray with screen in barycentric coords //----------------------------------------------------------------------------- bool C_VGuiScreen::IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ) { // Perform a raycast to see where in barycentric coordinates the ray hits // the viewscreen; if it doesn't hit it, you're not in the mode Vector origin, upt, vpt; ComputeEdges( &origin, &upt, &vpt ); return ComputeIntersectionBarycentricCoordinates( ray, origin, upt, vpt, *u, *v, t ); } //----------------------------------------------------------------------------- // Is the vgui screen backfacing? //----------------------------------------------------------------------------- bool C_VGuiScreen::IsBackfacing( const Vector &viewOrigin ) { // Compute a ray from camera to center of the screen.. Vector cameraToScreen; VectorSubtract( GetAbsOrigin(), viewOrigin, cameraToScreen ); // Figure out the face normal Vector zaxis; GetVectors( NULL, NULL, &zaxis ); // The actual backface cull return (DotProduct( zaxis, cameraToScreen ) > 0.0f); } //----------------------------------------------------------------------------- // Computes the panel center to world transform //----------------------------------------------------------------------------- void C_VGuiScreen::ComputePanelToWorld() { // The origin is at the upper-left corner of the screen Vector vecOrigin, vecUR, vecLL; ComputeEdges( &vecOrigin, &vecUR, &vecLL ); m_PanelToWorld.SetupMatrixOrgAngles( vecOrigin, GetAbsAngles() ); } //----------------------------------------------------------------------------- // a pass to set the z buffer... //----------------------------------------------------------------------------- void C_VGuiScreen::DrawScreenOverlay() { materials->MatrixMode( MATERIAL_MODEL ); materials->PushMatrix(); materials->LoadMatrix( m_PanelToWorld ); unsigned char pColor[4] = {255, 255, 255, 255}; CMeshBuilder meshBuilder; IMesh *pMesh = materials->GetDynamicMesh( true, NULL, NULL, m_OverlayMaterial ); meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); meshBuilder.Position3f( 0.0f, 0.0f, 0 ); meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); meshBuilder.Color4ubv( pColor ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( m_flWidth, 0.0f, 0 ); meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); meshBuilder.Color4ubv( pColor ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( m_flWidth, -m_flHeight, 0 ); meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); meshBuilder.Color4ubv( pColor ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( 0.0f, -m_flHeight, 0 ); meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); meshBuilder.Color4ubv( pColor ); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); materials->PopMatrix(); } //----------------------------------------------------------------------------- // Draws the panel using a 3D transform... //----------------------------------------------------------------------------- int C_VGuiScreen::DrawModel( int flags ) { vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); if (!pPanel || !IsActive()) return 0; // Don't bother drawing stuff not visible to me... C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if (!pLocalPlayer || !IsVisibleToTeam(pLocalPlayer->GetTeamNumber()) ) return 0; // Backface cull the entire panel here... if (IsBackfacing(CurrentViewOrigin())) return 0; // Recompute the panel-to-world center // FIXME: Can this be cached off? ComputePanelToWorld(); g_pMatSystemSurface->DrawPanelIn3DSpace( pPanel->GetVPanel(), m_PanelToWorld, m_nPixelWidth, m_nPixelHeight, m_flWidth, m_flHeight ); // Finally, a pass to set the z buffer... DrawScreenOverlay(); return 1; } //----------------------------------------------------------------------------- // // Enumator class for finding vgui screens close to the local player // //----------------------------------------------------------------------------- class CVGuiScreenEnumerator : public IPartitionEnumerator { DECLARE_CLASS_GAMEROOT( CVGuiScreenEnumerator, IPartitionEnumerator ); public: virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); int GetScreenCount(); C_VGuiScreen *GetVGuiScreen( int index ); private: CUtlVector< CHandle< C_VGuiScreen > > m_VguiScreens; }; IterationRetval_t CVGuiScreenEnumerator::EnumElement( IHandleEntity *pHandleEntity ) { C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); if ( pEnt == NULL ) return ITERATION_CONTINUE; // FIXME.. pretty expensive... C_VGuiScreen *pScreen = dynamic_cast<C_VGuiScreen*>(pEnt); if ( pScreen ) { int i = m_VguiScreens.AddToTail( ); m_VguiScreens[i].Set( pScreen ); } return ITERATION_CONTINUE; } int CVGuiScreenEnumerator::GetScreenCount() { return m_VguiScreens.Count(); } C_VGuiScreen *CVGuiScreenEnumerator::GetVGuiScreen( int index ) { return m_VguiScreens[index].Get(); } //----------------------------------------------------------------------------- // // Look for vgui screens, returns true if it found one ... // //----------------------------------------------------------------------------- C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &viewAngle, int nTeam ) { // Get the view direction... Vector lookDir; AngleVectors( viewAngle, &lookDir ); // Create a ray used for raytracing Vector lookEnd; VectorMA( viewPosition, 2.0f * VGUI_SCREEN_MODE_RADIUS, lookDir, lookEnd ); Ray_t lookRay; lookRay.Init( viewPosition, lookEnd ); // Look for vgui screens that are close to the player CVGuiScreenEnumerator localScreens; partition->EnumerateElementsInSphere( PARTITION_CLIENT_NON_STATIC_EDICTS, viewPosition, VGUI_SCREEN_MODE_RADIUS, false, &localScreens ); Vector vecOut, vecViewDelta; float flBestDist = 2.0f; C_VGuiScreen *pBestScreen = NULL; for (int i = localScreens.GetScreenCount(); --i >= 0; ) { C_VGuiScreen *pScreen = localScreens.GetVGuiScreen(i); // Don't bother with screens I'm behind... if (pScreen->IsBackfacing(viewPosition)) continue; // Don't bother with screens that are turned off if (!pScreen->IsActive()) continue; // FIXME: Should this maybe go into a derived class of some sort? // Don't bother with screens on the wrong team if (!pScreen->IsVisibleToTeam(nTeam)) continue; if ( !pScreen->AcceptsInput() ) continue; // Test perpendicular distance from the screen... pScreen->GetVectors( NULL, NULL, &vecOut ); VectorSubtract( viewPosition, pScreen->GetAbsOrigin(), vecViewDelta ); float flPerpDist = DotProduct(vecViewDelta, vecOut); if ( (flPerpDist < 0) || (flPerpDist > VGUI_SCREEN_MODE_RADIUS) ) continue; // Perform a raycast to see where in barycentric coordinates the ray hits // the viewscreen; if it doesn't hit it, you're not in the mode float u, v, t; if (!pScreen->IntersectWithRay( lookRay, &u, &v, &t )) continue; // Barycentric test if ((u < 0) || (v < 0) || (u > 1) || (v > 1)) continue; if ( t < flBestDist ) { flBestDist = t; pBestScreen = pScreen; } } return pBestScreen; } void ActivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) { if (pVguiScreenEnt) { Assert( dynamic_cast<C_VGuiScreen*>(pVguiScreenEnt) ); C_VGuiScreen *pVguiScreen = static_cast<C_VGuiScreen*>(pVguiScreenEnt); pVguiScreen->GainFocus( ); } } void DeactivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) { if (pVguiScreenEnt) { Assert( dynamic_cast<C_VGuiScreen*>(pVguiScreenEnt) ); C_VGuiScreen *pVguiScreen = static_cast<C_VGuiScreen*>(pVguiScreenEnt); pVguiScreen->LoseFocus( ); } } CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) { m_hEntity = NULL; } CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName, vgui::HScheme hScheme ) : BaseClass( parent, panelName, hScheme ) { m_hEntity = NULL; } bool CVGuiScreenPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) { const char *pResFile = pKeyValues->GetString( "resfile" ); if (pResFile[0] != 0) { KeyValues *pCachedKeyValues = CacheKeyValuesForFile( pResFile ); LoadControlSettings( pResFile, NULL, pCachedKeyValues ); } // Dimensions in pixels int nWidth, nHeight; nWidth = pKeyValues->GetInt( "pixelswide", 240 ); nHeight = pKeyValues->GetInt( "pixelshigh", 160 ); if ((nWidth <= 0) || (nHeight <= 0)) return false; // If init data isn't specified, then we're just precaching. if ( pInitData ) { m_hEntity.Set( pInitData->m_pEntity ); C_VGuiScreen *screen = dynamic_cast< C_VGuiScreen * >( pInitData->m_pEntity ); if ( screen ) { bool acceptsInput = pKeyValues->GetInt( "acceptsinput", 1 ) ? true : false; screen->SetAcceptsInput( acceptsInput ); } } SetBounds( 0, 0, nWidth, nHeight ); return true; } vgui::Panel *CVGuiScreenPanel::CreateControlByName(const char *controlName) { // Check the panel metaclass manager to make these controls... if (!Q_strncmp(controlName, "MaterialImage", 20)) { return new CBitmapPanel(NULL, "BitmapPanel"); } if (!Q_strncmp(controlName, "MaterialButton", 20)) { return new CBitmapButton(NULL, "BitmapButton", ""); } // Didn't find it? Just use the default stuff return BaseClass::CreateControlByName( controlName ); } DECLARE_VGUI_SCREEN_FACTORY( CVGuiScreenPanel, "vgui_screen_panel" );
Example Screenshots
VGUI screen used in mod
VGUI screen used to display dynamically drawn EKG data