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_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
307 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
308 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
309 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
312 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
317 particleeffectinfo_t *info = NULL;
318 const char *text = textstart;
320 for (linenumber = 1;;linenumber++)
323 for (arrayindex = 0;arrayindex < 16;arrayindex++)
324 argv[arrayindex][0] = 0;
327 if (!COM_ParseToken_Simple(&text, true, false, true))
329 if (!strcmp(com_token, "\n"))
333 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
339 #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;}
340 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
341 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
342 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
343 #define readfloat(var) checkparms(2);var = atof(argv[1])
344 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
345 if (!strcmp(argv[0], "effect"))
349 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
351 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
354 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
356 if (particleeffectname[effectnameindex][0])
358 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
363 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
367 // if we run out of names, abort
368 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
370 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
373 info = particleeffectinfo + numparticleeffectinfo++;
374 // copy entire info from baseline, then fix up the nameindex
375 *info = baselineparticleeffectinfo;
376 info->effectnameindex = effectnameindex;
378 else if (info == NULL)
380 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
383 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
384 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
385 else if (!strcmp(argv[0], "type"))
388 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
389 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
390 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
391 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
392 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
393 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
394 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
395 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
396 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
397 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
398 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
399 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
400 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
401 info->blendmode = particletype[info->particletype].blendmode;
402 info->orientation = particletype[info->particletype].orientation;
404 else if (!strcmp(argv[0], "blend"))
407 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
408 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
409 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
410 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
412 else if (!strcmp(argv[0], "orientation"))
415 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
416 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
417 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
418 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
419 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
421 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
422 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
423 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
424 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
425 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
426 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
427 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
428 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
429 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
430 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
431 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
432 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
433 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
434 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
435 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
436 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
437 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
438 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
439 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
440 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
441 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
442 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
443 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
444 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
445 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
446 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
447 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
448 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
449 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
450 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
451 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
452 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
453 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; }
454 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
456 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
465 int CL_ParticleEffectIndexForName(const char *name)
468 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
469 if (!strcmp(particleeffectname[i], name))
474 const char *CL_ParticleEffectNameForIndex(int i)
476 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
478 return particleeffectname[i];
481 // MUST match effectnameindex_t in client.h
482 static const char *standardeffectnames[EFFECT_TOTAL] =
506 "TE_TEI_BIGEXPLOSION",
522 static void CL_Particles_LoadEffectInfo(const char *customfile)
526 unsigned char *filedata;
527 fs_offset_t filesize;
528 char filename[MAX_QPATH];
529 numparticleeffectinfo = 0;
530 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
531 memset(particleeffectname, 0, sizeof(particleeffectname));
532 for (i = 0;i < EFFECT_TOTAL;i++)
533 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
534 for (filepass = 0;;filepass++)
539 strlcpy(filename, customfile, sizeof(filename));
541 strlcpy(filename, "effectinfo.txt", sizeof(filename));
543 else if (filepass == 1)
545 if (!cl.worldbasename[0] || customfile)
547 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
551 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
554 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
559 static void CL_Particles_LoadEffectInfo_f(void)
561 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
569 void CL_ReadPointFile_f (void);
570 void CL_Particles_Init (void)
572 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)");
573 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)");
575 Cvar_RegisterVariable (&cl_particles);
576 Cvar_RegisterVariable (&cl_particles_quality);
577 Cvar_RegisterVariable (&cl_particles_alpha);
578 Cvar_RegisterVariable (&cl_particles_size);
579 Cvar_RegisterVariable (&cl_particles_quake);
580 Cvar_RegisterVariable (&cl_particles_blood);
581 Cvar_RegisterVariable (&cl_particles_blood_alpha);
582 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
584 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
585 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
586 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
587 Cvar_RegisterVariable (&cl_particles_explosions_shell);
588 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
589 Cvar_RegisterVariable (&cl_particles_rain);
590 Cvar_RegisterVariable (&cl_particles_snow);
591 Cvar_RegisterVariable (&cl_particles_smoke);
592 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
593 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
594 Cvar_RegisterVariable (&cl_particles_sparks);
595 Cvar_RegisterVariable (&cl_particles_bubbles);
596 Cvar_RegisterVariable (&cl_particles_visculling);
597 Cvar_RegisterVariable (&cl_particles_collisions);
598 Cvar_RegisterVariable (&cl_decals);
599 Cvar_RegisterVariable (&cl_decals_visculling);
600 Cvar_RegisterVariable (&cl_decals_time);
601 Cvar_RegisterVariable (&cl_decals_fadetime);
602 Cvar_RegisterVariable (&cl_decals_newsystem);
603 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
604 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
605 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
606 Cvar_RegisterVariable (&cl_decals_models);
607 Cvar_RegisterVariable (&cl_decals_bias);
608 Cvar_RegisterVariable (&cl_decals_max);
611 void CL_Particles_Shutdown (void)
615 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
616 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
618 // list of all 26 parameters:
619 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
620 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
621 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
622 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
623 // palpha - opacity of particle as 0-255 (can be more than 255)
624 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
625 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
626 // pgravity - how much effect gravity has on the particle (0-1)
627 // 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
628 // px,py,pz - starting origin of particle
629 // pvx,pvy,pvz - starting velocity of particle
630 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
631 // blendmode - one of the PBLEND_ values
632 // orientation - one of the PARTICLE_ values
633 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
634 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
635 // stainalpha: opacity of the stain as factor for alpha
636 // stainsize: size of the stain as factor for palpha
637 // angle: base rotation of the particle geometry around its center normal
638 // spin: rotation speed of the particle geometry around its center normal
639 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])
644 if (!cl_particles.integer)
646 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
647 if (cl.free_particle >= cl.max_particles)
650 lifetime = palpha / min(1, palphafade);
651 part = &cl.particles[cl.free_particle++];
652 if (cl.num_particles < cl.free_particle)
653 cl.num_particles = cl.free_particle;
654 memset(part, 0, sizeof(*part));
655 VectorCopy(sortorigin, part->sortorigin);
656 part->typeindex = ptypeindex;
657 part->blendmode = blendmode;
658 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
660 particletexture_t *tex = &particletexture[ptex];
661 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
662 part->orientation = PARTICLE_VBEAM;
664 part->orientation = PARTICLE_HBEAM;
667 part->orientation = orientation;
668 l2 = (int)lhrandom(0.5, 256.5);
670 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
671 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
672 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
675 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
676 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
677 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
679 part->alpha = palpha;
680 part->alphafade = palphafade;
681 part->staintexnum = staintex;
682 if(staincolor1 >= 0 && staincolor2 >= 0)
684 l2 = (int)lhrandom(0.5, 256.5);
686 if(blendmode == PBLEND_INVMOD)
688 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
689 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
690 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
694 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
695 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
696 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
698 if(r > 0xFF) r = 0xFF;
699 if(g > 0xFF) g = 0xFF;
700 if(b > 0xFF) b = 0xFF;
704 r = part->color[0]; // -1 is shorthand for stain = particle color
708 part->staincolor[0] = r;
709 part->staincolor[1] = g;
710 part->staincolor[2] = b;
711 part->stainalpha = palpha * stainalpha;
712 part->stainsize = psize * stainsize;
715 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
717 part->color[0] *= tint[0];
718 part->color[1] *= tint[1];
719 part->color[2] *= tint[2];
721 part->alpha *= tint[3];
722 part->alphafade *= tint[3];
723 part->stainalpha *= tint[3];
727 part->sizeincrease = psizeincrease;
728 part->gravity = pgravity;
729 part->bounce = pbounce;
730 part->stretch = stretch;
732 part->org[0] = px + originjitter * v[0];
733 part->org[1] = py + originjitter * v[1];
734 part->org[2] = pz + originjitter * v[2];
735 part->vel[0] = pvx + velocityjitter * v[0];
736 part->vel[1] = pvy + velocityjitter * v[1];
737 part->vel[2] = pvz + velocityjitter * v[2];
739 part->airfriction = pairfriction;
740 part->liquidfriction = pliquidfriction;
741 part->die = cl.time + lifetime;
742 part->delayedspawn = cl.time;
743 // part->delayedcollisions = 0;
744 part->qualityreduction = pqualityreduction;
747 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
748 if (part->typeindex == pt_rain)
752 float lifetime = part->die - cl.time;
755 // turn raindrop into simple spark and create delayedspawn splash effect
756 part->typeindex = pt_spark;
758 VectorMA(part->org, lifetime, part->vel, endvec);
759 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
760 part->die = cl.time + lifetime * trace.fraction;
761 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);
764 part2->delayedspawn = part->die;
765 part2->die += part->die - cl.time;
766 for (i = rand() & 7;i < 10;i++)
768 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);
771 part2->delayedspawn = part->die;
772 part2->die += part->die - cl.time;
778 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
780 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
783 VectorMA(part->org, lifetime, part->vel, endvec);
784 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
785 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
792 static void CL_ImmediateBloodStain(particle_t *part)
797 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
798 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
800 VectorCopy(part->vel, v);
802 staintex = part->staintexnum;
803 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);
806 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
807 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
809 VectorCopy(part->vel, v);
811 staintex = tex_blooddecal[rand()&7];
812 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);
816 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
820 entity_render_t *ent = &cl.entities[hitent].render;
821 unsigned char color[3];
822 if (!cl_decals.integer)
824 if (!ent->allowdecals)
827 l2 = (int)lhrandom(0.5, 256.5);
829 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
830 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
831 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
833 if (cl_decals_newsystem.integer)
836 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);
838 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);
842 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
843 if (cl.free_decal >= cl.max_decals)
845 decal = &cl.decals[cl.free_decal++];
846 if (cl.num_decals < cl.free_decal)
847 cl.num_decals = cl.free_decal;
848 memset(decal, 0, sizeof(*decal));
849 decal->decalsequence = cl.decalsequence++;
850 decal->typeindex = pt_decal;
851 decal->texnum = texnum;
852 VectorMA(org, cl_decals_bias.value, normal, decal->org);
853 VectorCopy(normal, decal->normal);
855 decal->alpha = alpha;
856 decal->time2 = cl.time;
857 decal->color[0] = color[0];
858 decal->color[1] = color[1];
859 decal->color[2] = color[2];
862 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
863 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
864 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
866 decal->owner = hitent;
867 decal->clusterindex = -1000; // no vis culling unless we're sure
870 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
871 decal->ownermodel = cl.entities[decal->owner].render.model;
872 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
873 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
877 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
879 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
881 decal->clusterindex = leaf->clusterindex;
886 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
893 int besthitent = 0, hitent;
896 for (i = 0;i < 32;i++)
899 VectorMA(org, maxdist, org2, org2);
900 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
901 // take the closest trace result that doesn't end up hitting a NOMARKS
902 // surface (sky for example)
903 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
905 bestfrac = trace.fraction;
907 VectorCopy(trace.endpos, bestorg);
908 VectorCopy(trace.plane.normal, bestnormal);
912 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
915 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
916 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
917 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)
920 matrix4x4_t tempmatrix;
923 VectorLerp(originmins, 0.5, originmaxs, center);
924 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
925 if (effectnameindex == EFFECT_SVC_PARTICLE)
927 if (cl_particles.integer)
929 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
931 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
932 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
933 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
936 count *= cl_particles_quality.value;
937 for (;count > 0;count--)
939 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
940 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);
945 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
946 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
947 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
948 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
949 else if (effectnameindex == EFFECT_TE_SPIKE)
951 if (cl_particles_bulletimpacts.integer)
953 if (cl_particles_quake.integer)
955 if (cl_particles_smoke.integer)
956 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
960 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
961 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
962 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);
966 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
967 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
969 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
971 if (cl_particles_bulletimpacts.integer)
973 if (cl_particles_quake.integer)
975 if (cl_particles_smoke.integer)
976 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
980 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
981 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
982 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);
986 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
987 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
988 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);
990 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
992 if (cl_particles_bulletimpacts.integer)
994 if (cl_particles_quake.integer)
996 if (cl_particles_smoke.integer)
997 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1001 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1002 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1003 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);
1007 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1008 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1010 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1012 if (cl_particles_bulletimpacts.integer)
1014 if (cl_particles_quake.integer)
1016 if (cl_particles_smoke.integer)
1017 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1021 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1022 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1023 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);
1027 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1028 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1029 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);
1031 else if (effectnameindex == EFFECT_TE_BLOOD)
1033 if (!cl_particles_blood.integer)
1035 if (cl_particles_quake.integer)
1036 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1039 static double bloodaccumulator = 0;
1040 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1041 //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);
1042 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1043 for (;bloodaccumulator > 0;bloodaccumulator--)
1045 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);
1046 if (immediatebloodstain && part)
1048 immediatebloodstain = false;
1049 CL_ImmediateBloodStain(part);
1054 else if (effectnameindex == EFFECT_TE_SPARK)
1055 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1056 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1058 // plasma scorch mark
1059 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1060 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1061 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1063 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1065 if (cl_particles_bulletimpacts.integer)
1067 if (cl_particles_quake.integer)
1068 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1071 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1072 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1073 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);
1077 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1078 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1080 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1082 if (cl_particles_bulletimpacts.integer)
1084 if (cl_particles_quake.integer)
1085 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1088 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1089 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1090 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);
1094 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1095 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1096 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);
1098 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1100 CL_ParticleExplosion(center);
1101 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);
1103 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1105 CL_ParticleExplosion(center);
1106 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);
1108 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1110 if (cl_particles_quake.integer)
1113 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1116 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);
1118 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);
1122 CL_ParticleExplosion(center);
1123 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);
1125 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1126 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);
1127 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1129 count *= cl_particles_quality.value;
1131 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);
1133 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1135 float i, j, inc, vel;
1138 inc = 8 / cl_particles_quality.value;
1139 for (i = -128;i < 128;i += inc)
1141 for (j = -128;j < 128;j += inc)
1143 dir[0] = j + lhrandom(0, inc);
1144 dir[1] = i + lhrandom(0, inc);
1146 org[0] = center[0] + dir[0];
1147 org[1] = center[1] + dir[1];
1148 org[2] = center[2] + lhrandom(0, 64);
1149 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1150 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);
1154 else if (effectnameindex == EFFECT_TE_TELEPORT)
1156 float i, j, k, inc, vel;
1159 if (cl_particles_quake.integer)
1160 inc = 4 / cl_particles_quality.value;
1162 inc = 8 / cl_particles_quality.value;
1163 for (i = -16;i < 16;i += inc)
1165 for (j = -16;j < 16;j += inc)
1167 for (k = -24;k < 32;k += inc)
1169 VectorSet(dir, i*8, j*8, k*8);
1170 VectorNormalize(dir);
1171 vel = lhrandom(50, 113);
1172 if (cl_particles_quake.integer)
1173 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);
1175 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);
1179 if (!cl_particles_quake.integer)
1180 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);
1181 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);
1183 else if (effectnameindex == EFFECT_TE_TEI_G3)
1184 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);
1185 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1187 if (cl_particles_smoke.integer)
1189 count *= 0.25f * cl_particles_quality.value;
1191 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1194 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1196 CL_ParticleExplosion(center);
1197 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);
1199 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1202 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1203 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1204 if (cl_particles_smoke.integer)
1205 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1206 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);
1207 if (cl_particles_sparks.integer)
1208 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1209 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);
1210 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);
1212 else if (effectnameindex == EFFECT_EF_FLAME)
1214 if (!spawnparticles)
1216 count *= 300 * cl_particles_quality.value;
1218 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);
1219 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);
1221 else if (effectnameindex == EFFECT_EF_STARDUST)
1223 if (!spawnparticles)
1225 count *= 200 * cl_particles_quality.value;
1227 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);
1228 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);
1230 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1234 int smoke, blood, bubbles, r, color;
1236 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1239 Vector4Set(light, 0, 0, 0, 0);
1241 if (effectnameindex == EFFECT_TR_ROCKET)
1242 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1243 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1245 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1246 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1248 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1250 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1251 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1255 matrix4x4_t tempmatrix;
1256 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1257 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);
1258 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1262 if (!spawnparticles)
1265 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1268 VectorSubtract(originmaxs, originmins, dir);
1269 len = VectorNormalizeLength(dir);
1272 dec = -ent->persistent.trail_time;
1273 ent->persistent.trail_time += len;
1274 if (ent->persistent.trail_time < 0.01f)
1277 // if we skip out, leave it reset
1278 ent->persistent.trail_time = 0.0f;
1283 // advance into this frame to reach the first puff location
1284 VectorMA(originmins, dec, dir, pos);
1287 smoke = cl_particles.integer && cl_particles_smoke.integer;
1288 blood = cl_particles.integer && cl_particles_blood.integer;
1289 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1290 qd = 1.0f / cl_particles_quality.value;
1297 if (effectnameindex == EFFECT_TR_BLOOD)
1299 if (cl_particles_quake.integer)
1301 color = particlepalette[67 + (rand()&3)];
1302 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);
1307 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);
1310 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1312 if (cl_particles_quake.integer)
1315 color = particlepalette[67 + (rand()&3)];
1316 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);
1321 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);
1327 if (effectnameindex == EFFECT_TR_ROCKET)
1329 if (cl_particles_quake.integer)
1332 color = particlepalette[ramp3[r]];
1333 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);
1337 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);
1338 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);
1341 else if (effectnameindex == EFFECT_TR_GRENADE)
1343 if (cl_particles_quake.integer)
1346 color = particlepalette[ramp3[r]];
1347 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);
1351 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);
1354 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1356 if (cl_particles_quake.integer)
1359 color = particlepalette[52 + (rand()&7)];
1360 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);
1361 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1363 else if (gamemode == GAME_GOODVSBAD2)
1366 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);
1370 color = particlepalette[20 + (rand()&7)];
1371 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);
1374 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1376 if (cl_particles_quake.integer)
1379 color = particlepalette[230 + (rand()&7)];
1380 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);
1381 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);
1385 color = particlepalette[226 + (rand()&7)];
1386 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);
1389 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1391 if (cl_particles_quake.integer)
1393 color = particlepalette[152 + (rand()&3)];
1394 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);
1396 else if (gamemode == GAME_GOODVSBAD2)
1399 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);
1401 else if (gamemode == GAME_PRYDON)
1404 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);
1407 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);
1409 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1412 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);
1414 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1417 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);
1419 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1420 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);
1424 if (effectnameindex == EFFECT_TR_ROCKET)
1425 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);
1426 else if (effectnameindex == EFFECT_TR_GRENADE)
1427 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);
1429 // advance to next time and position
1432 VectorMA (pos, dec, dir, pos);
1435 ent->persistent.trail_time = len;
1438 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1441 // this is also called on point effects with spawndlight = true and
1442 // spawnparticles = true
1443 // it is called CL_ParticleTrail because most code does not want to supply
1444 // these parameters, only trail handling does
1445 static void CL_NewParticlesFromEffectinfo(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], float fade, qboolean istrail)
1447 qboolean found = false;
1449 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1451 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1452 return; // no such effect
1454 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1456 int effectinfoindex;
1459 particleeffectinfo_t *info;
1471 qboolean underwater;
1472 qboolean immediatebloodstain;
1474 float avgtint[4], tint[4], tintlerp;
1475 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1476 VectorLerp(originmins, 0.5, originmaxs, center);
1477 supercontents = CL_PointSuperContents(center);
1478 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1479 VectorSubtract(originmaxs, originmins, traildir);
1480 traillen = VectorLength(traildir);
1481 VectorNormalize(traildir);
1484 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1488 Vector4Set(avgtint, 1, 1, 1, 1);
1490 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1492 if (info->effectnameindex == effectnameindex)
1495 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1497 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1500 // if trailspacing is set, only ever use this effect as trail
1501 if (info->trailspacing > 0 && !istrail)
1504 // spawn a dlight if requested
1505 if (info->lightradiusstart > 0 && spawndlight)
1507 matrix4x4_t tempmatrix;
1509 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1511 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1512 if (info->lighttime > 0 && info->lightradiusfade > 0)
1514 // light flash (explosion, etc)
1515 // called when effect starts
1516 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);
1518 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1521 // called by CL_LinkNetworkEntity
1522 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1523 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1524 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1525 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1526 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);
1527 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1531 if (!spawnparticles)
1536 if (info->tex[1] > info->tex[0])
1538 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1539 tex = min(tex, info->tex[1] - 1);
1541 if(info->staintex[0] < 0)
1542 staintex = info->staintex[0];
1545 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1546 staintex = min(staintex, info->staintex[1] - 1);
1548 if (info->particletype == pt_decal)
1550 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1551 AnglesFromVectors(angles, velocity, NULL, false);
1552 AngleVectors(angles, forward, right, up);
1553 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1555 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]);
1557 else if (info->orientation == PARTICLE_HBEAM)
1559 AnglesFromVectors(angles, traildir, NULL, false);
1560 AngleVectors(angles, forward, right, up);
1561 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1563 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);
1567 if (!cl_particles.integer)
1569 switch (info->particletype)
1571 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1572 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1573 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1574 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1575 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1576 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1579 VectorCopy(originmins, trailpos);
1582 float cnt = info->countabsolute;
1583 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1584 if (info->trailspacing > 0)
1585 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1587 info->particleaccumulator += cnt;
1588 trailstep = traillen / cnt;
1589 immediatebloodstain = false;
1591 AnglesFromVectors(angles, traildir, NULL, false);
1595 float cnt = info->countabsolute;
1596 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1598 info->particleaccumulator += cnt;
1600 immediatebloodstain =
1601 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1603 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1605 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1606 AnglesFromVectors(angles, velocity, NULL, false);
1608 AngleVectors(angles, forward, right, up);
1609 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1610 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1611 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1612 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1614 if (info->tex[1] > info->tex[0])
1616 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1617 tex = min(tex, info->tex[1] - 1);
1621 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1622 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1623 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1627 tintlerp = lhrandom(0, 1);
1628 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1631 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);
1632 if (immediatebloodstain && part)
1634 immediatebloodstain = false;
1635 CL_ImmediateBloodStain(part);
1638 VectorMA(trailpos, trailstep, traildir, trailpos);
1645 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1648 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], float fade)
1650 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1653 void CL_ParticleBox(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], float fade)
1655 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1658 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)
1660 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1668 void CL_EntityParticles (const entity_t *ent)
1671 vec_t pitch, yaw, dist = 64, beamlength = 16;
1673 static vec3_t avelocities[NUMVERTEXNORMALS];
1674 if (!cl_particles.integer) return;
1675 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1677 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1679 if (!avelocities[0][0])
1680 for (i = 0;i < NUMVERTEXNORMALS;i++)
1681 for (j = 0;j < 3;j++)
1682 avelocities[i][j] = lhrandom(0, 2.55);
1684 for (i = 0;i < NUMVERTEXNORMALS;i++)
1686 yaw = cl.time * avelocities[i][0];
1687 pitch = cl.time * avelocities[i][1];
1688 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1689 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1690 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1691 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);
1696 void CL_ReadPointFile_f (void)
1698 double org[3], leakorg[3];
1701 char *pointfile = NULL, *pointfilepos, *t, tchar;
1702 char name[MAX_QPATH];
1707 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1708 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1711 Con_Printf("Could not open %s\n", name);
1715 Con_Printf("Reading %s...\n", name);
1716 VectorClear(leakorg);
1719 pointfilepos = pointfile;
1720 while (*pointfilepos)
1722 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1727 while (*t && *t != '\n' && *t != '\r')
1731 #if _MSC_VER >= 1400
1732 #define sscanf sscanf_s
1734 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1735 VectorCopy(org, vecorg);
1741 VectorCopy(org, leakorg);
1744 if (cl.num_particles < cl.max_particles - 3)
1747 CL_NewParticle(vecorg, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1750 Mem_Free(pointfile);
1751 VectorCopy(leakorg, vecorg);
1752 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1754 CL_NewParticle(vecorg, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1755 CL_NewParticle(vecorg, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1756 CL_NewParticle(vecorg, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1761 CL_ParseParticleEffect
1763 Parse an effect out of the server message
1766 void CL_ParseParticleEffect (void)
1769 int i, count, msgcount, color;
1771 MSG_ReadVector(&cl_message, org, cls.protocol);
1772 for (i=0 ; i<3 ; i++)
1773 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1774 msgcount = MSG_ReadByte(&cl_message);
1775 color = MSG_ReadByte(&cl_message);
1777 if (msgcount == 255)
1782 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1787 CL_ParticleExplosion
1791 void CL_ParticleExplosion (const vec3_t org)
1797 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1798 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1800 if (cl_particles_quake.integer)
1802 for (i = 0;i < 1024;i++)
1808 color = particlepalette[ramp1[r]];
1809 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);
1813 color = particlepalette[ramp2[r]];
1814 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);
1820 i = CL_PointSuperContents(org);
1821 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1823 if (cl_particles.integer && cl_particles_bubbles.integer)
1824 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1825 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);
1829 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1831 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1838 VectorMA(org, 128, v2, v);
1839 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1841 while (k < 16 && trace.fraction < 0.1f);
1842 VectorSubtract(trace.endpos, org, v2);
1843 VectorScale(v2, 2.0f, v2);
1844 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);
1850 if (cl_particles_explosions_shell.integer)
1851 R_NewExplosion(org);
1856 CL_ParticleExplosion2
1860 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1863 if (!cl_particles.integer) return;
1865 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1867 k = particlepalette[colorStart + (i % colorLength)];
1868 if (cl_particles_quake.integer)
1869 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);
1871 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);
1875 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1878 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1879 if (cl_particles_sparks.integer)
1881 sparkcount *= cl_particles_quality.value;
1882 while(sparkcount-- > 0)
1883 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);
1887 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1890 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1891 if (cl_particles_smoke.integer)
1893 smokecount *= cl_particles_quality.value;
1894 while(smokecount-- > 0)
1895 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);
1899 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)
1903 if (!cl_particles.integer) return;
1904 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1906 count = (int)(count * cl_particles_quality.value);
1909 k = particlepalette[colorbase + (rand()&3)];
1910 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);
1914 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1917 float minz, maxz, lifetime = 30;
1919 if (!cl_particles.integer) return;
1920 if (dir[2] < 0) // falling
1922 minz = maxs[2] + dir[2] * 0.1;
1925 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1930 maxz = maxs[2] + dir[2] * 0.1;
1932 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1935 count = (int)(count * cl_particles_quality.value);
1940 if (!cl_particles_rain.integer) break;
1941 count *= 4; // ick, this should be in the mod or maps?
1945 k = particlepalette[colorbase + (rand()&3)];
1946 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1947 if (gamemode == GAME_GOODVSBAD2)
1948 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);
1950 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);
1954 if (!cl_particles_snow.integer) break;
1957 k = particlepalette[colorbase + (rand()&3)];
1958 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1959 if (gamemode == GAME_GOODVSBAD2)
1960 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);
1962 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);
1966 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1970 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1971 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1972 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1973 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1974 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1975 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1977 #define PARTICLETEXTURESIZE 64
1978 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1980 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1984 dz = 1 - (dx*dx+dy*dy);
1985 if (dz > 0) // it does hit the sphere
1989 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1990 VectorNormalize(normal);
1991 dot = DotProduct(normal, light);
1992 if (dot > 0.5) // interior reflection
1993 f += ((dot * 2) - 1);
1994 else if (dot < -0.5) // exterior reflection
1995 f += ((dot * -2) - 1);
1997 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1998 VectorNormalize(normal);
1999 dot = DotProduct(normal, light);
2000 if (dot > 0.5) // interior reflection
2001 f += ((dot * 2) - 1);
2002 else if (dot < -0.5) // exterior reflection
2003 f += ((dot * -2) - 1);
2005 f += 16; // just to give it a haze so you can see the outline
2006 f = bound(0, f, 255);
2007 return (unsigned char) f;
2013 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2014 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2016 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2017 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2018 *width = particlefontcellwidth;
2019 *height = particlefontcellheight;
2022 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2024 int basex, basey, w, h, y;
2025 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2026 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2027 Sys_Error("invalid particle texture size for autogenerating");
2028 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2029 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2032 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2035 float cx, cy, dx, dy, f, iradius;
2037 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2038 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2039 iradius = 1.0f / radius;
2040 alpha *= (1.0f / 255.0f);
2041 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2043 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2047 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2052 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2053 d[0] += (int)(f * (blue - d[0]));
2054 d[1] += (int)(f * (green - d[1]));
2055 d[2] += (int)(f * (red - d[2]));
2062 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2065 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2067 data[0] = bound(minb, data[0], maxb);
2068 data[1] = bound(ming, data[1], maxg);
2069 data[2] = bound(minr, data[2], maxr);
2074 static void particletextureinvert(unsigned char *data)
2077 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2079 data[0] = 255 - data[0];
2080 data[1] = 255 - data[1];
2081 data[2] = 255 - data[2];
2085 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2086 static void R_InitBloodTextures (unsigned char *particletexturedata)
2089 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2090 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2093 for (i = 0;i < 8;i++)
2095 memset(data, 255, datasize);
2096 for (k = 0;k < 24;k++)
2097 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2098 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2099 particletextureinvert(data);
2100 setuptex(tex_bloodparticle[i], data, particletexturedata);
2104 for (i = 0;i < 8;i++)
2106 memset(data, 255, datasize);
2108 for (j = 1;j < 10;j++)
2109 for (k = min(j, m - 1);k < m;k++)
2110 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2111 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2112 particletextureinvert(data);
2113 setuptex(tex_blooddecal[i], data, particletexturedata);
2119 //uncomment this to make engine save out particle font to a tga file when run
2120 //#define DUMPPARTICLEFONT
2122 static void R_InitParticleTexture (void)
2124 int x, y, d, i, k, m;
2125 int basex, basey, w, h;
2126 float dx, dy, f, s1, t1, s2, t2;
2129 fs_offset_t filesize;
2130 char texturename[MAX_QPATH];
2133 // a note: decals need to modulate (multiply) the background color to
2134 // properly darken it (stain), and they need to be able to alpha fade,
2135 // this is a very difficult challenge because it means fading to white
2136 // (no change to background) rather than black (darkening everything
2137 // behind the whole decal polygon), and to accomplish this the texture is
2138 // inverted (dark red blood on white background becomes brilliant cyan
2139 // and white on black background) so we can alpha fade it to black, then
2140 // we invert it again during the blendfunc to make it work...
2142 #ifndef DUMPPARTICLEFONT
2143 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2146 particlefonttexture = decalskinframe->base;
2147 // TODO maybe allow custom grid size?
2148 particlefontwidth = image_width;
2149 particlefontheight = image_height;
2150 particlefontcellwidth = image_width / 8;
2151 particlefontcellheight = image_height / 8;
2152 particlefontcols = 8;
2153 particlefontrows = 8;
2158 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2159 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2160 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2161 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2162 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2164 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2165 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2166 particlefontcols = 8;
2167 particlefontrows = 8;
2169 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2172 for (i = 0;i < 8;i++)
2174 memset(data, 255, datasize);
2177 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2178 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2180 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2182 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2183 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2185 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2186 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2188 d = (int)(d * (1-(dx*dx+dy*dy)));
2189 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2190 d = bound(0, d, 255);
2191 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2198 setuptex(tex_smoke[i], data, particletexturedata);
2202 memset(data, 255, datasize);
2203 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2205 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2206 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2208 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2209 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2210 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2213 setuptex(tex_rainsplash, data, particletexturedata);
2216 memset(data, 255, datasize);
2217 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2219 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2220 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2222 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2223 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2224 d = bound(0, d, 255);
2225 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2228 setuptex(tex_particle, data, particletexturedata);
2231 memset(data, 255, datasize);
2232 light[0] = 1;light[1] = 1;light[2] = 1;
2233 VectorNormalize(light);
2234 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2236 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2237 // stretch upper half of bubble by +50% and shrink lower half by -50%
2238 // (this gives an elongated teardrop shape)
2240 dy = (dy - 0.5f) * 2.0f;
2242 dy = (dy - 0.5f) / 1.5f;
2243 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2245 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2246 // shrink bubble width to half
2248 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2251 setuptex(tex_raindrop, data, particletexturedata);
2254 memset(data, 255, datasize);
2255 light[0] = 1;light[1] = 1;light[2] = 1;
2256 VectorNormalize(light);
2257 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2259 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2260 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2262 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2263 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2266 setuptex(tex_bubble, data, particletexturedata);
2268 // Blood particles and blood decals
2269 R_InitBloodTextures (particletexturedata);
2272 for (i = 0;i < 8;i++)
2274 memset(data, 255, datasize);
2275 for (k = 0;k < 12;k++)
2276 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2277 for (k = 0;k < 3;k++)
2278 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2279 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2280 particletextureinvert(data);
2281 setuptex(tex_bulletdecal[i], data, particletexturedata);
2284 #ifdef DUMPPARTICLEFONT
2285 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2288 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2289 particlefonttexture = decalskinframe->base;
2291 Mem_Free(particletexturedata);
2296 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2298 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2299 particletexture[i].texture = particlefonttexture;
2300 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2301 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2302 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2303 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2306 #ifndef DUMPPARTICLEFONT
2307 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2308 if (!particletexture[tex_beam].texture)
2311 unsigned char noise3[64][64], data2[64][16][4];
2313 fractalnoise(&noise3[0][0], 64, 4);
2315 for (y = 0;y < 64;y++)
2317 dy = (y - 0.5f*64) / (64*0.5f-1);
2318 for (x = 0;x < 16;x++)
2320 dx = (x - 0.5f*16) / (16*0.5f-2);
2321 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2322 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2323 data2[y][x][3] = 255;
2327 #ifdef DUMPPARTICLEFONT
2328 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2330 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2332 particletexture[tex_beam].s1 = 0;
2333 particletexture[tex_beam].t1 = 0;
2334 particletexture[tex_beam].s2 = 1;
2335 particletexture[tex_beam].t2 = 1;
2337 // now load an texcoord/texture override file
2338 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2345 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2347 if(!strcmp(com_token, "\n"))
2348 continue; // empty line
2349 i = atoi(com_token);
2357 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2359 strlcpy(texturename, com_token, sizeof(texturename));
2360 s1 = atof(com_token);
2361 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2364 t1 = atof(com_token);
2365 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2367 s2 = atof(com_token);
2368 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2370 t2 = atof(com_token);
2371 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2372 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2373 strlcpy(texturename, com_token, sizeof(texturename));
2380 if (!texturename[0])
2382 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2385 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2387 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2390 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2393 // R_SkinFrame_LoadExternal already complained
2396 particletexture[i].texture = sf->base;
2397 particletexture[i].s1 = s1;
2398 particletexture[i].t1 = t1;
2399 particletexture[i].s2 = s2;
2400 particletexture[i].t2 = t2;
2406 static void r_part_start(void)
2409 // generate particlepalette for convenience from the main one
2410 for (i = 0;i < 256;i++)
2411 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2412 particletexturepool = R_AllocTexturePool();
2413 R_InitParticleTexture ();
2414 CL_Particles_LoadEffectInfo(NULL);
2417 static void r_part_shutdown(void)
2419 R_FreeTexturePool(&particletexturepool);
2422 static void r_part_newmap(void)
2425 R_SkinFrame_MarkUsed(decalskinframe);
2426 CL_Particles_LoadEffectInfo(NULL);
2429 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2430 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2432 void R_Particles_Init (void)
2435 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2437 particle_elements[i*6+0] = i*4+0;
2438 particle_elements[i*6+1] = i*4+1;
2439 particle_elements[i*6+2] = i*4+2;
2440 particle_elements[i*6+3] = i*4+0;
2441 particle_elements[i*6+4] = i*4+2;
2442 particle_elements[i*6+5] = i*4+3;
2445 Cvar_RegisterVariable(&r_drawparticles);
2446 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2447 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2448 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2449 Cvar_RegisterVariable(&r_drawdecals);
2450 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2451 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2454 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2456 int surfacelistindex;
2458 float *v3f, *t2f, *c4f;
2459 particletexture_t *tex;
2460 vec_t right[3], up[3], size, ca;
2461 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2463 RSurf_ActiveWorldEntity();
2465 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2466 // R_Mesh_ResetTextureState();
2467 GL_DepthMask(false);
2468 GL_DepthRange(0, 1);
2469 GL_PolygonOffset(0, 0);
2471 GL_CullFace(GL_NONE);
2473 // generate all the vertices at once
2474 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2476 d = cl.decals + surfacelist[surfacelistindex];
2479 c4f = particle_color4f + 16*surfacelistindex;
2480 ca = d->alpha * alphascale;
2481 // ensure alpha multiplier saturates properly
2482 if (ca > 1.0f / 256.0f)
2484 if (r_refdef.fogenabled)
2485 ca *= RSurf_FogVertex(d->org);
2486 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2487 Vector4Copy(c4f, c4f + 4);
2488 Vector4Copy(c4f, c4f + 8);
2489 Vector4Copy(c4f, c4f + 12);
2491 // calculate vertex positions
2492 size = d->size * cl_particles_size.value;
2493 VectorVectors(d->normal, right, up);
2494 VectorScale(right, size, right);
2495 VectorScale(up, size, up);
2496 v3f = particle_vertex3f + 12*surfacelistindex;
2497 v3f[ 0] = d->org[0] - right[0] - up[0];
2498 v3f[ 1] = d->org[1] - right[1] - up[1];
2499 v3f[ 2] = d->org[2] - right[2] - up[2];
2500 v3f[ 3] = d->org[0] - right[0] + up[0];
2501 v3f[ 4] = d->org[1] - right[1] + up[1];
2502 v3f[ 5] = d->org[2] - right[2] + up[2];
2503 v3f[ 6] = d->org[0] + right[0] + up[0];
2504 v3f[ 7] = d->org[1] + right[1] + up[1];
2505 v3f[ 8] = d->org[2] + right[2] + up[2];
2506 v3f[ 9] = d->org[0] + right[0] - up[0];
2507 v3f[10] = d->org[1] + right[1] - up[1];
2508 v3f[11] = d->org[2] + right[2] - up[2];
2510 // calculate texcoords
2511 tex = &particletexture[d->texnum];
2512 t2f = particle_texcoord2f + 8*surfacelistindex;
2513 t2f[0] = tex->s1;t2f[1] = tex->t2;
2514 t2f[2] = tex->s1;t2f[3] = tex->t1;
2515 t2f[4] = tex->s2;t2f[5] = tex->t1;
2516 t2f[6] = tex->s2;t2f[7] = tex->t2;
2519 // now render the decals all at once
2520 // (this assumes they all use one particle font texture!)
2521 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2522 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2523 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2524 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2527 void R_DrawDecals (void)
2530 int drawdecals = r_drawdecals.integer;
2535 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2537 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2538 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2540 // LordHavoc: early out conditions
2544 decalfade = frametime * 256 / cl_decals_fadetime.value;
2545 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2546 drawdist2 = drawdist2*drawdist2;
2548 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2550 if (!decal->typeindex)
2553 if (killsequence - decal->decalsequence > 0)
2556 if (cl.time > decal->time2 + cl_decals_time.value)
2558 decal->alpha -= decalfade;
2559 if (decal->alpha <= 0)
2565 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2567 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2568 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2574 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2580 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))
2581 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2584 decal->typeindex = 0;
2585 if (cl.free_decal > i)
2589 // reduce cl.num_decals if possible
2590 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2593 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2595 decal_t *olddecals = cl.decals;
2596 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2597 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2598 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2599 Mem_Free(olddecals);
2602 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2605 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2607 vec3_t vecorg, vecvel, baseright, baseup;
2608 int surfacelistindex;
2609 int batchstart, batchcount;
2610 const particle_t *p;
2612 rtexture_t *texture;
2613 float *v3f, *t2f, *c4f;
2614 particletexture_t *tex;
2615 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2616 // float ambient[3], diffuse[3], diffusenormal[3];
2617 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2618 vec4_t colormultiplier;
2619 float minparticledist_start, minparticledist_end;
2622 RSurf_ActiveWorldEntity();
2624 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));
2626 r_refdef.stats[r_stat_particles] += numsurfaces;
2627 // R_Mesh_ResetTextureState();
2628 GL_DepthMask(false);
2629 GL_DepthRange(0, 1);
2630 GL_PolygonOffset(0, 0);
2632 GL_CullFace(GL_NONE);
2634 spintime = r_refdef.scene.time;
2636 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2637 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2638 dofade = (minparticledist_start < minparticledist_end);
2640 // first generate all the vertices at once
2641 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2643 p = cl.particles + surfacelist[surfacelistindex];
2645 blendmode = (pblend_t)p->blendmode;
2647 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2648 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2649 alpha = palpha * colormultiplier[3];
2650 // ensure alpha multiplier saturates properly
2656 case PBLEND_INVALID:
2658 // additive and modulate can just fade out in fog (this is correct)
2659 if (r_refdef.fogenabled)
2660 alpha *= RSurf_FogVertex(p->org);
2661 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2662 alpha *= 1.0f / 256.0f;
2663 c4f[0] = p->color[0] * alpha;
2664 c4f[1] = p->color[1] * alpha;
2665 c4f[2] = p->color[2] * alpha;
2669 // additive and modulate can just fade out in fog (this is correct)
2670 if (r_refdef.fogenabled)
2671 alpha *= RSurf_FogVertex(p->org);
2672 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2673 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2674 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2675 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2679 c4f[0] = p->color[0] * colormultiplier[0];
2680 c4f[1] = p->color[1] * colormultiplier[1];
2681 c4f[2] = p->color[2] * colormultiplier[2];
2683 // note: lighting is not cheap!
2684 if (particletype[p->typeindex].lighting)
2686 vecorg[0] = p->org[0];
2687 vecorg[1] = p->org[1];
2688 vecorg[2] = p->org[2];
2689 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2691 // mix in the fog color
2692 if (r_refdef.fogenabled)
2694 fog = RSurf_FogVertex(p->org);
2696 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2697 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2698 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2700 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2701 VectorScale(c4f, alpha, c4f);
2704 // copy the color into the other three vertices
2705 Vector4Copy(c4f, c4f + 4);
2706 Vector4Copy(c4f, c4f + 8);
2707 Vector4Copy(c4f, c4f + 12);
2709 size = p->size * cl_particles_size.value;
2710 tex = &particletexture[p->texnum];
2711 switch(p->orientation)
2713 // case PARTICLE_INVALID:
2714 case PARTICLE_BILLBOARD:
2715 if (p->angle + p->spin)
2717 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2718 spinsin = sin(spinrad) * size;
2719 spincos = cos(spinrad) * size;
2720 spinm1 = -p->stretch * spincos;
2723 spinm4 = -p->stretch * spincos;
2724 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2725 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2729 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2730 VectorScale(r_refdef.view.up, size, up);
2733 v3f[ 0] = p->org[0] - right[0] - up[0];
2734 v3f[ 1] = p->org[1] - right[1] - up[1];
2735 v3f[ 2] = p->org[2] - right[2] - up[2];
2736 v3f[ 3] = p->org[0] - right[0] + up[0];
2737 v3f[ 4] = p->org[1] - right[1] + up[1];
2738 v3f[ 5] = p->org[2] - right[2] + up[2];
2739 v3f[ 6] = p->org[0] + right[0] + up[0];
2740 v3f[ 7] = p->org[1] + right[1] + up[1];
2741 v3f[ 8] = p->org[2] + right[2] + up[2];
2742 v3f[ 9] = p->org[0] + right[0] - up[0];
2743 v3f[10] = p->org[1] + right[1] - up[1];
2744 v3f[11] = p->org[2] + right[2] - up[2];
2745 t2f[0] = tex->s1;t2f[1] = tex->t2;
2746 t2f[2] = tex->s1;t2f[3] = tex->t1;
2747 t2f[4] = tex->s2;t2f[5] = tex->t1;
2748 t2f[6] = tex->s2;t2f[7] = tex->t2;
2750 case PARTICLE_ORIENTED_DOUBLESIDED:
2751 vecvel[0] = p->vel[0];
2752 vecvel[1] = p->vel[1];
2753 vecvel[2] = p->vel[2];
2754 VectorVectors(vecvel, baseright, baseup);
2755 if (p->angle + p->spin)
2757 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2758 spinsin = sin(spinrad) * size;
2759 spincos = cos(spinrad) * size;
2760 spinm1 = p->stretch * spincos;
2763 spinm4 = p->stretch * spincos;
2764 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2765 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2769 VectorScale(baseright, size * p->stretch, right);
2770 VectorScale(baseup, size, up);
2772 v3f[ 0] = p->org[0] - right[0] - up[0];
2773 v3f[ 1] = p->org[1] - right[1] - up[1];
2774 v3f[ 2] = p->org[2] - right[2] - up[2];
2775 v3f[ 3] = p->org[0] - right[0] + up[0];
2776 v3f[ 4] = p->org[1] - right[1] + up[1];
2777 v3f[ 5] = p->org[2] - right[2] + up[2];
2778 v3f[ 6] = p->org[0] + right[0] + up[0];
2779 v3f[ 7] = p->org[1] + right[1] + up[1];
2780 v3f[ 8] = p->org[2] + right[2] + up[2];
2781 v3f[ 9] = p->org[0] + right[0] - up[0];
2782 v3f[10] = p->org[1] + right[1] - up[1];
2783 v3f[11] = p->org[2] + right[2] - up[2];
2784 t2f[0] = tex->s1;t2f[1] = tex->t2;
2785 t2f[2] = tex->s1;t2f[3] = tex->t1;
2786 t2f[4] = tex->s2;t2f[5] = tex->t1;
2787 t2f[6] = tex->s2;t2f[7] = tex->t2;
2789 case PARTICLE_SPARK:
2790 len = VectorLength(p->vel);
2791 VectorNormalize2(p->vel, up);
2792 lenfactor = p->stretch * 0.04 * len;
2793 if(lenfactor < size * 0.5)
2794 lenfactor = size * 0.5;
2795 VectorMA(p->org, -lenfactor, up, v);
2796 VectorMA(p->org, lenfactor, up, up2);
2797 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2798 t2f[0] = tex->s1;t2f[1] = tex->t2;
2799 t2f[2] = tex->s1;t2f[3] = tex->t1;
2800 t2f[4] = tex->s2;t2f[5] = tex->t1;
2801 t2f[6] = tex->s2;t2f[7] = tex->t2;
2803 case PARTICLE_VBEAM:
2804 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2805 VectorSubtract(p->vel, p->org, up);
2806 VectorNormalize(up);
2807 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2808 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2809 t2f[0] = tex->s2;t2f[1] = v[0];
2810 t2f[2] = tex->s1;t2f[3] = v[0];
2811 t2f[4] = tex->s1;t2f[5] = v[1];
2812 t2f[6] = tex->s2;t2f[7] = v[1];
2814 case PARTICLE_HBEAM:
2815 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2816 VectorSubtract(p->vel, p->org, up);
2817 VectorNormalize(up);
2818 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2819 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2820 t2f[0] = v[0];t2f[1] = tex->t1;
2821 t2f[2] = v[0];t2f[3] = tex->t2;
2822 t2f[4] = v[1];t2f[5] = tex->t2;
2823 t2f[6] = v[1];t2f[7] = tex->t1;
2828 // now render batches of particles based on blendmode and texture
2829 blendmode = PBLEND_INVALID;
2833 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2834 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2836 p = cl.particles + surfacelist[surfacelistindex];
2838 if (texture != particletexture[p->texnum].texture)
2840 texture = particletexture[p->texnum].texture;
2841 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2844 if (p->blendmode == PBLEND_INVMOD)
2846 // inverse modulate blend - group these
2847 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2848 // iterate until we find a change in settings
2849 batchstart = surfacelistindex++;
2850 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2852 p = cl.particles + surfacelist[surfacelistindex];
2853 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2859 // additive or alpha blend - group these
2860 // (we can group these because we premultiplied the texture alpha)
2861 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2862 // iterate until we find a change in settings
2863 batchstart = surfacelistindex++;
2864 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2866 p = cl.particles + surfacelist[surfacelistindex];
2867 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2872 batchcount = surfacelistindex - batchstart;
2873 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2877 void R_DrawParticles (void)
2880 int drawparticles = r_drawparticles.integer;
2881 float minparticledist_start;
2883 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2889 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2890 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2892 // LordHavoc: early out conditions
2893 if (!cl.num_particles)
2896 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2897 gravity = frametime * cl.movevars_gravity;
2898 update = frametime > 0;
2899 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2900 drawdist2 = drawdist2*drawdist2;
2902 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2906 if (cl.free_particle > i)
2907 cl.free_particle = i;
2913 if (p->delayedspawn > cl.time)
2916 p->size += p->sizeincrease * frametime;
2917 p->alpha -= p->alphafade * frametime;
2919 if (p->alpha <= 0 || p->die <= cl.time)
2922 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2924 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2926 if (p->typeindex == pt_blood)
2927 p->size += frametime * 8;
2929 p->vel[2] -= p->gravity * gravity;
2930 f = 1.0f - min(p->liquidfriction * frametime, 1);
2931 VectorScale(p->vel, f, p->vel);
2935 p->vel[2] -= p->gravity * gravity;
2938 f = 1.0f - min(p->airfriction * frametime, 1);
2939 VectorScale(p->vel, f, p->vel);
2943 VectorCopy(p->org, oldorg);
2944 VectorMA(p->org, frametime, p->vel, p->org);
2945 // if (p->bounce && cl.time >= p->delayedcollisions)
2946 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2948 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);
2949 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2950 // or if the trace hit something flagged as NOIMPACT
2951 // then remove the particle
2952 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2954 VectorCopy(trace.endpos, p->org);
2955 // react if the particle hit something
2956 if (trace.fraction < 1)
2958 VectorCopy(trace.endpos, p->org);
2960 if (p->staintexnum >= 0)
2962 // blood - splash on solid
2963 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2966 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2967 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2968 if (cl_decals.integer)
2970 // create a decal for the blood splat
2971 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2972 if (cl_decals_newsystem_bloodsmears.integer)
2974 VectorCopy(p->vel, decaldir);
2975 VectorNormalize(decaldir);
2978 VectorCopy(trace.plane.normal, decaldir);
2979 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2984 if (p->typeindex == pt_blood)
2986 // blood - splash on solid
2987 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2989 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2991 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)));
2992 if (cl_decals.integer)
2994 // create a decal for the blood splat
2995 if (cl_decals_newsystem_bloodsmears.integer)
2997 VectorCopy(p->vel, decaldir);
2998 VectorNormalize(decaldir);
3001 VectorCopy(trace.plane.normal, decaldir);
3002 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, 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);
3007 else if (p->bounce < 0)
3009 // bounce -1 means remove on impact
3014 // anything else - bounce off solid
3015 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3016 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3021 if (VectorLength2(p->vel) < 0.03)
3023 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3025 VectorClear(p->vel);
3029 if (p->typeindex != pt_static)
3031 switch (p->typeindex)
3033 case pt_entityparticle:
3034 // particle that removes itself after one rendered frame
3041 a = CL_PointSuperContents(p->org);
3042 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3046 a = CL_PointSuperContents(p->org);
3047 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3051 a = CL_PointSuperContents(p->org);
3052 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3056 if (cl.time > p->time2)
3059 p->time2 = cl.time + (rand() & 3) * 0.1;
3060 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3061 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3063 a = CL_PointSuperContents(p->org);
3064 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3072 else if (p->delayedspawn > cl.time)
3076 // don't render particles too close to the view (they chew fillrate)
3077 // also don't render particles behind the view (useless)
3078 // further checks to cull to the frustum would be too slow here
3079 switch(p->typeindex)
3082 // beams have no culling
3083 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3086 if(cl_particles_visculling.integer)
3087 if (!r_refdef.viewcache.world_novis)
3088 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3090 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3092 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3095 // anything else just has to be in front of the viewer and visible at this distance
3096 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3097 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3104 if (cl.free_particle > i)
3105 cl.free_particle = i;
3108 // reduce cl.num_particles if possible
3109 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3112 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3114 particle_t *oldparticles = cl.particles;
3115 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3116 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3117 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3118 Mem_Free(oldparticles);