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
47 #define PARTICLEEFFECT_FORCENEAREST 4
48 #define PARTICLEEFFECT_DEFINED 2147483648U
50 typedef struct particleeffectinfo_s
52 int effectnameindex; // which effect this belongs to
53 // PARTICLEEFFECT_* bits
55 // blood effects may spawn very few particles, so proper fraction-overflow
56 // handling is very important, this variable keeps track of the fraction
57 double particleaccumulator;
58 // the math is: countabsolute + requestedcount * countmultiplier * quality
59 // absolute number of particles to spawn, often used for decals
60 // (unaffected by quality and requestedcount)
62 // multiplier for the number of particles CL_ParticleEffect was told to
63 // spawn, most effects do not really have a count and hence use 1, so
64 // this is often the actual count to spawn, not merely a multiplier
65 float countmultiplier;
66 // if > 0 this causes the particle to spawn in an evenly spaced line from
67 // originmins to originmaxs (causing them to describe a trail, not a box)
69 // type of particle to spawn (defines some aspects of behavior)
71 // blending mode used on this particle type
73 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
74 porientation_t orientation;
75 // range of colors to choose from in hex RRGGBB (like HTML color tags),
76 // randomly interpolated at spawn
77 unsigned int color[2];
78 // a random texture is chosen in this range (note the second value is one
79 // past the last choosable, so for example 8,16 chooses any from 8 up and
81 // if start and end of the range are the same, no randomization is done
83 // range of size values randomly chosen when spawning, plus size increase over time
85 // range of alpha values randomly chosen when spawning, plus alpha fade
87 // how long the particle should live (note it is also removed if alpha drops to 0)
89 // how much gravity affects this particle (negative makes it fly up!)
91 // how much bounce the particle has when it hits a surface
92 // if negative the particle is removed on impact
94 // if in air this friction is applied
95 // if negative the particle accelerates
97 // if in liquid (water/slime/lava) this friction is applied
98 // if negative the particle accelerates
100 // these offsets are added to the values given to particleeffect(), and
101 // then an ellipsoid-shaped jitter is added as defined by these
102 // (they are the 3 radii)
104 // stretch velocity factor (used for sparks)
105 float originoffset[3];
106 float relativeoriginoffset[3];
107 float velocityoffset[3];
108 float relativevelocityoffset[3];
109 float originjitter[3];
110 float velocityjitter[3];
111 float velocitymultiplier;
112 // an effect can also spawn a dlight
113 float lightradiusstart;
114 float lightradiusfade;
119 float lightcorona[2];
120 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
125 float rotate[4]; // min/max base angle, min/max rotation over time
127 particleeffectinfo_t;
129 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
131 int numparticleeffectinfo;
132 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
134 static int particlepalette[256];
136 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
137 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
138 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
139 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
140 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
141 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
142 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
143 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
144 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
145 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
146 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
147 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
148 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
149 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
150 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
151 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
152 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
153 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
154 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
155 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
156 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
157 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
158 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
159 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
160 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
161 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
162 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
163 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
164 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
165 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
166 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
167 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
170 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
171 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
172 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
174 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
176 // particletexture_t is a rectangle in the particlefonttexture
177 typedef struct particletexture_s
180 float s1, t1, s2, t2;
184 static rtexturepool_t *particletexturepool;
185 static rtexture_t *particlefonttexture;
186 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
187 skinframe_t *decalskinframe;
189 // texture numbers in particle font
190 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
191 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
192 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
193 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
194 static const int tex_rainsplash = 32;
195 static const int tex_square = 33;
196 static const int tex_beam = 60;
197 static const int tex_bubble = 62;
198 static const int tex_raindrop = 61;
199 static const int tex_particle = 63;
201 particleeffectinfo_t baselineparticleeffectinfo =
203 0, //int effectnameindex; // which effect this belongs to
204 // PARTICLEEFFECT_* bits
206 // blood effects may spawn very few particles, so proper fraction-overflow
207 // handling is very important, this variable keeps track of the fraction
208 0.0, //double particleaccumulator;
209 // the math is: countabsolute + requestedcount * countmultiplier * quality
210 // absolute number of particles to spawn, often used for decals
211 // (unaffected by quality and requestedcount)
212 0.0f, //float countabsolute;
213 // multiplier for the number of particles CL_ParticleEffect was told to
214 // spawn, most effects do not really have a count and hence use 1, so
215 // this is often the actual count to spawn, not merely a multiplier
216 0.0f, //float countmultiplier;
217 // if > 0 this causes the particle to spawn in an evenly spaced line from
218 // originmins to originmaxs (causing them to describe a trail, not a box)
219 0.0f, //float trailspacing;
220 // type of particle to spawn (defines some aspects of behavior)
221 pt_alphastatic, //ptype_t particletype;
222 // blending mode used on this particle type
223 PBLEND_ALPHA, //pblend_t blendmode;
224 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
225 PARTICLE_BILLBOARD, //porientation_t orientation;
226 // range of colors to choose from in hex RRGGBB (like HTML color tags),
227 // randomly interpolated at spawn
228 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
229 // a random texture is chosen in this range (note the second value is one
230 // past the last choosable, so for example 8,16 chooses any from 8 up and
232 // if start and end of the range are the same, no randomization is done
233 {63, 63 /* tex_particle */}, //int tex[2];
234 // range of size values randomly chosen when spawning, plus size increase over time
235 {1, 1, 0.0f}, //float size[3];
236 // range of alpha values randomly chosen when spawning, plus alpha fade
237 {0.0f, 256.0f, 256.0f}, //float alpha[3];
238 // how long the particle should live (note it is also removed if alpha drops to 0)
239 {16777216.0f, 16777216.0f}, //float time[2];
240 // how much gravity affects this particle (negative makes it fly up!)
241 0.0f, //float gravity;
242 // how much bounce the particle has when it hits a surface
243 // if negative the particle is removed on impact
244 0.0f, //float bounce;
245 // if in air this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float airfriction;
248 // if in liquid (water/slime/lava) this friction is applied
249 // if negative the particle accelerates
250 0.0f, //float liquidfriction;
251 // these offsets are added to the values given to particleeffect(), and
252 // then an ellipsoid-shaped jitter is added as defined by these
253 // (they are the 3 radii)
254 1.0f, //float stretchfactor;
255 // stretch velocity factor (used for sparks)
256 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
259 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
260 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
261 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
262 0.0f, //float velocitymultiplier;
263 // an effect can also spawn a dlight
264 0.0f, //float lightradiusstart;
265 0.0f, //float lightradiusfade;
266 16777216.0f, //float lighttime;
267 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
268 true, //qbool lightshadow;
269 0, //int lightcubemapnum;
270 {1.0f, 0.25f}, //float lightcorona[2];
271 {(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!
272 {-1, -1}, //int staintex[2];
273 {1.0f, 1.0f}, //float stainalpha[2];
274 {2.0f, 2.0f}, //float stainsize[2];
276 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
279 cvar_t cl_particles = {CF_CLIENT | CF_ARCHIVE, "cl_particles", "1", "enables particle effects"};
280 cvar_t cl_particles_quality = {CF_CLIENT | CF_ARCHIVE, "cl_particles_quality", "1", "multiplies number of particles"};
281 cvar_t cl_particles_alpha = {CF_CLIENT | CF_ARCHIVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
282 cvar_t cl_particles_size = {CF_CLIENT | CF_ARCHIVE, "cl_particles_size", "1", "multiplies particle size"};
283 cvar_t cl_particles_quake = {CF_CLIENT | CF_ARCHIVE, "cl_particles_quake", "0", "0: Fancy particles; 1: Disc particles like GLQuake; 2: Square particles like software-rendered Quake"};
284 cvar_t cl_particles_blood = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood", "1", "enables blood effects"};
285 cvar_t cl_particles_blood_alpha = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
286 cvar_t cl_particles_blood_decal_alpha = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
287 cvar_t cl_particles_blood_decal_scalemin = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
288 cvar_t cl_particles_blood_decal_scalemax = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
289 cvar_t cl_particles_blood_bloodhack = {CF_CLIENT | CF_ARCHIVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
290 cvar_t cl_particles_bulletimpacts = {CF_CLIENT | CF_ARCHIVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
291 cvar_t cl_particles_explosions_sparks = {CF_CLIENT | CF_ARCHIVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
292 cvar_t cl_particles_explosions_shell = {CF_CLIENT | CF_ARCHIVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
293 cvar_t cl_particles_rain = {CF_CLIENT | CF_ARCHIVE, "cl_particles_rain", "1", "enables rain effects"};
294 cvar_t cl_particles_snow = {CF_CLIENT | CF_ARCHIVE, "cl_particles_snow", "1", "enables snow effects"};
295 cvar_t cl_particles_smoke = {CF_CLIENT | CF_ARCHIVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
296 cvar_t cl_particles_smoke_alpha = {CF_CLIENT | CF_ARCHIVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
297 cvar_t cl_particles_smoke_alphafade = {CF_CLIENT | CF_ARCHIVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
298 cvar_t cl_particles_sparks = {CF_CLIENT | CF_ARCHIVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
299 cvar_t cl_particles_bubbles = {CF_CLIENT | CF_ARCHIVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
300 cvar_t cl_particles_visculling = {CF_CLIENT | CF_ARCHIVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
301 cvar_t cl_particles_collisions = {CF_CLIENT | CF_ARCHIVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
302 cvar_t cl_particles_forcetraileffects = {CF_CLIENT, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
303 cvar_t cl_decals = {CF_CLIENT | CF_ARCHIVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
304 cvar_t cl_decals_time = {CF_CLIENT | CF_ARCHIVE, "cl_decals_time", "20", "how long before decals start to fade away"};
305 cvar_t cl_decals_fadetime = {CF_CLIENT | CF_ARCHIVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
306 cvar_t cl_decals_newsystem_intensitymultiplier = {CF_CLIENT | CF_ARCHIVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
307 cvar_t cl_decals_newsystem_immediatebloodstain = {CF_CLIENT | CF_ARCHIVE, "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"};
308 cvar_t cl_decals_newsystem_bloodsmears = {CF_CLIENT | CF_ARCHIVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
309 cvar_t cl_decals_models = {CF_CLIENT | CF_ARCHIVE, "cl_decals_models", "0", "enables decals on animated models"};
310 cvar_t cl_decals_bias = {CF_CLIENT | CF_ARCHIVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
311 cvar_t cl_decals_max = {CF_CLIENT | CF_ARCHIVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
314 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
320 particleeffectinfo_t *info = NULL;
321 const char *text = textstart;
323 for (linenumber = 1;;linenumber++)
326 for (arrayindex = 0;arrayindex < 16;arrayindex++)
327 argv[arrayindex][0] = 0;
330 if (!COM_ParseToken_Simple(&text, true, false, true))
332 if (!strcmp(com_token, "\n"))
336 dp_strlcpy(argv[argc], com_token, sizeof(argv[argc]));
342 #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;}
343 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
344 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
345 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
346 #define readfloat(var) checkparms(2);var = atof(argv[1])
347 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
348 if (!strcmp(argv[0], "effect"))
352 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
354 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
357 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
359 if (particleeffectname[effectnameindex][0])
361 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
366 dp_strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
370 // if we run out of names, abort
371 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
373 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
376 for(i = 0; i < numparticleeffectinfo; ++i)
378 info = particleeffectinfo + i;
379 if(!(info->flags & PARTICLEEFFECT_DEFINED))
380 if(info->effectnameindex == effectnameindex)
383 if(i < numparticleeffectinfo)
385 info = particleeffectinfo + numparticleeffectinfo++;
386 // copy entire info from baseline, then fix up the nameindex
387 *info = baselineparticleeffectinfo;
388 info->effectnameindex = effectnameindex;
391 else if (info == NULL)
393 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
397 info->flags |= PARTICLEEFFECT_DEFINED;
398 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
399 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
400 else if (!strcmp(argv[0], "type"))
403 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
404 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
405 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
406 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
407 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
408 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
409 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
410 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
411 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
412 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
413 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
414 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
415 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
416 info->blendmode = particletype[info->particletype].blendmode;
417 info->orientation = particletype[info->particletype].orientation;
419 else if (!strcmp(argv[0], "blend"))
422 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
423 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
424 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
425 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
427 else if (!strcmp(argv[0], "orientation"))
430 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
431 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
432 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
433 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
434 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
436 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
437 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
438 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
439 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
440 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
441 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
442 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
443 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
444 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
445 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
446 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
447 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
448 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
449 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
450 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
451 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
452 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
453 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
454 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
455 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
456 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
457 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
458 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
459 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
460 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
461 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
462 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
463 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
464 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
465 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
466 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
467 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
468 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; }
469 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
470 else if (!strcmp(argv[0], "forcenearest")) {checkparms(1);info->flags |= PARTICLEEFFECT_FORCENEAREST;}
472 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
481 int CL_ParticleEffectIndexForName(const char *name)
484 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
485 if (!strcmp(particleeffectname[i], name))
490 const char *CL_ParticleEffectNameForIndex(int i)
492 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
494 return particleeffectname[i];
497 // MUST match effectnameindex_t in client.h
498 static const char *standardeffectnames[EFFECT_TOTAL] =
522 "TE_TEI_BIGEXPLOSION",
538 static void CL_Particles_LoadEffectInfo(const char *customfile)
542 unsigned char *filedata;
543 fs_offset_t filesize;
544 char filename[MAX_QPATH];
545 numparticleeffectinfo = 0;
546 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
547 memset(particleeffectname, 0, sizeof(particleeffectname));
548 for (i = 0;i < EFFECT_TOTAL;i++)
549 dp_strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
550 for (filepass = 0;;filepass++)
555 dp_strlcpy(filename, customfile, sizeof(filename));
557 dp_strlcpy(filename, "effectinfo.txt", sizeof(filename));
559 else if (filepass == 1)
561 if (!cl.worldbasename[0] || customfile)
563 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
567 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
570 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
575 static void CL_Particles_LoadEffectInfo_f(cmd_state_t *cmd)
577 CL_Particles_LoadEffectInfo(Cmd_Argc(cmd) > 1 ? Cmd_Argv(cmd, 1) : NULL);
585 void CL_ReadPointFile_f(cmd_state_t *cmd);
586 void CL_Particles_Init (void)
588 Cmd_AddCommand(CF_CLIENT, "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)");
589 Cmd_AddCommand(CF_CLIENT, "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)");
591 Cvar_RegisterVariable (&cl_particles);
592 Cvar_RegisterVariable (&cl_particles_quality);
593 Cvar_RegisterVariable (&cl_particles_alpha);
594 Cvar_RegisterVariable (&cl_particles_size);
595 Cvar_RegisterVariable (&cl_particles_quake);
596 Cvar_RegisterVariable (&cl_particles_blood);
597 Cvar_RegisterVariable (&cl_particles_blood_alpha);
598 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
599 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
600 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
601 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
602 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
603 Cvar_RegisterVariable (&cl_particles_explosions_shell);
604 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
605 Cvar_RegisterVariable (&cl_particles_rain);
606 Cvar_RegisterVariable (&cl_particles_snow);
607 Cvar_RegisterVariable (&cl_particles_smoke);
608 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
609 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
610 Cvar_RegisterVariable (&cl_particles_sparks);
611 Cvar_RegisterVariable (&cl_particles_bubbles);
612 Cvar_RegisterVariable (&cl_particles_visculling);
613 Cvar_RegisterVariable (&cl_particles_collisions);
614 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
615 Cvar_RegisterVariable (&cl_decals);
616 Cvar_RegisterVariable (&cl_decals_time);
617 Cvar_RegisterVariable (&cl_decals_fadetime);
618 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
619 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
620 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
621 Cvar_RegisterVariable (&cl_decals_models);
622 Cvar_RegisterVariable (&cl_decals_bias);
623 Cvar_RegisterVariable (&cl_decals_max);
626 void CL_Particles_Shutdown (void)
630 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
631 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
636 * @brief Creates a new particle and returns a pointer to it
638 * @param[in] sortorigin ?
639 * @param[in] ptypeindex Any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
640 * @param[in] pcolor1,pcolor2 Minimum and maximum range of color, randomly interpolated with pcolor2 to decide particle color
641 * @param[in] ptex Any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
642 * @param[in] psize Size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
643 * @param[in] psizeincrease ?
644 * @param[in] palpha Opacity of particle as 0-255 (can be more than 255)
645 * @param[in] palphafade Rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
646 * @param[in] pgravity How much effect gravity has on the particle (0-1)
647 * @param[in] 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
648 * @param[in] px,py,pz Starting origin of particle
649 * @param[in] pvx,pvy,pvz Starting velocity of particle
650 * @param[in] pairfriction How much the particle slows down, in air, per second (0-1 typically, can slowdown faster than 1)
651 * @param[in] pliquidfriction How much the particle slows down, in liquids, per second (0-1 typically, can slowdown faster than 1)
652 * @param[in] originjitter ?
653 * @param[in] velocityjitter ?
654 * @param[in] pqualityreduction ?
655 * @param[in] lifetime How long the particle can live (note it is also removed if alpha drops to nothing)
656 * @param[in] stretch ?
657 * @param[in] blendmode One of the PBLEND_ values
658 * @param[in] orientation One of the PARTICLE_ values
659 * @param[in] staincolor1 Minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
660 * @param[in] staincolor2 Minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
661 * @param[in] staintex Any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
662 * @param[in] angle Base rotation of the particle geometry around its center normal
663 * @param[in] spin Rotation speed of the particle geometry around its center normal
664 * @param[in] tint The tint
666 * @return Pointer to the new particle
668 particle_t *CL_NewParticle(
669 const vec3_t sortorigin,
670 unsigned short ptypeindex,
671 int pcolor1, int pcolor2,
679 float px, float py, float pz,
680 float pvx, float pvy, float pvz,
682 float pliquidfriction,
684 float velocityjitter,
685 qbool pqualityreduction,
689 porientation_t orientation,
702 if (!cl_particles.integer)
704 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
705 if (cl.free_particle >= cl.max_particles)
708 lifetime = palpha / min(1, palphafade);
709 part = &cl.particles[cl.free_particle++];
710 if (cl.num_particles < cl.free_particle)
711 cl.num_particles = cl.free_particle;
712 memset(part, 0, sizeof(*part));
713 VectorCopy(sortorigin, part->sortorigin);
714 part->typeindex = ptypeindex;
715 part->blendmode = blendmode;
716 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
718 particletexture_t *tex = &particletexture[ptex];
719 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
720 part->orientation = PARTICLE_VBEAM;
722 part->orientation = PARTICLE_HBEAM;
725 part->orientation = orientation;
726 l2 = (int)lhrandom(0.5, 256.5);
728 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
729 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
730 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
733 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
734 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
735 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
737 part->alpha = palpha;
738 part->alphafade = palphafade;
739 part->staintexnum = staintex;
740 if(staincolor1 >= 0 && staincolor2 >= 0)
742 l2 = (int)lhrandom(0.5, 256.5);
744 if(blendmode == PBLEND_INVMOD)
746 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
747 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
748 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
752 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
753 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
754 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
756 if(r > 0xFF) r = 0xFF;
757 if(g > 0xFF) g = 0xFF;
758 if(b > 0xFF) b = 0xFF;
762 r = part->color[0]; // -1 is shorthand for stain = particle color
766 part->staincolor[0] = r;
767 part->staincolor[1] = g;
768 part->staincolor[2] = b;
769 part->stainalpha = palpha * stainalpha;
770 part->stainsize = psize * stainsize;
773 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
775 part->color[0] *= tint[0];
776 part->color[1] *= tint[1];
777 part->color[2] *= tint[2];
779 part->alpha *= tint[3];
780 part->alphafade *= tint[3];
781 part->stainalpha *= tint[3];
785 part->sizeincrease = psizeincrease;
786 part->gravity = pgravity;
787 part->bounce = pbounce;
788 part->stretch = stretch;
790 part->org[0] = px + originjitter * v[0];
791 part->org[1] = py + originjitter * v[1];
792 part->org[2] = pz + originjitter * v[2];
793 part->vel[0] = pvx + velocityjitter * v[0];
794 part->vel[1] = pvy + velocityjitter * v[1];
795 part->vel[2] = pvz + velocityjitter * v[2];
797 part->airfriction = pairfriction;
798 part->liquidfriction = pliquidfriction;
799 part->die = cl.time + lifetime;
800 part->delayedspawn = cl.time;
801 // part->delayedcollisions = 0;
802 part->qualityreduction = pqualityreduction;
805 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
806 if (part->typeindex == pt_rain)
812 // turn raindrop into simple spark and create delayedspawn splash effect
813 part->typeindex = pt_spark;
815 VectorMA(part->org, lifetime, part->vel, endvec);
816 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
817 part->die = cl.time + lifetime * trace.fraction;
818 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);
821 part2->delayedspawn = part->die;
822 part2->die += part->die - cl.time;
823 for (i = rand() & 7;i < 10;i++)
825 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0.1, 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);
828 part2->delayedspawn = part->die;
829 part2->die += part->die - cl.time;
835 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
837 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
840 VectorMA(part->org, lifetime, part->vel, endvec);
841 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
842 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
852 * @brief Creates a simple particle, a square like Quake, or a disc like GLQuake
854 * @param[in] origin ?
855 * @param[in] color_1,color_2 Minimum and maximum range of color, randomly interpolated with pcolor2 to decide particle color
856 * @param[in] gravity How much effect gravity has on the particle (0-1)
857 * @param[in] offset_x,offset_y,offset_z Starting origin of particle
858 * @param[in] velocity_offset_x,velocity_offset_y,velocity_offset_z Starting velocity of particle
859 * @param[in] air_friction How much the particle slows down, in air, per second (0-1 typically, can slowdown faster than 1)
860 * @param[in] liquid_friction How much the particle slows down, in liquids, per second (0-1 typically, can slowdown faster than 1)
861 * @param[in] origin_jitter ?
862 * @param[in] velocity_jitter ?
863 * @param[in] lifetime How long the particle can live (note it is also removed if alpha drops to nothing)
865 * @return Pointer to the new particle
867 particle_t *CL_NewQuakeParticle(
872 const float offset_x,
873 const float offset_y,
874 const float offset_z,
875 const float velocity_offset_x,
876 const float velocity_offset_y,
877 const float velocity_offset_z,
878 const float air_friction,
879 const float liquid_friction,
880 const float origin_jitter,
881 const float velocity_jitter,
882 const float lifetime)
886 // Set the particle texture based on the value of cl_particles_quake; defaulting to the GLQuake disc
887 if (cl_particles_quake.integer == 2)
888 texture = tex_square;
890 texture = tex_particle;
892 return CL_NewParticle(
894 pt_alphastatic, // type
914 true, // quality reduction
917 PBLEND_ALPHA, // blend mode
918 PARTICLE_BILLBOARD, // orientation
932 static void CL_ImmediateBloodStain(particle_t *part)
937 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
938 if (part->staintexnum >= 0 && cl_decals.integer)
940 VectorCopy(part->vel, v);
942 staintex = part->staintexnum;
943 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);
946 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
947 if (part->typeindex == pt_blood && cl_decals.integer)
949 VectorCopy(part->vel, v);
951 staintex = tex_blooddecal[rand()&7];
952 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);
956 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
959 entity_render_t *ent = &cl.entities[hitent].render;
960 unsigned char color[3];
961 if (!cl_decals.integer)
963 if (!ent->allowdecals)
966 l2 = (int)lhrandom(0.5, 256.5);
968 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
969 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
970 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
973 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);
975 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);
978 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
985 int besthitent = 0, hitent;
988 for (i = 0;i < 32;i++)
991 VectorMA(org, maxdist, org2, org2);
992 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
993 // take the closest trace result that doesn't end up hitting a NOMARKS
994 // surface (sky for example)
995 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
997 bestfrac = trace.fraction;
999 VectorCopy(trace.endpos, bestorg);
1000 VectorCopy(trace.plane.normal, bestnormal);
1004 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
1007 // generates a cubemap name with prefix flags based on info flags (for now only `!`)
1008 static char *LightCubemapNumToName(char *vabuf, size_t vasize, int lightcubemapnum, int flags)
1010 if (lightcubemapnum <= 0)
1012 // `!` is prepended if the cubemap must be nearest-filtered
1013 if (flags & PARTICLEEFFECT_FORCENEAREST)
1014 return va(vabuf, vasize, "!cubemaps/%i", lightcubemapnum);
1015 return va(vabuf, vasize, "cubemaps/%i", lightcubemapnum);
1018 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
1019 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
1020 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, qbool spawndlight, qbool spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qbool wanttrail);
1021 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, qbool spawndlight, qbool spawnparticles, qbool wanttrail)
1024 matrix4x4_t lightmatrix;
1027 VectorLerp(originmins, 0.5, originmaxs, center);
1028 Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
1029 if (effectnameindex == EFFECT_SVC_PARTICLE)
1031 if (cl_particles.integer)
1033 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
1035 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1036 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
1037 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1040 count *= cl_particles_quality.value;
1041 for (;count > 0;count--)
1043 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
1044 CL_NewQuakeParticle(
1049 lhrandom(originmins[0], originmaxs[0]), // offset x
1050 lhrandom(originmins[1], originmaxs[1]), // offset y
1051 lhrandom(originmins[2], originmaxs[2]), // offset z
1052 lhrandom(velocitymins[0], velocitymaxs[0]), // velocity offset x
1053 lhrandom(velocitymins[1], velocitymaxs[1]), // velocity offset y
1054 lhrandom(velocitymins[2], velocitymaxs[2]), // velocity offset z
1056 0, // liquid friction
1058 3, // velocity jitter
1059 lhrandom(0.1, 0.4) // lifetime
1065 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
1066 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1067 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
1068 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1069 else if (effectnameindex == EFFECT_TE_SPIKE)
1071 if (cl_particles_bulletimpacts.integer)
1073 if (cl_particles_quake.integer)
1075 if (cl_particles_smoke.integer)
1076 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1080 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1081 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1082 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);
1086 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1087 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1089 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
1091 if (cl_particles_bulletimpacts.integer)
1093 if (cl_particles_quake.integer)
1095 if (cl_particles_smoke.integer)
1096 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1100 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1101 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1102 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);
1106 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1107 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1108 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1110 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1112 if (cl_particles_bulletimpacts.integer)
1114 if (cl_particles_quake.integer)
1116 if (cl_particles_smoke.integer)
1117 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1121 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1122 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1123 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);
1127 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1128 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1130 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1132 if (cl_particles_bulletimpacts.integer)
1134 if (cl_particles_quake.integer)
1136 if (cl_particles_smoke.integer)
1137 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1141 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1142 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1143 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);
1147 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1148 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1149 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1151 else if (effectnameindex == EFFECT_TE_BLOOD)
1153 if (!cl_particles_blood.integer)
1155 if (cl_particles_quake.integer)
1156 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1159 static double bloodaccumulator = 0;
1160 qbool immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1161 //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);
1162 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1163 for (;bloodaccumulator > 0;bloodaccumulator--)
1165 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);
1166 if (immediatebloodstain && part)
1168 immediatebloodstain = false;
1169 CL_ImmediateBloodStain(part);
1174 else if (effectnameindex == EFFECT_TE_SPARK)
1175 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1176 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1178 // plasma scorch mark
1179 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1180 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1181 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1183 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1185 if (cl_particles_bulletimpacts.integer)
1187 if (cl_particles_quake.integer)
1188 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1191 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1192 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1193 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);
1197 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1198 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1200 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1202 if (cl_particles_bulletimpacts.integer)
1204 if (cl_particles_quake.integer)
1205 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1208 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1209 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1210 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);
1214 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1215 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1216 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1218 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1220 CL_ParticleExplosion(center);
1221 CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1223 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1225 CL_ParticleExplosion(center);
1226 CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1228 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1230 if (cl_particles_quake.integer)
1233 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1236 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);
1238 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);
1242 CL_ParticleExplosion(center);
1243 CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1245 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1246 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1247 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1249 count *= cl_particles_quality.value;
1251 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);
1253 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1255 float i, j, inc, vel;
1258 inc = 8 / cl_particles_quality.value;
1259 for (i = -128;i < 128;i += inc)
1261 for (j = -128;j < 128;j += inc)
1263 dir[0] = j + lhrandom(0, inc);
1264 dir[1] = i + lhrandom(0, inc);
1266 org[0] = center[0] + dir[0];
1267 org[1] = center[1] + dir[1];
1268 org[2] = center[2] + lhrandom(0, 64);
1269 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1270 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);
1274 else if (effectnameindex == EFFECT_TE_TELEPORT)
1276 float i, j, k, inc, vel;
1279 if (cl_particles_quake.integer)
1280 inc = 4 / cl_particles_quality.value;
1282 inc = 8 / cl_particles_quality.value;
1283 for (i = -16;i < 16;i += inc)
1285 for (j = -16;j < 16;j += inc)
1287 for (k = -24;k < 32;k += inc)
1289 VectorSet(dir, i*8, j*8, k*8);
1290 VectorNormalize(dir);
1291 vel = lhrandom(50, 113);
1292 if (cl_particles_quake.integer)
1293 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);
1295 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);
1299 if (!cl_particles_quake.integer)
1300 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);
1301 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1303 else if (effectnameindex == EFFECT_TE_TEI_G3)
1304 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);
1305 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1307 if (cl_particles_smoke.integer)
1309 count *= 0.25f * cl_particles_quality.value;
1311 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);
1314 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1316 CL_ParticleExplosion(center);
1317 CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, NULL, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1319 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1322 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1323 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1324 if (cl_particles_smoke.integer)
1325 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1326 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);
1327 if (cl_particles_sparks.integer)
1328 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1329 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);
1330 CL_AllocLightFlash(NULL, &lightmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1332 else if (effectnameindex == EFFECT_EF_FLAME)
1334 if (!spawnparticles)
1336 count *= 300 * cl_particles_quality.value;
1338 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);
1339 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1341 else if (effectnameindex == EFFECT_EF_STARDUST)
1343 if (!spawnparticles)
1345 count *= 200 * cl_particles_quality.value;
1347 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);
1348 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1350 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1354 int smoke, blood, bubbles, r, color, spawnedcount;
1356 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1359 Vector4Set(light, 0, 0, 0, 0);
1361 if (effectnameindex == EFFECT_TR_ROCKET)
1362 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1363 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1365 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1366 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1368 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1370 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1371 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1375 matrix4x4_t traillightmatrix;
1376 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1377 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1378 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1382 if (!spawnparticles)
1385 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1388 VectorSubtract(originmaxs, originmins, dir);
1389 len = VectorNormalizeLength(dir);
1393 dec = -ent->persistent.trail_time;
1394 ent->persistent.trail_time += len;
1395 if (ent->persistent.trail_time < 0.01f)
1398 // if we skip out, leave it reset
1399 ent->persistent.trail_time = 0.0f;
1404 // advance into this frame to reach the first puff location
1405 VectorMA(originmins, dec, dir, pos);
1408 smoke = cl_particles.integer && cl_particles_smoke.integer;
1409 blood = cl_particles.integer && cl_particles_blood.integer;
1410 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1411 qd = 1.0f / cl_particles_quality.value;
1414 while (len >= 0 && ++spawnedcount <= 16384)
1419 if (effectnameindex == EFFECT_TR_BLOOD)
1421 if (cl_particles_quake.integer)
1423 color = particlepalette[67 + (rand()&3)];
1424 CL_NewQuakeParticle(center, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1429 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);
1432 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1434 if (cl_particles_quake.integer)
1437 color = particlepalette[67 + (rand()&3)];
1438 CL_NewQuakeParticle(center, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1443 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);
1449 if (effectnameindex == EFFECT_TR_ROCKET)
1451 if (cl_particles_quake.integer)
1454 color = particlepalette[ramp3[r]];
1455 CL_NewQuakeParticle(center, color, color, -0.10, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1459 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);
1460 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);
1463 else if (effectnameindex == EFFECT_TR_GRENADE)
1465 if (cl_particles_quake.integer)
1468 color = particlepalette[ramp3[r]];
1469 CL_NewQuakeParticle(center, color, color, -0.15, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1473 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);
1476 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1478 if (cl_particles_quake.integer)
1481 color = particlepalette[52 + (rand()&7)];
1482 CL_NewQuakeParticle(center, color, color, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, 0.5);
1483 CL_NewQuakeParticle(center, color, color, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, 0.5);
1485 else if (gamemode == GAME_GOODVSBAD2)
1488 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);
1492 color = particlepalette[20 + (rand()&7)];
1493 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);
1496 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1498 if (cl_particles_quake.integer)
1501 color = particlepalette[230 + (rand()&7)];
1502 CL_NewQuakeParticle(center, color, color, 0, pos[0], pos[1], pos[2], 30 * dir[1], 30 * -dir[0], 0, 0, 0, 0, 0, 0.5);
1503 CL_NewQuakeParticle(center, color, color, 0, pos[0], pos[1], pos[2], 30 * -dir[1], 30 * dir[0], 0, 0, 0, 0, 0, 0.5);
1507 color = particlepalette[226 + (rand()&7)];
1508 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);
1511 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1513 if (cl_particles_quake.integer)
1515 color = particlepalette[152 + (rand()&3)];
1516 CL_NewQuakeParticle(center, color, color, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, 0.3);
1518 else if (gamemode == GAME_GOODVSBAD2)
1521 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);
1523 else if (gamemode == GAME_PRYDON)
1526 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);
1529 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);
1531 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1534 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);
1536 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1539 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);
1541 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1542 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);
1546 if (effectnameindex == EFFECT_TR_ROCKET)
1547 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);
1548 else if (effectnameindex == EFFECT_TR_GRENADE)
1549 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);
1551 // advance to next time and position
1554 VectorMA (pos, dec, dir, pos);
1557 ent->persistent.trail_time = len;
1560 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1563 // this is also called on point effects with spawndlight = true and
1564 // spawnparticles = true
1565 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, qbool spawndlight, qbool spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qbool wanttrail)
1567 qbool found = false;
1569 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1571 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1572 return; // no such effect
1574 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1576 int effectinfoindex;
1579 particleeffectinfo_t *info;
1592 qbool immediatebloodstain;
1594 float avgtint[4], tint[4], tintlerp;
1595 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1596 VectorLerp(originmins, 0.5, originmaxs, center);
1597 supercontents = CL_PointSuperContents(center);
1598 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1599 VectorSubtract(originmaxs, originmins, traildir);
1600 traillen = VectorLength(traildir);
1601 VectorNormalize(traildir);
1604 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1608 Vector4Set(avgtint, 1, 1, 1, 1);
1610 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1612 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1614 qbool definedastrail = info->trailspacing > 0;
1616 qbool drawastrail = wanttrail;
1617 if (cl_particles_forcetraileffects.integer)
1618 drawastrail = drawastrail || definedastrail;
1621 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1623 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1626 // spawn a dlight if requested
1627 if (info->lightradiusstart > 0 && spawndlight)
1629 matrix4x4_t tempmatrix;
1631 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1633 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1634 if (info->lighttime > 0 && info->lightradiusfade > 0)
1636 // light flash (explosion, etc)
1637 // called when effect starts
1638 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, LightCubemapNumToName(vabuf, sizeof(vabuf), info->lightcubemapnum, info->flags), -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1640 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1643 // called by CL_LinkNetworkEntity
1644 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1645 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1646 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1647 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1648 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, LightCubemapNumToName(vabuf, sizeof(vabuf), info->lightcubemapnum, info->flags), info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1649 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1653 if (!spawnparticles)
1658 if (info->tex[1] > info->tex[0])
1660 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1661 tex = min(tex, info->tex[1] - 1);
1663 if(info->staintex[0] < 0)
1664 staintex = info->staintex[0];
1667 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1668 staintex = min(staintex, info->staintex[1] - 1);
1670 if (info->particletype == pt_decal)
1672 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1673 AnglesFromVectors(angles, velocity, NULL, false);
1674 AngleVectors(angles, forward, right, up);
1675 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1677 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]);
1679 else if (info->orientation == PARTICLE_HBEAM)
1684 AnglesFromVectors(angles, traildir, NULL, false);
1685 AngleVectors(angles, forward, right, up);
1686 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1688 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);
1693 if (!cl_particles.integer)
1695 switch (info->particletype)
1697 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1698 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1699 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1700 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1701 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1702 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1706 cnt = info->countabsolute;
1707 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1708 // if drawastrail is not set, we will
1709 // use the regular cnt-based random
1710 // particle spawning at the center; so
1711 // do NOT apply trailspacing then!
1712 if (drawastrail && definedastrail)
1713 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1716 continue; // nothing to draw
1717 info->particleaccumulator += cnt;
1719 if (drawastrail || definedastrail)
1720 immediatebloodstain = false;
1722 immediatebloodstain =
1723 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1725 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1729 VectorCopy(originmins, trailpos);
1730 trailstep = traillen / cnt;
1734 VectorCopy(center, trailpos);
1740 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1741 AnglesFromVectors(angles, velocity, NULL, false);
1744 AnglesFromVectors(angles, traildir, NULL, false);
1746 AngleVectors(angles, forward, right, up);
1747 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1748 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1749 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1750 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1752 if (info->tex[1] > info->tex[0])
1754 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1755 tex = min(tex, info->tex[1] - 1);
1757 if (!(drawastrail || definedastrail))
1759 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1760 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1761 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1765 tintlerp = lhrandom(0, 1);
1766 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1769 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);
1770 if (immediatebloodstain && part)
1772 immediatebloodstain = false;
1773 CL_ImmediateBloodStain(part);
1776 VectorMA(trailpos, trailstep, traildir, trailpos);
1783 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1786 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, qbool spawndlight, qbool spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1788 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1791 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, qbool spawndlight, qbool spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1793 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1796 // note: this one ONLY does boxes!
1797 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)
1799 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1807 void CL_EntityParticles (const entity_t *ent)
1810 vec_t pitch, yaw, dist = 64, beamlength = 16;
1812 static vec3_t avelocities[NUMVERTEXNORMALS];
1813 if (!cl_particles.integer) return;
1814 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1816 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1818 if (!avelocities[0][0])
1819 for (i = 0;i < NUMVERTEXNORMALS;i++)
1820 for (j = 0;j < 3;j++)
1821 avelocities[i][j] = lhrandom(0, 2.55);
1823 for (i = 0;i < NUMVERTEXNORMALS;i++)
1825 yaw = cl.time * avelocities[i][0];
1826 pitch = cl.time * avelocities[i][1];
1827 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1828 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1829 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1830 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);
1835 void CL_ReadPointFile_f(cmd_state_t *cmd)
1837 double org[3], leakorg[3];
1840 char *pointfile = NULL, *pointfilepos, *t, tchar;
1841 char name[MAX_QPATH];
1846 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1847 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1850 Con_Printf("Could not open %s\n", name);
1854 Con_Printf("Reading %s...\n", name);
1855 VectorClear(leakorg);
1858 pointfilepos = pointfile;
1859 while (*pointfilepos)
1861 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1866 while (*t && *t != '\n' && *t != '\r')
1870 #if _MSC_VER >= 1400
1871 #define sscanf sscanf_s
1873 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1874 VectorCopy(org, vecorg);
1880 VectorCopy(org, leakorg);
1883 if (cl.num_particles < cl.max_particles - 3)
1886 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);
1889 Mem_Free(pointfile);
1890 VectorCopy(leakorg, vecorg);
1891 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1898 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);
1899 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);
1900 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);
1905 CL_ParseParticleEffect
1907 Parse an effect out of the server message
1910 void CL_ParseParticleEffect (void)
1913 int i, count, msgcount, color;
1915 MSG_ReadVector(&cl_message, org, cls.protocol);
1916 for (i=0 ; i<3 ; i++)
1917 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1918 msgcount = MSG_ReadByte(&cl_message);
1919 color = MSG_ReadByte(&cl_message);
1921 if (msgcount == 255)
1926 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1931 CL_ParticleExplosion
1935 void CL_ParticleExplosion (const vec3_t org)
1939 particle_t *particle;
1941 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1942 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1944 if (cl_particles_quake.integer)
1946 for (i = 0; i < 1024; i++)
1953 color = particlepalette[ramp1[r]];
1955 particle = CL_NewQuakeParticle(
1959 org[0], org[1], org[2], // offset
1960 0, 0, 0, // velocity
1962 0, // liquid friction
1963 16, // origin jitter
1964 256, // velocity jitter
1967 particle->typeindex = pt_explode;
1971 color = particlepalette[ramp2[r]];
1973 particle = CL_NewQuakeParticle(
1977 org[0], org[1], org[2], // offset
1978 0, 0, 0, // velocity
1980 0, // liquid friction
1981 16, // origin jitter
1982 256, // velocity jitter
1986 particle->typeindex = pt_explode2;
1989 particle->time2 = r; // time2 is used to progress the colour ramp index
1994 i = CL_PointSuperContents(org);
1995 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1997 if (cl_particles.integer && cl_particles_bubbles.integer)
1998 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1999 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);
2003 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
2005 for (i = 0;i < 512 * cl_particles_quality.value;i++)
2012 VectorMA(org, 128, v2, v);
2013 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
2015 while (k++ < 16 && trace.fraction < 0.1f);
2016 VectorSubtract(trace.endpos, org, v2);
2017 VectorScale(v2, 2.0f, v2);
2018 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);
2024 if (cl_particles_explosions_shell.integer)
2025 R_NewExplosion(org);
2030 CL_ParticleExplosion2
2034 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
2037 if (!cl_particles.integer) return;
2039 for (i = 0;i < 512 * cl_particles_quality.value;i++)
2041 k = particlepalette[colorStart + (i % colorLength)];
2042 if (cl_particles_quake.integer)
2043 CL_NewQuakeParticle(org, k, k, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, 0.3);
2045 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);
2049 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
2052 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
2053 if (cl_particles_sparks.integer)
2055 sparkcount *= cl_particles_quality.value;
2056 while(sparkcount-- > 0)
2057 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);
2061 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
2064 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
2065 if (cl_particles_smoke.integer)
2067 smokecount *= cl_particles_quality.value;
2068 while(smokecount-- > 0)
2069 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);
2073 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)
2077 if (!cl_particles.integer) return;
2078 VectorMAM(0.5f, mins, 0.5f, maxs, center);
2080 count = (int)(count * cl_particles_quality.value);
2083 k = particlepalette[colorbase + (rand()&3)];
2084 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);
2088 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
2091 float minz, maxz, lifetime = 30;
2092 float particle_size;
2095 if (!cl_particles.integer) return;
2096 if (dir[2] < 0) // falling
2098 minz = maxs[2] + dir[2] * 0.1;
2101 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
2106 maxz = maxs[2] + dir[2] * 0.1;
2108 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
2111 count = (int)(count * cl_particles_quality.value);
2116 if (!cl_particles_rain.integer) break;
2118 count *= 4; // ick, this should be in the mod or maps?
2119 particle_size = (gamemode == GAME_GOODVSBAD2) ? 20 : 0.5;
2123 k = particlepalette[colorbase + (rand()&3)];
2124 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
2125 CL_NewParticle(org, pt_rain, k, k, tex_particle, particle_size, 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);
2129 if (!cl_particles_snow.integer) break;
2131 particle_size = (gamemode == GAME_GOODVSBAD2) ? 20 : 1.0;
2135 k = particlepalette[colorbase + (rand()&3)];
2136 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
2137 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);
2141 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
2145 cvar_t r_drawparticles = {CF_CLIENT, "r_drawparticles", "1", "enables drawing of particles"};
2146 static cvar_t r_drawparticles_drawdistance = {CF_CLIENT | CF_ARCHIVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
2147 static cvar_t r_drawparticles_nearclip_min = {CF_CLIENT | CF_ARCHIVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
2148 static cvar_t r_drawparticles_nearclip_max = {CF_CLIENT | CF_ARCHIVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
2149 cvar_t r_drawdecals = {CF_CLIENT, "r_drawdecals", "1", "enables drawing of decals"};
2150 static cvar_t r_drawdecals_drawdistance = {CF_CLIENT | CF_ARCHIVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
2152 #define PARTICLETEXTURESIZE 64
2153 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2155 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2159 dz = 1 - (dx*dx+dy*dy);
2160 if (dz > 0) // it does hit the sphere
2164 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2165 VectorNormalize(normal);
2166 dot = DotProduct(normal, light);
2167 if (dot > 0.5) // interior reflection
2168 f += ((dot * 2) - 1);
2169 else if (dot < -0.5) // exterior reflection
2170 f += ((dot * -2) - 1);
2172 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2173 VectorNormalize(normal);
2174 dot = DotProduct(normal, light);
2175 if (dot > 0.5) // interior reflection
2176 f += ((dot * 2) - 1);
2177 else if (dot < -0.5) // exterior reflection
2178 f += ((dot * -2) - 1);
2180 f += 16; // just to give it a haze so you can see the outline
2181 f = bound(0, f, 255);
2182 return (unsigned char) f;
2188 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2189 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2191 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2192 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2193 *width = particlefontcellwidth;
2194 *height = particlefontcellheight;
2197 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2199 int basex, basey, w, h, y;
2200 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2201 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2202 Sys_Error("invalid particle texture size for autogenerating");
2203 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2204 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2207 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2210 float cx, cy, dx, dy, f, iradius;
2212 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2213 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2214 iradius = 1.0f / radius;
2215 alpha *= (1.0f / 255.0f);
2216 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2218 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2222 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2227 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2228 d[0] += (int)(f * (blue - d[0]));
2229 d[1] += (int)(f * (green - d[1]));
2230 d[2] += (int)(f * (red - d[2]));
2237 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2240 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2242 data[0] = bound(minb, data[0], maxb);
2243 data[1] = bound(ming, data[1], maxg);
2244 data[2] = bound(minr, data[2], maxr);
2249 static void particletextureinvert(unsigned char *data)
2252 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2254 data[0] = 255 - data[0];
2255 data[1] = 255 - data[1];
2256 data[2] = 255 - data[2];
2260 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2261 static void R_InitBloodTextures (unsigned char *particletexturedata)
2264 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2265 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2268 for (i = 0;i < 8;i++)
2270 memset(data, 255, datasize);
2271 for (k = 0;k < 24;k++)
2272 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2273 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2274 particletextureinvert(data);
2275 setuptex(tex_bloodparticle[i], data, particletexturedata);
2279 for (i = 0;i < 8;i++)
2281 memset(data, 255, datasize);
2283 for (j = 1;j < 10;j++)
2284 for (k = min(j, m - 1);k < m;k++)
2285 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2286 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2287 particletextureinvert(data);
2288 setuptex(tex_blooddecal[i], data, particletexturedata);
2294 //uncomment this to make engine save out particle font to a tga file when run
2295 //#define DUMPPARTICLEFONT
2297 static void R_InitParticleTexture (void)
2299 int x, y, d, i, k, m;
2300 int basex, basey, w, h;
2301 float dx, dy, f, s1, t1, s2, t2;
2304 fs_offset_t filesize;
2305 char texturename[MAX_QPATH];
2308 // a note: decals need to modulate (multiply) the background color to
2309 // properly darken it (stain), and they need to be able to alpha fade,
2310 // this is a very difficult challenge because it means fading to white
2311 // (no change to background) rather than black (darkening everything
2312 // behind the whole decal polygon), and to accomplish this the texture is
2313 // inverted (dark red blood on white background becomes brilliant cyan
2314 // and white on black background) so we can alpha fade it to black, then
2315 // we invert it again during the blendfunc to make it work...
2317 #ifndef DUMPPARTICLEFONT
2318 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false, false);
2321 particlefonttexture = decalskinframe->base;
2322 // TODO maybe allow custom grid size?
2323 particlefontwidth = image_width;
2324 particlefontheight = image_height;
2325 particlefontcellwidth = image_width / 8;
2326 particlefontcellheight = image_height / 8;
2327 particlefontcols = 8;
2328 particlefontrows = 8;
2333 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2334 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2335 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2336 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2337 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2339 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2340 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2341 particlefontcols = 8;
2342 particlefontrows = 8;
2344 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2347 for (i = 0;i < 8;i++)
2349 memset(data, 255, datasize);
2352 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2353 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2355 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2357 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2358 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2360 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2361 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2363 d = (int)(d * (1-(dx*dx+dy*dy)));
2364 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2365 d = bound(0, d, 255);
2366 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2373 setuptex(tex_smoke[i], data, particletexturedata);
2377 memset(data, 255, datasize);
2378 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2380 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2381 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2383 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2384 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2385 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2388 setuptex(tex_rainsplash, data, particletexturedata);
2391 memset(data, 255, datasize);
2392 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2394 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2395 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2397 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2398 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2399 d = bound(0, d, 255);
2400 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2403 setuptex(tex_particle, data, particletexturedata);
2406 memset(data, 255, datasize);
2407 light[0] = 1;light[1] = 1;light[2] = 1;
2408 VectorNormalize(light);
2409 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2411 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2412 // stretch upper half of bubble by +50% and shrink lower half by -50%
2413 // (this gives an elongated teardrop shape)
2415 dy = (dy - 0.5f) * 2.0f;
2417 dy = (dy - 0.5f) / 1.5f;
2418 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2420 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2421 // shrink bubble width to half
2423 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2426 setuptex(tex_raindrop, data, particletexturedata);
2429 memset(data, 255, datasize);
2430 light[0] = 1;light[1] = 1;light[2] = 1;
2431 VectorNormalize(light);
2432 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2434 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2435 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2437 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2438 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2441 setuptex(tex_bubble, data, particletexturedata);
2443 // Blood particles and blood decals
2444 R_InitBloodTextures (particletexturedata);
2447 for (i = 0;i < 8;i++)
2449 memset(data, 255, datasize);
2450 for (k = 0;k < 12;k++)
2451 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2452 for (k = 0;k < 3;k++)
2453 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2454 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2455 particletextureinvert(data);
2456 setuptex(tex_bulletdecal[i], data, particletexturedata);
2459 #ifdef DUMPPARTICLEFONT
2460 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2463 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, 0, 0, 0, false);
2464 particlefonttexture = decalskinframe->base;
2466 Mem_Free(particletexturedata);
2471 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2473 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2474 particletexture[i].texture = particlefonttexture;
2475 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2476 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2477 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2478 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2481 #ifndef DUMPPARTICLEFONT
2482 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2483 if (!particletexture[tex_beam].texture)
2486 unsigned char noise3[64][64], data2[64][16][4];
2488 fractalnoise(&noise3[0][0], 64, 4);
2490 for (y = 0;y < 64;y++)
2492 dy = (y - 0.5f*64) / (64*0.5f-1);
2493 for (x = 0;x < 16;x++)
2495 dx = (x - 0.5f*16) / (16*0.5f-2);
2496 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2497 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2498 data2[y][x][3] = 255;
2502 #ifdef DUMPPARTICLEFONT
2503 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2505 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2507 particletexture[tex_beam].s1 = 0;
2508 particletexture[tex_beam].t1 = 0;
2509 particletexture[tex_beam].s2 = 1;
2510 particletexture[tex_beam].t2 = 1;
2512 // now load an texcoord/texture override file
2513 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2520 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2522 if(!strcmp(com_token, "\n"))
2523 continue; // empty line
2524 i = atoi(com_token);
2532 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2534 dp_strlcpy(texturename, com_token, sizeof(texturename));
2535 s1 = atof(com_token);
2536 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2539 t1 = atof(com_token);
2540 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2542 s2 = atof(com_token);
2543 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2545 t2 = atof(com_token);
2546 dp_strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2547 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2548 dp_strlcpy(texturename, com_token, sizeof(texturename));
2555 if (!texturename[0])
2557 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2560 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2562 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2565 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, true); // note: this loads as sRGB if sRGB is active!
2566 particletexture[i].texture = sf->base;
2567 particletexture[i].s1 = s1;
2568 particletexture[i].t1 = t1;
2569 particletexture[i].s2 = s2;
2570 particletexture[i].t2 = t2;
2576 static void r_part_start(void)
2579 // generate particlepalette for convenience from the main one
2580 for (i = 0;i < 256;i++)
2581 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2582 particletexturepool = R_AllocTexturePool();
2583 R_InitParticleTexture ();
2584 CL_Particles_LoadEffectInfo(NULL);
2587 static void r_part_shutdown(void)
2589 R_FreeTexturePool(&particletexturepool);
2592 static void r_part_newmap(void)
2595 R_SkinFrame_MarkUsed(decalskinframe);
2596 CL_Particles_LoadEffectInfo(NULL);
2599 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2600 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2602 void R_Particles_Init (void)
2605 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2607 particle_elements[i*6+0] = i*4+0;
2608 particle_elements[i*6+1] = i*4+1;
2609 particle_elements[i*6+2] = i*4+2;
2610 particle_elements[i*6+3] = i*4+0;
2611 particle_elements[i*6+4] = i*4+2;
2612 particle_elements[i*6+5] = i*4+3;
2615 Cvar_RegisterVariable(&r_drawparticles);
2616 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2617 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2618 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2619 Cvar_RegisterVariable(&r_drawdecals);
2620 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2621 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2624 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2626 vec3_t vecorg, vecvel, baseright, baseup;
2627 int surfacelistindex;
2628 int batchstart, batchcount;
2629 const particle_t *p;
2631 rtexture_t *texture;
2632 float *v3f, *t2f, *c4f;
2633 particletexture_t *tex;
2634 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2635 // float ambient[3], diffuse[3], diffusenormal[3];
2636 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2637 vec4_t colormultiplier;
2638 float minparticledist_start, minparticledist_end;
2641 RSurf_ActiveModelEntity(r_refdef.scene.worldentity, false, false, false);
2643 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));
2645 r_refdef.stats[r_stat_particles] += numsurfaces;
2646 // R_Mesh_ResetTextureState();
2647 GL_DepthMask(false);
2648 GL_DepthRange(0, 1);
2649 GL_PolygonOffset(0, 0);
2651 GL_CullFace(GL_NONE);
2653 spintime = r_refdef.scene.time;
2655 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2656 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2657 dofade = (minparticledist_start < minparticledist_end);
2659 // first generate all the vertices at once
2660 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2662 p = cl.particles + surfacelist[surfacelistindex];
2664 blendmode = (pblend_t)p->blendmode;
2666 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2667 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2668 alpha = palpha * colormultiplier[3];
2669 // ensure alpha multiplier saturates properly
2675 case PBLEND_INVALID:
2677 // additive and modulate can just fade out in fog (this is correct)
2678 if (r_refdef.fogenabled)
2679 alpha *= RSurf_FogVertex(p->org);
2680 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2681 alpha *= 1.0f / 256.0f;
2682 c4f[0] = p->color[0] * alpha;
2683 c4f[1] = p->color[1] * alpha;
2684 c4f[2] = p->color[2] * alpha;
2688 // additive and modulate can just fade out in fog (this is correct)
2689 if (r_refdef.fogenabled)
2690 alpha *= RSurf_FogVertex(p->org);
2691 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2692 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2693 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2694 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2698 c4f[0] = p->color[0] * colormultiplier[0];
2699 c4f[1] = p->color[1] * colormultiplier[1];
2700 c4f[2] = p->color[2] * colormultiplier[2];
2702 // note: lighting is not cheap!
2703 if (particletype[p->typeindex].lighting)
2705 float a[3], c[3], dir[3];
2706 vecorg[0] = p->org[0];
2707 vecorg[1] = p->org[1];
2708 vecorg[2] = p->org[2];
2709 R_CompleteLightPoint(a, c, dir, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT, r_refdef.scene.lightmapintensity, r_refdef.scene.ambientintensity);
2710 c4f[0] = p->color[0] * colormultiplier[0] * (a[0] + 0.25f * c[0]);
2711 c4f[1] = p->color[1] * colormultiplier[1] * (a[1] + 0.25f * c[1]);
2712 c4f[2] = p->color[2] * colormultiplier[2] * (a[2] + 0.25f * c[2]);
2714 // mix in the fog color
2715 if (r_refdef.fogenabled)
2717 fog = RSurf_FogVertex(p->org);
2719 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2720 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2721 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2723 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2724 VectorScale(c4f, alpha, c4f);
2727 // copy the color into the other three vertices
2728 Vector4Copy(c4f, c4f + 4);
2729 Vector4Copy(c4f, c4f + 8);
2730 Vector4Copy(c4f, c4f + 12);
2732 size = p->size * cl_particles_size.value;
2733 tex = &particletexture[p->texnum];
2734 switch(p->orientation)
2736 // case PARTICLE_INVALID:
2737 case PARTICLE_BILLBOARD:
2738 if (p->angle + p->spin)
2740 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2741 spinsin = sin(spinrad) * size;
2742 spincos = cos(spinrad) * size;
2743 spinm1 = -p->stretch * spincos;
2746 spinm4 = -p->stretch * spincos;
2747 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2748 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2752 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2753 VectorScale(r_refdef.view.up, size, up);
2756 v3f[ 0] = p->org[0] - right[0] - up[0];
2757 v3f[ 1] = p->org[1] - right[1] - up[1];
2758 v3f[ 2] = p->org[2] - right[2] - up[2];
2759 v3f[ 3] = p->org[0] - right[0] + up[0];
2760 v3f[ 4] = p->org[1] - right[1] + up[1];
2761 v3f[ 5] = p->org[2] - right[2] + up[2];
2762 v3f[ 6] = p->org[0] + right[0] + up[0];
2763 v3f[ 7] = p->org[1] + right[1] + up[1];
2764 v3f[ 8] = p->org[2] + right[2] + up[2];
2765 v3f[ 9] = p->org[0] + right[0] - up[0];
2766 v3f[10] = p->org[1] + right[1] - up[1];
2767 v3f[11] = p->org[2] + right[2] - up[2];
2768 t2f[0] = tex->s1;t2f[1] = tex->t2;
2769 t2f[2] = tex->s1;t2f[3] = tex->t1;
2770 t2f[4] = tex->s2;t2f[5] = tex->t1;
2771 t2f[6] = tex->s2;t2f[7] = tex->t2;
2773 case PARTICLE_ORIENTED_DOUBLESIDED:
2774 vecvel[0] = p->vel[0];
2775 vecvel[1] = p->vel[1];
2776 vecvel[2] = p->vel[2];
2777 VectorVectors(vecvel, baseright, baseup);
2778 if (p->angle + p->spin)
2780 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2781 spinsin = sin(spinrad) * size;
2782 spincos = cos(spinrad) * size;
2783 spinm1 = p->stretch * spincos;
2786 spinm4 = p->stretch * spincos;
2787 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2788 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2792 VectorScale(baseright, size * p->stretch, right);
2793 VectorScale(baseup, size, up);
2795 v3f[ 0] = p->org[0] - right[0] - up[0];
2796 v3f[ 1] = p->org[1] - right[1] - up[1];
2797 v3f[ 2] = p->org[2] - right[2] - up[2];
2798 v3f[ 3] = p->org[0] - right[0] + up[0];
2799 v3f[ 4] = p->org[1] - right[1] + up[1];
2800 v3f[ 5] = p->org[2] - right[2] + up[2];
2801 v3f[ 6] = p->org[0] + right[0] + up[0];
2802 v3f[ 7] = p->org[1] + right[1] + up[1];
2803 v3f[ 8] = p->org[2] + right[2] + up[2];
2804 v3f[ 9] = p->org[0] + right[0] - up[0];
2805 v3f[10] = p->org[1] + right[1] - up[1];
2806 v3f[11] = p->org[2] + right[2] - up[2];
2807 t2f[0] = tex->s1;t2f[1] = tex->t2;
2808 t2f[2] = tex->s1;t2f[3] = tex->t1;
2809 t2f[4] = tex->s2;t2f[5] = tex->t1;
2810 t2f[6] = tex->s2;t2f[7] = tex->t2;
2812 case PARTICLE_SPARK:
2813 len = VectorLength(p->vel);
2814 VectorNormalize2(p->vel, up);
2815 lenfactor = p->stretch * 0.04 * len;
2816 if(lenfactor < size * 0.5)
2817 lenfactor = size * 0.5;
2818 VectorMA(p->org, -lenfactor, up, v);
2819 VectorMA(p->org, lenfactor, up, up2);
2820 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2821 t2f[0] = tex->s1;t2f[1] = tex->t2;
2822 t2f[2] = tex->s1;t2f[3] = tex->t1;
2823 t2f[4] = tex->s2;t2f[5] = tex->t1;
2824 t2f[6] = tex->s2;t2f[7] = tex->t2;
2826 case PARTICLE_VBEAM:
2827 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2828 VectorSubtract(p->vel, p->org, up);
2829 VectorNormalize(up);
2830 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2831 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2832 t2f[0] = tex->s2;t2f[1] = v[0];
2833 t2f[2] = tex->s1;t2f[3] = v[0];
2834 t2f[4] = tex->s1;t2f[5] = v[1];
2835 t2f[6] = tex->s2;t2f[7] = v[1];
2837 case PARTICLE_HBEAM:
2838 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2839 VectorSubtract(p->vel, p->org, up);
2840 VectorNormalize(up);
2841 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2842 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2843 t2f[0] = v[0];t2f[1] = tex->t1;
2844 t2f[2] = v[0];t2f[3] = tex->t2;
2845 t2f[4] = v[1];t2f[5] = tex->t2;
2846 t2f[6] = v[1];t2f[7] = tex->t1;
2849 if (r_showparticleedges.integer)
2851 R_DebugLine(v3f, v3f + 3);
2852 R_DebugLine(v3f + 3, v3f + 6);
2853 R_DebugLine(v3f + 6, v3f + 9);
2854 R_DebugLine(v3f + 9, v3f);
2858 // now render batches of particles based on blendmode and texture
2859 blendmode = PBLEND_INVALID;
2863 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2864 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2866 p = cl.particles + surfacelist[surfacelistindex];
2868 if (texture != particletexture[p->texnum].texture)
2870 texture = particletexture[p->texnum].texture;
2871 R_SetupShader_Generic(texture, false, false, false);
2874 if (p->blendmode == PBLEND_INVMOD)
2876 // inverse modulate blend - group these
2877 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2878 // iterate until we find a change in settings
2879 batchstart = surfacelistindex++;
2880 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2882 p = cl.particles + surfacelist[surfacelistindex];
2883 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2889 // additive or alpha blend - group these
2890 // (we can group these because we premultiplied the texture alpha)
2891 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2892 // iterate until we find a change in settings
2893 batchstart = surfacelistindex++;
2894 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2896 p = cl.particles + surfacelist[surfacelistindex];
2897 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2902 batchcount = surfacelistindex - batchstart;
2903 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2907 void R_DrawParticles (void)
2910 int drawparticles = r_drawparticles.integer;
2911 float minparticledist_start;
2913 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2918 float pt_explode_frame_interval, pt_explode2_frame_interval;
2921 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2922 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2924 // Handling of the colour ramp for pt_explode and pt_explode2
2925 pt_explode_frame_interval = frametime * 10;
2926 pt_explode2_frame_interval = frametime * 15;
2928 // LadyHavoc: early out conditions
2929 if (!cl.num_particles)
2932 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2933 gravity = frametime * cl.movevars_gravity;
2934 update = frametime > 0;
2935 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2936 drawdist2 = drawdist2*drawdist2;
2938 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2942 if (cl.free_particle > i)
2943 cl.free_particle = i;
2949 if (p->delayedspawn > cl.time)
2952 p->size += p->sizeincrease * frametime;
2953 p->alpha -= p->alphafade * frametime;
2955 if (p->alpha <= 0 || p->die <= cl.time)
2958 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2960 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2962 if (p->typeindex == pt_blood)
2963 p->size += frametime * 8;
2965 p->vel[2] -= p->gravity * gravity;
2966 f = 1.0f - min(p->liquidfriction * frametime, 1);
2967 VectorScale(p->vel, f, p->vel);
2971 p->vel[2] -= p->gravity * gravity;
2974 f = 1.0f - min(p->airfriction * frametime, 1);
2975 VectorScale(p->vel, f, p->vel);
2979 VectorCopy(p->org, oldorg);
2980 VectorMA(p->org, frametime, p->vel, p->org);
2981 // if (p->bounce && cl.time >= p->delayedcollisions)
2982 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2984 trace = CL_TraceLine(oldorg, p->org, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, 0, collision_extendmovelength.value, true, false, &hitent, false, false);
2985 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2986 // or if the trace hit something flagged as NOIMPACT
2987 // then remove the particle
2988 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2990 VectorCopy(trace.endpos, p->org);
2991 // react if the particle hit something
2992 if (trace.fraction < 1)
2994 VectorCopy(trace.endpos, p->org);
2996 if (p->staintexnum >= 0)
2998 // blood - splash on solid
2999 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
3002 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
3003 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
3004 if (cl_decals.integer)
3006 // create a decal for the blood splat
3007 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
3008 if (cl_decals_newsystem_bloodsmears.integer)
3010 VectorCopy(p->vel, decaldir);
3011 VectorNormalize(decaldir);
3014 VectorCopy(trace.plane.normal, decaldir);
3015 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3020 if (p->typeindex == pt_blood)
3022 // blood - splash on solid
3023 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3025 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3027 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)));
3028 if (cl_decals.integer)
3030 // create a decal for the blood splat
3031 if (cl_decals_newsystem_bloodsmears.integer)
3033 VectorCopy(p->vel, decaldir);
3034 VectorNormalize(decaldir);
3037 VectorCopy(trace.plane.normal, decaldir);
3038 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);
3043 else if (p->bounce < 0)
3045 // bounce -1 means remove on impact
3050 // anything else - bounce off solid
3051 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3052 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3057 if (VectorLength2(p->vel) < 0.03)
3059 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3061 VectorClear(p->vel);
3065 if (p->typeindex != pt_static)
3067 switch (p->typeindex)
3069 case pt_entityparticle:
3070 // particle that removes itself after one rendered frame
3077 a = CL_PointSuperContents(p->org);
3078 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3082 a = CL_PointSuperContents(p->org);
3083 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3087 a = CL_PointSuperContents(p->org);
3088 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
3092 if (cl.time > p->time2)
3095 p->time2 = cl.time + (rand() & 3) * 0.1;
3096 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3097 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3099 a = CL_PointSuperContents(p->org);
3100 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
3103 // Progress the particle colour up the ramp
3104 p->time2 += pt_explode_frame_interval;
3110 color = particlepalette[ramp1[(int)p->time2]];
3111 p->color[0] = color >> 16;
3112 p->color[1] = color >> 8;
3113 p->color[2] = color >> 0;
3118 // Progress the particle colour up the ramp
3119 p->time2 += pt_explode2_frame_interval;
3125 color = particlepalette[ramp2[(int)p->time2]];
3126 p->color[0] = color >> 16;
3127 p->color[1] = color >> 8;
3128 p->color[2] = color >> 0;
3136 else if (p->delayedspawn > cl.time)
3140 // don't render particles too close to the view (they chew fillrate)
3141 // also don't render particles behind the view (useless)
3142 // further checks to cull to the frustum would be too slow here
3143 switch(p->typeindex)
3146 // beams have no culling
3147 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3150 if(cl_particles_visculling.integer)
3151 if (!r_refdef.viewcache.world_novis)
3152 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3154 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3156 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3159 // anything else just has to be in front of the viewer and visible at this distance
3160 if (!r_refdef.view.useperspective || (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)))
3161 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3168 if (cl.free_particle > i)
3169 cl.free_particle = i;
3172 // reduce cl.num_particles if possible
3173 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3176 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3178 particle_t *oldparticles = cl.particles;
3179 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3180 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3181 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3182 Mem_Free(oldparticles);