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 float lightcorona[2];
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
123 float rotate[4]; // min/max base angle, min/max rotation over time
125 particleeffectinfo_t;
127 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
129 int numparticleeffectinfo;
130 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
132 static int particlepalette[256];
134 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
135 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
136 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
137 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
138 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
139 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
140 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
141 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
142 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
143 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
144 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
145 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
146 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
147 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
148 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
149 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
150 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
151 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
152 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
153 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
154 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
155 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
156 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
157 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
158 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
159 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
160 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
161 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
162 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
163 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
164 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
165 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
168 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
169 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
170 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
172 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
174 // particletexture_t is a rectangle in the particlefonttexture
175 typedef struct particletexture_s
178 float s1, t1, s2, t2;
182 static rtexturepool_t *particletexturepool;
183 static rtexture_t *particlefonttexture;
184 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 skinframe_t *decalskinframe;
187 // texture numbers in particle font
188 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
189 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
190 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
191 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
192 static const int tex_rainsplash = 32;
193 static const int tex_particle = 63;
194 static const int tex_bubble = 62;
195 static const int tex_raindrop = 61;
196 static const int tex_beam = 60;
198 particleeffectinfo_t baselineparticleeffectinfo =
200 0, //int effectnameindex; // which effect this belongs to
201 // PARTICLEEFFECT_* bits
203 // blood effects may spawn very few particles, so proper fraction-overflow
204 // handling is very important, this variable keeps track of the fraction
205 0.0, //double particleaccumulator;
206 // the math is: countabsolute + requestedcount * countmultiplier * quality
207 // absolute number of particles to spawn, often used for decals
208 // (unaffected by quality and requestedcount)
209 0.0f, //float countabsolute;
210 // multiplier for the number of particles CL_ParticleEffect was told to
211 // spawn, most effects do not really have a count and hence use 1, so
212 // this is often the actual count to spawn, not merely a multiplier
213 0.0f, //float countmultiplier;
214 // if > 0 this causes the particle to spawn in an evenly spaced line from
215 // originmins to originmaxs (causing them to describe a trail, not a box)
216 0.0f, //float trailspacing;
217 // type of particle to spawn (defines some aspects of behavior)
218 pt_alphastatic, //ptype_t particletype;
219 // blending mode used on this particle type
220 PBLEND_ALPHA, //pblend_t blendmode;
221 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
222 PARTICLE_BILLBOARD, //porientation_t orientation;
223 // range of colors to choose from in hex RRGGBB (like HTML color tags),
224 // randomly interpolated at spawn
225 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
226 // a random texture is chosen in this range (note the second value is one
227 // past the last choosable, so for example 8,16 chooses any from 8 up and
229 // if start and end of the range are the same, no randomization is done
230 {63, 63 /* tex_particle */}, //int tex[2];
231 // range of size values randomly chosen when spawning, plus size increase over time
232 {1, 1, 0.0f}, //float size[3];
233 // range of alpha values randomly chosen when spawning, plus alpha fade
234 {0.0f, 256.0f, 256.0f}, //float alpha[3];
235 // how long the particle should live (note it is also removed if alpha drops to 0)
236 {16777216.0f, 16777216.0f}, //float time[2];
237 // how much gravity affects this particle (negative makes it fly up!)
238 0.0f, //float gravity;
239 // how much bounce the particle has when it hits a surface
240 // if negative the particle is removed on impact
241 0.0f, //float bounce;
242 // if in air this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float airfriction;
245 // if in liquid (water/slime/lava) this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float liquidfriction;
248 // these offsets are added to the values given to particleeffect(), and
249 // then an ellipsoid-shaped jitter is added as defined by these
250 // (they are the 3 radii)
251 1.0f, //float stretchfactor;
252 // stretch velocity factor (used for sparks)
253 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
259 0.0f, //float velocitymultiplier;
260 // an effect can also spawn a dlight
261 0.0f, //float lightradiusstart;
262 0.0f, //float lightradiusfade;
263 16777216.0f, //float lighttime;
264 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
265 true, //qboolean lightshadow;
266 0, //int lightcubemapnum;
267 {1.0f, 0.25f}, //float lightcorona[2];
268 {(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!
269 {-1, -1}, //int staintex[2];
270 {1.0f, 1.0f}, //float stainalpha[2];
271 {2.0f, 2.0f}, //float stainsize[2];
273 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
276 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
277 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
278 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
279 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
280 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
281 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
282 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
283 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
284 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
285 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
286 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
287 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
288 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
289 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
290 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
291 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
292 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
293 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
294 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
295 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
296 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
297 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
298 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)"};
299 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
300 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
301 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
302 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
303 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
304 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)"};
305 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"};
306 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
307 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
308 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
311 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
316 particleeffectinfo_t *info = NULL;
317 const char *text = textstart;
319 for (linenumber = 1;;linenumber++)
322 for (arrayindex = 0;arrayindex < 16;arrayindex++)
323 argv[arrayindex][0] = 0;
326 if (!COM_ParseToken_Simple(&text, true, false, true))
328 if (!strcmp(com_token, "\n"))
332 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
338 #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;}
339 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
340 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
341 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
342 #define readfloat(var) checkparms(2);var = atof(argv[1])
343 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
344 if (!strcmp(argv[0], "effect"))
348 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
350 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
353 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
355 if (particleeffectname[effectnameindex][0])
357 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
362 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
366 // if we run out of names, abort
367 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
369 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
372 info = particleeffectinfo + numparticleeffectinfo++;
373 // copy entire info from baseline, then fix up the nameindex
374 *info = baselineparticleeffectinfo;
375 info->effectnameindex = effectnameindex;
377 else if (info == NULL)
379 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
382 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
383 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
384 else if (!strcmp(argv[0], "type"))
387 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
388 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
389 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
390 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
391 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
392 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
393 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
394 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
395 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
396 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
397 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
398 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
399 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
400 info->blendmode = particletype[info->particletype].blendmode;
401 info->orientation = particletype[info->particletype].orientation;
403 else if (!strcmp(argv[0], "blend"))
406 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
407 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
408 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
409 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
411 else if (!strcmp(argv[0], "orientation"))
414 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
415 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
416 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
417 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
418 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
420 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
421 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
422 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
423 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
424 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
425 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
426 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
427 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
428 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
429 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
430 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
431 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
432 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
433 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
434 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
435 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
436 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
437 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
438 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
439 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
440 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
441 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
442 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
443 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
444 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
445 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
446 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
447 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
448 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
449 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
450 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
451 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
452 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; }
453 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
455 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
464 int CL_ParticleEffectIndexForName(const char *name)
467 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
468 if (!strcmp(particleeffectname[i], name))
473 const char *CL_ParticleEffectNameForIndex(int i)
475 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
477 return particleeffectname[i];
480 // MUST match effectnameindex_t in client.h
481 static const char *standardeffectnames[EFFECT_TOTAL] =
505 "TE_TEI_BIGEXPLOSION",
521 static void CL_Particles_LoadEffectInfo(const char *customfile)
525 unsigned char *filedata;
526 fs_offset_t filesize;
527 char filename[MAX_QPATH];
528 numparticleeffectinfo = 0;
529 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
530 memset(particleeffectname, 0, sizeof(particleeffectname));
531 for (i = 0;i < EFFECT_TOTAL;i++)
532 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
533 for (filepass = 0;;filepass++)
538 strlcpy(filename, customfile, sizeof(filename));
540 strlcpy(filename, "effectinfo.txt", sizeof(filename));
542 else if (filepass == 1)
544 if (!cl.worldbasename[0] || customfile)
546 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
550 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
553 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
558 static void CL_Particles_LoadEffectInfo_f(void)
560 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
568 void CL_ReadPointFile_f (void);
569 void CL_Particles_Init (void)
571 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)");
572 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
574 Cvar_RegisterVariable (&cl_particles);
575 Cvar_RegisterVariable (&cl_particles_quality);
576 Cvar_RegisterVariable (&cl_particles_alpha);
577 Cvar_RegisterVariable (&cl_particles_size);
578 Cvar_RegisterVariable (&cl_particles_quake);
579 Cvar_RegisterVariable (&cl_particles_blood);
580 Cvar_RegisterVariable (&cl_particles_blood_alpha);
581 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
582 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
584 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
585 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
586 Cvar_RegisterVariable (&cl_particles_explosions_shell);
587 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
588 Cvar_RegisterVariable (&cl_particles_rain);
589 Cvar_RegisterVariable (&cl_particles_snow);
590 Cvar_RegisterVariable (&cl_particles_smoke);
591 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
592 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
593 Cvar_RegisterVariable (&cl_particles_sparks);
594 Cvar_RegisterVariable (&cl_particles_bubbles);
595 Cvar_RegisterVariable (&cl_particles_visculling);
596 Cvar_RegisterVariable (&cl_particles_collisions);
597 Cvar_RegisterVariable (&cl_decals);
598 Cvar_RegisterVariable (&cl_decals_visculling);
599 Cvar_RegisterVariable (&cl_decals_time);
600 Cvar_RegisterVariable (&cl_decals_fadetime);
601 Cvar_RegisterVariable (&cl_decals_newsystem);
602 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
603 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
604 Cvar_RegisterVariable (&cl_decals_models);
605 Cvar_RegisterVariable (&cl_decals_bias);
606 Cvar_RegisterVariable (&cl_decals_max);
609 void CL_Particles_Shutdown (void)
613 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
614 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
616 // list of all 26 parameters:
617 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
618 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
619 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
620 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
621 // palpha - opacity of particle as 0-255 (can be more than 255)
622 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
623 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
624 // pgravity - how much effect gravity has on the particle (0-1)
625 // 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
626 // px,py,pz - starting origin of particle
627 // pvx,pvy,pvz - starting velocity of particle
628 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
629 // blendmode - one of the PBLEND_ values
630 // orientation - one of the PARTICLE_ values
631 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
632 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
633 // stainalpha: opacity of the stain as factor for alpha
634 // stainsize: size of the stain as factor for palpha
635 // angle: base rotation of the particle geometry around its center normal
636 // spin: rotation speed of the particle geometry around its center normal
637 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])
642 if (!cl_particles.integer)
644 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
645 if (cl.free_particle >= cl.max_particles)
648 lifetime = palpha / min(1, palphafade);
649 part = &cl.particles[cl.free_particle++];
650 if (cl.num_particles < cl.free_particle)
651 cl.num_particles = cl.free_particle;
652 memset(part, 0, sizeof(*part));
653 VectorCopy(sortorigin, part->sortorigin);
654 part->typeindex = ptypeindex;
655 part->blendmode = blendmode;
656 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
658 particletexture_t *tex = &particletexture[ptex];
659 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
660 part->orientation = PARTICLE_VBEAM;
662 part->orientation = PARTICLE_HBEAM;
665 part->orientation = orientation;
666 l2 = (int)lhrandom(0.5, 256.5);
668 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
669 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
670 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
673 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
674 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
675 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
677 part->alpha = palpha;
678 part->alphafade = palphafade;
679 part->staintexnum = staintex;
680 if(staincolor1 >= 0 && staincolor2 >= 0)
682 l2 = (int)lhrandom(0.5, 256.5);
684 if(blendmode == PBLEND_INVMOD)
686 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
687 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
688 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
692 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
693 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
694 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
696 if(r > 0xFF) r = 0xFF;
697 if(g > 0xFF) g = 0xFF;
698 if(b > 0xFF) b = 0xFF;
702 r = part->color[0]; // -1 is shorthand for stain = particle color
706 part->staincolor[0] = r;
707 part->staincolor[1] = g;
708 part->staincolor[2] = b;
709 part->stainalpha = palpha * stainalpha;
710 part->stainsize = psize * stainsize;
713 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
715 part->color[0] *= tint[0];
716 part->color[1] *= tint[1];
717 part->color[2] *= tint[2];
719 part->alpha *= tint[3];
720 part->alphafade *= tint[3];
721 part->stainalpha *= tint[3];
725 part->sizeincrease = psizeincrease;
726 part->gravity = pgravity;
727 part->bounce = pbounce;
728 part->stretch = stretch;
730 part->org[0] = px + originjitter * v[0];
731 part->org[1] = py + originjitter * v[1];
732 part->org[2] = pz + originjitter * v[2];
733 part->vel[0] = pvx + velocityjitter * v[0];
734 part->vel[1] = pvy + velocityjitter * v[1];
735 part->vel[2] = pvz + velocityjitter * v[2];
737 part->airfriction = pairfriction;
738 part->liquidfriction = pliquidfriction;
739 part->die = cl.time + lifetime;
740 part->delayedspawn = cl.time;
741 // part->delayedcollisions = 0;
742 part->qualityreduction = pqualityreduction;
745 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
746 if (part->typeindex == pt_rain)
750 float lifetime = part->die - cl.time;
753 // turn raindrop into simple spark and create delayedspawn splash effect
754 part->typeindex = pt_spark;
756 VectorMA(part->org, lifetime, part->vel, endvec);
757 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
758 part->die = cl.time + lifetime * trace.fraction;
759 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);
762 part2->delayedspawn = part->die;
763 part2->die += part->die - cl.time;
764 for (i = rand() & 7;i < 10;i++)
766 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);
769 part2->delayedspawn = part->die;
770 part2->die += part->die - cl.time;
776 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
778 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
781 VectorMA(part->org, lifetime, part->vel, endvec);
782 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
783 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
790 static void CL_ImmediateBloodStain(particle_t *part)
795 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
796 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
798 VectorCopy(part->vel, v);
800 staintex = part->staintexnum;
801 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);
804 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
805 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
807 VectorCopy(part->vel, v);
809 staintex = tex_blooddecal[rand()&7];
810 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);
814 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
818 entity_render_t *ent = &cl.entities[hitent].render;
819 unsigned char color[3];
820 if (!cl_decals.integer)
822 if (!ent->allowdecals)
825 l2 = (int)lhrandom(0.5, 256.5);
827 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
828 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
829 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
831 if (cl_decals_newsystem.integer)
834 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);
836 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);
840 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
841 if (cl.free_decal >= cl.max_decals)
843 decal = &cl.decals[cl.free_decal++];
844 if (cl.num_decals < cl.free_decal)
845 cl.num_decals = cl.free_decal;
846 memset(decal, 0, sizeof(*decal));
847 decal->decalsequence = cl.decalsequence++;
848 decal->typeindex = pt_decal;
849 decal->texnum = texnum;
850 VectorMA(org, cl_decals_bias.value, normal, decal->org);
851 VectorCopy(normal, decal->normal);
853 decal->alpha = alpha;
854 decal->time2 = cl.time;
855 decal->color[0] = color[0];
856 decal->color[1] = color[1];
857 decal->color[2] = color[2];
860 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
861 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
862 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
864 decal->owner = hitent;
865 decal->clusterindex = -1000; // no vis culling unless we're sure
868 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
869 decal->ownermodel = cl.entities[decal->owner].render.model;
870 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
871 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
875 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
877 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
879 decal->clusterindex = leaf->clusterindex;
884 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
891 int besthitent = 0, hitent;
894 for (i = 0;i < 32;i++)
897 VectorMA(org, maxdist, org2, org2);
898 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
899 // take the closest trace result that doesn't end up hitting a NOMARKS
900 // surface (sky for example)
901 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
903 bestfrac = trace.fraction;
905 VectorCopy(trace.endpos, bestorg);
906 VectorCopy(trace.plane.normal, bestnormal);
910 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
913 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
914 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
915 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)
918 matrix4x4_t tempmatrix;
920 VectorLerp(originmins, 0.5, originmaxs, center);
921 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
922 if (effectnameindex == EFFECT_SVC_PARTICLE)
924 if (cl_particles.integer)
926 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
928 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
929 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
930 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
933 count *= cl_particles_quality.value;
934 for (;count > 0;count--)
936 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
937 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);
942 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
943 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
944 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
945 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
946 else if (effectnameindex == EFFECT_TE_SPIKE)
948 if (cl_particles_bulletimpacts.integer)
950 if (cl_particles_quake.integer)
952 if (cl_particles_smoke.integer)
953 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
957 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
958 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
959 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);
963 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
964 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
966 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
972 if (cl_particles_smoke.integer)
973 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
977 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
978 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
979 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
983 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
984 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
985 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);
987 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
989 if (cl_particles_bulletimpacts.integer)
991 if (cl_particles_quake.integer)
993 if (cl_particles_smoke.integer)
994 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
998 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
999 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1000 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);
1004 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1005 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1007 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1009 if (cl_particles_bulletimpacts.integer)
1011 if (cl_particles_quake.integer)
1013 if (cl_particles_smoke.integer)
1014 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1018 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1019 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1020 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);
1024 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1025 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1026 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);
1028 else if (effectnameindex == EFFECT_TE_BLOOD)
1030 if (!cl_particles_blood.integer)
1032 if (cl_particles_quake.integer)
1033 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1036 static double bloodaccumulator = 0;
1037 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1038 //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);
1039 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1040 for (;bloodaccumulator > 0;bloodaccumulator--)
1042 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);
1043 if (immediatebloodstain && part)
1045 immediatebloodstain = false;
1046 CL_ImmediateBloodStain(part);
1051 else if (effectnameindex == EFFECT_TE_SPARK)
1052 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1053 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1055 // plasma scorch mark
1056 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1057 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1058 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1060 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1062 if (cl_particles_bulletimpacts.integer)
1064 if (cl_particles_quake.integer)
1065 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1068 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1069 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1070 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);
1074 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1075 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1077 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1079 if (cl_particles_bulletimpacts.integer)
1081 if (cl_particles_quake.integer)
1082 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1085 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1086 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1087 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);
1091 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1092 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1093 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);
1095 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1097 CL_ParticleExplosion(center);
1098 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);
1100 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1102 CL_ParticleExplosion(center);
1103 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);
1105 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1107 if (cl_particles_quake.integer)
1110 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1113 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);
1115 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);
1119 CL_ParticleExplosion(center);
1120 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);
1122 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1123 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);
1124 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1126 count *= cl_particles_quality.value;
1128 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);
1130 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1132 float i, j, inc, vel;
1135 inc = 8 / cl_particles_quality.value;
1136 for (i = -128;i < 128;i += inc)
1138 for (j = -128;j < 128;j += inc)
1140 dir[0] = j + lhrandom(0, inc);
1141 dir[1] = i + lhrandom(0, inc);
1143 org[0] = center[0] + dir[0];
1144 org[1] = center[1] + dir[1];
1145 org[2] = center[2] + lhrandom(0, 64);
1146 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1147 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);
1151 else if (effectnameindex == EFFECT_TE_TELEPORT)
1153 float i, j, k, inc, vel;
1156 if (cl_particles_quake.integer)
1157 inc = 4 / cl_particles_quality.value;
1159 inc = 8 / cl_particles_quality.value;
1160 for (i = -16;i < 16;i += inc)
1162 for (j = -16;j < 16;j += inc)
1164 for (k = -24;k < 32;k += inc)
1166 VectorSet(dir, i*8, j*8, k*8);
1167 VectorNormalize(dir);
1168 vel = lhrandom(50, 113);
1169 if (cl_particles_quake.integer)
1170 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);
1172 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);
1176 if (!cl_particles_quake.integer)
1177 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);
1178 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);
1180 else if (effectnameindex == EFFECT_TE_TEI_G3)
1181 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);
1182 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1184 if (cl_particles_smoke.integer)
1186 count *= 0.25f * 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, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1191 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1193 CL_ParticleExplosion(center);
1194 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);
1196 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1199 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1200 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1201 if (cl_particles_smoke.integer)
1202 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1203 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);
1204 if (cl_particles_sparks.integer)
1205 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1206 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);
1207 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);
1209 else if (effectnameindex == EFFECT_EF_FLAME)
1211 count *= 300 * cl_particles_quality.value;
1213 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);
1214 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);
1216 else if (effectnameindex == EFFECT_EF_STARDUST)
1218 count *= 200 * cl_particles_quality.value;
1220 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);
1221 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);
1223 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1227 int smoke, blood, bubbles, r, color;
1229 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1232 Vector4Set(light, 0, 0, 0, 0);
1234 if (effectnameindex == EFFECT_TR_ROCKET)
1235 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1236 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1238 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1239 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1241 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1243 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1244 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1248 matrix4x4_t tempmatrix;
1249 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1250 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);
1251 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1255 if (!spawnparticles)
1258 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1261 VectorSubtract(originmaxs, originmins, dir);
1262 len = VectorNormalizeLength(dir);
1265 dec = -ent->persistent.trail_time;
1266 ent->persistent.trail_time += len;
1267 if (ent->persistent.trail_time < 0.01f)
1270 // if we skip out, leave it reset
1271 ent->persistent.trail_time = 0.0f;
1276 // advance into this frame to reach the first puff location
1277 VectorMA(originmins, dec, dir, pos);
1280 smoke = cl_particles.integer && cl_particles_smoke.integer;
1281 blood = cl_particles.integer && cl_particles_blood.integer;
1282 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1283 qd = 1.0f / cl_particles_quality.value;
1290 if (effectnameindex == EFFECT_TR_BLOOD)
1292 if (cl_particles_quake.integer)
1294 color = particlepalette[67 + (rand()&3)];
1295 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);
1300 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);
1303 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1305 if (cl_particles_quake.integer)
1308 color = particlepalette[67 + (rand()&3)];
1309 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);
1314 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);
1320 if (effectnameindex == EFFECT_TR_ROCKET)
1322 if (cl_particles_quake.integer)
1325 color = particlepalette[ramp3[r]];
1326 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);
1330 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);
1331 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);
1334 else if (effectnameindex == EFFECT_TR_GRENADE)
1336 if (cl_particles_quake.integer)
1339 color = particlepalette[ramp3[r]];
1340 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);
1344 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);
1347 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1349 if (cl_particles_quake.integer)
1352 color = particlepalette[52 + (rand()&7)];
1353 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1354 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);
1356 else if (gamemode == GAME_GOODVSBAD2)
1359 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);
1363 color = particlepalette[20 + (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_KNIGHTSPIKE)
1369 if (cl_particles_quake.integer)
1372 color = particlepalette[230 + (rand()&7)];
1373 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);
1374 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);
1378 color = particlepalette[226 + (rand()&7)];
1379 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);
1382 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1384 if (cl_particles_quake.integer)
1386 color = particlepalette[152 + (rand()&3)];
1387 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);
1389 else if (gamemode == GAME_GOODVSBAD2)
1392 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);
1394 else if (gamemode == GAME_PRYDON)
1397 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);
1400 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);
1402 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1405 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);
1407 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1410 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);
1412 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1413 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);
1417 if (effectnameindex == EFFECT_TR_ROCKET)
1418 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);
1419 else if (effectnameindex == EFFECT_TR_GRENADE)
1420 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);
1422 // advance to next time and position
1425 VectorMA (pos, dec, dir, pos);
1428 ent->persistent.trail_time = len;
1431 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1434 // this is also called on point effects with spawndlight = true and
1435 // spawnparticles = true
1436 // it is called CL_ParticleTrail because most code does not want to supply
1437 // these parameters, only trail handling does
1438 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])
1440 qboolean found = false;
1442 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1444 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1445 return; // no such effect
1447 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1449 int effectinfoindex;
1452 particleeffectinfo_t *info;
1464 qboolean underwater;
1465 qboolean immediatebloodstain;
1467 float avgtint[4], tint[4], tintlerp;
1468 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1469 VectorLerp(originmins, 0.5, originmaxs, center);
1470 supercontents = CL_PointSuperContents(center);
1471 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1472 VectorSubtract(originmaxs, originmins, traildir);
1473 traillen = VectorLength(traildir);
1474 VectorNormalize(traildir);
1477 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1481 Vector4Set(avgtint, 1, 1, 1, 1);
1483 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1485 if (info->effectnameindex == effectnameindex)
1488 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1490 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1493 // spawn a dlight if requested
1494 if (info->lightradiusstart > 0 && spawndlight)
1496 matrix4x4_t tempmatrix;
1497 if (info->trailspacing > 0)
1498 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1500 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1501 if (info->lighttime > 0 && info->lightradiusfade > 0)
1503 // light flash (explosion, etc)
1504 // called when effect starts
1505 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, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1507 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1510 // called by CL_LinkNetworkEntity
1511 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1512 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1513 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1514 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1515 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, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1516 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1520 if (!spawnparticles)
1525 if (info->tex[1] > info->tex[0])
1527 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1528 tex = min(tex, info->tex[1] - 1);
1530 if(info->staintex[0] < 0)
1531 staintex = info->staintex[0];
1534 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1535 staintex = min(staintex, info->staintex[1] - 1);
1537 if (info->particletype == pt_decal)
1539 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1540 AnglesFromVectors(angles, velocity, NULL, false);
1541 AngleVectors(angles, forward, right, up);
1542 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1544 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]);
1546 else if (info->orientation == PARTICLE_HBEAM)
1548 AnglesFromVectors(angles, traildir, NULL, false);
1549 AngleVectors(angles, forward, right, up);
1550 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1552 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);
1556 if (!cl_particles.integer)
1558 switch (info->particletype)
1560 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1561 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1562 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1563 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1564 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1565 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1568 VectorCopy(originmins, trailpos);
1569 if (info->trailspacing > 0)
1571 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1572 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1573 immediatebloodstain = false;
1575 AnglesFromVectors(angles, traildir, NULL, false);
1576 AngleVectors(angles, forward, right, up);
1577 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1578 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1582 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1584 immediatebloodstain =
1585 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1587 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1589 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1590 AnglesFromVectors(angles, velocity, NULL, false);
1591 AngleVectors(angles, forward, right, up);
1592 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1593 VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1595 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1596 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1598 if (info->tex[1] > info->tex[0])
1600 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1601 tex = min(tex, info->tex[1] - 1);
1605 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1606 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1607 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1611 tintlerp = lhrandom(0, 1);
1612 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1615 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);
1616 if (immediatebloodstain && part)
1618 immediatebloodstain = false;
1619 CL_ImmediateBloodStain(part);
1622 VectorMA(trailpos, trailstep, traildir, trailpos);
1629 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1632 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)
1634 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1642 void CL_EntityParticles (const entity_t *ent)
1645 vec_t pitch, yaw, dist = 64, beamlength = 16;
1647 static vec3_t avelocities[NUMVERTEXNORMALS];
1648 if (!cl_particles.integer) return;
1649 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1651 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1653 if (!avelocities[0][0])
1654 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1655 avelocities[0][i] = lhrandom(0, 2.55);
1657 for (i = 0;i < NUMVERTEXNORMALS;i++)
1659 yaw = cl.time * avelocities[i][0];
1660 pitch = cl.time * avelocities[i][1];
1661 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1662 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1663 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1664 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);
1669 void CL_ReadPointFile_f (void)
1671 double org[3], leakorg[3];
1674 char *pointfile = NULL, *pointfilepos, *t, tchar;
1675 char name[MAX_QPATH];
1680 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1681 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1684 Con_Printf("Could not open %s\n", name);
1688 Con_Printf("Reading %s...\n", name);
1689 VectorClear(leakorg);
1692 pointfilepos = pointfile;
1693 while (*pointfilepos)
1695 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1700 while (*t && *t != '\n' && *t != '\r')
1704 #if _MSC_VER >= 1400
1705 #define sscanf sscanf_s
1707 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1708 VectorCopy(org, vecorg);
1714 VectorCopy(org, leakorg);
1717 if (cl.num_particles < cl.max_particles - 3)
1720 CL_NewParticle(vecorg, 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);
1723 Mem_Free(pointfile);
1724 VectorCopy(leakorg, vecorg);
1725 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1727 CL_NewParticle(vecorg, 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);
1728 CL_NewParticle(vecorg, 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);
1729 CL_NewParticle(vecorg, 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);
1734 CL_ParseParticleEffect
1736 Parse an effect out of the server message
1739 void CL_ParseParticleEffect (void)
1742 int i, count, msgcount, color;
1744 MSG_ReadVector(&cl_message, org, cls.protocol);
1745 for (i=0 ; i<3 ; i++)
1746 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1747 msgcount = MSG_ReadByte(&cl_message);
1748 color = MSG_ReadByte(&cl_message);
1750 if (msgcount == 255)
1755 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1760 CL_ParticleExplosion
1764 void CL_ParticleExplosion (const vec3_t org)
1770 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1771 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1773 if (cl_particles_quake.integer)
1775 for (i = 0;i < 1024;i++)
1781 color = particlepalette[ramp1[r]];
1782 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);
1786 color = particlepalette[ramp2[r]];
1787 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);
1793 i = CL_PointSuperContents(org);
1794 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1796 if (cl_particles.integer && cl_particles_bubbles.integer)
1797 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1798 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);
1802 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1804 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1811 VectorMA(org, 128, v2, v);
1812 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1814 while (k < 16 && trace.fraction < 0.1f);
1815 VectorSubtract(trace.endpos, org, v2);
1816 VectorScale(v2, 2.0f, v2);
1817 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);
1823 if (cl_particles_explosions_shell.integer)
1824 R_NewExplosion(org);
1829 CL_ParticleExplosion2
1833 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1836 if (!cl_particles.integer) return;
1838 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1840 k = particlepalette[colorStart + (i % colorLength)];
1841 if (cl_particles_quake.integer)
1842 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);
1844 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);
1848 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1851 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1852 if (cl_particles_sparks.integer)
1854 sparkcount *= cl_particles_quality.value;
1855 while(sparkcount-- > 0)
1856 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);
1860 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1863 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1864 if (cl_particles_smoke.integer)
1866 smokecount *= cl_particles_quality.value;
1867 while(smokecount-- > 0)
1868 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);
1872 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)
1876 if (!cl_particles.integer) return;
1877 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1879 count = (int)(count * cl_particles_quality.value);
1882 k = particlepalette[colorbase + (rand()&3)];
1883 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);
1887 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1890 float minz, maxz, lifetime = 30;
1892 if (!cl_particles.integer) return;
1893 if (dir[2] < 0) // falling
1895 minz = maxs[2] + dir[2] * 0.1;
1898 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1903 maxz = maxs[2] + dir[2] * 0.1;
1905 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1908 count = (int)(count * cl_particles_quality.value);
1913 if (!cl_particles_rain.integer) break;
1914 count *= 4; // ick, this should be in the mod or maps?
1918 k = particlepalette[colorbase + (rand()&3)];
1919 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1920 if (gamemode == GAME_GOODVSBAD2)
1921 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);
1923 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);
1927 if (!cl_particles_snow.integer) break;
1930 k = particlepalette[colorbase + (rand()&3)];
1931 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1932 if (gamemode == GAME_GOODVSBAD2)
1933 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);
1935 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);
1939 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1943 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1944 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1945 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1946 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1947 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1948 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1950 #define PARTICLETEXTURESIZE 64
1951 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1953 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1957 dz = 1 - (dx*dx+dy*dy);
1958 if (dz > 0) // it does hit the sphere
1962 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1963 VectorNormalize(normal);
1964 dot = DotProduct(normal, light);
1965 if (dot > 0.5) // interior reflection
1966 f += ((dot * 2) - 1);
1967 else if (dot < -0.5) // exterior reflection
1968 f += ((dot * -2) - 1);
1970 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1971 VectorNormalize(normal);
1972 dot = DotProduct(normal, light);
1973 if (dot > 0.5) // interior reflection
1974 f += ((dot * 2) - 1);
1975 else if (dot < -0.5) // exterior reflection
1976 f += ((dot * -2) - 1);
1978 f += 16; // just to give it a haze so you can see the outline
1979 f = bound(0, f, 255);
1980 return (unsigned char) f;
1986 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1987 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1989 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1990 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1991 *width = particlefontcellwidth;
1992 *height = particlefontcellheight;
1995 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1997 int basex, basey, w, h, y;
1998 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1999 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2000 Sys_Error("invalid particle texture size for autogenerating");
2001 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2002 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2005 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2008 float cx, cy, dx, dy, f, iradius;
2010 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2011 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2012 iradius = 1.0f / radius;
2013 alpha *= (1.0f / 255.0f);
2014 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2016 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2020 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2025 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2026 d[0] += (int)(f * (blue - d[0]));
2027 d[1] += (int)(f * (green - d[1]));
2028 d[2] += (int)(f * (red - d[2]));
2035 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2038 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2040 data[0] = bound(minb, data[0], maxb);
2041 data[1] = bound(ming, data[1], maxg);
2042 data[2] = bound(minr, data[2], maxr);
2047 static void particletextureinvert(unsigned char *data)
2050 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2052 data[0] = 255 - data[0];
2053 data[1] = 255 - data[1];
2054 data[2] = 255 - data[2];
2058 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2059 static void R_InitBloodTextures (unsigned char *particletexturedata)
2062 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2063 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2066 for (i = 0;i < 8;i++)
2068 memset(data, 255, datasize);
2069 for (k = 0;k < 24;k++)
2070 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2071 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2072 particletextureinvert(data);
2073 setuptex(tex_bloodparticle[i], data, particletexturedata);
2077 for (i = 0;i < 8;i++)
2079 memset(data, 255, datasize);
2081 for (j = 1;j < 10;j++)
2082 for (k = min(j, m - 1);k < m;k++)
2083 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2084 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2085 particletextureinvert(data);
2086 setuptex(tex_blooddecal[i], data, particletexturedata);
2092 //uncomment this to make engine save out particle font to a tga file when run
2093 //#define DUMPPARTICLEFONT
2095 static void R_InitParticleTexture (void)
2097 int x, y, d, i, k, m;
2098 int basex, basey, w, h;
2099 float dx, dy, f, s1, t1, s2, t2;
2102 fs_offset_t filesize;
2103 char texturename[MAX_QPATH];
2106 // a note: decals need to modulate (multiply) the background color to
2107 // properly darken it (stain), and they need to be able to alpha fade,
2108 // this is a very difficult challenge because it means fading to white
2109 // (no change to background) rather than black (darkening everything
2110 // behind the whole decal polygon), and to accomplish this the texture is
2111 // inverted (dark red blood on white background becomes brilliant cyan
2112 // and white on black background) so we can alpha fade it to black, then
2113 // we invert it again during the blendfunc to make it work...
2115 #ifndef DUMPPARTICLEFONT
2116 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2119 particlefonttexture = decalskinframe->base;
2120 // TODO maybe allow custom grid size?
2121 particlefontwidth = image_width;
2122 particlefontheight = image_height;
2123 particlefontcellwidth = image_width / 8;
2124 particlefontcellheight = image_height / 8;
2125 particlefontcols = 8;
2126 particlefontrows = 8;
2131 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2132 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2133 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2134 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2135 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2137 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2138 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2139 particlefontcols = 8;
2140 particlefontrows = 8;
2142 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2145 for (i = 0;i < 8;i++)
2147 memset(data, 255, datasize);
2150 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2151 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2153 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2155 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2156 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2158 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2159 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2161 d = (int)(d * (1-(dx*dx+dy*dy)));
2162 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2163 d = bound(0, d, 255);
2164 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2171 setuptex(tex_smoke[i], data, particletexturedata);
2175 memset(data, 255, datasize);
2176 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2178 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2179 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2181 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2182 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2183 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2186 setuptex(tex_rainsplash, data, particletexturedata);
2189 memset(data, 255, datasize);
2190 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2192 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2193 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2195 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2196 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2197 d = bound(0, d, 255);
2198 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2201 setuptex(tex_particle, data, particletexturedata);
2204 memset(data, 255, datasize);
2205 light[0] = 1;light[1] = 1;light[2] = 1;
2206 VectorNormalize(light);
2207 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2209 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2210 // stretch upper half of bubble by +50% and shrink lower half by -50%
2211 // (this gives an elongated teardrop shape)
2213 dy = (dy - 0.5f) * 2.0f;
2215 dy = (dy - 0.5f) / 1.5f;
2216 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2218 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2219 // shrink bubble width to half
2221 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2224 setuptex(tex_raindrop, data, particletexturedata);
2227 memset(data, 255, datasize);
2228 light[0] = 1;light[1] = 1;light[2] = 1;
2229 VectorNormalize(light);
2230 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2232 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2233 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2235 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2236 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2239 setuptex(tex_bubble, data, particletexturedata);
2241 // Blood particles and blood decals
2242 R_InitBloodTextures (particletexturedata);
2245 for (i = 0;i < 8;i++)
2247 memset(data, 255, datasize);
2248 for (k = 0;k < 12;k++)
2249 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2250 for (k = 0;k < 3;k++)
2251 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2252 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2253 particletextureinvert(data);
2254 setuptex(tex_bulletdecal[i], data, particletexturedata);
2257 #ifdef DUMPPARTICLEFONT
2258 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2261 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2262 particlefonttexture = decalskinframe->base;
2264 Mem_Free(particletexturedata);
2269 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2271 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2272 particletexture[i].texture = particlefonttexture;
2273 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2274 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2275 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2276 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2279 #ifndef DUMPPARTICLEFONT
2280 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2281 if (!particletexture[tex_beam].texture)
2284 unsigned char noise3[64][64], data2[64][16][4];
2286 fractalnoise(&noise3[0][0], 64, 4);
2288 for (y = 0;y < 64;y++)
2290 dy = (y - 0.5f*64) / (64*0.5f-1);
2291 for (x = 0;x < 16;x++)
2293 dx = (x - 0.5f*16) / (16*0.5f-2);
2294 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2295 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2296 data2[y][x][3] = 255;
2300 #ifdef DUMPPARTICLEFONT
2301 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2303 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2305 particletexture[tex_beam].s1 = 0;
2306 particletexture[tex_beam].t1 = 0;
2307 particletexture[tex_beam].s2 = 1;
2308 particletexture[tex_beam].t2 = 1;
2310 // now load an texcoord/texture override file
2311 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2318 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2320 if(!strcmp(com_token, "\n"))
2321 continue; // empty line
2322 i = atoi(com_token);
2330 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2332 strlcpy(texturename, com_token, sizeof(texturename));
2333 s1 = atof(com_token);
2334 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2337 t1 = atof(com_token);
2338 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2340 s2 = atof(com_token);
2341 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2343 t2 = atof(com_token);
2344 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2345 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2346 strlcpy(texturename, com_token, sizeof(texturename));
2353 if (!texturename[0])
2355 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2358 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2360 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2363 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2366 // R_SkinFrame_LoadExternal already complained
2369 particletexture[i].texture = sf->base;
2370 particletexture[i].s1 = s1;
2371 particletexture[i].t1 = t1;
2372 particletexture[i].s2 = s2;
2373 particletexture[i].t2 = t2;
2379 static void r_part_start(void)
2382 // generate particlepalette for convenience from the main one
2383 for (i = 0;i < 256;i++)
2384 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2385 particletexturepool = R_AllocTexturePool();
2386 R_InitParticleTexture ();
2387 CL_Particles_LoadEffectInfo(NULL);
2390 static void r_part_shutdown(void)
2392 R_FreeTexturePool(&particletexturepool);
2395 static void r_part_newmap(void)
2398 R_SkinFrame_MarkUsed(decalskinframe);
2399 CL_Particles_LoadEffectInfo(NULL);
2402 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2403 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2405 void R_Particles_Init (void)
2408 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2410 particle_elements[i*6+0] = i*4+0;
2411 particle_elements[i*6+1] = i*4+1;
2412 particle_elements[i*6+2] = i*4+2;
2413 particle_elements[i*6+3] = i*4+0;
2414 particle_elements[i*6+4] = i*4+2;
2415 particle_elements[i*6+5] = i*4+3;
2418 Cvar_RegisterVariable(&r_drawparticles);
2419 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2420 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2421 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2422 Cvar_RegisterVariable(&r_drawdecals);
2423 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2424 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2427 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2429 int surfacelistindex;
2431 float *v3f, *t2f, *c4f;
2432 particletexture_t *tex;
2433 vec_t right[3], up[3], size, ca;
2434 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2436 RSurf_ActiveWorldEntity();
2438 r_refdef.stats.drawndecals += numsurfaces;
2439 // R_Mesh_ResetTextureState();
2440 GL_DepthMask(false);
2441 GL_DepthRange(0, 1);
2442 GL_PolygonOffset(0, 0);
2444 GL_CullFace(GL_NONE);
2446 // generate all the vertices at once
2447 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2449 d = cl.decals + surfacelist[surfacelistindex];
2452 c4f = particle_color4f + 16*surfacelistindex;
2453 ca = d->alpha * alphascale;
2454 // ensure alpha multiplier saturates properly
2455 if (ca > 1.0f / 256.0f)
2457 if (r_refdef.fogenabled)
2458 ca *= RSurf_FogVertex(d->org);
2459 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2460 Vector4Copy(c4f, c4f + 4);
2461 Vector4Copy(c4f, c4f + 8);
2462 Vector4Copy(c4f, c4f + 12);
2464 // calculate vertex positions
2465 size = d->size * cl_particles_size.value;
2466 VectorVectors(d->normal, right, up);
2467 VectorScale(right, size, right);
2468 VectorScale(up, size, up);
2469 v3f = particle_vertex3f + 12*surfacelistindex;
2470 v3f[ 0] = d->org[0] - right[0] - up[0];
2471 v3f[ 1] = d->org[1] - right[1] - up[1];
2472 v3f[ 2] = d->org[2] - right[2] - up[2];
2473 v3f[ 3] = d->org[0] - right[0] + up[0];
2474 v3f[ 4] = d->org[1] - right[1] + up[1];
2475 v3f[ 5] = d->org[2] - right[2] + up[2];
2476 v3f[ 6] = d->org[0] + right[0] + up[0];
2477 v3f[ 7] = d->org[1] + right[1] + up[1];
2478 v3f[ 8] = d->org[2] + right[2] + up[2];
2479 v3f[ 9] = d->org[0] + right[0] - up[0];
2480 v3f[10] = d->org[1] + right[1] - up[1];
2481 v3f[11] = d->org[2] + right[2] - up[2];
2483 // calculate texcoords
2484 tex = &particletexture[d->texnum];
2485 t2f = particle_texcoord2f + 8*surfacelistindex;
2486 t2f[0] = tex->s1;t2f[1] = tex->t2;
2487 t2f[2] = tex->s1;t2f[3] = tex->t1;
2488 t2f[4] = tex->s2;t2f[5] = tex->t1;
2489 t2f[6] = tex->s2;t2f[7] = tex->t2;
2492 // now render the decals all at once
2493 // (this assumes they all use one particle font texture!)
2494 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2495 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2496 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2497 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2500 void R_DrawDecals (void)
2503 int drawdecals = r_drawdecals.integer;
2508 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2510 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2511 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2513 // LordHavoc: early out conditions
2517 decalfade = frametime * 256 / cl_decals_fadetime.value;
2518 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2519 drawdist2 = drawdist2*drawdist2;
2521 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2523 if (!decal->typeindex)
2526 if (killsequence - decal->decalsequence > 0)
2529 if (cl.time > decal->time2 + cl_decals_time.value)
2531 decal->alpha -= decalfade;
2532 if (decal->alpha <= 0)
2538 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2540 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2541 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2547 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2553 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))
2554 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2557 decal->typeindex = 0;
2558 if (cl.free_decal > i)
2562 // reduce cl.num_decals if possible
2563 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2566 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2568 decal_t *olddecals = cl.decals;
2569 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2570 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2571 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2572 Mem_Free(olddecals);
2575 r_refdef.stats.totaldecals = cl.num_decals;
2578 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2580 vec3_t vecorg, vecvel, baseright, baseup;
2581 int surfacelistindex;
2582 int batchstart, batchcount;
2583 const particle_t *p;
2585 rtexture_t *texture;
2586 float *v3f, *t2f, *c4f;
2587 particletexture_t *tex;
2588 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2589 // float ambient[3], diffuse[3], diffusenormal[3];
2590 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2591 vec4_t colormultiplier;
2592 float minparticledist_start, minparticledist_end;
2595 RSurf_ActiveWorldEntity();
2597 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));
2599 r_refdef.stats.particles += numsurfaces;
2600 // R_Mesh_ResetTextureState();
2601 GL_DepthMask(false);
2602 GL_DepthRange(0, 1);
2603 GL_PolygonOffset(0, 0);
2605 GL_CullFace(GL_NONE);
2607 spintime = r_refdef.scene.time;
2609 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2610 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2611 dofade = (minparticledist_start < minparticledist_end);
2613 // first generate all the vertices at once
2614 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2616 p = cl.particles + surfacelist[surfacelistindex];
2618 blendmode = (pblend_t)p->blendmode;
2620 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2621 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2622 alpha = palpha * colormultiplier[3];
2623 // ensure alpha multiplier saturates properly
2629 case PBLEND_INVALID:
2631 // additive and modulate can just fade out in fog (this is correct)
2632 if (r_refdef.fogenabled)
2633 alpha *= RSurf_FogVertex(p->org);
2634 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2635 alpha *= 1.0f / 256.0f;
2636 c4f[0] = p->color[0] * alpha;
2637 c4f[1] = p->color[1] * alpha;
2638 c4f[2] = p->color[2] * alpha;
2642 // additive and modulate can just fade out in fog (this is correct)
2643 if (r_refdef.fogenabled)
2644 alpha *= RSurf_FogVertex(p->org);
2645 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2646 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2647 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2648 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2652 c4f[0] = p->color[0] * colormultiplier[0];
2653 c4f[1] = p->color[1] * colormultiplier[1];
2654 c4f[2] = p->color[2] * colormultiplier[2];
2656 // note: lighting is not cheap!
2657 if (particletype[p->typeindex].lighting)
2659 vecorg[0] = p->org[0];
2660 vecorg[1] = p->org[1];
2661 vecorg[2] = p->org[2];
2662 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2664 // mix in the fog color
2665 if (r_refdef.fogenabled)
2667 fog = RSurf_FogVertex(p->org);
2669 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2670 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2671 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2673 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2674 VectorScale(c4f, alpha, c4f);
2677 // copy the color into the other three vertices
2678 Vector4Copy(c4f, c4f + 4);
2679 Vector4Copy(c4f, c4f + 8);
2680 Vector4Copy(c4f, c4f + 12);
2682 size = p->size * cl_particles_size.value;
2683 tex = &particletexture[p->texnum];
2684 switch(p->orientation)
2686 // case PARTICLE_INVALID:
2687 case PARTICLE_BILLBOARD:
2688 if (p->angle + p->spin)
2690 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2691 spinsin = sin(spinrad) * size;
2692 spincos = cos(spinrad) * size;
2693 spinm1 = -p->stretch * spincos;
2696 spinm4 = -p->stretch * spincos;
2697 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2698 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2702 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2703 VectorScale(r_refdef.view.up, size, up);
2706 v3f[ 0] = p->org[0] - right[0] - up[0];
2707 v3f[ 1] = p->org[1] - right[1] - up[1];
2708 v3f[ 2] = p->org[2] - right[2] - up[2];
2709 v3f[ 3] = p->org[0] - right[0] + up[0];
2710 v3f[ 4] = p->org[1] - right[1] + up[1];
2711 v3f[ 5] = p->org[2] - right[2] + up[2];
2712 v3f[ 6] = p->org[0] + right[0] + up[0];
2713 v3f[ 7] = p->org[1] + right[1] + up[1];
2714 v3f[ 8] = p->org[2] + right[2] + up[2];
2715 v3f[ 9] = p->org[0] + right[0] - up[0];
2716 v3f[10] = p->org[1] + right[1] - up[1];
2717 v3f[11] = p->org[2] + right[2] - up[2];
2718 t2f[0] = tex->s1;t2f[1] = tex->t2;
2719 t2f[2] = tex->s1;t2f[3] = tex->t1;
2720 t2f[4] = tex->s2;t2f[5] = tex->t1;
2721 t2f[6] = tex->s2;t2f[7] = tex->t2;
2723 case PARTICLE_ORIENTED_DOUBLESIDED:
2724 vecvel[0] = p->vel[0];
2725 vecvel[1] = p->vel[1];
2726 vecvel[2] = p->vel[2];
2727 VectorVectors(vecvel, baseright, baseup);
2728 if (p->angle + p->spin)
2730 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2731 spinsin = sin(spinrad) * size;
2732 spincos = cos(spinrad) * size;
2733 spinm1 = p->stretch * spincos;
2736 spinm4 = p->stretch * spincos;
2737 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2738 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2742 VectorScale(baseright, size * p->stretch, right);
2743 VectorScale(baseup, size, up);
2745 v3f[ 0] = p->org[0] - right[0] - up[0];
2746 v3f[ 1] = p->org[1] - right[1] - up[1];
2747 v3f[ 2] = p->org[2] - right[2] - up[2];
2748 v3f[ 3] = p->org[0] - right[0] + up[0];
2749 v3f[ 4] = p->org[1] - right[1] + up[1];
2750 v3f[ 5] = p->org[2] - right[2] + up[2];
2751 v3f[ 6] = p->org[0] + right[0] + up[0];
2752 v3f[ 7] = p->org[1] + right[1] + up[1];
2753 v3f[ 8] = p->org[2] + right[2] + up[2];
2754 v3f[ 9] = p->org[0] + right[0] - up[0];
2755 v3f[10] = p->org[1] + right[1] - up[1];
2756 v3f[11] = p->org[2] + right[2] - up[2];
2757 t2f[0] = tex->s1;t2f[1] = tex->t2;
2758 t2f[2] = tex->s1;t2f[3] = tex->t1;
2759 t2f[4] = tex->s2;t2f[5] = tex->t1;
2760 t2f[6] = tex->s2;t2f[7] = tex->t2;
2762 case PARTICLE_SPARK:
2763 len = VectorLength(p->vel);
2764 VectorNormalize2(p->vel, up);
2765 lenfactor = p->stretch * 0.04 * len;
2766 if(lenfactor < size * 0.5)
2767 lenfactor = size * 0.5;
2768 VectorMA(p->org, -lenfactor, up, v);
2769 VectorMA(p->org, lenfactor, up, up2);
2770 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2771 t2f[0] = tex->s1;t2f[1] = tex->t2;
2772 t2f[2] = tex->s1;t2f[3] = tex->t1;
2773 t2f[4] = tex->s2;t2f[5] = tex->t1;
2774 t2f[6] = tex->s2;t2f[7] = tex->t2;
2776 case PARTICLE_VBEAM:
2777 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2778 VectorSubtract(p->vel, p->org, up);
2779 VectorNormalize(up);
2780 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2781 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2782 t2f[0] = tex->s2;t2f[1] = v[0];
2783 t2f[2] = tex->s1;t2f[3] = v[0];
2784 t2f[4] = tex->s1;t2f[5] = v[1];
2785 t2f[6] = tex->s2;t2f[7] = v[1];
2787 case PARTICLE_HBEAM:
2788 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2789 VectorSubtract(p->vel, p->org, up);
2790 VectorNormalize(up);
2791 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2792 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2793 t2f[0] = v[0];t2f[1] = tex->t1;
2794 t2f[2] = v[0];t2f[3] = tex->t2;
2795 t2f[4] = v[1];t2f[5] = tex->t2;
2796 t2f[6] = v[1];t2f[7] = tex->t1;
2801 // now render batches of particles based on blendmode and texture
2802 blendmode = PBLEND_INVALID;
2806 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2807 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2809 p = cl.particles + surfacelist[surfacelistindex];
2811 if (texture != particletexture[p->texnum].texture)
2813 texture = particletexture[p->texnum].texture;
2814 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2817 if (p->blendmode == PBLEND_INVMOD)
2819 // inverse modulate blend - group these
2820 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2821 // iterate until we find a change in settings
2822 batchstart = surfacelistindex++;
2823 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2825 p = cl.particles + surfacelist[surfacelistindex];
2826 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2832 // additive or alpha blend - group these
2833 // (we can group these because we premultiplied the texture alpha)
2834 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2835 // iterate until we find a change in settings
2836 batchstart = surfacelistindex++;
2837 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2839 p = cl.particles + surfacelist[surfacelistindex];
2840 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2845 batchcount = surfacelistindex - batchstart;
2846 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2850 void R_DrawParticles (void)
2853 int drawparticles = r_drawparticles.integer;
2854 float minparticledist_start;
2856 float gravity, frametime, f, dist, oldorg[3];
2862 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2863 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2865 // LordHavoc: early out conditions
2866 if (!cl.num_particles)
2869 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2870 gravity = frametime * cl.movevars_gravity;
2871 update = frametime > 0;
2872 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2873 drawdist2 = drawdist2*drawdist2;
2875 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2879 if (cl.free_particle > i)
2880 cl.free_particle = i;
2886 if (p->delayedspawn > cl.time)
2889 p->size += p->sizeincrease * frametime;
2890 p->alpha -= p->alphafade * frametime;
2892 if (p->alpha <= 0 || p->die <= cl.time)
2895 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2897 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2899 if (p->typeindex == pt_blood)
2900 p->size += frametime * 8;
2902 p->vel[2] -= p->gravity * gravity;
2903 f = 1.0f - min(p->liquidfriction * frametime, 1);
2904 VectorScale(p->vel, f, p->vel);
2908 p->vel[2] -= p->gravity * gravity;
2911 f = 1.0f - min(p->airfriction * frametime, 1);
2912 VectorScale(p->vel, f, p->vel);
2916 VectorCopy(p->org, oldorg);
2917 VectorMA(p->org, frametime, p->vel, p->org);
2918 // if (p->bounce && cl.time >= p->delayedcollisions)
2919 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2921 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);
2922 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2923 // or if the trace hit something flagged as NOIMPACT
2924 // then remove the particle
2925 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2927 VectorCopy(trace.endpos, p->org);
2928 // react if the particle hit something
2929 if (trace.fraction < 1)
2931 VectorCopy(trace.endpos, p->org);
2933 if (p->staintexnum >= 0)
2935 // blood - splash on solid
2936 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2939 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2940 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2941 if (cl_decals.integer)
2943 // create a decal for the blood splat
2944 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2945 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2950 if (p->typeindex == pt_blood)
2952 // blood - splash on solid
2953 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2955 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2957 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)));
2958 if (cl_decals.integer)
2960 // create a decal for the blood splat
2961 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);
2966 else if (p->bounce < 0)
2968 // bounce -1 means remove on impact
2973 // anything else - bounce off solid
2974 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2975 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2980 if (VectorLength2(p->vel) < 0.03)
2982 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2984 VectorClear(p->vel);
2988 if (p->typeindex != pt_static)
2990 switch (p->typeindex)
2992 case pt_entityparticle:
2993 // particle that removes itself after one rendered frame
3000 a = CL_PointSuperContents(p->org);
3001 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3005 a = CL_PointSuperContents(p->org);
3006 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3010 a = CL_PointSuperContents(p->org);
3011 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3015 if (cl.time > p->time2)
3018 p->time2 = cl.time + (rand() & 3) * 0.1;
3019 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3020 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3022 a = CL_PointSuperContents(p->org);
3023 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3031 else if (p->delayedspawn > cl.time)
3035 // don't render particles too close to the view (they chew fillrate)
3036 // also don't render particles behind the view (useless)
3037 // further checks to cull to the frustum would be too slow here
3038 switch(p->typeindex)
3041 // beams have no culling
3042 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3045 if(cl_particles_visculling.integer)
3046 if (!r_refdef.viewcache.world_novis)
3047 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3049 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3051 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3054 // anything else just has to be in front of the viewer and visible at this distance
3055 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3056 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3063 if (cl.free_particle > i)
3064 cl.free_particle = i;
3067 // reduce cl.num_particles if possible
3068 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3071 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3073 particle_t *oldparticles = cl.particles;
3074 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3075 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3076 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3077 Mem_Free(oldparticles);