2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
118 particleeffectinfo_t;
120 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
123 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
125 static int particlepalette[256];
127 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
128 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
129 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
130 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
131 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
132 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
133 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
134 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
135 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
136 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
137 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
138 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
139 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
140 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
141 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
142 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
143 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
144 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
145 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
146 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
147 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
148 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
149 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
150 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
151 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
152 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
153 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
154 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
155 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
156 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
157 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
158 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
161 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
162 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
163 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
165 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
167 // particletexture_t is a rectangle in the particlefonttexture
168 typedef struct particletexture_s
171 float s1, t1, s2, t2;
175 static rtexturepool_t *particletexturepool;
176 static rtexture_t *particlefonttexture;
177 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
178 skinframe_t *decalskinframe;
180 // texture numbers in particle font
181 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
182 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
183 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
184 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
185 static const int tex_rainsplash = 32;
186 static const int tex_particle = 63;
187 static const int tex_bubble = 62;
188 static const int tex_raindrop = 61;
189 static const int tex_beam = 60;
191 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
192 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
193 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
194 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
195 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
196 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
197 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
198 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
199 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
200 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
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_decal_alpha);
497 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
498 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
499 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
500 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
501 Cvar_RegisterVariable (&cl_particles_explosions_shell);
502 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
503 Cvar_RegisterVariable (&cl_particles_rain);
504 Cvar_RegisterVariable (&cl_particles_snow);
505 Cvar_RegisterVariable (&cl_particles_smoke);
506 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
507 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
508 Cvar_RegisterVariable (&cl_particles_sparks);
509 Cvar_RegisterVariable (&cl_particles_bubbles);
510 Cvar_RegisterVariable (&cl_particles_visculling);
511 Cvar_RegisterVariable (&cl_decals);
512 Cvar_RegisterVariable (&cl_decals_visculling);
513 Cvar_RegisterVariable (&cl_decals_time);
514 Cvar_RegisterVariable (&cl_decals_fadetime);
515 Cvar_RegisterVariable (&cl_decals_newsystem);
516 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
517 Cvar_RegisterVariable (&cl_decals_models);
518 Cvar_RegisterVariable (&cl_decals_bias);
519 Cvar_RegisterVariable (&cl_decals_max);
522 void CL_Particles_Shutdown (void)
526 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
527 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
529 // list of all 26 parameters:
530 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
531 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
532 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
533 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
534 // palpha - opacity of particle as 0-255 (can be more than 255)
535 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
536 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
537 // pgravity - how much effect gravity has on the particle (0-1)
538 // 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
539 // px,py,pz - starting origin of particle
540 // pvx,pvy,pvz - starting velocity of particle
541 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
542 // blendmode - one of the PBLEND_ values
543 // orientation - one of the PARTICLE_ values
544 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
545 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
546 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)
551 if (!cl_particles.integer)
553 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
554 if (cl.free_particle >= cl.max_particles)
557 lifetime = palpha / min(1, palphafade);
558 part = &cl.particles[cl.free_particle++];
559 if (cl.num_particles < cl.free_particle)
560 cl.num_particles = cl.free_particle;
561 memset(part, 0, sizeof(*part));
562 part->typeindex = ptypeindex;
563 part->blendmode = blendmode;
564 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
566 particletexture_t *tex = &particletexture[ptex];
567 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
568 part->orientation = PARTICLE_VBEAM;
570 part->orientation = PARTICLE_HBEAM;
573 part->orientation = orientation;
574 l2 = (int)lhrandom(0.5, 256.5);
576 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
577 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
578 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
579 part->staintexnum = staintex;
580 if(staincolor1 >= 0 && staincolor2 >= 0)
582 l2 = (int)lhrandom(0.5, 256.5);
584 if(blendmode == PBLEND_INVMOD)
586 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
587 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
588 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
592 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
593 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
594 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
596 if(r > 0xFF) r = 0xFF;
597 if(g > 0xFF) g = 0xFF;
598 if(b > 0xFF) b = 0xFF;
602 r = part->color[0]; // -1 is shorthand for stain = particle color
606 part->staincolor = r * 65536 + g * 256 + b;
609 part->sizeincrease = psizeincrease;
610 part->alpha = palpha;
611 part->alphafade = palphafade;
612 part->gravity = pgravity;
613 part->bounce = pbounce;
614 part->stretch = stretch;
616 part->org[0] = px + originjitter * v[0];
617 part->org[1] = py + originjitter * v[1];
618 part->org[2] = pz + originjitter * v[2];
619 part->vel[0] = pvx + velocityjitter * v[0];
620 part->vel[1] = pvy + velocityjitter * v[1];
621 part->vel[2] = pvz + velocityjitter * v[2];
623 part->airfriction = pairfriction;
624 part->liquidfriction = pliquidfriction;
625 part->die = cl.time + lifetime;
626 part->delayedcollisions = 0;
627 part->qualityreduction = pqualityreduction;
628 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
629 if (part->typeindex == pt_rain)
633 float lifetime = part->die - cl.time;
636 // turn raindrop into simple spark and create delayedspawn splash effect
637 part->typeindex = pt_spark;
639 VectorMA(part->org, lifetime, part->vel, endvec);
640 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
641 part->die = cl.time + lifetime * trace.fraction;
642 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);
645 part2->delayedspawn = part->die;
646 part2->die += part->die - cl.time;
647 for (i = rand() & 7;i < 10;i++)
649 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);
652 part2->delayedspawn = part->die;
653 part2->die += part->die - cl.time;
658 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
660 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
663 VectorMA(part->org, lifetime, part->vel, endvec);
664 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
665 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
671 static void CL_ImmediateBloodStain(particle_t *part)
676 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
677 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
679 VectorCopy(part->vel, v);
681 staintex = part->staintexnum;
682 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);
685 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
686 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
688 VectorCopy(part->vel, v);
690 staintex = tex_blooddecal[rand()&7];
691 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);
695 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
699 entity_render_t *ent = &cl.entities[hitent].render;
700 unsigned char color[3];
701 if (!cl_decals.integer)
703 if (!ent->allowdecals)
706 l2 = (int)lhrandom(0.5, 256.5);
708 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
709 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
710 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
712 if (cl_decals_newsystem.integer)
714 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);
718 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
719 if (cl.free_decal >= cl.max_decals)
721 decal = &cl.decals[cl.free_decal++];
722 if (cl.num_decals < cl.free_decal)
723 cl.num_decals = cl.free_decal;
724 memset(decal, 0, sizeof(*decal));
725 decal->decalsequence = cl.decalsequence++;
726 decal->typeindex = pt_decal;
727 decal->texnum = texnum;
728 VectorMA(org, cl_decals_bias.value, normal, decal->org);
729 VectorCopy(normal, decal->normal);
731 decal->alpha = alpha;
732 decal->time2 = cl.time;
733 decal->color[0] = color[0];
734 decal->color[1] = color[1];
735 decal->color[2] = color[2];
736 decal->owner = hitent;
737 decal->clusterindex = -1000; // no vis culling unless we're sure
740 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
741 decal->ownermodel = cl.entities[decal->owner].render.model;
742 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
743 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
747 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
749 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
751 decal->clusterindex = leaf->clusterindex;
756 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
759 float bestfrac, bestorg[3], bestnormal[3];
761 int besthitent = 0, hitent;
764 for (i = 0;i < 32;i++)
767 VectorMA(org, maxdist, org2, org2);
768 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
769 // take the closest trace result that doesn't end up hitting a NOMARKS
770 // surface (sky for example)
771 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
773 bestfrac = trace.fraction;
775 VectorCopy(trace.endpos, bestorg);
776 VectorCopy(trace.plane.normal, bestnormal);
780 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
783 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
784 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
785 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)
788 matrix4x4_t tempmatrix;
790 VectorLerp(originmins, 0.5, originmaxs, center);
791 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
792 if (effectnameindex == EFFECT_SVC_PARTICLE)
794 if (cl_particles.integer)
796 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
798 CL_ParticleExplosion(center);
799 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
800 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
803 count *= cl_particles_quality.value;
804 for (;count > 0;count--)
806 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
807 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);
812 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
813 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
814 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
815 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
816 else if (effectnameindex == EFFECT_TE_SPIKE)
818 if (cl_particles_bulletimpacts.integer)
820 if (cl_particles_quake.integer)
822 if (cl_particles_smoke.integer)
823 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
827 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
828 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
829 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);
833 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
834 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
836 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
838 if (cl_particles_bulletimpacts.integer)
840 if (cl_particles_quake.integer)
842 if (cl_particles_smoke.integer)
843 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
847 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
848 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
849 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);
853 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
854 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
855 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);
857 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
859 if (cl_particles_bulletimpacts.integer)
861 if (cl_particles_quake.integer)
863 if (cl_particles_smoke.integer)
864 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
868 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
869 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
870 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);
874 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
875 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
877 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
879 if (cl_particles_bulletimpacts.integer)
881 if (cl_particles_quake.integer)
883 if (cl_particles_smoke.integer)
884 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
888 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
889 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
890 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);
894 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
895 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
896 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);
898 else if (effectnameindex == EFFECT_TE_BLOOD)
900 if (!cl_particles_blood.integer)
902 if (cl_particles_quake.integer)
903 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
906 static double bloodaccumulator = 0;
907 qboolean immediatebloodstain = true;
908 //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);
909 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
910 for (;bloodaccumulator > 0;bloodaccumulator--)
912 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);
913 if (immediatebloodstain && part)
915 immediatebloodstain = false;
916 CL_ImmediateBloodStain(part);
921 else if (effectnameindex == EFFECT_TE_SPARK)
922 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
923 else if (effectnameindex == EFFECT_TE_PLASMABURN)
925 // plasma scorch mark
926 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
927 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
928 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
930 else if (effectnameindex == EFFECT_TE_GUNSHOT)
932 if (cl_particles_bulletimpacts.integer)
934 if (cl_particles_quake.integer)
935 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
938 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
939 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
940 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);
944 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
945 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
947 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
949 if (cl_particles_bulletimpacts.integer)
951 if (cl_particles_quake.integer)
952 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
955 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
956 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
957 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);
961 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
962 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
963 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);
965 else if (effectnameindex == EFFECT_TE_EXPLOSION)
967 CL_ParticleExplosion(center);
968 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);
970 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
972 CL_ParticleExplosion(center);
973 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);
975 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
977 if (cl_particles_quake.integer)
980 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
983 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);
985 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);
989 CL_ParticleExplosion(center);
990 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);
992 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
993 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);
994 else if (effectnameindex == EFFECT_TE_FLAMEJET)
996 count *= cl_particles_quality.value;
998 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);
1000 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1002 float i, j, inc, vel;
1005 inc = 8 / cl_particles_quality.value;
1006 for (i = -128;i < 128;i += inc)
1008 for (j = -128;j < 128;j += inc)
1010 dir[0] = j + lhrandom(0, inc);
1011 dir[1] = i + lhrandom(0, inc);
1013 org[0] = center[0] + dir[0];
1014 org[1] = center[1] + dir[1];
1015 org[2] = center[2] + lhrandom(0, 64);
1016 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1017 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);
1021 else if (effectnameindex == EFFECT_TE_TELEPORT)
1023 float i, j, k, inc, vel;
1026 if (cl_particles_quake.integer)
1027 inc = 4 / cl_particles_quality.value;
1029 inc = 8 / cl_particles_quality.value;
1030 for (i = -16;i < 16;i += inc)
1032 for (j = -16;j < 16;j += inc)
1034 for (k = -24;k < 32;k += inc)
1036 VectorSet(dir, i*8, j*8, k*8);
1037 VectorNormalize(dir);
1038 vel = lhrandom(50, 113);
1039 if (cl_particles_quake.integer)
1040 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);
1042 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);
1046 if (!cl_particles_quake.integer)
1047 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);
1048 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);
1050 else if (effectnameindex == EFFECT_TE_TEI_G3)
1051 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);
1052 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1054 if (cl_particles_smoke.integer)
1056 count *= 0.25f * cl_particles_quality.value;
1058 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);
1061 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1063 CL_ParticleExplosion(center);
1064 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);
1066 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1069 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1070 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1071 if (cl_particles_smoke.integer)
1072 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1073 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);
1074 if (cl_particles_sparks.integer)
1075 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1076 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);
1077 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);
1079 else if (effectnameindex == EFFECT_EF_FLAME)
1081 count *= 300 * cl_particles_quality.value;
1083 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);
1084 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);
1086 else if (effectnameindex == EFFECT_EF_STARDUST)
1088 count *= 200 * cl_particles_quality.value;
1090 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);
1091 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);
1093 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1097 int smoke, blood, bubbles, r, color;
1099 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1102 Vector4Set(light, 0, 0, 0, 0);
1104 if (effectnameindex == EFFECT_TR_ROCKET)
1105 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1106 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1108 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1109 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1111 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1113 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1114 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1118 matrix4x4_t tempmatrix;
1119 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1120 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);
1121 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1125 if (!spawnparticles)
1128 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1131 VectorSubtract(originmaxs, originmins, dir);
1132 len = VectorNormalizeLength(dir);
1135 dec = -ent->persistent.trail_time;
1136 ent->persistent.trail_time += len;
1137 if (ent->persistent.trail_time < 0.01f)
1140 // if we skip out, leave it reset
1141 ent->persistent.trail_time = 0.0f;
1146 // advance into this frame to reach the first puff location
1147 VectorMA(originmins, dec, dir, pos);
1150 smoke = cl_particles.integer && cl_particles_smoke.integer;
1151 blood = cl_particles.integer && cl_particles_blood.integer;
1152 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1153 qd = 1.0f / cl_particles_quality.value;
1160 if (effectnameindex == EFFECT_TR_BLOOD)
1162 if (cl_particles_quake.integer)
1164 color = particlepalette[67 + (rand()&3)];
1165 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);
1170 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);
1173 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1175 if (cl_particles_quake.integer)
1178 color = particlepalette[67 + (rand()&3)];
1179 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);
1184 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);
1190 if (effectnameindex == EFFECT_TR_ROCKET)
1192 if (cl_particles_quake.integer)
1195 color = particlepalette[ramp3[r]];
1196 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);
1200 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);
1201 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);
1204 else if (effectnameindex == EFFECT_TR_GRENADE)
1206 if (cl_particles_quake.integer)
1209 color = particlepalette[ramp3[r]];
1210 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);
1214 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);
1217 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1219 if (cl_particles_quake.integer)
1222 color = particlepalette[52 + (rand()&7)];
1223 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);
1224 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);
1226 else if (gamemode == GAME_GOODVSBAD2)
1229 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);
1233 color = particlepalette[20 + (rand()&7)];
1234 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);
1237 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1239 if (cl_particles_quake.integer)
1242 color = particlepalette[230 + (rand()&7)];
1243 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);
1244 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);
1248 color = particlepalette[226 + (rand()&7)];
1249 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);
1252 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1254 if (cl_particles_quake.integer)
1256 color = particlepalette[152 + (rand()&3)];
1257 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);
1259 else if (gamemode == GAME_GOODVSBAD2)
1262 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);
1264 else if (gamemode == GAME_PRYDON)
1267 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);
1270 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);
1272 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1275 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);
1277 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1280 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);
1282 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1283 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);
1287 if (effectnameindex == EFFECT_TR_ROCKET)
1288 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 else if (effectnameindex == EFFECT_TR_GRENADE)
1290 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);
1292 // advance to next time and position
1295 VectorMA (pos, dec, dir, pos);
1298 ent->persistent.trail_time = len;
1301 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1304 // this is also called on point effects with spawndlight = true and
1305 // spawnparticles = true
1306 // it is called CL_ParticleTrail because most code does not want to supply
1307 // these parameters, only trail handling does
1308 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)
1310 qboolean found = false;
1311 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1313 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1314 return; // no such effect
1316 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1318 int effectinfoindex;
1321 particleeffectinfo_t *info;
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 supercontents = CL_PointSuperContents(center);
1334 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1335 VectorSubtract(originmaxs, originmins, traildir);
1336 traillen = VectorLength(traildir);
1337 VectorNormalize(traildir);
1338 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1340 if (info->effectnameindex == effectnameindex)
1343 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1345 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1348 // spawn a dlight if requested
1349 if (info->lightradiusstart > 0 && spawndlight)
1351 matrix4x4_t tempmatrix;
1352 if (info->trailspacing > 0)
1353 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1355 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1356 if (info->lighttime > 0 && info->lightradiusfade > 0)
1358 // light flash (explosion, etc)
1359 // called when effect starts
1360 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);
1365 // called by CL_LinkNetworkEntity
1366 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1367 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);
1368 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1372 if (!spawnparticles)
1377 if (info->tex[1] > info->tex[0])
1379 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1380 tex = min(tex, info->tex[1] - 1);
1382 if(info->staintex[0] < 0)
1383 staintex = info->staintex[0];
1386 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1387 staintex = min(staintex, info->staintex[1] - 1);
1389 if (info->particletype == pt_decal)
1390 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]);
1391 else if (info->orientation == PARTICLE_HBEAM)
1392 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);
1395 if (!cl_particles.integer)
1397 switch (info->particletype)
1399 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1400 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1401 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1402 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1403 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1404 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1407 VectorCopy(originmins, trailpos);
1408 if (info->trailspacing > 0)
1410 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1411 trailstep = info->trailspacing / cl_particles_quality.value;
1412 immediatebloodstain = false;
1416 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1418 immediatebloodstain = info->particletype == pt_blood || staintex;
1420 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1421 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1423 if (info->tex[1] > info->tex[0])
1425 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1426 tex = min(tex, info->tex[1] - 1);
1430 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1431 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1432 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1435 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);
1436 if (immediatebloodstain && part)
1438 immediatebloodstain = false;
1439 CL_ImmediateBloodStain(part);
1442 VectorMA(trailpos, trailstep, traildir, trailpos);
1449 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1452 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)
1454 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1462 void CL_EntityParticles (const entity_t *ent)
1465 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1466 static vec3_t avelocities[NUMVERTEXNORMALS];
1467 if (!cl_particles.integer) return;
1468 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1470 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1472 if (!avelocities[0][0])
1473 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1474 avelocities[0][i] = lhrandom(0, 2.55);
1476 for (i = 0;i < NUMVERTEXNORMALS;i++)
1478 yaw = cl.time * avelocities[i][0];
1479 pitch = cl.time * avelocities[i][1];
1480 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1481 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1482 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1483 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);
1488 void CL_ReadPointFile_f (void)
1490 vec3_t org, leakorg;
1492 char *pointfile = NULL, *pointfilepos, *t, tchar;
1493 char name[MAX_OSPATH];
1498 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1499 strlcat (name, ".pts", sizeof (name));
1500 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1503 Con_Printf("Could not open %s\n", name);
1507 Con_Printf("Reading %s...\n", name);
1508 VectorClear(leakorg);
1511 pointfilepos = pointfile;
1512 while (*pointfilepos)
1514 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1519 while (*t && *t != '\n' && *t != '\r')
1523 #if _MSC_VER >= 1400
1524 #define sscanf sscanf_s
1526 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1532 VectorCopy(org, leakorg);
1535 if (cl.num_particles < cl.max_particles - 3)
1538 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);
1541 Mem_Free(pointfile);
1542 VectorCopy(leakorg, org);
1543 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1545 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);
1546 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);
1547 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);
1552 CL_ParseParticleEffect
1554 Parse an effect out of the server message
1557 void CL_ParseParticleEffect (void)
1560 int i, count, msgcount, color;
1562 MSG_ReadVector(org, cls.protocol);
1563 for (i=0 ; i<3 ; i++)
1564 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1565 msgcount = MSG_ReadByte ();
1566 color = MSG_ReadByte ();
1568 if (msgcount == 255)
1573 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1578 CL_ParticleExplosion
1582 void CL_ParticleExplosion (const vec3_t org)
1588 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1589 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1591 if (cl_particles_quake.integer)
1593 for (i = 0;i < 1024;i++)
1599 color = particlepalette[ramp1[r]];
1600 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);
1604 color = particlepalette[ramp2[r]];
1605 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);
1611 i = CL_PointSuperContents(org);
1612 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1614 if (cl_particles.integer && cl_particles_bubbles.integer)
1615 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1616 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);
1620 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1622 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1626 for (k = 0;k < 16;k++)
1629 VectorMA(org, 128, v2, v);
1630 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1631 if (trace.fraction >= 0.1)
1634 VectorSubtract(trace.endpos, org, v2);
1635 VectorScale(v2, 2.0f, v2);
1636 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);
1642 if (cl_particles_explosions_shell.integer)
1643 R_NewExplosion(org);
1648 CL_ParticleExplosion2
1652 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1655 if (!cl_particles.integer) return;
1657 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1659 k = particlepalette[colorStart + (i % colorLength)];
1660 if (cl_particles_quake.integer)
1661 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);
1663 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);
1667 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1669 if (cl_particles_sparks.integer)
1671 sparkcount *= cl_particles_quality.value;
1672 while(sparkcount-- > 0)
1673 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);
1677 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1679 if (cl_particles_smoke.integer)
1681 smokecount *= cl_particles_quality.value;
1682 while(smokecount-- > 0)
1683 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);
1687 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)
1690 if (!cl_particles.integer) return;
1692 count = (int)(count * cl_particles_quality.value);
1695 k = particlepalette[colorbase + (rand()&3)];
1696 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);
1700 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1703 float minz, maxz, lifetime = 30;
1704 if (!cl_particles.integer) return;
1705 if (dir[2] < 0) // falling
1707 minz = maxs[2] + dir[2] * 0.1;
1710 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1715 maxz = maxs[2] + dir[2] * 0.1;
1717 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1720 count = (int)(count * cl_particles_quality.value);
1725 if (!cl_particles_rain.integer) break;
1726 count *= 4; // ick, this should be in the mod or maps?
1730 k = particlepalette[colorbase + (rand()&3)];
1731 if (gamemode == GAME_GOODVSBAD2)
1732 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);
1734 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);
1738 if (!cl_particles_snow.integer) break;
1741 k = particlepalette[colorbase + (rand()&3)];
1742 if (gamemode == GAME_GOODVSBAD2)
1743 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);
1745 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);
1749 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1753 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1754 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1755 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1756 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1758 #define PARTICLETEXTURESIZE 64
1759 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1761 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1765 dz = 1 - (dx*dx+dy*dy);
1766 if (dz > 0) // it does hit the sphere
1770 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1771 VectorNormalize(normal);
1772 dot = DotProduct(normal, light);
1773 if (dot > 0.5) // interior reflection
1774 f += ((dot * 2) - 1);
1775 else if (dot < -0.5) // exterior reflection
1776 f += ((dot * -2) - 1);
1778 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1779 VectorNormalize(normal);
1780 dot = DotProduct(normal, light);
1781 if (dot > 0.5) // interior reflection
1782 f += ((dot * 2) - 1);
1783 else if (dot < -0.5) // exterior reflection
1784 f += ((dot * -2) - 1);
1786 f += 16; // just to give it a haze so you can see the outline
1787 f = bound(0, f, 255);
1788 return (unsigned char) f;
1794 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1795 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1797 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1798 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1799 *width = particlefontcellwidth;
1800 *height = particlefontcellheight;
1803 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1805 int basex, basey, w, h, y;
1806 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1807 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1808 Sys_Error("invalid particle texture size for autogenerating");
1809 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1810 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1813 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1816 float cx, cy, dx, dy, f, iradius;
1818 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1819 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1820 iradius = 1.0f / radius;
1821 alpha *= (1.0f / 255.0f);
1822 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1824 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1828 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1833 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1834 d[0] += (int)(f * (blue - d[0]));
1835 d[1] += (int)(f * (green - d[1]));
1836 d[2] += (int)(f * (red - d[2]));
1842 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1845 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1847 data[0] = bound(minb, data[0], maxb);
1848 data[1] = bound(ming, data[1], maxg);
1849 data[2] = bound(minr, data[2], maxr);
1853 void particletextureinvert(unsigned char *data)
1856 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1858 data[0] = 255 - data[0];
1859 data[1] = 255 - data[1];
1860 data[2] = 255 - data[2];
1864 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1865 static void R_InitBloodTextures (unsigned char *particletexturedata)
1868 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1869 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1872 for (i = 0;i < 8;i++)
1874 memset(data, 255, datasize);
1875 for (k = 0;k < 24;k++)
1876 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1877 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1878 particletextureinvert(data);
1879 setuptex(tex_bloodparticle[i], data, particletexturedata);
1883 for (i = 0;i < 8;i++)
1885 memset(data, 255, datasize);
1887 for (j = 1;j < 10;j++)
1888 for (k = min(j, m - 1);k < m;k++)
1889 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1890 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1891 particletextureinvert(data);
1892 setuptex(tex_blooddecal[i], data, particletexturedata);
1898 //uncomment this to make engine save out particle font to a tga file when run
1899 //#define DUMPPARTICLEFONT
1901 static void R_InitParticleTexture (void)
1903 int x, y, d, i, k, m;
1904 int basex, basey, w, h;
1908 fs_offset_t filesize;
1910 // a note: decals need to modulate (multiply) the background color to
1911 // properly darken it (stain), and they need to be able to alpha fade,
1912 // this is a very difficult challenge because it means fading to white
1913 // (no change to background) rather than black (darkening everything
1914 // behind the whole decal polygon), and to accomplish this the texture is
1915 // inverted (dark red blood on white background becomes brilliant cyan
1916 // and white on black background) so we can alpha fade it to black, then
1917 // we invert it again during the blendfunc to make it work...
1919 #ifndef DUMPPARTICLEFONT
1920 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1923 particlefonttexture = decalskinframe->base;
1924 // TODO maybe allow custom grid size?
1925 particlefontwidth = image_width;
1926 particlefontheight = image_height;
1927 particlefontcellwidth = image_width / 8;
1928 particlefontcellheight = image_height / 8;
1929 particlefontcols = 8;
1930 particlefontrows = 8;
1935 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1936 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1937 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1938 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1939 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1941 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1942 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1943 particlefontcols = 8;
1944 particlefontrows = 8;
1946 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1949 for (i = 0;i < 8;i++)
1951 memset(data, 255, datasize);
1954 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1955 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1957 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1959 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1960 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1962 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1963 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1965 d = (int)(d * (1-(dx*dx+dy*dy)));
1966 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1967 d = bound(0, d, 255);
1968 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1975 setuptex(tex_smoke[i], data, particletexturedata);
1979 memset(data, 255, datasize);
1980 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1982 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1983 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1985 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1986 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1987 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1990 setuptex(tex_rainsplash, data, particletexturedata);
1993 memset(data, 255, datasize);
1994 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1996 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1997 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1999 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2000 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2001 d = bound(0, d, 255);
2002 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2005 setuptex(tex_particle, data, particletexturedata);
2008 memset(data, 255, datasize);
2009 light[0] = 1;light[1] = 1;light[2] = 1;
2010 VectorNormalize(light);
2011 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2013 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2014 // stretch upper half of bubble by +50% and shrink lower half by -50%
2015 // (this gives an elongated teardrop shape)
2017 dy = (dy - 0.5f) * 2.0f;
2019 dy = (dy - 0.5f) / 1.5f;
2020 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2022 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 // shrink bubble width to half
2025 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2028 setuptex(tex_raindrop, data, particletexturedata);
2031 memset(data, 255, datasize);
2032 light[0] = 1;light[1] = 1;light[2] = 1;
2033 VectorNormalize(light);
2034 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2036 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2037 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2039 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2043 setuptex(tex_bubble, data, particletexturedata);
2045 // Blood particles and blood decals
2046 R_InitBloodTextures (particletexturedata);
2049 for (i = 0;i < 8;i++)
2051 memset(data, 255, datasize);
2052 for (k = 0;k < 12;k++)
2053 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2054 for (k = 0;k < 3;k++)
2055 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2056 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2057 particletextureinvert(data);
2058 setuptex(tex_bulletdecal[i], data, particletexturedata);
2061 #ifdef DUMPPARTICLEFONT
2062 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2065 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2066 particlefonttexture = decalskinframe->base;
2068 Mem_Free(particletexturedata);
2073 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2075 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2076 particletexture[i].texture = particlefonttexture;
2077 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2078 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2079 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2080 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2083 #ifndef DUMPPARTICLEFONT
2084 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2085 if (!particletexture[tex_beam].texture)
2088 unsigned char noise3[64][64], data2[64][16][4];
2090 fractalnoise(&noise3[0][0], 64, 4);
2092 for (y = 0;y < 64;y++)
2094 dy = (y - 0.5f*64) / (64*0.5f-1);
2095 for (x = 0;x < 16;x++)
2097 dx = (x - 0.5f*16) / (16*0.5f-2);
2098 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2099 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2100 data2[y][x][3] = 255;
2104 #ifdef DUMPPARTICLEFONT
2105 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2107 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2109 particletexture[tex_beam].s1 = 0;
2110 particletexture[tex_beam].t1 = 0;
2111 particletexture[tex_beam].s2 = 1;
2112 particletexture[tex_beam].t2 = 1;
2114 // now load an texcoord/texture override file
2115 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2122 if(!COM_ParseToken_Simple(&bufptr, true, false))
2124 if(!strcmp(com_token, "\n"))
2125 continue; // empty line
2126 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2127 particletexture[i].texture = particlefonttexture;
2129 if (!COM_ParseToken_Simple(&bufptr, true, false))
2131 if (!strcmp(com_token, "\n"))
2133 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2136 particletexture[i].s1 = atof(com_token);
2138 if (!COM_ParseToken_Simple(&bufptr, true, false))
2140 if (!strcmp(com_token, "\n"))
2142 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2145 particletexture[i].t1 = atof(com_token);
2147 if (!COM_ParseToken_Simple(&bufptr, true, false))
2149 if (!strcmp(com_token, "\n"))
2151 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2154 particletexture[i].s2 = atof(com_token);
2156 if (!COM_ParseToken_Simple(&bufptr, true, false))
2158 if (!strcmp(com_token, "\n"))
2160 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2163 particletexture[i].t2 = atof(com_token);
2169 static void r_part_start(void)
2172 // generate particlepalette for convenience from the main one
2173 for (i = 0;i < 256;i++)
2174 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2175 particletexturepool = R_AllocTexturePool();
2176 R_InitParticleTexture ();
2177 CL_Particles_LoadEffectInfo();
2180 static void r_part_shutdown(void)
2182 R_FreeTexturePool(&particletexturepool);
2185 static void r_part_newmap(void)
2188 R_SkinFrame_MarkUsed(decalskinframe);
2189 CL_Particles_LoadEffectInfo();
2192 #define BATCHSIZE 256
2193 unsigned short particle_elements[BATCHSIZE*6];
2194 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2196 void R_Particles_Init (void)
2199 for (i = 0;i < BATCHSIZE;i++)
2201 particle_elements[i*6+0] = i*4+0;
2202 particle_elements[i*6+1] = i*4+1;
2203 particle_elements[i*6+2] = i*4+2;
2204 particle_elements[i*6+3] = i*4+0;
2205 particle_elements[i*6+4] = i*4+2;
2206 particle_elements[i*6+5] = i*4+3;
2209 Cvar_RegisterVariable(&r_drawparticles);
2210 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2211 Cvar_RegisterVariable(&r_drawdecals);
2212 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2213 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2216 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2218 int surfacelistindex;
2220 float *v3f, *t2f, *c4f;
2221 particletexture_t *tex;
2222 float right[3], up[3], size, ca;
2223 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2224 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2226 RSurf_ActiveWorldEntity();
2228 r_refdef.stats.drawndecals += numsurfaces;
2229 R_Mesh_ResetTextureState();
2230 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2231 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2232 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2233 GL_DepthMask(false);
2234 GL_DepthRange(0, 1);
2235 GL_PolygonOffset(0, 0);
2237 GL_CullFace(GL_NONE);
2239 // generate all the vertices at once
2240 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2242 d = cl.decals + surfacelist[surfacelistindex];
2245 c4f = particle_color4f + 16*surfacelistindex;
2246 ca = d->alpha * alphascale;
2247 if (r_refdef.fogenabled)
2248 ca *= RSurf_FogVertex(d->org);
2249 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2250 Vector4Copy(c4f, c4f + 4);
2251 Vector4Copy(c4f, c4f + 8);
2252 Vector4Copy(c4f, c4f + 12);
2254 // calculate vertex positions
2255 size = d->size * cl_particles_size.value;
2256 VectorVectors(d->normal, right, up);
2257 VectorScale(right, size, right);
2258 VectorScale(up, size, up);
2259 v3f = particle_vertex3f + 12*surfacelistindex;
2260 v3f[ 0] = d->org[0] - right[0] - up[0];
2261 v3f[ 1] = d->org[1] - right[1] - up[1];
2262 v3f[ 2] = d->org[2] - right[2] - up[2];
2263 v3f[ 3] = d->org[0] - right[0] + up[0];
2264 v3f[ 4] = d->org[1] - right[1] + up[1];
2265 v3f[ 5] = d->org[2] - right[2] + up[2];
2266 v3f[ 6] = d->org[0] + right[0] + up[0];
2267 v3f[ 7] = d->org[1] + right[1] + up[1];
2268 v3f[ 8] = d->org[2] + right[2] + up[2];
2269 v3f[ 9] = d->org[0] + right[0] - up[0];
2270 v3f[10] = d->org[1] + right[1] - up[1];
2271 v3f[11] = d->org[2] + right[2] - up[2];
2273 // calculate texcoords
2274 tex = &particletexture[d->texnum];
2275 t2f = particle_texcoord2f + 8*surfacelistindex;
2276 t2f[0] = tex->s1;t2f[1] = tex->t2;
2277 t2f[2] = tex->s1;t2f[3] = tex->t1;
2278 t2f[4] = tex->s2;t2f[5] = tex->t1;
2279 t2f[6] = tex->s2;t2f[7] = tex->t2;
2282 // now render the decals all at once
2283 // (this assumes they all use one particle font texture!)
2284 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2285 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2286 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2289 void R_DrawDecals (void)
2292 int drawdecals = r_drawdecals.integer;
2297 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2299 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2300 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2302 // LordHavoc: early out conditions
2306 decalfade = frametime * 256 / cl_decals_fadetime.value;
2307 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2308 drawdist2 = drawdist2*drawdist2;
2310 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2312 if (!decal->typeindex)
2315 if (killsequence - decal->decalsequence > 0)
2318 if (cl.time > decal->time2 + cl_decals_time.value)
2320 decal->alpha -= decalfade;
2321 if (decal->alpha <= 0)
2327 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2329 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2330 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2336 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2342 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))
2343 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2346 decal->typeindex = 0;
2347 if (cl.free_decal > i)
2351 // reduce cl.num_decals if possible
2352 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2355 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2357 decal_t *olddecals = cl.decals;
2358 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2359 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2360 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2361 Mem_Free(olddecals);
2364 r_refdef.stats.totaldecals = cl.num_decals;
2367 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2369 int surfacelistindex;
2370 int batchstart, batchcount;
2371 const particle_t *p;
2373 rtexture_t *texture;
2374 float *v3f, *t2f, *c4f;
2375 particletexture_t *tex;
2376 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2377 float ambient[3], diffuse[3], diffusenormal[3];
2378 vec4_t colormultiplier;
2380 RSurf_ActiveWorldEntity();
2382 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));
2384 r_refdef.stats.particles += numsurfaces;
2385 R_Mesh_ResetTextureState();
2386 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2387 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2388 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2389 GL_DepthMask(false);
2390 GL_DepthRange(0, 1);
2391 GL_PolygonOffset(0, 0);
2393 GL_CullFace(GL_NONE);
2395 // first generate all the vertices at once
2396 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2398 p = cl.particles + surfacelist[surfacelistindex];
2400 blendmode = p->blendmode;
2404 case PBLEND_INVALID:
2406 c4f[0] = p->color[0] * (1.0f / 256.0f);
2407 c4f[1] = p->color[1] * (1.0f / 256.0f);
2408 c4f[2] = p->color[2] * (1.0f / 256.0f);
2409 c4f[3] = p->alpha * colormultiplier[3];
2410 // additive and modulate can just fade out in fog (this is correct)
2411 if (r_refdef.fogenabled)
2412 c4f[3] *= RSurf_FogVertex(p->org);
2413 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2420 c4f[0] = p->color[0] * colormultiplier[0];
2421 c4f[1] = p->color[1] * colormultiplier[1];
2422 c4f[2] = p->color[2] * colormultiplier[2];
2423 c4f[3] = p->alpha * colormultiplier[3];
2424 // additive and modulate can just fade out in fog (this is correct)
2425 if (r_refdef.fogenabled)
2426 c4f[3] *= RSurf_FogVertex(p->org);
2427 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2434 c4f[0] = p->color[0] * colormultiplier[0];
2435 c4f[1] = p->color[1] * colormultiplier[1];
2436 c4f[2] = p->color[2] * colormultiplier[2];
2437 c4f[3] = p->alpha * colormultiplier[3];
2438 // note: lighting is not cheap!
2439 if (particletype[p->typeindex].lighting)
2441 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2442 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2443 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2444 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2446 // mix in the fog color
2447 if (r_refdef.fogenabled)
2449 fog = RSurf_FogVertex(p->org);
2451 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2452 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2453 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2457 // copy the color into the other three vertices
2458 Vector4Copy(c4f, c4f + 4);
2459 Vector4Copy(c4f, c4f + 8);
2460 Vector4Copy(c4f, c4f + 12);
2462 size = p->size * cl_particles_size.value;
2463 tex = &particletexture[p->texnum];
2464 switch(p->orientation)
2466 case PARTICLE_INVALID:
2467 case PARTICLE_BILLBOARD:
2468 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2469 VectorScale(r_refdef.view.up, size, up);
2470 v3f[ 0] = p->org[0] - right[0] - up[0];
2471 v3f[ 1] = p->org[1] - right[1] - up[1];
2472 v3f[ 2] = p->org[2] - right[2] - up[2];
2473 v3f[ 3] = p->org[0] - right[0] + up[0];
2474 v3f[ 4] = p->org[1] - right[1] + up[1];
2475 v3f[ 5] = p->org[2] - right[2] + up[2];
2476 v3f[ 6] = p->org[0] + right[0] + up[0];
2477 v3f[ 7] = p->org[1] + right[1] + up[1];
2478 v3f[ 8] = p->org[2] + right[2] + up[2];
2479 v3f[ 9] = p->org[0] + right[0] - up[0];
2480 v3f[10] = p->org[1] + right[1] - up[1];
2481 v3f[11] = p->org[2] + right[2] - up[2];
2482 t2f[0] = tex->s1;t2f[1] = tex->t2;
2483 t2f[2] = tex->s1;t2f[3] = tex->t1;
2484 t2f[4] = tex->s2;t2f[5] = tex->t1;
2485 t2f[6] = tex->s2;t2f[7] = tex->t2;
2487 case PARTICLE_ORIENTED_DOUBLESIDED:
2488 VectorVectors(p->vel, right, up);
2489 VectorScale(right, size * p->stretch, right);
2490 VectorScale(up, size, up);
2491 v3f[ 0] = p->org[0] - right[0] - up[0];
2492 v3f[ 1] = p->org[1] - right[1] - up[1];
2493 v3f[ 2] = p->org[2] - right[2] - up[2];
2494 v3f[ 3] = p->org[0] - right[0] + up[0];
2495 v3f[ 4] = p->org[1] - right[1] + up[1];
2496 v3f[ 5] = p->org[2] - right[2] + up[2];
2497 v3f[ 6] = p->org[0] + right[0] + up[0];
2498 v3f[ 7] = p->org[1] + right[1] + up[1];
2499 v3f[ 8] = p->org[2] + right[2] + up[2];
2500 v3f[ 9] = p->org[0] + right[0] - up[0];
2501 v3f[10] = p->org[1] + right[1] - up[1];
2502 v3f[11] = p->org[2] + right[2] - up[2];
2503 t2f[0] = tex->s1;t2f[1] = tex->t2;
2504 t2f[2] = tex->s1;t2f[3] = tex->t1;
2505 t2f[4] = tex->s2;t2f[5] = tex->t1;
2506 t2f[6] = tex->s2;t2f[7] = tex->t2;
2508 case PARTICLE_SPARK:
2509 len = VectorLength(p->vel);
2510 VectorNormalize2(p->vel, up);
2511 lenfactor = p->stretch * 0.04 * len;
2512 if(lenfactor < size * 0.5)
2513 lenfactor = size * 0.5;
2514 VectorMA(p->org, -lenfactor, up, v);
2515 VectorMA(p->org, lenfactor, up, up2);
2516 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2517 t2f[0] = tex->s1;t2f[1] = tex->t2;
2518 t2f[2] = tex->s1;t2f[3] = tex->t1;
2519 t2f[4] = tex->s2;t2f[5] = tex->t1;
2520 t2f[6] = tex->s2;t2f[7] = tex->t2;
2522 case PARTICLE_VBEAM:
2523 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2524 VectorSubtract(p->vel, p->org, up);
2525 VectorNormalize(up);
2526 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2527 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2528 t2f[0] = tex->s2;t2f[1] = v[0];
2529 t2f[2] = tex->s1;t2f[3] = v[0];
2530 t2f[4] = tex->s1;t2f[5] = v[1];
2531 t2f[6] = tex->s2;t2f[7] = v[1];
2533 case PARTICLE_HBEAM:
2534 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2535 VectorSubtract(p->vel, p->org, up);
2536 VectorNormalize(up);
2537 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2538 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2539 t2f[0] = v[0];t2f[1] = tex->t1;
2540 t2f[2] = v[0];t2f[3] = tex->t2;
2541 t2f[4] = v[1];t2f[5] = tex->t2;
2542 t2f[6] = v[1];t2f[7] = tex->t1;
2547 // now render batches of particles based on blendmode and texture
2548 blendmode = PBLEND_INVALID;
2552 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2554 p = cl.particles + surfacelist[surfacelistindex];
2556 if (blendmode != p->blendmode)
2558 blendmode = p->blendmode;
2562 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2564 case PBLEND_INVALID:
2566 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2569 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2573 if (texture != particletexture[p->texnum].texture)
2575 texture = particletexture[p->texnum].texture;
2576 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2579 // iterate until we find a change in settings
2580 batchstart = surfacelistindex++;
2581 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2583 p = cl.particles + surfacelist[surfacelistindex];
2584 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2588 batchcount = surfacelistindex - batchstart;
2589 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2593 void R_DrawParticles (void)
2596 int drawparticles = r_drawparticles.integer;
2597 float minparticledist;
2599 float gravity, frametime, f, dist, oldorg[3];
2605 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2606 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2608 // LordHavoc: early out conditions
2609 if (!cl.num_particles)
2612 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2613 gravity = frametime * cl.movevars_gravity;
2614 update = frametime > 0;
2615 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2616 drawdist2 = drawdist2*drawdist2;
2618 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2622 if (cl.free_particle > i)
2623 cl.free_particle = i;
2629 if (p->delayedspawn > cl.time)
2631 p->delayedspawn = 0;
2633 p->size += p->sizeincrease * frametime;
2634 p->alpha -= p->alphafade * frametime;
2636 if (p->alpha <= 0 || p->die <= cl.time)
2639 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2641 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2643 if (p->typeindex == pt_blood)
2644 p->size += frametime * 8;
2646 p->vel[2] -= p->gravity * gravity;
2647 f = 1.0f - min(p->liquidfriction * frametime, 1);
2648 VectorScale(p->vel, f, p->vel);
2652 p->vel[2] -= p->gravity * gravity;
2655 f = 1.0f - min(p->airfriction * frametime, 1);
2656 VectorScale(p->vel, f, p->vel);
2660 VectorCopy(p->org, oldorg);
2661 VectorMA(p->org, frametime, p->vel, p->org);
2662 if (p->bounce && cl.time >= p->delayedcollisions)
2664 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);
2665 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2666 // or if the trace hit something flagged as NOIMPACT
2667 // then remove the particle
2668 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2670 VectorCopy(trace.endpos, p->org);
2671 // react if the particle hit something
2672 if (trace.fraction < 1)
2674 VectorCopy(trace.endpos, p->org);
2676 if (p->staintexnum >= 0)
2678 // blood - splash on solid
2679 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2682 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2683 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2684 if (cl_decals.integer)
2686 // create a decal for the blood splat
2687 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!
2692 if (p->typeindex == pt_blood)
2694 // blood - splash on solid
2695 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2697 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2699 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)));
2700 if (cl_decals.integer)
2702 // create a decal for the blood splat
2703 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2708 else if (p->bounce < 0)
2710 // bounce -1 means remove on impact
2715 // anything else - bounce off solid
2716 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2717 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2718 if (DotProduct(p->vel, p->vel) < 0.03)
2719 VectorClear(p->vel);
2725 if (p->typeindex != pt_static)
2727 switch (p->typeindex)
2729 case pt_entityparticle:
2730 // particle that removes itself after one rendered frame
2737 a = CL_PointSuperContents(p->org);
2738 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2742 a = CL_PointSuperContents(p->org);
2743 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2747 a = CL_PointSuperContents(p->org);
2748 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2752 if (cl.time > p->time2)
2755 p->time2 = cl.time + (rand() & 3) * 0.1;
2756 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2757 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2759 a = CL_PointSuperContents(p->org);
2760 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2768 else if (p->delayedspawn)
2772 // don't render particles too close to the view (they chew fillrate)
2773 // also don't render particles behind the view (useless)
2774 // further checks to cull to the frustum would be too slow here
2775 switch(p->typeindex)
2778 // beams have no culling
2779 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2782 if(cl_particles_visculling.integer)
2783 if (!r_refdef.viewcache.world_novis)
2784 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2786 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2788 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2791 // anything else just has to be in front of the viewer and visible at this distance
2792 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2793 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2800 if (cl.free_particle > i)
2801 cl.free_particle = i;
2804 // reduce cl.num_particles if possible
2805 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2808 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2810 particle_t *oldparticles = cl.particles;
2811 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2812 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2813 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2814 Mem_Free(oldparticles);