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, plus size increase over time
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;
105 qboolean lightshadow;
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 = 32;
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] = strtol(argv[1+arrayindex], NULL, 0)
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 = strtol(argv[1], NULL, 0)
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], "sizeincrease")) {readfloat(info->size[2]);}
300 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
301 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
302 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
303 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
304 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
305 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
306 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
307 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
308 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
309 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
310 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
311 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
312 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
313 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
314 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
315 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
316 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
317 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
318 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
319 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
321 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
330 int CL_ParticleEffectIndexForName(const char *name)
333 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
334 if (!strcmp(particleeffectname[i], name))
339 const char *CL_ParticleEffectNameForIndex(int i)
341 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
343 return particleeffectname[i];
346 // MUST match effectnameindex_t in client.h
347 static const char *standardeffectnames[EFFECT_TOTAL] =
372 "TE_TEI_BIGEXPLOSION",
388 void CL_Particles_LoadEffectInfo(void)
391 unsigned char *filedata;
392 fs_offset_t filesize;
393 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
394 memset(particleeffectname, 0, sizeof(particleeffectname));
395 for (i = 0;i < EFFECT_TOTAL;i++)
396 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
397 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
400 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
410 void CL_ReadPointFile_f (void);
411 void CL_Particles_Init (void)
413 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)");
414 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
416 Cvar_RegisterVariable (&cl_particles);
417 Cvar_RegisterVariable (&cl_particles_quality);
418 Cvar_RegisterVariable (&cl_particles_size);
419 Cvar_RegisterVariable (&cl_particles_quake);
420 Cvar_RegisterVariable (&cl_particles_blood);
421 Cvar_RegisterVariable (&cl_particles_blood_alpha);
422 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
423 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
424 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
425 Cvar_RegisterVariable (&cl_particles_explosions_shell);
426 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
427 Cvar_RegisterVariable (&cl_particles_smoke);
428 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
429 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
430 Cvar_RegisterVariable (&cl_particles_sparks);
431 Cvar_RegisterVariable (&cl_particles_bubbles);
432 Cvar_RegisterVariable (&cl_decals);
433 Cvar_RegisterVariable (&cl_decals_time);
434 Cvar_RegisterVariable (&cl_decals_fadetime);
437 void CL_Particles_Shutdown (void)
441 // list of all 26 parameters:
442 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
443 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
444 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
445 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
446 // palpha - opacity of particle as 0-255 (can be more than 255)
447 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
448 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
449 // pgravity - how much effect gravity has on the particle (0-1)
450 // 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
451 // px,py,pz - starting origin of particle
452 // pvx,pvy,pvz - starting velocity of particle
453 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
454 static particle_t *particle(particletype_t *ptype, 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 pfriction, float originjitter, float velocityjitter)
459 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
460 if (cl.free_particle >= cl.max_particles)
462 part = &cl.particles[cl.free_particle++];
463 if (cl.num_particles < cl.free_particle)
464 cl.num_particles = cl.free_particle;
465 memset(part, 0, sizeof(*part));
467 l2 = (int)lhrandom(0.5, 256.5);
469 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
472 part->color[3] = 0xFF;
475 part->sizeincrease = psizeincrease;
476 part->alpha = palpha;
477 part->alphafade = palphafade;
478 part->gravity = pgravity;
479 part->bounce = pbounce;
481 part->org[0] = px + originjitter * v[0];
482 part->org[1] = py + originjitter * v[1];
483 part->org[2] = pz + originjitter * v[2];
484 part->vel[0] = pvx + velocityjitter * v[0];
485 part->vel[1] = pvy + velocityjitter * v[1];
486 part->vel[2] = pvz + velocityjitter * v[2];
488 part->friction = pfriction;
492 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
495 if (!cl_decals.integer)
497 p = particle(particletype + pt_decal, color1, color2, texnum, size, 0, 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);
502 p->ownermodel = cl.entities[p->owner].render.model;
503 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
504 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
505 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
509 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
512 float bestfrac, bestorg[3], bestnormal[3];
514 int besthitent = 0, hitent;
517 for (i = 0;i < 32;i++)
520 VectorMA(org, maxdist, org2, org2);
521 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
522 // take the closest trace result that doesn't end up hitting a NOMARKS
523 // surface (sky for example)
524 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
526 bestfrac = trace.fraction;
528 VectorCopy(trace.endpos, bestorg);
529 VectorCopy(trace.plane.normal, bestnormal);
533 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
536 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
537 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)
540 matrix4x4_t tempmatrix;
541 VectorLerp(originmins, 0.5, originmaxs, center);
542 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
543 if (effectnameindex == EFFECT_SVC_PARTICLE)
545 if (cl_particles.integer)
547 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
549 CL_ParticleExplosion(center);
550 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
551 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
554 count *= cl_particles_quality.value;
555 for (;count > 0;count--)
557 int k = particlepalette[palettecolor + (rand()&7)];
558 if (cl_particles_quake.integer)
559 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 0);
560 else if (gamemode == GAME_GOODVSBAD2)
561 particle(particletype + 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, 8, 10);
563 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 15);
568 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
569 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
570 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
571 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
572 else if (effectnameindex == EFFECT_TE_SPIKE)
574 if (cl_particles_bulletimpacts.integer)
576 if (cl_particles_quake.integer)
578 if (cl_particles_smoke.integer)
579 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
582 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
585 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
586 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
588 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
590 if (cl_particles_bulletimpacts.integer)
592 if (cl_particles_quake.integer)
594 if (cl_particles_smoke.integer)
595 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
598 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
601 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
602 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
603 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);
605 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
607 if (cl_particles_bulletimpacts.integer)
609 if (cl_particles_quake.integer)
611 if (cl_particles_smoke.integer)
612 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
615 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
618 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
619 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
621 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
623 if (cl_particles_bulletimpacts.integer)
625 if (cl_particles_quake.integer)
627 if (cl_particles_smoke.integer)
628 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
631 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
634 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
635 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
636 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);
638 else if (effectnameindex == EFFECT_TE_BLOOD)
640 if (!cl_particles_blood.integer)
642 if (cl_particles_quake.integer)
643 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
646 static double bloodaccumulator = 0;
647 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
648 for (;bloodaccumulator > 0;bloodaccumulator--)
649 particle(particletype + 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, 0, 64);
652 else if (effectnameindex == EFFECT_TE_SPARK)
653 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
654 else if (effectnameindex == EFFECT_TE_PLASMABURN)
656 // plasma scorch mark
657 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
658 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
659 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
661 else if (effectnameindex == EFFECT_TE_GUNSHOT)
663 if (cl_particles_bulletimpacts.integer)
665 if (cl_particles_quake.integer)
666 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
668 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
671 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
672 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
674 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
676 if (cl_particles_bulletimpacts.integer)
678 if (cl_particles_quake.integer)
679 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
681 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
684 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
685 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
686 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);
688 else if (effectnameindex == EFFECT_TE_EXPLOSION)
690 CL_ParticleExplosion(center);
691 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);
693 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
695 CL_ParticleExplosion(center);
696 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);
698 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
700 if (cl_particles_quake.integer)
703 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
706 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, 16, 256);
708 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 16, 0);
712 CL_ParticleExplosion(center);
713 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);
715 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
716 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);
717 else if (effectnameindex == EFFECT_TE_FLAMEJET)
719 count *= cl_particles_quality.value;
721 particle(particletype + 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, 0, 128);
723 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
725 float i, j, inc, vel;
728 inc = 8 / cl_particles_quality.value;
729 for (i = -128;i < 128;i += inc)
731 for (j = -128;j < 128;j += inc)
733 dir[0] = j + lhrandom(0, inc);
734 dir[1] = i + lhrandom(0, inc);
736 org[0] = center[0] + dir[0];
737 org[1] = center[1] + dir[1];
738 org[2] = center[2] + lhrandom(0, 64);
739 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
740 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 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);
744 else if (effectnameindex == EFFECT_TE_TELEPORT)
746 float i, j, k, inc, vel;
749 inc = 4 / cl_particles_quality.value;
750 for (i = -16;i < 16;i += inc)
752 for (j = -16;j < 16;j += inc)
754 for (k = -24;k < 32;k += inc)
756 VectorSet(dir, i*8, j*8, k*8);
757 VectorNormalize(dir);
758 vel = lhrandom(50, 113);
759 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 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);
763 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);
765 else if (effectnameindex == EFFECT_TE_TEI_G3)
766 particle(particletype + 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);
767 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
769 if (cl_particles_smoke.integer)
771 count *= 0.25f * cl_particles_quality.value;
773 particle(particletype + 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, 1.5f, 6.0f);
776 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
778 CL_ParticleExplosion(center);
779 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);
781 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
784 if (cl_stainmaps.integer)
785 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
786 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
787 if (cl_particles_smoke.integer)
788 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
789 particle(particletype + 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, 20, 155);
790 if (cl_particles_sparks.integer)
791 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
792 particle(particletype + 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, 465);
793 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);
795 else if (effectnameindex == EFFECT_EF_FLAME)
797 count *= 300 * cl_particles_quality.value;
799 particle(particletype + 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, 16, 128);
800 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);
802 else if (effectnameindex == EFFECT_EF_STARDUST)
804 count *= 200 * cl_particles_quality.value;
806 particle(particletype + 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, 16, 128);
807 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);
809 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
813 int smoke, blood, bubbles, r, color;
815 if (effectnameindex == EFFECT_TR_ROCKET)
816 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);
817 else if (effectnameindex == EFFECT_TR_VORESPIKE)
819 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
820 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);
822 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);
824 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
825 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);
827 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
830 VectorSubtract(originmaxs, originmins, dir);
831 len = VectorNormalizeLength(dir);
832 dec = -ent->persistent.trail_time;
833 ent->persistent.trail_time += len;
834 if (ent->persistent.trail_time < 0.01f)
837 // if we skip out, leave it reset
838 ent->persistent.trail_time = 0.0f;
840 // advance into this frame to reach the first puff location
841 VectorMA(originmins, dec, dir, pos);
844 smoke = cl_particles.integer && cl_particles_smoke.integer;
845 blood = cl_particles.integer && cl_particles_blood.integer;
846 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
847 qd = 1.0f / cl_particles_quality.value;
854 if (effectnameindex == EFFECT_TR_BLOOD)
856 if (cl_particles_quake.integer)
858 color = particlepalette[67 + (rand()&3)];
859 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
864 particle(particletype + 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, 0, 64);
867 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
869 if (cl_particles_quake.integer)
872 color = particlepalette[67 + (rand()&3)];
873 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
878 particle(particletype + 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, 0, 64);
884 if (effectnameindex == EFFECT_TR_ROCKET)
886 if (cl_particles_quake.integer)
889 color = particlepalette[ramp3[r]];
890 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
894 particle(particletype + 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);
895 particle(particletype + 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, 20);
898 else if (effectnameindex == EFFECT_TR_GRENADE)
900 if (cl_particles_quake.integer)
903 color = particlepalette[ramp3[r]];
904 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
908 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, 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);
911 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
913 if (cl_particles_quake.integer)
916 color = particlepalette[52 + (rand()&7)];
917 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
918 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
920 else if (gamemode == GAME_GOODVSBAD2)
923 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
927 color = particlepalette[20 + (rand()&7)];
928 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
931 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
933 if (cl_particles_quake.integer)
936 color = particlepalette[230 + (rand()&7)];
937 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
938 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
942 color = particlepalette[226 + (rand()&7)];
943 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
946 else if (effectnameindex == EFFECT_TR_VORESPIKE)
948 if (cl_particles_quake.integer)
950 color = particlepalette[152 + (rand()&3)];
951 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
953 else if (gamemode == GAME_GOODVSBAD2)
956 particle(particletype + 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);
958 else if (gamemode == GAME_PRYDON)
961 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
964 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
966 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
969 particle(particletype + 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, 4);
971 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
974 particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 16);
976 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
977 particle(particletype + 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);
981 if (effectnameindex == EFFECT_TR_ROCKET)
982 particle(particletype + 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, (1.0 / 16.0), 0, 16);
983 else if (effectnameindex == EFFECT_TR_GRENADE)
984 particle(particletype + 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, (1.0 / 16.0), 0, 16);
986 // advance to next time and position
989 VectorMA (pos, dec, dir, pos);
991 ent->persistent.trail_time = len;
993 else if (developer.integer >= 1)
994 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
997 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)
1000 qboolean found = false;
1001 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1002 return; // invalid effect index
1003 if (!particleeffectname[effectnameindex][0])
1004 return; // no such effect
1005 VectorLerp(originmins, 0.5, originmaxs, center);
1006 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1008 int effectinfoindex;
1011 particleeffectinfo_t *info;
1013 vec3_t centervelocity;
1019 qboolean underwater;
1020 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1021 VectorLerp(originmins, 0.5, originmaxs, center);
1022 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1023 supercontents = CL_PointSuperContents(center);
1024 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1025 VectorSubtract(originmaxs, originmins, traildir);
1026 traillen = VectorLength(traildir);
1027 VectorNormalize(traildir);
1028 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1030 if (info->effectnameindex == effectnameindex)
1033 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1035 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1038 // spawn a dlight if requested
1039 if (info->lightradiusstart > 0)
1041 matrix4x4_t tempmatrix;
1042 if (info->trailspacing > 0)
1043 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1045 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1046 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);
1051 if (info->tex[1] > info->tex[0])
1053 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1054 tex = min(tex, info->tex[1] - 1);
1056 if (info->particletype == pt_decal)
1057 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]);
1058 else if (info->particletype == pt_beam)
1059 particle(particletype + 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);
1062 if (!cl_particles.integer)
1064 switch (info->particletype)
1066 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1067 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1068 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1069 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1072 VectorCopy(originmins, trailpos);
1073 if (info->trailspacing > 0)
1075 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1076 trailstep = info->trailspacing / cl_particles_quality.value;
1080 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1083 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1085 if (info->tex[1] > info->tex[0])
1087 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1088 tex = min(tex, info->tex[1] - 1);
1092 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1093 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1094 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1097 particle(particletype + 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, 0, 0);
1099 VectorMA(trailpos, trailstep, traildir, trailpos);
1106 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1114 void CL_EntityParticles (const entity_t *ent)
1117 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1118 static vec3_t avelocities[NUMVERTEXNORMALS];
1119 if (!cl_particles.integer) return;
1121 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1123 if (!avelocities[0][0])
1124 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1125 avelocities[0][i] = lhrandom(0, 2.55);
1127 for (i = 0;i < NUMVERTEXNORMALS;i++)
1129 yaw = cl.time * avelocities[i][0];
1130 pitch = cl.time * avelocities[i][1];
1131 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1132 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1133 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1134 particle(particletype + 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);
1139 void CL_ReadPointFile_f (void)
1141 vec3_t org, leakorg;
1143 char *pointfile = NULL, *pointfilepos, *t, tchar;
1144 char name[MAX_OSPATH];
1149 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1150 strlcat (name, ".pts", sizeof (name));
1151 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1154 Con_Printf("Could not open %s\n", name);
1158 Con_Printf("Reading %s...\n", name);
1159 VectorClear(leakorg);
1162 pointfilepos = pointfile;
1163 while (*pointfilepos)
1165 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1170 while (*t && *t != '\n' && *t != '\r')
1174 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1180 VectorCopy(org, leakorg);
1183 if (cl.num_particles < cl.max_particles - 3)
1186 particle(particletype + 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);
1189 Mem_Free(pointfile);
1190 VectorCopy(leakorg, org);
1191 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1193 particle(particletype + 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);
1194 particle(particletype + 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);
1195 particle(particletype + 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);
1200 CL_ParseParticleEffect
1202 Parse an effect out of the server message
1205 void CL_ParseParticleEffect (void)
1208 int i, count, msgcount, color;
1210 MSG_ReadVector(org, cls.protocol);
1211 for (i=0 ; i<3 ; i++)
1212 dir[i] = MSG_ReadChar ();
1213 msgcount = MSG_ReadByte ();
1214 color = MSG_ReadByte ();
1216 if (msgcount == 255)
1221 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1226 CL_ParticleExplosion
1230 void CL_ParticleExplosion (const vec3_t org)
1236 if (cl_stainmaps.integer)
1237 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1238 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1240 if (cl_particles_quake.integer)
1242 for (i = 0;i < 1024;i++)
1248 color = particlepalette[ramp1[r]];
1249 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 16, 256);
1253 color = particlepalette[ramp2[r]];
1254 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 16, 256);
1260 i = CL_PointSuperContents(org);
1261 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1263 if (cl_particles.integer && cl_particles_bubbles.integer)
1264 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1265 particle(particletype + 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, (1.0 / 16.0), 16, 96);
1269 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1271 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1273 for (i = 0;i < 32;i++)
1277 for (k = 0;k < 16;k++)
1279 v[0] = org[0] + lhrandom(-48, 48);
1280 v[1] = org[1] + lhrandom(-48, 48);
1281 v[2] = org[2] + lhrandom(-48, 48);
1282 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1283 if (trace.fraction >= 0.1)
1286 VectorSubtract(trace.endpos, org, v2);
1287 VectorScale(v2, 2.0f, v2);
1288 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 0, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0);
1292 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1293 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1294 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0, 256);
1298 if (cl_particles_explosions_shell.integer)
1299 R_NewExplosion(org);
1304 CL_ParticleExplosion2
1308 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1311 if (!cl_particles.integer) return;
1313 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1315 k = particlepalette[colorStart + (i % colorLength)];
1316 if (cl_particles_quake.integer)
1317 particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 8, 256);
1319 particle(particletype + 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), 8, 192);
1323 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1325 if (cl_particles_sparks.integer)
1327 sparkcount *= cl_particles_quality.value;
1328 while(sparkcount-- > 0)
1329 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 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]) + sv_gravity.value * 0.1, 0, 0, 64);
1331 if (cl_particles_smoke.integer)
1333 smokecount *= cl_particles_quality.value;
1334 while(smokecount-- > 0)
1335 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 0, 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);
1339 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)
1342 if (!cl_particles.integer) return;
1344 count = (int)(count * cl_particles_quality.value);
1347 k = particlepalette[colorbase + (rand()&3)];
1348 particle(particletype + 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, randomvel);
1352 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1355 float z, minz, maxz;
1357 if (!cl_particles.integer) return;
1358 if (dir[2] < 0) // falling
1363 minz = z - fabs(dir[2]) * 0.1;
1364 maxz = z + fabs(dir[2]) * 0.1;
1365 minz = bound(mins[2], minz, maxs[2]);
1366 maxz = bound(mins[2], maxz, maxs[2]);
1368 count = (int)(count * cl_particles_quality.value);
1373 count *= 4; // ick, this should be in the mod or maps?
1377 k = particlepalette[colorbase + (rand()&3)];
1378 if (gamemode == GAME_GOODVSBAD2)
1379 particle(particletype + pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1381 particle(particletype + pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
1387 k = particlepalette[colorbase + (rand()&3)];
1388 if (gamemode == GAME_GOODVSBAD2)
1389 p = particle(particletype + 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);
1391 p = particle(particletype + 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);
1393 VectorCopy(p->vel, p->relativedirection);
1397 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1406 void CL_MoveParticles (void)
1409 int i, maxparticle, j, a, content;
1410 float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1414 // LordHavoc: early out condition
1415 if (!cl.num_particles)
1417 cl.free_particle = 0;
1421 frametime = cl.time - cl.oldtime;
1422 gravity = frametime * sv_gravity.value;
1423 dvel = 1+4*frametime;
1424 bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1428 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1432 if (cl.free_particle > i)
1433 cl.free_particle = i;
1439 p->alpha -= p->alphafade * frametime;
1444 if (cl.free_particle > i)
1445 cl.free_particle = i;
1449 if (p->type->orientation != PARTICLE_BEAM)
1451 VectorCopy(p->org, oldorg);
1452 VectorMA(p->org, frametime, p->vel, p->org);
1453 VectorCopy(p->org, org);
1456 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);
1457 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1458 // or if the trace hit something flagged as NOIMPACT
1459 // then remove the particle
1460 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1465 // react if the particle hit something
1466 if (trace.fraction < 1)
1468 VectorCopy(trace.endpos, p->org);
1469 if (p->type == particletype + pt_rain)
1471 // raindrop - splash on solid/water/slime/lava
1473 // convert from a raindrop particle to a rainsplash decal
1474 VectorCopy(trace.plane.normal, p->vel);
1475 VectorAdd(p->org, p->vel, p->org);
1476 p->type = particletype + pt_raindecal;
1477 p->texnum = tex_rainsplash;
1479 p->alphafade = p->alpha / 0.4;
1484 p->sizeincrease = p->size * 16;
1487 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 32);
1489 else if (p->type == particletype + pt_blood)
1491 // blood - splash on solid
1492 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1497 if (!cl_decals.integer)
1502 // convert from a blood particle to a blood decal
1503 VectorCopy(trace.plane.normal, p->vel);
1504 VectorAdd(p->org, p->vel, p->org);
1505 if (cl_stainmaps.integer)
1506 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)));
1508 p->type = particletype + pt_decal;
1509 p->texnum = tex_blooddecal[rand()&7];
1511 p->ownermodel = cl.entities[hitent].render.model;
1512 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1513 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1521 else if (p->bounce < 0)
1523 // bounce -1 means remove on impact
1529 // anything else - bounce off solid
1530 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1531 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1532 if (DotProduct(p->vel, p->vel) < 0.03)
1533 VectorClear(p->vel);
1537 p->vel[2] -= p->gravity * gravity;
1541 f = p->friction * frametime;
1542 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1545 VectorScale(p->vel, f, p->vel);
1549 if (p->type != particletype + pt_static)
1551 switch (p->type - particletype)
1553 case pt_entityparticle:
1554 // particle that removes itself after one rendered frame
1561 a = CL_PointSuperContents(p->org);
1562 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1564 p->size += frametime * 8;
1565 //p->alpha -= bloodwaterfade;
1568 p->vel[2] -= gravity;
1569 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1573 a = CL_PointSuperContents(p->org);
1574 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1581 a = CL_PointSuperContents(p->org);
1582 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1586 if (cl.time > p->time2)
1589 p->time2 = cl.time + (rand() & 3) * 0.1;
1590 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1591 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1592 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1594 a = CL_PointSuperContents(p->org);
1595 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1599 //p->size += frametime * 15;
1602 // FIXME: this has fairly wacky handling of alpha
1603 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1604 if (cl.entities[p->owner].render.model == p->ownermodel)
1606 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1607 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1617 cl.num_particles = maxparticle + 1;
1620 #define MAX_PARTICLETEXTURES 64
1621 // particletexture_t is a rectangle in the particlefonttexture
1622 typedef struct particletexture_s
1624 rtexture_t *texture;
1625 float s1, t1, s2, t2;
1629 static rtexturepool_t *particletexturepool;
1630 static rtexture_t *particlefonttexture;
1631 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1633 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1635 #define PARTICLETEXTURESIZE 64
1636 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1638 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1642 dz = 1 - (dx*dx+dy*dy);
1643 if (dz > 0) // it does hit the sphere
1647 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1648 VectorNormalize(normal);
1649 dot = DotProduct(normal, light);
1650 if (dot > 0.5) // interior reflection
1651 f += ((dot * 2) - 1);
1652 else if (dot < -0.5) // exterior reflection
1653 f += ((dot * -2) - 1);
1655 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1656 VectorNormalize(normal);
1657 dot = DotProduct(normal, light);
1658 if (dot > 0.5) // interior reflection
1659 f += ((dot * 2) - 1);
1660 else if (dot < -0.5) // exterior reflection
1661 f += ((dot * -2) - 1);
1663 f += 16; // just to give it a haze so you can see the outline
1664 f = bound(0, f, 255);
1665 return (unsigned char) f;
1671 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1673 int basex, basey, y;
1674 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1675 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1676 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1677 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1680 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1683 float cx, cy, dx, dy, f, iradius;
1685 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1686 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1687 iradius = 1.0f / radius;
1688 alpha *= (1.0f / 255.0f);
1689 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1691 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1695 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1698 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1699 d[0] += (int)(f * (red - d[0]));
1700 d[1] += (int)(f * (green - d[1]));
1701 d[2] += (int)(f * (blue - d[2]));
1707 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1710 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1712 data[0] = bound(minr, data[0], maxr);
1713 data[1] = bound(ming, data[1], maxg);
1714 data[2] = bound(minb, data[2], maxb);
1718 void particletextureinvert(unsigned char *data)
1721 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1723 data[0] = 255 - data[0];
1724 data[1] = 255 - data[1];
1725 data[2] = 255 - data[2];
1729 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1730 static void R_InitBloodTextures (unsigned char *particletexturedata)
1733 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1736 for (i = 0;i < 8;i++)
1738 memset(&data[0][0][0], 255, sizeof(data));
1739 for (k = 0;k < 24;k++)
1740 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1741 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1742 particletextureinvert(&data[0][0][0]);
1743 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1747 for (i = 0;i < 8;i++)
1749 memset(&data[0][0][0], 255, sizeof(data));
1751 for (j = 1;j < 10;j++)
1752 for (k = min(j, m - 1);k < m;k++)
1753 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1754 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1755 particletextureinvert(&data[0][0][0]);
1756 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1761 //uncomment this to make engine save out particle font to a tga file when run
1762 //#define DUMPPARTICLEFONT
1764 static void R_InitParticleTexture (void)
1766 int x, y, d, i, k, m;
1770 // a note: decals need to modulate (multiply) the background color to
1771 // properly darken it (stain), and they need to be able to alpha fade,
1772 // this is a very difficult challenge because it means fading to white
1773 // (no change to background) rather than black (darkening everything
1774 // behind the whole decal polygon), and to accomplish this the texture is
1775 // inverted (dark red blood on white background becomes brilliant cyan
1776 // and white on black background) so we can alpha fade it to black, then
1777 // we invert it again during the blendfunc to make it work...
1779 #ifndef DUMPPARTICLEFONT
1780 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1781 if (!particlefonttexture)
1784 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1785 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1786 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1789 for (i = 0;i < 8;i++)
1791 memset(&data[0][0][0], 255, sizeof(data));
1794 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1796 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1797 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1799 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1801 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1802 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1804 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1805 d = (noise2[y][x] - 128) * 3 + 192;
1807 d = (int)(d * (1-(dx*dx+dy*dy)));
1808 d = (d * noise1[y][x]) >> 7;
1809 d = bound(0, d, 255);
1810 data[y][x][3] = (unsigned char) d;
1817 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1821 memset(&data[0][0][0], 255, sizeof(data));
1822 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1824 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1825 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1827 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1828 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1829 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1832 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1835 memset(&data[0][0][0], 255, sizeof(data));
1836 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1838 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1839 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1841 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1842 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1843 d = bound(0, d, 255);
1844 data[y][x][3] = (unsigned char) d;
1847 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1850 memset(&data[0][0][0], 255, sizeof(data));
1851 light[0] = 1;light[1] = 1;light[2] = 1;
1852 VectorNormalize(light);
1853 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1855 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1856 // stretch upper half of bubble by +50% and shrink lower half by -50%
1857 // (this gives an elongated teardrop shape)
1859 dy = (dy - 0.5f) * 2.0f;
1861 dy = (dy - 0.5f) / 1.5f;
1862 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1864 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1865 // shrink bubble width to half
1867 data[y][x][3] = shadebubble(dx, dy, light);
1870 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1873 memset(&data[0][0][0], 255, sizeof(data));
1874 light[0] = 1;light[1] = 1;light[2] = 1;
1875 VectorNormalize(light);
1876 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1878 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1879 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1881 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1882 data[y][x][3] = shadebubble(dx, dy, light);
1885 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1887 // Blood particles and blood decals
1888 R_InitBloodTextures (particletexturedata);
1891 for (i = 0;i < 8;i++)
1893 memset(&data[0][0][0], 255, sizeof(data));
1894 for (k = 0;k < 12;k++)
1895 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1896 for (k = 0;k < 3;k++)
1897 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1898 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1899 particletextureinvert(&data[0][0][0]);
1900 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1903 #ifdef DUMPPARTICLEFONT
1904 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1907 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1909 Mem_Free(particletexturedata);
1911 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1913 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1914 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1915 particletexture[i].texture = particlefonttexture;
1916 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1917 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1918 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1919 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1922 #ifndef DUMPPARTICLEFONT
1923 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1924 if (!particletexture[tex_beam].texture)
1927 unsigned char noise3[64][64], data2[64][16][4];
1929 fractalnoise(&noise3[0][0], 64, 4);
1931 for (y = 0;y < 64;y++)
1933 dy = (y - 0.5f*64) / (64*0.5f-1);
1934 for (x = 0;x < 16;x++)
1936 dx = (x - 0.5f*16) / (16*0.5f-2);
1937 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1938 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1939 data2[y][x][3] = 255;
1943 #ifdef DUMPPARTICLEFONT
1944 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1946 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1948 particletexture[tex_beam].s1 = 0;
1949 particletexture[tex_beam].t1 = 0;
1950 particletexture[tex_beam].s2 = 1;
1951 particletexture[tex_beam].t2 = 1;
1954 static void r_part_start(void)
1956 particletexturepool = R_AllocTexturePool();
1957 R_InitParticleTexture ();
1958 CL_Particles_LoadEffectInfo();
1961 static void r_part_shutdown(void)
1963 R_FreeTexturePool(&particletexturepool);
1966 static void r_part_newmap(void)
1970 #define BATCHSIZE 256
1971 int particle_element3i[BATCHSIZE*6];
1972 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1974 void R_Particles_Init (void)
1977 for (i = 0;i < BATCHSIZE;i++)
1979 particle_element3i[i*6+0] = i*4+0;
1980 particle_element3i[i*6+1] = i*4+1;
1981 particle_element3i[i*6+2] = i*4+2;
1982 particle_element3i[i*6+3] = i*4+0;
1983 particle_element3i[i*6+4] = i*4+2;
1984 particle_element3i[i*6+5] = i*4+3;
1987 Cvar_RegisterVariable(&r_drawparticles);
1988 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1991 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1993 int surfacelistindex;
1994 int batchstart, batchcount;
1995 const particle_t *p;
1997 rtexture_t *texture;
1998 float *v3f, *t2f, *c4f;
2000 R_Mesh_Matrix(&identitymatrix);
2001 R_Mesh_ResetTextureState();
2002 R_Mesh_VertexPointer(particle_vertex3f);
2003 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2004 R_Mesh_ColorPointer(particle_color4f);
2005 GL_DepthMask(false);
2008 // first generate all the vertices at once
2009 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2011 particletexture_t *tex;
2013 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2015 p = cl.particles + surfacelist[surfacelistindex];
2017 blendmode = p->type->blendmode;
2019 cr = p->color[0] * (1.0f / 255.0f);
2020 cg = p->color[1] * (1.0f / 255.0f);
2021 cb = p->color[2] * (1.0f / 255.0f);
2022 ca = p->alpha * (1.0f / 255.0f);
2023 if (blendmode == PBLEND_MOD)
2033 ca /= cl_particles_quality.value;
2034 if (p->type->lighting)
2036 float ambient[3], diffuse[3], diffusenormal[3];
2037 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2038 cr *= (ambient[0] + 0.5 * diffuse[0]);
2039 cg *= (ambient[1] + 0.5 * diffuse[1]);
2040 cb *= (ambient[2] + 0.5 * diffuse[2]);
2044 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_vieworigin));
2049 if (blendmode == PBLEND_ALPHA)
2051 cr += fogcolor[0] * fog;
2052 cg += fogcolor[1] * fog;
2053 cb += fogcolor[2] * fog;
2056 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2057 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2058 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2059 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2061 size = p->size * cl_particles_size.value;
2063 tex = &particletexture[p->texnum];
2064 if (p->type->orientation == PARTICLE_BILLBOARD)
2066 VectorScale(r_viewleft, -size, right);
2067 VectorScale(r_viewup, size, up);
2068 v3f[ 0] = org[0] - right[0] - up[0];
2069 v3f[ 1] = org[1] - right[1] - up[1];
2070 v3f[ 2] = org[2] - right[2] - up[2];
2071 v3f[ 3] = org[0] - right[0] + up[0];
2072 v3f[ 4] = org[1] - right[1] + up[1];
2073 v3f[ 5] = org[2] - right[2] + up[2];
2074 v3f[ 6] = org[0] + right[0] + up[0];
2075 v3f[ 7] = org[1] + right[1] + up[1];
2076 v3f[ 8] = org[2] + right[2] + up[2];
2077 v3f[ 9] = org[0] + right[0] - up[0];
2078 v3f[10] = org[1] + right[1] - up[1];
2079 v3f[11] = org[2] + right[2] - up[2];
2080 t2f[0] = tex->s1;t2f[1] = tex->t2;
2081 t2f[2] = tex->s1;t2f[3] = tex->t1;
2082 t2f[4] = tex->s2;t2f[5] = tex->t1;
2083 t2f[6] = tex->s2;t2f[7] = tex->t2;
2085 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2088 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2090 VectorNegate(p->vel, v);
2091 VectorVectors(v, right, up);
2094 VectorVectors(p->vel, right, up);
2095 VectorScale(right, size, right);
2096 VectorScale(up, size, up);
2097 v3f[ 0] = org[0] - right[0] - up[0];
2098 v3f[ 1] = org[1] - right[1] - up[1];
2099 v3f[ 2] = org[2] - right[2] - up[2];
2100 v3f[ 3] = org[0] - right[0] + up[0];
2101 v3f[ 4] = org[1] - right[1] + up[1];
2102 v3f[ 5] = org[2] - right[2] + up[2];
2103 v3f[ 6] = org[0] + right[0] + up[0];
2104 v3f[ 7] = org[1] + right[1] + up[1];
2105 v3f[ 8] = org[2] + right[2] + up[2];
2106 v3f[ 9] = org[0] + right[0] - up[0];
2107 v3f[10] = org[1] + right[1] - up[1];
2108 v3f[11] = org[2] + right[2] - up[2];
2109 t2f[0] = tex->s1;t2f[1] = tex->t2;
2110 t2f[2] = tex->s1;t2f[3] = tex->t1;
2111 t2f[4] = tex->s2;t2f[5] = tex->t1;
2112 t2f[6] = tex->s2;t2f[7] = tex->t2;
2114 else if (p->type->orientation == PARTICLE_SPARK)
2116 VectorMA(org, -0.02, p->vel, v);
2117 VectorMA(org, 0.02, p->vel, up2);
2118 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2119 t2f[0] = tex->s1;t2f[1] = tex->t2;
2120 t2f[2] = tex->s1;t2f[3] = tex->t1;
2121 t2f[4] = tex->s2;t2f[5] = tex->t1;
2122 t2f[6] = tex->s2;t2f[7] = tex->t2;
2124 else if (p->type->orientation == PARTICLE_BEAM)
2126 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2127 VectorSubtract(p->vel, org, up);
2128 VectorNormalize(up);
2129 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2130 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2131 t2f[0] = 1;t2f[1] = v[0];
2132 t2f[2] = 0;t2f[3] = v[0];
2133 t2f[4] = 0;t2f[5] = v[1];
2134 t2f[6] = 1;t2f[7] = v[1];
2138 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2143 // now render batches of particles based on blendmode and texture
2144 blendmode = PBLEND_ADD;
2145 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2146 texture = particletexture[63].texture;
2147 R_Mesh_TexBind(0, R_GetTexture(texture));
2148 GL_LockArrays(0, numsurfaces*4);
2151 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2153 p = cl.particles + surfacelist[surfacelistindex];
2155 if (blendmode != p->type->blendmode)
2158 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2160 batchstart = surfacelistindex;
2161 blendmode = p->type->blendmode;
2162 if (blendmode == PBLEND_ALPHA)
2163 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2164 else if (blendmode == PBLEND_ADD)
2165 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2166 else //if (blendmode == PBLEND_MOD)
2167 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2169 if (texture != particletexture[p->texnum].texture)
2172 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2174 batchstart = surfacelistindex;
2175 texture = particletexture[p->texnum].texture;
2176 R_Mesh_TexBind(0, R_GetTexture(texture));
2182 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2183 GL_LockArrays(0, 0);
2186 void R_DrawParticles (void)
2189 float minparticledist;
2192 // LordHavoc: early out conditions
2193 if ((!cl.num_particles) || (!r_drawparticles.integer))
2196 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2198 // LordHavoc: only render if not too close
2199 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2203 renderstats.particles++;
2204 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2205 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);