Models on VGUI Panels: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
mNo edit summary
No edit summary
Line 1: Line 1:
[[Image:Vguimodelrender.jpg|frame|Player model rendered in VGUI panel]]This article shows how to render a player model to a custom VGUI image panel, the player model can be shown with a weapon and any animation. This can be seen in the Counter-Strike Source character selection menu, thanks to Matt Boone for the information about this.


It involves editing ''sdk_clientmode.cpp'', ''teammenu.cpp'', and ''teammenu.h'' (or as appropriate for your mod). The basic premise is to generate a global list of the custom image panel, checking if any of the panel's are visible and then drawing the model they reference.
Possible expansions to this feature are to allow specification of the weapon model and animations required in the .res file entry.
This article provides full code listings and an example .res entry, to make use of this feature you will need to have some form of vgui panel, such as a team or equipment menu.
==teammenu==
We'll define the custom image panel and the global list in our team menu header and cpp file - there's nothing too complicated here so we'll whip through it quickly!
===teammenu.h===
The header file entry is easy to get your head around, we declare a new class that inherits from vgui::ImagePanel, we also extern a CUtlVector for our global list of our custom image panels (used in clientmode)
class CClassImagePanel : public vgui::ImagePanel
{
    public:<br>
        typedef vgui::ImagePanel BaseClass;<br>
        CClassImagePanel( vgui::Panel *pParent, const char *pName );
        virtual ~CClassImagePanel();
        virtual void ApplySettings( KeyValues *inResourceData );
        virtual void Paint();<br><br>
    public:
        char m_ModelName[128];
};<br>
extern CUtlVector<CClassImagePanel*> g_ClassImagePanels;
===teammenu.cpp===
Here's the core of our new custom image panel, the two most important functions are: ApplySettings where we find the name of the model that should be drawn with this panel and CreateControlByName which is required by any VGUI panel that has one of these image panels attached to it. We also define the global list of image panels, as described earlier.
CUtlVector<CClassImagePanel*> g_ClassImagePanels;<br>
CClassImagePanel::CClassImagePanel( vgui::Panel *pParent, const char *pName )
    : vgui::ImagePanel( pParent, pName )
{
    g_ClassImagePanels.AddToTail( this );
    m_ModelName[0] = 0;
}<br>
CClassImagePanel::~CClassImagePanel()
{
    g_ClassImagePanels.FindAndRemove( this );
}<br>
void CClassImagePanel::ApplySettings( KeyValues *inResourceData )
{
    const char *pName = inResourceData->GetString( "3DModel" );
    if ( pName )
    {
        Q_strncpy( m_ModelName, pName, sizeof( m_ModelName ) );
    }<br>
    BaseClass::ApplySettings( inResourceData );
}<br>
void CClassImagePanel::Paint()
{
    BaseClass::Paint();
}<br>
Panel *TeamMenu::CreateControlByName(const char *controlName)
{
    if ( Q_stricmp( controlName, "ClassImagePanel" ) == 0 )
    {
        return new CClassImagePanel( NULL, controlName );
    }<br>
    return BaseClass::CreateControlByName( controlName );
}
==sdk_clientmode.cpp==
This code is set out in the order of declaration in the cpp file - alternatively, you could declare prototypes of the utility functions and the update function then define them at the bottom of the file.
Declare handles to a baseanimatingoverlay for our player model and a baseanimating for our weapon model.
CHandle<C_BaseAnimatingOverlay> g_ClassImagePlayer; // player
CHandle<C_BaseAnimating> g_ClassImageWeapon; // weapon
===Helper Functions===
Utility function to let us know if a panel on our global list is visible or not - this dictates whether or not we should continue and draw the model.
// Utility to determine if the vgui panel is visible
bool WillPanelBeVisible( vgui::VPANEL hPanel )
{
    while ( hPanel )
    {
        if ( !vgui::ipanel()->IsVisible( hPanel ) )
            return false;
        hPanel = vgui::ipanel()->GetParent( hPanel );
    }
    return true;
}
Utility function to check if we should recreate the model data
// Called to see if we should be creating or recreating the model instances
bool ShouldRecreateClassImageEntity( C_BaseAnimating *pEnt, const char *pNewModelName )
{
    if ( !pNewModelName || !pNewModelName[0] )
        return false;
    if ( !pEnt )
        return true;<br>
    const model_t *pModel = pEnt->GetModel();<br>
    if ( !pModel )
        return true;
    const char *pName = modelinfo->GetModelName( pModel );<br>
    if ( !pName )
        return true;
    // reload only if names are different
    return( Q_stricmp( pName, pNewModelName ) != 0 );
}
===UpdateClassImageEntity===
This is our largest function, it sets the animation to play for the upper and lower sections of our player model, sets the weapon model to display, renders the model and updates the animation state.
void UpdateClassImageEntity(
        const char *pModelName,
        int x, int y, int width, int height )
{
    C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();<br>
    if ( !pLocalPlayer )
        return;<br><br>
    const char *pWeaponName = "models/weapons/f2000/w_f2000.mdl";
    const char *pWeaponSequence = "Idle_Upper_Aug";<br>
    C_BaseAnimatingOverlay *pPlayerModel = g_ClassImagePlayer.Get();<br>
    // Does the entity even exist yet?
    bool recreatePlayer = ShouldRecreateClassImageEntity( pPlayerModel, pModelName );
    if ( recreatePlayer )
    {
        if ( pPlayerModel )
            pPlayerModel->Remove();<br>
        pPlayerModel = new C_BaseAnimatingOverlay;
        pPlayerModel->InitializeAsClientEntity( pModelName, RENDER_GROUP_OPAQUE_ENTITY );
        pPlayerModel->AddEffects( EF_NODRAW ); // don't let the renderer draw the model normally<br>
        // have the player stand idle
        pPlayerModel->SetSequence( pPlayerModel->LookupSequence( "Idle_lower" ) );
        pPlayerModel->SetPoseParameter( 0, 0.0f ); // move_yaw
        pPlayerModel->SetPoseParameter( 1, 10.0f ); // body_pitch, look down a bit
        pPlayerModel->SetPoseParameter( 2, 0.0f ); // body_yaw
        pPlayerModel->SetPoseParameter( 3, 0.0f ); // move_y
        pPlayerModel->SetPoseParameter( 4, 0.0f ); // move_x, walk forward<br>
        g_ClassImagePlayer = pPlayerModel;
    }<br>
    C_BaseAnimating *pWeaponModel = g_ClassImageWeapon.Get();<br>
    // Does the entity even exist yet?
    if ( recreatePlayer || ShouldRecreateClassImageEntity( pWeaponModel, pWeaponName ) )
    {
        if ( pWeaponModel )
            pWeaponModel->Remove();<br>
        pWeaponModel = new C_BaseAnimating;
        pWeaponModel->InitializeAsClientEntity( pWeaponName, RENDER_GROUP_OPAQUE_ENTITY );
        pWeaponModel->AddEffects( EF_NODRAW ); // don't let the renderer draw the model normally
        pWeaponModel->FollowEntity( pPlayerModel ); // attach to player model
        g_ClassImageWeapon = pWeaponModel;
    }<br>
    Vector origin = pLocalPlayer->EyePosition();
    Vector lightOrigin = origin;<br>
    // find a spot inside the world for the dlight's origin, or it won't illuminate the model
    Vector testPos( origin.x - 100, origin.y, origin.z + 100 );
    trace_t tr;
    UTIL_TraceLine( origin, testPos, MASK_OPAQUE, pLocalPlayer, COLLISION_GROUP_NONE, &tr );
    if ( tr.fraction == 1.0f )
    {
        lightOrigin = tr.endpos;
    }
    else
    {
        // Now move the model away so we get the correct illumination
        lightOrigin = tr.endpos + Vector( 1, 0, -1 ); // pull out from the solid
        Vector start = lightOrigin;
        Vector end = lightOrigin + Vector( 100, 0, -100 );
        UTIL_TraceLine( start, end, MASK_OPAQUE, pLocalPlayer, COLLISION_GROUP_NONE, &tr );
        origin = tr.endpos;
    }<br>
    float ambient = engine->GetLightForPoint( origin, true ).Length();<br>
    // Make a light so the model is well lit.
    // use a non-zero number so we cannibalize ourselves next frame
    dlight_t *dl = effects->CL_AllocDlight( LIGHT_INDEX_TE_DYNAMIC+1 );<br>
    dl->flags = DLIGHT_NO_WORLD_ILLUMINATION;
    dl->origin = lightOrigin;
    // Go away immediately so it doesn't light the world
    dl->die = gpGlobals->curtime + 0.1f; too.<br>
    dl->color.r = dl->color.g = dl->color.b = 250;
    if ( ambient < 1.0f )
    {
        dl->color.exponent = 1 + (1 - ambient) * 2;
    }
    dl->radius = 400;<br>
    // move player model in front of our view
    pPlayerModel->SetAbsOrigin( origin );
    pPlayerModel->SetAbsAngles( QAngle( 0, 210, 0 ) );<br>
    // set upper body animation
    pPlayerModel->m_SequenceTransitioner.Update(
        pPlayerModel->GetModelPtr(),
        pPlayerModel->LookupSequence( "walk_lower" ),
        pPlayerModel->GetCycle(),
        pPlayerModel->GetPlaybackRate(),
        gpGlobals->realtime,
        false,
        true
        );<br>
    // Now, blend the lower and upper (aim) anims together
    pPlayerModel->SetNumAnimOverlays( 2 );
    int numOverlays = pPlayerModel->GetNumAnimOverlays();<br>
    for ( int i=0; i < numOverlays; ++i )
    {
                C_AnimationLayer *layer = pPlayerModel->GetAnimOverlay( i );<br>
                layer->flCycle = pPlayerModel->GetCycle();<br>
                if ( i )
                    layer->nSequence = pPlayerModel->LookupSequence( pWeaponSequence );
                else
                    layer->nSequence = pPlayerModel->LookupSequence( "walk_lower" );
        layer->flPlaybackrate = 1.0;
        layer->flWeight = 1.0f;
        layer->SetOrder( i );
    }<br>
    pPlayerModel->FrameAdvance( gpGlobals->frametime );<br>
    // Now draw it.
    CViewSetup view;
    view.x = x;
    view.y = y;
    view.width = width;
    view.height = height;<br>
    view.m_bOrtho = false;
    view.fov = 54;<br>
    view.origin = origin + Vector( -110, -5, -5 );<br>
    Vector vMins, vMaxs;
    pPlayerModel->C_BaseAnimating::GetRenderBounds( vMins, vMaxs );
    view.origin.z += ( vMins.z + vMaxs.z ) * 0.55f;<br>
    view.angles.Init();
    view.m_vUnreflectedOrigin = view.origin;
    view.zNear = VIEW_NEARZ;
    view.zFar = 1000;
    view.m_bForceAspectRatio1To1 = false;<br>
    Frustum dummyFrustum;
    render->ViewSetup3D( &view, dummyFrustum );<br>
    pPlayerModel->DrawModel( STUDIO_RENDER );<br>
    if ( pWeaponModel )
    {
        pWeaponModel->DrawModel( STUDIO_RENDER );
    }
}
===PostRenderVGUI===
void ClientModeSDKNormal::PostRenderVGui()
{
    // If the team menu is up, then we will render the model of the character that is currently selected.
    for ( int i=0; i < g_ClassImagePanels.Count(); i++ )
    {
        CClassImagePanel *pPanel = g_ClassImagePanels[i];
        if ( WillPanelBeVisible( pPanel->GetVPanel() ) )
        {
            // Ok, we have a visible class image panel.
            int x, y, w, h;
            pPanel->GetBounds( x, y, w, h );
            pPanel->LocalToScreen( x, y );<br>
            // Allow for the border.
            x += 3;
            y += 5;
            w -= 2;
            h -= 10;<br>
            UpdateClassImageEntity( g_ClassImagePanels[i]->m_ModelName, x, y, w, h );
            return;
        }
    }
}
==teammenu.res==
"classimage"
{
        "ControlName" "ClassImagePanel"
        "fieldName" "classimage"
        "xpos" "270"
        "ypos" "170"
        "wide" "512"
        "tall" "384"
        "autoResize" "0"
        "pinCorner" "0"
        "visible" "1"
        "enabled" "1"
        "textAlignment" "west"
        "3DModel" "models/player/iris.mdl"
        "scaleImage" "1"
        "zpos" "1"
}

Revision as of 08:01, 12 March 2006