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 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
120 float rotate[4]; // min/max base angle, min/max rotation over time
122 particleeffectinfo_t;
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 int numparticleeffectinfo;
127 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 static int particlepalette[256];
131 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
132 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
133 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
134 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
135 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
136 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
137 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
138 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
139 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
140 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
141 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
142 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
143 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
144 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
145 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
146 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
147 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
148 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
149 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
150 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
151 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
152 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
153 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
154 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
155 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
156 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
157 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
158 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
159 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
160 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
161 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
162 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
165 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
166 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
167 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 // particletexture_t is a rectangle in the particlefonttexture
172 typedef struct particletexture_s
175 float s1, t1, s2, t2;
179 static rtexturepool_t *particletexturepool;
180 static rtexture_t *particlefonttexture;
181 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
182 skinframe_t *decalskinframe;
184 // texture numbers in particle font
185 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
186 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
187 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
188 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
189 static const int tex_rainsplash = 32;
190 static const int tex_particle = 63;
191 static const int tex_bubble = 62;
192 static const int tex_raindrop = 61;
193 static const int tex_beam = 60;
195 particleeffectinfo_t baselineparticleeffectinfo =
197 0, //int effectnameindex; // which effect this belongs to
198 // PARTICLEEFFECT_* bits
200 // blood effects may spawn very few particles, so proper fraction-overflow
201 // handling is very important, this variable keeps track of the fraction
202 0.0, //double particleaccumulator;
203 // the math is: countabsolute + requestedcount * countmultiplier * quality
204 // absolute number of particles to spawn, often used for decals
205 // (unaffected by quality and requestedcount)
206 0.0f, //float countabsolute;
207 // multiplier for the number of particles CL_ParticleEffect was told to
208 // spawn, most effects do not really have a count and hence use 1, so
209 // this is often the actual count to spawn, not merely a multiplier
210 0.0f, //float countmultiplier;
211 // if > 0 this causes the particle to spawn in an evenly spaced line from
212 // originmins to originmaxs (causing them to describe a trail, not a box)
213 0.0f, //float trailspacing;
214 // type of particle to spawn (defines some aspects of behavior)
215 pt_alphastatic, //ptype_t particletype;
216 // blending mode used on this particle type
217 PBLEND_ALPHA, //pblend_t blendmode;
218 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
219 PARTICLE_BILLBOARD, //porientation_t orientation;
220 // range of colors to choose from in hex RRGGBB (like HTML color tags),
221 // randomly interpolated at spawn
222 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
223 // a random texture is chosen in this range (note the second value is one
224 // past the last choosable, so for example 8,16 chooses any from 8 up and
226 // if start and end of the range are the same, no randomization is done
227 {63, 63 /* tex_particle */}, //int tex[2];
228 // range of size values randomly chosen when spawning, plus size increase over time
229 {1, 1, 0.0f}, //float size[3];
230 // range of alpha values randomly chosen when spawning, plus alpha fade
231 {0.0f, 256.0f, 256.0f}, //float alpha[3];
232 // how long the particle should live (note it is also removed if alpha drops to 0)
233 {16777216.0f, 16777216.0f}, //float time[2];
234 // how much gravity affects this particle (negative makes it fly up!)
235 0.0f, //float gravity;
236 // how much bounce the particle has when it hits a surface
237 // if negative the particle is removed on impact
238 0.0f, //float bounce;
239 // if in air this friction is applied
240 // if negative the particle accelerates
241 0.0f, //float airfriction;
242 // if in liquid (water/slime/lava) this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float liquidfriction;
245 // these offsets are added to the values given to particleeffect(), and
246 // then an ellipsoid-shaped jitter is added as defined by these
247 // (they are the 3 radii)
248 1.0f, //float stretchfactor;
249 // stretch velocity factor (used for sparks)
250 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
251 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
252 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
253 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
254 0.0f, //float velocitymultiplier;
255 // an effect can also spawn a dlight
256 0.0f, //float lightradiusstart;
257 0.0f, //float lightradiusfade;
258 16777216.0f, //float lighttime;
259 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
260 true, //qboolean lightshadow;
261 0, //int lightcubemapnum;
262 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
263 {-1, -1}, //int staintex[2];
264 {1.0f, 1.0f}, //float stainalpha[2];
265 {2.0f, 2.0f}, //float stainsize[2];
267 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
270 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
271 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
272 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
273 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
274 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
275 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
276 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
277 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
278 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
279 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
280 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
281 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
282 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
283 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
284 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
285 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
286 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
287 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
288 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
289 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
290 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
291 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
292 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
293 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
294 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
295 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
296 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
297 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
298 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
299 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
300 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
301 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
302 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
305 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
310 particleeffectinfo_t *info = NULL;
311 const char *text = textstart;
313 for (linenumber = 1;;linenumber++)
316 for (arrayindex = 0;arrayindex < 16;arrayindex++)
317 argv[arrayindex][0] = 0;
320 if (!COM_ParseToken_Simple(&text, true, false, true))
322 if (!strcmp(com_token, "\n"))
326 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
332 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
333 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
334 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
335 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
336 #define readfloat(var) checkparms(2);var = atof(argv[1])
337 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
338 if (!strcmp(argv[0], "effect"))
342 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
344 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
347 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
349 if (particleeffectname[effectnameindex][0])
351 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
356 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
360 // if we run out of names, abort
361 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
363 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
366 info = particleeffectinfo + numparticleeffectinfo++;
367 // copy entire info from baseline, then fix up the nameindex
368 *info = baselineparticleeffectinfo;
369 info->effectnameindex = effectnameindex;
371 else if (info == NULL)
373 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
376 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
377 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
378 else if (!strcmp(argv[0], "type"))
381 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
382 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
383 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
384 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
385 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
386 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
387 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
388 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
389 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
390 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
391 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
392 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
393 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
394 info->blendmode = particletype[info->particletype].blendmode;
395 info->orientation = particletype[info->particletype].orientation;
397 else if (!strcmp(argv[0], "blend"))
400 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
401 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
402 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
403 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
405 else if (!strcmp(argv[0], "orientation"))
408 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
409 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
410 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
411 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
412 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
414 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
415 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
416 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
417 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
418 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
419 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
420 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
421 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
422 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
423 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
424 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
425 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
426 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
427 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
428 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
429 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
430 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
431 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
432 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
433 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
434 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
435 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
436 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
437 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
438 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
439 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
440 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
441 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
442 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
443 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
444 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
446 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
455 int CL_ParticleEffectIndexForName(const char *name)
458 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
459 if (!strcmp(particleeffectname[i], name))
464 const char *CL_ParticleEffectNameForIndex(int i)
466 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
468 return particleeffectname[i];
471 // MUST match effectnameindex_t in client.h
472 static const char *standardeffectnames[EFFECT_TOTAL] =
496 "TE_TEI_BIGEXPLOSION",
512 static void CL_Particles_LoadEffectInfo(void)
516 unsigned char *filedata;
517 fs_offset_t filesize;
518 char filename[MAX_QPATH];
519 numparticleeffectinfo = 0;
520 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
521 memset(particleeffectname, 0, sizeof(particleeffectname));
522 for (i = 0;i < EFFECT_TOTAL;i++)
523 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
524 for (filepass = 0;;filepass++)
527 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
528 else if (filepass == 1)
530 if (!cl.worldbasename[0])
532 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
536 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
539 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
549 void CL_ReadPointFile_f (void);
550 void CL_Particles_Init (void)
552 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)");
553 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
555 Cvar_RegisterVariable (&cl_particles);
556 Cvar_RegisterVariable (&cl_particles_quality);
557 Cvar_RegisterVariable (&cl_particles_alpha);
558 Cvar_RegisterVariable (&cl_particles_size);
559 Cvar_RegisterVariable (&cl_particles_quake);
560 Cvar_RegisterVariable (&cl_particles_blood);
561 Cvar_RegisterVariable (&cl_particles_blood_alpha);
562 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
563 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
564 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
565 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
566 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
567 Cvar_RegisterVariable (&cl_particles_explosions_shell);
568 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
569 Cvar_RegisterVariable (&cl_particles_rain);
570 Cvar_RegisterVariable (&cl_particles_snow);
571 Cvar_RegisterVariable (&cl_particles_smoke);
572 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
573 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
574 Cvar_RegisterVariable (&cl_particles_sparks);
575 Cvar_RegisterVariable (&cl_particles_bubbles);
576 Cvar_RegisterVariable (&cl_particles_visculling);
577 Cvar_RegisterVariable (&cl_particles_collisions);
578 Cvar_RegisterVariable (&cl_decals);
579 Cvar_RegisterVariable (&cl_decals_visculling);
580 Cvar_RegisterVariable (&cl_decals_time);
581 Cvar_RegisterVariable (&cl_decals_fadetime);
582 Cvar_RegisterVariable (&cl_decals_newsystem);
583 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
584 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
585 Cvar_RegisterVariable (&cl_decals_models);
586 Cvar_RegisterVariable (&cl_decals_bias);
587 Cvar_RegisterVariable (&cl_decals_max);
590 void CL_Particles_Shutdown (void)
594 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
595 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
597 // list of all 26 parameters:
598 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
599 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
600 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
601 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
602 // palpha - opacity of particle as 0-255 (can be more than 255)
603 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
604 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
605 // pgravity - how much effect gravity has on the particle (0-1)
606 // 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
607 // px,py,pz - starting origin of particle
608 // pvx,pvy,pvz - starting velocity of particle
609 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
610 // blendmode - one of the PBLEND_ values
611 // orientation - one of the PARTICLE_ values
612 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
613 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
614 // stainalpha: opacity of the stain as factor for alpha
615 // stainsize: size of the stain as factor for palpha
616 // angle: base rotation of the particle geometry around its center normal
617 // spin: rotation speed of the particle geometry around its center normal
618 particle_t *CL_NewParticle(const vec3_t sortorigin, 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, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
623 if (!cl_particles.integer)
625 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
626 if (cl.free_particle >= cl.max_particles)
629 lifetime = palpha / min(1, palphafade);
630 part = &cl.particles[cl.free_particle++];
631 if (cl.num_particles < cl.free_particle)
632 cl.num_particles = cl.free_particle;
633 memset(part, 0, sizeof(*part));
634 VectorCopy(sortorigin, part->sortorigin);
635 part->typeindex = ptypeindex;
636 part->blendmode = blendmode;
637 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
639 particletexture_t *tex = &particletexture[ptex];
640 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
641 part->orientation = PARTICLE_VBEAM;
643 part->orientation = PARTICLE_HBEAM;
646 part->orientation = orientation;
647 l2 = (int)lhrandom(0.5, 256.5);
649 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
650 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
651 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
654 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
655 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
656 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
658 part->alpha = palpha;
659 part->alphafade = palphafade;
660 part->staintexnum = staintex;
661 if(staincolor1 >= 0 && staincolor2 >= 0)
663 l2 = (int)lhrandom(0.5, 256.5);
665 if(blendmode == PBLEND_INVMOD)
667 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
668 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
669 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
673 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
674 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
675 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
677 if(r > 0xFF) r = 0xFF;
678 if(g > 0xFF) g = 0xFF;
679 if(b > 0xFF) b = 0xFF;
683 r = part->color[0]; // -1 is shorthand for stain = particle color
687 part->staincolor[0] = r;
688 part->staincolor[1] = g;
689 part->staincolor[2] = b;
690 part->stainalpha = palpha * stainalpha;
691 part->stainsize = psize * stainsize;
694 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
696 part->color[0] *= tint[0];
697 part->color[1] *= tint[1];
698 part->color[2] *= tint[2];
700 part->alpha *= tint[3];
701 part->alphafade *= tint[3];
702 part->stainalpha *= tint[3];
706 part->sizeincrease = psizeincrease;
707 part->gravity = pgravity;
708 part->bounce = pbounce;
709 part->stretch = stretch;
711 part->org[0] = px + originjitter * v[0];
712 part->org[1] = py + originjitter * v[1];
713 part->org[2] = pz + originjitter * v[2];
714 part->vel[0] = pvx + velocityjitter * v[0];
715 part->vel[1] = pvy + velocityjitter * v[1];
716 part->vel[2] = pvz + velocityjitter * v[2];
718 part->airfriction = pairfriction;
719 part->liquidfriction = pliquidfriction;
720 part->die = cl.time + lifetime;
721 part->delayedspawn = cl.time;
722 // part->delayedcollisions = 0;
723 part->qualityreduction = pqualityreduction;
726 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
727 if (part->typeindex == pt_rain)
731 float lifetime = part->die - cl.time;
734 // turn raindrop into simple spark and create delayedspawn splash effect
735 part->typeindex = pt_spark;
737 VectorMA(part->org, lifetime, part->vel, endvec);
738 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
739 part->die = cl.time + lifetime * trace.fraction;
740 part2 = CL_NewParticle(endvec, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
743 part2->delayedspawn = part->die;
744 part2->die += part->die - cl.time;
745 for (i = rand() & 7;i < 10;i++)
747 part2 = CL_NewParticle(endvec, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
750 part2->delayedspawn = part->die;
751 part2->die += part->die - cl.time;
757 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
759 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
762 VectorMA(part->org, lifetime, part->vel, endvec);
763 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
764 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
771 static void CL_ImmediateBloodStain(particle_t *part)
776 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
777 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
779 VectorCopy(part->vel, v);
781 staintex = part->staintexnum;
782 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
785 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
786 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
788 VectorCopy(part->vel, v);
790 staintex = tex_blooddecal[rand()&7];
791 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
795 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
799 entity_render_t *ent = &cl.entities[hitent].render;
800 unsigned char color[3];
801 if (!cl_decals.integer)
803 if (!ent->allowdecals)
806 l2 = (int)lhrandom(0.5, 256.5);
808 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
809 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
810 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
812 if (cl_decals_newsystem.integer)
815 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
817 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
821 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
822 if (cl.free_decal >= cl.max_decals)
824 decal = &cl.decals[cl.free_decal++];
825 if (cl.num_decals < cl.free_decal)
826 cl.num_decals = cl.free_decal;
827 memset(decal, 0, sizeof(*decal));
828 decal->decalsequence = cl.decalsequence++;
829 decal->typeindex = pt_decal;
830 decal->texnum = texnum;
831 VectorMA(org, cl_decals_bias.value, normal, decal->org);
832 VectorCopy(normal, decal->normal);
834 decal->alpha = alpha;
835 decal->time2 = cl.time;
836 decal->color[0] = color[0];
837 decal->color[1] = color[1];
838 decal->color[2] = color[2];
841 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
842 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
843 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
845 decal->owner = hitent;
846 decal->clusterindex = -1000; // no vis culling unless we're sure
849 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
850 decal->ownermodel = cl.entities[decal->owner].render.model;
851 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
852 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
856 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
858 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
860 decal->clusterindex = leaf->clusterindex;
865 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
868 float bestfrac, bestorg[3], bestnormal[3];
870 int besthitent = 0, hitent;
873 for (i = 0;i < 32;i++)
876 VectorMA(org, maxdist, org2, org2);
877 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
878 // take the closest trace result that doesn't end up hitting a NOMARKS
879 // surface (sky for example)
880 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
882 bestfrac = trace.fraction;
884 VectorCopy(trace.endpos, bestorg);
885 VectorCopy(trace.plane.normal, bestnormal);
889 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
892 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
893 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
894 static 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)
897 matrix4x4_t tempmatrix;
899 VectorLerp(originmins, 0.5, originmaxs, center);
900 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
901 if (effectnameindex == EFFECT_SVC_PARTICLE)
903 if (cl_particles.integer)
905 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
907 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
908 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
909 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
912 count *= cl_particles_quality.value;
913 for (;count > 0;count--)
915 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
916 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
921 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
922 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
923 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
924 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
925 else if (effectnameindex == EFFECT_TE_SPIKE)
927 if (cl_particles_bulletimpacts.integer)
929 if (cl_particles_quake.integer)
931 if (cl_particles_smoke.integer)
932 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
936 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
937 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
938 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
942 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
943 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
945 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
947 if (cl_particles_bulletimpacts.integer)
949 if (cl_particles_quake.integer)
951 if (cl_particles_smoke.integer)
952 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
956 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
957 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
958 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
962 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
963 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
964 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);
966 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
972 if (cl_particles_smoke.integer)
973 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
977 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
978 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
979 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
983 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
984 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
986 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
988 if (cl_particles_bulletimpacts.integer)
990 if (cl_particles_quake.integer)
992 if (cl_particles_smoke.integer)
993 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
997 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
998 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
999 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1003 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1004 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1005 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);
1007 else if (effectnameindex == EFFECT_TE_BLOOD)
1009 if (!cl_particles_blood.integer)
1011 if (cl_particles_quake.integer)
1012 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1015 static double bloodaccumulator = 0;
1016 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1017 //CL_NewParticle(center, 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, NULL);
1018 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1019 for (;bloodaccumulator > 0;bloodaccumulator--)
1021 part = CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1022 if (immediatebloodstain && part)
1024 immediatebloodstain = false;
1025 CL_ImmediateBloodStain(part);
1030 else if (effectnameindex == EFFECT_TE_SPARK)
1031 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1032 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1034 // plasma scorch mark
1035 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1036 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1037 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1039 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1041 if (cl_particles_bulletimpacts.integer)
1043 if (cl_particles_quake.integer)
1044 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1047 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1048 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1049 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1053 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1054 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1056 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1058 if (cl_particles_bulletimpacts.integer)
1060 if (cl_particles_quake.integer)
1061 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1064 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1065 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1066 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1070 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1071 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1072 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);
1074 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1076 CL_ParticleExplosion(center);
1077 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);
1079 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1081 CL_ParticleExplosion(center);
1082 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);
1084 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1086 if (cl_particles_quake.integer)
1089 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1092 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1094 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1098 CL_ParticleExplosion(center);
1099 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);
1101 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1102 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);
1103 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1105 count *= cl_particles_quality.value;
1107 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1109 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1111 float i, j, inc, vel;
1114 inc = 8 / cl_particles_quality.value;
1115 for (i = -128;i < 128;i += inc)
1117 for (j = -128;j < 128;j += inc)
1119 dir[0] = j + lhrandom(0, inc);
1120 dir[1] = i + lhrandom(0, inc);
1122 org[0] = center[0] + dir[0];
1123 org[1] = center[1] + dir[1];
1124 org[2] = center[2] + lhrandom(0, 64);
1125 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1126 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1130 else if (effectnameindex == EFFECT_TE_TELEPORT)
1132 float i, j, k, inc, vel;
1135 if (cl_particles_quake.integer)
1136 inc = 4 / cl_particles_quality.value;
1138 inc = 8 / cl_particles_quality.value;
1139 for (i = -16;i < 16;i += inc)
1141 for (j = -16;j < 16;j += inc)
1143 for (k = -24;k < 32;k += inc)
1145 VectorSet(dir, i*8, j*8, k*8);
1146 VectorNormalize(dir);
1147 vel = lhrandom(50, 113);
1148 if (cl_particles_quake.integer)
1149 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1151 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1155 if (!cl_particles_quake.integer)
1156 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1157 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);
1159 else if (effectnameindex == EFFECT_TE_TEI_G3)
1160 CL_NewParticle(center, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1161 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1163 if (cl_particles_smoke.integer)
1165 count *= 0.25f * cl_particles_quality.value;
1167 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1170 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1172 CL_ParticleExplosion(center);
1173 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);
1175 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1178 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1179 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1180 if (cl_particles_smoke.integer)
1181 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1182 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1183 if (cl_particles_sparks.integer)
1184 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1185 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1186 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);
1188 else if (effectnameindex == EFFECT_EF_FLAME)
1190 count *= 300 * cl_particles_quality.value;
1192 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1193 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);
1195 else if (effectnameindex == EFFECT_EF_STARDUST)
1197 count *= 200 * cl_particles_quality.value;
1199 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1200 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);
1202 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1206 int smoke, blood, bubbles, r, color;
1208 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1211 Vector4Set(light, 0, 0, 0, 0);
1213 if (effectnameindex == EFFECT_TR_ROCKET)
1214 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1215 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1217 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1218 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1220 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1222 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1223 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1227 matrix4x4_t tempmatrix;
1228 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1229 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);
1230 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1234 if (!spawnparticles)
1237 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1240 VectorSubtract(originmaxs, originmins, dir);
1241 len = VectorNormalizeLength(dir);
1244 dec = -ent->persistent.trail_time;
1245 ent->persistent.trail_time += len;
1246 if (ent->persistent.trail_time < 0.01f)
1249 // if we skip out, leave it reset
1250 ent->persistent.trail_time = 0.0f;
1255 // advance into this frame to reach the first puff location
1256 VectorMA(originmins, dec, dir, pos);
1259 smoke = cl_particles.integer && cl_particles_smoke.integer;
1260 blood = cl_particles.integer && cl_particles_blood.integer;
1261 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1262 qd = 1.0f / cl_particles_quality.value;
1269 if (effectnameindex == EFFECT_TR_BLOOD)
1271 if (cl_particles_quake.integer)
1273 color = particlepalette[67 + (rand()&3)];
1274 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1279 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1282 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1284 if (cl_particles_quake.integer)
1287 color = particlepalette[67 + (rand()&3)];
1288 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1293 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1299 if (effectnameindex == EFFECT_TR_ROCKET)
1301 if (cl_particles_quake.integer)
1304 color = particlepalette[ramp3[r]];
1305 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1309 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1310 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1313 else if (effectnameindex == EFFECT_TR_GRENADE)
1315 if (cl_particles_quake.integer)
1318 color = particlepalette[ramp3[r]];
1319 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1323 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1326 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1328 if (cl_particles_quake.integer)
1331 color = particlepalette[52 + (rand()&7)];
1332 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1333 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1335 else if (gamemode == GAME_GOODVSBAD2)
1338 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1342 color = particlepalette[20 + (rand()&7)];
1343 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1346 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1348 if (cl_particles_quake.integer)
1351 color = particlepalette[230 + (rand()&7)];
1352 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1353 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1357 color = particlepalette[226 + (rand()&7)];
1358 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1361 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1363 if (cl_particles_quake.integer)
1365 color = particlepalette[152 + (rand()&3)];
1366 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1368 else if (gamemode == GAME_GOODVSBAD2)
1371 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1373 else if (gamemode == GAME_PRYDON)
1376 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1379 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1381 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1384 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1386 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1389 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1391 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1392 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1396 if (effectnameindex == EFFECT_TR_ROCKET)
1397 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1398 else if (effectnameindex == EFFECT_TR_GRENADE)
1399 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1401 // advance to next time and position
1404 VectorMA (pos, dec, dir, pos);
1407 ent->persistent.trail_time = len;
1410 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1413 // this is also called on point effects with spawndlight = true and
1414 // spawnparticles = true
1415 // it is called CL_ParticleTrail because most code does not want to supply
1416 // these parameters, only trail handling does
1417 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, float tintmins[4], float tintmaxs[4])
1419 qboolean found = false;
1421 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1423 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1424 return; // no such effect
1426 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1428 int effectinfoindex;
1431 particleeffectinfo_t *info;
1438 qboolean underwater;
1439 qboolean immediatebloodstain;
1441 float avgtint[4], tint[4], tintlerp;
1442 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1443 VectorLerp(originmins, 0.5, originmaxs, center);
1444 supercontents = CL_PointSuperContents(center);
1445 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1446 VectorSubtract(originmaxs, originmins, traildir);
1447 traillen = VectorLength(traildir);
1448 VectorNormalize(traildir);
1451 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1455 Vector4Set(avgtint, 1, 1, 1, 1);
1457 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1459 if (info->effectnameindex == effectnameindex)
1462 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1464 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1467 // spawn a dlight if requested
1468 if (info->lightradiusstart > 0 && spawndlight)
1470 matrix4x4_t tempmatrix;
1471 if (info->trailspacing > 0)
1472 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1474 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1475 if (info->lighttime > 0 && info->lightradiusfade > 0)
1477 // light flash (explosion, etc)
1478 // called when effect starts
1479 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1481 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1484 // called by CL_LinkNetworkEntity
1485 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1486 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1487 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1488 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1489 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1490 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1494 if (!spawnparticles)
1499 if (info->tex[1] > info->tex[0])
1501 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1502 tex = min(tex, info->tex[1] - 1);
1504 if(info->staintex[0] < 0)
1505 staintex = info->staintex[0];
1508 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1509 staintex = min(staintex, info->staintex[1] - 1);
1511 if (info->particletype == pt_decal)
1512 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1513 else if (info->orientation == PARTICLE_HBEAM)
1514 CL_NewParticle(center, 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, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
1517 if (!cl_particles.integer)
1519 switch (info->particletype)
1521 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1522 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1523 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1524 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1525 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1526 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1529 VectorCopy(originmins, trailpos);
1530 if (info->trailspacing > 0)
1532 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1533 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1534 immediatebloodstain = false;
1538 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1540 immediatebloodstain =
1541 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1543 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1545 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1546 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1548 if (info->tex[1] > info->tex[0])
1550 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1551 tex = min(tex, info->tex[1] - 1);
1555 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1556 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1557 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1561 tintlerp = lhrandom(0, 1);
1562 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1565 part = CL_NewParticle(center, 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, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
1566 if (immediatebloodstain && part)
1568 immediatebloodstain = false;
1569 CL_ImmediateBloodStain(part);
1572 VectorMA(trailpos, trailstep, traildir, trailpos);
1579 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1582 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)
1584 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1592 void CL_EntityParticles (const entity_t *ent)
1595 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1596 static vec3_t avelocities[NUMVERTEXNORMALS];
1597 if (!cl_particles.integer) return;
1598 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1600 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1602 if (!avelocities[0][0])
1603 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1604 avelocities[0][i] = lhrandom(0, 2.55);
1606 for (i = 0;i < NUMVERTEXNORMALS;i++)
1608 yaw = cl.time * avelocities[i][0];
1609 pitch = cl.time * avelocities[i][1];
1610 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1611 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1612 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1613 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1618 void CL_ReadPointFile_f (void)
1620 vec3_t org, leakorg;
1622 char *pointfile = NULL, *pointfilepos, *t, tchar;
1623 char name[MAX_QPATH];
1628 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1629 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1632 Con_Printf("Could not open %s\n", name);
1636 Con_Printf("Reading %s...\n", name);
1637 VectorClear(leakorg);
1640 pointfilepos = pointfile;
1641 while (*pointfilepos)
1643 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1648 while (*t && *t != '\n' && *t != '\r')
1652 #if _MSC_VER >= 1400
1653 #define sscanf sscanf_s
1655 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1661 VectorCopy(org, leakorg);
1664 if (cl.num_particles < cl.max_particles - 3)
1667 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1670 Mem_Free(pointfile);
1671 VectorCopy(leakorg, org);
1672 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1674 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1675 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1676 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1681 CL_ParseParticleEffect
1683 Parse an effect out of the server message
1686 void CL_ParseParticleEffect (void)
1689 int i, count, msgcount, color;
1691 MSG_ReadVector(&cl_message, org, cls.protocol);
1692 for (i=0 ; i<3 ; i++)
1693 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1694 msgcount = MSG_ReadByte(&cl_message);
1695 color = MSG_ReadByte(&cl_message);
1697 if (msgcount == 255)
1702 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1707 CL_ParticleExplosion
1711 void CL_ParticleExplosion (const vec3_t org)
1717 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1718 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1720 if (cl_particles_quake.integer)
1722 for (i = 0;i < 1024;i++)
1728 color = particlepalette[ramp1[r]];
1729 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1733 color = particlepalette[ramp2[r]];
1734 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1740 i = CL_PointSuperContents(org);
1741 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1743 if (cl_particles.integer && cl_particles_bubbles.integer)
1744 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1745 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1749 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1751 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1758 VectorMA(org, 128, v2, v);
1759 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1761 while (k < 16 && trace.fraction < 0.1f);
1762 VectorSubtract(trace.endpos, org, v2);
1763 VectorScale(v2, 2.0f, v2);
1764 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1770 if (cl_particles_explosions_shell.integer)
1771 R_NewExplosion(org);
1776 CL_ParticleExplosion2
1780 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1783 if (!cl_particles.integer) return;
1785 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1787 k = particlepalette[colorStart + (i % colorLength)];
1788 if (cl_particles_quake.integer)
1789 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1791 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1795 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1798 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1799 if (cl_particles_sparks.integer)
1801 sparkcount *= cl_particles_quality.value;
1802 while(sparkcount-- > 0)
1803 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1807 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1810 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1811 if (cl_particles_smoke.integer)
1813 smokecount *= cl_particles_quality.value;
1814 while(smokecount-- > 0)
1815 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1819 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)
1823 if (!cl_particles.integer) return;
1824 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1826 count = (int)(count * cl_particles_quality.value);
1829 k = particlepalette[colorbase + (rand()&3)];
1830 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1834 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1837 float minz, maxz, lifetime = 30;
1839 if (!cl_particles.integer) return;
1840 if (dir[2] < 0) // falling
1842 minz = maxs[2] + dir[2] * 0.1;
1845 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1850 maxz = maxs[2] + dir[2] * 0.1;
1852 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1855 count = (int)(count * cl_particles_quality.value);
1860 if (!cl_particles_rain.integer) break;
1861 count *= 4; // ick, this should be in the mod or maps?
1865 k = particlepalette[colorbase + (rand()&3)];
1866 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1867 if (gamemode == GAME_GOODVSBAD2)
1868 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1870 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1874 if (!cl_particles_snow.integer) break;
1877 k = particlepalette[colorbase + (rand()&3)];
1878 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1879 if (gamemode == GAME_GOODVSBAD2)
1880 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1882 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1886 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1890 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1891 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1892 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1893 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1894 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1895 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1897 #define PARTICLETEXTURESIZE 64
1898 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1900 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1904 dz = 1 - (dx*dx+dy*dy);
1905 if (dz > 0) // it does hit the sphere
1909 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1910 VectorNormalize(normal);
1911 dot = DotProduct(normal, light);
1912 if (dot > 0.5) // interior reflection
1913 f += ((dot * 2) - 1);
1914 else if (dot < -0.5) // exterior reflection
1915 f += ((dot * -2) - 1);
1917 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1918 VectorNormalize(normal);
1919 dot = DotProduct(normal, light);
1920 if (dot > 0.5) // interior reflection
1921 f += ((dot * 2) - 1);
1922 else if (dot < -0.5) // exterior reflection
1923 f += ((dot * -2) - 1);
1925 f += 16; // just to give it a haze so you can see the outline
1926 f = bound(0, f, 255);
1927 return (unsigned char) f;
1933 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1934 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1936 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1937 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1938 *width = particlefontcellwidth;
1939 *height = particlefontcellheight;
1942 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1944 int basex, basey, w, h, y;
1945 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1946 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1947 Sys_Error("invalid particle texture size for autogenerating");
1948 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1949 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1952 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1955 float cx, cy, dx, dy, f, iradius;
1957 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1958 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1959 iradius = 1.0f / radius;
1960 alpha *= (1.0f / 255.0f);
1961 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1963 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1967 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1972 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1973 d[0] += (int)(f * (blue - d[0]));
1974 d[1] += (int)(f * (green - d[1]));
1975 d[2] += (int)(f * (red - d[2]));
1982 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1985 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1987 data[0] = bound(minb, data[0], maxb);
1988 data[1] = bound(ming, data[1], maxg);
1989 data[2] = bound(minr, data[2], maxr);
1994 static void particletextureinvert(unsigned char *data)
1997 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1999 data[0] = 255 - data[0];
2000 data[1] = 255 - data[1];
2001 data[2] = 255 - data[2];
2005 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2006 static void R_InitBloodTextures (unsigned char *particletexturedata)
2009 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2010 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2013 for (i = 0;i < 8;i++)
2015 memset(data, 255, datasize);
2016 for (k = 0;k < 24;k++)
2017 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2018 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2019 particletextureinvert(data);
2020 setuptex(tex_bloodparticle[i], data, particletexturedata);
2024 for (i = 0;i < 8;i++)
2026 memset(data, 255, datasize);
2028 for (j = 1;j < 10;j++)
2029 for (k = min(j, m - 1);k < m;k++)
2030 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2031 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2032 particletextureinvert(data);
2033 setuptex(tex_blooddecal[i], data, particletexturedata);
2039 //uncomment this to make engine save out particle font to a tga file when run
2040 //#define DUMPPARTICLEFONT
2042 static void R_InitParticleTexture (void)
2044 int x, y, d, i, k, m;
2045 int basex, basey, w, h;
2046 float dx, dy, f, s1, t1, s2, t2;
2049 fs_offset_t filesize;
2050 char texturename[MAX_QPATH];
2053 // a note: decals need to modulate (multiply) the background color to
2054 // properly darken it (stain), and they need to be able to alpha fade,
2055 // this is a very difficult challenge because it means fading to white
2056 // (no change to background) rather than black (darkening everything
2057 // behind the whole decal polygon), and to accomplish this the texture is
2058 // inverted (dark red blood on white background becomes brilliant cyan
2059 // and white on black background) so we can alpha fade it to black, then
2060 // we invert it again during the blendfunc to make it work...
2062 #ifndef DUMPPARTICLEFONT
2063 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2066 particlefonttexture = decalskinframe->base;
2067 // TODO maybe allow custom grid size?
2068 particlefontwidth = image_width;
2069 particlefontheight = image_height;
2070 particlefontcellwidth = image_width / 8;
2071 particlefontcellheight = image_height / 8;
2072 particlefontcols = 8;
2073 particlefontrows = 8;
2078 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2079 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2080 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2081 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2082 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2084 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2085 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2086 particlefontcols = 8;
2087 particlefontrows = 8;
2089 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2092 for (i = 0;i < 8;i++)
2094 memset(data, 255, datasize);
2097 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2098 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2100 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2102 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2103 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2105 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2106 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2108 d = (int)(d * (1-(dx*dx+dy*dy)));
2109 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2110 d = bound(0, d, 255);
2111 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2118 setuptex(tex_smoke[i], data, particletexturedata);
2122 memset(data, 255, datasize);
2123 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2125 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2126 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2128 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2129 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2130 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2133 setuptex(tex_rainsplash, data, particletexturedata);
2136 memset(data, 255, datasize);
2137 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2139 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2140 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2142 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2143 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2144 d = bound(0, d, 255);
2145 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2148 setuptex(tex_particle, data, particletexturedata);
2151 memset(data, 255, datasize);
2152 light[0] = 1;light[1] = 1;light[2] = 1;
2153 VectorNormalize(light);
2154 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2156 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2157 // stretch upper half of bubble by +50% and shrink lower half by -50%
2158 // (this gives an elongated teardrop shape)
2160 dy = (dy - 0.5f) * 2.0f;
2162 dy = (dy - 0.5f) / 1.5f;
2163 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2165 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2166 // shrink bubble width to half
2168 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2171 setuptex(tex_raindrop, data, particletexturedata);
2174 memset(data, 255, datasize);
2175 light[0] = 1;light[1] = 1;light[2] = 1;
2176 VectorNormalize(light);
2177 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2179 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2180 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2182 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2183 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2186 setuptex(tex_bubble, data, particletexturedata);
2188 // Blood particles and blood decals
2189 R_InitBloodTextures (particletexturedata);
2192 for (i = 0;i < 8;i++)
2194 memset(data, 255, datasize);
2195 for (k = 0;k < 12;k++)
2196 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2197 for (k = 0;k < 3;k++)
2198 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2199 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2200 particletextureinvert(data);
2201 setuptex(tex_bulletdecal[i], data, particletexturedata);
2204 #ifdef DUMPPARTICLEFONT
2205 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2208 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2209 particlefonttexture = decalskinframe->base;
2211 Mem_Free(particletexturedata);
2216 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2218 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2219 particletexture[i].texture = particlefonttexture;
2220 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2221 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2222 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2223 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2226 #ifndef DUMPPARTICLEFONT
2227 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2228 if (!particletexture[tex_beam].texture)
2231 unsigned char noise3[64][64], data2[64][16][4];
2233 fractalnoise(&noise3[0][0], 64, 4);
2235 for (y = 0;y < 64;y++)
2237 dy = (y - 0.5f*64) / (64*0.5f-1);
2238 for (x = 0;x < 16;x++)
2240 dx = (x - 0.5f*16) / (16*0.5f-2);
2241 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2242 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2243 data2[y][x][3] = 255;
2247 #ifdef DUMPPARTICLEFONT
2248 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2250 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2252 particletexture[tex_beam].s1 = 0;
2253 particletexture[tex_beam].t1 = 0;
2254 particletexture[tex_beam].s2 = 1;
2255 particletexture[tex_beam].t2 = 1;
2257 // now load an texcoord/texture override file
2258 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2265 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2267 if(!strcmp(com_token, "\n"))
2268 continue; // empty line
2269 i = atoi(com_token);
2277 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2279 strlcpy(texturename, com_token, sizeof(texturename));
2280 s1 = atof(com_token);
2281 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2284 t1 = atof(com_token);
2285 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2287 s2 = atof(com_token);
2288 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2290 t2 = atof(com_token);
2291 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2292 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2293 strlcpy(texturename, com_token, sizeof(texturename));
2300 if (!texturename[0])
2302 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2305 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2307 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2310 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2313 // R_SkinFrame_LoadExternal already complained
2316 particletexture[i].texture = sf->base;
2317 particletexture[i].s1 = s1;
2318 particletexture[i].t1 = t1;
2319 particletexture[i].s2 = s2;
2320 particletexture[i].t2 = t2;
2326 static void r_part_start(void)
2329 // generate particlepalette for convenience from the main one
2330 for (i = 0;i < 256;i++)
2331 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2332 particletexturepool = R_AllocTexturePool();
2333 R_InitParticleTexture ();
2334 CL_Particles_LoadEffectInfo();
2337 static void r_part_shutdown(void)
2339 R_FreeTexturePool(&particletexturepool);
2342 static void r_part_newmap(void)
2345 R_SkinFrame_MarkUsed(decalskinframe);
2346 CL_Particles_LoadEffectInfo();
2349 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2350 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2352 void R_Particles_Init (void)
2355 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2357 particle_elements[i*6+0] = i*4+0;
2358 particle_elements[i*6+1] = i*4+1;
2359 particle_elements[i*6+2] = i*4+2;
2360 particle_elements[i*6+3] = i*4+0;
2361 particle_elements[i*6+4] = i*4+2;
2362 particle_elements[i*6+5] = i*4+3;
2365 Cvar_RegisterVariable(&r_drawparticles);
2366 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2367 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2368 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2369 Cvar_RegisterVariable(&r_drawdecals);
2370 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2371 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2374 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2376 int surfacelistindex;
2378 float *v3f, *t2f, *c4f;
2379 particletexture_t *tex;
2380 float right[3], up[3], size, ca;
2381 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2383 RSurf_ActiveWorldEntity();
2385 r_refdef.stats.drawndecals += numsurfaces;
2386 // R_Mesh_ResetTextureState();
2387 GL_DepthMask(false);
2388 GL_DepthRange(0, 1);
2389 GL_PolygonOffset(0, 0);
2391 GL_CullFace(GL_NONE);
2393 // generate all the vertices at once
2394 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2396 d = cl.decals + surfacelist[surfacelistindex];
2399 c4f = particle_color4f + 16*surfacelistindex;
2400 ca = d->alpha * alphascale;
2401 // ensure alpha multiplier saturates properly
2402 if (ca > 1.0f / 256.0f)
2404 if (r_refdef.fogenabled)
2405 ca *= RSurf_FogVertex(d->org);
2406 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2407 Vector4Copy(c4f, c4f + 4);
2408 Vector4Copy(c4f, c4f + 8);
2409 Vector4Copy(c4f, c4f + 12);
2411 // calculate vertex positions
2412 size = d->size * cl_particles_size.value;
2413 VectorVectors(d->normal, right, up);
2414 VectorScale(right, size, right);
2415 VectorScale(up, size, up);
2416 v3f = particle_vertex3f + 12*surfacelistindex;
2417 v3f[ 0] = d->org[0] - right[0] - up[0];
2418 v3f[ 1] = d->org[1] - right[1] - up[1];
2419 v3f[ 2] = d->org[2] - right[2] - up[2];
2420 v3f[ 3] = d->org[0] - right[0] + up[0];
2421 v3f[ 4] = d->org[1] - right[1] + up[1];
2422 v3f[ 5] = d->org[2] - right[2] + up[2];
2423 v3f[ 6] = d->org[0] + right[0] + up[0];
2424 v3f[ 7] = d->org[1] + right[1] + up[1];
2425 v3f[ 8] = d->org[2] + right[2] + up[2];
2426 v3f[ 9] = d->org[0] + right[0] - up[0];
2427 v3f[10] = d->org[1] + right[1] - up[1];
2428 v3f[11] = d->org[2] + right[2] - up[2];
2430 // calculate texcoords
2431 tex = &particletexture[d->texnum];
2432 t2f = particle_texcoord2f + 8*surfacelistindex;
2433 t2f[0] = tex->s1;t2f[1] = tex->t2;
2434 t2f[2] = tex->s1;t2f[3] = tex->t1;
2435 t2f[4] = tex->s2;t2f[5] = tex->t1;
2436 t2f[6] = tex->s2;t2f[7] = tex->t2;
2439 // now render the decals all at once
2440 // (this assumes they all use one particle font texture!)
2441 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2442 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2443 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2444 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2447 void R_DrawDecals (void)
2450 int drawdecals = r_drawdecals.integer;
2455 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2457 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2458 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2460 // LordHavoc: early out conditions
2464 decalfade = frametime * 256 / cl_decals_fadetime.value;
2465 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2466 drawdist2 = drawdist2*drawdist2;
2468 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2470 if (!decal->typeindex)
2473 if (killsequence - decal->decalsequence > 0)
2476 if (cl.time > decal->time2 + cl_decals_time.value)
2478 decal->alpha -= decalfade;
2479 if (decal->alpha <= 0)
2485 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2487 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2488 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2494 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2500 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))
2501 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2504 decal->typeindex = 0;
2505 if (cl.free_decal > i)
2509 // reduce cl.num_decals if possible
2510 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2513 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2515 decal_t *olddecals = cl.decals;
2516 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2517 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2518 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2519 Mem_Free(olddecals);
2522 r_refdef.stats.totaldecals = cl.num_decals;
2525 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2527 int surfacelistindex;
2528 int batchstart, batchcount;
2529 const particle_t *p;
2531 rtexture_t *texture;
2532 float *v3f, *t2f, *c4f;
2533 particletexture_t *tex;
2534 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2535 // float ambient[3], diffuse[3], diffusenormal[3];
2536 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2537 vec4_t colormultiplier;
2538 float minparticledist_start, minparticledist_end;
2541 RSurf_ActiveWorldEntity();
2543 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));
2545 r_refdef.stats.particles += numsurfaces;
2546 // R_Mesh_ResetTextureState();
2547 GL_DepthMask(false);
2548 GL_DepthRange(0, 1);
2549 GL_PolygonOffset(0, 0);
2551 GL_CullFace(GL_NONE);
2553 spintime = r_refdef.scene.time;
2555 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2556 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2557 dofade = (minparticledist_start < minparticledist_end);
2559 // first generate all the vertices at once
2560 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2562 p = cl.particles + surfacelist[surfacelistindex];
2564 blendmode = (pblend_t)p->blendmode;
2566 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2567 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2568 alpha = palpha * colormultiplier[3];
2569 // ensure alpha multiplier saturates properly
2575 case PBLEND_INVALID:
2577 // additive and modulate can just fade out in fog (this is correct)
2578 if (r_refdef.fogenabled)
2579 alpha *= RSurf_FogVertex(p->org);
2580 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2581 alpha *= 1.0f / 256.0f;
2582 c4f[0] = p->color[0] * alpha;
2583 c4f[1] = p->color[1] * alpha;
2584 c4f[2] = p->color[2] * alpha;
2588 // additive and modulate can just fade out in fog (this is correct)
2589 if (r_refdef.fogenabled)
2590 alpha *= RSurf_FogVertex(p->org);
2591 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2592 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2593 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2594 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2598 c4f[0] = p->color[0] * colormultiplier[0];
2599 c4f[1] = p->color[1] * colormultiplier[1];
2600 c4f[2] = p->color[2] * colormultiplier[2];
2602 // note: lighting is not cheap!
2603 if (particletype[p->typeindex].lighting)
2604 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2605 // mix in the fog color
2606 if (r_refdef.fogenabled)
2608 fog = RSurf_FogVertex(p->org);
2610 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2611 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2612 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2614 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2615 VectorScale(c4f, alpha, c4f);
2618 // copy the color into the other three vertices
2619 Vector4Copy(c4f, c4f + 4);
2620 Vector4Copy(c4f, c4f + 8);
2621 Vector4Copy(c4f, c4f + 12);
2623 size = p->size * cl_particles_size.value;
2624 tex = &particletexture[p->texnum];
2625 switch(p->orientation)
2627 // case PARTICLE_INVALID:
2628 case PARTICLE_BILLBOARD:
2629 if (p->angle + p->spin)
2631 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2632 spinsin = sin(spinrad) * size;
2633 spincos = cos(spinrad) * size;
2634 spinm1 = -p->stretch * spincos;
2637 spinm4 = -p->stretch * spincos;
2638 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2639 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2643 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2644 VectorScale(r_refdef.view.up, size, up);
2647 v3f[ 0] = p->org[0] - right[0] - up[0];
2648 v3f[ 1] = p->org[1] - right[1] - up[1];
2649 v3f[ 2] = p->org[2] - right[2] - up[2];
2650 v3f[ 3] = p->org[0] - right[0] + up[0];
2651 v3f[ 4] = p->org[1] - right[1] + up[1];
2652 v3f[ 5] = p->org[2] - right[2] + up[2];
2653 v3f[ 6] = p->org[0] + right[0] + up[0];
2654 v3f[ 7] = p->org[1] + right[1] + up[1];
2655 v3f[ 8] = p->org[2] + right[2] + up[2];
2656 v3f[ 9] = p->org[0] + right[0] - up[0];
2657 v3f[10] = p->org[1] + right[1] - up[1];
2658 v3f[11] = p->org[2] + right[2] - up[2];
2659 t2f[0] = tex->s1;t2f[1] = tex->t2;
2660 t2f[2] = tex->s1;t2f[3] = tex->t1;
2661 t2f[4] = tex->s2;t2f[5] = tex->t1;
2662 t2f[6] = tex->s2;t2f[7] = tex->t2;
2664 case PARTICLE_ORIENTED_DOUBLESIDED:
2665 VectorVectors(p->vel, baseright, baseup);
2666 if (p->angle + p->spin)
2668 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2669 spinsin = sin(spinrad) * size;
2670 spincos = cos(spinrad) * size;
2671 spinm1 = p->stretch * spincos;
2674 spinm4 = p->stretch * spincos;
2675 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2676 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2680 VectorScale(baseright, size * p->stretch, right);
2681 VectorScale(baseup, size, up);
2683 v3f[ 0] = p->org[0] - right[0] - up[0];
2684 v3f[ 1] = p->org[1] - right[1] - up[1];
2685 v3f[ 2] = p->org[2] - right[2] - up[2];
2686 v3f[ 3] = p->org[0] - right[0] + up[0];
2687 v3f[ 4] = p->org[1] - right[1] + up[1];
2688 v3f[ 5] = p->org[2] - right[2] + up[2];
2689 v3f[ 6] = p->org[0] + right[0] + up[0];
2690 v3f[ 7] = p->org[1] + right[1] + up[1];
2691 v3f[ 8] = p->org[2] + right[2] + up[2];
2692 v3f[ 9] = p->org[0] + right[0] - up[0];
2693 v3f[10] = p->org[1] + right[1] - up[1];
2694 v3f[11] = p->org[2] + right[2] - up[2];
2695 t2f[0] = tex->s1;t2f[1] = tex->t2;
2696 t2f[2] = tex->s1;t2f[3] = tex->t1;
2697 t2f[4] = tex->s2;t2f[5] = tex->t1;
2698 t2f[6] = tex->s2;t2f[7] = tex->t2;
2700 case PARTICLE_SPARK:
2701 len = VectorLength(p->vel);
2702 VectorNormalize2(p->vel, up);
2703 lenfactor = p->stretch * 0.04 * len;
2704 if(lenfactor < size * 0.5)
2705 lenfactor = size * 0.5;
2706 VectorMA(p->org, -lenfactor, up, v);
2707 VectorMA(p->org, lenfactor, up, up2);
2708 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2709 t2f[0] = tex->s1;t2f[1] = tex->t2;
2710 t2f[2] = tex->s1;t2f[3] = tex->t1;
2711 t2f[4] = tex->s2;t2f[5] = tex->t1;
2712 t2f[6] = tex->s2;t2f[7] = tex->t2;
2714 case PARTICLE_VBEAM:
2715 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2716 VectorSubtract(p->vel, p->org, up);
2717 VectorNormalize(up);
2718 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2719 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2720 t2f[0] = tex->s2;t2f[1] = v[0];
2721 t2f[2] = tex->s1;t2f[3] = v[0];
2722 t2f[4] = tex->s1;t2f[5] = v[1];
2723 t2f[6] = tex->s2;t2f[7] = v[1];
2725 case PARTICLE_HBEAM:
2726 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2727 VectorSubtract(p->vel, p->org, up);
2728 VectorNormalize(up);
2729 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2730 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2731 t2f[0] = v[0];t2f[1] = tex->t1;
2732 t2f[2] = v[0];t2f[3] = tex->t2;
2733 t2f[4] = v[1];t2f[5] = tex->t2;
2734 t2f[6] = v[1];t2f[7] = tex->t1;
2739 // now render batches of particles based on blendmode and texture
2740 blendmode = PBLEND_INVALID;
2744 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2745 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2747 p = cl.particles + surfacelist[surfacelistindex];
2749 if (texture != particletexture[p->texnum].texture)
2751 texture = particletexture[p->texnum].texture;
2752 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2755 if (p->blendmode == PBLEND_INVMOD)
2757 // inverse modulate blend - group these
2758 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2759 // iterate until we find a change in settings
2760 batchstart = surfacelistindex++;
2761 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2763 p = cl.particles + surfacelist[surfacelistindex];
2764 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2770 // additive or alpha blend - group these
2771 // (we can group these because we premultiplied the texture alpha)
2772 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2773 // iterate until we find a change in settings
2774 batchstart = surfacelistindex++;
2775 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2777 p = cl.particles + surfacelist[surfacelistindex];
2778 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2783 batchcount = surfacelistindex - batchstart;
2784 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2788 void R_DrawParticles (void)
2791 int drawparticles = r_drawparticles.integer;
2792 float minparticledist_start;
2794 float gravity, frametime, f, dist, oldorg[3];
2800 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2801 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2803 // LordHavoc: early out conditions
2804 if (!cl.num_particles)
2807 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2808 gravity = frametime * cl.movevars_gravity;
2809 update = frametime > 0;
2810 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2811 drawdist2 = drawdist2*drawdist2;
2813 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2817 if (cl.free_particle > i)
2818 cl.free_particle = i;
2824 if (p->delayedspawn > cl.time)
2827 p->size += p->sizeincrease * frametime;
2828 p->alpha -= p->alphafade * frametime;
2830 if (p->alpha <= 0 || p->die <= cl.time)
2833 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2835 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2837 if (p->typeindex == pt_blood)
2838 p->size += frametime * 8;
2840 p->vel[2] -= p->gravity * gravity;
2841 f = 1.0f - min(p->liquidfriction * frametime, 1);
2842 VectorScale(p->vel, f, p->vel);
2846 p->vel[2] -= p->gravity * gravity;
2849 f = 1.0f - min(p->airfriction * frametime, 1);
2850 VectorScale(p->vel, f, p->vel);
2854 VectorCopy(p->org, oldorg);
2855 VectorMA(p->org, frametime, p->vel, p->org);
2856 // if (p->bounce && cl.time >= p->delayedcollisions)
2857 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2859 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false, false);
2860 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2861 // or if the trace hit something flagged as NOIMPACT
2862 // then remove the particle
2863 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2865 VectorCopy(trace.endpos, p->org);
2866 // react if the particle hit something
2867 if (trace.fraction < 1)
2869 VectorCopy(trace.endpos, p->org);
2871 if (p->staintexnum >= 0)
2873 // blood - splash on solid
2874 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2877 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2878 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2879 if (cl_decals.integer)
2881 // create a decal for the blood splat
2882 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2883 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2888 if (p->typeindex == pt_blood)
2890 // blood - splash on solid
2891 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2893 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2895 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)));
2896 if (cl_decals.integer)
2898 // create a decal for the blood splat
2899 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2904 else if (p->bounce < 0)
2906 // bounce -1 means remove on impact
2911 // anything else - bounce off solid
2912 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2913 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2918 if (VectorLength2(p->vel) < 0.03)
2920 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2922 VectorClear(p->vel);
2926 if (p->typeindex != pt_static)
2928 switch (p->typeindex)
2930 case pt_entityparticle:
2931 // particle that removes itself after one rendered frame
2938 a = CL_PointSuperContents(p->org);
2939 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2943 a = CL_PointSuperContents(p->org);
2944 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2948 a = CL_PointSuperContents(p->org);
2949 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2953 if (cl.time > p->time2)
2956 p->time2 = cl.time + (rand() & 3) * 0.1;
2957 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2958 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2960 a = CL_PointSuperContents(p->org);
2961 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2969 else if (p->delayedspawn > cl.time)
2973 // don't render particles too close to the view (they chew fillrate)
2974 // also don't render particles behind the view (useless)
2975 // further checks to cull to the frustum would be too slow here
2976 switch(p->typeindex)
2979 // beams have no culling
2980 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2983 if(cl_particles_visculling.integer)
2984 if (!r_refdef.viewcache.world_novis)
2985 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2987 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2989 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2992 // anything else just has to be in front of the viewer and visible at this distance
2993 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2994 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3001 if (cl.free_particle > i)
3002 cl.free_particle = i;
3005 // reduce cl.num_particles if possible
3006 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3009 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3011 particle_t *oldparticles = cl.particles;
3012 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3013 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3014 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3015 Mem_Free(oldparticles);