mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-12-01 14:40:28 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1287 lines
34 KiB
C
1287 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"
|
|
#include "cl_tent.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, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 ));
|
|
Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), 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 );
|
|
|
|
// 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();
|
|
}
|
|
}
|