2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float relativeoriginoffset[3];
105 float velocityoffset[3];
106 float relativevelocityoffset[3];
107 float originjitter[3];
108 float velocityjitter[3];
109 float velocitymultiplier;
110 // an effect can also spawn a dlight
111 float lightradiusstart;
112 float lightradiusfade;
115 qboolean lightshadow;
117 float lightcorona[2];
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
123 float rotate[4]; // min/max base angle, min/max rotation over time
125 particleeffectinfo_t;
127 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
129 int numparticleeffectinfo;
130 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
132 static int particlepalette[256];
134 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
135 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
136 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
137 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
138 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
139 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
140 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
141 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
142 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
143 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
144 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
145 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
146 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
147 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
148 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
149 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
150 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
151 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
152 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
153 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
154 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
155 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
156 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
157 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
158 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
159 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
160 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
161 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
162 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
163 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
164 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
165 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
168 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
169 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
170 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
172 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
174 // particletexture_t is a rectangle in the particlefonttexture
175 typedef struct particletexture_s
178 float s1, t1, s2, t2;
182 static rtexturepool_t *particletexturepool;
183 static rtexture_t *particlefonttexture;
184 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 skinframe_t *decalskinframe;
187 // texture numbers in particle font
188 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
189 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
190 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
191 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
192 static const int tex_rainsplash = 32;
193 static const int tex_particle = 63;
194 static const int tex_bubble = 62;
195 static const int tex_raindrop = 61;
196 static const int tex_beam = 60;
198 particleeffectinfo_t baselineparticleeffectinfo =
200 0, //int effectnameindex; // which effect this belongs to
201 // PARTICLEEFFECT_* bits
203 // blood effects may spawn very few particles, so proper fraction-overflow
204 // handling is very important, this variable keeps track of the fraction
205 0.0, //double particleaccumulator;
206 // the math is: countabsolute + requestedcount * countmultiplier * quality
207 // absolute number of particles to spawn, often used for decals
208 // (unaffected by quality and requestedcount)
209 0.0f, //float countabsolute;
210 // multiplier for the number of particles CL_ParticleEffect was told to
211 // spawn, most effects do not really have a count and hence use 1, so
212 // this is often the actual count to spawn, not merely a multiplier
213 0.0f, //float countmultiplier;
214 // if > 0 this causes the particle to spawn in an evenly spaced line from
215 // originmins to originmaxs (causing them to describe a trail, not a box)
216 0.0f, //float trailspacing;
217 // type of particle to spawn (defines some aspects of behavior)
218 pt_alphastatic, //ptype_t particletype;
219 // blending mode used on this particle type
220 PBLEND_ALPHA, //pblend_t blendmode;
221 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
222 PARTICLE_BILLBOARD, //porientation_t orientation;
223 // range of colors to choose from in hex RRGGBB (like HTML color tags),
224 // randomly interpolated at spawn
225 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
226 // a random texture is chosen in this range (note the second value is one
227 // past the last choosable, so for example 8,16 chooses any from 8 up and
229 // if start and end of the range are the same, no randomization is done
230 {63, 63 /* tex_particle */}, //int tex[2];
231 // range of size values randomly chosen when spawning, plus size increase over time
232 {1, 1, 0.0f}, //float size[3];
233 // range of alpha values randomly chosen when spawning, plus alpha fade
234 {0.0f, 256.0f, 256.0f}, //float alpha[3];
235 // how long the particle should live (note it is also removed if alpha drops to 0)
236 {16777216.0f, 16777216.0f}, //float time[2];
237 // how much gravity affects this particle (negative makes it fly up!)
238 0.0f, //float gravity;
239 // how much bounce the particle has when it hits a surface
240 // if negative the particle is removed on impact
241 0.0f, //float bounce;
242 // if in air this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float airfriction;
245 // if in liquid (water/slime/lava) this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float liquidfriction;
248 // these offsets are added to the values given to particleeffect(), and
249 // then an ellipsoid-shaped jitter is added as defined by these
250 // (they are the 3 radii)
251 1.0f, //float stretchfactor;
252 // stretch velocity factor (used for sparks)
253 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
259 0.0f, //float velocitymultiplier;
260 // an effect can also spawn a dlight
261 0.0f, //float lightradiusstart;
262 0.0f, //float lightradiusfade;
263 16777216.0f, //float lighttime;
264 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
265 true, //qboolean lightshadow;
266 0, //int lightcubemapnum;
267 {1.0f, 0.25f}, //float lightcorona[2];
268 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
269 {-1, -1}, //int staintex[2];
270 {1.0f, 1.0f}, //float stainalpha[2];
271 {2.0f, 2.0f}, //float stainsize[2];
273 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
276 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
277 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
278 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
279 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
280 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
281 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
282 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
283 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
284 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
285 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
286 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
287 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
288 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
289 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
290 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
291 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
292 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
293 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
294 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
295 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
296 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
297 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
298 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
299 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
300 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
301 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
302 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
303 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
304 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
305 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
306 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
307 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
308 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
309 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
312 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
317 particleeffectinfo_t *info = NULL;
318 const char *text = textstart;
320 for (linenumber = 1;;linenumber++)
323 for (arrayindex = 0;arrayindex < 16;arrayindex++)
324 argv[arrayindex][0] = 0;
327 if (!COM_ParseToken_Simple(&text, true, false, true))
329 if (!strcmp(com_token, "\n"))
333 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
339 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
340 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
341 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
342 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
343 #define readfloat(var) checkparms(2);var = atof(argv[1])
344 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
345 if (!strcmp(argv[0], "effect"))
349 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
351 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
354 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
356 if (particleeffectname[effectnameindex][0])
358 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
363 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
367 // if we run out of names, abort
368 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
370 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
373 info = particleeffectinfo + numparticleeffectinfo++;
374 // copy entire info from baseline, then fix up the nameindex
375 *info = baselineparticleeffectinfo;
376 info->effectnameindex = effectnameindex;
378 else if (info == NULL)
380 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
383 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
384 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
385 else if (!strcmp(argv[0], "type"))
388 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
389 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
390 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
391 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
392 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
393 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
394 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
395 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
396 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
397 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
398 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
399 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
400 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
401 info->blendmode = particletype[info->particletype].blendmode;
402 info->orientation = particletype[info->particletype].orientation;
404 else if (!strcmp(argv[0], "blend"))
407 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
408 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
409 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
410 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
412 else if (!strcmp(argv[0], "orientation"))
415 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
416 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
417 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
418 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
419 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
421 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
422 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
423 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
424 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
425 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
426 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
427 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
428 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
429 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
430 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
431 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
432 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
433 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
434 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
435 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
436 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
437 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
438 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
439 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
440 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
441 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
442 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
443 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
444 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
445 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
446 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
447 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
448 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
449 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
450 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
451 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
452 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
453 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
454 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
456 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
465 int CL_ParticleEffectIndexForName(const char *name)
468 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
469 if (!strcmp(particleeffectname[i], name))
474 const char *CL_ParticleEffectNameForIndex(int i)
476 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
478 return particleeffectname[i];
481 // MUST match effectnameindex_t in client.h
482 static const char *standardeffectnames[EFFECT_TOTAL] =
506 "TE_TEI_BIGEXPLOSION",
522 static void CL_Particles_LoadEffectInfo(const char *customfile)
526 unsigned char *filedata;
527 fs_offset_t filesize;
528 char filename[MAX_QPATH];
529 numparticleeffectinfo = 0;
530 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
531 memset(particleeffectname, 0, sizeof(particleeffectname));
532 for (i = 0;i < EFFECT_TOTAL;i++)
533 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
534 for (filepass = 0;;filepass++)
539 strlcpy(filename, customfile, sizeof(filename));
541 strlcpy(filename, "effectinfo.txt", sizeof(filename));
543 else if (filepass == 1)
545 if (!cl.worldbasename[0] || customfile)
547 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
551 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
554 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
559 static void CL_Particles_LoadEffectInfo_f(void)
561 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
569 void CL_ReadPointFile_f (void);
570 void CL_Particles_Init (void)
572 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
573 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
575 Cvar_RegisterVariable (&cl_particles);
576 Cvar_RegisterVariable (&cl_particles_quality);
577 Cvar_RegisterVariable (&cl_particles_alpha);
578 Cvar_RegisterVariable (&cl_particles_size);
579 Cvar_RegisterVariable (&cl_particles_quake);
580 Cvar_RegisterVariable (&cl_particles_blood);
581 Cvar_RegisterVariable (&cl_particles_blood_alpha);
582 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
584 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
585 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
586 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
587 Cvar_RegisterVariable (&cl_particles_explosions_shell);
588 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
589 Cvar_RegisterVariable (&cl_particles_rain);
590 Cvar_RegisterVariable (&cl_particles_snow);
591 Cvar_RegisterVariable (&cl_particles_smoke);
592 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
593 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
594 Cvar_RegisterVariable (&cl_particles_sparks);
595 Cvar_RegisterVariable (&cl_particles_bubbles);
596 Cvar_RegisterVariable (&cl_particles_visculling);
597 Cvar_RegisterVariable (&cl_particles_collisions);
598 Cvar_RegisterVariable (&cl_decals);
599 Cvar_RegisterVariable (&cl_decals_visculling);
600 Cvar_RegisterVariable (&cl_decals_time);
601 Cvar_RegisterVariable (&cl_decals_fadetime);
602 Cvar_RegisterVariable (&cl_decals_newsystem);
603 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
604 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
605 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
606 Cvar_RegisterVariable (&cl_decals_models);
607 Cvar_RegisterVariable (&cl_decals_bias);
608 Cvar_RegisterVariable (&cl_decals_max);
611 void CL_Particles_Shutdown (void)
615 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
616 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
618 // list of all 26 parameters:
619 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
620 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
621 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
622 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
623 // palpha - opacity of particle as 0-255 (can be more than 255)
624 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
625 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
626 // pgravity - how much effect gravity has on the particle (0-1)
627 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
628 // px,py,pz - starting origin of particle
629 // pvx,pvy,pvz - starting velocity of particle
630 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
631 // blendmode - one of the PBLEND_ values
632 // orientation - one of the PARTICLE_ values
633 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
634 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
635 // stainalpha: opacity of the stain as factor for alpha
636 // stainsize: size of the stain as factor for palpha
637 // angle: base rotation of the particle geometry around its center normal
638 // spin: rotation speed of the particle geometry around its center normal
639 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
644 if (!cl_particles.integer)
646 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
647 if (cl.free_particle >= cl.max_particles)
650 lifetime = palpha / min(1, palphafade);
651 part = &cl.particles[cl.free_particle++];
652 if (cl.num_particles < cl.free_particle)
653 cl.num_particles = cl.free_particle;
654 memset(part, 0, sizeof(*part));
655 VectorCopy(sortorigin, part->sortorigin);
656 part->typeindex = ptypeindex;
657 part->blendmode = blendmode;
658 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
660 particletexture_t *tex = &particletexture[ptex];
661 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
662 part->orientation = PARTICLE_VBEAM;
664 part->orientation = PARTICLE_HBEAM;
667 part->orientation = orientation;
668 l2 = (int)lhrandom(0.5, 256.5);
670 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
671 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
672 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
675 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
676 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
677 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
679 part->alpha = palpha;
680 part->alphafade = palphafade;
681 part->staintexnum = staintex;
682 if(staincolor1 >= 0 && staincolor2 >= 0)
684 l2 = (int)lhrandom(0.5, 256.5);
686 if(blendmode == PBLEND_INVMOD)
688 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
689 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
690 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
694 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
695 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
696 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
698 if(r > 0xFF) r = 0xFF;
699 if(g > 0xFF) g = 0xFF;
700 if(b > 0xFF) b = 0xFF;
704 r = part->color[0]; // -1 is shorthand for stain = particle color
708 part->staincolor[0] = r;
709 part->staincolor[1] = g;
710 part->staincolor[2] = b;
711 part->stainalpha = palpha * stainalpha;
712 part->stainsize = psize * stainsize;
715 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
717 part->color[0] *= tint[0];
718 part->color[1] *= tint[1];
719 part->color[2] *= tint[2];
721 part->alpha *= tint[3];
722 part->alphafade *= tint[3];
723 part->stainalpha *= tint[3];
727 part->sizeincrease = psizeincrease;
728 part->gravity = pgravity;
729 part->bounce = pbounce;
730 part->stretch = stretch;
732 part->org[0] = px + originjitter * v[0];
733 part->org[1] = py + originjitter * v[1];
734 part->org[2] = pz + originjitter * v[2];
735 part->vel[0] = pvx + velocityjitter * v[0];
736 part->vel[1] = pvy + velocityjitter * v[1];
737 part->vel[2] = pvz + velocityjitter * v[2];
739 part->airfriction = pairfriction;
740 part->liquidfriction = pliquidfriction;
741 part->die = cl.time + lifetime;
742 part->delayedspawn = cl.time;
743 // part->delayedcollisions = 0;
744 part->qualityreduction = pqualityreduction;
747 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
748 if (part->typeindex == pt_rain)
752 float lifetime = part->die - cl.time;
755 // turn raindrop into simple spark and create delayedspawn splash effect
756 part->typeindex = pt_spark;
758 VectorMA(part->org, lifetime, part->vel, endvec);
759 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
760 part->die = cl.time + lifetime * trace.fraction;
761 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
764 part2->delayedspawn = part->die;
765 part2->die += part->die - cl.time;
766 for (i = rand() & 7;i < 10;i++)
768 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
771 part2->delayedspawn = part->die;
772 part2->die += part->die - cl.time;
778 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
780 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
783 VectorMA(part->org, lifetime, part->vel, endvec);
784 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
785 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
792 static void CL_ImmediateBloodStain(particle_t *part)
797 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
798 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
800 VectorCopy(part->vel, v);
802 staintex = part->staintexnum;
803 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
806 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
807 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
809 VectorCopy(part->vel, v);
811 staintex = tex_blooddecal[rand()&7];
812 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
816 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
820 entity_render_t *ent = &cl.entities[hitent].render;
821 unsigned char color[3];
822 if (!cl_decals.integer)
824 if (!ent->allowdecals)
827 l2 = (int)lhrandom(0.5, 256.5);
829 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
830 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
831 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
833 if (cl_decals_newsystem.integer)
836 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
838 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
842 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
843 if (cl.free_decal >= cl.max_decals)
845 decal = &cl.decals[cl.free_decal++];
846 if (cl.num_decals < cl.free_decal)
847 cl.num_decals = cl.free_decal;
848 memset(decal, 0, sizeof(*decal));
849 decal->decalsequence = cl.decalsequence++;
850 decal->typeindex = pt_decal;
851 decal->texnum = texnum;
852 VectorMA(org, cl_decals_bias.value, normal, decal->org);
853 VectorCopy(normal, decal->normal);
855 decal->alpha = alpha;
856 decal->time2 = cl.time;
857 decal->color[0] = color[0];
858 decal->color[1] = color[1];
859 decal->color[2] = color[2];
862 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
863 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
864 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
866 decal->owner = hitent;
867 decal->clusterindex = -1000; // no vis culling unless we're sure
870 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
871 decal->ownermodel = cl.entities[decal->owner].render.model;
872 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
873 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
877 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
879 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
881 decal->clusterindex = leaf->clusterindex;
886 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
893 int besthitent = 0, hitent;
896 for (i = 0;i < 32;i++)
899 VectorMA(org, maxdist, org2, org2);
900 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
901 // take the closest trace result that doesn't end up hitting a NOMARKS
902 // surface (sky for example)
903 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
905 bestfrac = trace.fraction;
907 VectorCopy(trace.endpos, bestorg);
908 VectorCopy(trace.plane.normal, bestnormal);
912 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
915 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
916 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
917 static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
920 matrix4x4_t tempmatrix;
922 VectorLerp(originmins, 0.5, originmaxs, center);
923 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
924 if (effectnameindex == EFFECT_SVC_PARTICLE)
926 if (cl_particles.integer)
928 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
930 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
931 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
932 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
935 count *= cl_particles_quality.value;
936 for (;count > 0;count--)
938 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
939 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);
944 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
945 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
946 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
947 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
948 else if (effectnameindex == EFFECT_TE_SPIKE)
950 if (cl_particles_bulletimpacts.integer)
952 if (cl_particles_quake.integer)
954 if (cl_particles_smoke.integer)
955 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
959 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
960 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
961 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);
965 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
966 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
968 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
970 if (cl_particles_bulletimpacts.integer)
972 if (cl_particles_quake.integer)
974 if (cl_particles_smoke.integer)
975 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
979 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
980 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
981 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);
985 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
986 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
987 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);
989 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
991 if (cl_particles_bulletimpacts.integer)
993 if (cl_particles_quake.integer)
995 if (cl_particles_smoke.integer)
996 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1000 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1001 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1002 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);
1006 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1007 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1009 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1011 if (cl_particles_bulletimpacts.integer)
1013 if (cl_particles_quake.integer)
1015 if (cl_particles_smoke.integer)
1016 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1020 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1021 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1022 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);
1026 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1027 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1028 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);
1030 else if (effectnameindex == EFFECT_TE_BLOOD)
1032 if (!cl_particles_blood.integer)
1034 if (cl_particles_quake.integer)
1035 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1038 static double bloodaccumulator = 0;
1039 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1040 //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);
1041 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1042 for (;bloodaccumulator > 0;bloodaccumulator--)
1044 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);
1045 if (immediatebloodstain && part)
1047 immediatebloodstain = false;
1048 CL_ImmediateBloodStain(part);
1053 else if (effectnameindex == EFFECT_TE_SPARK)
1054 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1055 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1057 // plasma scorch mark
1058 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1059 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1060 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1062 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1064 if (cl_particles_bulletimpacts.integer)
1066 if (cl_particles_quake.integer)
1067 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1070 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1071 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1072 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);
1076 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1077 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1079 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1081 if (cl_particles_bulletimpacts.integer)
1083 if (cl_particles_quake.integer)
1084 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1087 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1088 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1089 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);
1093 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1094 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1095 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);
1097 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1099 CL_ParticleExplosion(center);
1100 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);
1102 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1104 CL_ParticleExplosion(center);
1105 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);
1107 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1109 if (cl_particles_quake.integer)
1112 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1115 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);
1117 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);
1121 CL_ParticleExplosion(center);
1122 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);
1124 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1125 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);
1126 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1128 count *= cl_particles_quality.value;
1130 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);
1132 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1134 float i, j, inc, vel;
1137 inc = 8 / cl_particles_quality.value;
1138 for (i = -128;i < 128;i += inc)
1140 for (j = -128;j < 128;j += inc)
1142 dir[0] = j + lhrandom(0, inc);
1143 dir[1] = i + lhrandom(0, inc);
1145 org[0] = center[0] + dir[0];
1146 org[1] = center[1] + dir[1];
1147 org[2] = center[2] + lhrandom(0, 64);
1148 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1149 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);
1153 else if (effectnameindex == EFFECT_TE_TELEPORT)
1155 float i, j, k, inc, vel;
1158 if (cl_particles_quake.integer)
1159 inc = 4 / cl_particles_quality.value;
1161 inc = 8 / cl_particles_quality.value;
1162 for (i = -16;i < 16;i += inc)
1164 for (j = -16;j < 16;j += inc)
1166 for (k = -24;k < 32;k += inc)
1168 VectorSet(dir, i*8, j*8, k*8);
1169 VectorNormalize(dir);
1170 vel = lhrandom(50, 113);
1171 if (cl_particles_quake.integer)
1172 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);
1174 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);
1178 if (!cl_particles_quake.integer)
1179 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);
1180 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);
1182 else if (effectnameindex == EFFECT_TE_TEI_G3)
1183 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);
1184 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1186 if (cl_particles_smoke.integer)
1188 count *= 0.25f * cl_particles_quality.value;
1190 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);
1193 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1195 CL_ParticleExplosion(center);
1196 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);
1198 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1201 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1202 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1203 if (cl_particles_smoke.integer)
1204 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1205 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);
1206 if (cl_particles_sparks.integer)
1207 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1208 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);
1209 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);
1211 else if (effectnameindex == EFFECT_EF_FLAME)
1213 count *= 300 * cl_particles_quality.value;
1215 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);
1216 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);
1218 else if (effectnameindex == EFFECT_EF_STARDUST)
1220 count *= 200 * cl_particles_quality.value;
1222 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);
1223 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);
1225 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1229 int smoke, blood, bubbles, r, color;
1231 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1234 Vector4Set(light, 0, 0, 0, 0);
1236 if (effectnameindex == EFFECT_TR_ROCKET)
1237 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1238 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1240 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1241 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1243 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1245 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1246 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1250 matrix4x4_t tempmatrix;
1251 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1252 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);
1253 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1257 if (!spawnparticles)
1260 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1263 VectorSubtract(originmaxs, originmins, dir);
1264 len = VectorNormalizeLength(dir);
1267 dec = -ent->persistent.trail_time;
1268 ent->persistent.trail_time += len;
1269 if (ent->persistent.trail_time < 0.01f)
1272 // if we skip out, leave it reset
1273 ent->persistent.trail_time = 0.0f;
1278 // advance into this frame to reach the first puff location
1279 VectorMA(originmins, dec, dir, pos);
1282 smoke = cl_particles.integer && cl_particles_smoke.integer;
1283 blood = cl_particles.integer && cl_particles_blood.integer;
1284 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1285 qd = 1.0f / cl_particles_quality.value;
1292 if (effectnameindex == EFFECT_TR_BLOOD)
1294 if (cl_particles_quake.integer)
1296 color = particlepalette[67 + (rand()&3)];
1297 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1302 CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1305 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1307 if (cl_particles_quake.integer)
1310 color = particlepalette[67 + (rand()&3)];
1311 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);
1316 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);
1322 if (effectnameindex == EFFECT_TR_ROCKET)
1324 if (cl_particles_quake.integer)
1327 color = particlepalette[ramp3[r]];
1328 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1332 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*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);
1333 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);
1336 else if (effectnameindex == EFFECT_TR_GRENADE)
1338 if (cl_particles_quake.integer)
1341 color = particlepalette[ramp3[r]];
1342 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);
1346 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);
1349 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1351 if (cl_particles_quake.integer)
1354 color = particlepalette[52 + (rand()&7)];
1355 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1356 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);
1358 else if (gamemode == GAME_GOODVSBAD2)
1361 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);
1365 color = particlepalette[20 + (rand()&7)];
1366 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);
1369 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1371 if (cl_particles_quake.integer)
1374 color = particlepalette[230 + (rand()&7)];
1375 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1376 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);
1380 color = particlepalette[226 + (rand()&7)];
1381 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);
1384 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1386 if (cl_particles_quake.integer)
1388 color = particlepalette[152 + (rand()&3)];
1389 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);
1391 else if (gamemode == GAME_GOODVSBAD2)
1394 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);
1396 else if (gamemode == GAME_PRYDON)
1399 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);
1402 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);
1404 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1407 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);
1409 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1412 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);
1414 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1415 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);
1419 if (effectnameindex == EFFECT_TR_ROCKET)
1420 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1421 else if (effectnameindex == EFFECT_TR_GRENADE)
1422 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);
1424 // advance to next time and position
1427 VectorMA (pos, dec, dir, pos);
1430 ent->persistent.trail_time = len;
1433 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1436 // this is also called on point effects with spawndlight = true and
1437 // spawnparticles = true
1438 // it is called CL_ParticleTrail because most code does not want to supply
1439 // these parameters, only trail handling does
1440 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])
1442 qboolean found = false;
1444 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1446 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1447 return; // no such effect
1449 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1451 int effectinfoindex;
1454 particleeffectinfo_t *info;
1466 qboolean underwater;
1467 qboolean immediatebloodstain;
1469 float avgtint[4], tint[4], tintlerp;
1470 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1471 VectorLerp(originmins, 0.5, originmaxs, center);
1472 supercontents = CL_PointSuperContents(center);
1473 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1474 VectorSubtract(originmaxs, originmins, traildir);
1475 traillen = VectorLength(traildir);
1476 VectorNormalize(traildir);
1479 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1483 Vector4Set(avgtint, 1, 1, 1, 1);
1485 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1487 if (info->effectnameindex == effectnameindex)
1490 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1492 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1495 // spawn a dlight if requested
1496 if (info->lightradiusstart > 0 && spawndlight)
1498 matrix4x4_t tempmatrix;
1499 if (info->trailspacing > 0)
1500 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1502 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1503 if (info->lighttime > 0 && info->lightradiusfade > 0)
1505 // light flash (explosion, etc)
1506 // called when effect starts
1507 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);
1509 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1512 // called by CL_LinkNetworkEntity
1513 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1514 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1515 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1516 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1517 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);
1518 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1522 if (!spawnparticles)
1527 if (info->tex[1] > info->tex[0])
1529 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1530 tex = min(tex, info->tex[1] - 1);
1532 if(info->staintex[0] < 0)
1533 staintex = info->staintex[0];
1536 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1537 staintex = min(staintex, info->staintex[1] - 1);
1539 if (info->particletype == pt_decal)
1541 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1542 AnglesFromVectors(angles, velocity, NULL, false);
1543 AngleVectors(angles, forward, right, up);
1544 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1546 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]);
1548 else if (info->orientation == PARTICLE_HBEAM)
1550 AnglesFromVectors(angles, traildir, NULL, false);
1551 AngleVectors(angles, forward, right, up);
1552 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1554 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);
1558 if (!cl_particles.integer)
1560 switch (info->particletype)
1562 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1563 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1564 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1565 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1566 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1567 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1570 VectorCopy(originmins, trailpos);
1571 if (info->trailspacing > 0)
1573 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1574 trailstep = info->trailspacing / cl_particles_quality.value;
1575 immediatebloodstain = false;
1577 AnglesFromVectors(angles, traildir, NULL, false);
1578 AngleVectors(angles, forward, right, up);
1579 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1580 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1584 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1586 immediatebloodstain =
1587 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1589 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1591 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1592 AnglesFromVectors(angles, velocity, NULL, false);
1593 AngleVectors(angles, forward, right, up);
1594 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1595 VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1597 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1598 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1600 if (info->tex[1] > info->tex[0])
1602 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1603 tex = min(tex, info->tex[1] - 1);
1607 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1608 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1609 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1613 tintlerp = lhrandom(0, 1);
1614 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1617 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);
1618 if (immediatebloodstain && part)
1620 immediatebloodstain = false;
1621 CL_ImmediateBloodStain(part);
1624 VectorMA(trailpos, trailstep, traildir, trailpos);
1631 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1634 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)
1636 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1644 void CL_EntityParticles (const entity_t *ent)
1647 vec_t pitch, yaw, dist = 64, beamlength = 16;
1649 static vec3_t avelocities[NUMVERTEXNORMALS];
1650 if (!cl_particles.integer) return;
1651 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1653 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1655 if (!avelocities[0][0])
1656 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1657 avelocities[0][i] = lhrandom(0, 2.55);
1659 for (i = 0;i < NUMVERTEXNORMALS;i++)
1661 yaw = cl.time * avelocities[i][0];
1662 pitch = cl.time * avelocities[i][1];
1663 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1664 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1665 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1666 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);
1671 void CL_ReadPointFile_f (void)
1673 double org[3], leakorg[3];
1676 char *pointfile = NULL, *pointfilepos, *t, tchar;
1677 char name[MAX_QPATH];
1682 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1683 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1686 Con_Printf("Could not open %s\n", name);
1690 Con_Printf("Reading %s...\n", name);
1691 VectorClear(leakorg);
1694 pointfilepos = pointfile;
1695 while (*pointfilepos)
1697 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1702 while (*t && *t != '\n' && *t != '\r')
1706 #if _MSC_VER >= 1400
1707 #define sscanf sscanf_s
1709 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1710 VectorCopy(org, vecorg);
1716 VectorCopy(org, leakorg);
1719 if (cl.num_particles < cl.max_particles - 3)
1722 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);
1725 Mem_Free(pointfile);
1726 VectorCopy(leakorg, vecorg);
1727 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1729 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);
1730 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);
1731 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);
1736 CL_ParseParticleEffect
1738 Parse an effect out of the server message
1741 void CL_ParseParticleEffect (void)
1744 int i, count, msgcount, color;
1746 MSG_ReadVector(&cl_message, org, cls.protocol);
1747 for (i=0 ; i<3 ; i++)
1748 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1749 msgcount = MSG_ReadByte(&cl_message);
1750 color = MSG_ReadByte(&cl_message);
1752 if (msgcount == 255)
1757 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1762 CL_ParticleExplosion
1766 void CL_ParticleExplosion (const vec3_t org)
1772 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1773 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1775 if (cl_particles_quake.integer)
1777 for (i = 0;i < 1024;i++)
1783 color = particlepalette[ramp1[r]];
1784 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);
1788 color = particlepalette[ramp2[r]];
1789 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);
1795 i = CL_PointSuperContents(org);
1796 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1798 if (cl_particles.integer && cl_particles_bubbles.integer)
1799 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1800 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);
1804 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1806 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1813 VectorMA(org, 128, v2, v);
1814 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1816 while (k < 16 && trace.fraction < 0.1f);
1817 VectorSubtract(trace.endpos, org, v2);
1818 VectorScale(v2, 2.0f, v2);
1819 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);
1825 if (cl_particles_explosions_shell.integer)
1826 R_NewExplosion(org);
1831 CL_ParticleExplosion2
1835 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1838 if (!cl_particles.integer) return;
1840 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1842 k = particlepalette[colorStart + (i % colorLength)];
1843 if (cl_particles_quake.integer)
1844 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);
1846 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);
1850 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1853 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1854 if (cl_particles_sparks.integer)
1856 sparkcount *= cl_particles_quality.value;
1857 while(sparkcount-- > 0)
1858 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);
1862 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1865 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1866 if (cl_particles_smoke.integer)
1868 smokecount *= cl_particles_quality.value;
1869 while(smokecount-- > 0)
1870 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);
1874 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)
1878 if (!cl_particles.integer) return;
1879 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1881 count = (int)(count * cl_particles_quality.value);
1884 k = particlepalette[colorbase + (rand()&3)];
1885 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);
1889 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1892 float minz, maxz, lifetime = 30;
1894 if (!cl_particles.integer) return;
1895 if (dir[2] < 0) // falling
1897 minz = maxs[2] + dir[2] * 0.1;
1900 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1905 maxz = maxs[2] + dir[2] * 0.1;
1907 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1910 count = (int)(count * cl_particles_quality.value);
1915 if (!cl_particles_rain.integer) break;
1916 count *= 4; // ick, this should be in the mod or maps?
1920 k = particlepalette[colorbase + (rand()&3)];
1921 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1922 if (gamemode == GAME_GOODVSBAD2)
1923 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);
1925 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);
1929 if (!cl_particles_snow.integer) break;
1932 k = particlepalette[colorbase + (rand()&3)];
1933 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1934 if (gamemode == GAME_GOODVSBAD2)
1935 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);
1937 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);
1941 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1945 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1946 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1947 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1948 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1949 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1950 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1952 #define PARTICLETEXTURESIZE 64
1953 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1955 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1959 dz = 1 - (dx*dx+dy*dy);
1960 if (dz > 0) // it does hit the sphere
1964 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1965 VectorNormalize(normal);
1966 dot = DotProduct(normal, light);
1967 if (dot > 0.5) // interior reflection
1968 f += ((dot * 2) - 1);
1969 else if (dot < -0.5) // exterior reflection
1970 f += ((dot * -2) - 1);
1972 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1973 VectorNormalize(normal);
1974 dot = DotProduct(normal, light);
1975 if (dot > 0.5) // interior reflection
1976 f += ((dot * 2) - 1);
1977 else if (dot < -0.5) // exterior reflection
1978 f += ((dot * -2) - 1);
1980 f += 16; // just to give it a haze so you can see the outline
1981 f = bound(0, f, 255);
1982 return (unsigned char) f;
1988 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1989 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1991 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1992 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1993 *width = particlefontcellwidth;
1994 *height = particlefontcellheight;
1997 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1999 int basex, basey, w, h, y;
2000 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2001 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2002 Sys_Error("invalid particle texture size for autogenerating");
2003 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2004 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2007 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2010 float cx, cy, dx, dy, f, iradius;
2012 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2013 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2014 iradius = 1.0f / radius;
2015 alpha *= (1.0f / 255.0f);
2016 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2018 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2022 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2027 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2028 d[0] += (int)(f * (blue - d[0]));
2029 d[1] += (int)(f * (green - d[1]));
2030 d[2] += (int)(f * (red - d[2]));
2037 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2040 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2042 data[0] = bound(minb, data[0], maxb);
2043 data[1] = bound(ming, data[1], maxg);
2044 data[2] = bound(minr, data[2], maxr);
2049 static void particletextureinvert(unsigned char *data)
2052 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2054 data[0] = 255 - data[0];
2055 data[1] = 255 - data[1];
2056 data[2] = 255 - data[2];
2060 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2061 static void R_InitBloodTextures (unsigned char *particletexturedata)
2064 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2065 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2068 for (i = 0;i < 8;i++)
2070 memset(data, 255, datasize);
2071 for (k = 0;k < 24;k++)
2072 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2073 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2074 particletextureinvert(data);
2075 setuptex(tex_bloodparticle[i], data, particletexturedata);
2079 for (i = 0;i < 8;i++)
2081 memset(data, 255, datasize);
2083 for (j = 1;j < 10;j++)
2084 for (k = min(j, m - 1);k < m;k++)
2085 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2086 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2087 particletextureinvert(data);
2088 setuptex(tex_blooddecal[i], data, particletexturedata);
2094 //uncomment this to make engine save out particle font to a tga file when run
2095 //#define DUMPPARTICLEFONT
2097 static void R_InitParticleTexture (void)
2099 int x, y, d, i, k, m;
2100 int basex, basey, w, h;
2101 float dx, dy, f, s1, t1, s2, t2;
2104 fs_offset_t filesize;
2105 char texturename[MAX_QPATH];
2108 // a note: decals need to modulate (multiply) the background color to
2109 // properly darken it (stain), and they need to be able to alpha fade,
2110 // this is a very difficult challenge because it means fading to white
2111 // (no change to background) rather than black (darkening everything
2112 // behind the whole decal polygon), and to accomplish this the texture is
2113 // inverted (dark red blood on white background becomes brilliant cyan
2114 // and white on black background) so we can alpha fade it to black, then
2115 // we invert it again during the blendfunc to make it work...
2117 #ifndef DUMPPARTICLEFONT
2118 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2121 particlefonttexture = decalskinframe->base;
2122 // TODO maybe allow custom grid size?
2123 particlefontwidth = image_width;
2124 particlefontheight = image_height;
2125 particlefontcellwidth = image_width / 8;
2126 particlefontcellheight = image_height / 8;
2127 particlefontcols = 8;
2128 particlefontrows = 8;
2133 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2134 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2135 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2136 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2137 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2139 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2140 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2141 particlefontcols = 8;
2142 particlefontrows = 8;
2144 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2147 for (i = 0;i < 8;i++)
2149 memset(data, 255, datasize);
2152 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2153 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2155 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2157 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2158 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2160 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2161 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2163 d = (int)(d * (1-(dx*dx+dy*dy)));
2164 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2165 d = bound(0, d, 255);
2166 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2173 setuptex(tex_smoke[i], data, particletexturedata);
2177 memset(data, 255, datasize);
2178 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2180 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2181 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2183 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2184 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2185 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2188 setuptex(tex_rainsplash, data, particletexturedata);
2191 memset(data, 255, datasize);
2192 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2194 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2195 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2197 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2198 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2199 d = bound(0, d, 255);
2200 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2203 setuptex(tex_particle, data, particletexturedata);
2206 memset(data, 255, datasize);
2207 light[0] = 1;light[1] = 1;light[2] = 1;
2208 VectorNormalize(light);
2209 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2211 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2212 // stretch upper half of bubble by +50% and shrink lower half by -50%
2213 // (this gives an elongated teardrop shape)
2215 dy = (dy - 0.5f) * 2.0f;
2217 dy = (dy - 0.5f) / 1.5f;
2218 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2220 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2221 // shrink bubble width to half
2223 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2226 setuptex(tex_raindrop, data, particletexturedata);
2229 memset(data, 255, datasize);
2230 light[0] = 1;light[1] = 1;light[2] = 1;
2231 VectorNormalize(light);
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 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2241 setuptex(tex_bubble, data, particletexturedata);
2243 // Blood particles and blood decals
2244 R_InitBloodTextures (particletexturedata);
2247 for (i = 0;i < 8;i++)
2249 memset(data, 255, datasize);
2250 for (k = 0;k < 12;k++)
2251 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2252 for (k = 0;k < 3;k++)
2253 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2254 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2255 particletextureinvert(data);
2256 setuptex(tex_bulletdecal[i], data, particletexturedata);
2259 #ifdef DUMPPARTICLEFONT
2260 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2263 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2264 particlefonttexture = decalskinframe->base;
2266 Mem_Free(particletexturedata);
2271 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2273 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2274 particletexture[i].texture = particlefonttexture;
2275 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2276 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2277 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2278 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2281 #ifndef DUMPPARTICLEFONT
2282 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2283 if (!particletexture[tex_beam].texture)
2286 unsigned char noise3[64][64], data2[64][16][4];
2288 fractalnoise(&noise3[0][0], 64, 4);
2290 for (y = 0;y < 64;y++)
2292 dy = (y - 0.5f*64) / (64*0.5f-1);
2293 for (x = 0;x < 16;x++)
2295 dx = (x - 0.5f*16) / (16*0.5f-2);
2296 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2297 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2298 data2[y][x][3] = 255;
2302 #ifdef DUMPPARTICLEFONT
2303 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2305 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2307 particletexture[tex_beam].s1 = 0;
2308 particletexture[tex_beam].t1 = 0;
2309 particletexture[tex_beam].s2 = 1;
2310 particletexture[tex_beam].t2 = 1;
2312 // now load an texcoord/texture override file
2313 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2320 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2322 if(!strcmp(com_token, "\n"))
2323 continue; // empty line
2324 i = atoi(com_token);
2332 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2334 strlcpy(texturename, com_token, sizeof(texturename));
2335 s1 = atof(com_token);
2336 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2339 t1 = atof(com_token);
2340 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2342 s2 = atof(com_token);
2343 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2345 t2 = atof(com_token);
2346 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2347 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2348 strlcpy(texturename, com_token, sizeof(texturename));
2355 if (!texturename[0])
2357 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2360 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2362 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2365 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2368 // R_SkinFrame_LoadExternal already complained
2371 particletexture[i].texture = sf->base;
2372 particletexture[i].s1 = s1;
2373 particletexture[i].t1 = t1;
2374 particletexture[i].s2 = s2;
2375 particletexture[i].t2 = t2;
2381 static void r_part_start(void)
2384 // generate particlepalette for convenience from the main one
2385 for (i = 0;i < 256;i++)
2386 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2387 particletexturepool = R_AllocTexturePool();
2388 R_InitParticleTexture ();
2389 CL_Particles_LoadEffectInfo(NULL);
2392 static void r_part_shutdown(void)
2394 R_FreeTexturePool(&particletexturepool);
2397 static void r_part_newmap(void)
2400 R_SkinFrame_MarkUsed(decalskinframe);
2401 CL_Particles_LoadEffectInfo(NULL);
2404 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2405 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2407 void R_Particles_Init (void)
2410 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2412 particle_elements[i*6+0] = i*4+0;
2413 particle_elements[i*6+1] = i*4+1;
2414 particle_elements[i*6+2] = i*4+2;
2415 particle_elements[i*6+3] = i*4+0;
2416 particle_elements[i*6+4] = i*4+2;
2417 particle_elements[i*6+5] = i*4+3;
2420 Cvar_RegisterVariable(&r_drawparticles);
2421 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2422 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2423 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2424 Cvar_RegisterVariable(&r_drawdecals);
2425 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2426 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2429 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2431 int surfacelistindex;
2433 float *v3f, *t2f, *c4f;
2434 particletexture_t *tex;
2435 vec_t right[3], up[3], size, ca;
2436 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2438 RSurf_ActiveWorldEntity();
2440 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2441 // R_Mesh_ResetTextureState();
2442 GL_DepthMask(false);
2443 GL_DepthRange(0, 1);
2444 GL_PolygonOffset(0, 0);
2446 GL_CullFace(GL_NONE);
2448 // generate all the vertices at once
2449 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2451 d = cl.decals + surfacelist[surfacelistindex];
2454 c4f = particle_color4f + 16*surfacelistindex;
2455 ca = d->alpha * alphascale;
2456 // ensure alpha multiplier saturates properly
2457 if (ca > 1.0f / 256.0f)
2459 if (r_refdef.fogenabled)
2460 ca *= RSurf_FogVertex(d->org);
2461 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2462 Vector4Copy(c4f, c4f + 4);
2463 Vector4Copy(c4f, c4f + 8);
2464 Vector4Copy(c4f, c4f + 12);
2466 // calculate vertex positions
2467 size = d->size * cl_particles_size.value;
2468 VectorVectors(d->normal, right, up);
2469 VectorScale(right, size, right);
2470 VectorScale(up, size, up);
2471 v3f = particle_vertex3f + 12*surfacelistindex;
2472 v3f[ 0] = d->org[0] - right[0] - up[0];
2473 v3f[ 1] = d->org[1] - right[1] - up[1];
2474 v3f[ 2] = d->org[2] - right[2] - up[2];
2475 v3f[ 3] = d->org[0] - right[0] + up[0];
2476 v3f[ 4] = d->org[1] - right[1] + up[1];
2477 v3f[ 5] = d->org[2] - right[2] + up[2];
2478 v3f[ 6] = d->org[0] + right[0] + up[0];
2479 v3f[ 7] = d->org[1] + right[1] + up[1];
2480 v3f[ 8] = d->org[2] + right[2] + up[2];
2481 v3f[ 9] = d->org[0] + right[0] - up[0];
2482 v3f[10] = d->org[1] + right[1] - up[1];
2483 v3f[11] = d->org[2] + right[2] - up[2];
2485 // calculate texcoords
2486 tex = &particletexture[d->texnum];
2487 t2f = particle_texcoord2f + 8*surfacelistindex;
2488 t2f[0] = tex->s1;t2f[1] = tex->t2;
2489 t2f[2] = tex->s1;t2f[3] = tex->t1;
2490 t2f[4] = tex->s2;t2f[5] = tex->t1;
2491 t2f[6] = tex->s2;t2f[7] = tex->t2;
2494 // now render the decals all at once
2495 // (this assumes they all use one particle font texture!)
2496 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2497 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2498 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2499 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2502 void R_DrawDecals (void)
2505 int drawdecals = r_drawdecals.integer;
2510 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2512 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2513 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2515 // LordHavoc: early out conditions
2519 decalfade = frametime * 256 / cl_decals_fadetime.value;
2520 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2521 drawdist2 = drawdist2*drawdist2;
2523 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2525 if (!decal->typeindex)
2528 if (killsequence - decal->decalsequence > 0)
2531 if (cl.time > decal->time2 + cl_decals_time.value)
2533 decal->alpha -= decalfade;
2534 if (decal->alpha <= 0)
2540 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2542 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2543 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2549 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2555 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))
2556 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2559 decal->typeindex = 0;
2560 if (cl.free_decal > i)
2564 // reduce cl.num_decals if possible
2565 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2568 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2570 decal_t *olddecals = cl.decals;
2571 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2572 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2573 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2574 Mem_Free(olddecals);
2577 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2580 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2582 vec3_t vecorg, vecvel, baseright, baseup;
2583 int surfacelistindex;
2584 int batchstart, batchcount;
2585 const particle_t *p;
2587 rtexture_t *texture;
2588 float *v3f, *t2f, *c4f;
2589 particletexture_t *tex;
2590 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2591 // float ambient[3], diffuse[3], diffusenormal[3];
2592 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2593 vec4_t colormultiplier;
2594 float minparticledist_start, minparticledist_end;
2597 RSurf_ActiveWorldEntity();
2599 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));
2601 r_refdef.stats[r_stat_particles] += numsurfaces;
2602 // R_Mesh_ResetTextureState();
2603 GL_DepthMask(false);
2604 GL_DepthRange(0, 1);
2605 GL_PolygonOffset(0, 0);
2607 GL_CullFace(GL_NONE);
2609 spintime = r_refdef.scene.time;
2611 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2612 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2613 dofade = (minparticledist_start < minparticledist_end);
2615 // first generate all the vertices at once
2616 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2618 p = cl.particles + surfacelist[surfacelistindex];
2620 blendmode = (pblend_t)p->blendmode;
2622 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2623 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2624 alpha = palpha * colormultiplier[3];
2625 // ensure alpha multiplier saturates properly
2631 case PBLEND_INVALID:
2633 // additive and modulate can just fade out in fog (this is correct)
2634 if (r_refdef.fogenabled)
2635 alpha *= RSurf_FogVertex(p->org);
2636 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2637 alpha *= 1.0f / 256.0f;
2638 c4f[0] = p->color[0] * alpha;
2639 c4f[1] = p->color[1] * alpha;
2640 c4f[2] = p->color[2] * alpha;
2644 // additive and modulate can just fade out in fog (this is correct)
2645 if (r_refdef.fogenabled)
2646 alpha *= RSurf_FogVertex(p->org);
2647 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2648 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2649 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2650 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2654 c4f[0] = p->color[0] * colormultiplier[0];
2655 c4f[1] = p->color[1] * colormultiplier[1];
2656 c4f[2] = p->color[2] * colormultiplier[2];
2658 // note: lighting is not cheap!
2659 if (particletype[p->typeindex].lighting)
2661 vecorg[0] = p->org[0];
2662 vecorg[1] = p->org[1];
2663 vecorg[2] = p->org[2];
2664 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2666 // mix in the fog color
2667 if (r_refdef.fogenabled)
2669 fog = RSurf_FogVertex(p->org);
2671 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2672 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2673 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2675 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2676 VectorScale(c4f, alpha, c4f);
2679 // copy the color into the other three vertices
2680 Vector4Copy(c4f, c4f + 4);
2681 Vector4Copy(c4f, c4f + 8);
2682 Vector4Copy(c4f, c4f + 12);
2684 size = p->size * cl_particles_size.value;
2685 tex = &particletexture[p->texnum];
2686 switch(p->orientation)
2688 // case PARTICLE_INVALID:
2689 case PARTICLE_BILLBOARD:
2690 if (p->angle + p->spin)
2692 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2693 spinsin = sin(spinrad) * size;
2694 spincos = cos(spinrad) * size;
2695 spinm1 = -p->stretch * spincos;
2698 spinm4 = -p->stretch * spincos;
2699 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2700 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2704 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2705 VectorScale(r_refdef.view.up, size, up);
2708 v3f[ 0] = p->org[0] - right[0] - up[0];
2709 v3f[ 1] = p->org[1] - right[1] - up[1];
2710 v3f[ 2] = p->org[2] - right[2] - up[2];
2711 v3f[ 3] = p->org[0] - right[0] + up[0];
2712 v3f[ 4] = p->org[1] - right[1] + up[1];
2713 v3f[ 5] = p->org[2] - right[2] + up[2];
2714 v3f[ 6] = p->org[0] + right[0] + up[0];
2715 v3f[ 7] = p->org[1] + right[1] + up[1];
2716 v3f[ 8] = p->org[2] + right[2] + up[2];
2717 v3f[ 9] = p->org[0] + right[0] - up[0];
2718 v3f[10] = p->org[1] + right[1] - up[1];
2719 v3f[11] = p->org[2] + right[2] - up[2];
2720 t2f[0] = tex->s1;t2f[1] = tex->t2;
2721 t2f[2] = tex->s1;t2f[3] = tex->t1;
2722 t2f[4] = tex->s2;t2f[5] = tex->t1;
2723 t2f[6] = tex->s2;t2f[7] = tex->t2;
2725 case PARTICLE_ORIENTED_DOUBLESIDED:
2726 vecvel[0] = p->vel[0];
2727 vecvel[1] = p->vel[1];
2728 vecvel[2] = p->vel[2];
2729 VectorVectors(vecvel, baseright, baseup);
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, baseright, spinm2, baseup, right);
2740 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2744 VectorScale(baseright, size * p->stretch, right);
2745 VectorScale(baseup, size, up);
2747 v3f[ 0] = p->org[0] - right[0] - up[0];
2748 v3f[ 1] = p->org[1] - right[1] - up[1];
2749 v3f[ 2] = p->org[2] - right[2] - up[2];
2750 v3f[ 3] = p->org[0] - right[0] + up[0];
2751 v3f[ 4] = p->org[1] - right[1] + up[1];
2752 v3f[ 5] = p->org[2] - right[2] + up[2];
2753 v3f[ 6] = p->org[0] + right[0] + up[0];
2754 v3f[ 7] = p->org[1] + right[1] + up[1];
2755 v3f[ 8] = p->org[2] + right[2] + up[2];
2756 v3f[ 9] = p->org[0] + right[0] - up[0];
2757 v3f[10] = p->org[1] + right[1] - up[1];
2758 v3f[11] = p->org[2] + right[2] - up[2];
2759 t2f[0] = tex->s1;t2f[1] = tex->t2;
2760 t2f[2] = tex->s1;t2f[3] = tex->t1;
2761 t2f[4] = tex->s2;t2f[5] = tex->t1;
2762 t2f[6] = tex->s2;t2f[7] = tex->t2;
2764 case PARTICLE_SPARK:
2765 len = VectorLength(p->vel);
2766 VectorNormalize2(p->vel, up);
2767 lenfactor = p->stretch * 0.04 * len;
2768 if(lenfactor < size * 0.5)
2769 lenfactor = size * 0.5;
2770 VectorMA(p->org, -lenfactor, up, v);
2771 VectorMA(p->org, lenfactor, up, up2);
2772 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2773 t2f[0] = tex->s1;t2f[1] = tex->t2;
2774 t2f[2] = tex->s1;t2f[3] = tex->t1;
2775 t2f[4] = tex->s2;t2f[5] = tex->t1;
2776 t2f[6] = tex->s2;t2f[7] = tex->t2;
2778 case PARTICLE_VBEAM:
2779 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2780 VectorSubtract(p->vel, p->org, up);
2781 VectorNormalize(up);
2782 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2783 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2784 t2f[0] = tex->s2;t2f[1] = v[0];
2785 t2f[2] = tex->s1;t2f[3] = v[0];
2786 t2f[4] = tex->s1;t2f[5] = v[1];
2787 t2f[6] = tex->s2;t2f[7] = v[1];
2789 case PARTICLE_HBEAM:
2790 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2791 VectorSubtract(p->vel, p->org, up);
2792 VectorNormalize(up);
2793 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2794 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2795 t2f[0] = v[0];t2f[1] = tex->t1;
2796 t2f[2] = v[0];t2f[3] = tex->t2;
2797 t2f[4] = v[1];t2f[5] = tex->t2;
2798 t2f[6] = v[1];t2f[7] = tex->t1;
2803 // now render batches of particles based on blendmode and texture
2804 blendmode = PBLEND_INVALID;
2808 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2809 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2811 p = cl.particles + surfacelist[surfacelistindex];
2813 if (texture != particletexture[p->texnum].texture)
2815 texture = particletexture[p->texnum].texture;
2816 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2819 if (p->blendmode == PBLEND_INVMOD)
2821 // inverse modulate blend - group these
2822 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2823 // iterate until we find a change in settings
2824 batchstart = surfacelistindex++;
2825 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2827 p = cl.particles + surfacelist[surfacelistindex];
2828 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2834 // additive or alpha blend - group these
2835 // (we can group these because we premultiplied the texture alpha)
2836 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2837 // iterate until we find a change in settings
2838 batchstart = surfacelistindex++;
2839 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2841 p = cl.particles + surfacelist[surfacelistindex];
2842 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2847 batchcount = surfacelistindex - batchstart;
2848 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2852 void R_DrawParticles (void)
2855 int drawparticles = r_drawparticles.integer;
2856 float minparticledist_start;
2858 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2864 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2865 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2867 // LordHavoc: early out conditions
2868 if (!cl.num_particles)
2871 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2872 gravity = frametime * cl.movevars_gravity;
2873 update = frametime > 0;
2874 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2875 drawdist2 = drawdist2*drawdist2;
2877 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2881 if (cl.free_particle > i)
2882 cl.free_particle = i;
2888 if (p->delayedspawn > cl.time)
2891 p->size += p->sizeincrease * frametime;
2892 p->alpha -= p->alphafade * frametime;
2894 if (p->alpha <= 0 || p->die <= cl.time)
2897 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2899 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2901 if (p->typeindex == pt_blood)
2902 p->size += frametime * 8;
2904 p->vel[2] -= p->gravity * gravity;
2905 f = 1.0f - min(p->liquidfriction * frametime, 1);
2906 VectorScale(p->vel, f, p->vel);
2910 p->vel[2] -= p->gravity * gravity;
2913 f = 1.0f - min(p->airfriction * frametime, 1);
2914 VectorScale(p->vel, f, p->vel);
2918 VectorCopy(p->org, oldorg);
2919 VectorMA(p->org, frametime, p->vel, p->org);
2920 // if (p->bounce && cl.time >= p->delayedcollisions)
2921 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2923 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);
2924 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2925 // or if the trace hit something flagged as NOIMPACT
2926 // then remove the particle
2927 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2929 VectorCopy(trace.endpos, p->org);
2930 // react if the particle hit something
2931 if (trace.fraction < 1)
2933 VectorCopy(trace.endpos, p->org);
2935 if (p->staintexnum >= 0)
2937 // blood - splash on solid
2938 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2941 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2942 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2943 if (cl_decals.integer)
2945 // create a decal for the blood splat
2946 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2947 if (cl_decals_newsystem_bloodsmears.integer)
2949 VectorCopy(p->vel, decaldir);
2950 VectorNormalize(decaldir);
2953 VectorCopy(trace.plane.normal, decaldir);
2954 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2959 if (p->typeindex == pt_blood)
2961 // blood - splash on solid
2962 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2964 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2966 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)));
2967 if (cl_decals.integer)
2969 // create a decal for the blood splat
2970 if (cl_decals_newsystem_bloodsmears.integer)
2972 VectorCopy(p->vel, decaldir);
2973 VectorNormalize(decaldir);
2976 VectorCopy(trace.plane.normal, decaldir);
2977 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);
2982 else if (p->bounce < 0)
2984 // bounce -1 means remove on impact
2989 // anything else - bounce off solid
2990 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2991 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2996 if (VectorLength2(p->vel) < 0.03)
2998 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3000 VectorClear(p->vel);
3004 if (p->typeindex != pt_static)
3006 switch (p->typeindex)
3008 case pt_entityparticle:
3009 // particle that removes itself after one rendered frame
3016 a = CL_PointSuperContents(p->org);
3017 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3021 a = CL_PointSuperContents(p->org);
3022 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3026 a = CL_PointSuperContents(p->org);
3027 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3031 if (cl.time > p->time2)
3034 p->time2 = cl.time + (rand() & 3) * 0.1;
3035 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3036 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3038 a = CL_PointSuperContents(p->org);
3039 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3047 else if (p->delayedspawn > cl.time)
3051 // don't render particles too close to the view (they chew fillrate)
3052 // also don't render particles behind the view (useless)
3053 // further checks to cull to the frustum would be too slow here
3054 switch(p->typeindex)
3057 // beams have no culling
3058 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3061 if(cl_particles_visculling.integer)
3062 if (!r_refdef.viewcache.world_novis)
3063 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3065 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3067 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3070 // anything else just has to be in front of the viewer and visible at this distance
3071 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3072 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3079 if (cl.free_particle > i)
3080 cl.free_particle = i;
3083 // reduce cl.num_particles if possible
3084 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3087 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3089 particle_t *oldparticles = cl.particles;
3090 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3091 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3092 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3093 Mem_Free(oldparticles);