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"
26 // must match ptype_t values
27 particletype_t particletype[pt_total] =
29 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
30 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
31 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
32 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
34 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
36 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
37 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
39 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
40 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
43 #define PARTICLEEFFECT_UNDERWATER 1
44 #define PARTICLEEFFECT_NOTUNDERWATER 2
46 typedef struct particleeffectinfo_s
48 int effectnameindex; // which effect this belongs to
49 // PARTICLEEFFECT_* bits
51 // blood effects may spawn very few particles, so proper fraction-overflow
52 // handling is very important, this variable keeps track of the fraction
53 double particleaccumulator;
54 // the math is: countabsolute + requestedcount * countmultiplier * quality
55 // absolute number of particles to spawn, often used for decals
56 // (unaffected by quality and requestedcount)
58 // multiplier for the number of particles CL_ParticleEffect was told to
59 // spawn, most effects do not really have a count and hence use 1, so
60 // this is often the actual count to spawn, not merely a multiplier
61 float countmultiplier;
62 // if > 0 this causes the particle to spawn in an evenly spaced line from
63 // originmins to originmaxs (causing them to describe a trail, not a box)
65 // type of particle to spawn (defines some aspects of behavior)
67 // range of colors to choose from in hex RRGGBB (like HTML color tags),
68 // randomly interpolated at spawn
69 unsigned int color[2];
70 // a random texture is chosen in this range (note the second value is one
71 // past the last choosable, so for example 8,16 chooses any from 8 up and
73 // if start and end of the range are the same, no randomization is done
75 // range of size values randomly chosen when spawning
77 // range of alpha values randomly chosen when spawning, plus alpha fade
79 // how long the particle should live (note it is also removed if alpha drops to 0)
81 // how much gravity affects this particle (negative makes it fly up!)
83 // how much bounce the particle has when it hits a surface
84 // if negative the particle is removed on impact
86 // if in air this friction is applied
87 // if negative the particle accelerates
89 // if in liquid (water/slime/lava) this friction is applied
90 // if negative the particle accelerates
92 // these offsets are added to the values given to particleeffect(), and
93 // then an ellipsoid-shaped jitter is added as defined by these
94 // (they are the 3 radii)
95 float originoffset[3];
96 float velocityoffset[3];
97 float originjitter[3];
98 float velocityjitter[3];
99 float velocitymultiplier;
100 // an effect can also spawn a dlight
101 float lightradiusstart;
102 float lightradiusfade;
106 float lightcubemapnum;
108 particleeffectinfo_t;
110 #define MAX_PARTICLEEFFECTNAME 256
111 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
113 #define MAX_PARTICLEEFFECTINFO 4096
115 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
117 static int particlepalette[256] =
119 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
120 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
121 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
122 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
123 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
124 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
125 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
126 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
127 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
128 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
129 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
130 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
131 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
132 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
133 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
134 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
135 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
136 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
137 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
138 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
139 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
140 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
141 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
142 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
143 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
144 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
145 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
146 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
147 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
148 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
149 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
150 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
153 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
154 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
155 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
157 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
159 // texture numbers in particle font
160 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
161 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
162 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
163 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
164 static const int tex_rainsplash[16] = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
165 static const int tex_particle = 63;
166 static const int tex_bubble = 62;
167 static const int tex_raindrop = 61;
168 static const int tex_beam = 60;
170 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
171 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles and reduces their alpha"};
172 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
173 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
174 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
175 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
176 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
177 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
178 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
179 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
180 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
181 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
182 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
183 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
184 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
185 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
186 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
187 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
188 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
191 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
197 particleeffectinfo_t *info = NULL;
198 const char *text = textstart;
200 effectinfoindex = -1;
201 for (linenumber = 1;;linenumber++)
204 for (arrayindex = 0;arrayindex < 16;arrayindex++)
205 argv[arrayindex][0] = 0;
208 if (!COM_ParseToken(&text, true))
210 if (!strcmp(com_token, "\n"))
214 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
220 #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;}
221 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = (int)atof(argv[1+arrayindex])
222 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
223 #define readint(var) checkparms(2);var = (int)atof(argv[1])
224 #define readfloat(var) checkparms(2);var = atof(argv[1])
225 if (!strcmp(argv[0], "effect"))
230 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
232 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
235 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
237 if (particleeffectname[effectnameindex][0])
239 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
244 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
248 // if we run out of names, abort
249 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
251 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
254 info = particleeffectinfo + effectinfoindex;
255 info->effectnameindex = effectnameindex;
256 info->particletype = pt_alphastatic;
257 info->tex[0] = tex_particle;
258 info->tex[1] = tex_particle;
259 info->color[0] = 0xFFFFFF;
260 info->color[1] = 0xFFFFFF;
264 info->alpha[1] = 256;
265 info->alpha[2] = 256;
266 info->time[0] = 9999;
267 info->time[1] = 9999;
268 VectorSet(info->lightcolor, 1, 1, 1);
269 info->lightshadow = true;
270 info->lighttime = 9999;
272 else if (info == NULL)
274 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
277 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
278 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
279 else if (!strcmp(argv[0], "type"))
282 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
283 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
284 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
285 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
286 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
287 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
288 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
289 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
290 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
291 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
292 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
293 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
294 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
296 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
297 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
298 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
299 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
300 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
301 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
302 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
303 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
304 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
305 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
306 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
307 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
308 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
309 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
310 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
311 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
312 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
313 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
314 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
315 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
316 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
317 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
318 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
320 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
329 int CL_ParticleEffectIndexForName(const char *name)
332 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
333 if (!strcmp(particleeffectname[i], name))
338 const char *CL_ParticleEffectNameForIndex(int i)
340 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
342 return particleeffectname[i];
345 // MUST match effectnameindex_t in client.h
346 static const char *standardeffectnames[EFFECT_TOTAL] =
371 "TE_TEI_BIGEXPLOSION",
387 void CL_Particles_LoadEffectInfo(void)
390 unsigned char *filedata;
391 fs_offset_t filesize;
392 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
393 memset(particleeffectname, 0, sizeof(particleeffectname));
394 for (i = 0;i < EFFECT_TOTAL;i++)
395 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
396 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
399 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
409 void CL_ReadPointFile_f (void);
410 void CL_Particles_Init (void)
412 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)");
413 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
415 Cvar_RegisterVariable (&cl_particles);
416 Cvar_RegisterVariable (&cl_particles_quality);
417 Cvar_RegisterVariable (&cl_particles_size);
418 Cvar_RegisterVariable (&cl_particles_quake);
419 Cvar_RegisterVariable (&cl_particles_blood);
420 Cvar_RegisterVariable (&cl_particles_blood_alpha);
421 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
422 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
423 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
424 Cvar_RegisterVariable (&cl_particles_explosions_shell);
425 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
426 Cvar_RegisterVariable (&cl_particles_smoke);
427 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
428 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
429 Cvar_RegisterVariable (&cl_particles_sparks);
430 Cvar_RegisterVariable (&cl_particles_bubbles);
431 Cvar_RegisterVariable (&cl_decals);
432 Cvar_RegisterVariable (&cl_decals_time);
433 Cvar_RegisterVariable (&cl_decals_fadetime);
436 void CL_Particles_Shutdown (void)
440 // list of all 26 parameters:
441 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
442 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
443 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
444 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
445 // palpha - opacity of particle as 0-255 (can be more than 255)
446 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
447 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
448 // pgravity - how much effect gravity has on the particle (0-1)
449 // 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
450 // px,py,pz - starting origin of particle
451 // pvx,pvy,pvz - starting velocity of particle
452 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
453 static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pfriction, float originjitter, float velocityjitter)
458 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
459 if (cl.free_particle >= cl.max_particles)
461 part = &cl.particles[cl.free_particle++];
462 if (cl.num_particles < cl.free_particle)
463 cl.num_particles = cl.free_particle;
464 memset(part, 0, sizeof(*part));
466 l2 = (int)lhrandom(0.5, 256.5);
468 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
469 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[3] = 0xFF;
474 part->alpha = palpha;
475 part->alphafade = palphafade;
476 part->gravity = pgravity;
477 part->bounce = pbounce;
479 part->org[0] = px + originjitter * v[0];
480 part->org[1] = py + originjitter * v[1];
481 part->org[2] = pz + originjitter * v[2];
482 part->vel[0] = pvx + velocityjitter * v[0];
483 part->vel[1] = pvy + velocityjitter * v[1];
484 part->vel[2] = pvz + velocityjitter * v[2];
486 part->friction = pfriction;
490 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
493 if (!cl_decals.integer)
495 p = particle(particletype + pt_decal, color1, color2, texnum, size, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0);
500 p->ownermodel = cl.entities[p->owner].render.model;
501 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
502 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
503 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
507 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
510 float bestfrac, bestorg[3], bestnormal[3];
512 int besthitent = 0, hitent;
515 for (i = 0;i < 32;i++)
518 VectorMA(org, maxdist, org2, org2);
519 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
520 // take the closest trace result that doesn't end up hitting a NOMARKS
521 // surface (sky for example)
522 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
524 bestfrac = trace.fraction;
526 VectorCopy(trace.endpos, bestorg);
527 VectorCopy(trace.plane.normal, bestnormal);
531 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
534 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
535 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)
538 matrix4x4_t tempmatrix;
539 VectorLerp(originmins, 0.5, originmaxs, center);
540 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
541 if (effectnameindex == EFFECT_TE_WIZSPIKE)
542 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
543 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
544 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
545 else if (effectnameindex == EFFECT_TE_SPIKE)
547 if (cl_particles_bulletimpacts.integer)
549 if (cl_particles_quake.integer)
551 if (cl_particles_smoke.integer)
552 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
555 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
558 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
559 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
561 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
563 if (cl_particles_bulletimpacts.integer)
565 if (cl_particles_quake.integer)
567 if (cl_particles_smoke.integer)
568 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
571 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
574 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
575 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
576 CL_AllocDlight(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);
578 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
580 if (cl_particles_bulletimpacts.integer)
582 if (cl_particles_quake.integer)
584 if (cl_particles_smoke.integer)
585 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
588 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
591 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
592 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
594 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
596 if (cl_particles_bulletimpacts.integer)
598 if (cl_particles_quake.integer)
600 if (cl_particles_smoke.integer)
601 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
604 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
607 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
608 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
609 CL_AllocDlight(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);
611 else if (effectnameindex == EFFECT_TE_BLOOD)
613 if (!cl_particles_blood.integer)
615 if (cl_particles_quake.integer)
616 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
619 static double bloodaccumulator = 0;
620 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
621 for (;bloodaccumulator > 0;bloodaccumulator--)
622 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 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, 0, 64);
625 else if (effectnameindex == EFFECT_TE_SPARK)
626 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
627 else if (effectnameindex == EFFECT_TE_PLASMABURN)
629 // plasma scorch mark
630 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
631 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
632 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
634 else if (effectnameindex == EFFECT_TE_GUNSHOT)
636 if (cl_particles_bulletimpacts.integer)
638 if (cl_particles_quake.integer)
639 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
641 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
644 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
645 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
647 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
649 if (cl_particles_bulletimpacts.integer)
651 if (cl_particles_quake.integer)
652 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
654 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
657 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
658 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
659 CL_AllocDlight(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);
661 else if (effectnameindex == EFFECT_TE_EXPLOSION)
663 CL_ParticleExplosion(center);
664 CL_AllocDlight(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);
666 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
668 CL_ParticleExplosion(center);
669 CL_AllocDlight(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);
671 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
673 if (cl_particles_quake.integer)
676 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
679 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, 16, 256);
681 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 16, 0);
685 CL_ParticleExplosion(center);
686 CL_AllocDlight(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);
688 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
689 CL_AllocDlight(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
690 else if (effectnameindex == EFFECT_TE_FLAMEJET)
692 count *= cl_particles_quality.value;
694 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 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, 0, 128);
696 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
698 float i, j, inc, vel;
701 inc = 8 / cl_particles_quality.value;
702 for (i = -128;i < 128;i += inc)
704 for (j = -128;j < 128;j += inc)
706 dir[0] = j + lhrandom(0, inc);
707 dir[1] = i + lhrandom(0, inc);
709 org[0] = center[0] + dir[0];
710 org[1] = center[1] + dir[1];
711 org[2] = center[2] + lhrandom(0, 64);
712 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
713 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 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);
717 else if (effectnameindex == EFFECT_TE_TELEPORT)
719 float i, j, k, inc, vel;
722 inc = 4 / cl_particles_quality.value;
723 for (i = -16;i < 16;i += inc)
725 for (j = -16;j < 16;j += inc)
727 for (k = -24;k < 32;k += inc)
729 VectorSet(dir, i*8, j*8, k*8);
730 VectorNormalize(dir);
731 vel = lhrandom(50, 113);
732 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 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);
736 CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
738 else if (effectnameindex == EFFECT_TE_TEI_G3)
739 particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0);
740 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
742 if (cl_particles_smoke.integer)
744 count *= 0.25f * cl_particles_quality.value;
746 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 1.5f, 6.0f);
749 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
751 CL_ParticleExplosion(center);
752 CL_AllocDlight(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);
754 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
757 if (cl_stainmaps.integer)
758 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
759 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
760 if (cl_particles_smoke.integer)
761 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
762 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 20, 155);
763 if (cl_particles_sparks.integer)
764 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
765 particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 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, 465);
766 CL_AllocDlight(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);
768 else if (effectnameindex == EFFECT_EF_FLAME)
770 count *= 300 * cl_particles_quality.value;
772 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 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, 16, 128);
773 CL_AllocDlight(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);
775 else if (effectnameindex == EFFECT_EF_STARDUST)
777 count *= 200 * cl_particles_quality.value;
779 particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 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, 16, 128);
780 CL_AllocDlight(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);
782 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
786 int smoke, blood, bubbles, r, color;
788 if (effectnameindex == EFFECT_TR_ROCKET)
789 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 3.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
790 else if (effectnameindex == EFFECT_TR_VORESPIKE)
792 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
793 CL_AllocDlight(&ent->render, &ent->render.matrix, 100, 0.3f, 0.6f, 1.2f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
795 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 1.2f, 0.5f, 1.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
797 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
798 CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 0.75f, 1.5f, 3.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
800 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
803 VectorSubtract(originmaxs, originmins, dir);
804 len = VectorNormalizeLength(dir);
805 dec = -ent->persistent.trail_time;
806 ent->persistent.trail_time += len;
807 if (ent->persistent.trail_time < 0.01f)
810 // if we skip out, leave it reset
811 ent->persistent.trail_time = 0.0f;
813 // advance into this frame to reach the first puff location
814 VectorMA(originmins, dec, dir, pos);
817 smoke = cl_particles.integer && cl_particles_smoke.integer;
818 blood = cl_particles.integer && cl_particles_blood.integer;
819 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
820 qd = 1.0f / cl_particles_quality.value;
827 if (effectnameindex == EFFECT_TR_BLOOD)
829 if (cl_particles_quake.integer)
831 color = particlepalette[67 + (rand()&3)];
832 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
837 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 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, 0, 64);
840 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
842 if (cl_particles_quake.integer)
845 color = particlepalette[67 + (rand()&3)];
846 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
851 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 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, 0, 64);
857 if (effectnameindex == EFFECT_TR_ROCKET)
859 if (cl_particles_quake.integer)
862 color = particlepalette[ramp3[r]];
863 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
867 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 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);
868 particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 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, 20);
871 else if (effectnameindex == EFFECT_TR_GRENADE)
873 if (cl_particles_quake.integer)
876 color = particlepalette[ramp3[r]];
877 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
881 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
884 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
886 if (cl_particles_quake.integer)
889 color = particlepalette[52 + (rand()&7)];
890 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
891 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
893 else if (gamemode == GAME_GOODVSBAD2)
896 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
900 color = particlepalette[20 + (rand()&7)];
901 particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
904 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
906 if (cl_particles_quake.integer)
909 color = particlepalette[230 + (rand()&7)];
910 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
911 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
915 color = particlepalette[226 + (rand()&7)];
916 particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
919 else if (effectnameindex == EFFECT_TR_VORESPIKE)
921 if (cl_particles_quake.integer)
923 color = particlepalette[152 + (rand()&3)];
924 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
926 else if (gamemode == GAME_GOODVSBAD2)
929 particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
931 else if (gamemode == GAME_PRYDON)
934 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
937 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
939 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
942 particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 4);
944 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
947 particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 16);
949 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
950 particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
954 if (effectnameindex == EFFECT_TR_ROCKET)
955 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
956 else if (effectnameindex == EFFECT_TR_GRENADE)
957 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
959 // advance to next time and position
962 VectorMA (pos, dec, dir, pos);
964 ent->persistent.trail_time = len;
966 else if (developer.integer >= 1)
967 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
970 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)
973 qboolean found = false;
974 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
975 return; // invalid effect index
976 if (!particleeffectname[effectnameindex][0])
977 return; // no such effect
978 VectorLerp(originmins, 0.5, originmaxs, center);
979 if (effectnameindex == EFFECT_SVC_PARTICLE)
981 if (!cl_particles.integer)
983 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
985 CL_ParticleExplosion(center);
986 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
987 CL_ParticleEffect(EFFECT_TE_BLOOD, pcount / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
990 float count = pcount * cl_particles_quality.value;
991 for (;count > 0;count--)
993 int k = particlepalette[palettecolor + (rand()&7)];
994 if (cl_particles_quake.integer)
995 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 0);
996 else if (gamemode == GAME_GOODVSBAD2)
997 particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 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, 8, 10);
999 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 15);
1003 else if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1005 int effectinfoindex;
1008 particleeffectinfo_t *info;
1010 vec3_t centervelocity;
1014 qboolean underwater;
1015 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1016 VectorLerp(originmins, 0.5, originmaxs, center);
1017 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1018 supercontents = CL_PointSuperContents(center);
1019 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1020 VectorSubtract(originmaxs, originmins, traildir);
1021 VectorNormalize(traildir);
1022 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1024 if (info->effectnameindex == effectnameindex)
1027 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1029 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1032 // spawn a dlight if requested
1033 if (info->lightradiusstart > 0)
1035 matrix4x4_t tempmatrix;
1036 if (info->trailspacing > 0)
1037 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1039 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1040 CL_AllocDlight(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);
1045 if (info->tex[1] > info->tex[0])
1047 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1048 tex = min(tex, info->tex[1] - 1);
1050 if (info->particletype == pt_decal)
1051 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]);
1052 else if (info->particletype == pt_beam)
1053 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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);
1056 if (!cl_particles.integer)
1058 switch (info->particletype)
1060 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1061 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1062 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1063 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1066 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1067 VectorCopy(originmins, trailpos);
1068 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1070 if (info->tex[1] > info->tex[0])
1072 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1073 tex = min(tex, info->tex[1] - 1);
1075 if (info->trailspacing <= 0)
1077 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1078 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1079 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1082 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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, 0, 0);
1083 if (info->trailspacing > 0)
1084 VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1091 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1099 void CL_EntityParticles (const entity_t *ent)
1102 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1103 static vec3_t avelocities[NUMVERTEXNORMALS];
1104 if (!cl_particles.integer) return;
1106 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1108 if (!avelocities[0][0])
1109 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1110 avelocities[0][i] = lhrandom(0, 2.55);
1112 for (i = 0;i < NUMVERTEXNORMALS;i++)
1114 yaw = cl.time * avelocities[i][0];
1115 pitch = cl.time * avelocities[i][1];
1116 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1117 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1118 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1119 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0);
1124 void CL_ReadPointFile_f (void)
1126 vec3_t org, leakorg;
1128 char *pointfile = NULL, *pointfilepos, *t, tchar;
1129 char name[MAX_OSPATH];
1134 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1135 strlcat (name, ".pts", sizeof (name));
1136 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1139 Con_Printf("Could not open %s\n", name);
1143 Con_Printf("Reading %s...\n", name);
1144 VectorClear(leakorg);
1147 pointfilepos = pointfile;
1148 while (*pointfilepos)
1150 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1155 while (*t && *t != '\n' && *t != '\r')
1159 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1165 VectorCopy(org, leakorg);
1168 if (cl.num_particles < cl.max_particles - 3)
1171 particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0);
1174 Mem_Free(pointfile);
1175 VectorCopy(leakorg, org);
1176 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1178 particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0);
1179 particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0);
1180 particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0);
1185 CL_ParseParticleEffect
1187 Parse an effect out of the server message
1190 void CL_ParseParticleEffect (void)
1193 int i, count, msgcount, color;
1195 MSG_ReadVector(org, cls.protocol);
1196 for (i=0 ; i<3 ; i++)
1197 dir[i] = MSG_ReadChar ();
1198 msgcount = MSG_ReadByte ();
1199 color = MSG_ReadByte ();
1201 if (msgcount == 255)
1206 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1211 CL_ParticleExplosion
1215 void CL_ParticleExplosion (const vec3_t org)
1221 if (cl_stainmaps.integer)
1222 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1223 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1225 if (cl_particles_quake.integer)
1227 for (i = 0;i < 1024;i++)
1233 color = particlepalette[ramp1[r]];
1234 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 16, 256);
1238 color = particlepalette[ramp2[r]];
1239 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 16, 256);
1245 i = CL_PointSuperContents(org);
1246 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1248 if (cl_particles.integer && cl_particles_bubbles.integer)
1249 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1250 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, (1.0 / 16.0), 16, 96);
1254 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1256 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1258 for (i = 0;i < 32;i++)
1262 for (k = 0;k < 16;k++)
1264 v[0] = org[0] + lhrandom(-48, 48);
1265 v[1] = org[1] + lhrandom(-48, 48);
1266 v[2] = org[2] + lhrandom(-48, 48);
1267 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1268 if (trace.fraction >= 0.1)
1271 VectorSubtract(trace.endpos, org, v2);
1272 VectorScale(v2, 2.0f, v2);
1273 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0);
1277 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1278 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1279 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0, 256);
1283 if (cl_particles_explosions_shell.integer)
1284 R_NewExplosion(org);
1289 CL_ParticleExplosion2
1293 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1296 if (!cl_particles.integer) return;
1298 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1300 k = particlepalette[colorStart + (i % colorLength)];
1301 if (cl_particles_quake.integer)
1302 particle(particletype + pt_static, k, k, tex_particle, 1, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 8, 256);
1304 particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), 8, 192);
1308 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1310 if (cl_particles_sparks.integer)
1312 sparkcount *= cl_particles_quality.value;
1313 while(sparkcount-- > 0)
1314 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 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]) + sv_gravity.value * 0.1, 0, 0, 64);
1316 if (cl_particles_smoke.integer)
1318 smokecount *= cl_particles_quality.value;
1319 while(smokecount-- > 0)
1320 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 255, 1024, 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);
1324 void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int gravity, int randomvel)
1327 if (!cl_particles.integer) return;
1329 count *= cl_particles_quality.value;
1332 k = particlepalette[colorbase + (rand()&3)];
1333 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 255, 128, gravity ? 1 : 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, randomvel);
1337 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1340 float z, minz, maxz;
1342 if (!cl_particles.integer) return;
1343 if (dir[2] < 0) // falling
1348 minz = z - fabs(dir[2]) * 0.1;
1349 maxz = z + fabs(dir[2]) * 0.1;
1350 minz = bound(mins[2], minz, maxs[2]);
1351 maxz = bound(mins[2], maxz, maxs[2]);
1353 count *= cl_particles_quality.value;
1358 count *= 4; // ick, this should be in the mod or maps?
1362 k = particlepalette[colorbase + (rand()&3)];
1363 if (gamemode == GAME_GOODVSBAD2)
1364 particle(particletype + pt_rain, k, k, tex_particle, 20, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1366 particle(particletype + pt_rain, k, k, tex_particle, 0.5, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1372 k = particlepalette[colorbase + (rand()&3)];
1373 if (gamemode == GAME_GOODVSBAD2)
1374 p = particle(particletype + pt_snow, k, k, tex_particle, 20, 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);
1376 p = particle(particletype + pt_snow, k, k, tex_particle, 1, 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);
1378 VectorCopy(p->vel, p->relativedirection);
1382 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1391 void CL_MoveParticles (void)
1394 int i, maxparticle, j, a, content;
1395 float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1399 // LordHavoc: early out condition
1400 if (!cl.num_particles)
1402 cl.free_particle = 0;
1406 frametime = cl.time - cl.oldtime;
1407 gravity = frametime * sv_gravity.value;
1408 dvel = 1+4*frametime;
1409 bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1413 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1420 p->alpha -= p->alphafade * frametime;
1428 if (p->type->orientation != PARTICLE_BEAM)
1430 VectorCopy(p->org, oldorg);
1431 VectorMA(p->org, frametime, p->vel, p->org);
1432 VectorCopy(p->org, org);
1435 trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), false);
1436 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1437 // or if the trace hit something flagged as NOIMPACT
1438 // then remove the particle
1439 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1444 // react if the particle hit something
1445 if (trace.fraction < 1)
1447 VectorCopy(trace.endpos, p->org);
1448 if (p->type == particletype + pt_rain)
1450 // raindrop - splash on solid/water/slime/lava
1452 // convert from a raindrop particle to a rainsplash decal
1453 VectorCopy(trace.plane.normal, p->vel);
1454 VectorAdd(p->org, p->vel, p->org);
1455 p->type = particletype + pt_raindecal;
1456 p->texnum = tex_rainsplash[0];
1458 p->alphafade = p->alpha / 0.4;
1465 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 32);
1467 else if (p->type == particletype + pt_blood)
1469 // blood - splash on solid
1470 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1475 if (!cl_decals.integer)
1480 // convert from a blood particle to a blood decal
1481 VectorCopy(trace.plane.normal, p->vel);
1482 VectorAdd(p->org, p->vel, p->org);
1483 if (cl_stainmaps.integer)
1484 R_Stain(p->org, 32, 32, 16, 16, p->alpha * p->size * (1.0f / 40.0f), 192, 48, 48, p->alpha * p->size * (1.0f / 40.0f));
1486 p->type = particletype + pt_decal;
1487 p->texnum = tex_blooddecal[rand()&7];
1489 p->ownermodel = cl.entities[hitent].render.model;
1490 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1491 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1499 else if (p->bounce < 0)
1501 // bounce -1 means remove on impact
1507 // anything else - bounce off solid
1508 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1509 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1510 if (DotProduct(p->vel, p->vel) < 0.03)
1511 VectorClear(p->vel);
1515 p->vel[2] -= p->gravity * gravity;
1519 f = p->friction * frametime;
1520 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1523 VectorScale(p->vel, f, p->vel);
1527 if (p->type != particletype + pt_static)
1529 switch (p->type - particletype)
1531 case pt_entityparticle:
1532 // particle that removes itself after one rendered frame
1539 a = CL_PointSuperContents(p->org);
1540 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1542 p->size += frametime * 8;
1543 //p->alpha -= bloodwaterfade;
1546 p->vel[2] -= gravity;
1547 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1551 a = CL_PointSuperContents(p->org);
1552 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1559 a = CL_PointSuperContents(p->org);
1560 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1564 if (cl.time > p->time2)
1567 p->time2 = cl.time + (rand() & 3) * 0.1;
1568 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1569 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1570 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1572 a = CL_PointSuperContents(p->org);
1573 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1577 //p->size += frametime * 15;
1580 // FIXME: this has fairly wacky handling of alpha
1581 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1582 if (cl.entities[p->owner].render.model == p->ownermodel)
1584 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1585 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1591 a = max(0, (cl.time - p->time2) * 40);
1593 p->texnum = tex_rainsplash[a];
1602 cl.num_particles = maxparticle + 1;
1603 cl.free_particle = 0;
1606 #define MAX_PARTICLETEXTURES 64
1607 // particletexture_t is a rectangle in the particlefonttexture
1608 typedef struct particletexture_s
1610 rtexture_t *texture;
1611 float s1, t1, s2, t2;
1615 static rtexturepool_t *particletexturepool;
1616 static rtexture_t *particlefonttexture;
1617 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1619 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1621 #define PARTICLETEXTURESIZE 64
1622 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1624 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1628 dz = 1 - (dx*dx+dy*dy);
1629 if (dz > 0) // it does hit the sphere
1633 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1634 VectorNormalize(normal);
1635 dot = DotProduct(normal, light);
1636 if (dot > 0.5) // interior reflection
1637 f += ((dot * 2) - 1);
1638 else if (dot < -0.5) // exterior reflection
1639 f += ((dot * -2) - 1);
1641 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1642 VectorNormalize(normal);
1643 dot = DotProduct(normal, light);
1644 if (dot > 0.5) // interior reflection
1645 f += ((dot * 2) - 1);
1646 else if (dot < -0.5) // exterior reflection
1647 f += ((dot * -2) - 1);
1649 f += 16; // just to give it a haze so you can see the outline
1650 f = bound(0, f, 255);
1651 return (unsigned char) f;
1657 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1659 int basex, basey, y;
1660 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1661 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1662 particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1663 particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1664 particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1665 particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1666 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1667 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1670 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1673 float cx, cy, dx, dy, f, iradius;
1675 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1676 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1677 iradius = 1.0f / radius;
1678 alpha *= (1.0f / 255.0f);
1679 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1681 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1685 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1688 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1689 d[0] += f * (red - d[0]);
1690 d[1] += f * (green - d[1]);
1691 d[2] += f * (blue - d[2]);
1697 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1700 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1702 data[0] = bound(minr, data[0], maxr);
1703 data[1] = bound(ming, data[1], maxg);
1704 data[2] = bound(minb, data[2], maxb);
1708 void particletextureinvert(unsigned char *data)
1711 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1713 data[0] = 255 - data[0];
1714 data[1] = 255 - data[1];
1715 data[2] = 255 - data[2];
1719 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1720 static void R_InitBloodTextures (unsigned char *particletexturedata)
1723 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1726 for (i = 0;i < 8;i++)
1728 memset(&data[0][0][0], 255, sizeof(data));
1729 for (k = 0;k < 24;k++)
1730 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1731 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1732 particletextureinvert(&data[0][0][0]);
1733 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1737 for (i = 0;i < 8;i++)
1739 memset(&data[0][0][0], 255, sizeof(data));
1741 for (j = 1;j < 10;j++)
1742 for (k = min(j, m - 1);k < m;k++)
1743 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1744 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1745 particletextureinvert(&data[0][0][0]);
1746 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1751 static void R_InitParticleTexture (void)
1753 int x, y, d, i, k, m;
1754 float dx, dy, radius, f, f2;
1755 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1757 unsigned char *particletexturedata;
1759 // a note: decals need to modulate (multiply) the background color to
1760 // properly darken it (stain), and they need to be able to alpha fade,
1761 // this is a very difficult challenge because it means fading to white
1762 // (no change to background) rather than black (darkening everything
1763 // behind the whole decal polygon), and to accomplish this the texture is
1764 // inverted (dark red blood on white background becomes brilliant cyan
1765 // and white on black background) so we can alpha fade it to black, then
1766 // we invert it again during the blendfunc to make it work...
1768 particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1769 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1772 for (i = 0;i < 8;i++)
1774 memset(&data[0][0][0], 255, sizeof(data));
1777 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1779 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1780 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1782 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1784 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1785 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1787 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1788 d = (noise2[y][x] - 128) * 3 + 192;
1790 d = d * (1-(dx*dx+dy*dy));
1791 d = (d * noise1[y][x]) >> 7;
1792 d = bound(0, d, 255);
1793 data[y][x][3] = (unsigned char) d;
1800 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1804 for (i = 0;i < 16;i++)
1806 memset(&data[0][0][0], 255, sizeof(data));
1807 radius = i * 3.0f / 4.0f / 16.0f;
1808 f2 = 255.0f * ((15.0f - i) / 15.0f);
1809 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1811 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1812 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1814 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1815 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1816 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1819 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1823 memset(&data[0][0][0], 255, sizeof(data));
1824 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1826 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1827 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1829 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1830 d = 256 * (1 - (dx*dx+dy*dy));
1831 d = bound(0, d, 255);
1832 data[y][x][3] = (unsigned char) d;
1835 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1838 memset(&data[0][0][0], 255, sizeof(data));
1839 light[0] = 1;light[1] = 1;light[2] = 1;
1840 VectorNormalize(light);
1841 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1843 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1844 // stretch upper half of bubble by +50% and shrink lower half by -50%
1845 // (this gives an elongated teardrop shape)
1847 dy = (dy - 0.5f) * 2.0f;
1849 dy = (dy - 0.5f) / 1.5f;
1850 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1852 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1853 // shrink bubble width to half
1855 data[y][x][3] = shadebubble(dx, dy, light);
1858 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1861 memset(&data[0][0][0], 255, sizeof(data));
1862 light[0] = 1;light[1] = 1;light[2] = 1;
1863 VectorNormalize(light);
1864 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1866 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1867 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1869 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1870 data[y][x][3] = shadebubble(dx, dy, light);
1873 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1875 // Blood particles and blood decals
1876 R_InitBloodTextures (particletexturedata);
1879 for (i = 0;i < 8;i++)
1881 memset(&data[0][0][0], 255, sizeof(data));
1882 for (k = 0;k < 12;k++)
1883 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1884 for (k = 0;k < 3;k++)
1885 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1886 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1887 particletextureinvert(&data[0][0][0]);
1888 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1892 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1895 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1896 if (!particlefonttexture)
1897 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1898 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1899 particletexture[i].texture = particlefonttexture;
1902 fractalnoise(&noise3[0][0], 64, 4);
1904 for (y = 0;y < 64;y++)
1906 dy = (y - 0.5f*64) / (64*0.5f-1);
1907 for (x = 0;x < 16;x++)
1909 dx = (x - 0.5f*16) / (16*0.5f-2);
1910 d = (1 - sqrt(fabs(dx))) * noise3[y][x];
1911 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1912 data2[y][x][3] = 255;
1917 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1920 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1921 if (!particletexture[tex_beam].texture)
1922 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1923 particletexture[tex_beam].s1 = 0;
1924 particletexture[tex_beam].t1 = 0;
1925 particletexture[tex_beam].s2 = 1;
1926 particletexture[tex_beam].t2 = 1;
1927 Mem_Free(particletexturedata);
1930 static void r_part_start(void)
1932 particletexturepool = R_AllocTexturePool();
1933 R_InitParticleTexture ();
1934 CL_Particles_LoadEffectInfo();
1937 static void r_part_shutdown(void)
1939 R_FreeTexturePool(&particletexturepool);
1942 static void r_part_newmap(void)
1946 void R_Particles_Init (void)
1948 Cvar_RegisterVariable(&r_drawparticles);
1949 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1952 float particle_vertex3f[12], particle_texcoord2f[8];
1954 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1956 const particle_t *p = cl.particles + surfacenumber;
1959 float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1960 particletexture_t *tex;
1962 VectorCopy(p->org, org);
1964 blendmode = p->type->blendmode;
1965 tex = &particletexture[p->texnum];
1966 cr = p->color[0] * (1.0f / 255.0f);
1967 cg = p->color[1] * (1.0f / 255.0f);
1968 cb = p->color[2] * (1.0f / 255.0f);
1969 ca = p->alpha * (1.0f / 255.0f);
1970 if (blendmode == PBLEND_MOD)
1980 ca /= cl_particles_quality.value;
1981 if (p->type->lighting)
1983 float ambient[3], diffuse[3], diffusenormal[3];
1984 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1985 cr *= (ambient[0] + 0.5 * diffuse[0]);
1986 cg *= (ambient[1] + 0.5 * diffuse[1]);
1987 cb *= (ambient[2] + 0.5 * diffuse[2]);
1991 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
1996 if (blendmode == PBLEND_ALPHA)
1998 cr += fogcolor[0] * fog;
1999 cg += fogcolor[1] * fog;
2000 cb += fogcolor[2] * fog;
2004 R_Mesh_Matrix(&identitymatrix);
2006 memset(&m, 0, sizeof(m));
2007 m.tex[0] = R_GetTexture(tex->texture);
2008 m.pointer_texcoord[0] = particle_texcoord2f;
2009 m.pointer_vertex = particle_vertex3f;
2012 GL_Color(cr, cg, cb, ca);
2014 if (blendmode == PBLEND_ALPHA)
2015 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2016 else if (blendmode == PBLEND_ADD)
2017 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2018 else //if (blendmode == PBLEND_MOD)
2019 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2020 GL_DepthMask(false);
2022 size = p->size * cl_particles_size.value;
2023 if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2025 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2028 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2030 VectorNegate(p->vel, v);
2031 VectorVectors(v, right, up);
2034 VectorVectors(p->vel, right, up);
2035 VectorScale(right, size, right);
2036 VectorScale(up, size, up);
2040 VectorScale(r_viewleft, -size, right);
2041 VectorScale(r_viewup, size, up);
2043 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2044 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2045 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2046 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2047 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2048 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2049 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2050 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2051 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2052 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2053 particle_vertex3f[10] = org[1] + right[1] - up[1];
2054 particle_vertex3f[11] = org[2] + right[2] - up[2];
2055 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2056 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2057 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2058 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2060 else if (p->type->orientation == PARTICLE_SPARK)
2062 VectorMA(p->org, -0.02, p->vel, v);
2063 VectorMA(p->org, 0.02, p->vel, up2);
2064 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2065 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2066 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2067 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2068 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2070 else if (p->type->orientation == PARTICLE_BEAM)
2072 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2073 VectorSubtract(p->vel, p->org, up);
2074 VectorNormalize(up);
2075 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2076 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2077 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2078 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2079 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2080 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2084 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2088 R_Mesh_Draw(0, 4, 2, polygonelements);
2091 void R_DrawParticles (void)
2094 float minparticledist;
2097 // LordHavoc: early out conditions
2098 if ((!cl.num_particles) || (!r_drawparticles.integer))
2101 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2103 // LordHavoc: only render if not too close
2104 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2108 renderstats.particles++;
2109 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2111 if (p->type == particletype + pt_decal)
2112 R_DrawParticle_TransparentCallback(0, i, 0);
2114 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);