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(void)
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++)
536 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
537 else if (filepass == 1)
539 if (!cl.worldbasename[0])
541 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
545 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
548 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
558 void CL_ReadPointFile_f (void);
559 void CL_Particles_Init (void)
561 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)");
562 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
564 Cvar_RegisterVariable (&cl_particles);
565 Cvar_RegisterVariable (&cl_particles_quality);
566 Cvar_RegisterVariable (&cl_particles_alpha);
567 Cvar_RegisterVariable (&cl_particles_size);
568 Cvar_RegisterVariable (&cl_particles_quake);
569 Cvar_RegisterVariable (&cl_particles_blood);
570 Cvar_RegisterVariable (&cl_particles_blood_alpha);
571 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
572 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
573 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
574 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
575 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
576 Cvar_RegisterVariable (&cl_particles_explosions_shell);
577 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
578 Cvar_RegisterVariable (&cl_particles_rain);
579 Cvar_RegisterVariable (&cl_particles_snow);
580 Cvar_RegisterVariable (&cl_particles_smoke);
581 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
582 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
583 Cvar_RegisterVariable (&cl_particles_sparks);
584 Cvar_RegisterVariable (&cl_particles_bubbles);
585 Cvar_RegisterVariable (&cl_particles_visculling);
586 Cvar_RegisterVariable (&cl_particles_collisions);
587 Cvar_RegisterVariable (&cl_decals);
588 Cvar_RegisterVariable (&cl_decals_visculling);
589 Cvar_RegisterVariable (&cl_decals_time);
590 Cvar_RegisterVariable (&cl_decals_fadetime);
591 Cvar_RegisterVariable (&cl_decals_newsystem);
592 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
593 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
594 Cvar_RegisterVariable (&cl_decals_models);
595 Cvar_RegisterVariable (&cl_decals_bias);
596 Cvar_RegisterVariable (&cl_decals_max);
599 void CL_Particles_Shutdown (void)
603 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
604 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
606 // list of all 26 parameters:
607 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
608 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
609 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
610 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
611 // palpha - opacity of particle as 0-255 (can be more than 255)
612 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
613 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
614 // pgravity - how much effect gravity has on the particle (0-1)
615 // 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
616 // px,py,pz - starting origin of particle
617 // pvx,pvy,pvz - starting velocity of particle
618 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
619 // blendmode - one of the PBLEND_ values
620 // orientation - one of the PARTICLE_ values
621 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
622 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
623 // stainalpha: opacity of the stain as factor for alpha
624 // stainsize: size of the stain as factor for palpha
625 // angle: base rotation of the particle geometry around its center normal
626 // spin: rotation speed of the particle geometry around its center normal
627 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])
632 if (!cl_particles.integer)
634 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
635 if (cl.free_particle >= cl.max_particles)
638 lifetime = palpha / min(1, palphafade);
639 part = &cl.particles[cl.free_particle++];
640 if (cl.num_particles < cl.free_particle)
641 cl.num_particles = cl.free_particle;
642 memset(part, 0, sizeof(*part));
643 VectorCopy(sortorigin, part->sortorigin);
644 part->typeindex = ptypeindex;
645 part->blendmode = blendmode;
646 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
648 particletexture_t *tex = &particletexture[ptex];
649 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
650 part->orientation = PARTICLE_VBEAM;
652 part->orientation = PARTICLE_HBEAM;
655 part->orientation = orientation;
656 l2 = (int)lhrandom(0.5, 256.5);
658 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
659 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
660 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
663 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
664 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
665 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
667 part->alpha = palpha;
668 part->alphafade = palphafade;
669 part->staintexnum = staintex;
670 if(staincolor1 >= 0 && staincolor2 >= 0)
672 l2 = (int)lhrandom(0.5, 256.5);
674 if(blendmode == PBLEND_INVMOD)
676 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
677 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
678 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
682 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
683 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
684 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
686 if(r > 0xFF) r = 0xFF;
687 if(g > 0xFF) g = 0xFF;
688 if(b > 0xFF) b = 0xFF;
692 r = part->color[0]; // -1 is shorthand for stain = particle color
696 part->staincolor[0] = r;
697 part->staincolor[1] = g;
698 part->staincolor[2] = b;
699 part->stainalpha = palpha * stainalpha;
700 part->stainsize = psize * stainsize;
703 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
705 part->color[0] *= tint[0];
706 part->color[1] *= tint[1];
707 part->color[2] *= tint[2];
709 part->alpha *= tint[3];
710 part->alphafade *= tint[3];
711 part->stainalpha *= tint[3];
715 part->sizeincrease = psizeincrease;
716 part->gravity = pgravity;
717 part->bounce = pbounce;
718 part->stretch = stretch;
720 part->org[0] = px + originjitter * v[0];
721 part->org[1] = py + originjitter * v[1];
722 part->org[2] = pz + originjitter * v[2];
723 part->vel[0] = pvx + velocityjitter * v[0];
724 part->vel[1] = pvy + velocityjitter * v[1];
725 part->vel[2] = pvz + velocityjitter * v[2];
727 part->airfriction = pairfriction;
728 part->liquidfriction = pliquidfriction;
729 part->die = cl.time + lifetime;
730 part->delayedspawn = cl.time;
731 // part->delayedcollisions = 0;
732 part->qualityreduction = pqualityreduction;
735 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
736 if (part->typeindex == pt_rain)
740 float lifetime = part->die - cl.time;
743 // turn raindrop into simple spark and create delayedspawn splash effect
744 part->typeindex = pt_spark;
746 VectorMA(part->org, lifetime, part->vel, endvec);
747 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
748 part->die = cl.time + lifetime * trace.fraction;
749 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);
752 part2->delayedspawn = part->die;
753 part2->die += part->die - cl.time;
754 for (i = rand() & 7;i < 10;i++)
756 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);
759 part2->delayedspawn = part->die;
760 part2->die += part->die - cl.time;
766 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
768 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
771 VectorMA(part->org, lifetime, part->vel, endvec);
772 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
773 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
780 static void CL_ImmediateBloodStain(particle_t *part)
785 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
786 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
788 VectorCopy(part->vel, v);
790 staintex = part->staintexnum;
791 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);
794 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
795 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
797 VectorCopy(part->vel, v);
799 staintex = tex_blooddecal[rand()&7];
800 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);
804 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
808 entity_render_t *ent = &cl.entities[hitent].render;
809 unsigned char color[3];
810 if (!cl_decals.integer)
812 if (!ent->allowdecals)
815 l2 = (int)lhrandom(0.5, 256.5);
817 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
818 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
819 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
821 if (cl_decals_newsystem.integer)
824 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);
826 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);
830 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
831 if (cl.free_decal >= cl.max_decals)
833 decal = &cl.decals[cl.free_decal++];
834 if (cl.num_decals < cl.free_decal)
835 cl.num_decals = cl.free_decal;
836 memset(decal, 0, sizeof(*decal));
837 decal->decalsequence = cl.decalsequence++;
838 decal->typeindex = pt_decal;
839 decal->texnum = texnum;
840 VectorMA(org, cl_decals_bias.value, normal, decal->org);
841 VectorCopy(normal, decal->normal);
843 decal->alpha = alpha;
844 decal->time2 = cl.time;
845 decal->color[0] = color[0];
846 decal->color[1] = color[1];
847 decal->color[2] = color[2];
850 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
851 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
852 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
854 decal->owner = hitent;
855 decal->clusterindex = -1000; // no vis culling unless we're sure
858 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
859 decal->ownermodel = cl.entities[decal->owner].render.model;
860 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
861 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
865 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
867 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
869 decal->clusterindex = leaf->clusterindex;
874 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
877 float bestfrac, bestorg[3], bestnormal[3];
879 int besthitent = 0, hitent;
882 for (i = 0;i < 32;i++)
885 VectorMA(org, maxdist, org2, org2);
886 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
887 // take the closest trace result that doesn't end up hitting a NOMARKS
888 // surface (sky for example)
889 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
891 bestfrac = trace.fraction;
893 VectorCopy(trace.endpos, bestorg);
894 VectorCopy(trace.plane.normal, bestnormal);
898 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
901 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
902 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
903 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)
906 matrix4x4_t tempmatrix;
908 VectorLerp(originmins, 0.5, originmaxs, center);
909 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
910 if (effectnameindex == EFFECT_SVC_PARTICLE)
912 if (cl_particles.integer)
914 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
916 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
917 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
918 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
921 count *= cl_particles_quality.value;
922 for (;count > 0;count--)
924 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
925 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);
930 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
931 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
932 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
933 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
934 else if (effectnameindex == EFFECT_TE_SPIKE)
936 if (cl_particles_bulletimpacts.integer)
938 if (cl_particles_quake.integer)
940 if (cl_particles_smoke.integer)
941 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
945 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
946 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
947 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);
951 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
952 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
954 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
956 if (cl_particles_bulletimpacts.integer)
958 if (cl_particles_quake.integer)
960 if (cl_particles_smoke.integer)
961 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
965 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
966 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
967 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);
971 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
972 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
973 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);
975 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
977 if (cl_particles_bulletimpacts.integer)
979 if (cl_particles_quake.integer)
981 if (cl_particles_smoke.integer)
982 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
986 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
987 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
988 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);
992 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
993 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
995 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
997 if (cl_particles_bulletimpacts.integer)
999 if (cl_particles_quake.integer)
1001 if (cl_particles_smoke.integer)
1002 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1006 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1007 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1008 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);
1012 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1013 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1014 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);
1016 else if (effectnameindex == EFFECT_TE_BLOOD)
1018 if (!cl_particles_blood.integer)
1020 if (cl_particles_quake.integer)
1021 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1024 static double bloodaccumulator = 0;
1025 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1026 //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);
1027 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1028 for (;bloodaccumulator > 0;bloodaccumulator--)
1030 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);
1031 if (immediatebloodstain && part)
1033 immediatebloodstain = false;
1034 CL_ImmediateBloodStain(part);
1039 else if (effectnameindex == EFFECT_TE_SPARK)
1040 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1041 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1043 // plasma scorch mark
1044 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1045 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1046 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1048 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1050 if (cl_particles_bulletimpacts.integer)
1052 if (cl_particles_quake.integer)
1053 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1056 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1057 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1058 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);
1062 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1063 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1065 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1067 if (cl_particles_bulletimpacts.integer)
1069 if (cl_particles_quake.integer)
1070 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1073 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1074 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1075 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);
1079 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1080 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1081 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);
1083 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1085 CL_ParticleExplosion(center);
1086 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);
1088 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1090 CL_ParticleExplosion(center);
1091 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);
1093 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1095 if (cl_particles_quake.integer)
1098 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1101 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);
1103 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);
1107 CL_ParticleExplosion(center);
1108 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);
1110 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1111 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);
1112 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1114 count *= cl_particles_quality.value;
1116 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);
1118 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1120 float i, j, inc, vel;
1123 inc = 8 / cl_particles_quality.value;
1124 for (i = -128;i < 128;i += inc)
1126 for (j = -128;j < 128;j += inc)
1128 dir[0] = j + lhrandom(0, inc);
1129 dir[1] = i + lhrandom(0, inc);
1131 org[0] = center[0] + dir[0];
1132 org[1] = center[1] + dir[1];
1133 org[2] = center[2] + lhrandom(0, 64);
1134 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1135 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);
1139 else if (effectnameindex == EFFECT_TE_TELEPORT)
1141 float i, j, k, inc, vel;
1144 if (cl_particles_quake.integer)
1145 inc = 4 / cl_particles_quality.value;
1147 inc = 8 / cl_particles_quality.value;
1148 for (i = -16;i < 16;i += inc)
1150 for (j = -16;j < 16;j += inc)
1152 for (k = -24;k < 32;k += inc)
1154 VectorSet(dir, i*8, j*8, k*8);
1155 VectorNormalize(dir);
1156 vel = lhrandom(50, 113);
1157 if (cl_particles_quake.integer)
1158 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);
1160 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);
1164 if (!cl_particles_quake.integer)
1165 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);
1166 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);
1168 else if (effectnameindex == EFFECT_TE_TEI_G3)
1169 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);
1170 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1172 if (cl_particles_smoke.integer)
1174 count *= 0.25f * cl_particles_quality.value;
1176 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);
1179 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1181 CL_ParticleExplosion(center);
1182 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);
1184 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1187 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1188 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1189 if (cl_particles_smoke.integer)
1190 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1191 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);
1192 if (cl_particles_sparks.integer)
1193 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1194 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);
1195 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);
1197 else if (effectnameindex == EFFECT_EF_FLAME)
1199 count *= 300 * cl_particles_quality.value;
1201 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);
1202 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);
1204 else if (effectnameindex == EFFECT_EF_STARDUST)
1206 count *= 200 * cl_particles_quality.value;
1208 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);
1209 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);
1211 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1215 int smoke, blood, bubbles, r, color;
1217 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1220 Vector4Set(light, 0, 0, 0, 0);
1222 if (effectnameindex == EFFECT_TR_ROCKET)
1223 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1224 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1226 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1227 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1229 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1231 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1232 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1236 matrix4x4_t tempmatrix;
1237 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1238 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);
1239 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1243 if (!spawnparticles)
1246 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1249 VectorSubtract(originmaxs, originmins, dir);
1250 len = VectorNormalizeLength(dir);
1253 dec = -ent->persistent.trail_time;
1254 ent->persistent.trail_time += len;
1255 if (ent->persistent.trail_time < 0.01f)
1258 // if we skip out, leave it reset
1259 ent->persistent.trail_time = 0.0f;
1264 // advance into this frame to reach the first puff location
1265 VectorMA(originmins, dec, dir, pos);
1268 smoke = cl_particles.integer && cl_particles_smoke.integer;
1269 blood = cl_particles.integer && cl_particles_blood.integer;
1270 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1271 qd = 1.0f / cl_particles_quality.value;
1278 if (effectnameindex == EFFECT_TR_BLOOD)
1280 if (cl_particles_quake.integer)
1282 color = particlepalette[67 + (rand()&3)];
1283 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);
1288 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);
1291 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1293 if (cl_particles_quake.integer)
1296 color = particlepalette[67 + (rand()&3)];
1297 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);
1302 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);
1308 if (effectnameindex == EFFECT_TR_ROCKET)
1310 if (cl_particles_quake.integer)
1313 color = particlepalette[ramp3[r]];
1314 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);
1318 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);
1319 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);
1322 else if (effectnameindex == EFFECT_TR_GRENADE)
1324 if (cl_particles_quake.integer)
1327 color = particlepalette[ramp3[r]];
1328 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);
1332 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);
1335 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1337 if (cl_particles_quake.integer)
1340 color = particlepalette[52 + (rand()&7)];
1341 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);
1342 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);
1344 else if (gamemode == GAME_GOODVSBAD2)
1347 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);
1351 color = particlepalette[20 + (rand()&7)];
1352 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);
1355 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1357 if (cl_particles_quake.integer)
1360 color = particlepalette[230 + (rand()&7)];
1361 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);
1362 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);
1366 color = particlepalette[226 + (rand()&7)];
1367 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);
1370 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1372 if (cl_particles_quake.integer)
1374 color = particlepalette[152 + (rand()&3)];
1375 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);
1377 else if (gamemode == GAME_GOODVSBAD2)
1380 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);
1382 else if (gamemode == GAME_PRYDON)
1385 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);
1388 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);
1390 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1393 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);
1395 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1398 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);
1400 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1401 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);
1405 if (effectnameindex == EFFECT_TR_ROCKET)
1406 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1407 else if (effectnameindex == EFFECT_TR_GRENADE)
1408 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);
1410 // advance to next time and position
1413 VectorMA (pos, dec, dir, pos);
1416 ent->persistent.trail_time = len;
1419 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1422 // this is also called on point effects with spawndlight = true and
1423 // spawnparticles = true
1424 // it is called CL_ParticleTrail because most code does not want to supply
1425 // these parameters, only trail handling does
1426 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])
1428 qboolean found = false;
1430 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1432 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1433 return; // no such effect
1435 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1437 int effectinfoindex;
1440 particleeffectinfo_t *info;
1452 qboolean underwater;
1453 qboolean immediatebloodstain;
1455 float avgtint[4], tint[4], tintlerp;
1456 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1457 VectorLerp(originmins, 0.5, originmaxs, center);
1458 supercontents = CL_PointSuperContents(center);
1459 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1460 VectorSubtract(originmaxs, originmins, traildir);
1461 traillen = VectorLength(traildir);
1462 VectorNormalize(traildir);
1465 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1469 Vector4Set(avgtint, 1, 1, 1, 1);
1471 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1473 if (info->effectnameindex == effectnameindex)
1476 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1478 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1481 // spawn a dlight if requested
1482 if (info->lightradiusstart > 0 && spawndlight)
1484 matrix4x4_t tempmatrix;
1485 if (info->trailspacing > 0)
1486 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1488 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1489 if (info->lighttime > 0 && info->lightradiusfade > 0)
1491 // light flash (explosion, etc)
1492 // called when effect starts
1493 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);
1495 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1498 // called by CL_LinkNetworkEntity
1499 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1500 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1501 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1502 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1503 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);
1504 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1508 if (!spawnparticles)
1513 if (info->tex[1] > info->tex[0])
1515 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1516 tex = min(tex, info->tex[1] - 1);
1518 if(info->staintex[0] < 0)
1519 staintex = info->staintex[0];
1522 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1523 staintex = min(staintex, info->staintex[1] - 1);
1525 if (info->particletype == pt_decal)
1527 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1528 AnglesFromVectors(angles, velocity, NULL, false);
1529 AngleVectors(angles, forward, right, up);
1530 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1532 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]);
1534 else if (info->orientation == PARTICLE_HBEAM)
1536 AnglesFromVectors(angles, traildir, NULL, false);
1537 AngleVectors(angles, forward, right, up);
1538 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1540 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);
1544 if (!cl_particles.integer)
1546 switch (info->particletype)
1548 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1549 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1550 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1551 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1552 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1553 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1556 VectorCopy(originmins, trailpos);
1557 if (info->trailspacing > 0)
1559 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1560 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1561 immediatebloodstain = false;
1563 AnglesFromVectors(angles, traildir, NULL, false);
1564 AngleVectors(angles, forward, right, up);
1565 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1566 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1570 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1572 immediatebloodstain =
1573 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1575 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1577 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1578 AnglesFromVectors(angles, velocity, NULL, false);
1579 AngleVectors(angles, forward, right, up);
1580 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1581 VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1583 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1584 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1586 if (info->tex[1] > info->tex[0])
1588 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1589 tex = min(tex, info->tex[1] - 1);
1593 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1594 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1595 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1599 tintlerp = lhrandom(0, 1);
1600 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1603 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);
1604 if (immediatebloodstain && part)
1606 immediatebloodstain = false;
1607 CL_ImmediateBloodStain(part);
1610 VectorMA(trailpos, trailstep, traildir, trailpos);
1617 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1620 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)
1622 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1630 void CL_EntityParticles (const entity_t *ent)
1633 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1634 static vec3_t avelocities[NUMVERTEXNORMALS];
1635 if (!cl_particles.integer) return;
1636 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1638 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1640 if (!avelocities[0][0])
1641 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1642 avelocities[0][i] = lhrandom(0, 2.55);
1644 for (i = 0;i < NUMVERTEXNORMALS;i++)
1646 yaw = cl.time * avelocities[i][0];
1647 pitch = cl.time * avelocities[i][1];
1648 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1649 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1650 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1651 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);
1656 void CL_ReadPointFile_f (void)
1658 vec3_t org, leakorg;
1660 char *pointfile = NULL, *pointfilepos, *t, tchar;
1661 char name[MAX_QPATH];
1666 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1667 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1670 Con_Printf("Could not open %s\n", name);
1674 Con_Printf("Reading %s...\n", name);
1675 VectorClear(leakorg);
1678 pointfilepos = pointfile;
1679 while (*pointfilepos)
1681 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1686 while (*t && *t != '\n' && *t != '\r')
1690 #if _MSC_VER >= 1400
1691 #define sscanf sscanf_s
1693 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1699 VectorCopy(org, leakorg);
1702 if (cl.num_particles < cl.max_particles - 3)
1705 CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1708 Mem_Free(pointfile);
1709 VectorCopy(leakorg, org);
1710 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1712 CL_NewParticle(org, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1713 CL_NewParticle(org, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1714 CL_NewParticle(org, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1719 CL_ParseParticleEffect
1721 Parse an effect out of the server message
1724 void CL_ParseParticleEffect (void)
1727 int i, count, msgcount, color;
1729 MSG_ReadVector(&cl_message, org, cls.protocol);
1730 for (i=0 ; i<3 ; i++)
1731 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1732 msgcount = MSG_ReadByte(&cl_message);
1733 color = MSG_ReadByte(&cl_message);
1735 if (msgcount == 255)
1740 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1745 CL_ParticleExplosion
1749 void CL_ParticleExplosion (const vec3_t org)
1755 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1756 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1758 if (cl_particles_quake.integer)
1760 for (i = 0;i < 1024;i++)
1766 color = particlepalette[ramp1[r]];
1767 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);
1771 color = particlepalette[ramp2[r]];
1772 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);
1778 i = CL_PointSuperContents(org);
1779 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1781 if (cl_particles.integer && cl_particles_bubbles.integer)
1782 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1783 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);
1787 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1789 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1796 VectorMA(org, 128, v2, v);
1797 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1799 while (k < 16 && trace.fraction < 0.1f);
1800 VectorSubtract(trace.endpos, org, v2);
1801 VectorScale(v2, 2.0f, v2);
1802 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);
1808 if (cl_particles_explosions_shell.integer)
1809 R_NewExplosion(org);
1814 CL_ParticleExplosion2
1818 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1821 if (!cl_particles.integer) return;
1823 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1825 k = particlepalette[colorStart + (i % colorLength)];
1826 if (cl_particles_quake.integer)
1827 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);
1829 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);
1833 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1836 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1837 if (cl_particles_sparks.integer)
1839 sparkcount *= cl_particles_quality.value;
1840 while(sparkcount-- > 0)
1841 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);
1845 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1848 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1849 if (cl_particles_smoke.integer)
1851 smokecount *= cl_particles_quality.value;
1852 while(smokecount-- > 0)
1853 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);
1857 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)
1861 if (!cl_particles.integer) return;
1862 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1864 count = (int)(count * cl_particles_quality.value);
1867 k = particlepalette[colorbase + (rand()&3)];
1868 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);
1872 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1875 float minz, maxz, lifetime = 30;
1877 if (!cl_particles.integer) return;
1878 if (dir[2] < 0) // falling
1880 minz = maxs[2] + dir[2] * 0.1;
1883 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1888 maxz = maxs[2] + dir[2] * 0.1;
1890 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1893 count = (int)(count * cl_particles_quality.value);
1898 if (!cl_particles_rain.integer) break;
1899 count *= 4; // ick, this should be in the mod or maps?
1903 k = particlepalette[colorbase + (rand()&3)];
1904 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1905 if (gamemode == GAME_GOODVSBAD2)
1906 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);
1908 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);
1912 if (!cl_particles_snow.integer) break;
1915 k = particlepalette[colorbase + (rand()&3)];
1916 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1917 if (gamemode == GAME_GOODVSBAD2)
1918 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);
1920 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);
1924 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1928 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1929 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1930 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1931 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1932 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1933 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1935 #define PARTICLETEXTURESIZE 64
1936 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1938 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1942 dz = 1 - (dx*dx+dy*dy);
1943 if (dz > 0) // it does hit the sphere
1947 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1948 VectorNormalize(normal);
1949 dot = DotProduct(normal, light);
1950 if (dot > 0.5) // interior reflection
1951 f += ((dot * 2) - 1);
1952 else if (dot < -0.5) // exterior reflection
1953 f += ((dot * -2) - 1);
1955 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1956 VectorNormalize(normal);
1957 dot = DotProduct(normal, light);
1958 if (dot > 0.5) // interior reflection
1959 f += ((dot * 2) - 1);
1960 else if (dot < -0.5) // exterior reflection
1961 f += ((dot * -2) - 1);
1963 f += 16; // just to give it a haze so you can see the outline
1964 f = bound(0, f, 255);
1965 return (unsigned char) f;
1971 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1972 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1974 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1975 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1976 *width = particlefontcellwidth;
1977 *height = particlefontcellheight;
1980 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1982 int basex, basey, w, h, y;
1983 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1984 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1985 Sys_Error("invalid particle texture size for autogenerating");
1986 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1987 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1990 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1993 float cx, cy, dx, dy, f, iradius;
1995 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1996 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1997 iradius = 1.0f / radius;
1998 alpha *= (1.0f / 255.0f);
1999 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2001 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2005 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2010 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2011 d[0] += (int)(f * (blue - d[0]));
2012 d[1] += (int)(f * (green - d[1]));
2013 d[2] += (int)(f * (red - d[2]));
2020 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2023 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2025 data[0] = bound(minb, data[0], maxb);
2026 data[1] = bound(ming, data[1], maxg);
2027 data[2] = bound(minr, data[2], maxr);
2032 static void particletextureinvert(unsigned char *data)
2035 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2037 data[0] = 255 - data[0];
2038 data[1] = 255 - data[1];
2039 data[2] = 255 - data[2];
2043 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2044 static void R_InitBloodTextures (unsigned char *particletexturedata)
2047 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2048 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2051 for (i = 0;i < 8;i++)
2053 memset(data, 255, datasize);
2054 for (k = 0;k < 24;k++)
2055 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2056 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2057 particletextureinvert(data);
2058 setuptex(tex_bloodparticle[i], data, particletexturedata);
2062 for (i = 0;i < 8;i++)
2064 memset(data, 255, datasize);
2066 for (j = 1;j < 10;j++)
2067 for (k = min(j, m - 1);k < m;k++)
2068 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2069 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2070 particletextureinvert(data);
2071 setuptex(tex_blooddecal[i], data, particletexturedata);
2077 //uncomment this to make engine save out particle font to a tga file when run
2078 //#define DUMPPARTICLEFONT
2080 static void R_InitParticleTexture (void)
2082 int x, y, d, i, k, m;
2083 int basex, basey, w, h;
2084 float dx, dy, f, s1, t1, s2, t2;
2087 fs_offset_t filesize;
2088 char texturename[MAX_QPATH];
2091 // a note: decals need to modulate (multiply) the background color to
2092 // properly darken it (stain), and they need to be able to alpha fade,
2093 // this is a very difficult challenge because it means fading to white
2094 // (no change to background) rather than black (darkening everything
2095 // behind the whole decal polygon), and to accomplish this the texture is
2096 // inverted (dark red blood on white background becomes brilliant cyan
2097 // and white on black background) so we can alpha fade it to black, then
2098 // we invert it again during the blendfunc to make it work...
2100 #ifndef DUMPPARTICLEFONT
2101 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2104 particlefonttexture = decalskinframe->base;
2105 // TODO maybe allow custom grid size?
2106 particlefontwidth = image_width;
2107 particlefontheight = image_height;
2108 particlefontcellwidth = image_width / 8;
2109 particlefontcellheight = image_height / 8;
2110 particlefontcols = 8;
2111 particlefontrows = 8;
2116 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2117 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2118 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2119 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2120 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2122 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2123 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2124 particlefontcols = 8;
2125 particlefontrows = 8;
2127 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2130 for (i = 0;i < 8;i++)
2132 memset(data, 255, datasize);
2135 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2136 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2138 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2140 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2141 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2143 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2144 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2146 d = (int)(d * (1-(dx*dx+dy*dy)));
2147 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2148 d = bound(0, d, 255);
2149 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2156 setuptex(tex_smoke[i], data, particletexturedata);
2160 memset(data, 255, datasize);
2161 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2163 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2164 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2166 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2167 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2168 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2171 setuptex(tex_rainsplash, data, particletexturedata);
2174 memset(data, 255, datasize);
2175 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2177 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2178 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2180 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2181 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2182 d = bound(0, d, 255);
2183 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2186 setuptex(tex_particle, data, particletexturedata);
2189 memset(data, 255, datasize);
2190 light[0] = 1;light[1] = 1;light[2] = 1;
2191 VectorNormalize(light);
2192 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2194 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2195 // stretch upper half of bubble by +50% and shrink lower half by -50%
2196 // (this gives an elongated teardrop shape)
2198 dy = (dy - 0.5f) * 2.0f;
2200 dy = (dy - 0.5f) / 1.5f;
2201 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2203 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2204 // shrink bubble width to half
2206 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2209 setuptex(tex_raindrop, data, particletexturedata);
2212 memset(data, 255, datasize);
2213 light[0] = 1;light[1] = 1;light[2] = 1;
2214 VectorNormalize(light);
2215 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2217 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2218 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2220 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2221 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2224 setuptex(tex_bubble, data, particletexturedata);
2226 // Blood particles and blood decals
2227 R_InitBloodTextures (particletexturedata);
2230 for (i = 0;i < 8;i++)
2232 memset(data, 255, datasize);
2233 for (k = 0;k < 12;k++)
2234 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2235 for (k = 0;k < 3;k++)
2236 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2237 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2238 particletextureinvert(data);
2239 setuptex(tex_bulletdecal[i], data, particletexturedata);
2242 #ifdef DUMPPARTICLEFONT
2243 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2246 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2247 particlefonttexture = decalskinframe->base;
2249 Mem_Free(particletexturedata);
2254 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2256 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2257 particletexture[i].texture = particlefonttexture;
2258 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2259 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2260 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2261 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2264 #ifndef DUMPPARTICLEFONT
2265 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2266 if (!particletexture[tex_beam].texture)
2269 unsigned char noise3[64][64], data2[64][16][4];
2271 fractalnoise(&noise3[0][0], 64, 4);
2273 for (y = 0;y < 64;y++)
2275 dy = (y - 0.5f*64) / (64*0.5f-1);
2276 for (x = 0;x < 16;x++)
2278 dx = (x - 0.5f*16) / (16*0.5f-2);
2279 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2280 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2281 data2[y][x][3] = 255;
2285 #ifdef DUMPPARTICLEFONT
2286 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2288 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2290 particletexture[tex_beam].s1 = 0;
2291 particletexture[tex_beam].t1 = 0;
2292 particletexture[tex_beam].s2 = 1;
2293 particletexture[tex_beam].t2 = 1;
2295 // now load an texcoord/texture override file
2296 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2303 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2305 if(!strcmp(com_token, "\n"))
2306 continue; // empty line
2307 i = atoi(com_token);
2315 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2317 strlcpy(texturename, com_token, sizeof(texturename));
2318 s1 = atof(com_token);
2319 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2322 t1 = atof(com_token);
2323 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2325 s2 = atof(com_token);
2326 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2328 t2 = atof(com_token);
2329 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2330 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2331 strlcpy(texturename, com_token, sizeof(texturename));
2338 if (!texturename[0])
2340 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2343 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2345 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2348 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2351 // R_SkinFrame_LoadExternal already complained
2354 particletexture[i].texture = sf->base;
2355 particletexture[i].s1 = s1;
2356 particletexture[i].t1 = t1;
2357 particletexture[i].s2 = s2;
2358 particletexture[i].t2 = t2;
2364 static void r_part_start(void)
2367 // generate particlepalette for convenience from the main one
2368 for (i = 0;i < 256;i++)
2369 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2370 particletexturepool = R_AllocTexturePool();
2371 R_InitParticleTexture ();
2372 CL_Particles_LoadEffectInfo();
2375 static void r_part_shutdown(void)
2377 R_FreeTexturePool(&particletexturepool);
2380 static void r_part_newmap(void)
2383 R_SkinFrame_MarkUsed(decalskinframe);
2384 CL_Particles_LoadEffectInfo();
2387 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2388 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2390 void R_Particles_Init (void)
2393 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2395 particle_elements[i*6+0] = i*4+0;
2396 particle_elements[i*6+1] = i*4+1;
2397 particle_elements[i*6+2] = i*4+2;
2398 particle_elements[i*6+3] = i*4+0;
2399 particle_elements[i*6+4] = i*4+2;
2400 particle_elements[i*6+5] = i*4+3;
2403 Cvar_RegisterVariable(&r_drawparticles);
2404 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2405 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2406 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2407 Cvar_RegisterVariable(&r_drawdecals);
2408 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2409 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2412 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2414 int surfacelistindex;
2416 float *v3f, *t2f, *c4f;
2417 particletexture_t *tex;
2418 float right[3], up[3], size, ca;
2419 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2421 RSurf_ActiveWorldEntity();
2423 r_refdef.stats.drawndecals += numsurfaces;
2424 // R_Mesh_ResetTextureState();
2425 GL_DepthMask(false);
2426 GL_DepthRange(0, 1);
2427 GL_PolygonOffset(0, 0);
2429 GL_CullFace(GL_NONE);
2431 // generate all the vertices at once
2432 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2434 d = cl.decals + surfacelist[surfacelistindex];
2437 c4f = particle_color4f + 16*surfacelistindex;
2438 ca = d->alpha * alphascale;
2439 // ensure alpha multiplier saturates properly
2440 if (ca > 1.0f / 256.0f)
2442 if (r_refdef.fogenabled)
2443 ca *= RSurf_FogVertex(d->org);
2444 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2445 Vector4Copy(c4f, c4f + 4);
2446 Vector4Copy(c4f, c4f + 8);
2447 Vector4Copy(c4f, c4f + 12);
2449 // calculate vertex positions
2450 size = d->size * cl_particles_size.value;
2451 VectorVectors(d->normal, right, up);
2452 VectorScale(right, size, right);
2453 VectorScale(up, size, up);
2454 v3f = particle_vertex3f + 12*surfacelistindex;
2455 v3f[ 0] = d->org[0] - right[0] - up[0];
2456 v3f[ 1] = d->org[1] - right[1] - up[1];
2457 v3f[ 2] = d->org[2] - right[2] - up[2];
2458 v3f[ 3] = d->org[0] - right[0] + up[0];
2459 v3f[ 4] = d->org[1] - right[1] + up[1];
2460 v3f[ 5] = d->org[2] - right[2] + up[2];
2461 v3f[ 6] = d->org[0] + right[0] + up[0];
2462 v3f[ 7] = d->org[1] + right[1] + up[1];
2463 v3f[ 8] = d->org[2] + right[2] + up[2];
2464 v3f[ 9] = d->org[0] + right[0] - up[0];
2465 v3f[10] = d->org[1] + right[1] - up[1];
2466 v3f[11] = d->org[2] + right[2] - up[2];
2468 // calculate texcoords
2469 tex = &particletexture[d->texnum];
2470 t2f = particle_texcoord2f + 8*surfacelistindex;
2471 t2f[0] = tex->s1;t2f[1] = tex->t2;
2472 t2f[2] = tex->s1;t2f[3] = tex->t1;
2473 t2f[4] = tex->s2;t2f[5] = tex->t1;
2474 t2f[6] = tex->s2;t2f[7] = tex->t2;
2477 // now render the decals all at once
2478 // (this assumes they all use one particle font texture!)
2479 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2480 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2481 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2482 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2485 void R_DrawDecals (void)
2488 int drawdecals = r_drawdecals.integer;
2493 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2495 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2496 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2498 // LordHavoc: early out conditions
2502 decalfade = frametime * 256 / cl_decals_fadetime.value;
2503 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2504 drawdist2 = drawdist2*drawdist2;
2506 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2508 if (!decal->typeindex)
2511 if (killsequence - decal->decalsequence > 0)
2514 if (cl.time > decal->time2 + cl_decals_time.value)
2516 decal->alpha -= decalfade;
2517 if (decal->alpha <= 0)
2523 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2525 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2526 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2532 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2538 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))
2539 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2542 decal->typeindex = 0;
2543 if (cl.free_decal > i)
2547 // reduce cl.num_decals if possible
2548 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2551 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2553 decal_t *olddecals = cl.decals;
2554 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2555 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2556 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2557 Mem_Free(olddecals);
2560 r_refdef.stats.totaldecals = cl.num_decals;
2563 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2565 int surfacelistindex;
2566 int batchstart, batchcount;
2567 const particle_t *p;
2569 rtexture_t *texture;
2570 float *v3f, *t2f, *c4f;
2571 particletexture_t *tex;
2572 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2573 // float ambient[3], diffuse[3], diffusenormal[3];
2574 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2575 vec4_t colormultiplier;
2576 float minparticledist_start, minparticledist_end;
2579 RSurf_ActiveWorldEntity();
2581 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));
2583 r_refdef.stats.particles += numsurfaces;
2584 // R_Mesh_ResetTextureState();
2585 GL_DepthMask(false);
2586 GL_DepthRange(0, 1);
2587 GL_PolygonOffset(0, 0);
2589 GL_CullFace(GL_NONE);
2591 spintime = r_refdef.scene.time;
2593 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2594 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2595 dofade = (minparticledist_start < minparticledist_end);
2597 // first generate all the vertices at once
2598 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2600 p = cl.particles + surfacelist[surfacelistindex];
2602 blendmode = (pblend_t)p->blendmode;
2604 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2605 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2606 alpha = palpha * colormultiplier[3];
2607 // ensure alpha multiplier saturates properly
2613 case PBLEND_INVALID:
2615 // additive and modulate can just fade out in fog (this is correct)
2616 if (r_refdef.fogenabled)
2617 alpha *= RSurf_FogVertex(p->org);
2618 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2619 alpha *= 1.0f / 256.0f;
2620 c4f[0] = p->color[0] * alpha;
2621 c4f[1] = p->color[1] * alpha;
2622 c4f[2] = p->color[2] * alpha;
2626 // additive and modulate can just fade out in fog (this is correct)
2627 if (r_refdef.fogenabled)
2628 alpha *= RSurf_FogVertex(p->org);
2629 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2630 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2631 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2632 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2636 c4f[0] = p->color[0] * colormultiplier[0];
2637 c4f[1] = p->color[1] * colormultiplier[1];
2638 c4f[2] = p->color[2] * colormultiplier[2];
2640 // note: lighting is not cheap!
2641 if (particletype[p->typeindex].lighting)
2642 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2643 // mix in the fog color
2644 if (r_refdef.fogenabled)
2646 fog = RSurf_FogVertex(p->org);
2648 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2649 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2650 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2652 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2653 VectorScale(c4f, alpha, c4f);
2656 // copy the color into the other three vertices
2657 Vector4Copy(c4f, c4f + 4);
2658 Vector4Copy(c4f, c4f + 8);
2659 Vector4Copy(c4f, c4f + 12);
2661 size = p->size * cl_particles_size.value;
2662 tex = &particletexture[p->texnum];
2663 switch(p->orientation)
2665 // case PARTICLE_INVALID:
2666 case PARTICLE_BILLBOARD:
2667 if (p->angle + p->spin)
2669 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2670 spinsin = sin(spinrad) * size;
2671 spincos = cos(spinrad) * size;
2672 spinm1 = -p->stretch * spincos;
2675 spinm4 = -p->stretch * spincos;
2676 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2677 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2681 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2682 VectorScale(r_refdef.view.up, size, up);
2685 v3f[ 0] = p->org[0] - right[0] - up[0];
2686 v3f[ 1] = p->org[1] - right[1] - up[1];
2687 v3f[ 2] = p->org[2] - right[2] - up[2];
2688 v3f[ 3] = p->org[0] - right[0] + up[0];
2689 v3f[ 4] = p->org[1] - right[1] + up[1];
2690 v3f[ 5] = p->org[2] - right[2] + up[2];
2691 v3f[ 6] = p->org[0] + right[0] + up[0];
2692 v3f[ 7] = p->org[1] + right[1] + up[1];
2693 v3f[ 8] = p->org[2] + right[2] + up[2];
2694 v3f[ 9] = p->org[0] + right[0] - up[0];
2695 v3f[10] = p->org[1] + right[1] - up[1];
2696 v3f[11] = p->org[2] + right[2] - up[2];
2697 t2f[0] = tex->s1;t2f[1] = tex->t2;
2698 t2f[2] = tex->s1;t2f[3] = tex->t1;
2699 t2f[4] = tex->s2;t2f[5] = tex->t1;
2700 t2f[6] = tex->s2;t2f[7] = tex->t2;
2702 case PARTICLE_ORIENTED_DOUBLESIDED:
2703 VectorVectors(p->vel, baseright, baseup);
2704 if (p->angle + p->spin)
2706 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2707 spinsin = sin(spinrad) * size;
2708 spincos = cos(spinrad) * size;
2709 spinm1 = p->stretch * spincos;
2712 spinm4 = p->stretch * spincos;
2713 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2714 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2718 VectorScale(baseright, size * p->stretch, right);
2719 VectorScale(baseup, size, up);
2721 v3f[ 0] = p->org[0] - right[0] - up[0];
2722 v3f[ 1] = p->org[1] - right[1] - up[1];
2723 v3f[ 2] = p->org[2] - right[2] - up[2];
2724 v3f[ 3] = p->org[0] - right[0] + up[0];
2725 v3f[ 4] = p->org[1] - right[1] + up[1];
2726 v3f[ 5] = p->org[2] - right[2] + up[2];
2727 v3f[ 6] = p->org[0] + right[0] + up[0];
2728 v3f[ 7] = p->org[1] + right[1] + up[1];
2729 v3f[ 8] = p->org[2] + right[2] + up[2];
2730 v3f[ 9] = p->org[0] + right[0] - up[0];
2731 v3f[10] = p->org[1] + right[1] - up[1];
2732 v3f[11] = p->org[2] + right[2] - up[2];
2733 t2f[0] = tex->s1;t2f[1] = tex->t2;
2734 t2f[2] = tex->s1;t2f[3] = tex->t1;
2735 t2f[4] = tex->s2;t2f[5] = tex->t1;
2736 t2f[6] = tex->s2;t2f[7] = tex->t2;
2738 case PARTICLE_SPARK:
2739 len = VectorLength(p->vel);
2740 VectorNormalize2(p->vel, up);
2741 lenfactor = p->stretch * 0.04 * len;
2742 if(lenfactor < size * 0.5)
2743 lenfactor = size * 0.5;
2744 VectorMA(p->org, -lenfactor, up, v);
2745 VectorMA(p->org, lenfactor, up, up2);
2746 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2747 t2f[0] = tex->s1;t2f[1] = tex->t2;
2748 t2f[2] = tex->s1;t2f[3] = tex->t1;
2749 t2f[4] = tex->s2;t2f[5] = tex->t1;
2750 t2f[6] = tex->s2;t2f[7] = tex->t2;
2752 case PARTICLE_VBEAM:
2753 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2754 VectorSubtract(p->vel, p->org, up);
2755 VectorNormalize(up);
2756 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2757 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2758 t2f[0] = tex->s2;t2f[1] = v[0];
2759 t2f[2] = tex->s1;t2f[3] = v[0];
2760 t2f[4] = tex->s1;t2f[5] = v[1];
2761 t2f[6] = tex->s2;t2f[7] = v[1];
2763 case PARTICLE_HBEAM:
2764 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2765 VectorSubtract(p->vel, p->org, up);
2766 VectorNormalize(up);
2767 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2768 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2769 t2f[0] = v[0];t2f[1] = tex->t1;
2770 t2f[2] = v[0];t2f[3] = tex->t2;
2771 t2f[4] = v[1];t2f[5] = tex->t2;
2772 t2f[6] = v[1];t2f[7] = tex->t1;
2777 // now render batches of particles based on blendmode and texture
2778 blendmode = PBLEND_INVALID;
2782 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2783 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2785 p = cl.particles + surfacelist[surfacelistindex];
2787 if (texture != particletexture[p->texnum].texture)
2789 texture = particletexture[p->texnum].texture;
2790 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2793 if (p->blendmode == PBLEND_INVMOD)
2795 // inverse modulate blend - group these
2796 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2797 // iterate until we find a change in settings
2798 batchstart = surfacelistindex++;
2799 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2801 p = cl.particles + surfacelist[surfacelistindex];
2802 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2808 // additive or alpha blend - group these
2809 // (we can group these because we premultiplied the texture alpha)
2810 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2811 // iterate until we find a change in settings
2812 batchstart = surfacelistindex++;
2813 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2815 p = cl.particles + surfacelist[surfacelistindex];
2816 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2821 batchcount = surfacelistindex - batchstart;
2822 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2826 void R_DrawParticles (void)
2829 int drawparticles = r_drawparticles.integer;
2830 float minparticledist_start;
2832 float gravity, frametime, f, dist, oldorg[3];
2838 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2839 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2841 // LordHavoc: early out conditions
2842 if (!cl.num_particles)
2845 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2846 gravity = frametime * cl.movevars_gravity;
2847 update = frametime > 0;
2848 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2849 drawdist2 = drawdist2*drawdist2;
2851 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2855 if (cl.free_particle > i)
2856 cl.free_particle = i;
2862 if (p->delayedspawn > cl.time)
2865 p->size += p->sizeincrease * frametime;
2866 p->alpha -= p->alphafade * frametime;
2868 if (p->alpha <= 0 || p->die <= cl.time)
2871 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2873 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2875 if (p->typeindex == pt_blood)
2876 p->size += frametime * 8;
2878 p->vel[2] -= p->gravity * gravity;
2879 f = 1.0f - min(p->liquidfriction * frametime, 1);
2880 VectorScale(p->vel, f, p->vel);
2884 p->vel[2] -= p->gravity * gravity;
2887 f = 1.0f - min(p->airfriction * frametime, 1);
2888 VectorScale(p->vel, f, p->vel);
2892 VectorCopy(p->org, oldorg);
2893 VectorMA(p->org, frametime, p->vel, p->org);
2894 // if (p->bounce && cl.time >= p->delayedcollisions)
2895 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2897 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);
2898 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2899 // or if the trace hit something flagged as NOIMPACT
2900 // then remove the particle
2901 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2903 VectorCopy(trace.endpos, p->org);
2904 // react if the particle hit something
2905 if (trace.fraction < 1)
2907 VectorCopy(trace.endpos, p->org);
2909 if (p->staintexnum >= 0)
2911 // blood - splash on solid
2912 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2915 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2916 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2917 if (cl_decals.integer)
2919 // create a decal for the blood splat
2920 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2921 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2926 if (p->typeindex == pt_blood)
2928 // blood - splash on solid
2929 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2931 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2933 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)));
2934 if (cl_decals.integer)
2936 // create a decal for the blood splat
2937 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);
2942 else if (p->bounce < 0)
2944 // bounce -1 means remove on impact
2949 // anything else - bounce off solid
2950 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2951 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2956 if (VectorLength2(p->vel) < 0.03)
2958 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2960 VectorClear(p->vel);
2964 if (p->typeindex != pt_static)
2966 switch (p->typeindex)
2968 case pt_entityparticle:
2969 // particle that removes itself after one rendered frame
2976 a = CL_PointSuperContents(p->org);
2977 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2981 a = CL_PointSuperContents(p->org);
2982 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2986 a = CL_PointSuperContents(p->org);
2987 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2991 if (cl.time > p->time2)
2994 p->time2 = cl.time + (rand() & 3) * 0.1;
2995 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2996 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2998 a = CL_PointSuperContents(p->org);
2999 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3007 else if (p->delayedspawn > cl.time)
3011 // don't render particles too close to the view (they chew fillrate)
3012 // also don't render particles behind the view (useless)
3013 // further checks to cull to the frustum would be too slow here
3014 switch(p->typeindex)
3017 // beams have no culling
3018 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3021 if(cl_particles_visculling.integer)
3022 if (!r_refdef.viewcache.world_novis)
3023 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3025 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3027 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3030 // anything else just has to be in front of the viewer and visible at this distance
3031 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3032 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3039 if (cl.free_particle > i)
3040 cl.free_particle = i;
3043 // reduce cl.num_particles if possible
3044 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3047 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3049 particle_t *oldparticles = cl.particles;
3050 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3051 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3052 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3053 Mem_Free(oldparticles);