470 lines
13 KiB
C++
470 lines
13 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
|
|
#include "render_pch.h"
|
|
#include "gl_matsysiface.h"
|
|
#include "gl_cvars.h"
|
|
#include "enginetrace.h"
|
|
#include "r_local.h"
|
|
#include "gl_model_private.h"
|
|
#include "materialsystem/imesh.h"
|
|
#include "cdll_engine_int.h"
|
|
#include "cl_main.h"
|
|
#include "debugoverlay.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
static ConVar r_drawlights( "r_drawlights", "0", FCVAR_CHEAT );
|
|
static ConVar r_drawlightinfo( "r_drawlightinfo", "0", FCVAR_CHEAT );
|
|
|
|
static bool s_bActivateLightSprites = false;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Should we draw light sprites over visible lights?
|
|
//-----------------------------------------------------------------------------
|
|
bool ActivateLightSprites( bool bActive )
|
|
{
|
|
bool bOldValue = s_bActivateLightSprites;
|
|
s_bActivateLightSprites = bActive;
|
|
return bOldValue;
|
|
}
|
|
|
|
|
|
#define LIGHT_MIN_LIGHT_VALUE 0.03f
|
|
|
|
float ComputeLightRadius( dworldlight_t *pLight, bool bIsHDR )
|
|
{
|
|
float flLightRadius = pLight->radius;
|
|
if (flLightRadius == 0.0f)
|
|
{
|
|
// HACKHACK: Usually our designers scale the light intensity by 0.5 in HDR
|
|
// This keeps the behavior of the cutoff radius consistent between LDR and HDR
|
|
float minLightValue = bIsHDR ? (LIGHT_MIN_LIGHT_VALUE * 0.5f) : LIGHT_MIN_LIGHT_VALUE;
|
|
|
|
// Compute the light range based on attenuation factors
|
|
float flIntensity = sqrtf( DotProduct( pLight->intensity, pLight->intensity ) );
|
|
if (pLight->quadratic_attn == 0.0f)
|
|
{
|
|
if (pLight->linear_attn == 0.0f)
|
|
{
|
|
// Infinite, but we're not going to draw it as such
|
|
flLightRadius = 2000;
|
|
}
|
|
else
|
|
{
|
|
flLightRadius = (flIntensity / minLightValue - pLight->constant_attn) / pLight->linear_attn;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float a = pLight->quadratic_attn;
|
|
float b = pLight->linear_attn;
|
|
float c = pLight->constant_attn - flIntensity / minLightValue;
|
|
float discrim = b * b - 4 * a * c;
|
|
if (discrim < 0.0f)
|
|
{
|
|
// Infinite, but we're not going to draw it as such
|
|
flLightRadius = 2000;
|
|
}
|
|
else
|
|
{
|
|
flLightRadius = (-b + sqrtf(discrim)) / (2.0f * a);
|
|
if (flLightRadius < 0)
|
|
flLightRadius = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return flLightRadius;
|
|
}
|
|
|
|
|
|
static void DrawLightSprite( dworldlight_t *pLight, float angleAttenFactor )
|
|
{
|
|
Vector lightToEye;
|
|
lightToEye = CurrentViewOrigin() - pLight->origin;
|
|
VectorNormalize( lightToEye );
|
|
Vector up( 0.0f, 0.0f, 1.0f );
|
|
Vector right;
|
|
CrossProduct( up, lightToEye, right );
|
|
VectorNormalize( right );
|
|
CrossProduct( lightToEye, right, up );
|
|
VectorNormalize( up );
|
|
|
|
/*
|
|
up *= dist;
|
|
right *= dist;
|
|
|
|
up *= ( 1.0f / 5.0f );
|
|
right *= ( 1.0f / 5.0f );
|
|
|
|
up *= 1.0f / sqrt( pLight->constant_attn + dist * pLight->linear_attn + dist * dist * pLight->quadratic_attn );
|
|
right *= 1.0f / sqrt( pLight->constant_attn + dist * pLight->linear_attn + dist * dist * pLight->quadratic_attn );
|
|
*/
|
|
|
|
// float distFactor = 1.0f / ( pLight->constant_attn + dist * pLight->linear_attn + dist * dist * pLight->quadratic_attn );
|
|
//float distFactor = 1.0f;
|
|
|
|
Vector color = pLight->intensity;
|
|
VectorNormalize( color );
|
|
color *= angleAttenFactor;
|
|
|
|
color[0] = pow( color[0], 1.0f / 2.2f );
|
|
color[1] = pow( color[1], 1.0f / 2.2f );
|
|
color[2] = pow( color[2], 1.0f / 2.2f );
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
pRenderContext->Bind( g_pMaterialLightSprite );
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( );
|
|
CMeshBuilder meshBuilder;
|
|
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
|
|
|
|
float radius = 16.0f;
|
|
Vector p;
|
|
|
|
ColorClamp( color );
|
|
|
|
p = pLight->origin + right * radius + up * radius;
|
|
meshBuilder.TexCoord2f( 0, 1.0f, 1.0f );
|
|
meshBuilder.Color3fv( color.Base() );
|
|
meshBuilder.Position3fv( p.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
p = pLight->origin + right * -radius + up * radius;
|
|
meshBuilder.TexCoord2f( 0, 0.0f, 1.0f );
|
|
meshBuilder.Color3fv( color.Base() );
|
|
meshBuilder.Position3fv( p.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
p = pLight->origin + right * -radius + up * -radius;
|
|
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f );
|
|
meshBuilder.Color3fv( color.Base() );
|
|
meshBuilder.Position3fv( p.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
p = pLight->origin + right * radius + up * -radius;
|
|
meshBuilder.TexCoord2f( 0, 1.0f, 0.0f );
|
|
meshBuilder.Color3fv( color.Base() );
|
|
meshBuilder.Position3fv( p.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
meshBuilder.End();
|
|
pMesh->Draw();
|
|
}
|
|
|
|
#define POINT_THETA_GRID 8
|
|
#define POINT_PHI_GRID 8
|
|
|
|
static void DrawPointLight( const Vector &vecOrigin, float flLightRadius )
|
|
{
|
|
int nVertCount = POINT_THETA_GRID * (POINT_PHI_GRID + 1);
|
|
int nIndexCount = 8 * POINT_THETA_GRID * POINT_PHI_GRID;
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
pRenderContext->Bind( g_materialWorldWireframeZBuffer );
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( );
|
|
CMeshBuilder meshBuilder;
|
|
meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertCount, nIndexCount );
|
|
|
|
float dTheta = 360.0f / POINT_THETA_GRID;
|
|
float dPhi = 180.0f / POINT_PHI_GRID;
|
|
|
|
Vector pt;
|
|
int i;
|
|
float flPhi = 0;
|
|
for ( i = 0; i <= POINT_PHI_GRID; ++i )
|
|
{
|
|
float flSinPhi = sin(DEG2RAD(flPhi));
|
|
float flCosPhi = cos(DEG2RAD(flPhi));
|
|
float flTheta = 0;
|
|
for ( int j = 0; j < POINT_THETA_GRID; ++j )
|
|
{
|
|
pt = vecOrigin;
|
|
pt.x += flLightRadius * cos(DEG2RAD(flTheta)) * flSinPhi;
|
|
pt.y += flLightRadius * sin(DEG2RAD(flTheta)) * flSinPhi;
|
|
pt.z += flLightRadius * flCosPhi;
|
|
|
|
meshBuilder.Position3fv( pt.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
flTheta += dTheta;
|
|
}
|
|
|
|
flPhi += dPhi;
|
|
}
|
|
|
|
for ( i = 0; i < POINT_THETA_GRID; ++i )
|
|
{
|
|
for ( int j = 0; j < POINT_PHI_GRID; ++j )
|
|
{
|
|
int nNextIndex = (j != POINT_PHI_GRID - 1) ? j + 1 : 0;
|
|
|
|
meshBuilder.Index( i * POINT_PHI_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( (i + 1) * POINT_PHI_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( (i + 1) * POINT_PHI_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( (i + 1) * POINT_PHI_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( (i + 1) * POINT_PHI_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( i * POINT_PHI_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( i * POINT_PHI_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( i * POINT_PHI_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
}
|
|
}
|
|
|
|
meshBuilder.End();
|
|
pMesh->Draw();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Draws the spot light
|
|
//-----------------------------------------------------------------------------
|
|
#define SPOT_GRID_LINE_COUNT 20
|
|
#define SPOT_GRID_LINE_DISTANCE 50
|
|
#define SPOT_RADIAL_GRID 8
|
|
|
|
void DrawSpotLight( dworldlight_t *pLight )
|
|
{
|
|
float flLightRadius = ComputeLightRadius( pLight, false );
|
|
|
|
int nGridLines = (int)(flLightRadius / SPOT_GRID_LINE_DISTANCE) + 1;
|
|
int nVertCount = SPOT_RADIAL_GRID * (nGridLines + 1);
|
|
int nIndexCount = 8 * SPOT_RADIAL_GRID * nGridLines;
|
|
|
|
// Compute a basis perpendicular to the normal
|
|
Vector xaxis, yaxis;
|
|
int nMinIndex = fabs(pLight->normal[0]) < fabs(pLight->normal[1]) ? 0 : 1;
|
|
nMinIndex = fabs(pLight->normal[nMinIndex]) < fabs(pLight->normal[2]) ? nMinIndex : 2;
|
|
Vector perp = vec3_origin;
|
|
perp[nMinIndex] = 1.0f;
|
|
CrossProduct( perp, pLight->normal, xaxis );
|
|
VectorNormalize( xaxis );
|
|
CrossProduct( pLight->normal, xaxis, yaxis );
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
pRenderContext->Bind( g_materialWorldWireframeZBuffer );
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( );
|
|
CMeshBuilder meshBuilder;
|
|
meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertCount, nIndexCount );
|
|
|
|
float flAngle = acos(pLight->stopdot2);
|
|
float flTanAngle = tan(flAngle);
|
|
float dTheta = 360.0f / SPOT_RADIAL_GRID;
|
|
float flDist = 0.0f;
|
|
|
|
int i;
|
|
for ( i = 0; i <= nGridLines; ++i )
|
|
{
|
|
Vector pt, vecCenter;
|
|
VectorMA( pLight->origin, flDist, pLight->normal, vecCenter );
|
|
|
|
float flRadius = flDist * flTanAngle;
|
|
|
|
float flTempAngle = 0;
|
|
for ( int j = 0; j < SPOT_RADIAL_GRID; ++j )
|
|
{
|
|
float flSin = sin( DEG2RAD( flTempAngle ) );
|
|
float flCos = cos( DEG2RAD( flTempAngle ) );
|
|
VectorMA( vecCenter, flRadius * flCos, xaxis, pt );
|
|
VectorMA( pt, flRadius * flSin, yaxis, pt );
|
|
|
|
meshBuilder.Position3fv( pt.Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
flTempAngle += dTheta;
|
|
}
|
|
|
|
flDist += SPOT_GRID_LINE_DISTANCE;
|
|
}
|
|
|
|
for ( i = 0; i < nGridLines; ++i )
|
|
{
|
|
for ( int j = 0; j < SPOT_RADIAL_GRID; ++j )
|
|
{
|
|
int nNextIndex = (j != SPOT_RADIAL_GRID - 1) ? j + 1 : 0;
|
|
|
|
meshBuilder.Index( i * SPOT_RADIAL_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( (i + 1) * SPOT_RADIAL_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( (i + 1) * SPOT_RADIAL_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( (i + 1) * SPOT_RADIAL_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( (i + 1) * SPOT_RADIAL_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( i * SPOT_RADIAL_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
|
|
meshBuilder.Index( i * SPOT_RADIAL_GRID + nNextIndex );
|
|
meshBuilder.AdvanceIndex();
|
|
meshBuilder.Index( i * SPOT_RADIAL_GRID + j );
|
|
meshBuilder.AdvanceIndex();
|
|
}
|
|
}
|
|
|
|
meshBuilder.End();
|
|
pMesh->Draw();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Draws sprites over all visible lights
|
|
// NOTE: This is used to render env-cubemaps
|
|
//-----------------------------------------------------------------------------
|
|
void DrawLightSprites( void )
|
|
{
|
|
if (!s_bActivateLightSprites)
|
|
return;
|
|
|
|
int i;
|
|
for (i = 0; i < host_state.worldbrush->numworldlights; i++)
|
|
{
|
|
dworldlight_t *pLight = &host_state.worldbrush->worldlights[i];
|
|
trace_t tr;
|
|
CTraceFilterWorldAndPropsOnly traceFilter;
|
|
Ray_t ray;
|
|
ray.Init( CurrentViewOrigin(), pLight->origin );
|
|
g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, &traceFilter, &tr );
|
|
if( tr.fraction < 1.0f )
|
|
continue;
|
|
|
|
float angleAttenFactor = 0.0f;
|
|
Vector lightToEye;
|
|
lightToEye = CurrentViewOrigin() - pLight->origin;
|
|
VectorNormalize( lightToEye );
|
|
switch( pLight->type )
|
|
{
|
|
case emit_point:
|
|
angleAttenFactor = 1.0f;
|
|
break;
|
|
case emit_spotlight:
|
|
continue;
|
|
break;
|
|
case emit_surface:
|
|
// garymcthack - don't do surface lights
|
|
continue;
|
|
if( DotProduct( lightToEye, pLight->normal ) < 0.0f )
|
|
{
|
|
continue;
|
|
}
|
|
angleAttenFactor = 1.0f;
|
|
break;
|
|
case emit_skylight:
|
|
case emit_skyambient:
|
|
continue;
|
|
default:
|
|
assert( 0 );
|
|
continue;
|
|
}
|
|
DrawLightSprite( pLight, angleAttenFactor );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Draws debugging information for the lights
|
|
//-----------------------------------------------------------------------------
|
|
void DrawLightDebuggingInfo( void )
|
|
{
|
|
int i;
|
|
char buf[256];
|
|
int lineOffset;
|
|
|
|
int nLight = r_drawlights.GetInt();
|
|
|
|
if ( r_drawlightinfo.GetBool() )
|
|
{
|
|
for (i = 0; i < host_state.worldbrush->numworldlights; i++)
|
|
{
|
|
dworldlight_t *pLight = &host_state.worldbrush->worldlights[i];
|
|
|
|
lineOffset = 0;
|
|
Q_snprintf( buf, sizeof( buf ), "light: %d\n", i+1 );
|
|
CDebugOverlay::AddTextOverlay( pLight->origin, lineOffset++, 0, buf );
|
|
Q_snprintf( buf, sizeof( buf ), "origin: <%d, %d, %d>\n", (int)pLight->origin[0], (int)pLight->origin[1], (int)pLight->origin[2] );
|
|
CDebugOverlay::AddTextOverlay( pLight->origin, lineOffset++, 0, buf );
|
|
|
|
if (!nLight)
|
|
{
|
|
// avoid a double debug draw
|
|
DrawLightSprite( pLight, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!nLight)
|
|
return;
|
|
|
|
for (i = 0; i < host_state.worldbrush->numworldlights; i++)
|
|
{
|
|
if ((nLight > 0) && (i != nLight-1))
|
|
continue;
|
|
|
|
dworldlight_t *pLight = &host_state.worldbrush->worldlights[i];
|
|
Vector lightToEye;
|
|
float angleAttenFactor = 0.0f;
|
|
switch( pLight->type )
|
|
{
|
|
case emit_point:
|
|
angleAttenFactor = 1.0f;
|
|
DrawPointLight( pLight->origin, ComputeLightRadius( pLight, false ) );
|
|
break;
|
|
case emit_spotlight:
|
|
angleAttenFactor = 1.0f;
|
|
DrawSpotLight( pLight );
|
|
break;
|
|
case emit_surface:
|
|
// garymcthack - don't do surface lights
|
|
continue;
|
|
lightToEye = CurrentViewOrigin() - pLight->origin;
|
|
VectorNormalize( lightToEye );
|
|
if( DotProduct( lightToEye, pLight->normal ) < 0.0f )
|
|
{
|
|
continue;
|
|
}
|
|
angleAttenFactor = 1.0f;
|
|
break;
|
|
case emit_skylight:
|
|
case emit_skyambient:
|
|
continue;
|
|
default:
|
|
assert( 0 );
|
|
continue;
|
|
}
|
|
DrawLightSprite( pLight, angleAttenFactor );
|
|
}
|
|
|
|
int lnum;
|
|
for (lnum=0 ; lnum<MAX_DLIGHTS ; lnum++)
|
|
{
|
|
// If the light's not active, then continue
|
|
if ( (r_dlightactive & (1 << lnum)) == 0 )
|
|
continue;
|
|
|
|
DrawPointLight( cl_dlights[lnum].origin, cl_dlights[lnum].GetRadius() );
|
|
}
|
|
}
|