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 relativeoriginoffset[3];
105 float velocityoffset[3];
106 float relativevelocityoffset[3];
107 float originjitter[3];
108 float velocityjitter[3];
109 float velocitymultiplier;
110 // an effect can also spawn a dlight
111 float lightradiusstart;
112 float lightradiusfade;
115 qboolean lightshadow;
117 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
122 float rotate[4]; // min/max base angle, min/max rotation over time
124 particleeffectinfo_t;
126 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
128 int numparticleeffectinfo;
129 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
131 static int particlepalette[256];
133 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
134 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
135 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
136 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
137 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
138 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
139 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
140 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
141 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
142 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
143 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
144 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
145 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
146 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
147 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
148 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
149 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
150 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
151 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
152 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
153 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
154 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
155 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
156 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
157 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
158 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
159 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
160 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
161 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
162 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
163 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
164 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
167 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
168 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
169 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
171 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
177 float s1, t1, s2, t2;
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
184 skinframe_t *decalskinframe;
186 // texture numbers in particle font
187 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
188 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
189 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
190 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
191 static const int tex_rainsplash = 32;
192 static const int tex_particle = 63;
193 static const int tex_bubble = 62;
194 static const int tex_raindrop = 61;
195 static const int tex_beam = 60;
197 particleeffectinfo_t baselineparticleeffectinfo =
199 0, //int effectnameindex; // which effect this belongs to
200 // PARTICLEEFFECT_* bits
202 // blood effects may spawn very few particles, so proper fraction-overflow
203 // handling is very important, this variable keeps track of the fraction
204 0.0, //double particleaccumulator;
205 // the math is: countabsolute + requestedcount * countmultiplier * quality
206 // absolute number of particles to spawn, often used for decals
207 // (unaffected by quality and requestedcount)
208 0.0f, //float countabsolute;
209 // multiplier for the number of particles CL_ParticleEffect was told to
210 // spawn, most effects do not really have a count and hence use 1, so
211 // this is often the actual count to spawn, not merely a multiplier
212 0.0f, //float countmultiplier;
213 // if > 0 this causes the particle to spawn in an evenly spaced line from
214 // originmins to originmaxs (causing them to describe a trail, not a box)
215 0.0f, //float trailspacing;
216 // type of particle to spawn (defines some aspects of behavior)
217 pt_alphastatic, //ptype_t particletype;
218 // blending mode used on this particle type
219 PBLEND_ALPHA, //pblend_t blendmode;
220 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
221 PARTICLE_BILLBOARD, //porientation_t orientation;
222 // range of colors to choose from in hex RRGGBB (like HTML color tags),
223 // randomly interpolated at spawn
224 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
225 // a random texture is chosen in this range (note the second value is one
226 // past the last choosable, so for example 8,16 chooses any from 8 up and
228 // if start and end of the range are the same, no randomization is done
229 {63, 63 /* tex_particle */}, //int tex[2];
230 // range of size values randomly chosen when spawning, plus size increase over time
231 {1, 1, 0.0f}, //float size[3];
232 // range of alpha values randomly chosen when spawning, plus alpha fade
233 {0.0f, 256.0f, 256.0f}, //float alpha[3];
234 // how long the particle should live (note it is also removed if alpha drops to 0)
235 {16777216.0f, 16777216.0f}, //float time[2];
236 // how much gravity affects this particle (negative makes it fly up!)
237 0.0f, //float gravity;
238 // how much bounce the particle has when it hits a surface
239 // if negative the particle is removed on impact
240 0.0f, //float bounce;
241 // if in air this friction is applied
242 // if negative the particle accelerates
243 0.0f, //float airfriction;
244 // if in liquid (water/slime/lava) this friction is applied
245 // if negative the particle accelerates
246 0.0f, //float liquidfriction;
247 // these offsets are added to the values given to particleeffect(), and
248 // then an ellipsoid-shaped jitter is added as defined by these
249 // (they are the 3 radii)
250 1.0f, //float stretchfactor;
251 // stretch velocity factor (used for sparks)
252 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
253 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
257 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
258 0.0f, //float velocitymultiplier;
259 // an effect can also spawn a dlight
260 0.0f, //float lightradiusstart;
261 0.0f, //float lightradiusfade;
262 16777216.0f, //float lighttime;
263 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
264 true, //qboolean lightshadow;
265 0, //int lightcubemapnum;
266 {(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!
267 {-1, -1}, //int staintex[2];
268 {1.0f, 1.0f}, //float stainalpha[2];
269 {2.0f, 2.0f}, //float stainsize[2];
271 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
274 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
275 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
276 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
277 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
278 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
279 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
280 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
281 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
282 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
283 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
284 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
285 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
286 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
287 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
288 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
289 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
290 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
291 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
292 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
293 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
294 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
295 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
296 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)"};
297 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
298 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
299 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
300 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
301 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
302 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)"};
303 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"};
304 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
305 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
306 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
309 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
314 particleeffectinfo_t *info = NULL;
315 const char *text = textstart;
317 for (linenumber = 1;;linenumber++)
320 for (arrayindex = 0;arrayindex < 16;arrayindex++)
321 argv[arrayindex][0] = 0;
324 if (!COM_ParseToken_Simple(&text, true, false, true))
326 if (!strcmp(com_token, "\n"))
330 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
336 #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;}
337 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
338 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
339 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
340 #define readfloat(var) checkparms(2);var = atof(argv[1])
341 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
342 if (!strcmp(argv[0], "effect"))
346 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
348 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
351 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
353 if (particleeffectname[effectnameindex][0])
355 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
360 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
364 // if we run out of names, abort
365 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
367 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
370 info = particleeffectinfo + numparticleeffectinfo++;
371 // copy entire info from baseline, then fix up the nameindex
372 *info = baselineparticleeffectinfo;
373 info->effectnameindex = effectnameindex;
375 else if (info == NULL)
377 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
380 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
381 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
382 else if (!strcmp(argv[0], "type"))
385 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
386 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
387 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
388 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
389 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
390 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
391 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
392 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
393 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
394 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
395 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
396 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
397 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
398 info->blendmode = particletype[info->particletype].blendmode;
399 info->orientation = particletype[info->particletype].orientation;
401 else if (!strcmp(argv[0], "blend"))
404 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
405 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
406 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
407 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
409 else if (!strcmp(argv[0], "orientation"))
412 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
413 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
414 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
415 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
416 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
418 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
419 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
420 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
421 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
422 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
423 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
424 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
425 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
426 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
427 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
428 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
429 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
430 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
431 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
432 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
433 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
434 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
435 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
436 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
437 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
438 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
439 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
440 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
441 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
442 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
443 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
444 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
445 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
446 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
447 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
448 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
449 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; }
450 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
452 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
461 int CL_ParticleEffectIndexForName(const char *name)
464 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
465 if (!strcmp(particleeffectname[i], name))
470 const char *CL_ParticleEffectNameForIndex(int i)
472 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
474 return particleeffectname[i];
477 // MUST match effectnameindex_t in client.h
478 static const char *standardeffectnames[EFFECT_TOTAL] =
502 "TE_TEI_BIGEXPLOSION",
518 static void CL_Particles_LoadEffectInfo(void)
522 unsigned char *filedata;
523 fs_offset_t filesize;
524 char filename[MAX_QPATH];
525 numparticleeffectinfo = 0;
526 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
527 memset(particleeffectname, 0, sizeof(particleeffectname));
528 for (i = 0;i < EFFECT_TOTAL;i++)
529 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
530 for (filepass = 0;;filepass++)
533 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
534 else if (filepass == 1)
536 if (!cl.worldbasename[0])
538 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
542 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
545 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
555 void CL_ReadPointFile_f (void);
556 void CL_Particles_Init (void)
558 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)");
559 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
561 Cvar_RegisterVariable (&cl_particles);
562 Cvar_RegisterVariable (&cl_particles_quality);
563 Cvar_RegisterVariable (&cl_particles_alpha);
564 Cvar_RegisterVariable (&cl_particles_size);
565 Cvar_RegisterVariable (&cl_particles_quake);
566 Cvar_RegisterVariable (&cl_particles_blood);
567 Cvar_RegisterVariable (&cl_particles_blood_alpha);
568 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
569 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
570 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
571 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
572 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
573 Cvar_RegisterVariable (&cl_particles_explosions_shell);
574 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
575 Cvar_RegisterVariable (&cl_particles_rain);
576 Cvar_RegisterVariable (&cl_particles_snow);
577 Cvar_RegisterVariable (&cl_particles_smoke);
578 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
579 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
580 Cvar_RegisterVariable (&cl_particles_sparks);
581 Cvar_RegisterVariable (&cl_particles_bubbles);
582 Cvar_RegisterVariable (&cl_particles_visculling);
583 Cvar_RegisterVariable (&cl_particles_collisions);
584 Cvar_RegisterVariable (&cl_decals);
585 Cvar_RegisterVariable (&cl_decals_visculling);
586 Cvar_RegisterVariable (&cl_decals_time);
587 Cvar_RegisterVariable (&cl_decals_fadetime);
588 Cvar_RegisterVariable (&cl_decals_newsystem);
589 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
590 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
591 Cvar_RegisterVariable (&cl_decals_models);
592 Cvar_RegisterVariable (&cl_decals_bias);
593 Cvar_RegisterVariable (&cl_decals_max);
596 void CL_Particles_Shutdown (void)
600 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
601 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
603 // list of all 26 parameters:
604 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
605 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
606 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
607 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
608 // palpha - opacity of particle as 0-255 (can be more than 255)
609 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
610 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
611 // pgravity - how much effect gravity has on the particle (0-1)
612 // 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
613 // px,py,pz - starting origin of particle
614 // pvx,pvy,pvz - starting velocity of particle
615 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
616 // blendmode - one of the PBLEND_ values
617 // orientation - one of the PARTICLE_ values
618 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
619 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
620 // stainalpha: opacity of the stain as factor for alpha
621 // stainsize: size of the stain as factor for palpha
622 // angle: base rotation of the particle geometry around its center normal
623 // spin: rotation speed of the particle geometry around its center normal
624 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])
629 if (!cl_particles.integer)
631 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
632 if (cl.free_particle >= cl.max_particles)
635 lifetime = palpha / min(1, palphafade);
636 part = &cl.particles[cl.free_particle++];
637 if (cl.num_particles < cl.free_particle)
638 cl.num_particles = cl.free_particle;
639 memset(part, 0, sizeof(*part));
640 VectorCopy(sortorigin, part->sortorigin);
641 part->typeindex = ptypeindex;
642 part->blendmode = blendmode;
643 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
645 particletexture_t *tex = &particletexture[ptex];
646 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
647 part->orientation = PARTICLE_VBEAM;
649 part->orientation = PARTICLE_HBEAM;
652 part->orientation = orientation;
653 l2 = (int)lhrandom(0.5, 256.5);
655 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
656 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
657 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
660 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
661 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
662 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
664 part->alpha = palpha;
665 part->alphafade = palphafade;
666 part->staintexnum = staintex;
667 if(staincolor1 >= 0 && staincolor2 >= 0)
669 l2 = (int)lhrandom(0.5, 256.5);
671 if(blendmode == PBLEND_INVMOD)
673 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
674 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
675 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
679 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
680 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
681 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
683 if(r > 0xFF) r = 0xFF;
684 if(g > 0xFF) g = 0xFF;
685 if(b > 0xFF) b = 0xFF;
689 r = part->color[0]; // -1 is shorthand for stain = particle color
693 part->staincolor[0] = r;
694 part->staincolor[1] = g;
695 part->staincolor[2] = b;
696 part->stainalpha = palpha * stainalpha;
697 part->stainsize = psize * stainsize;
700 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
702 part->color[0] *= tint[0];
703 part->color[1] *= tint[1];
704 part->color[2] *= tint[2];
706 part->alpha *= tint[3];
707 part->alphafade *= tint[3];
708 part->stainalpha *= tint[3];
712 part->sizeincrease = psizeincrease;
713 part->gravity = pgravity;
714 part->bounce = pbounce;
715 part->stretch = stretch;
717 part->org[0] = px + originjitter * v[0];
718 part->org[1] = py + originjitter * v[1];
719 part->org[2] = pz + originjitter * v[2];
720 part->vel[0] = pvx + velocityjitter * v[0];
721 part->vel[1] = pvy + velocityjitter * v[1];
722 part->vel[2] = pvz + velocityjitter * v[2];
724 part->airfriction = pairfriction;
725 part->liquidfriction = pliquidfriction;
726 part->die = cl.time + lifetime;
727 part->delayedspawn = cl.time;
728 // part->delayedcollisions = 0;
729 part->qualityreduction = pqualityreduction;
732 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
733 if (part->typeindex == pt_rain)
737 float lifetime = part->die - cl.time;
740 // turn raindrop into simple spark and create delayedspawn splash effect
741 part->typeindex = pt_spark;
743 VectorMA(part->org, lifetime, part->vel, endvec);
744 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
745 part->die = cl.time + lifetime * trace.fraction;
746 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);
749 part2->delayedspawn = part->die;
750 part2->die += part->die - cl.time;
751 for (i = rand() & 7;i < 10;i++)
753 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);
756 part2->delayedspawn = part->die;
757 part2->die += part->die - cl.time;
763 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
765 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
768 VectorMA(part->org, lifetime, part->vel, endvec);
769 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
770 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
777 static void CL_ImmediateBloodStain(particle_t *part)
782 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
783 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
785 VectorCopy(part->vel, v);
787 staintex = part->staintexnum;
788 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);
791 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
792 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
794 VectorCopy(part->vel, v);
796 staintex = tex_blooddecal[rand()&7];
797 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);
801 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
805 entity_render_t *ent = &cl.entities[hitent].render;
806 unsigned char color[3];
807 if (!cl_decals.integer)
809 if (!ent->allowdecals)
812 l2 = (int)lhrandom(0.5, 256.5);
814 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
815 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
816 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
818 if (cl_decals_newsystem.integer)
821 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);
823 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);
827 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
828 if (cl.free_decal >= cl.max_decals)
830 decal = &cl.decals[cl.free_decal++];
831 if (cl.num_decals < cl.free_decal)
832 cl.num_decals = cl.free_decal;
833 memset(decal, 0, sizeof(*decal));
834 decal->decalsequence = cl.decalsequence++;
835 decal->typeindex = pt_decal;
836 decal->texnum = texnum;
837 VectorMA(org, cl_decals_bias.value, normal, decal->org);
838 VectorCopy(normal, decal->normal);
840 decal->alpha = alpha;
841 decal->time2 = cl.time;
842 decal->color[0] = color[0];
843 decal->color[1] = color[1];
844 decal->color[2] = color[2];
847 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
848 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
849 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
851 decal->owner = hitent;
852 decal->clusterindex = -1000; // no vis culling unless we're sure
855 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
856 decal->ownermodel = cl.entities[decal->owner].render.model;
857 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
858 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
862 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
864 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
866 decal->clusterindex = leaf->clusterindex;
871 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
874 float bestfrac, bestorg[3], bestnormal[3];
876 int besthitent = 0, hitent;
879 for (i = 0;i < 32;i++)
882 VectorMA(org, maxdist, org2, org2);
883 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
884 // take the closest trace result that doesn't end up hitting a NOMARKS
885 // surface (sky for example)
886 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
888 bestfrac = trace.fraction;
890 VectorCopy(trace.endpos, bestorg);
891 VectorCopy(trace.plane.normal, bestnormal);
895 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
898 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
899 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
900 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)
903 matrix4x4_t tempmatrix;
905 VectorLerp(originmins, 0.5, originmaxs, center);
906 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
907 if (effectnameindex == EFFECT_SVC_PARTICLE)
909 if (cl_particles.integer)
911 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
913 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
914 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
915 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
918 count *= cl_particles_quality.value;
919 for (;count > 0;count--)
921 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
922 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);
927 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
928 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
929 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
930 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
931 else if (effectnameindex == EFFECT_TE_SPIKE)
933 if (cl_particles_bulletimpacts.integer)
935 if (cl_particles_quake.integer)
937 if (cl_particles_smoke.integer)
938 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
942 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
943 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
944 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);
948 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
949 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
951 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
953 if (cl_particles_bulletimpacts.integer)
955 if (cl_particles_quake.integer)
957 if (cl_particles_smoke.integer)
958 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
962 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
963 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
964 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);
968 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
969 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
970 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);
972 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
974 if (cl_particles_bulletimpacts.integer)
976 if (cl_particles_quake.integer)
978 if (cl_particles_smoke.integer)
979 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
983 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
984 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
985 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);
989 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
990 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
992 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
994 if (cl_particles_bulletimpacts.integer)
996 if (cl_particles_quake.integer)
998 if (cl_particles_smoke.integer)
999 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1003 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1004 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1005 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);
1009 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1010 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1011 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);
1013 else if (effectnameindex == EFFECT_TE_BLOOD)
1015 if (!cl_particles_blood.integer)
1017 if (cl_particles_quake.integer)
1018 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1021 static double bloodaccumulator = 0;
1022 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1023 //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);
1024 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1025 for (;bloodaccumulator > 0;bloodaccumulator--)
1027 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);
1028 if (immediatebloodstain && part)
1030 immediatebloodstain = false;
1031 CL_ImmediateBloodStain(part);
1036 else if (effectnameindex == EFFECT_TE_SPARK)
1037 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1038 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1040 // plasma scorch mark
1041 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1042 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1043 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1045 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1047 if (cl_particles_bulletimpacts.integer)
1049 if (cl_particles_quake.integer)
1050 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1053 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1054 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1055 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);
1059 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1060 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1062 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1064 if (cl_particles_bulletimpacts.integer)
1066 if (cl_particles_quake.integer)
1067 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1070 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1071 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1072 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);
1076 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1077 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1078 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);
1080 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1082 CL_ParticleExplosion(center);
1083 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);
1085 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1087 CL_ParticleExplosion(center);
1088 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);
1090 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1092 if (cl_particles_quake.integer)
1095 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1098 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);
1100 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);
1104 CL_ParticleExplosion(center);
1105 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);
1107 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1108 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);
1109 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1111 count *= cl_particles_quality.value;
1113 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);
1115 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1117 float i, j, inc, vel;
1120 inc = 8 / cl_particles_quality.value;
1121 for (i = -128;i < 128;i += inc)
1123 for (j = -128;j < 128;j += inc)
1125 dir[0] = j + lhrandom(0, inc);
1126 dir[1] = i + lhrandom(0, inc);
1128 org[0] = center[0] + dir[0];
1129 org[1] = center[1] + dir[1];
1130 org[2] = center[2] + lhrandom(0, 64);
1131 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1132 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);
1136 else if (effectnameindex == EFFECT_TE_TELEPORT)
1138 float i, j, k, inc, vel;
1141 if (cl_particles_quake.integer)
1142 inc = 4 / cl_particles_quality.value;
1144 inc = 8 / cl_particles_quality.value;
1145 for (i = -16;i < 16;i += inc)
1147 for (j = -16;j < 16;j += inc)
1149 for (k = -24;k < 32;k += inc)
1151 VectorSet(dir, i*8, j*8, k*8);
1152 VectorNormalize(dir);
1153 vel = lhrandom(50, 113);
1154 if (cl_particles_quake.integer)
1155 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);
1157 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);
1161 if (!cl_particles_quake.integer)
1162 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);
1163 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);
1165 else if (effectnameindex == EFFECT_TE_TEI_G3)
1166 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);
1167 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1169 if (cl_particles_smoke.integer)
1171 count *= 0.25f * cl_particles_quality.value;
1173 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);
1176 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1178 CL_ParticleExplosion(center);
1179 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);
1181 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1184 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1185 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1186 if (cl_particles_smoke.integer)
1187 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1188 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);
1189 if (cl_particles_sparks.integer)
1190 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1191 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);
1192 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);
1194 else if (effectnameindex == EFFECT_EF_FLAME)
1196 count *= 300 * cl_particles_quality.value;
1198 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);
1199 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);
1201 else if (effectnameindex == EFFECT_EF_STARDUST)
1203 count *= 200 * cl_particles_quality.value;
1205 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);
1206 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);
1208 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1212 int smoke, blood, bubbles, r, color;
1214 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1217 Vector4Set(light, 0, 0, 0, 0);
1219 if (effectnameindex == EFFECT_TR_ROCKET)
1220 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1221 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1223 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1224 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1226 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1228 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1229 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1233 matrix4x4_t tempmatrix;
1234 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1235 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);
1236 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1240 if (!spawnparticles)
1243 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1246 VectorSubtract(originmaxs, originmins, dir);
1247 len = VectorNormalizeLength(dir);
1250 dec = -ent->persistent.trail_time;
1251 ent->persistent.trail_time += len;
1252 if (ent->persistent.trail_time < 0.01f)
1255 // if we skip out, leave it reset
1256 ent->persistent.trail_time = 0.0f;
1261 // advance into this frame to reach the first puff location
1262 VectorMA(originmins, dec, dir, pos);
1265 smoke = cl_particles.integer && cl_particles_smoke.integer;
1266 blood = cl_particles.integer && cl_particles_blood.integer;
1267 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1268 qd = 1.0f / cl_particles_quality.value;
1275 if (effectnameindex == EFFECT_TR_BLOOD)
1277 if (cl_particles_quake.integer)
1279 color = particlepalette[67 + (rand()&3)];
1280 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);
1285 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);
1288 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1290 if (cl_particles_quake.integer)
1293 color = particlepalette[67 + (rand()&3)];
1294 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);
1299 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);
1305 if (effectnameindex == EFFECT_TR_ROCKET)
1307 if (cl_particles_quake.integer)
1310 color = particlepalette[ramp3[r]];
1311 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);
1315 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);
1316 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);
1319 else if (effectnameindex == EFFECT_TR_GRENADE)
1321 if (cl_particles_quake.integer)
1324 color = particlepalette[ramp3[r]];
1325 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);
1329 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);
1332 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1334 if (cl_particles_quake.integer)
1337 color = particlepalette[52 + (rand()&7)];
1338 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);
1339 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);
1341 else if (gamemode == GAME_GOODVSBAD2)
1344 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);
1348 color = particlepalette[20 + (rand()&7)];
1349 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);
1352 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1354 if (cl_particles_quake.integer)
1357 color = particlepalette[230 + (rand()&7)];
1358 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);
1359 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);
1363 color = particlepalette[226 + (rand()&7)];
1364 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);
1367 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1369 if (cl_particles_quake.integer)
1371 color = particlepalette[152 + (rand()&3)];
1372 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);
1374 else if (gamemode == GAME_GOODVSBAD2)
1377 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);
1379 else if (gamemode == GAME_PRYDON)
1382 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);
1385 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);
1387 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1390 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);
1392 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1395 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);
1397 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1398 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);
1402 if (effectnameindex == EFFECT_TR_ROCKET)
1403 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);
1404 else if (effectnameindex == EFFECT_TR_GRENADE)
1405 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);
1407 // advance to next time and position
1410 VectorMA (pos, dec, dir, pos);
1413 ent->persistent.trail_time = len;
1416 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1419 // this is also called on point effects with spawndlight = true and
1420 // spawnparticles = true
1421 // it is called CL_ParticleTrail because most code does not want to supply
1422 // these parameters, only trail handling does
1423 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])
1425 qboolean found = false;
1427 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1429 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1430 return; // no such effect
1432 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1434 int effectinfoindex;
1437 particleeffectinfo_t *info;
1449 qboolean underwater;
1450 qboolean immediatebloodstain;
1452 float avgtint[4], tint[4], tintlerp;
1453 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1454 VectorLerp(originmins, 0.5, originmaxs, center);
1455 supercontents = CL_PointSuperContents(center);
1456 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1457 VectorSubtract(originmaxs, originmins, traildir);
1458 traillen = VectorLength(traildir);
1459 VectorNormalize(traildir);
1462 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1466 Vector4Set(avgtint, 1, 1, 1, 1);
1468 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1470 if (info->effectnameindex == effectnameindex)
1473 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1475 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1478 // spawn a dlight if requested
1479 if (info->lightradiusstart > 0 && spawndlight)
1481 matrix4x4_t tempmatrix;
1482 if (info->trailspacing > 0)
1483 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1485 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1486 if (info->lighttime > 0 && info->lightradiusfade > 0)
1488 // light flash (explosion, etc)
1489 // called when effect starts
1490 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);
1492 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1495 // called by CL_LinkNetworkEntity
1496 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1497 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1498 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1499 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1500 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);
1501 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1505 if (!spawnparticles)
1510 if (info->tex[1] > info->tex[0])
1512 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1513 tex = min(tex, info->tex[1] - 1);
1515 if(info->staintex[0] < 0)
1516 staintex = info->staintex[0];
1519 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1520 staintex = min(staintex, info->staintex[1] - 1);
1522 if (info->particletype == pt_decal)
1524 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1525 AnglesFromVectors(angles, velocity, NULL, false);
1526 AngleVectors(angles, forward, right, up);
1527 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1529 CL_SpawnDecalParticleForPoint(trailpos, 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]);
1531 else if (info->orientation == PARTICLE_HBEAM)
1533 AnglesFromVectors(angles, traildir, NULL, false);
1534 AngleVectors(angles, forward, right, up);
1535 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1537 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] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[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);
1541 if (!cl_particles.integer)
1543 switch (info->particletype)
1545 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1546 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1547 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1548 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1549 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1550 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1553 VectorCopy(originmins, trailpos);
1554 if (info->trailspacing > 0)
1556 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1557 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1558 immediatebloodstain = false;
1560 AnglesFromVectors(angles, traildir, NULL, false);
1561 AngleVectors(angles, forward, right, up);
1562 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1563 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1567 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1569 immediatebloodstain =
1570 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1572 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1574 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1575 AnglesFromVectors(angles, velocity, NULL, false);
1576 AngleVectors(angles, forward, right, up);
1577 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1578 VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1580 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1581 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1583 if (info->tex[1] > info->tex[0])
1585 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1586 tex = min(tex, info->tex[1] - 1);
1590 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1591 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1592 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1596 tintlerp = lhrandom(0, 1);
1597 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1600 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] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[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);
1601 if (immediatebloodstain && part)
1603 immediatebloodstain = false;
1604 CL_ImmediateBloodStain(part);
1607 VectorMA(trailpos, trailstep, traildir, trailpos);
1614 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1617 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)
1619 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1627 void CL_EntityParticles (const entity_t *ent)
1630 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1631 static vec3_t avelocities[NUMVERTEXNORMALS];
1632 if (!cl_particles.integer) return;
1633 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1635 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1637 if (!avelocities[0][0])
1638 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1639 avelocities[0][i] = lhrandom(0, 2.55);
1641 for (i = 0;i < NUMVERTEXNORMALS;i++)
1643 yaw = cl.time * avelocities[i][0];
1644 pitch = cl.time * avelocities[i][1];
1645 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1646 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1647 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1648 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);
1653 void CL_ReadPointFile_f (void)
1655 vec3_t org, leakorg;
1657 char *pointfile = NULL, *pointfilepos, *t, tchar;
1658 char name[MAX_QPATH];
1663 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1664 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1667 Con_Printf("Could not open %s\n", name);
1671 Con_Printf("Reading %s...\n", name);
1672 VectorClear(leakorg);
1675 pointfilepos = pointfile;
1676 while (*pointfilepos)
1678 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1683 while (*t && *t != '\n' && *t != '\r')
1687 #if _MSC_VER >= 1400
1688 #define sscanf sscanf_s
1690 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1696 VectorCopy(org, leakorg);
1699 if (cl.num_particles < cl.max_particles - 3)
1702 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);
1705 Mem_Free(pointfile);
1706 VectorCopy(leakorg, org);
1707 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1709 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);
1710 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);
1711 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);
1716 CL_ParseParticleEffect
1718 Parse an effect out of the server message
1721 void CL_ParseParticleEffect (void)
1724 int i, count, msgcount, color;
1726 MSG_ReadVector(&cl_message, org, cls.protocol);
1727 for (i=0 ; i<3 ; i++)
1728 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1729 msgcount = MSG_ReadByte(&cl_message);
1730 color = MSG_ReadByte(&cl_message);
1732 if (msgcount == 255)
1737 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1742 CL_ParticleExplosion
1746 void CL_ParticleExplosion (const vec3_t org)
1752 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1753 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1755 if (cl_particles_quake.integer)
1757 for (i = 0;i < 1024;i++)
1763 color = particlepalette[ramp1[r]];
1764 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);
1768 color = particlepalette[ramp2[r]];
1769 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);
1775 i = CL_PointSuperContents(org);
1776 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1778 if (cl_particles.integer && cl_particles_bubbles.integer)
1779 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1780 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);
1784 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1786 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1793 VectorMA(org, 128, v2, v);
1794 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1796 while (k < 16 && trace.fraction < 0.1f);
1797 VectorSubtract(trace.endpos, org, v2);
1798 VectorScale(v2, 2.0f, v2);
1799 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);
1805 if (cl_particles_explosions_shell.integer)
1806 R_NewExplosion(org);
1811 CL_ParticleExplosion2
1815 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1818 if (!cl_particles.integer) return;
1820 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1822 k = particlepalette[colorStart + (i % colorLength)];
1823 if (cl_particles_quake.integer)
1824 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);
1826 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);
1830 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1833 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1834 if (cl_particles_sparks.integer)
1836 sparkcount *= cl_particles_quality.value;
1837 while(sparkcount-- > 0)
1838 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);
1842 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1845 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1846 if (cl_particles_smoke.integer)
1848 smokecount *= cl_particles_quality.value;
1849 while(smokecount-- > 0)
1850 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);
1854 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)
1858 if (!cl_particles.integer) return;
1859 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1861 count = (int)(count * cl_particles_quality.value);
1864 k = particlepalette[colorbase + (rand()&3)];
1865 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);
1869 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1872 float minz, maxz, lifetime = 30;
1874 if (!cl_particles.integer) return;
1875 if (dir[2] < 0) // falling
1877 minz = maxs[2] + dir[2] * 0.1;
1880 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1885 maxz = maxs[2] + dir[2] * 0.1;
1887 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1890 count = (int)(count * cl_particles_quality.value);
1895 if (!cl_particles_rain.integer) break;
1896 count *= 4; // ick, this should be in the mod or maps?
1900 k = particlepalette[colorbase + (rand()&3)];
1901 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1902 if (gamemode == GAME_GOODVSBAD2)
1903 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);
1905 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);
1909 if (!cl_particles_snow.integer) break;
1912 k = particlepalette[colorbase + (rand()&3)];
1913 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1914 if (gamemode == GAME_GOODVSBAD2)
1915 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);
1917 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);
1921 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1925 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1926 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1927 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1928 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1929 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1930 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1932 #define PARTICLETEXTURESIZE 64
1933 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1935 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1939 dz = 1 - (dx*dx+dy*dy);
1940 if (dz > 0) // it does hit the sphere
1944 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1945 VectorNormalize(normal);
1946 dot = DotProduct(normal, light);
1947 if (dot > 0.5) // interior reflection
1948 f += ((dot * 2) - 1);
1949 else if (dot < -0.5) // exterior reflection
1950 f += ((dot * -2) - 1);
1952 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1953 VectorNormalize(normal);
1954 dot = DotProduct(normal, light);
1955 if (dot > 0.5) // interior reflection
1956 f += ((dot * 2) - 1);
1957 else if (dot < -0.5) // exterior reflection
1958 f += ((dot * -2) - 1);
1960 f += 16; // just to give it a haze so you can see the outline
1961 f = bound(0, f, 255);
1962 return (unsigned char) f;
1968 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1969 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1971 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1972 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1973 *width = particlefontcellwidth;
1974 *height = particlefontcellheight;
1977 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1979 int basex, basey, w, h, y;
1980 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1981 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1982 Sys_Error("invalid particle texture size for autogenerating");
1983 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1984 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1987 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1990 float cx, cy, dx, dy, f, iradius;
1992 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1993 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1994 iradius = 1.0f / radius;
1995 alpha *= (1.0f / 255.0f);
1996 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1998 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2002 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2007 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2008 d[0] += (int)(f * (blue - d[0]));
2009 d[1] += (int)(f * (green - d[1]));
2010 d[2] += (int)(f * (red - d[2]));
2017 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2020 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2022 data[0] = bound(minb, data[0], maxb);
2023 data[1] = bound(ming, data[1], maxg);
2024 data[2] = bound(minr, data[2], maxr);
2029 static void particletextureinvert(unsigned char *data)
2032 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2034 data[0] = 255 - data[0];
2035 data[1] = 255 - data[1];
2036 data[2] = 255 - data[2];
2040 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2041 static void R_InitBloodTextures (unsigned char *particletexturedata)
2044 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2045 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2048 for (i = 0;i < 8;i++)
2050 memset(data, 255, datasize);
2051 for (k = 0;k < 24;k++)
2052 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2053 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2054 particletextureinvert(data);
2055 setuptex(tex_bloodparticle[i], data, particletexturedata);
2059 for (i = 0;i < 8;i++)
2061 memset(data, 255, datasize);
2063 for (j = 1;j < 10;j++)
2064 for (k = min(j, m - 1);k < m;k++)
2065 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2066 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2067 particletextureinvert(data);
2068 setuptex(tex_blooddecal[i], data, particletexturedata);
2074 //uncomment this to make engine save out particle font to a tga file when run
2075 //#define DUMPPARTICLEFONT
2077 static void R_InitParticleTexture (void)
2079 int x, y, d, i, k, m;
2080 int basex, basey, w, h;
2081 float dx, dy, f, s1, t1, s2, t2;
2084 fs_offset_t filesize;
2085 char texturename[MAX_QPATH];
2088 // a note: decals need to modulate (multiply) the background color to
2089 // properly darken it (stain), and they need to be able to alpha fade,
2090 // this is a very difficult challenge because it means fading to white
2091 // (no change to background) rather than black (darkening everything
2092 // behind the whole decal polygon), and to accomplish this the texture is
2093 // inverted (dark red blood on white background becomes brilliant cyan
2094 // and white on black background) so we can alpha fade it to black, then
2095 // we invert it again during the blendfunc to make it work...
2097 #ifndef DUMPPARTICLEFONT
2098 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2101 particlefonttexture = decalskinframe->base;
2102 // TODO maybe allow custom grid size?
2103 particlefontwidth = image_width;
2104 particlefontheight = image_height;
2105 particlefontcellwidth = image_width / 8;
2106 particlefontcellheight = image_height / 8;
2107 particlefontcols = 8;
2108 particlefontrows = 8;
2113 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2114 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2115 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2116 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2117 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2119 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2120 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2121 particlefontcols = 8;
2122 particlefontrows = 8;
2124 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2127 for (i = 0;i < 8;i++)
2129 memset(data, 255, datasize);
2132 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2133 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2135 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2137 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2138 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2140 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2141 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2143 d = (int)(d * (1-(dx*dx+dy*dy)));
2144 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2145 d = bound(0, d, 255);
2146 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2153 setuptex(tex_smoke[i], data, particletexturedata);
2157 memset(data, 255, datasize);
2158 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2160 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2161 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2163 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2164 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2165 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2168 setuptex(tex_rainsplash, data, particletexturedata);
2171 memset(data, 255, datasize);
2172 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2174 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2175 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2177 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2178 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2179 d = bound(0, d, 255);
2180 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2183 setuptex(tex_particle, data, particletexturedata);
2186 memset(data, 255, datasize);
2187 light[0] = 1;light[1] = 1;light[2] = 1;
2188 VectorNormalize(light);
2189 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2191 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2192 // stretch upper half of bubble by +50% and shrink lower half by -50%
2193 // (this gives an elongated teardrop shape)
2195 dy = (dy - 0.5f) * 2.0f;
2197 dy = (dy - 0.5f) / 1.5f;
2198 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2200 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2201 // shrink bubble width to half
2203 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2206 setuptex(tex_raindrop, data, particletexturedata);
2209 memset(data, 255, datasize);
2210 light[0] = 1;light[1] = 1;light[2] = 1;
2211 VectorNormalize(light);
2212 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2214 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2215 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2217 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2218 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2221 setuptex(tex_bubble, data, particletexturedata);
2223 // Blood particles and blood decals
2224 R_InitBloodTextures (particletexturedata);
2227 for (i = 0;i < 8;i++)
2229 memset(data, 255, datasize);
2230 for (k = 0;k < 12;k++)
2231 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2232 for (k = 0;k < 3;k++)
2233 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2234 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2235 particletextureinvert(data);
2236 setuptex(tex_bulletdecal[i], data, particletexturedata);
2239 #ifdef DUMPPARTICLEFONT
2240 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2243 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2244 particlefonttexture = decalskinframe->base;
2246 Mem_Free(particletexturedata);
2251 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2253 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2254 particletexture[i].texture = particlefonttexture;
2255 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2256 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2257 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2258 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2261 #ifndef DUMPPARTICLEFONT
2262 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2263 if (!particletexture[tex_beam].texture)
2266 unsigned char noise3[64][64], data2[64][16][4];
2268 fractalnoise(&noise3[0][0], 64, 4);
2270 for (y = 0;y < 64;y++)
2272 dy = (y - 0.5f*64) / (64*0.5f-1);
2273 for (x = 0;x < 16;x++)
2275 dx = (x - 0.5f*16) / (16*0.5f-2);
2276 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2277 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2278 data2[y][x][3] = 255;
2282 #ifdef DUMPPARTICLEFONT
2283 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2285 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2287 particletexture[tex_beam].s1 = 0;
2288 particletexture[tex_beam].t1 = 0;
2289 particletexture[tex_beam].s2 = 1;
2290 particletexture[tex_beam].t2 = 1;
2292 // now load an texcoord/texture override file
2293 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2300 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2302 if(!strcmp(com_token, "\n"))
2303 continue; // empty line
2304 i = atoi(com_token);
2312 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2314 strlcpy(texturename, com_token, sizeof(texturename));
2315 s1 = atof(com_token);
2316 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2319 t1 = atof(com_token);
2320 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2322 s2 = atof(com_token);
2323 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2325 t2 = atof(com_token);
2326 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2327 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2328 strlcpy(texturename, com_token, sizeof(texturename));
2335 if (!texturename[0])
2337 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2340 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2342 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2345 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2348 // R_SkinFrame_LoadExternal already complained
2351 particletexture[i].texture = sf->base;
2352 particletexture[i].s1 = s1;
2353 particletexture[i].t1 = t1;
2354 particletexture[i].s2 = s2;
2355 particletexture[i].t2 = t2;
2361 static void r_part_start(void)
2364 // generate particlepalette for convenience from the main one
2365 for (i = 0;i < 256;i++)
2366 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2367 particletexturepool = R_AllocTexturePool();
2368 R_InitParticleTexture ();
2369 CL_Particles_LoadEffectInfo();
2372 static void r_part_shutdown(void)
2374 R_FreeTexturePool(&particletexturepool);
2377 static void r_part_newmap(void)
2380 R_SkinFrame_MarkUsed(decalskinframe);
2381 CL_Particles_LoadEffectInfo();
2384 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2385 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2387 void R_Particles_Init (void)
2390 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2392 particle_elements[i*6+0] = i*4+0;
2393 particle_elements[i*6+1] = i*4+1;
2394 particle_elements[i*6+2] = i*4+2;
2395 particle_elements[i*6+3] = i*4+0;
2396 particle_elements[i*6+4] = i*4+2;
2397 particle_elements[i*6+5] = i*4+3;
2400 Cvar_RegisterVariable(&r_drawparticles);
2401 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2402 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2403 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2404 Cvar_RegisterVariable(&r_drawdecals);
2405 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2406 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2409 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2411 int surfacelistindex;
2413 float *v3f, *t2f, *c4f;
2414 particletexture_t *tex;
2415 float right[3], up[3], size, ca;
2416 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2418 RSurf_ActiveWorldEntity();
2420 r_refdef.stats.drawndecals += numsurfaces;
2421 // R_Mesh_ResetTextureState();
2422 GL_DepthMask(false);
2423 GL_DepthRange(0, 1);
2424 GL_PolygonOffset(0, 0);
2426 GL_CullFace(GL_NONE);
2428 // generate all the vertices at once
2429 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2431 d = cl.decals + surfacelist[surfacelistindex];
2434 c4f = particle_color4f + 16*surfacelistindex;
2435 ca = d->alpha * alphascale;
2436 // ensure alpha multiplier saturates properly
2437 if (ca > 1.0f / 256.0f)
2439 if (r_refdef.fogenabled)
2440 ca *= RSurf_FogVertex(d->org);
2441 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2442 Vector4Copy(c4f, c4f + 4);
2443 Vector4Copy(c4f, c4f + 8);
2444 Vector4Copy(c4f, c4f + 12);
2446 // calculate vertex positions
2447 size = d->size * cl_particles_size.value;
2448 VectorVectors(d->normal, right, up);
2449 VectorScale(right, size, right);
2450 VectorScale(up, size, up);
2451 v3f = particle_vertex3f + 12*surfacelistindex;
2452 v3f[ 0] = d->org[0] - right[0] - up[0];
2453 v3f[ 1] = d->org[1] - right[1] - up[1];
2454 v3f[ 2] = d->org[2] - right[2] - up[2];
2455 v3f[ 3] = d->org[0] - right[0] + up[0];
2456 v3f[ 4] = d->org[1] - right[1] + up[1];
2457 v3f[ 5] = d->org[2] - right[2] + up[2];
2458 v3f[ 6] = d->org[0] + right[0] + up[0];
2459 v3f[ 7] = d->org[1] + right[1] + up[1];
2460 v3f[ 8] = d->org[2] + right[2] + up[2];
2461 v3f[ 9] = d->org[0] + right[0] - up[0];
2462 v3f[10] = d->org[1] + right[1] - up[1];
2463 v3f[11] = d->org[2] + right[2] - up[2];
2465 // calculate texcoords
2466 tex = &particletexture[d->texnum];
2467 t2f = particle_texcoord2f + 8*surfacelistindex;
2468 t2f[0] = tex->s1;t2f[1] = tex->t2;
2469 t2f[2] = tex->s1;t2f[3] = tex->t1;
2470 t2f[4] = tex->s2;t2f[5] = tex->t1;
2471 t2f[6] = tex->s2;t2f[7] = tex->t2;
2474 // now render the decals all at once
2475 // (this assumes they all use one particle font texture!)
2476 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2477 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2478 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2479 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2482 void R_DrawDecals (void)
2485 int drawdecals = r_drawdecals.integer;
2490 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2492 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2493 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2495 // LordHavoc: early out conditions
2499 decalfade = frametime * 256 / cl_decals_fadetime.value;
2500 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2501 drawdist2 = drawdist2*drawdist2;
2503 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2505 if (!decal->typeindex)
2508 if (killsequence - decal->decalsequence > 0)
2511 if (cl.time > decal->time2 + cl_decals_time.value)
2513 decal->alpha -= decalfade;
2514 if (decal->alpha <= 0)
2520 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2522 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2523 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2529 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2535 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))
2536 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2539 decal->typeindex = 0;
2540 if (cl.free_decal > i)
2544 // reduce cl.num_decals if possible
2545 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2548 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2550 decal_t *olddecals = cl.decals;
2551 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2552 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2553 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2554 Mem_Free(olddecals);
2557 r_refdef.stats.totaldecals = cl.num_decals;
2560 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2562 int surfacelistindex;
2563 int batchstart, batchcount;
2564 const particle_t *p;
2566 rtexture_t *texture;
2567 float *v3f, *t2f, *c4f;
2568 particletexture_t *tex;
2569 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2570 // float ambient[3], diffuse[3], diffusenormal[3];
2571 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2572 vec4_t colormultiplier;
2573 float minparticledist_start, minparticledist_end;
2576 RSurf_ActiveWorldEntity();
2578 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));
2580 r_refdef.stats.particles += numsurfaces;
2581 // R_Mesh_ResetTextureState();
2582 GL_DepthMask(false);
2583 GL_DepthRange(0, 1);
2584 GL_PolygonOffset(0, 0);
2586 GL_CullFace(GL_NONE);
2588 spintime = r_refdef.scene.time;
2590 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2591 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2592 dofade = (minparticledist_start < minparticledist_end);
2594 // first generate all the vertices at once
2595 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2597 p = cl.particles + surfacelist[surfacelistindex];
2599 blendmode = (pblend_t)p->blendmode;
2601 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2602 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2603 alpha = palpha * colormultiplier[3];
2604 // ensure alpha multiplier saturates properly
2610 case PBLEND_INVALID:
2612 // additive and modulate can just fade out in fog (this is correct)
2613 if (r_refdef.fogenabled)
2614 alpha *= RSurf_FogVertex(p->org);
2615 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2616 alpha *= 1.0f / 256.0f;
2617 c4f[0] = p->color[0] * alpha;
2618 c4f[1] = p->color[1] * alpha;
2619 c4f[2] = p->color[2] * alpha;
2623 // additive and modulate can just fade out in fog (this is correct)
2624 if (r_refdef.fogenabled)
2625 alpha *= RSurf_FogVertex(p->org);
2626 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2627 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2628 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2629 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2633 c4f[0] = p->color[0] * colormultiplier[0];
2634 c4f[1] = p->color[1] * colormultiplier[1];
2635 c4f[2] = p->color[2] * colormultiplier[2];
2637 // note: lighting is not cheap!
2638 if (particletype[p->typeindex].lighting)
2639 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2640 // mix in the fog color
2641 if (r_refdef.fogenabled)
2643 fog = RSurf_FogVertex(p->org);
2645 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2646 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2647 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2649 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2650 VectorScale(c4f, alpha, c4f);
2653 // copy the color into the other three vertices
2654 Vector4Copy(c4f, c4f + 4);
2655 Vector4Copy(c4f, c4f + 8);
2656 Vector4Copy(c4f, c4f + 12);
2658 size = p->size * cl_particles_size.value;
2659 tex = &particletexture[p->texnum];
2660 switch(p->orientation)
2662 // case PARTICLE_INVALID:
2663 case PARTICLE_BILLBOARD:
2664 if (p->angle + p->spin)
2666 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2667 spinsin = sin(spinrad) * size;
2668 spincos = cos(spinrad) * size;
2669 spinm1 = -p->stretch * spincos;
2672 spinm4 = -p->stretch * spincos;
2673 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2674 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2678 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2679 VectorScale(r_refdef.view.up, size, up);
2682 v3f[ 0] = p->org[0] - right[0] - up[0];
2683 v3f[ 1] = p->org[1] - right[1] - up[1];
2684 v3f[ 2] = p->org[2] - right[2] - up[2];
2685 v3f[ 3] = p->org[0] - right[0] + up[0];
2686 v3f[ 4] = p->org[1] - right[1] + up[1];
2687 v3f[ 5] = p->org[2] - right[2] + up[2];
2688 v3f[ 6] = p->org[0] + right[0] + up[0];
2689 v3f[ 7] = p->org[1] + right[1] + up[1];
2690 v3f[ 8] = p->org[2] + right[2] + up[2];
2691 v3f[ 9] = p->org[0] + right[0] - up[0];
2692 v3f[10] = p->org[1] + right[1] - up[1];
2693 v3f[11] = p->org[2] + right[2] - up[2];
2694 t2f[0] = tex->s1;t2f[1] = tex->t2;
2695 t2f[2] = tex->s1;t2f[3] = tex->t1;
2696 t2f[4] = tex->s2;t2f[5] = tex->t1;
2697 t2f[6] = tex->s2;t2f[7] = tex->t2;
2699 case PARTICLE_ORIENTED_DOUBLESIDED:
2700 VectorVectors(p->vel, baseright, baseup);
2701 if (p->angle + p->spin)
2703 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2704 spinsin = sin(spinrad) * size;
2705 spincos = cos(spinrad) * size;
2706 spinm1 = p->stretch * spincos;
2709 spinm4 = p->stretch * spincos;
2710 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2711 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2715 VectorScale(baseright, size * p->stretch, right);
2716 VectorScale(baseup, size, up);
2718 v3f[ 0] = p->org[0] - right[0] - up[0];
2719 v3f[ 1] = p->org[1] - right[1] - up[1];
2720 v3f[ 2] = p->org[2] - right[2] - up[2];
2721 v3f[ 3] = p->org[0] - right[0] + up[0];
2722 v3f[ 4] = p->org[1] - right[1] + up[1];
2723 v3f[ 5] = p->org[2] - right[2] + up[2];
2724 v3f[ 6] = p->org[0] + right[0] + up[0];
2725 v3f[ 7] = p->org[1] + right[1] + up[1];
2726 v3f[ 8] = p->org[2] + right[2] + up[2];
2727 v3f[ 9] = p->org[0] + right[0] - up[0];
2728 v3f[10] = p->org[1] + right[1] - up[1];
2729 v3f[11] = p->org[2] + right[2] - up[2];
2730 t2f[0] = tex->s1;t2f[1] = tex->t2;
2731 t2f[2] = tex->s1;t2f[3] = tex->t1;
2732 t2f[4] = tex->s2;t2f[5] = tex->t1;
2733 t2f[6] = tex->s2;t2f[7] = tex->t2;
2735 case PARTICLE_SPARK:
2736 len = VectorLength(p->vel);
2737 VectorNormalize2(p->vel, up);
2738 lenfactor = p->stretch * 0.04 * len;
2739 if(lenfactor < size * 0.5)
2740 lenfactor = size * 0.5;
2741 VectorMA(p->org, -lenfactor, up, v);
2742 VectorMA(p->org, lenfactor, up, up2);
2743 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2744 t2f[0] = tex->s1;t2f[1] = tex->t2;
2745 t2f[2] = tex->s1;t2f[3] = tex->t1;
2746 t2f[4] = tex->s2;t2f[5] = tex->t1;
2747 t2f[6] = tex->s2;t2f[7] = tex->t2;
2749 case PARTICLE_VBEAM:
2750 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2751 VectorSubtract(p->vel, p->org, up);
2752 VectorNormalize(up);
2753 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2754 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2755 t2f[0] = tex->s2;t2f[1] = v[0];
2756 t2f[2] = tex->s1;t2f[3] = v[0];
2757 t2f[4] = tex->s1;t2f[5] = v[1];
2758 t2f[6] = tex->s2;t2f[7] = v[1];
2760 case PARTICLE_HBEAM:
2761 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2762 VectorSubtract(p->vel, p->org, up);
2763 VectorNormalize(up);
2764 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2765 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2766 t2f[0] = v[0];t2f[1] = tex->t1;
2767 t2f[2] = v[0];t2f[3] = tex->t2;
2768 t2f[4] = v[1];t2f[5] = tex->t2;
2769 t2f[6] = v[1];t2f[7] = tex->t1;
2774 // now render batches of particles based on blendmode and texture
2775 blendmode = PBLEND_INVALID;
2779 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2780 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2782 p = cl.particles + surfacelist[surfacelistindex];
2784 if (texture != particletexture[p->texnum].texture)
2786 texture = particletexture[p->texnum].texture;
2787 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2790 if (p->blendmode == PBLEND_INVMOD)
2792 // inverse modulate blend - group these
2793 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2794 // iterate until we find a change in settings
2795 batchstart = surfacelistindex++;
2796 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2798 p = cl.particles + surfacelist[surfacelistindex];
2799 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2805 // additive or alpha blend - group these
2806 // (we can group these because we premultiplied the texture alpha)
2807 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2808 // iterate until we find a change in settings
2809 batchstart = surfacelistindex++;
2810 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2812 p = cl.particles + surfacelist[surfacelistindex];
2813 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2818 batchcount = surfacelistindex - batchstart;
2819 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2823 void R_DrawParticles (void)
2826 int drawparticles = r_drawparticles.integer;
2827 float minparticledist_start;
2829 float gravity, frametime, f, dist, oldorg[3];
2835 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2836 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2838 // LordHavoc: early out conditions
2839 if (!cl.num_particles)
2842 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2843 gravity = frametime * cl.movevars_gravity;
2844 update = frametime > 0;
2845 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2846 drawdist2 = drawdist2*drawdist2;
2848 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2852 if (cl.free_particle > i)
2853 cl.free_particle = i;
2859 if (p->delayedspawn > cl.time)
2862 p->size += p->sizeincrease * frametime;
2863 p->alpha -= p->alphafade * frametime;
2865 if (p->alpha <= 0 || p->die <= cl.time)
2868 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2870 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2872 if (p->typeindex == pt_blood)
2873 p->size += frametime * 8;
2875 p->vel[2] -= p->gravity * gravity;
2876 f = 1.0f - min(p->liquidfriction * frametime, 1);
2877 VectorScale(p->vel, f, p->vel);
2881 p->vel[2] -= p->gravity * gravity;
2884 f = 1.0f - min(p->airfriction * frametime, 1);
2885 VectorScale(p->vel, f, p->vel);
2889 VectorCopy(p->org, oldorg);
2890 VectorMA(p->org, frametime, p->vel, p->org);
2891 // if (p->bounce && cl.time >= p->delayedcollisions)
2892 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2894 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);
2895 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2896 // or if the trace hit something flagged as NOIMPACT
2897 // then remove the particle
2898 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2900 VectorCopy(trace.endpos, p->org);
2901 // react if the particle hit something
2902 if (trace.fraction < 1)
2904 VectorCopy(trace.endpos, p->org);
2906 if (p->staintexnum >= 0)
2908 // blood - splash on solid
2909 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2912 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2913 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2914 if (cl_decals.integer)
2916 // create a decal for the blood splat
2917 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2918 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2923 if (p->typeindex == pt_blood)
2925 // blood - splash on solid
2926 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2928 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2930 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)));
2931 if (cl_decals.integer)
2933 // create a decal for the blood splat
2934 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);
2939 else if (p->bounce < 0)
2941 // bounce -1 means remove on impact
2946 // anything else - bounce off solid
2947 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2948 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2953 if (VectorLength2(p->vel) < 0.03)
2955 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2957 VectorClear(p->vel);
2961 if (p->typeindex != pt_static)
2963 switch (p->typeindex)
2965 case pt_entityparticle:
2966 // particle that removes itself after one rendered frame
2973 a = CL_PointSuperContents(p->org);
2974 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2978 a = CL_PointSuperContents(p->org);
2979 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2983 a = CL_PointSuperContents(p->org);
2984 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2988 if (cl.time > p->time2)
2991 p->time2 = cl.time + (rand() & 3) * 0.1;
2992 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2993 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2995 a = CL_PointSuperContents(p->org);
2996 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3004 else if (p->delayedspawn > cl.time)
3008 // don't render particles too close to the view (they chew fillrate)
3009 // also don't render particles behind the view (useless)
3010 // further checks to cull to the frustum would be too slow here
3011 switch(p->typeindex)
3014 // beams have no culling
3015 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3018 if(cl_particles_visculling.integer)
3019 if (!r_refdef.viewcache.world_novis)
3020 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3022 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3024 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3027 // anything else just has to be in front of the viewer and visible at this distance
3028 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3029 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3036 if (cl.free_particle > i)
3037 cl.free_particle = i;
3040 // reduce cl.num_particles if possible
3041 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3044 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3046 particle_t *oldparticles = cl.particles;
3047 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3048 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3049 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3050 Mem_Free(oldparticles);