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)
1122 return; // invalid effect index
1123 if (!particleeffectname[effectnameindex][0])
1124 return; // no such effect
1125 VectorLerp(originmins, 0.5, originmaxs, center);
1126 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1128 int effectinfoindex;
1131 particleeffectinfo_t *info;
1133 vec3_t centervelocity;
1139 qboolean underwater;
1140 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1141 VectorLerp(originmins, 0.5, originmaxs, center);
1142 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1143 supercontents = CL_PointSuperContents(center);
1144 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1145 VectorSubtract(originmaxs, originmins, traildir);
1146 traillen = VectorLength(traildir);
1147 VectorNormalize(traildir);
1148 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1150 if (info->effectnameindex == effectnameindex)
1153 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1155 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1158 // spawn a dlight if requested
1159 if (info->lightradiusstart > 0 && spawndlight)
1161 matrix4x4_t tempmatrix;
1162 if (info->trailspacing > 0)
1163 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1165 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1166 if (info->lighttime > 0 && info->lightradiusfade > 0)
1168 // light flash (explosion, etc)
1169 // called when effect starts
1170 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);
1175 // called by CL_LinkNetworkEntity
1176 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1177 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);
1181 if (!spawnparticles)
1186 if (info->tex[1] > info->tex[0])
1188 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1189 tex = min(tex, info->tex[1] - 1);
1191 if (info->particletype == pt_decal)
1192 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]);
1193 else if (info->particletype == pt_beam)
1194 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);
1197 if (!cl_particles.integer)
1199 switch (info->particletype)
1201 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1202 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1203 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1204 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1205 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1206 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1209 VectorCopy(originmins, trailpos);
1210 if (info->trailspacing > 0)
1212 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1213 trailstep = info->trailspacing / cl_particles_quality.value;
1217 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1220 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1221 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1223 if (info->tex[1] > info->tex[0])
1225 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1226 tex = min(tex, info->tex[1] - 1);
1230 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1231 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1232 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1235 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);
1237 VectorMA(trailpos, trailstep, traildir, trailpos);
1244 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1247 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)
1249 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1257 void CL_EntityParticles (const entity_t *ent)
1260 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1261 static vec3_t avelocities[NUMVERTEXNORMALS];
1262 if (!cl_particles.integer) return;
1263 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1265 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1267 if (!avelocities[0][0])
1268 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1269 avelocities[0][i] = lhrandom(0, 2.55);
1271 for (i = 0;i < NUMVERTEXNORMALS;i++)
1273 yaw = cl.time * avelocities[i][0];
1274 pitch = cl.time * avelocities[i][1];
1275 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1276 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1277 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1278 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);
1283 void CL_ReadPointFile_f (void)
1285 vec3_t org, leakorg;
1287 char *pointfile = NULL, *pointfilepos, *t, tchar;
1288 char name[MAX_OSPATH];
1293 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1294 strlcat (name, ".pts", sizeof (name));
1295 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1298 Con_Printf("Could not open %s\n", name);
1302 Con_Printf("Reading %s...\n", name);
1303 VectorClear(leakorg);
1306 pointfilepos = pointfile;
1307 while (*pointfilepos)
1309 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1314 while (*t && *t != '\n' && *t != '\r')
1318 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1324 VectorCopy(org, leakorg);
1327 if (cl.num_particles < cl.max_particles - 3)
1330 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);
1333 Mem_Free(pointfile);
1334 VectorCopy(leakorg, org);
1335 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1337 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);
1338 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);
1339 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);
1344 CL_ParseParticleEffect
1346 Parse an effect out of the server message
1349 void CL_ParseParticleEffect (void)
1352 int i, count, msgcount, color;
1354 MSG_ReadVector(org, cls.protocol);
1355 for (i=0 ; i<3 ; i++)
1356 dir[i] = MSG_ReadChar ();
1357 msgcount = MSG_ReadByte ();
1358 color = MSG_ReadByte ();
1360 if (msgcount == 255)
1365 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1370 CL_ParticleExplosion
1374 void CL_ParticleExplosion (const vec3_t org)
1380 if (cl_stainmaps.integer)
1381 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1382 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1384 if (cl_particles_quake.integer)
1386 for (i = 0;i < 1024;i++)
1392 color = particlepalette[ramp1[r]];
1393 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);
1397 color = particlepalette[ramp2[r]];
1398 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);
1404 i = CL_PointSuperContents(org);
1405 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1407 if (cl_particles.integer && cl_particles_bubbles.integer)
1408 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1409 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);
1413 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1415 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1419 for (k = 0;k < 16;k++)
1422 VectorMA(org, 128, v2, v);
1423 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1424 if (trace.fraction >= 0.1)
1427 VectorSubtract(trace.endpos, org, v2);
1428 VectorScale(v2, 2.0f, v2);
1429 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);
1435 if (cl_particles_explosions_shell.integer)
1436 R_NewExplosion(org);
1441 CL_ParticleExplosion2
1445 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1448 if (!cl_particles.integer) return;
1450 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1452 k = particlepalette[colorStart + (i % colorLength)];
1453 if (cl_particles_quake.integer)
1454 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);
1456 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);
1460 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1462 if (cl_particles_sparks.integer)
1464 sparkcount *= cl_particles_quality.value;
1465 while(sparkcount-- > 0)
1466 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);
1470 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1472 if (cl_particles_smoke.integer)
1474 smokecount *= cl_particles_quality.value;
1475 while(smokecount-- > 0)
1476 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);
1480 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)
1483 if (!cl_particles.integer) return;
1485 count = (int)(count * cl_particles_quality.value);
1488 k = particlepalette[colorbase + (rand()&3)];
1489 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);
1493 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1496 float minz, maxz, lifetime = 30;
1497 if (!cl_particles.integer) return;
1498 if (dir[2] < 0) // falling
1500 minz = maxs[2] + dir[2] * 0.1;
1503 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1508 maxz = maxs[2] + dir[2] * 0.1;
1510 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1513 count = (int)(count * cl_particles_quality.value);
1518 if (!cl_particles_rain.integer) break;
1519 count *= 4; // ick, this should be in the mod or maps?
1523 k = particlepalette[colorbase + (rand()&3)];
1524 if (gamemode == GAME_GOODVSBAD2)
1525 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);
1527 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);
1531 if (!cl_particles_snow.integer) break;
1534 k = particlepalette[colorbase + (rand()&3)];
1535 if (gamemode == GAME_GOODVSBAD2)
1536 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);
1538 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);
1542 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1546 #define MAX_PARTICLETEXTURES 64
1547 // particletexture_t is a rectangle in the particlefonttexture
1548 typedef struct particletexture_s
1550 rtexture_t *texture;
1551 float s1, t1, s2, t2;
1555 static rtexturepool_t *particletexturepool;
1556 static rtexture_t *particlefonttexture;
1557 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1559 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1560 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1561 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1562 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1564 #define PARTICLETEXTURESIZE 64
1565 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1567 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1571 dz = 1 - (dx*dx+dy*dy);
1572 if (dz > 0) // it does hit the sphere
1576 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1577 VectorNormalize(normal);
1578 dot = DotProduct(normal, light);
1579 if (dot > 0.5) // interior reflection
1580 f += ((dot * 2) - 1);
1581 else if (dot < -0.5) // exterior reflection
1582 f += ((dot * -2) - 1);
1584 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1585 VectorNormalize(normal);
1586 dot = DotProduct(normal, light);
1587 if (dot > 0.5) // interior reflection
1588 f += ((dot * 2) - 1);
1589 else if (dot < -0.5) // exterior reflection
1590 f += ((dot * -2) - 1);
1592 f += 16; // just to give it a haze so you can see the outline
1593 f = bound(0, f, 255);
1594 return (unsigned char) f;
1600 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1602 int basex, basey, y;
1603 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1604 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1605 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1606 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1609 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1612 float cx, cy, dx, dy, f, iradius;
1614 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1615 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1616 iradius = 1.0f / radius;
1617 alpha *= (1.0f / 255.0f);
1618 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1620 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1624 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1629 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1630 d[0] += (int)(f * (blue - d[0]));
1631 d[1] += (int)(f * (green - d[1]));
1632 d[2] += (int)(f * (red - d[2]));
1638 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1641 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1643 data[0] = bound(minb, data[0], maxb);
1644 data[1] = bound(ming, data[1], maxg);
1645 data[2] = bound(minr, data[2], maxr);
1649 void particletextureinvert(unsigned char *data)
1652 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1654 data[0] = 255 - data[0];
1655 data[1] = 255 - data[1];
1656 data[2] = 255 - data[2];
1660 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1661 static void R_InitBloodTextures (unsigned char *particletexturedata)
1664 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1667 for (i = 0;i < 8;i++)
1669 memset(&data[0][0][0], 255, sizeof(data));
1670 for (k = 0;k < 24;k++)
1671 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1672 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1673 particletextureinvert(&data[0][0][0]);
1674 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1678 for (i = 0;i < 8;i++)
1680 memset(&data[0][0][0], 255, sizeof(data));
1682 for (j = 1;j < 10;j++)
1683 for (k = min(j, m - 1);k < m;k++)
1684 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1685 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1686 particletextureinvert(&data[0][0][0]);
1687 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1692 //uncomment this to make engine save out particle font to a tga file when run
1693 //#define DUMPPARTICLEFONT
1695 static void R_InitParticleTexture (void)
1697 int x, y, d, i, k, m;
1701 // a note: decals need to modulate (multiply) the background color to
1702 // properly darken it (stain), and they need to be able to alpha fade,
1703 // this is a very difficult challenge because it means fading to white
1704 // (no change to background) rather than black (darkening everything
1705 // behind the whole decal polygon), and to accomplish this the texture is
1706 // inverted (dark red blood on white background becomes brilliant cyan
1707 // and white on black background) so we can alpha fade it to black, then
1708 // we invert it again during the blendfunc to make it work...
1710 #ifndef DUMPPARTICLEFONT
1711 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1712 if (!particlefonttexture)
1715 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1716 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1717 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1720 for (i = 0;i < 8;i++)
1722 memset(&data[0][0][0], 255, sizeof(data));
1725 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1727 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1728 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1730 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1732 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1733 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1735 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1736 d = (noise2[y][x] - 128) * 3 + 192;
1738 d = (int)(d * (1-(dx*dx+dy*dy)));
1739 d = (d * noise1[y][x]) >> 7;
1740 d = bound(0, d, 255);
1741 data[y][x][3] = (unsigned char) d;
1748 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1752 memset(&data[0][0][0], 255, sizeof(data));
1753 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1755 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1756 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1758 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1759 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1760 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1763 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1766 memset(&data[0][0][0], 255, sizeof(data));
1767 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1769 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1770 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1772 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1773 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1774 d = bound(0, d, 255);
1775 data[y][x][3] = (unsigned char) d;
1778 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1781 memset(&data[0][0][0], 255, sizeof(data));
1782 light[0] = 1;light[1] = 1;light[2] = 1;
1783 VectorNormalize(light);
1784 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1786 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1787 // stretch upper half of bubble by +50% and shrink lower half by -50%
1788 // (this gives an elongated teardrop shape)
1790 dy = (dy - 0.5f) * 2.0f;
1792 dy = (dy - 0.5f) / 1.5f;
1793 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1795 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1796 // shrink bubble width to half
1798 data[y][x][3] = shadebubble(dx, dy, light);
1801 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1804 memset(&data[0][0][0], 255, sizeof(data));
1805 light[0] = 1;light[1] = 1;light[2] = 1;
1806 VectorNormalize(light);
1807 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1809 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1810 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1812 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1813 data[y][x][3] = shadebubble(dx, dy, light);
1816 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1818 // Blood particles and blood decals
1819 R_InitBloodTextures (particletexturedata);
1822 for (i = 0;i < 8;i++)
1824 memset(&data[0][0][0], 255, sizeof(data));
1825 for (k = 0;k < 12;k++)
1826 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1827 for (k = 0;k < 3;k++)
1828 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1829 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1830 particletextureinvert(&data[0][0][0]);
1831 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1834 #ifdef DUMPPARTICLEFONT
1835 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1838 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1840 Mem_Free(particletexturedata);
1842 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1844 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1845 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1846 particletexture[i].texture = particlefonttexture;
1847 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1848 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1849 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1850 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1853 #ifndef DUMPPARTICLEFONT
1854 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1855 if (!particletexture[tex_beam].texture)
1858 unsigned char noise3[64][64], data2[64][16][4];
1860 fractalnoise(&noise3[0][0], 64, 4);
1862 for (y = 0;y < 64;y++)
1864 dy = (y - 0.5f*64) / (64*0.5f-1);
1865 for (x = 0;x < 16;x++)
1867 dx = (x - 0.5f*16) / (16*0.5f-2);
1868 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1869 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1870 data2[y][x][3] = 255;
1874 #ifdef DUMPPARTICLEFONT
1875 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1877 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1879 particletexture[tex_beam].s1 = 0;
1880 particletexture[tex_beam].t1 = 0;
1881 particletexture[tex_beam].s2 = 1;
1882 particletexture[tex_beam].t2 = 1;
1885 static void r_part_start(void)
1887 particletexturepool = R_AllocTexturePool();
1888 R_InitParticleTexture ();
1889 CL_Particles_LoadEffectInfo();
1892 static void r_part_shutdown(void)
1894 R_FreeTexturePool(&particletexturepool);
1897 static void r_part_newmap(void)
1901 #define BATCHSIZE 256
1902 int particle_element3i[BATCHSIZE*6];
1904 void R_Particles_Init (void)
1907 for (i = 0;i < BATCHSIZE;i++)
1909 particle_element3i[i*6+0] = i*4+0;
1910 particle_element3i[i*6+1] = i*4+1;
1911 particle_element3i[i*6+2] = i*4+2;
1912 particle_element3i[i*6+3] = i*4+0;
1913 particle_element3i[i*6+4] = i*4+2;
1914 particle_element3i[i*6+5] = i*4+3;
1917 Cvar_RegisterVariable(&r_drawparticles);
1918 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1919 Cvar_RegisterVariable(&r_drawdecals);
1920 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1921 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1924 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1926 int surfacelistindex;
1928 float *v3f, *t2f, *c4f;
1929 particletexture_t *tex;
1930 float right[3], up[3], size, ca;
1931 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1932 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1934 r_refdef.stats.decals += numsurfaces;
1935 R_Mesh_Matrix(&identitymatrix);
1936 R_Mesh_ResetTextureState();
1937 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1938 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1939 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1940 GL_DepthMask(false);
1941 GL_DepthRange(0, 1);
1942 GL_PolygonOffset(0, 0);
1944 GL_CullFace(GL_NONE);
1946 // generate all the vertices at once
1947 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1949 d = cl.decals + surfacelist[surfacelistindex];
1952 c4f = particle_color4f + 16*surfacelistindex;
1953 ca = d->alpha * alphascale;
1954 if (r_refdef.fogenabled)
1955 ca *= FogPoint_World(d->org);
1956 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1957 Vector4Copy(c4f, c4f + 4);
1958 Vector4Copy(c4f, c4f + 8);
1959 Vector4Copy(c4f, c4f + 12);
1961 // calculate vertex positions
1962 size = d->size * cl_particles_size.value;
1963 VectorVectors(d->normal, right, up);
1964 VectorScale(right, size, right);
1965 VectorScale(up, size, up);
1966 v3f = particle_vertex3f + 12*surfacelistindex;
1967 v3f[ 0] = d->org[0] - right[0] - up[0];
1968 v3f[ 1] = d->org[1] - right[1] - up[1];
1969 v3f[ 2] = d->org[2] - right[2] - up[2];
1970 v3f[ 3] = d->org[0] - right[0] + up[0];
1971 v3f[ 4] = d->org[1] - right[1] + up[1];
1972 v3f[ 5] = d->org[2] - right[2] + up[2];
1973 v3f[ 6] = d->org[0] + right[0] + up[0];
1974 v3f[ 7] = d->org[1] + right[1] + up[1];
1975 v3f[ 8] = d->org[2] + right[2] + up[2];
1976 v3f[ 9] = d->org[0] + right[0] - up[0];
1977 v3f[10] = d->org[1] + right[1] - up[1];
1978 v3f[11] = d->org[2] + right[2] - up[2];
1980 // calculate texcoords
1981 tex = &particletexture[d->texnum];
1982 t2f = particle_texcoord2f + 8*surfacelistindex;
1983 t2f[0] = tex->s1;t2f[1] = tex->t2;
1984 t2f[2] = tex->s1;t2f[3] = tex->t1;
1985 t2f[4] = tex->s2;t2f[5] = tex->t1;
1986 t2f[6] = tex->s2;t2f[7] = tex->t2;
1989 // now render the decals all at once
1990 // (this assumes they all use one particle font texture!)
1991 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1992 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
1993 GL_LockArrays(0, numsurfaces*4);
1994 R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
1995 GL_LockArrays(0, 0);
1998 void R_DrawDecals (void)
2004 // used as if (i & qualitymask) to skip some less important particles
2005 // according to cl_minfps
2006 int qualitymask = (1 << r_refdef.view.qualityreduction) - 1;
2007 float drawdist2 = r_drawdecals_drawdistance.value * r_drawdecals_drawdistance.value;
2009 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2010 cl.decals_updatetime += frametime;
2012 // LordHavoc: early out conditions
2013 if ((!cl.num_decals) || (!r_drawdecals.integer))
2016 decalfade = frametime * 256 / cl_decals_fadetime.value;
2018 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2020 if (!decal->typeindex)
2023 if (cl.time > decal->time2 + cl_decals_time.value)
2025 decal->alpha -= decalfade;
2026 if (decal->alpha <= 0)
2032 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2034 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2035 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2041 // skip some of the less important decals according to cl_minfps
2042 if (i & qualitymask)
2045 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))
2046 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2049 decal->typeindex = 0;
2050 if (cl.free_decal > i)
2054 // reduce cl.num_decals if possible
2055 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2058 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2060 decal_t *olddecals = cl.decals;
2061 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2062 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2063 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2064 Mem_Free(olddecals);
2068 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2070 int surfacelistindex;
2071 int batchstart, batchcount;
2072 const particle_t *p;
2074 rtexture_t *texture;
2075 float *v3f, *t2f, *c4f;
2076 particletexture_t *tex;
2077 float up2[3], v[3], right[3], up[3], fog, ifog, size;
2078 float ambient[3], diffuse[3], diffusenormal[3];
2079 vec4_t colormultiplier;
2080 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2082 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));
2084 r_refdef.stats.particles += numsurfaces;
2085 R_Mesh_Matrix(&identitymatrix);
2086 R_Mesh_ResetTextureState();
2087 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2088 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2089 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2090 GL_DepthMask(false);
2091 GL_DepthRange(0, 1);
2092 GL_PolygonOffset(0, 0);
2094 GL_CullFace(GL_NONE);
2096 // first generate all the vertices at once
2097 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2099 p = cl.particles + surfacelist[surfacelistindex];
2101 blendmode = particletype[p->typeindex].blendmode;
2103 c4f[0] = p->color[0] * colormultiplier[0];
2104 c4f[1] = p->color[1] * colormultiplier[1];
2105 c4f[2] = p->color[2] * colormultiplier[2];
2106 c4f[3] = p->alpha * colormultiplier[3];
2111 // additive and modulate can just fade out in fog (this is correct)
2112 if (r_refdef.fogenabled)
2113 c4f[3] *= FogPoint_World(p->org);
2114 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2121 // note: lighting is not cheap!
2122 if (particletype[p->typeindex].lighting)
2124 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2125 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2126 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2127 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2129 // mix in the fog color
2130 if (r_refdef.fogenabled)
2132 fog = FogPoint_World(p->org);
2134 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2135 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2136 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2140 // copy the color into the other three vertices
2141 Vector4Copy(c4f, c4f + 4);
2142 Vector4Copy(c4f, c4f + 8);
2143 Vector4Copy(c4f, c4f + 12);
2145 size = p->size * cl_particles_size.value;
2146 tex = &particletexture[p->texnum];
2147 switch(particletype[p->typeindex].orientation)
2149 case PARTICLE_BILLBOARD:
2150 VectorScale(r_refdef.view.left, -size, right);
2151 VectorScale(r_refdef.view.up, size, up);
2152 v3f[ 0] = p->org[0] - right[0] - up[0];
2153 v3f[ 1] = p->org[1] - right[1] - up[1];
2154 v3f[ 2] = p->org[2] - right[2] - up[2];
2155 v3f[ 3] = p->org[0] - right[0] + up[0];
2156 v3f[ 4] = p->org[1] - right[1] + up[1];
2157 v3f[ 5] = p->org[2] - right[2] + up[2];
2158 v3f[ 6] = p->org[0] + right[0] + up[0];
2159 v3f[ 7] = p->org[1] + right[1] + up[1];
2160 v3f[ 8] = p->org[2] + right[2] + up[2];
2161 v3f[ 9] = p->org[0] + right[0] - up[0];
2162 v3f[10] = p->org[1] + right[1] - up[1];
2163 v3f[11] = p->org[2] + right[2] - up[2];
2164 t2f[0] = tex->s1;t2f[1] = tex->t2;
2165 t2f[2] = tex->s1;t2f[3] = tex->t1;
2166 t2f[4] = tex->s2;t2f[5] = tex->t1;
2167 t2f[6] = tex->s2;t2f[7] = tex->t2;
2169 case PARTICLE_ORIENTED_DOUBLESIDED:
2170 VectorVectors(p->vel, right, up);
2171 VectorScale(right, size, right);
2172 VectorScale(up, size, up);
2173 v3f[ 0] = p->org[0] - right[0] - up[0];
2174 v3f[ 1] = p->org[1] - right[1] - up[1];
2175 v3f[ 2] = p->org[2] - right[2] - up[2];
2176 v3f[ 3] = p->org[0] - right[0] + up[0];
2177 v3f[ 4] = p->org[1] - right[1] + up[1];
2178 v3f[ 5] = p->org[2] - right[2] + up[2];
2179 v3f[ 6] = p->org[0] + right[0] + up[0];
2180 v3f[ 7] = p->org[1] + right[1] + up[1];
2181 v3f[ 8] = p->org[2] + right[2] + up[2];
2182 v3f[ 9] = p->org[0] + right[0] - up[0];
2183 v3f[10] = p->org[1] + right[1] - up[1];
2184 v3f[11] = p->org[2] + right[2] - up[2];
2185 t2f[0] = tex->s1;t2f[1] = tex->t2;
2186 t2f[2] = tex->s1;t2f[3] = tex->t1;
2187 t2f[4] = tex->s2;t2f[5] = tex->t1;
2188 t2f[6] = tex->s2;t2f[7] = tex->t2;
2190 case PARTICLE_SPARK:
2191 VectorMA(p->org, -0.04, p->vel, v);
2192 VectorMA(p->org, 0.04, p->vel, up2);
2193 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2194 t2f[0] = tex->s1;t2f[1] = tex->t2;
2195 t2f[2] = tex->s1;t2f[3] = tex->t1;
2196 t2f[4] = tex->s2;t2f[5] = tex->t1;
2197 t2f[6] = tex->s2;t2f[7] = tex->t2;
2200 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2201 VectorSubtract(p->vel, p->org, up);
2202 VectorNormalize(up);
2203 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2204 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2205 t2f[0] = 1;t2f[1] = v[0];
2206 t2f[2] = 0;t2f[3] = v[0];
2207 t2f[4] = 0;t2f[5] = v[1];
2208 t2f[6] = 1;t2f[7] = v[1];
2213 // now render batches of particles based on blendmode and texture
2216 GL_LockArrays(0, numsurfaces*4);
2219 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2221 p = cl.particles + surfacelist[surfacelistindex];
2223 if (blendmode != particletype[p->typeindex].blendmode)
2225 blendmode = particletype[p->typeindex].blendmode;
2229 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2232 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2235 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2239 if (texture != particletexture[p->texnum].texture)
2241 texture = particletexture[p->texnum].texture;
2242 R_Mesh_TexBind(0, R_GetTexture(texture));
2245 // iterate until we find a change in settings
2246 batchstart = surfacelistindex++;
2247 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2249 p = cl.particles + surfacelist[surfacelistindex];
2250 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2254 batchcount = surfacelistindex - batchstart;
2255 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2257 GL_LockArrays(0, 0);
2260 void R_DrawParticles (void)
2263 float minparticledist;
2265 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2266 float drawdist2 = r_drawparticles_drawdistance.value * r_drawparticles_drawdistance.value;
2270 // used as if (i & qualitymask) to skip some less important particles
2271 // according to cl_minfps
2272 int qualitymask = (1 << r_refdef.view.qualityreduction) - 1;
2274 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2275 cl.particles_updatetime += frametime;
2277 // LordHavoc: early out conditions
2278 if ((!cl.num_particles) || (!r_drawparticles.integer))
2281 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2282 gravity = frametime * cl.movevars_gravity;
2283 dvel = 1+4*frametime;
2284 decalfade = frametime * 255 / cl_decals_fadetime.value;
2285 update = frametime > 0;
2287 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2291 if (cl.free_particle > i)
2292 cl.free_particle = i;
2298 if (p->delayedspawn > cl.time)
2300 p->delayedspawn = 0;
2304 p->size += p->sizeincrease * frametime;
2305 p->alpha -= p->alphafade * frametime;
2307 if (p->alpha <= 0 || p->die <= cl.time)
2310 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2312 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2314 if (p->typeindex == pt_blood)
2315 p->size += frametime * 8;
2317 p->vel[2] -= p->gravity * gravity;
2318 f = 1.0f - min(p->liquidfriction * frametime, 1);
2319 VectorScale(p->vel, f, p->vel);
2323 p->vel[2] -= p->gravity * gravity;
2326 f = 1.0f - min(p->airfriction * frametime, 1);
2327 VectorScale(p->vel, f, p->vel);
2331 VectorCopy(p->org, oldorg);
2332 VectorMA(p->org, frametime, p->vel, p->org);
2333 if (p->bounce && cl.time >= p->delayedcollisions)
2335 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);
2336 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2337 // or if the trace hit something flagged as NOIMPACT
2338 // then remove the particle
2339 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2341 VectorCopy(trace.endpos, p->org);
2342 // react if the particle hit something
2343 if (trace.fraction < 1)
2345 VectorCopy(trace.endpos, p->org);
2346 if (p->typeindex == pt_blood)
2348 // blood - splash on solid
2349 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2351 if (cl_stainmaps.integer)
2352 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)));
2353 if (cl_decals.integer)
2355 // create a decal for the blood splat
2356 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);
2360 else if (p->bounce < 0)
2362 // bounce -1 means remove on impact
2367 // anything else - bounce off solid
2368 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2369 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2370 if (DotProduct(p->vel, p->vel) < 0.03)
2371 VectorClear(p->vel);
2377 if (p->typeindex != pt_static)
2379 switch (p->typeindex)
2381 case pt_entityparticle:
2382 // particle that removes itself after one rendered frame
2389 a = CL_PointSuperContents(p->org);
2390 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2394 a = CL_PointSuperContents(p->org);
2395 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2399 a = CL_PointSuperContents(p->org);
2400 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2404 if (cl.time > p->time2)
2407 p->time2 = cl.time + (rand() & 3) * 0.1;
2408 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2409 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2411 a = CL_PointSuperContents(p->org);
2412 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2420 else if (p->delayedspawn)
2423 // skip some of the less important particles according to cl_minfps
2424 if ((i & qualitymask) && p->qualityreduction)
2427 // don't render particles too close to the view (they chew fillrate)
2428 // also don't render particles behind the view (useless)
2429 // further checks to cull to the frustum would be too slow here
2430 switch(p->typeindex)
2433 // beams have no culling
2434 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2437 // anything else just has to be in front of the viewer and visible at this distance
2438 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2439 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2446 if (cl.free_particle > i)
2447 cl.free_particle = i;
2450 // reduce cl.num_particles if possible
2451 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2454 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2456 particle_t *oldparticles = cl.particles;
2457 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2458 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2459 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2460 Mem_Free(oldparticles);