2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float relativeoriginoffset[3];
105 float velocityoffset[3];
106 float relativevelocityoffset[3];
107 float originjitter[3];
108 float velocityjitter[3];
109 float velocitymultiplier;
110 // an effect can also spawn a dlight
111 float lightradiusstart;
112 float lightradiusfade;
115 qboolean lightshadow;
117 float lightcorona[2];
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
123 float rotate[4]; // min/max base angle, min/max rotation over time
125 particleeffectinfo_t;
127 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
129 int numparticleeffectinfo;
130 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
132 static int particlepalette[256];
134 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
135 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
136 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
137 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
138 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
139 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
140 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
141 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
142 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
143 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
144 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
145 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
146 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
147 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
148 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
149 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
150 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
151 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
152 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
153 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
154 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
155 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
156 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
157 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
158 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
159 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
160 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
161 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
162 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
163 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
164 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
165 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
168 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
169 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
170 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
172 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
174 // particletexture_t is a rectangle in the particlefonttexture
175 typedef struct particletexture_s
178 float s1, t1, s2, t2;
182 static rtexturepool_t *particletexturepool;
183 static rtexture_t *particlefonttexture;
184 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 skinframe_t *decalskinframe;
187 // texture numbers in particle font
188 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
189 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
190 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
191 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
192 static const int tex_rainsplash = 32;
193 static const int tex_particle = 63;
194 static const int tex_bubble = 62;
195 static const int tex_raindrop = 61;
196 static const int tex_beam = 60;
198 particleeffectinfo_t baselineparticleeffectinfo =
200 0, //int effectnameindex; // which effect this belongs to
201 // PARTICLEEFFECT_* bits
203 // blood effects may spawn very few particles, so proper fraction-overflow
204 // handling is very important, this variable keeps track of the fraction
205 0.0, //double particleaccumulator;
206 // the math is: countabsolute + requestedcount * countmultiplier * quality
207 // absolute number of particles to spawn, often used for decals
208 // (unaffected by quality and requestedcount)
209 0.0f, //float countabsolute;
210 // multiplier for the number of particles CL_ParticleEffect was told to
211 // spawn, most effects do not really have a count and hence use 1, so
212 // this is often the actual count to spawn, not merely a multiplier
213 0.0f, //float countmultiplier;
214 // if > 0 this causes the particle to spawn in an evenly spaced line from
215 // originmins to originmaxs (causing them to describe a trail, not a box)
216 0.0f, //float trailspacing;
217 // type of particle to spawn (defines some aspects of behavior)
218 pt_alphastatic, //ptype_t particletype;
219 // blending mode used on this particle type
220 PBLEND_ALPHA, //pblend_t blendmode;
221 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
222 PARTICLE_BILLBOARD, //porientation_t orientation;
223 // range of colors to choose from in hex RRGGBB (like HTML color tags),
224 // randomly interpolated at spawn
225 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
226 // a random texture is chosen in this range (note the second value is one
227 // past the last choosable, so for example 8,16 chooses any from 8 up and
229 // if start and end of the range are the same, no randomization is done
230 {63, 63 /* tex_particle */}, //int tex[2];
231 // range of size values randomly chosen when spawning, plus size increase over time
232 {1, 1, 0.0f}, //float size[3];
233 // range of alpha values randomly chosen when spawning, plus alpha fade
234 {0.0f, 256.0f, 256.0f}, //float alpha[3];
235 // how long the particle should live (note it is also removed if alpha drops to 0)
236 {16777216.0f, 16777216.0f}, //float time[2];
237 // how much gravity affects this particle (negative makes it fly up!)
238 0.0f, //float gravity;
239 // how much bounce the particle has when it hits a surface
240 // if negative the particle is removed on impact
241 0.0f, //float bounce;
242 // if in air this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float airfriction;
245 // if in liquid (water/slime/lava) this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float liquidfriction;
248 // these offsets are added to the values given to particleeffect(), and
249 // then an ellipsoid-shaped jitter is added as defined by these
250 // (they are the 3 radii)
251 1.0f, //float stretchfactor;
252 // stretch velocity factor (used for sparks)
253 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
259 0.0f, //float velocitymultiplier;
260 // an effect can also spawn a dlight
261 0.0f, //float lightradiusstart;
262 0.0f, //float lightradiusfade;
263 16777216.0f, //float lighttime;
264 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
265 true, //qboolean lightshadow;
266 0, //int lightcubemapnum;
267 {1.0f, 0.25f}, //float lightcorona[2];
268 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
269 {-1, -1}, //int staintex[2];
270 {1.0f, 1.0f}, //float stainalpha[2];
271 {2.0f, 2.0f}, //float stainsize[2];
273 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
276 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
277 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
278 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
279 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
280 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
281 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
282 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
283 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
284 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
285 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
286 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
287 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
288 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
289 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
290 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
291 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
292 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
293 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
294 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
295 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
296 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
297 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
298 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
299 cvar_t cl_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
300 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
301 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
302 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
303 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
304 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
305 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
306 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
307 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
308 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
309 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
310 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
313 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
318 particleeffectinfo_t *info = NULL;
319 const char *text = textstart;
321 for (linenumber = 1;;linenumber++)
324 for (arrayindex = 0;arrayindex < 16;arrayindex++)
325 argv[arrayindex][0] = 0;
328 if (!COM_ParseToken_Simple(&text, true, false, true))
330 if (!strcmp(com_token, "\n"))
334 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
340 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
341 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
342 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
343 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
344 #define readfloat(var) checkparms(2);var = atof(argv[1])
345 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
346 if (!strcmp(argv[0], "effect"))
350 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
352 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
355 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
357 if (particleeffectname[effectnameindex][0])
359 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
364 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
368 // if we run out of names, abort
369 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
371 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
374 info = particleeffectinfo + numparticleeffectinfo++;
375 // copy entire info from baseline, then fix up the nameindex
376 *info = baselineparticleeffectinfo;
377 info->effectnameindex = effectnameindex;
379 else if (info == NULL)
381 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
384 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
385 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
386 else if (!strcmp(argv[0], "type"))
389 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
390 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
391 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
392 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
393 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
394 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
395 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
396 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
397 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
398 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
399 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
400 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
401 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
402 info->blendmode = particletype[info->particletype].blendmode;
403 info->orientation = particletype[info->particletype].orientation;
405 else if (!strcmp(argv[0], "blend"))
408 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
409 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
410 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
411 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
413 else if (!strcmp(argv[0], "orientation"))
416 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
417 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
418 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
419 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
420 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
422 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
423 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
424 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
425 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
426 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
427 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
428 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
429 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
430 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
431 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
432 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
433 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
434 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
435 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
436 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
437 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
438 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
439 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
440 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
441 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
442 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
443 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
444 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
445 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
446 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
447 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
448 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
449 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
450 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
451 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
452 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
453 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
454 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
455 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
457 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
466 int CL_ParticleEffectIndexForName(const char *name)
469 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
470 if (!strcmp(particleeffectname[i], name))
475 const char *CL_ParticleEffectNameForIndex(int i)
477 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
479 return particleeffectname[i];
482 // MUST match effectnameindex_t in client.h
483 static const char *standardeffectnames[EFFECT_TOTAL] =
507 "TE_TEI_BIGEXPLOSION",
523 static void CL_Particles_LoadEffectInfo(const char *customfile)
527 unsigned char *filedata;
528 fs_offset_t filesize;
529 char filename[MAX_QPATH];
530 numparticleeffectinfo = 0;
531 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
532 memset(particleeffectname, 0, sizeof(particleeffectname));
533 for (i = 0;i < EFFECT_TOTAL;i++)
534 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
535 for (filepass = 0;;filepass++)
540 strlcpy(filename, customfile, sizeof(filename));
542 strlcpy(filename, "effectinfo.txt", sizeof(filename));
544 else if (filepass == 1)
546 if (!cl.worldbasename[0] || customfile)
548 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
552 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
555 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
560 static void CL_Particles_LoadEffectInfo_f(void)
562 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
570 void CL_ReadPointFile_f (void);
571 void CL_Particles_Init (void)
573 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
574 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
576 Cvar_RegisterVariable (&cl_particles);
577 Cvar_RegisterVariable (&cl_particles_quality);
578 Cvar_RegisterVariable (&cl_particles_alpha);
579 Cvar_RegisterVariable (&cl_particles_size);
580 Cvar_RegisterVariable (&cl_particles_quake);
581 Cvar_RegisterVariable (&cl_particles_blood);
582 Cvar_RegisterVariable (&cl_particles_blood_alpha);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
584 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
585 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
586 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
587 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
588 Cvar_RegisterVariable (&cl_particles_explosions_shell);
589 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
590 Cvar_RegisterVariable (&cl_particles_rain);
591 Cvar_RegisterVariable (&cl_particles_snow);
592 Cvar_RegisterVariable (&cl_particles_smoke);
593 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
594 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
595 Cvar_RegisterVariable (&cl_particles_sparks);
596 Cvar_RegisterVariable (&cl_particles_bubbles);
597 Cvar_RegisterVariable (&cl_particles_visculling);
598 Cvar_RegisterVariable (&cl_particles_collisions);
599 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
600 Cvar_RegisterVariable (&cl_decals);
601 Cvar_RegisterVariable (&cl_decals_visculling);
602 Cvar_RegisterVariable (&cl_decals_time);
603 Cvar_RegisterVariable (&cl_decals_fadetime);
604 Cvar_RegisterVariable (&cl_decals_newsystem);
605 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
606 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
607 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
608 Cvar_RegisterVariable (&cl_decals_models);
609 Cvar_RegisterVariable (&cl_decals_bias);
610 Cvar_RegisterVariable (&cl_decals_max);
613 void CL_Particles_Shutdown (void)
617 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
618 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
620 // list of all 26 parameters:
621 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
622 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
623 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
624 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
625 // palpha - opacity of particle as 0-255 (can be more than 255)
626 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
627 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
628 // pgravity - how much effect gravity has on the particle (0-1)
629 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
630 // px,py,pz - starting origin of particle
631 // pvx,pvy,pvz - starting velocity of particle
632 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
633 // blendmode - one of the PBLEND_ values
634 // orientation - one of the PARTICLE_ values
635 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
636 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
637 // stainalpha: opacity of the stain as factor for alpha
638 // stainsize: size of the stain as factor for palpha
639 // angle: base rotation of the particle geometry around its center normal
640 // spin: rotation speed of the particle geometry around its center normal
641 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
646 if (!cl_particles.integer)
648 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
649 if (cl.free_particle >= cl.max_particles)
652 lifetime = palpha / min(1, palphafade);
653 part = &cl.particles[cl.free_particle++];
654 if (cl.num_particles < cl.free_particle)
655 cl.num_particles = cl.free_particle;
656 memset(part, 0, sizeof(*part));
657 VectorCopy(sortorigin, part->sortorigin);
658 part->typeindex = ptypeindex;
659 part->blendmode = blendmode;
660 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
662 particletexture_t *tex = &particletexture[ptex];
663 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
664 part->orientation = PARTICLE_VBEAM;
666 part->orientation = PARTICLE_HBEAM;
669 part->orientation = orientation;
670 l2 = (int)lhrandom(0.5, 256.5);
672 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
673 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
674 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
677 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
678 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
679 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
681 part->alpha = palpha;
682 part->alphafade = palphafade;
683 part->staintexnum = staintex;
684 if(staincolor1 >= 0 && staincolor2 >= 0)
686 l2 = (int)lhrandom(0.5, 256.5);
688 if(blendmode == PBLEND_INVMOD)
690 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
691 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
692 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
696 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
697 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
698 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
700 if(r > 0xFF) r = 0xFF;
701 if(g > 0xFF) g = 0xFF;
702 if(b > 0xFF) b = 0xFF;
706 r = part->color[0]; // -1 is shorthand for stain = particle color
710 part->staincolor[0] = r;
711 part->staincolor[1] = g;
712 part->staincolor[2] = b;
713 part->stainalpha = palpha * stainalpha;
714 part->stainsize = psize * stainsize;
717 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
719 part->color[0] *= tint[0];
720 part->color[1] *= tint[1];
721 part->color[2] *= tint[2];
723 part->alpha *= tint[3];
724 part->alphafade *= tint[3];
725 part->stainalpha *= tint[3];
729 part->sizeincrease = psizeincrease;
730 part->gravity = pgravity;
731 part->bounce = pbounce;
732 part->stretch = stretch;
734 part->org[0] = px + originjitter * v[0];
735 part->org[1] = py + originjitter * v[1];
736 part->org[2] = pz + originjitter * v[2];
737 part->vel[0] = pvx + velocityjitter * v[0];
738 part->vel[1] = pvy + velocityjitter * v[1];
739 part->vel[2] = pvz + velocityjitter * v[2];
741 part->airfriction = pairfriction;
742 part->liquidfriction = pliquidfriction;
743 part->die = cl.time + lifetime;
744 part->delayedspawn = cl.time;
745 // part->delayedcollisions = 0;
746 part->qualityreduction = pqualityreduction;
749 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
750 if (part->typeindex == pt_rain)
754 float lifetime = part->die - cl.time;
757 // turn raindrop into simple spark and create delayedspawn splash effect
758 part->typeindex = pt_spark;
760 VectorMA(part->org, lifetime, part->vel, endvec);
761 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
762 part->die = cl.time + lifetime * trace.fraction;
763 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
766 part2->delayedspawn = part->die;
767 part2->die += part->die - cl.time;
768 for (i = rand() & 7;i < 10;i++)
770 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
773 part2->delayedspawn = part->die;
774 part2->die += part->die - cl.time;
780 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
782 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
785 VectorMA(part->org, lifetime, part->vel, endvec);
786 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
787 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
794 static void CL_ImmediateBloodStain(particle_t *part)
799 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
800 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
802 VectorCopy(part->vel, v);
804 staintex = part->staintexnum;
805 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
808 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
809 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
811 VectorCopy(part->vel, v);
813 staintex = tex_blooddecal[rand()&7];
814 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
818 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
822 entity_render_t *ent = &cl.entities[hitent].render;
823 unsigned char color[3];
824 if (!cl_decals.integer)
826 if (!ent->allowdecals)
829 l2 = (int)lhrandom(0.5, 256.5);
831 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
832 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
833 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
835 if (cl_decals_newsystem.integer)
838 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
840 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
844 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
845 if (cl.free_decal >= cl.max_decals)
847 decal = &cl.decals[cl.free_decal++];
848 if (cl.num_decals < cl.free_decal)
849 cl.num_decals = cl.free_decal;
850 memset(decal, 0, sizeof(*decal));
851 decal->decalsequence = cl.decalsequence++;
852 decal->typeindex = pt_decal;
853 decal->texnum = texnum;
854 VectorMA(org, cl_decals_bias.value, normal, decal->org);
855 VectorCopy(normal, decal->normal);
857 decal->alpha = alpha;
858 decal->time2 = cl.time;
859 decal->color[0] = color[0];
860 decal->color[1] = color[1];
861 decal->color[2] = color[2];
864 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
865 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
866 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
868 decal->owner = hitent;
869 decal->clusterindex = -1000; // no vis culling unless we're sure
872 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
873 decal->ownermodel = cl.entities[decal->owner].render.model;
874 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
875 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
879 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
881 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
883 decal->clusterindex = leaf->clusterindex;
888 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
895 int besthitent = 0, hitent;
898 for (i = 0;i < 32;i++)
901 VectorMA(org, maxdist, org2, org2);
902 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
903 // take the closest trace result that doesn't end up hitting a NOMARKS
904 // surface (sky for example)
905 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
907 bestfrac = trace.fraction;
909 VectorCopy(trace.endpos, bestorg);
910 VectorCopy(trace.plane.normal, bestnormal);
914 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
917 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
918 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
919 static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail);
920 static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, qboolean wanttrail)
923 matrix4x4_t tempmatrix;
926 VectorLerp(originmins, 0.5, originmaxs, center);
927 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
928 if (effectnameindex == EFFECT_SVC_PARTICLE)
930 if (cl_particles.integer)
932 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
934 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
935 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
936 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
939 count *= cl_particles_quality.value;
940 for (;count > 0;count--)
942 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
943 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);
948 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
949 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
950 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
951 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
952 else if (effectnameindex == EFFECT_TE_SPIKE)
954 if (cl_particles_bulletimpacts.integer)
956 if (cl_particles_quake.integer)
958 if (cl_particles_smoke.integer)
959 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
963 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
964 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
965 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);
969 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
970 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
972 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
974 if (cl_particles_bulletimpacts.integer)
976 if (cl_particles_quake.integer)
978 if (cl_particles_smoke.integer)
979 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
983 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
984 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
985 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);
989 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
990 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
991 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);
993 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
995 if (cl_particles_bulletimpacts.integer)
997 if (cl_particles_quake.integer)
999 if (cl_particles_smoke.integer)
1000 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1004 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1005 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1006 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);
1010 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1011 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1013 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1015 if (cl_particles_bulletimpacts.integer)
1017 if (cl_particles_quake.integer)
1019 if (cl_particles_smoke.integer)
1020 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1024 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1025 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1026 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);
1030 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1031 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1032 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);
1034 else if (effectnameindex == EFFECT_TE_BLOOD)
1036 if (!cl_particles_blood.integer)
1038 if (cl_particles_quake.integer)
1039 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1042 static double bloodaccumulator = 0;
1043 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1044 //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);
1045 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1046 for (;bloodaccumulator > 0;bloodaccumulator--)
1048 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);
1049 if (immediatebloodstain && part)
1051 immediatebloodstain = false;
1052 CL_ImmediateBloodStain(part);
1057 else if (effectnameindex == EFFECT_TE_SPARK)
1058 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1059 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1061 // plasma scorch mark
1062 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1063 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1064 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1066 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1068 if (cl_particles_bulletimpacts.integer)
1070 if (cl_particles_quake.integer)
1071 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1074 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1075 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1076 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);
1080 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1081 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1083 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1085 if (cl_particles_bulletimpacts.integer)
1087 if (cl_particles_quake.integer)
1088 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1091 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1092 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1093 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);
1097 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1098 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1099 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);
1101 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1103 CL_ParticleExplosion(center);
1104 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);
1106 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1108 CL_ParticleExplosion(center);
1109 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);
1111 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1113 if (cl_particles_quake.integer)
1116 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1119 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);
1121 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);
1125 CL_ParticleExplosion(center);
1126 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);
1128 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1129 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);
1130 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1132 count *= cl_particles_quality.value;
1134 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);
1136 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1138 float i, j, inc, vel;
1141 inc = 8 / cl_particles_quality.value;
1142 for (i = -128;i < 128;i += inc)
1144 for (j = -128;j < 128;j += inc)
1146 dir[0] = j + lhrandom(0, inc);
1147 dir[1] = i + lhrandom(0, inc);
1149 org[0] = center[0] + dir[0];
1150 org[1] = center[1] + dir[1];
1151 org[2] = center[2] + lhrandom(0, 64);
1152 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1153 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);
1157 else if (effectnameindex == EFFECT_TE_TELEPORT)
1159 float i, j, k, inc, vel;
1162 if (cl_particles_quake.integer)
1163 inc = 4 / cl_particles_quality.value;
1165 inc = 8 / cl_particles_quality.value;
1166 for (i = -16;i < 16;i += inc)
1168 for (j = -16;j < 16;j += inc)
1170 for (k = -24;k < 32;k += inc)
1172 VectorSet(dir, i*8, j*8, k*8);
1173 VectorNormalize(dir);
1174 vel = lhrandom(50, 113);
1175 if (cl_particles_quake.integer)
1176 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);
1178 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);
1182 if (!cl_particles_quake.integer)
1183 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);
1184 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);
1186 else if (effectnameindex == EFFECT_TE_TEI_G3)
1187 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);
1188 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1190 if (cl_particles_smoke.integer)
1192 count *= 0.25f * cl_particles_quality.value;
1194 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);
1197 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1199 CL_ParticleExplosion(center);
1200 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);
1202 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1205 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1206 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1207 if (cl_particles_smoke.integer)
1208 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1209 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);
1210 if (cl_particles_sparks.integer)
1211 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1212 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);
1213 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);
1215 else if (effectnameindex == EFFECT_EF_FLAME)
1217 if (!spawnparticles)
1219 count *= 300 * cl_particles_quality.value;
1221 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);
1222 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);
1224 else if (effectnameindex == EFFECT_EF_STARDUST)
1226 if (!spawnparticles)
1228 count *= 200 * cl_particles_quality.value;
1230 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);
1231 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);
1233 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1237 int smoke, blood, bubbles, r, color;
1239 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1242 Vector4Set(light, 0, 0, 0, 0);
1244 if (effectnameindex == EFFECT_TR_ROCKET)
1245 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1246 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1248 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1249 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1251 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1253 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1254 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1258 matrix4x4_t tempmatrix;
1259 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1260 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);
1261 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1265 if (!spawnparticles)
1268 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1271 VectorSubtract(originmaxs, originmins, dir);
1272 len = VectorNormalizeLength(dir);
1275 dec = -ent->persistent.trail_time;
1276 ent->persistent.trail_time += len;
1277 if (ent->persistent.trail_time < 0.01f)
1280 // if we skip out, leave it reset
1281 ent->persistent.trail_time = 0.0f;
1286 // advance into this frame to reach the first puff location
1287 VectorMA(originmins, dec, dir, pos);
1290 smoke = cl_particles.integer && cl_particles_smoke.integer;
1291 blood = cl_particles.integer && cl_particles_blood.integer;
1292 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1293 qd = 1.0f / cl_particles_quality.value;
1300 if (effectnameindex == EFFECT_TR_BLOOD)
1302 if (cl_particles_quake.integer)
1304 color = particlepalette[67 + (rand()&3)];
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, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1310 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);
1313 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1315 if (cl_particles_quake.integer)
1318 color = particlepalette[67 + (rand()&3)];
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, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1324 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);
1330 if (effectnameindex == EFFECT_TR_ROCKET)
1332 if (cl_particles_quake.integer)
1335 color = particlepalette[ramp3[r]];
1336 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);
1340 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);
1341 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);
1344 else if (effectnameindex == EFFECT_TR_GRENADE)
1346 if (cl_particles_quake.integer)
1349 color = particlepalette[ramp3[r]];
1350 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);
1354 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);
1357 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1359 if (cl_particles_quake.integer)
1362 color = particlepalette[52 + (rand()&7)];
1363 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1364 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);
1366 else if (gamemode == GAME_GOODVSBAD2)
1369 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);
1373 color = particlepalette[20 + (rand()&7)];
1374 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);
1377 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1379 if (cl_particles_quake.integer)
1382 color = particlepalette[230 + (rand()&7)];
1383 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1384 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);
1388 color = particlepalette[226 + (rand()&7)];
1389 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);
1392 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1394 if (cl_particles_quake.integer)
1396 color = particlepalette[152 + (rand()&3)];
1397 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);
1399 else if (gamemode == GAME_GOODVSBAD2)
1402 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);
1404 else if (gamemode == GAME_PRYDON)
1407 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);
1410 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);
1412 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1415 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);
1417 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1420 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);
1422 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1423 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);
1427 if (effectnameindex == EFFECT_TR_ROCKET)
1428 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1429 else if (effectnameindex == EFFECT_TR_GRENADE)
1430 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);
1432 // advance to next time and position
1435 VectorMA (pos, dec, dir, pos);
1438 ent->persistent.trail_time = len;
1441 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1444 // this is also called on point effects with spawndlight = true and
1445 // spawnparticles = true
1446 static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail)
1448 qboolean found = false;
1450 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1452 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1453 return; // no such effect
1455 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1457 int effectinfoindex;
1460 particleeffectinfo_t *info;
1472 qboolean underwater;
1473 qboolean immediatebloodstain;
1475 float avgtint[4], tint[4], tintlerp;
1476 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1477 VectorLerp(originmins, 0.5, originmaxs, center);
1478 supercontents = CL_PointSuperContents(center);
1479 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1480 VectorSubtract(originmaxs, originmins, traildir);
1481 traillen = VectorLength(traildir);
1482 VectorNormalize(traildir);
1485 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1489 Vector4Set(avgtint, 1, 1, 1, 1);
1491 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1493 if (info->effectnameindex == effectnameindex)
1495 qboolean definedastrail = info->trailspacing > 0;
1497 qboolean drawastrail = wanttrail;
1498 if (cl_particles_forcetraileffects.integer)
1499 drawastrail = drawastrail || definedastrail;
1502 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1504 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1507 // spawn a dlight if requested
1508 if (info->lightradiusstart > 0 && spawndlight)
1510 matrix4x4_t tempmatrix;
1512 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1514 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1515 if (info->lighttime > 0 && info->lightradiusfade > 0)
1517 // light flash (explosion, etc)
1518 // called when effect starts
1519 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1521 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1524 // called by CL_LinkNetworkEntity
1525 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1526 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1527 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1528 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1529 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1530 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1534 if (!spawnparticles)
1539 if (info->tex[1] > info->tex[0])
1541 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1542 tex = min(tex, info->tex[1] - 1);
1544 if(info->staintex[0] < 0)
1545 staintex = info->staintex[0];
1548 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1549 staintex = min(staintex, info->staintex[1] - 1);
1551 if (info->particletype == pt_decal)
1553 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1554 AnglesFromVectors(angles, velocity, NULL, false);
1555 AngleVectors(angles, forward, right, up);
1556 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1558 CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1560 else if (info->orientation == PARTICLE_HBEAM)
1565 AnglesFromVectors(angles, traildir, NULL, false);
1566 AngleVectors(angles, forward, right, up);
1567 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1569 CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
1574 if (!cl_particles.integer)
1576 switch (info->particletype)
1578 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1579 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1580 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1581 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1582 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1583 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1587 cnt = info->countabsolute;
1588 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1589 // if drawastrail is not set, we will
1590 // use the regular cnt-based random
1591 // particle spawning at the center; so
1592 // do NOT apply trailspacing then!
1593 if (drawastrail && definedastrail)
1594 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1597 continue; // nothing to draw
1598 info->particleaccumulator += cnt;
1600 if (drawastrail || definedastrail)
1601 immediatebloodstain = false;
1603 immediatebloodstain =
1604 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1606 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1610 VectorCopy(originmins, trailpos);
1611 trailstep = traillen / cnt;
1615 VectorCopy(center, trailpos);
1621 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1622 AnglesFromVectors(angles, velocity, NULL, false);
1625 AnglesFromVectors(angles, traildir, NULL, false);
1627 AngleVectors(angles, forward, right, up);
1628 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1629 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1630 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1631 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1633 if (info->tex[1] > info->tex[0])
1635 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1636 tex = min(tex, info->tex[1] - 1);
1638 if (!(drawastrail || definedastrail))
1640 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1641 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1642 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1646 tintlerp = lhrandom(0, 1);
1647 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1650 part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
1651 if (immediatebloodstain && part)
1653 immediatebloodstain = false;
1654 CL_ImmediateBloodStain(part);
1657 VectorMA(trailpos, trailstep, traildir, trailpos);
1664 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1667 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1669 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1672 void CL_ParticleBox(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1674 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1677 // note: this one ONLY does boxes!
1678 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)
1680 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1688 void CL_EntityParticles (const entity_t *ent)
1691 vec_t pitch, yaw, dist = 64, beamlength = 16;
1693 static vec3_t avelocities[NUMVERTEXNORMALS];
1694 if (!cl_particles.integer) return;
1695 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1697 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1699 if (!avelocities[0][0])
1700 for (i = 0;i < NUMVERTEXNORMALS;i++)
1701 for (j = 0;j < 3;j++)
1702 avelocities[i][j] = lhrandom(0, 2.55);
1704 for (i = 0;i < NUMVERTEXNORMALS;i++)
1706 yaw = cl.time * avelocities[i][0];
1707 pitch = cl.time * avelocities[i][1];
1708 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1709 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1710 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1711 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);
1716 void CL_ReadPointFile_f (void)
1718 double org[3], leakorg[3];
1721 char *pointfile = NULL, *pointfilepos, *t, tchar;
1722 char name[MAX_QPATH];
1727 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1728 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1731 Con_Printf("Could not open %s\n", name);
1735 Con_Printf("Reading %s...\n", name);
1736 VectorClear(leakorg);
1739 pointfilepos = pointfile;
1740 while (*pointfilepos)
1742 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1747 while (*t && *t != '\n' && *t != '\r')
1751 #if _MSC_VER >= 1400
1752 #define sscanf sscanf_s
1754 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1755 VectorCopy(org, vecorg);
1761 VectorCopy(org, leakorg);
1764 if (cl.num_particles < cl.max_particles - 3)
1767 CL_NewParticle(vecorg, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1770 Mem_Free(pointfile);
1771 VectorCopy(leakorg, vecorg);
1772 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1774 CL_NewParticle(vecorg, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1775 CL_NewParticle(vecorg, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1776 CL_NewParticle(vecorg, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1781 CL_ParseParticleEffect
1783 Parse an effect out of the server message
1786 void CL_ParseParticleEffect (void)
1789 int i, count, msgcount, color;
1791 MSG_ReadVector(&cl_message, org, cls.protocol);
1792 for (i=0 ; i<3 ; i++)
1793 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1794 msgcount = MSG_ReadByte(&cl_message);
1795 color = MSG_ReadByte(&cl_message);
1797 if (msgcount == 255)
1802 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1807 CL_ParticleExplosion
1811 void CL_ParticleExplosion (const vec3_t org)
1817 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1818 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1820 if (cl_particles_quake.integer)
1822 for (i = 0;i < 1024;i++)
1828 color = particlepalette[ramp1[r]];
1829 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1833 color = particlepalette[ramp2[r]];
1834 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);
1840 i = CL_PointSuperContents(org);
1841 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1843 if (cl_particles.integer && cl_particles_bubbles.integer)
1844 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1845 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);
1849 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1851 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1858 VectorMA(org, 128, v2, v);
1859 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1861 while (k < 16 && trace.fraction < 0.1f);
1862 VectorSubtract(trace.endpos, org, v2);
1863 VectorScale(v2, 2.0f, v2);
1864 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);
1870 if (cl_particles_explosions_shell.integer)
1871 R_NewExplosion(org);
1876 CL_ParticleExplosion2
1880 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1883 if (!cl_particles.integer) return;
1885 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1887 k = particlepalette[colorStart + (i % colorLength)];
1888 if (cl_particles_quake.integer)
1889 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);
1891 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);
1895 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1898 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1899 if (cl_particles_sparks.integer)
1901 sparkcount *= cl_particles_quality.value;
1902 while(sparkcount-- > 0)
1903 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);
1907 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1910 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1911 if (cl_particles_smoke.integer)
1913 smokecount *= cl_particles_quality.value;
1914 while(smokecount-- > 0)
1915 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);
1919 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)
1923 if (!cl_particles.integer) return;
1924 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1926 count = (int)(count * cl_particles_quality.value);
1929 k = particlepalette[colorbase + (rand()&3)];
1930 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);
1934 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1937 float minz, maxz, lifetime = 30;
1939 if (!cl_particles.integer) return;
1940 if (dir[2] < 0) // falling
1942 minz = maxs[2] + dir[2] * 0.1;
1945 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1950 maxz = maxs[2] + dir[2] * 0.1;
1952 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1955 count = (int)(count * cl_particles_quality.value);
1960 if (!cl_particles_rain.integer) break;
1961 count *= 4; // ick, this should be in the mod or maps?
1965 k = particlepalette[colorbase + (rand()&3)];
1966 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1967 if (gamemode == GAME_GOODVSBAD2)
1968 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);
1970 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);
1974 if (!cl_particles_snow.integer) break;
1977 k = particlepalette[colorbase + (rand()&3)];
1978 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1979 if (gamemode == GAME_GOODVSBAD2)
1980 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);
1982 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);
1986 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1990 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1991 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1992 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1993 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1994 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1995 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1997 #define PARTICLETEXTURESIZE 64
1998 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2000 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2004 dz = 1 - (dx*dx+dy*dy);
2005 if (dz > 0) // it does hit the sphere
2009 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2010 VectorNormalize(normal);
2011 dot = DotProduct(normal, light);
2012 if (dot > 0.5) // interior reflection
2013 f += ((dot * 2) - 1);
2014 else if (dot < -0.5) // exterior reflection
2015 f += ((dot * -2) - 1);
2017 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2018 VectorNormalize(normal);
2019 dot = DotProduct(normal, light);
2020 if (dot > 0.5) // interior reflection
2021 f += ((dot * 2) - 1);
2022 else if (dot < -0.5) // exterior reflection
2023 f += ((dot * -2) - 1);
2025 f += 16; // just to give it a haze so you can see the outline
2026 f = bound(0, f, 255);
2027 return (unsigned char) f;
2033 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2034 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2036 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2037 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2038 *width = particlefontcellwidth;
2039 *height = particlefontcellheight;
2042 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2044 int basex, basey, w, h, y;
2045 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2046 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2047 Sys_Error("invalid particle texture size for autogenerating");
2048 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2049 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2052 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2055 float cx, cy, dx, dy, f, iradius;
2057 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2058 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2059 iradius = 1.0f / radius;
2060 alpha *= (1.0f / 255.0f);
2061 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2063 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2067 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2072 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2073 d[0] += (int)(f * (blue - d[0]));
2074 d[1] += (int)(f * (green - d[1]));
2075 d[2] += (int)(f * (red - d[2]));
2082 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2085 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2087 data[0] = bound(minb, data[0], maxb);
2088 data[1] = bound(ming, data[1], maxg);
2089 data[2] = bound(minr, data[2], maxr);
2094 static void particletextureinvert(unsigned char *data)
2097 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2099 data[0] = 255 - data[0];
2100 data[1] = 255 - data[1];
2101 data[2] = 255 - data[2];
2105 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2106 static void R_InitBloodTextures (unsigned char *particletexturedata)
2109 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2110 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2113 for (i = 0;i < 8;i++)
2115 memset(data, 255, datasize);
2116 for (k = 0;k < 24;k++)
2117 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2118 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2119 particletextureinvert(data);
2120 setuptex(tex_bloodparticle[i], data, particletexturedata);
2124 for (i = 0;i < 8;i++)
2126 memset(data, 255, datasize);
2128 for (j = 1;j < 10;j++)
2129 for (k = min(j, m - 1);k < m;k++)
2130 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2131 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2132 particletextureinvert(data);
2133 setuptex(tex_blooddecal[i], data, particletexturedata);
2139 //uncomment this to make engine save out particle font to a tga file when run
2140 //#define DUMPPARTICLEFONT
2142 static void R_InitParticleTexture (void)
2144 int x, y, d, i, k, m;
2145 int basex, basey, w, h;
2146 float dx, dy, f, s1, t1, s2, t2;
2149 fs_offset_t filesize;
2150 char texturename[MAX_QPATH];
2153 // a note: decals need to modulate (multiply) the background color to
2154 // properly darken it (stain), and they need to be able to alpha fade,
2155 // this is a very difficult challenge because it means fading to white
2156 // (no change to background) rather than black (darkening everything
2157 // behind the whole decal polygon), and to accomplish this the texture is
2158 // inverted (dark red blood on white background becomes brilliant cyan
2159 // and white on black background) so we can alpha fade it to black, then
2160 // we invert it again during the blendfunc to make it work...
2162 #ifndef DUMPPARTICLEFONT
2163 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2166 particlefonttexture = decalskinframe->base;
2167 // TODO maybe allow custom grid size?
2168 particlefontwidth = image_width;
2169 particlefontheight = image_height;
2170 particlefontcellwidth = image_width / 8;
2171 particlefontcellheight = image_height / 8;
2172 particlefontcols = 8;
2173 particlefontrows = 8;
2178 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2179 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2180 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2181 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2182 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2184 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2185 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2186 particlefontcols = 8;
2187 particlefontrows = 8;
2189 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2192 for (i = 0;i < 8;i++)
2194 memset(data, 255, datasize);
2197 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2198 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2200 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2202 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2203 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2205 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2206 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2208 d = (int)(d * (1-(dx*dx+dy*dy)));
2209 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2210 d = bound(0, d, 255);
2211 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2218 setuptex(tex_smoke[i], data, particletexturedata);
2222 memset(data, 255, datasize);
2223 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2225 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2226 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2228 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2229 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2230 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2233 setuptex(tex_rainsplash, data, particletexturedata);
2236 memset(data, 255, datasize);
2237 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2239 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2240 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2242 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2243 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2244 d = bound(0, d, 255);
2245 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2248 setuptex(tex_particle, data, particletexturedata);
2251 memset(data, 255, datasize);
2252 light[0] = 1;light[1] = 1;light[2] = 1;
2253 VectorNormalize(light);
2254 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2256 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2257 // stretch upper half of bubble by +50% and shrink lower half by -50%
2258 // (this gives an elongated teardrop shape)
2260 dy = (dy - 0.5f) * 2.0f;
2262 dy = (dy - 0.5f) / 1.5f;
2263 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2265 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2266 // shrink bubble width to half
2268 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2271 setuptex(tex_raindrop, data, particletexturedata);
2274 memset(data, 255, datasize);
2275 light[0] = 1;light[1] = 1;light[2] = 1;
2276 VectorNormalize(light);
2277 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2279 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2280 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2282 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2283 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2286 setuptex(tex_bubble, data, particletexturedata);
2288 // Blood particles and blood decals
2289 R_InitBloodTextures (particletexturedata);
2292 for (i = 0;i < 8;i++)
2294 memset(data, 255, datasize);
2295 for (k = 0;k < 12;k++)
2296 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2297 for (k = 0;k < 3;k++)
2298 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2299 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2300 particletextureinvert(data);
2301 setuptex(tex_bulletdecal[i], data, particletexturedata);
2304 #ifdef DUMPPARTICLEFONT
2305 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2308 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2309 particlefonttexture = decalskinframe->base;
2311 Mem_Free(particletexturedata);
2316 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2318 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2319 particletexture[i].texture = particlefonttexture;
2320 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2321 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2322 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2323 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2326 #ifndef DUMPPARTICLEFONT
2327 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2328 if (!particletexture[tex_beam].texture)
2331 unsigned char noise3[64][64], data2[64][16][4];
2333 fractalnoise(&noise3[0][0], 64, 4);
2335 for (y = 0;y < 64;y++)
2337 dy = (y - 0.5f*64) / (64*0.5f-1);
2338 for (x = 0;x < 16;x++)
2340 dx = (x - 0.5f*16) / (16*0.5f-2);
2341 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2342 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2343 data2[y][x][3] = 255;
2347 #ifdef DUMPPARTICLEFONT
2348 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2350 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2352 particletexture[tex_beam].s1 = 0;
2353 particletexture[tex_beam].t1 = 0;
2354 particletexture[tex_beam].s2 = 1;
2355 particletexture[tex_beam].t2 = 1;
2357 // now load an texcoord/texture override file
2358 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2365 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2367 if(!strcmp(com_token, "\n"))
2368 continue; // empty line
2369 i = atoi(com_token);
2377 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2379 strlcpy(texturename, com_token, sizeof(texturename));
2380 s1 = atof(com_token);
2381 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2384 t1 = atof(com_token);
2385 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2387 s2 = atof(com_token);
2388 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2390 t2 = atof(com_token);
2391 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2392 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2393 strlcpy(texturename, com_token, sizeof(texturename));
2400 if (!texturename[0])
2402 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2405 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2407 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2410 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2413 // R_SkinFrame_LoadExternal already complained
2416 particletexture[i].texture = sf->base;
2417 particletexture[i].s1 = s1;
2418 particletexture[i].t1 = t1;
2419 particletexture[i].s2 = s2;
2420 particletexture[i].t2 = t2;
2426 static void r_part_start(void)
2429 // generate particlepalette for convenience from the main one
2430 for (i = 0;i < 256;i++)
2431 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2432 particletexturepool = R_AllocTexturePool();
2433 R_InitParticleTexture ();
2434 CL_Particles_LoadEffectInfo(NULL);
2437 static void r_part_shutdown(void)
2439 R_FreeTexturePool(&particletexturepool);
2442 static void r_part_newmap(void)
2445 R_SkinFrame_MarkUsed(decalskinframe);
2446 CL_Particles_LoadEffectInfo(NULL);
2449 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2450 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2452 void R_Particles_Init (void)
2455 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2457 particle_elements[i*6+0] = i*4+0;
2458 particle_elements[i*6+1] = i*4+1;
2459 particle_elements[i*6+2] = i*4+2;
2460 particle_elements[i*6+3] = i*4+0;
2461 particle_elements[i*6+4] = i*4+2;
2462 particle_elements[i*6+5] = i*4+3;
2465 Cvar_RegisterVariable(&r_drawparticles);
2466 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2467 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2468 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2469 Cvar_RegisterVariable(&r_drawdecals);
2470 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2471 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2474 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2476 int surfacelistindex;
2478 float *v3f, *t2f, *c4f;
2479 particletexture_t *tex;
2480 vec_t right[3], up[3], size, ca;
2481 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2483 RSurf_ActiveWorldEntity();
2485 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2486 // R_Mesh_ResetTextureState();
2487 GL_DepthMask(false);
2488 GL_DepthRange(0, 1);
2489 GL_PolygonOffset(0, 0);
2491 GL_CullFace(GL_NONE);
2493 // generate all the vertices at once
2494 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2496 d = cl.decals + surfacelist[surfacelistindex];
2499 c4f = particle_color4f + 16*surfacelistindex;
2500 ca = d->alpha * alphascale;
2501 // ensure alpha multiplier saturates properly
2502 if (ca > 1.0f / 256.0f)
2504 if (r_refdef.fogenabled)
2505 ca *= RSurf_FogVertex(d->org);
2506 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2507 Vector4Copy(c4f, c4f + 4);
2508 Vector4Copy(c4f, c4f + 8);
2509 Vector4Copy(c4f, c4f + 12);
2511 // calculate vertex positions
2512 size = d->size * cl_particles_size.value;
2513 VectorVectors(d->normal, right, up);
2514 VectorScale(right, size, right);
2515 VectorScale(up, size, up);
2516 v3f = particle_vertex3f + 12*surfacelistindex;
2517 v3f[ 0] = d->org[0] - right[0] - up[0];
2518 v3f[ 1] = d->org[1] - right[1] - up[1];
2519 v3f[ 2] = d->org[2] - right[2] - up[2];
2520 v3f[ 3] = d->org[0] - right[0] + up[0];
2521 v3f[ 4] = d->org[1] - right[1] + up[1];
2522 v3f[ 5] = d->org[2] - right[2] + up[2];
2523 v3f[ 6] = d->org[0] + right[0] + up[0];
2524 v3f[ 7] = d->org[1] + right[1] + up[1];
2525 v3f[ 8] = d->org[2] + right[2] + up[2];
2526 v3f[ 9] = d->org[0] + right[0] - up[0];
2527 v3f[10] = d->org[1] + right[1] - up[1];
2528 v3f[11] = d->org[2] + right[2] - up[2];
2530 // calculate texcoords
2531 tex = &particletexture[d->texnum];
2532 t2f = particle_texcoord2f + 8*surfacelistindex;
2533 t2f[0] = tex->s1;t2f[1] = tex->t2;
2534 t2f[2] = tex->s1;t2f[3] = tex->t1;
2535 t2f[4] = tex->s2;t2f[5] = tex->t1;
2536 t2f[6] = tex->s2;t2f[7] = tex->t2;
2539 // now render the decals all at once
2540 // (this assumes they all use one particle font texture!)
2541 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2542 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2543 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2544 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2547 void R_DrawDecals (void)
2550 int drawdecals = r_drawdecals.integer;
2555 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2557 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2558 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2560 // LordHavoc: early out conditions
2564 decalfade = frametime * 256 / cl_decals_fadetime.value;
2565 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2566 drawdist2 = drawdist2*drawdist2;
2568 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2570 if (!decal->typeindex)
2573 if (killsequence - decal->decalsequence > 0)
2576 if (cl.time > decal->time2 + cl_decals_time.value)
2578 decal->alpha -= decalfade;
2579 if (decal->alpha <= 0)
2585 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2587 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2588 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2594 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2600 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))
2601 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2604 decal->typeindex = 0;
2605 if (cl.free_decal > i)
2609 // reduce cl.num_decals if possible
2610 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2613 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2615 decal_t *olddecals = cl.decals;
2616 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2617 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2618 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2619 Mem_Free(olddecals);
2622 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2625 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2627 vec3_t vecorg, vecvel, baseright, baseup;
2628 int surfacelistindex;
2629 int batchstart, batchcount;
2630 const particle_t *p;
2632 rtexture_t *texture;
2633 float *v3f, *t2f, *c4f;
2634 particletexture_t *tex;
2635 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2636 // float ambient[3], diffuse[3], diffusenormal[3];
2637 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2638 vec4_t colormultiplier;
2639 float minparticledist_start, minparticledist_end;
2642 RSurf_ActiveWorldEntity();
2644 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));
2646 r_refdef.stats[r_stat_particles] += numsurfaces;
2647 // R_Mesh_ResetTextureState();
2648 GL_DepthMask(false);
2649 GL_DepthRange(0, 1);
2650 GL_PolygonOffset(0, 0);
2652 GL_CullFace(GL_NONE);
2654 spintime = r_refdef.scene.time;
2656 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2657 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2658 dofade = (minparticledist_start < minparticledist_end);
2660 // first generate all the vertices at once
2661 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2663 p = cl.particles + surfacelist[surfacelistindex];
2665 blendmode = (pblend_t)p->blendmode;
2667 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2668 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2669 alpha = palpha * colormultiplier[3];
2670 // ensure alpha multiplier saturates properly
2676 case PBLEND_INVALID:
2678 // additive and modulate can just fade out in fog (this is correct)
2679 if (r_refdef.fogenabled)
2680 alpha *= RSurf_FogVertex(p->org);
2681 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2682 alpha *= 1.0f / 256.0f;
2683 c4f[0] = p->color[0] * alpha;
2684 c4f[1] = p->color[1] * alpha;
2685 c4f[2] = p->color[2] * alpha;
2689 // additive and modulate can just fade out in fog (this is correct)
2690 if (r_refdef.fogenabled)
2691 alpha *= RSurf_FogVertex(p->org);
2692 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2693 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2694 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2695 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2699 c4f[0] = p->color[0] * colormultiplier[0];
2700 c4f[1] = p->color[1] * colormultiplier[1];
2701 c4f[2] = p->color[2] * colormultiplier[2];
2703 // note: lighting is not cheap!
2704 if (particletype[p->typeindex].lighting)
2706 vecorg[0] = p->org[0];
2707 vecorg[1] = p->org[1];
2708 vecorg[2] = p->org[2];
2709 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2711 // mix in the fog color
2712 if (r_refdef.fogenabled)
2714 fog = RSurf_FogVertex(p->org);
2716 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2717 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2718 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2720 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2721 VectorScale(c4f, alpha, c4f);
2724 // copy the color into the other three vertices
2725 Vector4Copy(c4f, c4f + 4);
2726 Vector4Copy(c4f, c4f + 8);
2727 Vector4Copy(c4f, c4f + 12);
2729 size = p->size * cl_particles_size.value;
2730 tex = &particletexture[p->texnum];
2731 switch(p->orientation)
2733 // case PARTICLE_INVALID:
2734 case PARTICLE_BILLBOARD:
2735 if (p->angle + p->spin)
2737 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2738 spinsin = sin(spinrad) * size;
2739 spincos = cos(spinrad) * size;
2740 spinm1 = -p->stretch * spincos;
2743 spinm4 = -p->stretch * spincos;
2744 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2745 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2749 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2750 VectorScale(r_refdef.view.up, size, up);
2753 v3f[ 0] = p->org[0] - right[0] - up[0];
2754 v3f[ 1] = p->org[1] - right[1] - up[1];
2755 v3f[ 2] = p->org[2] - right[2] - up[2];
2756 v3f[ 3] = p->org[0] - right[0] + up[0];
2757 v3f[ 4] = p->org[1] - right[1] + up[1];
2758 v3f[ 5] = p->org[2] - right[2] + up[2];
2759 v3f[ 6] = p->org[0] + right[0] + up[0];
2760 v3f[ 7] = p->org[1] + right[1] + up[1];
2761 v3f[ 8] = p->org[2] + right[2] + up[2];
2762 v3f[ 9] = p->org[0] + right[0] - up[0];
2763 v3f[10] = p->org[1] + right[1] - up[1];
2764 v3f[11] = p->org[2] + right[2] - up[2];
2765 t2f[0] = tex->s1;t2f[1] = tex->t2;
2766 t2f[2] = tex->s1;t2f[3] = tex->t1;
2767 t2f[4] = tex->s2;t2f[5] = tex->t1;
2768 t2f[6] = tex->s2;t2f[7] = tex->t2;
2770 case PARTICLE_ORIENTED_DOUBLESIDED:
2771 vecvel[0] = p->vel[0];
2772 vecvel[1] = p->vel[1];
2773 vecvel[2] = p->vel[2];
2774 VectorVectors(vecvel, baseright, baseup);
2775 if (p->angle + p->spin)
2777 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2778 spinsin = sin(spinrad) * size;
2779 spincos = cos(spinrad) * size;
2780 spinm1 = p->stretch * spincos;
2783 spinm4 = p->stretch * spincos;
2784 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2785 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2789 VectorScale(baseright, size * p->stretch, right);
2790 VectorScale(baseup, size, up);
2792 v3f[ 0] = p->org[0] - right[0] - up[0];
2793 v3f[ 1] = p->org[1] - right[1] - up[1];
2794 v3f[ 2] = p->org[2] - right[2] - up[2];
2795 v3f[ 3] = p->org[0] - right[0] + up[0];
2796 v3f[ 4] = p->org[1] - right[1] + up[1];
2797 v3f[ 5] = p->org[2] - right[2] + up[2];
2798 v3f[ 6] = p->org[0] + right[0] + up[0];
2799 v3f[ 7] = p->org[1] + right[1] + up[1];
2800 v3f[ 8] = p->org[2] + right[2] + up[2];
2801 v3f[ 9] = p->org[0] + right[0] - up[0];
2802 v3f[10] = p->org[1] + right[1] - up[1];
2803 v3f[11] = p->org[2] + right[2] - up[2];
2804 t2f[0] = tex->s1;t2f[1] = tex->t2;
2805 t2f[2] = tex->s1;t2f[3] = tex->t1;
2806 t2f[4] = tex->s2;t2f[5] = tex->t1;
2807 t2f[6] = tex->s2;t2f[7] = tex->t2;
2809 case PARTICLE_SPARK:
2810 len = VectorLength(p->vel);
2811 VectorNormalize2(p->vel, up);
2812 lenfactor = p->stretch * 0.04 * len;
2813 if(lenfactor < size * 0.5)
2814 lenfactor = size * 0.5;
2815 VectorMA(p->org, -lenfactor, up, v);
2816 VectorMA(p->org, lenfactor, up, up2);
2817 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2818 t2f[0] = tex->s1;t2f[1] = tex->t2;
2819 t2f[2] = tex->s1;t2f[3] = tex->t1;
2820 t2f[4] = tex->s2;t2f[5] = tex->t1;
2821 t2f[6] = tex->s2;t2f[7] = tex->t2;
2823 case PARTICLE_VBEAM:
2824 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2825 VectorSubtract(p->vel, p->org, up);
2826 VectorNormalize(up);
2827 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2828 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2829 t2f[0] = tex->s2;t2f[1] = v[0];
2830 t2f[2] = tex->s1;t2f[3] = v[0];
2831 t2f[4] = tex->s1;t2f[5] = v[1];
2832 t2f[6] = tex->s2;t2f[7] = v[1];
2834 case PARTICLE_HBEAM:
2835 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2836 VectorSubtract(p->vel, p->org, up);
2837 VectorNormalize(up);
2838 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2839 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2840 t2f[0] = v[0];t2f[1] = tex->t1;
2841 t2f[2] = v[0];t2f[3] = tex->t2;
2842 t2f[4] = v[1];t2f[5] = tex->t2;
2843 t2f[6] = v[1];t2f[7] = tex->t1;
2848 // now render batches of particles based on blendmode and texture
2849 blendmode = PBLEND_INVALID;
2853 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2854 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2856 p = cl.particles + surfacelist[surfacelistindex];
2858 if (texture != particletexture[p->texnum].texture)
2860 texture = particletexture[p->texnum].texture;
2861 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2864 if (p->blendmode == PBLEND_INVMOD)
2866 // inverse modulate blend - group these
2867 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2868 // iterate until we find a change in settings
2869 batchstart = surfacelistindex++;
2870 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2872 p = cl.particles + surfacelist[surfacelistindex];
2873 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2879 // additive or alpha blend - group these
2880 // (we can group these because we premultiplied the texture alpha)
2881 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2882 // iterate until we find a change in settings
2883 batchstart = surfacelistindex++;
2884 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2886 p = cl.particles + surfacelist[surfacelistindex];
2887 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2892 batchcount = surfacelistindex - batchstart;
2893 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2897 void R_DrawParticles (void)
2900 int drawparticles = r_drawparticles.integer;
2901 float minparticledist_start;
2903 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2909 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2910 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2912 // LordHavoc: early out conditions
2913 if (!cl.num_particles)
2916 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2917 gravity = frametime * cl.movevars_gravity;
2918 update = frametime > 0;
2919 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2920 drawdist2 = drawdist2*drawdist2;
2922 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2926 if (cl.free_particle > i)
2927 cl.free_particle = i;
2933 if (p->delayedspawn > cl.time)
2936 p->size += p->sizeincrease * frametime;
2937 p->alpha -= p->alphafade * frametime;
2939 if (p->alpha <= 0 || p->die <= cl.time)
2942 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2944 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2946 if (p->typeindex == pt_blood)
2947 p->size += frametime * 8;
2949 p->vel[2] -= p->gravity * gravity;
2950 f = 1.0f - min(p->liquidfriction * frametime, 1);
2951 VectorScale(p->vel, f, p->vel);
2955 p->vel[2] -= p->gravity * gravity;
2958 f = 1.0f - min(p->airfriction * frametime, 1);
2959 VectorScale(p->vel, f, p->vel);
2963 VectorCopy(p->org, oldorg);
2964 VectorMA(p->org, frametime, p->vel, p->org);
2965 // if (p->bounce && cl.time >= p->delayedcollisions)
2966 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2968 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);
2969 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2970 // or if the trace hit something flagged as NOIMPACT
2971 // then remove the particle
2972 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2974 VectorCopy(trace.endpos, p->org);
2975 // react if the particle hit something
2976 if (trace.fraction < 1)
2978 VectorCopy(trace.endpos, p->org);
2980 if (p->staintexnum >= 0)
2982 // blood - splash on solid
2983 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2986 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2987 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2988 if (cl_decals.integer)
2990 // create a decal for the blood splat
2991 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2992 if (cl_decals_newsystem_bloodsmears.integer)
2994 VectorCopy(p->vel, decaldir);
2995 VectorNormalize(decaldir);
2998 VectorCopy(trace.plane.normal, decaldir);
2999 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3004 if (p->typeindex == pt_blood)
3006 // blood - splash on solid
3007 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3009 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3011 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)));
3012 if (cl_decals.integer)
3014 // create a decal for the blood splat
3015 if (cl_decals_newsystem_bloodsmears.integer)
3017 VectorCopy(p->vel, decaldir);
3018 VectorNormalize(decaldir);
3021 VectorCopy(trace.plane.normal, decaldir);
3022 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
3027 else if (p->bounce < 0)
3029 // bounce -1 means remove on impact
3034 // anything else - bounce off solid
3035 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3036 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3041 if (VectorLength2(p->vel) < 0.03)
3043 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3045 VectorClear(p->vel);
3049 if (p->typeindex != pt_static)
3051 switch (p->typeindex)
3053 case pt_entityparticle:
3054 // particle that removes itself after one rendered frame
3061 a = CL_PointSuperContents(p->org);
3062 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3066 a = CL_PointSuperContents(p->org);
3067 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3071 a = CL_PointSuperContents(p->org);
3072 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3076 if (cl.time > p->time2)
3079 p->time2 = cl.time + (rand() & 3) * 0.1;
3080 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3081 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3083 a = CL_PointSuperContents(p->org);
3084 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3092 else if (p->delayedspawn > cl.time)
3096 // don't render particles too close to the view (they chew fillrate)
3097 // also don't render particles behind the view (useless)
3098 // further checks to cull to the frustum would be too slow here
3099 switch(p->typeindex)
3102 // beams have no culling
3103 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3106 if(cl_particles_visculling.integer)
3107 if (!r_refdef.viewcache.world_novis)
3108 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3110 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3112 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3115 // anything else just has to be in front of the viewer and visible at this distance
3116 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3117 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3124 if (cl.free_particle > i)
3125 cl.free_particle = i;
3128 // reduce cl.num_particles if possible
3129 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3132 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3134 particle_t *oldparticles = cl.particles;
3135 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3136 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3137 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3138 Mem_Free(oldparticles);