xash3d-fwgs/ref/gl/gl_decals.c

1286 lines
34 KiB
C

/*
gl_decals.c - decal paste and rendering
Copyright (C) 2010 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 "gl_local.h"
#define DECAL_OVERLAP_DISTANCE 2
#define DECAL_DISTANCE 4 // too big values produce more clipped polygons
#define MAX_DECALCLIPVERT 32 // produced vertexes of fragmented decal
#define DECAL_CACHEENTRY 256 // MUST BE POWER OF 2 or code below needs to change!
#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE
// empirically determined constants for minimizing overalpping decals
#define MAX_OVERLAP_DECALS 6
#define DECAL_OVERLAP_DIST 8
#define MIN_DECAL_SCALE 0.01f
#define MAX_DECAL_SCALE 16.0f
// clip edges
#define LEFT_EDGE 0
#define RIGHT_EDGE 1
#define TOP_EDGE 2
#define BOTTOM_EDGE 3
// This structure contains the information used to create new decals
typedef struct
{
vec3_t m_Position; // world coordinates of the decal center
model_t *m_pModel; // the model the decal is going to be applied in
int m_iTexture; // The decal material
int m_Size; // Size of the decal (in world coords)
int m_Flags;
int m_Entity; // Entity the decal is applied to.
float m_scale;
int m_decalWidth;
int m_decalHeight;
vec3_t m_Basis[3];
} decalinfo_t;
static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE];
static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE];
decal_t gDecalPool[MAX_RENDER_DECALS];
static int gDecalCount;
void R_ClearDecals( void )
{
memset( gDecalPool, 0, sizeof( gDecalPool ));
gDecalCount = 0;
}
// unlink pdecal from any surface it's attached to
static void R_DecalUnlink( decal_t *pdecal )
{
decal_t *tmp;
if( pdecal->psurface )
{
if( pdecal->psurface->pdecals == pdecal )
{
pdecal->psurface->pdecals = pdecal->pnext;
}
else
{
tmp = pdecal->psurface->pdecals;
if( !tmp ) gEngfuncs.Host_Error( "R_DecalUnlink: bad decal list\n" );
while( tmp->pnext )
{
if( tmp->pnext == pdecal )
{
tmp->pnext = pdecal->pnext;
break;
}
tmp = tmp->pnext;
}
}
}
if( pdecal->polys )
Mem_Free( pdecal->polys );
pdecal->psurface = NULL;
pdecal->polys = NULL;
}
// Just reuse next decal in list
// A decal that spans multiple surfaces will use multiple decal_t pool entries,
// as each surface needs it's own.
static decal_t *R_DecalAlloc( decal_t *pdecal )
{
int limit = MAX_RENDER_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( gDecalCount >= limit )
gDecalCount = 0;
pdecal = &gDecalPool[gDecalCount]; // reuse next decal
gDecalCount++;
count++;
} while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit );
}
// if decal is already linked to a surface, unlink it.
R_DecalUnlink( pdecal );
return pdecal;
}
//-----------------------------------------------------------------------------
// find decal image and grab size from it
//-----------------------------------------------------------------------------
static void R_GetDecalDimensions( int texture, int *width, int *height )
{
if( width ) *width = 1; // to avoid divide by zero
if( height ) *height = 1;
R_GetTextureParms( width, height, texture );
}
//-----------------------------------------------------------------------------
// compute the decal basis based on surface normal
//-----------------------------------------------------------------------------
void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] )
{
vec3_t surfaceNormal;
// setup normal
if( surf->flags & SURF_PLANEBACK )
VectorNegate( surf->plane->normal, surfaceNormal );
else VectorCopy( surf->plane->normal, surfaceNormal );
VectorNormalize2( surfaceNormal, textureSpaceBasis[2] );
#if 0
if( FBitSet( flags, FDECAL_CUSTOM ))
{
vec3_t pSAxis = { 1, 0, 0 };
// T = S cross N
CrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] );
// Name sure they aren't parallel or antiparallel
// In that case, fall back to the normal algorithm.
if( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 )
{
// S = N cross T
CrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] );
VectorNormalizeFast( textureSpaceBasis[0] );
VectorNormalizeFast( textureSpaceBasis[1] );
return;
}
// Fall through to the standard algorithm for parallel or antiparallel
}
#endif
VectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] );
VectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] );
}
void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )
{
int width, height;
// Compute the non-scaled decal basis
R_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis );
R_GetDecalDimensions( texture, &width, &height );
// world width of decal = ptexture->width / pDecal->scale
// world height of decal = ptexture->height / pDecal->scale
// scale is inverse, scales world space to decal u/v space [0,1]
// OPTIMIZE: Get rid of these divides
decalWorldScale[0] = (float)pDecal->scale / width;
decalWorldScale[1] = (float)pDecal->scale / height;
VectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] );
VectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] );
}
// Build the initial list of vertices from the surface verts into the global array, 'verts'.
void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts )
{
float *v;
int i;
for( i = 0, v = surf->polys->verts[0]; i < surf->polys->numverts; i++, v += VERTEXSIZE, verts += VERTEXSIZE )
{
VectorCopy( v, verts ); // copy model space coordinates
verts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f;
verts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f;
verts[5] = verts[6] = 0.0f;
}
}
// Figure out where the decal maps onto the surface.
void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )
{
R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );
// Generate texture coordinates for each vertex in decal s,t space
// probably should pre-generate this, store it and use it for decal-decal collisions
// as in R_DecalsIntersect()
pDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] );
pDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] );
}
// Quick and dirty sutherland Hodgman clipper
// Clip polygon to decal in texture space
// JAY: This code is lame, change it later. It does way too much work per frame
// It can be made to recursively call the clipping code and only copy the vertex list once
int R_ClipInside( float *vert, int edge )
{
switch( edge )
{
case LEFT_EDGE:
if( vert[3] > 0.0f )
return 1;
return 0;
case RIGHT_EDGE:
if( vert[3] < 1.0f )
return 1;
return 0;
case TOP_EDGE:
if( vert[4] > 0.0f )
return 1;
return 0;
case BOTTOM_EDGE:
if( vert[4] < 1.0f )
return 1;
return 0;
}
return 0;
}
void R_ClipIntersect( float *one, float *two, float *out, int edge )
{
float t;
// t is the parameter of the line between one and two clipped to the edge
// or the fraction of the clipped point between one & two
// vert[0], vert[1], vert[2] is X, Y, Z
// vert[3] is u
// vert[4] is v
// vert[5] is lightmap u
// vert[6] is lightmap v
if( edge < TOP_EDGE )
{
if( edge == LEFT_EDGE )
{
// left
t = ((one[3] - 0.0f) / (one[3] - two[3]));
out[3] = out[5] = 0.0f;
}
else
{
// right
t = ((one[3] - 1.0f) / (one[3] - two[3]));
out[3] = out[5] = 1.0f;
}
out[4] = one[4] + (two[4] - one[4]) * t;
out[6] = one[6] + (two[6] - one[6]) * t;
}
else
{
if( edge == TOP_EDGE )
{
// top
t = ((one[4] - 0.0f) / (one[4] - two[4]));
out[4] = out[6] = 0.0f;
}
else
{
// bottom
t = ((one[4] - 1.0f) / (one[4] - two[4]));
out[4] = out[6] = 1.0f;
}
out[3] = one[3] + (two[3] - one[3]) * t;
out[5] = one[5] + (two[4] - one[5]) * t;
}
VectorLerp( one, t, two, out );
}
static int SHClip( float *vert, int vertCount, float *out, int edge )
{
int j, outCount;
float *s, *p;
outCount = 0;
s = &vert[(vertCount - 1) * VERTEXSIZE];
for( j = 0; j < vertCount; j++ )
{
p = &vert[j * VERTEXSIZE];
if( R_ClipInside( p, edge ))
{
if( R_ClipInside( s, edge ))
{
// Add a vertex and advance out to next vertex
memcpy( out, p, sizeof( float ) * VERTEXSIZE );
out += VERTEXSIZE;
outCount++;
}
else
{
R_ClipIntersect( s, p, out, edge );
out += VERTEXSIZE;
outCount++;
memcpy( out, p, sizeof( float ) * VERTEXSIZE );
out += VERTEXSIZE;
outCount++;
}
}
else
{
if( R_ClipInside( s, edge ))
{
R_ClipIntersect( p, s, out, edge );
out += VERTEXSIZE;
outCount++;
}
}
s = p;
}
return outCount;
}
float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount )
{
float *pOutVerts = g_DecalClipVerts[0];
int outCount;
// clip the polygon to the decal texture space
outCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE );
outCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE );
outCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE );
outCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE );
if( pVertCount )
*pVertCount = outCount;
return pOutVerts;
}
//-----------------------------------------------------------------------------
// Generate clipped vertex list for decal pdecal projected onto polygon psurf
//-----------------------------------------------------------------------------
float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount )
{
float decalWorldScale[2];
vec3_t textureSpaceBasis[3];
// figure out where the decal maps onto the surface.
R_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );
// build the initial list of vertices from the surface verts.
R_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] );
return R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount );
}
// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf
static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount )
{
float s, t;
mtexinfo_t *tex;
mextrasurf_t *info = surf->info;
float sample_size;
int j;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
tex = surf->texinfo;
for( j = 0; j < vertCount; j++, v += VERTEXSIZE )
{
// lightmap texture coordinates
s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];
s += surf->light_s * sample_size;
s += sample_size * 0.5f;
s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;
t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];
t += surf->light_t * sample_size;
t += sample_size * 0.5f;
t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height;
v[5] = s;
v[6] = t;
}
}
// Check for intersecting decals on this surface
static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount )
{
int texture;
decal_t *plast, *pDecal;
vec3_t decalExtents[2];
float lastArea = 2;
int mapSize[2];
plast = NULL;
*pcount = 0;
// (Same as R_SetupDecalClip).
texture = decalinfo->m_iTexture;
// precalculate the extents of decalinfo's decal in world space.
R_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] );
VectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] );
VectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] );
pDecal = surf->pdecals;
while( pDecal )
{
texture = pDecal->texture;
// Don't steal bigger decals and replace them with smaller decals
// Don't steal permanent decals
if( !FBitSet( pDecal->flags, FDECAL_PERMANENT ))
{
vec3_t testBasis[3];
vec3_t testPosition[2];
float testWorldScale[2];
vec2_t vDecalMin, vDecalMax;
vec2_t vUnionMin, vUnionMax;
R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale );
VectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] );
VectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] );
// Here, we project the min and max extents of the decal that got passed in into
// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were
// clipping a triangle into pDecal's clip space.
Vector2Set( vDecalMin,
DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,
DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );
VectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] );
VectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] );
Vector2Set( vDecalMax,
DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,
DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );
// Now figure out the part of the projection that intersects pDecal's
// clip box [0,0,1,1].
Vector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 ));
Vector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 ));
if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 )
{
// Figure out how much of this intersects the (0,0) - (1,1) bbox.
float flArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]);
if( flArea > 0.6f )
{
*pcount += 1;
if( !plast || flArea <= lastArea )
{
plast = pDecal;
lastArea = flArea;
}
}
}
}
pDecal = pDecal->pnext;
}
return plast;
}
/*
====================
R_DecalCreatePoly
creates mesh for decal on first rendering
====================
*/
glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf )
{
int lnumverts;
glpoly_t *poly;
float *v;
int i;
if( pdecal->polys ) // already created?
return pdecal->polys;
v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts );
if( !lnumverts ) return NULL; // probably this never happens
// allocate glpoly
// REFTODO: com_studiocache pool!
poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float ));
poly->next = pdecal->polys;
poly->flags = surf->flags;
pdecal->polys = poly;
poly->numverts = lnumverts;
for( i = 0; i < lnumverts; i++, v += VERTEXSIZE )
{
VectorCopy( v, poly->verts[i] );
poly->verts[i][3] = v[3];
poly->verts[i][4] = v[4];
poly->verts[i][5] = v[5];
poly->verts[i][6] = v[6];
}
return poly;
}
// Add the decal to the surface's list of decals.
static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo )
{
decal_t *pold;
pdecal->pnext = NULL;
pold = surf->pdecals;
if( pold )
{
while( pold->pnext )
pold = pold->pnext;
pold->pnext = pdecal;
}
else
{
surf->pdecals = pdecal;
}
// tag surface
pdecal->psurface = surf;
// at this point decal are linked with surface
// and will be culled, drawing and sorting
// together with surface
// alloc clipped poly for decal
R_DecalCreatePoly( decalinfo, pdecal, surf );
R_AddDecalVBO( pdecal, surf );
}
static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y )
{
decal_t *pdecal, *pold;
int count, vertCount;
if( !surf ) return; // ???
pold = R_DecalIntersect( decalinfo, surf, &count );
if( count < MAX_OVERLAP_DECALS ) pold = NULL;
pdecal = R_DecalAlloc( pold );
if( !pdecal ) return; // r_decals == 0 ???
pdecal->flags = decalinfo->m_Flags;
VectorCopy( decalinfo->m_Position, pdecal->position );
pdecal->dx = x;
pdecal->dy = y;
// set scaling
pdecal->scale = decalinfo->m_scale;
pdecal->entityIndex = decalinfo->m_Entity;
pdecal->texture = decalinfo->m_iTexture;
// check to see if the decal actually intersects the surface
// if not, then remove the decal
R_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount );
if( !vertCount )
{
R_DecalUnlink( pdecal );
return;
}
// add to the surface's list
R_AddDecalToSurface( pdecal, surf, decalinfo );
}
void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )
{
// get the texture associated with this surface
mtexinfo_t *tex = surf->texinfo;
decal_t *decal = surf->pdecals;
vec4_t textureU, textureV;
float s, t, w, h;
connstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE );
// we in restore mode
if( state == ca_connected || state == ca_validate )
{
// NOTE: we may have the decal on this surface that come from another level.
// check duplicate with same position and texture
while( decal != NULL )
{
if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture )
return; // decal already exists, don't place it again
decal = decal->pnext;
}
}
Vector4Copy( tex->vecs[0], textureU );
Vector4Copy( tex->vecs[1], textureV );
// project decal center into the texture space of the surface
s = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0];
t = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1];
// Determine the decal basis (measured in world space)
// Note that the decal basis vectors 0 and 1 will always lie in the same
// plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits.
R_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis );
// Compute an effective width and height (axis aligned) in the parent texture space
// How does this work? decalBasis[0] represents the u-direction (width)
// of the decal measured in world space, decalBasis[1] represents the
// v-direction (height) measured in world space.
// textureVecsTexelsPerWorldUnits[0] represents the u direction of
// the surface's texture space measured in world space (with the appropriate
// scale factor folded in), and textureVecsTexelsPerWorldUnits[1]
// represents the texture space v direction. We want to find the dimensions (w,h)
// of a square measured in texture space, axis aligned to that coordinate system.
// All we need to do is to find the components of the decal edge vectors
// (decalWidth * decalBasis[0], decalHeight * decalBasis[1])
// in texture coordinates:
w = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] )) +
fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] ));
h = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] )) +
fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] ));
// move s,t to upper left corner
s -= ( w * 0.5f );
t -= ( h * 0.5f );
// Is this rect within the surface? -- tex width & height are unsigned
if( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h))
{
return; // nope
}
// stamp it
R_DecalCreate( decalinfo, surf, s, t );
}
//-----------------------------------------------------------------------------
// iterate over all surfaces on a node, looking for surfaces to decal
//-----------------------------------------------------------------------------
static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
{
// iterate over all surfaces in the node
msurface_t *surf;
int i;
surf = model->surfaces + node->firstsurface;
for( i = 0; i < node->numsurfaces; i++, surf++ )
{
// never apply decals on the water or sky surfaces
if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))
continue;
if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled )
continue;
R_DecalSurface( surf, decalinfo );
}
}
//-----------------------------------------------------------------------------
// Recursive routine to find surface to apply a decal to. World coordinates of
// the decal are passed in r_recalpos like the rest of the engine. This should
// be called through R_DecalShoot()
//-----------------------------------------------------------------------------
static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
{
mplane_t *splitplane;
float dist;
Assert( node != NULL );
if( node->contents < 0 )
{
// hit a leaf
return;
}
splitplane = node->plane;
dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;
// This is arbitrarily set to 10 right now. In an ideal world we'd have the
// exact surface but we don't so, this tells me which planes are "sort of
// close" to the gunshot -- the gunshot is actually 4 units in front of the
// wall (see dlls\weapons.cpp). We also need to check to see if the decal
// actually intersects the texture space of the surface, as this method tags
// parallel surfaces in the same node always.
// JAY: This still tags faces that aren't correct at edges because we don't
// have a surface normal
if( dist > decalinfo->m_Size )
{
R_DecalNode( model, node->children[0], decalinfo );
}
else if( dist < -decalinfo->m_Size )
{
R_DecalNode( model, node->children[1], decalinfo );
}
else
{
if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )
R_DecalNodeSurfaces( model, node, decalinfo );
R_DecalNode( model, node->children[0], decalinfo );
R_DecalNode( model, node->children[1], decalinfo );
}
}
// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords
void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale )
{
decalinfo_t decalInfo;
cl_entity_t *ent = NULL;
model_t *model = NULL;
int width, height;
hull_t *hull;
if( textureIndex <= 0 || textureIndex >= MAX_TEXTURES )
{
gEngfuncs.Con_Printf( S_ERROR "Decal has invalid texture!\n" );
return;
}
if( entityIndex > 0 )
{
ent = gEngfuncs.GetEntityByIndex( entityIndex );
if( modelIndex > 0 ) model = gEngfuncs.pfnGetModelByIndex( modelIndex );
else if( ent != NULL ) model = gEngfuncs.pfnGetModelByIndex( ent->curstate.modelindex );
else return;
}
else if( modelIndex > 0 )
model = gEngfuncs.pfnGetModelByIndex( modelIndex );
else model = WORLDMODEL;
if( !model ) return;
if( model->type != mod_brush )
{
gEngfuncs.Con_Printf( S_ERROR "Decals must hit mod_brush!\n" );
return;
}
decalInfo.m_pModel = model;
hull = &model->hulls[0]; // always use #0 hull
// NOTE: all the decals at 'first shoot' placed into local space of parent entity
// and won't transform again on a next restore, levelchange etc
if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE ))
{
vec3_t pos_l;
// transform decal position in local bmodel space
if( !VectorIsNull( ent->angles ))
{
matrix4x4 matrix;
Matrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f );
Matrix4x4_VectorITransform( matrix, pos, pos_l );
}
else
{
VectorSubtract( pos, ent->origin, pos_l );
}
VectorCopy( pos_l, decalInfo.m_Position );
// decal position moved into local space
SetBits( flags, FDECAL_LOCAL_SPACE );
}
else
{
// already in local space
VectorCopy( pos, decalInfo.m_Position );
}
// this decal must use landmark for correct transition
// because their model exist only in world-space
if( !FBitSet( model->flags, MODEL_HAS_ORIGIN ))
SetBits( flags, FDECAL_USE_LANDMARK );
// more state used by R_DecalNode()
decalInfo.m_iTexture = textureIndex;
decalInfo.m_Entity = entityIndex;
decalInfo.m_Flags = flags;
R_GetDecalDimensions( textureIndex, &width, &height );
decalInfo.m_Size = width >> 1;
if(( height >> 1 ) > decalInfo.m_Size )
decalInfo.m_Size = height >> 1;
decalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE );
// compute the decal dimensions in world space
decalInfo.m_decalWidth = width / decalInfo.m_scale;
decalInfo.m_decalHeight = height / decalInfo.m_scale;
R_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo );
}
// Build the vertex list for a decal on a surface and clip it to the surface.
// This is a template so it can work on world surfaces and dynamic displacement
// triangles the same way.
float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount )
{
glpoly_t *p = pDecal->polys;
int i, count;
float *v, *v2;
if( p )
{
v = g_DecalClipVerts[0];
count = p->numverts;
v2 = p->verts[0];
// if we have mesh so skip clipping and just copy vertexes out (perf)
for( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE )
{
VectorCopy( v2, v );
v[3] = v2[3];
v[4] = v2[4];
v[5] = v2[5];
v[6] = v2[6];
}
// restore pointer
v = g_DecalClipVerts[0];
}
else
{
v = R_DecalVertsClip( pDecal, surf, texture, &count );
R_DecalVertsLight( v, surf, count );
}
if( outCount )
*outCount = count;
return v;
}
void DrawSingleDecal( decal_t *pDecal, msurface_t *fa )
{
float *v;
int i, numVerts;
v = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts );
if( !numVerts ) return;
GL_Bind( XASH_TEXTURE0, pDecal->texture );
pglBegin( GL_POLYGON );
for( i = 0; i < numVerts; i++, v += VERTEXSIZE )
{
pglTexCoord2f( v[3], v[4] );
pglVertex3fv( v );
}
pglEnd();
}
void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse )
{
decal_t *p;
cl_entity_t *e;
if( !fa->pdecals ) return;
e = RI.currententity;
Assert( e != NULL );
if( single )
{
if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )
{
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
if( e->curstate.rendermode == kRenderTransAlpha )
pglDisable( GL_ALPHA_TEST );
}
if( e->curstate.rendermode == kRenderTransColor )
pglEnable( GL_TEXTURE_2D );
if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )
GL_Cull( GL_NONE );
if( gl_polyoffset.value )
{
pglEnable( GL_POLYGON_OFFSET_FILL );
pglPolygonOffset( -1.0f, -gl_polyoffset.value );
}
}
if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled )
{
mtexinfo_t *tex = fa->texinfo;
for( p = fa->pdecals; p; p = p->pnext )
{
if( p->texture )
{
float *o, *v;
int i, numVerts;
o = R_DecalSetupVerts( p, fa, p->texture, &numVerts );
pglEnable( GL_STENCIL_TEST );
pglStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFF );
pglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
pglStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
pglBegin( GL_POLYGON );
for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE )
{
v[5] = ( DotProduct( v, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width;
v[6] = ( DotProduct( v, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height;
pglTexCoord2f( v[5], v[6] );
pglVertex3fv( v );
}
pglEnd();
pglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
pglEnable( GL_ALPHA_TEST );
pglBegin( GL_POLYGON );
for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE )
{
pglTexCoord2f( v[5], v[6] );
pglVertex3fv( v );
}
pglEnd();
pglDisable( GL_ALPHA_TEST );
pglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
pglStencilFunc( GL_EQUAL, 0, 0xFFFFFFFF );
pglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
}
}
}
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
if( reverse && e->curstate.rendermode == kRenderTransTexture )
{
decal_t *list[1024];
int i, count;
for( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext )
if( p->texture ) list[count++] = p;
for( i = count - 1; i >= 0; i-- )
DrawSingleDecal( list[i], fa );
}
else
{
for( p = fa->pdecals; p; p = p->pnext )
{
if( !p->texture ) continue;
DrawSingleDecal( p, fa );
}
}
if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled )
pglDisable( GL_STENCIL_TEST );
if( single )
{
if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )
{
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
if( e->curstate.rendermode == kRenderTransAlpha )
pglEnable( GL_ALPHA_TEST );
}
if( gl_polyoffset.value )
pglDisable( GL_POLYGON_OFFSET_FILL );
if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )
GL_Cull( GL_FRONT );
if( e->curstate.rendermode == kRenderTransColor )
pglDisable( GL_TEXTURE_2D );
// restore blendfunc here
if( e->curstate.rendermode == kRenderTransAdd || e->curstate.rendermode == kRenderGlow )
pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
}
}
void DrawDecalsBatch( void )
{
cl_entity_t *e;
int i;
if( !tr.num_draw_decals )
return;
e = RI.currententity;
Assert( e != NULL );
if( e->curstate.rendermode != kRenderTransTexture )
{
pglEnable( GL_BLEND );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglDepthMask( GL_FALSE );
}
if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )
GL_Cull( GL_NONE );
if( gl_polyoffset.value )
{
pglEnable( GL_POLYGON_OFFSET_FILL );
pglPolygonOffset( -1.0f, -gl_polyoffset.value );
}
for( i = 0; i < tr.num_draw_decals; i++ )
{
DrawSurfaceDecals( tr.draw_decals[i], false, false );
}
if( e->curstate.rendermode != kRenderTransTexture )
{
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
pglDisable( GL_ALPHA_TEST );
}
if( gl_polyoffset.value )
pglDisable( GL_POLYGON_OFFSET_FILL );
if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )
GL_Cull( GL_FRONT );
tr.num_draw_decals = 0;
}
/*
=============================================================
DECALS SERIALIZATION
=============================================================
*/
static qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry )
{
if( !pdecal || !( pdecal->psurface ))
return false;
VectorCopy( pdecal->position, entry->position );
entry->entityIndex = pdecal->entityIndex;
// Grab surface plane equation
if( pdecal->psurface->flags & SURF_PLANEBACK )
VectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal );
else VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal );
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pList -
// count -
// Output : static int
//-----------------------------------------------------------------------------
static int DecalListAdd( decallist_t *pList, int count )
{
vec3_t tmp;
decallist_t *pdecal;
int i;
pdecal = pList + count;
for( i = 0; i < count; i++ )
{
if( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex )
{
VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge
if( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE )
return count;
}
}
// this is a new decal
return count + 1;
}
static int DecalDepthCompare( const void *a, const void *b )
{
const decallist_t *elem1, *elem2;
elem1 = (const decallist_t *)a;
elem2 = (const decallist_t *)b;
if( elem1->depth > elem2->depth )
return 1;
if( elem1->depth < elem2->depth )
return -1;
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Called by CSaveRestore::SaveClientState
// Input : *pList -
// Output : int
//-----------------------------------------------------------------------------
int R_CreateDecalList( decallist_t *pList )
{
int total = 0;
int i, depth;
if( WORLDMODEL )
{
for( i = 0; i < MAX_RENDER_DECALS; i++ )
{
decal_t *decal = &gDecalPool[i];
decal_t *pdecals;
// decal is in use and is not a custom decal
if( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE ))
continue;
// compute depth
depth = 0;
pdecals = decal->psurface->pdecals;
while( pdecals && pdecals != decal )
{
depth++;
pdecals = pdecals->pnext;
}
pList[total].depth = depth;
pList[total].flags = decal->flags;
pList[total].scale = decal->scale;
R_DecalUnProject( decal, &pList[total] );
COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name ));
// check to see if the decal should be added
total = DecalListAdd( pList, total );
}
if( gEngfuncs.drawFuncs->R_CreateStudioDecalList )
{
total += gEngfuncs.drawFuncs->R_CreateStudioDecalList( pList, total );
}
}
// sort the decals lowest depth first, so they can be re-applied in order
qsort( pList, total, sizeof( decallist_t ), DecalDepthCompare );
return total;
}
/*
===============
R_DecalRemoveAll
remove all decals with specified texture
===============
*/
void R_DecalRemoveAll( int textureIndex )
{
decal_t *pdecal;
int i;
if( textureIndex < 0 || textureIndex >= MAX_TEXTURES )
return; // out of bounds
for( i = 0; i < gDecalCount; i++ )
{
pdecal = &gDecalPool[i];
// don't remove permanent decals
if( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT ))
continue;
if( !textureIndex || ( pdecal->texture == textureIndex ))
R_DecalUnlink( pdecal );
}
}
/*
===============
R_EntityRemoveDecals
remove all decals from specified entity
===============
*/
void R_EntityRemoveDecals( model_t *mod )
{
msurface_t *psurf;
decal_t *p;
int i;
if( !mod || mod->type != mod_brush )
return;
psurf = &mod->surfaces[mod->firstmodelsurface];
for( i = 0; i < mod->nummodelsurfaces; i++, psurf++ )
{
for( p = psurf->pdecals; p; p = p->pnext )
R_DecalUnlink( p );
}
}
/*
===============
R_ClearAllDecals
remove all decals from anything
used for full decals restart
===============
*/
void R_ClearAllDecals( void )
{
decal_t *pdecal;
int i;
// because gDecalCount may be zeroed after recach the decal limit
for( i = 0; i < MAX_RENDER_DECALS; i++ )
{
pdecal = &gDecalPool[i];
R_DecalUnlink( pdecal );
}
if( gEngfuncs.drawFuncs->R_ClearStudioDecals )
{
gEngfuncs.drawFuncs->R_ClearStudioDecals();
}
}