/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_particle.c -- particle rendering // moved from r_main.c #include "r_local.h" #define random() ((rand () & 0x7fff) / ((float)0x7fff)) #define crandom() (2.0 * (random() - 0.5)) //#define BINARY_PART_SORT #ifdef BINARY_PART_SORT /* =================================================== BINARY PARTICLE SORT =================================================== */ int partstosort; sortedelement_t theparts[MAX_PARTICLES]; sortedelement_t *parts_prerender; // Is this really needed? //sortedelement_t *parts_last; void renderParticle (particle_t *p); void renderDecal (particle_t *p); void resetPartSortList (void) { partstosort = 0; parts_prerender = NULL; //parts_last = NULL; } qboolean particleClip( float len ) { if (gl_particle_min->value > 0) { if (len < gl_particle_min->value) return true; } if (gl_particle_max->value > 0) { if (len > gl_particle_max->value) return true; } return false; } sortedelement_t *NewSortPart ( particle_t *p) { vec3_t distance; sortedelement_t *element; element = &theparts[partstosort]; VectorSubtract(p->origin, r_origin, distance); VectorCopy(p->origin, element->org); element->data = p; element->len = VectorLength(distance); element->left = NULL; element->right = NULL; return element; } void AddPartTransTree( particle_t *p ) { sortedelement_t *thisPart; thisPart = NewSortPart(p); if (particleClip(thisPart->len)) return; if (parts_prerender) ElementAddNode(parts_prerender, thisPart); else parts_prerender = thisPart; //parts_last = thisPart; partstosort++; } void R_BuildParticleList (void) { int i; resetPartSortList(); for ( i=0; i < r_newrefdef.num_particles; i++) AddPartTransTree(&r_newrefdef.particles[i]); } void RenderParticleTree (sortedelement_t *element) { if (!element) return; RenderParticleTree(element->left); if (element->data) renderParticle((particle_t *)element->data); RenderParticleTree(element->right); } #endif // BINARY_PART_SORT /* =================================================== STANDARD PARTICLE SORT =================================================== */ // not used anymore /*typedef struct sortedent_s { entity_t *ent; vec_t len; } sortedent_t; sortedent_t theoldents[MAX_ENTITIES];*/ typedef struct sortedpart_s { particle_t *p; vec_t len; } sortedpart_t; sortedpart_t theoldparts[MAX_PARTICLES]; int transCompare (const void *arg1, const void *arg2 ) { const sortedpart_t *a1, *a2; //was sortedent_t a1 = (sortedpart_t *) arg1; a2 = (sortedpart_t *) arg2; return (a2->len - a1->len); } sortedpart_t NewSortedPart (particle_t *p) { vec3_t result; sortedpart_t s_part; s_part.p = p; VectorSubtract(p->origin, r_origin, result); s_part.len = (result[0] * result[0]) + (result[1] * result[1]) + (result[2] * result[2]); return s_part; } void R_SortParticlesOnList (void) { int i/*, j=0*/; for (i = 0; i < r_newrefdef.num_particles; i++) theoldparts[i] = NewSortedPart(&r_newrefdef.particles[i]); qsort((void *) theoldparts, r_newrefdef.num_particles, sizeof(sortedpart_t), transCompare); } /* =================================================== PARTICLE RENDERING =================================================== */ int texParticle (int type) { // check for out of bounds image num type = max( type, 0 ); type = min( type, PARTICLE_TYPES-1 ); // check for bad particle image num if (!r_particletextures[type]) r_particletextures[type] = r_notexture; return r_particletextures[type]->texnum; } void vectoanglerolled (vec3_t value1, float angleyaw, vec3_t angles); /*void vectoanglerolled (vec3_t value1, float angleyaw, vec3_t angles) { float forward, yaw, pitch; yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); if (pitch < 0) pitch += 360; angles[PITCH] = -pitch; angles[YAW] = yaw; angles[ROLL] = - angleyaw; }*/ float AngleFind(float input) { return 180.0/input; } void setBeamAngles (vec3_t start, vec3_t end, vec3_t up, vec3_t right) { vec3_t move, delta; VectorSubtract(end, start, move); VectorNormalize(move); VectorCopy(move, up); VectorSubtract(r_newrefdef.vieworg, start, delta); CrossProduct(up, delta, right); //if(!VectorCompare(right, vec3_origin)) VectorNormalize(right); } vec3_t particle_coord[4]; qboolean ParticleOverbright; void SetParticleOverbright (qboolean toggle) { if ( (toggle && !ParticleOverbright) || (!toggle && ParticleOverbright) ) { SetVertexOverbrights(toggle); ParticleOverbright = toggle; } } void getParticleLight (particle_t *p, vec3_t pos, float lighting, vec3_t shadelight) { int j; float lightest = 0; if (!lighting) { VectorSet(shadelight, p->red, p->green, p->blue); return; } R_LightPoint (pos, shadelight, false); shadelight[0]= (lighting*shadelight[0]+(1-lighting)) * p->red; shadelight[1]= (lighting*shadelight[1]+(1-lighting)) * p->green; shadelight[2]= (lighting*shadelight[2]+(1-lighting)) * p->blue; //this cleans up the lighting { for (j=0;j<3;j++) if (shadelight[j]>lightest) lightest= shadelight[j]; if (lightest>255) for (j=0;j<3;j++) { shadelight[j]*= 255/lightest; if (shadelight[j]>255) shadelight[j] = 255; } for (j=0;j<3;j++) { if (shadelight[j]<0) shadelight[j] = 0; } } } //id move this somewhere less dirty, but im too damn lazy - psychospaz #define DEPTHHACK_RANGE_SHORT 0.9985f #define DEPTHHACK_RANGE_MID 0.975f #define DEPTHHACK_RANGE_LONG 0.95f /* =============== renderParticle =============== */ void renderParticle (particle_t *p) { float size, lighting = gl_particle_lighting->value; // int oldrender=0, rendertype=0; vec3_t up = {vup[0] * 0.75f, vup[1] * 0.75f, vup[2] * 0.75f}; vec3_t right = {vright[0] * 0.75f, vright[1] * 0.75f, vright[2] * 0.75f}; vec3_t shadelight; qboolean shaded; byte alpha; vec3_t coord[4]; VectorCopy(particle_coord[0], coord[0]); VectorCopy(particle_coord[1], coord[1]); VectorCopy(particle_coord[2], coord[2]); VectorCopy(particle_coord[3], coord[3]); GL_BlendFunction (p->blendfunc_src, p->blendfunc_dst); alpha = p->alpha * 255; size = (p->size>0.1) ? p->size : 0.1; if (!(p->flags&PART_SPARK)) GL_Bind(texParticle(p->image)); if (p->flags&PART_DEPTHHACK_SHORT) //nice little poly-peeking - psychospaz qglDepthRange (gldepthmin, gldepthmin + DEPTHHACK_RANGE_SHORT*(gldepthmax-gldepthmin)); if (p->flags&PART_DEPTHHACK_MID) qglDepthRange (gldepthmin, gldepthmin + DEPTHHACK_RANGE_MID*(gldepthmax-gldepthmin)); if (p->flags&PART_DEPTHHACK_LONG) qglDepthRange (gldepthmin, gldepthmin + DEPTHHACK_RANGE_LONG*(gldepthmax-gldepthmin)); if (p->flags&PART_OVERBRIGHT) SetParticleOverbright(true); shaded = (p->flags&PART_SHADED); if (shaded) { getParticleLight (p, p->origin, lighting, shadelight); qglColor4ub((byte)shadelight[0], (byte)shadelight[1], (byte)shadelight[2], alpha); } else qglColor4ub((byte)p->red, (byte)p->green, (byte)p->blue, alpha); if (p->flags&PART_SPARK) //QMB style particles { int angle; vec3_t v; qglDisable (GL_TEXTURE_2D); qglPushMatrix(); { qglBegin (GL_TRIANGLE_FAN); { qglVertex3fv (p->origin); qglColor4ub( 0, 0, 0, 0); for (angle=7*22.5 ; angle>=0 ; angle-=22.5) { v[0]=p->origin[0] - p->angle[0]*size + (right[0]*cos(angle) + up[0]*sin(angle)); v[1]=p->origin[1] - p->angle[1]*size + (right[1]*cos(angle) + up[1]*sin(angle)); v[2]=p->origin[2] - p->angle[2]*size + (right[2]*cos(angle) + up[2]*sin(angle)); qglVertex3fv (v); } } qglEnd (); } qglPopMatrix (); qglEnable (GL_TEXTURE_2D); } else if (p->flags&PART_BEAM) //given start and end positions, will have fun here :) { //p->angle = start //p->origin= end vec3_t angl_coord[4]; vec3_t ang_up, ang_right;//, vdelta; setBeamAngles(p->angle, p->origin, ang_up, ang_right); VectorScale(ang_right, size, ang_right); VectorAdd(p->origin, ang_right, angl_coord[0]); VectorAdd(p->angle, ang_right, angl_coord[1]); VectorSubtract(p->angle, ang_right, angl_coord[2]); VectorSubtract(p->origin, ang_right, angl_coord[3]); qglPushMatrix(); { qglBegin (GL_QUADS); { qglTexCoord2f (0, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1, 1); qglVertex3fv (angl_coord[3]); } qglEnd (); } qglPopMatrix (); } else if (p->flags&PART_LIGHTNING) //given start and end positions, will have fun here :) { //p->angle = start //p->origin= end float tlen, halflen, len, dec = size, warpfactor, warpadd, oldwarpadd, warpsize, time, i=0, factor;//, j; vec3_t move, lastvec, thisvec, tempvec; vec3_t angl_coord[4]; vec3_t ang_up, ang_right, vdelta; warpsize = dec * 0.4; //Knightmare changed warpfactor = M_PI*1.0; time = -r_newrefdef.time*20.0; VectorSubtract(p->origin, p->angle, move); len = VectorNormalize(move); VectorCopy(move, ang_up); VectorSubtract(r_newrefdef.vieworg, p->angle, vdelta); CrossProduct(ang_up, vdelta, ang_right); if(!VectorCompare(ang_right, vec3_origin)) VectorNormalize(ang_right); VectorScale (ang_right, dec, ang_right); VectorScale (ang_up, dec, ang_up); VectorScale(move, dec, move); VectorCopy(p->angle, lastvec); VectorAdd(lastvec, move, thisvec); VectorCopy(thisvec, tempvec); //lightning starts at point and then flares out #define LIGHTNINGWARPFUNCTION 0.25*sin(time+i+warpfactor)*(dec/len) warpadd = LIGHTNINGWARPFUNCTION; //was 0.30*sin(time+warpfactor) factor = 1; halflen = len/2.0; thisvec[0]= (thisvec[0]*2 + crandom()*5)/2; thisvec[1]= (thisvec[1]*2 + crandom()*5)/2; thisvec[2]= (thisvec[2]*2 + crandom()*5)/2; while (len>dec) { i+=warpsize; VectorAdd(thisvec, ang_right, angl_coord[0]); VectorAdd(lastvec, ang_right, angl_coord[1]); VectorSubtract(lastvec, ang_right, angl_coord[2]); VectorSubtract(thisvec, ang_right, angl_coord[3]); qglPushMatrix(); { qglBegin (GL_QUADS); { qglTexCoord2f (0+warpadd, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0+oldwarpadd, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1+oldwarpadd, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1+warpadd, 1); qglVertex3fv (angl_coord[3]); } qglEnd (); } qglPopMatrix (); tlen = (len 1+size/3.0) // factor = 1+size/3.0; if (factor > 4) factor = 4; else if (factor < 1) factor = 1; oldwarpadd = warpadd; warpadd = LIGHTNINGWARPFUNCTION; // was 0.30*sin(time+i+warpfactor) len-=dec; VectorCopy(thisvec, lastvec); VectorAdd(tempvec, move, tempvec); VectorAdd(thisvec, move, thisvec); thisvec[0]= ((thisvec[0] + crandom()*size) + tempvec[0]*factor)/(factor+1); thisvec[1]= ((thisvec[1] + crandom()*size) + tempvec[1]*factor)/(factor+1); thisvec[2]= ((thisvec[2] + crandom()*size) + tempvec[2]*factor)/(factor+1); } i+=warpsize; // one more time if (len>0) { VectorAdd(p->origin, ang_right, angl_coord[0]); VectorAdd(lastvec, ang_right, angl_coord[1]); VectorSubtract(lastvec, ang_right, angl_coord[2]); VectorSubtract(p->origin, ang_right, angl_coord[3]); qglPushMatrix(); { qglBegin (GL_QUADS); { qglTexCoord2f (0+warpadd, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0+oldwarpadd, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1+oldwarpadd, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1+warpadd, 1); qglVertex3fv (angl_coord[3]); } qglEnd (); } qglPopMatrix (); } } else if (p->flags&PART_DIRECTION) //streched out in direction...beams etc... { //float len, dot; vec3_t angl_coord[4]; vec3_t ang_up, ang_right, vdelta; VectorAdd(p->angle, p->origin, vdelta); setBeamAngles(vdelta, p->origin, ang_up, ang_right); VectorScale (ang_right, 0.75f, ang_right); VectorScale (ang_up, 0.75f * VectorLength(p->angle), ang_up); VectorAdd (ang_up, ang_right, angl_coord[0]); VectorSubtract (ang_right, ang_up, angl_coord[1]); VectorNegate (angl_coord[0], angl_coord[2]); VectorNegate (angl_coord[1], angl_coord[3]); qglPushMatrix(); { qglTranslatef( p->origin[0], p->origin[1], p->origin[2] ); qglScalef(size, size, size ); qglBegin (GL_QUADS); { qglTexCoord2f (0, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1, 1); qglVertex3fv (angl_coord[3]); } qglEnd (); } qglPopMatrix (); } else if (p->flags&PART_ANGLED) //facing direction... { vec3_t angl_coord[4]; vec3_t ang_up, ang_right, ang_forward; AngleVectors(p->angle, ang_forward, ang_right, ang_up); VectorScale (ang_right, 0.75f, ang_right); VectorScale (ang_up, 0.75f, ang_up); VectorAdd (ang_up, ang_right, angl_coord[0]); VectorSubtract (ang_right, ang_up, angl_coord[1]); VectorNegate (angl_coord[0], angl_coord[2]); VectorNegate (angl_coord[1], angl_coord[3]); qglPushMatrix(); { qglTranslatef( p->origin[0], p->origin[1], p->origin[2] ); qglScalef(size, size, size ); qglBegin (GL_QUADS); { qglTexCoord2f (0, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1, 1); qglVertex3fv (angl_coord[3]); } qglEnd (); } qglPopMatrix (); } else //normal sprites { qglPushMatrix(); { qglTranslatef( p->origin[0], p->origin[1], p->origin[2] ); qglScalef(size, size, size ); qglBegin (GL_QUADS); { if (p->angle[2]) //if we have roll, we do calcs { vec3_t angl_coord[4]; vec3_t ang_up, ang_right, ang_forward; VectorSubtract(p->origin, r_newrefdef.vieworg, angl_coord[0]); vectoanglerolled(angl_coord[0], p->angle[2], angl_coord[1]); AngleVectors(angl_coord[1], ang_forward, ang_right, ang_up); VectorScale (ang_forward, 0.75f, ang_forward); VectorScale (ang_right, 0.75f, ang_right); VectorScale (ang_up, 0.75f, ang_up); VectorAdd (ang_up, ang_right, angl_coord[0]); VectorSubtract (ang_right, ang_up, angl_coord[1]); VectorNegate (angl_coord[0], angl_coord[2]); VectorNegate (angl_coord[1], angl_coord[3]); qglTexCoord2f (0, 1); qglVertex3fv (angl_coord[0]); qglTexCoord2f (0, 0); qglVertex3fv (angl_coord[1]); qglTexCoord2f (1, 0); qglVertex3fv (angl_coord[2]); qglTexCoord2f (1, 1); qglVertex3fv (angl_coord[3]); } else { qglTexCoord2f (0, 1); qglVertex3fv (coord[0]); qglTexCoord2f (0, 0); qglVertex3fv (coord[1]); qglTexCoord2f (1, 0); qglVertex3fv (coord[2]); qglTexCoord2f (1, 1); qglVertex3fv (coord[3]); } } qglEnd (); } qglPopMatrix (); } if (p->flags&PART_OVERBRIGHT) SetParticleOverbright(false); if (p->flags&PART_DEPTHHACK) qglDepthRange (gldepthmin, gldepthmax); } /* =============== R_DrawAllParticles =============== */ void R_DrawAllParticles (void) { int i; particle_t *p; qboolean fog_on = false; vec3_t up = {vup[0] * 0.75f, vup[1] * 0.75f, vup[2] * 0.75f}; vec3_t right = {vright[0] * 0.75f, vright[1] * 0.75f, vright[2] * 0.75f}; VectorAdd (up, right, particle_coord[0]); VectorSubtract (right, up, particle_coord[1]); VectorNegate (particle_coord[0], particle_coord[2]); VectorNegate (particle_coord[1], particle_coord[3]); // no fog on particles if (qglIsEnabled(GL_FOG)) // check if fog is enabled { fog_on = true; qglDisable(GL_FOG); // if so, disable it } qglEnable( GL_TEXTURE_2D ); GL_TexEnv( GL_MODULATE ); qglDepthMask (false); //qglBlendFunc (GL_SRC_ALPHA, GL_ONE); qglEnable( GL_BLEND ); GL_ShadeModel (GL_SMOOTH); ParticleOverbright = false; for ( i=0; i < r_newrefdef.num_particles; i++) { if (gl_transrendersort->value && !(r_newrefdef.rdflags & RDF_NOWORLDMODEL)) { p = theoldparts[i].p; if ( gl_particledistance->value > 0 && theoldparts[i].len>(100.0*gl_particledistance->value)) continue; } else p=&r_newrefdef.particles[i]; renderParticle(p); } qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_TexEnv( GL_MODULATE ); qglDepthMask (true); qglDisable ( GL_BLEND ); qglColor4f (1,1,1,1); // re-enable fog if it was on if (fog_on) qglEnable(GL_FOG); } #ifdef BINARY_PART_SORT /* =============== R_DrawParticles =============== */ void R_DrawParticles (sortedelement_t *list) { qboolean fog_on = false; vec3_t up = {vup[0] * 0.75f, vup[1] * 0.75f, vup[2] * 0.75f}; vec3_t right = {vright[0] * 0.75f, vright[1] * 0.75f, vright[2] * 0.75f}; VectorAdd (up, right, particle_coord[0]); VectorSubtract (right, up, particle_coord[1]); VectorNegate (particle_coord[0], particle_coord[2]); VectorNegate (particle_coord[1], particle_coord[3]); // no fog on particles if (qglIsEnabled(GL_FOG)) // check if fog is enabled { fog_on = true; qglDisable(GL_FOG); // if so, disable it } qglEnable( GL_TEXTURE_2D ); GL_TexEnv( GL_MODULATE ); qglDepthMask (false); //qglBlendFunc (GL_SRC_ALPHA, GL_ONE); qglEnable( GL_BLEND ); GL_ShadeModel (GL_SMOOTH); RenderParticleTree(list); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_TexEnv( GL_MODULATE ); qglDepthMask (true); qglDisable ( GL_BLEND ); qglColor4f (1,1,1,1); // re-enable fog if it was on if (fog_on) qglEnable(GL_FOG); } #endif // BINARY_PART_SORT /* =================================================== DECAL RENDERING =================================================== */ #ifdef DECALS /* =============== renderDecal =============== */ vec3_t ParticleVec[4]; vec3_t shadelight; void renderDecal (particle_t *p) { float size, alpha; vec3_t angl_coord[4]; vec3_t ang_up, ang_right, ang_forward, color; size = (p->size>0.1) ? p->size : 0.1; alpha = p->alpha; GL_BlendFunction (p->blendfunc_src, p->blendfunc_dst); GL_Bind(texParticle(p->image)); if (p->flags & PART_SHADED) { getParticleLight (p, p->origin, gl_particle_lighting->value, shadelight); VectorSet(color, shadelight[0]/255.0, shadelight[1]/255.0, shadelight[2]/255.0); } else { VectorSet(shadelight, p->red, p->green, p->blue); VectorSet(color, p->red/255.0, p->green/255.0, p->blue/255.0); } if (p->flags & PART_ALPHACOLOR) qglColor4f(color[0]*alpha,color[1]*alpha,color[2]*alpha, alpha); else qglColor4f(color[0],color[1],color[2], alpha); if (!p->decal) { AngleVectors(p->angle, ang_forward, ang_right, ang_up); VectorScale (ang_right, 0.75f, ang_right); VectorScale (ang_up, 0.75f, ang_up); VectorAdd (ang_up, ang_right, angl_coord[0]); VectorSubtract (ang_right, ang_up, angl_coord[1]); VectorNegate (angl_coord[0], angl_coord[2]); VectorNegate (angl_coord[1], angl_coord[3]); VectorMA(p->origin, size, angl_coord[0], ParticleVec[0]); VectorMA(p->origin, size, angl_coord[1], ParticleVec[1]); VectorMA(p->origin, size, angl_coord[2], ParticleVec[2]); VectorMA(p->origin, size, angl_coord[3], ParticleVec[3]); } qglPushMatrix(); if (p->decal) { qglEnable(GL_POLYGON_OFFSET_FILL); qglPolygonOffset(-2, -1); qglBegin (GL_TRIANGLE_FAN); { int i; for (i = 0; i < p->decal->numpolys; i++) { qglTexCoord2f (p->decal->coords[i][0], p->decal->coords[i][1]); qglVertex3fv (p->decal->polys[i]); } } qglEnd (); qglDisable(GL_POLYGON_OFFSET_FILL); } else { qglBegin (GL_QUADS); { qglTexCoord2f (0, 1); qglVertex3fv (ParticleVec[0]); qglTexCoord2f (0, 0); qglVertex3fv (ParticleVec[1]); qglTexCoord2f (1, 0); qglVertex3fv (ParticleVec[2]); qglTexCoord2f (1, 1); qglVertex3fv (ParticleVec[3]); } qglEnd (); } qglPopMatrix (); } /* =============== R_DrawAllDecals =============== */ vec3_t particle_coord[4]; void R_DrawAllDecals (void) { vec3_t up = {vup[0] * 0.75f, vup[1] * 0.75f, vup[2] * 0.75f}; vec3_t right = {vright[0] * 0.75f, vright[1] * 0.75f, vright[2] * 0.75f}; int i; particle_t *d; VectorAdd (up, right, particle_coord[0]); VectorSubtract (right, up, particle_coord[1]); VectorNegate (particle_coord[0], particle_coord[2]); VectorNegate (particle_coord[1], particle_coord[3]); qglEnable (GL_TEXTURE_2D); GL_TexEnv( GL_MODULATE ); qglDepthMask (false); GLSTATE_ENABLE_BLEND GL_ShadeModel (GL_SMOOTH); GLSTATE_DISABLE_ALPHATEST for ( i=0; i < r_newrefdef.num_decals; i++) { d = &r_newrefdef.decals[i]; if (d->decal) // skip if not going to be visible if ((d->decal->node == NULL) || (d->decal->node->visframe != r_visframecount)) continue; renderDecal(d); } qglDepthRange (gldepthmin, gldepthmax); GL_BlendFunction (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_TexEnv( GL_MODULATE ); qglDepthMask (true); GLSTATE_DISABLE_BLEND qglColor4f (1,1,1,1); } #endif