Paranoia2/cl_dll/render/gl_dlight.cpp

781 lines
19 KiB
C++

/*
gl_dlight.cpp - dynamic lighting
Copyright (C) 2014 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "hud.h"
#include <stringlib.h>
#include "cl_util.h"
#include "pm_defs.h"
#include "event_api.h"
#include "gl_local.h"
#include "gl_studio.h"
#include "gl_world.h"
#include "gl_grass.h"
/*
=============================================================================
PROJECTED LIGHTS
=============================================================================
*/
/*
===============
CL_ClearDlights
===============
*/
void CL_ClearDlights( void )
{
memset( tr.dlights, 0, sizeof( tr.dlights ));
}
/*
===============
CL_AllocDlight
===============
*/
CDynLight *CL_AllocDlight( int key )
{
CDynLight *dl;
// first look for an exact key match
if( key )
{
dl = tr.dlights;
for( int i = 0; i < MAX_DLIGHTS; i++, dl++ )
{
if( dl->key == key )
{
// reuse this light
return dl;
}
}
}
float time = GET_CLIENT_TIME();
// then look for anything else
dl = tr.dlights;
for( int i = 0; i < MAX_DLIGHTS; i++, dl++ )
{
if( dl->die < time && dl->key == 0 )
{
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
}
dl = &tr.dlights[0];
memset( dl, 0, sizeof( *dl ));
dl->key = key;
return dl;
}
/*
===============
CL_DecayLights
===============
*/
void CL_DecayLights( void )
{
float time = GET_CLIENT_TIME();
CDynLight *pl = tr.dlights;
for( int i = 0; i < MAX_DLIGHTS; i++, pl++ )
{
if( !pl->radius ) continue;
pl->radius -= tr.frametime * pl->decay;
if( pl->radius < 0.0f ) pl->radius = 0.0f;
if( pl->Expired( ))
memset( pl, 0, sizeof( *pl ));
}
}
//=====================================
// HasDynamicLights
//
// Return count dynamic lights for current frame
//=====================================
int HasDynamicLights( void )
{
int numDlights = 0;
if( !worldmodel->lightdata || !CVAR_TO_BOOL( cv_dynamiclight ))
return numDlights;
for( int i = 0; i < MAX_DLIGHTS; i++ )
{
CDynLight *pl = &tr.dlights[i];
if( pl->Expired( )) continue;
numDlights++;
}
return numDlights;
}
//=====================================
// HasStaticLights
//
// Return count static lights for current frame
//=====================================
int HasStaticLights( void )
{
int i, numLights = 0;
mworldlight_t *wl;
for( i = 0, wl = world->worldlights; i < world->numworldlights; i++, wl++ )
{
if( wl->emittype == emit_ignored )
continue;
if( !CHECKVISBIT( RI->view.vislight, i ))
continue;
numLights++;
}
return numLights;
}
/*
=============================================================================
DYNAMIC LIGHTS
=============================================================================
*/
/*
==================
R_AnimateLight
==================
*/
void R_AnimateLight( void )
{
int i, k, flight, clight;
float l, lerpfrac, backlerp;
float scale = 1.0f;
lightstyle_t *ls;
scale = tr.diffuseFactor;
if( tr.realframecount != 1 && !CVAR_TO_BOOL( r_lightstyles ))
return;
// light animations
// 'm' is normal light, 'a' is no light, 'z' is double bright
for( i = 0; i < MAX_LIGHTSTYLES; i++ )
{
ls = GET_LIGHTSTYLE( i );
if( !worldmodel->lightdata )
{
tr.lightstyle[i] = 256.0f * scale;
continue;
}
flight = (int)Q_floor( ls->time * 10.0f );
clight = (int)Q_ceil( ls->time * 10.0f );
lerpfrac = ( ls->time * 10 ) - flight;
backlerp = 1.0f - lerpfrac;
if( !ls->length )
{
tr.lightstyle[i] = 256.0f * scale;
continue;
}
else if( ls->length == 1 )
{
float value = ls->map[0];
// turn off the baked sunlight
if( tr.sun_light_enabled && i == LS_SKY )
value = (float)('a' - 'a');
// single length style so don't bother interpolating
tr.lightstyle[i] = value * 22.0f * scale;
continue;
}
else if( !ls->interp || !CVAR_TO_BOOL( r_lightstyle_lerping ))
{
tr.lightstyle[i] = ls->map[flight%ls->length] * 22.0f * scale;
continue;
}
// interpolate animating light
// frame just gone
k = ls->map[flight % ls->length];
l = (float)( k * 22.0f ) * backlerp;
// upcoming frame
k = ls->map[clight % ls->length];
l += (float)( k * 22.0f ) * lerpfrac;
tr.lightstyle[i] = l * scale;
}
}
/*
=======================================================================
GATHER DYNAMIC LIGHTING
=======================================================================
*/
/*
=================
R_LightsForPoint
=================
*/
Vector R_LightsForPoint( const Vector &point, float radius )
{
Vector lightColor;
if( radius <= 0.0f )
radius = 1.0f;
lightColor = g_vecZero;
for( int lnum = 0; lnum < MAX_DLIGHTS; lnum++ )
{
CDynLight *dl = &tr.dlights[lnum];
float atten = 1.0f;
if( dl->type == LIGHT_DIRECTIONAL )
continue;
if( dl->die < GET_CLIENT_TIME() || !dl->radius )
continue;
Vector dir = (dl->origin - point);
float dist = dir.Length();
if( !dist || dist > ( dl->radius + radius ))
continue;
if( dl->frustum.CullSphere( point, radius ))
continue;
atten = 1.0 - saturate( pow( dist * ( 1.0f / dl->radius ), 2.2f ));
if( atten <= 0.0 ) continue; // fast reject
if( dl->type == LIGHT_SPOT )
{
Vector lightDir = dl->frustum.GetPlane( FRUSTUM_FAR )->normal.Normalize();
float fov_x, fov_y;
// BUGBUG: we use 5:4 aspect not an 4:3
if( dl->flags & DLF_ASPECT3X4 )
fov_y = dl->fov * (5.0f / 4.0f);
else if( dl->flags & DLF_ASPECT4X3 )
fov_y = dl->fov * (4.0f / 5.0f);
else fov_y = dl->fov;
fov_x = dl->fov;
// spot attenuation
float spotDot = DotProduct( dir.Normalize(), lightDir );
fov_x = DEG2RAD( fov_x * 0.25f );
fov_y = DEG2RAD( fov_y * 0.25f );
float spotCos = cos( fov_x + fov_y );
if( spotDot < spotCos ) continue;
}
lightColor += (dl->color * 0.5f * atten);
}
return lightColor;
}
/*
================
R_GetLightVectors
Get light vectors for entity
================
*/
void R_GetLightVectors( cl_entity_t *pEnt, Vector &origin, Vector &angles )
{
// fill default case
origin = pEnt->origin;
angles = pEnt->angles;
// try to grab position from attachment
if( pEnt->curstate.aiment > 0 && pEnt->curstate.movetype == MOVETYPE_FOLLOW )
{
cl_entity_t *pParent = GET_ENTITY( pEnt->curstate.aiment );
studiohdr_t *pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pParent->model );
if( pParent && pParent->model && pStudioHeader != NULL )
{
// make sure what model really has attachements
if( pEnt->curstate.body > 0 && ( pStudioHeader && pStudioHeader->numattachments > 0 ))
{
int num = bound( 1, pEnt->curstate.body, MAXSTUDIOATTACHMENTS );
R_StudioAttachmentPosAng( pParent, num - 1, &origin, &angles );
angles[PITCH] = -angles[PITCH]; // stupid quake bug
}
else if( pParent->curstate.movetype == MOVETYPE_STEP )
{
origin = pParent->origin;
angles = pParent->angles;
// add the eye position for monster
if( pParent->curstate.eflags & EFLAG_SLERP )
origin += pStudioHeader->eyeposition;
}
else
{
origin = pParent->origin;
angles = pParent->angles;
}
}
}
// all other parent types will be attached on the server
}
/*
================
R_SetupLightTexture
Must be called after R_SetupLightParams
================
*/
void R_SetupLightTexture( CDynLight *pl, int texture )
{
ASSERT( pl != NULL );
pl->spotlightTexture = texture;
}
/*
================
R_SetupLightParams
================
*/
void R_SetupLightParams( CDynLight *pl, const Vector &origin, const Vector &angles, float radius, float fov, int type, int flags )
{
pl->type = bound( LIGHT_SPOT, type, LIGHT_DIRECTIONAL );
if( pl->type == LIGHT_OMNI )
{
for( int i = 0; i < MAX_SHADOWMAPS; i++ )
pl->shadowTexture[i] = tr.depthCubemap; // stub
if( !GL_Support( R_TEXTURECUBEMAP_EXT ))
flags |= DLF_NOSHADOWS;
}
else
{
for( int i = 0; i < MAX_SHADOWMAPS; i++ )
pl->shadowTexture[i] = tr.depthTexture; // stub
}
if( pl->origin != origin || pl->angles != angles || pl->fov != fov || pl->radius != radius || pl->flags != flags )
{
pl->origin = origin;
pl->angles = angles;
pl->radius = radius;
pl->flags = flags;
pl->update = true;
pl->fov = fov;
}
}
/*
================
R_SetupLightProjection
General setup light projections.
Calling only once per frame
================
*/
void R_SetupLightProjection( CDynLight *pl )
{
if( !pl->update )
{
if( pl->type != LIGHT_DIRECTIONAL )
{
if( !R_ScissorForFrustum( &pl->frustum, &pl->x, &pl->y, &pl->w, &pl->h ))
SetBits( pl->flags, DLF_CULLED );
else ClearBits( pl->flags, DLF_CULLED );
}
return;
}
if( pl->type == LIGHT_SPOT )
{
float fov_x, fov_y;
// BUGBUG: we use 5:4 aspect not an 4:3
if( pl->flags & DLF_ASPECT3X4 )
fov_y = pl->fov * (5.0f / 4.0f);
else if( pl->flags & DLF_ASPECT4X3 )
fov_y = pl->fov * (4.0f / 5.0f);
else fov_y = pl->fov;
// e.g. for fake cinema projectors
if( FBitSet( pl->flags, DLF_FLIPTEXTURE ))
fov_x = -pl->fov;
else fov_x = pl->fov;
pl->projectionMatrix.CreateProjection( fov_x, fov_y, Z_NEAR_LIGHT, pl->radius );
pl->modelviewMatrix.CreateModelview(); // init quake world orientation
pl->viewMatrix = matrix4x4( pl->origin, pl->angles );
// transform projector by position and angles
pl->modelviewMatrix.ConcatRotate( -pl->angles.z, 1, 0, 0 );
pl->modelviewMatrix.ConcatRotate( -pl->angles.x, 0, 1, 0 );
pl->modelviewMatrix.ConcatRotate( -pl->angles.y, 0, 0, 1 );
pl->modelviewMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z );
pl->frustum.InitProjection( pl->viewMatrix, Z_NEAR_LIGHT, pl->radius, pl->fov, fov_y );
pl->frustum.ComputeFrustumBounds( pl->absmin, pl->absmax );
pl->frustum.DisablePlane( FRUSTUM_FAR ); // only use plane.normal
}
else if( pl->type == LIGHT_OMNI )
{
pl->modelviewMatrix.CreateModelview();
pl->viewMatrix = matrix4x4( pl->origin, g_vecZero );
pl->projectionMatrix.CreateProjection( 90.0f, 90.0f, Z_NEAR_LIGHT, pl->radius );
// transform omnilight by position
pl->modelviewMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z );
pl->frustum.InitBoxFrustum( pl->origin, pl->radius );
pl->frustum.ComputeFrustumBounds( pl->absmin, pl->absmax );
}
else if( pl->type == LIGHT_DIRECTIONAL )
{
Vector skyDir, angles, up;
skyDir = tr.sky_normal.Normalize();
VectorAngles2( skyDir, angles );
AngleVectors( angles, NULL, NULL, up );
pl->viewMatrix.LookAt( tr.cached_vieworigin, skyDir, up );
pl->modelviewMatrix = pl->viewMatrix;
ClearBounds( pl->absmin, pl->absmax );
}
else
{
HOST_ERROR( "R_SetupLightProjection: unknown light type %i\n", pl->type );
}
if( pl->type != LIGHT_DIRECTIONAL )
{
matrix4x4 projectionView, s1;
projectionView = pl->projectionMatrix.Concat( pl->modelviewMatrix );
pl->lightviewProjMatrix = projectionView;
s1.CreateTranslate( 0.5f, 0.5f, 0.5f );
s1.ConcatScale( 0.5f, 0.5f, 0.5f );
pl->textureMatrix[0] = projectionView;
pl->shadowMatrix[0] = s1.Concat( projectionView );
if( !R_ScissorForFrustum( &pl->frustum, &pl->x, &pl->y, &pl->w, &pl->h ))
SetBits( pl->flags, DLF_CULLED );
else ClearBits( pl->flags, DLF_CULLED );
}
pl->update = false;
}
/*
================
R_SetupDynamicLights
================
*/
void R_SetupDynamicLights( void )
{
CDynLight *pl;
dlight_t *dl;
for( int lnum = 0; lnum < MAX_ENGINE_DLIGHTS; lnum++ )
{
dl = GET_DYNAMIC_LIGHT( lnum );
pl = &tr.dlights[MAX_ENGINE_DLIGHTS+lnum];
// NOTE: here we copies dlight settings 'as-is'
// without reallocating by key because key may
// be set indirectly without call CL_AllocDlight
if( dl->die < GET_CLIENT_TIME() || !dl->radius )
{
// light is expired. Clear it
memset( pl, 0, sizeof( *pl ));
continue;
}
pl->key = dl->key;
pl->die = dl->die + tr.frametime;
pl->decay = dl->decay;
pl->color.x = dl->color.r * (1.0 / 255.0f);
pl->color.y = dl->color.g * (1.0 / 255.0f);
pl->color.z = dl->color.b * (1.0 / 255.0f);
pl->origin = dl->origin;
pl->radius = dl->radius;
pl->type = LIGHT_OMNI;
pl->update = true;
R_SetupLightProjection( pl );
}
pl = tr.dlights;
for( int i = 0; i < MAX_DLIGHTS; i++, pl++ )
{
if( pl->Expired( )) continue;
R_SetupLightProjection( pl );
}
}
/*
=======================================================================
WORLDLIGHTS PROCESSING
=======================================================================
*/
static vec_t LightDistanceFalloff( const mworldlight_t *wl, const Vector &direction )
{
Vector delta = direction;
float dist = VectorNormalize( delta );
float dot = 1.0f; // assume dot is 1.0f
dist = Q_max( dist, 1.0 );
switch( wl->emittype )
{
case emit_surface:
return dot / (dist * dist);
case emit_skylight:
return dot;
break;
case emit_spotlight: // directional & positional
case emit_point:
// cull out stuff that's too far
if( dist > wl->radius )
return 0.0f;
return dot / (dist * dist);
break;
}
return 1.0f;
}
//-----------------------------------------------------------------------------
// This method returns the effective intensity of a light as seen from
// a particular point. PVS is used to speed up the task.
//-----------------------------------------------------------------------------
static float LightIntensityAtPoint( mworldlight_t *wl, const Vector &mid, bool skipZ )
{
lightzbuffer_t *pZBuf = world->shadowzbuffers;
Vector direction;
// special case lights
if( wl->emittype == emit_skylight )
{
// check to see if you can hit the sky texture
Vector end = mid + wl->normal * -BOGUS_RANGE; // max_range * sqrt(3)
msurface_t *surf = gEngfuncs.pEventAPI->EV_TraceSurface( 0, (float *)&mid, (float *)end );
// here, we didn't hit the sky, so we must be in shadow
if( !surf || !FBitSet( surf->flags, SURF_DRAWSKY ))
return 0.0f;
return 1.0f;
}
// all other lights
// check distance
direction = wl->origin - mid;
float ratio = LightDistanceFalloff( wl, direction );
// Early out for really low-intensity lights
// That way we don't need to ray-cast or normalize
float intensity = VectorMax( wl->intensity );
if( intensity * ratio < 4e-3 )
return 0.0f;
if( skipZ ) return ratio;
float dist = VectorNormalize( direction );
float flTraceDistance = dist;
// check if we are so close to the light that we shouldn't use our coarse z buf
if( dist < 8 * SHADOW_ZBUF_RES )
pZBuf = NULL;
LightShadowZBufferSample_t *pSample = NULL;
Vector epnt = mid;
if( pZBuf )
{
pSample = &( pZBuf->GetSample( direction ));
if(( pSample->m_flHitDistance < pSample->m_flTraceDistance ) || ( pSample->m_flTraceDistance >= dist ))
{
// hit!
if( dist > pSample->m_flHitDistance + 8 ) // shadow hit
return 0;
return ratio;
}
// cache miss
flTraceDistance = Q_max( 100.0f, 2.0f * dist ); // trace a little further for better caching
epnt += direction * ( dist - flTraceDistance );
}
pmtrace_t pm;
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
gEngfuncs.pEventAPI->EV_PlayerTrace( wl->origin, epnt, PM_WORLD_ONLY, -1, &pm );
// pm.fraction = 1.0f - pm.fraction;
float flHitDistance = ( pm.startsolid ) ? FLT_EPSILON : ( pm.fraction ) * flTraceDistance;
if( pSample )
{
pSample->m_flTraceDistance = flTraceDistance;
pSample->m_flHitDistance = ( pm.fraction >= 1.0 ) ? 1.0e23 : flHitDistance;
}
if( dist > flHitDistance + 8 )
return 0.0f;
return ratio;
}
static float LightIntensityInBox( mworldlight_t *wl, const Vector &mid, const Vector &mins, const Vector &maxs, bool skipZ )
{
float sphereRadius;
float angle, sinAngle;
float dist, distSqr;
// Choose the point closest on the box to the light to get max intensity
// within the box....
switch( wl->emittype )
{
case emit_spotlight: // directional & positional
sphereRadius = (maxs - mid).Length();
// first do a sphere/sphere check
dist = (wl->origin - mid).Length();
if( dist > (sphereRadius + wl->radius ))
return 0;
// PERFORMANCE: precalc this and store in the light?
angle = acos( wl->stopdot2 );
sinAngle = sin( angle );
if( !IsSphereIntersectingCone( mid, sphereRadius, wl->origin, wl->normal, sinAngle, wl->stopdot2 ))
return 0;
// NOTE: fall through to radius check in point case
case emit_point:
distSqr = CalcSqrDistanceToAABB( mins, maxs, wl->origin );
if( distSqr > wl->radius * wl->radius )
return 0;
break;
case emit_surface: // directional & positional, fixed cone size
sphereRadius = (maxs - mid).Length();
// first do a sphere/sphere check
dist = (wl->origin - mid).Length();
if( dist > ( sphereRadius + wl->radius ))
return 0;
// PERFORMANCE: precalc this and store in the light?
if( !IsSphereIntersectingCone( mid, sphereRadius, wl->origin, wl->normal, 1.0f, 0.0f ))
return 0;
break;
}
return LightIntensityAtPoint( wl, mid, skipZ );
}
/*
=================
R_FindWorldLights
search for lights that potentially can lit bbox
=================
*/
void R_FindWorldLights( const Vector &origin, const Vector &mins, const Vector &maxs, byte lights[MAXDYNLIGHTS], bool skipZ )
{
mworldlight_t *wl = world->worldlights;
Vector absmin = origin + mins;
Vector absmax = origin + maxs;
vec2_t indexes[32];
int count = 0;
for( int i = 0; i < world->numworldlights; i++, wl++ )
{
if( !Mod_BoxVisible( absmin, absmax, wl->pvs ))
continue;
float ratio = LightIntensityInBox( wl, origin, mins, maxs, skipZ );
// no light contribution?
if( ratio <= 0.0f ) continue;
Vector add = wl->intensity * ratio;
float illum = VectorMax( add );
//Msg( "#%i, type %d, illum %.5f, intensity %g %g %g\n", i, wl->emittype, illum, wl->intensity[0], wl->intensity[1], wl->intensity[2] );
if( illum <= 4e-3 )
continue;
if( count >= ARRAYSIZE( indexes ) / 2 )
break;
indexes[count][0] = i;
indexes[count][1] = illum;
count++;
}
memset( lights, 255, sizeof( byte ) * MAXDYNLIGHTS );
get_next_light:
float maxIllum = 0.0;
int ignored = -1;
int light = 255;
for( int i = 0; i < count; i++ )
{
if( indexes[i][0] == -1.0f )
continue;
// skylight has a maximum priority
if( indexes[i][1] > maxIllum )
{
maxIllum = indexes[i][1];
light = indexes[i][0];
ignored = i;
}
}
if( ignored == -1 )
return;
int i;
for( i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ );
if( i < (int)cv_deferred_maxlights->value )
lights[i] = light; // nearest light for surf
indexes[ignored][0] = -1; // this light is handled
// if( count > (int)cv_deferred_maxlights->value && i == (int)cv_deferred_maxlights->value )
// Msg( "skipped light %i intensity %g, type %d\n", light, maxIllum, world->worldlights[light].emittype );
goto get_next_light;
}