forked from FWGS/Paranoia2
1973 lines
52 KiB
C++
1973 lines
52 KiB
C++
//
|
|
// written by BUzer for HL: Paranoia modification
|
|
//
|
|
// 2006
|
|
|
|
#include "hud.h"
|
|
#include "cl_util.h"
|
|
#include "const.h"
|
|
#include "cdll_int.h"
|
|
#include "com_model.h"
|
|
#include "entity_types.h"
|
|
#include "r_efx.h"
|
|
#include "event_api.h"
|
|
#include "pm_defs.h"
|
|
#include "pmtrace.h"
|
|
#include <utlarray.h>
|
|
#include "gl_local.h"
|
|
#include "gl_decals.h"
|
|
#include <stringlib.h>
|
|
#include "gl_shader.h"
|
|
#include "gl_world.h"
|
|
#include "gl_studio.h"
|
|
#include "gl_occlusion.h"
|
|
|
|
#define MAX_CLIPVERTS 64 // don't change this
|
|
#define MAX_GROUPENTRIES 512
|
|
#define MAX_BRUSH_DECALS 4096
|
|
#define MAX_DECAL_VERTICES 32 // per one fragment, enough in most cases
|
|
#define MAX_DECAL_INDICES (MAX_DECAL_VERTICES * 3)
|
|
#define DECAL_VERTICES_HASH_SIZE (MAX_DECAL_VERTICES >> 2)
|
|
|
|
#define MAX_DECAL_VERTS (MAX_DECAL_VERTICES * MAX_BRUSH_DECALS)
|
|
#define MAX_DECAL_ELEMS (MAX_DECAL_INDICES * MAX_BRUSH_DECALS)
|
|
|
|
typedef CUtlArray<int> CIntVector;
|
|
|
|
typedef struct decalVertex_s
|
|
{
|
|
Vector point;
|
|
int index;
|
|
decalVertex_s* nextHash;
|
|
} decalVertex_t;
|
|
|
|
// used for build new decals
|
|
typedef struct
|
|
{
|
|
// decal baseinfo
|
|
short entityIndex;
|
|
const DecalGroupEntry *decalDesc;
|
|
model_t *model;
|
|
byte flags;
|
|
float angle;
|
|
brushdecal_t *current;
|
|
Vector origin;
|
|
Vector axis[3]; // up, right, normal
|
|
|
|
// decal clipinfo
|
|
Vector mins, maxs;
|
|
|
|
mplane_t planes[6];
|
|
mplane_t splitPlanes[2];
|
|
|
|
Vector textureVecs[2];
|
|
|
|
word indices[MAX_DECAL_INDICES];
|
|
word numIndices;
|
|
|
|
// clipped vertices
|
|
decalVertex_t vertices[MAX_DECAL_VERTICES];
|
|
decalVertex_t *verticesHashTable[DECAL_VERTICES_HASH_SIZE];
|
|
byte numVertices;
|
|
} decalClip_t;
|
|
|
|
void DecalGroupEntry :: PreloadTextures( void )
|
|
{
|
|
char path[256];
|
|
char name[64];
|
|
|
|
if( m_init ) return;
|
|
|
|
if( m_DecalName[0] == '#' )
|
|
Q_strncpy( name, m_DecalName + 1, sizeof( name ));
|
|
else Q_strncpy( name, m_DecalName, sizeof( name ));
|
|
Q_snprintf( path, sizeof( path ), "gfx/decals/%s", name );
|
|
int id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP );
|
|
if( !id )
|
|
{
|
|
// decal was completely missed
|
|
m_init = true;
|
|
return;
|
|
}
|
|
|
|
gl_diffuse_id = id;
|
|
|
|
if( FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, gl_diffuse_id ), TF_HAS_ALPHA ))
|
|
opaque = true;
|
|
|
|
Q_snprintf( path, sizeof( path ), "gfx/decals/%s_norm", name );
|
|
if( IMAGE_EXISTS( path ))
|
|
gl_normalmap_id = LOAD_TEXTURE( path, NULL, 0, TF_NORMALMAP );
|
|
else gl_normalmap_id = tr.normalmapTexture;
|
|
|
|
Q_snprintf( path, sizeof( path ), "gfx/decals/%s_gloss", name );
|
|
if( IMAGE_EXISTS( path ))
|
|
gl_specular_id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP );
|
|
else gl_specular_id = tr.blackTexture;
|
|
|
|
Q_snprintf( path, sizeof( path ), "gfx/decals/%s_height", name );
|
|
if( IMAGE_EXISTS( path ))
|
|
gl_heightmap_id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP );
|
|
else gl_heightmap_id = tr.whiteTexture;
|
|
|
|
m_init = true;
|
|
}
|
|
|
|
DecalGroup *pDecalGroupList = NULL;
|
|
|
|
DecalGroup :: DecalGroup( const char *name, int numelems, DecalGroupEntry *source )
|
|
{
|
|
Q_strncpy( m_chGroupName, name, sizeof( m_chGroupName ));
|
|
|
|
pEntryArray = new DecalGroupEntry[numelems];
|
|
memcpy( pEntryArray, source, sizeof( DecalGroupEntry ) * numelems );
|
|
size = numelems;
|
|
|
|
// setup upcast
|
|
for( int i = 0; i < size; i++ )
|
|
pEntryArray[i].group = this;
|
|
|
|
pnext = pDecalGroupList;
|
|
pDecalGroupList = this;
|
|
}
|
|
|
|
DecalGroup :: ~DecalGroup( void )
|
|
{
|
|
for( int i = 0; i < size; i++ )
|
|
{
|
|
if( pEntryArray[i].gl_diffuse_id != 0 )
|
|
FREE_TEXTURE( pEntryArray[i].gl_diffuse_id );
|
|
if( pEntryArray[i].gl_normalmap_id != tr.normalmapTexture )
|
|
FREE_TEXTURE( pEntryArray[i].gl_normalmap_id );
|
|
if( pEntryArray[i].gl_specular_id != tr.blackTexture )
|
|
FREE_TEXTURE( pEntryArray[i].gl_specular_id );
|
|
if( pEntryArray[i].gl_heightmap_id != tr.whiteTexture )
|
|
FREE_TEXTURE( pEntryArray[i].gl_heightmap_id );
|
|
}
|
|
|
|
delete[] pEntryArray;
|
|
}
|
|
|
|
DecalGroupEntry *DecalGroup :: GetEntry( int num )
|
|
{
|
|
if( num < 0 || num >= size )
|
|
return NULL;
|
|
return &pEntryArray[num];
|
|
}
|
|
|
|
DecalGroupEntry *DecalGroup :: GetEntry( const char *name, int flags )
|
|
{
|
|
if( FBitSet( flags, FDECAL_NORANDOM ) && Q_strchr( name, '@' ))
|
|
{
|
|
// NOTE: restored decal contain name same as 'group@name'
|
|
// so we need separate them before search group
|
|
char *sep = Q_strchr( name, '@' );
|
|
if( sep != NULL ) *sep = '\0';
|
|
char *decalname = sep + 1;
|
|
|
|
DecalGroup *groupDesc = FindGroup( name );
|
|
if( !groupDesc )
|
|
{
|
|
ALERT( at_warning, "RestoreDecal: group %s is not exist\n", name );
|
|
return NULL;
|
|
}
|
|
|
|
return groupDesc->FindEntry( decalname );
|
|
}
|
|
else
|
|
{
|
|
DecalGroup *groupDesc = DecalGroup::FindGroup( name );
|
|
if( !groupDesc )
|
|
{
|
|
ALERT( at_warning, "CreateDecal: group %s is not exist\n", name );
|
|
return NULL;
|
|
}
|
|
|
|
return groupDesc->GetRandomDecal();
|
|
}
|
|
}
|
|
|
|
DecalGroupEntry *DecalGroup :: FindEntry( const char *name )
|
|
{
|
|
for( int i = 0; i < size; i++ )
|
|
{
|
|
if( !Q_strcmp( pEntryArray[i].m_DecalName, name ))
|
|
return &pEntryArray[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
DecalGroupEntry *DecalGroup :: GetRandomDecal( void )
|
|
{
|
|
return &pEntryArray[RANDOM_LONG( 0, size - 1 )];
|
|
}
|
|
|
|
DecalGroup *DecalGroup :: FindGroup( const char *name )
|
|
{
|
|
DecalGroup *plist = pDecalGroupList;
|
|
|
|
while( plist )
|
|
{
|
|
if( !Q_strcmp( plist->m_chGroupName, name ))
|
|
return plist;
|
|
plist = plist->pnext;
|
|
}
|
|
|
|
return NULL; // nothing found
|
|
}
|
|
|
|
static dvert_t g_decalVertexCache[MAX_DECAL_VERTS]; // 4.00 mbytes here if max decals count is 4096
|
|
static word g_decalIndexCache[MAX_DECAL_ELEMS]; // 1.5 mbytes here if max decals count is 4096
|
|
static unsigned int g_decalVertUsed;
|
|
static unsigned int g_decalElemUsed;
|
|
|
|
static brushdecal_t gDecalPool[MAX_BRUSH_DECALS];
|
|
static int gDecalCycle;
|
|
static int gDecalCount;
|
|
|
|
// ===========================
|
|
// Decals creation
|
|
// ===========================
|
|
// unlink pdecal from any surface it's attached to
|
|
static void R_UnlinkDecal( brushdecal_t *pdecal )
|
|
{
|
|
brushdecal_t *tmp;
|
|
|
|
if( pdecal->surface )
|
|
{
|
|
mextrasurf_t *es = pdecal->surface;
|
|
|
|
ASSERT( es != NULL );
|
|
|
|
if( es->pdecals == pdecal )
|
|
{
|
|
es->pdecals = pdecal->pnext;
|
|
}
|
|
else
|
|
{
|
|
tmp = es->pdecals;
|
|
if( !tmp ) HOST_ERROR( "R_UnlinkDecal: bad decal list\n" );
|
|
|
|
while( tmp->pnext )
|
|
{
|
|
if( tmp->pnext == pdecal )
|
|
{
|
|
tmp->pnext = pdecal->pnext;
|
|
break;
|
|
}
|
|
tmp = tmp->pnext;
|
|
}
|
|
}
|
|
|
|
if( FBitSet( es->surf->flags, SURF_REFLECT_PUDDLE ))
|
|
{
|
|
int puddleCount = 0;
|
|
|
|
tmp = es->pdecals;
|
|
while( tmp )
|
|
{
|
|
if( FBitSet( tmp->flags, FDECAL_PUDDLE ))
|
|
puddleCount++;
|
|
tmp = tmp->pnext;
|
|
}
|
|
|
|
if( !puddleCount )
|
|
{
|
|
ClearBits( es->surf->flags, SURF_REFLECT_PUDDLE );
|
|
GL_DeleteOcclusionQuery( es->surf );
|
|
}
|
|
}
|
|
|
|
if( !es->pdecals )
|
|
ClearBits( es->surf->flags, SURF_HAS_DECALS );
|
|
}
|
|
|
|
// decals are not used dynamic memory, just clear it
|
|
pdecal->surface = NULL;
|
|
}
|
|
|
|
static brushdecal_t *R_AllocDecal( brushdecal_t *pdecal = NULL )
|
|
{
|
|
int limit = MAX_BRUSH_DECALS;
|
|
|
|
if( r_decals->value < limit )
|
|
limit = r_decals->value;
|
|
|
|
if( !limit ) return NULL;
|
|
|
|
if( !pdecal )
|
|
{
|
|
int count = 0;
|
|
|
|
// check for the odd possiblity of infinte loop
|
|
do
|
|
{
|
|
if( gDecalCycle >= limit )
|
|
gDecalCycle = 0;
|
|
|
|
pdecal = &gDecalPool[gDecalCycle]; // reuse next decal
|
|
gDecalCycle++;
|
|
count++;
|
|
} while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit );
|
|
|
|
// decal allocated
|
|
if( gDecalCount < limit )
|
|
gDecalCount++;
|
|
}
|
|
|
|
// if decal is already linked to a surface, unlink it.
|
|
R_UnlinkDecal( pdecal );
|
|
|
|
return pdecal;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_ShaderDecalForward
|
|
|
|
Select the program for surface (diffuse\puddle)
|
|
===============
|
|
*/
|
|
static word R_ShaderDecalForward( brushdecal_t *decal )
|
|
{
|
|
char glname[64];
|
|
char options[MAX_OPTIONS_LENGTH];
|
|
const DecalGroupEntry *texinfo = decal->texinfo;
|
|
mextrasurf_t *es= decal->surface;
|
|
bool mirrorSurface = false;
|
|
bool have_parallax = false;
|
|
bool have_bumpmap = false;
|
|
msurface_t *s = es->surf;
|
|
|
|
ASSERT( worldmodel != NULL );
|
|
|
|
if( decal->forwardScene.IsValid( ))
|
|
return decal->forwardScene.GetHandle(); // valid
|
|
|
|
Q_strncpy( glname, "forward/decal_bmodel", sizeof( glname ));
|
|
memset( options, 0, sizeof( options ));
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
GL_AddShaderDirective( options, "DECAL_PUDDLE" );
|
|
|
|
if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f )
|
|
{
|
|
if( cv_parallax->value == 1.0f )
|
|
GL_AddShaderDirective( options, "PARALLAX_SIMPLE" );
|
|
else if( cv_parallax->value >= 2.0f )
|
|
GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" );
|
|
have_parallax = true;
|
|
}
|
|
|
|
material_t *mat = R_TextureAnimation( s )->material;
|
|
|
|
// process lightstyles
|
|
for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ )
|
|
{
|
|
if( tr.sun_light_enabled && s->styles[i] == LS_SKY )
|
|
continue; // skip the sunlight due realtime sun is enabled
|
|
GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i ));
|
|
}
|
|
|
|
if( CVAR_TO_BOOL( cv_brdf ))
|
|
GL_AddShaderDirective( options, "APPLY_PBS" );
|
|
|
|
// NOTE: deluxemap and normalmap are separate because some modes may using
|
|
// normalmap directly e.g. for mirror distorsion
|
|
if( es->normals ) GL_AddShaderDirective( options, "HAS_DELUXEMAP" );
|
|
|
|
if( CVAR_TO_BOOL( cv_specular ) && ( texinfo->gl_specular_id != tr.blackTexture ))
|
|
GL_AddShaderDirective( options, "HAS_GLOSSMAP" );
|
|
|
|
if( !texinfo->opaque )
|
|
{
|
|
// GL_DST_COLOR, GL_SRC_COLOR
|
|
GL_AddShaderDirective( options, "APPLY_COLORBLEND" );
|
|
}
|
|
|
|
if(( texinfo->gl_normalmap_id != tr.normalmapTexture ) && CVAR_TO_BOOL( cv_bump ))
|
|
{
|
|
GL_AddShaderDirective( options, "HAS_NORMALMAP" );
|
|
GL_EncodeNormal( options, texinfo->gl_normalmap_id );
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
GL_AddShaderDirective( options, "APPLY_REFRACTION" );
|
|
have_bumpmap = true;
|
|
}
|
|
|
|
if( have_parallax || have_bumpmap )
|
|
GL_AddShaderDirective( options, "COMPUTE_TBN" );
|
|
|
|
if( FBitSet( mat->flags, BRUSH_TRANSPARENT ))
|
|
GL_AddShaderDirective( options, "ALPHA_TEST" );
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
{
|
|
if( CVAR_TO_BOOL( cv_realtime_puddles ) && CVAR_TO_BOOL( r_allow_mirrors ))
|
|
GL_AddShaderDirective( options, "PLANAR_REFLECTION" );
|
|
else GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" );
|
|
}
|
|
|
|
if( tr.fogEnabled )
|
|
GL_AddShaderDirective( options, "APPLY_FOG_EXP" );
|
|
|
|
word shaderNum = GL_FindUberShader( glname, options );
|
|
if( !shaderNum ) return 0; // something bad happens
|
|
|
|
decal->forwardScene.SetShader( shaderNum );
|
|
|
|
return shaderNum;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_ShaderDecalDeferred
|
|
|
|
Select the program for surface (diffuse\puddle)
|
|
===============
|
|
*/
|
|
static word R_ShaderDecalDeferred( brushdecal_t *decal )
|
|
{
|
|
char glname[64];
|
|
char options[MAX_OPTIONS_LENGTH];
|
|
const DecalGroupEntry *texinfo = decal->texinfo;
|
|
mextrasurf_t *es= decal->surface;
|
|
bool mirrorSurface = false;
|
|
bool have_parallax = false;
|
|
bool have_bumpmap = false;
|
|
msurface_t *s = es->surf;
|
|
|
|
ASSERT( worldmodel != NULL );
|
|
|
|
if( decal->deferredScene.IsValid( ))
|
|
return decal->deferredScene.GetHandle(); // valid
|
|
|
|
Q_strncpy( glname, "deferred/scene_decal_bmodel", sizeof( glname ));
|
|
memset( options, 0, sizeof( options ));
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
GL_AddShaderDirective( options, "DECAL_PUDDLE" );
|
|
|
|
if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f )
|
|
{
|
|
if( cv_parallax->value == 1.0f )
|
|
GL_AddShaderDirective( options, "PARALLAX_SIMPLE" );
|
|
else if( cv_parallax->value >= 2.0f )
|
|
GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" );
|
|
have_parallax = true;
|
|
}
|
|
|
|
material_t *mat = R_TextureAnimation( s )->material;
|
|
|
|
if( CVAR_TO_BOOL( cv_specular ) && ( texinfo->gl_specular_id != tr.blackTexture ))
|
|
GL_AddShaderDirective( options, "HAS_GLOSSMAP" );
|
|
|
|
if( !texinfo->opaque )
|
|
{
|
|
// GL_DST_COLOR, GL_SRC_COLOR
|
|
GL_AddShaderDirective( options, "APPLY_COLORBLEND" );
|
|
}
|
|
|
|
if(( texinfo->gl_normalmap_id != tr.normalmapTexture ) && CVAR_TO_BOOL( cv_bump ))
|
|
{
|
|
GL_AddShaderDirective( options, "HAS_NORMALMAP" );
|
|
GL_EncodeNormal( options, texinfo->gl_normalmap_id );
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
GL_AddShaderDirective( options, "APPLY_REFRACTION" );
|
|
have_bumpmap = true;
|
|
}
|
|
|
|
if( have_parallax || have_bumpmap )
|
|
GL_AddShaderDirective( options, "COMPUTE_TBN" );
|
|
|
|
if( FBitSet( mat->flags, BRUSH_TRANSPARENT ))
|
|
GL_AddShaderDirective( options, "ALPHA_TEST" );
|
|
|
|
if( FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" );
|
|
|
|
word shaderNum = GL_FindUberShader( glname, options );
|
|
if( !shaderNum ) return 0; // something bad happens
|
|
|
|
decal->deferredScene.SetShader( shaderNum );
|
|
|
|
return shaderNum;
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
DECAL CLIPPING
|
|
|
|
==============================================================================
|
|
*/
|
|
/*
|
|
==================
|
|
R_DecalPointHashKey
|
|
==================
|
|
*/
|
|
static uint R_DecalPointHashKey( const Vector &point, uint hashSize )
|
|
{
|
|
uint hashKey = 0;
|
|
|
|
hashKey ^= int( fabs( point.x ));
|
|
hashKey ^= int( fabs( point.y ));
|
|
hashKey ^= int( fabs( point.z ));
|
|
|
|
hashKey &= (hashSize - 1);
|
|
|
|
return hashKey;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_DecalPointCull
|
|
==================
|
|
*/
|
|
static void R_DecalPointCull( const mplane_t *planes, int numVertices, const bvert_t *vertices, byte *cullBits )
|
|
{
|
|
float d0, d1, d2, d3, d4, d5;
|
|
int bits;
|
|
|
|
for( int i = 0; i < numVertices; i++ )
|
|
{
|
|
bits = 0;
|
|
|
|
d0 = PlaneDiff( vertices[i].vertex, &planes[0] );
|
|
d1 = PlaneDiff( vertices[i].vertex, &planes[1] );
|
|
d2 = PlaneDiff( vertices[i].vertex, &planes[2] );
|
|
d3 = PlaneDiff( vertices[i].vertex, &planes[3] );
|
|
d4 = PlaneDiff( vertices[i].vertex, &planes[4] );
|
|
d5 = PlaneDiff( vertices[i].vertex, &planes[5] );
|
|
|
|
bits |= FLOATSIGNBITSET( d0 ) << 0;
|
|
bits |= FLOATSIGNBITSET( d1 ) << 1;
|
|
bits |= FLOATSIGNBITSET( d2 ) << 2;
|
|
bits |= FLOATSIGNBITSET( d3 ) << 3;
|
|
bits |= FLOATSIGNBITSET( d4 ) << 4;
|
|
bits |= FLOATSIGNBITSET( d5 ) << 5;
|
|
|
|
cullBits[i] = bits;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_ClearDecalClip
|
|
==================
|
|
*/
|
|
static void R_ClearDecalClip( decalClip_t *clip )
|
|
{
|
|
if( !clip->numIndices && !clip->numVertices )
|
|
return;
|
|
|
|
// clear the hash table
|
|
memset( clip->verticesHashTable, 0, sizeof( clip->verticesHashTable ));
|
|
|
|
// clear the arrays
|
|
clip->numVertices = clip->numIndices = 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_DecalIntersect
|
|
|
|
check if this decal is intersected with others
|
|
==================
|
|
*/
|
|
static brushdecal_t *R_DecalIntersect( decalClip_t *clip, msurface_t *surf )
|
|
{
|
|
float texc_orig_x = DotProduct( clip->textureVecs[0], clip->origin );
|
|
float texc_orig_y = DotProduct( clip->textureVecs[1], clip->origin );
|
|
float xsize = (float)clip->decalDesc->ysize;
|
|
float ysize = (float)clip->decalDesc->xsize;
|
|
brushdecal_t *pDecal = surf->info->pdecals;
|
|
float overlay = clip->decalDesc->overlay;
|
|
|
|
while( pDecal )
|
|
{
|
|
if( !FBitSet( pDecal->flags, FDECAL_PERMANENT ))
|
|
{
|
|
if(( pDecal->texinfo->xsize == xsize ) && ( pDecal->texinfo->ysize == ysize ))
|
|
{
|
|
float texc_x = fabs( DotProduct( pDecal->position, clip->textureVecs[0] ) - texc_orig_x );
|
|
float texc_y = fabs( DotProduct( pDecal->position, clip->textureVecs[1] ) - texc_orig_y );
|
|
|
|
// replace existed decal
|
|
if( texc_x < ( xsize * overlay ) && texc_y < ( ysize * overlay ))
|
|
return pDecal;
|
|
}
|
|
}
|
|
|
|
pDecal = pDecal->pnext;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void R_DecalComputeTBN( brushdecal_t *decal )
|
|
{
|
|
if( decal->texinfo->gl_normalmap_id == tr.normalmapTexture && decal->texinfo->gl_heightmap_id == tr.whiteTexture )
|
|
return; // not needs
|
|
|
|
// build a map from vertex to a list of triangles that share the vert.
|
|
CUtlArray<CIntVector> vertToTriMap;
|
|
|
|
vertToTriMap.AddMultipleToTail( decal->numVerts );
|
|
int triID;
|
|
|
|
for( triID = 0; triID < (decal->numElems / 3); triID++ )
|
|
{
|
|
vertToTriMap[decal->elems[triID*3+0]].AddToTail( triID );
|
|
vertToTriMap[decal->elems[triID*3+1]].AddToTail( triID );
|
|
vertToTriMap[decal->elems[triID*3+2]].AddToTail( triID );
|
|
}
|
|
|
|
// calculate the tangent space for each triangle.
|
|
CUtlArray<Vector> triSVect, triTVect;
|
|
triSVect.AddMultipleToTail( (decal->numElems / 3) );
|
|
triTVect.AddMultipleToTail( (decal->numElems / 3) );
|
|
|
|
float *v[3], *tc[3];
|
|
|
|
for( triID = 0; triID < (decal->numElems / 3); triID++ )
|
|
{
|
|
for( int i = 0; i < 3; i++ )
|
|
{
|
|
v[i] = (float *)&decal->verts[decal->elems[triID*3+i]].vertex;
|
|
tc[i] = (float *)&decal->verts[decal->elems[triID*3+i]].stcoord0;
|
|
}
|
|
|
|
CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] );
|
|
}
|
|
|
|
// calculate an average tangent space for each vertex.
|
|
for( int vertID = 0; vertID < decal->numVerts; vertID++ )
|
|
{
|
|
dvert_t *v = &decal->verts[vertID];
|
|
const Vector &normal = v->normal;
|
|
Vector &finalSVect = v->tangent;
|
|
Vector &finalTVect = v->binormal;
|
|
Vector sVect, tVect;
|
|
|
|
sVect = tVect = g_vecZero;
|
|
|
|
for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ )
|
|
{
|
|
sVect += triSVect[vertToTriMap[vertID][triID]];
|
|
tVect += triTVect[vertToTriMap[vertID][triID]];
|
|
}
|
|
|
|
Vector tmpVect = CrossProduct( sVect, tVect );
|
|
bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f;
|
|
|
|
if( !leftHanded )
|
|
{
|
|
tVect = CrossProduct( normal, sVect );
|
|
sVect = CrossProduct( tVect, normal );
|
|
}
|
|
else
|
|
{
|
|
tVect = CrossProduct( sVect, normal );
|
|
sVect = CrossProduct( normal, tVect );
|
|
}
|
|
|
|
finalSVect = -sVect.Normalize();
|
|
finalTVect = -tVect.Normalize();
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_AddDecal
|
|
==================
|
|
*/
|
|
static void R_AddDecal( decalClip_t *clip, msurface_t *surf )
|
|
{
|
|
if( !clip->numIndices || !clip->numVertices )
|
|
return;
|
|
|
|
DecalGroupEntry *entry = (DecalGroupEntry *)clip->decalDesc;
|
|
|
|
entry->PreloadTextures(); // time to cache decal textures
|
|
|
|
if( !entry->gl_diffuse_id )
|
|
{
|
|
// decal texture was missed?
|
|
R_ClearDecalClip( clip );
|
|
return;
|
|
}
|
|
|
|
brushdecal_t *newdecal = NULL;
|
|
brushdecal_t *olddecal = NULL;
|
|
|
|
if( FBitSet( clip->flags, FDECAL_PERMANENT ))
|
|
{
|
|
newdecal = R_AllocDecal();
|
|
}
|
|
else
|
|
{
|
|
if( !clip->current ) // don't intersect with self-fragments!
|
|
olddecal = R_DecalIntersect( clip, surf );
|
|
newdecal = R_AllocDecal( olddecal );
|
|
}
|
|
|
|
if( !newdecal )
|
|
{
|
|
ALERT( at_error, "MAX_BRUSH_DECALS limit exceeded!\n" );
|
|
R_ClearDecalClip( clip );
|
|
return;
|
|
}
|
|
|
|
// puddles required reflections
|
|
if( FBitSet( clip->flags, FDECAL_PUDDLE ) && clip->decalDesc->matdesc->reflectScale > 0.0f )
|
|
{
|
|
SetBits( surf->flags, SURF_REFLECT_PUDDLE );
|
|
GL_AllocOcclusionQuery( surf );
|
|
}
|
|
|
|
// now our surface has decals
|
|
SetBits( surf->flags, SURF_HAS_DECALS );
|
|
|
|
newdecal->pnext = NULL;
|
|
olddecal = surf->info->pdecals;
|
|
|
|
// place decal at tail of list
|
|
if( olddecal )
|
|
{
|
|
while( olddecal->pnext )
|
|
olddecal = olddecal->pnext;
|
|
olddecal->pnext = newdecal;
|
|
}
|
|
else
|
|
{
|
|
// first decal in chain
|
|
surf->info->pdecals = newdecal;
|
|
}
|
|
|
|
// tag surface
|
|
newdecal->surface = surf->info;
|
|
newdecal->position = clip->origin;
|
|
newdecal->impactPlaneNormal = clip->axis[2];
|
|
newdecal->model = clip->model;
|
|
newdecal->entityIndex = clip->entityIndex;
|
|
newdecal->texinfo = clip->decalDesc;
|
|
newdecal->flags = clip->flags;
|
|
newdecal->angle = clip->angle;
|
|
|
|
// NOTE: any decal may potentially fragmented to multiple parts
|
|
// don't save all the fragments because we don't need them to restore this right
|
|
// only once (any) fragment needs to be saved to full restore all the fragments
|
|
if( clip->current )
|
|
SetBits( newdecal->flags, FDECAL_DONTSAVE );
|
|
else clip->current = newdecal;
|
|
|
|
// init vertex cache
|
|
if( newdecal->numVerts < clip->numVertices )
|
|
{
|
|
// FIXME: cache realloc provoked 'leaks' in cache array
|
|
if( newdecal->numVerts )
|
|
ALERT( at_aiconsole, "R_AddDecal: vertex cache was reallocated (%i < %i)\n", newdecal->numVerts, clip->numVertices );
|
|
newdecal->verts = &g_decalVertexCache[g_decalVertUsed];
|
|
g_decalVertUsed += clip->numVertices;
|
|
newdecal->numVerts = clip->numVertices;
|
|
}
|
|
|
|
// init index cache
|
|
if( newdecal->numElems < clip->numIndices )
|
|
{
|
|
// FIXME: cache realloc provoked 'leaks' in cache array
|
|
if( newdecal->numElems )
|
|
ALERT( at_aiconsole, "R_AddDecal: index cache was reallocated (%i < %i)\n", newdecal->numElems, clip->numIndices );
|
|
newdecal->elems = &g_decalIndexCache[g_decalElemUsed];
|
|
g_decalElemUsed += clip->numIndices;
|
|
newdecal->numElems = clip->numIndices;
|
|
}
|
|
|
|
// Copy the indices
|
|
memcpy( newdecal->elems, clip->indices, clip->numIndices * sizeof( word ));
|
|
|
|
mtexinfo_t *tex = surf->texinfo;
|
|
|
|
// set up the vertices
|
|
for( int i = 0; i < clip->numVertices; i++ )
|
|
{
|
|
dvert_t *v = &newdecal->verts[i];
|
|
|
|
Vector point = clip->vertices[i].point;
|
|
Vector delta = point - clip->origin;
|
|
|
|
v->stcoord0[0] = DotProduct( clip->textureVecs[0], delta ) + 0.5f;
|
|
v->stcoord0[1] = DotProduct( clip->textureVecs[1], delta ) + 0.5f;
|
|
v->stcoord0[2] = (( DotProduct( point, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width );
|
|
v->stcoord0[3] = (( DotProduct( point, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height );
|
|
R_LightmapCoords( surf, point, newdecal->verts[i].lmcoord0, 0 ); // styles 0-1
|
|
R_LightmapCoords( surf, point, newdecal->verts[i].lmcoord1, 2 ); // styles 2-3
|
|
// NOTE: i can to place styles is outside but i'm leave it here to keep vertex aligned
|
|
memcpy( v->styles, surf->styles, sizeof( surf->styles ));
|
|
v->vertex = point;
|
|
if( FBitSet( surf->flags, SURF_PLANEBACK ))
|
|
v->normal = -surf->plane->normal;
|
|
else v->normal = surf->plane->normal;
|
|
}
|
|
|
|
R_DecalComputeTBN( newdecal );
|
|
R_ClearDecalClip( clip );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_AddDecalFragment
|
|
==================
|
|
*/
|
|
static void R_AddDecalFragment( decalClip_t *clip, msurface_t *fa, const bvert_t *verts, int numPoints, const Vector *points )
|
|
{
|
|
int index, indices[3];
|
|
decalVertex_t *vertex;
|
|
uint hashKey;
|
|
|
|
// for each triangle in the fragment
|
|
for( int i = 0; i < numPoints - 2; i++ )
|
|
{
|
|
indices[0] = 0;
|
|
indices[1] = i + 1;
|
|
indices[2] = i + 2;
|
|
|
|
// If we're going to overflow, add all the previous triangles to a separate decal
|
|
if(( clip->numIndices + 3 ) > MAX_DECAL_INDICES || ( clip->numVertices + 3 ) > MAX_DECAL_VERTICES )
|
|
R_AddDecal( clip, fa );
|
|
|
|
// add the triangle
|
|
for( int j = 0; j < 3; j++ )
|
|
{
|
|
index = indices[j];
|
|
|
|
// Check if this vertex already exists
|
|
hashKey = R_DecalPointHashKey( points[index], DECAL_VERTICES_HASH_SIZE );
|
|
|
|
for( vertex = clip->verticesHashTable[hashKey]; vertex; vertex = vertex->nextHash )
|
|
{
|
|
if( vertex->point.Compare( points[index], 0.01f ))
|
|
break;
|
|
}
|
|
|
|
// reuse an existing vertex or add a new one
|
|
if( !vertex )
|
|
{
|
|
clip->indices[clip->numIndices++] = clip->numVertices;
|
|
|
|
// add a new vertex
|
|
clip->vertices[clip->numVertices].point = points[index];
|
|
clip->vertices[clip->numVertices].index = clip->numVertices;
|
|
|
|
clip->vertices[clip->numVertices].nextHash = clip->verticesHashTable[hashKey];
|
|
clip->verticesHashTable[hashKey] = &clip->vertices[clip->numVertices];
|
|
|
|
clip->numVertices++;
|
|
}
|
|
else clip->indices[clip->numIndices++] = vertex->index;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_ClipTriangleToDecal
|
|
==================
|
|
*/
|
|
static void R_ClipTriangleToDecal( decalClip_t *clip, msurface_t *fa, int v0, int v1, int v2, const bvert_t *verts, int planeBits )
|
|
{
|
|
Vector points[2][MAX_CLIPVERTS];
|
|
Vector front[MAX_CLIPVERTS];
|
|
int numFront, pingPong = 0;
|
|
int numPoints;
|
|
|
|
// clip the triangle to the decal
|
|
numPoints = 3;
|
|
|
|
points[pingPong][0] = verts[v0].vertex;
|
|
points[pingPong][1] = verts[v1].vertex;
|
|
points[pingPong][2] = verts[v2].vertex;
|
|
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
if( !FBitSet( planeBits, BIT( i )))
|
|
continue;
|
|
|
|
if( !R_ClipPolygon( numPoints, points[pingPong], clip->planes + i, &numPoints, points[!pingPong] ))
|
|
return;
|
|
|
|
pingPong ^= 1;
|
|
}
|
|
|
|
if( numPoints < 3 ) return;
|
|
|
|
// add the fragment at the front of the first split plane
|
|
R_SplitPolygon( numPoints, points[pingPong], &clip->splitPlanes[0], &numFront, front, &numPoints, points[!pingPong] );
|
|
|
|
R_AddDecalFragment( clip, fa, verts, numFront, front );
|
|
|
|
// add the fragment at the front of the second split plane
|
|
R_SplitPolygon( numPoints, points[!pingPong], &clip->splitPlanes[1], &numFront, front, &numPoints, points[pingPong] );
|
|
|
|
R_AddDecalFragment( clip, fa, verts, numFront, front );
|
|
|
|
// add the fragment at the back of both split planes
|
|
R_AddDecalFragment( clip, fa, verts, numPoints, points[pingPong] );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_ClipSurfaceToDecal
|
|
==================
|
|
*/
|
|
static void R_ClipSurfaceToDecal( decalClip_t *clip, msurface_t *fa )
|
|
{
|
|
mextrasurf_t *esrf = fa->info;
|
|
bvert_t *verts = &world->vertexes[esrf->firstvertex];
|
|
byte cullBits[MAX_CLIPVERTS];
|
|
int v0, v1, v2;
|
|
|
|
ASSERT( esrf->numverts < MAX_CLIPVERTS );
|
|
|
|
if( FBitSet( fa->flags, SURF_PLANEBACK ))
|
|
{
|
|
if( DotProduct( clip->axis[2], fa->plane->normal ) > 0.0f )
|
|
return; // facing away
|
|
}
|
|
else
|
|
{
|
|
if( DotProduct( clip->axis[2], fa->plane->normal ) < 0.0f )
|
|
return; // facing away
|
|
}
|
|
|
|
// Categorize all points by the planes
|
|
R_DecalPointCull( clip->planes, esrf->numverts, verts, cullBits );
|
|
|
|
// clip the surface
|
|
for( int i = 0; i < esrf->numverts - 2; i++ )
|
|
{
|
|
v0 = 0;
|
|
v1 = i + 1;
|
|
v2 = i + 2;
|
|
|
|
if( cullBits[v0] & cullBits[v1] & cullBits[v2] )
|
|
continue; // completely off one side
|
|
|
|
// calculate two mostly perpendicular edge directions
|
|
Vector dir1 = verts[v0].vertex - verts[v1].vertex;
|
|
Vector dir2 = verts[v2].vertex - verts[v1].vertex;
|
|
|
|
// we have two edge directions, we can calculate a third vector from
|
|
// them, which is the direction of the triangle normal
|
|
Vector snorm = CrossProduct( dir1, dir2 ).Normalize();
|
|
|
|
// we multiply 0.5 by length of snorm to avoid normalizing
|
|
if( DotProduct( clip->axis[2], snorm ) < 0.0 )
|
|
continue; // greater than 90 degrees
|
|
|
|
// clip the triangle to the decal
|
|
R_ClipTriangleToDecal( clip, fa, v0, v1, v2, verts, cullBits[v0]|cullBits[v1]|cullBits[v2] );
|
|
}
|
|
|
|
// add a new decal if needed
|
|
R_AddDecal( clip, fa );
|
|
}
|
|
|
|
static void R_DecalNodeSurfaces( mnode_t *node, decalClip_t *clip )
|
|
{
|
|
// iterate over all surfaces in the node
|
|
msurface_t *surf = worldmodel->surfaces + node->firstsurface;
|
|
|
|
for( int i = 0; i < node->numsurfaces; i++, surf++ )
|
|
{
|
|
mextrasurf_t *esrf = surf->info;
|
|
|
|
// never apply decals on the water or sky surfaces
|
|
if( FBitSet( surf->flags, ( SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR|SURF_DRAWTILED )))
|
|
continue;
|
|
|
|
// no puddles on transparent surfaces or mirrors
|
|
if( FBitSet( clip->flags, FDECAL_PUDDLE ) && FBitSet( surf->flags, ( SURF_TRANSPARENT|SURF_REFLECT )))
|
|
continue;
|
|
|
|
if( !BoundsIntersect( esrf->mins, esrf->maxs, clip->mins, clip->maxs ))
|
|
continue;
|
|
|
|
R_ClipSurfaceToDecal( clip, surf );
|
|
}
|
|
}
|
|
|
|
static void R_DecalNode( mnode_t *node, decalClip_t *clip )
|
|
{
|
|
ASSERT( node != NULL );
|
|
|
|
// hit a leaf
|
|
if( node->contents < 0 )
|
|
return;
|
|
|
|
int s = BOX_ON_PLANE_SIDE( clip->mins, clip->maxs, node->plane );
|
|
|
|
if( s == 3 ) R_DecalNodeSurfaces( node, clip );
|
|
if( s & 1 ) R_DecalNode( node->children[0], clip );
|
|
if( s & 2 ) R_DecalNode( node->children[1], clip );
|
|
}
|
|
|
|
void CreateDecal( const Vector &vecEndPos, const Vector &vecPlaneNormal, float angle, const char *name, int flags, int entityIndex, int modelIndex, bool source )
|
|
{
|
|
int srcFlags = flags;
|
|
|
|
if( !pDecalGroupList )
|
|
return;
|
|
|
|
// probably this never happens
|
|
if(( g_decalVertUsed >= MAX_DECAL_VERTS ) || ( g_decalElemUsed >= MAX_DECAL_ELEMS ))
|
|
{
|
|
ALERT( at_error, "CreateDecal: cache decal is overflow!\n" );
|
|
return;
|
|
}
|
|
|
|
decalClip_t decalClip; // intermediate struct that used only for build new decals
|
|
cl_entity_t *ent = NULL;
|
|
|
|
decalClip.decalDesc = DecalGroup::GetEntry( name, flags );
|
|
if( !decalClip.decalDesc ) return;
|
|
|
|
// g-cont. allow more groups that starts from 'puddle'
|
|
if( !Q_strnicmp( name, "puddle", 6 ))
|
|
SetBits( flags, FDECAL_PUDDLE );
|
|
|
|
// puddles allowed only at floor surfaces
|
|
if( FBitSet( flags, FDECAL_PUDDLE ) && vecPlaneNormal != Vector( 0.0f, 0.0f, 1.0f ))
|
|
return;
|
|
|
|
decalClip.model = NULL;
|
|
|
|
if( entityIndex > 0 )
|
|
{
|
|
ent = GET_ENTITY( entityIndex );
|
|
|
|
if( modelIndex > 0 )
|
|
decalClip.model = MODEL_HANDLE( modelIndex );
|
|
else if( ent != NULL )
|
|
decalClip.model = MODEL_HANDLE( ent->curstate.modelindex );
|
|
else return;
|
|
}
|
|
else if( modelIndex > 0 )
|
|
decalClip.model = MODEL_HANDLE( modelIndex );
|
|
else decalClip.model = worldmodel;
|
|
|
|
if( !decalClip.model ) return;
|
|
|
|
if( decalClip.model->type != mod_brush )
|
|
{
|
|
ALERT( at_error, "Decals must hit mod_brush!\n" );
|
|
return;
|
|
}
|
|
|
|
if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE ))
|
|
{
|
|
// transform decal position in local bmodel space
|
|
if( ent->angles != g_vecZero )
|
|
{
|
|
matrix4x4 matrix( ent->origin, ent->angles );
|
|
|
|
// transfrom decal position into local space
|
|
decalClip.origin = matrix.VectorITransform( vecEndPos );
|
|
decalClip.axis[2] = matrix.VectorIRotate( vecPlaneNormal );
|
|
}
|
|
else
|
|
{
|
|
decalClip.origin = vecEndPos - ent->origin;
|
|
decalClip.axis[2] = vecPlaneNormal;
|
|
}
|
|
|
|
flags |= FDECAL_LOCAL_SPACE; // decal position moved into local space
|
|
}
|
|
else
|
|
{
|
|
// pass position in global
|
|
decalClip.origin = vecEndPos;
|
|
decalClip.axis[2] = vecPlaneNormal;
|
|
}
|
|
|
|
// this decal must use landmark for correct transition
|
|
// a models with origin brush just use local space
|
|
if( !FBitSet( decalClip.model->flags, MODEL_HAS_ORIGIN ))
|
|
flags |= FDECAL_USE_LANDMARK;
|
|
|
|
// just for consistency
|
|
RI->currententity = GET_ENTITY( entityIndex );
|
|
RI->currentmodel = RI->currententity->model;
|
|
|
|
// don't allow random decal select on a next save\restore
|
|
SetBits( flags, FDECAL_NORANDOM );
|
|
|
|
int xsize = decalClip.decalDesc->xsize;
|
|
int ysize = decalClip.decalDesc->ysize;
|
|
float s, c, depth = 1.0f;
|
|
matrix3x4 transform;
|
|
Vector up, right;
|
|
|
|
// Compute orientation
|
|
SinCos( DEG2RAD( anglemod( angle )), &s, &c );
|
|
VectorMatrix( decalClip.axis[2], right, up );
|
|
right = -right;
|
|
|
|
decalClip.axis[0] = (up * c) + (right * s);
|
|
decalClip.axis[1] = (right * c) - (up * s);
|
|
|
|
decalClip.mins.x = -xsize;
|
|
decalClip.mins.y = -ysize;
|
|
decalClip.mins.z = -depth;
|
|
decalClip.maxs.x = xsize;
|
|
decalClip.maxs.y = ysize;
|
|
decalClip.maxs.z = depth;
|
|
decalClip.current = NULL;
|
|
decalClip.angle = angle;
|
|
|
|
// need to properly transform decal bbox
|
|
transform.SetForward( decalClip.axis[0] );
|
|
transform.SetRight( decalClip.axis[1] );
|
|
transform.SetUp( decalClip.axis[2] );
|
|
transform.SetOrigin( decalClip.origin );
|
|
TransformAABB( transform, decalClip.mins, decalClip.maxs, decalClip.mins, decalClip.maxs );
|
|
|
|
// set up the clip planes
|
|
SetPlane( &decalClip.planes[0], decalClip.axis[0], DotProduct( decalClip.origin, decalClip.axis[0] ) - xsize );
|
|
SetPlane( &decalClip.planes[1],-decalClip.axis[0],-DotProduct( decalClip.origin, decalClip.axis[0] ) - xsize );
|
|
SetPlane( &decalClip.planes[2], decalClip.axis[1], DotProduct( decalClip.origin, decalClip.axis[1] ) - ysize );
|
|
SetPlane( &decalClip.planes[3],-decalClip.axis[1],-DotProduct( decalClip.origin, decalClip.axis[1] ) - ysize );
|
|
SetPlane( &decalClip.planes[4], decalClip.axis[2], DotProduct( decalClip.origin, decalClip.axis[2] ) - depth );
|
|
SetPlane( &decalClip.planes[5],-decalClip.axis[2],-DotProduct( decalClip.origin, decalClip.axis[2] ) - depth );
|
|
|
|
// set up the split planes
|
|
SetPlane( &decalClip.splitPlanes[0], decalClip.axis[2], DotProduct( decalClip.origin, decalClip.axis[2] ) + ( depth * 0.5f ));
|
|
SetPlane( &decalClip.splitPlanes[1],-decalClip.axis[2], -DotProduct( decalClip.origin, decalClip.axis[2] ) - ( depth * 0.5f ) + depth );
|
|
|
|
// compute the texture vectors
|
|
decalClip.textureVecs[0] = decalClip.axis[1] * ( 0.5f / ysize );
|
|
decalClip.textureVecs[1] = decalClip.axis[0] * ( 0.5f / xsize );
|
|
|
|
// Clear the arrays
|
|
decalClip.numIndices = 0;
|
|
decalClip.numVertices = 0;
|
|
decalClip.entityIndex = entityIndex;
|
|
decalClip.flags = flags;
|
|
|
|
// clear the hash table
|
|
memset( decalClip.verticesHashTable, 0, sizeof( decalClip.verticesHashTable ));
|
|
|
|
// g-cont. now using walking on bsp-tree instead of stupid linear search
|
|
R_DecalNode( &decalClip.model->nodes[decalClip.model->hulls[0].firstclipnode], &decalClip );
|
|
if( !source ) return; // to avoid recursion
|
|
|
|
// trying to place decals on contacted submodels too
|
|
// FIXME: this is doesn't working
|
|
for( int i = 0; i < 1024; i++ )
|
|
{
|
|
model_t *mod = MODEL_HANDLE( i );
|
|
|
|
if( !mod || mod->type != mod_brush || mod == decalClip.model )
|
|
continue;
|
|
|
|
if( mod->firstmodelsurface >= worldmodel->numsurfaces || !mod->nummodelsurfaces )
|
|
continue; // skip weird models
|
|
|
|
int entityIndex = 0;
|
|
|
|
if( mod->name[0] == '*' )
|
|
{
|
|
msurface_t *surf = mod->surfaces + mod->firstmodelsurface;
|
|
cl_entity_t *e = surf->info->parent;
|
|
if( !e ) continue; // entity invisible on the client
|
|
|
|
gl_state_t *glm = GL_GetCache( e->hCachedMatrix );
|
|
Vector absmin, absmax;
|
|
|
|
TransformAABB( glm->transform, mod->mins, mod->maxs, absmin, absmax );
|
|
|
|
if( !BoundsIntersect( absmin, absmax, vecEndPos, vecEndPos ))
|
|
continue; // no intersection with this model
|
|
entityIndex = e->index;
|
|
}
|
|
|
|
// trying to place decal on neighbored bmodel
|
|
CreateDecal( vecEndPos, vecPlaneNormal, angle, name, srcFlags, entityIndex, 0, false );
|
|
}
|
|
}
|
|
|
|
void CreateDecal( pmtrace_t *tr, const char *name, float angle, bool visent )
|
|
{
|
|
if( !g_fRenderInitialized )
|
|
return;
|
|
|
|
if( tr->allsolid || tr->fraction == 1.0 || tr->ent < 0 )
|
|
return;
|
|
|
|
physent_t *pe = NULL;
|
|
|
|
if( visent ) pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent );
|
|
else pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr->ent );
|
|
if( !pe ) return;
|
|
|
|
int entityIndex = pe->info;
|
|
int modelIndex = 0;
|
|
|
|
// modelindex is needs for properly save\restore
|
|
cl_entity_t *ent = GET_ENTITY( entityIndex );
|
|
if( ent ) modelIndex = ent->curstate.modelindex;
|
|
|
|
CreateDecal( tr->endpos, tr->plane.normal, angle, name, 0, entityIndex, modelIndex );
|
|
}
|
|
|
|
// debugging feature
|
|
void PasteViewDecal( void )
|
|
{
|
|
const char *name = CMD_ARGV( 1 );
|
|
float angle = 0.0f;
|
|
|
|
if( CMD_ARGC() <= 1 )
|
|
{
|
|
Msg( "usage: pastedecal <decal name>\n" );
|
|
return;
|
|
}
|
|
|
|
pmtrace_t *tr = gEngfuncs.pEventAPI->EV_VisTraceLine( GetVieworg(), (GetVieworg() + (GetVForward() * 1024.0f)), PM_NORMAL );
|
|
if( fabs( tr->plane.normal.z ) > 0.7f )
|
|
angle = RI->view.angles.y + 90.0f;
|
|
physent_t *pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent );
|
|
|
|
// now handle both types: studio and brushes
|
|
if( pe && pe->studiomodel )
|
|
g_StudioRenderer.StudioDecalShoot( tr, name, true );
|
|
else CreateDecal( tr, name, angle, true );
|
|
}
|
|
|
|
int SaveDecalList( decallist_t *pBaseList, int count )
|
|
{
|
|
int maxBrushDecals = MAX_BRUSH_DECALS + (MAX_BRUSH_DECALS - count);
|
|
decallist_t *pList = pBaseList + count; // shift list to first free slot
|
|
brushdecal_t *pdecal, *pdecals;
|
|
int total = 0, depth;
|
|
|
|
for( int i = 0; i < gDecalCount; i++ )
|
|
{
|
|
pdecal = &gDecalPool[i];
|
|
const DecalGroupEntry *tex = pdecal->texinfo;
|
|
|
|
if( !pdecal->surface || !tex || !tex->group )
|
|
continue; // unused
|
|
|
|
if( FBitSet( pdecal->flags, FDECAL_DONTSAVE ))
|
|
continue;
|
|
|
|
// compute depth
|
|
depth = 0;
|
|
pdecals = pdecal->surface->pdecals;
|
|
|
|
while( pdecals && pdecals != pdecal )
|
|
{
|
|
depth++;
|
|
pdecals = pdecals->pnext;
|
|
}
|
|
|
|
pList[total].depth = depth;
|
|
pList[total].flags = pdecal->flags;
|
|
pList[total].entityIndex = pdecal->entityIndex;
|
|
pList[total].position = pdecal->position;
|
|
pList[total].impactPlaneNormal = pdecal->impactPlaneNormal;
|
|
pList[total].scale = pdecal->angle;
|
|
Q_snprintf( pList[total].name, sizeof( pList[total].name ), "%s@%s", tex->group->GetName(), tex->m_DecalName );
|
|
total++;
|
|
|
|
// check for list overflow
|
|
if( total >= maxBrushDecals )
|
|
{
|
|
ALERT( at_error, "SaveDecalList: too many brush decals on save\restore\n" );
|
|
goto end_serialize;
|
|
}
|
|
}
|
|
end_serialize:
|
|
return total;
|
|
}
|
|
|
|
inline void R_DecalSetupVertex( dvert_t *vert )
|
|
{
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_TANGENT, vert->tangent );
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_BINORMAL, vert->binormal );
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_NORMAL, vert->normal );
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD0, vert->stcoord0 );
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD1, vert->lmcoord0 );
|
|
pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD2, vert->lmcoord1 );
|
|
pglVertexAttrib4ubvARB( ATTR_INDEX_LIGHT_STYLES, vert->styles );
|
|
pglVertexAttrib3fvARB( ATTR_INDEX_POSITION, vert->vertex );
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_SetDecalUniforms
|
|
|
|
================
|
|
*/
|
|
void R_SetDecalUniforms( brushdecal_t *decal )
|
|
{
|
|
mextrasurf_t *es = decal->surface;
|
|
cl_entity_t *e = es->parent;
|
|
msurface_t *s = es->surf;
|
|
int map, width, height;
|
|
Vector4D lightstyles;
|
|
|
|
// begin draw the sorted list
|
|
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
{
|
|
if( RI->currentshader != decal->deferredScene.GetShader() )
|
|
{
|
|
// force to bind new shader
|
|
GL_BindShader( decal->deferredScene.GetShader() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RI->currentshader != decal->forwardScene.GetShader() )
|
|
{
|
|
// force to bind new shader
|
|
GL_BindShader( decal->forwardScene.GetShader() );
|
|
}
|
|
}
|
|
|
|
material_t *mat = R_TextureAnimation( es->surf )->material;
|
|
gl_state_t *glm = GL_GetCache( e->hCachedMatrix );
|
|
glsl_program_t *shader = RI->currentshader;
|
|
matdesc_t *desc = decal->texinfo->matdesc;
|
|
GLfloat viewMatrix[16];
|
|
|
|
tr.modelorg = glm->GetModelOrigin();
|
|
|
|
// setup specified uniforms (and texture bindings)
|
|
for( int i = 0; i < shader->numUniforms; i++ )
|
|
{
|
|
uniform_t *u = &shader->uniforms[i];
|
|
|
|
switch( u->type )
|
|
{
|
|
case UT_COLORMAP:
|
|
if( Surf_CheckSubview( es, true ) && FBitSet( decal->flags, FDECAL_PUDDLE ))
|
|
u->SetValue( Surf_GetSubview( es )->texturenum );
|
|
else u->SetValue( mat->gl_diffuse_id );
|
|
break;
|
|
case UT_NORMALMAP:
|
|
u->SetValue( decal->texinfo->gl_normalmap_id );
|
|
break;
|
|
case UT_GLOSSMAP:
|
|
u->SetValue( decal->texinfo->gl_specular_id );
|
|
break;
|
|
case UT_LIGHTMAP:
|
|
if( R_FullBright( )) u->SetValue( tr.deluxemapTexture );
|
|
else u->SetValue( tr.lightmaps[es->lightmaptexturenum].lightmap );
|
|
break;
|
|
case UT_DELUXEMAP:
|
|
if( R_FullBright( )) u->SetValue( tr.grayTexture );
|
|
else u->SetValue( tr.lightmaps[es->lightmaptexturenum].deluxmap );
|
|
break;
|
|
case UT_DECALMAP:
|
|
u->SetValue( decal->texinfo->gl_diffuse_id );
|
|
break;
|
|
case UT_SCREENMAP:
|
|
u->SetValue( tr.screen_color );
|
|
break;
|
|
case UT_DEPTHMAP:
|
|
u->SetValue( tr.screen_depth );
|
|
break;
|
|
case UT_HEIGHTMAP:
|
|
u->SetValue( decal->texinfo->gl_heightmap_id );
|
|
break;
|
|
case UT_ENVMAP0:
|
|
case UT_ENVMAP:
|
|
if( es->cubemap[0] != NULL )
|
|
u->SetValue( es->cubemap[0]->texture );
|
|
else u->SetValue( tr.whiteCubeTexture );
|
|
break;
|
|
case UT_ENVMAP1:
|
|
if( es->cubemap[1] != NULL )
|
|
u->SetValue( es->cubemap[1]->texture );
|
|
else u->SetValue( tr.whiteCubeTexture );
|
|
break;
|
|
case UT_FITNORMALMAP:
|
|
u->SetValue( tr.normalsFitting );
|
|
break;
|
|
case UT_FRAGDATA0:
|
|
u->SetValue( tr.defscene_fbo->colortarget[0] );
|
|
break;
|
|
case UT_FRAGDATA1:
|
|
u->SetValue( tr.defscene_fbo->colortarget[1] );
|
|
break;
|
|
case UT_FRAGDATA2:
|
|
u->SetValue( tr.defscene_fbo->colortarget[2] );
|
|
break;
|
|
case UT_MODELMATRIX:
|
|
u->SetValue( &glm->modelMatrix[0] );
|
|
break;
|
|
case UT_REFLECTMATRIX:
|
|
if( Surf_CheckSubview( es, true ))
|
|
Surf_GetSubview( es )->matrix.CopyToArray( viewMatrix );
|
|
else memcpy( viewMatrix, glState.identityMatrix, sizeof( float ) * 16 );
|
|
u->SetValue( &viewMatrix[0] );
|
|
break;
|
|
case UT_SCREENSIZEINV:
|
|
u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height );
|
|
break;
|
|
case UT_ZFAR:
|
|
u->SetValue( -tr.farclip * 1.74f );
|
|
break;
|
|
case UT_LIGHTSTYLES:
|
|
for( map = 0; map < MAXLIGHTMAPS; map++ )
|
|
{
|
|
if( s->styles[map] != 255 )
|
|
lightstyles[map] = tr.lightstyle[s->styles[map]];
|
|
else lightstyles[map] = 0.0f;
|
|
}
|
|
u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w );
|
|
break;
|
|
case UT_LIGHTSTYLEVALUES:
|
|
u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES );
|
|
break;
|
|
case UT_REALTIME:
|
|
u->SetValue( (float)tr.time );
|
|
break;
|
|
case UT_FOGPARAMS:
|
|
if( e->curstate.renderfx == SKYBOX_ENTITY )
|
|
u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity );
|
|
else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity );
|
|
break;
|
|
case UT_TEXOFFSET:
|
|
u->SetValue( es->texofs[0], es->texofs[1] );
|
|
break;
|
|
case UT_VIEWORIGIN:
|
|
u->SetValue( tr.modelorg.x, tr.modelorg.y, tr.modelorg.z, e->hCachedMatrix ? 1.0f : 0.0f );
|
|
break;
|
|
case UT_SMOOTHNESS:
|
|
u->SetValue( desc->smoothness );
|
|
break;
|
|
case UT_DIFFUSEFACTOR:
|
|
u->SetValue( tr.diffuseFactor );
|
|
break;
|
|
case UT_AMBIENTFACTOR:
|
|
u->SetValue( tr.ambientFactor );
|
|
break;
|
|
case UT_REFRACTSCALE:
|
|
u->SetValue( bound( 0.0f, desc->refractScale, 1.0f ));
|
|
break;
|
|
case UT_REFLECTSCALE:
|
|
u->SetValue( bound( 0.0f, desc->reflectScale, 1.0f ));
|
|
break;
|
|
case UT_ABERRATIONSCALE:
|
|
u->SetValue( bound( 0.0f, desc->aberrationScale, 1.0f ));
|
|
break;
|
|
case UT_LERPFACTOR:
|
|
u->SetValue( es->lerpFactor );
|
|
break;
|
|
case UT_BOXMINS:
|
|
if( world->num_cubemaps > 0 )
|
|
{
|
|
Vector mins[2];
|
|
mins[0] = es->cubemap[0]->mins;
|
|
mins[1] = es->cubemap[1]->mins;
|
|
u->SetValue( &mins[0][0], 2 );
|
|
}
|
|
break;
|
|
case UT_BOXMAXS:
|
|
if( world->num_cubemaps > 0 )
|
|
{
|
|
Vector maxs[2];
|
|
maxs[0] = es->cubemap[0]->maxs;
|
|
maxs[1] = es->cubemap[1]->maxs;
|
|
u->SetValue( &maxs[0][0], 2 );
|
|
}
|
|
break;
|
|
case UT_CUBEORIGIN:
|
|
if( world->num_cubemaps > 0 )
|
|
{
|
|
Vector origin[2];
|
|
origin[0] = es->cubemap[0]->origin;
|
|
origin[1] = es->cubemap[1]->origin;
|
|
u->SetValue( &origin[0][0], 2 );
|
|
}
|
|
break;
|
|
case UT_CUBEMIPCOUNT:
|
|
if( world->num_cubemaps > 0 )
|
|
{
|
|
float r = Q_max( 1, es->cubemap[0]->numMips - cv_cube_lod_bias->value );
|
|
float g = Q_max( 1, es->cubemap[1]->numMips - cv_cube_lod_bias->value );
|
|
u->SetValue( r, g );
|
|
}
|
|
break;
|
|
case UT_LIGHTNUMS0:
|
|
u->SetValue( (float)es->lights[0], (float)es->lights[1], (float)es->lights[2], (float)es->lights[3] );
|
|
break;
|
|
case UT_LIGHTNUMS1:
|
|
u->SetValue( (float)es->lights[4], (float)es->lights[5], (float)es->lights[6], (float)es->lights[7] );
|
|
break;
|
|
case UT_RELIEFPARAMS:
|
|
width = RENDER_GET_PARM( PARM_TEX_WIDTH, decal->texinfo->gl_heightmap_id );
|
|
height = RENDER_GET_PARM( PARM_TEX_HEIGHT, decal->texinfo->gl_heightmap_id );
|
|
u->SetValue( (float)width, (float)height, desc->reliefScale, cv_shadow_offset->value );
|
|
break;
|
|
default:
|
|
ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawWireDecal( brushdecal_t *decal )
|
|
{
|
|
mextrasurf_t *es = decal->surface;
|
|
cl_entity_t *e = es->parent;
|
|
|
|
pglEnable( GL_BLEND );
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
|
if( FBitSet( decal->flags, FDECAL_DONTSAVE ))
|
|
pglColor4f( 1.0f, 0.5f, 0.36f, 0.99f );
|
|
else pglColor4f( 0.5f, 1.0f, 0.36f, 0.99f );
|
|
pglLineWidth( 4.0f );
|
|
|
|
pglDisable( GL_DEPTH_TEST );
|
|
pglEnable( GL_LINE_SMOOTH );
|
|
pglEnable( GL_POLYGON_SMOOTH );
|
|
pglHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
|
|
pglHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST );
|
|
|
|
gl_state_t *glm = GL_GetCache( e->hCachedMatrix );
|
|
|
|
// transform decal verts to actual position
|
|
for( int k = 0; k < decal->numElems; k += 3 )
|
|
{
|
|
pglBegin( GL_TRIANGLES );
|
|
pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+0]].vertex ));
|
|
pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+1]].vertex ));
|
|
pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+2]].vertex ));
|
|
pglEnd();
|
|
}
|
|
|
|
pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
|
|
pglDisable( GL_POLYGON_SMOOTH );
|
|
pglDisable( GL_LINE_SMOOTH );
|
|
pglEnable( GL_DEPTH_TEST );
|
|
pglDisable( GL_BLEND );
|
|
pglLineWidth( 1.0f );
|
|
}
|
|
|
|
static void R_DrawSurfaceDecalsDebug( msurface_t *surf, drawlist_t drawlist_type )
|
|
{
|
|
mextrasurf_t *fa = surf->info;
|
|
brushdecal_t *p;
|
|
|
|
for( p = fa->pdecals; p; p = p->pnext )
|
|
{
|
|
if( !p->surface || !p->texinfo )
|
|
continue; // bad decal?
|
|
|
|
if( drawlist_type == DRAWLIST_SOLID && !p->texinfo->opaque )
|
|
continue;
|
|
|
|
if( drawlist_type == DRAWLIST_TRANS && p->texinfo->opaque )
|
|
continue;
|
|
|
|
DrawWireDecal( p );
|
|
}
|
|
}
|
|
|
|
static void R_RenderSurfaceDecals( msurface_t *surf, drawlist_t drawlist_type )
|
|
{
|
|
mextrasurf_t *fa = surf->info;
|
|
brushdecal_t *p;
|
|
|
|
for( p = fa->pdecals; p; p = p->pnext )
|
|
{
|
|
if( !p->surface || !p->texinfo )
|
|
continue; // bad decal?
|
|
|
|
if( drawlist_type == DRAWLIST_SOLID && !p->texinfo->opaque )
|
|
continue;
|
|
|
|
if( drawlist_type == DRAWLIST_TRANS && p->texinfo->opaque )
|
|
continue;
|
|
|
|
// initialize decal shader
|
|
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
{
|
|
if( !R_ShaderDecalDeferred( p ))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( !R_ShaderDecalForward( p ))
|
|
continue;
|
|
}
|
|
R_SetDecalUniforms( p );
|
|
|
|
if( !FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
{
|
|
if( p->texinfo->opaque )
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
|
|
}
|
|
r_stats.c_total_tris += (p->numElems / 3);
|
|
|
|
// draw decal from cache
|
|
for( int k = 0; k < p->numElems; k += 3 )
|
|
{
|
|
pglBegin( GL_TRIANGLES );
|
|
R_DecalSetupVertex( &p->verts[p->elems[k+0]] );
|
|
R_DecalSetupVertex( &p->verts[p->elems[k+1]] );
|
|
R_DecalSetupVertex( &p->verts[p->elems[k+2]] );
|
|
pglEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void R_RenderSolidListDecalsDebug( drawlist_t drawlist_type )
|
|
{
|
|
GL_AlphaTest( GL_FALSE );
|
|
GL_DepthMask( GL_FALSE );
|
|
GL_BindShader( NULL );
|
|
GL_Blend( GL_FALSE );
|
|
|
|
for( int i = 0; i < RI->frame.solid_faces.Count(); i++ )
|
|
{
|
|
CSolidEntry *entry = &RI->frame.solid_faces[i];
|
|
|
|
if( entry->m_bDrawType != DRAWTYPE_SURFACE )
|
|
continue;
|
|
|
|
msurface_t *s = entry->m_pSurf;
|
|
|
|
if( !FBitSet( s->flags, SURF_HAS_DECALS ))
|
|
continue;
|
|
|
|
R_DrawSurfaceDecalsDebug( s, drawlist_type );
|
|
}
|
|
}
|
|
|
|
static void R_RenderTransListDecalsDebug( drawlist_t drawlist_type )
|
|
{
|
|
GL_AlphaTest( GL_FALSE );
|
|
GL_DepthMask( GL_FALSE );
|
|
GL_BindShader( NULL );
|
|
GL_Blend( GL_FALSE );
|
|
|
|
for( int i = 0; i < RI->frame.trans_list.Count(); i++ )
|
|
{
|
|
CTransEntry *entry = &RI->frame.trans_list[i];
|
|
|
|
if( entry->m_bDrawType != DRAWTYPE_SURFACE )
|
|
continue;
|
|
|
|
msurface_t *s = entry->m_pSurf;
|
|
|
|
if( !FBitSet( s->flags, SURF_HAS_DECALS ))
|
|
continue;
|
|
|
|
R_DrawSurfaceDecalsDebug( s, drawlist_type );
|
|
}
|
|
}
|
|
|
|
void R_RenderDecalsSolidList( drawlist_t drawlist_type )
|
|
{
|
|
if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW )))
|
|
return;
|
|
|
|
if( !gDecalCount || !CVAR_TO_BOOL( cv_decals ))
|
|
return;
|
|
|
|
if( !RI->frame.solid_faces.Count() )
|
|
return;
|
|
|
|
if( CVAR_TO_BOOL( cv_decalsdebug ))
|
|
{
|
|
R_RenderSolidListDecalsDebug( drawlist_type );
|
|
return;
|
|
}
|
|
|
|
if( GL_Support( R_SEAMLESS_CUBEMAP ))
|
|
pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
|
|
|
|
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
{
|
|
pglColorMaski( 3, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
|
|
pglColorMaski( 4, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
|
|
}
|
|
|
|
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
GL_Blend( GL_FALSE );
|
|
else GL_Blend( GL_TRUE );
|
|
GL_AlphaTest( GL_FALSE );
|
|
GL_DepthMask( GL_FALSE );
|
|
|
|
if( CVAR_TO_BOOL( r_polyoffset ))
|
|
{
|
|
pglEnable( GL_POLYGON_OFFSET_FILL );
|
|
pglPolygonOffset( -1.0f, -r_polyoffset->value );
|
|
}
|
|
|
|
for( int i = 0; i < RI->frame.solid_faces.Count(); i++ )
|
|
{
|
|
CSolidEntry *entry = &RI->frame.solid_faces[i];
|
|
|
|
if( entry->m_bDrawType != DRAWTYPE_SURFACE )
|
|
continue;
|
|
|
|
msurface_t *s = entry->m_pSurf;
|
|
|
|
if( !FBitSet( s->flags, SURF_HAS_DECALS ))
|
|
continue;
|
|
|
|
R_RenderSurfaceDecals( s, drawlist_type );
|
|
}
|
|
|
|
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
|
|
{
|
|
pglColorMaski( 3, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
|
|
pglColorMaski( 4, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
|
|
}
|
|
|
|
if( GL_Support( R_SEAMLESS_CUBEMAP ))
|
|
pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
|
|
if( CVAR_TO_BOOL( r_polyoffset ))
|
|
pglDisable( GL_POLYGON_OFFSET_FILL );
|
|
GL_CleanupAllTextureUnits();
|
|
GL_BindShader( NULL );
|
|
GL_Cull( GL_FRONT );
|
|
}
|
|
|
|
void R_RenderDecalsTransList( drawlist_t drawlist_type )
|
|
{
|
|
if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW )))
|
|
return;
|
|
|
|
if( !gDecalCount || !CVAR_TO_BOOL( cv_decals ))
|
|
return;
|
|
|
|
if( !RI->frame.trans_list.Count() )
|
|
return;
|
|
|
|
if( CVAR_TO_BOOL( cv_decalsdebug ))
|
|
{
|
|
R_RenderTransListDecalsDebug( drawlist_type );
|
|
return;
|
|
}
|
|
|
|
GL_Blend( GL_TRUE );
|
|
GL_AlphaTest( GL_FALSE );
|
|
GL_DepthMask( GL_FALSE );
|
|
GL_Cull( GL_NONE );
|
|
|
|
pglPolygonOffset( -1, -1 );
|
|
pglEnable( GL_POLYGON_OFFSET_FILL );
|
|
|
|
for( int i = 0; i < RI->frame.trans_list.Count(); i++ )
|
|
{
|
|
CTransEntry *entry = &RI->frame.trans_list[i];
|
|
|
|
if( entry->m_bDrawType != DRAWTYPE_SURFACE )
|
|
continue;
|
|
|
|
msurface_t *s = entry->m_pSurf;
|
|
|
|
if( !FBitSet( s->flags, SURF_HAS_DECALS ))
|
|
continue;
|
|
|
|
R_RenderSurfaceDecals( s, drawlist_type );
|
|
}
|
|
|
|
pglDisable( GL_POLYGON_OFFSET_FILL );
|
|
GL_CleanupAllTextureUnits();
|
|
GL_BindShader( NULL );
|
|
GL_Cull( GL_FRONT );
|
|
}
|
|
|
|
void R_RenderDecalsTransEntry( CTransEntry *entry, drawlist_t drawlist_type )
|
|
{
|
|
if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW )))
|
|
return;
|
|
|
|
if( !gDecalCount || !CVAR_TO_BOOL( cv_decals ))
|
|
return;
|
|
|
|
if( entry->m_bDrawType != DRAWTYPE_SURFACE )
|
|
return;
|
|
|
|
if( !FBitSet( entry->m_pSurf->flags, SURF_HAS_DECALS ))
|
|
return;
|
|
|
|
if( CVAR_TO_BOOL( cv_decalsdebug ))
|
|
{
|
|
R_DrawSurfaceDecalsDebug( entry->m_pSurf, drawlist_type );
|
|
return;
|
|
}
|
|
|
|
GL_Blend( GL_TRUE );
|
|
GL_AlphaTest( GL_FALSE );
|
|
GL_DepthMask( GL_FALSE );
|
|
GL_Cull( GL_NONE );
|
|
|
|
pglPolygonOffset( -1, -1 );
|
|
pglEnable( GL_POLYGON_OFFSET_FILL );
|
|
|
|
R_RenderSurfaceDecals( entry->m_pSurf, drawlist_type );
|
|
|
|
pglDisable( GL_POLYGON_OFFSET_FILL );
|
|
GL_CleanupAllTextureUnits();
|
|
GL_Cull( GL_FRONT );
|
|
}
|
|
|
|
void ClearDecals( void )
|
|
{
|
|
for( int i = 0; i < worldmodel->numsurfaces; i++ )
|
|
{
|
|
msurface_t *surf = &worldmodel->surfaces[i];
|
|
ClearBits( surf->flags, SURF_HAS_DECALS|SURF_REFLECT_PUDDLE );
|
|
surf->info->pdecals = NULL;
|
|
}
|
|
|
|
memset( gDecalPool, 0, sizeof( gDecalPool ));
|
|
g_decalVertUsed = g_decalElemUsed = 0;
|
|
gDecalCount = gDecalCycle = 0;
|
|
}
|
|
|
|
// ===========================
|
|
// Decals loading
|
|
// ===========================
|
|
void DecalsInit( void )
|
|
{
|
|
ADD_COMMAND( "pastedecal", PasteViewDecal );
|
|
ADD_COMMAND( "cleardecals", ClearDecals );
|
|
|
|
ALERT( at_aiconsole, "Loading decals\n" );
|
|
|
|
char *pfile = (char *)gEngfuncs.COM_LoadFile( "gfx/decals/decalinfo.txt", 5, NULL );
|
|
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "Cannot open file \"gfx/decals/decalinfo.txt\"\n" );
|
|
return;
|
|
}
|
|
|
|
ALERT( at_aiconsole, "Decal vertex cache %s, index cache %s\n", Q_memprint( sizeof( g_decalVertexCache )), Q_memprint( sizeof( g_decalIndexCache )));
|
|
|
|
char *ptext = pfile;
|
|
int counter = 0;
|
|
|
|
while( 1 )
|
|
{
|
|
// store position where group names recorded
|
|
char *groupnames = ptext;
|
|
|
|
// loop until we'll find decal names
|
|
char path[256], token[256];
|
|
int numgroups = 0;
|
|
|
|
while( 1 )
|
|
{
|
|
ptext = COM_ParseFile( ptext, token );
|
|
if( !ptext ) goto getout;
|
|
|
|
if( token[0] == '{' && !token[1] )
|
|
break;
|
|
numgroups++;
|
|
}
|
|
|
|
DecalGroupEntry tempentries[MAX_GROUPENTRIES];
|
|
int numtemp = 0;
|
|
|
|
while( 1 )
|
|
{
|
|
char sz_xsize[64];
|
|
char sz_ysize[64];
|
|
char sz_overlay[64];
|
|
|
|
ptext = COM_ParseFile( ptext, token );
|
|
if( !ptext ) goto getout;
|
|
if( token[0] == '}' )
|
|
break;
|
|
|
|
if( numtemp >= MAX_GROUPENTRIES )
|
|
{
|
|
ALERT( at_error, "Too many decals in group (%d max) - skipping %s\n", MAX_GROUPENTRIES, token );
|
|
continue;
|
|
}
|
|
|
|
ptext = COM_ParseFile( ptext, sz_xsize );
|
|
if( !ptext ) goto getout;
|
|
ptext = COM_ParseFile( ptext, sz_ysize );
|
|
if( !ptext ) goto getout;
|
|
ptext = COM_ParseFile( ptext, sz_overlay );
|
|
if( !ptext ) goto getout;
|
|
|
|
if( Q_strlen( token ) > 16 )
|
|
{
|
|
ALERT( at_error, "%s - got too large token when parsing decal info file\n", token );
|
|
continue;
|
|
}
|
|
|
|
if( token[0] == '$' )
|
|
{
|
|
Q_strncpy( token, token + 1, sizeof( token ));
|
|
tempentries[numtemp].opaque = true;// force solid
|
|
}
|
|
else tempentries[numtemp].opaque = false;
|
|
Q_strncpy( tempentries[numtemp].m_DecalName, token, sizeof( tempentries[numtemp].m_DecalName ));
|
|
tempentries[numtemp].xsize = Q_atof( sz_xsize ) / 2.0f;
|
|
tempentries[numtemp].ysize = Q_atof( sz_ysize ) / 2.0f;
|
|
tempentries[numtemp].overlay = Q_atof( sz_overlay ) * 2.0f;
|
|
Q_snprintf( path, sizeof( path ), "decals/%s", token );
|
|
tempentries[numtemp].matdesc = CL_FindMaterial( path ); // get description for decal texture
|
|
tempentries[numtemp].m_init = false; // need to preload
|
|
tempentries[numtemp].gl_normalmap_id = tr.normalmapTexture;
|
|
tempentries[numtemp].gl_specular_id = tr.blackTexture;
|
|
tempentries[numtemp].gl_heightmap_id = tr.whiteTexture;
|
|
tempentries[numtemp].gl_diffuse_id = 0; // assume no texture
|
|
numtemp++;
|
|
}
|
|
|
|
// get back to group names
|
|
for( int i = 0; i < numgroups; i++ )
|
|
{
|
|
groupnames = COM_ParseFile( groupnames, token );
|
|
if( !numtemp )
|
|
{
|
|
ALERT( at_warning, "got empty decal group: %s\n", token );
|
|
continue;
|
|
}
|
|
|
|
new DecalGroup( token, numtemp, tempentries );
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
getout:
|
|
gEngfuncs.COM_FreeFile( pfile );
|
|
ALERT( at_console, "%d decal groups created\n", counter );
|
|
}
|
|
|
|
void DecalsShutdown( void )
|
|
{
|
|
if( !pDecalGroupList )
|
|
return;
|
|
|
|
DecalGroup **prev = &pDecalGroupList;
|
|
DecalGroup *item;
|
|
|
|
while( 1 )
|
|
{
|
|
item = *prev;
|
|
if( !item ) break;
|
|
|
|
*prev = item->GetNext();
|
|
delete item;
|
|
}
|
|
} |