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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[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 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // particletexture_t is a rectangle in the particlefonttexture
171 typedef struct particletexture_s
174 float s1, t1, s2, t2;
178 static rtexturepool_t *particletexturepool;
179 static rtexture_t *particlefonttexture;
180 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
181 skinframe_t *decalskinframe;
183 // texture numbers in particle font
184 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
185 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
186 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
187 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
188 static const int tex_rainsplash = 32;
189 static const int tex_particle = 63;
190 static const int tex_bubble = 62;
191 static const int tex_raindrop = 61;
192 static const int tex_beam = 60;
194 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
195 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
196 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
197 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
198 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
199 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
200 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
201 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
202 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
203 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
204 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
205 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
206 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
207 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
208 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
209 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
210 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
211 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
212 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
213 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
214 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
215 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
216 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
217 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
218 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)"};
219 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
220 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
221 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
224 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
230 particleeffectinfo_t *info = NULL;
231 const char *text = textstart;
233 effectinfoindex = -1;
234 for (linenumber = 1;;linenumber++)
237 for (arrayindex = 0;arrayindex < 16;arrayindex++)
238 argv[arrayindex][0] = 0;
241 if (!COM_ParseToken_Simple(&text, true, false))
243 if (!strcmp(com_token, "\n"))
247 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
253 #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;}
254 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
255 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
256 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
257 #define readfloat(var) checkparms(2);var = atof(argv[1])
258 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
259 if (!strcmp(argv[0], "effect"))
264 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
266 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
269 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
271 if (particleeffectname[effectnameindex][0])
273 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
278 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
282 // if we run out of names, abort
283 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
285 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
288 info = particleeffectinfo + effectinfoindex;
289 info->effectnameindex = effectnameindex;
290 info->particletype = pt_alphastatic;
291 info->blendmode = particletype[info->particletype].blendmode;
292 info->orientation = particletype[info->particletype].orientation;
293 info->tex[0] = tex_particle;
294 info->tex[1] = tex_particle;
295 info->color[0] = 0xFFFFFF;
296 info->color[1] = 0xFFFFFF;
300 info->alpha[1] = 256;
301 info->alpha[2] = 256;
302 info->time[0] = 9999;
303 info->time[1] = 9999;
304 VectorSet(info->lightcolor, 1, 1, 1);
305 info->lightshadow = true;
306 info->lighttime = 9999;
307 info->stretchfactor = 1;
308 info->staincolor[0] = (unsigned int)-1;
309 info->staincolor[1] = (unsigned int)-1;
310 info->staintex[0] = -1;
311 info->staintex[1] = -1;
313 else if (info == NULL)
315 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
318 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
319 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
320 else if (!strcmp(argv[0], "type"))
323 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
324 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
325 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
326 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
327 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
328 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
329 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
330 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
331 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
332 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
333 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
334 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
335 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
336 info->blendmode = particletype[info->particletype].blendmode;
337 info->orientation = particletype[info->particletype].orientation;
339 else if (!strcmp(argv[0], "blend"))
342 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
343 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
344 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
345 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
347 else if (!strcmp(argv[0], "orientation"))
350 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
351 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
352 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
353 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
354 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
356 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
357 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
358 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
359 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
360 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
361 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
362 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
363 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
364 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
365 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
366 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
367 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
368 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
369 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
370 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
371 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
372 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
373 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
374 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
375 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
376 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
377 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
378 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
379 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
380 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
381 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
382 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
383 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
385 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
394 int CL_ParticleEffectIndexForName(const char *name)
397 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
398 if (!strcmp(particleeffectname[i], name))
403 const char *CL_ParticleEffectNameForIndex(int i)
405 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
407 return particleeffectname[i];
410 // MUST match effectnameindex_t in client.h
411 static const char *standardeffectnames[EFFECT_TOTAL] =
435 "TE_TEI_BIGEXPLOSION",
451 void CL_Particles_LoadEffectInfo(void)
455 unsigned char *filedata;
456 fs_offset_t filesize;
457 char filename[MAX_QPATH];
458 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
459 memset(particleeffectname, 0, sizeof(particleeffectname));
460 for (i = 0;i < EFFECT_TOTAL;i++)
461 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
462 for (filepass = 0;;filepass++)
465 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
466 else if (filepass == 1)
467 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
470 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
473 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
483 void CL_ReadPointFile_f (void);
484 void CL_Particles_Init (void)
486 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
487 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
489 Cvar_RegisterVariable (&cl_particles);
490 Cvar_RegisterVariable (&cl_particles_quality);
491 Cvar_RegisterVariable (&cl_particles_alpha);
492 Cvar_RegisterVariable (&cl_particles_size);
493 Cvar_RegisterVariable (&cl_particles_quake);
494 Cvar_RegisterVariable (&cl_particles_blood);
495 Cvar_RegisterVariable (&cl_particles_blood_alpha);
496 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
497 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
498 Cvar_RegisterVariable (&cl_particles_explosions_shell);
499 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
500 Cvar_RegisterVariable (&cl_particles_rain);
501 Cvar_RegisterVariable (&cl_particles_snow);
502 Cvar_RegisterVariable (&cl_particles_smoke);
503 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
504 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
505 Cvar_RegisterVariable (&cl_particles_sparks);
506 Cvar_RegisterVariable (&cl_particles_bubbles);
507 Cvar_RegisterVariable (&cl_particles_visculling);
508 Cvar_RegisterVariable (&cl_decals);
509 Cvar_RegisterVariable (&cl_decals_visculling);
510 Cvar_RegisterVariable (&cl_decals_time);
511 Cvar_RegisterVariable (&cl_decals_fadetime);
512 Cvar_RegisterVariable (&cl_decals_newsystem);
513 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
514 Cvar_RegisterVariable (&cl_decals_models);
515 Cvar_RegisterVariable (&cl_decals_bias);
516 Cvar_RegisterVariable (&cl_decals_max);
519 void CL_Particles_Shutdown (void)
523 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
524 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
526 // list of all 26 parameters:
527 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
528 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
529 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
530 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
531 // palpha - opacity of particle as 0-255 (can be more than 255)
532 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
533 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
534 // pgravity - how much effect gravity has on the particle (0-1)
535 // 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
536 // px,py,pz - starting origin of particle
537 // pvx,pvy,pvz - starting velocity of particle
538 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
539 // blendmode - one of the PBLEND_ values
540 // orientation - one of the PARTICLE_ values
541 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
542 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
543 particle_t *CL_NewParticle(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)
548 if (!cl_particles.integer)
550 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
551 if (cl.free_particle >= cl.max_particles)
554 lifetime = palpha / min(1, palphafade);
555 part = &cl.particles[cl.free_particle++];
556 if (cl.num_particles < cl.free_particle)
557 cl.num_particles = cl.free_particle;
558 memset(part, 0, sizeof(*part));
559 part->typeindex = ptypeindex;
560 part->blendmode = blendmode;
561 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
563 particletexture_t *tex = &particletexture[ptex];
564 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
565 part->orientation = PARTICLE_VBEAM;
567 part->orientation = PARTICLE_HBEAM;
570 part->orientation = orientation;
571 l2 = (int)lhrandom(0.5, 256.5);
573 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
574 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
575 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
576 part->staintexnum = staintex;
577 if(staincolor1 >= 0 && staincolor2 >= 0)
579 l2 = (int)lhrandom(0.5, 256.5);
581 if(blendmode == PBLEND_INVMOD)
583 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
584 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
585 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
589 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
590 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
591 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
593 if(r > 0xFF) r = 0xFF;
594 if(g > 0xFF) g = 0xFF;
595 if(b > 0xFF) b = 0xFF;
599 r = part->color[0]; // -1 is shorthand for stain = particle color
603 part->staincolor = r * 65536 + g * 256 + b;
606 part->sizeincrease = psizeincrease;
607 part->alpha = palpha;
608 part->alphafade = palphafade;
609 part->gravity = pgravity;
610 part->bounce = pbounce;
611 part->stretch = stretch;
613 part->org[0] = px + originjitter * v[0];
614 part->org[1] = py + originjitter * v[1];
615 part->org[2] = pz + originjitter * v[2];
616 part->vel[0] = pvx + velocityjitter * v[0];
617 part->vel[1] = pvy + velocityjitter * v[1];
618 part->vel[2] = pvz + velocityjitter * v[2];
620 part->airfriction = pairfriction;
621 part->liquidfriction = pliquidfriction;
622 part->die = cl.time + lifetime;
623 part->delayedcollisions = 0;
624 part->qualityreduction = pqualityreduction;
625 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
626 if (part->typeindex == pt_rain)
630 float lifetime = part->die - cl.time;
633 // turn raindrop into simple spark and create delayedspawn splash effect
634 part->typeindex = pt_spark;
636 VectorMA(part->org, lifetime, part->vel, endvec);
637 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
638 part->die = cl.time + lifetime * trace.fraction;
639 part2 = CL_NewParticle(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);
642 part2->delayedspawn = part->die;
643 part2->die += part->die - cl.time;
644 for (i = rand() & 7;i < 10;i++)
646 part2 = CL_NewParticle(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);
649 part2->delayedspawn = part->die;
650 part2->die += part->die - cl.time;
655 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
657 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
660 VectorMA(part->org, lifetime, part->vel, endvec);
661 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
662 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
668 static void CL_ImmediateBloodStain(particle_t *part)
673 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
674 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
676 VectorCopy(part->vel, v);
678 staintex = part->staintexnum;
679 R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(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);
682 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
683 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
685 VectorCopy(part->vel, v);
687 staintex = tex_blooddecal[rand()&7];
688 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);
692 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
696 entity_render_t *ent = &cl.entities[hitent].render;
697 unsigned char color[3];
698 if (!cl_decals.integer)
700 if (!ent->allowdecals)
703 l2 = (int)lhrandom(0.5, 256.5);
705 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
706 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
707 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
709 if (cl_decals_newsystem.integer)
711 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);
715 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
716 if (cl.free_decal >= cl.max_decals)
718 decal = &cl.decals[cl.free_decal++];
719 if (cl.num_decals < cl.free_decal)
720 cl.num_decals = cl.free_decal;
721 memset(decal, 0, sizeof(*decal));
722 decal->decalsequence = cl.decalsequence++;
723 decal->typeindex = pt_decal;
724 decal->texnum = texnum;
725 VectorMA(org, cl_decals_bias.value, normal, decal->org);
726 VectorCopy(normal, decal->normal);
728 decal->alpha = alpha;
729 decal->time2 = cl.time;
730 decal->color[0] = color[0];
731 decal->color[1] = color[1];
732 decal->color[2] = color[2];
733 decal->owner = hitent;
734 decal->clusterindex = -1000; // no vis culling unless we're sure
737 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
738 decal->ownermodel = cl.entities[decal->owner].render.model;
739 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
740 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
744 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
746 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
748 decal->clusterindex = leaf->clusterindex;
753 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
756 float bestfrac, bestorg[3], bestnormal[3];
758 int besthitent = 0, hitent;
761 for (i = 0;i < 32;i++)
764 VectorMA(org, maxdist, org2, org2);
765 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
766 // take the closest trace result that doesn't end up hitting a NOMARKS
767 // surface (sky for example)
768 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
770 bestfrac = trace.fraction;
772 VectorCopy(trace.endpos, bestorg);
773 VectorCopy(trace.plane.normal, bestnormal);
777 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
780 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
781 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
782 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)
785 matrix4x4_t tempmatrix;
787 VectorLerp(originmins, 0.5, originmaxs, center);
788 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
789 if (effectnameindex == EFFECT_SVC_PARTICLE)
791 if (cl_particles.integer)
793 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
795 CL_ParticleExplosion(center);
796 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
797 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
800 count *= cl_particles_quality.value;
801 for (;count > 0;count--)
803 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
804 CL_NewParticle(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);
809 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
810 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
811 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
812 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
813 else if (effectnameindex == EFFECT_TE_SPIKE)
815 if (cl_particles_bulletimpacts.integer)
817 if (cl_particles_quake.integer)
819 if (cl_particles_smoke.integer)
820 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
824 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
825 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
826 CL_NewParticle(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);
830 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
831 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
833 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
835 if (cl_particles_bulletimpacts.integer)
837 if (cl_particles_quake.integer)
839 if (cl_particles_smoke.integer)
840 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
844 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
845 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
846 CL_NewParticle(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);
850 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
851 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
852 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
854 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
856 if (cl_particles_bulletimpacts.integer)
858 if (cl_particles_quake.integer)
860 if (cl_particles_smoke.integer)
861 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
865 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
866 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
867 CL_NewParticle(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);
871 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
872 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
874 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
876 if (cl_particles_bulletimpacts.integer)
878 if (cl_particles_quake.integer)
880 if (cl_particles_smoke.integer)
881 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
885 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
886 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
887 CL_NewParticle(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);
891 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
892 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
893 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
895 else if (effectnameindex == EFFECT_TE_BLOOD)
897 if (!cl_particles_blood.integer)
899 if (cl_particles_quake.integer)
900 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
903 static double bloodaccumulator = 0;
904 qboolean immediatebloodstain = true;
905 //CL_NewParticle(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);
906 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
907 for (;bloodaccumulator > 0;bloodaccumulator--)
909 part = CL_NewParticle(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);
910 if (immediatebloodstain && part)
912 immediatebloodstain = false;
913 CL_ImmediateBloodStain(part);
918 else if (effectnameindex == EFFECT_TE_SPARK)
919 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
920 else if (effectnameindex == EFFECT_TE_PLASMABURN)
922 // plasma scorch mark
923 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
924 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
925 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
927 else if (effectnameindex == EFFECT_TE_GUNSHOT)
929 if (cl_particles_bulletimpacts.integer)
931 if (cl_particles_quake.integer)
932 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
935 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
936 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
937 CL_NewParticle(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);
941 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
942 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
944 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
946 if (cl_particles_bulletimpacts.integer)
948 if (cl_particles_quake.integer)
949 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
952 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
953 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
954 CL_NewParticle(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);
958 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
959 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
960 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
962 else if (effectnameindex == EFFECT_TE_EXPLOSION)
964 CL_ParticleExplosion(center);
965 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
967 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
969 CL_ParticleExplosion(center);
970 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
972 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
974 if (cl_particles_quake.integer)
977 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
980 CL_NewParticle(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);
982 CL_NewParticle(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);
986 CL_ParticleExplosion(center);
987 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
989 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
990 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
991 else if (effectnameindex == EFFECT_TE_FLAMEJET)
993 count *= cl_particles_quality.value;
995 CL_NewParticle(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);
997 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
999 float i, j, inc, vel;
1002 inc = 8 / cl_particles_quality.value;
1003 for (i = -128;i < 128;i += inc)
1005 for (j = -128;j < 128;j += inc)
1007 dir[0] = j + lhrandom(0, inc);
1008 dir[1] = i + lhrandom(0, inc);
1010 org[0] = center[0] + dir[0];
1011 org[1] = center[1] + dir[1];
1012 org[2] = center[2] + lhrandom(0, 64);
1013 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1014 CL_NewParticle(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);
1018 else if (effectnameindex == EFFECT_TE_TELEPORT)
1020 float i, j, k, inc, vel;
1023 if (cl_particles_quake.integer)
1024 inc = 4 / cl_particles_quality.value;
1026 inc = 8 / cl_particles_quality.value;
1027 for (i = -16;i < 16;i += inc)
1029 for (j = -16;j < 16;j += inc)
1031 for (k = -24;k < 32;k += inc)
1033 VectorSet(dir, i*8, j*8, k*8);
1034 VectorNormalize(dir);
1035 vel = lhrandom(50, 113);
1036 if (cl_particles_quake.integer)
1037 CL_NewParticle(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);
1039 CL_NewParticle(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);
1043 if (!cl_particles_quake.integer)
1044 CL_NewParticle(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);
1045 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1047 else if (effectnameindex == EFFECT_TE_TEI_G3)
1048 CL_NewParticle(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);
1049 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1051 if (cl_particles_smoke.integer)
1053 count *= 0.25f * cl_particles_quality.value;
1055 CL_NewParticle(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);
1058 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1060 CL_ParticleExplosion(center);
1061 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1063 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1066 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1067 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1068 if (cl_particles_smoke.integer)
1069 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1070 CL_NewParticle(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);
1071 if (cl_particles_sparks.integer)
1072 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1073 CL_NewParticle(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);
1074 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1076 else if (effectnameindex == EFFECT_EF_FLAME)
1078 count *= 300 * cl_particles_quality.value;
1080 CL_NewParticle(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);
1081 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1083 else if (effectnameindex == EFFECT_EF_STARDUST)
1085 count *= 200 * cl_particles_quality.value;
1087 CL_NewParticle(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);
1088 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1090 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1094 int smoke, blood, bubbles, r, color;
1096 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1099 Vector4Set(light, 0, 0, 0, 0);
1101 if (effectnameindex == EFFECT_TR_ROCKET)
1102 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1103 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1105 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1106 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1108 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1110 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1111 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1115 matrix4x4_t tempmatrix;
1116 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1117 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1118 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1122 if (!spawnparticles)
1125 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1128 VectorSubtract(originmaxs, originmins, dir);
1129 len = VectorNormalizeLength(dir);
1132 dec = -ent->persistent.trail_time;
1133 ent->persistent.trail_time += len;
1134 if (ent->persistent.trail_time < 0.01f)
1137 // if we skip out, leave it reset
1138 ent->persistent.trail_time = 0.0f;
1143 // advance into this frame to reach the first puff location
1144 VectorMA(originmins, dec, dir, pos);
1147 smoke = cl_particles.integer && cl_particles_smoke.integer;
1148 blood = cl_particles.integer && cl_particles_blood.integer;
1149 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1150 qd = 1.0f / cl_particles_quality.value;
1157 if (effectnameindex == EFFECT_TR_BLOOD)
1159 if (cl_particles_quake.integer)
1161 color = particlepalette[67 + (rand()&3)];
1162 CL_NewParticle(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);
1167 CL_NewParticle(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);
1170 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1172 if (cl_particles_quake.integer)
1175 color = particlepalette[67 + (rand()&3)];
1176 CL_NewParticle(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);
1181 CL_NewParticle(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);
1187 if (effectnameindex == EFFECT_TR_ROCKET)
1189 if (cl_particles_quake.integer)
1192 color = particlepalette[ramp3[r]];
1193 CL_NewParticle(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);
1197 CL_NewParticle(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);
1198 CL_NewParticle(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);
1201 else if (effectnameindex == EFFECT_TR_GRENADE)
1203 if (cl_particles_quake.integer)
1206 color = particlepalette[ramp3[r]];
1207 CL_NewParticle(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);
1211 CL_NewParticle(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);
1214 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1216 if (cl_particles_quake.integer)
1219 color = particlepalette[52 + (rand()&7)];
1220 CL_NewParticle(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);
1221 CL_NewParticle(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);
1223 else if (gamemode == GAME_GOODVSBAD2)
1226 CL_NewParticle(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);
1230 color = particlepalette[20 + (rand()&7)];
1231 CL_NewParticle(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);
1234 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1236 if (cl_particles_quake.integer)
1239 color = particlepalette[230 + (rand()&7)];
1240 CL_NewParticle(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);
1241 CL_NewParticle(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);
1245 color = particlepalette[226 + (rand()&7)];
1246 CL_NewParticle(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);
1249 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1251 if (cl_particles_quake.integer)
1253 color = particlepalette[152 + (rand()&3)];
1254 CL_NewParticle(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);
1256 else if (gamemode == GAME_GOODVSBAD2)
1259 CL_NewParticle(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);
1261 else if (gamemode == GAME_PRYDON)
1264 CL_NewParticle(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);
1267 CL_NewParticle(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);
1269 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1272 CL_NewParticle(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);
1274 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1277 CL_NewParticle(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);
1279 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1280 CL_NewParticle(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);
1284 if (effectnameindex == EFFECT_TR_ROCKET)
1285 CL_NewParticle(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);
1286 else if (effectnameindex == EFFECT_TR_GRENADE)
1287 CL_NewParticle(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);
1289 // advance to next time and position
1292 VectorMA (pos, dec, dir, pos);
1295 ent->persistent.trail_time = len;
1297 else if (developer.integer >= 1)
1298 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1301 // this is also called on point effects with spawndlight = true and
1302 // spawnparticles = true
1303 // it is called CL_ParticleTrail because most code does not want to supply
1304 // these parameters, only trail handling does
1305 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)
1308 qboolean found = false;
1309 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1311 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1312 return; // no such effect
1314 VectorLerp(originmins, 0.5, originmaxs, center);
1315 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1317 int effectinfoindex;
1320 particleeffectinfo_t *info;
1322 vec3_t centervelocity;
1328 qboolean underwater;
1329 qboolean immediatebloodstain;
1331 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1332 VectorLerp(originmins, 0.5, originmaxs, center);
1333 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1334 supercontents = CL_PointSuperContents(center);
1335 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1336 VectorSubtract(originmaxs, originmins, traildir);
1337 traillen = VectorLength(traildir);
1338 VectorNormalize(traildir);
1339 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1341 if (info->effectnameindex == effectnameindex)
1344 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1346 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1349 // spawn a dlight if requested
1350 if (info->lightradiusstart > 0 && spawndlight)
1352 matrix4x4_t tempmatrix;
1353 if (info->trailspacing > 0)
1354 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1356 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1357 if (info->lighttime > 0 && info->lightradiusfade > 0)
1359 // light flash (explosion, etc)
1360 // called when effect starts
1361 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1366 // called by CL_LinkNetworkEntity
1367 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1368 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1369 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1373 if (!spawnparticles)
1378 if (info->tex[1] > info->tex[0])
1380 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1381 tex = min(tex, info->tex[1] - 1);
1383 if(info->staintex[0] < 0)
1384 staintex = info->staintex[0];
1387 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1388 staintex = min(staintex, info->staintex[1] - 1);
1390 if (info->particletype == pt_decal)
1391 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1392 else if (info->orientation == PARTICLE_HBEAM)
1393 CL_NewParticle(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], originmins[1], originmins[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);
1396 if (!cl_particles.integer)
1398 switch (info->particletype)
1400 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1401 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1402 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1403 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1404 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1405 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1408 VectorCopy(originmins, trailpos);
1409 if (info->trailspacing > 0)
1411 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1412 trailstep = info->trailspacing / cl_particles_quality.value;
1413 immediatebloodstain = false;
1417 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1419 immediatebloodstain = info->particletype == pt_blood || staintex;
1421 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1422 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1424 if (info->tex[1] > info->tex[0])
1426 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1427 tex = min(tex, info->tex[1] - 1);
1431 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1432 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1433 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1436 part = CL_NewParticle(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], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[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);
1437 if (immediatebloodstain && part)
1439 immediatebloodstain = false;
1440 CL_ImmediateBloodStain(part);
1443 VectorMA(trailpos, trailstep, traildir, trailpos);
1450 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1453 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)
1455 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1463 void CL_EntityParticles (const entity_t *ent)
1466 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1467 static vec3_t avelocities[NUMVERTEXNORMALS];
1468 if (!cl_particles.integer) return;
1469 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1471 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1473 if (!avelocities[0][0])
1474 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1475 avelocities[0][i] = lhrandom(0, 2.55);
1477 for (i = 0;i < NUMVERTEXNORMALS;i++)
1479 yaw = cl.time * avelocities[i][0];
1480 pitch = cl.time * avelocities[i][1];
1481 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1482 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1483 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1484 CL_NewParticle(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);
1489 void CL_ReadPointFile_f (void)
1491 vec3_t org, leakorg;
1493 char *pointfile = NULL, *pointfilepos, *t, tchar;
1494 char name[MAX_OSPATH];
1499 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1500 strlcat (name, ".pts", sizeof (name));
1501 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1504 Con_Printf("Could not open %s\n", name);
1508 Con_Printf("Reading %s...\n", name);
1509 VectorClear(leakorg);
1512 pointfilepos = pointfile;
1513 while (*pointfilepos)
1515 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1520 while (*t && *t != '\n' && *t != '\r')
1524 #if _MSC_VER >= 1400
1525 #define sscanf sscanf_s
1527 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1533 VectorCopy(org, leakorg);
1536 if (cl.num_particles < cl.max_particles - 3)
1539 CL_NewParticle(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);
1542 Mem_Free(pointfile);
1543 VectorCopy(leakorg, org);
1544 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1546 CL_NewParticle(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);
1547 CL_NewParticle(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);
1548 CL_NewParticle(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);
1553 CL_ParseParticleEffect
1555 Parse an effect out of the server message
1558 void CL_ParseParticleEffect (void)
1561 int i, count, msgcount, color;
1563 MSG_ReadVector(org, cls.protocol);
1564 for (i=0 ; i<3 ; i++)
1565 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1566 msgcount = MSG_ReadByte ();
1567 color = MSG_ReadByte ();
1569 if (msgcount == 255)
1574 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1579 CL_ParticleExplosion
1583 void CL_ParticleExplosion (const vec3_t org)
1589 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1590 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1592 if (cl_particles_quake.integer)
1594 for (i = 0;i < 1024;i++)
1600 color = particlepalette[ramp1[r]];
1601 CL_NewParticle(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);
1605 color = particlepalette[ramp2[r]];
1606 CL_NewParticle(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);
1612 i = CL_PointSuperContents(org);
1613 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1615 if (cl_particles.integer && cl_particles_bubbles.integer)
1616 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1617 CL_NewParticle(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);
1621 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1623 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1627 for (k = 0;k < 16;k++)
1630 VectorMA(org, 128, v2, v);
1631 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1632 if (trace.fraction >= 0.1)
1635 VectorSubtract(trace.endpos, org, v2);
1636 VectorScale(v2, 2.0f, v2);
1637 CL_NewParticle(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);
1643 if (cl_particles_explosions_shell.integer)
1644 R_NewExplosion(org);
1649 CL_ParticleExplosion2
1653 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1656 if (!cl_particles.integer) return;
1658 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1660 k = particlepalette[colorStart + (i % colorLength)];
1661 if (cl_particles_quake.integer)
1662 CL_NewParticle(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);
1664 CL_NewParticle(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);
1668 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1670 if (cl_particles_sparks.integer)
1672 sparkcount *= cl_particles_quality.value;
1673 while(sparkcount-- > 0)
1674 CL_NewParticle(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);
1678 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1680 if (cl_particles_smoke.integer)
1682 smokecount *= cl_particles_quality.value;
1683 while(smokecount-- > 0)
1684 CL_NewParticle(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);
1688 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)
1691 if (!cl_particles.integer) return;
1693 count = (int)(count * cl_particles_quality.value);
1696 k = particlepalette[colorbase + (rand()&3)];
1697 CL_NewParticle(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);
1701 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1704 float minz, maxz, lifetime = 30;
1705 if (!cl_particles.integer) return;
1706 if (dir[2] < 0) // falling
1708 minz = maxs[2] + dir[2] * 0.1;
1711 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1716 maxz = maxs[2] + dir[2] * 0.1;
1718 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1721 count = (int)(count * cl_particles_quality.value);
1726 if (!cl_particles_rain.integer) break;
1727 count *= 4; // ick, this should be in the mod or maps?
1731 k = particlepalette[colorbase + (rand()&3)];
1732 if (gamemode == GAME_GOODVSBAD2)
1733 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1735 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1739 if (!cl_particles_snow.integer) break;
1742 k = particlepalette[colorbase + (rand()&3)];
1743 if (gamemode == GAME_GOODVSBAD2)
1744 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1746 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1750 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1754 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1755 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1756 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1757 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1759 #define PARTICLETEXTURESIZE 64
1760 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1762 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1766 dz = 1 - (dx*dx+dy*dy);
1767 if (dz > 0) // it does hit the sphere
1771 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1772 VectorNormalize(normal);
1773 dot = DotProduct(normal, light);
1774 if (dot > 0.5) // interior reflection
1775 f += ((dot * 2) - 1);
1776 else if (dot < -0.5) // exterior reflection
1777 f += ((dot * -2) - 1);
1779 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1780 VectorNormalize(normal);
1781 dot = DotProduct(normal, light);
1782 if (dot > 0.5) // interior reflection
1783 f += ((dot * 2) - 1);
1784 else if (dot < -0.5) // exterior reflection
1785 f += ((dot * -2) - 1);
1787 f += 16; // just to give it a haze so you can see the outline
1788 f = bound(0, f, 255);
1789 return (unsigned char) f;
1795 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1796 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1798 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1799 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1800 *width = particlefontcellwidth;
1801 *height = particlefontcellheight;
1804 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1806 int basex, basey, w, h, y;
1807 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1808 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1809 Sys_Error("invalid particle texture size for autogenerating");
1810 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1811 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1814 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1817 float cx, cy, dx, dy, f, iradius;
1819 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1820 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1821 iradius = 1.0f / radius;
1822 alpha *= (1.0f / 255.0f);
1823 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1825 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1829 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1834 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1835 d[0] += (int)(f * (blue - d[0]));
1836 d[1] += (int)(f * (green - d[1]));
1837 d[2] += (int)(f * (red - d[2]));
1843 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1846 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1848 data[0] = bound(minb, data[0], maxb);
1849 data[1] = bound(ming, data[1], maxg);
1850 data[2] = bound(minr, data[2], maxr);
1854 void particletextureinvert(unsigned char *data)
1857 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1859 data[0] = 255 - data[0];
1860 data[1] = 255 - data[1];
1861 data[2] = 255 - data[2];
1865 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1866 static void R_InitBloodTextures (unsigned char *particletexturedata)
1869 unsigned char *data = Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4);
1872 for (i = 0;i < 8;i++)
1874 memset(data, 255, sizeof(data));
1875 for (k = 0;k < 24;k++)
1876 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1877 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1878 particletextureinvert(data);
1879 setuptex(tex_bloodparticle[i], data, particletexturedata);
1883 for (i = 0;i < 8;i++)
1885 memset(data, 255, sizeof(data));
1887 for (j = 1;j < 10;j++)
1888 for (k = min(j, m - 1);k < m;k++)
1889 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1890 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1891 particletextureinvert(data);
1892 setuptex(tex_blooddecal[i], data, particletexturedata);
1898 //uncomment this to make engine save out particle font to a tga file when run
1899 //#define DUMPPARTICLEFONT
1901 static void R_InitParticleTexture (void)
1903 int x, y, d, i, k, m;
1904 int basex, basey, w, h;
1908 fs_offset_t filesize;
1910 // a note: decals need to modulate (multiply) the background color to
1911 // properly darken it (stain), and they need to be able to alpha fade,
1912 // this is a very difficult challenge because it means fading to white
1913 // (no change to background) rather than black (darkening everything
1914 // behind the whole decal polygon), and to accomplish this the texture is
1915 // inverted (dark red blood on white background becomes brilliant cyan
1916 // and white on black background) so we can alpha fade it to black, then
1917 // we invert it again during the blendfunc to make it work...
1919 #ifndef DUMPPARTICLEFONT
1920 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1923 particlefonttexture = decalskinframe->base;
1924 // TODO maybe allow custom grid size?
1925 particlefontwidth = image_width;
1926 particlefontheight = image_height;
1927 particlefontcellwidth = image_width / 8;
1928 particlefontcellheight = image_height / 8;
1929 particlefontcols = 8;
1930 particlefontrows = 8;
1935 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1936 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4);
1937 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1938 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1940 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1941 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1942 particlefontcols = 8;
1943 particlefontrows = 8;
1945 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1948 for (i = 0;i < 8;i++)
1950 memset(data, 255, sizeof(data));
1953 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1954 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1956 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1958 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1959 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1961 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1962 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1964 d = (int)(d * (1-(dx*dx+dy*dy)));
1965 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1966 d = bound(0, d, 255);
1967 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1974 setuptex(tex_smoke[i], data, particletexturedata);
1978 memset(data, 255, sizeof(data));
1979 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1981 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1982 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1984 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1985 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1986 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1989 setuptex(tex_rainsplash, data, particletexturedata);
1992 memset(data, 255, sizeof(data));
1993 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1995 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1996 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1998 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1999 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2000 d = bound(0, d, 255);
2001 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2004 setuptex(tex_particle, data, particletexturedata);
2007 memset(data, 255, sizeof(data));
2008 light[0] = 1;light[1] = 1;light[2] = 1;
2009 VectorNormalize(light);
2010 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2012 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2013 // stretch upper half of bubble by +50% and shrink lower half by -50%
2014 // (this gives an elongated teardrop shape)
2016 dy = (dy - 0.5f) * 2.0f;
2018 dy = (dy - 0.5f) / 1.5f;
2019 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2021 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2022 // shrink bubble width to half
2024 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2027 setuptex(tex_raindrop, data, particletexturedata);
2030 memset(data, 255, sizeof(data));
2031 light[0] = 1;light[1] = 1;light[2] = 1;
2032 VectorNormalize(light);
2033 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2035 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2036 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2038 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2039 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2042 setuptex(tex_bubble, data, particletexturedata);
2044 // Blood particles and blood decals
2045 R_InitBloodTextures (particletexturedata);
2048 for (i = 0;i < 8;i++)
2050 memset(data, 255, sizeof(data));
2051 for (k = 0;k < 12;k++)
2052 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2053 for (k = 0;k < 3;k++)
2054 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2055 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2056 particletextureinvert(data);
2057 setuptex(tex_bulletdecal[i], data, particletexturedata);
2060 #ifdef DUMPPARTICLEFONT
2061 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2064 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2065 particlefonttexture = decalskinframe->base;
2067 Mem_Free(particletexturedata);
2072 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2074 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2075 particletexture[i].texture = particlefonttexture;
2076 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2077 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2078 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2079 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2082 #ifndef DUMPPARTICLEFONT
2083 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true);
2084 if (!particletexture[tex_beam].texture)
2087 unsigned char noise3[64][64], data2[64][16][4];
2089 fractalnoise(&noise3[0][0], 64, 4);
2091 for (y = 0;y < 64;y++)
2093 dy = (y - 0.5f*64) / (64*0.5f-1);
2094 for (x = 0;x < 16;x++)
2096 dx = (x - 0.5f*16) / (16*0.5f-2);
2097 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2098 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2099 data2[y][x][3] = 255;
2103 #ifdef DUMPPARTICLEFONT
2104 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2106 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2108 particletexture[tex_beam].s1 = 0;
2109 particletexture[tex_beam].t1 = 0;
2110 particletexture[tex_beam].s2 = 1;
2111 particletexture[tex_beam].t2 = 1;
2113 // now load an texcoord/texture override file
2114 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2121 if(!COM_ParseToken_Simple(&bufptr, true, false))
2123 if(!strcmp(com_token, "\n"))
2124 continue; // empty line
2125 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2126 particletexture[i].texture = particlefonttexture;
2128 if (!COM_ParseToken_Simple(&bufptr, true, false))
2130 if (!strcmp(com_token, "\n"))
2132 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2135 particletexture[i].s1 = atof(com_token);
2137 if (!COM_ParseToken_Simple(&bufptr, true, false))
2139 if (!strcmp(com_token, "\n"))
2141 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2144 particletexture[i].t1 = atof(com_token);
2146 if (!COM_ParseToken_Simple(&bufptr, true, false))
2148 if (!strcmp(com_token, "\n"))
2150 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2153 particletexture[i].s2 = atof(com_token);
2155 if (!COM_ParseToken_Simple(&bufptr, true, false))
2157 if (!strcmp(com_token, "\n"))
2159 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2162 particletexture[i].t2 = atof(com_token);
2168 static void r_part_start(void)
2171 // generate particlepalette for convenience from the main one
2172 for (i = 0;i < 256;i++)
2173 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2174 particletexturepool = R_AllocTexturePool();
2175 R_InitParticleTexture ();
2176 CL_Particles_LoadEffectInfo();
2179 static void r_part_shutdown(void)
2181 R_FreeTexturePool(&particletexturepool);
2184 static void r_part_newmap(void)
2187 R_SkinFrame_MarkUsed(decalskinframe);
2188 CL_Particles_LoadEffectInfo();
2191 #define BATCHSIZE 256
2192 unsigned short particle_elements[BATCHSIZE*6];
2193 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2195 void R_Particles_Init (void)
2198 for (i = 0;i < BATCHSIZE;i++)
2200 particle_elements[i*6+0] = i*4+0;
2201 particle_elements[i*6+1] = i*4+1;
2202 particle_elements[i*6+2] = i*4+2;
2203 particle_elements[i*6+3] = i*4+0;
2204 particle_elements[i*6+4] = i*4+2;
2205 particle_elements[i*6+5] = i*4+3;
2208 Cvar_RegisterVariable(&r_drawparticles);
2209 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2210 Cvar_RegisterVariable(&r_drawdecals);
2211 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2212 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2215 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2217 int surfacelistindex;
2219 float *v3f, *t2f, *c4f;
2220 particletexture_t *tex;
2221 float right[3], up[3], size, ca;
2222 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2223 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2225 RSurf_ActiveWorldEntity();
2227 r_refdef.stats.drawndecals += numsurfaces;
2228 R_Mesh_ResetTextureState();
2229 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2230 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2231 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2232 GL_DepthMask(false);
2233 GL_DepthRange(0, 1);
2234 GL_PolygonOffset(0, 0);
2236 GL_CullFace(GL_NONE);
2238 // generate all the vertices at once
2239 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2241 d = cl.decals + surfacelist[surfacelistindex];
2244 c4f = particle_color4f + 16*surfacelistindex;
2245 ca = d->alpha * alphascale;
2246 if (r_refdef.fogenabled)
2247 ca *= RSurf_FogVertex(d->org);
2248 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2249 Vector4Copy(c4f, c4f + 4);
2250 Vector4Copy(c4f, c4f + 8);
2251 Vector4Copy(c4f, c4f + 12);
2253 // calculate vertex positions
2254 size = d->size * cl_particles_size.value;
2255 VectorVectors(d->normal, right, up);
2256 VectorScale(right, size, right);
2257 VectorScale(up, size, up);
2258 v3f = particle_vertex3f + 12*surfacelistindex;
2259 v3f[ 0] = d->org[0] - right[0] - up[0];
2260 v3f[ 1] = d->org[1] - right[1] - up[1];
2261 v3f[ 2] = d->org[2] - right[2] - up[2];
2262 v3f[ 3] = d->org[0] - right[0] + up[0];
2263 v3f[ 4] = d->org[1] - right[1] + up[1];
2264 v3f[ 5] = d->org[2] - right[2] + up[2];
2265 v3f[ 6] = d->org[0] + right[0] + up[0];
2266 v3f[ 7] = d->org[1] + right[1] + up[1];
2267 v3f[ 8] = d->org[2] + right[2] + up[2];
2268 v3f[ 9] = d->org[0] + right[0] - up[0];
2269 v3f[10] = d->org[1] + right[1] - up[1];
2270 v3f[11] = d->org[2] + right[2] - up[2];
2272 // calculate texcoords
2273 tex = &particletexture[d->texnum];
2274 t2f = particle_texcoord2f + 8*surfacelistindex;
2275 t2f[0] = tex->s1;t2f[1] = tex->t2;
2276 t2f[2] = tex->s1;t2f[3] = tex->t1;
2277 t2f[4] = tex->s2;t2f[5] = tex->t1;
2278 t2f[6] = tex->s2;t2f[7] = tex->t2;
2281 // now render the decals all at once
2282 // (this assumes they all use one particle font texture!)
2283 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2284 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2285 GL_LockArrays(0, numsurfaces*4);
2286 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2287 GL_LockArrays(0, 0);
2290 void R_DrawDecals (void)
2293 int drawdecals = r_drawdecals.integer;
2298 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2300 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2301 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2303 // LordHavoc: early out conditions
2307 decalfade = frametime * 256 / cl_decals_fadetime.value;
2308 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2309 drawdist2 = drawdist2*drawdist2;
2311 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2313 if (!decal->typeindex)
2316 if (killsequence - decal->decalsequence > 0)
2319 if (cl.time > decal->time2 + cl_decals_time.value)
2321 decal->alpha -= decalfade;
2322 if (decal->alpha <= 0)
2328 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2330 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2331 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2337 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2343 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2344 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2347 decal->typeindex = 0;
2348 if (cl.free_decal > i)
2352 // reduce cl.num_decals if possible
2353 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2356 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2358 decal_t *olddecals = cl.decals;
2359 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2360 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2361 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2362 Mem_Free(olddecals);
2365 r_refdef.stats.totaldecals = cl.num_decals;
2368 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2370 int surfacelistindex;
2371 int batchstart, batchcount;
2372 const particle_t *p;
2374 rtexture_t *texture;
2375 float *v3f, *t2f, *c4f;
2376 particletexture_t *tex;
2377 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2378 float ambient[3], diffuse[3], diffusenormal[3];
2379 vec4_t colormultiplier;
2381 RSurf_ActiveWorldEntity();
2383 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));
2385 r_refdef.stats.particles += numsurfaces;
2386 R_Mesh_ResetTextureState();
2387 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2388 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2389 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2390 GL_DepthMask(false);
2391 GL_DepthRange(0, 1);
2392 GL_PolygonOffset(0, 0);
2394 GL_CullFace(GL_NONE);
2396 // first generate all the vertices at once
2397 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2399 p = cl.particles + surfacelist[surfacelistindex];
2401 blendmode = p->blendmode;
2405 case PBLEND_INVALID:
2407 c4f[0] = p->color[0] * (1.0f / 256.0f);
2408 c4f[1] = p->color[1] * (1.0f / 256.0f);
2409 c4f[2] = p->color[2] * (1.0f / 256.0f);
2410 c4f[3] = p->alpha * colormultiplier[3];
2411 // additive and modulate can just fade out in fog (this is correct)
2412 if (r_refdef.fogenabled)
2413 c4f[3] *= RSurf_FogVertex(p->org);
2414 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2421 c4f[0] = p->color[0] * colormultiplier[0];
2422 c4f[1] = p->color[1] * colormultiplier[1];
2423 c4f[2] = p->color[2] * colormultiplier[2];
2424 c4f[3] = p->alpha * colormultiplier[3];
2425 // additive and modulate can just fade out in fog (this is correct)
2426 if (r_refdef.fogenabled)
2427 c4f[3] *= RSurf_FogVertex(p->org);
2428 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2435 c4f[0] = p->color[0] * colormultiplier[0];
2436 c4f[1] = p->color[1] * colormultiplier[1];
2437 c4f[2] = p->color[2] * colormultiplier[2];
2438 c4f[3] = p->alpha * colormultiplier[3];
2439 // note: lighting is not cheap!
2440 if (particletype[p->typeindex].lighting)
2442 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2443 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2444 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2445 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2447 // mix in the fog color
2448 if (r_refdef.fogenabled)
2450 fog = RSurf_FogVertex(p->org);
2452 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2453 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2454 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2458 // copy the color into the other three vertices
2459 Vector4Copy(c4f, c4f + 4);
2460 Vector4Copy(c4f, c4f + 8);
2461 Vector4Copy(c4f, c4f + 12);
2463 size = p->size * cl_particles_size.value;
2464 tex = &particletexture[p->texnum];
2465 switch(p->orientation)
2467 case PARTICLE_INVALID:
2468 case PARTICLE_BILLBOARD:
2469 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2470 VectorScale(r_refdef.view.up, size, up);
2471 v3f[ 0] = p->org[0] - right[0] - up[0];
2472 v3f[ 1] = p->org[1] - right[1] - up[1];
2473 v3f[ 2] = p->org[2] - right[2] - up[2];
2474 v3f[ 3] = p->org[0] - right[0] + up[0];
2475 v3f[ 4] = p->org[1] - right[1] + up[1];
2476 v3f[ 5] = p->org[2] - right[2] + up[2];
2477 v3f[ 6] = p->org[0] + right[0] + up[0];
2478 v3f[ 7] = p->org[1] + right[1] + up[1];
2479 v3f[ 8] = p->org[2] + right[2] + up[2];
2480 v3f[ 9] = p->org[0] + right[0] - up[0];
2481 v3f[10] = p->org[1] + right[1] - up[1];
2482 v3f[11] = p->org[2] + right[2] - up[2];
2483 t2f[0] = tex->s1;t2f[1] = tex->t2;
2484 t2f[2] = tex->s1;t2f[3] = tex->t1;
2485 t2f[4] = tex->s2;t2f[5] = tex->t1;
2486 t2f[6] = tex->s2;t2f[7] = tex->t2;
2488 case PARTICLE_ORIENTED_DOUBLESIDED:
2489 VectorVectors(p->vel, right, up);
2490 VectorScale(right, size * p->stretch, right);
2491 VectorScale(up, size, up);
2492 v3f[ 0] = p->org[0] - right[0] - up[0];
2493 v3f[ 1] = p->org[1] - right[1] - up[1];
2494 v3f[ 2] = p->org[2] - right[2] - up[2];
2495 v3f[ 3] = p->org[0] - right[0] + up[0];
2496 v3f[ 4] = p->org[1] - right[1] + up[1];
2497 v3f[ 5] = p->org[2] - right[2] + up[2];
2498 v3f[ 6] = p->org[0] + right[0] + up[0];
2499 v3f[ 7] = p->org[1] + right[1] + up[1];
2500 v3f[ 8] = p->org[2] + right[2] + up[2];
2501 v3f[ 9] = p->org[0] + right[0] - up[0];
2502 v3f[10] = p->org[1] + right[1] - up[1];
2503 v3f[11] = p->org[2] + right[2] - up[2];
2504 t2f[0] = tex->s1;t2f[1] = tex->t2;
2505 t2f[2] = tex->s1;t2f[3] = tex->t1;
2506 t2f[4] = tex->s2;t2f[5] = tex->t1;
2507 t2f[6] = tex->s2;t2f[7] = tex->t2;
2509 case PARTICLE_SPARK:
2510 len = VectorLength(p->vel);
2511 VectorNormalize2(p->vel, up);
2512 lenfactor = p->stretch * 0.04 * len;
2513 if(lenfactor < size * 0.5)
2514 lenfactor = size * 0.5;
2515 VectorMA(p->org, -lenfactor, up, v);
2516 VectorMA(p->org, lenfactor, up, up2);
2517 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2518 t2f[0] = tex->s1;t2f[1] = tex->t2;
2519 t2f[2] = tex->s1;t2f[3] = tex->t1;
2520 t2f[4] = tex->s2;t2f[5] = tex->t1;
2521 t2f[6] = tex->s2;t2f[7] = tex->t2;
2523 case PARTICLE_VBEAM:
2524 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2525 VectorSubtract(p->vel, p->org, up);
2526 VectorNormalize(up);
2527 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2528 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2529 t2f[0] = tex->s2;t2f[1] = v[0];
2530 t2f[2] = tex->s1;t2f[3] = v[0];
2531 t2f[4] = tex->s1;t2f[5] = v[1];
2532 t2f[6] = tex->s2;t2f[7] = v[1];
2534 case PARTICLE_HBEAM:
2535 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2536 VectorSubtract(p->vel, p->org, up);
2537 VectorNormalize(up);
2538 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2539 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2540 t2f[0] = v[0];t2f[1] = tex->t1;
2541 t2f[2] = v[0];t2f[3] = tex->t2;
2542 t2f[4] = v[1];t2f[5] = tex->t2;
2543 t2f[6] = v[1];t2f[7] = tex->t1;
2548 // now render batches of particles based on blendmode and texture
2549 blendmode = PBLEND_INVALID;
2551 GL_LockArrays(0, numsurfaces*4);
2554 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2556 p = cl.particles + surfacelist[surfacelistindex];
2558 if (blendmode != p->blendmode)
2560 blendmode = p->blendmode;
2564 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2566 case PBLEND_INVALID:
2568 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2571 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2575 if (texture != particletexture[p->texnum].texture)
2577 texture = particletexture[p->texnum].texture;
2578 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2581 // iterate until we find a change in settings
2582 batchstart = surfacelistindex++;
2583 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2585 p = cl.particles + surfacelist[surfacelistindex];
2586 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2590 batchcount = surfacelistindex - batchstart;
2591 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2593 GL_LockArrays(0, 0);
2596 void R_DrawParticles (void)
2599 int drawparticles = r_drawparticles.integer;
2600 float minparticledist;
2602 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2608 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2609 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2611 // LordHavoc: early out conditions
2612 if (!cl.num_particles)
2615 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2616 gravity = frametime * cl.movevars_gravity;
2617 dvel = 1+4*frametime;
2618 decalfade = frametime * 255 / cl_decals_fadetime.value;
2619 update = frametime > 0;
2620 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2621 drawdist2 = drawdist2*drawdist2;
2623 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2627 if (cl.free_particle > i)
2628 cl.free_particle = i;
2634 if (p->delayedspawn > cl.time)
2636 p->delayedspawn = 0;
2640 p->size += p->sizeincrease * frametime;
2641 p->alpha -= p->alphafade * frametime;
2643 if (p->alpha <= 0 || p->die <= cl.time)
2646 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2648 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2650 if (p->typeindex == pt_blood)
2651 p->size += frametime * 8;
2653 p->vel[2] -= p->gravity * gravity;
2654 f = 1.0f - min(p->liquidfriction * frametime, 1);
2655 VectorScale(p->vel, f, p->vel);
2659 p->vel[2] -= p->gravity * gravity;
2662 f = 1.0f - min(p->airfriction * frametime, 1);
2663 VectorScale(p->vel, f, p->vel);
2667 VectorCopy(p->org, oldorg);
2668 VectorMA(p->org, frametime, p->vel, p->org);
2669 if (p->bounce && cl.time >= p->delayedcollisions)
2671 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2672 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2673 // or if the trace hit something flagged as NOIMPACT
2674 // then remove the particle
2675 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2677 VectorCopy(trace.endpos, p->org);
2678 // react if the particle hit something
2679 if (trace.fraction < 1)
2681 VectorCopy(trace.endpos, p->org);
2683 if (p->staintexnum >= 0)
2685 // blood - splash on solid
2686 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2689 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2690 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2691 if (cl_decals.integer)
2693 // create a decal for the blood splat
2694 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2699 if (p->typeindex == pt_blood)
2701 // blood - splash on solid
2702 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2704 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2706 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)));
2707 if (cl_decals.integer)
2709 // create a decal for the blood splat
2710 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 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 * 2, p->alpha);
2715 else if (p->bounce < 0)
2717 // bounce -1 means remove on impact
2722 // anything else - bounce off solid
2723 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2724 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2725 if (DotProduct(p->vel, p->vel) < 0.03)
2726 VectorClear(p->vel);
2732 if (p->typeindex != pt_static)
2734 switch (p->typeindex)
2736 case pt_entityparticle:
2737 // particle that removes itself after one rendered frame
2744 a = CL_PointSuperContents(p->org);
2745 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2749 a = CL_PointSuperContents(p->org);
2750 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2754 a = CL_PointSuperContents(p->org);
2755 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2759 if (cl.time > p->time2)
2762 p->time2 = cl.time + (rand() & 3) * 0.1;
2763 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2764 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2766 a = CL_PointSuperContents(p->org);
2767 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2775 else if (p->delayedspawn)
2779 // don't render particles too close to the view (they chew fillrate)
2780 // also don't render particles behind the view (useless)
2781 // further checks to cull to the frustum would be too slow here
2782 switch(p->typeindex)
2785 // beams have no culling
2786 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2789 if(cl_particles_visculling.integer)
2790 if (!r_refdef.viewcache.world_novis)
2791 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2793 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2795 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2798 // anything else just has to be in front of the viewer and visible at this distance
2799 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2800 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2807 if (cl.free_particle > i)
2808 cl.free_particle = i;
2811 // reduce cl.num_particles if possible
2812 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2815 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2817 particle_t *oldparticles = cl.particles;
2818 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2819 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2820 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2821 Mem_Free(oldparticles);