2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 #define PARTICLEEFFECT_DEFINED 2147483648U
49 typedef struct particleeffectinfo_s
51 int effectnameindex; // which effect this belongs to
52 // PARTICLEEFFECT_* bits
54 // blood effects may spawn very few particles, so proper fraction-overflow
55 // handling is very important, this variable keeps track of the fraction
56 double particleaccumulator;
57 // the math is: countabsolute + requestedcount * countmultiplier * quality
58 // absolute number of particles to spawn, often used for decals
59 // (unaffected by quality and requestedcount)
61 // multiplier for the number of particles CL_ParticleEffect was told to
62 // spawn, most effects do not really have a count and hence use 1, so
63 // this is often the actual count to spawn, not merely a multiplier
64 float countmultiplier;
65 // if > 0 this causes the particle to spawn in an evenly spaced line from
66 // originmins to originmaxs (causing them to describe a trail, not a box)
68 // type of particle to spawn (defines some aspects of behavior)
70 // blending mode used on this particle type
72 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
73 porientation_t orientation;
74 // range of colors to choose from in hex RRGGBB (like HTML color tags),
75 // randomly interpolated at spawn
76 unsigned int color[2];
77 // a random texture is chosen in this range (note the second value is one
78 // past the last choosable, so for example 8,16 chooses any from 8 up and
80 // if start and end of the range are the same, no randomization is done
82 // range of size values randomly chosen when spawning, plus size increase over time
84 // range of alpha values randomly chosen when spawning, plus alpha fade
86 // how long the particle should live (note it is also removed if alpha drops to 0)
88 // how much gravity affects this particle (negative makes it fly up!)
90 // how much bounce the particle has when it hits a surface
91 // if negative the particle is removed on impact
93 // if in air this friction is applied
94 // if negative the particle accelerates
96 // if in liquid (water/slime/lava) this friction is applied
97 // if negative the particle accelerates
99 // these offsets are added to the values given to particleeffect(), and
100 // then an ellipsoid-shaped jitter is added as defined by these
101 // (they are the 3 radii)
103 // stretch velocity factor (used for sparks)
104 float originoffset[3];
105 float relativeoriginoffset[3];
106 float velocityoffset[3];
107 float relativevelocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 float lightcorona[2];
119 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
124 float rotate[4]; // min/max base angle, min/max rotation over time
126 particleeffectinfo_t;
128 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
130 int numparticleeffectinfo;
131 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
133 static int particlepalette[256];
135 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
136 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
137 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
138 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
139 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
140 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
141 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
142 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
143 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
144 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
145 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
146 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
147 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
148 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
149 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
150 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
151 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
152 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
153 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
154 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
155 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
156 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
157 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
158 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
159 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
160 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
161 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
162 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
163 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
164 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
165 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
166 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
169 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
170 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
171 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
173 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
175 // particletexture_t is a rectangle in the particlefonttexture
176 typedef struct particletexture_s
179 float s1, t1, s2, t2;
183 static rtexturepool_t *particletexturepool;
184 static rtexture_t *particlefonttexture;
185 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
186 skinframe_t *decalskinframe;
188 // texture numbers in particle font
189 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
190 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
191 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
192 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
193 static const int tex_rainsplash = 32;
194 static const int tex_particle = 63;
195 static const int tex_bubble = 62;
196 static const int tex_raindrop = 61;
197 static const int tex_beam = 60;
199 particleeffectinfo_t baselineparticleeffectinfo =
201 0, //int effectnameindex; // which effect this belongs to
202 // PARTICLEEFFECT_* bits
204 // blood effects may spawn very few particles, so proper fraction-overflow
205 // handling is very important, this variable keeps track of the fraction
206 0.0, //double particleaccumulator;
207 // the math is: countabsolute + requestedcount * countmultiplier * quality
208 // absolute number of particles to spawn, often used for decals
209 // (unaffected by quality and requestedcount)
210 0.0f, //float countabsolute;
211 // multiplier for the number of particles CL_ParticleEffect was told to
212 // spawn, most effects do not really have a count and hence use 1, so
213 // this is often the actual count to spawn, not merely a multiplier
214 0.0f, //float countmultiplier;
215 // if > 0 this causes the particle to spawn in an evenly spaced line from
216 // originmins to originmaxs (causing them to describe a trail, not a box)
217 0.0f, //float trailspacing;
218 // type of particle to spawn (defines some aspects of behavior)
219 pt_alphastatic, //ptype_t particletype;
220 // blending mode used on this particle type
221 PBLEND_ALPHA, //pblend_t blendmode;
222 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
223 PARTICLE_BILLBOARD, //porientation_t orientation;
224 // range of colors to choose from in hex RRGGBB (like HTML color tags),
225 // randomly interpolated at spawn
226 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
227 // a random texture is chosen in this range (note the second value is one
228 // past the last choosable, so for example 8,16 chooses any from 8 up and
230 // if start and end of the range are the same, no randomization is done
231 {63, 63 /* tex_particle */}, //int tex[2];
232 // range of size values randomly chosen when spawning, plus size increase over time
233 {1, 1, 0.0f}, //float size[3];
234 // range of alpha values randomly chosen when spawning, plus alpha fade
235 {0.0f, 256.0f, 256.0f}, //float alpha[3];
236 // how long the particle should live (note it is also removed if alpha drops to 0)
237 {16777216.0f, 16777216.0f}, //float time[2];
238 // how much gravity affects this particle (negative makes it fly up!)
239 0.0f, //float gravity;
240 // how much bounce the particle has when it hits a surface
241 // if negative the particle is removed on impact
242 0.0f, //float bounce;
243 // if in air this friction is applied
244 // if negative the particle accelerates
245 0.0f, //float airfriction;
246 // if in liquid (water/slime/lava) this friction is applied
247 // if negative the particle accelerates
248 0.0f, //float liquidfriction;
249 // these offsets are added to the values given to particleeffect(), and
250 // then an ellipsoid-shaped jitter is added as defined by these
251 // (they are the 3 radii)
252 1.0f, //float stretchfactor;
253 // stretch velocity factor (used for sparks)
254 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
258 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
259 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
260 0.0f, //float velocitymultiplier;
261 // an effect can also spawn a dlight
262 0.0f, //float lightradiusstart;
263 0.0f, //float lightradiusfade;
264 16777216.0f, //float lighttime;
265 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
266 true, //qboolean lightshadow;
267 0, //int lightcubemapnum;
268 {1.0f, 0.25f}, //float lightcorona[2];
269 {(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!
270 {-1, -1}, //int staintex[2];
271 {1.0f, 1.0f}, //float stainalpha[2];
272 {2.0f, 2.0f}, //float stainsize[2];
274 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
277 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
278 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
279 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
280 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
281 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
282 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
283 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
284 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
285 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
286 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
287 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
288 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
289 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
290 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
291 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
292 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
293 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
294 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
295 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
296 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
297 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
298 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
299 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)"};
300 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)"};
301 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
302 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
303 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
304 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
305 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
306 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)"};
307 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"};
308 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"};
309 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
310 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
311 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
314 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
320 particleeffectinfo_t *info = NULL;
321 const char *text = textstart;
323 for (linenumber = 1;;linenumber++)
326 for (arrayindex = 0;arrayindex < 16;arrayindex++)
327 argv[arrayindex][0] = 0;
330 if (!COM_ParseToken_Simple(&text, true, false, true))
332 if (!strcmp(com_token, "\n"))
336 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
342 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
343 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
344 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
345 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
346 #define readfloat(var) checkparms(2);var = atof(argv[1])
347 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
348 if (!strcmp(argv[0], "effect"))
352 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
354 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
357 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
359 if (particleeffectname[effectnameindex][0])
361 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
366 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
370 // if we run out of names, abort
371 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
373 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
376 for(i = 0; i < numparticleeffectinfo; ++i)
378 info = particleeffectinfo + i;
379 if(!(info->flags & PARTICLEEFFECT_DEFINED))
380 if(info->effectnameindex == effectnameindex)
383 if(i < numparticleeffectinfo)
385 info = particleeffectinfo + numparticleeffectinfo++;
386 // copy entire info from baseline, then fix up the nameindex
387 *info = baselineparticleeffectinfo;
388 info->effectnameindex = effectnameindex;
391 else if (info == NULL)
393 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
397 info->flags |= PARTICLEEFFECT_DEFINED;
398 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
399 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
400 else if (!strcmp(argv[0], "type"))
403 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
404 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
405 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
406 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
407 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
408 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
409 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
410 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
411 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
412 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
413 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
414 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
415 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
416 info->blendmode = particletype[info->particletype].blendmode;
417 info->orientation = particletype[info->particletype].orientation;
419 else if (!strcmp(argv[0], "blend"))
422 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
423 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
424 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
425 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
427 else if (!strcmp(argv[0], "orientation"))
430 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
431 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
432 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
433 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
434 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
436 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
437 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
438 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
439 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
440 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
441 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
442 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
443 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
444 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
445 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
446 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
447 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
448 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
449 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
450 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
451 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
452 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
453 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
454 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
455 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
456 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
457 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
458 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
459 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
460 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
461 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
462 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
463 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
464 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
465 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
466 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
467 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
468 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
469 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
471 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
480 int CL_ParticleEffectIndexForName(const char *name)
483 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
484 if (!strcmp(particleeffectname[i], name))
489 const char *CL_ParticleEffectNameForIndex(int i)
491 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
493 return particleeffectname[i];
496 // MUST match effectnameindex_t in client.h
497 static const char *standardeffectnames[EFFECT_TOTAL] =
521 "TE_TEI_BIGEXPLOSION",
537 static void CL_Particles_LoadEffectInfo(const char *customfile)
541 unsigned char *filedata;
542 fs_offset_t filesize;
543 char filename[MAX_QPATH];
544 numparticleeffectinfo = 0;
545 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
546 memset(particleeffectname, 0, sizeof(particleeffectname));
547 for (i = 0;i < EFFECT_TOTAL;i++)
548 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
549 for (filepass = 0;;filepass++)
554 strlcpy(filename, customfile, sizeof(filename));
556 strlcpy(filename, "effectinfo.txt", sizeof(filename));
558 else if (filepass == 1)
560 if (!cl.worldbasename[0] || customfile)
562 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
566 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
569 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
574 static void CL_Particles_LoadEffectInfo_f(cmd_state_t *cmd)
576 CL_Particles_LoadEffectInfo(Cmd_Argc(cmd) > 1 ? Cmd_Argv(cmd, 1) : NULL);
584 void CL_ReadPointFile_f(cmd_state_t *cmd);
585 void CL_Particles_Init (void)
587 Cmd_AddCommand(&cmd_client, "pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
588 Cmd_AddCommand(&cmd_client, "cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
590 Cvar_RegisterVariable (&cl_particles);
591 Cvar_RegisterVariable (&cl_particles_quality);
592 Cvar_RegisterVariable (&cl_particles_alpha);
593 Cvar_RegisterVariable (&cl_particles_size);
594 Cvar_RegisterVariable (&cl_particles_quake);
595 Cvar_RegisterVariable (&cl_particles_blood);
596 Cvar_RegisterVariable (&cl_particles_blood_alpha);
597 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
598 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
599 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
600 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
601 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
602 Cvar_RegisterVariable (&cl_particles_explosions_shell);
603 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
604 Cvar_RegisterVariable (&cl_particles_rain);
605 Cvar_RegisterVariable (&cl_particles_snow);
606 Cvar_RegisterVariable (&cl_particles_smoke);
607 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
608 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
609 Cvar_RegisterVariable (&cl_particles_sparks);
610 Cvar_RegisterVariable (&cl_particles_bubbles);
611 Cvar_RegisterVariable (&cl_particles_visculling);
612 Cvar_RegisterVariable (&cl_particles_collisions);
613 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
614 Cvar_RegisterVariable (&cl_decals);
615 Cvar_RegisterVariable (&cl_decals_visculling);
616 Cvar_RegisterVariable (&cl_decals_time);
617 Cvar_RegisterVariable (&cl_decals_fadetime);
618 Cvar_RegisterVariable (&cl_decals_newsystem);
619 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
620 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
621 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
622 Cvar_RegisterVariable (&cl_decals_models);
623 Cvar_RegisterVariable (&cl_decals_bias);
624 Cvar_RegisterVariable (&cl_decals_max);
627 void CL_Particles_Shutdown (void)
631 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
632 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
634 // list of all 26 parameters:
635 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
636 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
637 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
638 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
639 // palpha - opacity of particle as 0-255 (can be more than 255)
640 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
641 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
642 // pgravity - how much effect gravity has on the particle (0-1)
643 // 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
644 // px,py,pz - starting origin of particle
645 // pvx,pvy,pvz - starting velocity of particle
646 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
647 // blendmode - one of the PBLEND_ values
648 // orientation - one of the PARTICLE_ values
649 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
650 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
651 // stainalpha: opacity of the stain as factor for alpha
652 // stainsize: size of the stain as factor for palpha
653 // angle: base rotation of the particle geometry around its center normal
654 // spin: rotation speed of the particle geometry around its center normal
655 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])
660 if (!cl_particles.integer)
662 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
663 if (cl.free_particle >= cl.max_particles)
666 lifetime = palpha / min(1, palphafade);
667 part = &cl.particles[cl.free_particle++];
668 if (cl.num_particles < cl.free_particle)
669 cl.num_particles = cl.free_particle;
670 memset(part, 0, sizeof(*part));
671 VectorCopy(sortorigin, part->sortorigin);
672 part->typeindex = ptypeindex;
673 part->blendmode = blendmode;
674 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
676 particletexture_t *tex = &particletexture[ptex];
677 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
678 part->orientation = PARTICLE_VBEAM;
680 part->orientation = PARTICLE_HBEAM;
683 part->orientation = orientation;
684 l2 = (int)lhrandom(0.5, 256.5);
686 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
687 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
688 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
691 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
692 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
693 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
695 part->alpha = palpha;
696 part->alphafade = palphafade;
697 part->staintexnum = staintex;
698 if(staincolor1 >= 0 && staincolor2 >= 0)
700 l2 = (int)lhrandom(0.5, 256.5);
702 if(blendmode == PBLEND_INVMOD)
704 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
705 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
706 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
710 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
711 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
712 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
714 if(r > 0xFF) r = 0xFF;
715 if(g > 0xFF) g = 0xFF;
716 if(b > 0xFF) b = 0xFF;
720 r = part->color[0]; // -1 is shorthand for stain = particle color
724 part->staincolor[0] = r;
725 part->staincolor[1] = g;
726 part->staincolor[2] = b;
727 part->stainalpha = palpha * stainalpha;
728 part->stainsize = psize * stainsize;
731 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
733 part->color[0] *= tint[0];
734 part->color[1] *= tint[1];
735 part->color[2] *= tint[2];
737 part->alpha *= tint[3];
738 part->alphafade *= tint[3];
739 part->stainalpha *= tint[3];
743 part->sizeincrease = psizeincrease;
744 part->gravity = pgravity;
745 part->bounce = pbounce;
746 part->stretch = stretch;
748 part->org[0] = px + originjitter * v[0];
749 part->org[1] = py + originjitter * v[1];
750 part->org[2] = pz + originjitter * v[2];
751 part->vel[0] = pvx + velocityjitter * v[0];
752 part->vel[1] = pvy + velocityjitter * v[1];
753 part->vel[2] = pvz + velocityjitter * v[2];
755 part->airfriction = pairfriction;
756 part->liquidfriction = pliquidfriction;
757 part->die = cl.time + lifetime;
758 part->delayedspawn = cl.time;
759 // part->delayedcollisions = 0;
760 part->qualityreduction = pqualityreduction;
763 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
764 if (part->typeindex == pt_rain)
770 // turn raindrop into simple spark and create delayedspawn splash effect
771 part->typeindex = pt_spark;
773 VectorMA(part->org, lifetime, part->vel, endvec);
774 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
775 part->die = cl.time + lifetime * trace.fraction;
776 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);
779 part2->delayedspawn = part->die;
780 part2->die += part->die - cl.time;
781 for (i = rand() & 7;i < 10;i++)
783 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);
786 part2->delayedspawn = part->die;
787 part2->die += part->die - cl.time;
793 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
795 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
798 VectorMA(part->org, lifetime, part->vel, endvec);
799 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
800 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
807 static void CL_ImmediateBloodStain(particle_t *part)
812 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
813 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
815 VectorCopy(part->vel, v);
817 staintex = part->staintexnum;
818 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);
821 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
822 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
824 VectorCopy(part->vel, v);
826 staintex = tex_blooddecal[rand()&7];
827 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);
831 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
835 entity_render_t *ent = &cl.entities[hitent].render;
836 unsigned char color[3];
837 if (!cl_decals.integer)
839 if (!ent->allowdecals)
842 l2 = (int)lhrandom(0.5, 256.5);
844 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
845 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
846 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
848 if (cl_decals_newsystem.integer)
851 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);
853 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);
857 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
858 if (cl.free_decal >= cl.max_decals)
860 decal = &cl.decals[cl.free_decal++];
861 if (cl.num_decals < cl.free_decal)
862 cl.num_decals = cl.free_decal;
863 memset(decal, 0, sizeof(*decal));
864 decal->decalsequence = cl.decalsequence++;
865 decal->typeindex = pt_decal;
866 decal->texnum = texnum;
867 VectorMA(org, cl_decals_bias.value, normal, decal->org);
868 VectorCopy(normal, decal->normal);
870 decal->alpha = alpha;
871 decal->time2 = cl.time;
872 decal->color[0] = color[0];
873 decal->color[1] = color[1];
874 decal->color[2] = color[2];
877 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
878 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
879 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
881 decal->owner = hitent;
882 decal->clusterindex = -1000; // no vis culling unless we're sure
885 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
886 decal->ownermodel = cl.entities[decal->owner].render.model;
887 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
888 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
892 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
894 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
896 decal->clusterindex = leaf->clusterindex;
901 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
908 int besthitent = 0, hitent;
911 for (i = 0;i < 32;i++)
914 VectorMA(org, maxdist, org2, org2);
915 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
916 // take the closest trace result that doesn't end up hitting a NOMARKS
917 // surface (sky for example)
918 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
920 bestfrac = trace.fraction;
922 VectorCopy(trace.endpos, bestorg);
923 VectorCopy(trace.plane.normal, bestnormal);
927 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
930 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
931 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
932 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);
933 static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, qboolean wanttrail)
936 matrix4x4_t lightmatrix;
939 VectorLerp(originmins, 0.5, originmaxs, center);
940 Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
941 if (effectnameindex == EFFECT_SVC_PARTICLE)
943 if (cl_particles.integer)
945 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
947 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
948 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
949 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
952 count *= cl_particles_quality.value;
953 for (;count > 0;count--)
955 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
956 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);
961 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
962 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
963 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
964 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
965 else if (effectnameindex == EFFECT_TE_SPIKE)
967 if (cl_particles_bulletimpacts.integer)
969 if (cl_particles_quake.integer)
971 if (cl_particles_smoke.integer)
972 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
976 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
977 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
978 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);
982 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
983 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
985 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
987 if (cl_particles_bulletimpacts.integer)
989 if (cl_particles_quake.integer)
991 if (cl_particles_smoke.integer)
992 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
996 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
997 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
998 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);
1002 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1003 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1004 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1006 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1008 if (cl_particles_bulletimpacts.integer)
1010 if (cl_particles_quake.integer)
1012 if (cl_particles_smoke.integer)
1013 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1017 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1018 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1019 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);
1023 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1024 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1026 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1028 if (cl_particles_bulletimpacts.integer)
1030 if (cl_particles_quake.integer)
1032 if (cl_particles_smoke.integer)
1033 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1037 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1038 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1039 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);
1043 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1044 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1045 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1047 else if (effectnameindex == EFFECT_TE_BLOOD)
1049 if (!cl_particles_blood.integer)
1051 if (cl_particles_quake.integer)
1052 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1055 static double bloodaccumulator = 0;
1056 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1057 //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);
1058 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1059 for (;bloodaccumulator > 0;bloodaccumulator--)
1061 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);
1062 if (immediatebloodstain && part)
1064 immediatebloodstain = false;
1065 CL_ImmediateBloodStain(part);
1070 else if (effectnameindex == EFFECT_TE_SPARK)
1071 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1072 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1074 // plasma scorch mark
1075 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1076 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1077 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1079 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1081 if (cl_particles_bulletimpacts.integer)
1083 if (cl_particles_quake.integer)
1084 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1087 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1088 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1089 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);
1093 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1094 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1096 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1098 if (cl_particles_bulletimpacts.integer)
1100 if (cl_particles_quake.integer)
1101 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1104 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1105 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1106 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1110 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1111 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1112 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1114 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1116 CL_ParticleExplosion(center);
1117 CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1119 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1121 CL_ParticleExplosion(center);
1122 CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1124 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1126 if (cl_particles_quake.integer)
1129 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1132 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);
1134 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);
1138 CL_ParticleExplosion(center);
1139 CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1141 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1142 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1143 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1145 count *= cl_particles_quality.value;
1147 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);
1149 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1151 float i, j, inc, vel;
1154 inc = 8 / cl_particles_quality.value;
1155 for (i = -128;i < 128;i += inc)
1157 for (j = -128;j < 128;j += inc)
1159 dir[0] = j + lhrandom(0, inc);
1160 dir[1] = i + lhrandom(0, inc);
1162 org[0] = center[0] + dir[0];
1163 org[1] = center[1] + dir[1];
1164 org[2] = center[2] + lhrandom(0, 64);
1165 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1166 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);
1170 else if (effectnameindex == EFFECT_TE_TELEPORT)
1172 float i, j, k, inc, vel;
1175 if (cl_particles_quake.integer)
1176 inc = 4 / cl_particles_quality.value;
1178 inc = 8 / cl_particles_quality.value;
1179 for (i = -16;i < 16;i += inc)
1181 for (j = -16;j < 16;j += inc)
1183 for (k = -24;k < 32;k += inc)
1185 VectorSet(dir, i*8, j*8, k*8);
1186 VectorNormalize(dir);
1187 vel = lhrandom(50, 113);
1188 if (cl_particles_quake.integer)
1189 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);
1191 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);
1195 if (!cl_particles_quake.integer)
1196 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);
1197 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1199 else if (effectnameindex == EFFECT_TE_TEI_G3)
1200 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);
1201 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1203 if (cl_particles_smoke.integer)
1205 count *= 0.25f * cl_particles_quality.value;
1207 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);
1210 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1212 CL_ParticleExplosion(center);
1213 CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1215 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1218 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1219 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1220 if (cl_particles_smoke.integer)
1221 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1222 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);
1223 if (cl_particles_sparks.integer)
1224 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1225 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);
1226 CL_AllocLightFlash(NULL, &lightmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1228 else if (effectnameindex == EFFECT_EF_FLAME)
1230 if (!spawnparticles)
1232 count *= 300 * cl_particles_quality.value;
1234 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);
1235 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1237 else if (effectnameindex == EFFECT_EF_STARDUST)
1239 if (!spawnparticles)
1241 count *= 200 * cl_particles_quality.value;
1243 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);
1244 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1246 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1250 int smoke, blood, bubbles, r, color, spawnedcount;
1252 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1255 Vector4Set(light, 0, 0, 0, 0);
1257 if (effectnameindex == EFFECT_TR_ROCKET)
1258 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1259 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1261 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1262 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1264 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1266 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1267 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1271 matrix4x4_t traillightmatrix;
1272 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1273 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1274 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1278 if (!spawnparticles)
1281 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1284 VectorSubtract(originmaxs, originmins, dir);
1285 len = VectorNormalizeLength(dir);
1289 dec = -ent->persistent.trail_time;
1290 ent->persistent.trail_time += len;
1291 if (ent->persistent.trail_time < 0.01f)
1294 // if we skip out, leave it reset
1295 ent->persistent.trail_time = 0.0f;
1300 // advance into this frame to reach the first puff location
1301 VectorMA(originmins, dec, dir, pos);
1304 smoke = cl_particles.integer && cl_particles_smoke.integer;
1305 blood = cl_particles.integer && cl_particles_blood.integer;
1306 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1307 qd = 1.0f / cl_particles_quality.value;
1310 while (len >= 0 && ++spawnedcount <= 16384)
1315 if (effectnameindex == EFFECT_TR_BLOOD)
1317 if (cl_particles_quake.integer)
1319 color = particlepalette[67 + (rand()&3)];
1320 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);
1325 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);
1328 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1330 if (cl_particles_quake.integer)
1333 color = particlepalette[67 + (rand()&3)];
1334 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);
1339 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);
1345 if (effectnameindex == EFFECT_TR_ROCKET)
1347 if (cl_particles_quake.integer)
1350 color = particlepalette[ramp3[r]];
1351 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);
1355 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);
1356 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);
1359 else if (effectnameindex == EFFECT_TR_GRENADE)
1361 if (cl_particles_quake.integer)
1364 color = particlepalette[ramp3[r]];
1365 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);
1369 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);
1372 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1374 if (cl_particles_quake.integer)
1377 color = particlepalette[52 + (rand()&7)];
1378 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);
1379 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);
1381 else if (gamemode == GAME_GOODVSBAD2)
1384 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);
1388 color = particlepalette[20 + (rand()&7)];
1389 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1392 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1394 if (cl_particles_quake.integer)
1397 color = particlepalette[230 + (rand()&7)];
1398 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);
1399 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);
1403 color = particlepalette[226 + (rand()&7)];
1404 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);
1407 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1409 if (cl_particles_quake.integer)
1411 color = particlepalette[152 + (rand()&3)];
1412 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);
1414 else if (gamemode == GAME_GOODVSBAD2)
1417 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);
1419 else if (gamemode == GAME_PRYDON)
1422 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);
1425 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);
1427 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1430 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);
1432 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1435 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);
1437 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1438 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);
1442 if (effectnameindex == EFFECT_TR_ROCKET)
1443 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);
1444 else if (effectnameindex == EFFECT_TR_GRENADE)
1445 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);
1447 // advance to next time and position
1450 VectorMA (pos, dec, dir, pos);
1453 ent->persistent.trail_time = len;
1456 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1459 // this is also called on point effects with spawndlight = true and
1460 // spawnparticles = true
1461 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)
1463 qboolean found = false;
1465 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1467 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1468 return; // no such effect
1470 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1472 int effectinfoindex;
1475 particleeffectinfo_t *info;
1487 qboolean underwater;
1488 qboolean immediatebloodstain;
1490 float avgtint[4], tint[4], tintlerp;
1491 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1492 VectorLerp(originmins, 0.5, originmaxs, center);
1493 supercontents = CL_PointSuperContents(center);
1494 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1495 VectorSubtract(originmaxs, originmins, traildir);
1496 traillen = VectorLength(traildir);
1497 VectorNormalize(traildir);
1500 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1504 Vector4Set(avgtint, 1, 1, 1, 1);
1506 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1508 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1510 qboolean definedastrail = info->trailspacing > 0;
1512 qboolean drawastrail = wanttrail;
1513 if (cl_particles_forcetraileffects.integer)
1514 drawastrail = drawastrail || definedastrail;
1517 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1519 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1522 // spawn a dlight if requested
1523 if (info->lightradiusstart > 0 && spawndlight)
1525 matrix4x4_t tempmatrix;
1527 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1529 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1530 if (info->lighttime > 0 && info->lightradiusfade > 0)
1532 // light flash (explosion, etc)
1533 // called when effect starts
1534 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);
1536 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1539 // called by CL_LinkNetworkEntity
1540 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1541 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1542 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1543 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1544 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);
1545 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1549 if (!spawnparticles)
1554 if (info->tex[1] > info->tex[0])
1556 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1557 tex = min(tex, info->tex[1] - 1);
1559 if(info->staintex[0] < 0)
1560 staintex = info->staintex[0];
1563 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1564 staintex = min(staintex, info->staintex[1] - 1);
1566 if (info->particletype == pt_decal)
1568 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1569 AnglesFromVectors(angles, velocity, NULL, false);
1570 AngleVectors(angles, forward, right, up);
1571 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1573 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]);
1575 else if (info->orientation == PARTICLE_HBEAM)
1580 AnglesFromVectors(angles, traildir, NULL, false);
1581 AngleVectors(angles, forward, right, up);
1582 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1584 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);
1589 if (!cl_particles.integer)
1591 switch (info->particletype)
1593 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1594 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1595 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1596 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1597 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1598 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1602 cnt = info->countabsolute;
1603 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1604 // if drawastrail is not set, we will
1605 // use the regular cnt-based random
1606 // particle spawning at the center; so
1607 // do NOT apply trailspacing then!
1608 if (drawastrail && definedastrail)
1609 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1612 continue; // nothing to draw
1613 info->particleaccumulator += cnt;
1615 if (drawastrail || definedastrail)
1616 immediatebloodstain = false;
1618 immediatebloodstain =
1619 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1621 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1625 VectorCopy(originmins, trailpos);
1626 trailstep = traillen / cnt;
1630 VectorCopy(center, trailpos);
1636 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1637 AnglesFromVectors(angles, velocity, NULL, false);
1640 AnglesFromVectors(angles, traildir, NULL, false);
1642 AngleVectors(angles, forward, right, up);
1643 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1644 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1645 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1646 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1648 if (info->tex[1] > info->tex[0])
1650 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1651 tex = min(tex, info->tex[1] - 1);
1653 if (!(drawastrail || definedastrail))
1655 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1656 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1657 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1661 tintlerp = lhrandom(0, 1);
1662 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1665 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);
1666 if (immediatebloodstain && part)
1668 immediatebloodstain = false;
1669 CL_ImmediateBloodStain(part);
1672 VectorMA(trailpos, trailstep, traildir, trailpos);
1679 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1682 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)
1684 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1687 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)
1689 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1692 // note: this one ONLY does boxes!
1693 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)
1695 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1703 void CL_EntityParticles (const entity_t *ent)
1706 vec_t pitch, yaw, dist = 64, beamlength = 16;
1708 static vec3_t avelocities[NUMVERTEXNORMALS];
1709 if (!cl_particles.integer) return;
1710 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1712 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1714 if (!avelocities[0][0])
1715 for (i = 0;i < NUMVERTEXNORMALS;i++)
1716 for (j = 0;j < 3;j++)
1717 avelocities[i][j] = lhrandom(0, 2.55);
1719 for (i = 0;i < NUMVERTEXNORMALS;i++)
1721 yaw = cl.time * avelocities[i][0];
1722 pitch = cl.time * avelocities[i][1];
1723 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1724 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1725 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1726 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);
1731 void CL_ReadPointFile_f(cmd_state_t *cmd)
1733 double org[3], leakorg[3];
1736 char *pointfile = NULL, *pointfilepos, *t, tchar;
1737 char name[MAX_QPATH];
1742 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1743 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1746 Con_Printf("Could not open %s\n", name);
1750 Con_Printf("Reading %s...\n", name);
1751 VectorClear(leakorg);
1754 pointfilepos = pointfile;
1755 while (*pointfilepos)
1757 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1762 while (*t && *t != '\n' && *t != '\r')
1766 #if _MSC_VER >= 1400
1767 #define sscanf sscanf_s
1769 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1770 VectorCopy(org, vecorg);
1776 VectorCopy(org, leakorg);
1779 if (cl.num_particles < cl.max_particles - 3)
1782 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);
1785 Mem_Free(pointfile);
1786 VectorCopy(leakorg, vecorg);
1787 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1794 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);
1795 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);
1796 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);
1801 CL_ParseParticleEffect
1803 Parse an effect out of the server message
1806 void CL_ParseParticleEffect (void)
1809 int i, count, msgcount, color;
1811 MSG_ReadVector(&cl_message, org, cls.protocol);
1812 for (i=0 ; i<3 ; i++)
1813 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1814 msgcount = MSG_ReadByte(&cl_message);
1815 color = MSG_ReadByte(&cl_message);
1817 if (msgcount == 255)
1822 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1827 CL_ParticleExplosion
1831 void CL_ParticleExplosion (const vec3_t org)
1837 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1838 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1840 if (cl_particles_quake.integer)
1842 for (i = 0;i < 1024;i++)
1848 color = particlepalette[ramp1[r]];
1849 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);
1853 color = particlepalette[ramp2[r]];
1854 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);
1860 i = CL_PointSuperContents(org);
1861 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1863 if (cl_particles.integer && cl_particles_bubbles.integer)
1864 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1865 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);
1869 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1871 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1878 VectorMA(org, 128, v2, v);
1879 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
1881 while (k < 16 && trace.fraction < 0.1f);
1882 VectorSubtract(trace.endpos, org, v2);
1883 VectorScale(v2, 2.0f, v2);
1884 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);
1890 if (cl_particles_explosions_shell.integer)
1891 R_NewExplosion(org);
1896 CL_ParticleExplosion2
1900 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1903 if (!cl_particles.integer) return;
1905 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1907 k = particlepalette[colorStart + (i % colorLength)];
1908 if (cl_particles_quake.integer)
1909 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);
1911 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);
1915 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1918 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1919 if (cl_particles_sparks.integer)
1921 sparkcount *= cl_particles_quality.value;
1922 while(sparkcount-- > 0)
1923 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);
1927 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1930 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1931 if (cl_particles_smoke.integer)
1933 smokecount *= cl_particles_quality.value;
1934 while(smokecount-- > 0)
1935 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);
1939 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)
1943 if (!cl_particles.integer) return;
1944 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1946 count = (int)(count * cl_particles_quality.value);
1949 k = particlepalette[colorbase + (rand()&3)];
1950 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);
1954 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1957 float minz, maxz, lifetime = 30;
1959 if (!cl_particles.integer) return;
1960 if (dir[2] < 0) // falling
1962 minz = maxs[2] + dir[2] * 0.1;
1965 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1970 maxz = maxs[2] + dir[2] * 0.1;
1972 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1975 count = (int)(count * cl_particles_quality.value);
1980 if (!cl_particles_rain.integer) break;
1981 count *= 4; // ick, this should be in the mod or maps?
1985 k = particlepalette[colorbase + (rand()&3)];
1986 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1987 if (gamemode == GAME_GOODVSBAD2)
1988 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);
1990 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);
1994 if (!cl_particles_snow.integer) break;
1997 k = particlepalette[colorbase + (rand()&3)];
1998 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1999 if (gamemode == GAME_GOODVSBAD2)
2000 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);
2002 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);
2006 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
2010 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
2011 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
2012 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
2013 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
2014 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
2015 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
2017 #define PARTICLETEXTURESIZE 64
2018 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2020 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2024 dz = 1 - (dx*dx+dy*dy);
2025 if (dz > 0) // it does hit the sphere
2029 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2030 VectorNormalize(normal);
2031 dot = DotProduct(normal, light);
2032 if (dot > 0.5) // interior reflection
2033 f += ((dot * 2) - 1);
2034 else if (dot < -0.5) // exterior reflection
2035 f += ((dot * -2) - 1);
2037 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2038 VectorNormalize(normal);
2039 dot = DotProduct(normal, light);
2040 if (dot > 0.5) // interior reflection
2041 f += ((dot * 2) - 1);
2042 else if (dot < -0.5) // exterior reflection
2043 f += ((dot * -2) - 1);
2045 f += 16; // just to give it a haze so you can see the outline
2046 f = bound(0, f, 255);
2047 return (unsigned char) f;
2053 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2054 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2056 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2057 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2058 *width = particlefontcellwidth;
2059 *height = particlefontcellheight;
2062 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2064 int basex, basey, w, h, y;
2065 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2066 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2067 Sys_Error("invalid particle texture size for autogenerating");
2068 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2069 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2072 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2075 float cx, cy, dx, dy, f, iradius;
2077 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2078 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2079 iradius = 1.0f / radius;
2080 alpha *= (1.0f / 255.0f);
2081 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2083 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2087 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2092 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2093 d[0] += (int)(f * (blue - d[0]));
2094 d[1] += (int)(f * (green - d[1]));
2095 d[2] += (int)(f * (red - d[2]));
2102 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2105 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2107 data[0] = bound(minb, data[0], maxb);
2108 data[1] = bound(ming, data[1], maxg);
2109 data[2] = bound(minr, data[2], maxr);
2114 static void particletextureinvert(unsigned char *data)
2117 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2119 data[0] = 255 - data[0];
2120 data[1] = 255 - data[1];
2121 data[2] = 255 - data[2];
2125 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2126 static void R_InitBloodTextures (unsigned char *particletexturedata)
2129 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2130 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2133 for (i = 0;i < 8;i++)
2135 memset(data, 255, datasize);
2136 for (k = 0;k < 24;k++)
2137 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2138 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2139 particletextureinvert(data);
2140 setuptex(tex_bloodparticle[i], data, particletexturedata);
2144 for (i = 0;i < 8;i++)
2146 memset(data, 255, datasize);
2148 for (j = 1;j < 10;j++)
2149 for (k = min(j, m - 1);k < m;k++)
2150 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2151 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2152 particletextureinvert(data);
2153 setuptex(tex_blooddecal[i], data, particletexturedata);
2159 //uncomment this to make engine save out particle font to a tga file when run
2160 //#define DUMPPARTICLEFONT
2162 static void R_InitParticleTexture (void)
2164 int x, y, d, i, k, m;
2165 int basex, basey, w, h;
2166 float dx, dy, f, s1, t1, s2, t2;
2169 fs_offset_t filesize;
2170 char texturename[MAX_QPATH];
2173 // a note: decals need to modulate (multiply) the background color to
2174 // properly darken it (stain), and they need to be able to alpha fade,
2175 // this is a very difficult challenge because it means fading to white
2176 // (no change to background) rather than black (darkening everything
2177 // behind the whole decal polygon), and to accomplish this the texture is
2178 // inverted (dark red blood on white background becomes brilliant cyan
2179 // and white on black background) so we can alpha fade it to black, then
2180 // we invert it again during the blendfunc to make it work...
2182 #ifndef DUMPPARTICLEFONT
2183 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false, false);
2186 particlefonttexture = decalskinframe->base;
2187 // TODO maybe allow custom grid size?
2188 particlefontwidth = image_width;
2189 particlefontheight = image_height;
2190 particlefontcellwidth = image_width / 8;
2191 particlefontcellheight = image_height / 8;
2192 particlefontcols = 8;
2193 particlefontrows = 8;
2198 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2199 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2200 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2201 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2202 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2204 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2205 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2206 particlefontcols = 8;
2207 particlefontrows = 8;
2209 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2212 for (i = 0;i < 8;i++)
2214 memset(data, 255, datasize);
2217 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2218 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2220 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2222 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2223 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2225 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2226 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2228 d = (int)(d * (1-(dx*dx+dy*dy)));
2229 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2230 d = bound(0, d, 255);
2231 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2238 setuptex(tex_smoke[i], data, particletexturedata);
2242 memset(data, 255, datasize);
2243 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2245 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2246 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2248 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2249 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2250 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2253 setuptex(tex_rainsplash, data, particletexturedata);
2256 memset(data, 255, datasize);
2257 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2259 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2260 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2262 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2263 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2264 d = bound(0, d, 255);
2265 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2268 setuptex(tex_particle, data, particletexturedata);
2271 memset(data, 255, datasize);
2272 light[0] = 1;light[1] = 1;light[2] = 1;
2273 VectorNormalize(light);
2274 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2276 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2277 // stretch upper half of bubble by +50% and shrink lower half by -50%
2278 // (this gives an elongated teardrop shape)
2280 dy = (dy - 0.5f) * 2.0f;
2282 dy = (dy - 0.5f) / 1.5f;
2283 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2285 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2286 // shrink bubble width to half
2288 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2291 setuptex(tex_raindrop, data, particletexturedata);
2294 memset(data, 255, datasize);
2295 light[0] = 1;light[1] = 1;light[2] = 1;
2296 VectorNormalize(light);
2297 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2299 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2300 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2302 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2303 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2306 setuptex(tex_bubble, data, particletexturedata);
2308 // Blood particles and blood decals
2309 R_InitBloodTextures (particletexturedata);
2312 for (i = 0;i < 8;i++)
2314 memset(data, 255, datasize);
2315 for (k = 0;k < 12;k++)
2316 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2317 for (k = 0;k < 3;k++)
2318 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2319 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2320 particletextureinvert(data);
2321 setuptex(tex_bulletdecal[i], data, particletexturedata);
2324 #ifdef DUMPPARTICLEFONT
2325 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2328 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, 0, 0, 0, false);
2329 particlefonttexture = decalskinframe->base;
2331 Mem_Free(particletexturedata);
2336 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2338 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2339 particletexture[i].texture = particlefonttexture;
2340 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2341 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2342 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2343 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2346 #ifndef DUMPPARTICLEFONT
2347 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2348 if (!particletexture[tex_beam].texture)
2351 unsigned char noise3[64][64], data2[64][16][4];
2353 fractalnoise(&noise3[0][0], 64, 4);
2355 for (y = 0;y < 64;y++)
2357 dy = (y - 0.5f*64) / (64*0.5f-1);
2358 for (x = 0;x < 16;x++)
2360 dx = (x - 0.5f*16) / (16*0.5f-2);
2361 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2362 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2363 data2[y][x][3] = 255;
2367 #ifdef DUMPPARTICLEFONT
2368 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2370 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2372 particletexture[tex_beam].s1 = 0;
2373 particletexture[tex_beam].t1 = 0;
2374 particletexture[tex_beam].s2 = 1;
2375 particletexture[tex_beam].t2 = 1;
2377 // now load an texcoord/texture override file
2378 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2385 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2387 if(!strcmp(com_token, "\n"))
2388 continue; // empty line
2389 i = atoi(com_token);
2397 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2399 strlcpy(texturename, com_token, sizeof(texturename));
2400 s1 = atof(com_token);
2401 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2404 t1 = atof(com_token);
2405 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2407 s2 = atof(com_token);
2408 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2410 t2 = atof(com_token);
2411 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2412 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2413 strlcpy(texturename, com_token, sizeof(texturename));
2420 if (!texturename[0])
2422 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2425 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2427 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2430 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, true); // note: this loads as sRGB if sRGB is active!
2431 particletexture[i].texture = sf->base;
2432 particletexture[i].s1 = s1;
2433 particletexture[i].t1 = t1;
2434 particletexture[i].s2 = s2;
2435 particletexture[i].t2 = t2;
2441 static void r_part_start(void)
2444 // generate particlepalette for convenience from the main one
2445 for (i = 0;i < 256;i++)
2446 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2447 particletexturepool = R_AllocTexturePool();
2448 R_InitParticleTexture ();
2449 CL_Particles_LoadEffectInfo(NULL);
2452 static void r_part_shutdown(void)
2454 R_FreeTexturePool(&particletexturepool);
2457 static void r_part_newmap(void)
2460 R_SkinFrame_MarkUsed(decalskinframe);
2461 CL_Particles_LoadEffectInfo(NULL);
2464 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2465 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2467 void R_Particles_Init (void)
2470 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2472 particle_elements[i*6+0] = i*4+0;
2473 particle_elements[i*6+1] = i*4+1;
2474 particle_elements[i*6+2] = i*4+2;
2475 particle_elements[i*6+3] = i*4+0;
2476 particle_elements[i*6+4] = i*4+2;
2477 particle_elements[i*6+5] = i*4+3;
2480 Cvar_RegisterVariable(&r_drawparticles);
2481 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2482 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2483 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2484 Cvar_RegisterVariable(&r_drawdecals);
2485 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2486 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2489 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2491 int surfacelistindex;
2493 float *v3f, *t2f, *c4f;
2494 particletexture_t *tex;
2495 vec_t right[3], up[3], size, ca;
2496 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2498 RSurf_ActiveModelEntity(r_refdef.scene.worldentity, false, false, false);
2500 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2501 // R_Mesh_ResetTextureState();
2502 GL_DepthMask(false);
2503 GL_DepthRange(0, 1);
2504 GL_PolygonOffset(0, 0);
2506 GL_CullFace(GL_NONE);
2508 // generate all the vertices at once
2509 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2511 d = cl.decals + surfacelist[surfacelistindex];
2514 c4f = particle_color4f + 16*surfacelistindex;
2515 ca = d->alpha * alphascale;
2516 // ensure alpha multiplier saturates properly
2517 if (ca > 1.0f / 256.0f)
2519 if (r_refdef.fogenabled)
2520 ca *= RSurf_FogVertex(d->org);
2521 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2522 Vector4Copy(c4f, c4f + 4);
2523 Vector4Copy(c4f, c4f + 8);
2524 Vector4Copy(c4f, c4f + 12);
2526 // calculate vertex positions
2527 size = d->size * cl_particles_size.value;
2528 VectorVectors(d->normal, right, up);
2529 VectorScale(right, size, right);
2530 VectorScale(up, size, up);
2531 v3f = particle_vertex3f + 12*surfacelistindex;
2532 v3f[ 0] = d->org[0] - right[0] - up[0];
2533 v3f[ 1] = d->org[1] - right[1] - up[1];
2534 v3f[ 2] = d->org[2] - right[2] - up[2];
2535 v3f[ 3] = d->org[0] - right[0] + up[0];
2536 v3f[ 4] = d->org[1] - right[1] + up[1];
2537 v3f[ 5] = d->org[2] - right[2] + up[2];
2538 v3f[ 6] = d->org[0] + right[0] + up[0];
2539 v3f[ 7] = d->org[1] + right[1] + up[1];
2540 v3f[ 8] = d->org[2] + right[2] + up[2];
2541 v3f[ 9] = d->org[0] + right[0] - up[0];
2542 v3f[10] = d->org[1] + right[1] - up[1];
2543 v3f[11] = d->org[2] + right[2] - up[2];
2545 // calculate texcoords
2546 tex = &particletexture[d->texnum];
2547 t2f = particle_texcoord2f + 8*surfacelistindex;
2548 t2f[0] = tex->s1;t2f[1] = tex->t2;
2549 t2f[2] = tex->s1;t2f[3] = tex->t1;
2550 t2f[4] = tex->s2;t2f[5] = tex->t1;
2551 t2f[6] = tex->s2;t2f[7] = tex->t2;
2554 // now render the decals all at once
2555 // (this assumes they all use one particle font texture!)
2556 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2557 R_SetupShader_Generic(particletexture[63].texture, false, false, true);
2558 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2559 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2562 void R_DrawDecals (void)
2565 int drawdecals = r_drawdecals.integer;
2570 unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence);
2572 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2573 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2575 // LadyHavoc: early out conditions
2579 decalfade = frametime * 256 / cl_decals_fadetime.value;
2580 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2581 drawdist2 = drawdist2*drawdist2;
2583 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2585 if (!decal->typeindex)
2588 if (killsequence > decal->decalsequence)
2591 if (cl.time > decal->time2 + cl_decals_time.value)
2593 decal->alpha -= decalfade;
2594 if (decal->alpha <= 0)
2600 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2602 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2603 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2609 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2615 if (!r_refdef.view.useperspective || (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size)))
2616 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2619 decal->typeindex = 0;
2620 if (cl.free_decal > i)
2624 // reduce cl.num_decals if possible
2625 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2628 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2630 decal_t *olddecals = cl.decals;
2631 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2632 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2633 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2634 Mem_Free(olddecals);
2637 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2640 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2642 vec3_t vecorg, vecvel, baseright, baseup;
2643 int surfacelistindex;
2644 int batchstart, batchcount;
2645 const particle_t *p;
2647 rtexture_t *texture;
2648 float *v3f, *t2f, *c4f;
2649 particletexture_t *tex;
2650 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2651 // float ambient[3], diffuse[3], diffusenormal[3];
2652 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2653 vec4_t colormultiplier;
2654 float minparticledist_start, minparticledist_end;
2657 RSurf_ActiveModelEntity(r_refdef.scene.worldentity, false, false, false);
2659 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));
2661 r_refdef.stats[r_stat_particles] += numsurfaces;
2662 // R_Mesh_ResetTextureState();
2663 GL_DepthMask(false);
2664 GL_DepthRange(0, 1);
2665 GL_PolygonOffset(0, 0);
2667 GL_CullFace(GL_NONE);
2669 spintime = r_refdef.scene.time;
2671 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2672 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2673 dofade = (minparticledist_start < minparticledist_end);
2675 // first generate all the vertices at once
2676 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2678 p = cl.particles + surfacelist[surfacelistindex];
2680 blendmode = (pblend_t)p->blendmode;
2682 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2683 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2684 alpha = palpha * colormultiplier[3];
2685 // ensure alpha multiplier saturates properly
2691 case PBLEND_INVALID:
2693 // additive and modulate can just fade out in fog (this is correct)
2694 if (r_refdef.fogenabled)
2695 alpha *= RSurf_FogVertex(p->org);
2696 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2697 alpha *= 1.0f / 256.0f;
2698 c4f[0] = p->color[0] * alpha;
2699 c4f[1] = p->color[1] * alpha;
2700 c4f[2] = p->color[2] * alpha;
2704 // additive and modulate can just fade out in fog (this is correct)
2705 if (r_refdef.fogenabled)
2706 alpha *= RSurf_FogVertex(p->org);
2707 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2708 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2709 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2710 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2714 c4f[0] = p->color[0] * colormultiplier[0];
2715 c4f[1] = p->color[1] * colormultiplier[1];
2716 c4f[2] = p->color[2] * colormultiplier[2];
2718 // note: lighting is not cheap!
2719 if (particletype[p->typeindex].lighting)
2721 float a[3], c[3], dir[3];
2722 vecorg[0] = p->org[0];
2723 vecorg[1] = p->org[1];
2724 vecorg[2] = p->org[2];
2725 R_CompleteLightPoint(a, c, dir, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT, r_refdef.scene.lightmapintensity, r_refdef.scene.ambientintensity);
2726 c4f[0] = p->color[0] * colormultiplier[0] * (a[0] + 0.25f * c[0]);
2727 c4f[1] = p->color[1] * colormultiplier[1] * (a[1] + 0.25f * c[1]);
2728 c4f[2] = p->color[2] * colormultiplier[2] * (a[2] + 0.25f * c[2]);
2730 // mix in the fog color
2731 if (r_refdef.fogenabled)
2733 fog = RSurf_FogVertex(p->org);
2735 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2736 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2737 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2739 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2740 VectorScale(c4f, alpha, c4f);
2743 // copy the color into the other three vertices
2744 Vector4Copy(c4f, c4f + 4);
2745 Vector4Copy(c4f, c4f + 8);
2746 Vector4Copy(c4f, c4f + 12);
2748 size = p->size * cl_particles_size.value;
2749 tex = &particletexture[p->texnum];
2750 switch(p->orientation)
2752 // case PARTICLE_INVALID:
2753 case PARTICLE_BILLBOARD:
2754 if (p->angle + p->spin)
2756 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2757 spinsin = sin(spinrad) * size;
2758 spincos = cos(spinrad) * size;
2759 spinm1 = -p->stretch * spincos;
2762 spinm4 = -p->stretch * spincos;
2763 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2764 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2768 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2769 VectorScale(r_refdef.view.up, size, up);
2772 v3f[ 0] = p->org[0] - right[0] - up[0];
2773 v3f[ 1] = p->org[1] - right[1] - up[1];
2774 v3f[ 2] = p->org[2] - right[2] - up[2];
2775 v3f[ 3] = p->org[0] - right[0] + up[0];
2776 v3f[ 4] = p->org[1] - right[1] + up[1];
2777 v3f[ 5] = p->org[2] - right[2] + up[2];
2778 v3f[ 6] = p->org[0] + right[0] + up[0];
2779 v3f[ 7] = p->org[1] + right[1] + up[1];
2780 v3f[ 8] = p->org[2] + right[2] + up[2];
2781 v3f[ 9] = p->org[0] + right[0] - up[0];
2782 v3f[10] = p->org[1] + right[1] - up[1];
2783 v3f[11] = p->org[2] + right[2] - up[2];
2784 t2f[0] = tex->s1;t2f[1] = tex->t2;
2785 t2f[2] = tex->s1;t2f[3] = tex->t1;
2786 t2f[4] = tex->s2;t2f[5] = tex->t1;
2787 t2f[6] = tex->s2;t2f[7] = tex->t2;
2789 case PARTICLE_ORIENTED_DOUBLESIDED:
2790 vecvel[0] = p->vel[0];
2791 vecvel[1] = p->vel[1];
2792 vecvel[2] = p->vel[2];
2793 VectorVectors(vecvel, baseright, baseup);
2794 if (p->angle + p->spin)
2796 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2797 spinsin = sin(spinrad) * size;
2798 spincos = cos(spinrad) * size;
2799 spinm1 = p->stretch * spincos;
2802 spinm4 = p->stretch * spincos;
2803 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2804 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2808 VectorScale(baseright, size * p->stretch, right);
2809 VectorScale(baseup, size, up);
2811 v3f[ 0] = p->org[0] - right[0] - up[0];
2812 v3f[ 1] = p->org[1] - right[1] - up[1];
2813 v3f[ 2] = p->org[2] - right[2] - up[2];
2814 v3f[ 3] = p->org[0] - right[0] + up[0];
2815 v3f[ 4] = p->org[1] - right[1] + up[1];
2816 v3f[ 5] = p->org[2] - right[2] + up[2];
2817 v3f[ 6] = p->org[0] + right[0] + up[0];
2818 v3f[ 7] = p->org[1] + right[1] + up[1];
2819 v3f[ 8] = p->org[2] + right[2] + up[2];
2820 v3f[ 9] = p->org[0] + right[0] - up[0];
2821 v3f[10] = p->org[1] + right[1] - up[1];
2822 v3f[11] = p->org[2] + right[2] - up[2];
2823 t2f[0] = tex->s1;t2f[1] = tex->t2;
2824 t2f[2] = tex->s1;t2f[3] = tex->t1;
2825 t2f[4] = tex->s2;t2f[5] = tex->t1;
2826 t2f[6] = tex->s2;t2f[7] = tex->t2;
2828 case PARTICLE_SPARK:
2829 len = VectorLength(p->vel);
2830 VectorNormalize2(p->vel, up);
2831 lenfactor = p->stretch * 0.04 * len;
2832 if(lenfactor < size * 0.5)
2833 lenfactor = size * 0.5;
2834 VectorMA(p->org, -lenfactor, up, v);
2835 VectorMA(p->org, lenfactor, up, up2);
2836 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2837 t2f[0] = tex->s1;t2f[1] = tex->t2;
2838 t2f[2] = tex->s1;t2f[3] = tex->t1;
2839 t2f[4] = tex->s2;t2f[5] = tex->t1;
2840 t2f[6] = tex->s2;t2f[7] = tex->t2;
2842 case PARTICLE_VBEAM:
2843 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2844 VectorSubtract(p->vel, p->org, up);
2845 VectorNormalize(up);
2846 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2847 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2848 t2f[0] = tex->s2;t2f[1] = v[0];
2849 t2f[2] = tex->s1;t2f[3] = v[0];
2850 t2f[4] = tex->s1;t2f[5] = v[1];
2851 t2f[6] = tex->s2;t2f[7] = v[1];
2853 case PARTICLE_HBEAM:
2854 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2855 VectorSubtract(p->vel, p->org, up);
2856 VectorNormalize(up);
2857 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2858 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2859 t2f[0] = v[0];t2f[1] = tex->t1;
2860 t2f[2] = v[0];t2f[3] = tex->t2;
2861 t2f[4] = v[1];t2f[5] = tex->t2;
2862 t2f[6] = v[1];t2f[7] = tex->t1;
2865 if (r_showparticleedges.integer)
2867 R_DebugLine(v3f, v3f + 3);
2868 R_DebugLine(v3f + 3, v3f + 6);
2869 R_DebugLine(v3f + 6, v3f + 9);
2870 R_DebugLine(v3f + 9, v3f);
2874 // now render batches of particles based on blendmode and texture
2875 blendmode = PBLEND_INVALID;
2879 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2880 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2882 p = cl.particles + surfacelist[surfacelistindex];
2884 if (texture != particletexture[p->texnum].texture)
2886 texture = particletexture[p->texnum].texture;
2887 R_SetupShader_Generic(texture, false, false, false);
2890 if (p->blendmode == PBLEND_INVMOD)
2892 // inverse modulate blend - group these
2893 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2894 // iterate until we find a change in settings
2895 batchstart = surfacelistindex++;
2896 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2898 p = cl.particles + surfacelist[surfacelistindex];
2899 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2905 // additive or alpha blend - group these
2906 // (we can group these because we premultiplied the texture alpha)
2907 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2908 // iterate until we find a change in settings
2909 batchstart = surfacelistindex++;
2910 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2912 p = cl.particles + surfacelist[surfacelistindex];
2913 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2918 batchcount = surfacelistindex - batchstart;
2919 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2923 void R_DrawParticles (void)
2926 int drawparticles = r_drawparticles.integer;
2927 float minparticledist_start;
2929 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2935 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2936 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2938 // LadyHavoc: early out conditions
2939 if (!cl.num_particles)
2942 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2943 gravity = frametime * cl.movevars_gravity;
2944 update = frametime > 0;
2945 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2946 drawdist2 = drawdist2*drawdist2;
2948 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2952 if (cl.free_particle > i)
2953 cl.free_particle = i;
2959 if (p->delayedspawn > cl.time)
2962 p->size += p->sizeincrease * frametime;
2963 p->alpha -= p->alphafade * frametime;
2965 if (p->alpha <= 0 || p->die <= cl.time)
2968 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2970 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2972 if (p->typeindex == pt_blood)
2973 p->size += frametime * 8;
2975 p->vel[2] -= p->gravity * gravity;
2976 f = 1.0f - min(p->liquidfriction * frametime, 1);
2977 VectorScale(p->vel, f, p->vel);
2981 p->vel[2] -= p->gravity * gravity;
2984 f = 1.0f - min(p->airfriction * frametime, 1);
2985 VectorScale(p->vel, f, p->vel);
2989 VectorCopy(p->org, oldorg);
2990 VectorMA(p->org, frametime, p->vel, p->org);
2991 // if (p->bounce && cl.time >= p->delayedcollisions)
2992 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2994 trace = CL_TraceLine(oldorg, p->org, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, 0, collision_extendmovelength.value, true, false, &hitent, false, false);
2995 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2996 // or if the trace hit something flagged as NOIMPACT
2997 // then remove the particle
2998 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
3000 VectorCopy(trace.endpos, p->org);
3001 // react if the particle hit something
3002 if (trace.fraction < 1)
3004 VectorCopy(trace.endpos, p->org);
3006 if (p->staintexnum >= 0)
3008 // blood - splash on solid
3009 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
3012 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
3013 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
3014 if (cl_decals.integer)
3016 // create a decal for the blood splat
3017 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
3018 if (cl_decals_newsystem_bloodsmears.integer)
3020 VectorCopy(p->vel, decaldir);
3021 VectorNormalize(decaldir);
3024 VectorCopy(trace.plane.normal, decaldir);
3025 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3030 if (p->typeindex == pt_blood)
3032 // blood - splash on solid
3033 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3035 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3037 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)));
3038 if (cl_decals.integer)
3040 // create a decal for the blood splat
3041 if (cl_decals_newsystem_bloodsmears.integer)
3043 VectorCopy(p->vel, decaldir);
3044 VectorNormalize(decaldir);
3047 VectorCopy(trace.plane.normal, decaldir);
3048 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);
3053 else if (p->bounce < 0)
3055 // bounce -1 means remove on impact
3060 // anything else - bounce off solid
3061 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3062 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3067 if (VectorLength2(p->vel) < 0.03)
3069 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3071 VectorClear(p->vel);
3075 if (p->typeindex != pt_static)
3077 switch (p->typeindex)
3079 case pt_entityparticle:
3080 // particle that removes itself after one rendered frame
3087 a = CL_PointSuperContents(p->org);
3088 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3092 a = CL_PointSuperContents(p->org);
3093 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3097 a = CL_PointSuperContents(p->org);
3098 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
3102 if (cl.time > p->time2)
3105 p->time2 = cl.time + (rand() & 3) * 0.1;
3106 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3107 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3109 a = CL_PointSuperContents(p->org);
3110 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
3118 else if (p->delayedspawn > cl.time)
3122 // don't render particles too close to the view (they chew fillrate)
3123 // also don't render particles behind the view (useless)
3124 // further checks to cull to the frustum would be too slow here
3125 switch(p->typeindex)
3128 // beams have no culling
3129 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3132 if(cl_particles_visculling.integer)
3133 if (!r_refdef.viewcache.world_novis)
3134 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3136 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3138 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3141 // anything else just has to be in front of the viewer and visible at this distance
3142 if (!r_refdef.view.useperspective || (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)))
3143 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3150 if (cl.free_particle > i)
3151 cl.free_particle = i;
3154 // reduce cl.num_particles if possible
3155 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3158 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3160 particle_t *oldparticles = cl.particles;
3161 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3162 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3163 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3164 Mem_Free(oldparticles);