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_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
300 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
301 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
302 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
303 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
304 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
305 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)"};
306 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"};
307 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"};
308 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
309 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
310 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
313 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
318 particleeffectinfo_t *info = NULL;
319 const char *text = textstart;
321 for (linenumber = 1;;linenumber++)
324 for (arrayindex = 0;arrayindex < 16;arrayindex++)
325 argv[arrayindex][0] = 0;
328 if (!COM_ParseToken_Simple(&text, true, false, true))
330 if (!strcmp(com_token, "\n"))
334 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
340 #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;}
341 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
342 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
343 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
344 #define readfloat(var) checkparms(2);var = atof(argv[1])
345 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
346 if (!strcmp(argv[0], "effect"))
350 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
352 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
355 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
357 if (particleeffectname[effectnameindex][0])
359 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
364 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
368 // if we run out of names, abort
369 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
371 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
374 info = particleeffectinfo + numparticleeffectinfo++;
375 // copy entire info from baseline, then fix up the nameindex
376 *info = baselineparticleeffectinfo;
377 info->effectnameindex = effectnameindex;
379 else if (info == NULL)
381 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
384 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
385 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
386 else if (!strcmp(argv[0], "type"))
389 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
390 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
391 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
392 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
393 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
394 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
395 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
396 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
397 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
398 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
399 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
400 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
401 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
402 info->blendmode = particletype[info->particletype].blendmode;
403 info->orientation = particletype[info->particletype].orientation;
405 else if (!strcmp(argv[0], "blend"))
408 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
409 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
410 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
411 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
413 else if (!strcmp(argv[0], "orientation"))
416 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
417 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
418 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
419 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
420 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
422 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
423 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
424 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
425 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
426 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
427 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
428 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
429 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
430 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
431 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
432 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
433 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
434 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
435 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
436 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
437 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
438 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
439 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
440 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
441 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
442 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
443 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
444 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
445 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
446 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
447 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
448 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
449 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
450 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
451 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
452 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
453 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
454 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; }
455 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
457 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
466 int CL_ParticleEffectIndexForName(const char *name)
469 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
470 if (!strcmp(particleeffectname[i], name))
475 const char *CL_ParticleEffectNameForIndex(int i)
477 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
479 return particleeffectname[i];
482 // MUST match effectnameindex_t in client.h
483 static const char *standardeffectnames[EFFECT_TOTAL] =
507 "TE_TEI_BIGEXPLOSION",
523 static void CL_Particles_LoadEffectInfo(const char *customfile)
527 unsigned char *filedata;
528 fs_offset_t filesize;
529 char filename[MAX_QPATH];
530 numparticleeffectinfo = 0;
531 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
532 memset(particleeffectname, 0, sizeof(particleeffectname));
533 for (i = 0;i < EFFECT_TOTAL;i++)
534 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
535 for (filepass = 0;;filepass++)
540 strlcpy(filename, customfile, sizeof(filename));
542 strlcpy(filename, "effectinfo.txt", sizeof(filename));
544 else if (filepass == 1)
546 if (!cl.worldbasename[0] || customfile)
548 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
552 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
555 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
560 static void CL_Particles_LoadEffectInfo_f(void)
562 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
570 void CL_ReadPointFile_f (void);
571 void CL_Particles_Init (void)
573 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)");
574 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)");
576 Cvar_RegisterVariable (&cl_particles);
577 Cvar_RegisterVariable (&cl_particles_quality);
578 Cvar_RegisterVariable (&cl_particles_alpha);
579 Cvar_RegisterVariable (&cl_particles_size);
580 Cvar_RegisterVariable (&cl_particles_quake);
581 Cvar_RegisterVariable (&cl_particles_blood);
582 Cvar_RegisterVariable (&cl_particles_blood_alpha);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
584 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
585 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
586 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
587 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
588 Cvar_RegisterVariable (&cl_particles_explosions_shell);
589 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
590 Cvar_RegisterVariable (&cl_particles_rain);
591 Cvar_RegisterVariable (&cl_particles_snow);
592 Cvar_RegisterVariable (&cl_particles_smoke);
593 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
594 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
595 Cvar_RegisterVariable (&cl_particles_sparks);
596 Cvar_RegisterVariable (&cl_particles_bubbles);
597 Cvar_RegisterVariable (&cl_particles_visculling);
598 Cvar_RegisterVariable (&cl_particles_collisions);
599 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
600 Cvar_RegisterVariable (&cl_decals);
601 Cvar_RegisterVariable (&cl_decals_visculling);
602 Cvar_RegisterVariable (&cl_decals_time);
603 Cvar_RegisterVariable (&cl_decals_fadetime);
604 Cvar_RegisterVariable (&cl_decals_newsystem);
605 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
606 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
607 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
608 Cvar_RegisterVariable (&cl_decals_models);
609 Cvar_RegisterVariable (&cl_decals_bias);
610 Cvar_RegisterVariable (&cl_decals_max);
613 void CL_Particles_Shutdown (void)
617 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
618 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
620 // list of all 26 parameters:
621 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
622 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
623 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
624 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
625 // palpha - opacity of particle as 0-255 (can be more than 255)
626 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
627 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
628 // pgravity - how much effect gravity has on the particle (0-1)
629 // 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
630 // px,py,pz - starting origin of particle
631 // pvx,pvy,pvz - starting velocity of particle
632 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
633 // blendmode - one of the PBLEND_ values
634 // orientation - one of the PARTICLE_ values
635 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
636 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
637 // stainalpha: opacity of the stain as factor for alpha
638 // stainsize: size of the stain as factor for palpha
639 // angle: base rotation of the particle geometry around its center normal
640 // spin: rotation speed of the particle geometry around its center normal
641 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])
646 if (!cl_particles.integer)
648 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
649 if (cl.free_particle >= cl.max_particles)
652 lifetime = palpha / min(1, palphafade);
653 part = &cl.particles[cl.free_particle++];
654 if (cl.num_particles < cl.free_particle)
655 cl.num_particles = cl.free_particle;
656 memset(part, 0, sizeof(*part));
657 VectorCopy(sortorigin, part->sortorigin);
658 part->typeindex = ptypeindex;
659 part->blendmode = blendmode;
660 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
662 particletexture_t *tex = &particletexture[ptex];
663 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
664 part->orientation = PARTICLE_VBEAM;
666 part->orientation = PARTICLE_HBEAM;
669 part->orientation = orientation;
670 l2 = (int)lhrandom(0.5, 256.5);
672 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
673 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
674 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
677 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
678 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
679 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
681 part->alpha = palpha;
682 part->alphafade = palphafade;
683 part->staintexnum = staintex;
684 if(staincolor1 >= 0 && staincolor2 >= 0)
686 l2 = (int)lhrandom(0.5, 256.5);
688 if(blendmode == PBLEND_INVMOD)
690 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
691 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
692 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
696 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
697 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
698 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
700 if(r > 0xFF) r = 0xFF;
701 if(g > 0xFF) g = 0xFF;
702 if(b > 0xFF) b = 0xFF;
706 r = part->color[0]; // -1 is shorthand for stain = particle color
710 part->staincolor[0] = r;
711 part->staincolor[1] = g;
712 part->staincolor[2] = b;
713 part->stainalpha = palpha * stainalpha;
714 part->stainsize = psize * stainsize;
717 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
719 part->color[0] *= tint[0];
720 part->color[1] *= tint[1];
721 part->color[2] *= tint[2];
723 part->alpha *= tint[3];
724 part->alphafade *= tint[3];
725 part->stainalpha *= tint[3];
729 part->sizeincrease = psizeincrease;
730 part->gravity = pgravity;
731 part->bounce = pbounce;
732 part->stretch = stretch;
734 part->org[0] = px + originjitter * v[0];
735 part->org[1] = py + originjitter * v[1];
736 part->org[2] = pz + originjitter * v[2];
737 part->vel[0] = pvx + velocityjitter * v[0];
738 part->vel[1] = pvy + velocityjitter * v[1];
739 part->vel[2] = pvz + velocityjitter * v[2];
741 part->airfriction = pairfriction;
742 part->liquidfriction = pliquidfriction;
743 part->die = cl.time + lifetime;
744 part->delayedspawn = cl.time;
745 // part->delayedcollisions = 0;
746 part->qualityreduction = pqualityreduction;
749 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
750 if (part->typeindex == pt_rain)
754 float lifetime = part->die - cl.time;
757 // turn raindrop into simple spark and create delayedspawn splash effect
758 part->typeindex = pt_spark;
760 VectorMA(part->org, lifetime, part->vel, endvec);
761 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
762 part->die = cl.time + lifetime * trace.fraction;
763 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);
766 part2->delayedspawn = part->die;
767 part2->die += part->die - cl.time;
768 for (i = rand() & 7;i < 10;i++)
770 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);
773 part2->delayedspawn = part->die;
774 part2->die += part->die - cl.time;
780 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
782 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
785 VectorMA(part->org, lifetime, part->vel, endvec);
786 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
787 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
794 static void CL_ImmediateBloodStain(particle_t *part)
799 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
800 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
802 VectorCopy(part->vel, v);
804 staintex = part->staintexnum;
805 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);
808 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
809 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
811 VectorCopy(part->vel, v);
813 staintex = tex_blooddecal[rand()&7];
814 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);
818 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
822 entity_render_t *ent = &cl.entities[hitent].render;
823 unsigned char color[3];
824 if (!cl_decals.integer)
826 if (!ent->allowdecals)
829 l2 = (int)lhrandom(0.5, 256.5);
831 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
832 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
833 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
835 if (cl_decals_newsystem.integer)
838 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);
840 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);
844 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
845 if (cl.free_decal >= cl.max_decals)
847 decal = &cl.decals[cl.free_decal++];
848 if (cl.num_decals < cl.free_decal)
849 cl.num_decals = cl.free_decal;
850 memset(decal, 0, sizeof(*decal));
851 decal->decalsequence = cl.decalsequence++;
852 decal->typeindex = pt_decal;
853 decal->texnum = texnum;
854 VectorMA(org, cl_decals_bias.value, normal, decal->org);
855 VectorCopy(normal, decal->normal);
857 decal->alpha = alpha;
858 decal->time2 = cl.time;
859 decal->color[0] = color[0];
860 decal->color[1] = color[1];
861 decal->color[2] = color[2];
864 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
865 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
866 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
868 decal->owner = hitent;
869 decal->clusterindex = -1000; // no vis culling unless we're sure
872 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
873 decal->ownermodel = cl.entities[decal->owner].render.model;
874 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
875 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
879 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
881 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
883 decal->clusterindex = leaf->clusterindex;
888 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
895 int besthitent = 0, hitent;
898 for (i = 0;i < 32;i++)
901 VectorMA(org, maxdist, org2, org2);
902 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
903 // take the closest trace result that doesn't end up hitting a NOMARKS
904 // surface (sky for example)
905 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
907 bestfrac = trace.fraction;
909 VectorCopy(trace.endpos, bestorg);
910 VectorCopy(trace.plane.normal, bestnormal);
914 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
917 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
918 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
919 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)
922 matrix4x4_t tempmatrix;
925 VectorLerp(originmins, 0.5, originmaxs, center);
926 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
927 if (effectnameindex == EFFECT_SVC_PARTICLE)
929 if (cl_particles.integer)
931 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
933 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
934 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
935 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
938 count *= cl_particles_quality.value;
939 for (;count > 0;count--)
941 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
942 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);
947 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
948 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
949 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
950 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
951 else if (effectnameindex == EFFECT_TE_SPIKE)
953 if (cl_particles_bulletimpacts.integer)
955 if (cl_particles_quake.integer)
957 if (cl_particles_smoke.integer)
958 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
962 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
963 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
964 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
968 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
969 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
971 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
973 if (cl_particles_bulletimpacts.integer)
975 if (cl_particles_quake.integer)
977 if (cl_particles_smoke.integer)
978 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
982 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
983 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
984 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);
988 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
989 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
990 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);
992 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
994 if (cl_particles_bulletimpacts.integer)
996 if (cl_particles_quake.integer)
998 if (cl_particles_smoke.integer)
999 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1003 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1004 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1005 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1009 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1010 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1012 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1014 if (cl_particles_bulletimpacts.integer)
1016 if (cl_particles_quake.integer)
1018 if (cl_particles_smoke.integer)
1019 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1023 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1024 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1025 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);
1029 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1030 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1031 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);
1033 else if (effectnameindex == EFFECT_TE_BLOOD)
1035 if (!cl_particles_blood.integer)
1037 if (cl_particles_quake.integer)
1038 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1041 static double bloodaccumulator = 0;
1042 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1043 //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);
1044 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1045 for (;bloodaccumulator > 0;bloodaccumulator--)
1047 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);
1048 if (immediatebloodstain && part)
1050 immediatebloodstain = false;
1051 CL_ImmediateBloodStain(part);
1056 else if (effectnameindex == EFFECT_TE_SPARK)
1057 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1058 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1060 // plasma scorch mark
1061 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1062 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1063 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1065 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1067 if (cl_particles_bulletimpacts.integer)
1069 if (cl_particles_quake.integer)
1070 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1073 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1074 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1075 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1079 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1080 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1082 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1084 if (cl_particles_bulletimpacts.integer)
1086 if (cl_particles_quake.integer)
1087 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1090 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1091 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1092 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);
1096 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1097 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1098 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);
1100 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1102 CL_ParticleExplosion(center);
1103 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);
1105 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1107 CL_ParticleExplosion(center);
1108 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);
1110 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1112 if (cl_particles_quake.integer)
1115 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1118 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);
1120 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);
1124 CL_ParticleExplosion(center);
1125 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);
1127 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1128 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);
1129 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1131 count *= cl_particles_quality.value;
1133 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);
1135 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1137 float i, j, inc, vel;
1140 inc = 8 / cl_particles_quality.value;
1141 for (i = -128;i < 128;i += inc)
1143 for (j = -128;j < 128;j += inc)
1145 dir[0] = j + lhrandom(0, inc);
1146 dir[1] = i + lhrandom(0, inc);
1148 org[0] = center[0] + dir[0];
1149 org[1] = center[1] + dir[1];
1150 org[2] = center[2] + lhrandom(0, 64);
1151 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1152 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);
1156 else if (effectnameindex == EFFECT_TE_TELEPORT)
1158 float i, j, k, inc, vel;
1161 if (cl_particles_quake.integer)
1162 inc = 4 / cl_particles_quality.value;
1164 inc = 8 / cl_particles_quality.value;
1165 for (i = -16;i < 16;i += inc)
1167 for (j = -16;j < 16;j += inc)
1169 for (k = -24;k < 32;k += inc)
1171 VectorSet(dir, i*8, j*8, k*8);
1172 VectorNormalize(dir);
1173 vel = lhrandom(50, 113);
1174 if (cl_particles_quake.integer)
1175 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);
1177 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);
1181 if (!cl_particles_quake.integer)
1182 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);
1183 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);
1185 else if (effectnameindex == EFFECT_TE_TEI_G3)
1186 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);
1187 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1189 if (cl_particles_smoke.integer)
1191 count *= 0.25f * cl_particles_quality.value;
1193 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);
1196 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1198 CL_ParticleExplosion(center);
1199 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);
1201 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1204 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1205 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1206 if (cl_particles_smoke.integer)
1207 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1208 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);
1209 if (cl_particles_sparks.integer)
1210 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1211 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);
1212 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);
1214 else if (effectnameindex == EFFECT_EF_FLAME)
1216 if (!spawnparticles)
1218 count *= 300 * cl_particles_quality.value;
1220 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);
1221 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);
1223 else if (effectnameindex == EFFECT_EF_STARDUST)
1225 if (!spawnparticles)
1227 count *= 200 * cl_particles_quality.value;
1229 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);
1230 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);
1232 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1236 int smoke, blood, bubbles, r, color;
1238 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1241 Vector4Set(light, 0, 0, 0, 0);
1243 if (effectnameindex == EFFECT_TR_ROCKET)
1244 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1245 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1247 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1248 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1250 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1252 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1253 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1257 matrix4x4_t tempmatrix;
1258 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1259 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);
1260 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1264 if (!spawnparticles)
1267 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1270 VectorSubtract(originmaxs, originmins, dir);
1271 len = VectorNormalizeLength(dir);
1274 dec = -ent->persistent.trail_time;
1275 ent->persistent.trail_time += len;
1276 if (ent->persistent.trail_time < 0.01f)
1279 // if we skip out, leave it reset
1280 ent->persistent.trail_time = 0.0f;
1285 // advance into this frame to reach the first puff location
1286 VectorMA(originmins, dec, dir, pos);
1289 smoke = cl_particles.integer && cl_particles_smoke.integer;
1290 blood = cl_particles.integer && cl_particles_blood.integer;
1291 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1292 qd = 1.0f / cl_particles_quality.value;
1299 if (effectnameindex == EFFECT_TR_BLOOD)
1301 if (cl_particles_quake.integer)
1303 color = particlepalette[67 + (rand()&3)];
1304 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);
1309 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);
1312 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1314 if (cl_particles_quake.integer)
1317 color = particlepalette[67 + (rand()&3)];
1318 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);
1323 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);
1329 if (effectnameindex == EFFECT_TR_ROCKET)
1331 if (cl_particles_quake.integer)
1334 color = particlepalette[ramp3[r]];
1335 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);
1339 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);
1340 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);
1343 else if (effectnameindex == EFFECT_TR_GRENADE)
1345 if (cl_particles_quake.integer)
1348 color = particlepalette[ramp3[r]];
1349 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);
1353 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);
1356 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1358 if (cl_particles_quake.integer)
1361 color = particlepalette[52 + (rand()&7)];
1362 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1363 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);
1365 else if (gamemode == GAME_GOODVSBAD2)
1368 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);
1372 color = particlepalette[20 + (rand()&7)];
1373 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);
1376 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1378 if (cl_particles_quake.integer)
1381 color = particlepalette[230 + (rand()&7)];
1382 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);
1383 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);
1387 color = particlepalette[226 + (rand()&7)];
1388 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);
1391 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1393 if (cl_particles_quake.integer)
1395 color = particlepalette[152 + (rand()&3)];
1396 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);
1398 else if (gamemode == GAME_GOODVSBAD2)
1401 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);
1403 else if (gamemode == GAME_PRYDON)
1406 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);
1409 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);
1411 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1414 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);
1416 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1419 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);
1421 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1422 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);
1426 if (effectnameindex == EFFECT_TR_ROCKET)
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);
1428 else if (effectnameindex == EFFECT_TR_GRENADE)
1429 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);
1431 // advance to next time and position
1434 VectorMA (pos, dec, dir, pos);
1437 ent->persistent.trail_time = len;
1440 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1443 // this is also called on point effects with spawndlight = true and
1444 // spawnparticles = true
1445 // it is called CL_ParticleTrail because most code does not want to supply
1446 // these parameters, only trail handling does
1447 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 wanttrail)
1449 qboolean found = false;
1451 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1453 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1454 return; // no such effect
1456 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1458 int effectinfoindex;
1461 particleeffectinfo_t *info;
1473 qboolean underwater;
1474 qboolean immediatebloodstain;
1476 float avgtint[4], tint[4], tintlerp;
1477 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1478 VectorLerp(originmins, 0.5, originmaxs, center);
1479 supercontents = CL_PointSuperContents(center);
1480 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1481 VectorSubtract(originmaxs, originmins, traildir);
1482 traillen = VectorLength(traildir);
1483 VectorNormalize(traildir);
1486 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1490 Vector4Set(avgtint, 1, 1, 1, 1);
1492 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1494 if (info->effectnameindex == effectnameindex)
1496 qboolean definedastrail = info->trailspacing > 0;
1498 qboolean drawastrail = wanttrail;
1499 if (cl_particles_forcetraileffects.integer)
1500 drawastrail = drawastrail || definedastrail;
1503 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1505 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1508 // spawn a dlight if requested
1509 if (info->lightradiusstart > 0 && spawndlight)
1511 matrix4x4_t tempmatrix;
1513 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1515 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1516 if (info->lighttime > 0 && info->lightradiusfade > 0)
1518 // light flash (explosion, etc)
1519 // called when effect starts
1520 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);
1522 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1525 // called by CL_LinkNetworkEntity
1526 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1527 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1528 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1529 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1530 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);
1531 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1535 if (!spawnparticles)
1540 if (info->tex[1] > info->tex[0])
1542 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1543 tex = min(tex, info->tex[1] - 1);
1545 if(info->staintex[0] < 0)
1546 staintex = info->staintex[0];
1549 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1550 staintex = min(staintex, info->staintex[1] - 1);
1552 if (info->particletype == pt_decal)
1554 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1555 AnglesFromVectors(angles, velocity, NULL, false);
1556 AngleVectors(angles, forward, right, up);
1557 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1559 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]);
1561 else if (info->orientation == PARTICLE_HBEAM)
1566 AnglesFromVectors(angles, traildir, NULL, false);
1567 AngleVectors(angles, forward, right, up);
1568 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1570 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);
1575 if (!cl_particles.integer)
1577 switch (info->particletype)
1579 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1580 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1581 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1582 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1583 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1584 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1588 cnt = info->countabsolute;
1589 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1590 // if drawastrail is not set, we will
1591 // use the regular cnt-based random
1592 // particle spawning at the center; so
1593 // do NOT apply trailspacing then!
1594 if (drawastrail && definedastrail)
1595 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1598 continue; // nothing to draw
1599 info->particleaccumulator += cnt;
1601 if (drawastrail || definedastrail)
1602 immediatebloodstain = false;
1604 immediatebloodstain =
1605 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1607 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1611 VectorCopy(originmins, trailpos);
1612 trailstep = traillen / cnt;
1613 AnglesFromVectors(angles, traildir, NULL, false);
1617 VectorCopy(center, trailpos);
1619 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1620 AnglesFromVectors(angles, velocity, NULL, false);
1623 AngleVectors(angles, forward, right, up);
1624 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1625 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1626 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1627 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1629 if (info->tex[1] > info->tex[0])
1631 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1632 tex = min(tex, info->tex[1] - 1);
1634 if (!(drawastrail || definedastrail))
1636 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1637 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1638 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1642 tintlerp = lhrandom(0, 1);
1643 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1646 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);
1647 if (immediatebloodstain && part)
1649 immediatebloodstain = false;
1650 CL_ImmediateBloodStain(part);
1653 VectorMA(trailpos, trailstep, traildir, trailpos);
1660 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1663 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)
1665 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1668 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)
1670 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1673 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)
1675 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1683 void CL_EntityParticles (const entity_t *ent)
1686 vec_t pitch, yaw, dist = 64, beamlength = 16;
1688 static vec3_t avelocities[NUMVERTEXNORMALS];
1689 if (!cl_particles.integer) return;
1690 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1692 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1694 if (!avelocities[0][0])
1695 for (i = 0;i < NUMVERTEXNORMALS;i++)
1696 for (j = 0;j < 3;j++)
1697 avelocities[i][j] = lhrandom(0, 2.55);
1699 for (i = 0;i < NUMVERTEXNORMALS;i++)
1701 yaw = cl.time * avelocities[i][0];
1702 pitch = cl.time * avelocities[i][1];
1703 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1704 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1705 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1706 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);
1711 void CL_ReadPointFile_f (void)
1713 double org[3], leakorg[3];
1716 char *pointfile = NULL, *pointfilepos, *t, tchar;
1717 char name[MAX_QPATH];
1722 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1723 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1726 Con_Printf("Could not open %s\n", name);
1730 Con_Printf("Reading %s...\n", name);
1731 VectorClear(leakorg);
1734 pointfilepos = pointfile;
1735 while (*pointfilepos)
1737 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1742 while (*t && *t != '\n' && *t != '\r')
1746 #if _MSC_VER >= 1400
1747 #define sscanf sscanf_s
1749 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1750 VectorCopy(org, vecorg);
1756 VectorCopy(org, leakorg);
1759 if (cl.num_particles < cl.max_particles - 3)
1762 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);
1765 Mem_Free(pointfile);
1766 VectorCopy(leakorg, vecorg);
1767 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1769 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);
1770 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);
1771 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);
1776 CL_ParseParticleEffect
1778 Parse an effect out of the server message
1781 void CL_ParseParticleEffect (void)
1784 int i, count, msgcount, color;
1786 MSG_ReadVector(&cl_message, org, cls.protocol);
1787 for (i=0 ; i<3 ; i++)
1788 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1789 msgcount = MSG_ReadByte(&cl_message);
1790 color = MSG_ReadByte(&cl_message);
1792 if (msgcount == 255)
1797 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1802 CL_ParticleExplosion
1806 void CL_ParticleExplosion (const vec3_t org)
1812 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1813 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1815 if (cl_particles_quake.integer)
1817 for (i = 0;i < 1024;i++)
1823 color = particlepalette[ramp1[r]];
1824 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);
1828 color = particlepalette[ramp2[r]];
1829 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);
1835 i = CL_PointSuperContents(org);
1836 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1838 if (cl_particles.integer && cl_particles_bubbles.integer)
1839 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1840 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);
1844 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1846 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1853 VectorMA(org, 128, v2, v);
1854 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1856 while (k < 16 && trace.fraction < 0.1f);
1857 VectorSubtract(trace.endpos, org, v2);
1858 VectorScale(v2, 2.0f, v2);
1859 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);
1865 if (cl_particles_explosions_shell.integer)
1866 R_NewExplosion(org);
1871 CL_ParticleExplosion2
1875 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1878 if (!cl_particles.integer) return;
1880 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1882 k = particlepalette[colorStart + (i % colorLength)];
1883 if (cl_particles_quake.integer)
1884 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);
1886 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);
1890 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1893 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1894 if (cl_particles_sparks.integer)
1896 sparkcount *= cl_particles_quality.value;
1897 while(sparkcount-- > 0)
1898 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);
1902 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1905 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1906 if (cl_particles_smoke.integer)
1908 smokecount *= cl_particles_quality.value;
1909 while(smokecount-- > 0)
1910 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);
1914 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)
1918 if (!cl_particles.integer) return;
1919 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1921 count = (int)(count * cl_particles_quality.value);
1924 k = particlepalette[colorbase + (rand()&3)];
1925 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);
1929 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1932 float minz, maxz, lifetime = 30;
1934 if (!cl_particles.integer) return;
1935 if (dir[2] < 0) // falling
1937 minz = maxs[2] + dir[2] * 0.1;
1940 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1945 maxz = maxs[2] + dir[2] * 0.1;
1947 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1950 count = (int)(count * cl_particles_quality.value);
1955 if (!cl_particles_rain.integer) break;
1956 count *= 4; // ick, this should be in the mod or maps?
1960 k = particlepalette[colorbase + (rand()&3)];
1961 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1962 if (gamemode == GAME_GOODVSBAD2)
1963 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);
1965 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);
1969 if (!cl_particles_snow.integer) break;
1972 k = particlepalette[colorbase + (rand()&3)];
1973 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1974 if (gamemode == GAME_GOODVSBAD2)
1975 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);
1977 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);
1981 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1985 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1986 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1987 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1988 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1989 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1990 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1992 #define PARTICLETEXTURESIZE 64
1993 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1995 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1999 dz = 1 - (dx*dx+dy*dy);
2000 if (dz > 0) // it does hit the sphere
2004 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2005 VectorNormalize(normal);
2006 dot = DotProduct(normal, light);
2007 if (dot > 0.5) // interior reflection
2008 f += ((dot * 2) - 1);
2009 else if (dot < -0.5) // exterior reflection
2010 f += ((dot * -2) - 1);
2012 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2013 VectorNormalize(normal);
2014 dot = DotProduct(normal, light);
2015 if (dot > 0.5) // interior reflection
2016 f += ((dot * 2) - 1);
2017 else if (dot < -0.5) // exterior reflection
2018 f += ((dot * -2) - 1);
2020 f += 16; // just to give it a haze so you can see the outline
2021 f = bound(0, f, 255);
2022 return (unsigned char) f;
2028 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2029 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2031 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2032 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2033 *width = particlefontcellwidth;
2034 *height = particlefontcellheight;
2037 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2039 int basex, basey, w, h, y;
2040 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2041 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2042 Sys_Error("invalid particle texture size for autogenerating");
2043 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2044 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2047 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2050 float cx, cy, dx, dy, f, iradius;
2052 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2053 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2054 iradius = 1.0f / radius;
2055 alpha *= (1.0f / 255.0f);
2056 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2058 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2062 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2067 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2068 d[0] += (int)(f * (blue - d[0]));
2069 d[1] += (int)(f * (green - d[1]));
2070 d[2] += (int)(f * (red - d[2]));
2077 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2080 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2082 data[0] = bound(minb, data[0], maxb);
2083 data[1] = bound(ming, data[1], maxg);
2084 data[2] = bound(minr, data[2], maxr);
2089 static void particletextureinvert(unsigned char *data)
2092 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2094 data[0] = 255 - data[0];
2095 data[1] = 255 - data[1];
2096 data[2] = 255 - data[2];
2100 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2101 static void R_InitBloodTextures (unsigned char *particletexturedata)
2104 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2105 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2108 for (i = 0;i < 8;i++)
2110 memset(data, 255, datasize);
2111 for (k = 0;k < 24;k++)
2112 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2113 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2114 particletextureinvert(data);
2115 setuptex(tex_bloodparticle[i], data, particletexturedata);
2119 for (i = 0;i < 8;i++)
2121 memset(data, 255, datasize);
2123 for (j = 1;j < 10;j++)
2124 for (k = min(j, m - 1);k < m;k++)
2125 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2126 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2127 particletextureinvert(data);
2128 setuptex(tex_blooddecal[i], data, particletexturedata);
2134 //uncomment this to make engine save out particle font to a tga file when run
2135 //#define DUMPPARTICLEFONT
2137 static void R_InitParticleTexture (void)
2139 int x, y, d, i, k, m;
2140 int basex, basey, w, h;
2141 float dx, dy, f, s1, t1, s2, t2;
2144 fs_offset_t filesize;
2145 char texturename[MAX_QPATH];
2148 // a note: decals need to modulate (multiply) the background color to
2149 // properly darken it (stain), and they need to be able to alpha fade,
2150 // this is a very difficult challenge because it means fading to white
2151 // (no change to background) rather than black (darkening everything
2152 // behind the whole decal polygon), and to accomplish this the texture is
2153 // inverted (dark red blood on white background becomes brilliant cyan
2154 // and white on black background) so we can alpha fade it to black, then
2155 // we invert it again during the blendfunc to make it work...
2157 #ifndef DUMPPARTICLEFONT
2158 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2161 particlefonttexture = decalskinframe->base;
2162 // TODO maybe allow custom grid size?
2163 particlefontwidth = image_width;
2164 particlefontheight = image_height;
2165 particlefontcellwidth = image_width / 8;
2166 particlefontcellheight = image_height / 8;
2167 particlefontcols = 8;
2168 particlefontrows = 8;
2173 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2174 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2175 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2176 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2177 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2179 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2180 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2181 particlefontcols = 8;
2182 particlefontrows = 8;
2184 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2187 for (i = 0;i < 8;i++)
2189 memset(data, 255, datasize);
2192 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2193 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2195 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2197 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2198 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2200 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2201 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2203 d = (int)(d * (1-(dx*dx+dy*dy)));
2204 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2205 d = bound(0, d, 255);
2206 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2213 setuptex(tex_smoke[i], data, particletexturedata);
2217 memset(data, 255, datasize);
2218 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2220 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2221 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2223 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2224 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2225 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2228 setuptex(tex_rainsplash, data, particletexturedata);
2231 memset(data, 255, datasize);
2232 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2234 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2235 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2237 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2238 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2239 d = bound(0, d, 255);
2240 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2243 setuptex(tex_particle, data, particletexturedata);
2246 memset(data, 255, datasize);
2247 light[0] = 1;light[1] = 1;light[2] = 1;
2248 VectorNormalize(light);
2249 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2251 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2252 // stretch upper half of bubble by +50% and shrink lower half by -50%
2253 // (this gives an elongated teardrop shape)
2255 dy = (dy - 0.5f) * 2.0f;
2257 dy = (dy - 0.5f) / 1.5f;
2258 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2260 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2261 // shrink bubble width to half
2263 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2266 setuptex(tex_raindrop, data, particletexturedata);
2269 memset(data, 255, datasize);
2270 light[0] = 1;light[1] = 1;light[2] = 1;
2271 VectorNormalize(light);
2272 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2274 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2275 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2277 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2278 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2281 setuptex(tex_bubble, data, particletexturedata);
2283 // Blood particles and blood decals
2284 R_InitBloodTextures (particletexturedata);
2287 for (i = 0;i < 8;i++)
2289 memset(data, 255, datasize);
2290 for (k = 0;k < 12;k++)
2291 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2292 for (k = 0;k < 3;k++)
2293 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2294 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2295 particletextureinvert(data);
2296 setuptex(tex_bulletdecal[i], data, particletexturedata);
2299 #ifdef DUMPPARTICLEFONT
2300 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2303 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2304 particlefonttexture = decalskinframe->base;
2306 Mem_Free(particletexturedata);
2311 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2313 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2314 particletexture[i].texture = particlefonttexture;
2315 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2316 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2317 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2318 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2321 #ifndef DUMPPARTICLEFONT
2322 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2323 if (!particletexture[tex_beam].texture)
2326 unsigned char noise3[64][64], data2[64][16][4];
2328 fractalnoise(&noise3[0][0], 64, 4);
2330 for (y = 0;y < 64;y++)
2332 dy = (y - 0.5f*64) / (64*0.5f-1);
2333 for (x = 0;x < 16;x++)
2335 dx = (x - 0.5f*16) / (16*0.5f-2);
2336 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2337 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2338 data2[y][x][3] = 255;
2342 #ifdef DUMPPARTICLEFONT
2343 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2345 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2347 particletexture[tex_beam].s1 = 0;
2348 particletexture[tex_beam].t1 = 0;
2349 particletexture[tex_beam].s2 = 1;
2350 particletexture[tex_beam].t2 = 1;
2352 // now load an texcoord/texture override file
2353 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2360 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2362 if(!strcmp(com_token, "\n"))
2363 continue; // empty line
2364 i = atoi(com_token);
2372 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2374 strlcpy(texturename, com_token, sizeof(texturename));
2375 s1 = atof(com_token);
2376 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2379 t1 = atof(com_token);
2380 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2382 s2 = atof(com_token);
2383 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2385 t2 = atof(com_token);
2386 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2387 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2388 strlcpy(texturename, com_token, sizeof(texturename));
2395 if (!texturename[0])
2397 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2400 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2402 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2405 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2408 // R_SkinFrame_LoadExternal already complained
2411 particletexture[i].texture = sf->base;
2412 particletexture[i].s1 = s1;
2413 particletexture[i].t1 = t1;
2414 particletexture[i].s2 = s2;
2415 particletexture[i].t2 = t2;
2421 static void r_part_start(void)
2424 // generate particlepalette for convenience from the main one
2425 for (i = 0;i < 256;i++)
2426 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2427 particletexturepool = R_AllocTexturePool();
2428 R_InitParticleTexture ();
2429 CL_Particles_LoadEffectInfo(NULL);
2432 static void r_part_shutdown(void)
2434 R_FreeTexturePool(&particletexturepool);
2437 static void r_part_newmap(void)
2440 R_SkinFrame_MarkUsed(decalskinframe);
2441 CL_Particles_LoadEffectInfo(NULL);
2444 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2445 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2447 void R_Particles_Init (void)
2450 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2452 particle_elements[i*6+0] = i*4+0;
2453 particle_elements[i*6+1] = i*4+1;
2454 particle_elements[i*6+2] = i*4+2;
2455 particle_elements[i*6+3] = i*4+0;
2456 particle_elements[i*6+4] = i*4+2;
2457 particle_elements[i*6+5] = i*4+3;
2460 Cvar_RegisterVariable(&r_drawparticles);
2461 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2462 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2463 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2464 Cvar_RegisterVariable(&r_drawdecals);
2465 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2466 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2469 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2471 int surfacelistindex;
2473 float *v3f, *t2f, *c4f;
2474 particletexture_t *tex;
2475 vec_t right[3], up[3], size, ca;
2476 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2478 RSurf_ActiveWorldEntity();
2480 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2481 // R_Mesh_ResetTextureState();
2482 GL_DepthMask(false);
2483 GL_DepthRange(0, 1);
2484 GL_PolygonOffset(0, 0);
2486 GL_CullFace(GL_NONE);
2488 // generate all the vertices at once
2489 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2491 d = cl.decals + surfacelist[surfacelistindex];
2494 c4f = particle_color4f + 16*surfacelistindex;
2495 ca = d->alpha * alphascale;
2496 // ensure alpha multiplier saturates properly
2497 if (ca > 1.0f / 256.0f)
2499 if (r_refdef.fogenabled)
2500 ca *= RSurf_FogVertex(d->org);
2501 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2502 Vector4Copy(c4f, c4f + 4);
2503 Vector4Copy(c4f, c4f + 8);
2504 Vector4Copy(c4f, c4f + 12);
2506 // calculate vertex positions
2507 size = d->size * cl_particles_size.value;
2508 VectorVectors(d->normal, right, up);
2509 VectorScale(right, size, right);
2510 VectorScale(up, size, up);
2511 v3f = particle_vertex3f + 12*surfacelistindex;
2512 v3f[ 0] = d->org[0] - right[0] - up[0];
2513 v3f[ 1] = d->org[1] - right[1] - up[1];
2514 v3f[ 2] = d->org[2] - right[2] - up[2];
2515 v3f[ 3] = d->org[0] - right[0] + up[0];
2516 v3f[ 4] = d->org[1] - right[1] + up[1];
2517 v3f[ 5] = d->org[2] - right[2] + up[2];
2518 v3f[ 6] = d->org[0] + right[0] + up[0];
2519 v3f[ 7] = d->org[1] + right[1] + up[1];
2520 v3f[ 8] = d->org[2] + right[2] + up[2];
2521 v3f[ 9] = d->org[0] + right[0] - up[0];
2522 v3f[10] = d->org[1] + right[1] - up[1];
2523 v3f[11] = d->org[2] + right[2] - up[2];
2525 // calculate texcoords
2526 tex = &particletexture[d->texnum];
2527 t2f = particle_texcoord2f + 8*surfacelistindex;
2528 t2f[0] = tex->s1;t2f[1] = tex->t2;
2529 t2f[2] = tex->s1;t2f[3] = tex->t1;
2530 t2f[4] = tex->s2;t2f[5] = tex->t1;
2531 t2f[6] = tex->s2;t2f[7] = tex->t2;
2534 // now render the decals all at once
2535 // (this assumes they all use one particle font texture!)
2536 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2537 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2538 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2539 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2542 void R_DrawDecals (void)
2545 int drawdecals = r_drawdecals.integer;
2550 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2552 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2553 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2555 // LordHavoc: early out conditions
2559 decalfade = frametime * 256 / cl_decals_fadetime.value;
2560 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2561 drawdist2 = drawdist2*drawdist2;
2563 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2565 if (!decal->typeindex)
2568 if (killsequence - decal->decalsequence > 0)
2571 if (cl.time > decal->time2 + cl_decals_time.value)
2573 decal->alpha -= decalfade;
2574 if (decal->alpha <= 0)
2580 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2582 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2583 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2589 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2595 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))
2596 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2599 decal->typeindex = 0;
2600 if (cl.free_decal > i)
2604 // reduce cl.num_decals if possible
2605 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2608 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2610 decal_t *olddecals = cl.decals;
2611 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2612 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2613 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2614 Mem_Free(olddecals);
2617 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2620 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2622 vec3_t vecorg, vecvel, baseright, baseup;
2623 int surfacelistindex;
2624 int batchstart, batchcount;
2625 const particle_t *p;
2627 rtexture_t *texture;
2628 float *v3f, *t2f, *c4f;
2629 particletexture_t *tex;
2630 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2631 // float ambient[3], diffuse[3], diffusenormal[3];
2632 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2633 vec4_t colormultiplier;
2634 float minparticledist_start, minparticledist_end;
2637 RSurf_ActiveWorldEntity();
2639 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));
2641 r_refdef.stats[r_stat_particles] += numsurfaces;
2642 // R_Mesh_ResetTextureState();
2643 GL_DepthMask(false);
2644 GL_DepthRange(0, 1);
2645 GL_PolygonOffset(0, 0);
2647 GL_CullFace(GL_NONE);
2649 spintime = r_refdef.scene.time;
2651 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2652 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2653 dofade = (minparticledist_start < minparticledist_end);
2655 // first generate all the vertices at once
2656 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2658 p = cl.particles + surfacelist[surfacelistindex];
2660 blendmode = (pblend_t)p->blendmode;
2662 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2663 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2664 alpha = palpha * colormultiplier[3];
2665 // ensure alpha multiplier saturates properly
2671 case PBLEND_INVALID:
2673 // additive and modulate can just fade out in fog (this is correct)
2674 if (r_refdef.fogenabled)
2675 alpha *= RSurf_FogVertex(p->org);
2676 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2677 alpha *= 1.0f / 256.0f;
2678 c4f[0] = p->color[0] * alpha;
2679 c4f[1] = p->color[1] * alpha;
2680 c4f[2] = p->color[2] * alpha;
2684 // additive and modulate can just fade out in fog (this is correct)
2685 if (r_refdef.fogenabled)
2686 alpha *= RSurf_FogVertex(p->org);
2687 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2688 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2689 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2690 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2694 c4f[0] = p->color[0] * colormultiplier[0];
2695 c4f[1] = p->color[1] * colormultiplier[1];
2696 c4f[2] = p->color[2] * colormultiplier[2];
2698 // note: lighting is not cheap!
2699 if (particletype[p->typeindex].lighting)
2701 vecorg[0] = p->org[0];
2702 vecorg[1] = p->org[1];
2703 vecorg[2] = p->org[2];
2704 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2706 // mix in the fog color
2707 if (r_refdef.fogenabled)
2709 fog = RSurf_FogVertex(p->org);
2711 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2712 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2713 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2715 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2716 VectorScale(c4f, alpha, c4f);
2719 // copy the color into the other three vertices
2720 Vector4Copy(c4f, c4f + 4);
2721 Vector4Copy(c4f, c4f + 8);
2722 Vector4Copy(c4f, c4f + 12);
2724 size = p->size * cl_particles_size.value;
2725 tex = &particletexture[p->texnum];
2726 switch(p->orientation)
2728 // case PARTICLE_INVALID:
2729 case PARTICLE_BILLBOARD:
2730 if (p->angle + p->spin)
2732 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2733 spinsin = sin(spinrad) * size;
2734 spincos = cos(spinrad) * size;
2735 spinm1 = -p->stretch * spincos;
2738 spinm4 = -p->stretch * spincos;
2739 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2740 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2744 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2745 VectorScale(r_refdef.view.up, size, up);
2748 v3f[ 0] = p->org[0] - right[0] - up[0];
2749 v3f[ 1] = p->org[1] - right[1] - up[1];
2750 v3f[ 2] = p->org[2] - right[2] - up[2];
2751 v3f[ 3] = p->org[0] - right[0] + up[0];
2752 v3f[ 4] = p->org[1] - right[1] + up[1];
2753 v3f[ 5] = p->org[2] - right[2] + up[2];
2754 v3f[ 6] = p->org[0] + right[0] + up[0];
2755 v3f[ 7] = p->org[1] + right[1] + up[1];
2756 v3f[ 8] = p->org[2] + right[2] + up[2];
2757 v3f[ 9] = p->org[0] + right[0] - up[0];
2758 v3f[10] = p->org[1] + right[1] - up[1];
2759 v3f[11] = p->org[2] + right[2] - up[2];
2760 t2f[0] = tex->s1;t2f[1] = tex->t2;
2761 t2f[2] = tex->s1;t2f[3] = tex->t1;
2762 t2f[4] = tex->s2;t2f[5] = tex->t1;
2763 t2f[6] = tex->s2;t2f[7] = tex->t2;
2765 case PARTICLE_ORIENTED_DOUBLESIDED:
2766 vecvel[0] = p->vel[0];
2767 vecvel[1] = p->vel[1];
2768 vecvel[2] = p->vel[2];
2769 VectorVectors(vecvel, baseright, baseup);
2770 if (p->angle + p->spin)
2772 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2773 spinsin = sin(spinrad) * size;
2774 spincos = cos(spinrad) * size;
2775 spinm1 = p->stretch * spincos;
2778 spinm4 = p->stretch * spincos;
2779 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2780 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2784 VectorScale(baseright, size * p->stretch, right);
2785 VectorScale(baseup, size, up);
2787 v3f[ 0] = p->org[0] - right[0] - up[0];
2788 v3f[ 1] = p->org[1] - right[1] - up[1];
2789 v3f[ 2] = p->org[2] - right[2] - up[2];
2790 v3f[ 3] = p->org[0] - right[0] + up[0];
2791 v3f[ 4] = p->org[1] - right[1] + up[1];
2792 v3f[ 5] = p->org[2] - right[2] + up[2];
2793 v3f[ 6] = p->org[0] + right[0] + up[0];
2794 v3f[ 7] = p->org[1] + right[1] + up[1];
2795 v3f[ 8] = p->org[2] + right[2] + up[2];
2796 v3f[ 9] = p->org[0] + right[0] - up[0];
2797 v3f[10] = p->org[1] + right[1] - up[1];
2798 v3f[11] = p->org[2] + right[2] - up[2];
2799 t2f[0] = tex->s1;t2f[1] = tex->t2;
2800 t2f[2] = tex->s1;t2f[3] = tex->t1;
2801 t2f[4] = tex->s2;t2f[5] = tex->t1;
2802 t2f[6] = tex->s2;t2f[7] = tex->t2;
2804 case PARTICLE_SPARK:
2805 len = VectorLength(p->vel);
2806 VectorNormalize2(p->vel, up);
2807 lenfactor = p->stretch * 0.04 * len;
2808 if(lenfactor < size * 0.5)
2809 lenfactor = size * 0.5;
2810 VectorMA(p->org, -lenfactor, up, v);
2811 VectorMA(p->org, lenfactor, up, up2);
2812 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2813 t2f[0] = tex->s1;t2f[1] = tex->t2;
2814 t2f[2] = tex->s1;t2f[3] = tex->t1;
2815 t2f[4] = tex->s2;t2f[5] = tex->t1;
2816 t2f[6] = tex->s2;t2f[7] = tex->t2;
2818 case PARTICLE_VBEAM:
2819 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2820 VectorSubtract(p->vel, p->org, up);
2821 VectorNormalize(up);
2822 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2823 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2824 t2f[0] = tex->s2;t2f[1] = v[0];
2825 t2f[2] = tex->s1;t2f[3] = v[0];
2826 t2f[4] = tex->s1;t2f[5] = v[1];
2827 t2f[6] = tex->s2;t2f[7] = v[1];
2829 case PARTICLE_HBEAM:
2830 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2831 VectorSubtract(p->vel, p->org, up);
2832 VectorNormalize(up);
2833 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2834 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2835 t2f[0] = v[0];t2f[1] = tex->t1;
2836 t2f[2] = v[0];t2f[3] = tex->t2;
2837 t2f[4] = v[1];t2f[5] = tex->t2;
2838 t2f[6] = v[1];t2f[7] = tex->t1;
2843 // now render batches of particles based on blendmode and texture
2844 blendmode = PBLEND_INVALID;
2848 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2849 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2851 p = cl.particles + surfacelist[surfacelistindex];
2853 if (texture != particletexture[p->texnum].texture)
2855 texture = particletexture[p->texnum].texture;
2856 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2859 if (p->blendmode == PBLEND_INVMOD)
2861 // inverse modulate blend - group these
2862 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2863 // iterate until we find a change in settings
2864 batchstart = surfacelistindex++;
2865 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2867 p = cl.particles + surfacelist[surfacelistindex];
2868 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2874 // additive or alpha blend - group these
2875 // (we can group these because we premultiplied the texture alpha)
2876 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2877 // iterate until we find a change in settings
2878 batchstart = surfacelistindex++;
2879 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2881 p = cl.particles + surfacelist[surfacelistindex];
2882 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2887 batchcount = surfacelistindex - batchstart;
2888 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2892 void R_DrawParticles (void)
2895 int drawparticles = r_drawparticles.integer;
2896 float minparticledist_start;
2898 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2904 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2905 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2907 // LordHavoc: early out conditions
2908 if (!cl.num_particles)
2911 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2912 gravity = frametime * cl.movevars_gravity;
2913 update = frametime > 0;
2914 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2915 drawdist2 = drawdist2*drawdist2;
2917 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2921 if (cl.free_particle > i)
2922 cl.free_particle = i;
2928 if (p->delayedspawn > cl.time)
2931 p->size += p->sizeincrease * frametime;
2932 p->alpha -= p->alphafade * frametime;
2934 if (p->alpha <= 0 || p->die <= cl.time)
2937 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2939 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2941 if (p->typeindex == pt_blood)
2942 p->size += frametime * 8;
2944 p->vel[2] -= p->gravity * gravity;
2945 f = 1.0f - min(p->liquidfriction * frametime, 1);
2946 VectorScale(p->vel, f, p->vel);
2950 p->vel[2] -= p->gravity * gravity;
2953 f = 1.0f - min(p->airfriction * frametime, 1);
2954 VectorScale(p->vel, f, p->vel);
2958 VectorCopy(p->org, oldorg);
2959 VectorMA(p->org, frametime, p->vel, p->org);
2960 // if (p->bounce && cl.time >= p->delayedcollisions)
2961 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2963 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);
2964 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2965 // or if the trace hit something flagged as NOIMPACT
2966 // then remove the particle
2967 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2969 VectorCopy(trace.endpos, p->org);
2970 // react if the particle hit something
2971 if (trace.fraction < 1)
2973 VectorCopy(trace.endpos, p->org);
2975 if (p->staintexnum >= 0)
2977 // blood - splash on solid
2978 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2981 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2982 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2983 if (cl_decals.integer)
2985 // create a decal for the blood splat
2986 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2987 if (cl_decals_newsystem_bloodsmears.integer)
2989 VectorCopy(p->vel, decaldir);
2990 VectorNormalize(decaldir);
2993 VectorCopy(trace.plane.normal, decaldir);
2994 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2999 if (p->typeindex == pt_blood)
3001 // blood - splash on solid
3002 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3004 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3006 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)));
3007 if (cl_decals.integer)
3009 // create a decal for the blood splat
3010 if (cl_decals_newsystem_bloodsmears.integer)
3012 VectorCopy(p->vel, decaldir);
3013 VectorNormalize(decaldir);
3016 VectorCopy(trace.plane.normal, decaldir);
3017 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);
3022 else if (p->bounce < 0)
3024 // bounce -1 means remove on impact
3029 // anything else - bounce off solid
3030 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3031 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3036 if (VectorLength2(p->vel) < 0.03)
3038 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3040 VectorClear(p->vel);
3044 if (p->typeindex != pt_static)
3046 switch (p->typeindex)
3048 case pt_entityparticle:
3049 // particle that removes itself after one rendered frame
3056 a = CL_PointSuperContents(p->org);
3057 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3061 a = CL_PointSuperContents(p->org);
3062 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3066 a = CL_PointSuperContents(p->org);
3067 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3071 if (cl.time > p->time2)
3074 p->time2 = cl.time + (rand() & 3) * 0.1;
3075 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3076 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3078 a = CL_PointSuperContents(p->org);
3079 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3087 else if (p->delayedspawn > cl.time)
3091 // don't render particles too close to the view (they chew fillrate)
3092 // also don't render particles behind the view (useless)
3093 // further checks to cull to the frustum would be too slow here
3094 switch(p->typeindex)
3097 // beams have no culling
3098 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3101 if(cl_particles_visculling.integer)
3102 if (!r_refdef.viewcache.world_novis)
3103 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3105 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3107 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3110 // anything else just has to be in front of the viewer and visible at this distance
3111 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3112 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3119 if (cl.free_particle > i)
3120 cl.free_particle = i;
3123 // reduce cl.num_particles if possible
3124 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3127 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3129 particle_t *oldparticles = cl.particles;
3130 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3131 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3132 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3133 Mem_Free(oldparticles);