2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #define MAX_PARTICLES 16384 // default max # of particles at one time
24 #define ABSOLUTE_MIN_PARTICLES 512 // no fewer than this no matter what's on the command line
26 // LordHavoc: added dust, smoke, snow, bloodcloud, and many others
29 pt_static, pt_grav, pt_slowgrav, pt_blob, pt_blob2, pt_bulletsmoke, pt_smoke, pt_snow, pt_rain, pt_bloodcloud, pt_fallfadespark, pt_bubble, pt_fade, pt_smokecloud, pt_splash, pt_flame, pt_glow, pt_decal, pt_blood, pt_bloodsplatter
33 typedef struct particle_s
42 byte dynlight; // if set the particle will be dynamically lit (if r_dynamicparticles is on), used for smoke and blood
43 byte rendermode; // a TPOLYTYPE_ value
47 float time2; // used for various things (snow fluttering, for example)
48 float bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical of bouncing particles)
50 vec3_t vel2; // used for snow fluttering (base velocity, wind for instance)
51 vec3_t direction; // used by decals
55 float TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal);
57 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
58 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
59 int ramp3[8] = {0x6d, 0x6b, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
61 rtexture_t *particletexture;
62 rtexture_t *smokeparticletexture[8];
63 rtexture_t *rainparticletexture;
64 rtexture_t *bubbleparticletexture;
66 particle_t *particles;
69 vec3_t r_pright, r_pup, r_ppn;
72 particle_t **freeparticles; // list used only in compacting particles array
74 cvar_t r_particles = {"r_particles", "1"};
75 cvar_t r_drawparticles = {"r_drawparticles", "1"};
76 cvar_t r_dynamicparticles = {"r_dynamicparticles", "1", true};
78 byte shadebubble(float dx, float dy, vec3_t light)
82 if ((dx*dx+dy*dy) < 1) // it does hit the sphere
84 dz = 1 - (dx*dx+dy*dy);
87 normal[0] = dx;normal[1] = dy;normal[2] = dz;
88 VectorNormalize(normal);
89 dot = DotProduct(normal, light);
90 if (dot > 0.5) // interior reflection
92 else if (dot < -0.5) // exterior reflection
93 f += ((dot * -2) - 1);
95 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
96 VectorNormalize(normal);
97 dot = DotProduct(normal, light);
98 if (dot > 0.5) // interior reflection
100 else if (dot < -0.5) // exterior reflection
101 f += ((dot * -2) - 1);
103 f += 16; // just to give it a haze so you can see the outline
104 f = bound(0, f, 255);
111 void R_InitParticleTexture (void)
115 byte data[32][32][4], noise1[64][64], noise2[64][64];
118 for (y = 0;y < 32;y++)
121 for (x = 0;x < 32;x++)
123 data[y][x][0] = data[y][x][1] = data[y][x][2] = 255;
125 d = (255 - (dx*dx+dy*dy));
127 data[y][x][3] = (byte) d;
130 particletexture = R_LoadTexture ("particletexture", 32, 32, &data[0][0][0], TEXF_MIPMAP | TEXF_ALPHA | TEXF_RGBA | TEXF_PRECACHE);
132 for (i = 0;i < 8;i++)
136 fractalnoise(&noise1[0][0], 64, 4);
137 fractalnoise(&noise2[0][0], 64, 8);
139 for (y = 0;y < 32;y++)
142 for (x = 0;x < 32;x++)
145 j = (noise1[y][x] - 128) * 2 + 128;
147 if (j > 255) j = 255;
148 data[y][x][0] = data[y][x][1] = data[y][x][2] = j;
150 d = (noise2[y][x] - 128) * 4 + 128;
153 d = (d * (255 - (int) (dx*dx+dy*dy))) >> 8;
154 //j = (sqrt(dx*dx+dy*dy) * 2.0f - 16.0f);
156 // d = (d * (255 - j*j)) >> 8;
158 if (d > 255) d = 255;
159 data[y][x][3] = (byte) d;
168 smokeparticletexture[i] = R_LoadTexture (va("smokeparticletexture%d", i), 32, 32, &data[0][0][0], TEXF_MIPMAP | TEXF_ALPHA | TEXF_RGBA | TEXF_PRECACHE);
171 light[0] = 1;light[1] = 1;light[2] = 1;
172 VectorNormalize(light);
173 for (x=0 ; x<32 ; x++)
175 for (y=0 ; y<32 ; y++)
177 data[y][x][0] = data[y][x][1] = data[y][x][2] = 255;
178 data[y][x][3] = shadebubble((x - 16) * (1.0 / 8.0), y < 24 ? (y - 24) * (1.0 / 24.0) : (y - 24) * (1.0 / 8.0), light);
181 rainparticletexture = R_LoadTexture ("rainparticletexture", 32, 32, &data[0][0][0], TEXF_MIPMAP | TEXF_ALPHA | TEXF_RGBA | TEXF_PRECACHE);
183 light[0] = 1;light[1] = 1;light[2] = 1;
184 VectorNormalize(light);
185 for (x=0 ; x<32 ; x++)
187 for (y=0 ; y<32 ; y++)
189 data[y][x][0] = data[y][x][1] = data[y][x][2] = 255;
190 data[y][x][3] = shadebubble((x - 16) * (1.0 / 16.0), (y - 16) * (1.0 / 16.0), light);
193 bubbleparticletexture = R_LoadTexture ("bubbleparticletexture", 32, 32, &data[0][0][0], TEXF_MIPMAP | TEXF_ALPHA | TEXF_RGBA | TEXF_PRECACHE);
198 particles = (particle_t *) qmalloc(r_numparticles * sizeof(particle_t));
199 freeparticles = (void *) qmalloc(r_numparticles * sizeof(particle_t *));
201 R_InitParticleTexture ();
204 void r_part_shutdown()
208 qfree(freeparticles);
216 void R_ReadPointFile_f (void);
217 void R_Particles_Init (void)
221 i = COM_CheckParm ("-particles");
225 r_numparticles = (int)(atoi(com_argv[i+1]));
226 if (r_numparticles < ABSOLUTE_MIN_PARTICLES)
227 r_numparticles = ABSOLUTE_MIN_PARTICLES;
231 r_numparticles = MAX_PARTICLES;
234 Cmd_AddCommand ("pointfile", R_ReadPointFile_f);
236 Cvar_RegisterVariable (&r_particles);
237 Cvar_RegisterVariable (&r_drawparticles);
238 Cvar_RegisterVariable (&r_dynamicparticles);
240 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown);
243 //void particle(int ptype, int pcolor, int ptex, int prendermode, int plight, float pscale, float palpha, float ptime, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz)
244 #define particle(ptype, pcolor, ptex, prendermode, plight, pscale, palpha, ptime, pbounce, px, py, pz, pvx, pvy, pvz)\
247 if (numparticles >= r_numparticles)\
249 part = &particles[numparticles++];\
250 part->type = (ptype);\
251 part->color = (pcolor);\
253 part->dynlight = (plight);\
254 part->rendermode = (prendermode);\
255 part->scale = (pscale);\
256 part->alpha = (palpha);\
257 part->die = cl.time + (ptime);\
258 part->bounce = (pbounce);\
259 part->org[0] = (px);\
260 part->org[1] = (py);\
261 part->org[2] = (pz);\
262 part->vel[0] = (pvx);\
263 part->vel[1] = (pvy);\
264 part->vel[2] = (pvz);\
266 part->vel2[0] = part->vel2[1] = part->vel2[2] = 0;\
268 #define particle2(ptype, pcolor, ptex, prendermode, plight, pscale, palpha, ptime, pbounce, pbase, poscale, pvscale)\
271 if (numparticles >= r_numparticles)\
273 part = &particles[numparticles++];\
274 part->type = (ptype);\
275 part->color = (pcolor);\
277 part->dynlight = (plight);\
278 part->rendermode = (prendermode);\
279 part->scale = (pscale);\
280 part->alpha = (palpha);\
281 part->die = cl.time + (ptime);\
282 part->bounce = (pbounce);\
283 part->org[0] = lhrandom(-(poscale), (poscale)) + (pbase)[0];\
284 part->org[1] = lhrandom(-(poscale), (poscale)) + (pbase)[1];\
285 part->org[2] = lhrandom(-(poscale), (poscale)) + (pbase)[2];\
286 part->vel[0] = lhrandom(-(pvscale), (pvscale));\
287 part->vel[1] = lhrandom(-(pvscale), (pvscale));\
288 part->vel[2] = lhrandom(-(pvscale), (pvscale));\
290 part->vel2[0] = part->vel2[1] = part->vel2[2] = 0;\
292 #define particle3(ptype, pcolor, ptex, prendermode, plight, pscale, palpha, ptime, pbounce, pbase, pscalex, pscaley, pscalez, pvscalex, pvscaley, pvscalez)\
295 if (numparticles >= r_numparticles)\
297 part = &particles[numparticles++];\
298 part->type = (ptype);\
299 part->color = (pcolor);\
301 part->dynlight = (plight);\
302 part->rendermode = (prendermode);\
303 part->scale = (pscale);\
304 part->alpha = (palpha);\
305 part->die = cl.time + (ptime);\
306 part->bounce = (pbounce);\
307 part->org[0] = lhrandom(-(pscalex), (pscalex)) + (pbase)[0];\
308 part->org[1] = lhrandom(-(pscaley), (pscaley)) + (pbase)[1];\
309 part->org[2] = lhrandom(-(pscalez), (pscalez)) + (pbase)[2];\
310 part->vel[0] = lhrandom(-(pvscalex), (pvscalex));\
311 part->vel[1] = lhrandom(-(pvscaley), (pvscaley));\
312 part->vel[2] = lhrandom(-(pvscalez), (pvscalez));\
314 part->vel2[0] = part->vel2[1] = part->vel2[2] = 0;\
316 #define particle4(ptype, pcolor, ptex, prendermode, plight, pscale, palpha, ptime, pbounce, px, py, pz, pvx, pvy, pvz, ptime2, pvx2, pvy2, pvz2)\
319 if (numparticles >= r_numparticles)\
321 part = &particles[numparticles++];\
322 part->type = (ptype);\
323 part->color = (pcolor);\
325 part->dynlight = (plight);\
326 part->rendermode = (prendermode);\
327 part->scale = (pscale);\
328 part->alpha = (palpha);\
329 part->die = cl.time + (ptime);\
330 part->bounce = (pbounce);\
331 part->org[0] = (px);\
332 part->org[1] = (py);\
333 part->org[2] = (pz);\
334 part->vel[0] = (pvx);\
335 part->vel[1] = (pvy);\
336 part->vel[2] = (pvz);\
337 part->time2 = (ptime2);\
338 part->vel2[0] = (pvx2);\
339 part->vel2[1] = (pvy2);\
340 part->vel2[2] = (pvz2);\
348 void R_EntityParticles (entity_t *ent)
352 float sp, sy, cp, cy;
356 static vec3_t avelocities[NUMVERTEXNORMALS];
357 if (!r_particles.value) return; // LordHavoc: particles are optional
362 if (!avelocities[0][0])
363 for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
364 avelocities[0][i] = (rand()&255) * 0.01;
366 for (i=0 ; i<NUMVERTEXNORMALS ; i++)
368 angle = cl.time * avelocities[i][0];
371 angle = cl.time * avelocities[i][1];
379 particle(pt_static, 0x6f, particletexture, TPOLYTYPE_ALPHA, false, 2, 255, 0, 0, ent->origin[0] + m_bytenormals[i][0]*dist + forward[0]*beamlength, ent->origin[1] + m_bytenormals[i][1]*dist + forward[1]*beamlength, ent->origin[2] + m_bytenormals[i][2]*dist + forward[2]*beamlength, 0, 0, 0);
389 void R_ClearParticles (void)
392 // free_particles = &particles[0];
393 // active_particles = NULL;
395 // for (i=0 ;i<r_numparticles ; i++)
396 // particles[i].next = &particles[i+1];
397 // particles[r_numparticles-1].next = NULL;
403 void R_ReadPointFile_f (void)
409 char name[MAX_OSPATH];
411 sprintf (name,"maps/%s.pts", sv.name);
413 COM_FOpenFile (name, &f, false);
416 Con_Printf ("couldn't open %s\n", name);
420 Con_Printf ("Reading %s...\n", name);
424 r = fscanf (f,"%f %f %f\n", &org[0], &org[1], &org[2]);
429 if (numparticles >= r_numparticles)
431 Con_Printf ("Not enough free particles\n");
434 particle(pt_static, (-c)&15, particletexture, TPOLYTYPE_ALPHA, false, 2, 255, 99999, 0, org[0], org[1], org[2], 0, 0, 0);
438 Con_Printf ("%i points read\n", c);
443 R_ParseParticleEffect
445 Parse an effect out of the server message
448 void R_ParseParticleEffect (void)
451 int i, count, msgcount, color;
453 for (i=0 ; i<3 ; i++)
454 org[i] = MSG_ReadCoord ();
455 for (i=0 ; i<3 ; i++)
456 dir[i] = MSG_ReadChar () * (1.0/16);
457 msgcount = MSG_ReadByte ();
458 color = MSG_ReadByte ();
465 R_RunParticleEffect (org, dir, color, count);
474 void R_ParticleExplosion (vec3_t org, int smoke)
477 if (!r_particles.value) return; // LordHavoc: particles are optional
479 // particle(pt_smoke, (rand()&7) + 8, smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, true, 30, 255, 2, org[0], org[1], org[2], 0, 0, 0);
481 i = Mod_PointInLeaf(org, cl.worldmodel)->contents;
482 if (i == CONTENTS_SLIME || i == CONTENTS_WATER)
484 for (i=0 ; i<128 ; i++)
485 particle2(pt_bubble, (rand()&3) + 12, bubbleparticletexture, TPOLYTYPE_ADD, false, lhrandom(1, 2), 255, 2, 1.5, org, 16, 96);
494 float f, forg[3], fvel[3], fvel2[3];
495 // for (i = 0;i < 256;i++)
496 // particle(pt_fallfadespark, ramp3[rand()%6], particletexture, TPOLYTYPE_ALPHA, false, 1.5, lhrandom(128, 255), 5, lhrandom(-16, 16) + org[0], lhrandom(-16, 16) + org[1], lhrandom(-16, 16) + org[2], lhrandom(-192, 192), lhrandom(-192, 192), lhrandom(-192, 192) + 192);
497 // for (i = 0;i < 256;i++)
498 // particle2(pt_fallfadespark, ramp3[rand()%6], particletexture, TPOLYTYPE_ALPHA, false, 1.5, lhrandom(128, 255), 5, org, 15, 150);
499 for (i = 0;i < 32;i++)
501 fvel[0] = lhrandom(-150, 150);
502 fvel[1] = lhrandom(-150, 150);
503 fvel[2] = lhrandom(-150, 150) + 80;
504 // particle(pt_flamefall, 106 + (rand()%6), particletexture, TPOLYTYPE_ALPHA, false, 3, 255, 5, forg[0] + lhrandom(-5, 5), forg[1] + lhrandom(-5, 5), forg[2] + lhrandom(-5, 5), fvel2[0], fvel2[1], fvel2[2]);
505 for (j = 0;j < 64;j++)
507 forg[0] = lhrandom(-20, 20) + org[0];
508 forg[1] = lhrandom(-20, 20) + org[1];
509 forg[2] = lhrandom(-20, 20) + org[2];
510 fvel2[0] = fvel[0] + lhrandom(-30, 30);
511 fvel2[1] = fvel[1] + lhrandom(-30, 30);
512 fvel2[2] = fvel[2] + lhrandom(-30, 30);
513 f = lhrandom(0.2, 1);
517 particle(pt_flamefall, 106 + (rand()%6), particletexture, TPOLYTYPE_ALPHA, false, 5, lhrandom(96, 192), 5, forg[0], forg[1], forg[2], fvel2[0], fvel2[1], fvel2[2]);
520 // for (i = 0;i < 16;i++)
521 // particle2(pt_smoke, 12+(rand()&3), smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, true, 20, 192, 99, org, 20, 0);
522 // for (i = 0;i < 50;i++)
523 // particle2(pt_flamingdebris, ramp3[rand()%6], particletexture, TPOLYTYPE_ALPHA, false, 3, 255, 99, org, 10, 200);
524 // for (i = 0;i < 30;i++)
525 // particle2(pt_smokingdebris, 10 + (rand()%6), particletexture, TPOLYTYPE_ALPHA, false, 2, 255, 99, org, 10, 100);
536 void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
539 if (!r_particles.value) return; // LordHavoc: particles are optional
541 for (i = 0;i < 512;i++)
542 particle2(pt_fade, colorStart + (i % colorLength), particletexture, TPOLYTYPE_ALPHA, false, 1.5, 255, 0.3, 0, org, 8, 192);
551 void R_BlobExplosion (vec3_t org)
554 if (!r_particles.value) return; // LordHavoc: particles are optional
556 for (i=0 ; i<512 ; i++)
557 particle3(pt_blob, 66+(rand()%6), particletexture, TPOLYTYPE_ALPHA, false, 2, 255, lhrandom(1, 1.4), 0, org, 16, 16, 16, 4, 4, 128);
558 for (i=0 ; i<512 ; i++)
559 particle3(pt_blob2, 150+(rand()%6), particletexture, TPOLYTYPE_ALPHA, false, 2, 255, lhrandom(1, 1.4), 0, org, 16, 16, 16, 4, 4, 128);
568 void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
570 if (!r_particles.value) return; // LordHavoc: particles are optional
574 R_ParticleExplosion(org, false);
578 particle2(pt_fade, color + (rand()&7), particletexture, TPOLYTYPE_ALPHA, false, 1, 128, 1, 0, org, 8, 15);
581 // LordHavoc: added this for spawning sparks/dust (which have strong gravity)
587 void R_SparkShower (vec3_t org, vec3_t dir, int count)
589 if (!r_particles.value) return; // LordHavoc: particles are optional
592 particle(pt_bulletsmoke, 12+(rand()&3), smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, true, 1, 160, 99, 0, org[0], org[1], org[2], lhrandom(-4, 4), lhrandom(-4, 4), 16);
595 particle(pt_fallfadespark, ramp3[rand()%6], particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(0, 255), 1.5, 1.5, org[0], org[1], org[2], lhrandom(-64, 64), lhrandom(-64, 64), lhrandom(-64, 64) + 128);
598 void R_BloodPuff (vec3_t org, vec3_t vel, int count)
600 if (!r_particles.value) return; // LordHavoc: particles are optional
606 particle(pt_bloodsplatter, 68+(rand()&3), smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, true, lhrandom(10, 20), min(count, 10) * 25 + 5, 99, -1, org[0], org[1], org[2], vel[0] + lhrandom(-64, 64), vel[1] + lhrandom(-64, 64), vel[2] + lhrandom(-64, 64));
611 void R_BloodShower (vec3_t mins, vec3_t maxs, float velspeed, int count)
616 if (!r_particles.value) return; // LordHavoc: particles are optional
618 VectorSubtract(maxs, mins, diff);
619 center[0] = (mins[0] + maxs[0]) * 0.5;
620 center[1] = (mins[1] + maxs[1]) * 0.5;
621 center[2] = (mins[2] + maxs[2]) * 0.5;
622 // FIXME: change velspeed back to 2.0x after fixing mod
623 velscale[0] = velspeed * 2.0 / diff[0];
624 velscale[1] = velspeed * 2.0 / diff[1];
625 velscale[2] = velspeed * 2.0 / diff[2];
630 org[0] = lhrandom(mins[0], maxs[0]);
631 org[1] = lhrandom(mins[1], maxs[1]);
632 org[2] = lhrandom(mins[2], maxs[2]);
633 vel[0] = (org[0] - center[0]) * velscale[0];
634 vel[1] = (org[1] - center[1]) * velscale[1];
635 vel[2] = (org[2] - center[2]) * velscale[2];
636 particle(pt_bloodsplatter, 68+(rand()&3), smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, true, lhrandom(10, 25), 255, 99, -1, org[0], org[1], org[2], vel[0], vel[1], vel[2]);
640 void R_ParticleCube (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int gravity, int randomvel)
643 if (!r_particles.value) return; // LordHavoc: particles are optional
644 if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
645 if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
646 if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
649 particle(gravity ? pt_grav : pt_static, colorbase + (rand()&3), particletexture, TPOLYTYPE_ALPHA, false, 2, 255, lhrandom(1, 2), 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0] + lhrandom(-randomvel, randomvel), dir[1] + lhrandom(-randomvel, randomvel), dir[2] + lhrandom(-randomvel, randomvel));
652 void R_ParticleRain (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int type)
656 if (!r_particles.value) return; // LordHavoc: particles are optional
657 if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
658 if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
659 if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
660 if (dir[2] < 0) // falling
662 t = (maxs[2] - mins[2]) / -dir[2];
667 t = (maxs[2] - mins[2]) / dir[2];
670 if (t < 0 || t > 2) // sanity check
678 vel[0] = dir[0] + lhrandom(-16, 16);
679 vel[1] = dir[1] + lhrandom(-16, 16);
680 vel[2] = dir[2] + lhrandom(-32, 32);
681 particle4(pt_rain, colorbase + (rand()&3), rainparticletexture, TPOLYTYPE_ALPHA, true, 3, 255, t, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), z, vel[0], vel[1], vel[2], 0, vel[0], vel[1], vel[2]);
687 vel[0] = dir[0] + lhrandom(-16, 16);
688 vel[1] = dir[1] + lhrandom(-16, 16);
689 vel[2] = dir[2] + lhrandom(-32, 32);
690 particle4(pt_snow, colorbase + (rand()&3), particletexture, TPOLYTYPE_ALPHA, false, 2, 255, t, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), z, vel[0], vel[1], vel[2], 0, vel[0], vel[1], vel[2]);
694 Host_Error("R_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
698 void R_FlameCube (vec3_t mins, vec3_t maxs, int count)
701 if (!r_particles.value) return; // LordHavoc: particles are optional
702 if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
703 if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
704 if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
707 particle(pt_flame, 224 + (rand()&15), smokeparticletexture[rand()&7], TPOLYTYPE_ADD, false, 1, lhrandom(64, 255), 5, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), lhrandom(-16, 16), lhrandom(-16, 16), lhrandom(-16, 48));
710 void R_Flames (vec3_t org, vec3_t vel, int count)
712 if (!r_particles.value) return; // LordHavoc: particles are optional
715 particle(pt_flame, 224 + (rand()&15), smokeparticletexture[rand()&7], TPOLYTYPE_ADD, false, 1, lhrandom(64, 255), 5, 1.5, org[0], org[1], org[2], vel[0] + lhrandom(-16, 16), vel[1] + lhrandom(-16, 16), vel[2] + lhrandom(-16, 16));
726 void R_LavaSplash (vec3_t origin)
731 if (!r_particles.value) return; // LordHavoc: particles are optional
733 for (i=-128 ; i<128 ; i+=8)
735 for (j=-128 ; j<128 ; j+=8)
737 dir[0] = j + lhrandom(0, 8);
738 dir[1] = i + lhrandom(0, 8);
740 org[0] = origin[0] + dir[0];
741 org[1] = origin[1] + dir[1];
742 org[2] = origin[2] + lhrandom(0, 64);
743 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
744 particle(pt_slowgrav, 224 + (rand()&7), particletexture, TPOLYTYPE_ALPHA, false, 3, 128, lhrandom(2, 2.5), 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel);
745 // particle(pt_slowgrav, 224 + (rand()&7), particletexture, TPOLYTYPE_ALPHA, false, 3, 128, lhrandom(2, 2.5), 0, origin[0] + i, origin[1] + j, origin[2] + lhrandom(0, 63), i * lhrandom(0.125, 0.25), j * lhrandom(0.125, 0.25), lhrandom(64, 128));
756 void R_TeleportSplash (vec3_t org)
759 if (!r_particles.value) return; // LordHavoc: particles are optional
761 for (i=-16 ; i<16 ; i+=8)
762 for (j=-16 ; j<16 ; j+=8)
763 for (k=-24 ; k<32 ; k+=8)
764 particle(pt_fade, 254, particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(64, 128), 5, 0, org[0] + i + lhrandom(0, 8), org[1] + j + lhrandom(0, 8), org[2] + k + lhrandom(0, 8), i*2 + lhrandom(-12.5, 12.5), j*2 + lhrandom(-12.5, 12.5), k*2 + lhrandom(27.5, 52.5));
767 void R_RocketTrail (vec3_t start, vec3_t end, int type, entity_t *ent)
769 vec3_t vec, dir, vel;
770 float len, dec = 0, speed;
771 int contents, bubbles, polytype;
773 if (!r_particles.value) return; // LordHavoc: particles are optional
775 VectorSubtract(end, start, dir);
776 VectorNormalize(dir);
779 if (type == 0) // rocket glow
780 particle(pt_glow, 254, particletexture, TPOLYTYPE_ADD, false, 10, 160, 999, 0, start[0] - 12 * dir[0], start[1] - 12 * dir[1], start[2] - 12 * dir[2], 0, 0, 0);
785 return; // no particles to spawn this frame (sparse trail)
790 VectorSubtract (end, start, vec);
791 len = VectorNormalizeLength (vec);
794 // advance the trail time
795 ent->trail_time = cl.time;
798 speed = len / (cl.time - cl.oldtime);
799 VectorScale(vec, speed, vel);
801 // advance into this frame to reach the first puff location
802 dec = t - cl.oldtime;
804 VectorMA(start, dec, vec, start);
806 contents = Mod_PointInLeaf(start, cl.worldmodel)->contents;
807 if (contents == CONTENTS_SKY || contents == CONTENTS_LAVA)
809 // advance the trail time
810 ent->trail_time = cl.time;
814 bubbles = (contents == CONTENTS_WATER || contents == CONTENTS_SLIME);
816 polytype = TPOLYTYPE_ALPHA;
817 if (ent->effects & EF_ADDITIVE)
818 polytype = TPOLYTYPE_ADD;
824 case 0: // rocket trail
828 particle(pt_bubble, 254, bubbleparticletexture, polytype, false, lhrandom(1, 2), 255, 2, 1.5, start[0], start[1], start[2], lhrandom(-16, 16), lhrandom(-16, 16), lhrandom(-16, 16));
833 particle(pt_smoke, 254, smokeparticletexture[rand()&7], polytype, true, 2, 160, 9999, 0, start[0], start[1], start[2], 0, 0, 16);
836 particle(pt_fallfadespark, 0x68 + (rand() & 7), particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(128, 255), 5, 0, start[0], start[1], start[2], lhrandom(-64, 64) - vel[0] * 0.25, lhrandom(-64, 64) - vel[1] * 0.25, lhrandom(-64, 64) - vel[2] * 0.25);
837 particle(pt_fallfadespark, 0x68 + (rand() & 7), particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(128, 255), 5, 0, start[0], start[1], start[2], lhrandom(-64, 64) - vel[0] * 0.25, lhrandom(-64, 64) - vel[1] * 0.25, lhrandom(-64, 64) - vel[2] * 0.25);
838 // particle(pt_fallfadespark, 0x68 + (rand() & 7), particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(128, 255), 5, 0, start[0], start[1], start[2], lhrandom(-64, 64) - vel[0] * 0.25, lhrandom(-64, 64) - vel[1] * 0.25, lhrandom(-64, 64) - vel[2] * 0.25);
839 // particle(pt_fallfadespark, 0x68 + (rand() & 7), particletexture, TPOLYTYPE_ADD, false, 1, lhrandom(128, 255), 5, 0, start[0], start[1], start[2], lhrandom(-64, 64) - vel[0] * 0.25, lhrandom(-64, 64) - vel[1] * 0.25, lhrandom(-64, 64) - vel[2] * 0.25);
844 case 1: // grenade trail
845 // FIXME: make it gradually stop smoking
849 particle(pt_bubble, 254, bubbleparticletexture, polytype, false, lhrandom(1, 2), 255, 2, 1.5, start[0], start[1], start[2], lhrandom(-16, 16), lhrandom(-16, 16), lhrandom(-16, 16));
854 particle(pt_smoke, 6, smokeparticletexture[rand()&7], polytype, true, 2, 160, 9999, 0, start[0], start[1], start[2], 0, 0, 16);
861 particle(pt_bloodsplatter, 67+(rand()&3), smokeparticletexture[rand()&7], polytype, true, lhrandom(5, 20), 255, 9999, -1, start[0], start[1], start[2], vel[0] + lhrandom(-64, 64), vel[1] + lhrandom(-64, 64), vel[2] + lhrandom(-64, 64));
864 case 4: // slight blood
866 particle(pt_bloodsplatter, 67+(rand()&3), smokeparticletexture[rand()&7], polytype, true, lhrandom(5, 20), 192, 9999, -1, start[0], start[1], start[2], vel[0] + lhrandom(-64, 64), vel[1] + lhrandom(-64, 64), vel[2] + lhrandom(-64, 64));
869 case 3: // green tracer
871 particle(pt_fade, 56, smokeparticletexture[rand()&7], polytype, false, 4, 255, 9999, 0, start[0], start[1], start[2], 0, 0, 0);
874 case 5: // flame tracer
876 particle(pt_fade, 234, smokeparticletexture[rand()&7], polytype, false, 4, 255, 9999, 0, start[0], start[1], start[2], 0, 0, 0);
879 case 6: // voor trail
880 dec = 0.05f; // sparse trail
881 particle(pt_fade, 152 + (rand()&3), smokeparticletexture[rand()&7], polytype, false, 4, 255, 9999, 0, start[0], start[1], start[2], 0, 0, 0);
884 case 7: // Nehahra smoke tracer
886 particle(pt_smoke, 12, smokeparticletexture[rand()&7], polytype, true, 10, 64, 9999, 0, start[0], start[1], start[2], 0, 0, 0);
890 // advance to next time and position
893 VectorMA (start, dec, vec, start);
898 void R_RocketTrail2 (vec3_t start, vec3_t end, int color, entity_t *ent)
902 if (!r_particles.value) return; // LordHavoc: particles are optional
904 VectorSubtract (end, start, vec);
905 len = (int) (VectorNormalizeLength (vec) * (1.0f / 3.0f));
906 VectorScale(vec, 3, vec);
909 particle(pt_smoke, color, particletexture, TPOLYTYPE_ALPHA, false, 8, 192, 99, 0, start[0], start[1], start[2], 0, 0, 0);
910 VectorAdd (start, vec, start);
920 extern cvar_t sv_gravity;
922 void R_MoveParticles (void)
925 int i, activeparticles, maxparticle, j, a;
927 float gravity, dvel, frametime;
929 // LordHavoc: early out condition
933 frametime = cl.time - cl.oldtime;
934 gravity = frametime * sv_gravity.value;
935 dvel = 1+4*frametime;
940 for (i = 0, p = particles;i < numparticles;i++, p++)
942 if (p->die < cl.time)
944 freeparticles[j++] = p;
948 VectorCopy(p->org, p->oldorg);
949 p->org[0] += p->vel[0]*frametime;
950 p->org[1] += p->vel[1]*frametime;
951 p->org[2] += p->vel[2]*frametime;
956 if (TraceLine(p->oldorg, p->org, v, normal) < 1)
958 VectorCopy(v, p->org);
963 // have to negate the direction (why?)
964 VectorNegate(normal, p->direction);
966 p->time2 = cl.time + 30;
970 dist = DotProduct(p->vel, normal) * -p->bounce;
971 VectorMAQuick(p->vel, dist, normal, p->vel);
972 if (DotProduct(p->vel, p->vel) < 0.03)
975 // hack - world is static, therefore there won't be any moving or disappearing surfaces to worry about
987 // LordHavoc: drop-through because of shared code
996 p->vel[2] -= gravity;
999 p->vel[2] -= gravity * 0.05;
1002 if (cl.time > p->time2)
1004 p->time2 = cl.time + (rand() & 3) * 0.1;
1005 p->vel[0] = (rand()&63)-32 + p->vel2[0];
1006 p->vel[1] = (rand()&63)-32 + p->vel2[1];
1007 p->vel[2] = (rand()&63)-32 + p->vel2[2];
1009 a = Mod_PointInLeaf(p->org, cl.worldmodel)->contents;
1010 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1013 if (a == CONTENTS_SOLID && Mod_PointInLeaf(p->oldorg, cl.worldmodel)->contents == CONTENTS_SOLID)
1014 break; // still in solid
1015 p->die = cl.time + 1000;
1016 p->vel[0] = p->vel[1] = p->vel[2] = 0;
1020 case CONTENTS_SLIME:
1021 p->tex = smokeparticletexture[rand()&7];
1022 p->type = pt_smokecloud;
1027 case CONTENTS_WATER:
1028 p->tex = smokeparticletexture[rand()&7];
1029 p->type = pt_splash;
1034 default: // CONTENTS_SOLID and any others
1035 TraceLine(p->oldorg, p->org, v, normal);
1036 VectorCopy(v, p->org);
1037 p->tex = smokeparticletexture[rand()&7];
1039 VectorClear(p->vel);
1045 // if (Mod_PointInLeaf(p->org, cl.worldmodel)->contents != CONTENTS_EMPTY)
1050 p->scale += frametime * 16;
1051 p->alpha -= frametime * 256;
1052 p->vel[2] -= gravity * 0.25;
1057 // if (Mod_PointInLeaf(p->org, cl.worldmodel)->contents != CONTENTS_EMPTY)
1062 p->scale += frametime * 16;
1063 p->alpha -= frametime * 512;
1064 p->vel[2] -= gravity * 0.25;
1068 case pt_bloodsplatter:
1069 // if (Mod_PointInLeaf(p->org, cl.worldmodel)->contents != CONTENTS_EMPTY)
1074 p->alpha -= frametime * 128;
1077 p->vel[2] -= gravity * 0.5;
1079 case pt_fallfadespark:
1080 p->alpha -= frametime * 256;
1081 p->vel[2] -= gravity;
1086 p->alpha -= frametime * 512;
1091 a = Mod_PointInLeaf(p->org, cl.worldmodel)->contents;
1092 if (a != CONTENTS_WATER && a != CONTENTS_SLIME)
1094 p->tex = smokeparticletexture[rand()&7];
1095 p->type = pt_splash;
1098 p->vel[0] = p->vel[1] = p->vel[2] = 0;
1099 p->die = cl.time + 1000;
1102 p->vel[2] += gravity * 0.25;
1103 p->vel[0] *= (1 - (frametime * 0.0625));
1104 p->vel[1] *= (1 - (frametime * 0.0625));
1105 p->vel[2] *= (1 - (frametime * 0.0625));
1106 if (cl.time > p->time2)
1108 p->time2 = cl.time + lhrandom(0, 0.5);
1109 p->vel[0] += lhrandom(-32,32);
1110 p->vel[1] += lhrandom(-32,32);
1111 p->vel[2] += lhrandom(-32,32);
1113 p->alpha -= frametime * 64;
1119 // LordHavoc: for smoke trails
1120 case pt_bulletsmoke:
1121 p->scale += frametime * 60;
1122 p->alpha -= frametime * 512;
1123 p->vel[2] += gravity * 0.05;
1128 p->scale += frametime * 20;
1129 p->alpha -= frametime * 256;
1130 p->vel[2] += gravity * 0.05;
1135 p->scale += frametime * 64;
1136 p->alpha -= frametime * 256;
1137 p->vel[2] += gravity * 0.05;
1142 p->scale += frametime * 24;
1143 p->alpha -= frametime * 512;
1148 a = Mod_PointInLeaf(p->org, cl.worldmodel)->contents;
1149 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1152 if (a == CONTENTS_SOLID && Mod_PointInLeaf(p->oldorg, cl.worldmodel)->contents == CONTENTS_SOLID)
1153 break; // still in solid
1154 p->die = cl.time + 1000;
1155 p->vel[0] = p->vel[1] = p->vel[2] = 0;
1159 case CONTENTS_SLIME:
1160 p->tex = smokeparticletexture[rand()&7];
1161 p->type = pt_smokecloud;
1166 case CONTENTS_WATER:
1167 p->tex = smokeparticletexture[rand()&7];
1168 p->type = pt_splash;
1172 default: // CONTENTS_SOLID and any others
1173 TraceLine(p->oldorg, p->org, v, normal);
1174 VectorCopy(v, p->org);
1175 p->tex = smokeparticletexture[rand()&7];
1176 p->type = pt_splash;
1179 particle(pt_fallfadespark, 245, particletexture, TPOLYTYPE_ADD, false, 1, 64, 1, 1.3, p->org[0], p->org[1], p->org[2] + 1, lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(-32, 32) + 48);
1180 particle(pt_fallfadespark, 245, particletexture, TPOLYTYPE_ADD, false, 1, 128, 1, 1.3, p->org[0], p->org[1], p->org[2] + 1, lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(-32, 32) + 48);
1181 particle(pt_fallfadespark, 245, particletexture, TPOLYTYPE_ADD, false, 1, 192, 1, 1.3, p->org[0], p->org[1], p->org[2] + 1, lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(-32, 32) + 48);
1182 particle(pt_fallfadespark, 245, particletexture, TPOLYTYPE_ADD, false, 1, 255, 1, 1.3, p->org[0], p->org[1], p->org[2] + 1, lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(-32, 32) + 48);
1188 p->alpha -= frametime * 512;
1189 p->vel[2] += gravity * 0.2;
1194 case pt_flamingdebris:
1195 if (cl.time >= p->time2)
1197 p->time2 = cl.time + 0.01;
1198 particle2(pt_flame, p->color, particletexture, TPOLYTYPE_ADD, false, 4, p->alpha, 999, 0, p->org, 0, 50);
1200 p->alpha -= frametime * 512;
1201 p->vel[2] -= gravity * 0.5f;
1202 if (Mod_PointInLeaf(p->org, cl.worldmodel)->contents != CONTENTS_EMPTY)
1207 case pt_smokingdebris:
1208 if (cl.time >= p->time2)
1210 p->time2 = cl.time + 0.01;
1211 particle2(pt_flame, 15, smokeparticletexture[rand()&7], TPOLYTYPE_ALPHA, false, 4, p->alpha, 999, 0, p->org, 0, 50);
1213 p->alpha -= frametime * 512;
1214 p->vel[2] -= gravity * 0.5f;
1215 if (Mod_PointInLeaf(p->org, cl.worldmodel)->contents != CONTENTS_EMPTY)
1221 p->alpha -= frametime * 512;
1222 p->vel[2] -= gravity * 0.5f;
1233 if (cl.time > p->time2)
1235 p->alpha -= frametime * 256;
1243 printf("unknown particle type %i\n", p->type);
1248 // LordHavoc: immediate removal of unnecessary particles (must be done to ensure compactor below operates properly in all cases)
1249 if (p->die < cl.time)
1250 freeparticles[j++] = p;
1257 // fill in gaps to compact the array
1259 while (maxparticle >= activeparticles)
1261 *freeparticles[i++] = particles[maxparticle--];
1262 while (maxparticle >= activeparticles && particles[maxparticle].die < cl.time)
1265 numparticles = activeparticles;
1268 void R_DrawParticles (void)
1271 int i, r,g,b,a, dynlight;
1272 float scale, scale2, minparticledist;
1274 vec3_t up, right, uprightangles, up2, right2, tempcolor, corner, decalright, decalup, v;
1276 // LordHavoc: early out condition
1277 if ((!numparticles) || (!r_drawparticles.value))
1280 dynlight = r_dynamicparticles.value;
1281 if (!r_dynamic.value)
1283 c_particles += numparticles;
1285 VectorScale (vup, 1.5, up);
1286 VectorScale (vright, 1.5, right);
1288 uprightangles[0] = 0;
1289 uprightangles[1] = r_refdef.viewangles[1];
1290 uprightangles[2] = 0;
1291 AngleVectors (uprightangles, NULL, right2, up2);
1293 minparticledist = DotProduct(r_refdef.vieworg, vpn) + 16.0f;
1295 for (i = 0, p = particles;i < numparticles;i++, p++)
1297 // LordHavoc: unnecessary (array was already compacted)
1298 // if (p->die < cl.time)
1301 if (p->type == pt_decal)
1303 VectorSubtract(p->org, r_refdef.vieworg, v);
1304 if (DotProduct(p->direction, v) < 0)
1308 // LordHavoc: only render if not too close
1309 if (DotProduct(p->org, vpn) < minparticledist)
1312 color24 = (byte *) &d_8to24table[(int)p->color];
1317 if (dynlight && (p->dynlight || dynlight >= 2)) // LordHavoc: only light blood and smoke
1319 R_CompleteLightPoint(tempcolor, p->org);
1320 r = (r * (int) tempcolor[0]) >> 7;
1321 g = (g * (int) tempcolor[1]) >> 7;
1322 b = (b * (int) tempcolor[2]) >> 7;
1324 transpolybegin(R_GetTexture(p->tex), 0, R_GetTexture(p->tex), p->rendermode);
1325 scale = p->scale * -0.5;scale2 = p->scale;
1326 if (p->type == pt_decal)
1328 VectorVectors(p->direction, decalright, decalup);
1329 corner[0] = p->org[0] + decalup[0]*scale + decalright[0]*scale;
1330 corner[1] = p->org[1] + decalup[1]*scale + decalright[1]*scale;
1331 corner[2] = p->org[2] + decalup[2]*scale + decalright[2]*scale;
1332 transpolyvert(corner[0] , corner[1] , corner[2] , 0,1,r,g,b,a);
1333 transpolyvert(corner[0] + decalup[0]*scale2 , corner[1] + decalup[1]*scale2 , corner[2] + decalup[2]*scale2 , 0,0,r,g,b,a);
1334 transpolyvert(corner[0] + decalup[0]*scale2 + decalright[0]*scale2, corner[1] + decalup[1]*scale2 + decalright[1]*scale2, corner[2] + decalup[2]*scale2 + decalright[2]*scale2, 1,0,r,g,b,a);
1335 transpolyvert(corner[0] + decalright[0]*scale2, corner[1] + decalright[1]*scale2, corner[2] + decalright[2]*scale2, 1,1,r,g,b,a);
1337 else if (p->tex == rainparticletexture) // rain streak
1339 corner[0] = p->org[0] + up2[0]*scale + right2[0]*scale;
1340 corner[1] = p->org[1] + up2[1]*scale + right2[1]*scale;
1341 corner[2] = p->org[2] + up2[2]*scale + right2[2]*scale;
1342 transpolyvert(corner[0] , corner[1] , corner[2] , 0,1,r,g,b,a);
1343 transpolyvert(corner[0] + up2[0]*scale2 , corner[1] + up2[1]*scale2 , corner[2] + up2[2]*scale2 , 0,0,r,g,b,a);
1344 transpolyvert(corner[0] + up2[0]*scale2 + right2[0]*scale2, corner[1] + up2[1]*scale2 + right2[1]*scale2, corner[2] + up2[2]*scale2 + right2[2]*scale2, 1,0,r,g,b,a);
1345 transpolyvert(corner[0] + right2[0]*scale2, corner[1] + right2[1]*scale2, corner[2] + right2[2]*scale2, 1,1,r,g,b,a);
1349 corner[0] = p->org[0] + up[0]*scale + right[0]*scale;
1350 corner[1] = p->org[1] + up[1]*scale + right[1]*scale;
1351 corner[2] = p->org[2] + up[2]*scale + right[2]*scale;
1352 transpolyvert(corner[0] , corner[1] , corner[2] , 0,1,r,g,b,a);
1353 transpolyvert(corner[0] + up[0]*scale2 , corner[1] + up[1]*scale2 , corner[2] + up[2]*scale2 , 0,0,r,g,b,a);
1354 transpolyvert(corner[0] + up[0]*scale2 + right[0]*scale2, corner[1] + up[1]*scale2 + right[1]*scale2, corner[2] + up[2]*scale2 + right[2]*scale2, 1,0,r,g,b,a);
1355 transpolyvert(corner[0] + right[0]*scale2, corner[1] + right[1]*scale2, corner[2] + right[2]*scale2, 1,1,r,g,b,a);