/* Copyright (C) 1996-1997 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_part.c #include "quakedef.h" #ifdef GLQUAKE typedef enum { pt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2 } ptype_t; typedef struct particle_s { vec3_t org; float color; vec3_t vel; float ramp; float die; ptype_t type; struct particle_s *next; } particle_t; #else // software #include "d_local.h" #include "r_local.h" #endif #define DEFAULT_NUM_PARTICLES 2048 #define ABSOLUTE_MIN_PARTICLES 512 #define ABSOLUTE_MAX_PARTICLES 8192 static int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61}; static int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66}; static int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; static particle_t *particles, *active_particles, *free_particles; static int r_numparticles; vec3_t r_pright, r_pup, r_ppn; #ifndef GLQUAKE #if !id386 /* ============== D_DrawParticle ============== */ void D_DrawParticle (particle_t *pparticle) { vec3_t local, transformed; float zi; byte *pdest; short *pz; int i, izi, pix, count, u, v; // transform point VectorSubtract (pparticle->org, r_origin, local); transformed[0] = DotProduct (local, r_pright); transformed[1] = DotProduct (local, r_pup); transformed[2] = DotProduct (local, r_ppn); if (transformed[2] < PARTICLE_Z_CLIP) return; // project the point // FIXME: preadjust xcenter and ycenter zi = 1.0 / transformed[2]; u = (int)(xcenter + zi * transformed[0] + 0.5); v = (int)(ycenter - zi * transformed[1] + 0.5); if ((v > d_vrectbottom_particle) || (u > d_vrectright_particle) || (v < d_vrecty) || (u < d_vrectx)) return; pz = d_pzbuffer + (d_zwidth * v) + u; pdest = d_viewbuffer + d_scantable[v] + u; izi = (int)(zi * 0x8000); pix = izi >> d_pix_shift; if (pix < d_pix_min) pix = d_pix_min; else if (pix > d_pix_max) pix = d_pix_max; switch (pix) { case 1: count = 1 << d_y_aspect_shift; for ( ; count ; count--, pz += d_zwidth, pdest += screenwidth) { if (pz[0] <= izi) { pz[0] = izi; pdest[0] = pparticle->color; } } break; case 2: count = 2 << d_y_aspect_shift; for ( ; count ; count--, pz += d_zwidth, pdest += screenwidth) { if (pz[0] <= izi) { pz[0] = izi; pdest[0] = pparticle->color; } if (pz[1] <= izi) { pz[1] = izi; pdest[1] = pparticle->color; } } break; case 3: count = 3 << d_y_aspect_shift; for ( ; count ; count--, pz += d_zwidth, pdest += screenwidth) { if (pz[0] <= izi) { pz[0] = izi; pdest[0] = pparticle->color; } if (pz[1] <= izi) { pz[1] = izi; pdest[1] = pparticle->color; } if (pz[2] <= izi) { pz[2] = izi; pdest[2] = pparticle->color; } } break; case 4: count = 4 << d_y_aspect_shift; for ( ; count ; count--, pz += d_zwidth, pdest += screenwidth) { if (pz[0] <= izi) { pz[0] = izi; pdest[0] = pparticle->color; } if (pz[1] <= izi) { pz[1] = izi; pdest[1] = pparticle->color; } if (pz[2] <= izi) { pz[2] = izi; pdest[2] = pparticle->color; } if (pz[3] <= izi) { pz[3] = izi; pdest[3] = pparticle->color; } } break; default: count = pix << d_y_aspect_shift; for ( ; count ; count--, pz += d_zwidth, pdest += screenwidth) { for (i=0 ; icolor; } } } break; } } #endif // !id386 #endif // GLQUAKE #ifdef GLQUAKE static void Classic_LoadParticleTexures (void) { int i, x, y; unsigned int data[32][32]; particletexture = texture_extension_number++; GL_Bind (particletexture); // clear to transparent white for (i=0 ; i<32*32 ; i++) { ((unsigned *)data)[i] = 0x00FFFFFF; } // draw a circle in the top left corner for (x=0 ; x<16 ; x++) { for (y=0 ; y<16 ; y++) { if ((x - 7.5) * (x - 7.5) + (y - 7.5) * (y - 7.5) <= 8 * 8) { data[y][x] = 0xFFFFFFFF; // solid white } } } glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); GL_Upload32 ((unsigned *)data, 32, 32, TEX_MIPMAP | TEX_ALPHA); } #endif /* =============== R_InitParticles =============== */ void Classic_InitParticles (void) { int i; if ((i = COM_CheckParm ("-particles")) && i+1 < com_argc) { r_numparticles = (int)(Q_atoi(com_argv[i+1])); r_numparticles = bound(ABSOLUTE_MIN_PARTICLES, r_numparticles, ABSOLUTE_MAX_PARTICLES); } else { r_numparticles = DEFAULT_NUM_PARTICLES; } particles = (particle_t *)Hunk_AllocName (r_numparticles * sizeof(particle_t), "classic:particles"); #ifdef GLQUAKE Classic_LoadParticleTexures (); #endif } /* =============== R_EntityParticles =============== */ #define NUMVERTEXNORMALS 162 extern float r_avertexnormals[NUMVERTEXNORMALS][3]; vec3_t avelocities[NUMVERTEXNORMALS]; float beamlength = 16; void R_EntityParticles (entity_t *ent) { int i, count; particle_t *p; float angle, dist, sr, sp, sy, cr, cp, cy; vec3_t forward; dist = 64; count = 50; if (!avelocities[0][0]) for (i=0 ; inext; p->next = active_particles; active_particles = p; p->die = cl.time + 0.01; p->color = 0x6f; p->type = pt_explode; p->org[0] = ent->origin[0] + r_avertexnormals[i][0]*dist + forward[0]*beamlength; p->org[1] = ent->origin[1] + r_avertexnormals[i][1]*dist + forward[1]*beamlength; p->org[2] = ent->origin[2] + r_avertexnormals[i][2]*dist + forward[2]*beamlength; } } /* =============== R_ClearParticles =============== */ void Classic_ClearParticles (void) { int i; free_particles = &particles[0]; active_particles = NULL; for (i=0 ; inext; p->next = active_particles; active_particles = p; p->die = 99999; p->color = (-c)&15; p->type = pt_static; VectorCopy (vec3_origin, p->vel); VectorCopy (org, p->org); } fclose (f); Con_Printf ("%i points read\n", c); } /* =============== R_ParseParticleEffect Parse an effect out of the server message =============== */ void R_ParseParticleEffect (void) { vec3_t org, dir; int i, count, color; for (i=0 ; i<3 ; i++) org[i] = MSG_ReadCoord (); for (i=0 ; i<3 ; i++) dir[i] = MSG_ReadChar () * (1.0 / 16); count = MSG_ReadByte (); color = MSG_ReadByte (); // HACK: unfortunately the effect of the exploding barrel is quite poor // in the original progs - no light, *sigh* - so I added some extras - joe if (count == 255) { dlight_t *dl; // joe: nehahra has improved features for barrels if (nehahra) return; if (cl_explosiontype.value == 4) R_RunParticleEffect (org, dir, 225, 50); else R_ParticleExplosion (org); // the missing light if (cl_explosionlight.value) { dl = CL_AllocDlight (0); VectorCopy (org, dl->origin); dl->radius = 150 + 200 * bound (0, cl_explosionlight.value, 1); dl->die = cl.time + 0.5; dl->decay = 300; #ifdef GLQUAKE dl->type = SetDlightColor (cl_explosionlightcolor.value, lt_explosion, true); #endif } } else { R_RunParticleEffect (org, dir, color, count); } } /* =============== R_ParticleExplosion =============== */ void Classic_ParticleExplosion (vec3_t org) { int i, j; particle_t *p; if (cl_explosiontype.value == 2) // just a sprite return; for (i=0 ; i<1024 ; i++) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 5; p->color = ramp1[0]; p->ramp = rand() & 3; p->type = (i & 1) ? pt_explode : pt_explode2; for (j=0 ; j<3 ; j++) { p->org[j] = org[j] + ((rand() % 32) - 16); p->vel[j] = (rand() % 512) - 256; } } } /* =============== Classic_ColorMappedExplosion // joe: previously R_ParticleExplosion2 =============== */ void Classic_ColorMappedExplosion (vec3_t org, int colorStart, int colorLength) { int i, j; particle_t *p; int colorMod = 0; if (cl_explosiontype.value == 2) return; for (i=0 ; i<512 ; i++) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 0.3; p->color = colorStart + (colorMod % colorLength); colorMod++; p->type = pt_blob; for (j=0 ; j<3 ; j++) { p->org[j] = org[j] + ((rand() % 32) - 16); p->vel[j] = (rand() % 512) - 256; } } } /* =============== Classic_BlobExplosion =============== */ void Classic_BlobExplosion (vec3_t org) { int i, j; particle_t *p; for (i=0 ; i<1024 ; i++) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 1 + (rand() & 8) * 0.05; if (i & 1) { p->type = pt_blob; p->color = 66 + rand() % 6; } else { p->type = pt_blob2; p->color = 150 + rand() % 6; } for (j=0 ; j<3 ; j++) { p->org[j] = org[j] + ((rand() % 32) - 16); p->vel[j] = (rand() % 512) - 256; } } } /* =============== Classic_RunParticleEffect =============== */ void Classic_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) { int i, j; particle_t *p; for (i=0 ; inext; p->next = active_particles; active_particles = p; p->die = cl.time + 0.1 * (rand() % 5); p->color = (color & ~7) + (rand() & 7); p->type = pt_grav; for (j=0 ; j<3 ; j++) { p->org[j] = org[j] + ((rand() & 15) - 8); p->vel[j] = dir[j] * 15; } } } /* =============== Classic_LavaSplash =============== */ void Classic_LavaSplash (vec3_t org) { int i, j, k; particle_t *p; float vel; vec3_t dir; for (i=-16 ; i<16 ; i++) { for (j=-16 ; j<16 ; j++) { for (k=0 ; k<1 ; k++) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 2 + (rand() & 31) * 0.02; p->color = 224 + (rand() & 7); p->type = pt_grav; dir[0] = j * 8 + (rand() & 7); dir[1] = i * 8 + (rand() & 7); dir[2] = 256; p->org[0] = org[0] + dir[0]; p->org[1] = org[1] + dir[1]; p->org[2] = org[2] + (rand() & 63); VectorNormalize (dir); vel = 50 + (rand() & 63); VectorScale (dir, vel, p->vel); } } } } /* =============== Classic_TeleportSplash =============== */ void Classic_TeleportSplash (vec3_t org) { int i, j, k; particle_t *p; float vel; vec3_t dir; for (i=-16 ; i<16 ; i+=4) { for (j=-16 ; j<16 ; j+=4) { for (k=-24 ; k<32 ; k+=4) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 0.2 + (rand() & 7) * 0.02; p->color = 7 + (rand() & 7); p->type = pt_grav; dir[0] = j * 8; dir[1] = i * 8; dir[2] = k * 8; p->org[0] = org[0] + i + (rand() & 3); p->org[1] = org[1] + j + (rand() & 3); p->org[2] = org[2] + k + (rand() & 3); VectorNormalize (dir); vel = 50 + (rand() & 63); VectorScale (dir, vel, p->vel); } } } } /* =============== Classic_ZerstorerRain =============== */ void Classic_ZerstorerRain (vec3_t org) { int i; particle_t *p; // don't draw if paused or in the console, or otherwise if (key_dest != key_game) return; for (i = 0; i < 16; i++) { if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->die = cl.time + 1; p->type = pt_static; p->color = 39 + (rand () % 5); p->org[0] = org[0] + (rand () % 256) - 128; p->org[1] = org[1] + (rand () % 256) - 128; p->org[2] = org[2] + (rand () % 256) - 128; p->vel[0] = 0; p->vel[0] = 0; p->vel[2] = -128; } } /* =============== Classic_RocketTrail =============== */ void Classic_RocketTrail (vec3_t start, vec3_t end, int type) { vec3_t vec; float len; int j, dec; particle_t *p; static int tracercount; VectorSubtract (end, start, vec); len = VectorNormalize (vec); if (type < 128) { dec = 3; } else { dec = 1; type -= 128; } while (len > 0) { len -= dec; if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; VectorCopy (vec3_origin, p->vel); p->die = cl.time + 2; switch (type) { case GRENADE_TRAIL: p->ramp = (rand() & 3) + 2; p->color = ramp3[(int)p->ramp]; p->type = pt_fire; for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() % 6) - 3); break; case BLOOD_TRAIL: p->type = pt_grav; p->color = 67 + (rand() & 3); for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() % 6) - 3); break; case TRACER1_TRAIL: case TRACER2_TRAIL: p->die = cl.time + 0.5; p->type = pt_static; p->color = (type == 3) ? 52 + ((tracercount & 4) << 1) : 230 + ((tracercount & 4) << 1); tracercount++; VectorCopy (start, p->org); if (tracercount & 1) { p->vel[0] = 30 * vec[1]; p->vel[1] = 30 * -vec[0]; } else { p->vel[0] = 30 * -vec[1]; p->vel[1] = 30 * vec[0]; } break; case SLIGHT_BLOOD_TRAIL: p->type = pt_grav; p->color = 67 + (rand() & 3); for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() % 6) - 3); len -= 3; break; case VOOR_TRAIL: p->color = 9*16 + 8 + (rand() & 3); p->type = pt_static; p->die = cl.time + 0.3; for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() & 15) - 8); break; case NEHAHRA_SMOKE: // nehahra smoke tracer p->color = 12 + (rand() & 3); p->type = pt_fire; p->die = cl.time + 1; for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() & 3) - 2); break; case ALT_ROCKET_TRAIL: case ROCKET_TRAIL: default: p->ramp = (rand() & 3); p->color = ramp3[(int)p->ramp]; p->type = pt_fire; for (j=0 ; j<3 ; j++) p->org[j] = start[j] + ((rand() % 6) - 3); break; } VectorAdd (start, vec, start); } } /* =============== Classic_DrawParticles =============== */ void Classic_DrawParticles (void) { particle_t *p, *kill; int i; float grav, time1, time2, time3, dvel, frametime; #ifdef GLQUAKE unsigned char *at, theAlpha; vec3_t up, right; float dist, scale, r_partscale; #endif if (!active_particles) return; #ifdef GLQUAKE r_partscale = 0.004 * tan(r_refdef.fov_x * (M_PI / 180) * 0.5f); GL_Bind (particletexture); glEnable (GL_BLEND); glDepthMask (GL_FALSE); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBegin (GL_TRIANGLES); VectorScale (vup, 1.5, up); VectorScale (vright, 1.5, right); #else VectorScale (vright, xscaleshrink, r_pright); VectorScale (vup, yscaleshrink, r_pup); VectorCopy (vpn, r_ppn); #endif frametime = fabs(cl.ctime - cl.oldtime); if (cl.paused) // joe: pace from FuhQuake frametime = 0; time3 = frametime * 15; time2 = frametime * 10; // 15; time1 = frametime * 5; grav = frametime * sv_gravity.value * 0.05; dvel = 4 * frametime; for ( ; ; ) { kill = active_particles; if (kill && kill->die < cl.time) { active_particles = kill->next; kill->next = free_particles; free_particles = kill; continue; } break; } for (p = active_particles ; p ; p = p->next) { for ( ; ; ) { kill = p->next; if (kill && kill->die < cl.time) { p->next = kill->next; kill->next = free_particles; free_particles = kill; continue; } break; } #ifdef GLQUAKE // hack a scale up to keep particles from disapearing dist = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1] + (p->org[2] - r_origin[2])*vpn[2]; scale = 1 + dist * r_partscale; at = (byte *)&d_8to24table[(int)p->color]; theAlpha = (p->type == pt_fire) ? 255 * (6 - p->ramp) / 6 : 255; glColor4ub (*at, *(at + 1), *(at + 2), theAlpha); glTexCoord2f (0,0); glVertex3fv (p->org); glTexCoord2f (1,0); glVertex3f (p->org[0] + up[0]*scale, p->org[1] + up[1]*scale, p->org[2] + up[2]*scale); glTexCoord2f (0,1); glVertex3f (p->org[0] + right[0]*scale, p->org[1] + right[1]*scale, p->org[2] + right[2]*scale); #else D_DrawParticle (p); #endif p->org[0] += p->vel[0] * frametime; p->org[1] += p->vel[1] * frametime; p->org[2] += p->vel[2] * frametime; switch (p->type) { case pt_static: break; case pt_fire: p->ramp += time1; if (p->ramp >= 6) p->die = -1; else p->color = ramp3[(int)p->ramp]; p->vel[2] += grav; break; case pt_explode: p->ramp += time2; if (p->ramp >= 8) p->die = -1; else p->color = ramp1[(int)p->ramp]; for (i=0 ; i<3 ; i++) p->vel[i] += p->vel[i]*dvel; p->vel[2] -= (cl_explosiontype.value == 1) ? grav : grav * 30; break; case pt_explode2: p->ramp += time3; if (p->ramp >= 8) p->die = -1; else p->color = ramp2[(int)p->ramp]; for (i=0 ; i<3 ; i++) p->vel[i] -= p->vel[i]*frametime; p->vel[2] -= (cl_explosiontype.value == 1) ? grav : grav * 30; break; case pt_blob: for (i=0 ; i<3 ; i++) p->vel[i] += p->vel[i]*dvel; p->vel[2] -= grav; break; case pt_blob2: for (i=0 ; i<2 ; i++) p->vel[i] -= p->vel[i]*dvel; p->vel[2] -= grav; break; case pt_grav: case pt_slowgrav: p->vel[2] -= grav; break; } } #ifdef GLQUAKE glEnd (); glDisable (GL_BLEND); glDepthMask (GL_TRUE); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glColor3f (1, 1, 1); #endif } void R_InitParticles (void) { Classic_InitParticles (); #ifdef GLQUAKE QMB_InitParticles (); #endif } void R_ClearParticles (void) { Classic_ClearParticles (); #ifdef GLQUAKE QMB_ClearParticles (); #endif } void R_DrawParticles (void) { Classic_DrawParticles (); #ifdef GLQUAKE QMB_DrawParticles (); #endif } void R_ColorMappedExplosion (vec3_t org, int colorStart, int colorLength) { #ifdef GLQUAKE if (qmb_initialized && gl_part_explosions.value) QMB_ColorMappedExplosion (org, colorStart, colorLength); else #endif Classic_ColorMappedExplosion (org, colorStart, colorLength); } void R_SpriteRain (vec3_t org) { #ifdef GLQUAKE Classic_ZerstorerRain (org); #endif } #define RunParticleEffect(var, org, dir, color, count) \ if (qmb_initialized && gl_part_##var.value) \ QMB_RunParticleEffect(org, dir, color, count); \ else \ Classic_RunParticleEffect(org, dir, color, count); void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) { #ifndef GLQUAKE Classic_RunParticleEffect(org, dir, color, count); #else if (color == 73 || color == 225) { RunParticleEffect(blood, org, dir, color, count); return; } switch (count) { case 10: case 20: case 30: RunParticleEffect(spikes, org, dir, color, count); break; default: RunParticleEffect(gunshots, org, dir, color, count); break; } #endif } void R_RocketTrail (vec3_t start, vec3_t end, int type) { #ifdef GLQUAKE if (qmb_initialized && gl_part_trails.value) QMB_RocketTrail (start, end, type); else #endif Classic_RocketTrail (start, end, type); } #ifdef GLQUAKE #define ParticleFunction(var, name) \ void R_##name (vec3_t org) { \ if (qmb_initialized && gl_part_##var.value) \ QMB_##name(org); \ else \ Classic_##name(org); \ } #else #define ParticleFunction(var, name) \ void R_##name (vec3_t org) { \ Classic_##name(org); \ } #endif ParticleFunction(explosions, ParticleExplosion); ParticleFunction(blobs, BlobExplosion); ParticleFunction(lavasplash, LavaSplash); ParticleFunction(telesplash, TeleportSplash);