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];
796 part->airfriction = pairfriction;
797 part->liquidfriction = pliquidfriction;
798 part->die = cl.time + lifetime;
799 part->delayedspawn = cl.time;
800 // part->delayedcollisions = 0;
801 part->qualityreduction = pqualityreduction;
804 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
805 if (part->typeindex == pt_rain)
811 // turn raindrop into simple spark and create delayedspawn splash effect
812 part->typeindex = pt_spark;
814 VectorMA(part->org, lifetime, part->vel, endvec);
815 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
816 part->die = cl.time + lifetime * trace.fraction;
817 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);
820 part2->delayedspawn = part->die;
821 part2->die += part->die - cl.time;
822 for (i = rand() & 7;i < 10;i++)
824 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);
827 part2->delayedspawn = part->die;
828 part2->die += part->die - cl.time;
833 else if (part->typeindex == pt_explode || part->typeindex == pt_explode2)
834 part->time2 = rand()&3; // time2 is used to progress the colour ramp index
837 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
839 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
842 VectorMA(part->org, lifetime, part->vel, endvec);
843 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
844 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
854 * @brief Creates a simple particle, a square like Quake, or a disc like GLQuake
856 * @param[in] origin ?
857 * @param[in] color_1,color_2 Minimum and maximum range of color, randomly interpolated with pcolor2 to decide particle color
858 * @param[in] gravity How much effect gravity has on the particle (0-1)
859 * @param[in] offset_x,offset_y,offset_z Starting origin of particle
860 * @param[in] velocity_offset_x,velocity_offset_y,velocity_offset_z Starting velocity of particle
861 * @param[in] air_friction How much the particle slows down, in air, per second (0-1 typically, can slowdown faster than 1)
862 * @param[in] liquid_friction How much the particle slows down, in liquids, per second (0-1 typically, can slowdown faster than 1)
863 * @param[in] origin_jitter ?
864 * @param[in] velocity_jitter ?
865 * @param[in] lifetime How long the particle can live (note it is also removed if alpha drops to nothing)
867 * @return Pointer to the new particle
869 particle_t *CL_NewQuakeParticle(
871 const unsigned short ptypeindex,
875 const float offset_x,
876 const float offset_y,
877 const float offset_z,
878 const float velocity_offset_x,
879 const float velocity_offset_y,
880 const float velocity_offset_z,
881 const float air_friction,
882 const float liquid_friction,
883 const float origin_jitter,
884 const float velocity_jitter,
885 const float lifetime)
889 // Set the particle texture based on the value of cl_particles_quake; defaulting to the GLQuake disc
890 if (cl_particles_quake.integer == 2)
891 texture = tex_square;
893 texture = tex_particle;
895 return CL_NewParticle(
917 true, // quality reduction
920 PBLEND_ALPHA, // blend mode
921 PARTICLE_BILLBOARD, // orientation
935 static void CL_ImmediateBloodStain(particle_t *part)
940 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
941 if (part->staintexnum >= 0 && cl_decals.integer)
943 VectorCopy(part->vel, v);
945 staintex = part->staintexnum;
946 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);
949 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
950 if (part->typeindex == pt_blood && cl_decals.integer)
952 VectorCopy(part->vel, v);
954 staintex = tex_blooddecal[rand()&7];
955 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);
959 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
962 entity_render_t *ent = &cl.entities[hitent].render;
963 unsigned char color[3];
964 if (!cl_decals.integer)
966 if (!ent->allowdecals)
969 l2 = (int)lhrandom(0.5, 256.5);
971 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
972 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
973 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
976 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);
978 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);
981 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
988 int besthitent = 0, hitent;
991 for (i = 0;i < 32;i++)
994 VectorMA(org, maxdist, org2, org2);
995 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
996 // take the closest trace result that doesn't end up hitting a NOMARKS
997 // surface (sky for example)
998 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
1000 bestfrac = trace.fraction;
1001 besthitent = hitent;
1002 VectorCopy(trace.endpos, bestorg);
1003 VectorCopy(trace.plane.normal, bestnormal);
1007 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
1010 // generates a cubemap name with prefix flags based on info flags (for now only `!`)
1011 static char *LightCubemapNumToName(char *vabuf, size_t vasize, int lightcubemapnum, int flags)
1013 if (lightcubemapnum <= 0)
1015 // `!` is prepended if the cubemap must be nearest-filtered
1016 if (flags & PARTICLEEFFECT_FORCENEAREST)
1017 return va(vabuf, vasize, "!cubemaps/%i", lightcubemapnum);
1018 return va(vabuf, vasize, "cubemaps/%i", lightcubemapnum);
1021 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
1022 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
1023 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);
1024 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)
1027 matrix4x4_t lightmatrix;
1030 VectorLerp(originmins, 0.5, originmaxs, center);
1031 Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
1032 if (effectnameindex == EFFECT_SVC_PARTICLE)
1034 if (cl_particles.integer)
1036 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
1038 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1039 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
1040 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1043 count *= cl_particles_quality.value;
1044 for (;count > 0;count--)
1046 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
1047 CL_NewQuakeParticle(
1049 pt_alphastatic, // type
1053 lhrandom(originmins[0], originmaxs[0]), // offset x
1054 lhrandom(originmins[1], originmaxs[1]), // offset y
1055 lhrandom(originmins[2], originmaxs[2]), // offset z
1056 lhrandom(velocitymins[0], velocitymaxs[0]), // velocity offset x
1057 lhrandom(velocitymins[1], velocitymaxs[1]), // velocity offset y
1058 lhrandom(velocitymins[2], velocitymaxs[2]), // velocity offset z
1060 0, // liquid friction
1062 3, // velocity jitter
1063 lhrandom(0.1, 0.4) // lifetime
1069 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
1070 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1071 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
1072 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1073 else if (effectnameindex == EFFECT_TE_SPIKE)
1075 if (cl_particles_bulletimpacts.integer)
1077 if (cl_particles_quake.integer)
1079 if (cl_particles_smoke.integer)
1080 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1084 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1085 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1086 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);
1090 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1091 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1093 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
1095 if (cl_particles_bulletimpacts.integer)
1097 if (cl_particles_quake.integer)
1099 if (cl_particles_smoke.integer)
1100 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1104 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1105 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1106 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);
1110 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1111 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1112 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);
1114 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1116 if (cl_particles_bulletimpacts.integer)
1118 if (cl_particles_quake.integer)
1120 if (cl_particles_smoke.integer)
1121 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1125 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1126 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1127 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);
1131 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1132 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1134 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1136 if (cl_particles_bulletimpacts.integer)
1138 if (cl_particles_quake.integer)
1140 if (cl_particles_smoke.integer)
1141 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1145 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1146 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1147 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);
1151 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1152 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1153 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);
1155 else if (effectnameindex == EFFECT_TE_BLOOD)
1157 if (!cl_particles_blood.integer)
1159 if (cl_particles_quake.integer)
1160 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1163 static double bloodaccumulator = 0;
1164 qbool immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1165 //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);
1166 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1167 for (;bloodaccumulator > 0;bloodaccumulator--)
1169 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);
1170 if (immediatebloodstain && part)
1172 immediatebloodstain = false;
1173 CL_ImmediateBloodStain(part);
1178 else if (effectnameindex == EFFECT_TE_SPARK)
1179 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1180 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1182 // plasma scorch mark
1183 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1184 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1185 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1187 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1189 if (cl_particles_bulletimpacts.integer)
1191 if (cl_particles_quake.integer)
1192 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1195 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1196 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1197 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);
1201 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1202 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1204 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1206 if (cl_particles_bulletimpacts.integer)
1208 if (cl_particles_quake.integer)
1209 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1212 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1213 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1214 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);
1218 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1219 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1220 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);
1222 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1224 CL_ParticleExplosion(center);
1225 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);
1227 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1229 CL_ParticleExplosion(center);
1230 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);
1232 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1234 if (cl_particles_quake.integer)
1237 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1240 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);
1242 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);
1246 CL_ParticleExplosion(center);
1247 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);
1249 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1250 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);
1251 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1253 count *= cl_particles_quality.value;
1255 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);
1257 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1259 float i, j, inc, vel;
1262 inc = 8 / cl_particles_quality.value;
1263 for (i = -128;i < 128;i += inc)
1265 for (j = -128;j < 128;j += inc)
1267 dir[0] = j + lhrandom(0, inc);
1268 dir[1] = i + lhrandom(0, inc);
1270 org[0] = center[0] + dir[0];
1271 org[1] = center[1] + dir[1];
1272 org[2] = center[2] + lhrandom(0, 64);
1273 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1274 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);
1278 else if (effectnameindex == EFFECT_TE_TELEPORT)
1280 float i, j, k, inc, vel;
1283 if (cl_particles_quake.integer)
1284 inc = 4 / cl_particles_quality.value;
1286 inc = 8 / cl_particles_quality.value;
1287 for (i = -16;i < 16;i += inc)
1289 for (j = -16;j < 16;j += inc)
1291 for (k = -24;k < 32;k += inc)
1293 VectorSet(dir, i*8, j*8, k*8);
1294 VectorNormalize(dir);
1295 vel = lhrandom(50, 113);
1296 if (cl_particles_quake.integer)
1297 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);
1299 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);
1303 if (!cl_particles_quake.integer)
1304 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);
1305 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);
1307 else if (effectnameindex == EFFECT_TE_TEI_G3)
1308 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);
1309 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1311 if (cl_particles_smoke.integer)
1313 count *= 0.25f * cl_particles_quality.value;
1315 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);
1318 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1320 CL_ParticleExplosion(center);
1321 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);
1323 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1326 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1327 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1328 if (cl_particles_smoke.integer)
1329 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1330 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);
1331 if (cl_particles_sparks.integer)
1332 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1333 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);
1334 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);
1336 else if (effectnameindex == EFFECT_EF_FLAME)
1338 if (!spawnparticles)
1340 count *= 300 * cl_particles_quality.value;
1342 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);
1343 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);
1345 else if (effectnameindex == EFFECT_EF_STARDUST)
1347 if (!spawnparticles)
1349 count *= 200 * cl_particles_quality.value;
1351 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);
1352 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);
1354 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1358 int smoke, blood, bubbles, r, color, spawnedcount;
1360 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1363 Vector4Set(light, 0, 0, 0, 0);
1365 if (effectnameindex == EFFECT_TR_ROCKET)
1366 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1367 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1369 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1370 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1372 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1374 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1375 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1379 matrix4x4_t traillightmatrix;
1380 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1381 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);
1382 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1386 if (!spawnparticles)
1389 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1392 VectorSubtract(originmaxs, originmins, dir);
1393 len = VectorNormalizeLength(dir);
1397 dec = -ent->persistent.trail_time;
1398 ent->persistent.trail_time += len;
1399 if (ent->persistent.trail_time < 0.01f)
1402 // if we skip out, leave it reset
1403 ent->persistent.trail_time = 0.0f;
1408 // advance into this frame to reach the first puff location
1409 VectorMA(originmins, dec, dir, pos);
1412 smoke = cl_particles.integer && cl_particles_smoke.integer;
1413 blood = cl_particles.integer && cl_particles_blood.integer;
1414 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1415 qd = 1.0f / cl_particles_quality.value;
1418 while (len >= 0 && ++spawnedcount <= 16384)
1423 if (effectnameindex == EFFECT_TR_BLOOD)
1425 if (cl_particles_quake.integer)
1427 color = particlepalette[67 + (rand()&3)];
1428 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1433 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);
1436 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1438 if (cl_particles_quake.integer)
1441 color = particlepalette[67 + (rand()&3)];
1442 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1447 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);
1453 if (effectnameindex == EFFECT_TR_ROCKET)
1455 if (cl_particles_quake.integer)
1458 color = particlepalette[ramp3[r]];
1459 CL_NewQuakeParticle(center, pt_alphastatic, color, color, -0.10, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1463 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);
1464 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);
1467 else if (effectnameindex == EFFECT_TR_GRENADE)
1469 if (cl_particles_quake.integer)
1472 color = particlepalette[ramp3[r]];
1473 CL_NewQuakeParticle(center, pt_alphastatic, color, color, -0.15, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1477 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);
1480 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1482 if (cl_particles_quake.integer)
1485 color = particlepalette[52 + (rand()&7)];
1486 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, 0.5);
1487 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, 0.5);
1489 else if (gamemode == GAME_GOODVSBAD2)
1492 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);
1496 color = particlepalette[20 + (rand()&7)];
1497 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);
1500 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1502 if (cl_particles_quake.integer)
1505 color = particlepalette[230 + (rand()&7)];
1506 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30 * dir[1], 30 * -dir[0], 0, 0, 0, 0, 0, 0.5);
1507 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30 * -dir[1], 30 * dir[0], 0, 0, 0, 0, 0, 0.5);
1511 color = particlepalette[226 + (rand()&7)];
1512 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);
1515 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1517 if (cl_particles_quake.integer)
1519 color = particlepalette[152 + (rand()&3)];
1520 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, 0.3);
1522 else if (gamemode == GAME_GOODVSBAD2)
1525 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);
1527 else if (gamemode == GAME_PRYDON)
1530 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);
1533 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);
1535 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1538 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);
1540 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1543 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);
1545 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1546 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);
1550 if (effectnameindex == EFFECT_TR_ROCKET)
1551 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);
1552 else if (effectnameindex == EFFECT_TR_GRENADE)
1553 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);
1555 // advance to next time and position
1558 VectorMA (pos, dec, dir, pos);
1561 ent->persistent.trail_time = len;
1564 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1567 // this is also called on point effects with spawndlight = true and
1568 // spawnparticles = true
1569 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)
1571 qbool found = false;
1573 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1575 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1576 return; // no such effect
1578 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1580 int effectinfoindex;
1583 particleeffectinfo_t *info;
1596 qbool immediatebloodstain;
1598 float avgtint[4], tint[4], tintlerp;
1599 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1600 VectorLerp(originmins, 0.5, originmaxs, center);
1601 supercontents = CL_PointSuperContents(center);
1602 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1603 VectorSubtract(originmaxs, originmins, traildir);
1604 traillen = VectorLength(traildir);
1605 VectorNormalize(traildir);
1608 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1612 Vector4Set(avgtint, 1, 1, 1, 1);
1614 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1616 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1618 qbool definedastrail = info->trailspacing > 0;
1620 qbool drawastrail = wanttrail;
1621 if (cl_particles_forcetraileffects.integer)
1622 drawastrail = drawastrail || definedastrail;
1625 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1627 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1630 // spawn a dlight if requested
1631 if (info->lightradiusstart > 0 && spawndlight)
1633 matrix4x4_t tempmatrix;
1635 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1637 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1638 if (info->lighttime > 0 && info->lightradiusfade > 0)
1640 // light flash (explosion, etc)
1641 // called when effect starts
1642 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);
1644 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1647 // called by CL_LinkNetworkEntity
1648 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1649 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1650 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1651 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1652 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);
1653 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1657 if (!spawnparticles)
1662 if (info->tex[1] > info->tex[0])
1664 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1665 tex = min(tex, info->tex[1] - 1);
1667 if(info->staintex[0] < 0)
1668 staintex = info->staintex[0];
1671 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1672 staintex = min(staintex, info->staintex[1] - 1);
1674 if (info->particletype == pt_decal)
1676 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1677 AnglesFromVectors(angles, velocity, NULL, false);
1678 AngleVectors(angles, forward, right, up);
1679 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1681 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]);
1683 else if (info->orientation == PARTICLE_HBEAM)
1688 AnglesFromVectors(angles, traildir, NULL, false);
1689 AngleVectors(angles, forward, right, up);
1690 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1692 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);
1697 if (!cl_particles.integer)
1699 switch (info->particletype)
1701 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1702 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1703 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1704 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1705 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1706 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1710 cnt = info->countabsolute;
1711 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1712 // if drawastrail is not set, we will
1713 // use the regular cnt-based random
1714 // particle spawning at the center; so
1715 // do NOT apply trailspacing then!
1716 if (drawastrail && definedastrail)
1717 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1720 continue; // nothing to draw
1721 info->particleaccumulator += cnt;
1723 if (drawastrail || definedastrail)
1724 immediatebloodstain = false;
1726 immediatebloodstain =
1727 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1729 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1733 VectorCopy(originmins, trailpos);
1734 trailstep = traillen / cnt;
1738 VectorCopy(center, trailpos);
1744 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1745 AnglesFromVectors(angles, velocity, NULL, false);
1748 AnglesFromVectors(angles, traildir, NULL, false);
1750 AngleVectors(angles, forward, right, up);
1751 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1752 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1753 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1754 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1756 if (info->tex[1] > info->tex[0])
1758 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1759 tex = min(tex, info->tex[1] - 1);
1761 if (!(drawastrail || definedastrail))
1763 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1764 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1765 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1769 tintlerp = lhrandom(0, 1);
1770 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1773 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);
1774 if (immediatebloodstain && part)
1776 immediatebloodstain = false;
1777 CL_ImmediateBloodStain(part);
1780 VectorMA(trailpos, trailstep, traildir, trailpos);
1787 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1790 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)
1792 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1795 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)
1797 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1800 // note: this one ONLY does boxes!
1801 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)
1803 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1811 void CL_EntityParticles (const entity_t *ent)
1814 vec_t pitch, yaw, dist = 64, beamlength = 16;
1816 static vec3_t avelocities[NUMVERTEXNORMALS];
1817 if (!cl_particles.integer) return;
1818 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1820 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1822 if (!avelocities[0][0])
1823 for (i = 0;i < NUMVERTEXNORMALS;i++)
1824 for (j = 0;j < 3;j++)
1825 avelocities[i][j] = lhrandom(0, 2.55);
1827 for (i = 0;i < NUMVERTEXNORMALS;i++)
1829 yaw = cl.time * avelocities[i][0];
1830 pitch = cl.time * avelocities[i][1];
1831 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1832 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1833 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1834 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);
1839 void CL_ReadPointFile_f(cmd_state_t *cmd)
1841 double org[3], leakorg[3];
1844 char *pointfile = NULL, *pointfilepos, *t, tchar;
1845 char name[MAX_QPATH];
1850 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1851 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1854 Con_Printf("Could not open %s\n", name);
1858 Con_Printf("Reading %s...\n", name);
1859 VectorClear(leakorg);
1862 pointfilepos = pointfile;
1863 while (*pointfilepos)
1865 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1870 while (*t && *t != '\n' && *t != '\r')
1874 #if _MSC_VER >= 1400
1875 #define sscanf sscanf_s
1877 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1878 VectorCopy(org, vecorg);
1884 VectorCopy(org, leakorg);
1887 if (cl.num_particles < cl.max_particles - 3)
1890 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);
1893 Mem_Free(pointfile);
1894 VectorCopy(leakorg, vecorg);
1895 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1902 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);
1903 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);
1904 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);
1909 CL_ParseParticleEffect
1911 Parse an effect out of the server message
1914 void CL_ParseParticleEffect (void)
1917 int i, count, msgcount, color;
1919 MSG_ReadVector(&cl_message, org, cls.protocol);
1920 for (i=0 ; i<3 ; i++)
1921 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1922 msgcount = MSG_ReadByte(&cl_message);
1923 color = MSG_ReadByte(&cl_message);
1925 if (msgcount == 255)
1930 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1935 CL_ParticleExplosion
1939 void CL_ParticleExplosion (const vec3_t org)
1944 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1945 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1947 if (cl_particles_quake.integer)
1949 for (i = 0; i < 1024; i++)
1956 color = particlepalette[ramp1[r]];
1958 CL_NewQuakeParticle(
1963 org[0], org[1], org[2], // offset
1964 0, 0, 0, // velocity
1966 0, // liquid friction
1967 16, // origin jitter
1968 256, // velocity jitter
1974 color = particlepalette[ramp2[r]];
1976 CL_NewQuakeParticle(
1981 org[0], org[1], org[2], // offset
1982 0, 0, 0, // velocity
1984 0, // liquid friction
1985 16, // origin jitter
1986 256, // velocity jitter
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, pt_alphastatic, 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 // LadyHavoc: early out conditions
2925 if (!cl.num_particles)
2928 // Handling of the colour ramp for pt_explode and pt_explode2
2929 pt_explode_frame_interval = frametime * 10;
2930 pt_explode2_frame_interval = frametime * 15;
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))
3104 // Progress the particle colour up the ramp
3105 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;
3117 // Progress the particle colour up the ramp
3118 p->time2 += pt_explode2_frame_interval;
3123 color = particlepalette[ramp2[(int)p->time2]];
3124 p->color[0] = color >> 16;
3125 p->color[1] = color >> 8;
3126 p->color[2] = color >> 0;
3134 else if (p->delayedspawn > cl.time)
3138 // don't render particles too close to the view (they chew fillrate)
3139 // also don't render particles behind the view (useless)
3140 // further checks to cull to the frustum would be too slow here
3141 switch(p->typeindex)
3144 // beams have no culling
3145 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3148 if(cl_particles_visculling.integer)
3149 if (!r_refdef.viewcache.world_novis)
3150 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3152 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3154 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3157 // anything else just has to be in front of the viewer and visible at this distance
3158 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)))
3159 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3166 if (cl.free_particle > i)
3167 cl.free_particle = i;
3170 // reduce cl.num_particles if possible
3171 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3174 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3176 particle_t *oldparticles = cl.particles;
3177 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3178 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3179 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3180 Mem_Free(oldparticles);