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];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];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 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1870 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1873 for (i = 0;i < 8;i++)
1875 memset(data, 255, datasize);
1876 for (k = 0;k < 24;k++)
1877 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1878 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1879 particletextureinvert(data);
1880 setuptex(tex_bloodparticle[i], data, particletexturedata);
1884 for (i = 0;i < 8;i++)
1886 memset(data, 255, datasize);
1888 for (j = 1;j < 10;j++)
1889 for (k = min(j, m - 1);k < m;k++)
1890 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1891 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1892 particletextureinvert(data);
1893 setuptex(tex_blooddecal[i], data, particletexturedata);
1899 //uncomment this to make engine save out particle font to a tga file when run
1900 //#define DUMPPARTICLEFONT
1902 static void R_InitParticleTexture (void)
1904 int x, y, d, i, k, m;
1905 int basex, basey, w, h;
1909 fs_offset_t filesize;
1911 // a note: decals need to modulate (multiply) the background color to
1912 // properly darken it (stain), and they need to be able to alpha fade,
1913 // this is a very difficult challenge because it means fading to white
1914 // (no change to background) rather than black (darkening everything
1915 // behind the whole decal polygon), and to accomplish this the texture is
1916 // inverted (dark red blood on white background becomes brilliant cyan
1917 // and white on black background) so we can alpha fade it to black, then
1918 // we invert it again during the blendfunc to make it work...
1920 #ifndef DUMPPARTICLEFONT
1921 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1924 particlefonttexture = decalskinframe->base;
1925 // TODO maybe allow custom grid size?
1926 particlefontwidth = image_width;
1927 particlefontheight = image_height;
1928 particlefontcellwidth = image_width / 8;
1929 particlefontcellheight = image_height / 8;
1930 particlefontcols = 8;
1931 particlefontrows = 8;
1936 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1937 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1938 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1939 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1940 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1942 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1943 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1944 particlefontcols = 8;
1945 particlefontrows = 8;
1947 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1950 for (i = 0;i < 8;i++)
1952 memset(data, 255, datasize);
1955 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1956 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1958 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1960 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1961 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1963 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1964 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1966 d = (int)(d * (1-(dx*dx+dy*dy)));
1967 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1968 d = bound(0, d, 255);
1969 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1976 setuptex(tex_smoke[i], data, particletexturedata);
1980 memset(data, 255, datasize);
1981 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1983 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1984 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1986 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1987 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1988 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1991 setuptex(tex_rainsplash, data, particletexturedata);
1994 memset(data, 255, datasize);
1995 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1997 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1998 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2000 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2001 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2002 d = bound(0, d, 255);
2003 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2006 setuptex(tex_particle, data, particletexturedata);
2009 memset(data, 255, datasize);
2010 light[0] = 1;light[1] = 1;light[2] = 1;
2011 VectorNormalize(light);
2012 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2014 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2015 // stretch upper half of bubble by +50% and shrink lower half by -50%
2016 // (this gives an elongated teardrop shape)
2018 dy = (dy - 0.5f) * 2.0f;
2020 dy = (dy - 0.5f) / 1.5f;
2021 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2023 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2024 // shrink bubble width to half
2026 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2029 setuptex(tex_raindrop, data, particletexturedata);
2032 memset(data, 255, datasize);
2033 light[0] = 1;light[1] = 1;light[2] = 1;
2034 VectorNormalize(light);
2035 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2037 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2038 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2040 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2044 setuptex(tex_bubble, data, particletexturedata);
2046 // Blood particles and blood decals
2047 R_InitBloodTextures (particletexturedata);
2050 for (i = 0;i < 8;i++)
2052 memset(data, 255, datasize);
2053 for (k = 0;k < 12;k++)
2054 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2055 for (k = 0;k < 3;k++)
2056 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2057 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2058 particletextureinvert(data);
2059 setuptex(tex_bulletdecal[i], data, particletexturedata);
2062 #ifdef DUMPPARTICLEFONT
2063 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2066 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2067 particlefonttexture = decalskinframe->base;
2069 Mem_Free(particletexturedata);
2074 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2076 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2077 particletexture[i].texture = particlefonttexture;
2078 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2079 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2080 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2081 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2084 #ifndef DUMPPARTICLEFONT
2085 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true);
2086 if (!particletexture[tex_beam].texture)
2089 unsigned char noise3[64][64], data2[64][16][4];
2091 fractalnoise(&noise3[0][0], 64, 4);
2093 for (y = 0;y < 64;y++)
2095 dy = (y - 0.5f*64) / (64*0.5f-1);
2096 for (x = 0;x < 16;x++)
2098 dx = (x - 0.5f*16) / (16*0.5f-2);
2099 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2100 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2101 data2[y][x][3] = 255;
2105 #ifdef DUMPPARTICLEFONT
2106 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2108 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2110 particletexture[tex_beam].s1 = 0;
2111 particletexture[tex_beam].t1 = 0;
2112 particletexture[tex_beam].s2 = 1;
2113 particletexture[tex_beam].t2 = 1;
2115 // now load an texcoord/texture override file
2116 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2123 if(!COM_ParseToken_Simple(&bufptr, true, false))
2125 if(!strcmp(com_token, "\n"))
2126 continue; // empty line
2127 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2128 particletexture[i].texture = particlefonttexture;
2130 if (!COM_ParseToken_Simple(&bufptr, true, false))
2132 if (!strcmp(com_token, "\n"))
2134 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2137 particletexture[i].s1 = atof(com_token);
2139 if (!COM_ParseToken_Simple(&bufptr, true, false))
2141 if (!strcmp(com_token, "\n"))
2143 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2146 particletexture[i].t1 = atof(com_token);
2148 if (!COM_ParseToken_Simple(&bufptr, true, false))
2150 if (!strcmp(com_token, "\n"))
2152 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2155 particletexture[i].s2 = atof(com_token);
2157 if (!COM_ParseToken_Simple(&bufptr, true, false))
2159 if (!strcmp(com_token, "\n"))
2161 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2164 particletexture[i].t2 = atof(com_token);
2170 static void r_part_start(void)
2173 // generate particlepalette for convenience from the main one
2174 for (i = 0;i < 256;i++)
2175 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2176 particletexturepool = R_AllocTexturePool();
2177 R_InitParticleTexture ();
2178 CL_Particles_LoadEffectInfo();
2181 static void r_part_shutdown(void)
2183 R_FreeTexturePool(&particletexturepool);
2186 static void r_part_newmap(void)
2189 R_SkinFrame_MarkUsed(decalskinframe);
2190 CL_Particles_LoadEffectInfo();
2193 #define BATCHSIZE 256
2194 unsigned short particle_elements[BATCHSIZE*6];
2195 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2197 void R_Particles_Init (void)
2200 for (i = 0;i < BATCHSIZE;i++)
2202 particle_elements[i*6+0] = i*4+0;
2203 particle_elements[i*6+1] = i*4+1;
2204 particle_elements[i*6+2] = i*4+2;
2205 particle_elements[i*6+3] = i*4+0;
2206 particle_elements[i*6+4] = i*4+2;
2207 particle_elements[i*6+5] = i*4+3;
2210 Cvar_RegisterVariable(&r_drawparticles);
2211 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2212 Cvar_RegisterVariable(&r_drawdecals);
2213 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2214 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2217 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2219 int surfacelistindex;
2221 float *v3f, *t2f, *c4f;
2222 particletexture_t *tex;
2223 float right[3], up[3], size, ca;
2224 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2225 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2227 RSurf_ActiveWorldEntity();
2229 r_refdef.stats.drawndecals += numsurfaces;
2230 R_Mesh_ResetTextureState();
2231 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2232 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2233 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2234 GL_DepthMask(false);
2235 GL_DepthRange(0, 1);
2236 GL_PolygonOffset(0, 0);
2238 GL_CullFace(GL_NONE);
2240 // generate all the vertices at once
2241 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2243 d = cl.decals + surfacelist[surfacelistindex];
2246 c4f = particle_color4f + 16*surfacelistindex;
2247 ca = d->alpha * alphascale;
2248 if (r_refdef.fogenabled)
2249 ca *= RSurf_FogVertex(d->org);
2250 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2251 Vector4Copy(c4f, c4f + 4);
2252 Vector4Copy(c4f, c4f + 8);
2253 Vector4Copy(c4f, c4f + 12);
2255 // calculate vertex positions
2256 size = d->size * cl_particles_size.value;
2257 VectorVectors(d->normal, right, up);
2258 VectorScale(right, size, right);
2259 VectorScale(up, size, up);
2260 v3f = particle_vertex3f + 12*surfacelistindex;
2261 v3f[ 0] = d->org[0] - right[0] - up[0];
2262 v3f[ 1] = d->org[1] - right[1] - up[1];
2263 v3f[ 2] = d->org[2] - right[2] - up[2];
2264 v3f[ 3] = d->org[0] - right[0] + up[0];
2265 v3f[ 4] = d->org[1] - right[1] + up[1];
2266 v3f[ 5] = d->org[2] - right[2] + up[2];
2267 v3f[ 6] = d->org[0] + right[0] + up[0];
2268 v3f[ 7] = d->org[1] + right[1] + up[1];
2269 v3f[ 8] = d->org[2] + right[2] + up[2];
2270 v3f[ 9] = d->org[0] + right[0] - up[0];
2271 v3f[10] = d->org[1] + right[1] - up[1];
2272 v3f[11] = d->org[2] + right[2] - up[2];
2274 // calculate texcoords
2275 tex = &particletexture[d->texnum];
2276 t2f = particle_texcoord2f + 8*surfacelistindex;
2277 t2f[0] = tex->s1;t2f[1] = tex->t2;
2278 t2f[2] = tex->s1;t2f[3] = tex->t1;
2279 t2f[4] = tex->s2;t2f[5] = tex->t1;
2280 t2f[6] = tex->s2;t2f[7] = tex->t2;
2283 // now render the decals all at once
2284 // (this assumes they all use one particle font texture!)
2285 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2286 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2287 GL_LockArrays(0, numsurfaces*4);
2288 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2289 GL_LockArrays(0, 0);
2292 void R_DrawDecals (void)
2295 int drawdecals = r_drawdecals.integer;
2300 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2302 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2303 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2305 // LordHavoc: early out conditions
2309 decalfade = frametime * 256 / cl_decals_fadetime.value;
2310 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2311 drawdist2 = drawdist2*drawdist2;
2313 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2315 if (!decal->typeindex)
2318 if (killsequence - decal->decalsequence > 0)
2321 if (cl.time > decal->time2 + cl_decals_time.value)
2323 decal->alpha -= decalfade;
2324 if (decal->alpha <= 0)
2330 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2332 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2333 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2339 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2345 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))
2346 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2349 decal->typeindex = 0;
2350 if (cl.free_decal > i)
2354 // reduce cl.num_decals if possible
2355 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2358 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2360 decal_t *olddecals = cl.decals;
2361 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2362 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2363 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2364 Mem_Free(olddecals);
2367 r_refdef.stats.totaldecals = cl.num_decals;
2370 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2372 int surfacelistindex;
2373 int batchstart, batchcount;
2374 const particle_t *p;
2376 rtexture_t *texture;
2377 float *v3f, *t2f, *c4f;
2378 particletexture_t *tex;
2379 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2380 float ambient[3], diffuse[3], diffusenormal[3];
2381 vec4_t colormultiplier;
2383 RSurf_ActiveWorldEntity();
2385 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));
2387 r_refdef.stats.particles += numsurfaces;
2388 R_Mesh_ResetTextureState();
2389 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2390 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2391 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2392 GL_DepthMask(false);
2393 GL_DepthRange(0, 1);
2394 GL_PolygonOffset(0, 0);
2396 GL_CullFace(GL_NONE);
2398 // first generate all the vertices at once
2399 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2401 p = cl.particles + surfacelist[surfacelistindex];
2403 blendmode = p->blendmode;
2407 case PBLEND_INVALID:
2409 c4f[0] = p->color[0] * (1.0f / 256.0f);
2410 c4f[1] = p->color[1] * (1.0f / 256.0f);
2411 c4f[2] = p->color[2] * (1.0f / 256.0f);
2412 c4f[3] = p->alpha * colormultiplier[3];
2413 // additive and modulate can just fade out in fog (this is correct)
2414 if (r_refdef.fogenabled)
2415 c4f[3] *= RSurf_FogVertex(p->org);
2416 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2423 c4f[0] = p->color[0] * colormultiplier[0];
2424 c4f[1] = p->color[1] * colormultiplier[1];
2425 c4f[2] = p->color[2] * colormultiplier[2];
2426 c4f[3] = p->alpha * colormultiplier[3];
2427 // additive and modulate can just fade out in fog (this is correct)
2428 if (r_refdef.fogenabled)
2429 c4f[3] *= RSurf_FogVertex(p->org);
2430 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2437 c4f[0] = p->color[0] * colormultiplier[0];
2438 c4f[1] = p->color[1] * colormultiplier[1];
2439 c4f[2] = p->color[2] * colormultiplier[2];
2440 c4f[3] = p->alpha * colormultiplier[3];
2441 // note: lighting is not cheap!
2442 if (particletype[p->typeindex].lighting)
2444 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2445 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2446 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2447 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2449 // mix in the fog color
2450 if (r_refdef.fogenabled)
2452 fog = RSurf_FogVertex(p->org);
2454 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2455 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2456 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2460 // copy the color into the other three vertices
2461 Vector4Copy(c4f, c4f + 4);
2462 Vector4Copy(c4f, c4f + 8);
2463 Vector4Copy(c4f, c4f + 12);
2465 size = p->size * cl_particles_size.value;
2466 tex = &particletexture[p->texnum];
2467 switch(p->orientation)
2469 case PARTICLE_INVALID:
2470 case PARTICLE_BILLBOARD:
2471 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2472 VectorScale(r_refdef.view.up, size, up);
2473 v3f[ 0] = p->org[0] - right[0] - up[0];
2474 v3f[ 1] = p->org[1] - right[1] - up[1];
2475 v3f[ 2] = p->org[2] - right[2] - up[2];
2476 v3f[ 3] = p->org[0] - right[0] + up[0];
2477 v3f[ 4] = p->org[1] - right[1] + up[1];
2478 v3f[ 5] = p->org[2] - right[2] + up[2];
2479 v3f[ 6] = p->org[0] + right[0] + up[0];
2480 v3f[ 7] = p->org[1] + right[1] + up[1];
2481 v3f[ 8] = p->org[2] + right[2] + up[2];
2482 v3f[ 9] = p->org[0] + right[0] - up[0];
2483 v3f[10] = p->org[1] + right[1] - up[1];
2484 v3f[11] = p->org[2] + right[2] - up[2];
2485 t2f[0] = tex->s1;t2f[1] = tex->t2;
2486 t2f[2] = tex->s1;t2f[3] = tex->t1;
2487 t2f[4] = tex->s2;t2f[5] = tex->t1;
2488 t2f[6] = tex->s2;t2f[7] = tex->t2;
2490 case PARTICLE_ORIENTED_DOUBLESIDED:
2491 VectorVectors(p->vel, right, up);
2492 VectorScale(right, size * p->stretch, right);
2493 VectorScale(up, size, up);
2494 v3f[ 0] = p->org[0] - right[0] - up[0];
2495 v3f[ 1] = p->org[1] - right[1] - up[1];
2496 v3f[ 2] = p->org[2] - right[2] - up[2];
2497 v3f[ 3] = p->org[0] - right[0] + up[0];
2498 v3f[ 4] = p->org[1] - right[1] + up[1];
2499 v3f[ 5] = p->org[2] - right[2] + up[2];
2500 v3f[ 6] = p->org[0] + right[0] + up[0];
2501 v3f[ 7] = p->org[1] + right[1] + up[1];
2502 v3f[ 8] = p->org[2] + right[2] + up[2];
2503 v3f[ 9] = p->org[0] + right[0] - up[0];
2504 v3f[10] = p->org[1] + right[1] - up[1];
2505 v3f[11] = p->org[2] + right[2] - up[2];
2506 t2f[0] = tex->s1;t2f[1] = tex->t2;
2507 t2f[2] = tex->s1;t2f[3] = tex->t1;
2508 t2f[4] = tex->s2;t2f[5] = tex->t1;
2509 t2f[6] = tex->s2;t2f[7] = tex->t2;
2511 case PARTICLE_SPARK:
2512 len = VectorLength(p->vel);
2513 VectorNormalize2(p->vel, up);
2514 lenfactor = p->stretch * 0.04 * len;
2515 if(lenfactor < size * 0.5)
2516 lenfactor = size * 0.5;
2517 VectorMA(p->org, -lenfactor, up, v);
2518 VectorMA(p->org, lenfactor, up, up2);
2519 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2520 t2f[0] = tex->s1;t2f[1] = tex->t2;
2521 t2f[2] = tex->s1;t2f[3] = tex->t1;
2522 t2f[4] = tex->s2;t2f[5] = tex->t1;
2523 t2f[6] = tex->s2;t2f[7] = tex->t2;
2525 case PARTICLE_VBEAM:
2526 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2527 VectorSubtract(p->vel, p->org, up);
2528 VectorNormalize(up);
2529 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2530 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2531 t2f[0] = tex->s2;t2f[1] = v[0];
2532 t2f[2] = tex->s1;t2f[3] = v[0];
2533 t2f[4] = tex->s1;t2f[5] = v[1];
2534 t2f[6] = tex->s2;t2f[7] = v[1];
2536 case PARTICLE_HBEAM:
2537 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2538 VectorSubtract(p->vel, p->org, up);
2539 VectorNormalize(up);
2540 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2541 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2542 t2f[0] = v[0];t2f[1] = tex->t1;
2543 t2f[2] = v[0];t2f[3] = tex->t2;
2544 t2f[4] = v[1];t2f[5] = tex->t2;
2545 t2f[6] = v[1];t2f[7] = tex->t1;
2550 // now render batches of particles based on blendmode and texture
2551 blendmode = PBLEND_INVALID;
2553 GL_LockArrays(0, numsurfaces*4);
2556 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2558 p = cl.particles + surfacelist[surfacelistindex];
2560 if (blendmode != p->blendmode)
2562 blendmode = p->blendmode;
2566 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2568 case PBLEND_INVALID:
2570 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2573 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2577 if (texture != particletexture[p->texnum].texture)
2579 texture = particletexture[p->texnum].texture;
2580 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2583 // iterate until we find a change in settings
2584 batchstart = surfacelistindex++;
2585 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2587 p = cl.particles + surfacelist[surfacelistindex];
2588 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2592 batchcount = surfacelistindex - batchstart;
2593 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2595 GL_LockArrays(0, 0);
2598 void R_DrawParticles (void)
2601 int drawparticles = r_drawparticles.integer;
2602 float minparticledist;
2604 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2610 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2611 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2613 // LordHavoc: early out conditions
2614 if (!cl.num_particles)
2617 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2618 gravity = frametime * cl.movevars_gravity;
2619 dvel = 1+4*frametime;
2620 decalfade = frametime * 255 / cl_decals_fadetime.value;
2621 update = frametime > 0;
2622 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2623 drawdist2 = drawdist2*drawdist2;
2625 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2629 if (cl.free_particle > i)
2630 cl.free_particle = i;
2636 if (p->delayedspawn > cl.time)
2638 p->delayedspawn = 0;
2642 p->size += p->sizeincrease * frametime;
2643 p->alpha -= p->alphafade * frametime;
2645 if (p->alpha <= 0 || p->die <= cl.time)
2648 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2650 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2652 if (p->typeindex == pt_blood)
2653 p->size += frametime * 8;
2655 p->vel[2] -= p->gravity * gravity;
2656 f = 1.0f - min(p->liquidfriction * frametime, 1);
2657 VectorScale(p->vel, f, p->vel);
2661 p->vel[2] -= p->gravity * gravity;
2664 f = 1.0f - min(p->airfriction * frametime, 1);
2665 VectorScale(p->vel, f, p->vel);
2669 VectorCopy(p->org, oldorg);
2670 VectorMA(p->org, frametime, p->vel, p->org);
2671 if (p->bounce && cl.time >= p->delayedcollisions)
2673 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);
2674 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2675 // or if the trace hit something flagged as NOIMPACT
2676 // then remove the particle
2677 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2679 VectorCopy(trace.endpos, p->org);
2680 // react if the particle hit something
2681 if (trace.fraction < 1)
2683 VectorCopy(trace.endpos, p->org);
2685 if (p->staintexnum >= 0)
2687 // blood - splash on solid
2688 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2691 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2692 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2693 if (cl_decals.integer)
2695 // create a decal for the blood splat
2696 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!
2701 if (p->typeindex == pt_blood)
2703 // blood - splash on solid
2704 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2706 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2708 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)));
2709 if (cl_decals.integer)
2711 // create a decal for the blood splat
2712 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);
2717 else if (p->bounce < 0)
2719 // bounce -1 means remove on impact
2724 // anything else - bounce off solid
2725 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2726 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2727 if (DotProduct(p->vel, p->vel) < 0.03)
2728 VectorClear(p->vel);
2734 if (p->typeindex != pt_static)
2736 switch (p->typeindex)
2738 case pt_entityparticle:
2739 // particle that removes itself after one rendered frame
2746 a = CL_PointSuperContents(p->org);
2747 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2751 a = CL_PointSuperContents(p->org);
2752 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2756 a = CL_PointSuperContents(p->org);
2757 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2761 if (cl.time > p->time2)
2764 p->time2 = cl.time + (rand() & 3) * 0.1;
2765 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2766 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2768 a = CL_PointSuperContents(p->org);
2769 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2777 else if (p->delayedspawn)
2781 // don't render particles too close to the view (they chew fillrate)
2782 // also don't render particles behind the view (useless)
2783 // further checks to cull to the frustum would be too slow here
2784 switch(p->typeindex)
2787 // beams have no culling
2788 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2791 if(cl_particles_visculling.integer)
2792 if (!r_refdef.viewcache.world_novis)
2793 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2795 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2797 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2800 // anything else just has to be in front of the viewer and visible at this distance
2801 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2802 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2809 if (cl.free_particle > i)
2810 cl.free_particle = i;
2813 // reduce cl.num_particles if possible
2814 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2817 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2819 particle_t *oldparticles = cl.particles;
2820 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2821 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2822 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2823 Mem_Free(oldparticles);