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 || !particleeffectname[effectnameindex][0])
1123 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1124 return; // no such effect
1126 VectorLerp(originmins, 0.5, originmaxs, center);
1127 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1129 int effectinfoindex;
1132 particleeffectinfo_t *info;
1134 vec3_t centervelocity;
1140 qboolean underwater;
1141 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1142 VectorLerp(originmins, 0.5, originmaxs, center);
1143 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1144 supercontents = CL_PointSuperContents(center);
1145 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1146 VectorSubtract(originmaxs, originmins, traildir);
1147 traillen = VectorLength(traildir);
1148 VectorNormalize(traildir);
1149 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1151 if (info->effectnameindex == effectnameindex)
1154 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1156 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1159 // spawn a dlight if requested
1160 if (info->lightradiusstart > 0 && spawndlight)
1162 matrix4x4_t tempmatrix;
1163 if (info->trailspacing > 0)
1164 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1166 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1167 if (info->lighttime > 0 && info->lightradiusfade > 0)
1169 // light flash (explosion, etc)
1170 // called when effect starts
1171 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);
1176 // called by CL_LinkNetworkEntity
1177 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1178 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);
1182 if (!spawnparticles)
1187 if (info->tex[1] > info->tex[0])
1189 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1190 tex = min(tex, info->tex[1] - 1);
1192 if (info->particletype == pt_decal)
1193 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]);
1194 else if (info->particletype == pt_beam)
1195 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);
1198 if (!cl_particles.integer)
1200 switch (info->particletype)
1202 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1203 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1204 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1205 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1206 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1207 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1210 VectorCopy(originmins, trailpos);
1211 if (info->trailspacing > 0)
1213 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1214 trailstep = info->trailspacing / cl_particles_quality.value;
1218 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1221 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1222 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1224 if (info->tex[1] > info->tex[0])
1226 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1227 tex = min(tex, info->tex[1] - 1);
1231 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1232 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1233 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1236 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);
1238 VectorMA(trailpos, trailstep, traildir, trailpos);
1245 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1248 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)
1250 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1258 void CL_EntityParticles (const entity_t *ent)
1261 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1262 static vec3_t avelocities[NUMVERTEXNORMALS];
1263 if (!cl_particles.integer) return;
1264 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1266 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1268 if (!avelocities[0][0])
1269 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1270 avelocities[0][i] = lhrandom(0, 2.55);
1272 for (i = 0;i < NUMVERTEXNORMALS;i++)
1274 yaw = cl.time * avelocities[i][0];
1275 pitch = cl.time * avelocities[i][1];
1276 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1277 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1278 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1279 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);
1284 void CL_ReadPointFile_f (void)
1286 vec3_t org, leakorg;
1288 char *pointfile = NULL, *pointfilepos, *t, tchar;
1289 char name[MAX_OSPATH];
1294 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1295 strlcat (name, ".pts", sizeof (name));
1296 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1299 Con_Printf("Could not open %s\n", name);
1303 Con_Printf("Reading %s...\n", name);
1304 VectorClear(leakorg);
1307 pointfilepos = pointfile;
1308 while (*pointfilepos)
1310 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1315 while (*t && *t != '\n' && *t != '\r')
1319 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1325 VectorCopy(org, leakorg);
1328 if (cl.num_particles < cl.max_particles - 3)
1331 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);
1334 Mem_Free(pointfile);
1335 VectorCopy(leakorg, org);
1336 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1338 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);
1339 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);
1340 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);
1345 CL_ParseParticleEffect
1347 Parse an effect out of the server message
1350 void CL_ParseParticleEffect (void)
1353 int i, count, msgcount, color;
1355 MSG_ReadVector(org, cls.protocol);
1356 for (i=0 ; i<3 ; i++)
1357 dir[i] = MSG_ReadChar ();
1358 msgcount = MSG_ReadByte ();
1359 color = MSG_ReadByte ();
1361 if (msgcount == 255)
1366 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1371 CL_ParticleExplosion
1375 void CL_ParticleExplosion (const vec3_t org)
1381 if (cl_stainmaps.integer)
1382 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1383 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1385 if (cl_particles_quake.integer)
1387 for (i = 0;i < 1024;i++)
1393 color = particlepalette[ramp1[r]];
1394 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);
1398 color = particlepalette[ramp2[r]];
1399 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);
1405 i = CL_PointSuperContents(org);
1406 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1408 if (cl_particles.integer && cl_particles_bubbles.integer)
1409 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1410 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);
1414 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1416 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1420 for (k = 0;k < 16;k++)
1423 VectorMA(org, 128, v2, v);
1424 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1425 if (trace.fraction >= 0.1)
1428 VectorSubtract(trace.endpos, org, v2);
1429 VectorScale(v2, 2.0f, v2);
1430 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);
1436 if (cl_particles_explosions_shell.integer)
1437 R_NewExplosion(org);
1442 CL_ParticleExplosion2
1446 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1449 if (!cl_particles.integer) return;
1451 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1453 k = particlepalette[colorStart + (i % colorLength)];
1454 if (cl_particles_quake.integer)
1455 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);
1457 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);
1461 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1463 if (cl_particles_sparks.integer)
1465 sparkcount *= cl_particles_quality.value;
1466 while(sparkcount-- > 0)
1467 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);
1471 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1473 if (cl_particles_smoke.integer)
1475 smokecount *= cl_particles_quality.value;
1476 while(smokecount-- > 0)
1477 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);
1481 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)
1484 if (!cl_particles.integer) return;
1486 count = (int)(count * cl_particles_quality.value);
1489 k = particlepalette[colorbase + (rand()&3)];
1490 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);
1494 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1497 float minz, maxz, lifetime = 30;
1498 if (!cl_particles.integer) return;
1499 if (dir[2] < 0) // falling
1501 minz = maxs[2] + dir[2] * 0.1;
1504 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1509 maxz = maxs[2] + dir[2] * 0.1;
1511 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1514 count = (int)(count * cl_particles_quality.value);
1519 if (!cl_particles_rain.integer) break;
1520 count *= 4; // ick, this should be in the mod or maps?
1524 k = particlepalette[colorbase + (rand()&3)];
1525 if (gamemode == GAME_GOODVSBAD2)
1526 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);
1528 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);
1532 if (!cl_particles_snow.integer) break;
1535 k = particlepalette[colorbase + (rand()&3)];
1536 if (gamemode == GAME_GOODVSBAD2)
1537 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);
1539 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);
1543 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1547 #define MAX_PARTICLETEXTURES 64
1548 // particletexture_t is a rectangle in the particlefonttexture
1549 typedef struct particletexture_s
1551 rtexture_t *texture;
1552 float s1, t1, s2, t2;
1556 static rtexturepool_t *particletexturepool;
1557 static rtexture_t *particlefonttexture;
1558 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1560 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1561 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1562 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1563 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1565 #define PARTICLETEXTURESIZE 64
1566 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1568 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1572 dz = 1 - (dx*dx+dy*dy);
1573 if (dz > 0) // it does hit the sphere
1577 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1578 VectorNormalize(normal);
1579 dot = DotProduct(normal, light);
1580 if (dot > 0.5) // interior reflection
1581 f += ((dot * 2) - 1);
1582 else if (dot < -0.5) // exterior reflection
1583 f += ((dot * -2) - 1);
1585 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1586 VectorNormalize(normal);
1587 dot = DotProduct(normal, light);
1588 if (dot > 0.5) // interior reflection
1589 f += ((dot * 2) - 1);
1590 else if (dot < -0.5) // exterior reflection
1591 f += ((dot * -2) - 1);
1593 f += 16; // just to give it a haze so you can see the outline
1594 f = bound(0, f, 255);
1595 return (unsigned char) f;
1601 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1603 int basex, basey, y;
1604 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1605 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1606 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1607 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1610 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1613 float cx, cy, dx, dy, f, iradius;
1615 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1616 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1617 iradius = 1.0f / radius;
1618 alpha *= (1.0f / 255.0f);
1619 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1621 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1625 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1630 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1631 d[0] += (int)(f * (blue - d[0]));
1632 d[1] += (int)(f * (green - d[1]));
1633 d[2] += (int)(f * (red - d[2]));
1639 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1642 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1644 data[0] = bound(minb, data[0], maxb);
1645 data[1] = bound(ming, data[1], maxg);
1646 data[2] = bound(minr, data[2], maxr);
1650 void particletextureinvert(unsigned char *data)
1653 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1655 data[0] = 255 - data[0];
1656 data[1] = 255 - data[1];
1657 data[2] = 255 - data[2];
1661 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1662 static void R_InitBloodTextures (unsigned char *particletexturedata)
1665 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1668 for (i = 0;i < 8;i++)
1670 memset(&data[0][0][0], 255, sizeof(data));
1671 for (k = 0;k < 24;k++)
1672 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1673 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1674 particletextureinvert(&data[0][0][0]);
1675 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1679 for (i = 0;i < 8;i++)
1681 memset(&data[0][0][0], 255, sizeof(data));
1683 for (j = 1;j < 10;j++)
1684 for (k = min(j, m - 1);k < m;k++)
1685 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1686 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1687 particletextureinvert(&data[0][0][0]);
1688 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1693 //uncomment this to make engine save out particle font to a tga file when run
1694 //#define DUMPPARTICLEFONT
1696 static void R_InitParticleTexture (void)
1698 int x, y, d, i, k, m;
1702 // a note: decals need to modulate (multiply) the background color to
1703 // properly darken it (stain), and they need to be able to alpha fade,
1704 // this is a very difficult challenge because it means fading to white
1705 // (no change to background) rather than black (darkening everything
1706 // behind the whole decal polygon), and to accomplish this the texture is
1707 // inverted (dark red blood on white background becomes brilliant cyan
1708 // and white on black background) so we can alpha fade it to black, then
1709 // we invert it again during the blendfunc to make it work...
1711 #ifndef DUMPPARTICLEFONT
1712 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1713 if (!particlefonttexture)
1716 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1717 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1718 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1721 for (i = 0;i < 8;i++)
1723 memset(&data[0][0][0], 255, sizeof(data));
1726 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1728 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1729 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1731 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1733 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1734 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1736 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1737 d = (noise2[y][x] - 128) * 3 + 192;
1739 d = (int)(d * (1-(dx*dx+dy*dy)));
1740 d = (d * noise1[y][x]) >> 7;
1741 d = bound(0, d, 255);
1742 data[y][x][3] = (unsigned char) d;
1749 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1753 memset(&data[0][0][0], 255, sizeof(data));
1754 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1756 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1757 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1759 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1760 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1761 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1764 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1767 memset(&data[0][0][0], 255, sizeof(data));
1768 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1770 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1771 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1773 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1774 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1775 d = bound(0, d, 255);
1776 data[y][x][3] = (unsigned char) d;
1779 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1782 memset(&data[0][0][0], 255, sizeof(data));
1783 light[0] = 1;light[1] = 1;light[2] = 1;
1784 VectorNormalize(light);
1785 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1787 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1788 // stretch upper half of bubble by +50% and shrink lower half by -50%
1789 // (this gives an elongated teardrop shape)
1791 dy = (dy - 0.5f) * 2.0f;
1793 dy = (dy - 0.5f) / 1.5f;
1794 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1796 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1797 // shrink bubble width to half
1799 data[y][x][3] = shadebubble(dx, dy, light);
1802 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1805 memset(&data[0][0][0], 255, sizeof(data));
1806 light[0] = 1;light[1] = 1;light[2] = 1;
1807 VectorNormalize(light);
1808 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1810 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1811 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1813 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1814 data[y][x][3] = shadebubble(dx, dy, light);
1817 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1819 // Blood particles and blood decals
1820 R_InitBloodTextures (particletexturedata);
1823 for (i = 0;i < 8;i++)
1825 memset(&data[0][0][0], 255, sizeof(data));
1826 for (k = 0;k < 12;k++)
1827 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1828 for (k = 0;k < 3;k++)
1829 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1830 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1831 particletextureinvert(&data[0][0][0]);
1832 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1835 #ifdef DUMPPARTICLEFONT
1836 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1839 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1841 Mem_Free(particletexturedata);
1843 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1845 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1846 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1847 particletexture[i].texture = particlefonttexture;
1848 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1849 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1850 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1851 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1854 #ifndef DUMPPARTICLEFONT
1855 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1856 if (!particletexture[tex_beam].texture)
1859 unsigned char noise3[64][64], data2[64][16][4];
1861 fractalnoise(&noise3[0][0], 64, 4);
1863 for (y = 0;y < 64;y++)
1865 dy = (y - 0.5f*64) / (64*0.5f-1);
1866 for (x = 0;x < 16;x++)
1868 dx = (x - 0.5f*16) / (16*0.5f-2);
1869 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1870 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1871 data2[y][x][3] = 255;
1875 #ifdef DUMPPARTICLEFONT
1876 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1878 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1880 particletexture[tex_beam].s1 = 0;
1881 particletexture[tex_beam].t1 = 0;
1882 particletexture[tex_beam].s2 = 1;
1883 particletexture[tex_beam].t2 = 1;
1886 static void r_part_start(void)
1888 particletexturepool = R_AllocTexturePool();
1889 R_InitParticleTexture ();
1890 CL_Particles_LoadEffectInfo();
1893 static void r_part_shutdown(void)
1895 R_FreeTexturePool(&particletexturepool);
1898 static void r_part_newmap(void)
1902 #define BATCHSIZE 256
1903 int particle_element3i[BATCHSIZE*6];
1905 void R_Particles_Init (void)
1908 for (i = 0;i < BATCHSIZE;i++)
1910 particle_element3i[i*6+0] = i*4+0;
1911 particle_element3i[i*6+1] = i*4+1;
1912 particle_element3i[i*6+2] = i*4+2;
1913 particle_element3i[i*6+3] = i*4+0;
1914 particle_element3i[i*6+4] = i*4+2;
1915 particle_element3i[i*6+5] = i*4+3;
1918 Cvar_RegisterVariable(&r_drawparticles);
1919 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1920 Cvar_RegisterVariable(&r_drawdecals);
1921 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1922 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1925 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1927 int surfacelistindex;
1929 float *v3f, *t2f, *c4f;
1930 particletexture_t *tex;
1931 float right[3], up[3], size, ca;
1932 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1933 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1935 r_refdef.stats.decals += numsurfaces;
1936 R_Mesh_Matrix(&identitymatrix);
1937 R_Mesh_ResetTextureState();
1938 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1939 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1940 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1941 R_SetupGenericShader(true);
1942 GL_DepthMask(false);
1943 GL_DepthRange(0, 1);
1944 GL_PolygonOffset(0, 0);
1946 GL_CullFace(GL_NONE);
1948 // generate all the vertices at once
1949 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1951 d = cl.decals + surfacelist[surfacelistindex];
1954 c4f = particle_color4f + 16*surfacelistindex;
1955 ca = d->alpha * alphascale;
1956 if (r_refdef.fogenabled)
1957 ca *= FogPoint_World(d->org);
1958 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1959 Vector4Copy(c4f, c4f + 4);
1960 Vector4Copy(c4f, c4f + 8);
1961 Vector4Copy(c4f, c4f + 12);
1963 // calculate vertex positions
1964 size = d->size * cl_particles_size.value;
1965 VectorVectors(d->normal, right, up);
1966 VectorScale(right, size, right);
1967 VectorScale(up, size, up);
1968 v3f = particle_vertex3f + 12*surfacelistindex;
1969 v3f[ 0] = d->org[0] - right[0] - up[0];
1970 v3f[ 1] = d->org[1] - right[1] - up[1];
1971 v3f[ 2] = d->org[2] - right[2] - up[2];
1972 v3f[ 3] = d->org[0] - right[0] + up[0];
1973 v3f[ 4] = d->org[1] - right[1] + up[1];
1974 v3f[ 5] = d->org[2] - right[2] + up[2];
1975 v3f[ 6] = d->org[0] + right[0] + up[0];
1976 v3f[ 7] = d->org[1] + right[1] + up[1];
1977 v3f[ 8] = d->org[2] + right[2] + up[2];
1978 v3f[ 9] = d->org[0] + right[0] - up[0];
1979 v3f[10] = d->org[1] + right[1] - up[1];
1980 v3f[11] = d->org[2] + right[2] - up[2];
1982 // calculate texcoords
1983 tex = &particletexture[d->texnum];
1984 t2f = particle_texcoord2f + 8*surfacelistindex;
1985 t2f[0] = tex->s1;t2f[1] = tex->t2;
1986 t2f[2] = tex->s1;t2f[3] = tex->t1;
1987 t2f[4] = tex->s2;t2f[5] = tex->t1;
1988 t2f[6] = tex->s2;t2f[7] = tex->t2;
1991 // now render the decals all at once
1992 // (this assumes they all use one particle font texture!)
1993 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1994 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
1995 GL_LockArrays(0, numsurfaces*4);
1996 R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
1997 GL_LockArrays(0, 0);
2000 void R_DrawDecals (void)
2008 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2009 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2011 // LordHavoc: early out conditions
2012 if ((!cl.num_decals) || (!r_drawdecals.integer))
2015 decalfade = frametime * 256 / cl_decals_fadetime.value;
2016 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2017 drawdist2 = drawdist2*drawdist2;
2019 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2021 if (!decal->typeindex)
2024 if (cl.time > decal->time2 + cl_decals_time.value)
2026 decal->alpha -= decalfade;
2027 if (decal->alpha <= 0)
2033 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2035 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2036 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2042 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))
2043 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2046 decal->typeindex = 0;
2047 if (cl.free_decal > i)
2051 // reduce cl.num_decals if possible
2052 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2055 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2057 decal_t *olddecals = cl.decals;
2058 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2059 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2060 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2061 Mem_Free(olddecals);
2065 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2067 int surfacelistindex;
2068 int batchstart, batchcount;
2069 const particle_t *p;
2071 rtexture_t *texture;
2072 float *v3f, *t2f, *c4f;
2073 particletexture_t *tex;
2074 float up2[3], v[3], right[3], up[3], fog, ifog, size;
2075 float ambient[3], diffuse[3], diffusenormal[3];
2076 vec4_t colormultiplier;
2077 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2079 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));
2081 r_refdef.stats.particles += numsurfaces;
2082 R_Mesh_Matrix(&identitymatrix);
2083 R_Mesh_ResetTextureState();
2084 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2085 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2086 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2087 R_SetupGenericShader(true);
2088 GL_DepthMask(false);
2089 GL_DepthRange(0, 1);
2090 GL_PolygonOffset(0, 0);
2092 GL_CullFace(GL_NONE);
2094 // first generate all the vertices at once
2095 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2097 p = cl.particles + surfacelist[surfacelistindex];
2099 blendmode = particletype[p->typeindex].blendmode;
2101 c4f[0] = p->color[0] * colormultiplier[0];
2102 c4f[1] = p->color[1] * colormultiplier[1];
2103 c4f[2] = p->color[2] * colormultiplier[2];
2104 c4f[3] = p->alpha * colormultiplier[3];
2109 // additive and modulate can just fade out in fog (this is correct)
2110 if (r_refdef.fogenabled)
2111 c4f[3] *= FogPoint_World(p->org);
2112 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2119 // note: lighting is not cheap!
2120 if (particletype[p->typeindex].lighting)
2122 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2123 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2124 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2125 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2127 // mix in the fog color
2128 if (r_refdef.fogenabled)
2130 fog = FogPoint_World(p->org);
2132 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2133 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2134 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2138 // copy the color into the other three vertices
2139 Vector4Copy(c4f, c4f + 4);
2140 Vector4Copy(c4f, c4f + 8);
2141 Vector4Copy(c4f, c4f + 12);
2143 size = p->size * cl_particles_size.value;
2144 tex = &particletexture[p->texnum];
2145 switch(particletype[p->typeindex].orientation)
2147 case PARTICLE_BILLBOARD:
2148 VectorScale(r_refdef.view.left, -size, right);
2149 VectorScale(r_refdef.view.up, size, up);
2150 v3f[ 0] = p->org[0] - right[0] - up[0];
2151 v3f[ 1] = p->org[1] - right[1] - up[1];
2152 v3f[ 2] = p->org[2] - right[2] - up[2];
2153 v3f[ 3] = p->org[0] - right[0] + up[0];
2154 v3f[ 4] = p->org[1] - right[1] + up[1];
2155 v3f[ 5] = p->org[2] - right[2] + up[2];
2156 v3f[ 6] = p->org[0] + right[0] + up[0];
2157 v3f[ 7] = p->org[1] + right[1] + up[1];
2158 v3f[ 8] = p->org[2] + right[2] + up[2];
2159 v3f[ 9] = p->org[0] + right[0] - up[0];
2160 v3f[10] = p->org[1] + right[1] - up[1];
2161 v3f[11] = p->org[2] + right[2] - up[2];
2162 t2f[0] = tex->s1;t2f[1] = tex->t2;
2163 t2f[2] = tex->s1;t2f[3] = tex->t1;
2164 t2f[4] = tex->s2;t2f[5] = tex->t1;
2165 t2f[6] = tex->s2;t2f[7] = tex->t2;
2167 case PARTICLE_ORIENTED_DOUBLESIDED:
2168 VectorVectors(p->vel, right, up);
2169 VectorScale(right, size, right);
2170 VectorScale(up, size, up);
2171 v3f[ 0] = p->org[0] - right[0] - up[0];
2172 v3f[ 1] = p->org[1] - right[1] - up[1];
2173 v3f[ 2] = p->org[2] - right[2] - up[2];
2174 v3f[ 3] = p->org[0] - right[0] + up[0];
2175 v3f[ 4] = p->org[1] - right[1] + up[1];
2176 v3f[ 5] = p->org[2] - right[2] + up[2];
2177 v3f[ 6] = p->org[0] + right[0] + up[0];
2178 v3f[ 7] = p->org[1] + right[1] + up[1];
2179 v3f[ 8] = p->org[2] + right[2] + up[2];
2180 v3f[ 9] = p->org[0] + right[0] - up[0];
2181 v3f[10] = p->org[1] + right[1] - up[1];
2182 v3f[11] = p->org[2] + right[2] - up[2];
2183 t2f[0] = tex->s1;t2f[1] = tex->t2;
2184 t2f[2] = tex->s1;t2f[3] = tex->t1;
2185 t2f[4] = tex->s2;t2f[5] = tex->t1;
2186 t2f[6] = tex->s2;t2f[7] = tex->t2;
2188 case PARTICLE_SPARK:
2189 VectorMA(p->org, -0.04, p->vel, v);
2190 VectorMA(p->org, 0.04, p->vel, up2);
2191 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2192 t2f[0] = tex->s1;t2f[1] = tex->t2;
2193 t2f[2] = tex->s1;t2f[3] = tex->t1;
2194 t2f[4] = tex->s2;t2f[5] = tex->t1;
2195 t2f[6] = tex->s2;t2f[7] = tex->t2;
2198 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2199 VectorSubtract(p->vel, p->org, up);
2200 VectorNormalize(up);
2201 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2202 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2203 t2f[0] = 1;t2f[1] = v[0];
2204 t2f[2] = 0;t2f[3] = v[0];
2205 t2f[4] = 0;t2f[5] = v[1];
2206 t2f[6] = 1;t2f[7] = v[1];
2211 // now render batches of particles based on blendmode and texture
2214 GL_LockArrays(0, numsurfaces*4);
2217 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2219 p = cl.particles + surfacelist[surfacelistindex];
2221 if (blendmode != particletype[p->typeindex].blendmode)
2223 blendmode = particletype[p->typeindex].blendmode;
2227 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2230 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2233 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2237 if (texture != particletexture[p->texnum].texture)
2239 texture = particletexture[p->texnum].texture;
2240 R_Mesh_TexBind(0, R_GetTexture(texture));
2243 // iterate until we find a change in settings
2244 batchstart = surfacelistindex++;
2245 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2247 p = cl.particles + surfacelist[surfacelistindex];
2248 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2252 batchcount = surfacelistindex - batchstart;
2253 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2255 GL_LockArrays(0, 0);
2258 void R_DrawParticles (void)
2261 float minparticledist;
2263 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2269 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2270 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2272 // LordHavoc: early out conditions
2273 if ((!cl.num_particles) || (!r_drawparticles.integer))
2276 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2277 gravity = frametime * cl.movevars_gravity;
2278 dvel = 1+4*frametime;
2279 decalfade = frametime * 255 / cl_decals_fadetime.value;
2280 update = frametime > 0;
2281 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2282 drawdist2 = drawdist2*drawdist2;
2284 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2288 if (cl.free_particle > i)
2289 cl.free_particle = i;
2295 if (p->delayedspawn > cl.time)
2297 p->delayedspawn = 0;
2301 p->size += p->sizeincrease * frametime;
2302 p->alpha -= p->alphafade * frametime;
2304 if (p->alpha <= 0 || p->die <= cl.time)
2307 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2309 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2311 if (p->typeindex == pt_blood)
2312 p->size += frametime * 8;
2314 p->vel[2] -= p->gravity * gravity;
2315 f = 1.0f - min(p->liquidfriction * frametime, 1);
2316 VectorScale(p->vel, f, p->vel);
2320 p->vel[2] -= p->gravity * gravity;
2323 f = 1.0f - min(p->airfriction * frametime, 1);
2324 VectorScale(p->vel, f, p->vel);
2328 VectorCopy(p->org, oldorg);
2329 VectorMA(p->org, frametime, p->vel, p->org);
2330 if (p->bounce && cl.time >= p->delayedcollisions)
2332 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);
2333 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2334 // or if the trace hit something flagged as NOIMPACT
2335 // then remove the particle
2336 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2338 VectorCopy(trace.endpos, p->org);
2339 // react if the particle hit something
2340 if (trace.fraction < 1)
2342 VectorCopy(trace.endpos, p->org);
2343 if (p->typeindex == pt_blood)
2345 // blood - splash on solid
2346 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2348 if (cl_stainmaps.integer)
2349 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)));
2350 if (cl_decals.integer)
2352 // create a decal for the blood splat
2353 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);
2357 else if (p->bounce < 0)
2359 // bounce -1 means remove on impact
2364 // anything else - bounce off solid
2365 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2366 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2367 if (DotProduct(p->vel, p->vel) < 0.03)
2368 VectorClear(p->vel);
2374 if (p->typeindex != pt_static)
2376 switch (p->typeindex)
2378 case pt_entityparticle:
2379 // particle that removes itself after one rendered frame
2386 a = CL_PointSuperContents(p->org);
2387 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2391 a = CL_PointSuperContents(p->org);
2392 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2396 a = CL_PointSuperContents(p->org);
2397 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2401 if (cl.time > p->time2)
2404 p->time2 = cl.time + (rand() & 3) * 0.1;
2405 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2406 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2408 a = CL_PointSuperContents(p->org);
2409 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2417 else if (p->delayedspawn)
2420 // don't render particles too close to the view (they chew fillrate)
2421 // also don't render particles behind the view (useless)
2422 // further checks to cull to the frustum would be too slow here
2423 switch(p->typeindex)
2426 // beams have no culling
2427 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2430 // anything else just has to be in front of the viewer and visible at this distance
2431 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2432 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2439 if (cl.free_particle > i)
2440 cl.free_particle = i;
2443 // reduce cl.num_particles if possible
2444 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2447 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2449 particle_t *oldparticles = cl.particles;
2450 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2451 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2452 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2453 Mem_Free(oldparticles);