FOV and LOS Checks (HL1 SDK)

This is another article I wrote on The Wavelength in 2003. It is specific to the Half-Life 1 SDK, incase there’s anybody out there still writing HL1 mods:

This tutorial is sort of a compilation of several tutorials I have found regarding field of view and line of sight. These people (including myself) should be given credit where/when it is due: X-0ut, bigguy, and the Holy Wars team.

For starters, we need to declare the external variable which stores the client’s view angles, so towards the top of the file entity.cpp find:

extern vec3_t v_origin;
int g_iAlive = 1;

Add the v_angles variable to the end of the extern list after v_origin so that same block of code will look like this:

extern vec3_t v_origin, v_angles;
int g_iAlive = 1;

Now that we have the client’s view angles, lets add some functions!
Still in the same file (entity.cpp) after:

extern "C"
{
//—[SNIP]—//
    struct cl_entity_s DLLEXPORT *HUD_GetUserEntity( int index );
}

Put this code in:

// mazor @add

// mazor – this is holy wars' code, be sure to thank them
#define FOV_MIN_DISTANCE 100
static bool PlayerIsInFOV( struct cl_entity_s *ent )
{
    Vector up, right, forward;
    AngleVectors( v_angles, forward, right, up );
    float distance = ( ent->curstate.origin – v_origin ).Length();

    // nearby players are always in FOV
    if ( distance <= FOV_MIN_DISTANCE * 2 )
        return true;

    // if it's not in front, don't even bother
    Vector vecDir = ( ent->curstate.origin – v_origin ).Normalize( );
    if ( DotProduct ( vecDir, forward ) < 0 )
        return false;

    return true;
}
// Returns true if the given entity is in the current Field Of View
// It's not 100% accurate, and it can fail for large objects – but it's fine
// for our purposes (optimization).
static bool IsInFOV( struct cl_entity_s *ent )
{
    Vector forward, right, up;
    AngleVectors( v_angles, forward, right, up );
    float distance = ( ent->curstate.origin – v_origin ).Length();

    // nearby objects are always in FOV
    if ( distance <= FOV_MIN_DISTANCE )
        return true;

    // if it's not in front, don't even bother
    Vector vecDir = ( ent->curstate.origin – v_origin ).Normalize( );
    if ( DotProduct ( vecDir, forward ) < 0 )
        return false;

    // this code is partially lifted from somewhere else in HL's code
    float dot = fabs( DotProduct ( vecDir, right ) )
        + fabs( DotProduct ( vecDir, up ) ) * 0.5;
    // tweak for distance
    dot *= 1.0 + 0.2 * ( distance / 8192 );

    float arc = 1 – (gHUD.m_iFOV/360); // mazor – I'm not sure how accurate this is, but it seem to work fine
    if ( dot > arc )
        return false;

    return true;
}
// mazor – end holy wars' code

// mazor – this is bigguy's code, thank him dearly
static bool IsInLOS( struct cl_entity_s *pTarget )
{
    // we've got a 3rd person camera…
    Vector forward, up, vecL
ook, vecTarget;
    pmtrace_t   tr;
    AngleVectors(v_angles, forward, NULL, up);
    vecLook = v_origin;
    vecTarget = pTarget->origin;

    for (int i = 0; i < 6; i++)
    {
        vecTarget.x += gEngfuncs.pfnRandomFloat(pTarget->curstate.mins.x, pTarget->curstate.maxs.x);
        vecTarget.y += gEngfuncs.pfnRandomFloat(pTarget->curstate.mins.y, pTarget->curstate.maxs.y);
        vecTarget.z += gEngfuncs.pfnRandomFloat(pTarget->curstate.mins.z, pTarget->curstate.maxs.z);

        if (!pTarget->player) // ALWAYS ALWAYS draw the player if theyre in the FOV, regardless if we can trace to them
        {
            tr = *(gEngfuncs.PM_TraceLine( (float *)&vecLook, (float *)&vecTarget, 0, 2, -1 ));
            // PM_ParticleLine((float *)&vecLook, (float *)&vecTarget, 77, 1.0, 0.0 );

            if (tr.fraction < 1)
            {
                if (tr.ent)
                    return true; // assume we hit something we can see through
                else
                    continue;
            }
            else
                return true;
        }
        else
            return true;
        vecTarget = pTarget->origin; // reset target's origin
    }

    return false;
}
// mazor – end bigguy's code

// mazor end

That just defined all the functions we’ll need to calculate if a player or entity is inside the client’s FOV and LOS. Now we just need to make use of these functions, so find the function int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ), still in entity.cpp, and put this block of code inside of it:

// mazor begin
    switch ( type )
    {
    case ET_NORMAL:
        if( ent->model->type == mod_brush || ent->model->type == mod_sprite ) // always render brush models and sprites
            break; // always draw brush models
        if( g_iUser1 == 0 ) // if we aren't spectator
        {
            if( !IsInLOS(ent) )
                return 0;
            if( !IsInFOV(ent) )
                return 0;
        }
        // mazor end
        break;
    case ET_PLAYER:
        if( g_iUser1 == 0 )
        {
            if( !PlayerIsInFOV(ent) )
                return 0;
        }
        break;
    case ET_BEAM:
    case ET_TEMPENTITY:
    case ET_FRAGMENTED:
    default:
        break;
    }
// mazor end

Please note that if you have any other special things you need to do to the entities, it might be a good idea to add them to this struct, but by default, it should work.

KNOWN ISSUE: When an entity is on the verge of being masked by a world brush, the LOS calculation will cause the entity to flicker very rapidly, hence why this SHOULD NOT be calculated on players!

Leave a reply