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;
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[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"))
298 unsigned color[6] = {0};
300 sscanf( argv[1], "%i %i %i", &color[0], &color[1], &color[2] );
301 sscanf( argv[2], "%i %i %i", &color[3], &color[4], &color[5] );
302 info->color[0] = color[0] + (color[1] << 8) + (color[2] << 16);
303 info->color[1] = color[3] + (color[4] << 8) + (color[5] << 16);
305 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
306 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
307 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
308 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
309 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
310 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
311 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
312 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
313 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
314 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
315 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
316 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
317 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
318 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
319 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
320 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
321 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
322 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
323 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
324 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
325 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
326 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
328 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
337 int CL_ParticleEffectIndexForName(const char *name)
340 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
341 if (!strcmp(particleeffectname[i], name))
346 const char *CL_ParticleEffectNameForIndex(int i)
348 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
350 return particleeffectname[i];
353 // MUST match effectnameindex_t in client.h
354 static const char *standardeffectnames[EFFECT_TOTAL] =
379 "TE_TEI_BIGEXPLOSION",
395 void CL_Particles_LoadEffectInfo(void)
398 unsigned char *filedata;
399 fs_offset_t filesize;
400 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
401 memset(particleeffectname, 0, sizeof(particleeffectname));
402 for (i = 0;i < EFFECT_TOTAL;i++)
403 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
404 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
407 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
417 void CL_ReadPointFile_f (void);
418 void CL_Particles_Init (void)
420 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)");
421 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
423 Cvar_RegisterVariable (&cl_particles);
424 Cvar_RegisterVariable (&cl_particles_quality);
425 Cvar_RegisterVariable (&cl_particles_size);
426 Cvar_RegisterVariable (&cl_particles_quake);
427 Cvar_RegisterVariable (&cl_particles_blood);
428 Cvar_RegisterVariable (&cl_particles_blood_alpha);
429 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
430 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
431 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
432 Cvar_RegisterVariable (&cl_particles_explosions_shell);
433 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
434 Cvar_RegisterVariable (&cl_particles_smoke);
435 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
436 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
437 Cvar_RegisterVariable (&cl_particles_sparks);
438 Cvar_RegisterVariable (&cl_particles_bubbles);
439 Cvar_RegisterVariable (&cl_decals);
440 Cvar_RegisterVariable (&cl_decals_time);
441 Cvar_RegisterVariable (&cl_decals_fadetime);
444 void CL_Particles_Shutdown (void)
448 // list of all 26 parameters:
449 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
450 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
451 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
452 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
453 // palpha - opacity of particle as 0-255 (can be more than 255)
454 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
455 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
456 // pgravity - how much effect gravity has on the particle (0-1)
457 // 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
458 // px,py,pz - starting origin of particle
459 // pvx,pvy,pvz - starting velocity of particle
460 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
461 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)
466 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
467 if (cl.free_particle >= cl.max_particles)
469 part = &cl.particles[cl.free_particle++];
470 if (cl.num_particles < cl.free_particle)
471 cl.num_particles = cl.free_particle;
472 memset(part, 0, sizeof(*part));
474 l2 = (int)lhrandom(0.5, 256.5);
476 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
477 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
478 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
479 part->color[3] = 0xFF;
482 part->alpha = palpha;
483 part->alphafade = palphafade;
484 part->gravity = pgravity;
485 part->bounce = pbounce;
487 part->org[0] = px + originjitter * v[0];
488 part->org[1] = py + originjitter * v[1];
489 part->org[2] = pz + originjitter * v[2];
490 part->vel[0] = pvx + velocityjitter * v[0];
491 part->vel[1] = pvy + velocityjitter * v[1];
492 part->vel[2] = pvz + velocityjitter * v[2];
494 part->friction = pfriction;
498 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
501 if (!cl_decals.integer)
503 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);
508 p->ownermodel = cl.entities[p->owner].render.model;
509 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
510 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
511 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
515 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
518 float bestfrac, bestorg[3], bestnormal[3];
520 int besthitent = 0, hitent;
523 for (i = 0;i < 32;i++)
526 VectorMA(org, maxdist, org2, org2);
527 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
528 // take the closest trace result that doesn't end up hitting a NOMARKS
529 // surface (sky for example)
530 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
532 bestfrac = trace.fraction;
534 VectorCopy(trace.endpos, bestorg);
535 VectorCopy(trace.plane.normal, bestnormal);
539 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
542 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
543 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)
546 matrix4x4_t tempmatrix;
547 VectorLerp(originmins, 0.5, originmaxs, center);
548 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
549 if (effectnameindex == EFFECT_TE_WIZSPIKE)
550 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
551 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
552 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
553 else if (effectnameindex == EFFECT_TE_SPIKE)
555 if (cl_particles_bulletimpacts.integer)
557 if (cl_particles_quake.integer)
559 if (cl_particles_smoke.integer)
560 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
563 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
566 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
567 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
569 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
571 if (cl_particles_bulletimpacts.integer)
573 if (cl_particles_quake.integer)
575 if (cl_particles_smoke.integer)
576 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
579 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
582 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
583 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
584 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);
586 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
588 if (cl_particles_bulletimpacts.integer)
590 if (cl_particles_quake.integer)
592 if (cl_particles_smoke.integer)
593 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
596 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
599 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
600 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
602 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
604 if (cl_particles_bulletimpacts.integer)
606 if (cl_particles_quake.integer)
608 if (cl_particles_smoke.integer)
609 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
612 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
615 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
616 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
617 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);
619 else if (effectnameindex == EFFECT_TE_BLOOD)
621 if (!cl_particles_blood.integer)
623 if (cl_particles_quake.integer)
624 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
627 static double bloodaccumulator = 0;
628 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
629 for (;bloodaccumulator > 0;bloodaccumulator--)
630 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);
633 else if (effectnameindex == EFFECT_TE_SPARK)
634 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
635 else if (effectnameindex == EFFECT_TE_PLASMABURN)
637 // plasma scorch mark
638 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
639 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
640 CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
642 else if (effectnameindex == EFFECT_TE_GUNSHOT)
644 if (cl_particles_bulletimpacts.integer)
646 if (cl_particles_quake.integer)
647 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
649 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
652 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
653 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
655 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
657 if (cl_particles_bulletimpacts.integer)
659 if (cl_particles_quake.integer)
660 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
662 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
665 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
666 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667 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);
669 else if (effectnameindex == EFFECT_TE_EXPLOSION)
671 CL_ParticleExplosion(center);
672 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);
674 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
676 CL_ParticleExplosion(center);
677 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);
679 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
681 if (cl_particles_quake.integer)
684 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
687 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);
689 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);
693 CL_ParticleExplosion(center);
694 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);
696 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
697 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);
698 else if (effectnameindex == EFFECT_TE_FLAMEJET)
700 count *= cl_particles_quality.value;
702 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);
704 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
706 float i, j, inc, vel;
709 inc = 8 / cl_particles_quality.value;
710 for (i = -128;i < 128;i += inc)
712 for (j = -128;j < 128;j += inc)
714 dir[0] = j + lhrandom(0, inc);
715 dir[1] = i + lhrandom(0, inc);
717 org[0] = center[0] + dir[0];
718 org[1] = center[1] + dir[1];
719 org[2] = center[2] + lhrandom(0, 64);
720 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
721 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);
725 else if (effectnameindex == EFFECT_TE_TELEPORT)
727 float i, j, k, inc, vel;
730 inc = 4 / cl_particles_quality.value;
731 for (i = -16;i < 16;i += inc)
733 for (j = -16;j < 16;j += inc)
735 for (k = -24;k < 32;k += inc)
737 VectorSet(dir, i*8, j*8, k*8);
738 VectorNormalize(dir);
739 vel = lhrandom(50, 113);
740 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);
744 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);
746 else if (effectnameindex == EFFECT_TE_TEI_G3)
747 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);
748 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
750 if (cl_particles_smoke.integer)
752 count *= 0.25f * cl_particles_quality.value;
754 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);
757 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
759 CL_ParticleExplosion(center);
760 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);
762 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
765 if (cl_stainmaps.integer)
766 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
767 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
768 if (cl_particles_smoke.integer)
769 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
770 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);
771 if (cl_particles_sparks.integer)
772 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
773 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);
774 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);
776 else if (effectnameindex == EFFECT_EF_FLAME)
778 count *= 300 * cl_particles_quality.value;
780 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);
781 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);
783 else if (effectnameindex == EFFECT_EF_STARDUST)
785 count *= 200 * cl_particles_quality.value;
787 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);
788 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);
790 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
794 int smoke, blood, bubbles, r, color;
796 if (effectnameindex == EFFECT_TR_ROCKET)
797 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);
798 else if (effectnameindex == EFFECT_TR_VORESPIKE)
800 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
801 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);
803 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);
805 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
806 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);
808 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
811 VectorSubtract(originmaxs, originmins, dir);
812 len = VectorNormalizeLength(dir);
813 dec = -ent->persistent.trail_time;
814 ent->persistent.trail_time += len;
815 if (ent->persistent.trail_time < 0.01f)
818 // if we skip out, leave it reset
819 ent->persistent.trail_time = 0.0f;
821 // advance into this frame to reach the first puff location
822 VectorMA(originmins, dec, dir, pos);
825 smoke = cl_particles.integer && cl_particles_smoke.integer;
826 blood = cl_particles.integer && cl_particles_blood.integer;
827 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
828 qd = 1.0f / cl_particles_quality.value;
835 if (effectnameindex == EFFECT_TR_BLOOD)
837 if (cl_particles_quake.integer)
839 color = particlepalette[67 + (rand()&3)];
840 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);
845 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);
848 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
850 if (cl_particles_quake.integer)
853 color = particlepalette[67 + (rand()&3)];
854 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);
859 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);
865 if (effectnameindex == EFFECT_TR_ROCKET)
867 if (cl_particles_quake.integer)
870 color = particlepalette[ramp3[r]];
871 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);
875 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);
876 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);
879 else if (effectnameindex == EFFECT_TR_GRENADE)
881 if (cl_particles_quake.integer)
884 color = particlepalette[ramp3[r]];
885 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);
889 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);
892 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
894 if (cl_particles_quake.integer)
897 color = particlepalette[52 + (rand()&7)];
898 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);
899 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);
901 else if (gamemode == GAME_GOODVSBAD2)
904 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);
908 color = particlepalette[20 + (rand()&7)];
909 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);
912 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
914 if (cl_particles_quake.integer)
917 color = particlepalette[230 + (rand()&7)];
918 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);
919 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);
923 color = particlepalette[226 + (rand()&7)];
924 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);
927 else if (effectnameindex == EFFECT_TR_VORESPIKE)
929 if (cl_particles_quake.integer)
931 color = particlepalette[152 + (rand()&3)];
932 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);
934 else if (gamemode == GAME_GOODVSBAD2)
937 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);
939 else if (gamemode == GAME_PRYDON)
942 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);
945 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);
947 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
950 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);
952 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
955 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);
957 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
958 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);
962 if (effectnameindex == EFFECT_TR_ROCKET)
963 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);
964 else if (effectnameindex == EFFECT_TR_GRENADE)
965 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);
967 // advance to next time and position
970 VectorMA (pos, dec, dir, pos);
972 ent->persistent.trail_time = len;
974 else if (developer.integer >= 1)
975 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
978 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)
981 qboolean found = false;
982 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
983 return; // invalid effect index
984 if (!particleeffectname[effectnameindex][0])
985 return; // no such effect
986 VectorLerp(originmins, 0.5, originmaxs, center);
987 if (effectnameindex == EFFECT_SVC_PARTICLE)
989 if (!cl_particles.integer)
991 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
993 CL_ParticleExplosion(center);
994 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
995 CL_ParticleEffect(EFFECT_TE_BLOOD, pcount / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
998 float count = pcount * cl_particles_quality.value;
999 for (;count > 0;count--)
1001 int k = particlepalette[palettecolor + (rand()&7)];
1002 if (cl_particles_quake.integer)
1003 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);
1004 else if (gamemode == GAME_GOODVSBAD2)
1005 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);
1007 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);
1011 else if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1013 int effectinfoindex;
1016 particleeffectinfo_t *info;
1018 vec3_t centervelocity;
1022 qboolean underwater;
1023 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1024 VectorLerp(originmins, 0.5, originmaxs, center);
1025 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1026 supercontents = CL_PointSuperContents(center);
1027 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1028 VectorSubtract(originmaxs, originmins, traildir);
1029 VectorNormalize(traildir);
1030 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1032 if (info->effectnameindex == effectnameindex)
1035 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1037 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1040 // spawn a dlight if requested
1041 if (info->lightradiusstart > 0)
1043 matrix4x4_t tempmatrix;
1044 if (info->trailspacing > 0)
1045 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1047 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1048 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);
1053 if (info->tex[1] > info->tex[0])
1055 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1056 tex = min(tex, info->tex[1] - 1);
1058 if (info->particletype == pt_decal)
1059 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]);
1060 else if (info->particletype == pt_beam)
1061 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);
1064 if (!cl_particles.integer)
1066 switch (info->particletype)
1068 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1069 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1070 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1071 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1074 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1075 VectorCopy(originmins, trailpos);
1076 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1078 if (info->tex[1] > info->tex[0])
1080 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1081 tex = min(tex, info->tex[1] - 1);
1083 if (info->trailspacing <= 0)
1085 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1086 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1087 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1090 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);
1091 if (info->trailspacing > 0)
1092 VectorMA(trailpos, info->trailspacing, traildir, trailpos);
1099 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
1107 void CL_EntityParticles (const entity_t *ent)
1110 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1111 static vec3_t avelocities[NUMVERTEXNORMALS];
1112 if (!cl_particles.integer) return;
1114 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1116 if (!avelocities[0][0])
1117 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1118 avelocities[0][i] = lhrandom(0, 2.55);
1120 for (i = 0;i < NUMVERTEXNORMALS;i++)
1122 yaw = cl.time * avelocities[i][0];
1123 pitch = cl.time * avelocities[i][1];
1124 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1125 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1126 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1127 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);
1132 void CL_ReadPointFile_f (void)
1134 vec3_t org, leakorg;
1136 char *pointfile = NULL, *pointfilepos, *t, tchar;
1137 char name[MAX_OSPATH];
1142 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1143 strlcat (name, ".pts", sizeof (name));
1144 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1147 Con_Printf("Could not open %s\n", name);
1151 Con_Printf("Reading %s...\n", name);
1152 VectorClear(leakorg);
1155 pointfilepos = pointfile;
1156 while (*pointfilepos)
1158 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1163 while (*t && *t != '\n' && *t != '\r')
1167 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1173 VectorCopy(org, leakorg);
1176 if (cl.num_particles < cl.max_particles - 3)
1179 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);
1182 Mem_Free(pointfile);
1183 VectorCopy(leakorg, org);
1184 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1186 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);
1187 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);
1188 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);
1193 CL_ParseParticleEffect
1195 Parse an effect out of the server message
1198 void CL_ParseParticleEffect (void)
1201 int i, count, msgcount, color;
1203 MSG_ReadVector(org, cls.protocol);
1204 for (i=0 ; i<3 ; i++)
1205 dir[i] = MSG_ReadChar ();
1206 msgcount = MSG_ReadByte ();
1207 color = MSG_ReadByte ();
1209 if (msgcount == 255)
1214 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1219 CL_ParticleExplosion
1223 void CL_ParticleExplosion (const vec3_t org)
1229 if (cl_stainmaps.integer)
1230 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1231 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1233 if (cl_particles_quake.integer)
1235 for (i = 0;i < 1024;i++)
1241 color = particlepalette[ramp1[r]];
1242 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);
1246 color = particlepalette[ramp2[r]];
1247 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);
1253 i = CL_PointSuperContents(org);
1254 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1256 if (cl_particles.integer && cl_particles_bubbles.integer)
1257 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1258 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);
1262 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1264 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1266 for (i = 0;i < 32;i++)
1270 for (k = 0;k < 16;k++)
1272 v[0] = org[0] + lhrandom(-48, 48);
1273 v[1] = org[1] + lhrandom(-48, 48);
1274 v[2] = org[2] + lhrandom(-48, 48);
1275 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1276 if (trace.fraction >= 0.1)
1279 VectorSubtract(trace.endpos, org, v2);
1280 VectorScale(v2, 2.0f, v2);
1281 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);
1285 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1286 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1287 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);
1291 if (cl_particles_explosions_shell.integer)
1292 R_NewExplosion(org);
1297 CL_ParticleExplosion2
1301 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1304 if (!cl_particles.integer) return;
1306 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1308 k = particlepalette[colorStart + (i % colorLength)];
1309 if (cl_particles_quake.integer)
1310 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);
1312 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);
1316 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1318 if (cl_particles_sparks.integer)
1320 sparkcount *= cl_particles_quality.value;
1321 while(sparkcount-- > 0)
1322 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);
1324 if (cl_particles_smoke.integer)
1326 smokecount *= cl_particles_quality.value;
1327 while(smokecount-- > 0)
1328 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);
1332 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)
1335 if (!cl_particles.integer) return;
1337 count = (int)(count * cl_particles_quality.value);
1340 k = particlepalette[colorbase + (rand()&3)];
1341 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 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);
1345 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1348 float z, minz, maxz;
1350 if (!cl_particles.integer) return;
1351 if (dir[2] < 0) // falling
1356 minz = z - fabs(dir[2]) * 0.1;
1357 maxz = z + fabs(dir[2]) * 0.1;
1358 minz = bound(mins[2], minz, maxs[2]);
1359 maxz = bound(mins[2], maxz, maxs[2]);
1361 count = (int)(count * cl_particles_quality.value);
1366 count *= 4; // ick, this should be in the mod or maps?
1370 k = particlepalette[colorbase + (rand()&3)];
1371 if (gamemode == GAME_GOODVSBAD2)
1372 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);
1374 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);
1380 k = particlepalette[colorbase + (rand()&3)];
1381 if (gamemode == GAME_GOODVSBAD2)
1382 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);
1384 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);
1386 VectorCopy(p->vel, p->relativedirection);
1390 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1399 void CL_MoveParticles (void)
1402 int i, maxparticle, j, a, content;
1403 float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1407 // LordHavoc: early out condition
1408 if (!cl.num_particles)
1410 cl.free_particle = 0;
1414 frametime = cl.time - cl.oldtime;
1415 gravity = frametime * sv_gravity.value;
1416 dvel = 1+4*frametime;
1417 bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1421 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1428 p->alpha -= p->alphafade * frametime;
1436 if (p->type->orientation != PARTICLE_BEAM)
1438 VectorCopy(p->org, oldorg);
1439 VectorMA(p->org, frametime, p->vel, p->org);
1440 VectorCopy(p->org, org);
1443 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);
1444 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1445 // or if the trace hit something flagged as NOIMPACT
1446 // then remove the particle
1447 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1452 // react if the particle hit something
1453 if (trace.fraction < 1)
1455 VectorCopy(trace.endpos, p->org);
1456 if (p->type == particletype + pt_rain)
1458 // raindrop - splash on solid/water/slime/lava
1460 // convert from a raindrop particle to a rainsplash decal
1461 VectorCopy(trace.plane.normal, p->vel);
1462 VectorAdd(p->org, p->vel, p->org);
1463 p->type = particletype + pt_raindecal;
1464 p->texnum = tex_rainsplash[0];
1466 p->alphafade = p->alpha / 0.4;
1473 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);
1475 else if (p->type == particletype + pt_blood)
1477 // blood - splash on solid
1478 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1483 if (!cl_decals.integer)
1488 // convert from a blood particle to a blood decal
1489 VectorCopy(trace.plane.normal, p->vel);
1490 VectorAdd(p->org, p->vel, p->org);
1491 if (cl_stainmaps.integer)
1492 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)));
1494 p->type = particletype + pt_decal;
1495 p->texnum = tex_blooddecal[rand()&7];
1497 p->ownermodel = cl.entities[hitent].render.model;
1498 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1499 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1507 else if (p->bounce < 0)
1509 // bounce -1 means remove on impact
1515 // anything else - bounce off solid
1516 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1517 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1518 if (DotProduct(p->vel, p->vel) < 0.03)
1519 VectorClear(p->vel);
1523 p->vel[2] -= p->gravity * gravity;
1527 f = p->friction * frametime;
1528 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1531 VectorScale(p->vel, f, p->vel);
1535 if (p->type != particletype + pt_static)
1537 switch (p->type - particletype)
1539 case pt_entityparticle:
1540 // particle that removes itself after one rendered frame
1547 a = CL_PointSuperContents(p->org);
1548 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1550 p->size += frametime * 8;
1551 //p->alpha -= bloodwaterfade;
1554 p->vel[2] -= gravity;
1555 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1559 a = CL_PointSuperContents(p->org);
1560 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1567 a = CL_PointSuperContents(p->org);
1568 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1572 if (cl.time > p->time2)
1575 p->time2 = cl.time + (rand() & 3) * 0.1;
1576 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1577 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1578 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1580 a = CL_PointSuperContents(p->org);
1581 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1585 //p->size += frametime * 15;
1588 // FIXME: this has fairly wacky handling of alpha
1589 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1590 if (cl.entities[p->owner].render.model == p->ownermodel)
1592 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1593 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1599 a = (int)max(0, (cl.time - p->time2) * 40);
1601 p->texnum = tex_rainsplash[a];
1610 cl.num_particles = maxparticle + 1;
1611 cl.free_particle = 0;
1614 #define MAX_PARTICLETEXTURES 64
1615 // particletexture_t is a rectangle in the particlefonttexture
1616 typedef struct particletexture_s
1618 rtexture_t *texture;
1619 float s1, t1, s2, t2;
1623 static rtexturepool_t *particletexturepool;
1624 static rtexture_t *particlefonttexture;
1625 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1627 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1629 #define PARTICLETEXTURESIZE 64
1630 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1632 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1636 dz = 1 - (dx*dx+dy*dy);
1637 if (dz > 0) // it does hit the sphere
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 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1650 VectorNormalize(normal);
1651 dot = DotProduct(normal, light);
1652 if (dot > 0.5) // interior reflection
1653 f += ((dot * 2) - 1);
1654 else if (dot < -0.5) // exterior reflection
1655 f += ((dot * -2) - 1);
1657 f += 16; // just to give it a haze so you can see the outline
1658 f = bound(0, f, 255);
1659 return (unsigned char) f;
1665 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1667 int basex, basey, y;
1668 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1669 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1670 particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1671 particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1672 particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1673 particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1674 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1675 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1678 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1681 float cx, cy, dx, dy, f, iradius;
1683 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1684 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1685 iradius = 1.0f / radius;
1686 alpha *= (1.0f / 255.0f);
1687 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1689 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1693 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1696 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1697 d[0] += (int)(f * (red - d[0]));
1698 d[1] += (int)(f * (green - d[1]));
1699 d[2] += (int)(f * (blue - d[2]));
1705 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1708 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1710 data[0] = bound(minr, data[0], maxr);
1711 data[1] = bound(ming, data[1], maxg);
1712 data[2] = bound(minb, data[2], maxb);
1716 void particletextureinvert(unsigned char *data)
1719 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1721 data[0] = 255 - data[0];
1722 data[1] = 255 - data[1];
1723 data[2] = 255 - data[2];
1727 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1728 static void R_InitBloodTextures (unsigned char *particletexturedata)
1731 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1734 for (i = 0;i < 8;i++)
1736 memset(&data[0][0][0], 255, sizeof(data));
1737 for (k = 0;k < 24;k++)
1738 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1739 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1740 particletextureinvert(&data[0][0][0]);
1741 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1745 for (i = 0;i < 8;i++)
1747 memset(&data[0][0][0], 255, sizeof(data));
1749 for (j = 1;j < 10;j++)
1750 for (k = min(j, m - 1);k < m;k++)
1751 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1752 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1753 particletextureinvert(&data[0][0][0]);
1754 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1759 static void R_InitParticleTexture (void)
1761 int x, y, d, i, k, m;
1762 float dx, dy, radius, f, f2;
1763 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1765 unsigned char *particletexturedata;
1767 // a note: decals need to modulate (multiply) the background color to
1768 // properly darken it (stain), and they need to be able to alpha fade,
1769 // this is a very difficult challenge because it means fading to white
1770 // (no change to background) rather than black (darkening everything
1771 // behind the whole decal polygon), and to accomplish this the texture is
1772 // inverted (dark red blood on white background becomes brilliant cyan
1773 // and white on black background) so we can alpha fade it to black, then
1774 // we invert it again during the blendfunc to make it work...
1776 particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1777 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1780 for (i = 0;i < 8;i++)
1782 memset(&data[0][0][0], 255, sizeof(data));
1785 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1787 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1788 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1790 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1792 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1793 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1795 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1796 d = (noise2[y][x] - 128) * 3 + 192;
1798 d = (int)(d * (1-(dx*dx+dy*dy)));
1799 d = (d * noise1[y][x]) >> 7;
1800 d = bound(0, d, 255);
1801 data[y][x][3] = (unsigned char) d;
1808 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1812 for (i = 0;i < 16;i++)
1814 memset(&data[0][0][0], 255, sizeof(data));
1815 radius = i * 3.0f / 4.0f / 16.0f;
1816 f2 = 255.0f * ((15.0f - i) / 15.0f);
1817 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1819 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1820 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1822 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1823 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1824 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1827 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1831 memset(&data[0][0][0], 255, sizeof(data));
1832 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1834 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1835 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1837 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1838 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1839 d = bound(0, d, 255);
1840 data[y][x][3] = (unsigned char) d;
1843 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1846 memset(&data[0][0][0], 255, sizeof(data));
1847 light[0] = 1;light[1] = 1;light[2] = 1;
1848 VectorNormalize(light);
1849 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1851 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1852 // stretch upper half of bubble by +50% and shrink lower half by -50%
1853 // (this gives an elongated teardrop shape)
1855 dy = (dy - 0.5f) * 2.0f;
1857 dy = (dy - 0.5f) / 1.5f;
1858 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1860 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1861 // shrink bubble width to half
1863 data[y][x][3] = shadebubble(dx, dy, light);
1866 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1869 memset(&data[0][0][0], 255, sizeof(data));
1870 light[0] = 1;light[1] = 1;light[2] = 1;
1871 VectorNormalize(light);
1872 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1874 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1875 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1877 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1878 data[y][x][3] = shadebubble(dx, dy, light);
1881 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1883 // Blood particles and blood decals
1884 R_InitBloodTextures (particletexturedata);
1887 for (i = 0;i < 8;i++)
1889 memset(&data[0][0][0], 255, sizeof(data));
1890 for (k = 0;k < 12;k++)
1891 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1892 for (k = 0;k < 3;k++)
1893 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1894 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1895 particletextureinvert(&data[0][0][0]);
1896 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1900 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1903 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1904 if (!particlefonttexture)
1905 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1906 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1907 particletexture[i].texture = particlefonttexture;
1910 fractalnoise(&noise3[0][0], 64, 4);
1912 for (y = 0;y < 64;y++)
1914 dy = (y - 0.5f*64) / (64*0.5f-1);
1915 for (x = 0;x < 16;x++)
1917 dx = (x - 0.5f*16) / (16*0.5f-2);
1918 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1919 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1920 data2[y][x][3] = 255;
1925 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1928 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1929 if (!particletexture[tex_beam].texture)
1930 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1931 particletexture[tex_beam].s1 = 0;
1932 particletexture[tex_beam].t1 = 0;
1933 particletexture[tex_beam].s2 = 1;
1934 particletexture[tex_beam].t2 = 1;
1935 Mem_Free(particletexturedata);
1938 static void r_part_start(void)
1940 particletexturepool = R_AllocTexturePool();
1941 R_InitParticleTexture ();
1942 CL_Particles_LoadEffectInfo();
1945 static void r_part_shutdown(void)
1947 R_FreeTexturePool(&particletexturepool);
1950 static void r_part_newmap(void)
1954 void R_Particles_Init (void)
1956 Cvar_RegisterVariable(&r_drawparticles);
1957 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1960 float particle_vertex3f[12], particle_texcoord2f[8];
1962 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1964 const particle_t *p = cl.particles + surfacenumber;
1967 float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1968 particletexture_t *tex;
1970 VectorCopy(p->org, org);
1972 blendmode = p->type->blendmode;
1973 tex = &particletexture[p->texnum];
1974 cr = p->color[0] * (1.0f / 255.0f);
1975 cg = p->color[1] * (1.0f / 255.0f);
1976 cb = p->color[2] * (1.0f / 255.0f);
1977 ca = p->alpha * (1.0f / 255.0f);
1978 if (blendmode == PBLEND_MOD)
1988 ca /= cl_particles_quality.value;
1989 if (p->type->lighting)
1991 float ambient[3], diffuse[3], diffusenormal[3];
1992 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1993 cr *= (ambient[0] + 0.5 * diffuse[0]);
1994 cg *= (ambient[1] + 0.5 * diffuse[1]);
1995 cb *= (ambient[2] + 0.5 * diffuse[2]);
1999 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
2004 if (blendmode == PBLEND_ALPHA)
2006 cr += fogcolor[0] * fog;
2007 cg += fogcolor[1] * fog;
2008 cb += fogcolor[2] * fog;
2012 R_Mesh_Matrix(&identitymatrix);
2014 memset(&m, 0, sizeof(m));
2015 m.tex[0] = R_GetTexture(tex->texture);
2016 m.pointer_texcoord[0] = particle_texcoord2f;
2017 m.pointer_vertex = particle_vertex3f;
2020 GL_Color(cr, cg, cb, ca);
2022 if (blendmode == PBLEND_ALPHA)
2023 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2024 else if (blendmode == PBLEND_ADD)
2025 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2026 else //if (blendmode == PBLEND_MOD)
2027 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2028 GL_DepthMask(false);
2030 size = p->size * cl_particles_size.value;
2031 if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2033 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2036 if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2038 VectorNegate(p->vel, v);
2039 VectorVectors(v, right, up);
2042 VectorVectors(p->vel, right, up);
2043 VectorScale(right, size, right);
2044 VectorScale(up, size, up);
2048 VectorScale(r_viewleft, -size, right);
2049 VectorScale(r_viewup, size, up);
2051 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2052 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2053 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2054 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2055 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2056 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2057 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2058 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2059 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2060 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2061 particle_vertex3f[10] = org[1] + right[1] - up[1];
2062 particle_vertex3f[11] = org[2] + right[2] - up[2];
2063 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2064 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2065 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2066 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2068 else if (p->type->orientation == PARTICLE_SPARK)
2070 VectorMA(p->org, -0.02, p->vel, v);
2071 VectorMA(p->org, 0.02, p->vel, up2);
2072 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2073 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2074 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2075 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2076 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2078 else if (p->type->orientation == PARTICLE_BEAM)
2080 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2081 VectorSubtract(p->vel, p->org, up);
2082 VectorNormalize(up);
2083 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2084 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2085 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2086 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2087 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2088 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2092 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2096 R_Mesh_Draw(0, 4, 2, polygonelements);
2099 void R_DrawParticles (void)
2102 float minparticledist;
2105 // LordHavoc: early out conditions
2106 if ((!cl.num_particles) || (!r_drawparticles.integer))
2109 minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2111 // LordHavoc: only render if not too close
2112 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2116 renderstats.particles++;
2117 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2119 if (p->type == particletype + pt_decal)
2120 R_DrawParticle_TransparentCallback(0, i, 0);
2122 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);