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)
1311 qboolean found = false;
1312 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1314 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1315 return; // no such effect
1317 VectorLerp(originmins, 0.5, originmaxs, center);
1318 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1320 int effectinfoindex;
1323 particleeffectinfo_t *info;
1325 vec3_t centervelocity;
1331 qboolean underwater;
1332 qboolean immediatebloodstain;
1334 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1335 VectorLerp(originmins, 0.5, originmaxs, center);
1336 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1337 supercontents = CL_PointSuperContents(center);
1338 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1339 VectorSubtract(originmaxs, originmins, traildir);
1340 traillen = VectorLength(traildir);
1341 VectorNormalize(traildir);
1342 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1344 if (info->effectnameindex == effectnameindex)
1347 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1349 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1352 // spawn a dlight if requested
1353 if (info->lightradiusstart > 0 && spawndlight)
1355 matrix4x4_t tempmatrix;
1356 if (info->trailspacing > 0)
1357 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1359 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1360 if (info->lighttime > 0 && info->lightradiusfade > 0)
1362 // light flash (explosion, etc)
1363 // called when effect starts
1364 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);
1369 // called by CL_LinkNetworkEntity
1370 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1371 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);
1372 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1376 if (!spawnparticles)
1381 if (info->tex[1] > info->tex[0])
1383 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1384 tex = min(tex, info->tex[1] - 1);
1386 if(info->staintex[0] < 0)
1387 staintex = info->staintex[0];
1390 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1391 staintex = min(staintex, info->staintex[1] - 1);
1393 if (info->particletype == pt_decal)
1394 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]);
1395 else if (info->orientation == PARTICLE_HBEAM)
1396 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);
1399 if (!cl_particles.integer)
1401 switch (info->particletype)
1403 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1404 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1405 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1406 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1407 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1408 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1411 VectorCopy(originmins, trailpos);
1412 if (info->trailspacing > 0)
1414 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1415 trailstep = info->trailspacing / cl_particles_quality.value;
1416 immediatebloodstain = false;
1420 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1422 immediatebloodstain = info->particletype == pt_blood || staintex;
1424 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1425 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1427 if (info->tex[1] > info->tex[0])
1429 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1430 tex = min(tex, info->tex[1] - 1);
1434 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1435 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1436 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1439 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);
1440 if (immediatebloodstain && part)
1442 immediatebloodstain = false;
1443 CL_ImmediateBloodStain(part);
1446 VectorMA(trailpos, trailstep, traildir, trailpos);
1453 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1456 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)
1458 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1466 void CL_EntityParticles (const entity_t *ent)
1469 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1470 static vec3_t avelocities[NUMVERTEXNORMALS];
1471 if (!cl_particles.integer) return;
1472 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1474 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1476 if (!avelocities[0][0])
1477 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1478 avelocities[0][i] = lhrandom(0, 2.55);
1480 for (i = 0;i < NUMVERTEXNORMALS;i++)
1482 yaw = cl.time * avelocities[i][0];
1483 pitch = cl.time * avelocities[i][1];
1484 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1485 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1486 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1487 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);
1492 void CL_ReadPointFile_f (void)
1494 vec3_t org, leakorg;
1496 char *pointfile = NULL, *pointfilepos, *t, tchar;
1497 char name[MAX_OSPATH];
1502 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1503 strlcat (name, ".pts", sizeof (name));
1504 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1507 Con_Printf("Could not open %s\n", name);
1511 Con_Printf("Reading %s...\n", name);
1512 VectorClear(leakorg);
1515 pointfilepos = pointfile;
1516 while (*pointfilepos)
1518 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1523 while (*t && *t != '\n' && *t != '\r')
1527 #if _MSC_VER >= 1400
1528 #define sscanf sscanf_s
1530 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1536 VectorCopy(org, leakorg);
1539 if (cl.num_particles < cl.max_particles - 3)
1542 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);
1545 Mem_Free(pointfile);
1546 VectorCopy(leakorg, org);
1547 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1549 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);
1550 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);
1551 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);
1556 CL_ParseParticleEffect
1558 Parse an effect out of the server message
1561 void CL_ParseParticleEffect (void)
1564 int i, count, msgcount, color;
1566 MSG_ReadVector(org, cls.protocol);
1567 for (i=0 ; i<3 ; i++)
1568 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1569 msgcount = MSG_ReadByte ();
1570 color = MSG_ReadByte ();
1572 if (msgcount == 255)
1577 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1582 CL_ParticleExplosion
1586 void CL_ParticleExplosion (const vec3_t org)
1592 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1593 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1595 if (cl_particles_quake.integer)
1597 for (i = 0;i < 1024;i++)
1603 color = particlepalette[ramp1[r]];
1604 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);
1608 color = particlepalette[ramp2[r]];
1609 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);
1615 i = CL_PointSuperContents(org);
1616 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1618 if (cl_particles.integer && cl_particles_bubbles.integer)
1619 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1620 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);
1624 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1626 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1630 for (k = 0;k < 16;k++)
1633 VectorMA(org, 128, v2, v);
1634 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1635 if (trace.fraction >= 0.1)
1638 VectorSubtract(trace.endpos, org, v2);
1639 VectorScale(v2, 2.0f, v2);
1640 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);
1646 if (cl_particles_explosions_shell.integer)
1647 R_NewExplosion(org);
1652 CL_ParticleExplosion2
1656 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1659 if (!cl_particles.integer) return;
1661 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1663 k = particlepalette[colorStart + (i % colorLength)];
1664 if (cl_particles_quake.integer)
1665 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);
1667 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);
1671 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1673 if (cl_particles_sparks.integer)
1675 sparkcount *= cl_particles_quality.value;
1676 while(sparkcount-- > 0)
1677 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);
1681 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1683 if (cl_particles_smoke.integer)
1685 smokecount *= cl_particles_quality.value;
1686 while(smokecount-- > 0)
1687 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);
1691 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)
1694 if (!cl_particles.integer) return;
1696 count = (int)(count * cl_particles_quality.value);
1699 k = particlepalette[colorbase + (rand()&3)];
1700 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);
1704 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1707 float minz, maxz, lifetime = 30;
1708 if (!cl_particles.integer) return;
1709 if (dir[2] < 0) // falling
1711 minz = maxs[2] + dir[2] * 0.1;
1714 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1719 maxz = maxs[2] + dir[2] * 0.1;
1721 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1724 count = (int)(count * cl_particles_quality.value);
1729 if (!cl_particles_rain.integer) break;
1730 count *= 4; // ick, this should be in the mod or maps?
1734 k = particlepalette[colorbase + (rand()&3)];
1735 if (gamemode == GAME_GOODVSBAD2)
1736 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);
1738 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);
1742 if (!cl_particles_snow.integer) break;
1745 k = particlepalette[colorbase + (rand()&3)];
1746 if (gamemode == GAME_GOODVSBAD2)
1747 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);
1749 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);
1753 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1757 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1758 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1759 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1760 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1762 #define PARTICLETEXTURESIZE 64
1763 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1765 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1769 dz = 1 - (dx*dx+dy*dy);
1770 if (dz > 0) // it does hit the sphere
1774 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1775 VectorNormalize(normal);
1776 dot = DotProduct(normal, light);
1777 if (dot > 0.5) // interior reflection
1778 f += ((dot * 2) - 1);
1779 else if (dot < -0.5) // exterior reflection
1780 f += ((dot * -2) - 1);
1782 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1783 VectorNormalize(normal);
1784 dot = DotProduct(normal, light);
1785 if (dot > 0.5) // interior reflection
1786 f += ((dot * 2) - 1);
1787 else if (dot < -0.5) // exterior reflection
1788 f += ((dot * -2) - 1);
1790 f += 16; // just to give it a haze so you can see the outline
1791 f = bound(0, f, 255);
1792 return (unsigned char) f;
1798 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1799 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1801 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1802 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1803 *width = particlefontcellwidth;
1804 *height = particlefontcellheight;
1807 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1809 int basex, basey, w, h, y;
1810 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1811 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1812 Sys_Error("invalid particle texture size for autogenerating");
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1814 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1817 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1820 float cx, cy, dx, dy, f, iradius;
1822 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1823 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1824 iradius = 1.0f / radius;
1825 alpha *= (1.0f / 255.0f);
1826 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1828 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1832 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1837 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1838 d[0] += (int)(f * (blue - d[0]));
1839 d[1] += (int)(f * (green - d[1]));
1840 d[2] += (int)(f * (red - d[2]));
1846 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1849 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1851 data[0] = bound(minb, data[0], maxb);
1852 data[1] = bound(ming, data[1], maxg);
1853 data[2] = bound(minr, data[2], maxr);
1857 void particletextureinvert(unsigned char *data)
1860 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1862 data[0] = 255 - data[0];
1863 data[1] = 255 - data[1];
1864 data[2] = 255 - data[2];
1868 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1869 static void R_InitBloodTextures (unsigned char *particletexturedata)
1872 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1873 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1876 for (i = 0;i < 8;i++)
1878 memset(data, 255, datasize);
1879 for (k = 0;k < 24;k++)
1880 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1881 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1882 particletextureinvert(data);
1883 setuptex(tex_bloodparticle[i], data, particletexturedata);
1887 for (i = 0;i < 8;i++)
1889 memset(data, 255, datasize);
1891 for (j = 1;j < 10;j++)
1892 for (k = min(j, m - 1);k < m;k++)
1893 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1894 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1895 particletextureinvert(data);
1896 setuptex(tex_blooddecal[i], data, particletexturedata);
1902 //uncomment this to make engine save out particle font to a tga file when run
1903 //#define DUMPPARTICLEFONT
1905 static void R_InitParticleTexture (void)
1907 int x, y, d, i, k, m;
1908 int basex, basey, w, h;
1912 fs_offset_t filesize;
1914 // a note: decals need to modulate (multiply) the background color to
1915 // properly darken it (stain), and they need to be able to alpha fade,
1916 // this is a very difficult challenge because it means fading to white
1917 // (no change to background) rather than black (darkening everything
1918 // behind the whole decal polygon), and to accomplish this the texture is
1919 // inverted (dark red blood on white background becomes brilliant cyan
1920 // and white on black background) so we can alpha fade it to black, then
1921 // we invert it again during the blendfunc to make it work...
1923 #ifndef DUMPPARTICLEFONT
1924 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1927 particlefonttexture = decalskinframe->base;
1928 // TODO maybe allow custom grid size?
1929 particlefontwidth = image_width;
1930 particlefontheight = image_height;
1931 particlefontcellwidth = image_width / 8;
1932 particlefontcellheight = image_height / 8;
1933 particlefontcols = 8;
1934 particlefontrows = 8;
1939 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1940 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1941 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1942 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1943 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1945 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1946 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1947 particlefontcols = 8;
1948 particlefontrows = 8;
1950 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1953 for (i = 0;i < 8;i++)
1955 memset(data, 255, datasize);
1958 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1959 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1961 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1963 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1964 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1966 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1967 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1969 d = (int)(d * (1-(dx*dx+dy*dy)));
1970 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1971 d = bound(0, d, 255);
1972 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1979 setuptex(tex_smoke[i], data, particletexturedata);
1983 memset(data, 255, datasize);
1984 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1986 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1987 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1989 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1990 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1991 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1994 setuptex(tex_rainsplash, data, particletexturedata);
1997 memset(data, 255, datasize);
1998 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2000 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2001 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2003 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2004 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2005 d = bound(0, d, 255);
2006 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2009 setuptex(tex_particle, data, particletexturedata);
2012 memset(data, 255, datasize);
2013 light[0] = 1;light[1] = 1;light[2] = 1;
2014 VectorNormalize(light);
2015 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2017 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2018 // stretch upper half of bubble by +50% and shrink lower half by -50%
2019 // (this gives an elongated teardrop shape)
2021 dy = (dy - 0.5f) * 2.0f;
2023 dy = (dy - 0.5f) / 1.5f;
2024 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2026 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2027 // shrink bubble width to half
2029 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2032 setuptex(tex_raindrop, data, particletexturedata);
2035 memset(data, 255, datasize);
2036 light[0] = 1;light[1] = 1;light[2] = 1;
2037 VectorNormalize(light);
2038 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2040 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2043 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2044 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2047 setuptex(tex_bubble, data, particletexturedata);
2049 // Blood particles and blood decals
2050 R_InitBloodTextures (particletexturedata);
2053 for (i = 0;i < 8;i++)
2055 memset(data, 255, datasize);
2056 for (k = 0;k < 12;k++)
2057 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2058 for (k = 0;k < 3;k++)
2059 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2060 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2061 particletextureinvert(data);
2062 setuptex(tex_bulletdecal[i], data, particletexturedata);
2065 #ifdef DUMPPARTICLEFONT
2066 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2069 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2070 particlefonttexture = decalskinframe->base;
2072 Mem_Free(particletexturedata);
2077 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2079 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2080 particletexture[i].texture = particlefonttexture;
2081 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2082 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2083 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2084 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2087 #ifndef DUMPPARTICLEFONT
2088 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true);
2089 if (!particletexture[tex_beam].texture)
2092 unsigned char noise3[64][64], data2[64][16][4];
2094 fractalnoise(&noise3[0][0], 64, 4);
2096 for (y = 0;y < 64;y++)
2098 dy = (y - 0.5f*64) / (64*0.5f-1);
2099 for (x = 0;x < 16;x++)
2101 dx = (x - 0.5f*16) / (16*0.5f-2);
2102 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2103 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2104 data2[y][x][3] = 255;
2108 #ifdef DUMPPARTICLEFONT
2109 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2111 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2113 particletexture[tex_beam].s1 = 0;
2114 particletexture[tex_beam].t1 = 0;
2115 particletexture[tex_beam].s2 = 1;
2116 particletexture[tex_beam].t2 = 1;
2118 // now load an texcoord/texture override file
2119 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2126 if(!COM_ParseToken_Simple(&bufptr, true, false))
2128 if(!strcmp(com_token, "\n"))
2129 continue; // empty line
2130 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2131 particletexture[i].texture = particlefonttexture;
2133 if (!COM_ParseToken_Simple(&bufptr, true, false))
2135 if (!strcmp(com_token, "\n"))
2137 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2140 particletexture[i].s1 = atof(com_token);
2142 if (!COM_ParseToken_Simple(&bufptr, true, false))
2144 if (!strcmp(com_token, "\n"))
2146 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2149 particletexture[i].t1 = atof(com_token);
2151 if (!COM_ParseToken_Simple(&bufptr, true, false))
2153 if (!strcmp(com_token, "\n"))
2155 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2158 particletexture[i].s2 = atof(com_token);
2160 if (!COM_ParseToken_Simple(&bufptr, true, false))
2162 if (!strcmp(com_token, "\n"))
2164 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2167 particletexture[i].t2 = atof(com_token);
2173 static void r_part_start(void)
2176 // generate particlepalette for convenience from the main one
2177 for (i = 0;i < 256;i++)
2178 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2179 particletexturepool = R_AllocTexturePool();
2180 R_InitParticleTexture ();
2181 CL_Particles_LoadEffectInfo();
2184 static void r_part_shutdown(void)
2186 R_FreeTexturePool(&particletexturepool);
2189 static void r_part_newmap(void)
2192 R_SkinFrame_MarkUsed(decalskinframe);
2193 CL_Particles_LoadEffectInfo();
2196 #define BATCHSIZE 256
2197 unsigned short particle_elements[BATCHSIZE*6];
2198 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2200 void R_Particles_Init (void)
2203 for (i = 0;i < BATCHSIZE;i++)
2205 particle_elements[i*6+0] = i*4+0;
2206 particle_elements[i*6+1] = i*4+1;
2207 particle_elements[i*6+2] = i*4+2;
2208 particle_elements[i*6+3] = i*4+0;
2209 particle_elements[i*6+4] = i*4+2;
2210 particle_elements[i*6+5] = i*4+3;
2213 Cvar_RegisterVariable(&r_drawparticles);
2214 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2215 Cvar_RegisterVariable(&r_drawdecals);
2216 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2217 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2220 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2222 int surfacelistindex;
2224 float *v3f, *t2f, *c4f;
2225 particletexture_t *tex;
2226 float right[3], up[3], size, ca;
2227 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2228 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2230 RSurf_ActiveWorldEntity();
2232 r_refdef.stats.drawndecals += numsurfaces;
2233 R_Mesh_ResetTextureState();
2234 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2235 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2236 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2237 GL_DepthMask(false);
2238 GL_DepthRange(0, 1);
2239 GL_PolygonOffset(0, 0);
2241 GL_CullFace(GL_NONE);
2243 // generate all the vertices at once
2244 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2246 d = cl.decals + surfacelist[surfacelistindex];
2249 c4f = particle_color4f + 16*surfacelistindex;
2250 ca = d->alpha * alphascale;
2251 if (r_refdef.fogenabled)
2252 ca *= RSurf_FogVertex(d->org);
2253 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2254 Vector4Copy(c4f, c4f + 4);
2255 Vector4Copy(c4f, c4f + 8);
2256 Vector4Copy(c4f, c4f + 12);
2258 // calculate vertex positions
2259 size = d->size * cl_particles_size.value;
2260 VectorVectors(d->normal, right, up);
2261 VectorScale(right, size, right);
2262 VectorScale(up, size, up);
2263 v3f = particle_vertex3f + 12*surfacelistindex;
2264 v3f[ 0] = d->org[0] - right[0] - up[0];
2265 v3f[ 1] = d->org[1] - right[1] - up[1];
2266 v3f[ 2] = d->org[2] - right[2] - up[2];
2267 v3f[ 3] = d->org[0] - right[0] + up[0];
2268 v3f[ 4] = d->org[1] - right[1] + up[1];
2269 v3f[ 5] = d->org[2] - right[2] + up[2];
2270 v3f[ 6] = d->org[0] + right[0] + up[0];
2271 v3f[ 7] = d->org[1] + right[1] + up[1];
2272 v3f[ 8] = d->org[2] + right[2] + up[2];
2273 v3f[ 9] = d->org[0] + right[0] - up[0];
2274 v3f[10] = d->org[1] + right[1] - up[1];
2275 v3f[11] = d->org[2] + right[2] - up[2];
2277 // calculate texcoords
2278 tex = &particletexture[d->texnum];
2279 t2f = particle_texcoord2f + 8*surfacelistindex;
2280 t2f[0] = tex->s1;t2f[1] = tex->t2;
2281 t2f[2] = tex->s1;t2f[3] = tex->t1;
2282 t2f[4] = tex->s2;t2f[5] = tex->t1;
2283 t2f[6] = tex->s2;t2f[7] = tex->t2;
2286 // now render the decals all at once
2287 // (this assumes they all use one particle font texture!)
2288 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2289 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2290 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2293 void R_DrawDecals (void)
2296 int drawdecals = r_drawdecals.integer;
2301 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2303 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2304 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2306 // LordHavoc: early out conditions
2310 decalfade = frametime * 256 / cl_decals_fadetime.value;
2311 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2312 drawdist2 = drawdist2*drawdist2;
2314 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2316 if (!decal->typeindex)
2319 if (killsequence - decal->decalsequence > 0)
2322 if (cl.time > decal->time2 + cl_decals_time.value)
2324 decal->alpha -= decalfade;
2325 if (decal->alpha <= 0)
2331 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2333 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2334 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2340 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2346 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))
2347 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2350 decal->typeindex = 0;
2351 if (cl.free_decal > i)
2355 // reduce cl.num_decals if possible
2356 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2359 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2361 decal_t *olddecals = cl.decals;
2362 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2363 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2364 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2365 Mem_Free(olddecals);
2368 r_refdef.stats.totaldecals = cl.num_decals;
2371 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2373 int surfacelistindex;
2374 int batchstart, batchcount;
2375 const particle_t *p;
2377 rtexture_t *texture;
2378 float *v3f, *t2f, *c4f;
2379 particletexture_t *tex;
2380 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2381 float ambient[3], diffuse[3], diffusenormal[3];
2382 vec4_t colormultiplier;
2384 RSurf_ActiveWorldEntity();
2386 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));
2388 r_refdef.stats.particles += numsurfaces;
2389 R_Mesh_ResetTextureState();
2390 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2391 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2392 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2393 GL_DepthMask(false);
2394 GL_DepthRange(0, 1);
2395 GL_PolygonOffset(0, 0);
2397 GL_CullFace(GL_NONE);
2399 // first generate all the vertices at once
2400 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2402 p = cl.particles + surfacelist[surfacelistindex];
2404 blendmode = p->blendmode;
2408 case PBLEND_INVALID:
2410 c4f[0] = p->color[0] * (1.0f / 256.0f);
2411 c4f[1] = p->color[1] * (1.0f / 256.0f);
2412 c4f[2] = p->color[2] * (1.0f / 256.0f);
2413 c4f[3] = p->alpha * colormultiplier[3];
2414 // additive and modulate can just fade out in fog (this is correct)
2415 if (r_refdef.fogenabled)
2416 c4f[3] *= RSurf_FogVertex(p->org);
2417 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2424 c4f[0] = p->color[0] * colormultiplier[0];
2425 c4f[1] = p->color[1] * colormultiplier[1];
2426 c4f[2] = p->color[2] * colormultiplier[2];
2427 c4f[3] = p->alpha * colormultiplier[3];
2428 // additive and modulate can just fade out in fog (this is correct)
2429 if (r_refdef.fogenabled)
2430 c4f[3] *= RSurf_FogVertex(p->org);
2431 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2438 c4f[0] = p->color[0] * colormultiplier[0];
2439 c4f[1] = p->color[1] * colormultiplier[1];
2440 c4f[2] = p->color[2] * colormultiplier[2];
2441 c4f[3] = p->alpha * colormultiplier[3];
2442 // note: lighting is not cheap!
2443 if (particletype[p->typeindex].lighting)
2445 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2446 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2447 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2448 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2450 // mix in the fog color
2451 if (r_refdef.fogenabled)
2453 fog = RSurf_FogVertex(p->org);
2455 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2456 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2457 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2461 // copy the color into the other three vertices
2462 Vector4Copy(c4f, c4f + 4);
2463 Vector4Copy(c4f, c4f + 8);
2464 Vector4Copy(c4f, c4f + 12);
2466 size = p->size * cl_particles_size.value;
2467 tex = &particletexture[p->texnum];
2468 switch(p->orientation)
2470 case PARTICLE_INVALID:
2471 case PARTICLE_BILLBOARD:
2472 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2473 VectorScale(r_refdef.view.up, size, up);
2474 v3f[ 0] = p->org[0] - right[0] - up[0];
2475 v3f[ 1] = p->org[1] - right[1] - up[1];
2476 v3f[ 2] = p->org[2] - right[2] - up[2];
2477 v3f[ 3] = p->org[0] - right[0] + up[0];
2478 v3f[ 4] = p->org[1] - right[1] + up[1];
2479 v3f[ 5] = p->org[2] - right[2] + up[2];
2480 v3f[ 6] = p->org[0] + right[0] + up[0];
2481 v3f[ 7] = p->org[1] + right[1] + up[1];
2482 v3f[ 8] = p->org[2] + right[2] + up[2];
2483 v3f[ 9] = p->org[0] + right[0] - up[0];
2484 v3f[10] = p->org[1] + right[1] - up[1];
2485 v3f[11] = p->org[2] + right[2] - up[2];
2486 t2f[0] = tex->s1;t2f[1] = tex->t2;
2487 t2f[2] = tex->s1;t2f[3] = tex->t1;
2488 t2f[4] = tex->s2;t2f[5] = tex->t1;
2489 t2f[6] = tex->s2;t2f[7] = tex->t2;
2491 case PARTICLE_ORIENTED_DOUBLESIDED:
2492 VectorVectors(p->vel, right, up);
2493 VectorScale(right, size * p->stretch, right);
2494 VectorScale(up, size, up);
2495 v3f[ 0] = p->org[0] - right[0] - up[0];
2496 v3f[ 1] = p->org[1] - right[1] - up[1];
2497 v3f[ 2] = p->org[2] - right[2] - up[2];
2498 v3f[ 3] = p->org[0] - right[0] + up[0];
2499 v3f[ 4] = p->org[1] - right[1] + up[1];
2500 v3f[ 5] = p->org[2] - right[2] + up[2];
2501 v3f[ 6] = p->org[0] + right[0] + up[0];
2502 v3f[ 7] = p->org[1] + right[1] + up[1];
2503 v3f[ 8] = p->org[2] + right[2] + up[2];
2504 v3f[ 9] = p->org[0] + right[0] - up[0];
2505 v3f[10] = p->org[1] + right[1] - up[1];
2506 v3f[11] = p->org[2] + right[2] - up[2];
2507 t2f[0] = tex->s1;t2f[1] = tex->t2;
2508 t2f[2] = tex->s1;t2f[3] = tex->t1;
2509 t2f[4] = tex->s2;t2f[5] = tex->t1;
2510 t2f[6] = tex->s2;t2f[7] = tex->t2;
2512 case PARTICLE_SPARK:
2513 len = VectorLength(p->vel);
2514 VectorNormalize2(p->vel, up);
2515 lenfactor = p->stretch * 0.04 * len;
2516 if(lenfactor < size * 0.5)
2517 lenfactor = size * 0.5;
2518 VectorMA(p->org, -lenfactor, up, v);
2519 VectorMA(p->org, lenfactor, up, up2);
2520 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2521 t2f[0] = tex->s1;t2f[1] = tex->t2;
2522 t2f[2] = tex->s1;t2f[3] = tex->t1;
2523 t2f[4] = tex->s2;t2f[5] = tex->t1;
2524 t2f[6] = tex->s2;t2f[7] = tex->t2;
2526 case PARTICLE_VBEAM:
2527 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2528 VectorSubtract(p->vel, p->org, up);
2529 VectorNormalize(up);
2530 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2531 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2532 t2f[0] = tex->s2;t2f[1] = v[0];
2533 t2f[2] = tex->s1;t2f[3] = v[0];
2534 t2f[4] = tex->s1;t2f[5] = v[1];
2535 t2f[6] = tex->s2;t2f[7] = v[1];
2537 case PARTICLE_HBEAM:
2538 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2539 VectorSubtract(p->vel, p->org, up);
2540 VectorNormalize(up);
2541 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2542 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2543 t2f[0] = v[0];t2f[1] = tex->t1;
2544 t2f[2] = v[0];t2f[3] = tex->t2;
2545 t2f[4] = v[1];t2f[5] = tex->t2;
2546 t2f[6] = v[1];t2f[7] = tex->t1;
2551 // now render batches of particles based on blendmode and texture
2552 blendmode = PBLEND_INVALID;
2556 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2558 p = cl.particles + surfacelist[surfacelistindex];
2560 if (blendmode != p->blendmode)
2562 blendmode = p->blendmode;
2566 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2568 case PBLEND_INVALID:
2570 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2573 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2577 if (texture != particletexture[p->texnum].texture)
2579 texture = particletexture[p->texnum].texture;
2580 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2583 // iterate until we find a change in settings
2584 batchstart = surfacelistindex++;
2585 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2587 p = cl.particles + surfacelist[surfacelistindex];
2588 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2592 batchcount = surfacelistindex - batchstart;
2593 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2597 void R_DrawParticles (void)
2600 int drawparticles = r_drawparticles.integer;
2601 float minparticledist;
2603 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2609 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2610 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2612 // LordHavoc: early out conditions
2613 if (!cl.num_particles)
2616 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2617 gravity = frametime * cl.movevars_gravity;
2618 dvel = 1+4*frametime;
2619 decalfade = frametime * 255 / cl_decals_fadetime.value;
2620 update = frametime > 0;
2621 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2622 drawdist2 = drawdist2*drawdist2;
2624 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2628 if (cl.free_particle > i)
2629 cl.free_particle = i;
2635 if (p->delayedspawn > cl.time)
2637 p->delayedspawn = 0;
2641 p->size += p->sizeincrease * frametime;
2642 p->alpha -= p->alphafade * frametime;
2644 if (p->alpha <= 0 || p->die <= cl.time)
2647 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2649 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2651 if (p->typeindex == pt_blood)
2652 p->size += frametime * 8;
2654 p->vel[2] -= p->gravity * gravity;
2655 f = 1.0f - min(p->liquidfriction * frametime, 1);
2656 VectorScale(p->vel, f, p->vel);
2660 p->vel[2] -= p->gravity * gravity;
2663 f = 1.0f - min(p->airfriction * frametime, 1);
2664 VectorScale(p->vel, f, p->vel);
2668 VectorCopy(p->org, oldorg);
2669 VectorMA(p->org, frametime, p->vel, p->org);
2670 if (p->bounce && cl.time >= p->delayedcollisions)
2672 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);
2673 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2674 // or if the trace hit something flagged as NOIMPACT
2675 // then remove the particle
2676 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2678 VectorCopy(trace.endpos, p->org);
2679 // react if the particle hit something
2680 if (trace.fraction < 1)
2682 VectorCopy(trace.endpos, p->org);
2684 if (p->staintexnum >= 0)
2686 // blood - splash on solid
2687 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2690 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2691 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2692 if (cl_decals.integer)
2694 // create a decal for the blood splat
2695 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!
2700 if (p->typeindex == pt_blood)
2702 // blood - splash on solid
2703 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2705 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2707 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)));
2708 if (cl_decals.integer)
2710 // create a decal for the blood splat
2711 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);
2716 else if (p->bounce < 0)
2718 // bounce -1 means remove on impact
2723 // anything else - bounce off solid
2724 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2725 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2726 if (DotProduct(p->vel, p->vel) < 0.03)
2727 VectorClear(p->vel);
2733 if (p->typeindex != pt_static)
2735 switch (p->typeindex)
2737 case pt_entityparticle:
2738 // particle that removes itself after one rendered frame
2745 a = CL_PointSuperContents(p->org);
2746 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2750 a = CL_PointSuperContents(p->org);
2751 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2755 a = CL_PointSuperContents(p->org);
2756 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2760 if (cl.time > p->time2)
2763 p->time2 = cl.time + (rand() & 3) * 0.1;
2764 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2765 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2767 a = CL_PointSuperContents(p->org);
2768 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2776 else if (p->delayedspawn)
2780 // don't render particles too close to the view (they chew fillrate)
2781 // also don't render particles behind the view (useless)
2782 // further checks to cull to the frustum would be too slow here
2783 switch(p->typeindex)
2786 // beams have no culling
2787 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2790 if(cl_particles_visculling.integer)
2791 if (!r_refdef.viewcache.world_novis)
2792 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2794 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2796 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2799 // anything else just has to be in front of the viewer and visible at this distance
2800 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2801 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2808 if (cl.free_particle > i)
2809 cl.free_particle = i;
2812 // reduce cl.num_particles if possible
2813 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2816 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2818 particle_t *oldparticles = cl.particles;
2819 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2820 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2821 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2822 Mem_Free(oldparticles);