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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {0, 0, false}, // pt_dead
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // range of colors to choose from in hex RRGGBB (like HTML color tags),
73 // randomly interpolated at spawn
74 unsigned int color[2];
75 // a random texture is chosen in this range (note the second value is one
76 // past the last choosable, so for example 8,16 chooses any from 8 up and
78 // if start and end of the range are the same, no randomization is done
80 // range of size values randomly chosen when spawning, plus size increase over time
82 // range of alpha values randomly chosen when spawning, plus alpha fade
84 // how long the particle should live (note it is also removed if alpha drops to 0)
86 // how much gravity affects this particle (negative makes it fly up!)
88 // how much bounce the particle has when it hits a surface
89 // if negative the particle is removed on impact
91 // if in air this friction is applied
92 // if negative the particle accelerates
94 // if in liquid (water/slime/lava) this friction is applied
95 // if negative the particle accelerates
97 // these offsets are added to the values given to particleeffect(), and
98 // then an ellipsoid-shaped jitter is added as defined by these
99 // (they are the 3 radii)
100 float originoffset[3];
101 float velocityoffset[3];
102 float originjitter[3];
103 float velocityjitter[3];
104 float velocitymultiplier;
105 // an effect can also spawn a dlight
106 float lightradiusstart;
107 float lightradiusfade;
110 qboolean lightshadow;
113 particleeffectinfo_t;
115 #define MAX_PARTICLEEFFECTNAME 256
116 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
118 #define MAX_PARTICLEEFFECTINFO 4096
120 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
122 static int particlepalette[256] =
124 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
125 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
126 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
127 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
128 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
129 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
130 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
131 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
132 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
133 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
134 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
135 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
136 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
137 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
138 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
139 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
140 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
141 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
142 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
143 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
144 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
145 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
146 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
147 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
148 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
149 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
150 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
151 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
152 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
153 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
154 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
155 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
158 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
159 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
160 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
162 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
164 // texture numbers in particle font
165 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
166 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
167 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
168 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
169 static const int tex_rainsplash = 32;
170 static const int tex_particle = 63;
171 static const int tex_bubble = 62;
172 static const int tex_raindrop = 61;
173 static const int tex_beam = 60;
175 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
176 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
177 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
178 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
179 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
180 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
181 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
182 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
183 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
184 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
185 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
186 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
187 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
188 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
189 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
190 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
191 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
192 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
193 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
194 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
195 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
198 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
204 particleeffectinfo_t *info = NULL;
205 const char *text = textstart;
207 effectinfoindex = -1;
208 for (linenumber = 1;;linenumber++)
211 for (arrayindex = 0;arrayindex < 16;arrayindex++)
212 argv[arrayindex][0] = 0;
215 if (!COM_ParseToken_Simple(&text, true, false))
217 if (!strcmp(com_token, "\n"))
221 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
227 #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;}
228 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
229 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
230 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
231 #define readfloat(var) checkparms(2);var = atof(argv[1])
232 if (!strcmp(argv[0], "effect"))
237 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
239 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
242 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
244 if (particleeffectname[effectnameindex][0])
246 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
251 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
255 // if we run out of names, abort
256 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
258 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
261 info = particleeffectinfo + effectinfoindex;
262 info->effectnameindex = effectnameindex;
263 info->particletype = pt_alphastatic;
264 info->tex[0] = tex_particle;
265 info->tex[1] = tex_particle;
266 info->color[0] = 0xFFFFFF;
267 info->color[1] = 0xFFFFFF;
271 info->alpha[1] = 256;
272 info->alpha[2] = 256;
273 info->time[0] = 9999;
274 info->time[1] = 9999;
275 VectorSet(info->lightcolor, 1, 1, 1);
276 info->lightshadow = true;
277 info->lighttime = 9999;
279 else if (info == NULL)
281 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
284 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
285 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
286 else if (!strcmp(argv[0], "type"))
289 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
290 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
291 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
292 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
293 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
294 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
295 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
296 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
297 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
298 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
299 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
300 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
301 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
303 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
304 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
305 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
306 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
307 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
308 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
309 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
310 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
311 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
312 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
313 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
314 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
315 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
316 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
317 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
318 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
319 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
320 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
321 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
322 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
323 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
324 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
325 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
326 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
328 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
337 int CL_ParticleEffectIndexForName(const char *name)
340 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
341 if (!strcmp(particleeffectname[i], name))
346 const char *CL_ParticleEffectNameForIndex(int i)
348 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
350 return particleeffectname[i];
353 // MUST match effectnameindex_t in client.h
354 static const char *standardeffectnames[EFFECT_TOTAL] =
378 "TE_TEI_BIGEXPLOSION",
394 void CL_Particles_LoadEffectInfo(void)
397 unsigned char *filedata;
398 fs_offset_t filesize;
399 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
400 memset(particleeffectname, 0, sizeof(particleeffectname));
401 for (i = 0;i < EFFECT_TOTAL;i++)
402 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
403 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
406 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
416 void CL_ReadPointFile_f (void);
417 void CL_Particles_Init (void)
419 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)");
420 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
422 Cvar_RegisterVariable (&cl_particles);
423 Cvar_RegisterVariable (&cl_particles_quality);
424 Cvar_RegisterVariable (&cl_particles_alpha);
425 Cvar_RegisterVariable (&cl_particles_size);
426 Cvar_RegisterVariable (&cl_particles_quake);
427 Cvar_RegisterVariable (&cl_particles_blood);
428 Cvar_RegisterVariable (&cl_particles_blood_alpha);
429 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
430 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
431 Cvar_RegisterVariable (&cl_particles_explosions_shell);
432 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
433 Cvar_RegisterVariable (&cl_particles_rain);
434 Cvar_RegisterVariable (&cl_particles_snow);
435 Cvar_RegisterVariable (&cl_particles_smoke);
436 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
437 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
438 Cvar_RegisterVariable (&cl_particles_sparks);
439 Cvar_RegisterVariable (&cl_particles_bubbles);
440 Cvar_RegisterVariable (&cl_decals);
441 Cvar_RegisterVariable (&cl_decals_time);
442 Cvar_RegisterVariable (&cl_decals_fadetime);
445 void CL_Particles_Shutdown (void)
449 // list of all 26 parameters:
450 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
451 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
452 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
453 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
454 // palpha - opacity of particle as 0-255 (can be more than 255)
455 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
456 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
457 // pgravity - how much effect gravity has on the particle (0-1)
458 // 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
459 // px,py,pz - starting origin of particle
460 // pvx,pvy,pvz - starting velocity of particle
461 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
462 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, qboolean pqualityreduction, float lifetime)
467 if (!cl_particles.integer)
469 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
470 if (cl.free_particle >= cl.max_particles)
473 lifetime = palpha / min(1, palphafade);
474 part = &cl.particles[cl.free_particle++];
475 if (cl.num_particles < cl.free_particle)
476 cl.num_particles = cl.free_particle;
477 memset(part, 0, sizeof(*part));
478 part->typeindex = ptypeindex;
479 l2 = (int)lhrandom(0.5, 256.5);
481 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
482 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
483 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
486 part->sizeincrease = psizeincrease;
487 part->alpha = palpha;
488 part->alphafade = palphafade;
489 part->gravity = pgravity;
490 part->bounce = pbounce;
492 part->org[0] = px + originjitter * v[0];
493 part->org[1] = py + originjitter * v[1];
494 part->org[2] = pz + originjitter * v[2];
495 part->vel[0] = pvx + velocityjitter * v[0];
496 part->vel[1] = pvy + velocityjitter * v[1];
497 part->vel[2] = pvz + velocityjitter * v[2];
499 part->airfriction = pairfriction;
500 part->liquidfriction = pliquidfriction;
501 part->die = cl.time + lifetime;
502 part->delayedcollisions = 0;
503 part->qualityreduction = pqualityreduction;
504 if (part->typeindex == pt_blood)
505 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)
506 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
507 if (part->typeindex == pt_rain)
511 float lifetime = part->die - cl.time;
514 // turn raindrop into simple spark and create delayedspawn splash effect
515 part->typeindex = pt_spark;
517 VectorMA(part->org, lifetime, part->vel, endvec);
518 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
519 part->die = cl.time + lifetime * trace.fraction;
520 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, pqualityreduction, 0);
523 part2->delayedspawn = part->die;
524 part2->die += part->die - cl.time;
525 for (i = rand() & 7;i < 10;i++)
527 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, pqualityreduction, 0);
530 part2->delayedspawn = part->die;
531 part2->die += part->die - cl.time;
536 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
538 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
541 VectorMA(part->org, lifetime, part->vel, endvec);
542 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
543 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
548 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
552 if (!cl_decals.integer)
554 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
555 if (cl.free_decal >= cl.max_decals)
557 decal = &cl.decals[cl.free_decal++];
558 if (cl.num_decals < cl.free_decal)
559 cl.num_decals = cl.free_decal;
560 memset(decal, 0, sizeof(*decal));
561 decal->typeindex = pt_decal;
562 decal->texnum = texnum;
563 VectorAdd(org, normal, decal->org);
564 VectorCopy(normal, decal->normal);
566 decal->alpha = alpha;
567 decal->time2 = cl.time;
568 l2 = (int)lhrandom(0.5, 256.5);
570 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
571 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
572 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
573 decal->owner = hitent;
576 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
577 decal->ownermodel = cl.entities[decal->owner].render.model;
578 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
579 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
583 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
586 float bestfrac, bestorg[3], bestnormal[3];
588 int besthitent = 0, hitent;
591 for (i = 0;i < 32;i++)
594 VectorMA(org, maxdist, org2, org2);
595 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
596 // take the closest trace result that doesn't end up hitting a NOMARKS
597 // surface (sky for example)
598 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
600 bestfrac = trace.fraction;
602 VectorCopy(trace.endpos, bestorg);
603 VectorCopy(trace.plane.normal, bestnormal);
607 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
610 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
611 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
612 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)
615 matrix4x4_t tempmatrix;
616 VectorLerp(originmins, 0.5, originmaxs, center);
617 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
618 if (effectnameindex == EFFECT_SVC_PARTICLE)
620 if (cl_particles.integer)
622 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
624 CL_ParticleExplosion(center);
625 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
626 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
629 count *= cl_particles_quality.value;
630 for (;count > 0;count--)
632 int k = particlepalette[palettecolor + (rand()&7)];
633 if (cl_particles_quake.integer)
634 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, true, 0);
635 else if (gamemode == GAME_GOODVSBAD2)
636 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, true, 0);
638 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, true, 0);
643 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
644 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
645 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
647 else if (effectnameindex == EFFECT_TE_SPIKE)
649 if (cl_particles_bulletimpacts.integer)
651 if (cl_particles_quake.integer)
653 if (cl_particles_smoke.integer)
654 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
658 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
659 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
663 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
664 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
666 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
668 if (cl_particles_bulletimpacts.integer)
670 if (cl_particles_quake.integer)
672 if (cl_particles_smoke.integer)
673 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
677 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
678 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
682 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
683 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
684 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);
686 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
688 if (cl_particles_bulletimpacts.integer)
690 if (cl_particles_quake.integer)
692 if (cl_particles_smoke.integer)
693 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
697 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
698 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
702 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
703 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
705 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
707 if (cl_particles_bulletimpacts.integer)
709 if (cl_particles_quake.integer)
711 if (cl_particles_smoke.integer)
712 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
716 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
717 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
721 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
722 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
723 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);
725 else if (effectnameindex == EFFECT_TE_BLOOD)
727 if (!cl_particles_blood.integer)
729 if (cl_particles_quake.integer)
730 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
733 static double bloodaccumulator = 0;
734 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
735 for (;bloodaccumulator > 0;bloodaccumulator--)
736 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, true, 0);
739 else if (effectnameindex == EFFECT_TE_SPARK)
740 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
741 else if (effectnameindex == EFFECT_TE_PLASMABURN)
743 // plasma scorch mark
744 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
745 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
746 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
748 else if (effectnameindex == EFFECT_TE_GUNSHOT)
750 if (cl_particles_bulletimpacts.integer)
752 if (cl_particles_quake.integer)
753 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
756 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
757 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
761 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
762 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
764 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
766 if (cl_particles_bulletimpacts.integer)
768 if (cl_particles_quake.integer)
769 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
772 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
773 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
777 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
778 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
779 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);
781 else if (effectnameindex == EFFECT_TE_EXPLOSION)
783 CL_ParticleExplosion(center);
784 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);
786 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
788 CL_ParticleExplosion(center);
789 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);
791 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
793 if (cl_particles_quake.integer)
796 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
799 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, true, 0);
801 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, true, 0);
805 CL_ParticleExplosion(center);
806 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);
808 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
809 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);
810 else if (effectnameindex == EFFECT_TE_FLAMEJET)
812 count *= cl_particles_quality.value;
814 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, true, 0);
816 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
818 float i, j, inc, vel;
821 inc = 8 / cl_particles_quality.value;
822 for (i = -128;i < 128;i += inc)
824 for (j = -128;j < 128;j += inc)
826 dir[0] = j + lhrandom(0, inc);
827 dir[1] = i + lhrandom(0, inc);
829 org[0] = center[0] + dir[0];
830 org[1] = center[1] + dir[1];
831 org[2] = center[2] + lhrandom(0, 64);
832 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
833 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, true, 0);
837 else if (effectnameindex == EFFECT_TE_TELEPORT)
839 float i, j, k, inc, vel;
842 inc = 8 / cl_particles_quality.value;
843 for (i = -16;i < 16;i += inc)
845 for (j = -16;j < 16;j += inc)
847 for (k = -24;k < 32;k += inc)
849 VectorSet(dir, i*8, j*8, k*8);
850 VectorNormalize(dir);
851 vel = lhrandom(50, 113);
852 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, true, 0);
856 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, false, 0);
857 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);
859 else if (effectnameindex == EFFECT_TE_TEI_G3)
860 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, false, 0);
861 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
863 if (cl_particles_smoke.integer)
865 count *= 0.25f * cl_particles_quality.value;
867 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, true, 0);
870 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
872 CL_ParticleExplosion(center);
873 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);
875 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
878 if (cl_stainmaps.integer)
879 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
880 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
881 if (cl_particles_smoke.integer)
882 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
883 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, true, 0);
884 if (cl_particles_sparks.integer)
885 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
886 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, true, 0);
887 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);
889 else if (effectnameindex == EFFECT_EF_FLAME)
891 count *= 300 * cl_particles_quality.value;
893 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, true, 0);
894 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);
896 else if (effectnameindex == EFFECT_EF_STARDUST)
898 count *= 200 * cl_particles_quality.value;
900 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, true, 0);
901 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);
903 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
907 int smoke, blood, bubbles, r, color;
909 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
912 Vector4Set(light, 0, 0, 0, 0);
914 if (effectnameindex == EFFECT_TR_ROCKET)
915 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
916 else if (effectnameindex == EFFECT_TR_VORESPIKE)
918 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
919 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
921 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
923 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
924 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
928 matrix4x4_t tempmatrix;
929 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
930 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
937 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
940 VectorSubtract(originmaxs, originmins, dir);
941 len = VectorNormalizeLength(dir);
944 dec = -ent->persistent.trail_time;
945 ent->persistent.trail_time += len;
946 if (ent->persistent.trail_time < 0.01f)
949 // if we skip out, leave it reset
950 ent->persistent.trail_time = 0.0f;
955 // advance into this frame to reach the first puff location
956 VectorMA(originmins, dec, dir, pos);
959 smoke = cl_particles.integer && cl_particles_smoke.integer;
960 blood = cl_particles.integer && cl_particles_blood.integer;
961 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
962 qd = 1.0f / cl_particles_quality.value;
969 if (effectnameindex == EFFECT_TR_BLOOD)
971 if (cl_particles_quake.integer)
973 color = particlepalette[67 + (rand()&3)];
974 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, true, 0);
979 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, true, 0);
982 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
984 if (cl_particles_quake.integer)
987 color = particlepalette[67 + (rand()&3)];
988 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, true, 0);
993 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, true, 0);
999 if (effectnameindex == EFFECT_TR_ROCKET)
1001 if (cl_particles_quake.integer)
1004 color = particlepalette[ramp3[r]];
1005 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, true, 0);
1009 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, true, 0);
1010 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, true, 0);
1013 else if (effectnameindex == EFFECT_TR_GRENADE)
1015 if (cl_particles_quake.integer)
1018 color = particlepalette[ramp3[r]];
1019 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, true, 0);
1023 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, true, 0);
1026 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1028 if (cl_particles_quake.integer)
1031 color = particlepalette[52 + (rand()&7)];
1032 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, true, 0);
1033 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, true, 0);
1035 else if (gamemode == GAME_GOODVSBAD2)
1038 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, true, 0);
1042 color = particlepalette[20 + (rand()&7)];
1043 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, true, 0);
1046 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1048 if (cl_particles_quake.integer)
1051 color = particlepalette[230 + (rand()&7)];
1052 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, true, 0);
1053 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, true, 0);
1057 color = particlepalette[226 + (rand()&7)];
1058 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, true, 0);
1061 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1063 if (cl_particles_quake.integer)
1065 color = particlepalette[152 + (rand()&3)];
1066 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, true, 0);
1068 else if (gamemode == GAME_GOODVSBAD2)
1071 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, true, 0);
1073 else if (gamemode == GAME_PRYDON)
1076 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, true, 0);
1079 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, true, 0);
1081 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1084 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, false, 0);
1086 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1089 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, true, 0);
1091 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1092 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, true, 0);
1096 if (effectnameindex == EFFECT_TR_ROCKET)
1097 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, true, 0);
1098 else if (effectnameindex == EFFECT_TR_GRENADE)
1099 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, true, 0);
1101 // advance to next time and position
1104 VectorMA (pos, dec, dir, pos);
1107 ent->persistent.trail_time = len;
1109 else if (developer.integer >= 1)
1110 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1113 // this is also called on point effects with spawndlight = true and
1114 // spawnparticles = true
1115 // it is called CL_ParticleTrail because most code does not want to supply
1116 // these parameters, only trail handling does
1117 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)
1120 qboolean found = false;
1121 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1123 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1124 return; // invalid effect index
1126 if (!particleeffectname[effectnameindex][0])
1127 return; // no such effect
1128 VectorLerp(originmins, 0.5, originmaxs, center);
1129 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1131 int effectinfoindex;
1134 particleeffectinfo_t *info;
1136 vec3_t centervelocity;
1142 qboolean underwater;
1143 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1144 VectorLerp(originmins, 0.5, originmaxs, center);
1145 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1146 supercontents = CL_PointSuperContents(center);
1147 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1148 VectorSubtract(originmaxs, originmins, traildir);
1149 traillen = VectorLength(traildir);
1150 VectorNormalize(traildir);
1151 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1153 if (info->effectnameindex == effectnameindex)
1156 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1158 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1161 // spawn a dlight if requested
1162 if (info->lightradiusstart > 0 && spawndlight)
1164 matrix4x4_t tempmatrix;
1165 if (info->trailspacing > 0)
1166 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1168 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1169 if (info->lighttime > 0 && info->lightradiusfade > 0)
1171 // light flash (explosion, etc)
1172 // called when effect starts
1173 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);
1178 // called by CL_LinkNetworkEntity
1179 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1180 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.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);
1184 if (!spawnparticles)
1189 if (info->tex[1] > info->tex[0])
1191 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1192 tex = min(tex, info->tex[1] - 1);
1194 if (info->particletype == pt_decal)
1195 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]);
1196 else if (info->particletype == pt_beam)
1197 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, false, 0);
1200 if (!cl_particles.integer)
1202 switch (info->particletype)
1204 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1205 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1206 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1207 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1208 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1209 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1212 VectorCopy(originmins, trailpos);
1213 if (info->trailspacing > 0)
1215 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1216 trailstep = info->trailspacing / cl_particles_quality.value;
1220 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1223 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1224 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1226 if (info->tex[1] > info->tex[0])
1228 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1229 tex = min(tex, info->tex[1] - 1);
1233 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1234 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1235 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1238 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, info->countabsolute <= 0, 0);
1240 VectorMA(trailpos, trailstep, traildir, trailpos);
1247 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1250 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)
1252 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1260 void CL_EntityParticles (const entity_t *ent)
1263 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1264 static vec3_t avelocities[NUMVERTEXNORMALS];
1265 if (!cl_particles.integer) return;
1266 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1268 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1270 if (!avelocities[0][0])
1271 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1272 avelocities[0][i] = lhrandom(0, 2.55);
1274 for (i = 0;i < NUMVERTEXNORMALS;i++)
1276 yaw = cl.time * avelocities[i][0];
1277 pitch = cl.time * avelocities[i][1];
1278 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1279 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1280 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1281 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, true, 0);
1286 void CL_ReadPointFile_f (void)
1288 vec3_t org, leakorg;
1290 char *pointfile = NULL, *pointfilepos, *t, tchar;
1291 char name[MAX_OSPATH];
1296 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1297 strlcat (name, ".pts", sizeof (name));
1298 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1301 Con_Printf("Could not open %s\n", name);
1305 Con_Printf("Reading %s...\n", name);
1306 VectorClear(leakorg);
1309 pointfilepos = pointfile;
1310 while (*pointfilepos)
1312 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1317 while (*t && *t != '\n' && *t != '\r')
1321 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1327 VectorCopy(org, leakorg);
1330 if (cl.num_particles < cl.max_particles - 3)
1333 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, true, 1<<30);
1336 Mem_Free(pointfile);
1337 VectorCopy(leakorg, org);
1338 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1340 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, false, 1<<30);
1341 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, false, 1<<30);
1342 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, false, 1<<30);
1347 CL_ParseParticleEffect
1349 Parse an effect out of the server message
1352 void CL_ParseParticleEffect (void)
1355 int i, count, msgcount, color;
1357 MSG_ReadVector(org, cls.protocol);
1358 for (i=0 ; i<3 ; i++)
1359 dir[i] = MSG_ReadChar ();
1360 msgcount = MSG_ReadByte ();
1361 color = MSG_ReadByte ();
1363 if (msgcount == 255)
1368 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1373 CL_ParticleExplosion
1377 void CL_ParticleExplosion (const vec3_t org)
1383 if (cl_stainmaps.integer)
1384 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1385 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1387 if (cl_particles_quake.integer)
1389 for (i = 0;i < 1024;i++)
1395 color = particlepalette[ramp1[r]];
1396 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, true, 0);
1400 color = particlepalette[ramp2[r]];
1401 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, true, 0);
1407 i = CL_PointSuperContents(org);
1408 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1410 if (cl_particles.integer && cl_particles_bubbles.integer)
1411 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1412 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, true, 0);
1416 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1418 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1422 for (k = 0;k < 16;k++)
1425 VectorMA(org, 128, v2, v);
1426 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1427 if (trace.fraction >= 0.1)
1430 VectorSubtract(trace.endpos, org, v2);
1431 VectorScale(v2, 2.0f, v2);
1432 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, true, 0);
1438 if (cl_particles_explosions_shell.integer)
1439 R_NewExplosion(org);
1444 CL_ParticleExplosion2
1448 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1451 if (!cl_particles.integer) return;
1453 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1455 k = particlepalette[colorStart + (i % colorLength)];
1456 if (cl_particles_quake.integer)
1457 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, true, 0);
1459 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, true, 0);
1463 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1465 if (cl_particles_sparks.integer)
1467 sparkcount *= cl_particles_quality.value;
1468 while(sparkcount-- > 0)
1469 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, true, 0);
1473 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1475 if (cl_particles_smoke.integer)
1477 smokecount *= cl_particles_quality.value;
1478 while(smokecount-- > 0)
1479 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, true, 0);
1483 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)
1486 if (!cl_particles.integer) return;
1488 count = (int)(count * cl_particles_quality.value);
1491 k = particlepalette[colorbase + (rand()&3)];
1492 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, true, 0);
1496 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1499 float minz, maxz, lifetime = 30;
1500 if (!cl_particles.integer) return;
1501 if (dir[2] < 0) // falling
1503 minz = maxs[2] + dir[2] * 0.1;
1506 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1511 maxz = maxs[2] + dir[2] * 0.1;
1513 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1516 count = (int)(count * cl_particles_quality.value);
1521 if (!cl_particles_rain.integer) break;
1522 count *= 4; // ick, this should be in the mod or maps?
1526 k = particlepalette[colorbase + (rand()&3)];
1527 if (gamemode == GAME_GOODVSBAD2)
1528 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 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, true, lifetime);
1530 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 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, true, lifetime);
1534 if (!cl_particles_snow.integer) break;
1537 k = particlepalette[colorbase + (rand()&3)];
1538 if (gamemode == GAME_GOODVSBAD2)
1539 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, true, lifetime);
1541 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, true, lifetime);
1545 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1549 #define MAX_PARTICLETEXTURES 64
1550 // particletexture_t is a rectangle in the particlefonttexture
1551 typedef struct particletexture_s
1553 rtexture_t *texture;
1554 float s1, t1, s2, t2;
1558 static rtexturepool_t *particletexturepool;
1559 static rtexture_t *particlefonttexture;
1560 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1562 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1563 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1564 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1565 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1567 #define PARTICLETEXTURESIZE 64
1568 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1570 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1574 dz = 1 - (dx*dx+dy*dy);
1575 if (dz > 0) // it does hit the sphere
1579 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1580 VectorNormalize(normal);
1581 dot = DotProduct(normal, light);
1582 if (dot > 0.5) // interior reflection
1583 f += ((dot * 2) - 1);
1584 else if (dot < -0.5) // exterior reflection
1585 f += ((dot * -2) - 1);
1587 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1588 VectorNormalize(normal);
1589 dot = DotProduct(normal, light);
1590 if (dot > 0.5) // interior reflection
1591 f += ((dot * 2) - 1);
1592 else if (dot < -0.5) // exterior reflection
1593 f += ((dot * -2) - 1);
1595 f += 16; // just to give it a haze so you can see the outline
1596 f = bound(0, f, 255);
1597 return (unsigned char) f;
1603 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1605 int basex, basey, y;
1606 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1607 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1608 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1609 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1612 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1615 float cx, cy, dx, dy, f, iradius;
1617 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1618 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1619 iradius = 1.0f / radius;
1620 alpha *= (1.0f / 255.0f);
1621 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1623 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1627 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1632 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1633 d[0] += (int)(f * (blue - d[0]));
1634 d[1] += (int)(f * (green - d[1]));
1635 d[2] += (int)(f * (red - d[2]));
1641 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1644 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1646 data[0] = bound(minb, data[0], maxb);
1647 data[1] = bound(ming, data[1], maxg);
1648 data[2] = bound(minr, data[2], maxr);
1652 void particletextureinvert(unsigned char *data)
1655 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1657 data[0] = 255 - data[0];
1658 data[1] = 255 - data[1];
1659 data[2] = 255 - data[2];
1663 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1664 static void R_InitBloodTextures (unsigned char *particletexturedata)
1667 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1670 for (i = 0;i < 8;i++)
1672 memset(&data[0][0][0], 255, sizeof(data));
1673 for (k = 0;k < 24;k++)
1674 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1675 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1676 particletextureinvert(&data[0][0][0]);
1677 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1681 for (i = 0;i < 8;i++)
1683 memset(&data[0][0][0], 255, sizeof(data));
1685 for (j = 1;j < 10;j++)
1686 for (k = min(j, m - 1);k < m;k++)
1687 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1688 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1689 particletextureinvert(&data[0][0][0]);
1690 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1695 //uncomment this to make engine save out particle font to a tga file when run
1696 //#define DUMPPARTICLEFONT
1698 static void R_InitParticleTexture (void)
1700 int x, y, d, i, k, m;
1704 // a note: decals need to modulate (multiply) the background color to
1705 // properly darken it (stain), and they need to be able to alpha fade,
1706 // this is a very difficult challenge because it means fading to white
1707 // (no change to background) rather than black (darkening everything
1708 // behind the whole decal polygon), and to accomplish this the texture is
1709 // inverted (dark red blood on white background becomes brilliant cyan
1710 // and white on black background) so we can alpha fade it to black, then
1711 // we invert it again during the blendfunc to make it work...
1713 #ifndef DUMPPARTICLEFONT
1714 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1715 if (!particlefonttexture)
1718 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1719 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1720 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1723 for (i = 0;i < 8;i++)
1725 memset(&data[0][0][0], 255, sizeof(data));
1728 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1730 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1731 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1733 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1735 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1736 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1738 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1739 d = (noise2[y][x] - 128) * 3 + 192;
1741 d = (int)(d * (1-(dx*dx+dy*dy)));
1742 d = (d * noise1[y][x]) >> 7;
1743 d = bound(0, d, 255);
1744 data[y][x][3] = (unsigned char) d;
1751 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1755 memset(&data[0][0][0], 255, sizeof(data));
1756 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1758 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1759 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1761 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1762 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1763 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1766 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1769 memset(&data[0][0][0], 255, sizeof(data));
1770 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1772 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1773 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1775 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1776 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1777 d = bound(0, d, 255);
1778 data[y][x][3] = (unsigned char) d;
1781 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1784 memset(&data[0][0][0], 255, sizeof(data));
1785 light[0] = 1;light[1] = 1;light[2] = 1;
1786 VectorNormalize(light);
1787 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1789 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1790 // stretch upper half of bubble by +50% and shrink lower half by -50%
1791 // (this gives an elongated teardrop shape)
1793 dy = (dy - 0.5f) * 2.0f;
1795 dy = (dy - 0.5f) / 1.5f;
1796 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1798 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1799 // shrink bubble width to half
1801 data[y][x][3] = shadebubble(dx, dy, light);
1804 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1807 memset(&data[0][0][0], 255, sizeof(data));
1808 light[0] = 1;light[1] = 1;light[2] = 1;
1809 VectorNormalize(light);
1810 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1812 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1813 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1815 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1816 data[y][x][3] = shadebubble(dx, dy, light);
1819 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1821 // Blood particles and blood decals
1822 R_InitBloodTextures (particletexturedata);
1825 for (i = 0;i < 8;i++)
1827 memset(&data[0][0][0], 255, sizeof(data));
1828 for (k = 0;k < 12;k++)
1829 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1830 for (k = 0;k < 3;k++)
1831 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1832 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1833 particletextureinvert(&data[0][0][0]);
1834 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1837 #ifdef DUMPPARTICLEFONT
1838 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1841 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1843 Mem_Free(particletexturedata);
1845 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1847 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1848 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1849 particletexture[i].texture = particlefonttexture;
1850 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1851 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1852 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1853 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1856 #ifndef DUMPPARTICLEFONT
1857 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1858 if (!particletexture[tex_beam].texture)
1861 unsigned char noise3[64][64], data2[64][16][4];
1863 fractalnoise(&noise3[0][0], 64, 4);
1865 for (y = 0;y < 64;y++)
1867 dy = (y - 0.5f*64) / (64*0.5f-1);
1868 for (x = 0;x < 16;x++)
1870 dx = (x - 0.5f*16) / (16*0.5f-2);
1871 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1872 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1873 data2[y][x][3] = 255;
1877 #ifdef DUMPPARTICLEFONT
1878 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1880 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1882 particletexture[tex_beam].s1 = 0;
1883 particletexture[tex_beam].t1 = 0;
1884 particletexture[tex_beam].s2 = 1;
1885 particletexture[tex_beam].t2 = 1;
1888 static void r_part_start(void)
1890 particletexturepool = R_AllocTexturePool();
1891 R_InitParticleTexture ();
1892 CL_Particles_LoadEffectInfo();
1895 static void r_part_shutdown(void)
1897 R_FreeTexturePool(&particletexturepool);
1900 static void r_part_newmap(void)
1904 #define BATCHSIZE 256
1905 int particle_element3i[BATCHSIZE*6];
1907 void R_Particles_Init (void)
1910 for (i = 0;i < BATCHSIZE;i++)
1912 particle_element3i[i*6+0] = i*4+0;
1913 particle_element3i[i*6+1] = i*4+1;
1914 particle_element3i[i*6+2] = i*4+2;
1915 particle_element3i[i*6+3] = i*4+0;
1916 particle_element3i[i*6+4] = i*4+2;
1917 particle_element3i[i*6+5] = i*4+3;
1920 Cvar_RegisterVariable(&r_drawparticles);
1921 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1922 Cvar_RegisterVariable(&r_drawdecals);
1923 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1924 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1927 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1929 int surfacelistindex;
1931 float *v3f, *t2f, *c4f;
1932 particletexture_t *tex;
1933 float right[3], up[3], size, ca;
1934 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1935 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1937 r_refdef.stats.decals += numsurfaces;
1938 R_Mesh_Matrix(&identitymatrix);
1939 R_Mesh_ResetTextureState();
1940 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1941 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1942 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1943 R_SetupGenericShader(true);
1944 GL_DepthMask(false);
1945 GL_DepthRange(0, 1);
1946 GL_PolygonOffset(0, 0);
1948 GL_CullFace(GL_NONE);
1950 // generate all the vertices at once
1951 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1953 d = cl.decals + surfacelist[surfacelistindex];
1956 c4f = particle_color4f + 16*surfacelistindex;
1957 ca = d->alpha * alphascale;
1958 if (r_refdef.fogenabled)
1959 ca *= FogPoint_World(d->org);
1960 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1961 Vector4Copy(c4f, c4f + 4);
1962 Vector4Copy(c4f, c4f + 8);
1963 Vector4Copy(c4f, c4f + 12);
1965 // calculate vertex positions
1966 size = d->size * cl_particles_size.value;
1967 VectorVectors(d->normal, right, up);
1968 VectorScale(right, size, right);
1969 VectorScale(up, size, up);
1970 v3f = particle_vertex3f + 12*surfacelistindex;
1971 v3f[ 0] = d->org[0] - right[0] - up[0];
1972 v3f[ 1] = d->org[1] - right[1] - up[1];
1973 v3f[ 2] = d->org[2] - right[2] - up[2];
1974 v3f[ 3] = d->org[0] - right[0] + up[0];
1975 v3f[ 4] = d->org[1] - right[1] + up[1];
1976 v3f[ 5] = d->org[2] - right[2] + up[2];
1977 v3f[ 6] = d->org[0] + right[0] + up[0];
1978 v3f[ 7] = d->org[1] + right[1] + up[1];
1979 v3f[ 8] = d->org[2] + right[2] + up[2];
1980 v3f[ 9] = d->org[0] + right[0] - up[0];
1981 v3f[10] = d->org[1] + right[1] - up[1];
1982 v3f[11] = d->org[2] + right[2] - up[2];
1984 // calculate texcoords
1985 tex = &particletexture[d->texnum];
1986 t2f = particle_texcoord2f + 8*surfacelistindex;
1987 t2f[0] = tex->s1;t2f[1] = tex->t2;
1988 t2f[2] = tex->s1;t2f[3] = tex->t1;
1989 t2f[4] = tex->s2;t2f[5] = tex->t1;
1990 t2f[6] = tex->s2;t2f[7] = tex->t2;
1993 // now render the decals all at once
1994 // (this assumes they all use one particle font texture!)
1995 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1996 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
1997 GL_LockArrays(0, numsurfaces*4);
1998 R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
1999 GL_LockArrays(0, 0);
2002 void R_DrawDecals (void)
2010 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2011 cl.decals_updatetime += frametime;
2013 // LordHavoc: early out conditions
2014 if ((!cl.num_decals) || (!r_drawdecals.integer))
2017 decalfade = frametime * 256 / cl_decals_fadetime.value;
2018 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2019 drawdist2 = drawdist2*drawdist2;
2021 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2023 if (!decal->typeindex)
2026 if (cl.time > decal->time2 + cl_decals_time.value)
2028 decal->alpha -= decalfade;
2029 if (decal->alpha <= 0)
2035 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2037 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2038 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2044 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2045 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2048 decal->typeindex = 0;
2049 if (cl.free_decal > i)
2053 // reduce cl.num_decals if possible
2054 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2057 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2059 decal_t *olddecals = cl.decals;
2060 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2061 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2062 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2063 Mem_Free(olddecals);
2067 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2069 int surfacelistindex;
2070 int batchstart, batchcount;
2071 const particle_t *p;
2073 rtexture_t *texture;
2074 float *v3f, *t2f, *c4f;
2075 particletexture_t *tex;
2076 float up2[3], v[3], right[3], up[3], fog, ifog, size;
2077 float ambient[3], diffuse[3], diffusenormal[3];
2078 vec4_t colormultiplier;
2079 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2081 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2083 r_refdef.stats.particles += numsurfaces;
2084 R_Mesh_Matrix(&identitymatrix);
2085 R_Mesh_ResetTextureState();
2086 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2087 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2088 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2089 R_SetupGenericShader(true);
2090 GL_DepthMask(false);
2091 GL_DepthRange(0, 1);
2092 GL_PolygonOffset(0, 0);
2094 GL_CullFace(GL_NONE);
2096 // first generate all the vertices at once
2097 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2099 p = cl.particles + surfacelist[surfacelistindex];
2101 blendmode = particletype[p->typeindex].blendmode;
2103 c4f[0] = p->color[0] * colormultiplier[0];
2104 c4f[1] = p->color[1] * colormultiplier[1];
2105 c4f[2] = p->color[2] * colormultiplier[2];
2106 c4f[3] = p->alpha * colormultiplier[3];
2111 // additive and modulate can just fade out in fog (this is correct)
2112 if (r_refdef.fogenabled)
2113 c4f[3] *= FogPoint_World(p->org);
2114 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2121 // note: lighting is not cheap!
2122 if (particletype[p->typeindex].lighting)
2124 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2125 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2126 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2127 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2129 // mix in the fog color
2130 if (r_refdef.fogenabled)
2132 fog = FogPoint_World(p->org);
2134 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2135 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2136 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2140 // copy the color into the other three vertices
2141 Vector4Copy(c4f, c4f + 4);
2142 Vector4Copy(c4f, c4f + 8);
2143 Vector4Copy(c4f, c4f + 12);
2145 size = p->size * cl_particles_size.value;
2146 tex = &particletexture[p->texnum];
2147 switch(particletype[p->typeindex].orientation)
2149 case PARTICLE_BILLBOARD:
2150 VectorScale(r_refdef.view.left, -size, right);
2151 VectorScale(r_refdef.view.up, size, up);
2152 v3f[ 0] = p->org[0] - right[0] - up[0];
2153 v3f[ 1] = p->org[1] - right[1] - up[1];
2154 v3f[ 2] = p->org[2] - right[2] - up[2];
2155 v3f[ 3] = p->org[0] - right[0] + up[0];
2156 v3f[ 4] = p->org[1] - right[1] + up[1];
2157 v3f[ 5] = p->org[2] - right[2] + up[2];
2158 v3f[ 6] = p->org[0] + right[0] + up[0];
2159 v3f[ 7] = p->org[1] + right[1] + up[1];
2160 v3f[ 8] = p->org[2] + right[2] + up[2];
2161 v3f[ 9] = p->org[0] + right[0] - up[0];
2162 v3f[10] = p->org[1] + right[1] - up[1];
2163 v3f[11] = p->org[2] + right[2] - up[2];
2164 t2f[0] = tex->s1;t2f[1] = tex->t2;
2165 t2f[2] = tex->s1;t2f[3] = tex->t1;
2166 t2f[4] = tex->s2;t2f[5] = tex->t1;
2167 t2f[6] = tex->s2;t2f[7] = tex->t2;
2169 case PARTICLE_ORIENTED_DOUBLESIDED:
2170 VectorVectors(p->vel, right, up);
2171 VectorScale(right, size, right);
2172 VectorScale(up, size, up);
2173 v3f[ 0] = p->org[0] - right[0] - up[0];
2174 v3f[ 1] = p->org[1] - right[1] - up[1];
2175 v3f[ 2] = p->org[2] - right[2] - up[2];
2176 v3f[ 3] = p->org[0] - right[0] + up[0];
2177 v3f[ 4] = p->org[1] - right[1] + up[1];
2178 v3f[ 5] = p->org[2] - right[2] + up[2];
2179 v3f[ 6] = p->org[0] + right[0] + up[0];
2180 v3f[ 7] = p->org[1] + right[1] + up[1];
2181 v3f[ 8] = p->org[2] + right[2] + up[2];
2182 v3f[ 9] = p->org[0] + right[0] - up[0];
2183 v3f[10] = p->org[1] + right[1] - up[1];
2184 v3f[11] = p->org[2] + right[2] - up[2];
2185 t2f[0] = tex->s1;t2f[1] = tex->t2;
2186 t2f[2] = tex->s1;t2f[3] = tex->t1;
2187 t2f[4] = tex->s2;t2f[5] = tex->t1;
2188 t2f[6] = tex->s2;t2f[7] = tex->t2;
2190 case PARTICLE_SPARK:
2191 VectorMA(p->org, -0.04, p->vel, v);
2192 VectorMA(p->org, 0.04, p->vel, up2);
2193 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2194 t2f[0] = tex->s1;t2f[1] = tex->t2;
2195 t2f[2] = tex->s1;t2f[3] = tex->t1;
2196 t2f[4] = tex->s2;t2f[5] = tex->t1;
2197 t2f[6] = tex->s2;t2f[7] = tex->t2;
2200 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2201 VectorSubtract(p->vel, p->org, up);
2202 VectorNormalize(up);
2203 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2204 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2205 t2f[0] = 1;t2f[1] = v[0];
2206 t2f[2] = 0;t2f[3] = v[0];
2207 t2f[4] = 0;t2f[5] = v[1];
2208 t2f[6] = 1;t2f[7] = v[1];
2213 // now render batches of particles based on blendmode and texture
2216 GL_LockArrays(0, numsurfaces*4);
2219 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2221 p = cl.particles + surfacelist[surfacelistindex];
2223 if (blendmode != particletype[p->typeindex].blendmode)
2225 blendmode = particletype[p->typeindex].blendmode;
2229 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2232 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2235 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2239 if (texture != particletexture[p->texnum].texture)
2241 texture = particletexture[p->texnum].texture;
2242 R_Mesh_TexBind(0, R_GetTexture(texture));
2245 // iterate until we find a change in settings
2246 batchstart = surfacelistindex++;
2247 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2249 p = cl.particles + surfacelist[surfacelistindex];
2250 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2254 batchcount = surfacelistindex - batchstart;
2255 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2257 GL_LockArrays(0, 0);
2260 void R_DrawParticles (void)
2263 float minparticledist;
2265 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2271 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2272 cl.particles_updatetime += frametime;
2274 // LordHavoc: early out conditions
2275 if ((!cl.num_particles) || (!r_drawparticles.integer))
2278 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2279 gravity = frametime * cl.movevars_gravity;
2280 dvel = 1+4*frametime;
2281 decalfade = frametime * 255 / cl_decals_fadetime.value;
2282 update = frametime > 0;
2283 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2284 drawdist2 = drawdist2*drawdist2;
2286 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2290 if (cl.free_particle > i)
2291 cl.free_particle = i;
2297 if (p->delayedspawn > cl.time)
2299 p->delayedspawn = 0;
2303 p->size += p->sizeincrease * frametime;
2304 p->alpha -= p->alphafade * frametime;
2306 if (p->alpha <= 0 || p->die <= cl.time)
2309 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2311 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2313 if (p->typeindex == pt_blood)
2314 p->size += frametime * 8;
2316 p->vel[2] -= p->gravity * gravity;
2317 f = 1.0f - min(p->liquidfriction * frametime, 1);
2318 VectorScale(p->vel, f, p->vel);
2322 p->vel[2] -= p->gravity * gravity;
2325 f = 1.0f - min(p->airfriction * frametime, 1);
2326 VectorScale(p->vel, f, p->vel);
2330 VectorCopy(p->org, oldorg);
2331 VectorMA(p->org, frametime, p->vel, p->org);
2332 if (p->bounce && cl.time >= p->delayedcollisions)
2334 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);
2335 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2336 // or if the trace hit something flagged as NOIMPACT
2337 // then remove the particle
2338 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2340 VectorCopy(trace.endpos, p->org);
2341 // react if the particle hit something
2342 if (trace.fraction < 1)
2344 VectorCopy(trace.endpos, p->org);
2345 if (p->typeindex == pt_blood)
2347 // blood - splash on solid
2348 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2350 if (cl_stainmaps.integer)
2351 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)));
2352 if (cl_decals.integer)
2354 // create a decal for the blood splat
2355 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);
2359 else if (p->bounce < 0)
2361 // bounce -1 means remove on impact
2366 // anything else - bounce off solid
2367 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2368 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2369 if (DotProduct(p->vel, p->vel) < 0.03)
2370 VectorClear(p->vel);
2376 if (p->typeindex != pt_static)
2378 switch (p->typeindex)
2380 case pt_entityparticle:
2381 // particle that removes itself after one rendered frame
2388 a = CL_PointSuperContents(p->org);
2389 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2393 a = CL_PointSuperContents(p->org);
2394 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2398 a = CL_PointSuperContents(p->org);
2399 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2403 if (cl.time > p->time2)
2406 p->time2 = cl.time + (rand() & 3) * 0.1;
2407 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2408 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2410 a = CL_PointSuperContents(p->org);
2411 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2419 else if (p->delayedspawn)
2422 // don't render particles too close to the view (they chew fillrate)
2423 // also don't render particles behind the view (useless)
2424 // further checks to cull to the frustum would be too slow here
2425 switch(p->typeindex)
2428 // beams have no culling
2429 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2432 // anything else just has to be in front of the viewer and visible at this distance
2433 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2434 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2441 if (cl.free_particle > i)
2442 cl.free_particle = i;
2445 // reduce cl.num_particles if possible
2446 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2449 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2451 particle_t *oldparticles = cl.particles;
2452 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2453 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2454 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2455 Mem_Free(oldparticles);