2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
119 particleeffectinfo_t;
121 #define MAX_PARTICLEEFFECTNAME 256
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
124 #define MAX_PARTICLEEFFECTINFO 4096
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // texture numbers in particle font
171 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
172 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
173 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
174 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
175 static const int tex_rainsplash = 32;
176 static const int tex_particle = 63;
177 static const int tex_bubble = 62;
178 static const int tex_raindrop = 61;
179 static const int tex_beam = 60;
181 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
182 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
183 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
184 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
185 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
186 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
187 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
188 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
189 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
190 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
191 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
192 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
193 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
194 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
195 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
196 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
197 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
198 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
199 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
200 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
201 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
202 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
203 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
206 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
212 particleeffectinfo_t *info = NULL;
213 const char *text = textstart;
215 effectinfoindex = -1;
216 for (linenumber = 1;;linenumber++)
219 for (arrayindex = 0;arrayindex < 16;arrayindex++)
220 argv[arrayindex][0] = 0;
223 if (!COM_ParseToken_Simple(&text, true, false))
225 if (!strcmp(com_token, "\n"))
229 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
235 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
236 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
237 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
238 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
239 #define readfloat(var) checkparms(2);var = atof(argv[1])
240 if (!strcmp(argv[0], "effect"))
245 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
247 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
250 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
252 if (particleeffectname[effectnameindex][0])
254 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
259 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
263 // if we run out of names, abort
264 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
266 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
269 info = particleeffectinfo + effectinfoindex;
270 info->effectnameindex = effectnameindex;
271 info->particletype = pt_alphastatic;
272 info->blendmode = particletype[info->particletype].blendmode;
273 info->orientation = particletype[info->particletype].orientation;
274 info->tex[0] = tex_particle;
275 info->tex[1] = tex_particle;
276 info->color[0] = 0xFFFFFF;
277 info->color[1] = 0xFFFFFF;
281 info->alpha[1] = 256;
282 info->alpha[2] = 256;
283 info->time[0] = 9999;
284 info->time[1] = 9999;
285 VectorSet(info->lightcolor, 1, 1, 1);
286 info->lightshadow = true;
287 info->lighttime = 9999;
288 info->stretchfactor = 1;
290 else if (info == NULL)
292 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
295 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
296 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
297 else if (!strcmp(argv[0], "type"))
300 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
301 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
302 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
303 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
304 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
305 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
306 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
307 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
308 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
309 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
310 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
311 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
312 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
313 info->blendmode = particletype[info->particletype].blendmode;
314 info->orientation = particletype[info->particletype].orientation;
316 else if (!strcmp(argv[0], "blend"))
319 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
320 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
321 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
322 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
324 else if (!strcmp(argv[0], "orientation"))
327 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
328 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
329 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
330 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
331 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
333 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
334 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
335 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
336 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
337 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
338 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
339 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
340 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
341 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
342 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
343 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
344 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
345 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
346 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
347 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
348 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
349 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
350 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
351 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
352 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
353 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
354 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
355 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
356 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
357 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
359 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
368 int CL_ParticleEffectIndexForName(const char *name)
371 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
372 if (!strcmp(particleeffectname[i], name))
377 const char *CL_ParticleEffectNameForIndex(int i)
379 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
381 return particleeffectname[i];
384 // MUST match effectnameindex_t in client.h
385 static const char *standardeffectnames[EFFECT_TOTAL] =
409 "TE_TEI_BIGEXPLOSION",
425 void CL_Particles_LoadEffectInfo(void)
428 unsigned char *filedata;
429 fs_offset_t filesize;
430 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
431 memset(particleeffectname, 0, sizeof(particleeffectname));
432 for (i = 0;i < EFFECT_TOTAL;i++)
433 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
434 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
437 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
447 void CL_ReadPointFile_f (void);
448 void CL_Particles_Init (void)
450 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)");
451 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
453 Cvar_RegisterVariable (&cl_particles);
454 Cvar_RegisterVariable (&cl_particles_quality);
455 Cvar_RegisterVariable (&cl_particles_alpha);
456 Cvar_RegisterVariable (&cl_particles_size);
457 Cvar_RegisterVariable (&cl_particles_quake);
458 Cvar_RegisterVariable (&cl_particles_blood);
459 Cvar_RegisterVariable (&cl_particles_blood_alpha);
460 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
461 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
462 Cvar_RegisterVariable (&cl_particles_explosions_shell);
463 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
464 Cvar_RegisterVariable (&cl_particles_rain);
465 Cvar_RegisterVariable (&cl_particles_snow);
466 Cvar_RegisterVariable (&cl_particles_smoke);
467 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
468 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
469 Cvar_RegisterVariable (&cl_particles_sparks);
470 Cvar_RegisterVariable (&cl_particles_bubbles);
471 Cvar_RegisterVariable (&cl_particles_visculling);
472 Cvar_RegisterVariable (&cl_decals);
473 Cvar_RegisterVariable (&cl_decals_visculling);
474 Cvar_RegisterVariable (&cl_decals_time);
475 Cvar_RegisterVariable (&cl_decals_fadetime);
478 void CL_Particles_Shutdown (void)
482 // list of all 26 parameters:
483 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
484 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
485 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
486 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
487 // palpha - opacity of particle as 0-255 (can be more than 255)
488 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
489 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
490 // pgravity - how much effect gravity has on the particle (0-1)
491 // 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
492 // px,py,pz - starting origin of particle
493 // pvx,pvy,pvz - starting velocity of particle
494 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
495 // blendmode - one of the PBLEND_ values
496 // orientation - one of the PARTICLE_ values
497 static 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)
502 if (!cl_particles.integer)
504 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
505 if (cl.free_particle >= cl.max_particles)
508 lifetime = palpha / min(1, palphafade);
509 part = &cl.particles[cl.free_particle++];
510 if (cl.num_particles < cl.free_particle)
511 cl.num_particles = cl.free_particle;
512 memset(part, 0, sizeof(*part));
513 part->typeindex = ptypeindex;
514 part->blendmode = blendmode;
515 part->orientation = orientation;
516 l2 = (int)lhrandom(0.5, 256.5);
518 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
519 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
520 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
523 part->sizeincrease = psizeincrease;
524 part->alpha = palpha;
525 part->alphafade = palphafade;
526 part->gravity = pgravity;
527 part->bounce = pbounce;
528 part->stretch = stretch;
530 part->org[0] = px + originjitter * v[0];
531 part->org[1] = py + originjitter * v[1];
532 part->org[2] = pz + originjitter * v[2];
533 part->vel[0] = pvx + velocityjitter * v[0];
534 part->vel[1] = pvy + velocityjitter * v[1];
535 part->vel[2] = pvz + velocityjitter * v[2];
537 part->airfriction = pairfriction;
538 part->liquidfriction = pliquidfriction;
539 part->die = cl.time + lifetime;
540 part->delayedcollisions = 0;
541 part->qualityreduction = pqualityreduction;
542 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
543 if (part->typeindex == pt_rain)
547 float lifetime = part->die - cl.time;
550 // turn raindrop into simple spark and create delayedspawn splash effect
551 part->typeindex = pt_spark;
553 VectorMA(part->org, lifetime, part->vel, endvec);
554 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
555 part->die = cl.time + lifetime * trace.fraction;
556 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);
559 part2->delayedspawn = part->die;
560 part2->die += part->die - cl.time;
561 for (i = rand() & 7;i < 10;i++)
563 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);
566 part2->delayedspawn = part->die;
567 part2->die += part->die - cl.time;
572 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
574 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
577 VectorMA(part->org, lifetime, part->vel, endvec);
578 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
579 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
584 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
588 if (!cl_decals.integer)
590 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
591 if (cl.free_decal >= cl.max_decals)
593 decal = &cl.decals[cl.free_decal++];
594 if (cl.num_decals < cl.free_decal)
595 cl.num_decals = cl.free_decal;
596 memset(decal, 0, sizeof(*decal));
597 decal->typeindex = pt_decal;
598 decal->texnum = texnum;
599 VectorAdd(org, normal, decal->org);
600 VectorCopy(normal, decal->normal);
602 decal->alpha = alpha;
603 decal->time2 = cl.time;
604 l2 = (int)lhrandom(0.5, 256.5);
606 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
607 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
608 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
609 decal->owner = hitent;
610 decal->clusterindex = -1000; // no vis culling unless we're sure
613 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
614 decal->ownermodel = cl.entities[decal->owner].render.model;
615 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
616 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
620 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
622 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
624 decal->clusterindex = leaf->clusterindex;
627 Con_Printf("hitent=%i clusterindex=%i\n", hitent, decal->clusterindex);
630 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
633 float bestfrac, bestorg[3], bestnormal[3];
635 int besthitent = 0, hitent;
638 for (i = 0;i < 32;i++)
641 VectorMA(org, maxdist, org2, org2);
642 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
643 // take the closest trace result that doesn't end up hitting a NOMARKS
644 // surface (sky for example)
645 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
647 bestfrac = trace.fraction;
649 VectorCopy(trace.endpos, bestorg);
650 VectorCopy(trace.plane.normal, bestnormal);
654 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
657 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
658 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
659 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)
662 matrix4x4_t tempmatrix;
663 VectorLerp(originmins, 0.5, originmaxs, center);
664 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
665 if (effectnameindex == EFFECT_SVC_PARTICLE)
667 if (cl_particles.integer)
669 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
671 CL_ParticleExplosion(center);
672 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
673 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
676 count *= cl_particles_quality.value;
677 for (;count > 0;count--)
679 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
680 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);
685 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
686 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
687 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
688 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
689 else if (effectnameindex == EFFECT_TE_SPIKE)
691 if (cl_particles_bulletimpacts.integer)
693 if (cl_particles_quake.integer)
695 if (cl_particles_smoke.integer)
696 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
700 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
701 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
702 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);
706 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
707 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
709 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
711 if (cl_particles_bulletimpacts.integer)
713 if (cl_particles_quake.integer)
715 if (cl_particles_smoke.integer)
716 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
720 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
721 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
722 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);
726 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
727 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
728 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);
730 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
732 if (cl_particles_bulletimpacts.integer)
734 if (cl_particles_quake.integer)
736 if (cl_particles_smoke.integer)
737 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
741 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
742 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
743 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);
747 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
748 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
750 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
752 if (cl_particles_bulletimpacts.integer)
754 if (cl_particles_quake.integer)
756 if (cl_particles_smoke.integer)
757 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
761 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
762 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
763 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);
767 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
768 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
769 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);
771 else if (effectnameindex == EFFECT_TE_BLOOD)
773 if (!cl_particles_blood.integer)
775 if (cl_particles_quake.integer)
776 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
779 static double bloodaccumulator = 0;
780 //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);
781 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
782 for (;bloodaccumulator > 0;bloodaccumulator--)
783 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);
786 else if (effectnameindex == EFFECT_TE_SPARK)
787 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
788 else if (effectnameindex == EFFECT_TE_PLASMABURN)
790 // plasma scorch mark
791 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
792 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
793 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
795 else if (effectnameindex == EFFECT_TE_GUNSHOT)
797 if (cl_particles_bulletimpacts.integer)
799 if (cl_particles_quake.integer)
800 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
803 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
804 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
805 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);
809 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
810 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
812 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
814 if (cl_particles_bulletimpacts.integer)
816 if (cl_particles_quake.integer)
817 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
820 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
821 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
822 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);
826 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
827 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
828 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);
830 else if (effectnameindex == EFFECT_TE_EXPLOSION)
832 CL_ParticleExplosion(center);
833 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);
835 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
837 CL_ParticleExplosion(center);
838 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);
840 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
842 if (cl_particles_quake.integer)
845 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
848 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);
850 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);
854 CL_ParticleExplosion(center);
855 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);
857 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
858 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);
859 else if (effectnameindex == EFFECT_TE_FLAMEJET)
861 count *= cl_particles_quality.value;
863 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);
865 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
867 float i, j, inc, vel;
870 inc = 8 / cl_particles_quality.value;
871 for (i = -128;i < 128;i += inc)
873 for (j = -128;j < 128;j += inc)
875 dir[0] = j + lhrandom(0, inc);
876 dir[1] = i + lhrandom(0, inc);
878 org[0] = center[0] + dir[0];
879 org[1] = center[1] + dir[1];
880 org[2] = center[2] + lhrandom(0, 64);
881 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
882 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);
886 else if (effectnameindex == EFFECT_TE_TELEPORT)
888 float i, j, k, inc, vel;
891 if (cl_particles_quake.integer)
892 inc = 4 / cl_particles_quality.value;
894 inc = 8 / cl_particles_quality.value;
895 for (i = -16;i < 16;i += inc)
897 for (j = -16;j < 16;j += inc)
899 for (k = -24;k < 32;k += inc)
901 VectorSet(dir, i*8, j*8, k*8);
902 VectorNormalize(dir);
903 vel = lhrandom(50, 113);
904 if (cl_particles_quake.integer)
905 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);
907 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);
911 if (!cl_particles_quake.integer)
912 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);
913 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);
915 else if (effectnameindex == EFFECT_TE_TEI_G3)
916 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_BEAM);
917 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
919 if (cl_particles_smoke.integer)
921 count *= 0.25f * cl_particles_quality.value;
923 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);
926 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
928 CL_ParticleExplosion(center);
929 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);
931 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
934 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
935 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
936 if (cl_particles_smoke.integer)
937 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
938 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);
939 if (cl_particles_sparks.integer)
940 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
941 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);
942 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);
944 else if (effectnameindex == EFFECT_EF_FLAME)
946 count *= 300 * cl_particles_quality.value;
948 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);
949 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);
951 else if (effectnameindex == EFFECT_EF_STARDUST)
953 count *= 200 * cl_particles_quality.value;
955 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);
956 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);
958 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
962 int smoke, blood, bubbles, r, color;
964 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
967 Vector4Set(light, 0, 0, 0, 0);
969 if (effectnameindex == EFFECT_TR_ROCKET)
970 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
971 else if (effectnameindex == EFFECT_TR_VORESPIKE)
973 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
974 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
976 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
978 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
979 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
983 matrix4x4_t tempmatrix;
984 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
985 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);
986 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
993 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
996 VectorSubtract(originmaxs, originmins, dir);
997 len = VectorNormalizeLength(dir);
1000 dec = -ent->persistent.trail_time;
1001 ent->persistent.trail_time += len;
1002 if (ent->persistent.trail_time < 0.01f)
1005 // if we skip out, leave it reset
1006 ent->persistent.trail_time = 0.0f;
1011 // advance into this frame to reach the first puff location
1012 VectorMA(originmins, dec, dir, pos);
1015 smoke = cl_particles.integer && cl_particles_smoke.integer;
1016 blood = cl_particles.integer && cl_particles_blood.integer;
1017 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1018 qd = 1.0f / cl_particles_quality.value;
1025 if (effectnameindex == EFFECT_TR_BLOOD)
1027 if (cl_particles_quake.integer)
1029 color = particlepalette[67 + (rand()&3)];
1030 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);
1035 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);
1038 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1040 if (cl_particles_quake.integer)
1043 color = particlepalette[67 + (rand()&3)];
1044 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);
1049 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);
1055 if (effectnameindex == EFFECT_TR_ROCKET)
1057 if (cl_particles_quake.integer)
1060 color = particlepalette[ramp3[r]];
1061 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);
1065 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);
1066 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);
1069 else if (effectnameindex == EFFECT_TR_GRENADE)
1071 if (cl_particles_quake.integer)
1074 color = particlepalette[ramp3[r]];
1075 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);
1079 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);
1082 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1084 if (cl_particles_quake.integer)
1087 color = particlepalette[52 + (rand()&7)];
1088 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);
1089 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);
1091 else if (gamemode == GAME_GOODVSBAD2)
1094 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);
1098 color = particlepalette[20 + (rand()&7)];
1099 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);
1102 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1104 if (cl_particles_quake.integer)
1107 color = particlepalette[230 + (rand()&7)];
1108 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);
1109 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);
1113 color = particlepalette[226 + (rand()&7)];
1114 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);
1117 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1119 if (cl_particles_quake.integer)
1121 color = particlepalette[152 + (rand()&3)];
1122 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);
1124 else if (gamemode == GAME_GOODVSBAD2)
1127 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);
1129 else if (gamemode == GAME_PRYDON)
1132 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);
1135 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);
1137 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1140 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);
1142 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1145 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);
1147 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1148 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);
1152 if (effectnameindex == EFFECT_TR_ROCKET)
1153 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);
1154 else if (effectnameindex == EFFECT_TR_GRENADE)
1155 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);
1157 // advance to next time and position
1160 VectorMA (pos, dec, dir, pos);
1163 ent->persistent.trail_time = len;
1165 else if (developer.integer >= 1)
1166 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1169 // this is also called on point effects with spawndlight = true and
1170 // spawnparticles = true
1171 // it is called CL_ParticleTrail because most code does not want to supply
1172 // these parameters, only trail handling does
1173 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)
1176 qboolean found = false;
1177 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1179 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1180 return; // no such effect
1182 VectorLerp(originmins, 0.5, originmaxs, center);
1183 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1185 int effectinfoindex;
1188 particleeffectinfo_t *info;
1190 vec3_t centervelocity;
1196 qboolean underwater;
1197 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1198 VectorLerp(originmins, 0.5, originmaxs, center);
1199 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1200 supercontents = CL_PointSuperContents(center);
1201 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1202 VectorSubtract(originmaxs, originmins, traildir);
1203 traillen = VectorLength(traildir);
1204 VectorNormalize(traildir);
1205 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1207 if (info->effectnameindex == effectnameindex)
1210 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1212 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1215 // spawn a dlight if requested
1216 if (info->lightradiusstart > 0 && spawndlight)
1218 matrix4x4_t tempmatrix;
1219 if (info->trailspacing > 0)
1220 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1222 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1223 if (info->lighttime > 0 && info->lightradiusfade > 0)
1225 // light flash (explosion, etc)
1226 // called when effect starts
1227 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);
1232 // called by CL_LinkNetworkEntity
1233 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1234 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);
1235 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1239 if (!spawnparticles)
1244 if (info->tex[1] > info->tex[0])
1246 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1247 tex = min(tex, info->tex[1] - 1);
1249 if (info->particletype == pt_decal)
1250 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]);
1251 else if (info->orientation == PARTICLE_BEAM)
1252 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);
1255 if (!cl_particles.integer)
1257 switch (info->particletype)
1259 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1260 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1261 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1262 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1263 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1264 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1267 VectorCopy(originmins, trailpos);
1268 if (info->trailspacing > 0)
1270 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1271 trailstep = info->trailspacing / cl_particles_quality.value;
1275 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1278 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1279 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1281 if (info->tex[1] > info->tex[0])
1283 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1284 tex = min(tex, info->tex[1] - 1);
1288 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1289 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1290 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1293 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);
1295 VectorMA(trailpos, trailstep, traildir, trailpos);
1302 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1305 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)
1307 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1315 void CL_EntityParticles (const entity_t *ent)
1318 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1319 static vec3_t avelocities[NUMVERTEXNORMALS];
1320 if (!cl_particles.integer) return;
1321 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1323 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1325 if (!avelocities[0][0])
1326 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1327 avelocities[0][i] = lhrandom(0, 2.55);
1329 for (i = 0;i < NUMVERTEXNORMALS;i++)
1331 yaw = cl.time * avelocities[i][0];
1332 pitch = cl.time * avelocities[i][1];
1333 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1334 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1335 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1336 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);
1341 void CL_ReadPointFile_f (void)
1343 vec3_t org, leakorg;
1345 char *pointfile = NULL, *pointfilepos, *t, tchar;
1346 char name[MAX_OSPATH];
1351 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1352 strlcat (name, ".pts", sizeof (name));
1353 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1356 Con_Printf("Could not open %s\n", name);
1360 Con_Printf("Reading %s...\n", name);
1361 VectorClear(leakorg);
1364 pointfilepos = pointfile;
1365 while (*pointfilepos)
1367 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1372 while (*t && *t != '\n' && *t != '\r')
1376 #if _MSC_VER >= 1400
1377 #define sscanf sscanf_s
1379 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1385 VectorCopy(org, leakorg);
1388 if (cl.num_particles < cl.max_particles - 3)
1391 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);
1394 Mem_Free(pointfile);
1395 VectorCopy(leakorg, org);
1396 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1398 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_BEAM);
1399 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_BEAM);
1400 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_BEAM);
1405 CL_ParseParticleEffect
1407 Parse an effect out of the server message
1410 void CL_ParseParticleEffect (void)
1413 int i, count, msgcount, color;
1415 MSG_ReadVector(org, cls.protocol);
1416 for (i=0 ; i<3 ; i++)
1417 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1418 msgcount = MSG_ReadByte ();
1419 color = MSG_ReadByte ();
1421 if (msgcount == 255)
1426 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1431 CL_ParticleExplosion
1435 void CL_ParticleExplosion (const vec3_t org)
1441 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1442 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1444 if (cl_particles_quake.integer)
1446 for (i = 0;i < 1024;i++)
1452 color = particlepalette[ramp1[r]];
1453 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);
1457 color = particlepalette[ramp2[r]];
1458 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);
1464 i = CL_PointSuperContents(org);
1465 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1467 if (cl_particles.integer && cl_particles_bubbles.integer)
1468 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1469 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);
1473 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1475 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1479 for (k = 0;k < 16;k++)
1482 VectorMA(org, 128, v2, v);
1483 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1484 if (trace.fraction >= 0.1)
1487 VectorSubtract(trace.endpos, org, v2);
1488 VectorScale(v2, 2.0f, v2);
1489 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);
1495 if (cl_particles_explosions_shell.integer)
1496 R_NewExplosion(org);
1501 CL_ParticleExplosion2
1505 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1508 if (!cl_particles.integer) return;
1510 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1512 k = particlepalette[colorStart + (i % colorLength)];
1513 if (cl_particles_quake.integer)
1514 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);
1516 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);
1520 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1522 if (cl_particles_sparks.integer)
1524 sparkcount *= cl_particles_quality.value;
1525 while(sparkcount-- > 0)
1526 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);
1530 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1532 if (cl_particles_smoke.integer)
1534 smokecount *= cl_particles_quality.value;
1535 while(smokecount-- > 0)
1536 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);
1540 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)
1543 if (!cl_particles.integer) return;
1545 count = (int)(count * cl_particles_quality.value);
1548 k = particlepalette[colorbase + (rand()&3)];
1549 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);
1553 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1556 float minz, maxz, lifetime = 30;
1557 if (!cl_particles.integer) return;
1558 if (dir[2] < 0) // falling
1560 minz = maxs[2] + dir[2] * 0.1;
1563 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1568 maxz = maxs[2] + dir[2] * 0.1;
1570 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1573 count = (int)(count * cl_particles_quality.value);
1578 if (!cl_particles_rain.integer) break;
1579 count *= 4; // ick, this should be in the mod or maps?
1583 k = particlepalette[colorbase + (rand()&3)];
1584 if (gamemode == GAME_GOODVSBAD2)
1585 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);
1587 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);
1591 if (!cl_particles_snow.integer) break;
1594 k = particlepalette[colorbase + (rand()&3)];
1595 if (gamemode == GAME_GOODVSBAD2)
1596 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);
1598 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);
1602 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1606 #define MAX_PARTICLETEXTURES 64
1607 // particletexture_t is a rectangle in the particlefonttexture
1608 typedef struct particletexture_s
1610 rtexture_t *texture;
1611 float s1, t1, s2, t2;
1615 static rtexturepool_t *particletexturepool;
1616 static rtexture_t *particlefonttexture;
1617 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1619 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1620 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1621 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1622 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1624 #define PARTICLETEXTURESIZE 64
1625 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1627 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1631 dz = 1 - (dx*dx+dy*dy);
1632 if (dz > 0) // it does hit the sphere
1636 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1637 VectorNormalize(normal);
1638 dot = DotProduct(normal, light);
1639 if (dot > 0.5) // interior reflection
1640 f += ((dot * 2) - 1);
1641 else if (dot < -0.5) // exterior reflection
1642 f += ((dot * -2) - 1);
1644 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1645 VectorNormalize(normal);
1646 dot = DotProduct(normal, light);
1647 if (dot > 0.5) // interior reflection
1648 f += ((dot * 2) - 1);
1649 else if (dot < -0.5) // exterior reflection
1650 f += ((dot * -2) - 1);
1652 f += 16; // just to give it a haze so you can see the outline
1653 f = bound(0, f, 255);
1654 return (unsigned char) f;
1660 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1662 int basex, basey, y;
1663 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1664 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1665 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1666 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1669 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1672 float cx, cy, dx, dy, f, iradius;
1674 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1675 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1676 iradius = 1.0f / radius;
1677 alpha *= (1.0f / 255.0f);
1678 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1680 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1684 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1689 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1690 d[0] += (int)(f * (blue - d[0]));
1691 d[1] += (int)(f * (green - d[1]));
1692 d[2] += (int)(f * (red - d[2]));
1698 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1701 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1703 data[0] = bound(minb, data[0], maxb);
1704 data[1] = bound(ming, data[1], maxg);
1705 data[2] = bound(minr, data[2], maxr);
1709 void particletextureinvert(unsigned char *data)
1712 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1714 data[0] = 255 - data[0];
1715 data[1] = 255 - data[1];
1716 data[2] = 255 - data[2];
1720 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1721 static void R_InitBloodTextures (unsigned char *particletexturedata)
1724 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1727 for (i = 0;i < 8;i++)
1729 memset(&data[0][0][0], 255, sizeof(data));
1730 for (k = 0;k < 24;k++)
1731 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1732 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1733 particletextureinvert(&data[0][0][0]);
1734 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1738 for (i = 0;i < 8;i++)
1740 memset(&data[0][0][0], 255, sizeof(data));
1742 for (j = 1;j < 10;j++)
1743 for (k = min(j, m - 1);k < m;k++)
1744 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1745 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1746 particletextureinvert(&data[0][0][0]);
1747 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1752 //uncomment this to make engine save out particle font to a tga file when run
1753 //#define DUMPPARTICLEFONT
1755 static void R_InitParticleTexture (void)
1757 int x, y, d, i, k, m;
1761 // a note: decals need to modulate (multiply) the background color to
1762 // properly darken it (stain), and they need to be able to alpha fade,
1763 // this is a very difficult challenge because it means fading to white
1764 // (no change to background) rather than black (darkening everything
1765 // behind the whole decal polygon), and to accomplish this the texture is
1766 // inverted (dark red blood on white background becomes brilliant cyan
1767 // and white on black background) so we can alpha fade it to black, then
1768 // we invert it again during the blendfunc to make it work...
1770 #ifndef DUMPPARTICLEFONT
1771 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1772 if (!particlefonttexture)
1775 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1776 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1777 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1780 for (i = 0;i < 8;i++)
1782 memset(&data[0][0][0], 255, sizeof(data));
1785 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1787 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1788 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1790 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1792 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1793 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1795 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1796 d = (noise2[y][x] - 128) * 3 + 192;
1798 d = (int)(d * (1-(dx*dx+dy*dy)));
1799 d = (d * noise1[y][x]) >> 7;
1800 d = bound(0, d, 255);
1801 data[y][x][3] = (unsigned char) d;
1808 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1812 memset(&data[0][0][0], 255, sizeof(data));
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1815 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1816 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1818 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1819 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1820 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1823 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1826 memset(&data[0][0][0], 255, sizeof(data));
1827 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1829 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1830 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1832 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1833 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1834 d = bound(0, d, 255);
1835 data[y][x][3] = (unsigned char) d;
1838 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1841 memset(&data[0][0][0], 255, sizeof(data));
1842 light[0] = 1;light[1] = 1;light[2] = 1;
1843 VectorNormalize(light);
1844 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1846 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1847 // stretch upper half of bubble by +50% and shrink lower half by -50%
1848 // (this gives an elongated teardrop shape)
1850 dy = (dy - 0.5f) * 2.0f;
1852 dy = (dy - 0.5f) / 1.5f;
1853 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1855 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1856 // shrink bubble width to half
1858 data[y][x][3] = shadebubble(dx, dy, light);
1861 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1864 memset(&data[0][0][0], 255, sizeof(data));
1865 light[0] = 1;light[1] = 1;light[2] = 1;
1866 VectorNormalize(light);
1867 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1869 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1870 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1872 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1873 data[y][x][3] = shadebubble(dx, dy, light);
1876 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1878 // Blood particles and blood decals
1879 R_InitBloodTextures (particletexturedata);
1882 for (i = 0;i < 8;i++)
1884 memset(&data[0][0][0], 255, sizeof(data));
1885 for (k = 0;k < 12;k++)
1886 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1887 for (k = 0;k < 3;k++)
1888 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1889 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1890 particletextureinvert(&data[0][0][0]);
1891 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1894 #ifdef DUMPPARTICLEFONT
1895 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1898 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1900 Mem_Free(particletexturedata);
1902 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1904 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1905 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1906 particletexture[i].texture = particlefonttexture;
1907 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1908 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1909 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1910 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1913 #ifndef DUMPPARTICLEFONT
1914 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1915 if (!particletexture[tex_beam].texture)
1918 unsigned char noise3[64][64], data2[64][16][4];
1920 fractalnoise(&noise3[0][0], 64, 4);
1922 for (y = 0;y < 64;y++)
1924 dy = (y - 0.5f*64) / (64*0.5f-1);
1925 for (x = 0;x < 16;x++)
1927 dx = (x - 0.5f*16) / (16*0.5f-2);
1928 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1929 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1930 data2[y][x][3] = 255;
1934 #ifdef DUMPPARTICLEFONT
1935 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1937 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1939 particletexture[tex_beam].s1 = 0;
1940 particletexture[tex_beam].t1 = 0;
1941 particletexture[tex_beam].s2 = 1;
1942 particletexture[tex_beam].t2 = 1;
1945 static void r_part_start(void)
1948 // generate particlepalette for convenience from the main one
1949 for (i = 0;i < 256;i++)
1950 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1951 particletexturepool = R_AllocTexturePool();
1952 R_InitParticleTexture ();
1953 CL_Particles_LoadEffectInfo();
1956 static void r_part_shutdown(void)
1958 R_FreeTexturePool(&particletexturepool);
1961 static void r_part_newmap(void)
1963 CL_Particles_LoadEffectInfo();
1966 #define BATCHSIZE 256
1967 unsigned short particle_elements[BATCHSIZE*6];
1969 void R_Particles_Init (void)
1972 for (i = 0;i < BATCHSIZE;i++)
1974 particle_elements[i*6+0] = i*4+0;
1975 particle_elements[i*6+1] = i*4+1;
1976 particle_elements[i*6+2] = i*4+2;
1977 particle_elements[i*6+3] = i*4+0;
1978 particle_elements[i*6+4] = i*4+2;
1979 particle_elements[i*6+5] = i*4+3;
1982 Cvar_RegisterVariable(&r_drawparticles);
1983 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1984 Cvar_RegisterVariable(&r_drawdecals);
1985 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1986 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1989 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1991 int surfacelistindex;
1993 float *v3f, *t2f, *c4f;
1994 particletexture_t *tex;
1995 float right[3], up[3], size, ca;
1996 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1997 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1999 r_refdef.stats.decals += numsurfaces;
2000 R_Mesh_Matrix(&identitymatrix);
2001 R_Mesh_ResetTextureState();
2002 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2003 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2004 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2005 R_SetupGenericShader(true);
2006 GL_DepthMask(false);
2007 GL_DepthRange(0, 1);
2008 GL_PolygonOffset(0, 0);
2010 GL_CullFace(GL_NONE);
2012 // generate all the vertices at once
2013 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2015 d = cl.decals + surfacelist[surfacelistindex];
2018 c4f = particle_color4f + 16*surfacelistindex;
2019 ca = d->alpha * alphascale;
2020 if (r_refdef.fogenabled)
2021 ca *= FogPoint_World(d->org);
2022 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2023 Vector4Copy(c4f, c4f + 4);
2024 Vector4Copy(c4f, c4f + 8);
2025 Vector4Copy(c4f, c4f + 12);
2027 // calculate vertex positions
2028 size = d->size * cl_particles_size.value;
2029 VectorVectors(d->normal, right, up);
2030 VectorScale(right, size, right);
2031 VectorScale(up, size, up);
2032 v3f = particle_vertex3f + 12*surfacelistindex;
2033 v3f[ 0] = d->org[0] - right[0] - up[0];
2034 v3f[ 1] = d->org[1] - right[1] - up[1];
2035 v3f[ 2] = d->org[2] - right[2] - up[2];
2036 v3f[ 3] = d->org[0] - right[0] + up[0];
2037 v3f[ 4] = d->org[1] - right[1] + up[1];
2038 v3f[ 5] = d->org[2] - right[2] + up[2];
2039 v3f[ 6] = d->org[0] + right[0] + up[0];
2040 v3f[ 7] = d->org[1] + right[1] + up[1];
2041 v3f[ 8] = d->org[2] + right[2] + up[2];
2042 v3f[ 9] = d->org[0] + right[0] - up[0];
2043 v3f[10] = d->org[1] + right[1] - up[1];
2044 v3f[11] = d->org[2] + right[2] - up[2];
2046 // calculate texcoords
2047 tex = &particletexture[d->texnum];
2048 t2f = particle_texcoord2f + 8*surfacelistindex;
2049 t2f[0] = tex->s1;t2f[1] = tex->t2;
2050 t2f[2] = tex->s1;t2f[3] = tex->t1;
2051 t2f[4] = tex->s2;t2f[5] = tex->t1;
2052 t2f[6] = tex->s2;t2f[7] = tex->t2;
2055 // now render the decals all at once
2056 // (this assumes they all use one particle font texture!)
2057 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2058 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2059 GL_LockArrays(0, numsurfaces*4);
2060 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2061 GL_LockArrays(0, 0);
2064 void R_DrawDecals (void)
2072 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2073 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2075 // LordHavoc: early out conditions
2076 if ((!cl.num_decals) || (!r_drawdecals.integer))
2079 decalfade = frametime * 256 / cl_decals_fadetime.value;
2080 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2081 drawdist2 = drawdist2*drawdist2;
2083 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2085 if (!decal->typeindex)
2088 if (cl.time > decal->time2 + cl_decals_time.value)
2090 decal->alpha -= decalfade;
2091 if (decal->alpha <= 0)
2097 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2099 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2100 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2106 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2109 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))
2110 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2113 decal->typeindex = 0;
2114 if (cl.free_decal > i)
2118 // reduce cl.num_decals if possible
2119 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2122 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2124 decal_t *olddecals = cl.decals;
2125 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2126 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2127 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2128 Mem_Free(olddecals);
2132 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2134 int surfacelistindex;
2135 int batchstart, batchcount;
2136 const particle_t *p;
2138 rtexture_t *texture;
2139 float *v3f, *t2f, *c4f;
2140 particletexture_t *tex;
2141 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2142 float ambient[3], diffuse[3], diffusenormal[3];
2143 vec4_t colormultiplier;
2144 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2146 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));
2148 r_refdef.stats.particles += numsurfaces;
2149 R_Mesh_Matrix(&identitymatrix);
2150 R_Mesh_ResetTextureState();
2151 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2152 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2153 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2154 R_SetupGenericShader(true);
2155 GL_DepthMask(false);
2156 GL_DepthRange(0, 1);
2157 GL_PolygonOffset(0, 0);
2159 GL_CullFace(GL_NONE);
2161 // first generate all the vertices at once
2162 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2164 p = cl.particles + surfacelist[surfacelistindex];
2166 blendmode = p->blendmode;
2168 c4f[0] = p->color[0] * colormultiplier[0];
2169 c4f[1] = p->color[1] * colormultiplier[1];
2170 c4f[2] = p->color[2] * colormultiplier[2];
2171 c4f[3] = p->alpha * colormultiplier[3];
2174 case PBLEND_INVALID:
2177 // additive and modulate can just fade out in fog (this is correct)
2178 if (r_refdef.fogenabled)
2179 c4f[3] *= FogPoint_World(p->org);
2180 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2187 // note: lighting is not cheap!
2188 if (particletype[p->typeindex].lighting)
2190 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2191 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2192 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2193 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2195 // mix in the fog color
2196 if (r_refdef.fogenabled)
2198 fog = FogPoint_World(p->org);
2200 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2201 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2202 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2206 // copy the color into the other three vertices
2207 Vector4Copy(c4f, c4f + 4);
2208 Vector4Copy(c4f, c4f + 8);
2209 Vector4Copy(c4f, c4f + 12);
2211 size = p->size * cl_particles_size.value;
2212 tex = &particletexture[p->texnum];
2213 switch(p->orientation)
2215 case PARTICLE_INVALID:
2216 case PARTICLE_BILLBOARD:
2217 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2218 VectorScale(r_refdef.view.up, size, up);
2219 v3f[ 0] = p->org[0] - right[0] - up[0];
2220 v3f[ 1] = p->org[1] - right[1] - up[1];
2221 v3f[ 2] = p->org[2] - right[2] - up[2];
2222 v3f[ 3] = p->org[0] - right[0] + up[0];
2223 v3f[ 4] = p->org[1] - right[1] + up[1];
2224 v3f[ 5] = p->org[2] - right[2] + up[2];
2225 v3f[ 6] = p->org[0] + right[0] + up[0];
2226 v3f[ 7] = p->org[1] + right[1] + up[1];
2227 v3f[ 8] = p->org[2] + right[2] + up[2];
2228 v3f[ 9] = p->org[0] + right[0] - up[0];
2229 v3f[10] = p->org[1] + right[1] - up[1];
2230 v3f[11] = p->org[2] + right[2] - up[2];
2231 t2f[0] = tex->s1;t2f[1] = tex->t2;
2232 t2f[2] = tex->s1;t2f[3] = tex->t1;
2233 t2f[4] = tex->s2;t2f[5] = tex->t1;
2234 t2f[6] = tex->s2;t2f[7] = tex->t2;
2236 case PARTICLE_ORIENTED_DOUBLESIDED:
2237 VectorVectors(p->vel, right, up);
2238 VectorScale(right, size * p->stretch, right);
2239 VectorScale(up, size, up);
2240 v3f[ 0] = p->org[0] - right[0] - up[0];
2241 v3f[ 1] = p->org[1] - right[1] - up[1];
2242 v3f[ 2] = p->org[2] - right[2] - up[2];
2243 v3f[ 3] = p->org[0] - right[0] + up[0];
2244 v3f[ 4] = p->org[1] - right[1] + up[1];
2245 v3f[ 5] = p->org[2] - right[2] + up[2];
2246 v3f[ 6] = p->org[0] + right[0] + up[0];
2247 v3f[ 7] = p->org[1] + right[1] + up[1];
2248 v3f[ 8] = p->org[2] + right[2] + up[2];
2249 v3f[ 9] = p->org[0] + right[0] - up[0];
2250 v3f[10] = p->org[1] + right[1] - up[1];
2251 v3f[11] = p->org[2] + right[2] - up[2];
2252 t2f[0] = tex->s1;t2f[1] = tex->t2;
2253 t2f[2] = tex->s1;t2f[3] = tex->t1;
2254 t2f[4] = tex->s2;t2f[5] = tex->t1;
2255 t2f[6] = tex->s2;t2f[7] = tex->t2;
2257 case PARTICLE_SPARK:
2258 len = VectorLength(p->vel);
2259 VectorNormalize2(p->vel, up);
2260 lenfactor = p->stretch * 0.04 * len;
2261 if(lenfactor < size * 0.5)
2262 lenfactor = size * 0.5;
2263 VectorMA(p->org, -lenfactor, up, v);
2264 VectorMA(p->org, lenfactor, up, up2);
2265 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2266 t2f[0] = tex->s1;t2f[1] = tex->t2;
2267 t2f[2] = tex->s1;t2f[3] = tex->t1;
2268 t2f[4] = tex->s2;t2f[5] = tex->t1;
2269 t2f[6] = tex->s2;t2f[7] = tex->t2;
2272 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2273 VectorSubtract(p->vel, p->org, up);
2274 VectorNormalize(up);
2275 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2276 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2277 t2f[0] = 1;t2f[1] = v[0];
2278 t2f[2] = 0;t2f[3] = v[0];
2279 t2f[4] = 0;t2f[5] = v[1];
2280 t2f[6] = 1;t2f[7] = v[1];
2285 // now render batches of particles based on blendmode and texture
2286 blendmode = PBLEND_INVALID;
2288 GL_LockArrays(0, numsurfaces*4);
2291 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2293 p = cl.particles + surfacelist[surfacelistindex];
2295 if (blendmode != p->blendmode)
2297 blendmode = p->blendmode;
2301 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2303 case PBLEND_INVALID:
2305 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2308 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2312 if (texture != particletexture[p->texnum].texture)
2314 texture = particletexture[p->texnum].texture;
2315 R_Mesh_TexBind(0, R_GetTexture(texture));
2318 // iterate until we find a change in settings
2319 batchstart = surfacelistindex++;
2320 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2322 p = cl.particles + surfacelist[surfacelistindex];
2323 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2327 batchcount = surfacelistindex - batchstart;
2328 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2330 GL_LockArrays(0, 0);
2333 void R_DrawParticles (void)
2336 float minparticledist;
2338 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2344 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2345 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2347 // LordHavoc: early out conditions
2348 if ((!cl.num_particles) || (!r_drawparticles.integer))
2351 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2352 gravity = frametime * cl.movevars_gravity;
2353 dvel = 1+4*frametime;
2354 decalfade = frametime * 255 / cl_decals_fadetime.value;
2355 update = frametime > 0;
2356 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2357 drawdist2 = drawdist2*drawdist2;
2359 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2363 if (cl.free_particle > i)
2364 cl.free_particle = i;
2370 if (p->delayedspawn > cl.time)
2372 p->delayedspawn = 0;
2376 p->size += p->sizeincrease * frametime;
2377 p->alpha -= p->alphafade * frametime;
2379 if (p->alpha <= 0 || p->die <= cl.time)
2382 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2384 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2386 if (p->typeindex == pt_blood)
2387 p->size += frametime * 8;
2389 p->vel[2] -= p->gravity * gravity;
2390 f = 1.0f - min(p->liquidfriction * frametime, 1);
2391 VectorScale(p->vel, f, p->vel);
2395 p->vel[2] -= p->gravity * gravity;
2398 f = 1.0f - min(p->airfriction * frametime, 1);
2399 VectorScale(p->vel, f, p->vel);
2403 VectorCopy(p->org, oldorg);
2404 VectorMA(p->org, frametime, p->vel, p->org);
2405 if (p->bounce && cl.time >= p->delayedcollisions)
2407 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2408 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2409 // or if the trace hit something flagged as NOIMPACT
2410 // then remove the particle
2411 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2413 VectorCopy(trace.endpos, p->org);
2414 // react if the particle hit something
2415 if (trace.fraction < 1)
2417 VectorCopy(trace.endpos, p->org);
2418 if (p->typeindex == pt_blood)
2420 // blood - splash on solid
2421 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2423 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)));
2424 if (cl_decals.integer)
2426 // create a decal for the blood splat
2427 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2431 else if (p->bounce < 0)
2433 // bounce -1 means remove on impact
2438 // anything else - bounce off solid
2439 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2440 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2441 if (DotProduct(p->vel, p->vel) < 0.03)
2442 VectorClear(p->vel);
2448 if (p->typeindex != pt_static)
2450 switch (p->typeindex)
2452 case pt_entityparticle:
2453 // particle that removes itself after one rendered frame
2460 a = CL_PointSuperContents(p->org);
2461 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2465 a = CL_PointSuperContents(p->org);
2466 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2470 a = CL_PointSuperContents(p->org);
2471 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2475 if (cl.time > p->time2)
2478 p->time2 = cl.time + (rand() & 3) * 0.1;
2479 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2480 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2482 a = CL_PointSuperContents(p->org);
2483 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2491 else if (p->delayedspawn)
2494 // don't render particles too close to the view (they chew fillrate)
2495 // also don't render particles behind the view (useless)
2496 // further checks to cull to the frustum would be too slow here
2497 switch(p->typeindex)
2500 // beams have no culling
2501 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2504 if(cl_particles_visculling.integer)
2505 if (!r_refdef.viewcache.world_novis)
2506 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2508 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2510 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2513 // anything else just has to be in front of the viewer and visible at this distance
2514 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2515 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2522 if (cl.free_particle > i)
2523 cl.free_particle = i;
2526 // reduce cl.num_particles if possible
2527 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2530 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2532 particle_t *oldparticles = cl.particles;
2533 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2534 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2535 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2536 Mem_Free(oldparticles);