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 velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
120 float rotate[4]; // min/max base angle, min/max rotation over time
122 particleeffectinfo_t;
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 int numparticleeffectinfo;
127 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 static int particlepalette[256];
131 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
132 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
133 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
134 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
135 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
136 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
137 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
138 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
139 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
140 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
141 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
142 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
143 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
144 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
145 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
146 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
147 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
148 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
149 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
150 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
151 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
152 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
153 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
154 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
155 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
156 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
157 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
158 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
159 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
160 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
161 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
162 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
165 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
166 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
167 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 // particletexture_t is a rectangle in the particlefonttexture
172 typedef struct particletexture_s
175 float s1, t1, s2, t2;
179 static rtexturepool_t *particletexturepool;
180 static rtexture_t *particlefonttexture;
181 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
182 skinframe_t *decalskinframe;
184 // texture numbers in particle font
185 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
186 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
187 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
188 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
189 static const int tex_rainsplash = 32;
190 static const int tex_particle = 63;
191 static const int tex_bubble = 62;
192 static const int tex_raindrop = 61;
193 static const int tex_beam = 60;
195 particleeffectinfo_t baselineparticleeffectinfo =
197 0, //int effectnameindex; // which effect this belongs to
198 // PARTICLEEFFECT_* bits
200 // blood effects may spawn very few particles, so proper fraction-overflow
201 // handling is very important, this variable keeps track of the fraction
202 0.0, //double particleaccumulator;
203 // the math is: countabsolute + requestedcount * countmultiplier * quality
204 // absolute number of particles to spawn, often used for decals
205 // (unaffected by quality and requestedcount)
206 0.0f, //float countabsolute;
207 // multiplier for the number of particles CL_ParticleEffect was told to
208 // spawn, most effects do not really have a count and hence use 1, so
209 // this is often the actual count to spawn, not merely a multiplier
210 0.0f, //float countmultiplier;
211 // if > 0 this causes the particle to spawn in an evenly spaced line from
212 // originmins to originmaxs (causing them to describe a trail, not a box)
213 0.0f, //float trailspacing;
214 // type of particle to spawn (defines some aspects of behavior)
215 pt_alphastatic, //ptype_t particletype;
216 // blending mode used on this particle type
217 PBLEND_ALPHA, //pblend_t blendmode;
218 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
219 PARTICLE_BILLBOARD, //porientation_t orientation;
220 // range of colors to choose from in hex RRGGBB (like HTML color tags),
221 // randomly interpolated at spawn
222 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
223 // a random texture is chosen in this range (note the second value is one
224 // past the last choosable, so for example 8,16 chooses any from 8 up and
226 // if start and end of the range are the same, no randomization is done
227 {63, 63 /* tex_particle */}, //int tex[2];
228 // range of size values randomly chosen when spawning, plus size increase over time
229 {1, 1, 0.0f}, //float size[3];
230 // range of alpha values randomly chosen when spawning, plus alpha fade
231 {0.0f, 256.0f, 256.0f}, //float alpha[3];
232 // how long the particle should live (note it is also removed if alpha drops to 0)
233 {16777216.0f, 16777216.0f}, //float time[2];
234 // how much gravity affects this particle (negative makes it fly up!)
235 0.0f, //float gravity;
236 // how much bounce the particle has when it hits a surface
237 // if negative the particle is removed on impact
238 0.0f, //float bounce;
239 // if in air this friction is applied
240 // if negative the particle accelerates
241 0.0f, //float airfriction;
242 // if in liquid (water/slime/lava) this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float liquidfriction;
245 // these offsets are added to the values given to particleeffect(), and
246 // then an ellipsoid-shaped jitter is added as defined by these
247 // (they are the 3 radii)
248 1.0f, //float stretchfactor;
249 // stretch velocity factor (used for sparks)
250 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
251 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
252 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
253 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
254 0.0f, //float velocitymultiplier;
255 // an effect can also spawn a dlight
256 0.0f, //float lightradiusstart;
257 0.0f, //float lightradiusfade;
258 16777216.0f, //float lighttime;
259 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
260 true, //qboolean lightshadow;
261 0, //int lightcubemapnum;
262 {(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!
263 {-1, -1}, //int staintex[2];
264 {1.0f, 1.0f}, //float stainalpha[2];
265 {2.0f, 2.0f}, //float stainsize[2];
267 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
270 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
271 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
272 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
273 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
274 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
275 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
276 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
277 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
278 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
279 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
280 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
281 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
282 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
283 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
284 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
285 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
286 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
287 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
288 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
289 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
290 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
291 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
292 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)"};
293 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
294 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
295 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
296 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
297 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
298 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)"};
299 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"};
300 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
301 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
302 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
305 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
310 particleeffectinfo_t *info = NULL;
311 const char *text = textstart;
313 for (linenumber = 1;;linenumber++)
316 for (arrayindex = 0;arrayindex < 16;arrayindex++)
317 argv[arrayindex][0] = 0;
320 if (!COM_ParseToken_Simple(&text, true, false))
322 if (!strcmp(com_token, "\n"))
326 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
332 #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;}
333 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
334 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
335 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
336 #define readfloat(var) checkparms(2);var = atof(argv[1])
337 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
338 if (!strcmp(argv[0], "effect"))
342 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
344 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
347 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
349 if (particleeffectname[effectnameindex][0])
351 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
356 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
360 // if we run out of names, abort
361 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
363 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
366 info = particleeffectinfo + numparticleeffectinfo++;
367 // copy entire info from baseline, then fix up the nameindex
368 *info = baselineparticleeffectinfo;
369 info->effectnameindex = effectnameindex;
371 else if (info == NULL)
373 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
376 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
377 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
378 else if (!strcmp(argv[0], "type"))
381 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
382 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
383 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
384 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
385 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
386 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
387 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
388 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
389 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
390 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
391 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
392 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
393 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
394 info->blendmode = particletype[info->particletype].blendmode;
395 info->orientation = particletype[info->particletype].orientation;
397 else if (!strcmp(argv[0], "blend"))
400 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
401 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
402 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
403 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
405 else if (!strcmp(argv[0], "orientation"))
408 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
409 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
410 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
411 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
412 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
414 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
415 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
416 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
417 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
418 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
419 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
420 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
421 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
422 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
423 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
424 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
425 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
426 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
427 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
428 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
429 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
430 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
431 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
432 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
433 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
434 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
435 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
436 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
437 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
438 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
439 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
440 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
441 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
442 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
443 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; }
444 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
446 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
455 int CL_ParticleEffectIndexForName(const char *name)
458 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
459 if (!strcmp(particleeffectname[i], name))
464 const char *CL_ParticleEffectNameForIndex(int i)
466 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
468 return particleeffectname[i];
471 // MUST match effectnameindex_t in client.h
472 static const char *standardeffectnames[EFFECT_TOTAL] =
496 "TE_TEI_BIGEXPLOSION",
512 void CL_Particles_LoadEffectInfo(void)
516 unsigned char *filedata;
517 fs_offset_t filesize;
518 char filename[MAX_QPATH];
519 numparticleeffectinfo = 0;
520 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
521 memset(particleeffectname, 0, sizeof(particleeffectname));
522 for (i = 0;i < EFFECT_TOTAL;i++)
523 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
524 for (filepass = 0;;filepass++)
527 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
528 else if (filepass == 1)
530 if (!cl.worldbasename[0])
532 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
536 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
539 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
549 void CL_ReadPointFile_f (void);
550 void CL_Particles_Init (void)
552 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)");
553 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
555 Cvar_RegisterVariable (&cl_particles);
556 Cvar_RegisterVariable (&cl_particles_quality);
557 Cvar_RegisterVariable (&cl_particles_alpha);
558 Cvar_RegisterVariable (&cl_particles_size);
559 Cvar_RegisterVariable (&cl_particles_quake);
560 Cvar_RegisterVariable (&cl_particles_blood);
561 Cvar_RegisterVariable (&cl_particles_blood_alpha);
562 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
563 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
564 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
565 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
566 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
567 Cvar_RegisterVariable (&cl_particles_explosions_shell);
568 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
569 Cvar_RegisterVariable (&cl_particles_rain);
570 Cvar_RegisterVariable (&cl_particles_snow);
571 Cvar_RegisterVariable (&cl_particles_smoke);
572 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
573 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
574 Cvar_RegisterVariable (&cl_particles_sparks);
575 Cvar_RegisterVariable (&cl_particles_bubbles);
576 Cvar_RegisterVariable (&cl_particles_visculling);
577 Cvar_RegisterVariable (&cl_particles_collisions);
578 Cvar_RegisterVariable (&cl_decals);
579 Cvar_RegisterVariable (&cl_decals_visculling);
580 Cvar_RegisterVariable (&cl_decals_time);
581 Cvar_RegisterVariable (&cl_decals_fadetime);
582 Cvar_RegisterVariable (&cl_decals_newsystem);
583 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
584 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
585 Cvar_RegisterVariable (&cl_decals_models);
586 Cvar_RegisterVariable (&cl_decals_bias);
587 Cvar_RegisterVariable (&cl_decals_max);
590 void CL_Particles_Shutdown (void)
594 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
595 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
597 // list of all 26 parameters:
598 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
599 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
600 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
601 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
602 // palpha - opacity of particle as 0-255 (can be more than 255)
603 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
604 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
605 // pgravity - how much effect gravity has on the particle (0-1)
606 // 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
607 // px,py,pz - starting origin of particle
608 // pvx,pvy,pvz - starting velocity of particle
609 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
610 // blendmode - one of the PBLEND_ values
611 // orientation - one of the PARTICLE_ values
612 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
613 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
614 // stainalpha: opacity of the stain as factor for alpha
615 // stainsize: size of the stain as factor for palpha
616 // angle: base rotation of the particle geometry around its center normal
617 // spin: rotation speed of the particle geometry around its center normal
618 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])
623 if (!cl_particles.integer)
625 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
626 if (cl.free_particle >= cl.max_particles)
629 lifetime = palpha / min(1, palphafade);
630 part = &cl.particles[cl.free_particle++];
631 if (cl.num_particles < cl.free_particle)
632 cl.num_particles = cl.free_particle;
633 memset(part, 0, sizeof(*part));
634 VectorCopy(sortorigin, part->sortorigin);
635 part->typeindex = ptypeindex;
636 part->blendmode = blendmode;
637 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
639 particletexture_t *tex = &particletexture[ptex];
640 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
641 part->orientation = PARTICLE_VBEAM;
643 part->orientation = PARTICLE_HBEAM;
646 part->orientation = orientation;
647 l2 = (int)lhrandom(0.5, 256.5);
649 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
650 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
651 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
654 part->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[0]) * 256.0f);
655 part->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[1]) * 256.0f);
656 part->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[2]) * 256.0f);
658 part->alpha = palpha;
659 part->alphafade = palphafade;
660 part->staintexnum = staintex;
661 if(staincolor1 >= 0 && staincolor2 >= 0)
663 l2 = (int)lhrandom(0.5, 256.5);
665 if(blendmode == PBLEND_INVMOD)
667 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
668 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
669 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
673 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
674 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
675 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
677 if(r > 0xFF) r = 0xFF;
678 if(g > 0xFF) g = 0xFF;
679 if(b > 0xFF) b = 0xFF;
683 r = part->color[0]; // -1 is shorthand for stain = particle color
687 part->staincolor[0] = r;
688 part->staincolor[1] = g;
689 part->staincolor[2] = b;
690 part->stainalpha = palpha * stainalpha;
691 part->stainsize = psize * stainsize;
694 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
696 part->color[0] *= tint[0];
697 part->color[1] *= tint[1];
698 part->color[2] *= tint[2];
700 part->alpha *= tint[3];
701 part->alphafade *= tint[3];
702 part->stainalpha *= tint[3];
706 part->sizeincrease = psizeincrease;
707 part->gravity = pgravity;
708 part->bounce = pbounce;
709 part->stretch = stretch;
711 part->org[0] = px + originjitter * v[0];
712 part->org[1] = py + originjitter * v[1];
713 part->org[2] = pz + originjitter * v[2];
714 part->vel[0] = pvx + velocityjitter * v[0];
715 part->vel[1] = pvy + velocityjitter * v[1];
716 part->vel[2] = pvz + velocityjitter * v[2];
718 part->airfriction = pairfriction;
719 part->liquidfriction = pliquidfriction;
720 part->die = cl.time + lifetime;
721 part->delayedspawn = cl.time;
722 // part->delayedcollisions = 0;
723 part->qualityreduction = pqualityreduction;
726 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
727 if (part->typeindex == pt_rain)
731 float lifetime = part->die - cl.time;
734 // turn raindrop into simple spark and create delayedspawn splash effect
735 part->typeindex = pt_spark;
737 VectorMA(part->org, lifetime, part->vel, endvec);
738 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
739 part->die = cl.time + lifetime * trace.fraction;
740 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);
743 part2->delayedspawn = part->die;
744 part2->die += part->die - cl.time;
745 for (i = rand() & 7;i < 10;i++)
747 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);
750 part2->delayedspawn = part->die;
751 part2->die += part->die - cl.time;
757 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
759 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
762 VectorMA(part->org, lifetime, part->vel, endvec);
763 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
764 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
771 static void CL_ImmediateBloodStain(particle_t *part)
776 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
777 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
779 VectorCopy(part->vel, v);
781 staintex = part->staintexnum;
782 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);
785 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
786 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
788 VectorCopy(part->vel, v);
790 staintex = tex_blooddecal[rand()&7];
791 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);
795 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
799 entity_render_t *ent = &cl.entities[hitent].render;
800 unsigned char color[3];
801 if (!cl_decals.integer)
803 if (!ent->allowdecals)
806 l2 = (int)lhrandom(0.5, 256.5);
808 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
809 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
810 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
812 if (cl_decals_newsystem.integer)
815 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);
817 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);
821 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
822 if (cl.free_decal >= cl.max_decals)
824 decal = &cl.decals[cl.free_decal++];
825 if (cl.num_decals < cl.free_decal)
826 cl.num_decals = cl.free_decal;
827 memset(decal, 0, sizeof(*decal));
828 decal->decalsequence = cl.decalsequence++;
829 decal->typeindex = pt_decal;
830 decal->texnum = texnum;
831 VectorMA(org, cl_decals_bias.value, normal, decal->org);
832 VectorCopy(normal, decal->normal);
834 decal->alpha = alpha;
835 decal->time2 = cl.time;
836 decal->color[0] = color[0];
837 decal->color[1] = color[1];
838 decal->color[2] = color[2];
841 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
842 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
843 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
845 decal->owner = hitent;
846 decal->clusterindex = -1000; // no vis culling unless we're sure
849 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
850 decal->ownermodel = cl.entities[decal->owner].render.model;
851 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
852 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
856 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
858 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
860 decal->clusterindex = leaf->clusterindex;
865 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
868 float bestfrac, bestorg[3], bestnormal[3];
870 int besthitent = 0, hitent;
873 for (i = 0;i < 32;i++)
876 VectorMA(org, maxdist, org2, org2);
877 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
878 // take the closest trace result that doesn't end up hitting a NOMARKS
879 // surface (sky for example)
880 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
882 bestfrac = trace.fraction;
884 VectorCopy(trace.endpos, bestorg);
885 VectorCopy(trace.plane.normal, bestnormal);
889 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
892 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
893 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
894 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)
897 matrix4x4_t tempmatrix;
899 VectorLerp(originmins, 0.5, originmaxs, center);
900 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
901 if (effectnameindex == EFFECT_SVC_PARTICLE)
903 if (cl_particles.integer)
905 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
907 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
908 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
909 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
912 count *= cl_particles_quality.value;
913 for (;count > 0;count--)
915 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
916 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);
921 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
922 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
923 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
924 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
925 else if (effectnameindex == EFFECT_TE_SPIKE)
927 if (cl_particles_bulletimpacts.integer)
929 if (cl_particles_quake.integer)
931 if (cl_particles_smoke.integer)
932 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
936 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
937 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
938 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);
942 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
943 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
945 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
947 if (cl_particles_bulletimpacts.integer)
949 if (cl_particles_quake.integer)
951 if (cl_particles_smoke.integer)
952 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
956 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
957 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
958 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);
962 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
963 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
964 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);
966 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
972 if (cl_particles_smoke.integer)
973 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
977 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
978 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
979 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);
983 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
984 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
986 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
988 if (cl_particles_bulletimpacts.integer)
990 if (cl_particles_quake.integer)
992 if (cl_particles_smoke.integer)
993 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
997 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
998 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
999 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);
1003 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1004 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1005 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);
1007 else if (effectnameindex == EFFECT_TE_BLOOD)
1009 if (!cl_particles_blood.integer)
1011 if (cl_particles_quake.integer)
1012 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1015 static double bloodaccumulator = 0;
1016 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1017 //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);
1018 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1019 for (;bloodaccumulator > 0;bloodaccumulator--)
1021 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);
1022 if (immediatebloodstain && part)
1024 immediatebloodstain = false;
1025 CL_ImmediateBloodStain(part);
1030 else if (effectnameindex == EFFECT_TE_SPARK)
1031 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1032 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1034 // plasma scorch mark
1035 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1036 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1037 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1039 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1041 if (cl_particles_bulletimpacts.integer)
1043 if (cl_particles_quake.integer)
1044 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1047 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1048 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1049 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);
1053 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1054 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1056 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1058 if (cl_particles_bulletimpacts.integer)
1060 if (cl_particles_quake.integer)
1061 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1064 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1065 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1066 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);
1070 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1071 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1072 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);
1074 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1076 CL_ParticleExplosion(center);
1077 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);
1079 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1081 CL_ParticleExplosion(center);
1082 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);
1084 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1086 if (cl_particles_quake.integer)
1089 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1092 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);
1094 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);
1098 CL_ParticleExplosion(center);
1099 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);
1101 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1102 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);
1103 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1105 count *= cl_particles_quality.value;
1107 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);
1109 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1111 float i, j, inc, vel;
1114 inc = 8 / cl_particles_quality.value;
1115 for (i = -128;i < 128;i += inc)
1117 for (j = -128;j < 128;j += inc)
1119 dir[0] = j + lhrandom(0, inc);
1120 dir[1] = i + lhrandom(0, inc);
1122 org[0] = center[0] + dir[0];
1123 org[1] = center[1] + dir[1];
1124 org[2] = center[2] + lhrandom(0, 64);
1125 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1126 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);
1130 else if (effectnameindex == EFFECT_TE_TELEPORT)
1132 float i, j, k, inc, vel;
1135 if (cl_particles_quake.integer)
1136 inc = 4 / cl_particles_quality.value;
1138 inc = 8 / cl_particles_quality.value;
1139 for (i = -16;i < 16;i += inc)
1141 for (j = -16;j < 16;j += inc)
1143 for (k = -24;k < 32;k += inc)
1145 VectorSet(dir, i*8, j*8, k*8);
1146 VectorNormalize(dir);
1147 vel = lhrandom(50, 113);
1148 if (cl_particles_quake.integer)
1149 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);
1151 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);
1155 if (!cl_particles_quake.integer)
1156 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);
1157 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);
1159 else if (effectnameindex == EFFECT_TE_TEI_G3)
1160 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);
1161 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1163 if (cl_particles_smoke.integer)
1165 count *= 0.25f * cl_particles_quality.value;
1167 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);
1170 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1172 CL_ParticleExplosion(center);
1173 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);
1175 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1178 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1179 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1180 if (cl_particles_smoke.integer)
1181 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1182 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);
1183 if (cl_particles_sparks.integer)
1184 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1185 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);
1186 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);
1188 else if (effectnameindex == EFFECT_EF_FLAME)
1190 count *= 300 * cl_particles_quality.value;
1192 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);
1193 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);
1195 else if (effectnameindex == EFFECT_EF_STARDUST)
1197 count *= 200 * cl_particles_quality.value;
1199 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);
1200 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);
1202 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1206 int smoke, blood, bubbles, r, color;
1208 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1211 Vector4Set(light, 0, 0, 0, 0);
1213 if (effectnameindex == EFFECT_TR_ROCKET)
1214 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1215 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1217 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1218 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1220 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1222 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1223 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1227 matrix4x4_t tempmatrix;
1228 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1229 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);
1230 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1234 if (!spawnparticles)
1237 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1240 VectorSubtract(originmaxs, originmins, dir);
1241 len = VectorNormalizeLength(dir);
1244 dec = -ent->persistent.trail_time;
1245 ent->persistent.trail_time += len;
1246 if (ent->persistent.trail_time < 0.01f)
1249 // if we skip out, leave it reset
1250 ent->persistent.trail_time = 0.0f;
1255 // advance into this frame to reach the first puff location
1256 VectorMA(originmins, dec, dir, pos);
1259 smoke = cl_particles.integer && cl_particles_smoke.integer;
1260 blood = cl_particles.integer && cl_particles_blood.integer;
1261 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1262 qd = 1.0f / cl_particles_quality.value;
1269 if (effectnameindex == EFFECT_TR_BLOOD)
1271 if (cl_particles_quake.integer)
1273 color = particlepalette[67 + (rand()&3)];
1274 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);
1279 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);
1282 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1284 if (cl_particles_quake.integer)
1287 color = particlepalette[67 + (rand()&3)];
1288 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);
1293 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);
1299 if (effectnameindex == EFFECT_TR_ROCKET)
1301 if (cl_particles_quake.integer)
1304 color = particlepalette[ramp3[r]];
1305 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);
1309 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);
1310 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);
1313 else if (effectnameindex == EFFECT_TR_GRENADE)
1315 if (cl_particles_quake.integer)
1318 color = particlepalette[ramp3[r]];
1319 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);
1323 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);
1326 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1328 if (cl_particles_quake.integer)
1331 color = particlepalette[52 + (rand()&7)];
1332 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);
1333 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);
1335 else if (gamemode == GAME_GOODVSBAD2)
1338 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);
1342 color = particlepalette[20 + (rand()&7)];
1343 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);
1346 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1348 if (cl_particles_quake.integer)
1351 color = particlepalette[230 + (rand()&7)];
1352 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1353 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);
1357 color = particlepalette[226 + (rand()&7)];
1358 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);
1361 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1363 if (cl_particles_quake.integer)
1365 color = particlepalette[152 + (rand()&3)];
1366 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);
1368 else if (gamemode == GAME_GOODVSBAD2)
1371 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);
1373 else if (gamemode == GAME_PRYDON)
1376 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);
1379 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);
1381 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1384 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);
1386 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1389 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);
1391 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1392 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);
1396 if (effectnameindex == EFFECT_TR_ROCKET)
1397 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);
1398 else if (effectnameindex == EFFECT_TR_GRENADE)
1399 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);
1401 // advance to next time and position
1404 VectorMA (pos, dec, dir, pos);
1407 ent->persistent.trail_time = len;
1410 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1413 // this is also called on point effects with spawndlight = true and
1414 // spawnparticles = true
1415 // it is called CL_ParticleTrail because most code does not want to supply
1416 // these parameters, only trail handling does
1417 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])
1419 qboolean found = false;
1420 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1422 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1423 return; // no such effect
1425 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1427 int effectinfoindex;
1430 particleeffectinfo_t *info;
1437 qboolean underwater;
1438 qboolean immediatebloodstain;
1440 float avgtint[4], tint[4], tintlerp;
1441 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1442 VectorLerp(originmins, 0.5, originmaxs, center);
1443 supercontents = CL_PointSuperContents(center);
1444 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1445 VectorSubtract(originmaxs, originmins, traildir);
1446 traillen = VectorLength(traildir);
1447 VectorNormalize(traildir);
1450 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1454 Vector4Set(avgtint, 1, 1, 1, 1);
1456 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1458 if (info->effectnameindex == effectnameindex)
1461 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1463 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1466 // spawn a dlight if requested
1467 if (info->lightradiusstart > 0 && spawndlight)
1469 matrix4x4_t tempmatrix;
1470 if (info->trailspacing > 0)
1471 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1473 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1474 if (info->lighttime > 0 && info->lightradiusfade > 0)
1476 // light flash (explosion, etc)
1477 // called when effect starts
1478 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, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1480 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1483 // called by CL_LinkNetworkEntity
1484 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1485 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1486 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1487 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1488 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1489 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1493 if (!spawnparticles)
1498 if (info->tex[1] > info->tex[0])
1500 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1501 tex = min(tex, info->tex[1] - 1);
1503 if(info->staintex[0] < 0)
1504 staintex = info->staintex[0];
1507 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1508 staintex = min(staintex, info->staintex[1] - 1);
1510 if (info->particletype == pt_decal)
1511 CL_SpawnDecalParticleForPoint(center, 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]);
1512 else if (info->orientation == PARTICLE_HBEAM)
1513 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], originmins[1], originmins[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);
1516 if (!cl_particles.integer)
1518 switch (info->particletype)
1520 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1521 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1522 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1523 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1524 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1525 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1528 VectorCopy(originmins, trailpos);
1529 if (info->trailspacing > 0)
1531 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1532 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1533 immediatebloodstain = false;
1537 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1539 immediatebloodstain =
1540 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1542 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1544 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1545 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1547 if (info->tex[1] > info->tex[0])
1549 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1550 tex = min(tex, info->tex[1] - 1);
1554 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1555 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1556 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1560 tintlerp = lhrandom(0, 1);
1561 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1564 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], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[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);
1565 if (immediatebloodstain && part)
1567 immediatebloodstain = false;
1568 CL_ImmediateBloodStain(part);
1571 VectorMA(trailpos, trailstep, traildir, trailpos);
1578 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1581 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)
1583 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1591 void CL_EntityParticles (const entity_t *ent)
1594 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1595 static vec3_t avelocities[NUMVERTEXNORMALS];
1596 if (!cl_particles.integer) return;
1597 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1599 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1601 if (!avelocities[0][0])
1602 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1603 avelocities[0][i] = lhrandom(0, 2.55);
1605 for (i = 0;i < NUMVERTEXNORMALS;i++)
1607 yaw = cl.time * avelocities[i][0];
1608 pitch = cl.time * avelocities[i][1];
1609 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1610 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1611 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1612 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);
1617 void CL_ReadPointFile_f (void)
1619 vec3_t org, leakorg;
1621 char *pointfile = NULL, *pointfilepos, *t, tchar;
1622 char name[MAX_QPATH];
1627 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1628 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1631 Con_Printf("Could not open %s\n", name);
1635 Con_Printf("Reading %s...\n", name);
1636 VectorClear(leakorg);
1639 pointfilepos = pointfile;
1640 while (*pointfilepos)
1642 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1647 while (*t && *t != '\n' && *t != '\r')
1651 #if _MSC_VER >= 1400
1652 #define sscanf sscanf_s
1654 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1660 VectorCopy(org, leakorg);
1663 if (cl.num_particles < cl.max_particles - 3)
1666 CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1669 Mem_Free(pointfile);
1670 VectorCopy(leakorg, org);
1671 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1673 CL_NewParticle(org, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1674 CL_NewParticle(org, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1675 CL_NewParticle(org, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1680 CL_ParseParticleEffect
1682 Parse an effect out of the server message
1685 void CL_ParseParticleEffect (void)
1688 int i, count, msgcount, color;
1690 MSG_ReadVector(org, cls.protocol);
1691 for (i=0 ; i<3 ; i++)
1692 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1693 msgcount = MSG_ReadByte ();
1694 color = MSG_ReadByte ();
1696 if (msgcount == 255)
1701 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1706 CL_ParticleExplosion
1710 void CL_ParticleExplosion (const vec3_t org)
1716 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1717 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1719 if (cl_particles_quake.integer)
1721 for (i = 0;i < 1024;i++)
1727 color = particlepalette[ramp1[r]];
1728 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);
1732 color = particlepalette[ramp2[r]];
1733 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);
1739 i = CL_PointSuperContents(org);
1740 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1742 if (cl_particles.integer && cl_particles_bubbles.integer)
1743 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1744 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);
1748 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1750 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1757 VectorMA(org, 128, v2, v);
1758 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1760 while (k < 16 && trace.fraction < 0.1f);
1761 VectorSubtract(trace.endpos, org, v2);
1762 VectorScale(v2, 2.0f, v2);
1763 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);
1769 if (cl_particles_explosions_shell.integer)
1770 R_NewExplosion(org);
1775 CL_ParticleExplosion2
1779 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1782 if (!cl_particles.integer) return;
1784 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1786 k = particlepalette[colorStart + (i % colorLength)];
1787 if (cl_particles_quake.integer)
1788 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);
1790 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);
1794 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1797 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1798 if (cl_particles_sparks.integer)
1800 sparkcount *= cl_particles_quality.value;
1801 while(sparkcount-- > 0)
1802 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);
1806 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1809 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1810 if (cl_particles_smoke.integer)
1812 smokecount *= cl_particles_quality.value;
1813 while(smokecount-- > 0)
1814 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);
1818 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)
1822 if (!cl_particles.integer) return;
1823 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1825 count = (int)(count * cl_particles_quality.value);
1828 k = particlepalette[colorbase + (rand()&3)];
1829 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);
1833 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1836 float minz, maxz, lifetime = 30;
1838 if (!cl_particles.integer) return;
1839 if (dir[2] < 0) // falling
1841 minz = maxs[2] + dir[2] * 0.1;
1844 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1849 maxz = maxs[2] + dir[2] * 0.1;
1851 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1854 count = (int)(count * cl_particles_quality.value);
1859 if (!cl_particles_rain.integer) break;
1860 count *= 4; // ick, this should be in the mod or maps?
1864 k = particlepalette[colorbase + (rand()&3)];
1865 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1866 if (gamemode == GAME_GOODVSBAD2)
1867 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);
1869 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);
1873 if (!cl_particles_snow.integer) break;
1876 k = particlepalette[colorbase + (rand()&3)];
1877 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1878 if (gamemode == GAME_GOODVSBAD2)
1879 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);
1881 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);
1885 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1889 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1890 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1891 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1892 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1893 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1894 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1896 #define PARTICLETEXTURESIZE 64
1897 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1899 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1903 dz = 1 - (dx*dx+dy*dy);
1904 if (dz > 0) // it does hit the sphere
1908 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1909 VectorNormalize(normal);
1910 dot = DotProduct(normal, light);
1911 if (dot > 0.5) // interior reflection
1912 f += ((dot * 2) - 1);
1913 else if (dot < -0.5) // exterior reflection
1914 f += ((dot * -2) - 1);
1916 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1917 VectorNormalize(normal);
1918 dot = DotProduct(normal, light);
1919 if (dot > 0.5) // interior reflection
1920 f += ((dot * 2) - 1);
1921 else if (dot < -0.5) // exterior reflection
1922 f += ((dot * -2) - 1);
1924 f += 16; // just to give it a haze so you can see the outline
1925 f = bound(0, f, 255);
1926 return (unsigned char) f;
1932 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1933 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1935 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1936 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1937 *width = particlefontcellwidth;
1938 *height = particlefontcellheight;
1941 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1943 int basex, basey, w, h, y;
1944 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1945 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1946 Sys_Error("invalid particle texture size for autogenerating");
1947 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1948 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1951 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1954 float cx, cy, dx, dy, f, iradius;
1956 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1957 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1958 iradius = 1.0f / radius;
1959 alpha *= (1.0f / 255.0f);
1960 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1962 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1966 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1971 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1972 d[0] += (int)(f * (blue - d[0]));
1973 d[1] += (int)(f * (green - d[1]));
1974 d[2] += (int)(f * (red - d[2]));
1980 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1983 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1985 data[0] = bound(minb, data[0], maxb);
1986 data[1] = bound(ming, data[1], maxg);
1987 data[2] = bound(minr, data[2], maxr);
1991 void particletextureinvert(unsigned char *data)
1994 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1996 data[0] = 255 - data[0];
1997 data[1] = 255 - data[1];
1998 data[2] = 255 - data[2];
2002 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2003 static void R_InitBloodTextures (unsigned char *particletexturedata)
2006 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2007 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2010 for (i = 0;i < 8;i++)
2012 memset(data, 255, datasize);
2013 for (k = 0;k < 24;k++)
2014 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2015 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2016 particletextureinvert(data);
2017 setuptex(tex_bloodparticle[i], data, particletexturedata);
2021 for (i = 0;i < 8;i++)
2023 memset(data, 255, datasize);
2025 for (j = 1;j < 10;j++)
2026 for (k = min(j, m - 1);k < m;k++)
2027 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2028 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2029 particletextureinvert(data);
2030 setuptex(tex_blooddecal[i], data, particletexturedata);
2036 //uncomment this to make engine save out particle font to a tga file when run
2037 //#define DUMPPARTICLEFONT
2039 static void R_InitParticleTexture (void)
2041 int x, y, d, i, k, m;
2042 int basex, basey, w, h;
2043 float dx, dy, f, s1, t1, s2, t2;
2046 fs_offset_t filesize;
2047 char texturename[MAX_QPATH];
2050 // a note: decals need to modulate (multiply) the background color to
2051 // properly darken it (stain), and they need to be able to alpha fade,
2052 // this is a very difficult challenge because it means fading to white
2053 // (no change to background) rather than black (darkening everything
2054 // behind the whole decal polygon), and to accomplish this the texture is
2055 // inverted (dark red blood on white background becomes brilliant cyan
2056 // and white on black background) so we can alpha fade it to black, then
2057 // we invert it again during the blendfunc to make it work...
2059 #ifndef DUMPPARTICLEFONT
2060 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2063 particlefonttexture = decalskinframe->base;
2064 // TODO maybe allow custom grid size?
2065 particlefontwidth = image_width;
2066 particlefontheight = image_height;
2067 particlefontcellwidth = image_width / 8;
2068 particlefontcellheight = image_height / 8;
2069 particlefontcols = 8;
2070 particlefontrows = 8;
2075 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2076 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2077 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2078 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2079 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2081 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2082 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2083 particlefontcols = 8;
2084 particlefontrows = 8;
2086 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2089 for (i = 0;i < 8;i++)
2091 memset(data, 255, datasize);
2094 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2095 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2097 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2099 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2100 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2102 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2103 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2105 d = (int)(d * (1-(dx*dx+dy*dy)));
2106 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2107 d = bound(0, d, 255);
2108 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2115 setuptex(tex_smoke[i], data, particletexturedata);
2119 memset(data, 255, datasize);
2120 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2122 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2123 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2125 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2126 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2127 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2130 setuptex(tex_rainsplash, data, particletexturedata);
2133 memset(data, 255, datasize);
2134 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2136 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2137 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2139 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2140 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2141 d = bound(0, d, 255);
2142 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2145 setuptex(tex_particle, data, particletexturedata);
2148 memset(data, 255, datasize);
2149 light[0] = 1;light[1] = 1;light[2] = 1;
2150 VectorNormalize(light);
2151 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2153 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2154 // stretch upper half of bubble by +50% and shrink lower half by -50%
2155 // (this gives an elongated teardrop shape)
2157 dy = (dy - 0.5f) * 2.0f;
2159 dy = (dy - 0.5f) / 1.5f;
2160 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2162 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2163 // shrink bubble width to half
2165 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2168 setuptex(tex_raindrop, data, particletexturedata);
2171 memset(data, 255, datasize);
2172 light[0] = 1;light[1] = 1;light[2] = 1;
2173 VectorNormalize(light);
2174 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2176 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2177 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2179 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2180 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2183 setuptex(tex_bubble, data, particletexturedata);
2185 // Blood particles and blood decals
2186 R_InitBloodTextures (particletexturedata);
2189 for (i = 0;i < 8;i++)
2191 memset(data, 255, datasize);
2192 for (k = 0;k < 12;k++)
2193 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2194 for (k = 0;k < 3;k++)
2195 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2196 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2197 particletextureinvert(data);
2198 setuptex(tex_bulletdecal[i], data, particletexturedata);
2201 #ifdef DUMPPARTICLEFONT
2202 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2205 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2206 particlefonttexture = decalskinframe->base;
2208 Mem_Free(particletexturedata);
2213 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2215 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2216 particletexture[i].texture = particlefonttexture;
2217 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2218 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2219 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2220 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2223 #ifndef DUMPPARTICLEFONT
2224 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2225 if (!particletexture[tex_beam].texture)
2228 unsigned char noise3[64][64], data2[64][16][4];
2230 fractalnoise(&noise3[0][0], 64, 4);
2232 for (y = 0;y < 64;y++)
2234 dy = (y - 0.5f*64) / (64*0.5f-1);
2235 for (x = 0;x < 16;x++)
2237 dx = (x - 0.5f*16) / (16*0.5f-2);
2238 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2239 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2240 data2[y][x][3] = 255;
2244 #ifdef DUMPPARTICLEFONT
2245 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2247 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2249 particletexture[tex_beam].s1 = 0;
2250 particletexture[tex_beam].t1 = 0;
2251 particletexture[tex_beam].s2 = 1;
2252 particletexture[tex_beam].t2 = 1;
2254 // now load an texcoord/texture override file
2255 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2262 if(!COM_ParseToken_Simple(&bufptr, true, false))
2264 if(!strcmp(com_token, "\n"))
2265 continue; // empty line
2266 i = atoi(com_token);
2274 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2276 strlcpy(texturename, com_token, sizeof(texturename));
2277 s1 = atof(com_token);
2278 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2281 t1 = atof(com_token);
2282 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2284 s2 = atof(com_token);
2285 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2287 t2 = atof(com_token);
2288 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2289 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2290 strlcpy(texturename, com_token, sizeof(texturename));
2297 if (!texturename[0])
2299 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2302 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2304 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2307 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
2310 // R_SkinFrame_LoadExternal already complained
2313 particletexture[i].texture = sf->base;
2314 particletexture[i].s1 = s1;
2315 particletexture[i].t1 = t1;
2316 particletexture[i].s2 = s2;
2317 particletexture[i].t2 = t2;
2323 static void r_part_start(void)
2326 // generate particlepalette for convenience from the main one
2327 for (i = 0;i < 256;i++)
2328 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2329 particletexturepool = R_AllocTexturePool();
2330 R_InitParticleTexture ();
2331 CL_Particles_LoadEffectInfo();
2334 static void r_part_shutdown(void)
2336 R_FreeTexturePool(&particletexturepool);
2339 static void r_part_newmap(void)
2342 R_SkinFrame_MarkUsed(decalskinframe);
2343 CL_Particles_LoadEffectInfo();
2346 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2347 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2349 void R_Particles_Init (void)
2352 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2354 particle_elements[i*6+0] = i*4+0;
2355 particle_elements[i*6+1] = i*4+1;
2356 particle_elements[i*6+2] = i*4+2;
2357 particle_elements[i*6+3] = i*4+0;
2358 particle_elements[i*6+4] = i*4+2;
2359 particle_elements[i*6+5] = i*4+3;
2362 Cvar_RegisterVariable(&r_drawparticles);
2363 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2364 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2365 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2366 Cvar_RegisterVariable(&r_drawdecals);
2367 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2368 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2371 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2373 int surfacelistindex;
2375 float *v3f, *t2f, *c4f;
2376 particletexture_t *tex;
2377 float right[3], up[3], size, ca;
2378 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2380 RSurf_ActiveWorldEntity();
2382 r_refdef.stats.drawndecals += numsurfaces;
2383 // R_Mesh_ResetTextureState();
2384 GL_DepthMask(false);
2385 GL_DepthRange(0, 1);
2386 GL_PolygonOffset(0, 0);
2388 GL_CullFace(GL_NONE);
2390 // generate all the vertices at once
2391 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2393 d = cl.decals + surfacelist[surfacelistindex];
2396 c4f = particle_color4f + 16*surfacelistindex;
2397 ca = d->alpha * alphascale;
2398 // ensure alpha multiplier saturates properly
2399 if (ca > 1.0f / 256.0f)
2401 if (r_refdef.fogenabled)
2402 ca *= RSurf_FogVertex(d->org);
2403 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2404 Vector4Copy(c4f, c4f + 4);
2405 Vector4Copy(c4f, c4f + 8);
2406 Vector4Copy(c4f, c4f + 12);
2408 // calculate vertex positions
2409 size = d->size * cl_particles_size.value;
2410 VectorVectors(d->normal, right, up);
2411 VectorScale(right, size, right);
2412 VectorScale(up, size, up);
2413 v3f = particle_vertex3f + 12*surfacelistindex;
2414 v3f[ 0] = d->org[0] - right[0] - up[0];
2415 v3f[ 1] = d->org[1] - right[1] - up[1];
2416 v3f[ 2] = d->org[2] - right[2] - up[2];
2417 v3f[ 3] = d->org[0] - right[0] + up[0];
2418 v3f[ 4] = d->org[1] - right[1] + up[1];
2419 v3f[ 5] = d->org[2] - right[2] + up[2];
2420 v3f[ 6] = d->org[0] + right[0] + up[0];
2421 v3f[ 7] = d->org[1] + right[1] + up[1];
2422 v3f[ 8] = d->org[2] + right[2] + up[2];
2423 v3f[ 9] = d->org[0] + right[0] - up[0];
2424 v3f[10] = d->org[1] + right[1] - up[1];
2425 v3f[11] = d->org[2] + right[2] - up[2];
2427 // calculate texcoords
2428 tex = &particletexture[d->texnum];
2429 t2f = particle_texcoord2f + 8*surfacelistindex;
2430 t2f[0] = tex->s1;t2f[1] = tex->t2;
2431 t2f[2] = tex->s1;t2f[3] = tex->t1;
2432 t2f[4] = tex->s2;t2f[5] = tex->t1;
2433 t2f[6] = tex->s2;t2f[7] = tex->t2;
2436 // now render the decals all at once
2437 // (this assumes they all use one particle font texture!)
2438 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2439 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false);
2440 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2441 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2444 void R_DrawDecals (void)
2447 int drawdecals = r_drawdecals.integer;
2452 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2454 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2455 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2457 // LordHavoc: early out conditions
2461 decalfade = frametime * 256 / cl_decals_fadetime.value;
2462 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2463 drawdist2 = drawdist2*drawdist2;
2465 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2467 if (!decal->typeindex)
2470 if (killsequence - decal->decalsequence > 0)
2473 if (cl.time > decal->time2 + cl_decals_time.value)
2475 decal->alpha -= decalfade;
2476 if (decal->alpha <= 0)
2482 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2484 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2485 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2491 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2497 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))
2498 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2501 decal->typeindex = 0;
2502 if (cl.free_decal > i)
2506 // reduce cl.num_decals if possible
2507 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2510 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2512 decal_t *olddecals = cl.decals;
2513 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2514 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2515 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2516 Mem_Free(olddecals);
2519 r_refdef.stats.totaldecals = cl.num_decals;
2522 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2524 int surfacelistindex;
2525 int batchstart, batchcount;
2526 const particle_t *p;
2528 rtexture_t *texture;
2529 float *v3f, *t2f, *c4f;
2530 particletexture_t *tex;
2531 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2532 // float ambient[3], diffuse[3], diffusenormal[3];
2533 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2534 vec4_t colormultiplier;
2535 float minparticledist_start, minparticledist_end;
2538 RSurf_ActiveWorldEntity();
2540 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));
2542 r_refdef.stats.particles += numsurfaces;
2543 // R_Mesh_ResetTextureState();
2544 GL_DepthMask(false);
2545 GL_DepthRange(0, 1);
2546 GL_PolygonOffset(0, 0);
2548 GL_CullFace(GL_NONE);
2550 spintime = r_refdef.scene.time;
2552 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2553 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2554 dofade = (minparticledist_start < minparticledist_end);
2556 // first generate all the vertices at once
2557 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2559 p = cl.particles + surfacelist[surfacelistindex];
2561 blendmode = (pblend_t)p->blendmode;
2563 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2564 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2565 alpha = palpha * colormultiplier[3];
2566 // ensure alpha multiplier saturates properly
2572 case PBLEND_INVALID:
2574 // additive and modulate can just fade out in fog (this is correct)
2575 if (r_refdef.fogenabled)
2576 alpha *= RSurf_FogVertex(p->org);
2577 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2578 alpha *= 1.0f / 256.0f;
2579 c4f[0] = p->color[0] * alpha;
2580 c4f[1] = p->color[1] * alpha;
2581 c4f[2] = p->color[2] * alpha;
2585 // additive and modulate can just fade out in fog (this is correct)
2586 if (r_refdef.fogenabled)
2587 alpha *= RSurf_FogVertex(p->org);
2588 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2589 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2590 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2591 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2595 c4f[0] = p->color[0] * colormultiplier[0];
2596 c4f[1] = p->color[1] * colormultiplier[1];
2597 c4f[2] = p->color[2] * colormultiplier[2];
2599 // note: lighting is not cheap!
2600 if (particletype[p->typeindex].lighting)
2601 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2602 // mix in the fog color
2603 if (r_refdef.fogenabled)
2605 fog = RSurf_FogVertex(p->org);
2607 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2608 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2609 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2611 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2612 VectorScale(c4f, alpha, c4f);
2615 // copy the color into the other three vertices
2616 Vector4Copy(c4f, c4f + 4);
2617 Vector4Copy(c4f, c4f + 8);
2618 Vector4Copy(c4f, c4f + 12);
2620 size = p->size * cl_particles_size.value;
2621 tex = &particletexture[p->texnum];
2622 switch(p->orientation)
2624 // case PARTICLE_INVALID:
2625 case PARTICLE_BILLBOARD:
2626 if (p->angle + p->spin)
2628 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2629 spinsin = sin(spinrad) * size;
2630 spincos = cos(spinrad) * size;
2631 spinm1 = -p->stretch * spincos;
2634 spinm4 = -p->stretch * spincos;
2635 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2636 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2640 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2641 VectorScale(r_refdef.view.up, size, up);
2644 v3f[ 0] = p->org[0] - right[0] - up[0];
2645 v3f[ 1] = p->org[1] - right[1] - up[1];
2646 v3f[ 2] = p->org[2] - right[2] - up[2];
2647 v3f[ 3] = p->org[0] - right[0] + up[0];
2648 v3f[ 4] = p->org[1] - right[1] + up[1];
2649 v3f[ 5] = p->org[2] - right[2] + up[2];
2650 v3f[ 6] = p->org[0] + right[0] + up[0];
2651 v3f[ 7] = p->org[1] + right[1] + up[1];
2652 v3f[ 8] = p->org[2] + right[2] + up[2];
2653 v3f[ 9] = p->org[0] + right[0] - up[0];
2654 v3f[10] = p->org[1] + right[1] - up[1];
2655 v3f[11] = p->org[2] + right[2] - up[2];
2656 t2f[0] = tex->s1;t2f[1] = tex->t2;
2657 t2f[2] = tex->s1;t2f[3] = tex->t1;
2658 t2f[4] = tex->s2;t2f[5] = tex->t1;
2659 t2f[6] = tex->s2;t2f[7] = tex->t2;
2661 case PARTICLE_ORIENTED_DOUBLESIDED:
2662 VectorVectors(p->vel, baseright, baseup);
2663 if (p->angle + p->spin)
2665 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2666 spinsin = sin(spinrad) * size;
2667 spincos = cos(spinrad) * size;
2668 spinm1 = p->stretch * spincos;
2671 spinm4 = p->stretch * spincos;
2672 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2673 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2677 VectorScale(baseright, size * p->stretch, right);
2678 VectorScale(baseup, size, up);
2680 v3f[ 0] = p->org[0] - right[0] - up[0];
2681 v3f[ 1] = p->org[1] - right[1] - up[1];
2682 v3f[ 2] = p->org[2] - right[2] - up[2];
2683 v3f[ 3] = p->org[0] - right[0] + up[0];
2684 v3f[ 4] = p->org[1] - right[1] + up[1];
2685 v3f[ 5] = p->org[2] - right[2] + up[2];
2686 v3f[ 6] = p->org[0] + right[0] + up[0];
2687 v3f[ 7] = p->org[1] + right[1] + up[1];
2688 v3f[ 8] = p->org[2] + right[2] + up[2];
2689 v3f[ 9] = p->org[0] + right[0] - up[0];
2690 v3f[10] = p->org[1] + right[1] - up[1];
2691 v3f[11] = p->org[2] + right[2] - up[2];
2692 t2f[0] = tex->s1;t2f[1] = tex->t2;
2693 t2f[2] = tex->s1;t2f[3] = tex->t1;
2694 t2f[4] = tex->s2;t2f[5] = tex->t1;
2695 t2f[6] = tex->s2;t2f[7] = tex->t2;
2697 case PARTICLE_SPARK:
2698 len = VectorLength(p->vel);
2699 VectorNormalize2(p->vel, up);
2700 lenfactor = p->stretch * 0.04 * len;
2701 if(lenfactor < size * 0.5)
2702 lenfactor = size * 0.5;
2703 VectorMA(p->org, -lenfactor, up, v);
2704 VectorMA(p->org, lenfactor, up, up2);
2705 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2706 t2f[0] = tex->s1;t2f[1] = tex->t2;
2707 t2f[2] = tex->s1;t2f[3] = tex->t1;
2708 t2f[4] = tex->s2;t2f[5] = tex->t1;
2709 t2f[6] = tex->s2;t2f[7] = tex->t2;
2711 case PARTICLE_VBEAM:
2712 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2713 VectorSubtract(p->vel, p->org, up);
2714 VectorNormalize(up);
2715 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2716 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2717 t2f[0] = tex->s2;t2f[1] = v[0];
2718 t2f[2] = tex->s1;t2f[3] = v[0];
2719 t2f[4] = tex->s1;t2f[5] = v[1];
2720 t2f[6] = tex->s2;t2f[7] = v[1];
2722 case PARTICLE_HBEAM:
2723 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2724 VectorSubtract(p->vel, p->org, up);
2725 VectorNormalize(up);
2726 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2727 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2728 t2f[0] = v[0];t2f[1] = tex->t1;
2729 t2f[2] = v[0];t2f[3] = tex->t2;
2730 t2f[4] = v[1];t2f[5] = tex->t2;
2731 t2f[6] = v[1];t2f[7] = tex->t1;
2736 // now render batches of particles based on blendmode and texture
2737 blendmode = PBLEND_INVALID;
2741 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2742 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2744 p = cl.particles + surfacelist[surfacelistindex];
2746 if (texture != particletexture[p->texnum].texture)
2748 texture = particletexture[p->texnum].texture;
2749 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false);
2752 if (p->blendmode == PBLEND_INVMOD)
2754 // inverse modulate blend - group these
2755 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2756 // iterate until we find a change in settings
2757 batchstart = surfacelistindex++;
2758 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2760 p = cl.particles + surfacelist[surfacelistindex];
2761 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2767 // additive or alpha blend - group these
2768 // (we can group these because we premultiplied the texture alpha)
2769 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2770 // iterate until we find a change in settings
2771 batchstart = surfacelistindex++;
2772 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2774 p = cl.particles + surfacelist[surfacelistindex];
2775 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2780 batchcount = surfacelistindex - batchstart;
2781 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2785 void R_DrawParticles (void)
2788 int drawparticles = r_drawparticles.integer;
2789 float minparticledist_start;
2791 float gravity, frametime, f, dist, oldorg[3];
2797 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2798 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2800 // LordHavoc: early out conditions
2801 if (!cl.num_particles)
2804 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2805 gravity = frametime * cl.movevars_gravity;
2806 update = frametime > 0;
2807 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2808 drawdist2 = drawdist2*drawdist2;
2810 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2814 if (cl.free_particle > i)
2815 cl.free_particle = i;
2821 if (p->delayedspawn > cl.time)
2824 p->size += p->sizeincrease * frametime;
2825 p->alpha -= p->alphafade * frametime;
2827 if (p->alpha <= 0 || p->die <= cl.time)
2830 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2832 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2834 if (p->typeindex == pt_blood)
2835 p->size += frametime * 8;
2837 p->vel[2] -= p->gravity * gravity;
2838 f = 1.0f - min(p->liquidfriction * frametime, 1);
2839 VectorScale(p->vel, f, p->vel);
2843 p->vel[2] -= p->gravity * gravity;
2846 f = 1.0f - min(p->airfriction * frametime, 1);
2847 VectorScale(p->vel, f, p->vel);
2851 VectorCopy(p->org, oldorg);
2852 VectorMA(p->org, frametime, p->vel, p->org);
2853 // if (p->bounce && cl.time >= p->delayedcollisions)
2854 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2856 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);
2857 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2858 // or if the trace hit something flagged as NOIMPACT
2859 // then remove the particle
2860 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2862 VectorCopy(trace.endpos, p->org);
2863 // react if the particle hit something
2864 if (trace.fraction < 1)
2866 VectorCopy(trace.endpos, p->org);
2868 if (p->staintexnum >= 0)
2870 // blood - splash on solid
2871 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2874 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2875 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2876 if (cl_decals.integer)
2878 // create a decal for the blood splat
2879 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2880 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2885 if (p->typeindex == pt_blood)
2887 // blood - splash on solid
2888 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2890 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2892 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)));
2893 if (cl_decals.integer)
2895 // create a decal for the blood splat
2896 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2901 else if (p->bounce < 0)
2903 // bounce -1 means remove on impact
2908 // anything else - bounce off solid
2909 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2910 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2915 if (VectorLength2(p->vel) < 0.03)
2917 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2919 VectorClear(p->vel);
2923 if (p->typeindex != pt_static)
2925 switch (p->typeindex)
2927 case pt_entityparticle:
2928 // particle that removes itself after one rendered frame
2935 a = CL_PointSuperContents(p->org);
2936 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2940 a = CL_PointSuperContents(p->org);
2941 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2945 a = CL_PointSuperContents(p->org);
2946 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2950 if (cl.time > p->time2)
2953 p->time2 = cl.time + (rand() & 3) * 0.1;
2954 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2955 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2957 a = CL_PointSuperContents(p->org);
2958 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2966 else if (p->delayedspawn > cl.time)
2970 // don't render particles too close to the view (they chew fillrate)
2971 // also don't render particles behind the view (useless)
2972 // further checks to cull to the frustum would be too slow here
2973 switch(p->typeindex)
2976 // beams have no culling
2977 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2980 if(cl_particles_visculling.integer)
2981 if (!r_refdef.viewcache.world_novis)
2982 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2984 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2986 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2989 // anything else just has to be in front of the viewer and visible at this distance
2990 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2991 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2998 if (cl.free_particle > i)
2999 cl.free_particle = i;
3002 // reduce cl.num_particles if possible
3003 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3006 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3008 particle_t *oldparticles = cl.particles;
3009 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3010 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3011 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3012 Mem_Free(oldparticles);