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 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {0, 0, false}, // pt_dead
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // range of colors to choose from in hex RRGGBB (like HTML color tags),
70 // randomly interpolated at spawn
71 unsigned int color[2];
72 // a random texture is chosen in this range (note the second value is one
73 // past the last choosable, so for example 8,16 chooses any from 8 up and
75 // if start and end of the range are the same, no randomization is done
77 // range of size values randomly chosen when spawning, plus size increase over time
79 // range of alpha values randomly chosen when spawning, plus alpha fade
81 // how long the particle should live (note it is also removed if alpha drops to 0)
83 // how much gravity affects this particle (negative makes it fly up!)
85 // how much bounce the particle has when it hits a surface
86 // if negative the particle is removed on impact
88 // if in air this friction is applied
89 // if negative the particle accelerates
91 // if in liquid (water/slime/lava) this friction is applied
92 // if negative the particle accelerates
94 // these offsets are added to the values given to particleeffect(), and
95 // then an ellipsoid-shaped jitter is added as defined by these
96 // (they are the 3 radii)
97 float originoffset[3];
98 float velocityoffset[3];
99 float originjitter[3];
100 float velocityjitter[3];
101 float velocitymultiplier;
102 // an effect can also spawn a dlight
103 float lightradiusstart;
104 float lightradiusfade;
107 qboolean lightshadow;
110 particleeffectinfo_t;
112 #define MAX_PARTICLEEFFECTNAME 256
113 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
115 #define MAX_PARTICLEEFFECTINFO 4096
117 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
119 static int particlepalette[256] =
121 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
122 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
123 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
124 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
125 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
126 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
127 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
128 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
129 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
130 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
131 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
132 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
133 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
134 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
135 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
136 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
137 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
138 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
139 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
140 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
141 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
142 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
143 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
144 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
145 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
146 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
147 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
148 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
149 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
150 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
151 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
152 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
155 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
156 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
157 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
159 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
161 // texture numbers in particle font
162 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
163 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
164 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
165 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
166 static const int tex_rainsplash = 32;
167 static const int tex_particle = 63;
168 static const int tex_bubble = 62;
169 static const int tex_raindrop = 61;
170 static const int tex_beam = 60;
172 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
173 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
174 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
175 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
176 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
177 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
178 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
179 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
180 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
181 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
182 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
183 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
184 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
185 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
186 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
187 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
188 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
189 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
190 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
191 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
192 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
195 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
201 particleeffectinfo_t *info = NULL;
202 const char *text = textstart;
204 effectinfoindex = -1;
205 for (linenumber = 1;;linenumber++)
208 for (arrayindex = 0;arrayindex < 16;arrayindex++)
209 argv[arrayindex][0] = 0;
212 if (!COM_ParseToken_Simple(&text, true, false))
214 if (!strcmp(com_token, "\n"))
218 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
224 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
225 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
226 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
227 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
228 #define readfloat(var) checkparms(2);var = atof(argv[1])
229 if (!strcmp(argv[0], "effect"))
234 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
236 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
239 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
241 if (particleeffectname[effectnameindex][0])
243 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
248 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
252 // if we run out of names, abort
253 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
255 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
258 info = particleeffectinfo + effectinfoindex;
259 info->effectnameindex = effectnameindex;
260 info->particletype = pt_alphastatic;
261 info->tex[0] = tex_particle;
262 info->tex[1] = tex_particle;
263 info->color[0] = 0xFFFFFF;
264 info->color[1] = 0xFFFFFF;
268 info->alpha[1] = 256;
269 info->alpha[2] = 256;
270 info->time[0] = 9999;
271 info->time[1] = 9999;
272 VectorSet(info->lightcolor, 1, 1, 1);
273 info->lightshadow = true;
274 info->lighttime = 9999;
276 else if (info == NULL)
278 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
281 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
282 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
283 else if (!strcmp(argv[0], "type"))
286 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
287 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
288 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
289 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
290 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
291 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
292 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
293 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
294 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
295 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
296 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
297 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
298 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
300 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
301 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
302 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
303 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
304 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
305 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
306 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
307 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
308 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
309 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
310 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
311 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
312 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
313 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
314 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
315 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
316 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
317 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
318 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
319 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
320 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
321 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
322 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
323 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
325 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
334 int CL_ParticleEffectIndexForName(const char *name)
337 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
338 if (!strcmp(particleeffectname[i], name))
343 const char *CL_ParticleEffectNameForIndex(int i)
345 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
347 return particleeffectname[i];
350 // MUST match effectnameindex_t in client.h
351 static const char *standardeffectnames[EFFECT_TOTAL] =
375 "TE_TEI_BIGEXPLOSION",
391 void CL_Particles_LoadEffectInfo(void)
394 unsigned char *filedata;
395 fs_offset_t filesize;
396 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
397 memset(particleeffectname, 0, sizeof(particleeffectname));
398 for (i = 0;i < EFFECT_TOTAL;i++)
399 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
400 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
403 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
413 void CL_ReadPointFile_f (void);
414 void CL_Particles_Init (void)
416 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
417 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
419 Cvar_RegisterVariable (&cl_particles);
420 Cvar_RegisterVariable (&cl_particles_quality);
421 Cvar_RegisterVariable (&cl_particles_alpha);
422 Cvar_RegisterVariable (&cl_particles_size);
423 Cvar_RegisterVariable (&cl_particles_quake);
424 Cvar_RegisterVariable (&cl_particles_blood);
425 Cvar_RegisterVariable (&cl_particles_blood_alpha);
426 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
427 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
428 Cvar_RegisterVariable (&cl_particles_explosions_shell);
429 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
430 Cvar_RegisterVariable (&cl_particles_rain);
431 Cvar_RegisterVariable (&cl_particles_snow);
432 Cvar_RegisterVariable (&cl_particles_smoke);
433 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
434 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
435 Cvar_RegisterVariable (&cl_particles_sparks);
436 Cvar_RegisterVariable (&cl_particles_bubbles);
437 Cvar_RegisterVariable (&cl_decals);
438 Cvar_RegisterVariable (&cl_decals_time);
439 Cvar_RegisterVariable (&cl_decals_fadetime);
442 void CL_Particles_Shutdown (void)
446 // list of all 26 parameters:
447 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
448 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
449 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
450 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
451 // palpha - opacity of particle as 0-255 (can be more than 255)
452 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
453 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
454 // pgravity - how much effect gravity has on the particle (0-1)
455 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
456 // px,py,pz - starting origin of particle
457 // pvx,pvy,pvz - starting velocity of particle
458 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
459 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
464 if (!cl_particles.integer)
466 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
467 if (cl.free_particle >= cl.max_particles)
469 part = &cl.particles[cl.free_particle++];
470 if (cl.num_particles < cl.free_particle)
471 cl.num_particles = cl.free_particle;
472 memset(part, 0, sizeof(*part));
473 part->typeindex = ptypeindex;
474 l2 = (int)lhrandom(0.5, 256.5);
476 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
477 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
478 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
479 part->color[3] = 0xFF;
482 part->sizeincrease = psizeincrease;
483 part->alpha = palpha;
484 part->alphafade = palphafade;
485 part->gravity = pgravity;
486 part->bounce = pbounce;
488 part->org[0] = px + originjitter * v[0];
489 part->org[1] = py + originjitter * v[1];
490 part->org[2] = pz + originjitter * v[2];
491 part->vel[0] = pvx + velocityjitter * v[0];
492 part->vel[1] = pvy + velocityjitter * v[1];
493 part->vel[2] = pvz + velocityjitter * v[2];
495 part->airfriction = pairfriction;
496 part->liquidfriction = pliquidfriction;
497 part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
498 part->delayedcollisions = 0;
499 if (part->typeindex == pt_blood)
500 part->gravity += 1; // FIXME: this is a legacy hack, effectinfo.txt doesn't have gravity on blood (nor do the particle calls in the engine)
501 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
502 if (part->typeindex == pt_rain)
506 float lifetime = part->die - cl.time;
509 // turn raindrop into simple spark and create delayedspawn splash effect
510 part->typeindex = pt_spark;
512 VectorMA(part->org, lifetime, part->vel, endvec);
513 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
514 part->die = cl.time + lifetime * trace.fraction;
515 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0);
518 part2->delayedspawn = part->die;
519 part2->die += part->die - cl.time;
520 for (i = rand() & 7;i < 10;i++)
522 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32);
525 part2->delayedspawn = part->die;
526 part2->die += part->die - cl.time;
531 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
533 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
536 VectorMA(part->org, lifetime, part->vel, endvec);
537 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
538 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
543 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
547 if (!cl_decals.integer)
549 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
550 if (cl.free_decal >= cl.max_decals)
552 decal = &cl.decals[cl.free_decal++];
553 if (cl.num_decals < cl.free_decal)
554 cl.num_decals = cl.free_decal;
555 memset(decal, 0, sizeof(*decal));
556 decal->typeindex = pt_decal;
557 decal->texnum = texnum;
558 VectorAdd(org, normal, decal->org);
559 VectorCopy(normal, decal->normal);
561 decal->alpha = alpha;
562 decal->time2 = cl.time;
563 l2 = (int)lhrandom(0.5, 256.5);
565 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
566 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
567 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
568 decal->color[3] = 0xFF;
569 decal->owner = hitent;
572 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
573 decal->ownermodel = cl.entities[decal->owner].render.model;
574 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
575 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
579 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
582 float bestfrac, bestorg[3], bestnormal[3];
584 int besthitent = 0, hitent;
587 for (i = 0;i < 32;i++)
590 VectorMA(org, maxdist, org2, org2);
591 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
592 // take the closest trace result that doesn't end up hitting a NOMARKS
593 // surface (sky for example)
594 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
596 bestfrac = trace.fraction;
598 VectorCopy(trace.endpos, bestorg);
599 VectorCopy(trace.plane.normal, bestnormal);
603 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
606 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
607 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
608 void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
611 matrix4x4_t tempmatrix;
612 VectorLerp(originmins, 0.5, originmaxs, center);
613 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
614 if (effectnameindex == EFFECT_SVC_PARTICLE)
616 if (cl_particles.integer)
618 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
620 CL_ParticleExplosion(center);
621 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
622 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
625 count *= cl_particles_quality.value;
626 for (;count > 0;count--)
628 int k = particlepalette[palettecolor + (rand()&7)];
629 if (cl_particles_quake.integer)
630 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
631 else if (gamemode == GAME_GOODVSBAD2)
632 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
634 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
639 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
640 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
641 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
642 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
643 else if (effectnameindex == EFFECT_TE_SPIKE)
645 if (cl_particles_bulletimpacts.integer)
647 if (cl_particles_quake.integer)
649 if (cl_particles_smoke.integer)
650 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
654 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
655 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
659 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
660 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
664 if (cl_particles_bulletimpacts.integer)
666 if (cl_particles_quake.integer)
668 if (cl_particles_smoke.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
673 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
674 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
678 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
679 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
680 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
682 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
684 if (cl_particles_bulletimpacts.integer)
686 if (cl_particles_quake.integer)
688 if (cl_particles_smoke.integer)
689 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
693 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
694 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
698 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
699 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
701 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
703 if (cl_particles_bulletimpacts.integer)
705 if (cl_particles_quake.integer)
707 if (cl_particles_smoke.integer)
708 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
712 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
713 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
717 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
718 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
719 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
721 else if (effectnameindex == EFFECT_TE_BLOOD)
723 if (!cl_particles_blood.integer)
725 if (cl_particles_quake.integer)
726 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
729 static double bloodaccumulator = 0;
730 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
731 for (;bloodaccumulator > 0;bloodaccumulator--)
732 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
735 else if (effectnameindex == EFFECT_TE_SPARK)
736 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
737 else if (effectnameindex == EFFECT_TE_PLASMABURN)
739 // plasma scorch mark
740 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
741 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
742 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
744 else if (effectnameindex == EFFECT_TE_GUNSHOT)
746 if (cl_particles_bulletimpacts.integer)
748 if (cl_particles_quake.integer)
749 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
752 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
753 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
757 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
758 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
760 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
762 if (cl_particles_bulletimpacts.integer)
764 if (cl_particles_quake.integer)
765 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
768 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
769 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
773 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
774 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
775 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
777 else if (effectnameindex == EFFECT_TE_EXPLOSION)
779 CL_ParticleExplosion(center);
780 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
782 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
784 CL_ParticleExplosion(center);
785 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
787 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
789 if (cl_particles_quake.integer)
792 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
795 CL_NewParticle(pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
797 CL_NewParticle(pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
801 CL_ParticleExplosion(center);
802 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
804 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
805 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
806 else if (effectnameindex == EFFECT_TE_FLAMEJET)
808 count *= cl_particles_quality.value;
810 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
812 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
814 float i, j, inc, vel;
817 inc = 8 / cl_particles_quality.value;
818 for (i = -128;i < 128;i += inc)
820 for (j = -128;j < 128;j += inc)
822 dir[0] = j + lhrandom(0, inc);
823 dir[1] = i + lhrandom(0, inc);
825 org[0] = center[0] + dir[0];
826 org[1] = center[1] + dir[1];
827 org[2] = center[2] + lhrandom(0, 64);
828 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
829 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
833 else if (effectnameindex == EFFECT_TE_TELEPORT)
835 float i, j, k, inc, vel;
838 inc = 8 / cl_particles_quality.value;
839 for (i = -16;i < 16;i += inc)
841 for (j = -16;j < 16;j += inc)
843 for (k = -24;k < 32;k += inc)
845 VectorSet(dir, i*8, j*8, k*8);
846 VectorNormalize(dir);
847 vel = lhrandom(50, 113);
848 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
852 CL_NewParticle(pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
853 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
855 else if (effectnameindex == EFFECT_TE_TEI_G3)
856 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
857 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
859 if (cl_particles_smoke.integer)
861 count *= 0.25f * cl_particles_quality.value;
863 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
866 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
868 CL_ParticleExplosion(center);
869 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
871 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
874 if (cl_stainmaps.integer)
875 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
876 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
877 if (cl_particles_smoke.integer)
878 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
879 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
880 if (cl_particles_sparks.integer)
881 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
882 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
883 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
885 else if (effectnameindex == EFFECT_EF_FLAME)
887 count *= 300 * cl_particles_quality.value;
889 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
890 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
892 else if (effectnameindex == EFFECT_EF_STARDUST)
894 count *= 200 * cl_particles_quality.value;
896 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
897 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
899 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
903 int smoke, blood, bubbles, r, color;
905 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
908 Vector4Set(light, 0, 0, 0, 0);
910 if (effectnameindex == EFFECT_TR_ROCKET)
911 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
912 else if (effectnameindex == EFFECT_TR_VORESPIKE)
914 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
915 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
917 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
919 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
920 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
924 matrix4x4_t tempmatrix;
925 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
926 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
933 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
936 VectorSubtract(originmaxs, originmins, dir);
937 len = VectorNormalizeLength(dir);
940 dec = -ent->persistent.trail_time;
941 ent->persistent.trail_time += len;
942 if (ent->persistent.trail_time < 0.01f)
945 // if we skip out, leave it reset
946 ent->persistent.trail_time = 0.0f;
951 // advance into this frame to reach the first puff location
952 VectorMA(originmins, dec, dir, pos);
955 smoke = cl_particles.integer && cl_particles_smoke.integer;
956 blood = cl_particles.integer && cl_particles_blood.integer;
957 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
958 qd = 1.0f / cl_particles_quality.value;
965 if (effectnameindex == EFFECT_TR_BLOOD)
967 if (cl_particles_quake.integer)
969 color = particlepalette[67 + (rand()&3)];
970 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
975 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
978 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
980 if (cl_particles_quake.integer)
983 color = particlepalette[67 + (rand()&3)];
984 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
989 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
995 if (effectnameindex == EFFECT_TR_ROCKET)
997 if (cl_particles_quake.integer)
1000 color = particlepalette[ramp3[r]];
1001 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1005 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1006 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
1009 else if (effectnameindex == EFFECT_TR_GRENADE)
1011 if (cl_particles_quake.integer)
1014 color = particlepalette[ramp3[r]];
1015 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1019 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1022 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1024 if (cl_particles_quake.integer)
1027 color = particlepalette[52 + (rand()&7)];
1028 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1029 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1031 else if (gamemode == GAME_GOODVSBAD2)
1034 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1038 color = particlepalette[20 + (rand()&7)];
1039 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1042 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1044 if (cl_particles_quake.integer)
1047 color = particlepalette[230 + (rand()&7)];
1048 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1049 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1053 color = particlepalette[226 + (rand()&7)];
1054 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1057 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1059 if (cl_particles_quake.integer)
1061 color = particlepalette[152 + (rand()&3)];
1062 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
1064 else if (gamemode == GAME_GOODVSBAD2)
1067 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1069 else if (gamemode == GAME_PRYDON)
1072 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1075 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1077 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1080 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
1082 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1085 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
1087 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1088 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1092 if (effectnameindex == EFFECT_TR_ROCKET)
1093 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1094 else if (effectnameindex == EFFECT_TR_GRENADE)
1095 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1097 // advance to next time and position
1100 VectorMA (pos, dec, dir, pos);
1103 ent->persistent.trail_time = len;
1105 else if (developer.integer >= 1)
1106 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1109 // this is also called on point effects with spawndlight = true and
1110 // spawnparticles = true
1111 // it is called CL_ParticleTrail because most code does not want to supply
1112 // these parameters, only trail handling does
1113 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
1116 qboolean found = false;
1117 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1118 return; // invalid effect index
1119 if (!particleeffectname[effectnameindex][0])
1120 return; // no such effect
1121 VectorLerp(originmins, 0.5, originmaxs, center);
1122 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1124 int effectinfoindex;
1127 particleeffectinfo_t *info;
1129 vec3_t centervelocity;
1135 qboolean underwater;
1136 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1137 VectorLerp(originmins, 0.5, originmaxs, center);
1138 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1139 supercontents = CL_PointSuperContents(center);
1140 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1141 VectorSubtract(originmaxs, originmins, traildir);
1142 traillen = VectorLength(traildir);
1143 VectorNormalize(traildir);
1144 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1146 if (info->effectnameindex == effectnameindex)
1149 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1151 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1154 // spawn a dlight if requested
1155 if (info->lightradiusstart > 0 && spawndlight)
1157 matrix4x4_t tempmatrix;
1158 if (info->trailspacing > 0)
1159 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1161 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1162 if (info->lighttime > 0 && info->lightradiusfade > 0)
1164 // light flash (explosion, etc)
1165 // called when effect starts
1166 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1171 // called by CL_LinkNetworkEntity
1172 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1173 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1177 if (!spawnparticles)
1182 if (info->tex[1] > info->tex[0])
1184 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1185 tex = min(tex, info->tex[1] - 1);
1187 if (info->particletype == pt_decal)
1188 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1189 else if (info->particletype == pt_beam)
1190 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
1193 if (!cl_particles.integer)
1195 switch (info->particletype)
1197 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1198 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1199 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1200 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1201 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1202 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1205 VectorCopy(originmins, trailpos);
1206 if (info->trailspacing > 0)
1208 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1209 trailstep = info->trailspacing / cl_particles_quality.value;
1213 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1216 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1217 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1219 if (info->tex[1] > info->tex[0])
1221 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1222 tex = min(tex, info->tex[1] - 1);
1226 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1227 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1228 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1231 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
1233 VectorMA(trailpos, trailstep, traildir, trailpos);
1240 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1243 void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor)
1245 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1253 void CL_EntityParticles (const entity_t *ent)
1256 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1257 static vec3_t avelocities[NUMVERTEXNORMALS];
1258 if (!cl_particles.integer) return;
1260 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1262 if (!avelocities[0][0])
1263 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1264 avelocities[0][i] = lhrandom(0, 2.55);
1266 for (i = 0;i < NUMVERTEXNORMALS;i++)
1268 yaw = cl.time * avelocities[i][0];
1269 pitch = cl.time * avelocities[i][1];
1270 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1271 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1272 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1273 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
1278 void CL_ReadPointFile_f (void)
1280 vec3_t org, leakorg;
1282 char *pointfile = NULL, *pointfilepos, *t, tchar;
1283 char name[MAX_OSPATH];
1288 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1289 strlcat (name, ".pts", sizeof (name));
1290 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1293 Con_Printf("Could not open %s\n", name);
1297 Con_Printf("Reading %s...\n", name);
1298 VectorClear(leakorg);
1301 pointfilepos = pointfile;
1302 while (*pointfilepos)
1304 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1309 while (*t && *t != '\n' && *t != '\r')
1313 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1319 VectorCopy(org, leakorg);
1322 if (cl.num_particles < cl.max_particles - 3)
1325 CL_NewParticle(pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
1328 Mem_Free(pointfile);
1329 VectorCopy(leakorg, org);
1330 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1332 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0);
1333 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0);
1334 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0);
1339 CL_ParseParticleEffect
1341 Parse an effect out of the server message
1344 void CL_ParseParticleEffect (void)
1347 int i, count, msgcount, color;
1349 MSG_ReadVector(org, cls.protocol);
1350 for (i=0 ; i<3 ; i++)
1351 dir[i] = MSG_ReadChar ();
1352 msgcount = MSG_ReadByte ();
1353 color = MSG_ReadByte ();
1355 if (msgcount == 255)
1360 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1365 CL_ParticleExplosion
1369 void CL_ParticleExplosion (const vec3_t org)
1375 if (cl_stainmaps.integer)
1376 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1377 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1379 if (cl_particles_quake.integer)
1381 for (i = 0;i < 1024;i++)
1387 color = particlepalette[ramp1[r]];
1388 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1392 color = particlepalette[ramp2[r]];
1393 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1399 i = CL_PointSuperContents(org);
1400 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1402 if (cl_particles.integer && cl_particles_bubbles.integer)
1403 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1404 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
1408 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1410 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1414 for (k = 0;k < 16;k++)
1417 VectorMA(org, 128, v2, v);
1418 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1419 if (trace.fraction >= 0.1)
1422 VectorSubtract(trace.endpos, org, v2);
1423 VectorScale(v2, 2.0f, v2);
1424 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1430 if (cl_particles_explosions_shell.integer)
1431 R_NewExplosion(org);
1436 CL_ParticleExplosion2
1440 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1443 if (!cl_particles.integer) return;
1445 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1447 k = particlepalette[colorStart + (i % colorLength)];
1448 if (cl_particles_quake.integer)
1449 CL_NewParticle(pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
1451 CL_NewParticle(pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
1455 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1457 if (cl_particles_sparks.integer)
1459 sparkcount *= cl_particles_quality.value;
1460 while(sparkcount-- > 0)
1461 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64);
1465 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1467 if (cl_particles_smoke.integer)
1469 smokecount *= cl_particles_quality.value;
1470 while(smokecount-- > 0)
1471 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0);
1475 void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel)
1478 if (!cl_particles.integer) return;
1480 count = (int)(count * cl_particles_quality.value);
1483 k = particlepalette[colorbase + (rand()&3)];
1484 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
1488 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1491 float z, minz, maxz;
1492 if (!cl_particles.integer) return;
1493 if (dir[2] < 0) // falling
1498 minz = z - fabs(dir[2]) * 0.1;
1499 maxz = z + fabs(dir[2]) * 0.1;
1500 minz = bound(mins[2], minz, maxs[2]);
1501 maxz = bound(mins[2], maxz, maxs[2]);
1503 count = (int)(count * cl_particles_quality.value);
1508 if (!cl_particles_rain.integer) break;
1509 count *= 4; // ick, this should be in the mod or maps?
1513 k = particlepalette[colorbase + (rand()&3)];
1514 if (gamemode == GAME_GOODVSBAD2)
1515 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1517 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1521 if (!cl_particles_snow.integer) break;
1524 k = particlepalette[colorbase + (rand()&3)];
1525 if (gamemode == GAME_GOODVSBAD2)
1526 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1528 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1532 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1541 void CL_MoveDecals (void)
1547 // LordHavoc: early out condition
1554 decalfade = bound(0, cl.time - cl.oldtime, 0.1) * 255 / cl_decals_fadetime.value;
1556 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
1558 if (!decal->typeindex)
1561 // heavily optimized decal case
1562 // FIXME: this has fairly wacky handling of alpha
1563 if (cl.time > decal->time2 + cl_decals_time.value)
1565 decal->alpha -= decalfade;
1566 if (decal->alpha <= 0)
1568 decal->typeindex = 0;
1569 if (cl.free_decal > i)
1577 if (cl.entities[decal->owner].render.model == decal->ownermodel)
1579 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
1580 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
1584 decal->typeindex = 0;
1585 if (cl.free_decal > i)
1591 // reduce cl.num_decals if possible
1592 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
1601 void CL_MoveParticles (void)
1604 int i, j, a, content;
1605 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
1609 // LordHavoc: early out condition
1610 if (!cl.num_particles)
1612 cl.free_particle = 0;
1616 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1617 gravity = frametime * cl.movevars_gravity;
1618 dvel = 1+4*frametime;
1619 decalfade = frametime * 255 / cl_decals_fadetime.value;
1622 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1626 if (cl.free_particle > i)
1627 cl.free_particle = i;
1631 if (p->delayedspawn)
1633 if (p->delayedspawn > cl.time)
1635 p->delayedspawn = 0;
1640 p->size += p->sizeincrease * frametime;
1641 p->alpha -= p->alphafade * frametime;
1643 if (p->alpha <= 0 || p->die <= cl.time)
1646 if (cl.free_particle > i)
1647 cl.free_particle = i;
1651 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
1653 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
1655 if (p->typeindex == pt_blood)
1656 p->size += frametime * 8;
1658 p->vel[2] -= p->gravity * gravity;
1659 f = 1.0f - min(p->liquidfriction * frametime, 1);
1660 VectorScale(p->vel, f, p->vel);
1664 p->vel[2] -= p->gravity * gravity;
1667 f = 1.0f - min(p->airfriction * frametime, 1);
1668 VectorScale(p->vel, f, p->vel);
1672 VectorCopy(p->org, oldorg);
1673 VectorMA(p->org, frametime, p->vel, p->org);
1674 if (p->bounce && cl.time >= p->delayedcollisions)
1676 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
1677 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1678 // or if the trace hit something flagged as NOIMPACT
1679 // then remove the particle
1680 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
1683 if (cl.free_particle > i)
1684 cl.free_particle = i;
1687 VectorCopy(trace.endpos, p->org);
1688 // react if the particle hit something
1689 if (trace.fraction < 1)
1691 VectorCopy(trace.endpos, p->org);
1692 if (p->typeindex == pt_rain)
1694 // raindrop - splash on solid/water/slime/lava
1696 // convert from a raindrop particle to a rainsplash decal
1697 VectorCopy(trace.plane.normal, p->vel);
1698 VectorAdd(p->org, p->vel, p->org);
1699 p->typeindex = pt_raindecal;
1700 p->texnum = tex_rainsplash;
1702 p->alphafade = p->alpha / 0.4;
1705 p->liquidfriction = 0;
1708 p->sizeincrease = p->size * 20;
1709 count = (int)lhrandom(1, 10);
1711 CL_NewParticle(pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
1714 else if (p->typeindex == pt_blood)
1716 // blood - splash on solid
1717 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1722 if (cl_stainmaps.integer)
1723 R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
1724 if (!cl_decals.integer)
1729 // create a decal for the blood splat
1730 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
1732 if (cl.free_particle > i)
1733 cl.free_particle = i;
1736 else if (p->bounce < 0)
1738 // bounce -1 means remove on impact
1740 if (cl.free_particle > i)
1741 cl.free_particle = i;
1746 // anything else - bounce off solid
1747 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1748 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1749 if (DotProduct(p->vel, p->vel) < 0.03)
1750 VectorClear(p->vel);
1756 if (p->typeindex != pt_static)
1758 switch (p->typeindex)
1760 case pt_entityparticle:
1761 // particle that removes itself after one rendered frame
1765 if (cl.free_particle > i)
1766 cl.free_particle = i;
1772 a = CL_PointSuperContents(p->org);
1773 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1776 if (cl.free_particle > i)
1777 cl.free_particle = i;
1781 a = CL_PointSuperContents(p->org);
1782 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1785 if (cl.free_particle > i)
1786 cl.free_particle = i;
1790 a = CL_PointSuperContents(p->org);
1791 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1794 if (cl.free_particle > i)
1795 cl.free_particle = i;
1799 if (cl.time > p->time2)
1802 p->time2 = cl.time + (rand() & 3) * 0.1;
1803 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
1804 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
1806 a = CL_PointSuperContents(p->org);
1807 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1810 if (cl.free_particle > i)
1811 cl.free_particle = i;
1820 // reduce cl.num_particles if possible
1821 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
1825 #define MAX_PARTICLETEXTURES 64
1826 // particletexture_t is a rectangle in the particlefonttexture
1827 typedef struct particletexture_s
1829 rtexture_t *texture;
1830 float s1, t1, s2, t2;
1834 static rtexturepool_t *particletexturepool;
1835 static rtexture_t *particlefonttexture;
1836 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1838 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1839 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1841 #define PARTICLETEXTURESIZE 64
1842 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1844 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1848 dz = 1 - (dx*dx+dy*dy);
1849 if (dz > 0) // it does hit the sphere
1853 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1854 VectorNormalize(normal);
1855 dot = DotProduct(normal, light);
1856 if (dot > 0.5) // interior reflection
1857 f += ((dot * 2) - 1);
1858 else if (dot < -0.5) // exterior reflection
1859 f += ((dot * -2) - 1);
1861 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1862 VectorNormalize(normal);
1863 dot = DotProduct(normal, light);
1864 if (dot > 0.5) // interior reflection
1865 f += ((dot * 2) - 1);
1866 else if (dot < -0.5) // exterior reflection
1867 f += ((dot * -2) - 1);
1869 f += 16; // just to give it a haze so you can see the outline
1870 f = bound(0, f, 255);
1871 return (unsigned char) f;
1877 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1879 int basex, basey, y;
1880 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1881 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1882 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1883 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1886 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1889 float cx, cy, dx, dy, f, iradius;
1891 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1892 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1893 iradius = 1.0f / radius;
1894 alpha *= (1.0f / 255.0f);
1895 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1897 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1901 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1906 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1907 d[0] += (int)(f * (red - d[0]));
1908 d[1] += (int)(f * (green - d[1]));
1909 d[2] += (int)(f * (blue - d[2]));
1915 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1918 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1920 data[0] = bound(minr, data[0], maxr);
1921 data[1] = bound(ming, data[1], maxg);
1922 data[2] = bound(minb, data[2], maxb);
1926 void particletextureinvert(unsigned char *data)
1929 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1931 data[0] = 255 - data[0];
1932 data[1] = 255 - data[1];
1933 data[2] = 255 - data[2];
1937 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1938 static void R_InitBloodTextures (unsigned char *particletexturedata)
1941 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1944 for (i = 0;i < 8;i++)
1946 memset(&data[0][0][0], 255, sizeof(data));
1947 for (k = 0;k < 24;k++)
1948 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1949 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1950 particletextureinvert(&data[0][0][0]);
1951 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1955 for (i = 0;i < 8;i++)
1957 memset(&data[0][0][0], 255, sizeof(data));
1959 for (j = 1;j < 10;j++)
1960 for (k = min(j, m - 1);k < m;k++)
1961 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1962 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1963 particletextureinvert(&data[0][0][0]);
1964 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1969 //uncomment this to make engine save out particle font to a tga file when run
1970 //#define DUMPPARTICLEFONT
1972 static void R_InitParticleTexture (void)
1974 int x, y, d, i, k, m;
1978 // a note: decals need to modulate (multiply) the background color to
1979 // properly darken it (stain), and they need to be able to alpha fade,
1980 // this is a very difficult challenge because it means fading to white
1981 // (no change to background) rather than black (darkening everything
1982 // behind the whole decal polygon), and to accomplish this the texture is
1983 // inverted (dark red blood on white background becomes brilliant cyan
1984 // and white on black background) so we can alpha fade it to black, then
1985 // we invert it again during the blendfunc to make it work...
1987 #ifndef DUMPPARTICLEFONT
1988 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
1989 if (!particlefonttexture)
1992 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1993 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1994 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1997 for (i = 0;i < 8;i++)
1999 memset(&data[0][0][0], 255, sizeof(data));
2002 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
2004 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2005 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2007 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2009 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2010 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2012 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2013 d = (noise2[y][x] - 128) * 3 + 192;
2015 d = (int)(d * (1-(dx*dx+dy*dy)));
2016 d = (d * noise1[y][x]) >> 7;
2017 d = bound(0, d, 255);
2018 data[y][x][3] = (unsigned char) d;
2025 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
2029 memset(&data[0][0][0], 255, sizeof(data));
2030 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2032 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2033 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2035 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2036 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2037 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
2040 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
2043 memset(&data[0][0][0], 255, sizeof(data));
2044 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2046 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2047 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2049 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2050 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2051 d = bound(0, d, 255);
2052 data[y][x][3] = (unsigned char) d;
2055 setuptex(tex_particle, &data[0][0][0], particletexturedata);
2058 memset(&data[0][0][0], 255, sizeof(data));
2059 light[0] = 1;light[1] = 1;light[2] = 1;
2060 VectorNormalize(light);
2061 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2063 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2064 // stretch upper half of bubble by +50% and shrink lower half by -50%
2065 // (this gives an elongated teardrop shape)
2067 dy = (dy - 0.5f) * 2.0f;
2069 dy = (dy - 0.5f) / 1.5f;
2070 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2072 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2073 // shrink bubble width to half
2075 data[y][x][3] = shadebubble(dx, dy, light);
2078 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2081 memset(&data[0][0][0], 255, sizeof(data));
2082 light[0] = 1;light[1] = 1;light[2] = 1;
2083 VectorNormalize(light);
2084 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2086 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2087 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2089 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2090 data[y][x][3] = shadebubble(dx, dy, light);
2093 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2095 // Blood particles and blood decals
2096 R_InitBloodTextures (particletexturedata);
2099 for (i = 0;i < 8;i++)
2101 memset(&data[0][0][0], 255, sizeof(data));
2102 for (k = 0;k < 12;k++)
2103 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2104 for (k = 0;k < 3;k++)
2105 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2106 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2107 particletextureinvert(&data[0][0][0]);
2108 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2111 #ifdef DUMPPARTICLEFONT
2112 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2115 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2117 Mem_Free(particletexturedata);
2119 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2121 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2122 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2123 particletexture[i].texture = particlefonttexture;
2124 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2125 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2126 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2127 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2130 #ifndef DUMPPARTICLEFONT
2131 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
2132 if (!particletexture[tex_beam].texture)
2135 unsigned char noise3[64][64], data2[64][16][4];
2137 fractalnoise(&noise3[0][0], 64, 4);
2139 for (y = 0;y < 64;y++)
2141 dy = (y - 0.5f*64) / (64*0.5f-1);
2142 for (x = 0;x < 16;x++)
2144 dx = (x - 0.5f*16) / (16*0.5f-2);
2145 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2146 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2147 data2[y][x][3] = 255;
2151 #ifdef DUMPPARTICLEFONT
2152 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2154 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2156 particletexture[tex_beam].s1 = 0;
2157 particletexture[tex_beam].t1 = 0;
2158 particletexture[tex_beam].s2 = 1;
2159 particletexture[tex_beam].t2 = 1;
2162 static void r_part_start(void)
2164 particletexturepool = R_AllocTexturePool();
2165 R_InitParticleTexture ();
2166 CL_Particles_LoadEffectInfo();
2169 static void r_part_shutdown(void)
2171 R_FreeTexturePool(&particletexturepool);
2174 static void r_part_newmap(void)
2178 #define BATCHSIZE 256
2179 int particle_element3i[BATCHSIZE*6];
2180 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2182 void R_Particles_Init (void)
2185 for (i = 0;i < BATCHSIZE;i++)
2187 particle_element3i[i*6+0] = i*4+0;
2188 particle_element3i[i*6+1] = i*4+1;
2189 particle_element3i[i*6+2] = i*4+2;
2190 particle_element3i[i*6+3] = i*4+0;
2191 particle_element3i[i*6+4] = i*4+2;
2192 particle_element3i[i*6+5] = i*4+3;
2195 Cvar_RegisterVariable(&r_drawparticles);
2196 Cvar_RegisterVariable(&r_drawdecals);
2197 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2200 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2202 int surfacelistindex;
2203 int batchstart, batchcount;
2206 rtexture_t *texture;
2207 float *v3f, *t2f, *c4f;
2209 r_refdef.stats.decals += numsurfaces;
2210 R_Mesh_Matrix(&identitymatrix);
2211 R_Mesh_ResetTextureState();
2212 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2213 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2214 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2215 GL_DepthMask(false);
2216 GL_DepthRange(0, 1);
2217 GL_PolygonOffset(0, 0);
2219 GL_CullFace(GL_NONE);
2221 // first generate all the vertices at once
2222 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2224 particletexture_t *tex;
2226 float right[3], up[3], fog, cr, cg, cb, ca, size;
2228 d = cl.decals + surfacelist[surfacelistindex];
2230 //blendmode = particletype[d->typeindex].blendmode;
2232 cr = d->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2233 cg = d->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2234 cb = d->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2235 ca = d->alpha * (1.0f / 255.0f);
2236 //if (blendmode == PBLEND_MOD)
2246 ca *= cl_particles_alpha.value;
2247 if (r_refdef.fogenabled)
2249 fog = FogPoint_World(d->org);
2253 //if (blendmode == PBLEND_ALPHA)
2256 // cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2257 // cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2258 // cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2261 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2262 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2263 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2264 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2266 size = d->size * cl_particles_size.value;
2268 tex = &particletexture[d->texnum];
2270 // PARTICLE_ORIENTED_DOUBLESIDED
2271 VectorVectors(d->normal, right, up);
2272 VectorScale(right, size, right);
2273 VectorScale(up, size, up);
2274 v3f[ 0] = org[0] - right[0] - up[0];
2275 v3f[ 1] = org[1] - right[1] - up[1];
2276 v3f[ 2] = org[2] - right[2] - up[2];
2277 v3f[ 3] = org[0] - right[0] + up[0];
2278 v3f[ 4] = org[1] - right[1] + up[1];
2279 v3f[ 5] = org[2] - right[2] + up[2];
2280 v3f[ 6] = org[0] + right[0] + up[0];
2281 v3f[ 7] = org[1] + right[1] + up[1];
2282 v3f[ 8] = org[2] + right[2] + up[2];
2283 v3f[ 9] = org[0] + right[0] - up[0];
2284 v3f[10] = org[1] + right[1] - up[1];
2285 v3f[11] = org[2] + right[2] - up[2];
2286 t2f[0] = tex->s1;t2f[1] = tex->t2;
2287 t2f[2] = tex->s1;t2f[3] = tex->t1;
2288 t2f[4] = tex->s2;t2f[5] = tex->t1;
2289 t2f[6] = tex->s2;t2f[7] = tex->t2;
2292 // now render batches of particles based on blendmode and texture
2293 blendmode = PBLEND_ADD;
2294 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2295 texture = particletexture[63].texture;
2296 R_Mesh_TexBind(0, R_GetTexture(texture));
2297 GL_LockArrays(0, numsurfaces*4);
2300 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2302 d = cl.decals + surfacelist[surfacelistindex];
2304 if (blendmode != particletype[d->typeindex].blendmode)
2307 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2309 batchstart = surfacelistindex;
2310 blendmode = particletype[d->typeindex].blendmode;
2311 if (blendmode == PBLEND_ALPHA)
2312 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2313 else if (blendmode == PBLEND_ADD)
2314 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2315 else //if (blendmode == PBLEND_MOD)
2316 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2318 if (texture != particletexture[d->texnum].texture)
2321 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2323 batchstart = surfacelistindex;
2324 texture = particletexture[d->texnum].texture;
2325 R_Mesh_TexBind(0, R_GetTexture(texture));
2331 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2332 GL_LockArrays(0, 0);
2335 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2337 int surfacelistindex;
2338 int batchstart, batchcount;
2339 const particle_t *p;
2341 rtexture_t *texture;
2342 float *v3f, *t2f, *c4f;
2344 r_refdef.stats.particles += numsurfaces;
2345 R_Mesh_Matrix(&identitymatrix);
2346 R_Mesh_ResetTextureState();
2347 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2348 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2349 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2350 GL_DepthMask(false);
2351 GL_DepthRange(0, 1);
2352 GL_PolygonOffset(0, 0);
2354 GL_CullFace(GL_NONE);
2356 // first generate all the vertices at once
2357 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2359 particletexture_t *tex;
2361 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2363 p = cl.particles + surfacelist[surfacelistindex];
2365 blendmode = particletype[p->typeindex].blendmode;
2367 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2368 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2369 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2370 ca = p->alpha * (1.0f / 255.0f);
2371 if (blendmode == PBLEND_MOD)
2381 ca *= cl_particles_alpha.value;
2382 if (particletype[p->typeindex].lighting)
2384 float ambient[3], diffuse[3], diffusenormal[3];
2385 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2386 cr *= (ambient[0] + 0.5 * diffuse[0]);
2387 cg *= (ambient[1] + 0.5 * diffuse[1]);
2388 cb *= (ambient[2] + 0.5 * diffuse[2]);
2390 if (r_refdef.fogenabled)
2392 fog = FogPoint_World(p->org);
2396 if (blendmode == PBLEND_ALPHA)
2399 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2400 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2401 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2404 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2405 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2406 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2407 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2409 size = p->size * cl_particles_size.value;
2411 tex = &particletexture[p->texnum];
2412 switch(particletype[p->typeindex].orientation)
2414 case PARTICLE_BILLBOARD:
2415 VectorScale(r_view.left, -size, right);
2416 VectorScale(r_view.up, size, up);
2417 v3f[ 0] = org[0] - right[0] - up[0];
2418 v3f[ 1] = org[1] - right[1] - up[1];
2419 v3f[ 2] = org[2] - right[2] - up[2];
2420 v3f[ 3] = org[0] - right[0] + up[0];
2421 v3f[ 4] = org[1] - right[1] + up[1];
2422 v3f[ 5] = org[2] - right[2] + up[2];
2423 v3f[ 6] = org[0] + right[0] + up[0];
2424 v3f[ 7] = org[1] + right[1] + up[1];
2425 v3f[ 8] = org[2] + right[2] + up[2];
2426 v3f[ 9] = org[0] + right[0] - up[0];
2427 v3f[10] = org[1] + right[1] - up[1];
2428 v3f[11] = org[2] + right[2] - up[2];
2429 t2f[0] = tex->s1;t2f[1] = tex->t2;
2430 t2f[2] = tex->s1;t2f[3] = tex->t1;
2431 t2f[4] = tex->s2;t2f[5] = tex->t1;
2432 t2f[6] = tex->s2;t2f[7] = tex->t2;
2434 case PARTICLE_ORIENTED_DOUBLESIDED:
2435 VectorVectors(p->vel, right, up);
2436 VectorScale(right, size, right);
2437 VectorScale(up, size, up);
2438 v3f[ 0] = org[0] - right[0] - up[0];
2439 v3f[ 1] = org[1] - right[1] - up[1];
2440 v3f[ 2] = org[2] - right[2] - up[2];
2441 v3f[ 3] = org[0] - right[0] + up[0];
2442 v3f[ 4] = org[1] - right[1] + up[1];
2443 v3f[ 5] = org[2] - right[2] + up[2];
2444 v3f[ 6] = org[0] + right[0] + up[0];
2445 v3f[ 7] = org[1] + right[1] + up[1];
2446 v3f[ 8] = org[2] + right[2] + up[2];
2447 v3f[ 9] = org[0] + right[0] - up[0];
2448 v3f[10] = org[1] + right[1] - up[1];
2449 v3f[11] = org[2] + right[2] - up[2];
2450 t2f[0] = tex->s1;t2f[1] = tex->t2;
2451 t2f[2] = tex->s1;t2f[3] = tex->t1;
2452 t2f[4] = tex->s2;t2f[5] = tex->t1;
2453 t2f[6] = tex->s2;t2f[7] = tex->t2;
2455 case PARTICLE_SPARK:
2456 VectorMA(org, -0.02, p->vel, v);
2457 VectorMA(org, 0.02, p->vel, up2);
2458 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2459 t2f[0] = tex->s1;t2f[1] = tex->t2;
2460 t2f[2] = tex->s1;t2f[3] = tex->t1;
2461 t2f[4] = tex->s2;t2f[5] = tex->t1;
2462 t2f[6] = tex->s2;t2f[7] = tex->t2;
2465 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2466 VectorSubtract(p->vel, org, up);
2467 VectorNormalize(up);
2468 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2469 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2470 t2f[0] = 1;t2f[1] = v[0];
2471 t2f[2] = 0;t2f[3] = v[0];
2472 t2f[4] = 0;t2f[5] = v[1];
2473 t2f[6] = 1;t2f[7] = v[1];
2478 // now render batches of particles based on blendmode and texture
2479 blendmode = PBLEND_ADD;
2480 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2481 texture = particletexture[63].texture;
2482 R_Mesh_TexBind(0, R_GetTexture(texture));
2483 GL_LockArrays(0, numsurfaces*4);
2486 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2488 p = cl.particles + surfacelist[surfacelistindex];
2490 if (blendmode != particletype[p->typeindex].blendmode)
2493 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2495 batchstart = surfacelistindex;
2496 blendmode = particletype[p->typeindex].blendmode;
2497 if (blendmode == PBLEND_ALPHA)
2498 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2499 else if (blendmode == PBLEND_ADD)
2500 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2501 else //if (blendmode == PBLEND_MOD)
2502 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2504 if (texture != particletexture[p->texnum].texture)
2507 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2509 batchstart = surfacelistindex;
2510 texture = particletexture[p->texnum].texture;
2511 R_Mesh_TexBind(0, R_GetTexture(texture));
2517 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2518 GL_LockArrays(0, 0);
2521 void R_DrawDecals (void)
2526 // LordHavoc: early out conditions
2527 if ((!cl.num_decals) || (!r_drawdecals.integer))
2530 // LordHavoc: only render if not too close
2531 for (i = 0, d = cl.decals;i < cl.num_decals;i++, d++)
2535 r_refdef.stats.decals++;
2536 R_MeshQueue_AddTransparent(d->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2541 void R_DrawParticles (void)
2544 float minparticledist;
2547 // LordHavoc: early out conditions
2548 if ((!cl.num_particles) || (!r_drawparticles.integer))
2551 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2553 // LordHavoc: only render if not too close
2554 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2555 if (p->typeindex && !p->delayedspawn && (DotProduct(p->org, r_view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM))
2556 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);