2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float relativeoriginoffset[3];
105 float velocityoffset[3];
106 float relativevelocityoffset[3];
107 float originjitter[3];
108 float velocityjitter[3];
109 float velocitymultiplier;
110 // an effect can also spawn a dlight
111 float lightradiusstart;
112 float lightradiusfade;
115 qboolean lightshadow;
117 float lightcorona[2];
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
123 float rotate[4]; // min/max base angle, min/max rotation over time
125 particleeffectinfo_t;
127 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
129 int numparticleeffectinfo;
130 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
132 static int particlepalette[256];
134 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
135 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
136 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
137 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
138 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
139 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
140 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
141 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
142 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
143 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
144 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
145 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
146 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
147 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
148 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
149 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
150 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
151 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
152 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
153 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
154 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
155 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
156 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
157 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
158 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
159 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
160 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
161 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
162 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
163 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
164 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
165 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
168 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
169 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
170 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
172 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
174 // particletexture_t is a rectangle in the particlefonttexture
175 typedef struct particletexture_s
178 float s1, t1, s2, t2;
182 static rtexturepool_t *particletexturepool;
183 static rtexture_t *particlefonttexture;
184 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
185 skinframe_t *decalskinframe;
187 // texture numbers in particle font
188 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
189 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
190 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
191 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
192 static const int tex_rainsplash = 32;
193 static const int tex_particle = 63;
194 static const int tex_bubble = 62;
195 static const int tex_raindrop = 61;
196 static const int tex_beam = 60;
198 particleeffectinfo_t baselineparticleeffectinfo =
200 0, //int effectnameindex; // which effect this belongs to
201 // PARTICLEEFFECT_* bits
203 // blood effects may spawn very few particles, so proper fraction-overflow
204 // handling is very important, this variable keeps track of the fraction
205 0.0, //double particleaccumulator;
206 // the math is: countabsolute + requestedcount * countmultiplier * quality
207 // absolute number of particles to spawn, often used for decals
208 // (unaffected by quality and requestedcount)
209 0.0f, //float countabsolute;
210 // multiplier for the number of particles CL_ParticleEffect was told to
211 // spawn, most effects do not really have a count and hence use 1, so
212 // this is often the actual count to spawn, not merely a multiplier
213 0.0f, //float countmultiplier;
214 // if > 0 this causes the particle to spawn in an evenly spaced line from
215 // originmins to originmaxs (causing them to describe a trail, not a box)
216 0.0f, //float trailspacing;
217 // type of particle to spawn (defines some aspects of behavior)
218 pt_alphastatic, //ptype_t particletype;
219 // blending mode used on this particle type
220 PBLEND_ALPHA, //pblend_t blendmode;
221 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
222 PARTICLE_BILLBOARD, //porientation_t orientation;
223 // range of colors to choose from in hex RRGGBB (like HTML color tags),
224 // randomly interpolated at spawn
225 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
226 // a random texture is chosen in this range (note the second value is one
227 // past the last choosable, so for example 8,16 chooses any from 8 up and
229 // if start and end of the range are the same, no randomization is done
230 {63, 63 /* tex_particle */}, //int tex[2];
231 // range of size values randomly chosen when spawning, plus size increase over time
232 {1, 1, 0.0f}, //float size[3];
233 // range of alpha values randomly chosen when spawning, plus alpha fade
234 {0.0f, 256.0f, 256.0f}, //float alpha[3];
235 // how long the particle should live (note it is also removed if alpha drops to 0)
236 {16777216.0f, 16777216.0f}, //float time[2];
237 // how much gravity affects this particle (negative makes it fly up!)
238 0.0f, //float gravity;
239 // how much bounce the particle has when it hits a surface
240 // if negative the particle is removed on impact
241 0.0f, //float bounce;
242 // if in air this friction is applied
243 // if negative the particle accelerates
244 0.0f, //float airfriction;
245 // if in liquid (water/slime/lava) this friction is applied
246 // if negative the particle accelerates
247 0.0f, //float liquidfriction;
248 // these offsets are added to the values given to particleeffect(), and
249 // then an ellipsoid-shaped jitter is added as defined by these
250 // (they are the 3 radii)
251 1.0f, //float stretchfactor;
252 // stretch velocity factor (used for sparks)
253 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
254 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
258 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
259 0.0f, //float velocitymultiplier;
260 // an effect can also spawn a dlight
261 0.0f, //float lightradiusstart;
262 0.0f, //float lightradiusfade;
263 16777216.0f, //float lighttime;
264 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
265 true, //qboolean lightshadow;
266 0, //int lightcubemapnum;
267 {1.0f, 0.25f}, //float lightcorona[2];
268 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
269 {-1, -1}, //int staintex[2];
270 {1.0f, 1.0f}, //float stainalpha[2];
271 {2.0f, 2.0f}, //float stainsize[2];
273 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
276 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
277 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
278 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
279 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
280 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
281 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
282 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
283 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
284 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
285 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
286 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
287 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
288 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
289 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
290 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
291 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
292 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
293 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
294 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
295 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
296 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
297 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
298 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
299 cvar_t cl_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
300 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
301 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
302 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
303 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
304 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
305 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
306 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
307 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
308 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
309 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
310 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
313 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
318 particleeffectinfo_t *info = NULL;
319 const char *text = textstart;
321 for (linenumber = 1;;linenumber++)
324 for (arrayindex = 0;arrayindex < 16;arrayindex++)
325 argv[arrayindex][0] = 0;
328 if (!COM_ParseToken_Simple(&text, true, false, true))
330 if (!strcmp(com_token, "\n"))
334 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
340 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
341 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
342 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
343 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
344 #define readfloat(var) checkparms(2);var = atof(argv[1])
345 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
346 if (!strcmp(argv[0], "effect"))
350 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
352 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
355 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
357 if (particleeffectname[effectnameindex][0])
359 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
364 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
368 // if we run out of names, abort
369 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
371 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
374 info = particleeffectinfo + numparticleeffectinfo++;
375 // copy entire info from baseline, then fix up the nameindex
376 *info = baselineparticleeffectinfo;
377 info->effectnameindex = effectnameindex;
379 else if (info == NULL)
381 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
384 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
385 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
386 else if (!strcmp(argv[0], "type"))
389 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
390 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
391 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
392 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
393 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
394 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
395 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
396 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
397 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
398 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
399 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
400 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
401 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
402 info->blendmode = particletype[info->particletype].blendmode;
403 info->orientation = particletype[info->particletype].orientation;
405 else if (!strcmp(argv[0], "blend"))
408 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
409 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
410 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
411 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
413 else if (!strcmp(argv[0], "orientation"))
416 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
417 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
418 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
419 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
420 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
422 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
423 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
424 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
425 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
426 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
427 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
428 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
429 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
430 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
431 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
432 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
433 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
434 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
435 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
436 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
437 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
438 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
439 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
440 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
441 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
442 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
443 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
444 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
445 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
446 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
447 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
448 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
449 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
450 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
451 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
452 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
453 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
454 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
455 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
457 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
466 int CL_ParticleEffectIndexForName(const char *name)
469 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
470 if (!strcmp(particleeffectname[i], name))
475 const char *CL_ParticleEffectNameForIndex(int i)
477 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
479 return particleeffectname[i];
482 // MUST match effectnameindex_t in client.h
483 static const char *standardeffectnames[EFFECT_TOTAL] =
507 "TE_TEI_BIGEXPLOSION",
523 static void CL_Particles_LoadEffectInfo(const char *customfile)
527 unsigned char *filedata;
528 fs_offset_t filesize;
529 char filename[MAX_QPATH];
530 numparticleeffectinfo = 0;
531 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
532 memset(particleeffectname, 0, sizeof(particleeffectname));
533 for (i = 0;i < EFFECT_TOTAL;i++)
534 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
535 for (filepass = 0;;filepass++)
540 strlcpy(filename, customfile, sizeof(filename));
542 strlcpy(filename, "effectinfo.txt", sizeof(filename));
544 else if (filepass == 1)
546 if (!cl.worldbasename[0] || customfile)
548 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
552 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
555 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
560 static void CL_Particles_LoadEffectInfo_f(void)
562 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
570 void CL_ReadPointFile_f (void);
571 void CL_Particles_Init (void)
573 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
574 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
576 Cvar_RegisterVariable (&cl_particles);
577 Cvar_RegisterVariable (&cl_particles_quality);
578 Cvar_RegisterVariable (&cl_particles_alpha);
579 Cvar_RegisterVariable (&cl_particles_size);
580 Cvar_RegisterVariable (&cl_particles_quake);
581 Cvar_RegisterVariable (&cl_particles_blood);
582 Cvar_RegisterVariable (&cl_particles_blood_alpha);
583 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
584 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
585 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
586 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
587 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
588 Cvar_RegisterVariable (&cl_particles_explosions_shell);
589 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
590 Cvar_RegisterVariable (&cl_particles_rain);
591 Cvar_RegisterVariable (&cl_particles_snow);
592 Cvar_RegisterVariable (&cl_particles_smoke);
593 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
594 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
595 Cvar_RegisterVariable (&cl_particles_sparks);
596 Cvar_RegisterVariable (&cl_particles_bubbles);
597 Cvar_RegisterVariable (&cl_particles_visculling);
598 Cvar_RegisterVariable (&cl_particles_collisions);
599 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
600 Cvar_RegisterVariable (&cl_decals);
601 Cvar_RegisterVariable (&cl_decals_visculling);
602 Cvar_RegisterVariable (&cl_decals_time);
603 Cvar_RegisterVariable (&cl_decals_fadetime);
604 Cvar_RegisterVariable (&cl_decals_newsystem);
605 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
606 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
607 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
608 Cvar_RegisterVariable (&cl_decals_models);
609 Cvar_RegisterVariable (&cl_decals_bias);
610 Cvar_RegisterVariable (&cl_decals_max);
613 void CL_Particles_Shutdown (void)
617 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
618 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
620 // list of all 26 parameters:
621 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
622 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
623 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
624 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
625 // palpha - opacity of particle as 0-255 (can be more than 255)
626 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
627 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
628 // pgravity - how much effect gravity has on the particle (0-1)
629 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
630 // px,py,pz - starting origin of particle
631 // pvx,pvy,pvz - starting velocity of particle
632 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
633 // blendmode - one of the PBLEND_ values
634 // orientation - one of the PARTICLE_ values
635 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
636 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
637 // stainalpha: opacity of the stain as factor for alpha
638 // stainsize: size of the stain as factor for palpha
639 // angle: base rotation of the particle geometry around its center normal
640 // spin: rotation speed of the particle geometry around its center normal
641 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
646 if (!cl_particles.integer)
648 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
649 if (cl.free_particle >= cl.max_particles)
652 lifetime = palpha / min(1, palphafade);
653 part = &cl.particles[cl.free_particle++];
654 if (cl.num_particles < cl.free_particle)
655 cl.num_particles = cl.free_particle;
656 memset(part, 0, sizeof(*part));
657 VectorCopy(sortorigin, part->sortorigin);
658 part->typeindex = ptypeindex;
659 part->blendmode = blendmode;
660 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
662 particletexture_t *tex = &particletexture[ptex];
663 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
664 part->orientation = PARTICLE_VBEAM;
666 part->orientation = PARTICLE_HBEAM;
669 part->orientation = orientation;
670 l2 = (int)lhrandom(0.5, 256.5);
672 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
673 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
674 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
677 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
678 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
679 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
681 part->alpha = palpha;
682 part->alphafade = palphafade;
683 part->staintexnum = staintex;
684 if(staincolor1 >= 0 && staincolor2 >= 0)
686 l2 = (int)lhrandom(0.5, 256.5);
688 if(blendmode == PBLEND_INVMOD)
690 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
691 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
692 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
696 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
697 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
698 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
700 if(r > 0xFF) r = 0xFF;
701 if(g > 0xFF) g = 0xFF;
702 if(b > 0xFF) b = 0xFF;
706 r = part->color[0]; // -1 is shorthand for stain = particle color
710 part->staincolor[0] = r;
711 part->staincolor[1] = g;
712 part->staincolor[2] = b;
713 part->stainalpha = palpha * stainalpha;
714 part->stainsize = psize * stainsize;
717 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
719 part->color[0] *= tint[0];
720 part->color[1] *= tint[1];
721 part->color[2] *= tint[2];
723 part->alpha *= tint[3];
724 part->alphafade *= tint[3];
725 part->stainalpha *= tint[3];
729 part->sizeincrease = psizeincrease;
730 part->gravity = pgravity;
731 part->bounce = pbounce;
732 part->stretch = stretch;
734 part->org[0] = px + originjitter * v[0];
735 part->org[1] = py + originjitter * v[1];
736 part->org[2] = pz + originjitter * v[2];
737 part->vel[0] = pvx + velocityjitter * v[0];
738 part->vel[1] = pvy + velocityjitter * v[1];
739 part->vel[2] = pvz + velocityjitter * v[2];
741 part->airfriction = pairfriction;
742 part->liquidfriction = pliquidfriction;
743 part->die = cl.time + lifetime;
744 part->delayedspawn = cl.time;
745 // part->delayedcollisions = 0;
746 part->qualityreduction = pqualityreduction;
749 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
750 if (part->typeindex == pt_rain)
754 float lifetime = part->die - cl.time;
757 // turn raindrop into simple spark and create delayedspawn splash effect
758 part->typeindex = pt_spark;
760 VectorMA(part->org, lifetime, part->vel, endvec);
761 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
762 part->die = cl.time + lifetime * trace.fraction;
763 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
766 part2->delayedspawn = part->die;
767 part2->die += part->die - cl.time;
768 for (i = rand() & 7;i < 10;i++)
770 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
773 part2->delayedspawn = part->die;
774 part2->die += part->die - cl.time;
780 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
782 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
785 VectorMA(part->org, lifetime, part->vel, endvec);
786 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
787 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
794 static void CL_ImmediateBloodStain(particle_t *part)
799 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
800 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
802 VectorCopy(part->vel, v);
804 staintex = part->staintexnum;
805 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
808 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
809 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
811 VectorCopy(part->vel, v);
813 staintex = tex_blooddecal[rand()&7];
814 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
818 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
822 entity_render_t *ent = &cl.entities[hitent].render;
823 unsigned char color[3];
824 if (!cl_decals.integer)
826 if (!ent->allowdecals)
829 l2 = (int)lhrandom(0.5, 256.5);
831 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
832 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
833 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
835 if (cl_decals_newsystem.integer)
838 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
840 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
844 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
845 if (cl.free_decal >= cl.max_decals)
847 decal = &cl.decals[cl.free_decal++];
848 if (cl.num_decals < cl.free_decal)
849 cl.num_decals = cl.free_decal;
850 memset(decal, 0, sizeof(*decal));
851 decal->decalsequence = cl.decalsequence++;
852 decal->typeindex = pt_decal;
853 decal->texnum = texnum;
854 VectorMA(org, cl_decals_bias.value, normal, decal->org);
855 VectorCopy(normal, decal->normal);
857 decal->alpha = alpha;
858 decal->time2 = cl.time;
859 decal->color[0] = color[0];
860 decal->color[1] = color[1];
861 decal->color[2] = color[2];
864 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
865 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
866 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
868 decal->owner = hitent;
869 decal->clusterindex = -1000; // no vis culling unless we're sure
872 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
873 decal->ownermodel = cl.entities[decal->owner].render.model;
874 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
875 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
879 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
881 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
883 decal->clusterindex = leaf->clusterindex;
888 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
895 int besthitent = 0, hitent;
898 for (i = 0;i < 32;i++)
901 VectorMA(org, maxdist, org2, org2);
902 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
903 // take the closest trace result that doesn't end up hitting a NOMARKS
904 // surface (sky for example)
905 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
907 bestfrac = trace.fraction;
909 VectorCopy(trace.endpos, bestorg);
910 VectorCopy(trace.plane.normal, bestnormal);
914 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
917 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
918 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
919 static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
922 matrix4x4_t tempmatrix;
925 VectorLerp(originmins, 0.5, originmaxs, center);
926 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
927 if (effectnameindex == EFFECT_SVC_PARTICLE)
929 if (cl_particles.integer)
931 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
933 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
934 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
935 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
938 count *= cl_particles_quality.value;
939 for (;count > 0;count--)
941 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
942 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
947 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
948 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
949 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
950 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
951 else if (effectnameindex == EFFECT_TE_SPIKE)
953 if (cl_particles_bulletimpacts.integer)
955 if (cl_particles_quake.integer)
957 if (cl_particles_smoke.integer)
958 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
962 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
963 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
964 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);
968 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
969 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
971 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
973 if (cl_particles_bulletimpacts.integer)
975 if (cl_particles_quake.integer)
977 if (cl_particles_smoke.integer)
978 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
982 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
983 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
984 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);
988 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
989 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
990 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
992 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
994 if (cl_particles_bulletimpacts.integer)
996 if (cl_particles_quake.integer)
998 if (cl_particles_smoke.integer)
999 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1003 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1004 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1005 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);
1009 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1010 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1012 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1014 if (cl_particles_bulletimpacts.integer)
1016 if (cl_particles_quake.integer)
1018 if (cl_particles_smoke.integer)
1019 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1023 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1024 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1025 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);
1029 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1030 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1031 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1033 else if (effectnameindex == EFFECT_TE_BLOOD)
1035 if (!cl_particles_blood.integer)
1037 if (cl_particles_quake.integer)
1038 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
1041 static double bloodaccumulator = 0;
1042 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1043 //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);
1044 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1045 for (;bloodaccumulator > 0;bloodaccumulator--)
1047 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);
1048 if (immediatebloodstain && part)
1050 immediatebloodstain = false;
1051 CL_ImmediateBloodStain(part);
1056 else if (effectnameindex == EFFECT_TE_SPARK)
1057 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1058 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1060 // plasma scorch mark
1061 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1062 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1063 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1065 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1067 if (cl_particles_bulletimpacts.integer)
1069 if (cl_particles_quake.integer)
1070 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1073 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1074 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1075 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);
1079 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1080 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1082 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1084 if (cl_particles_bulletimpacts.integer)
1086 if (cl_particles_quake.integer)
1087 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1090 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1091 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1092 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);
1096 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1097 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1098 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1100 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1102 CL_ParticleExplosion(center);
1103 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1105 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1107 CL_ParticleExplosion(center);
1108 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1110 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1112 if (cl_particles_quake.integer)
1115 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1118 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);
1120 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);
1124 CL_ParticleExplosion(center);
1125 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1127 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1128 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1129 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1131 count *= cl_particles_quality.value;
1133 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);
1135 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1137 float i, j, inc, vel;
1140 inc = 8 / cl_particles_quality.value;
1141 for (i = -128;i < 128;i += inc)
1143 for (j = -128;j < 128;j += inc)
1145 dir[0] = j + lhrandom(0, inc);
1146 dir[1] = i + lhrandom(0, inc);
1148 org[0] = center[0] + dir[0];
1149 org[1] = center[1] + dir[1];
1150 org[2] = center[2] + lhrandom(0, 64);
1151 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1152 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);
1156 else if (effectnameindex == EFFECT_TE_TELEPORT)
1158 float i, j, k, inc, vel;
1161 if (cl_particles_quake.integer)
1162 inc = 4 / cl_particles_quality.value;
1164 inc = 8 / cl_particles_quality.value;
1165 for (i = -16;i < 16;i += inc)
1167 for (j = -16;j < 16;j += inc)
1169 for (k = -24;k < 32;k += inc)
1171 VectorSet(dir, i*8, j*8, k*8);
1172 VectorNormalize(dir);
1173 vel = lhrandom(50, 113);
1174 if (cl_particles_quake.integer)
1175 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);
1177 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);
1181 if (!cl_particles_quake.integer)
1182 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);
1183 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1185 else if (effectnameindex == EFFECT_TE_TEI_G3)
1186 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);
1187 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1189 if (cl_particles_smoke.integer)
1191 count *= 0.25f * cl_particles_quality.value;
1193 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);
1196 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1198 CL_ParticleExplosion(center);
1199 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1201 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1204 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1205 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1206 if (cl_particles_smoke.integer)
1207 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1208 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);
1209 if (cl_particles_sparks.integer)
1210 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1211 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);
1212 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1214 else if (effectnameindex == EFFECT_EF_FLAME)
1216 if (!spawnparticles)
1218 count *= 300 * cl_particles_quality.value;
1220 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);
1221 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1223 else if (effectnameindex == EFFECT_EF_STARDUST)
1225 if (!spawnparticles)
1227 count *= 200 * cl_particles_quality.value;
1229 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);
1230 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1232 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1236 int smoke, blood, bubbles, r, color;
1238 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1241 Vector4Set(light, 0, 0, 0, 0);
1243 if (effectnameindex == EFFECT_TR_ROCKET)
1244 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1245 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1247 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1248 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1250 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1252 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1253 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1257 matrix4x4_t tempmatrix;
1258 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1259 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1260 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1264 if (!spawnparticles)
1267 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1270 VectorSubtract(originmaxs, originmins, dir);
1271 len = VectorNormalizeLength(dir);
1274 dec = -ent->persistent.trail_time;
1275 ent->persistent.trail_time += len;
1276 if (ent->persistent.trail_time < 0.01f)
1279 // if we skip out, leave it reset
1280 ent->persistent.trail_time = 0.0f;
1285 // advance into this frame to reach the first puff location
1286 VectorMA(originmins, dec, dir, pos);
1289 smoke = cl_particles.integer && cl_particles_smoke.integer;
1290 blood = cl_particles.integer && cl_particles_blood.integer;
1291 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1292 qd = 1.0f / cl_particles_quality.value;
1299 if (effectnameindex == EFFECT_TR_BLOOD)
1301 if (cl_particles_quake.integer)
1303 color = particlepalette[67 + (rand()&3)];
1304 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1309 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);
1312 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1314 if (cl_particles_quake.integer)
1317 color = particlepalette[67 + (rand()&3)];
1318 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1323 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);
1329 if (effectnameindex == EFFECT_TR_ROCKET)
1331 if (cl_particles_quake.integer)
1334 color = particlepalette[ramp3[r]];
1335 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1339 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);
1340 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);
1343 else if (effectnameindex == EFFECT_TR_GRENADE)
1345 if (cl_particles_quake.integer)
1348 color = particlepalette[ramp3[r]];
1349 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1353 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);
1356 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1358 if (cl_particles_quake.integer)
1361 color = particlepalette[52 + (rand()&7)];
1362 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1363 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1365 else if (gamemode == GAME_GOODVSBAD2)
1368 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);
1372 color = particlepalette[20 + (rand()&7)];
1373 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);
1376 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1378 if (cl_particles_quake.integer)
1381 color = particlepalette[230 + (rand()&7)];
1382 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1383 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1387 color = particlepalette[226 + (rand()&7)];
1388 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);
1391 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1393 if (cl_particles_quake.integer)
1395 color = particlepalette[152 + (rand()&3)];
1396 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1398 else if (gamemode == GAME_GOODVSBAD2)
1401 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);
1403 else if (gamemode == GAME_PRYDON)
1406 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);
1409 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);
1411 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1414 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);
1416 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1419 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);
1421 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1422 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);
1426 if (effectnameindex == EFFECT_TR_ROCKET)
1427 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);
1428 else if (effectnameindex == EFFECT_TR_GRENADE)
1429 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);
1431 // advance to next time and position
1434 VectorMA (pos, dec, dir, pos);
1437 ent->persistent.trail_time = len;
1440 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1443 // this is also called on point effects with spawndlight = true and
1444 // spawnparticles = true
1445 // it is called CL_ParticleTrail because most code does not want to supply
1446 // these parameters, only trail handling does
1447 static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail)
1449 qboolean found = false;
1451 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1453 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1454 return; // no such effect
1456 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1458 int effectinfoindex;
1461 particleeffectinfo_t *info;
1473 qboolean underwater;
1474 qboolean immediatebloodstain;
1476 float avgtint[4], tint[4], tintlerp;
1477 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1478 VectorLerp(originmins, 0.5, originmaxs, center);
1479 supercontents = CL_PointSuperContents(center);
1480 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1481 VectorSubtract(originmaxs, originmins, traildir);
1482 traillen = VectorLength(traildir);
1483 VectorNormalize(traildir);
1486 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1490 Vector4Set(avgtint, 1, 1, 1, 1);
1492 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1494 if (info->effectnameindex == effectnameindex)
1496 qboolean definedastrail = info->trailspacing > 0;
1498 qboolean drawastrail = wanttrail;
1499 if (cl_particles_forcetraileffects.integer)
1500 drawastrail = drawastrail || definedastrail;
1503 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1505 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1508 // trail effects may only ever be drawn as trail
1509 if (!drawastrail && definedastrail)
1512 // spawn a dlight if requested
1513 if (info->lightradiusstart > 0 && spawndlight)
1515 matrix4x4_t tempmatrix;
1517 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1519 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1520 if (info->lighttime > 0 && info->lightradiusfade > 0)
1522 // light flash (explosion, etc)
1523 // called when effect starts
1524 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1526 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1529 // called by CL_LinkNetworkEntity
1530 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1531 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1532 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1533 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1534 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1535 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1539 if (!spawnparticles)
1544 if (info->tex[1] > info->tex[0])
1546 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1547 tex = min(tex, info->tex[1] - 1);
1549 if(info->staintex[0] < 0)
1550 staintex = info->staintex[0];
1553 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1554 staintex = min(staintex, info->staintex[1] - 1);
1556 if (info->particletype == pt_decal)
1558 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1559 AnglesFromVectors(angles, velocity, NULL, false);
1560 AngleVectors(angles, forward, right, up);
1561 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1563 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]);
1565 else if (info->orientation == PARTICLE_HBEAM)
1567 AnglesFromVectors(angles, traildir, NULL, false);
1568 AngleVectors(angles, forward, right, up);
1569 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1571 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);
1575 if (!cl_particles.integer)
1577 switch (info->particletype)
1579 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1580 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1581 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1582 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1583 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1584 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1587 VectorCopy(originmins, trailpos);
1590 float cnt = info->countabsolute;
1591 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1592 if (info->trailspacing > 0)
1593 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1595 info->particleaccumulator += cnt;
1596 trailstep = traillen / cnt;
1597 immediatebloodstain = false;
1599 AnglesFromVectors(angles, traildir, NULL, false);
1603 float cnt = info->countabsolute;
1604 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1606 info->particleaccumulator += cnt;
1608 immediatebloodstain =
1609 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1611 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1613 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1614 AnglesFromVectors(angles, velocity, NULL, false);
1616 AngleVectors(angles, forward, right, up);
1617 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1618 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1619 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1620 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1622 if (info->tex[1] > info->tex[0])
1624 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1625 tex = min(tex, info->tex[1] - 1);
1629 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1630 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1631 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1635 tintlerp = lhrandom(0, 1);
1636 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1639 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);
1640 if (immediatebloodstain && part)
1642 immediatebloodstain = false;
1643 CL_ImmediateBloodStain(part);
1646 VectorMA(trailpos, trailstep, traildir, trailpos);
1653 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1656 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1658 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1661 void CL_ParticleBox(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
1663 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1666 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)
1668 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1676 void CL_EntityParticles (const entity_t *ent)
1679 vec_t pitch, yaw, dist = 64, beamlength = 16;
1681 static vec3_t avelocities[NUMVERTEXNORMALS];
1682 if (!cl_particles.integer) return;
1683 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1685 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1687 if (!avelocities[0][0])
1688 for (i = 0;i < NUMVERTEXNORMALS;i++)
1689 for (j = 0;j < 3;j++)
1690 avelocities[i][j] = lhrandom(0, 2.55);
1692 for (i = 0;i < NUMVERTEXNORMALS;i++)
1694 yaw = cl.time * avelocities[i][0];
1695 pitch = cl.time * avelocities[i][1];
1696 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1697 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1698 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1699 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);
1704 void CL_ReadPointFile_f (void)
1706 double org[3], leakorg[3];
1709 char *pointfile = NULL, *pointfilepos, *t, tchar;
1710 char name[MAX_QPATH];
1715 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1716 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1719 Con_Printf("Could not open %s\n", name);
1723 Con_Printf("Reading %s...\n", name);
1724 VectorClear(leakorg);
1727 pointfilepos = pointfile;
1728 while (*pointfilepos)
1730 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1735 while (*t && *t != '\n' && *t != '\r')
1739 #if _MSC_VER >= 1400
1740 #define sscanf sscanf_s
1742 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1743 VectorCopy(org, vecorg);
1749 VectorCopy(org, leakorg);
1752 if (cl.num_particles < cl.max_particles - 3)
1755 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);
1758 Mem_Free(pointfile);
1759 VectorCopy(leakorg, vecorg);
1760 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1762 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);
1763 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);
1764 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);
1769 CL_ParseParticleEffect
1771 Parse an effect out of the server message
1774 void CL_ParseParticleEffect (void)
1777 int i, count, msgcount, color;
1779 MSG_ReadVector(&cl_message, org, cls.protocol);
1780 for (i=0 ; i<3 ; i++)
1781 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1782 msgcount = MSG_ReadByte(&cl_message);
1783 color = MSG_ReadByte(&cl_message);
1785 if (msgcount == 255)
1790 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1795 CL_ParticleExplosion
1799 void CL_ParticleExplosion (const vec3_t org)
1805 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1806 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1808 if (cl_particles_quake.integer)
1810 for (i = 0;i < 1024;i++)
1816 color = particlepalette[ramp1[r]];
1817 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1821 color = particlepalette[ramp2[r]];
1822 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1828 i = CL_PointSuperContents(org);
1829 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1831 if (cl_particles.integer && cl_particles_bubbles.integer)
1832 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1833 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);
1837 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1839 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1846 VectorMA(org, 128, v2, v);
1847 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1849 while (k < 16 && trace.fraction < 0.1f);
1850 VectorSubtract(trace.endpos, org, v2);
1851 VectorScale(v2, 2.0f, v2);
1852 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);
1858 if (cl_particles_explosions_shell.integer)
1859 R_NewExplosion(org);
1864 CL_ParticleExplosion2
1868 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1871 if (!cl_particles.integer) return;
1873 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1875 k = particlepalette[colorStart + (i % colorLength)];
1876 if (cl_particles_quake.integer)
1877 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1879 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);
1883 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1886 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1887 if (cl_particles_sparks.integer)
1889 sparkcount *= cl_particles_quality.value;
1890 while(sparkcount-- > 0)
1891 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);
1895 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1898 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1899 if (cl_particles_smoke.integer)
1901 smokecount *= cl_particles_quality.value;
1902 while(smokecount-- > 0)
1903 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);
1907 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)
1911 if (!cl_particles.integer) return;
1912 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1914 count = (int)(count * cl_particles_quality.value);
1917 k = particlepalette[colorbase + (rand()&3)];
1918 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);
1922 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1925 float minz, maxz, lifetime = 30;
1927 if (!cl_particles.integer) return;
1928 if (dir[2] < 0) // falling
1930 minz = maxs[2] + dir[2] * 0.1;
1933 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1938 maxz = maxs[2] + dir[2] * 0.1;
1940 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1943 count = (int)(count * cl_particles_quality.value);
1948 if (!cl_particles_rain.integer) break;
1949 count *= 4; // ick, this should be in the mod or maps?
1953 k = particlepalette[colorbase + (rand()&3)];
1954 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1955 if (gamemode == GAME_GOODVSBAD2)
1956 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1958 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1962 if (!cl_particles_snow.integer) break;
1965 k = particlepalette[colorbase + (rand()&3)];
1966 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1967 if (gamemode == GAME_GOODVSBAD2)
1968 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1970 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);
1974 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1978 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1979 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1980 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1981 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1982 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1983 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1985 #define PARTICLETEXTURESIZE 64
1986 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1988 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1992 dz = 1 - (dx*dx+dy*dy);
1993 if (dz > 0) // it does hit the sphere
1997 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1998 VectorNormalize(normal);
1999 dot = DotProduct(normal, light);
2000 if (dot > 0.5) // interior reflection
2001 f += ((dot * 2) - 1);
2002 else if (dot < -0.5) // exterior reflection
2003 f += ((dot * -2) - 1);
2005 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2006 VectorNormalize(normal);
2007 dot = DotProduct(normal, light);
2008 if (dot > 0.5) // interior reflection
2009 f += ((dot * 2) - 1);
2010 else if (dot < -0.5) // exterior reflection
2011 f += ((dot * -2) - 1);
2013 f += 16; // just to give it a haze so you can see the outline
2014 f = bound(0, f, 255);
2015 return (unsigned char) f;
2021 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2022 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2024 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2025 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2026 *width = particlefontcellwidth;
2027 *height = particlefontcellheight;
2030 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2032 int basex, basey, w, h, y;
2033 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2034 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2035 Sys_Error("invalid particle texture size for autogenerating");
2036 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2037 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2040 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2043 float cx, cy, dx, dy, f, iradius;
2045 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2046 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2047 iradius = 1.0f / radius;
2048 alpha *= (1.0f / 255.0f);
2049 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2051 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2055 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2060 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2061 d[0] += (int)(f * (blue - d[0]));
2062 d[1] += (int)(f * (green - d[1]));
2063 d[2] += (int)(f * (red - d[2]));
2070 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2073 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2075 data[0] = bound(minb, data[0], maxb);
2076 data[1] = bound(ming, data[1], maxg);
2077 data[2] = bound(minr, data[2], maxr);
2082 static void particletextureinvert(unsigned char *data)
2085 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2087 data[0] = 255 - data[0];
2088 data[1] = 255 - data[1];
2089 data[2] = 255 - data[2];
2093 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2094 static void R_InitBloodTextures (unsigned char *particletexturedata)
2097 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2098 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2101 for (i = 0;i < 8;i++)
2103 memset(data, 255, datasize);
2104 for (k = 0;k < 24;k++)
2105 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2106 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2107 particletextureinvert(data);
2108 setuptex(tex_bloodparticle[i], data, particletexturedata);
2112 for (i = 0;i < 8;i++)
2114 memset(data, 255, datasize);
2116 for (j = 1;j < 10;j++)
2117 for (k = min(j, m - 1);k < m;k++)
2118 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2119 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2120 particletextureinvert(data);
2121 setuptex(tex_blooddecal[i], data, particletexturedata);
2127 //uncomment this to make engine save out particle font to a tga file when run
2128 //#define DUMPPARTICLEFONT
2130 static void R_InitParticleTexture (void)
2132 int x, y, d, i, k, m;
2133 int basex, basey, w, h;
2134 float dx, dy, f, s1, t1, s2, t2;
2137 fs_offset_t filesize;
2138 char texturename[MAX_QPATH];
2141 // a note: decals need to modulate (multiply) the background color to
2142 // properly darken it (stain), and they need to be able to alpha fade,
2143 // this is a very difficult challenge because it means fading to white
2144 // (no change to background) rather than black (darkening everything
2145 // behind the whole decal polygon), and to accomplish this the texture is
2146 // inverted (dark red blood on white background becomes brilliant cyan
2147 // and white on black background) so we can alpha fade it to black, then
2148 // we invert it again during the blendfunc to make it work...
2150 #ifndef DUMPPARTICLEFONT
2151 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2154 particlefonttexture = decalskinframe->base;
2155 // TODO maybe allow custom grid size?
2156 particlefontwidth = image_width;
2157 particlefontheight = image_height;
2158 particlefontcellwidth = image_width / 8;
2159 particlefontcellheight = image_height / 8;
2160 particlefontcols = 8;
2161 particlefontrows = 8;
2166 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2167 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2168 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2169 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2170 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2172 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2173 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2174 particlefontcols = 8;
2175 particlefontrows = 8;
2177 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2180 for (i = 0;i < 8;i++)
2182 memset(data, 255, datasize);
2185 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2186 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2188 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2190 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2191 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2193 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2194 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2196 d = (int)(d * (1-(dx*dx+dy*dy)));
2197 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2198 d = bound(0, d, 255);
2199 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2206 setuptex(tex_smoke[i], data, particletexturedata);
2210 memset(data, 255, datasize);
2211 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2213 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2214 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2216 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2217 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2218 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2221 setuptex(tex_rainsplash, data, particletexturedata);
2224 memset(data, 255, datasize);
2225 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2227 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2228 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2230 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2231 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2232 d = bound(0, d, 255);
2233 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2236 setuptex(tex_particle, data, particletexturedata);
2239 memset(data, 255, datasize);
2240 light[0] = 1;light[1] = 1;light[2] = 1;
2241 VectorNormalize(light);
2242 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2244 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2245 // stretch upper half of bubble by +50% and shrink lower half by -50%
2246 // (this gives an elongated teardrop shape)
2248 dy = (dy - 0.5f) * 2.0f;
2250 dy = (dy - 0.5f) / 1.5f;
2251 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2253 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2254 // shrink bubble width to half
2256 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2259 setuptex(tex_raindrop, data, particletexturedata);
2262 memset(data, 255, datasize);
2263 light[0] = 1;light[1] = 1;light[2] = 1;
2264 VectorNormalize(light);
2265 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2267 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2268 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2270 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2271 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2274 setuptex(tex_bubble, data, particletexturedata);
2276 // Blood particles and blood decals
2277 R_InitBloodTextures (particletexturedata);
2280 for (i = 0;i < 8;i++)
2282 memset(data, 255, datasize);
2283 for (k = 0;k < 12;k++)
2284 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2285 for (k = 0;k < 3;k++)
2286 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2287 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2288 particletextureinvert(data);
2289 setuptex(tex_bulletdecal[i], data, particletexturedata);
2292 #ifdef DUMPPARTICLEFONT
2293 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2296 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2297 particlefonttexture = decalskinframe->base;
2299 Mem_Free(particletexturedata);
2304 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2306 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2307 particletexture[i].texture = particlefonttexture;
2308 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2309 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2310 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2311 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2314 #ifndef DUMPPARTICLEFONT
2315 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2316 if (!particletexture[tex_beam].texture)
2319 unsigned char noise3[64][64], data2[64][16][4];
2321 fractalnoise(&noise3[0][0], 64, 4);
2323 for (y = 0;y < 64;y++)
2325 dy = (y - 0.5f*64) / (64*0.5f-1);
2326 for (x = 0;x < 16;x++)
2328 dx = (x - 0.5f*16) / (16*0.5f-2);
2329 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2330 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2331 data2[y][x][3] = 255;
2335 #ifdef DUMPPARTICLEFONT
2336 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2338 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2340 particletexture[tex_beam].s1 = 0;
2341 particletexture[tex_beam].t1 = 0;
2342 particletexture[tex_beam].s2 = 1;
2343 particletexture[tex_beam].t2 = 1;
2345 // now load an texcoord/texture override file
2346 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2353 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2355 if(!strcmp(com_token, "\n"))
2356 continue; // empty line
2357 i = atoi(com_token);
2365 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2367 strlcpy(texturename, com_token, sizeof(texturename));
2368 s1 = atof(com_token);
2369 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2372 t1 = atof(com_token);
2373 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2375 s2 = atof(com_token);
2376 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2378 t2 = atof(com_token);
2379 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2380 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2381 strlcpy(texturename, com_token, sizeof(texturename));
2388 if (!texturename[0])
2390 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2393 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2395 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2398 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2401 // R_SkinFrame_LoadExternal already complained
2404 particletexture[i].texture = sf->base;
2405 particletexture[i].s1 = s1;
2406 particletexture[i].t1 = t1;
2407 particletexture[i].s2 = s2;
2408 particletexture[i].t2 = t2;
2414 static void r_part_start(void)
2417 // generate particlepalette for convenience from the main one
2418 for (i = 0;i < 256;i++)
2419 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2420 particletexturepool = R_AllocTexturePool();
2421 R_InitParticleTexture ();
2422 CL_Particles_LoadEffectInfo(NULL);
2425 static void r_part_shutdown(void)
2427 R_FreeTexturePool(&particletexturepool);
2430 static void r_part_newmap(void)
2433 R_SkinFrame_MarkUsed(decalskinframe);
2434 CL_Particles_LoadEffectInfo(NULL);
2437 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2438 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2440 void R_Particles_Init (void)
2443 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2445 particle_elements[i*6+0] = i*4+0;
2446 particle_elements[i*6+1] = i*4+1;
2447 particle_elements[i*6+2] = i*4+2;
2448 particle_elements[i*6+3] = i*4+0;
2449 particle_elements[i*6+4] = i*4+2;
2450 particle_elements[i*6+5] = i*4+3;
2453 Cvar_RegisterVariable(&r_drawparticles);
2454 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2455 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2456 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2457 Cvar_RegisterVariable(&r_drawdecals);
2458 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2459 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2462 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2464 int surfacelistindex;
2466 float *v3f, *t2f, *c4f;
2467 particletexture_t *tex;
2468 vec_t right[3], up[3], size, ca;
2469 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2471 RSurf_ActiveWorldEntity();
2473 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2474 // R_Mesh_ResetTextureState();
2475 GL_DepthMask(false);
2476 GL_DepthRange(0, 1);
2477 GL_PolygonOffset(0, 0);
2479 GL_CullFace(GL_NONE);
2481 // generate all the vertices at once
2482 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2484 d = cl.decals + surfacelist[surfacelistindex];
2487 c4f = particle_color4f + 16*surfacelistindex;
2488 ca = d->alpha * alphascale;
2489 // ensure alpha multiplier saturates properly
2490 if (ca > 1.0f / 256.0f)
2492 if (r_refdef.fogenabled)
2493 ca *= RSurf_FogVertex(d->org);
2494 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2495 Vector4Copy(c4f, c4f + 4);
2496 Vector4Copy(c4f, c4f + 8);
2497 Vector4Copy(c4f, c4f + 12);
2499 // calculate vertex positions
2500 size = d->size * cl_particles_size.value;
2501 VectorVectors(d->normal, right, up);
2502 VectorScale(right, size, right);
2503 VectorScale(up, size, up);
2504 v3f = particle_vertex3f + 12*surfacelistindex;
2505 v3f[ 0] = d->org[0] - right[0] - up[0];
2506 v3f[ 1] = d->org[1] - right[1] - up[1];
2507 v3f[ 2] = d->org[2] - right[2] - up[2];
2508 v3f[ 3] = d->org[0] - right[0] + up[0];
2509 v3f[ 4] = d->org[1] - right[1] + up[1];
2510 v3f[ 5] = d->org[2] - right[2] + up[2];
2511 v3f[ 6] = d->org[0] + right[0] + up[0];
2512 v3f[ 7] = d->org[1] + right[1] + up[1];
2513 v3f[ 8] = d->org[2] + right[2] + up[2];
2514 v3f[ 9] = d->org[0] + right[0] - up[0];
2515 v3f[10] = d->org[1] + right[1] - up[1];
2516 v3f[11] = d->org[2] + right[2] - up[2];
2518 // calculate texcoords
2519 tex = &particletexture[d->texnum];
2520 t2f = particle_texcoord2f + 8*surfacelistindex;
2521 t2f[0] = tex->s1;t2f[1] = tex->t2;
2522 t2f[2] = tex->s1;t2f[3] = tex->t1;
2523 t2f[4] = tex->s2;t2f[5] = tex->t1;
2524 t2f[6] = tex->s2;t2f[7] = tex->t2;
2527 // now render the decals all at once
2528 // (this assumes they all use one particle font texture!)
2529 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2530 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2531 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2532 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2535 void R_DrawDecals (void)
2538 int drawdecals = r_drawdecals.integer;
2543 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2545 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2546 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2548 // LordHavoc: early out conditions
2552 decalfade = frametime * 256 / cl_decals_fadetime.value;
2553 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2554 drawdist2 = drawdist2*drawdist2;
2556 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2558 if (!decal->typeindex)
2561 if (killsequence - decal->decalsequence > 0)
2564 if (cl.time > decal->time2 + cl_decals_time.value)
2566 decal->alpha -= decalfade;
2567 if (decal->alpha <= 0)
2573 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2575 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2576 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2582 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2588 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2589 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2592 decal->typeindex = 0;
2593 if (cl.free_decal > i)
2597 // reduce cl.num_decals if possible
2598 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2601 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2603 decal_t *olddecals = cl.decals;
2604 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2605 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2606 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2607 Mem_Free(olddecals);
2610 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2613 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2615 vec3_t vecorg, vecvel, baseright, baseup;
2616 int surfacelistindex;
2617 int batchstart, batchcount;
2618 const particle_t *p;
2620 rtexture_t *texture;
2621 float *v3f, *t2f, *c4f;
2622 particletexture_t *tex;
2623 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2624 // float ambient[3], diffuse[3], diffusenormal[3];
2625 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2626 vec4_t colormultiplier;
2627 float minparticledist_start, minparticledist_end;
2630 RSurf_ActiveWorldEntity();
2632 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));
2634 r_refdef.stats[r_stat_particles] += numsurfaces;
2635 // R_Mesh_ResetTextureState();
2636 GL_DepthMask(false);
2637 GL_DepthRange(0, 1);
2638 GL_PolygonOffset(0, 0);
2640 GL_CullFace(GL_NONE);
2642 spintime = r_refdef.scene.time;
2644 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2645 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2646 dofade = (minparticledist_start < minparticledist_end);
2648 // first generate all the vertices at once
2649 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2651 p = cl.particles + surfacelist[surfacelistindex];
2653 blendmode = (pblend_t)p->blendmode;
2655 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2656 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2657 alpha = palpha * colormultiplier[3];
2658 // ensure alpha multiplier saturates properly
2664 case PBLEND_INVALID:
2666 // additive and modulate can just fade out in fog (this is correct)
2667 if (r_refdef.fogenabled)
2668 alpha *= RSurf_FogVertex(p->org);
2669 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2670 alpha *= 1.0f / 256.0f;
2671 c4f[0] = p->color[0] * alpha;
2672 c4f[1] = p->color[1] * alpha;
2673 c4f[2] = p->color[2] * alpha;
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 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2682 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2683 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2687 c4f[0] = p->color[0] * colormultiplier[0];
2688 c4f[1] = p->color[1] * colormultiplier[1];
2689 c4f[2] = p->color[2] * colormultiplier[2];
2691 // note: lighting is not cheap!
2692 if (particletype[p->typeindex].lighting)
2694 vecorg[0] = p->org[0];
2695 vecorg[1] = p->org[1];
2696 vecorg[2] = p->org[2];
2697 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2699 // mix in the fog color
2700 if (r_refdef.fogenabled)
2702 fog = RSurf_FogVertex(p->org);
2704 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2705 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2706 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2708 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2709 VectorScale(c4f, alpha, c4f);
2712 // copy the color into the other three vertices
2713 Vector4Copy(c4f, c4f + 4);
2714 Vector4Copy(c4f, c4f + 8);
2715 Vector4Copy(c4f, c4f + 12);
2717 size = p->size * cl_particles_size.value;
2718 tex = &particletexture[p->texnum];
2719 switch(p->orientation)
2721 // case PARTICLE_INVALID:
2722 case PARTICLE_BILLBOARD:
2723 if (p->angle + p->spin)
2725 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2726 spinsin = sin(spinrad) * size;
2727 spincos = cos(spinrad) * size;
2728 spinm1 = -p->stretch * spincos;
2731 spinm4 = -p->stretch * spincos;
2732 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2733 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2737 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2738 VectorScale(r_refdef.view.up, size, up);
2741 v3f[ 0] = p->org[0] - right[0] - up[0];
2742 v3f[ 1] = p->org[1] - right[1] - up[1];
2743 v3f[ 2] = p->org[2] - right[2] - up[2];
2744 v3f[ 3] = p->org[0] - right[0] + up[0];
2745 v3f[ 4] = p->org[1] - right[1] + up[1];
2746 v3f[ 5] = p->org[2] - right[2] + up[2];
2747 v3f[ 6] = p->org[0] + right[0] + up[0];
2748 v3f[ 7] = p->org[1] + right[1] + up[1];
2749 v3f[ 8] = p->org[2] + right[2] + up[2];
2750 v3f[ 9] = p->org[0] + right[0] - up[0];
2751 v3f[10] = p->org[1] + right[1] - up[1];
2752 v3f[11] = p->org[2] + right[2] - up[2];
2753 t2f[0] = tex->s1;t2f[1] = tex->t2;
2754 t2f[2] = tex->s1;t2f[3] = tex->t1;
2755 t2f[4] = tex->s2;t2f[5] = tex->t1;
2756 t2f[6] = tex->s2;t2f[7] = tex->t2;
2758 case PARTICLE_ORIENTED_DOUBLESIDED:
2759 vecvel[0] = p->vel[0];
2760 vecvel[1] = p->vel[1];
2761 vecvel[2] = p->vel[2];
2762 VectorVectors(vecvel, baseright, baseup);
2763 if (p->angle + p->spin)
2765 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2766 spinsin = sin(spinrad) * size;
2767 spincos = cos(spinrad) * size;
2768 spinm1 = p->stretch * spincos;
2771 spinm4 = p->stretch * spincos;
2772 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2773 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2777 VectorScale(baseright, size * p->stretch, right);
2778 VectorScale(baseup, size, up);
2780 v3f[ 0] = p->org[0] - right[0] - up[0];
2781 v3f[ 1] = p->org[1] - right[1] - up[1];
2782 v3f[ 2] = p->org[2] - right[2] - up[2];
2783 v3f[ 3] = p->org[0] - right[0] + up[0];
2784 v3f[ 4] = p->org[1] - right[1] + up[1];
2785 v3f[ 5] = p->org[2] - right[2] + up[2];
2786 v3f[ 6] = p->org[0] + right[0] + up[0];
2787 v3f[ 7] = p->org[1] + right[1] + up[1];
2788 v3f[ 8] = p->org[2] + right[2] + up[2];
2789 v3f[ 9] = p->org[0] + right[0] - up[0];
2790 v3f[10] = p->org[1] + right[1] - up[1];
2791 v3f[11] = p->org[2] + right[2] - up[2];
2792 t2f[0] = tex->s1;t2f[1] = tex->t2;
2793 t2f[2] = tex->s1;t2f[3] = tex->t1;
2794 t2f[4] = tex->s2;t2f[5] = tex->t1;
2795 t2f[6] = tex->s2;t2f[7] = tex->t2;
2797 case PARTICLE_SPARK:
2798 len = VectorLength(p->vel);
2799 VectorNormalize2(p->vel, up);
2800 lenfactor = p->stretch * 0.04 * len;
2801 if(lenfactor < size * 0.5)
2802 lenfactor = size * 0.5;
2803 VectorMA(p->org, -lenfactor, up, v);
2804 VectorMA(p->org, lenfactor, up, up2);
2805 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2806 t2f[0] = tex->s1;t2f[1] = tex->t2;
2807 t2f[2] = tex->s1;t2f[3] = tex->t1;
2808 t2f[4] = tex->s2;t2f[5] = tex->t1;
2809 t2f[6] = tex->s2;t2f[7] = tex->t2;
2811 case PARTICLE_VBEAM:
2812 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2813 VectorSubtract(p->vel, p->org, up);
2814 VectorNormalize(up);
2815 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2816 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2817 t2f[0] = tex->s2;t2f[1] = v[0];
2818 t2f[2] = tex->s1;t2f[3] = v[0];
2819 t2f[4] = tex->s1;t2f[5] = v[1];
2820 t2f[6] = tex->s2;t2f[7] = v[1];
2822 case PARTICLE_HBEAM:
2823 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2824 VectorSubtract(p->vel, p->org, up);
2825 VectorNormalize(up);
2826 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2827 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2828 t2f[0] = v[0];t2f[1] = tex->t1;
2829 t2f[2] = v[0];t2f[3] = tex->t2;
2830 t2f[4] = v[1];t2f[5] = tex->t2;
2831 t2f[6] = v[1];t2f[7] = tex->t1;
2836 // now render batches of particles based on blendmode and texture
2837 blendmode = PBLEND_INVALID;
2841 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2842 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2844 p = cl.particles + surfacelist[surfacelistindex];
2846 if (texture != particletexture[p->texnum].texture)
2848 texture = particletexture[p->texnum].texture;
2849 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2852 if (p->blendmode == PBLEND_INVMOD)
2854 // inverse modulate blend - group these
2855 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2856 // iterate until we find a change in settings
2857 batchstart = surfacelistindex++;
2858 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2860 p = cl.particles + surfacelist[surfacelistindex];
2861 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2867 // additive or alpha blend - group these
2868 // (we can group these because we premultiplied the texture alpha)
2869 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2870 // iterate until we find a change in settings
2871 batchstart = surfacelistindex++;
2872 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2874 p = cl.particles + surfacelist[surfacelistindex];
2875 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2880 batchcount = surfacelistindex - batchstart;
2881 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2885 void R_DrawParticles (void)
2888 int drawparticles = r_drawparticles.integer;
2889 float minparticledist_start;
2891 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2897 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2898 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2900 // LordHavoc: early out conditions
2901 if (!cl.num_particles)
2904 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2905 gravity = frametime * cl.movevars_gravity;
2906 update = frametime > 0;
2907 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2908 drawdist2 = drawdist2*drawdist2;
2910 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2914 if (cl.free_particle > i)
2915 cl.free_particle = i;
2921 if (p->delayedspawn > cl.time)
2924 p->size += p->sizeincrease * frametime;
2925 p->alpha -= p->alphafade * frametime;
2927 if (p->alpha <= 0 || p->die <= cl.time)
2930 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2932 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2934 if (p->typeindex == pt_blood)
2935 p->size += frametime * 8;
2937 p->vel[2] -= p->gravity * gravity;
2938 f = 1.0f - min(p->liquidfriction * frametime, 1);
2939 VectorScale(p->vel, f, p->vel);
2943 p->vel[2] -= p->gravity * gravity;
2946 f = 1.0f - min(p->airfriction * frametime, 1);
2947 VectorScale(p->vel, f, p->vel);
2951 VectorCopy(p->org, oldorg);
2952 VectorMA(p->org, frametime, p->vel, p->org);
2953 // if (p->bounce && cl.time >= p->delayedcollisions)
2954 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2956 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false, false);
2957 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2958 // or if the trace hit something flagged as NOIMPACT
2959 // then remove the particle
2960 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2962 VectorCopy(trace.endpos, p->org);
2963 // react if the particle hit something
2964 if (trace.fraction < 1)
2966 VectorCopy(trace.endpos, p->org);
2968 if (p->staintexnum >= 0)
2970 // blood - splash on solid
2971 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2974 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2975 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2976 if (cl_decals.integer)
2978 // create a decal for the blood splat
2979 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2980 if (cl_decals_newsystem_bloodsmears.integer)
2982 VectorCopy(p->vel, decaldir);
2983 VectorNormalize(decaldir);
2986 VectorCopy(trace.plane.normal, decaldir);
2987 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2992 if (p->typeindex == pt_blood)
2994 // blood - splash on solid
2995 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2997 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2999 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)));
3000 if (cl_decals.integer)
3002 // create a decal for the blood splat
3003 if (cl_decals_newsystem_bloodsmears.integer)
3005 VectorCopy(p->vel, decaldir);
3006 VectorNormalize(decaldir);
3009 VectorCopy(trace.plane.normal, decaldir);
3010 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);
3015 else if (p->bounce < 0)
3017 // bounce -1 means remove on impact
3022 // anything else - bounce off solid
3023 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3024 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3029 if (VectorLength2(p->vel) < 0.03)
3031 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3033 VectorClear(p->vel);
3037 if (p->typeindex != pt_static)
3039 switch (p->typeindex)
3041 case pt_entityparticle:
3042 // particle that removes itself after one rendered frame
3049 a = CL_PointSuperContents(p->org);
3050 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3054 a = CL_PointSuperContents(p->org);
3055 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3059 a = CL_PointSuperContents(p->org);
3060 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3064 if (cl.time > p->time2)
3067 p->time2 = cl.time + (rand() & 3) * 0.1;
3068 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3069 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3071 a = CL_PointSuperContents(p->org);
3072 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3080 else if (p->delayedspawn > cl.time)
3084 // don't render particles too close to the view (they chew fillrate)
3085 // also don't render particles behind the view (useless)
3086 // further checks to cull to the frustum would be too slow here
3087 switch(p->typeindex)
3090 // beams have no culling
3091 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3094 if(cl_particles_visculling.integer)
3095 if (!r_refdef.viewcache.world_novis)
3096 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3098 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3100 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3103 // anything else just has to be in front of the viewer and visible at this distance
3104 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3105 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3112 if (cl.free_particle > i)
3113 cl.free_particle = i;
3116 // reduce cl.num_particles if possible
3117 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3120 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3122 particle_t *oldparticles = cl.particles;
3123 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3124 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3125 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3126 Mem_Free(oldparticles);