2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
119 particleeffectinfo_t;
121 #define MAX_PARTICLEEFFECTNAME 256
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
124 #define MAX_PARTICLEEFFECTINFO 4096
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // texture numbers in particle font
171 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
172 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
173 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
174 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
175 static const int tex_rainsplash = 32;
176 static const int tex_particle = 63;
177 static const int tex_bubble = 62;
178 static const int tex_raindrop = 61;
179 static const int tex_beam = 60;
181 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
182 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
183 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
184 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
185 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
186 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
187 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
188 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
189 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
190 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
191 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
192 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
193 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
194 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
195 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
196 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
197 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
198 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
199 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
200 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
201 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
202 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
203 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
206 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
212 particleeffectinfo_t *info = NULL;
213 const char *text = textstart;
215 effectinfoindex = -1;
216 for (linenumber = 1;;linenumber++)
219 for (arrayindex = 0;arrayindex < 16;arrayindex++)
220 argv[arrayindex][0] = 0;
223 if (!COM_ParseToken_Simple(&text, true, false))
225 if (!strcmp(com_token, "\n"))
229 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
235 #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;}
236 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
237 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
238 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
239 #define readfloat(var) checkparms(2);var = atof(argv[1])
240 if (!strcmp(argv[0], "effect"))
245 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
247 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
250 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
252 if (particleeffectname[effectnameindex][0])
254 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
259 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
263 // if we run out of names, abort
264 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
266 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
269 info = particleeffectinfo + effectinfoindex;
270 info->effectnameindex = effectnameindex;
271 info->particletype = pt_alphastatic;
272 info->blendmode = particletype[info->particletype].blendmode;
273 info->orientation = particletype[info->particletype].orientation;
274 info->tex[0] = tex_particle;
275 info->tex[1] = tex_particle;
276 info->color[0] = 0xFFFFFF;
277 info->color[1] = 0xFFFFFF;
281 info->alpha[1] = 256;
282 info->alpha[2] = 256;
283 info->time[0] = 9999;
284 info->time[1] = 9999;
285 VectorSet(info->lightcolor, 1, 1, 1);
286 info->lightshadow = true;
287 info->lighttime = 9999;
288 info->stretchfactor = 1;
290 else if (info == NULL)
292 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
295 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
296 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
297 else if (!strcmp(argv[0], "type"))
300 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
301 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
302 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
303 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
304 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
305 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
306 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
307 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
308 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
309 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
310 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
311 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
312 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
313 info->blendmode = particletype[info->particletype].blendmode;
314 info->orientation = particletype[info->particletype].orientation;
316 else if (!strcmp(argv[0], "blend"))
319 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
320 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
321 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
322 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
324 else if (!strcmp(argv[0], "orientation"))
327 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
328 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
329 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
330 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
331 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
333 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
334 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
335 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
336 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
337 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
338 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
339 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
340 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
341 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
342 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
343 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
344 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
345 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
346 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
347 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
348 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
349 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
350 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
351 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
352 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
353 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
354 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
355 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
356 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
357 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
359 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
368 int CL_ParticleEffectIndexForName(const char *name)
371 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
372 if (!strcmp(particleeffectname[i], name))
377 const char *CL_ParticleEffectNameForIndex(int i)
379 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
381 return particleeffectname[i];
384 // MUST match effectnameindex_t in client.h
385 static const char *standardeffectnames[EFFECT_TOTAL] =
409 "TE_TEI_BIGEXPLOSION",
425 void CL_Particles_LoadEffectInfo(void)
428 unsigned char *filedata;
429 fs_offset_t filesize;
430 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
431 memset(particleeffectname, 0, sizeof(particleeffectname));
432 for (i = 0;i < EFFECT_TOTAL;i++)
433 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
434 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
437 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
447 void CL_ReadPointFile_f (void);
448 void CL_Particles_Init (void)
450 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)");
451 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
453 Cvar_RegisterVariable (&cl_particles);
454 Cvar_RegisterVariable (&cl_particles_quality);
455 Cvar_RegisterVariable (&cl_particles_alpha);
456 Cvar_RegisterVariable (&cl_particles_size);
457 Cvar_RegisterVariable (&cl_particles_quake);
458 Cvar_RegisterVariable (&cl_particles_blood);
459 Cvar_RegisterVariable (&cl_particles_blood_alpha);
460 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
461 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
462 Cvar_RegisterVariable (&cl_particles_explosions_shell);
463 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
464 Cvar_RegisterVariable (&cl_particles_rain);
465 Cvar_RegisterVariable (&cl_particles_snow);
466 Cvar_RegisterVariable (&cl_particles_smoke);
467 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
468 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
469 Cvar_RegisterVariable (&cl_particles_sparks);
470 Cvar_RegisterVariable (&cl_particles_bubbles);
471 Cvar_RegisterVariable (&cl_particles_visculling);
472 Cvar_RegisterVariable (&cl_decals);
473 Cvar_RegisterVariable (&cl_decals_visculling);
474 Cvar_RegisterVariable (&cl_decals_time);
475 Cvar_RegisterVariable (&cl_decals_fadetime);
478 void CL_Particles_Shutdown (void)
482 // list of all 26 parameters:
483 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
484 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
485 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
486 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
487 // palpha - opacity of particle as 0-255 (can be more than 255)
488 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
489 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
490 // pgravity - how much effect gravity has on the particle (0-1)
491 // 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
492 // px,py,pz - starting origin of particle
493 // pvx,pvy,pvz - starting velocity of particle
494 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
495 // blendmode - one of the PBLEND_ values
496 // orientation - one of the PARTICLE_ values
497 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation)
502 if (!cl_particles.integer)
504 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
505 if (cl.free_particle >= cl.max_particles)
508 lifetime = palpha / min(1, palphafade);
509 part = &cl.particles[cl.free_particle++];
510 if (cl.num_particles < cl.free_particle)
511 cl.num_particles = cl.free_particle;
512 memset(part, 0, sizeof(*part));
513 part->typeindex = ptypeindex;
514 part->blendmode = blendmode;
515 part->orientation = orientation;
516 l2 = (int)lhrandom(0.5, 256.5);
518 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
519 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
520 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
523 part->sizeincrease = psizeincrease;
524 part->alpha = palpha;
525 part->alphafade = palphafade;
526 part->gravity = pgravity;
527 part->bounce = pbounce;
528 part->stretch = stretch;
530 part->org[0] = px + originjitter * v[0];
531 part->org[1] = py + originjitter * v[1];
532 part->org[2] = pz + originjitter * v[2];
533 part->vel[0] = pvx + velocityjitter * v[0];
534 part->vel[1] = pvy + velocityjitter * v[1];
535 part->vel[2] = pvz + velocityjitter * v[2];
537 part->airfriction = pairfriction;
538 part->liquidfriction = pliquidfriction;
539 part->die = cl.time + lifetime;
540 part->delayedcollisions = 0;
541 part->qualityreduction = pqualityreduction;
542 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
543 if (part->typeindex == pt_rain)
547 float lifetime = part->die - cl.time;
550 // turn raindrop into simple spark and create delayedspawn splash effect
551 part->typeindex = pt_spark;
553 VectorMA(part->org, lifetime, part->vel, endvec);
554 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
555 part->die = cl.time + lifetime * trace.fraction;
556 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED);
559 part2->delayedspawn = part->die;
560 part2->die += part->die - cl.time;
561 for (i = rand() & 7;i < 10;i++)
563 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
566 part2->delayedspawn = part->die;
567 part2->die += part->die - cl.time;
572 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
574 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
577 VectorMA(part->org, lifetime, part->vel, endvec);
578 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
579 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
584 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
588 if (!cl_decals.integer)
590 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
591 if (cl.free_decal >= cl.max_decals)
593 decal = &cl.decals[cl.free_decal++];
594 if (cl.num_decals < cl.free_decal)
595 cl.num_decals = cl.free_decal;
596 memset(decal, 0, sizeof(*decal));
597 decal->typeindex = pt_decal;
598 decal->texnum = texnum;
599 VectorAdd(org, normal, decal->org);
600 VectorCopy(normal, decal->normal);
602 decal->alpha = alpha;
603 decal->time2 = cl.time;
604 l2 = (int)lhrandom(0.5, 256.5);
606 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
607 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
608 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
609 decal->owner = hitent;
610 decal->clusterindex = -1000; // no vis culling unless we're sure
613 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
614 decal->ownermodel = cl.entities[decal->owner].render.model;
615 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
616 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
620 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
622 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
624 decal->clusterindex = leaf->clusterindex;
629 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
632 float bestfrac, bestorg[3], bestnormal[3];
634 int besthitent = 0, hitent;
637 for (i = 0;i < 32;i++)
640 VectorMA(org, maxdist, org2, org2);
641 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
642 // take the closest trace result that doesn't end up hitting a NOMARKS
643 // surface (sky for example)
644 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
646 bestfrac = trace.fraction;
648 VectorCopy(trace.endpos, bestorg);
649 VectorCopy(trace.plane.normal, bestnormal);
653 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
656 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
657 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
658 void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
661 matrix4x4_t tempmatrix;
662 VectorLerp(originmins, 0.5, originmaxs, center);
663 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
664 if (effectnameindex == EFFECT_SVC_PARTICLE)
666 if (cl_particles.integer)
668 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
670 CL_ParticleExplosion(center);
671 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
672 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
675 count *= cl_particles_quality.value;
676 for (;count > 0;count--)
678 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
679 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
684 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
685 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
686 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
687 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
688 else if (effectnameindex == EFFECT_TE_SPIKE)
690 if (cl_particles_bulletimpacts.integer)
692 if (cl_particles_quake.integer)
694 if (cl_particles_smoke.integer)
695 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
699 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
700 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
701 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
705 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
706 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
708 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
710 if (cl_particles_bulletimpacts.integer)
712 if (cl_particles_quake.integer)
714 if (cl_particles_smoke.integer)
715 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
719 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
720 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
721 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
725 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
726 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
727 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
729 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
731 if (cl_particles_bulletimpacts.integer)
733 if (cl_particles_quake.integer)
735 if (cl_particles_smoke.integer)
736 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
740 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
741 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
742 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
746 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
747 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
749 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
751 if (cl_particles_bulletimpacts.integer)
753 if (cl_particles_quake.integer)
755 if (cl_particles_smoke.integer)
756 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
760 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
761 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
762 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
766 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
767 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
768 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
770 else if (effectnameindex == EFFECT_TE_BLOOD)
772 if (!cl_particles_blood.integer)
774 if (cl_particles_quake.integer)
775 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
778 static double bloodaccumulator = 0;
779 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
780 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
781 for (;bloodaccumulator > 0;bloodaccumulator--)
782 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
785 else if (effectnameindex == EFFECT_TE_SPARK)
786 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
787 else if (effectnameindex == EFFECT_TE_PLASMABURN)
789 // plasma scorch mark
790 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
791 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
792 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
794 else if (effectnameindex == EFFECT_TE_GUNSHOT)
796 if (cl_particles_bulletimpacts.integer)
798 if (cl_particles_quake.integer)
799 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
802 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
803 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
804 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
808 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
809 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
811 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
813 if (cl_particles_bulletimpacts.integer)
815 if (cl_particles_quake.integer)
816 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
819 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
820 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
821 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
825 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
826 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
827 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
829 else if (effectnameindex == EFFECT_TE_EXPLOSION)
831 CL_ParticleExplosion(center);
832 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
834 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
836 CL_ParticleExplosion(center);
837 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
839 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
841 if (cl_particles_quake.integer)
844 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
847 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
849 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
853 CL_ParticleExplosion(center);
854 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
856 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
857 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
858 else if (effectnameindex == EFFECT_TE_FLAMEJET)
860 count *= cl_particles_quality.value;
862 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
864 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
866 float i, j, inc, vel;
869 inc = 8 / cl_particles_quality.value;
870 for (i = -128;i < 128;i += inc)
872 for (j = -128;j < 128;j += inc)
874 dir[0] = j + lhrandom(0, inc);
875 dir[1] = i + lhrandom(0, inc);
877 org[0] = center[0] + dir[0];
878 org[1] = center[1] + dir[1];
879 org[2] = center[2] + lhrandom(0, 64);
880 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
881 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
885 else if (effectnameindex == EFFECT_TE_TELEPORT)
887 float i, j, k, inc, vel;
890 if (cl_particles_quake.integer)
891 inc = 4 / cl_particles_quality.value;
893 inc = 8 / cl_particles_quality.value;
894 for (i = -16;i < 16;i += inc)
896 for (j = -16;j < 16;j += inc)
898 for (k = -24;k < 32;k += inc)
900 VectorSet(dir, i*8, j*8, k*8);
901 VectorNormalize(dir);
902 vel = lhrandom(50, 113);
903 if (cl_particles_quake.integer)
904 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
906 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
910 if (!cl_particles_quake.integer)
911 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
912 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
914 else if (effectnameindex == EFFECT_TE_TEI_G3)
915 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BEAM);
916 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
918 if (cl_particles_smoke.integer)
920 count *= 0.25f * cl_particles_quality.value;
922 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
925 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
927 CL_ParticleExplosion(center);
928 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
930 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
933 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
934 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
935 if (cl_particles_smoke.integer)
936 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
937 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
938 if (cl_particles_sparks.integer)
939 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
940 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
941 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
943 else if (effectnameindex == EFFECT_EF_FLAME)
945 count *= 300 * cl_particles_quality.value;
947 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
948 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
950 else if (effectnameindex == EFFECT_EF_STARDUST)
952 count *= 200 * cl_particles_quality.value;
954 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
955 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
957 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
961 int smoke, blood, bubbles, r, color;
963 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
966 Vector4Set(light, 0, 0, 0, 0);
968 if (effectnameindex == EFFECT_TR_ROCKET)
969 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
970 else if (effectnameindex == EFFECT_TR_VORESPIKE)
972 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
973 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
975 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
977 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
978 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
982 matrix4x4_t tempmatrix;
983 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
984 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
985 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
992 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
995 VectorSubtract(originmaxs, originmins, dir);
996 len = VectorNormalizeLength(dir);
999 dec = -ent->persistent.trail_time;
1000 ent->persistent.trail_time += len;
1001 if (ent->persistent.trail_time < 0.01f)
1004 // if we skip out, leave it reset
1005 ent->persistent.trail_time = 0.0f;
1010 // advance into this frame to reach the first puff location
1011 VectorMA(originmins, dec, dir, pos);
1014 smoke = cl_particles.integer && cl_particles_smoke.integer;
1015 blood = cl_particles.integer && cl_particles_blood.integer;
1016 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1017 qd = 1.0f / cl_particles_quality.value;
1024 if (effectnameindex == EFFECT_TR_BLOOD)
1026 if (cl_particles_quake.integer)
1028 color = particlepalette[67 + (rand()&3)];
1029 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1034 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
1037 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1039 if (cl_particles_quake.integer)
1042 color = particlepalette[67 + (rand()&3)];
1043 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1048 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
1054 if (effectnameindex == EFFECT_TR_ROCKET)
1056 if (cl_particles_quake.integer)
1059 color = particlepalette[ramp3[r]];
1060 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1064 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1065 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1068 else if (effectnameindex == EFFECT_TR_GRENADE)
1070 if (cl_particles_quake.integer)
1073 color = particlepalette[ramp3[r]];
1074 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1078 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1081 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1083 if (cl_particles_quake.integer)
1086 color = particlepalette[52 + (rand()&7)];
1087 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1088 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1090 else if (gamemode == GAME_GOODVSBAD2)
1093 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1097 color = particlepalette[20 + (rand()&7)];
1098 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1101 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1103 if (cl_particles_quake.integer)
1106 color = particlepalette[230 + (rand()&7)];
1107 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1108 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1112 color = particlepalette[226 + (rand()&7)];
1113 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1116 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1118 if (cl_particles_quake.integer)
1120 color = particlepalette[152 + (rand()&3)];
1121 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1123 else if (gamemode == GAME_GOODVSBAD2)
1126 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1128 else if (gamemode == GAME_PRYDON)
1131 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1134 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1136 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1139 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1141 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1144 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1146 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1147 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1151 if (effectnameindex == EFFECT_TR_ROCKET)
1152 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1153 else if (effectnameindex == EFFECT_TR_GRENADE)
1154 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1156 // advance to next time and position
1159 VectorMA (pos, dec, dir, pos);
1162 ent->persistent.trail_time = len;
1164 else if (developer.integer >= 1)
1165 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1168 // this is also called on point effects with spawndlight = true and
1169 // spawnparticles = true
1170 // it is called CL_ParticleTrail because most code does not want to supply
1171 // these parameters, only trail handling does
1172 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
1175 qboolean found = false;
1176 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1178 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1179 return; // no such effect
1181 VectorLerp(originmins, 0.5, originmaxs, center);
1182 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1184 int effectinfoindex;
1187 particleeffectinfo_t *info;
1189 vec3_t centervelocity;
1195 qboolean underwater;
1196 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1197 VectorLerp(originmins, 0.5, originmaxs, center);
1198 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1199 supercontents = CL_PointSuperContents(center);
1200 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1201 VectorSubtract(originmaxs, originmins, traildir);
1202 traillen = VectorLength(traildir);
1203 VectorNormalize(traildir);
1204 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1206 if (info->effectnameindex == effectnameindex)
1209 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1211 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1214 // spawn a dlight if requested
1215 if (info->lightradiusstart > 0 && spawndlight)
1217 matrix4x4_t tempmatrix;
1218 if (info->trailspacing > 0)
1219 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1221 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1222 if (info->lighttime > 0 && info->lightradiusfade > 0)
1224 // light flash (explosion, etc)
1225 // called when effect starts
1226 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1231 // called by CL_LinkNetworkEntity
1232 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1233 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1234 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1238 if (!spawnparticles)
1243 if (info->tex[1] > info->tex[0])
1245 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1246 tex = min(tex, info->tex[1] - 1);
1248 if (info->particletype == pt_decal)
1249 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]);
1250 else if (info->orientation == PARTICLE_BEAM)
1251 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation);
1254 if (!cl_particles.integer)
1256 switch (info->particletype)
1258 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1259 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1260 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1261 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1262 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1263 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1266 VectorCopy(originmins, trailpos);
1267 if (info->trailspacing > 0)
1269 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1270 trailstep = info->trailspacing / cl_particles_quality.value;
1274 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1277 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1278 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1280 if (info->tex[1] > info->tex[0])
1282 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1283 tex = min(tex, info->tex[1] - 1);
1287 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1288 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1289 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1292 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation);
1294 VectorMA(trailpos, trailstep, traildir, trailpos);
1301 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1304 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)
1306 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1314 void CL_EntityParticles (const entity_t *ent)
1317 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1318 static vec3_t avelocities[NUMVERTEXNORMALS];
1319 if (!cl_particles.integer) return;
1320 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1322 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1324 if (!avelocities[0][0])
1325 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1326 avelocities[0][i] = lhrandom(0, 2.55);
1328 for (i = 0;i < NUMVERTEXNORMALS;i++)
1330 yaw = cl.time * avelocities[i][0];
1331 pitch = cl.time * avelocities[i][1];
1332 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1333 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1334 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1335 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1340 void CL_ReadPointFile_f (void)
1342 vec3_t org, leakorg;
1344 char *pointfile = NULL, *pointfilepos, *t, tchar;
1345 char name[MAX_OSPATH];
1350 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1351 strlcat (name, ".pts", sizeof (name));
1352 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1355 Con_Printf("Could not open %s\n", name);
1359 Con_Printf("Reading %s...\n", name);
1360 VectorClear(leakorg);
1363 pointfilepos = pointfile;
1364 while (*pointfilepos)
1366 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1371 while (*t && *t != '\n' && *t != '\r')
1375 #if _MSC_VER >= 1400
1376 #define sscanf sscanf_s
1378 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1384 VectorCopy(org, leakorg);
1387 if (cl.num_particles < cl.max_particles - 3)
1390 CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1393 Mem_Free(pointfile);
1394 VectorCopy(leakorg, org);
1395 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1397 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1398 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1399 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1404 CL_ParseParticleEffect
1406 Parse an effect out of the server message
1409 void CL_ParseParticleEffect (void)
1412 int i, count, msgcount, color;
1414 MSG_ReadVector(org, cls.protocol);
1415 for (i=0 ; i<3 ; i++)
1416 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1417 msgcount = MSG_ReadByte ();
1418 color = MSG_ReadByte ();
1420 if (msgcount == 255)
1425 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1430 CL_ParticleExplosion
1434 void CL_ParticleExplosion (const vec3_t org)
1440 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1441 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1443 if (cl_particles_quake.integer)
1445 for (i = 0;i < 1024;i++)
1451 color = particlepalette[ramp1[r]];
1452 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1456 color = particlepalette[ramp2[r]];
1457 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1463 i = CL_PointSuperContents(org);
1464 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1466 if (cl_particles.integer && cl_particles_bubbles.integer)
1467 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1468 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1472 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1474 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1478 for (k = 0;k < 16;k++)
1481 VectorMA(org, 128, v2, v);
1482 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1483 if (trace.fraction >= 0.1)
1486 VectorSubtract(trace.endpos, org, v2);
1487 VectorScale(v2, 2.0f, v2);
1488 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
1494 if (cl_particles_explosions_shell.integer)
1495 R_NewExplosion(org);
1500 CL_ParticleExplosion2
1504 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1507 if (!cl_particles.integer) return;
1509 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1511 k = particlepalette[colorStart + (i % colorLength)];
1512 if (cl_particles_quake.integer)
1513 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1515 CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1519 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1521 if (cl_particles_sparks.integer)
1523 sparkcount *= cl_particles_quality.value;
1524 while(sparkcount-- > 0)
1525 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
1529 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1531 if (cl_particles_smoke.integer)
1533 smokecount *= cl_particles_quality.value;
1534 while(smokecount-- > 0)
1535 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1539 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)
1542 if (!cl_particles.integer) return;
1544 count = (int)(count * cl_particles_quality.value);
1547 k = particlepalette[colorbase + (rand()&3)];
1548 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1552 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1555 float minz, maxz, lifetime = 30;
1556 if (!cl_particles.integer) return;
1557 if (dir[2] < 0) // falling
1559 minz = maxs[2] + dir[2] * 0.1;
1562 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1567 maxz = maxs[2] + dir[2] * 0.1;
1569 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1572 count = (int)(count * cl_particles_quality.value);
1577 if (!cl_particles_rain.integer) break;
1578 count *= 4; // ick, this should be in the mod or maps?
1582 k = particlepalette[colorbase + (rand()&3)];
1583 if (gamemode == GAME_GOODVSBAD2)
1584 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK);
1586 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK);
1590 if (!cl_particles_snow.integer) break;
1593 k = particlepalette[colorbase + (rand()&3)];
1594 if (gamemode == GAME_GOODVSBAD2)
1595 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1597 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1601 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1605 #define MAX_PARTICLETEXTURES 64
1606 // particletexture_t is a rectangle in the particlefonttexture
1607 typedef struct particletexture_s
1609 rtexture_t *texture;
1610 float s1, t1, s2, t2;
1614 static rtexturepool_t *particletexturepool;
1615 static rtexture_t *particlefonttexture;
1616 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1618 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1619 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1620 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1621 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1623 #define PARTICLETEXTURESIZE 64
1624 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1626 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1630 dz = 1 - (dx*dx+dy*dy);
1631 if (dz > 0) // it does hit the sphere
1635 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1636 VectorNormalize(normal);
1637 dot = DotProduct(normal, light);
1638 if (dot > 0.5) // interior reflection
1639 f += ((dot * 2) - 1);
1640 else if (dot < -0.5) // exterior reflection
1641 f += ((dot * -2) - 1);
1643 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1644 VectorNormalize(normal);
1645 dot = DotProduct(normal, light);
1646 if (dot > 0.5) // interior reflection
1647 f += ((dot * 2) - 1);
1648 else if (dot < -0.5) // exterior reflection
1649 f += ((dot * -2) - 1);
1651 f += 16; // just to give it a haze so you can see the outline
1652 f = bound(0, f, 255);
1653 return (unsigned char) f;
1659 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1661 int basex, basey, y;
1662 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1663 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1664 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1665 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1668 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1671 float cx, cy, dx, dy, f, iradius;
1673 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1674 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1675 iradius = 1.0f / radius;
1676 alpha *= (1.0f / 255.0f);
1677 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1679 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1683 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1688 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1689 d[0] += (int)(f * (blue - d[0]));
1690 d[1] += (int)(f * (green - d[1]));
1691 d[2] += (int)(f * (red - d[2]));
1697 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1700 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1702 data[0] = bound(minb, data[0], maxb);
1703 data[1] = bound(ming, data[1], maxg);
1704 data[2] = bound(minr, data[2], maxr);
1708 void particletextureinvert(unsigned char *data)
1711 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1713 data[0] = 255 - data[0];
1714 data[1] = 255 - data[1];
1715 data[2] = 255 - data[2];
1719 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1720 static void R_InitBloodTextures (unsigned char *particletexturedata)
1723 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1726 for (i = 0;i < 8;i++)
1728 memset(&data[0][0][0], 255, sizeof(data));
1729 for (k = 0;k < 24;k++)
1730 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1731 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1732 particletextureinvert(&data[0][0][0]);
1733 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1737 for (i = 0;i < 8;i++)
1739 memset(&data[0][0][0], 255, sizeof(data));
1741 for (j = 1;j < 10;j++)
1742 for (k = min(j, m - 1);k < m;k++)
1743 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1744 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1745 particletextureinvert(&data[0][0][0]);
1746 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1751 //uncomment this to make engine save out particle font to a tga file when run
1752 //#define DUMPPARTICLEFONT
1754 static void R_InitParticleTexture (void)
1756 int x, y, d, i, k, m;
1760 // a note: decals need to modulate (multiply) the background color to
1761 // properly darken it (stain), and they need to be able to alpha fade,
1762 // this is a very difficult challenge because it means fading to white
1763 // (no change to background) rather than black (darkening everything
1764 // behind the whole decal polygon), and to accomplish this the texture is
1765 // inverted (dark red blood on white background becomes brilliant cyan
1766 // and white on black background) so we can alpha fade it to black, then
1767 // we invert it again during the blendfunc to make it work...
1769 #ifndef DUMPPARTICLEFONT
1770 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1771 if (!particlefonttexture)
1774 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1775 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1776 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1779 for (i = 0;i < 8;i++)
1781 memset(&data[0][0][0], 255, sizeof(data));
1784 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1786 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1787 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1789 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1791 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1792 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1794 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1795 d = (noise2[y][x] - 128) * 3 + 192;
1797 d = (int)(d * (1-(dx*dx+dy*dy)));
1798 d = (d * noise1[y][x]) >> 7;
1799 d = bound(0, d, 255);
1800 data[y][x][3] = (unsigned char) d;
1807 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1811 memset(&data[0][0][0], 255, sizeof(data));
1812 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1814 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1815 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1817 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1818 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1819 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1822 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1825 memset(&data[0][0][0], 255, sizeof(data));
1826 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1828 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1829 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1831 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1832 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1833 d = bound(0, d, 255);
1834 data[y][x][3] = (unsigned char) d;
1837 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1840 memset(&data[0][0][0], 255, sizeof(data));
1841 light[0] = 1;light[1] = 1;light[2] = 1;
1842 VectorNormalize(light);
1843 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1845 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1846 // stretch upper half of bubble by +50% and shrink lower half by -50%
1847 // (this gives an elongated teardrop shape)
1849 dy = (dy - 0.5f) * 2.0f;
1851 dy = (dy - 0.5f) / 1.5f;
1852 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1854 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1855 // shrink bubble width to half
1857 data[y][x][3] = shadebubble(dx, dy, light);
1860 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1863 memset(&data[0][0][0], 255, sizeof(data));
1864 light[0] = 1;light[1] = 1;light[2] = 1;
1865 VectorNormalize(light);
1866 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1868 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1869 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1871 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1872 data[y][x][3] = shadebubble(dx, dy, light);
1875 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1877 // Blood particles and blood decals
1878 R_InitBloodTextures (particletexturedata);
1881 for (i = 0;i < 8;i++)
1883 memset(&data[0][0][0], 255, sizeof(data));
1884 for (k = 0;k < 12;k++)
1885 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1886 for (k = 0;k < 3;k++)
1887 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1888 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1889 particletextureinvert(&data[0][0][0]);
1890 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1893 #ifdef DUMPPARTICLEFONT
1894 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1897 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1899 Mem_Free(particletexturedata);
1901 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1903 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1904 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1905 particletexture[i].texture = particlefonttexture;
1906 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1907 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1908 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1909 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1912 #ifndef DUMPPARTICLEFONT
1913 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1914 if (!particletexture[tex_beam].texture)
1917 unsigned char noise3[64][64], data2[64][16][4];
1919 fractalnoise(&noise3[0][0], 64, 4);
1921 for (y = 0;y < 64;y++)
1923 dy = (y - 0.5f*64) / (64*0.5f-1);
1924 for (x = 0;x < 16;x++)
1926 dx = (x - 0.5f*16) / (16*0.5f-2);
1927 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1928 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1929 data2[y][x][3] = 255;
1933 #ifdef DUMPPARTICLEFONT
1934 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1936 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1938 particletexture[tex_beam].s1 = 0;
1939 particletexture[tex_beam].t1 = 0;
1940 particletexture[tex_beam].s2 = 1;
1941 particletexture[tex_beam].t2 = 1;
1944 static void r_part_start(void)
1947 // generate particlepalette for convenience from the main one
1948 for (i = 0;i < 256;i++)
1949 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1950 particletexturepool = R_AllocTexturePool();
1951 R_InitParticleTexture ();
1952 CL_Particles_LoadEffectInfo();
1955 static void r_part_shutdown(void)
1957 R_FreeTexturePool(&particletexturepool);
1960 static void r_part_newmap(void)
1962 CL_Particles_LoadEffectInfo();
1965 #define BATCHSIZE 256
1966 unsigned short particle_elements[BATCHSIZE*6];
1968 void R_Particles_Init (void)
1971 for (i = 0;i < BATCHSIZE;i++)
1973 particle_elements[i*6+0] = i*4+0;
1974 particle_elements[i*6+1] = i*4+1;
1975 particle_elements[i*6+2] = i*4+2;
1976 particle_elements[i*6+3] = i*4+0;
1977 particle_elements[i*6+4] = i*4+2;
1978 particle_elements[i*6+5] = i*4+3;
1981 Cvar_RegisterVariable(&r_drawparticles);
1982 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1983 Cvar_RegisterVariable(&r_drawdecals);
1984 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1985 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1988 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1990 int surfacelistindex;
1992 float *v3f, *t2f, *c4f;
1993 particletexture_t *tex;
1994 float right[3], up[3], size, ca;
1995 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1996 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1998 r_refdef.stats.decals += numsurfaces;
1999 R_Mesh_Matrix(&identitymatrix);
2000 R_Mesh_ResetTextureState();
2001 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2002 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2003 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2004 R_SetupGenericShader(true);
2005 GL_DepthMask(false);
2006 GL_DepthRange(0, 1);
2007 GL_PolygonOffset(0, 0);
2009 GL_CullFace(GL_NONE);
2011 // generate all the vertices at once
2012 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2014 d = cl.decals + surfacelist[surfacelistindex];
2017 c4f = particle_color4f + 16*surfacelistindex;
2018 ca = d->alpha * alphascale;
2019 if (r_refdef.fogenabled)
2020 ca *= FogPoint_World(d->org);
2021 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2022 Vector4Copy(c4f, c4f + 4);
2023 Vector4Copy(c4f, c4f + 8);
2024 Vector4Copy(c4f, c4f + 12);
2026 // calculate vertex positions
2027 size = d->size * cl_particles_size.value;
2028 VectorVectors(d->normal, right, up);
2029 VectorScale(right, size, right);
2030 VectorScale(up, size, up);
2031 v3f = particle_vertex3f + 12*surfacelistindex;
2032 v3f[ 0] = d->org[0] - right[0] - up[0];
2033 v3f[ 1] = d->org[1] - right[1] - up[1];
2034 v3f[ 2] = d->org[2] - right[2] - up[2];
2035 v3f[ 3] = d->org[0] - right[0] + up[0];
2036 v3f[ 4] = d->org[1] - right[1] + up[1];
2037 v3f[ 5] = d->org[2] - right[2] + up[2];
2038 v3f[ 6] = d->org[0] + right[0] + up[0];
2039 v3f[ 7] = d->org[1] + right[1] + up[1];
2040 v3f[ 8] = d->org[2] + right[2] + up[2];
2041 v3f[ 9] = d->org[0] + right[0] - up[0];
2042 v3f[10] = d->org[1] + right[1] - up[1];
2043 v3f[11] = d->org[2] + right[2] - up[2];
2045 // calculate texcoords
2046 tex = &particletexture[d->texnum];
2047 t2f = particle_texcoord2f + 8*surfacelistindex;
2048 t2f[0] = tex->s1;t2f[1] = tex->t2;
2049 t2f[2] = tex->s1;t2f[3] = tex->t1;
2050 t2f[4] = tex->s2;t2f[5] = tex->t1;
2051 t2f[6] = tex->s2;t2f[7] = tex->t2;
2054 // now render the decals all at once
2055 // (this assumes they all use one particle font texture!)
2056 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2057 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2058 GL_LockArrays(0, numsurfaces*4);
2059 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2060 GL_LockArrays(0, 0);
2063 void R_DrawDecals (void)
2071 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2072 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2074 // LordHavoc: early out conditions
2075 if ((!cl.num_decals) || (!r_drawdecals.integer))
2078 decalfade = frametime * 256 / cl_decals_fadetime.value;
2079 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2080 drawdist2 = drawdist2*drawdist2;
2082 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2084 if (!decal->typeindex)
2087 if (cl.time > decal->time2 + cl_decals_time.value)
2089 decal->alpha -= decalfade;
2090 if (decal->alpha <= 0)
2096 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2098 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2099 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2105 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2108 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2109 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2112 decal->typeindex = 0;
2113 if (cl.free_decal > i)
2117 // reduce cl.num_decals if possible
2118 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2121 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2123 decal_t *olddecals = cl.decals;
2124 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2125 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2126 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2127 Mem_Free(olddecals);
2131 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2133 int surfacelistindex;
2134 int batchstart, batchcount;
2135 const particle_t *p;
2137 rtexture_t *texture;
2138 float *v3f, *t2f, *c4f;
2139 particletexture_t *tex;
2140 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2141 float ambient[3], diffuse[3], diffusenormal[3];
2142 vec4_t colormultiplier;
2143 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2145 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2147 r_refdef.stats.particles += numsurfaces;
2148 R_Mesh_Matrix(&identitymatrix);
2149 R_Mesh_ResetTextureState();
2150 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2151 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2152 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2153 R_SetupGenericShader(true);
2154 GL_DepthMask(false);
2155 GL_DepthRange(0, 1);
2156 GL_PolygonOffset(0, 0);
2158 GL_CullFace(GL_NONE);
2160 // first generate all the vertices at once
2161 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2163 p = cl.particles + surfacelist[surfacelistindex];
2165 blendmode = p->blendmode;
2167 c4f[0] = p->color[0] * colormultiplier[0];
2168 c4f[1] = p->color[1] * colormultiplier[1];
2169 c4f[2] = p->color[2] * colormultiplier[2];
2170 c4f[3] = p->alpha * colormultiplier[3];
2173 case PBLEND_INVALID:
2176 // additive and modulate can just fade out in fog (this is correct)
2177 if (r_refdef.fogenabled)
2178 c4f[3] *= FogPoint_World(p->org);
2179 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2186 // note: lighting is not cheap!
2187 if (particletype[p->typeindex].lighting)
2189 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2190 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2191 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2192 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2194 // mix in the fog color
2195 if (r_refdef.fogenabled)
2197 fog = FogPoint_World(p->org);
2199 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2200 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2201 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2205 // copy the color into the other three vertices
2206 Vector4Copy(c4f, c4f + 4);
2207 Vector4Copy(c4f, c4f + 8);
2208 Vector4Copy(c4f, c4f + 12);
2210 size = p->size * cl_particles_size.value;
2211 tex = &particletexture[p->texnum];
2212 switch(p->orientation)
2214 case PARTICLE_INVALID:
2215 case PARTICLE_BILLBOARD:
2216 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2217 VectorScale(r_refdef.view.up, size, up);
2218 v3f[ 0] = p->org[0] - right[0] - up[0];
2219 v3f[ 1] = p->org[1] - right[1] - up[1];
2220 v3f[ 2] = p->org[2] - right[2] - up[2];
2221 v3f[ 3] = p->org[0] - right[0] + up[0];
2222 v3f[ 4] = p->org[1] - right[1] + up[1];
2223 v3f[ 5] = p->org[2] - right[2] + up[2];
2224 v3f[ 6] = p->org[0] + right[0] + up[0];
2225 v3f[ 7] = p->org[1] + right[1] + up[1];
2226 v3f[ 8] = p->org[2] + right[2] + up[2];
2227 v3f[ 9] = p->org[0] + right[0] - up[0];
2228 v3f[10] = p->org[1] + right[1] - up[1];
2229 v3f[11] = p->org[2] + right[2] - up[2];
2230 t2f[0] = tex->s1;t2f[1] = tex->t2;
2231 t2f[2] = tex->s1;t2f[3] = tex->t1;
2232 t2f[4] = tex->s2;t2f[5] = tex->t1;
2233 t2f[6] = tex->s2;t2f[7] = tex->t2;
2235 case PARTICLE_ORIENTED_DOUBLESIDED:
2236 VectorVectors(p->vel, right, up);
2237 VectorScale(right, size * p->stretch, right);
2238 VectorScale(up, size, up);
2239 v3f[ 0] = p->org[0] - right[0] - up[0];
2240 v3f[ 1] = p->org[1] - right[1] - up[1];
2241 v3f[ 2] = p->org[2] - right[2] - up[2];
2242 v3f[ 3] = p->org[0] - right[0] + up[0];
2243 v3f[ 4] = p->org[1] - right[1] + up[1];
2244 v3f[ 5] = p->org[2] - right[2] + up[2];
2245 v3f[ 6] = p->org[0] + right[0] + up[0];
2246 v3f[ 7] = p->org[1] + right[1] + up[1];
2247 v3f[ 8] = p->org[2] + right[2] + up[2];
2248 v3f[ 9] = p->org[0] + right[0] - up[0];
2249 v3f[10] = p->org[1] + right[1] - up[1];
2250 v3f[11] = p->org[2] + right[2] - up[2];
2251 t2f[0] = tex->s1;t2f[1] = tex->t2;
2252 t2f[2] = tex->s1;t2f[3] = tex->t1;
2253 t2f[4] = tex->s2;t2f[5] = tex->t1;
2254 t2f[6] = tex->s2;t2f[7] = tex->t2;
2256 case PARTICLE_SPARK:
2257 len = VectorLength(p->vel);
2258 VectorNormalize2(p->vel, up);
2259 lenfactor = p->stretch * 0.04 * len;
2260 if(lenfactor < size * 0.5)
2261 lenfactor = size * 0.5;
2262 VectorMA(p->org, -lenfactor, up, v);
2263 VectorMA(p->org, lenfactor, up, up2);
2264 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2265 t2f[0] = tex->s1;t2f[1] = tex->t2;
2266 t2f[2] = tex->s1;t2f[3] = tex->t1;
2267 t2f[4] = tex->s2;t2f[5] = tex->t1;
2268 t2f[6] = tex->s2;t2f[7] = tex->t2;
2271 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2272 VectorSubtract(p->vel, p->org, up);
2273 VectorNormalize(up);
2274 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2275 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2276 t2f[0] = 1;t2f[1] = v[0];
2277 t2f[2] = 0;t2f[3] = v[0];
2278 t2f[4] = 0;t2f[5] = v[1];
2279 t2f[6] = 1;t2f[7] = v[1];
2284 // now render batches of particles based on blendmode and texture
2285 blendmode = PBLEND_INVALID;
2287 GL_LockArrays(0, numsurfaces*4);
2290 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2292 p = cl.particles + surfacelist[surfacelistindex];
2294 if (blendmode != p->blendmode)
2296 blendmode = p->blendmode;
2300 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2302 case PBLEND_INVALID:
2304 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2307 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2311 if (texture != particletexture[p->texnum].texture)
2313 texture = particletexture[p->texnum].texture;
2314 R_Mesh_TexBind(0, R_GetTexture(texture));
2317 // iterate until we find a change in settings
2318 batchstart = surfacelistindex++;
2319 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2321 p = cl.particles + surfacelist[surfacelistindex];
2322 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2326 batchcount = surfacelistindex - batchstart;
2327 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2329 GL_LockArrays(0, 0);
2332 void R_DrawParticles (void)
2335 float minparticledist;
2337 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2343 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2344 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2346 // LordHavoc: early out conditions
2347 if ((!cl.num_particles) || (!r_drawparticles.integer))
2350 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2351 gravity = frametime * cl.movevars_gravity;
2352 dvel = 1+4*frametime;
2353 decalfade = frametime * 255 / cl_decals_fadetime.value;
2354 update = frametime > 0;
2355 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2356 drawdist2 = drawdist2*drawdist2;
2358 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2362 if (cl.free_particle > i)
2363 cl.free_particle = i;
2369 if (p->delayedspawn > cl.time)
2371 p->delayedspawn = 0;
2375 p->size += p->sizeincrease * frametime;
2376 p->alpha -= p->alphafade * frametime;
2378 if (p->alpha <= 0 || p->die <= cl.time)
2381 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2383 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2385 if (p->typeindex == pt_blood)
2386 p->size += frametime * 8;
2388 p->vel[2] -= p->gravity * gravity;
2389 f = 1.0f - min(p->liquidfriction * frametime, 1);
2390 VectorScale(p->vel, f, p->vel);
2394 p->vel[2] -= p->gravity * gravity;
2397 f = 1.0f - min(p->airfriction * frametime, 1);
2398 VectorScale(p->vel, f, p->vel);
2402 VectorCopy(p->org, oldorg);
2403 VectorMA(p->org, frametime, p->vel, p->org);
2404 if (p->bounce && cl.time >= p->delayedcollisions)
2406 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2407 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2408 // or if the trace hit something flagged as NOIMPACT
2409 // then remove the particle
2410 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2412 VectorCopy(trace.endpos, p->org);
2413 // react if the particle hit something
2414 if (trace.fraction < 1)
2416 VectorCopy(trace.endpos, p->org);
2417 if (p->typeindex == pt_blood)
2419 // blood - splash on solid
2420 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2422 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2423 if (cl_decals.integer)
2425 // create a decal for the blood splat
2426 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2430 else if (p->bounce < 0)
2432 // bounce -1 means remove on impact
2437 // anything else - bounce off solid
2438 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2439 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2440 if (DotProduct(p->vel, p->vel) < 0.03)
2441 VectorClear(p->vel);
2447 if (p->typeindex != pt_static)
2449 switch (p->typeindex)
2451 case pt_entityparticle:
2452 // particle that removes itself after one rendered frame
2459 a = CL_PointSuperContents(p->org);
2460 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2464 a = CL_PointSuperContents(p->org);
2465 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2469 a = CL_PointSuperContents(p->org);
2470 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2474 if (cl.time > p->time2)
2477 p->time2 = cl.time + (rand() & 3) * 0.1;
2478 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2479 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2481 a = CL_PointSuperContents(p->org);
2482 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2490 else if (p->delayedspawn)
2493 // don't render particles too close to the view (they chew fillrate)
2494 // also don't render particles behind the view (useless)
2495 // further checks to cull to the frustum would be too slow here
2496 switch(p->typeindex)
2499 // beams have no culling
2500 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2503 if(cl_particles_visculling.integer)
2504 if (!r_refdef.viewcache.world_novis)
2505 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2507 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2509 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2512 // anything else just has to be in front of the viewer and visible at this distance
2513 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2514 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2521 if (cl.free_particle > i)
2522 cl.free_particle = i;
2525 // reduce cl.num_particles if possible
2526 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2529 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2531 particle_t *oldparticles = cl.particles;
2532 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2533 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2534 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2535 Mem_Free(oldparticles);