2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float relativeoriginoffset[3];
105 float velocityoffset[3];
106 float relativevelocityoffset[3];
107 float originjitter[3];
108 float velocityjitter[3];
109 float velocitymultiplier;
110 // an effect can also spawn a dlight
111 float lightradiusstart;
112 float lightradiusfade;
115 qboolean lightshadow;
117 float lightcorona[2];
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
123 float rotate[4]; // min/max base angle, min/max rotation over time
125 particleeffectinfo_t;
127 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
129 int numparticleeffectinfo;
130 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
132 static int particlepalette[256];
134 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
135 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
136 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
137 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
138 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
139 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
140 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
141 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
142 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
143 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
144 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
145 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
146 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
147 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
148 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
149 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
150 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
151 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
152 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
153 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
154 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
155 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
156 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
157 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
158 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
159 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
160 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
161 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
162 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
163 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
164 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
165 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
168 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
169 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
170 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
172 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
174 // particletexture_t is a rectangle in the particlefonttexture
175 typedef struct particletexture_s
178 float s1, t1, s2, t2;
182 static rtexturepool_t *particletexturepool;
183 static rtexture_t *particlefonttexture;
184 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 skinframe_t *decalskinframe;
187 // texture numbers in particle font
188 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
189 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
190 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
191 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
192 static const int tex_rainsplash = 32;
193 static const int tex_particle = 63;
194 static const int tex_bubble = 62;
195 static const int tex_raindrop = 61;
196 static const int tex_beam = 60;
198 particleeffectinfo_t baselineparticleeffectinfo =
200 0, //int effectnameindex; // which effect this belongs to
201 // PARTICLEEFFECT_* bits
203 // blood effects may spawn very few particles, so proper fraction-overflow
204 // handling is very important, this variable keeps track of the fraction
205 0.0, //double particleaccumulator;
206 // the math is: countabsolute + requestedcount * countmultiplier * quality
207 // absolute number of particles to spawn, often used for decals
208 // (unaffected by quality and requestedcount)
209 0.0f, //float countabsolute;
210 // multiplier for the number of particles CL_ParticleEffect was told to
211 // spawn, most effects do not really have a count and hence use 1, so
212 // this is often the actual count to spawn, not merely a multiplier
213 0.0f, //float countmultiplier;
214 // if > 0 this causes the particle to spawn in an evenly spaced line from
215 // originmins to originmaxs (causing them to describe a trail, not a box)
216 0.0f, //float trailspacing;
217 // type of particle to spawn (defines some aspects of behavior)
218 pt_alphastatic, //ptype_t particletype;
219 // blending mode used on this particle type
220 PBLEND_ALPHA, //pblend_t blendmode;
221 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
222 PARTICLE_BILLBOARD, //porientation_t orientation;
223 // range of colors to choose from in hex RRGGBB (like HTML color tags),
224 // randomly interpolated at spawn
225 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
226 // a random texture is chosen in this range (note the second value is one
227 // past the last choosable, so for example 8,16 chooses any from 8 up and
229 // if start and end of the range are the same, no randomization is done
230 {63, 63 /* tex_particle */}, //int tex[2];
231 // range of size values randomly chosen when spawning, plus size increase over time
232 {1, 1, 0.0f}, //float size[3];
233 // range of alpha values randomly chosen when spawning, plus alpha fade
234 {0.0f, 256.0f, 256.0f}, //float alpha[3];
235 // how long the particle should live (note it is also removed if alpha drops to 0)
236 {16777216.0f, 16777216.0f}, //float time[2];
237 // how much gravity affects this particle (negative makes it fly up!)
238 0.0f, //float gravity;
239 // how much bounce the particle has when it hits a surface
240 // if negative the particle is removed on impact
241 0.0f, //float bounce;
242 // if in air this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float airfriction;
245 // if in liquid (water/slime/lava) this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float liquidfriction;
248 // these offsets are added to the values given to particleeffect(), and
249 // then an ellipsoid-shaped jitter is added as defined by these
250 // (they are the 3 radii)
251 1.0f, //float stretchfactor;
252 // stretch velocity factor (used for sparks)
253 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
259 0.0f, //float velocitymultiplier;
260 // an effect can also spawn a dlight
261 0.0f, //float lightradiusstart;
262 0.0f, //float lightradiusfade;
263 16777216.0f, //float lighttime;
264 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
265 true, //qboolean lightshadow;
266 0, //int lightcubemapnum;
267 {1.0f, 0.25f}, //float lightcorona[2];
268 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
269 {-1, -1}, //int staintex[2];
270 {1.0f, 1.0f}, //float stainalpha[2];
271 {2.0f, 2.0f}, //float stainsize[2];
273 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
276 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
277 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
278 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
279 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
280 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
281 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
282 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
283 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
284 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
285 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
286 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
287 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
288 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
289 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
290 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
291 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
292 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
293 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
294 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
295 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
296 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
297 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
298 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
299 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
300 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
301 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
302 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
303 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
304 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
305 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
306 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
307 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
308 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
311 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
316 particleeffectinfo_t *info = NULL;
317 const char *text = textstart;
319 for (linenumber = 1;;linenumber++)
322 for (arrayindex = 0;arrayindex < 16;arrayindex++)
323 argv[arrayindex][0] = 0;
326 if (!COM_ParseToken_Simple(&text, true, false, true))
328 if (!strcmp(com_token, "\n"))
332 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
338 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
339 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
340 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
341 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
342 #define readfloat(var) checkparms(2);var = atof(argv[1])
343 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
344 if (!strcmp(argv[0], "effect"))
348 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
350 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
353 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
355 if (particleeffectname[effectnameindex][0])
357 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
362 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
366 // if we run out of names, abort
367 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
369 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
372 info = particleeffectinfo + numparticleeffectinfo++;
373 // copy entire info from baseline, then fix up the nameindex
374 *info = baselineparticleeffectinfo;
375 info->effectnameindex = effectnameindex;
377 else if (info == NULL)
379 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
382 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
383 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
384 else if (!strcmp(argv[0], "type"))
387 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
388 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
389 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
390 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
391 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
392 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
393 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
394 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
395 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
396 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
397 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
398 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
399 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
400 info->blendmode = particletype[info->particletype].blendmode;
401 info->orientation = particletype[info->particletype].orientation;
403 else if (!strcmp(argv[0], "blend"))
406 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
407 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
408 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
409 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
411 else if (!strcmp(argv[0], "orientation"))
414 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
415 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
416 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
417 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
418 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
420 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
421 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
422 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
423 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
424 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
425 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
426 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
427 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
428 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
429 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
430 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
431 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
432 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
433 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
434 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
435 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
436 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
437 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
438 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
439 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
440 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
441 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
442 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
443 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
444 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
445 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
446 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
447 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
448 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
449 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
450 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
451 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
452 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
453 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
455 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
464 int CL_ParticleEffectIndexForName(const char *name)
467 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
468 if (!strcmp(particleeffectname[i], name))
473 const char *CL_ParticleEffectNameForIndex(int i)
475 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
477 return particleeffectname[i];
480 // MUST match effectnameindex_t in client.h
481 static const char *standardeffectnames[EFFECT_TOTAL] =
505 "TE_TEI_BIGEXPLOSION",
521 static void CL_Particles_LoadEffectInfo(const char *customfile)
525 unsigned char *filedata;
526 fs_offset_t filesize;
527 char filename[MAX_QPATH];
528 numparticleeffectinfo = 0;
529 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
530 memset(particleeffectname, 0, sizeof(particleeffectname));
531 for (i = 0;i < EFFECT_TOTAL;i++)
532 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
533 for (filepass = 0;;filepass++)
538 dpsnprintf(filename, sizeof(filename), customfile);
540 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
542 else if (filepass == 1)
544 if (!cl.worldbasename[0] || customfile)
546 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
550 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
553 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
558 void CL_Particles_LoadEffectInfo_f(void)
560 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
568 void CL_ReadPointFile_f (void);
569 void CL_Particles_Init (void)
571 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
572 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
574 Cvar_RegisterVariable (&cl_particles);
575 Cvar_RegisterVariable (&cl_particles_quality);
576 Cvar_RegisterVariable (&cl_particles_alpha);
577 Cvar_RegisterVariable (&cl_particles_size);
578 Cvar_RegisterVariable (&cl_particles_quake);
579 Cvar_RegisterVariable (&cl_particles_blood);
580 Cvar_RegisterVariable (&cl_particles_blood_alpha);
581 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
582 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
584 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
585 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
586 Cvar_RegisterVariable (&cl_particles_explosions_shell);
587 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
588 Cvar_RegisterVariable (&cl_particles_rain);
589 Cvar_RegisterVariable (&cl_particles_snow);
590 Cvar_RegisterVariable (&cl_particles_smoke);
591 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
592 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
593 Cvar_RegisterVariable (&cl_particles_sparks);
594 Cvar_RegisterVariable (&cl_particles_bubbles);
595 Cvar_RegisterVariable (&cl_particles_visculling);
596 Cvar_RegisterVariable (&cl_particles_collisions);
597 Cvar_RegisterVariable (&cl_decals);
598 Cvar_RegisterVariable (&cl_decals_visculling);
599 Cvar_RegisterVariable (&cl_decals_time);
600 Cvar_RegisterVariable (&cl_decals_fadetime);
601 Cvar_RegisterVariable (&cl_decals_newsystem);
602 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
603 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
604 Cvar_RegisterVariable (&cl_decals_models);
605 Cvar_RegisterVariable (&cl_decals_bias);
606 Cvar_RegisterVariable (&cl_decals_max);
609 void CL_Particles_Shutdown (void)
613 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
614 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
616 // list of all 26 parameters:
617 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
618 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
619 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
620 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
621 // palpha - opacity of particle as 0-255 (can be more than 255)
622 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
623 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
624 // pgravity - how much effect gravity has on the particle (0-1)
625 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
626 // px,py,pz - starting origin of particle
627 // pvx,pvy,pvz - starting velocity of particle
628 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
629 // blendmode - one of the PBLEND_ values
630 // orientation - one of the PARTICLE_ values
631 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
632 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
633 // stainalpha: opacity of the stain as factor for alpha
634 // stainsize: size of the stain as factor for palpha
635 // angle: base rotation of the particle geometry around its center normal
636 // spin: rotation speed of the particle geometry around its center normal
637 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
642 if (!cl_particles.integer)
644 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
645 if (cl.free_particle >= cl.max_particles)
648 lifetime = palpha / min(1, palphafade);
649 part = &cl.particles[cl.free_particle++];
650 if (cl.num_particles < cl.free_particle)
651 cl.num_particles = cl.free_particle;
652 memset(part, 0, sizeof(*part));
653 VectorCopy(sortorigin, part->sortorigin);
654 part->typeindex = ptypeindex;
655 part->blendmode = blendmode;
656 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
658 particletexture_t *tex = &particletexture[ptex];
659 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
660 part->orientation = PARTICLE_VBEAM;
662 part->orientation = PARTICLE_HBEAM;
665 part->orientation = orientation;
666 l2 = (int)lhrandom(0.5, 256.5);
668 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
669 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
670 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
673 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
674 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
675 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
677 part->alpha = palpha;
678 part->alphafade = palphafade;
679 part->staintexnum = staintex;
680 if(staincolor1 >= 0 && staincolor2 >= 0)
682 l2 = (int)lhrandom(0.5, 256.5);
684 if(blendmode == PBLEND_INVMOD)
686 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
687 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
688 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
692 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
693 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
694 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
696 if(r > 0xFF) r = 0xFF;
697 if(g > 0xFF) g = 0xFF;
698 if(b > 0xFF) b = 0xFF;
702 r = part->color[0]; // -1 is shorthand for stain = particle color
706 part->staincolor[0] = r;
707 part->staincolor[1] = g;
708 part->staincolor[2] = b;
709 part->stainalpha = palpha * stainalpha;
710 part->stainsize = psize * stainsize;
713 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
715 part->color[0] *= tint[0];
716 part->color[1] *= tint[1];
717 part->color[2] *= tint[2];
719 part->alpha *= tint[3];
720 part->alphafade *= tint[3];
721 part->stainalpha *= tint[3];
725 part->sizeincrease = psizeincrease;
726 part->gravity = pgravity;
727 part->bounce = pbounce;
728 part->stretch = stretch;
730 part->org[0] = px + originjitter * v[0];
731 part->org[1] = py + originjitter * v[1];
732 part->org[2] = pz + originjitter * v[2];
733 part->vel[0] = pvx + velocityjitter * v[0];
734 part->vel[1] = pvy + velocityjitter * v[1];
735 part->vel[2] = pvz + velocityjitter * v[2];
737 part->airfriction = pairfriction;
738 part->liquidfriction = pliquidfriction;
739 part->die = cl.time + lifetime;
740 part->delayedspawn = cl.time;
741 // part->delayedcollisions = 0;
742 part->qualityreduction = pqualityreduction;
745 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
746 if (part->typeindex == pt_rain)
750 float lifetime = part->die - cl.time;
753 // turn raindrop into simple spark and create delayedspawn splash effect
754 part->typeindex = pt_spark;
756 VectorMA(part->org, lifetime, part->vel, endvec);
757 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
758 part->die = cl.time + lifetime * trace.fraction;
759 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
762 part2->delayedspawn = part->die;
763 part2->die += part->die - cl.time;
764 for (i = rand() & 7;i < 10;i++)
766 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
769 part2->delayedspawn = part->die;
770 part2->die += part->die - cl.time;
776 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
778 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
781 VectorMA(part->org, lifetime, part->vel, endvec);
782 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
783 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
790 static void CL_ImmediateBloodStain(particle_t *part)
795 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
796 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
798 VectorCopy(part->vel, v);
800 staintex = part->staintexnum;
801 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
804 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
805 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
807 VectorCopy(part->vel, v);
809 staintex = tex_blooddecal[rand()&7];
810 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
814 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
818 entity_render_t *ent = &cl.entities[hitent].render;
819 unsigned char color[3];
820 if (!cl_decals.integer)
822 if (!ent->allowdecals)
825 l2 = (int)lhrandom(0.5, 256.5);
827 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
828 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
829 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
831 if (cl_decals_newsystem.integer)
834 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
836 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
840 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
841 if (cl.free_decal >= cl.max_decals)
843 decal = &cl.decals[cl.free_decal++];
844 if (cl.num_decals < cl.free_decal)
845 cl.num_decals = cl.free_decal;
846 memset(decal, 0, sizeof(*decal));
847 decal->decalsequence = cl.decalsequence++;
848 decal->typeindex = pt_decal;
849 decal->texnum = texnum;
850 VectorMA(org, cl_decals_bias.value, normal, decal->org);
851 VectorCopy(normal, decal->normal);
853 decal->alpha = alpha;
854 decal->time2 = cl.time;
855 decal->color[0] = color[0];
856 decal->color[1] = color[1];
857 decal->color[2] = color[2];
860 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
861 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
862 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
864 decal->owner = hitent;
865 decal->clusterindex = -1000; // no vis culling unless we're sure
868 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
869 decal->ownermodel = cl.entities[decal->owner].render.model;
870 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
871 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
875 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
877 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
879 decal->clusterindex = leaf->clusterindex;
884 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
887 float bestfrac, bestorg[3], bestnormal[3];
889 int besthitent = 0, hitent;
892 for (i = 0;i < 32;i++)
895 VectorMA(org, maxdist, org2, org2);
896 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
897 // take the closest trace result that doesn't end up hitting a NOMARKS
898 // surface (sky for example)
899 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
901 bestfrac = trace.fraction;
903 VectorCopy(trace.endpos, bestorg);
904 VectorCopy(trace.plane.normal, bestnormal);
908 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
911 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
912 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
913 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)
916 matrix4x4_t tempmatrix;
918 VectorLerp(originmins, 0.5, originmaxs, center);
919 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
920 if (effectnameindex == EFFECT_SVC_PARTICLE)
922 if (cl_particles.integer)
924 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
926 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
927 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
928 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
931 count *= cl_particles_quality.value;
932 for (;count > 0;count--)
934 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
935 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);
940 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
941 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
942 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
943 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
944 else if (effectnameindex == EFFECT_TE_SPIKE)
946 if (cl_particles_bulletimpacts.integer)
948 if (cl_particles_quake.integer)
950 if (cl_particles_smoke.integer)
951 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
955 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
956 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
957 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);
961 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
962 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
964 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
966 if (cl_particles_bulletimpacts.integer)
968 if (cl_particles_quake.integer)
970 if (cl_particles_smoke.integer)
971 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
975 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
976 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
977 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);
981 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
982 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
983 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);
985 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
987 if (cl_particles_bulletimpacts.integer)
989 if (cl_particles_quake.integer)
991 if (cl_particles_smoke.integer)
992 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
996 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
997 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
998 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);
1002 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1003 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1005 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1007 if (cl_particles_bulletimpacts.integer)
1009 if (cl_particles_quake.integer)
1011 if (cl_particles_smoke.integer)
1012 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1016 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1017 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1018 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);
1022 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1023 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1024 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);
1026 else if (effectnameindex == EFFECT_TE_BLOOD)
1028 if (!cl_particles_blood.integer)
1030 if (cl_particles_quake.integer)
1031 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1034 static double bloodaccumulator = 0;
1035 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1036 //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);
1037 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1038 for (;bloodaccumulator > 0;bloodaccumulator--)
1040 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);
1041 if (immediatebloodstain && part)
1043 immediatebloodstain = false;
1044 CL_ImmediateBloodStain(part);
1049 else if (effectnameindex == EFFECT_TE_SPARK)
1050 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1051 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1053 // plasma scorch mark
1054 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1055 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1056 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1058 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1060 if (cl_particles_bulletimpacts.integer)
1062 if (cl_particles_quake.integer)
1063 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1066 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1067 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1068 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);
1072 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1073 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1075 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1077 if (cl_particles_bulletimpacts.integer)
1079 if (cl_particles_quake.integer)
1080 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1083 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1084 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1085 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);
1089 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1090 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1091 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);
1093 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1095 CL_ParticleExplosion(center);
1096 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);
1098 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1100 CL_ParticleExplosion(center);
1101 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);
1103 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1105 if (cl_particles_quake.integer)
1108 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1111 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);
1113 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);
1117 CL_ParticleExplosion(center);
1118 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);
1120 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1121 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);
1122 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1124 count *= cl_particles_quality.value;
1126 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);
1128 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1130 float i, j, inc, vel;
1133 inc = 8 / cl_particles_quality.value;
1134 for (i = -128;i < 128;i += inc)
1136 for (j = -128;j < 128;j += inc)
1138 dir[0] = j + lhrandom(0, inc);
1139 dir[1] = i + lhrandom(0, inc);
1141 org[0] = center[0] + dir[0];
1142 org[1] = center[1] + dir[1];
1143 org[2] = center[2] + lhrandom(0, 64);
1144 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1145 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);
1149 else if (effectnameindex == EFFECT_TE_TELEPORT)
1151 float i, j, k, inc, vel;
1154 if (cl_particles_quake.integer)
1155 inc = 4 / cl_particles_quality.value;
1157 inc = 8 / cl_particles_quality.value;
1158 for (i = -16;i < 16;i += inc)
1160 for (j = -16;j < 16;j += inc)
1162 for (k = -24;k < 32;k += inc)
1164 VectorSet(dir, i*8, j*8, k*8);
1165 VectorNormalize(dir);
1166 vel = lhrandom(50, 113);
1167 if (cl_particles_quake.integer)
1168 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);
1170 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);
1174 if (!cl_particles_quake.integer)
1175 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);
1176 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);
1178 else if (effectnameindex == EFFECT_TE_TEI_G3)
1179 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);
1180 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1182 if (cl_particles_smoke.integer)
1184 count *= 0.25f * cl_particles_quality.value;
1186 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);
1189 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1191 CL_ParticleExplosion(center);
1192 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);
1194 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1197 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1198 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1199 if (cl_particles_smoke.integer)
1200 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1201 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);
1202 if (cl_particles_sparks.integer)
1203 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1204 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);
1205 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);
1207 else if (effectnameindex == EFFECT_EF_FLAME)
1209 count *= 300 * cl_particles_quality.value;
1211 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);
1212 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);
1214 else if (effectnameindex == EFFECT_EF_STARDUST)
1216 count *= 200 * cl_particles_quality.value;
1218 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);
1219 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);
1221 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1225 int smoke, blood, bubbles, r, color;
1227 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1230 Vector4Set(light, 0, 0, 0, 0);
1232 if (effectnameindex == EFFECT_TR_ROCKET)
1233 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1234 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1236 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1237 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1239 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1241 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1242 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1246 matrix4x4_t tempmatrix;
1247 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1248 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);
1249 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1253 if (!spawnparticles)
1256 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1259 VectorSubtract(originmaxs, originmins, dir);
1260 len = VectorNormalizeLength(dir);
1263 dec = -ent->persistent.trail_time;
1264 ent->persistent.trail_time += len;
1265 if (ent->persistent.trail_time < 0.01f)
1268 // if we skip out, leave it reset
1269 ent->persistent.trail_time = 0.0f;
1274 // advance into this frame to reach the first puff location
1275 VectorMA(originmins, dec, dir, pos);
1278 smoke = cl_particles.integer && cl_particles_smoke.integer;
1279 blood = cl_particles.integer && cl_particles_blood.integer;
1280 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1281 qd = 1.0f / cl_particles_quality.value;
1288 if (effectnameindex == EFFECT_TR_BLOOD)
1290 if (cl_particles_quake.integer)
1292 color = particlepalette[67 + (rand()&3)];
1293 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);
1298 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);
1301 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1303 if (cl_particles_quake.integer)
1306 color = particlepalette[67 + (rand()&3)];
1307 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);
1312 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);
1318 if (effectnameindex == EFFECT_TR_ROCKET)
1320 if (cl_particles_quake.integer)
1323 color = particlepalette[ramp3[r]];
1324 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);
1328 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);
1329 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);
1332 else if (effectnameindex == EFFECT_TR_GRENADE)
1334 if (cl_particles_quake.integer)
1337 color = particlepalette[ramp3[r]];
1338 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);
1342 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);
1345 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1347 if (cl_particles_quake.integer)
1350 color = particlepalette[52 + (rand()&7)];
1351 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);
1352 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1354 else if (gamemode == GAME_GOODVSBAD2)
1357 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);
1361 color = particlepalette[20 + (rand()&7)];
1362 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);
1365 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1367 if (cl_particles_quake.integer)
1370 color = particlepalette[230 + (rand()&7)];
1371 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);
1372 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);
1376 color = particlepalette[226 + (rand()&7)];
1377 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);
1380 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1382 if (cl_particles_quake.integer)
1384 color = particlepalette[152 + (rand()&3)];
1385 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);
1387 else if (gamemode == GAME_GOODVSBAD2)
1390 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);
1392 else if (gamemode == GAME_PRYDON)
1395 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);
1398 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);
1400 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1403 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);
1405 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1408 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);
1410 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1411 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);
1415 if (effectnameindex == EFFECT_TR_ROCKET)
1416 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);
1417 else if (effectnameindex == EFFECT_TR_GRENADE)
1418 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1420 // advance to next time and position
1423 VectorMA (pos, dec, dir, pos);
1426 ent->persistent.trail_time = len;
1429 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1432 // this is also called on point effects with spawndlight = true and
1433 // spawnparticles = true
1434 // it is called CL_ParticleTrail because most code does not want to supply
1435 // these parameters, only trail handling does
1436 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])
1438 qboolean found = false;
1440 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1442 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1443 return; // no such effect
1445 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1447 int effectinfoindex;
1450 particleeffectinfo_t *info;
1462 qboolean underwater;
1463 qboolean immediatebloodstain;
1465 float avgtint[4], tint[4], tintlerp;
1466 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1467 VectorLerp(originmins, 0.5, originmaxs, center);
1468 supercontents = CL_PointSuperContents(center);
1469 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1470 VectorSubtract(originmaxs, originmins, traildir);
1471 traillen = VectorLength(traildir);
1472 VectorNormalize(traildir);
1475 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1479 Vector4Set(avgtint, 1, 1, 1, 1);
1481 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1483 if (info->effectnameindex == effectnameindex)
1486 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1488 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1491 // spawn a dlight if requested
1492 if (info->lightradiusstart > 0 && spawndlight)
1494 matrix4x4_t tempmatrix;
1495 if (info->trailspacing > 0)
1496 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1498 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1499 if (info->lighttime > 0 && info->lightradiusfade > 0)
1501 // light flash (explosion, etc)
1502 // called when effect starts
1503 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);
1505 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1508 // called by CL_LinkNetworkEntity
1509 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1510 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1511 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1512 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1513 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);
1514 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1518 if (!spawnparticles)
1523 if (info->tex[1] > info->tex[0])
1525 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1526 tex = min(tex, info->tex[1] - 1);
1528 if(info->staintex[0] < 0)
1529 staintex = info->staintex[0];
1532 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1533 staintex = min(staintex, info->staintex[1] - 1);
1535 if (info->particletype == pt_decal)
1537 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1538 AnglesFromVectors(angles, velocity, NULL, false);
1539 AngleVectors(angles, forward, right, up);
1540 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1542 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]);
1544 else if (info->orientation == PARTICLE_HBEAM)
1546 AnglesFromVectors(angles, traildir, NULL, false);
1547 AngleVectors(angles, forward, right, up);
1548 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1550 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);
1554 if (!cl_particles.integer)
1556 switch (info->particletype)
1558 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1559 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1560 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1561 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1562 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1563 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1566 VectorCopy(originmins, trailpos);
1567 if (info->trailspacing > 0)
1569 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1570 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1571 immediatebloodstain = false;
1573 AnglesFromVectors(angles, traildir, NULL, false);
1574 AngleVectors(angles, forward, right, up);
1575 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1576 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1580 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1582 immediatebloodstain =
1583 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1585 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1587 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1588 AnglesFromVectors(angles, velocity, NULL, false);
1589 AngleVectors(angles, forward, right, up);
1590 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1591 VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1593 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1594 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1596 if (info->tex[1] > info->tex[0])
1598 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1599 tex = min(tex, info->tex[1] - 1);
1603 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1604 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1605 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1609 tintlerp = lhrandom(0, 1);
1610 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1613 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);
1614 if (immediatebloodstain && part)
1616 immediatebloodstain = false;
1617 CL_ImmediateBloodStain(part);
1620 VectorMA(trailpos, trailstep, traildir, trailpos);
1627 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1630 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)
1632 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1640 void CL_EntityParticles (const entity_t *ent)
1643 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1644 static vec3_t avelocities[NUMVERTEXNORMALS];
1645 if (!cl_particles.integer) return;
1646 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1648 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1650 if (!avelocities[0][0])
1651 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1652 avelocities[0][i] = lhrandom(0, 2.55);
1654 for (i = 0;i < NUMVERTEXNORMALS;i++)
1656 yaw = cl.time * avelocities[i][0];
1657 pitch = cl.time * avelocities[i][1];
1658 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1659 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1660 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1661 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);
1666 void CL_ReadPointFile_f (void)
1668 vec3_t org, leakorg;
1670 char *pointfile = NULL, *pointfilepos, *t, tchar;
1671 char name[MAX_QPATH];
1676 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1677 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1680 Con_Printf("Could not open %s\n", name);
1684 Con_Printf("Reading %s...\n", name);
1685 VectorClear(leakorg);
1688 pointfilepos = pointfile;
1689 while (*pointfilepos)
1691 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1696 while (*t && *t != '\n' && *t != '\r')
1700 #if _MSC_VER >= 1400
1701 #define sscanf sscanf_s
1703 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1709 VectorCopy(org, leakorg);
1712 if (cl.num_particles < cl.max_particles - 3)
1715 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);
1718 Mem_Free(pointfile);
1719 VectorCopy(leakorg, org);
1720 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1722 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);
1723 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);
1724 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);
1729 CL_ParseParticleEffect
1731 Parse an effect out of the server message
1734 void CL_ParseParticleEffect (void)
1737 int i, count, msgcount, color;
1739 MSG_ReadVector(&cl_message, org, cls.protocol);
1740 for (i=0 ; i<3 ; i++)
1741 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1742 msgcount = MSG_ReadByte(&cl_message);
1743 color = MSG_ReadByte(&cl_message);
1745 if (msgcount == 255)
1750 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1755 CL_ParticleExplosion
1759 void CL_ParticleExplosion (const vec3_t org)
1765 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1766 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1768 if (cl_particles_quake.integer)
1770 for (i = 0;i < 1024;i++)
1776 color = particlepalette[ramp1[r]];
1777 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);
1781 color = particlepalette[ramp2[r]];
1782 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1788 i = CL_PointSuperContents(org);
1789 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1791 if (cl_particles.integer && cl_particles_bubbles.integer)
1792 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1793 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);
1797 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1799 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1806 VectorMA(org, 128, v2, v);
1807 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1809 while (k < 16 && trace.fraction < 0.1f);
1810 VectorSubtract(trace.endpos, org, v2);
1811 VectorScale(v2, 2.0f, v2);
1812 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);
1818 if (cl_particles_explosions_shell.integer)
1819 R_NewExplosion(org);
1824 CL_ParticleExplosion2
1828 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1831 if (!cl_particles.integer) return;
1833 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1835 k = particlepalette[colorStart + (i % colorLength)];
1836 if (cl_particles_quake.integer)
1837 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);
1839 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);
1843 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1846 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1847 if (cl_particles_sparks.integer)
1849 sparkcount *= cl_particles_quality.value;
1850 while(sparkcount-- > 0)
1851 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);
1855 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1858 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1859 if (cl_particles_smoke.integer)
1861 smokecount *= cl_particles_quality.value;
1862 while(smokecount-- > 0)
1863 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);
1867 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)
1871 if (!cl_particles.integer) return;
1872 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1874 count = (int)(count * cl_particles_quality.value);
1877 k = particlepalette[colorbase + (rand()&3)];
1878 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);
1882 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1885 float minz, maxz, lifetime = 30;
1887 if (!cl_particles.integer) return;
1888 if (dir[2] < 0) // falling
1890 minz = maxs[2] + dir[2] * 0.1;
1893 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1898 maxz = maxs[2] + dir[2] * 0.1;
1900 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1903 count = (int)(count * cl_particles_quality.value);
1908 if (!cl_particles_rain.integer) break;
1909 count *= 4; // ick, this should be in the mod or maps?
1913 k = particlepalette[colorbase + (rand()&3)];
1914 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1915 if (gamemode == GAME_GOODVSBAD2)
1916 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);
1918 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);
1922 if (!cl_particles_snow.integer) break;
1925 k = particlepalette[colorbase + (rand()&3)];
1926 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1927 if (gamemode == GAME_GOODVSBAD2)
1928 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);
1930 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);
1934 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1938 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1939 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1940 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1941 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1942 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1943 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1945 #define PARTICLETEXTURESIZE 64
1946 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1948 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1952 dz = 1 - (dx*dx+dy*dy);
1953 if (dz > 0) // it does hit the sphere
1957 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1958 VectorNormalize(normal);
1959 dot = DotProduct(normal, light);
1960 if (dot > 0.5) // interior reflection
1961 f += ((dot * 2) - 1);
1962 else if (dot < -0.5) // exterior reflection
1963 f += ((dot * -2) - 1);
1965 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1966 VectorNormalize(normal);
1967 dot = DotProduct(normal, light);
1968 if (dot > 0.5) // interior reflection
1969 f += ((dot * 2) - 1);
1970 else if (dot < -0.5) // exterior reflection
1971 f += ((dot * -2) - 1);
1973 f += 16; // just to give it a haze so you can see the outline
1974 f = bound(0, f, 255);
1975 return (unsigned char) f;
1981 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1982 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1984 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1985 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1986 *width = particlefontcellwidth;
1987 *height = particlefontcellheight;
1990 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1992 int basex, basey, w, h, y;
1993 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1994 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1995 Sys_Error("invalid particle texture size for autogenerating");
1996 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1997 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2000 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2003 float cx, cy, dx, dy, f, iradius;
2005 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2006 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2007 iradius = 1.0f / radius;
2008 alpha *= (1.0f / 255.0f);
2009 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2011 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2015 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2020 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2021 d[0] += (int)(f * (blue - d[0]));
2022 d[1] += (int)(f * (green - d[1]));
2023 d[2] += (int)(f * (red - d[2]));
2030 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2033 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2035 data[0] = bound(minb, data[0], maxb);
2036 data[1] = bound(ming, data[1], maxg);
2037 data[2] = bound(minr, data[2], maxr);
2042 static void particletextureinvert(unsigned char *data)
2045 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2047 data[0] = 255 - data[0];
2048 data[1] = 255 - data[1];
2049 data[2] = 255 - data[2];
2053 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2054 static void R_InitBloodTextures (unsigned char *particletexturedata)
2057 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2058 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2061 for (i = 0;i < 8;i++)
2063 memset(data, 255, datasize);
2064 for (k = 0;k < 24;k++)
2065 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2066 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2067 particletextureinvert(data);
2068 setuptex(tex_bloodparticle[i], data, particletexturedata);
2072 for (i = 0;i < 8;i++)
2074 memset(data, 255, datasize);
2076 for (j = 1;j < 10;j++)
2077 for (k = min(j, m - 1);k < m;k++)
2078 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2079 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2080 particletextureinvert(data);
2081 setuptex(tex_blooddecal[i], data, particletexturedata);
2087 //uncomment this to make engine save out particle font to a tga file when run
2088 //#define DUMPPARTICLEFONT
2090 static void R_InitParticleTexture (void)
2092 int x, y, d, i, k, m;
2093 int basex, basey, w, h;
2094 float dx, dy, f, s1, t1, s2, t2;
2097 fs_offset_t filesize;
2098 char texturename[MAX_QPATH];
2101 // a note: decals need to modulate (multiply) the background color to
2102 // properly darken it (stain), and they need to be able to alpha fade,
2103 // this is a very difficult challenge because it means fading to white
2104 // (no change to background) rather than black (darkening everything
2105 // behind the whole decal polygon), and to accomplish this the texture is
2106 // inverted (dark red blood on white background becomes brilliant cyan
2107 // and white on black background) so we can alpha fade it to black, then
2108 // we invert it again during the blendfunc to make it work...
2110 #ifndef DUMPPARTICLEFONT
2111 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2114 particlefonttexture = decalskinframe->base;
2115 // TODO maybe allow custom grid size?
2116 particlefontwidth = image_width;
2117 particlefontheight = image_height;
2118 particlefontcellwidth = image_width / 8;
2119 particlefontcellheight = image_height / 8;
2120 particlefontcols = 8;
2121 particlefontrows = 8;
2126 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2127 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2128 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2129 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2130 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2132 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2133 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2134 particlefontcols = 8;
2135 particlefontrows = 8;
2137 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2140 for (i = 0;i < 8;i++)
2142 memset(data, 255, datasize);
2145 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2146 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2148 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2150 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2151 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2153 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2154 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2156 d = (int)(d * (1-(dx*dx+dy*dy)));
2157 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2158 d = bound(0, d, 255);
2159 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2166 setuptex(tex_smoke[i], data, particletexturedata);
2170 memset(data, 255, datasize);
2171 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2173 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2174 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2176 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2177 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2178 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2181 setuptex(tex_rainsplash, data, particletexturedata);
2184 memset(data, 255, datasize);
2185 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2187 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2188 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2190 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2191 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2192 d = bound(0, d, 255);
2193 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2196 setuptex(tex_particle, data, particletexturedata);
2199 memset(data, 255, datasize);
2200 light[0] = 1;light[1] = 1;light[2] = 1;
2201 VectorNormalize(light);
2202 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2204 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2205 // stretch upper half of bubble by +50% and shrink lower half by -50%
2206 // (this gives an elongated teardrop shape)
2208 dy = (dy - 0.5f) * 2.0f;
2210 dy = (dy - 0.5f) / 1.5f;
2211 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2213 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2214 // shrink bubble width to half
2216 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2219 setuptex(tex_raindrop, data, particletexturedata);
2222 memset(data, 255, datasize);
2223 light[0] = 1;light[1] = 1;light[2] = 1;
2224 VectorNormalize(light);
2225 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2227 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2228 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2230 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2231 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2234 setuptex(tex_bubble, data, particletexturedata);
2236 // Blood particles and blood decals
2237 R_InitBloodTextures (particletexturedata);
2240 for (i = 0;i < 8;i++)
2242 memset(data, 255, datasize);
2243 for (k = 0;k < 12;k++)
2244 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2245 for (k = 0;k < 3;k++)
2246 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2247 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2248 particletextureinvert(data);
2249 setuptex(tex_bulletdecal[i], data, particletexturedata);
2252 #ifdef DUMPPARTICLEFONT
2253 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2256 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2257 particlefonttexture = decalskinframe->base;
2259 Mem_Free(particletexturedata);
2264 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2266 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2267 particletexture[i].texture = particlefonttexture;
2268 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2269 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2270 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2271 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2274 #ifndef DUMPPARTICLEFONT
2275 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2276 if (!particletexture[tex_beam].texture)
2279 unsigned char noise3[64][64], data2[64][16][4];
2281 fractalnoise(&noise3[0][0], 64, 4);
2283 for (y = 0;y < 64;y++)
2285 dy = (y - 0.5f*64) / (64*0.5f-1);
2286 for (x = 0;x < 16;x++)
2288 dx = (x - 0.5f*16) / (16*0.5f-2);
2289 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2290 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2291 data2[y][x][3] = 255;
2295 #ifdef DUMPPARTICLEFONT
2296 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2298 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2300 particletexture[tex_beam].s1 = 0;
2301 particletexture[tex_beam].t1 = 0;
2302 particletexture[tex_beam].s2 = 1;
2303 particletexture[tex_beam].t2 = 1;
2305 // now load an texcoord/texture override file
2306 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2313 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2315 if(!strcmp(com_token, "\n"))
2316 continue; // empty line
2317 i = atoi(com_token);
2325 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2327 strlcpy(texturename, com_token, sizeof(texturename));
2328 s1 = atof(com_token);
2329 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2332 t1 = atof(com_token);
2333 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2335 s2 = atof(com_token);
2336 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2338 t2 = atof(com_token);
2339 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2340 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2341 strlcpy(texturename, com_token, sizeof(texturename));
2348 if (!texturename[0])
2350 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2353 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2355 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2358 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2361 // R_SkinFrame_LoadExternal already complained
2364 particletexture[i].texture = sf->base;
2365 particletexture[i].s1 = s1;
2366 particletexture[i].t1 = t1;
2367 particletexture[i].s2 = s2;
2368 particletexture[i].t2 = t2;
2374 static void r_part_start(void)
2377 // generate particlepalette for convenience from the main one
2378 for (i = 0;i < 256;i++)
2379 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2380 particletexturepool = R_AllocTexturePool();
2381 R_InitParticleTexture ();
2382 CL_Particles_LoadEffectInfo(NULL);
2385 static void r_part_shutdown(void)
2387 R_FreeTexturePool(&particletexturepool);
2390 static void r_part_newmap(void)
2393 R_SkinFrame_MarkUsed(decalskinframe);
2394 CL_Particles_LoadEffectInfo(NULL);
2397 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2398 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2400 void R_Particles_Init (void)
2403 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2405 particle_elements[i*6+0] = i*4+0;
2406 particle_elements[i*6+1] = i*4+1;
2407 particle_elements[i*6+2] = i*4+2;
2408 particle_elements[i*6+3] = i*4+0;
2409 particle_elements[i*6+4] = i*4+2;
2410 particle_elements[i*6+5] = i*4+3;
2413 Cvar_RegisterVariable(&r_drawparticles);
2414 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2415 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2416 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2417 Cvar_RegisterVariable(&r_drawdecals);
2418 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2419 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2422 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2424 int surfacelistindex;
2426 float *v3f, *t2f, *c4f;
2427 particletexture_t *tex;
2428 float right[3], up[3], size, ca;
2429 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2431 RSurf_ActiveWorldEntity();
2433 r_refdef.stats.drawndecals += numsurfaces;
2434 // R_Mesh_ResetTextureState();
2435 GL_DepthMask(false);
2436 GL_DepthRange(0, 1);
2437 GL_PolygonOffset(0, 0);
2439 GL_CullFace(GL_NONE);
2441 // generate all the vertices at once
2442 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2444 d = cl.decals + surfacelist[surfacelistindex];
2447 c4f = particle_color4f + 16*surfacelistindex;
2448 ca = d->alpha * alphascale;
2449 // ensure alpha multiplier saturates properly
2450 if (ca > 1.0f / 256.0f)
2452 if (r_refdef.fogenabled)
2453 ca *= RSurf_FogVertex(d->org);
2454 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2455 Vector4Copy(c4f, c4f + 4);
2456 Vector4Copy(c4f, c4f + 8);
2457 Vector4Copy(c4f, c4f + 12);
2459 // calculate vertex positions
2460 size = d->size * cl_particles_size.value;
2461 VectorVectors(d->normal, right, up);
2462 VectorScale(right, size, right);
2463 VectorScale(up, size, up);
2464 v3f = particle_vertex3f + 12*surfacelistindex;
2465 v3f[ 0] = d->org[0] - right[0] - up[0];
2466 v3f[ 1] = d->org[1] - right[1] - up[1];
2467 v3f[ 2] = d->org[2] - right[2] - up[2];
2468 v3f[ 3] = d->org[0] - right[0] + up[0];
2469 v3f[ 4] = d->org[1] - right[1] + up[1];
2470 v3f[ 5] = d->org[2] - right[2] + up[2];
2471 v3f[ 6] = d->org[0] + right[0] + up[0];
2472 v3f[ 7] = d->org[1] + right[1] + up[1];
2473 v3f[ 8] = d->org[2] + right[2] + up[2];
2474 v3f[ 9] = d->org[0] + right[0] - up[0];
2475 v3f[10] = d->org[1] + right[1] - up[1];
2476 v3f[11] = d->org[2] + right[2] - up[2];
2478 // calculate texcoords
2479 tex = &particletexture[d->texnum];
2480 t2f = particle_texcoord2f + 8*surfacelistindex;
2481 t2f[0] = tex->s1;t2f[1] = tex->t2;
2482 t2f[2] = tex->s1;t2f[3] = tex->t1;
2483 t2f[4] = tex->s2;t2f[5] = tex->t1;
2484 t2f[6] = tex->s2;t2f[7] = tex->t2;
2487 // now render the decals all at once
2488 // (this assumes they all use one particle font texture!)
2489 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2490 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2491 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2492 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2495 void R_DrawDecals (void)
2498 int drawdecals = r_drawdecals.integer;
2503 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2505 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2506 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2508 // LordHavoc: early out conditions
2512 decalfade = frametime * 256 / cl_decals_fadetime.value;
2513 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2514 drawdist2 = drawdist2*drawdist2;
2516 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2518 if (!decal->typeindex)
2521 if (killsequence - decal->decalsequence > 0)
2524 if (cl.time > decal->time2 + cl_decals_time.value)
2526 decal->alpha -= decalfade;
2527 if (decal->alpha <= 0)
2533 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2535 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2536 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2542 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2548 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))
2549 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2552 decal->typeindex = 0;
2553 if (cl.free_decal > i)
2557 // reduce cl.num_decals if possible
2558 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2561 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2563 decal_t *olddecals = cl.decals;
2564 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2565 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2566 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2567 Mem_Free(olddecals);
2570 r_refdef.stats.totaldecals = cl.num_decals;
2573 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2575 int surfacelistindex;
2576 int batchstart, batchcount;
2577 const particle_t *p;
2579 rtexture_t *texture;
2580 float *v3f, *t2f, *c4f;
2581 particletexture_t *tex;
2582 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2583 // float ambient[3], diffuse[3], diffusenormal[3];
2584 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2585 vec4_t colormultiplier;
2586 float minparticledist_start, minparticledist_end;
2589 RSurf_ActiveWorldEntity();
2591 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));
2593 r_refdef.stats.particles += numsurfaces;
2594 // R_Mesh_ResetTextureState();
2595 GL_DepthMask(false);
2596 GL_DepthRange(0, 1);
2597 GL_PolygonOffset(0, 0);
2599 GL_CullFace(GL_NONE);
2601 spintime = r_refdef.scene.time;
2603 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2604 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2605 dofade = (minparticledist_start < minparticledist_end);
2607 // first generate all the vertices at once
2608 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2610 p = cl.particles + surfacelist[surfacelistindex];
2612 blendmode = (pblend_t)p->blendmode;
2614 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2615 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2616 alpha = palpha * colormultiplier[3];
2617 // ensure alpha multiplier saturates properly
2623 case PBLEND_INVALID:
2625 // additive and modulate can just fade out in fog (this is correct)
2626 if (r_refdef.fogenabled)
2627 alpha *= RSurf_FogVertex(p->org);
2628 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2629 alpha *= 1.0f / 256.0f;
2630 c4f[0] = p->color[0] * alpha;
2631 c4f[1] = p->color[1] * alpha;
2632 c4f[2] = p->color[2] * alpha;
2636 // additive and modulate can just fade out in fog (this is correct)
2637 if (r_refdef.fogenabled)
2638 alpha *= RSurf_FogVertex(p->org);
2639 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2640 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2641 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2642 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2646 c4f[0] = p->color[0] * colormultiplier[0];
2647 c4f[1] = p->color[1] * colormultiplier[1];
2648 c4f[2] = p->color[2] * colormultiplier[2];
2650 // note: lighting is not cheap!
2651 if (particletype[p->typeindex].lighting)
2652 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2653 // mix in the fog color
2654 if (r_refdef.fogenabled)
2656 fog = RSurf_FogVertex(p->org);
2658 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2659 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2660 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2662 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2663 VectorScale(c4f, alpha, c4f);
2666 // copy the color into the other three vertices
2667 Vector4Copy(c4f, c4f + 4);
2668 Vector4Copy(c4f, c4f + 8);
2669 Vector4Copy(c4f, c4f + 12);
2671 size = p->size * cl_particles_size.value;
2672 tex = &particletexture[p->texnum];
2673 switch(p->orientation)
2675 // case PARTICLE_INVALID:
2676 case PARTICLE_BILLBOARD:
2677 if (p->angle + p->spin)
2679 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2680 spinsin = sin(spinrad) * size;
2681 spincos = cos(spinrad) * size;
2682 spinm1 = -p->stretch * spincos;
2685 spinm4 = -p->stretch * spincos;
2686 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2687 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2691 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2692 VectorScale(r_refdef.view.up, size, up);
2695 v3f[ 0] = p->org[0] - right[0] - up[0];
2696 v3f[ 1] = p->org[1] - right[1] - up[1];
2697 v3f[ 2] = p->org[2] - right[2] - up[2];
2698 v3f[ 3] = p->org[0] - right[0] + up[0];
2699 v3f[ 4] = p->org[1] - right[1] + up[1];
2700 v3f[ 5] = p->org[2] - right[2] + up[2];
2701 v3f[ 6] = p->org[0] + right[0] + up[0];
2702 v3f[ 7] = p->org[1] + right[1] + up[1];
2703 v3f[ 8] = p->org[2] + right[2] + up[2];
2704 v3f[ 9] = p->org[0] + right[0] - up[0];
2705 v3f[10] = p->org[1] + right[1] - up[1];
2706 v3f[11] = p->org[2] + right[2] - up[2];
2707 t2f[0] = tex->s1;t2f[1] = tex->t2;
2708 t2f[2] = tex->s1;t2f[3] = tex->t1;
2709 t2f[4] = tex->s2;t2f[5] = tex->t1;
2710 t2f[6] = tex->s2;t2f[7] = tex->t2;
2712 case PARTICLE_ORIENTED_DOUBLESIDED:
2713 VectorVectors(p->vel, baseright, baseup);
2714 if (p->angle + p->spin)
2716 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2717 spinsin = sin(spinrad) * size;
2718 spincos = cos(spinrad) * size;
2719 spinm1 = p->stretch * spincos;
2722 spinm4 = p->stretch * spincos;
2723 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2724 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2728 VectorScale(baseright, size * p->stretch, right);
2729 VectorScale(baseup, size, up);
2731 v3f[ 0] = p->org[0] - right[0] - up[0];
2732 v3f[ 1] = p->org[1] - right[1] - up[1];
2733 v3f[ 2] = p->org[2] - right[2] - up[2];
2734 v3f[ 3] = p->org[0] - right[0] + up[0];
2735 v3f[ 4] = p->org[1] - right[1] + up[1];
2736 v3f[ 5] = p->org[2] - right[2] + up[2];
2737 v3f[ 6] = p->org[0] + right[0] + up[0];
2738 v3f[ 7] = p->org[1] + right[1] + up[1];
2739 v3f[ 8] = p->org[2] + right[2] + up[2];
2740 v3f[ 9] = p->org[0] + right[0] - up[0];
2741 v3f[10] = p->org[1] + right[1] - up[1];
2742 v3f[11] = p->org[2] + right[2] - up[2];
2743 t2f[0] = tex->s1;t2f[1] = tex->t2;
2744 t2f[2] = tex->s1;t2f[3] = tex->t1;
2745 t2f[4] = tex->s2;t2f[5] = tex->t1;
2746 t2f[6] = tex->s2;t2f[7] = tex->t2;
2748 case PARTICLE_SPARK:
2749 len = VectorLength(p->vel);
2750 VectorNormalize2(p->vel, up);
2751 lenfactor = p->stretch * 0.04 * len;
2752 if(lenfactor < size * 0.5)
2753 lenfactor = size * 0.5;
2754 VectorMA(p->org, -lenfactor, up, v);
2755 VectorMA(p->org, lenfactor, up, up2);
2756 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2757 t2f[0] = tex->s1;t2f[1] = tex->t2;
2758 t2f[2] = tex->s1;t2f[3] = tex->t1;
2759 t2f[4] = tex->s2;t2f[5] = tex->t1;
2760 t2f[6] = tex->s2;t2f[7] = tex->t2;
2762 case PARTICLE_VBEAM:
2763 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2764 VectorSubtract(p->vel, p->org, up);
2765 VectorNormalize(up);
2766 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2767 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2768 t2f[0] = tex->s2;t2f[1] = v[0];
2769 t2f[2] = tex->s1;t2f[3] = v[0];
2770 t2f[4] = tex->s1;t2f[5] = v[1];
2771 t2f[6] = tex->s2;t2f[7] = v[1];
2773 case PARTICLE_HBEAM:
2774 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2775 VectorSubtract(p->vel, p->org, up);
2776 VectorNormalize(up);
2777 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2778 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2779 t2f[0] = v[0];t2f[1] = tex->t1;
2780 t2f[2] = v[0];t2f[3] = tex->t2;
2781 t2f[4] = v[1];t2f[5] = tex->t2;
2782 t2f[6] = v[1];t2f[7] = tex->t1;
2787 // now render batches of particles based on blendmode and texture
2788 blendmode = PBLEND_INVALID;
2792 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2793 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2795 p = cl.particles + surfacelist[surfacelistindex];
2797 if (texture != particletexture[p->texnum].texture)
2799 texture = particletexture[p->texnum].texture;
2800 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2803 if (p->blendmode == PBLEND_INVMOD)
2805 // inverse modulate blend - group these
2806 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2807 // iterate until we find a change in settings
2808 batchstart = surfacelistindex++;
2809 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2811 p = cl.particles + surfacelist[surfacelistindex];
2812 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2818 // additive or alpha blend - group these
2819 // (we can group these because we premultiplied the texture alpha)
2820 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2821 // iterate until we find a change in settings
2822 batchstart = surfacelistindex++;
2823 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2825 p = cl.particles + surfacelist[surfacelistindex];
2826 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2831 batchcount = surfacelistindex - batchstart;
2832 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2836 void R_DrawParticles (void)
2839 int drawparticles = r_drawparticles.integer;
2840 float minparticledist_start;
2842 float gravity, frametime, f, dist, oldorg[3];
2848 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2849 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2851 // LordHavoc: early out conditions
2852 if (!cl.num_particles)
2855 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2856 gravity = frametime * cl.movevars_gravity;
2857 update = frametime > 0;
2858 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2859 drawdist2 = drawdist2*drawdist2;
2861 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2865 if (cl.free_particle > i)
2866 cl.free_particle = i;
2872 if (p->delayedspawn > cl.time)
2875 p->size += p->sizeincrease * frametime;
2876 p->alpha -= p->alphafade * frametime;
2878 if (p->alpha <= 0 || p->die <= cl.time)
2881 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2883 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2885 if (p->typeindex == pt_blood)
2886 p->size += frametime * 8;
2888 p->vel[2] -= p->gravity * gravity;
2889 f = 1.0f - min(p->liquidfriction * frametime, 1);
2890 VectorScale(p->vel, f, p->vel);
2894 p->vel[2] -= p->gravity * gravity;
2897 f = 1.0f - min(p->airfriction * frametime, 1);
2898 VectorScale(p->vel, f, p->vel);
2902 VectorCopy(p->org, oldorg);
2903 VectorMA(p->org, frametime, p->vel, p->org);
2904 // if (p->bounce && cl.time >= p->delayedcollisions)
2905 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2907 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);
2908 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2909 // or if the trace hit something flagged as NOIMPACT
2910 // then remove the particle
2911 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2913 VectorCopy(trace.endpos, p->org);
2914 // react if the particle hit something
2915 if (trace.fraction < 1)
2917 VectorCopy(trace.endpos, p->org);
2919 if (p->staintexnum >= 0)
2921 // blood - splash on solid
2922 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2925 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2926 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2927 if (cl_decals.integer)
2929 // create a decal for the blood splat
2930 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2931 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2936 if (p->typeindex == pt_blood)
2938 // blood - splash on solid
2939 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2941 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2943 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)));
2944 if (cl_decals.integer)
2946 // create a decal for the blood splat
2947 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);
2952 else if (p->bounce < 0)
2954 // bounce -1 means remove on impact
2959 // anything else - bounce off solid
2960 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2961 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2966 if (VectorLength2(p->vel) < 0.03)
2968 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2970 VectorClear(p->vel);
2974 if (p->typeindex != pt_static)
2976 switch (p->typeindex)
2978 case pt_entityparticle:
2979 // particle that removes itself after one rendered frame
2986 a = CL_PointSuperContents(p->org);
2987 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2991 a = CL_PointSuperContents(p->org);
2992 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2996 a = CL_PointSuperContents(p->org);
2997 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3001 if (cl.time > p->time2)
3004 p->time2 = cl.time + (rand() & 3) * 0.1;
3005 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3006 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3008 a = CL_PointSuperContents(p->org);
3009 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3017 else if (p->delayedspawn > cl.time)
3021 // don't render particles too close to the view (they chew fillrate)
3022 // also don't render particles behind the view (useless)
3023 // further checks to cull to the frustum would be too slow here
3024 switch(p->typeindex)
3027 // beams have no culling
3028 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3031 if(cl_particles_visculling.integer)
3032 if (!r_refdef.viewcache.world_novis)
3033 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3035 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3037 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3040 // anything else just has to be in front of the viewer and visible at this distance
3041 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3042 R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3049 if (cl.free_particle > i)
3050 cl.free_particle = i;
3053 // reduce cl.num_particles if possible
3054 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3057 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3059 particle_t *oldparticles = cl.particles;
3060 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3061 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3062 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3063 Mem_Free(oldparticles);